#include "cell.h" #include #include #include // dsp.mix_blobs(blobs, volumes) // blobs: Array of stoned blobs (stereo f32 PCM, all same length) // volumes: Array of floats (volume per blob) // returns: stoned blob (mixed audio) // All blobs must be the same byte length. JSC_CCALL(dsp_mix_blobs, if (argc < 2) return JS_ThrowTypeError(js, "dsp.mix_blobs(blobs, volumes) requires 2 arguments"); JSValue blobs_arr = argv[0]; JSValue vols_arr = argv[1]; if (!JS_IsArray(js, blobs_arr)) return JS_ThrowTypeError(js, "blobs must be an array"); if (!JS_IsArray(js, vols_arr)) return JS_ThrowTypeError(js, "volumes must be an array"); int len = 0; JSValue len_val = JS_GetPropertyStr(js, blobs_arr, "length"); JS_ToInt32(js, &len, len_val); JS_FreeValue(js, len_val); if (len == 0) { // Return empty stoned blob return js_new_blob_stoned_copy(js, NULL, 0); } // Get first blob to determine output size JSValue first_blob = JS_GetPropertyUint32(js, blobs_arr, 0); size_t out_bytes; float *first_data = (float*)js_get_blob_data(js, &out_bytes, first_blob); JS_FreeValue(js, first_blob); if (first_data == (void*)-1) return JS_EXCEPTION; if (out_bytes == 0) return js_new_blob_stoned_copy(js, NULL, 0); size_t num_samples = out_bytes / sizeof(float); float *mix_buf = calloc(num_samples, sizeof(float)); if (!mix_buf) return JS_ThrowOutOfMemory(js); for (int i = 0; i < len; i++) { JSValue blob_val = JS_GetPropertyUint32(js, blobs_arr, i); JSValue vol_val = JS_GetPropertyUint32(js, vols_arr, i); size_t blob_len; float *blob_data = (float*)js_get_blob_data(js, &blob_len, blob_val); JS_FreeValue(js, blob_val); if (blob_data == (void*)-1) { JS_FreeValue(js, vol_val); free(mix_buf); return JS_EXCEPTION; } double vol = 1.0; JS_ToFloat64(js, &vol, vol_val); JS_FreeValue(js, vol_val); // Mix samples (use min length to avoid overrun) size_t samples = blob_len / sizeof(float); if (samples > num_samples) samples = num_samples; for (size_t s = 0; s < samples; s++) { mix_buf[s] += blob_data[s] * (float)vol; } } JSValue result = js_new_blob_stoned_copy(js, mix_buf, out_bytes); free(mix_buf); return result; ) // dsp.lpf(blob, options) // blob: stoned blob (stereo f32 PCM) // options: { cutoff: 0.0-1.0 (normalized frequency), channels: 2 } // returns: stoned blob (filtered audio) // Simple one-pole lowpass filter per channel JSC_CCALL(dsp_lpf, if (argc < 2) return JS_ThrowTypeError(js, "dsp.lpf(blob, options) requires 2 arguments"); size_t len; float *data = (float*)js_get_blob_data(js, &len, argv[0]); if (data == (void*)-1) return JS_EXCEPTION; if (len == 0) return js_new_blob_stoned_copy(js, NULL, 0); // Get options double cutoff = 0.5; int32_t channels = 2; JSValue cutoff_val = JS_GetPropertyStr(js, argv[1], "cutoff"); JSValue channels_val = JS_GetPropertyStr(js, argv[1], "channels"); if (!JS_IsNull(cutoff_val)) JS_ToFloat64(js, &cutoff, cutoff_val); if (!JS_IsNull(channels_val)) JS_ToInt32(js, &channels, channels_val); JS_FreeValue(js, cutoff_val); JS_FreeValue(js, channels_val); if (cutoff < 0.0) cutoff = 0.0; if (cutoff > 1.0) cutoff = 1.0; if (channels < 1) channels = 1; // Compute filter coefficient (simple one-pole: y[n] = alpha*x[n] + (1-alpha)*y[n-1]) // alpha = cutoff (0 = no signal, 1 = no filtering) float alpha = (float)cutoff; size_t num_samples = len / sizeof(float); float *out = malloc(len); if (!out) return JS_ThrowOutOfMemory(js); // Allocate state per channel float *prev = calloc(channels, sizeof(float)); if (!prev) { free(out); return JS_ThrowOutOfMemory(js); } for (size_t i = 0; i < num_samples; i++) { int ch = i % channels; float x = data[i]; float y = alpha * x + (1.0f - alpha) * prev[ch]; prev[ch] = y; out[i] = y; } free(prev); JSValue result = js_new_blob_stoned_copy(js, out, len); free(out); return result; ) // dsp.silence(frames, channels) // Returns a stoned blob of silence (zeroed f32 samples) JSC_CCALL(dsp_silence, int32_t frames = 1024; int32_t channels = 2; if (argc >= 1) JS_ToInt32(js, &frames, argv[0]); if (argc >= 2) JS_ToInt32(js, &channels, argv[1]); if (frames < 0) frames = 0; if (channels < 1) channels = 1; size_t bytes = (size_t)frames * channels * sizeof(float); float *buf = calloc(frames * channels, sizeof(float)); if (!buf) return JS_ThrowOutOfMemory(js); JSValue result = js_new_blob_stoned_copy(js, buf, bytes); free(buf); return result; ) // dsp.mono_to_stereo(blob) // Converts a mono f32 blob to stereo by duplicating samples JSC_CCALL(dsp_mono_to_stereo, size_t len; float *data = (float*)js_get_blob_data(js, &len, argv[0]); if (data == (void*)-1) return JS_EXCEPTION; if (len == 0) return js_new_blob_stoned_copy(js, NULL, 0); size_t mono_samples = len / sizeof(float); size_t stereo_bytes = mono_samples * 2 * sizeof(float); float *out = malloc(stereo_bytes); if (!out) return JS_ThrowOutOfMemory(js); for (size_t i = 0; i < mono_samples; i++) { out[i * 2] = data[i]; out[i * 2 + 1] = data[i]; } JSValue result = js_new_blob_stoned_copy(js, out, stereo_bytes); free(out); return result; ) static const JSCFunctionListEntry js_dsp_funcs[] = { MIST_FUNC_DEF(dsp, mix_blobs, 2), MIST_FUNC_DEF(dsp, lpf, 2), MIST_FUNC_DEF(dsp, silence, 2), MIST_FUNC_DEF(dsp, mono_to_stereo, 1) }; CELL_USE_FUNCS(js_dsp_funcs)