Files
cell/source/qjs_sdl.c
2025-05-26 16:24:19 -05:00

463 lines
13 KiB
C

#include "qjs_sdl.h"
#include "jsffi.h"
#include "qjs_macros.h"
#include <SDL3/SDL.h>
// SDL Free functions
void SDL_Camera_free(JSRuntime *rt, SDL_Camera *cam)
{
SDL_CloseCamera(cam);
}
void SDL_AudioStream_free(JSRuntime *rt, SDL_AudioStream *st) {
SDL_DestroyAudioStream(st);
}
// Class definitions for SDL types
QJSCLASS(SDL_Camera,)
QJSCLASS(SDL_AudioStream,)
// Internal keymod function for input module
static JSValue js_keymod(JSContext *js)
{
SDL_Keymod modstate = SDL_GetModState();
JSValue ret = JS_NewObject(js);
if (SDL_KMOD_CTRL & modstate)
JS_SetPropertyStr(js,ret,"ctrl", JS_NewBool(js,1));
if (SDL_KMOD_SHIFT & modstate)
JS_SetPropertyStr(js,ret,"shift", JS_NewBool(js,1));
if (SDL_KMOD_ALT & modstate)
JS_SetPropertyStr(js,ret,"alt", JS_NewBool(js,1));
if (SDL_KMOD_GUI & modstate)
JS_SetPropertyStr(js,ret,"super", JS_NewBool(js,1));
if (SDL_KMOD_NUM & modstate)
JS_SetPropertyStr(js,ret,"numlock", JS_NewBool(js,1));
if (SDL_KMOD_CAPS & modstate)
JS_SetPropertyStr(js,ret,"caps", JS_NewBool(js,1));
if (SDL_KMOD_SCROLL & modstate)
JS_SetPropertyStr(js,ret,"scrolllock", JS_NewBool(js,1));
if (SDL_KMOD_MODE & modstate)
JS_SetPropertyStr(js,ret,"mode", JS_NewBool(js,1));
return ret;
}
// INPUT FUNCTIONS
JSC_CCALL(input_mouse_lock, SDL_CaptureMouse(JS_ToBool(js,argv[0])))
JSC_CCALL(input_mouse_show,
if (JS_ToBool(js,argv[0]))
SDL_ShowCursor();
else
SDL_HideCursor();
)
JSC_CCALL(input_keyname,
return JS_NewString(js, SDL_GetKeyName(js2number(js,argv[0])));
)
JSC_CCALL(input_keymod,
return js_keymod(js);
)
JSC_CCALL(input_mousestate,
float x,y;
SDL_MouseButtonFlags flags = SDL_GetMouseState(&x,&y);
JSValue m = JS_NewObject(js);
JS_SetPropertyStr(js,m,"x", number2js(js,x));
JS_SetPropertyStr(js,m,"y", number2js(js,y));
if (flags & SDL_BUTTON_LMASK)
JS_SetPropertyStr(js, m, "left", JS_NewBool(js, 1));
if (flags & SDL_BUTTON_MMASK)
JS_SetPropertyStr(js, m, "middle", JS_NewBool(js, 1));
if (flags & SDL_BUTTON_RMASK)
JS_SetPropertyStr(js, m, "right", JS_NewBool(js, 1));
if (flags & SDL_BUTTON_X1MASK)
JS_SetPropertyStr(js, m, "x1", JS_NewBool(js, 1));
if (flags & SDL_BUTTON_X2MASK)
JS_SetPropertyStr(js, m, "x2", JS_NewBool(js, 1));
return m;
)
static const JSCFunctionListEntry js_input_funcs[] = {
MIST_FUNC_DEF(input, mouse_show, 1),
MIST_FUNC_DEF(input, mouse_lock, 1),
MIST_FUNC_DEF(input, keyname, 1),
MIST_FUNC_DEF(input, keymod, 0),
MIST_FUNC_DEF(input, mousestate, 0),
};
JSValue js_input_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_input_funcs,countof(js_input_funcs));
// Initialize SDL cursor class (no functions)
JSValue c_types = JS_GetPropertyStr(js, JS_GetGlobalObject(js), "c_types");
JS_FreeValue(js, c_types);
return mod;
}
// CAMERA FUNCTIONS
JSC_CCALL(camera_list,
int num;
SDL_CameraID *ids = SDL_GetCameras(&num);
if (num == 0) return JS_UNDEFINED;
JSValue jsids = JS_NewArray(js);
for (int i = 0; i < num; i++)
JS_SetPropertyUint32(js,jsids, i, number2js(js,ids[i]));
return jsids;
)
JSC_CCALL(camera_open,
int id = js2number(js,argv[0]);
SDL_Camera *cam = SDL_OpenCamera(id, NULL);
if (!cam) ret = JS_ThrowReferenceError(js, "Could not open camera %d: %s\n", id, SDL_GetError());
else
ret = SDL_Camera2js(js,cam);
)
JSC_CCALL(camera_name,
const char *name = SDL_GetCameraName(js2number(js,argv[0]));
if (!name) return JS_ThrowReferenceError(js, "Could not get camera name from id %d.", (int)js2number(js,argv[0]));
return JS_NewString(js, name);
)
JSC_CCALL(camera_position,
SDL_CameraPosition pos = SDL_GetCameraPosition(js2number(js,argv[0]));
switch(pos) {
case SDL_CAMERA_POSITION_UNKNOWN: return JS_NewString(js,"unknown");
case SDL_CAMERA_POSITION_FRONT_FACING: return JS_NewString(js,"front");
case SDL_CAMERA_POSITION_BACK_FACING: return JS_NewString(js,"back");
}
)
static const JSCFunctionListEntry js_camera_funcs[] = {
MIST_FUNC_DEF(camera, list, 0),
MIST_FUNC_DEF(camera, open, 1),
MIST_FUNC_DEF(camera, name, 1),
MIST_FUNC_DEF(camera, position, 1),
};
JSC_CCALL(camera_capture,
/*
SDL_ClearError();
SDL_Camera *cam = js2SDL_Camera(js,self);
if (!cam) return JS_ThrowReferenceError(js,"Self was not a camera: %s", SDL_GetError());
SDL_Surface *surf = SDL_AcquireCameraFrame(cam, NULL);
if (!surf) {
const char *msg = SDL_GetError();
if (msg[0] != 0)
return JS_ThrowReferenceError(js,"Could not get camera frame: %s", SDL_GetError());
else return JS_UNDEFINED;
}
return SDL_Surface2js(js,surf);
SDL_Surface *newsurf = SDL_CreateSurface(surf->w, surf->h, surf->format);
SDL_ReleaseCameraFrame(cam,surf);
int didit = SDL_BlitSurface(surf, NULL, newsurf, NULL);
if (!didit) {
SDL_DestroySurface(newsurf);
return JS_ThrowReferenceError(js, "Could not blit: %s", SDL_GetError());
}
return SDL_Surface2js(js,newsurf);
*/
/* SDL_Camera *cam = js2SDL_Camera(js,self);
SDL_Surface *surf = js2SDL_Surface(js,argv[0]);
SDL_ReleaseCameraFrame(cam,surf);
*/
)
static const JSCFunctionListEntry js_SDL_Camera_funcs[] =
{
MIST_FUNC_DEF(camera, capture, 0),
};
JSValue js_camera_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_camera_funcs,countof(js_camera_funcs));
// Initialize SDL camera class with functions
JSValue c_types = JS_GetPropertyStr(js, JS_GetGlobalObject(js), "c_types");
QJSCLASSPREP_FUNCS(SDL_Camera)
JS_FreeValue(js, c_types);
return mod;
}
// SDL AUDIO FUNCTIONS
// Audio format lookup table and conversion functions
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;i<countof(map);++i)
if (!strcmp(f,map[i].s)) { *out = map[i].f; return 1; }
return 0;
}
static const char *fmt2str(SDL_AudioFormat f)
{
for (size_t i = 0; i < countof(fmt_lut); ++i)
if (fmt_lut[i].f == f) return fmt_lut[i].s;
return "unknown";
}
static JSValue audiospec2js(JSContext *js, const SDL_AudioSpec *spec)
{
JSValue o = JS_NewObject(js);
/* stringify format (u8/s16/s32/f32) */
JS_SetPropertyStr(js, o, "format",
JS_NewString(js, fmt2str(spec->format)));
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)
{
SDL_AudioSpec spec;
JSValue v;
v = JS_GetPropertyStr(js, obj, "format");
if (!JS_IsUndefined(v)) {
const char *s = JS_ToCString(js, v);
format_str_to_enum(s, &spec.format);
JS_FreeCString(js, s);
}
JS_FreeValue(js, v);
v = JS_GetPropertyStr(js, obj, "channels");
if (!JS_IsUndefined(v)) JS_ToInt32(js, &spec.channels, v);
JS_FreeValue(js, v);
v = JS_GetPropertyStr(js, obj, "samplerate");
if (!JS_IsUndefined(v)) JS_ToInt32(js, &spec.freq, v);
JS_FreeValue(js, v);
return spec;
}
JSC_CCALL(sdl_audio_drivers,
int num = SDL_GetNumAudioDrivers();
JSValue arr = JS_NewArray(js);
for (int i = 0; i < num; i++)
JS_SetPropertyUint32(js, arr, i, JS_NewString(js, SDL_GetAudioDriver(i)));
return arr;
)
JSC_CCALL(sdl_audio_devices,
int n;
SDL_AudioDeviceID *ids = SDL_GetAudioPlaybackDevices(&n);
JSValue arr = JS_NewArray(js);
for (int i = 0; i < n; i++)
JS_SetPropertyUint32(js,arr,i,JS_NewString(js, SDL_GetAudioDeviceName(ids[i])));
return arr;
)
JSC_CCALL(sdl_audio_open_stream,
const char *type = JS_IsString(argv[0]) ? JS_ToCString(js, argv[0]) : NULL;
SDL_AudioDeviceID devid = !strcmp(type, "capture") ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING : SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK;
if (type)
JS_FreeCString(js, type);
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());
return SDL_AudioStream2js(js, st);
)
static const JSCFunctionListEntry js_sdl_audio_funcs[] = {
MIST_FUNC_DEF(sdl_audio, drivers, 0),
MIST_FUNC_DEF(sdl_audio, devices, 0),
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),
};
JSValue js_sdl_audio_use(JSContext *js) {
if (!SDL_Init(SDL_INIT_AUDIO))
return JS_ThrowInternalError(js, "Unable to initialize audio subsystem: %s", SDL_GetError());
QJSCLASSPREP_FUNCS(SDL_AudioStream)
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_sdl_audio_funcs,countof(js_sdl_audio_funcs));
return mod;
}