Added fenster.
h3rald h3rald@h3rald.com
Sun, 14 Dec 2025 11:43:05 +0100
2 files changed,
531 insertions(+),
0 deletions(-)
A
fenster.h
@@ -0,0 +1,371 @@
+#ifndef FENSTER_H +#define FENSTER_H + +#if defined(__APPLE__) +#include <CoreGraphics/CoreGraphics.h> +#include <objc/NSObjCRuntime.h> +#include <objc/objc-runtime.h> +#elif defined(_WIN32) +#include <windows.h> +#else +#define _DEFAULT_SOURCE 1 +#include <X11/XKBlib.h> +#include <X11/Xlib.h> +#include <X11/keysym.h> +#include <time.h> +#endif + +#include <stdint.h> +#include <stdlib.h> + +struct fenster { + const char *title; + const int width; + const int height; + uint32_t *buf; + int keys[256]; /* keys are mostly ASCII, but arrows are 17..20 */ + int mod; /* mod is 4 bits mask, ctrl=1, shift=2, alt=4, meta=8 */ + int x; + int y; + int mouse; +#if defined(__APPLE__) + id wnd; +#elif defined(_WIN32) + HWND hwnd; +#else + Display *dpy; + Window w; + GC gc; + XImage *img; +#endif +}; + +#ifndef FENSTER_API +#define FENSTER_API extern +#endif +FENSTER_API int fenster_open(struct fenster *f); +FENSTER_API int fenster_loop(struct fenster *f); +FENSTER_API void fenster_close(struct fenster *f); +FENSTER_API void fenster_sleep(int64_t ms); +FENSTER_API int64_t fenster_time(void); +#define fenster_pixel(f, x, y) ((f)->buf[((y) * (f)->width) + (x)]) + +#ifndef FENSTER_HEADER +#if defined(__APPLE__) +#define msg(r, o, s) ((r(*)(id, SEL))objc_msgSend)(o, sel_getUid(s)) +#define msg1(r, o, s, A, a) \ + ((r(*)(id, SEL, A))objc_msgSend)(o, sel_getUid(s), a) +#define msg2(r, o, s, A, a, B, b) \ + ((r(*)(id, SEL, A, B))objc_msgSend)(o, sel_getUid(s), a, b) +#define msg3(r, o, s, A, a, B, b, C, c) \ + ((r(*)(id, SEL, A, B, C))objc_msgSend)(o, sel_getUid(s), a, b, c) +#define msg4(r, o, s, A, a, B, b, C, c, D, d) \ + ((r(*)(id, SEL, A, B, C, D))objc_msgSend)(o, sel_getUid(s), a, b, c, d) + +#define cls(x) ((id)objc_getClass(x)) + +extern id const NSDefaultRunLoopMode; +extern id const NSApp; + +static void fenster_draw_rect(id v, SEL s, CGRect r) { + (void)r, (void)s; + struct fenster *f = (struct fenster *)objc_getAssociatedObject(v, "fenster"); + CGContextRef context = + msg(CGContextRef, msg(id, cls("NSGraphicsContext"), "currentContext"), + "graphicsPort"); + CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); + CGDataProviderRef provider = CGDataProviderCreateWithData( + NULL, f->buf, f->width * f->height * 4, NULL); + CGImageRef img = + CGImageCreate(f->width, f->height, 8, 32, f->width * 4, space, + kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, + provider, NULL, false, kCGRenderingIntentDefault); + CGColorSpaceRelease(space); + CGDataProviderRelease(provider); + CGContextDrawImage(context, CGRectMake(0, 0, f->width, f->height), img); + CGImageRelease(img); +} + +static BOOL fenster_should_close(id v, SEL s, id w) { + (void)v, (void)s, (void)w; + msg1(void, NSApp, "terminate:", id, NSApp); + return YES; +} + +FENSTER_API int fenster_open(struct fenster *f) { + msg(id, cls("NSApplication"), "sharedApplication"); + msg1(void, NSApp, "setActivationPolicy:", NSInteger, 0); + f->wnd = msg4(id, msg(id, cls("NSWindow"), "alloc"), + "initWithContentRect:styleMask:backing:defer:", CGRect, + CGRectMake(0, 0, f->width, f->height), NSUInteger, 3, + NSUInteger, 2, BOOL, NO); + Class windelegate = + objc_allocateClassPair((Class)cls("NSObject"), "FensterDelegate", 0); + class_addMethod(windelegate, sel_getUid("windowShouldClose:"), + (IMP)fenster_should_close, "c@:@"); + objc_registerClassPair(windelegate); + msg1(void, f->wnd, "setDelegate:", id, + msg(id, msg(id, (id)windelegate, "alloc"), "init")); + Class c = objc_allocateClassPair((Class)cls("NSView"), "FensterView", 0); + class_addMethod(c, sel_getUid("drawRect:"), (IMP)fenster_draw_rect, "i@:@@"); + objc_registerClassPair(c); + + id v = msg(id, msg(id, (id)c, "alloc"), "init"); + msg1(void, f->wnd, "setContentView:", id, v); + objc_setAssociatedObject(v, "fenster", (id)f, OBJC_ASSOCIATION_ASSIGN); + + id title = msg1(id, cls("NSString"), "stringWithUTF8String:", const char *, + f->title); + msg1(void, f->wnd, "setTitle:", id, title); + msg1(void, f->wnd, "makeKeyAndOrderFront:", id, nil); + msg(void, f->wnd, "center"); + msg1(void, NSApp, "activateIgnoringOtherApps:", BOOL, YES); + return 0; +} + +FENSTER_API void fenster_close(struct fenster *f) { + msg(void, f->wnd, "close"); +} + +// clang-format off +static const uint8_t FENSTER_KEYCODES[128] = {65,83,68,70,72,71,90,88,67,86,0,66,81,87,69,82,89,84,49,50,51,52,54,53,61,57,55,45,56,48,93,79,85,91,73,80,10,76,74,39,75,59,92,44,47,78,77,46,9,32,96,8,0,27,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,26,2,3,127,0,5,0,4,0,20,19,18,17,0}; +// clang-format on +FENSTER_API int fenster_loop(struct fenster *f) { + msg1(void, msg(id, f->wnd, "contentView"), "setNeedsDisplay:", BOOL, YES); + id ev = msg4(id, NSApp, + "nextEventMatchingMask:untilDate:inMode:dequeue:", NSUInteger, + NSUIntegerMax, id, NULL, id, NSDefaultRunLoopMode, BOOL, YES); + if (!ev) + return 0; + NSUInteger evtype = msg(NSUInteger, ev, "type"); + switch (evtype) { + case 1: /* NSEventTypeMouseDown */ + f->mouse |= 1; + break; + case 2: /* NSEventTypeMouseUp*/ + f->mouse &= ~1; + break; + case 5: + case 6: { /* NSEventTypeMouseMoved */ + CGPoint xy = msg(CGPoint, ev, "locationInWindow"); + f->x = (int)xy.x; + f->y = (int)(f->height - xy.y); + return 0; + } + case 10: /*NSEventTypeKeyDown*/ + case 11: /*NSEventTypeKeyUp:*/ { + NSUInteger k = msg(NSUInteger, ev, "keyCode"); + f->keys[k < 127 ? FENSTER_KEYCODES[k] : 0] = evtype == 10; + NSUInteger mod = msg(NSUInteger, ev, "modifierFlags") >> 17; + f->mod = (mod & 0xc) | ((mod & 1) << 1) | ((mod >> 1) & 1); + return 0; + } + } + msg1(void, NSApp, "sendEvent:", id, ev); + return 0; +} +#elif defined(_WIN32) +// clang-format off +static const uint8_t FENSTER_KEYCODES[] = {0,27,49,50,51,52,53,54,55,56,57,48,45,61,8,9,81,87,69,82,84,89,85,73,79,80,91,93,10,0,65,83,68,70,71,72,74,75,76,59,39,96,0,92,90,88,67,86,66,78,77,44,46,47,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,17,3,0,20,0,19,0,5,18,4,26,127}; +// clang-format on +typedef struct BINFO{ + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[3]; +}BINFO; +static LRESULT CALLBACK fenster_wndproc(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam) { + struct fenster *f = (struct fenster *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + switch (msg) { + case WM_PAINT: { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + HDC memdc = CreateCompatibleDC(hdc); + HBITMAP hbmp = CreateCompatibleBitmap(hdc, f->width, f->height); + HBITMAP oldbmp = SelectObject(memdc, hbmp); + BINFO bi = {{sizeof(bi), f->width, -f->height, 1, 32, BI_BITFIELDS}}; + bi.bmiColors[0].rgbRed = 0xff; + bi.bmiColors[1].rgbGreen = 0xff; + bi.bmiColors[2].rgbBlue = 0xff; + SetDIBitsToDevice(memdc, 0, 0, f->width, f->height, 0, 0, 0, f->height, + f->buf, (BITMAPINFO *)&bi, DIB_RGB_COLORS); + BitBlt(hdc, 0, 0, f->width, f->height, memdc, 0, 0, SRCCOPY); + SelectObject(memdc, oldbmp); + DeleteObject(hbmp); + DeleteDC(memdc); + EndPaint(hwnd, &ps); + } break; + case WM_CLOSE: + DestroyWindow(hwnd); + break; + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + f->mouse = (msg == WM_LBUTTONDOWN); + break; + case WM_MOUSEMOVE: + f->y = HIWORD(lParam), f->x = LOWORD(lParam); + break; + case WM_KEYDOWN: + case WM_KEYUP: { + f->mod = ((GetKeyState(VK_CONTROL) & 0x8000) >> 15) | + ((GetKeyState(VK_SHIFT) & 0x8000) >> 14) | + ((GetKeyState(VK_MENU) & 0x8000) >> 13) | + (((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & 0x8000) >> 12); + f->keys[FENSTER_KEYCODES[HIWORD(lParam) & 0x1ff]] = !((lParam >> 31) & 1); + } break; + case WM_DESTROY: + PostQuitMessage(0); + break; + default: + return DefWindowProc(hwnd, msg, wParam, lParam); + } + return 0; +} + +FENSTER_API int fenster_open(struct fenster *f) { + HINSTANCE hInstance = GetModuleHandle(NULL); + WNDCLASSEX wc = {0}; + wc.cbSize = sizeof(WNDCLASSEX); + wc.style = CS_VREDRAW | CS_HREDRAW; + wc.lpfnWndProc = fenster_wndproc; + wc.hInstance = hInstance; + wc.lpszClassName = f->title; + RegisterClassEx(&wc); + f->hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, f->title, f->title, + WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, + f->width, f->height, NULL, NULL, hInstance, NULL); + + if (f->hwnd == NULL) + return -1; + SetWindowLongPtr(f->hwnd, GWLP_USERDATA, (LONG_PTR)f); + ShowWindow(f->hwnd, SW_NORMAL); + UpdateWindow(f->hwnd); + return 0; +} + +FENSTER_API void fenster_close(struct fenster *f) { (void)f; } + +FENSTER_API int fenster_loop(struct fenster *f) { + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) + return -1; + TranslateMessage(&msg); + DispatchMessage(&msg); + } + InvalidateRect(f->hwnd, NULL, TRUE); + return 0; +} +#else +// clang-format off +static int FENSTER_KEYCODES[124] = {XK_BackSpace,8,XK_Delete,127,XK_Down,18,XK_End,5,XK_Escape,27,XK_Home,2,XK_Insert,26,XK_Left,20,XK_Page_Down,4,XK_Page_Up,3,XK_Return,10,XK_Right,19,XK_Tab,9,XK_Up,17,XK_apostrophe,39,XK_backslash,92,XK_bracketleft,91,XK_bracketright,93,XK_comma,44,XK_equal,61,XK_grave,96,XK_minus,45,XK_period,46,XK_semicolon,59,XK_slash,47,XK_space,32,XK_a,65,XK_b,66,XK_c,67,XK_d,68,XK_e,69,XK_f,70,XK_g,71,XK_h,72,XK_i,73,XK_j,74,XK_k,75,XK_l,76,XK_m,77,XK_n,78,XK_o,79,XK_p,80,XK_q,81,XK_r,82,XK_s,83,XK_t,84,XK_u,85,XK_v,86,XK_w,87,XK_x,88,XK_y,89,XK_z,90,XK_0,48,XK_1,49,XK_2,50,XK_3,51,XK_4,52,XK_5,53,XK_6,54,XK_7,55,XK_8,56,XK_9,57}; +// clang-format on +FENSTER_API int fenster_open(struct fenster *f) { + f->dpy = XOpenDisplay(NULL); + int screen = DefaultScreen(f->dpy); + f->w = XCreateSimpleWindow(f->dpy, RootWindow(f->dpy, screen), 0, 0, f->width, + f->height, 0, BlackPixel(f->dpy, screen), + WhitePixel(f->dpy, screen)); + f->gc = XCreateGC(f->dpy, f->w, 0, 0); + XSelectInput(f->dpy, f->w, + ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | + ButtonReleaseMask | PointerMotionMask); + XStoreName(f->dpy, f->w, f->title); + XMapWindow(f->dpy, f->w); + XSync(f->dpy, f->w); + f->img = XCreateImage(f->dpy, DefaultVisual(f->dpy, 0), 24, ZPixmap, 0, + (char *)f->buf, f->width, f->height, 32, 0); + return 0; +} +FENSTER_API void fenster_close(struct fenster *f) { XCloseDisplay(f->dpy); } +FENSTER_API int fenster_loop(struct fenster *f) { + XEvent ev; + XPutImage(f->dpy, f->w, f->gc, f->img, 0, 0, 0, 0, f->width, f->height); + XFlush(f->dpy); + while (XPending(f->dpy)) { + XNextEvent(f->dpy, &ev); + switch (ev.type) { + case ButtonPress: + case ButtonRelease: + f->mouse = (ev.type == ButtonPress); + break; + case MotionNotify: + f->x = ev.xmotion.x, f->y = ev.xmotion.y; + break; + case KeyPress: + case KeyRelease: { + int m = ev.xkey.state; + int k = XkbKeycodeToKeysym(f->dpy, ev.xkey.keycode, 0, 0); + for (unsigned int i = 0; i < 124; i += 2) { + if (FENSTER_KEYCODES[i] == k) { + f->keys[FENSTER_KEYCODES[i + 1]] = (ev.type == KeyPress); + break; + } + } + f->mod = (!!(m & ControlMask)) | (!!(m & ShiftMask) << 1) | + (!!(m & Mod1Mask) << 2) | (!!(m & Mod4Mask) << 3); + } break; + } + } + return 0; +} +#endif + +#ifdef _WIN32 +FENSTER_API void fenster_sleep(int64_t ms) { Sleep(ms); } +FENSTER_API int64_t fenster_time() { + LARGE_INTEGER freq, count; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&count); + return (int64_t)(count.QuadPart * 1000.0 / freq.QuadPart); +} +#else +FENSTER_API void fenster_sleep(int64_t ms) { + struct timespec ts; + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000; + nanosleep(&ts, NULL); +} +FENSTER_API int64_t fenster_time(void) { + struct timespec time; + clock_gettime(CLOCK_REALTIME, &time); + return time.tv_sec * 1000 + (time.tv_nsec / 1000000); +} +#endif + +#ifdef __cplusplus +class Fenster { + struct fenster f; + int64_t now; + +public: + Fenster(const int w, const int h, const char *title) + : f{.title = title, .width = w, .height = h} { + this->f.buf = new uint32_t[w * h]; + this->now = fenster_time(); + fenster_open(&this->f); + } + ~Fenster() { + fenster_close(&this->f); + delete[] this->f.buf; + } + bool loop(const int fps) { + int64_t t = fenster_time(); + if (t - this->now < 1000 / fps) { + fenster_sleep(t - now); + } + this->now = t; + return fenster_loop(&this->f) == 0; + } + inline uint32_t &px(const int x, const int y) { + return fenster_pixel(&this->f, x, y); + } + bool key(int c) { return c >= 0 && c < 128 ? this->f.keys[c] : false; } + int x() { return this->f.x; } + int y() { return this->f.y; } + int mouse() { return this->f.mouse; } + int mod() { return this->f.mod; } +}; +#endif /* __cplusplus */ + +#endif /* !FENSTER_HEADER */ +#endif /* FENSTER_H */
A
fenster_audio.h
@@ -0,0 +1,160 @@
+#ifndef FENSTER_AUDIO_H +#define FENSTER_AUDIO_H + +#ifndef FENSTER_SAMPLE_RATE +#define FENSTER_SAMPLE_RATE 44100 +#endif + +#ifndef FENSTER_AUDIO_BUFSZ +#define FENSTER_AUDIO_BUFSZ 8192 +#endif + +#if defined(__APPLE__) +#include <AudioToolbox/AudioQueue.h> +struct fenster_audio { + AudioQueueRef queue; + size_t pos; + float buf[FENSTER_AUDIO_BUFSZ]; + dispatch_semaphore_t drained; + dispatch_semaphore_t full; +}; +#elif defined(_WIN32) +#include <mmsystem.h> +#include <windows.h> +struct fenster_audio { + WAVEHDR header; + HWAVEOUT wo; + WAVEHDR hdr[2]; + int16_t buf[2][FENSTER_AUDIO_BUFSZ]; +}; +#elif defined(__linux__) +struct fenster_audio { + void *pcm; + float buf[FENSTER_AUDIO_BUFSZ]; + size_t pos; +}; +#endif + +#ifndef FENSTER_API +#define FENSTER_API extern +#endif +FENSTER_API int fenster_audio_open(struct fenster_audio *f); +FENSTER_API int fenster_audio_available(struct fenster_audio *f); +FENSTER_API void fenster_audio_write(struct fenster_audio *f, float *buf, + size_t n); +FENSTER_API void fenster_audio_close(struct fenster_audio *f); + +#ifndef FENSTER_HEADER +#if defined(__APPLE__) +static void fenster_audio_cb(void *p, AudioQueueRef q, AudioQueueBufferRef b) { + struct fenster_audio *fa = (struct fenster_audio *)p; + dispatch_semaphore_wait(fa->full, DISPATCH_TIME_FOREVER); + memmove(b->mAudioData, fa->buf, sizeof(fa->buf)); + dispatch_semaphore_signal(fa->drained); + AudioQueueEnqueueBuffer(q, b, 0, NULL); +} +FENSTER_API int fenster_audio_open(struct fenster_audio *fa) { + AudioStreamBasicDescription format = {0}; + format.mSampleRate = FENSTER_SAMPLE_RATE; + format.mFormatID = kAudioFormatLinearPCM; + format.mFormatFlags = kLinearPCMFormatFlagIsFloat | kAudioFormatFlagIsPacked; + format.mBitsPerChannel = 32; + format.mFramesPerPacket = format.mChannelsPerFrame = 1; + format.mBytesPerPacket = format.mBytesPerFrame = 4; + fa->drained = dispatch_semaphore_create(1); + fa->full = dispatch_semaphore_create(0); + AudioQueueNewOutput(&format, fenster_audio_cb, fa, NULL, NULL, 0, &fa->queue); + for (int i = 0; i < 2; i++) { + AudioQueueBufferRef buffer = NULL; + AudioQueueAllocateBuffer(fa->queue, FENSTER_AUDIO_BUFSZ * 4, &buffer); + buffer->mAudioDataByteSize = FENSTER_AUDIO_BUFSZ * 4; + memset(buffer->mAudioData, 0, buffer->mAudioDataByteSize); + AudioQueueEnqueueBuffer(fa->queue, buffer, 0, NULL); + } + AudioQueueStart(fa->queue, NULL); + return 0; +} +FENSTER_API void fenster_audio_close(struct fenster_audio *fa) { + AudioQueueStop(fa->queue, false); + AudioQueueDispose(fa->queue, false); +} +FENSTER_API int fenster_audio_available(struct fenster_audio *fa) { + if (dispatch_semaphore_wait(fa->drained, DISPATCH_TIME_NOW)) + return 0; + return FENSTER_AUDIO_BUFSZ - fa->pos; +} +FENSTER_API void fenster_audio_write(struct fenster_audio *fa, float *buf, + size_t n) { + while (fa->pos < FENSTER_AUDIO_BUFSZ && n > 0) { + fa->buf[fa->pos++] = *buf++, n--; + } + if (fa->pos >= FENSTER_AUDIO_BUFSZ) { + fa->pos = 0; + dispatch_semaphore_signal(fa->full); + } +} +#elif defined(_WIN32) +FENSTER_API int fenster_audio_open(struct fenster_audio *fa) { + WAVEFORMATEX wfx = {WAVE_FORMAT_PCM, 1, FENSTER_SAMPLE_RATE, FENSTER_SAMPLE_RATE * 2, 1, 16, 0}; + waveOutOpen(&fa->wo, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL); + for (int i = 0; i < 2; i++) { + fa->hdr[i].lpData = fa->buf[i]; + fa->hdr[i].dwBufferLength = FENSTER_AUDIO_BUFSZ * 2; + waveOutPrepareHeader(fa->wo, &fa->hdr[i], sizeof(WAVEHDR)); + waveOutWrite(fa->wo, &fa->hdr[i], sizeof(WAVEHDR)); + } + return 0; +} +FENSTER_API int fenster_audio_available(struct fenster_audio *fa) { + for (int i = 0; i < 2; i++) + if (fa->hdr[i].dwFlags & WHDR_DONE) + return FENSTER_AUDIO_BUFSZ; + return 0; +} +FENSTER_API void fenster_audio_write(struct fenster_audio *fa, float *buf, + size_t n) { + for (int i = 0; i < 2; i++) { + if (fa->hdr[i].dwFlags & WHDR_DONE) { + for (unsigned j = 0; j < n; j++) { + fa->buf[i][j] = (int16_t)(buf[j] * 32767); + } + waveOutWrite(fa->wo, &fa->hdr[i], sizeof(WAVEHDR)); + return; + } + } +} +FENSTER_API void fenster_audio_close(struct fenster_audio *fa) { + waveOutClose(fa->wo); +} +#elif defined(__linux__) +int snd_pcm_open(void **, const char *, int, int); +int snd_pcm_set_params(void *, int, int, int, int, int, int); +int snd_pcm_avail(void *); +int snd_pcm_writei(void *, const void *, unsigned long); +int snd_pcm_recover(void *, int, int); +int snd_pcm_close(void *); +FENSTER_API int fenster_audio_open(struct fenster_audio *fa) { + if (snd_pcm_open(&fa->pcm, "default", 0, 0)) + return -1; + int fmt = (*(unsigned char *)(&(uint16_t){1})) ? 14 : 15; + return snd_pcm_set_params(fa->pcm, fmt, 3, 1, FENSTER_SAMPLE_RATE, 1, 100000); +} +FENSTER_API int fenster_audio_available(struct fenster_audio *fa) { + int n = snd_pcm_avail(fa->pcm); + if (n < 0) + snd_pcm_recover(fa->pcm, n, 0); + return n; +} +FENSTER_API void fenster_audio_write(struct fenster_audio *fa, float *buf, + size_t n) { + int r = snd_pcm_writei(fa->pcm, buf, n); + if (r < 0) + snd_pcm_recover(fa->pcm, r, 0); +} +FENSTER_API void fenster_audio_close(struct fenster_audio *fa) { + snd_pcm_close(fa->pcm); +} +#endif + +#endif /* FENSTER_HEADER */ +#endif /* FENSTER_AUDIO_H */