From fd19ecb41ed25bec0e6eab33c8366e17b78dce56 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Wed, 26 Nov 2025 02:07:44 -0600 Subject: [PATCH] sdl audio --- meson.build | 1 + source/qjs_sdl.c | 272 +----------------------------- source/qjs_sdl_audio.c | 372 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 375 insertions(+), 270 deletions(-) create mode 100644 source/qjs_sdl_audio.c diff --git a/meson.build b/meson.build index 6b9c1513..05bd3de7 100644 --- a/meson.build +++ b/meson.build @@ -273,6 +273,7 @@ src += [ # engine 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_sdl_gpu.c', + 'qjs_sdl_audio.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', diff --git a/source/qjs_sdl.c b/source/qjs_sdl.c index 68542017..82c0e26a 100644 --- a/source/qjs_sdl.c +++ b/source/qjs_sdl.c @@ -14,13 +14,8 @@ void SDL_Camera_free(JSRuntime *rt, SDL_Camera *cam) SDL_CloseCamera(cam); } -void SDL_AudioStream_free(JSRuntime *rt, SDL_AudioStream *st) { - SDL_DestroyAudioStream(st); -} - // Class definitions for SDL types QJSCLASS(SDL_Camera,) -QJSCLASS(SDL_AudioStream,) // CAMERA FUNCTIONS @@ -289,271 +284,8 @@ JSValue js_camera_use(JSContext *js) { return mod; } -// SDL AUDIO FUNCTIONS - -// Audio format lookup table and conversion functions -static const struct { const char *s; SDL_AudioFormat f; } fmt_lut[] = { - { "u8", SDL_AUDIO_U8 }, /* Unsigned 8-bit */ - { "s8", SDL_AUDIO_S8 }, /* Signed 8-bit */ - { "s16", SDL_AUDIO_S16 }, /* Signed 16-bit, host endian */ - { "s32", SDL_AUDIO_S32 }, /* Signed 32-bit, host endian */ - { "f32", SDL_AUDIO_F32 } /* Float 32-bit, host endian */ -}; - -static int format_str_to_enum(const char *f, SDL_AudioFormat *out) -{ - struct { const char *s; SDL_AudioFormat f; } map[] = { - {"u8", SDL_AUDIO_U8 }, {"s16", SDL_AUDIO_S16}, - {"s32", SDL_AUDIO_S32}, {"f32", SDL_AUDIO_F32} - }; - for (size_t i=0;iformat))); - - JS_SetPropertyStr(js, o, "channels", - JS_NewInt32(js, spec->channels)); - - JS_SetPropertyStr(js, o, "samplerate", - JS_NewInt32(js, spec->freq)); - - return o; -} - -static SDL_AudioSpec js2audiospec(JSContext *js, JSValue obj) -{ - SDL_AudioSpec spec; - - JSValue v; - - v = JS_GetPropertyStr(js, obj, "format"); - if (!JS_IsNull(v)) { - const char *s = JS_ToCString(js, v); - format_str_to_enum(s, &spec.format); - JS_FreeCString(js, s); - } - JS_FreeValue(js, v); - - v = JS_GetPropertyStr(js, obj, "channels"); - if (!JS_IsNull(v)) JS_ToInt32(js, &spec.channels, v); - JS_FreeValue(js, v); - - v = JS_GetPropertyStr(js, obj, "samplerate"); - if (!JS_IsNull(v)) JS_ToInt32(js, &spec.freq, v); - JS_FreeValue(js, v); - - return spec; -} - -JSC_CCALL(sdl_audio_drivers, - int num = SDL_GetNumAudioDrivers(); - JSValue arr = JS_NewArray(js); - for (int i = 0; i < num; i++) - JS_SetPropertyUint32(js, arr, i, JS_NewString(js, SDL_GetAudioDriver(i))); - return arr; -) - -JSC_CCALL(sdl_audio_devices, - int n; - SDL_AudioDeviceID *ids = SDL_GetAudioPlaybackDevices(&n); - - JSValue arr = JS_NewArray(js); - for (int i = 0; i < n; i++) - JS_SetPropertyUint32(js,arr,i,JS_NewString(js, SDL_GetAudioDeviceName(ids[i]))); - - return arr; -) - -JSC_CCALL(sdl_audio_open_stream, - const char *type = JS_IsString(argv[0]) ? JS_ToCString(js, argv[0]) : NULL; - SDL_AudioDeviceID devid = !strcmp(type, "capture") ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING : SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; - - if (type) - JS_FreeCString(js, type); - - SDL_AudioStream *st; - - if (JS_IsNull(argv[1])) - st = SDL_OpenAudioDeviceStream(devid, NULL, NULL, NULL); - else { - SDL_AudioSpec want = js2audiospec(js, argv[1]); - st = SDL_OpenAudioDeviceStream(devid, &want, NULL, NULL); - } - - if (!st) - return JS_ThrowInternalError(js, "open failed: %s", SDL_GetError()); - - return SDL_AudioStream2js(js, st); -) - -static const JSCFunctionListEntry js_sdl_audio_funcs[] = { - MIST_FUNC_DEF(sdl_audio, drivers, 0), - MIST_FUNC_DEF(sdl_audio, devices, 0), - MIST_FUNC_DEF(sdl_audio, open_stream, 2), -}; - -JSC_CCALL(sdl_audiostream_get_format, - SDL_AudioStream *as = js2SDL_AudioStream(js, self); - SDL_AudioSpec src; - SDL_AudioSpec dst; - SDL_GetAudioStreamFormat(as, &src, &dst); - JSValue obj = JS_NewObject(js); - JS_SetPropertyStr(js, obj, "src", audiospec2js(js, &src)); - JS_SetPropertyStr(js, obj, "dst", audiospec2js(js, &dst)); - return obj; -) - -JSC_CCALL(sdl_audiostream_set_format, - SDL_AudioStream *as=js2SDL_AudioStream(js,self); - const SDL_AudioSpec *src_ptr=NULL,*dst_ptr=NULL; - SDL_AudioSpec src={0},dst={0}; - - if(argc>0&&!JS_IsNull(argv[0])){ - src=js2audiospec(js,argv[0]); - src_ptr=&src; - } - if(argc>1&&!JS_IsNull(argv[1])){ - dst=js2audiospec(js,argv[1]); - dst_ptr=&dst; - } - - if(!SDL_SetAudioStreamFormat(as,src_ptr,dst_ptr)) - return JS_ThrowInternalError(js,"%s",SDL_GetError()); - - return JS_NULL; -) - -JSC_CCALL(sdl_audiostream_resume, - SDL_AudioStream *as = js2SDL_AudioStream(js,self); - if (!SDL_ResumeAudioStreamDevice(as)) - return JS_ThrowInternalError(js,"%s",SDL_GetError()); - - return JS_NULL; -) - -JSC_CCALL(sdl_audiostream_clear, - SDL_AudioStream *as=js2SDL_AudioStream(js,self); - if (!SDL_ClearAudioStream(as)) - return JS_ThrowInternalError(js,"%s",SDL_GetError()); - return JS_NULL; -) - -JSC_CCALL(sdl_audiostream_flush, - SDL_AudioStream *as=js2SDL_AudioStream(js,self); - if(!SDL_FlushAudioStream(as)) - return JS_ThrowInternalError(js,"%s",SDL_GetError()); - return JS_NULL; -) - -JSC_CCALL(sdl_audiostream_available, - SDL_AudioStream *as=js2SDL_AudioStream(js,self); - Sint64 n = SDL_GetAudioStreamAvailable(as); - if(n<0) return JS_ThrowInternalError(js,"%s",SDL_GetError()); - return JS_NewInt64(js,n); -) - -JSC_CCALL(sdl_audiostream_queued, - SDL_AudioStream *as=js2SDL_AudioStream(js,self); - Sint64 n = SDL_GetAudioStreamQueued(as); - if(n<0) return JS_ThrowInternalError(js,"%s",SDL_GetError()); - return JS_NewInt64(js,n); -) - -/* ---------- data IO ---------------------------------------------------- */ -JSC_CCALL(sdl_audiostream_put, - SDL_AudioStream *as=js2SDL_AudioStream(js,self); - size_t len; - void *buf = js_get_blob_data(js, &len, argv[0]); - if (!buf) - return JS_ThrowInternalError(js, "Requires array buffer."); - - if (!SDL_PutAudioStreamData(as,buf,len)) - return JS_ThrowInternalError(js, "%s", SDL_GetError()); - return JS_NULL; -) - -JSC_CCALL(sdl_audiostream_get, - SDL_AudioStream *as=js2SDL_AudioStream(js,self); - int want; - JS_ToInt32(js,&want,argv[0]); - void *data = malloc(want); - int got = SDL_GetAudioStreamData(as, data, want); - - if (got<0) { - free(data); - return JS_ThrowInternalError(js,"%s",SDL_GetError()); - } - - JSValue ab = js_new_blob_stoned_copy(js, data, got); - free(data); - - return ab; -) - -JSC_CCALL(sdl_audiostream_get_gain, - SDL_AudioStream *as=js2SDL_AudioStream(js,self); - return JS_NewFloat64(js,SDL_GetAudioStreamGain(as)); -) - -JSC_CCALL(sdl_audiostream_set_gain, - SDL_AudioStream *as=js2SDL_AudioStream(js,self); - double g; JS_ToFloat64(js,&g,argv[0]); - SDL_SetAudioStreamGain(as,(float)g); - return JS_NULL; -) - -JSC_CCALL(sdl_audiostream_get_freq_ratio, - SDL_AudioStream *as=js2SDL_AudioStream(js,self); - return JS_NewFloat64(js,SDL_GetAudioStreamFrequencyRatio(as)); -) - -JSC_CCALL(sdl_audiostream_set_freq_ratio, - SDL_AudioStream *as=js2SDL_AudioStream(js,self); - double r; JS_ToFloat64(js,&r,argv[0]); - SDL_SetAudioStreamFrequencyRatio(as,(float)r); - return JS_NULL; -) - -/* ---------- JS export list -------------------------------------------- */ -static const JSCFunctionListEntry js_SDL_AudioStream_funcs[] = { - MIST_FUNC_DEF(sdl_audiostream, get_format, 0), - MIST_FUNC_DEF(sdl_audiostream, set_format, 2), - MIST_FUNC_DEF(sdl_audiostream, resume, 0), - MIST_FUNC_DEF(sdl_audiostream, clear, 0), - MIST_FUNC_DEF(sdl_audiostream, flush, 0), - MIST_FUNC_DEF(sdl_audiostream, available, 0), - MIST_FUNC_DEF(sdl_audiostream, queued, 0), - MIST_FUNC_DEF(sdl_audiostream, put, 1), - MIST_FUNC_DEF(sdl_audiostream, get, 1), - MIST_FUNC_DEF(sdl_audiostream, set_gain, 1), - MIST_FUNC_DEF(sdl_audiostream, get_gain, 0), - MIST_FUNC_DEF(sdl_audiostream, set_freq_ratio, 1), - MIST_FUNC_DEF(sdl_audiostream, get_freq_ratio, 0), -}; - -JSValue js_sdl_audio_use(JSContext *js) { - if (!SDL_Init(SDL_INIT_AUDIO)) - return JS_ThrowInternalError(js, "Unable to initialize audio subsystem: %s", SDL_GetError()); - - QJSCLASSPREP_FUNCS(SDL_AudioStream) - +JSValue js_sdl_use(JSContext *js) { JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js,mod,js_sdl_audio_funcs,countof(js_sdl_audio_funcs)); + JS_SetPropertyStr(js, mod, "camera", js_camera_use(js)); return mod; } diff --git a/source/qjs_sdl_audio.c b/source/qjs_sdl_audio.c new file mode 100644 index 00000000..d5cd74b2 --- /dev/null +++ b/source/qjs_sdl_audio.c @@ -0,0 +1,372 @@ +#include "qjs_macros.h" +#include "jsffi.h" +#include "quickjs.h" + +#include +#include +#include "cell.h" + +#define countof(x) (sizeof(x)/sizeof((x)[0])) + +// Helper functions +double js2number(JSContext *js, JSValue v); +int js2bool(JSContext *js, JSValue v); + +// Free functions for finalizers +void SDL_AudioStream_free(JSRuntime *rt, SDL_AudioStream *stream) { + SDL_DestroyAudioStream(stream); +} + +// Class definitions +QJSCLASS(SDL_AudioStream,) + +// Conversion functions +SDL_AudioFormat js2SDL_AudioFormat(JSContext *js, JSValue v) { + int fmt = js2number(js, v); + return (SDL_AudioFormat)fmt; +} + +JSValue SDL_AudioFormat2js(JSContext *js, SDL_AudioFormat fmt) { + return JS_NewInt32(js, (int)fmt); +} + +SDL_AudioDeviceID js2SDL_AudioDeviceID(JSContext *js, JSValue v) { + return (SDL_AudioDeviceID)js2number(js, v); +} + +JSValue SDL_AudioDeviceID2js(JSContext *js, SDL_AudioDeviceID id) { + return JS_NewInt32(js, (int)id); +} + +SDL_AudioSpec js2SDL_AudioSpec(JSContext *js, JSValue v) { + SDL_AudioSpec spec = {0}; + JS_GETPROP(js, spec.format, v, format, SDL_AudioFormat) + JS_GETPROP(js, spec.channels, v, channels, number) + JS_GETPROP(js, spec.freq, v, freq, number) + return spec; +} + +JSValue SDL_AudioSpec2js(JSContext *js, SDL_AudioSpec spec) { + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "format", SDL_AudioFormat2js(js, spec.format)); + JS_SetPropertyStr(js, obj, "channels", JS_NewInt32(js, spec.channels)); + JS_SetPropertyStr(js, obj, "freq", JS_NewInt32(js, spec.freq)); + return obj; +} + +// Enum mappings for audio formats (simplified) +JSValue js_get_audio_drivers(JSContext *js, JSValue self, int argc, JSValue *argv) { + int count = SDL_GetNumAudioDrivers(); + JSValue arr = JS_NewArray(js); + for (int i = 0; i < count; i++) { + const char *driver = SDL_GetAudioDriver(i); + JS_SetPropertyUint32(js, arr, i, JS_NewString(js, driver)); + } + return arr; +} + +JSValue js_get_current_audio_driver(JSContext *js, JSValue self, int argc, JSValue *argv) { + const char *driver = SDL_GetCurrentAudioDriver(); + return driver ? JS_NewString(js, driver) : JS_NULL; +} + +JSValue js_get_audio_playback_devices(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioDeviceID *devices = SDL_GetAudioPlaybackDevices(NULL); + if (!devices) return JS_NULL; + JSValue arr = JS_NewArray(js); + for (int i = 0; devices[i]; i++) { + JS_SetPropertyUint32(js, arr, i, SDL_AudioDeviceID2js(js, devices[i])); + } + SDL_free(devices); + return arr; +} + +JSValue js_get_audio_recording_devices(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioDeviceID *devices = SDL_GetAudioRecordingDevices(NULL); + if (!devices) return JS_NULL; + JSValue arr = JS_NewArray(js); + for (int i = 0; devices[i]; i++) { + JS_SetPropertyUint32(js, arr, i, SDL_AudioDeviceID2js(js, devices[i])); + } + SDL_free(devices); + return arr; +} + +JSValue js_get_audio_device_name(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + const char *name = SDL_GetAudioDeviceName(devid); + return name ? JS_NewString(js, name) : JS_NULL; +} + +JSValue js_is_audio_device_playback(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + return JS_NewBool(js, SDL_IsAudioDevicePlayback(devid)); +} + +JSValue js_is_audio_device_physical(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + return JS_NewBool(js, SDL_IsAudioDevicePhysical(devid)); +} + +JSValue js_get_audio_device_format(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + SDL_AudioSpec spec; + if (!SDL_GetAudioDeviceFormat(devid, &spec, NULL)) { + return JS_NULL; + } + return SDL_AudioSpec2js(js, spec); +} + +JSValue js_open_audio_device_stream(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + SDL_AudioSpec spec = {0}; + if (argc > 1) { + spec = js2SDL_AudioSpec(js, argv[1]); + } + SDL_AudioStream *stream = SDL_OpenAudioDeviceStream(devid, &spec, NULL, NULL); + if (!stream) { + return JS_ThrowInternalError(js, "Failed to open audio device stream: %s", SDL_GetError()); + } + return SDL_AudioStream2js(js, stream); +} + +JSValue js_create_audio_stream(JSContext *js, JSValue self, int argc, JSValue *argv) { + SDL_AudioSpec src_spec = js2SDL_AudioSpec(js, argv[0]); + SDL_AudioSpec dst_spec = js2SDL_AudioSpec(js, argv[1]); + SDL_AudioStream *stream = SDL_CreateAudioStream(&src_spec, &dst_spec); + if (!stream) { + return JS_ThrowInternalError(js, "Failed to create audio stream: %s", SDL_GetError()); + } + return SDL_AudioStream2js(js, stream); +} + +JSC_CCALL(audio_stream_put_data, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + size_t len; + void *data = js_get_blob_data(js, &len, argv[0]); + if (!SDL_PutAudioStreamData(stream, data, len)) { + ret = JS_ThrowInternalError(js, "Failed to put audio stream data: %s", SDL_GetError()); + } +) + +JSC_CCALL(audio_stream_get_data, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + int len = js2number(js, argv[0]); + void *data = malloc(len); + int got = SDL_GetAudioStreamData(stream, data, len); + if (got < 0) { + free(data); + ret = JS_ThrowInternalError(js, "Failed to get audio stream data: %s", SDL_GetError()); + } else { + ret = js_new_blob_stoned_copy(js, data, got); + free(data); + } +) + +JSC_CCALL(audio_stream_available, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + ret = JS_NewInt32(js, SDL_GetAudioStreamAvailable(stream)); +) + +JSC_CCALL(audio_stream_queued, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + ret = JS_NewInt32(js, SDL_GetAudioStreamQueued(stream)); +) + +JSC_CCALL(audio_stream_flush, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_FlushAudioStream(stream); +) + +JSC_CCALL(audio_stream_clear, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_ClearAudioStream(stream); +) + +JSC_CCALL(audio_stream_bind, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + if (!SDL_BindAudioStream(devid, stream)) { + ret = JS_ThrowInternalError(js, "Failed to bind audio stream: %s", SDL_GetError()); + } +) + +JSC_CCALL(audio_stream_unbind, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_UnbindAudioStream(stream); +) + +JSC_CCALL(audio_stream_get_format, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_AudioSpec src, dst; + if (!SDL_GetAudioStreamFormat(stream, &src, &dst)) { + ret = JS_NULL; + } else { + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "src", SDL_AudioSpec2js(js, src)); + JS_SetPropertyStr(js, obj, "dst", SDL_AudioSpec2js(js, dst)); + ret = obj; + } +) + +JSC_CCALL(audio_stream_get_device, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_AudioDeviceID devid = SDL_GetAudioStreamDevice(stream); + ret = SDL_AudioDeviceID2js(js, devid); +) + +JSC_CCALL(audio_stream_get_gain, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + ret = JS_NewFloat64(js, SDL_GetAudioStreamGain(stream)); +) + +JSC_CCALL(audio_stream_set_gain, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + float gain = js2number(js, argv[0]); + if (!SDL_SetAudioStreamGain(stream, gain)) { + ret = JS_ThrowInternalError(js, "Failed to set audio stream gain: %s", SDL_GetError()); + } +) + +JSC_CCALL(audio_stream_set_frequency_ratio, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + float ratio = js2number(js, argv[0]); + if (!SDL_SetAudioStreamFrequencyRatio(stream, ratio)) { + ret = JS_ThrowInternalError(js, "Failed to set audio stream frequency ratio: %s", SDL_GetError()); + } +) + +JSC_CCALL(audio_stream_get_frequency_ratio, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + ret = JS_NewFloat64(js, SDL_GetAudioStreamFrequencyRatio(stream)); +) + +JSC_CCALL(audio_device_pause, + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + SDL_PauseAudioDevice(devid); +) + +JSC_CCALL(audio_device_resume, + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + SDL_ResumeAudioDevice(devid); +) + +JSC_CCALL(audio_device_paused, + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + ret = JS_NewBool(js, SDL_AudioDevicePaused(devid)); +) + +JSC_CCALL(audio_stream_device_paused, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + ret = JS_NewBool(js, SDL_AudioStreamDevicePaused(stream)); +) + +JSC_CCALL(audio_stream_pause_device, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_PauseAudioStreamDevice(stream); +) + +JSC_CCALL(audio_stream_resume_device, + SDL_AudioStream *stream = js2SDL_AudioStream(js, self); + SDL_ResumeAudioStreamDevice(stream); +) + +JSC_CCALL(audio_device_close, + SDL_AudioDeviceID devid = js2SDL_AudioDeviceID(js, argv[0]); + SDL_CloseAudioDevice(devid); +) + +JSValue js_load_wav(JSContext *js, JSValue self, int argc, JSValue *argv) { + const char *path = JS_ToCString(js, argv[0]); + SDL_AudioSpec spec; + Uint8 *data; + Uint32 len; + if (!SDL_LoadWAV(path, &spec, &data, &len)) { + JS_FreeCString(js, path); + return JS_ThrowInternalError(js, "Failed to load WAV: %s", SDL_GetError()); + } + JS_FreeCString(js, path); + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "spec", SDL_AudioSpec2js(js, spec)); + JS_SetPropertyStr(js, obj, "data", js_new_blob_stoned_copy(js, data, len)); + SDL_free(data); + return obj; +} + +JSC_CCALL(convert_audio_samples, + SDL_AudioSpec src_spec = js2SDL_AudioSpec(js, argv[0]); + SDL_AudioSpec dst_spec = js2SDL_AudioSpec(js, argv[1]); + size_t src_len; + void *src_data = js_get_blob_data(js, &src_len, argv[2]); + Uint8 *dst_data = NULL; + int dst_len = 0; + if (!SDL_ConvertAudioSamples(&src_spec, src_data, (int)src_len, &dst_spec, &dst_data, &dst_len)) { + ret = JS_ThrowInternalError(js, "Failed to convert audio samples: %s", SDL_GetError()); + } else { + ret = js_new_blob_stoned_copy(js, dst_data, dst_len); + SDL_free(dst_data); + } +) + +JSC_CCALL(mix_audio, + SDL_AudioFormat format = js2SDL_AudioFormat(js, argv[0]); + size_t len; + void *dst = js_get_blob_data(js, &len, argv[1]); + void *src = js_get_blob_data(js, &len, argv[2]); + float volume = js2number(js, argv[3]); + SDL_MixAudio(dst, src, format, len, volume); +) + +// Function list for SDL_AudioStream +static const JSCFunctionListEntry js_SDL_AudioStream_funcs[] = { + JS_CFUNC_DEF("put", 1, js_audio_stream_put_data), + JS_CFUNC_DEF("get", 1, js_audio_stream_get_data), + JS_CFUNC_DEF("available", 0, js_audio_stream_available), + JS_CFUNC_DEF("queued", 0, js_audio_stream_queued), + JS_CFUNC_DEF("flush", 0, js_audio_stream_flush), + JS_CFUNC_DEF("clear", 0, js_audio_stream_clear), + JS_CFUNC_DEF("bind", 1, js_audio_stream_bind), + JS_CFUNC_DEF("unbind", 0, js_audio_stream_unbind), + JS_CFUNC_DEF("get_format", 0, js_audio_stream_get_format), + JS_CFUNC_DEF("get_device", 0, js_audio_stream_get_device), + JS_CGETSET_DEF("gain", js_audio_stream_get_gain, js_audio_stream_set_gain), + JS_CGETSET_DEF("frequency_ratio", js_audio_stream_get_frequency_ratio, js_audio_stream_set_frequency_ratio), + JS_CFUNC_DEF("pause_device", 0, js_audio_stream_pause_device), + JS_CFUNC_DEF("resume_device", 0, js_audio_stream_resume_device), + JS_CFUNC_DEF("device_paused", 0, js_audio_stream_device_paused), +}; + +// Main function list +static const JSCFunctionListEntry js_sdl_audio_funcs[] = { + JS_CFUNC_DEF("get_drivers", 0, js_get_audio_drivers), + JS_CFUNC_DEF("get_current_driver", 0, js_get_current_audio_driver), + JS_CFUNC_DEF("get_playback_devices", 0, js_get_audio_playback_devices), + JS_CFUNC_DEF("get_recording_devices", 0, js_get_audio_recording_devices), + JS_CFUNC_DEF("get_device_name", 1, js_get_audio_device_name), + JS_CFUNC_DEF("is_playback_device", 1, js_is_audio_device_playback), + JS_CFUNC_DEF("is_physical_device", 1, js_is_audio_device_physical), + JS_CFUNC_DEF("get_device_format", 1, js_get_audio_device_format), + JS_CFUNC_DEF("open_device_stream", 1, js_open_audio_device_stream), + JS_CFUNC_DEF("create_stream", 2, js_create_audio_stream), + JS_CFUNC_DEF("pause_device", 1, js_audio_device_pause), + JS_CFUNC_DEF("resume_device", 1, js_audio_device_resume), + JS_CFUNC_DEF("device_paused", 1, js_audio_device_paused), + JS_CFUNC_DEF("close_device", 1, js_audio_device_close), + JS_CFUNC_DEF("load_wav", 1, js_load_wav), + JS_CFUNC_DEF("convert_samples", 3, js_convert_audio_samples), + JS_CFUNC_DEF("mix_audio", 4, js_mix_audio), +}; + +// Use function +JSValue js_sdl_audio_use(JSContext *js) { + JS_NewClassID(&js_SDL_AudioStream_id); + JS_NewClass(JS_GetRuntime(js), js_SDL_AudioStream_id, &js_SDL_AudioStream_class); + JSValue proto = JS_NewObject(js); + JS_SetPropertyFunctionList(js, proto, js_SDL_AudioStream_funcs, countof(js_SDL_AudioStream_funcs)); + JS_SetClassProto(js, js_SDL_AudioStream_id, proto); + + JSValue export = JS_NewObject(js); + JS_SetPropertyFunctionList(js, export, js_sdl_audio_funcs, countof(js_sdl_audio_funcs)); + return export; +}