// sound_playdate.c - Cell integration for Playdate Sound API
// Wraps pd_api_sound.h functions for JavaScript access

#include "cell.h"
#include "common.h"

// --- Helpers ---
static FilePlayer* js2fileplayer(JSContext *js, JSValueConst val) {
    int64_t ptr; if (JS_ToInt64(js, &ptr, val) < 0) return NULL;
    return (FilePlayer*)(intptr_t)ptr;
}
static SamplePlayer* js2sampleplayer(JSContext *js, JSValueConst val) {
    int64_t ptr; if (JS_ToInt64(js, &ptr, val) < 0) return NULL;
    return (SamplePlayer*)(intptr_t)ptr;
}
static AudioSample* js2sample(JSContext *js, JSValueConst val) {
    int64_t ptr; if (JS_ToInt64(js, &ptr, val) < 0) return NULL;
    return (AudioSample*)(intptr_t)ptr;
}
static PDSynth* js2synth(JSContext *js, JSValueConst val) {
    int64_t ptr; if (JS_ToInt64(js, &ptr, val) < 0) return NULL;
    return (PDSynth*)(intptr_t)ptr;
}
static SoundChannel* js2channel(JSContext *js, JSValueConst val) {
    int64_t ptr; if (JS_ToInt64(js, &ptr, val) < 0) return NULL;
    return (SoundChannel*)(intptr_t)ptr;
}

// --- Global ---
JSC_CCALL(sound_getCurrentTime,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewInt64(js, pd_sound->getCurrentTime());
)
JSC_CCALL(sound_getDefaultChannel,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    SoundChannel *ch = pd_sound->getDefaultChannel();
    return ch ? JS_NewInt64(js, (int64_t)(intptr_t)ch) : JS_NULL;
)
JSC_CCALL(sound_addChannel,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewBool(js, pd_sound->addChannel(js2channel(js, argv[0])));
)
JSC_CCALL(sound_removeChannel,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewBool(js, pd_sound->removeChannel(js2channel(js, argv[0])));
)
JSC_CCALL(sound_setOutputsActive,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->setOutputsActive(JS_ToBool(js, argv[0]), JS_ToBool(js, argv[1]));
    return JS_NULL;
)
JSC_CCALL(sound_getError,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    const char *err = pd_sound->getError();
    return err ? JS_NewString(js, err) : JS_NULL;
)

