From 7152ae093e51df2e13309405f95ba4a9189a360f Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 29 Apr 2025 12:43:25 -0500 Subject: [PATCH] audio working from soloud -> sdl3 --- scripts/modules/sound.js | 30 ++++++ source/jsffi.c | 201 +++++++++++++++++++++++++++++++++++++-- source/prosperon.c | 8 +- source/qjs_macros.h | 11 ++- source/qjs_soloud.c | 11 ++- tests/camera.js | 10 +- 6 files changed, 247 insertions(+), 24 deletions(-) diff --git a/scripts/modules/sound.js b/scripts/modules/sound.js index 6871afc3..31ce5d73 100644 --- a/scripts/modules/sound.js +++ b/scripts/modules/sound.js @@ -16,6 +16,7 @@ audio.pcm = function pcm(file) file = res.find_sound(file); if (!file) throw new Error(`Could not findfile ${file}`); if (pcms[file]) return pcms[file]; + var bytes = io.slurpbytes(file) var newpcm = soloud.load_wav_mem(io.slurpbytes(file)); pcms[file] = newpcm; return newpcm; @@ -73,4 +74,33 @@ audio.music = function music(file, fade = 0.5) { }; audio.music[doc.sym] = `Play the given music file, with an optional cross fade. The song will loop. When this is invoked again, the previous music is replaced.` +var ss = use('sdl_audio') + +var feeder = ss.open_stream("playback") + +feeder.set_format({format:"f32", channels:2,samplerate:44100}) + +feeder.resume() + +var FRAMES = 1024 +var CHANNELS = 2 +var BYTES_PER_F = 4 +var SAMPLES = FRAMES * CHANNELS +var CHUNK_BYTES = FRAMES * CHANNELS * BYTES_PER_F + +var mixview = new Float32Array(FRAMES*CHANNELS) +var mixbuf = mixview.buffer + +function pump() +{ + if (feeder.queued() < CHUNK_BYTES*3) { + var mm = soloud.mix(FRAMES) + feeder.put(mm) + } + + $_.delay(pump, 1/240) +} + +pump() + return audio; diff --git a/source/jsffi.c b/source/jsffi.c index f6558dad..377ffa5e 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -33,6 +33,8 @@ #include "qjs_soloud.h" #include "qjs_qr.h" +#include + void gui_input(SDL_Event *e); #ifdef _WIN32 @@ -3135,15 +3137,48 @@ static const JSCFunctionListEntry js_renderer_ctx_funcs[] = { MIST_FUNC_DEF(renderer, make_sprite_mesh, 2), }; +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) @@ -3198,9 +3233,14 @@ JSC_CCALL(sdl_audio_open_stream, if (type) JS_FreeCString(js, type); -// SDL_AudioSpec want = js2audiospec(js, argv[1]); - - SDL_AudioStream *st = SDL_OpenAudioDeviceStream(devid, NULL, NULL, NULL); + SDL_AudioStream *st; + + if (JS_IsUndefined(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()); @@ -3214,6 +3254,145 @@ static const JSCFunctionListEntry js_sdl_audio_funcs[] = { 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_IsUndefined(argv[0])){ + src=js2audiospec(js,argv[0]); + src_ptr=&src; + } + if(argc>1&&!JS_IsUndefined(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_UNDEFINED; +) + +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_UNDEFINED; +) + +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_UNDEFINED; +) + +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_UNDEFINED; +) + +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_GetArrayBuffer(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_UNDEFINED; +) + +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_NewArrayBufferCopy(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_UNDEFINED; +) + +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_UNDEFINED; +) + +/* ---------- 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), +}; + // GPU API JSC_CCALL(gpu_claim_window, SDL_GPUDevice *gpu = js2SDL_GPUDevice(js,self); @@ -7264,6 +7443,8 @@ void ffi_load(JSContext *js) QJSCLASSPREP_NO_FUNCS(SDL_Cursor) QJSCLASSPREP_FUNCS(SDL_Camera) + QJSCLASSPREP_FUNCS(SDL_AudioStream) + QJSCLASSPREP_FUNCS(renderer_ctx) QJSCLASSPREP_FUNCS(SDL_GPUDevice) diff --git a/source/prosperon.c b/source/prosperon.c index 9721cf33..d74dc6c9 100644 --- a/source/prosperon.c +++ b/source/prosperon.c @@ -325,7 +325,9 @@ void set_actor_state(prosperon_rt *actor) void actor_turn(prosperon_rt *actor, int greedy) { SDL_LockMutex(actor->turn); +#ifdef TRACY_ENABLE TracyCFiberEnter(actor->id); +#endif SDL_LockMutex(actor->msg_mutex); actor->state = ACTOR_RUNNING; @@ -412,13 +414,17 @@ void actor_turn(prosperon_rt *actor, int greedy) goto EVENT; END: +#ifdef TRACY_ENABLE TracyCFiberLeave(actor->id); +#endif SDL_UnlockMutex(actor->turn); set_actor_state(actor); return; KILL: - TracyCFiberLeave(actor->id); +#ifdef TRACY_ENABLE + TracyCFiberLeave(actor->id); +#endif SDL_UnlockMutex(actor->turn); actor_free(actor); } diff --git a/source/qjs_macros.h b/source/qjs_macros.h index 47f05fb5..24e0d0e9 100644 --- a/source/qjs_macros.h +++ b/source/qjs_macros.h @@ -1,4 +1,13 @@ -#include +#ifdef TRACY_ENABLE + #include +#else + /* Provide harmless stubs when Tracy is not in use so the rest of the code + can still call the hooks unconditionally. */ + #define TracyCAllocN(ptr, size, name) ((void)0) + #define TracyCFreeN(ptr, name) ((void)0) + #define TracyCFiberEnter(name) ((void)0) + #define TracyCFiberLeave(name) ((void)0) +#endif #define MIST_CFUNC_DEF(name, length, func1, props) { name, props, JS_DEF_CFUNC, 0, .u = { .func = { length, JS_CFUNC_generic, { .generic = func1 } } } } diff --git a/source/qjs_soloud.c b/source/qjs_soloud.c index aa8d894e..e663a309 100644 --- a/source/qjs_soloud.c +++ b/source/qjs_soloud.c @@ -75,7 +75,7 @@ JSCLASS(Bus, Bus_destroy) static JSValue js_soloud_make(JSContext *js, JSValue self, int argc, JSValue *argv) { soloud = Soloud_create(); - Soloud_initEx(soloud, SOLOUD_CLIP_ROUNDOFF, SOLOUD_AUTO, SOLOUD_AUTO, SOLOUD_AUTO, SOLOUD_AUTO); + Soloud_initEx(soloud, SOLOUD_CLIP_ROUNDOFF, SOLOUD_NULLDRIVER, 44100, SOLOUD_AUTO, 2); JSValue obj = JS_NewObject(js); JS_SetPropertyStr(js, obj, "channels", JS_NewFloat64(js, Soloud_getBackendChannels(soloud))); JS_SetPropertyStr(js, obj, "samplerate", JS_NewFloat64(js, Soloud_getBackendSamplerate(soloud))); @@ -93,10 +93,11 @@ static JSValue js_soloud_play(JSContext *js, JSValue self, int argc, JSValue *ar static JSValue js_soloud_mix(JSContext *js, JSValue self, int argc, JSValue *argv) { - size_t len; - void *data = JS_GetArrayBuffer(js, &len, argv[0]); - Soloud_mix(soloud, data, js2number(js,argv[1])); - return JS_UNDEFINED; + int req; + JS_ToInt32(js, &req, argv[0]); + float *buf = malloc(2*req*sizeof(float)); + Soloud_mix(soloud, buf, req); + return JS_NewArrayBufferCopy(js, buf, 2*req*sizeof(float)); } // Create a voice from a WAV file diff --git a/tests/camera.js b/tests/camera.js index 6f0db572..3d126b0f 100644 --- a/tests/camera.js +++ b/tests/camera.js @@ -64,12 +64,8 @@ function loop() } var sound = use('sound') -prosperon.myguy = sound.play("test.mp3") - -var ss = use('sdl_audio') -console.log(ss.drivers()) -console.log(ss.devices()) - -var feeder = ss.open_stream("playback", {format: "f32", channels:2, freq:48000}) +//prosperon.myguy = sound.play('test.mp3') loop() + +