#define SOKOL_AUDIO_IMPL #include "sokol/sokol_audio.h" #include "cell.h" // Helper to check bool property static int JS_GETBOOL(JSContext *js, JSValue obj, const char *prop) { JSValue v = JS_GetPropertyStr(js, obj, prop); int ret = JS_ToBool(js, v); JS_FreeValue(js, v); return ret; } // Global JS callback for audio streaming static JSContext *g_audio_js = NULL; static JSValue g_audio_callback = JS_NULL; // Audio stream callback that calls into JS static void audio_stream_callback(float *buffer, int num_frames, int num_channels) { if (!g_audio_js || JS_IsNull(g_audio_callback)) { // Fill with silence for (int i = 0; i < num_frames * num_channels; i++) { buffer[i] = 0.0f; } return; } // Create a blob from the buffer data int num_samples = num_frames * num_channels; size_t buffer_size = num_samples * sizeof(float); JSValue blob = js_new_blob_stoned_copy(g_audio_js, buffer, buffer_size); JSValue args[3] = { blob, JS_NewInt32(g_audio_js, num_frames), JS_NewInt32(g_audio_js, num_channels) }; JSValue result = JS_Call(g_audio_js, g_audio_callback, JS_NULL, 3, args); JS_FreeValue(g_audio_js, args[0]); JS_FreeValue(g_audio_js, args[1]); JS_FreeValue(g_audio_js, args[2]); JS_FreeValue(g_audio_js, result); } // ============================================================================ // SETUP / SHUTDOWN // ============================================================================ // audio.setup(desc) - Initialize sokol_audio // desc: { sample_rate, num_channels, buffer_frames, packet_frames, num_packets, stream_cb } JSC_CCALL(audio_setup, saudio_desc desc = {0}; if (argc > 0 && JS_IsObject(argv[0])) { JSValue cfg = argv[0]; JS_GETPROP(js, desc.sample_rate, cfg, sample_rate, number); JS_GETPROP(js, desc.num_channels, cfg, num_channels, number); JS_GETPROP(js, desc.buffer_frames, cfg, buffer_frames, number); JS_GETPROP(js, desc.packet_frames, cfg, packet_frames, number); JS_GETPROP(js, desc.num_packets, cfg, num_packets, number); // Check for stream callback JSValue cb_val = JS_GetPropertyStr(js, cfg, "stream_cb"); if (JS_IsFunction(js, cb_val)) { // Store the callback globally if (!JS_IsNull(g_audio_callback)) { JS_FreeValue(g_audio_js, g_audio_callback); } g_audio_js = js; g_audio_callback = JS_DupValue(js, cb_val); desc.stream_cb = audio_stream_callback; } JS_FreeValue(js, cb_val); } saudio_setup(&desc); ) // audio.shutdown() - Shutdown sokol_audio JSC_CCALL(audio_shutdown, saudio_shutdown(); // Clean up callback if (!JS_IsNull(g_audio_callback)) { JS_FreeValue(g_audio_js, g_audio_callback); g_audio_callback = JS_NULL; g_audio_js = NULL; } ) // audio.is_valid() - Check if audio is initialized JSC_CCALL(audio_is_valid, return JS_NewBool(js, saudio_isvalid()); ) // ============================================================================ // QUERY // ============================================================================ // audio.sample_rate() - Get actual sample rate JSC_CCALL(audio_sample_rate, return JS_NewInt32(js, saudio_sample_rate()); ) // audio.buffer_frames() - Get actual buffer size in frames JSC_CCALL(audio_buffer_frames, return JS_NewInt32(js, saudio_buffer_frames()); ) // audio.channels() - Get actual number of channels JSC_CCALL(audio_channels, return JS_NewInt32(js, saudio_channels()); ) // audio.suspended() - Check if audio is suspended (WebAudio only) JSC_CCALL(audio_suspended, return JS_NewBool(js, saudio_suspended()); ) // ============================================================================ // PUSH MODEL // ============================================================================ // audio.expect() - Get number of frames that can be pushed JSC_CCALL(audio_expect, return JS_NewInt32(js, saudio_expect()); ) // audio.push(samples) - Push audio samples (Float32Array) // Returns number of frames actually pushed JSC_CCALL(audio_push, size_t data_size; void *data_ptr = js_get_blob_data(js, &data_size, argv[0]); if (data_ptr && data_ptr != (void*)-1) { int num_channels = saudio_channels(); int num_frames = data_size / (sizeof(float) * num_channels); int pushed = saudio_push((const float*)data_ptr, num_frames); return JS_NewInt32(js, pushed); } return JS_NewInt32(js, 0); ) // ============================================================================ // MODULE INIT // ============================================================================ static const JSCFunctionListEntry js_audio_funcs[] = { JS_CFUNC_DEF("setup", 1, js_audio_setup), JS_CFUNC_DEF("shutdown", 0, js_audio_shutdown), JS_CFUNC_DEF("is_valid", 0, js_audio_is_valid), JS_CFUNC_DEF("sample_rate", 0, js_audio_sample_rate), JS_CFUNC_DEF("buffer_frames", 0, js_audio_buffer_frames), JS_CFUNC_DEF("channels", 0, js_audio_channels), JS_CFUNC_DEF("suspended", 0, js_audio_suspended), JS_CFUNC_DEF("expect", 0, js_audio_expect), JS_CFUNC_DEF("push", 1, js_audio_push), }; CELL_USE_INIT( JSValue audio = JS_NewObject(js); JS_SetPropertyFunctionList(js, audio, js_audio_funcs, sizeof(js_audio_funcs)/sizeof(js_audio_funcs[0])); return audio; )