// --- FilePlayer ---
JSC_CCALL(sound_newFilePlayer,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    FilePlayer *p = pd_sound->fileplayer->newPlayer();
    return p ? JS_NewInt64(js, (int64_t)(intptr_t)p) : JS_NULL;
)
JSC_CCALL(sound_freeFilePlayer,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    FilePlayer *p = js2fileplayer(js, argv[0]);
    if (p) pd_sound->fileplayer->freePlayer(p);
    return JS_NULL;
)
JSC_SCALL(sound_loadIntoFilePlayer,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    FilePlayer *p = js2fileplayer(js, argv[0]);
    ret = JS_NewBool(js, pd_sound->fileplayer->loadIntoPlayer(p, str));
)
JSC_CCALL(sound_filePlayerPlay,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    FilePlayer *p = js2fileplayer(js, argv[0]);
    int repeat = argc > 1 ? (int)js2number(js, argv[1]) : 1;
    return JS_NewBool(js, pd_sound->fileplayer->play(p, repeat));
)
JSC_CCALL(sound_filePlayerIsPlaying,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewBool(js, pd_sound->fileplayer->isPlaying(js2fileplayer(js, argv[0])));
)
JSC_CCALL(sound_filePlayerPause,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->fileplayer->pause(js2fileplayer(js, argv[0]));
    return JS_NULL;
)
JSC_CCALL(sound_filePlayerStop,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->fileplayer->stop(js2fileplayer(js, argv[0]));
    return JS_NULL;
)
JSC_CCALL(sound_filePlayerSetVolume,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->fileplayer->setVolume(js2fileplayer(js, argv[0]), (float)js2number(js, argv[1]), (float)js2number(js, argv[2]));
    return JS_NULL;
)
JSC_CCALL(sound_filePlayerGetVolume,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    float l, r; pd_sound->fileplayer->getVolume(js2fileplayer(js, argv[0]), &l, &r);
    JSValue obj = JS_NewObject(js);
    JS_SetPropertyStr(js, obj, "left", JS_NewFloat64(js, l));
    JS_SetPropertyStr(js, obj, "right", JS_NewFloat64(js, r));
    return obj;
)
JSC_CCALL(sound_filePlayerGetLength,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewFloat64(js, pd_sound->fileplayer->getLength(js2fileplayer(js, argv[0])));
)
JSC_CCALL(sound_filePlayerSetOffset,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->fileplayer->setOffset(js2fileplayer(js, argv[0]), (float)js2number(js, argv[1]));
    return JS_NULL;
)
JSC_CCALL(sound_filePlayerGetOffset,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewFloat64(js, pd_sound->fileplayer->getOffset(js2fileplayer(js, argv[0])));
)
JSC_CCALL(sound_filePlayerSetRate,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->fileplayer->setRate(js2fileplayer(js, argv[0]), (float)js2number(js, argv[1]));
    return JS_NULL;
)
JSC_CCALL(sound_filePlayerGetRate,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewFloat64(js, pd_sound->fileplayer->getRate(js2fileplayer(js, argv[0])));
)
JSC_CCALL(sound_filePlayerSetLoopRange,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->fileplayer->setLoopRange(js2fileplayer(js, argv[0]), (float)js2number(js, argv[1]), (float)js2number(js, argv[2]));
    return JS_NULL;
)
JSC_CCALL(sound_filePlayerDidUnderrun,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewBool(js, pd_sound->fileplayer->didUnderrun(js2fileplayer(js, argv[0])));
)
JSC_CCALL(sound_filePlayerSetStopOnUnderrun,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->fileplayer->setStopOnUnderrun(js2fileplayer(js, argv[0]), JS_ToBool(js, argv[1]));
    return JS_NULL;
)
JSC_CCALL(sound_filePlayerSetBufferLength,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->fileplayer->setBufferLength(js2fileplayer(js, argv[0]), (float)js2number(js, argv[1]));
    return JS_NULL;
)

// --- Sample ---
JSC_SCALL(sound_loadSample,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    AudioSample *s = pd_sound->sample->load(str);
    ret = s ? JS_NewInt64(js, (int64_t)(intptr_t)s) : JS_NULL;
)
JSC_CCALL(sound_freeSample,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    AudioSample *s = js2sample(js, argv[0]);
    if (s) pd_sound->sample->freeSample(s);
    return JS_NULL;
)
JSC_CCALL(sound_getSampleLength,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewFloat64(js, pd_sound->sample->getLength(js2sample(js, argv[0])));
)
JSC_CCALL(sound_newSampleBuffer,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    AudioSample *s = pd_sound->sample->newSampleBuffer((int)js2number(js, argv[0]));
    return s ? JS_NewInt64(js, (int64_t)(intptr_t)s) : JS_NULL;
)

