117 lines
3.6 KiB
C
117 lines
3.6 KiB
C
#include "cell.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
// dsp.mix(voices, frames)
|
|
// voices: Array of voice objects { pcm: blob, pos: int, vol: float }
|
|
// frames: Number of frames to generate (stereo f32)
|
|
// returns: blob (mixed audio)
|
|
JSC_CCALL(dsp_mix,
|
|
if (argc < 2) return JS_ThrowTypeError(js, "dsp.mix(voices, frames) requires 2 arguments");
|
|
|
|
JSValue voices_arr = argv[0];
|
|
if (!JS_IsArray(js, voices_arr)) return JS_ThrowTypeError(js, "voices must be an array");
|
|
|
|
int32_t num_frames;
|
|
if (JS_ToInt32(js, &num_frames, argv[1]) < 0) return JS_EXCEPTION;
|
|
if (num_frames <= 0) return JS_ThrowRangeError(js, "frames must be positive");
|
|
|
|
// Allocate output buffer (stereo f32)
|
|
size_t out_channels = 2;
|
|
size_t out_samples = num_frames * out_channels;
|
|
size_t out_bytes = out_samples * sizeof(float);
|
|
|
|
float *mix_buf = calloc(out_samples, sizeof(float));
|
|
if (!mix_buf) return JS_ThrowOutOfMemory(js);
|
|
|
|
// Iterate over voices
|
|
int len = 0;
|
|
JSValue len_val = JS_GetPropertyStr(js, voices_arr, "length");
|
|
JS_ToInt32(js, &len, len_val);
|
|
JS_FreeValue(js, len_val);
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
JSValue voice = JS_GetPropertyUint32(js, voices_arr, i);
|
|
if (JS_IsObject(voice)) {
|
|
JSValue pcm_val = JS_GetPropertyStr(js, voice, "pcm");
|
|
JSValue pos_val = JS_GetPropertyStr(js, voice, "pos");
|
|
JSValue vol_val = JS_GetPropertyStr(js, voice, "vol");
|
|
JSValue channels_val = JS_GetPropertyStr(js, voice, "channels");
|
|
|
|
size_t pcm_len;
|
|
float *pcm_data = (float*)js_get_blob_data(js, &pcm_len, pcm_val);
|
|
if (pcm_data == -1) {
|
|
JS_FreeValue(js, pcm_val);
|
|
JS_FreeValue(js, pos_val);
|
|
JS_FreeValue(js, vol_val);
|
|
JS_FreeValue(js, channels_val);
|
|
JS_FreeValue(js, voice);
|
|
free(mix_buf);
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (pcm_len == 0) {
|
|
JS_FreeValue(js, pcm_val);
|
|
JS_FreeValue(js, pos_val);
|
|
JS_FreeValue(js, vol_val);
|
|
JS_FreeValue(js, channels_val);
|
|
JS_FreeValue(js, voice);
|
|
free(mix_buf);
|
|
return JS_ThrowTypeError(js, "voice pcm data is empty");
|
|
}
|
|
|
|
int64_t pos = 0;
|
|
JS_ToInt64(js, &pos, pos_val);
|
|
|
|
double vol = 1.0;
|
|
if (!JS_IsNull(vol_val)) JS_ToFloat64(js, &vol, vol_val);
|
|
|
|
int32_t channels = 1;
|
|
if (!JS_IsNull(channels_val)) JS_ToInt32(js, &channels, channels_val);
|
|
|
|
if (pcm_data && pos >= 0) {
|
|
size_t total_samples = pcm_len / sizeof(float);
|
|
size_t total_frames = total_samples / channels;
|
|
|
|
for (int f = 0; f < num_frames; f++) {
|
|
if (pos + f >= total_frames) break; // End of sample
|
|
|
|
float L = 0, R = 0;
|
|
if (channels == 1) {
|
|
L = R = pcm_data[pos + f] * (float)vol;
|
|
} else if (channels >= 2) {
|
|
L = pcm_data[(pos + f) * channels] * (float)vol;
|
|
R = pcm_data[(pos + f) * channels + 1] * (float)vol;
|
|
}
|
|
|
|
mix_buf[f * 2] += L;
|
|
mix_buf[f * 2 + 1] += R;
|
|
}
|
|
|
|
int64_t new_pos = pos + num_frames;
|
|
if (new_pos > total_frames) new_pos = total_frames;
|
|
|
|
JS_SetPropertyStr(js, voice, "pos", JS_NewInt64(js, new_pos));
|
|
}
|
|
|
|
JS_FreeValue(js, pcm_val);
|
|
JS_FreeValue(js, pos_val);
|
|
JS_FreeValue(js, vol_val);
|
|
JS_FreeValue(js, channels_val);
|
|
}
|
|
JS_FreeValue(js, voice);
|
|
}
|
|
|
|
// Create result blob
|
|
JSValue result_blob = js_new_blob_stoned_copy(js, mix_buf, out_bytes);
|
|
free(mix_buf);
|
|
|
|
return result_blob;
|
|
)
|
|
|
|
static const JSCFunctionListEntry js_dsp_funcs[] = {
|
|
MIST_FUNC_DEF(dsp, mix, 2)
|
|
};
|
|
|
|
CELL_USE_FUNCS(js_dsp_funcs)
|