Files
cell-sokol/audio.c
2025-12-11 00:10:41 -06:00

171 lines
5.2 KiB
C

#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;
)