// --- SamplePlayer ---
JSC_CCALL(sound_newSamplePlayer,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    SamplePlayer *p = pd_sound->sampleplayer->newPlayer();
    return p ? JS_NewInt64(js, (int64_t)(intptr_t)p) : JS_NULL;
)
JSC_CCALL(sound_freeSamplePlayer,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    SamplePlayer *p = js2sampleplayer(js, argv[0]);
    if (p) pd_sound->sampleplayer->freePlayer(p);
    return JS_NULL;
)
JSC_CCALL(sound_samplePlayerSetSample,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->sampleplayer->setSample(js2sampleplayer(js, argv[0]), js2sample(js, argv[1]));
    return JS_NULL;
)
JSC_CCALL(sound_samplePlayerPlay,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    SamplePlayer *p = js2sampleplayer(js, argv[0]);
    int repeat = argc > 1 ? (int)js2number(js, argv[1]) : 1;
    float rate = argc > 2 ? (float)js2number(js, argv[2]) : 1.0f;
    return JS_NewBool(js, pd_sound->sampleplayer->play(p, repeat, rate));
)
JSC_CCALL(sound_samplePlayerIsPlaying,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewBool(js, pd_sound->sampleplayer->isPlaying(js2sampleplayer(js, argv[0])));
)
JSC_CCALL(sound_samplePlayerStop,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->sampleplayer->stop(js2sampleplayer(js, argv[0]));
    return JS_NULL;
)
JSC_CCALL(sound_samplePlayerSetVolume,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->sampleplayer->setVolume(js2sampleplayer(js, argv[0]), (float)js2number(js, argv[1]), (float)js2number(js, argv[2]));
    return JS_NULL;
)
JSC_CCALL(sound_samplePlayerGetVolume,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    float l, r; pd_sound->sampleplayer->getVolume(js2sampleplayer(js, argv[0]), &l, &r);
    JSValue obj = JS_NewObject(js);
    JS_SetPropertyStr(js, obj, "left", JS_NewFloat64(js, l));
    JS_SetPropertyStr(js, obj, "right", JS_NewFloat64(js, r));
    return obj;
)
JSC_CCALL(sound_samplePlayerGetLength,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewFloat64(js, pd_sound->sampleplayer->getLength(js2sampleplayer(js, argv[0])));
)
JSC_CCALL(sound_samplePlayerSetOffset,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->sampleplayer->setOffset(js2sampleplayer(js, argv[0]), (float)js2number(js, argv[1]));
    return JS_NULL;
)
JSC_CCALL(sound_samplePlayerGetOffset,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewFloat64(js, pd_sound->sampleplayer->getOffset(js2sampleplayer(js, argv[0])));
)
JSC_CCALL(sound_samplePlayerSetRate,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->sampleplayer->setRate(js2sampleplayer(js, argv[0]), (float)js2number(js, argv[1]));
    return JS_NULL;
)
JSC_CCALL(sound_samplePlayerGetRate,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewFloat64(js, pd_sound->sampleplayer->getRate(js2sampleplayer(js, argv[0])));
)
JSC_CCALL(sound_samplePlayerSetPlayRange,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->sampleplayer->setPlayRange(js2sampleplayer(js, argv[0]), (int)js2number(js, argv[1]), (int)js2number(js, argv[2]));
    return JS_NULL;
)
JSC_CCALL(sound_samplePlayerSetPaused,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->sampleplayer->setPaused(js2sampleplayer(js, argv[0]), JS_ToBool(js, argv[1]));
    return JS_NULL;
)

