/* * melisma - extensible and scriptable audio engine * Copyright (C) 2026 Ludvig Gunne Lindström * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include "mem.h" #include "event.h" #include "util.h" #include "melisma.h" #define WHEEL_SIZE 4096 #define MAX_EVENTS 2048 #define MAINTHREAD_EVENT_CAPACITY (1<<17) #define EVENT_HANDLE_TYPENAME "event handle" struct event { ml_event_id id; unsigned long seq; unsigned long rounds; int fn; int arg; ml_event_id next_sched; ml_event_id next_free; bool cancel; }; extern jack_client_t *client; static struct event *pool; static size_t pool_len; static size_t pool_cap; static size_t num_events; static size_t counter; static ml_event_id free_head = NO_EVENT; static ml_event_id wheel[WHEEL_SIZE]; static size_t cursor; static int fn_cache_ref; static void init_event(ml_event_id id) { struct event *e = &pool[id]; e->seq = counter++; e->next_free = NO_EVENT; e->next_sched = NO_EVENT; e->cancel = false; } static ml_event_id alloc_event(void) { ml_event_id id; if (free_head != NO_EVENT) { id = free_head; free_head = pool[id].next_free; goto done; } if (pool_len == pool_cap) { pool_cap = pool_cap == 0 ? 64 : pool_cap * 2; pool = srealloc(pool,sizeof(*pool)*pool_cap); } id = pool_len++; done: init_event(id); num_events++; return id; } static void free_event(ml_event_id id) { pool[id].seq = (unsigned long) -1; if (id == pool_len - 1) { pool_len--; } else { pool[id].next_free = free_head; free_head = id; } num_events--; } static void schedule(ml_event_id id, unsigned long t) { if (num_events == MAX_EVENTS) { fprintf(stderr,"WARNING: event dropped\n"); return; } unsigned long rounds = t / WHEEL_SIZE; unsigned long index = (t+cursor) % WHEEL_SIZE; ml_event_id next = wheel[index]; ml_event_id prev = NO_EVENT; while (next != NO_EVENT && pool[next].rounds <= rounds) { prev = next; next = pool[next].next_sched; } if (prev == NO_EVENT) { wheel[index] = id; } else { pool[prev].next_sched = id; } pool[id].rounds = rounds; pool[id].next_sched = next; } static bool cancel(long id, long seq) { extern lua_State *state; struct event *e = &pool[id]; if (e->seq == seq) { luaL_unref(state,LUA_REGISTRYINDEX,e->arg); e->arg = LUA_NOREF; e->cancel = true; return true; } return false; } ml_event_handle ml_schedule_event(unsigned long t, int fn, int arg) { ml_event_id id = alloc_event(); struct event *e = &pool[id]; e->fn = fn; e->arg = arg; schedule(id,t); ml_event_handle hdl = { .id = id, .seq = e->seq, }; return hdl; } void ml_cancel_event(const ml_event_handle *hdl) { cancel(hdl->id,hdl->seq); } ml_event_id next_event(unsigned long tmax, unsigned long *t) { *t = 0; while (true) { if (*t == tmax) { return NO_EVENT; } if (cursor == WHEEL_SIZE) { cursor = 0; } ml_event_id id = wheel[cursor]; if (id == NO_EVENT) { cursor++; (*t)++; continue; } if (pool[id].rounds > 0) { while (id != NO_EVENT) { pool[id].rounds--; id = pool[id].next_sched; } cursor++; (*t)++; continue; } wheel[cursor] = pool[id].next_sched; if (pool[id].cancel) { free_event(id); continue; } return id; } } void dispatch_event(ml_event_id id) { struct event *e = &pool[id]; int fn = e->fn; int arg = e->arg; extern lua_State *state; lua_rawgeti(state,LUA_REGISTRYINDEX,fn); lua_rawgeti(state,LUA_REGISTRYINDEX,arg); if (lua_pcall_with_backtrace(state,1,0) != LUA_OK) { const char *err = luaL_checkstring(state,-1); fprintf(stderr,"%s\n",err); lua_pop(state,1); } luaL_unref(state,LUA_REGISTRYINDEX,pool[id].arg); free_event(id); } static int make_fn_ref(lua_State *s, int fn) { int ref; int top = lua_gettop(s); lua_pushvalue(s,fn); fn = lua_gettop(s); lua_rawgeti(s,LUA_REGISTRYINDEX,fn_cache_ref); int fn_cache = lua_gettop(s); lua_pushvalue(s,fn); lua_gettable(s,fn_cache); if (lua_isinteger(s,-1)) { ref = lua_tointeger(s,-1); goto done; } lua_pushvalue(s,fn); ref = luaL_ref(s,LUA_REGISTRYINDEX); lua_pushvalue(s,fn); lua_pushinteger(s,ref); lua_settable(s,fn_cache); done: lua_settop(s,top); return ref; } static int l_schedule(lua_State *s) { float real_t = luaL_checknumber(s,1); luaL_checktype(s,2,LUA_TFUNCTION); int fn = make_fn_ref(s,2); lua_pushvalue(s,3); int arg = luaL_ref(s,LUA_REGISTRYINDEX); unsigned long sr = ml_sample_rate(); unsigned long t = sr * real_t; ml_event_id id = alloc_event(); struct event *e = &pool[id]; e->fn = fn; e->arg = arg; schedule(id,t); ml_event_handle *h = lua_newuserdata(s,sizeof *h); luaL_setmetatable(s,EVENT_HANDLE_TYPENAME); h->id = id; h->seq = e->seq; return 1; } static int l_cancel(lua_State *s) { ml_event_handle *h = luaL_checkudata(s,1,EVENT_HANDLE_TYPENAME); lua_pushboolean(s,cancel(h->id,h->seq)); return 1; } static int load_events(lua_State *s) { luaL_newmetatable(s,EVENT_HANDLE_TYPENAME); lua_newtable(s); int tbl = lua_gettop(s); lua_pushcfunction(s,l_schedule); lua_setfield(s,tbl,"schedule"); lua_pushcfunction(s,l_cancel); lua_setfield(s,tbl,"cancel"); lua_rawgeti(s,LUA_REGISTRYINDEX,fn_cache_ref); lua_setfield(s,tbl,"function_cache"); lua_settop(s,tbl); return 1; } static void create_module(lua_State *s) { int top = lua_gettop(s); lua_getglobal(s,"package"); int package = lua_gettop(s); lua_getfield(s,package,"preload"); int preload = lua_gettop(s); lua_pushcfunction(s,load_events); lua_setfield(s,preload,"melisma.event"); lua_settop(s,top); } static void create_fn_cache(lua_State *s) { lua_newtable(s); fn_cache_ref = luaL_ref(s,LUA_REGISTRYINDEX); } void init_events(lua_State *s) { create_fn_cache(s); create_module(s); for (size_t i = 0; i < WHEEL_SIZE; i++) { wheel[i] = NO_EVENT; } }