// --- Synth ---
JSC_CCALL(sound_newSynth,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    PDSynth *s = pd_sound->synth->newSynth();
    return s ? JS_NewInt64(js, (int64_t)(intptr_t)s) : JS_NULL;
)
JSC_CCALL(sound_freeSynth,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    PDSynth *s = js2synth(js, argv[0]);
    if (s) pd_sound->synth->freeSynth(s);
    return JS_NULL;
)
JSC_CCALL(sound_synthSetWaveform,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->synth->setWaveform(js2synth(js, argv[0]), (SoundWaveform)(int)js2number(js, argv[1]));
    return JS_NULL;
)
JSC_CCALL(sound_synthSetAttackTime,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->synth->setAttackTime(js2synth(js, argv[0]), (float)js2number(js, argv[1]));
    return JS_NULL;
)
JSC_CCALL(sound_synthSetDecayTime,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->synth->setDecayTime(js2synth(js, argv[0]), (float)js2number(js, argv[1]));
    return JS_NULL;
)
JSC_CCALL(sound_synthSetSustainLevel,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->synth->setSustainLevel(js2synth(js, argv[0]), (float)js2number(js, argv[1]));
    return JS_NULL;
)
JSC_CCALL(sound_synthSetReleaseTime,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->synth->setReleaseTime(js2synth(js, argv[0]), (float)js2number(js, argv[1]));
    return JS_NULL;
)
JSC_CCALL(sound_synthSetTranspose,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->synth->setTranspose(js2synth(js, argv[0]), (float)js2number(js, argv[1]));
    return JS_NULL;
)
JSC_CCALL(sound_synthPlayNote,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    PDSynth *s = js2synth(js, argv[0]);
    float freq = (float)js2number(js, argv[1]);
    float vel = argc > 2 ? (float)js2number(js, argv[2]) : 1.0f;
    float len = argc > 3 ? (float)js2number(js, argv[3]) : -1.0f;
    uint32_t when = argc > 4 ? (uint32_t)js2number(js, argv[4]) : 0;
    pd_sound->synth->playNote(s, freq, vel, len, when);
    return JS_NULL;
)
JSC_CCALL(sound_synthPlayMIDINote,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    PDSynth *s = js2synth(js, argv[0]);
    MIDINote note = (MIDINote)js2number(js, argv[1]);
    float vel = argc > 2 ? (float)js2number(js, argv[2]) : 1.0f;
    float len = argc > 3 ? (float)js2number(js, argv[3]) : -1.0f;
    uint32_t when = argc > 4 ? (uint32_t)js2number(js, argv[4]) : 0;
    pd_sound->synth->playMIDINote(s, note, vel, len, when);
    return JS_NULL;
)
JSC_CCALL(sound_synthNoteOff,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->synth->noteOff(js2synth(js, argv[0]), argc > 1 ? (uint32_t)js2number(js, argv[1]) : 0);
    return JS_NULL;
)
JSC_CCALL(sound_synthStop,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->synth->stop(js2synth(js, argv[0]));
    return JS_NULL;
)
JSC_CCALL(sound_synthSetVolume,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->synth->setVolume(js2synth(js, argv[0]), (float)js2number(js, argv[1]), (float)js2number(js, argv[2]));
    return JS_NULL;
)
JSC_CCALL(sound_synthGetVolume,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    float l, r; pd_sound->synth->getVolume(js2synth(js, argv[0]), &l, &r);
    JSValue obj = JS_NewObject(js);
    JS_SetPropertyStr(js, obj, "left", JS_NewFloat64(js, l));
    JS_SetPropertyStr(js, obj, "right", JS_NewFloat64(js, r));
    return obj;
)
JSC_CCALL(sound_synthIsPlaying,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewBool(js, pd_sound->synth->isPlaying(js2synth(js, argv[0])));
)
JSC_CCALL(sound_synthGetParameterCount,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewInt32(js, pd_sound->synth->getParameterCount(js2synth(js, argv[0])));
)
JSC_CCALL(sound_synthSetParameter,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewBool(js, pd_sound->synth->setParameter(js2synth(js, argv[0]), (int)js2number(js, argv[1]), (float)js2number(js, argv[2])));
)

// --- Channel ---
JSC_CCALL(sound_newChannel,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    SoundChannel *ch = pd_sound->channel->newChannel();
    return ch ? JS_NewInt64(js, (int64_t)(intptr_t)ch) : JS_NULL;
)
JSC_CCALL(sound_freeChannel,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    SoundChannel *ch = js2channel(js, argv[0]);
    if (ch) pd_sound->channel->freeChannel(ch);
    return JS_NULL;
)
JSC_CCALL(sound_channelSetVolume,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->channel->setVolume(js2channel(js, argv[0]), (float)js2number(js, argv[1]));
    return JS_NULL;
)
JSC_CCALL(sound_channelGetVolume,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    return JS_NewFloat64(js, pd_sound->channel->getVolume(js2channel(js, argv[0])));
)
JSC_CCALL(sound_channelSetPan,
    if (!pd_sound) return JS_RaiseDisrupt(js, "sound not initialized");
    pd_sound->channel->setPan(js2channel(js, argv[0]), (float)js2number(js, argv[1]));
    return JS_NULL;
)

static const JSCFunctionListEntry js_sound_funcs[] = {
    MIST_FUNC_DEF(sound, getCurrentTime, 0),
    MIST_FUNC_DEF(sound, getDefaultChannel, 0),
    MIST_FUNC_DEF(sound, addChannel, 1),
    MIST_FUNC_DEF(sound, removeChannel, 1),
    MIST_FUNC_DEF(sound, setOutputsActive, 2),
    MIST_FUNC_DEF(sound, getError, 0),
    MIST_FUNC_DEF(sound, newFilePlayer, 0),
    MIST_FUNC_DEF(sound, freeFilePlayer, 1),
    MIST_FUNC_DEF(sound, loadIntoFilePlayer, 2),
    MIST_FUNC_DEF(sound, filePlayerPlay, 2),
    MIST_FUNC_DEF(sound, filePlayerIsPlaying, 1),
    MIST_FUNC_DEF(sound, filePlayerPause, 1),
    MIST_FUNC_DEF(sound, filePlayerStop, 1),
    MIST_FUNC_DEF(sound, filePlayerSetVolume, 3),
    MIST_FUNC_DEF(sound, filePlayerGetVolume, 1),
    MIST_FUNC_DEF(sound, filePlayerGetLength, 1),
    MIST_FUNC_DEF(sound, filePlayerSetOffset, 2),
    MIST_FUNC_DEF(sound, filePlayerGetOffset, 1),
    MIST_FUNC_DEF(sound, filePlayerSetRate, 2),
    MIST_FUNC_DEF(sound, filePlayerGetRate, 1),
    MIST_FUNC_DEF(sound, filePlayerSetLoopRange, 3),
    MIST_FUNC_DEF(sound, filePlayerDidUnderrun, 1),
    MIST_FUNC_DEF(sound, filePlayerSetStopOnUnderrun, 2),
    MIST_FUNC_DEF(sound, filePlayerSetBufferLength, 2),
    MIST_FUNC_DEF(sound, loadSample, 1),
    MIST_FUNC_DEF(sound, freeSample, 1),
    MIST_FUNC_DEF(sound, getSampleLength, 1),
    MIST_FUNC_DEF(sound, newSampleBuffer, 1),
    MIST_FUNC_DEF(sound, newSamplePlayer, 0),
    MIST_FUNC_DEF(sound, freeSamplePlayer, 1),
    MIST_FUNC_DEF(sound, samplePlayerSetSample, 2),
    MIST_FUNC_DEF(sound, samplePlayerPlay, 3),
    MIST_FUNC_DEF(sound, samplePlayerIsPlaying, 1),
    MIST_FUNC_DEF(sound, samplePlayerStop, 1),
    MIST_FUNC_DEF(sound, samplePlayerSetVolume, 3),
    MIST_FUNC_DEF(sound, samplePlayerGetVolume, 1),
    MIST_FUNC_DEF(sound, samplePlayerGetLength, 1),
    MIST_FUNC_DEF(sound, samplePlayerSetOffset, 2),
    MIST_FUNC_DEF(sound, samplePlayerGetOffset, 1),
    MIST_FUNC_DEF(sound, samplePlayerSetRate, 2),
    MIST_FUNC_DEF(sound, samplePlayerGetRate, 1),
    MIST_FUNC_DEF(sound, samplePlayerSetPlayRange, 3),
    MIST_FUNC_DEF(sound, samplePlayerSetPaused, 2),
    MIST_FUNC_DEF(sound, newSynth, 0),
    MIST_FUNC_DEF(sound, freeSynth, 1),
    MIST_FUNC_DEF(sound, synthSetWaveform, 2),
    MIST_FUNC_DEF(sound, synthSetAttackTime, 2),
    MIST_FUNC_DEF(sound, synthSetDecayTime, 2),
    MIST_FUNC_DEF(sound, synthSetSustainLevel, 2),
    MIST_FUNC_DEF(sound, synthSetReleaseTime, 2),
    MIST_FUNC_DEF(sound, synthSetTranspose, 2),
    MIST_FUNC_DEF(sound, synthPlayNote, 5),
    MIST_FUNC_DEF(sound, synthPlayMIDINote, 5),
    MIST_FUNC_DEF(sound, synthNoteOff, 2),
    MIST_FUNC_DEF(sound, synthStop, 1),
    MIST_FUNC_DEF(sound, synthSetVolume, 3),
    MIST_FUNC_DEF(sound, synthGetVolume, 1),
    MIST_FUNC_DEF(sound, synthIsPlaying, 1),
    MIST_FUNC_DEF(sound, synthGetParameterCount, 1),
    MIST_FUNC_DEF(sound, synthSetParameter, 3),
    MIST_FUNC_DEF(sound, newChannel, 0),
    MIST_FUNC_DEF(sound, freeChannel, 1),
    MIST_FUNC_DEF(sound, channelSetVolume, 2),
    MIST_FUNC_DEF(sound, channelGetVolume, 1),
    MIST_FUNC_DEF(sound, channelSetPan, 2),
};

JSValue js_sound_use(JSContext *js) {
    JSValue mod = JS_NewObject(js);
    JS_SetPropertyFunctionList(js, mod, js_sound_funcs, countof(js_sound_funcs));
    return mod;
}
