/* * 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 #include #include "mem.h" #include "port.h" #include "graph.h" #include "event.h" extern jack_client_t *client; enum port_type { AUDIO_INPUT, MIDI_INPUT, AUDIO_OUTPUT, MIDI_OUTPUT, }; struct port { enum port_type type; jack_port_t *jack_port; void *jack_buf; int fn; ml_channel *chan; struct port *next; }; struct port_descriptor { const char *const lua_typename; const char *const jack_typename; const unsigned long jack_flags; const char *const peer_jack_typename; const unsigned long peer_jack_flags; struct port *list; }; static struct port_descriptor descriptors[] = { [AUDIO_INPUT] = { .lua_typename = "audio input port", .jack_typename = JACK_DEFAULT_AUDIO_TYPE, .jack_flags = JackPortIsInput, .peer_jack_typename = JACK_DEFAULT_AUDIO_TYPE, .peer_jack_flags = JackPortIsOutput, }, [MIDI_INPUT] = { .lua_typename = "midi input port", .jack_typename = JACK_DEFAULT_MIDI_TYPE, .jack_flags = JackPortIsInput, .peer_jack_typename = JACK_DEFAULT_MIDI_TYPE, .peer_jack_flags = JackPortIsOutput, }, [AUDIO_OUTPUT] = { .lua_typename = "audio output port", .jack_typename = JACK_DEFAULT_AUDIO_TYPE, .jack_flags = JackPortIsOutput, .peer_jack_typename = JACK_DEFAULT_AUDIO_TYPE, .peer_jack_flags = JackPortIsInput, }, [MIDI_OUTPUT] = { .lua_typename = "midi output port", .jack_typename = JACK_DEFAULT_MIDI_TYPE, .jack_flags = JackPortIsOutput, .peer_jack_typename = JACK_DEFAULT_MIDI_TYPE, .peer_jack_flags = JackPortIsInput, }, }; static void proc(const ml_proc_args *args) { struct port *port = args->userp; switch (port->type) { case AUDIO_INPUT: memcpy(args->out[0], port->jack_buf, sizeof(*args->out[0])*args->nframes); port->jack_buf = args->nframes + (float*)port->jack_buf; break; case AUDIO_OUTPUT: memcpy(port->jack_buf, args->in[0], sizeof(*args->in[0])*args->nframes); port->jack_buf = args->nframes + (float*)port->jack_buf; break; case MIDI_INPUT: case MIDI_OUTPUT: assert(0); } } // TODO: Use mutil.h for this when moving to a module static ml_channel *get_channel(lua_State *s, int i) { ml_channel **c; if ((c = luaL_testudata(s,i,MELISMA_OUTPUT_CHANNEL_TYPENAME)) != NULL) { return *c; } lua_getfield(s,i,"__ml_out"); if (!lua_isfunction(s,-1)) { lua_pop(s,1); goto error; } lua_pushvalue(s,i); lua_call(s,1,1); if ((c = luaL_testudata(s,-1,MELISMA_OUTPUT_CHANNEL_TYPENAME)) != NULL) { lua_pop(s,1); return *c; } lua_pop(s,1); error: luaL_argerror(s,i,"expected output channel"); return NULL; } static int l_open(lua_State *s) { int type_i = lua_upvalueindex(1); enum port_type type = lua_tointeger(s,type_i); struct port_descriptor *pd = &descriptors[type]; const char *name = luaL_checkstring(s,1); ml_channel *src = NULL; if (type == AUDIO_OUTPUT) { src = get_channel(s,2); } jack_port_t *jack_port = jack_port_register(client,name,pd->jack_typename,pd->jack_flags,0); if (jack_port == NULL) { luaL_error(s,"failed to register port '%s'",name); } struct port *port = scalloc(1,sizeof(*port)); port->type = type; port->jack_port = jack_port; port->jack_buf = NULL; port->fn = LUA_NOREF; port->next = pd->list; pd->list = port; switch (type) { case AUDIO_INPUT: { ml_node *node = ml_create_node(proc,port); port->chan = ml_add_output(node); } break; break; case MIDI_INPUT: { } break; case AUDIO_OUTPUT: { ml_node *node = ml_create_node(proc,port); ml_channel *out = ml_add_output(node); ml_channel *in = ml_add_input(node); ml_connect(src,in); ml_connect(out,ml_add_input(ml_get_root_node())); } break; case MIDI_OUTPUT: { } break; } *(void**) lua_newuserdata(s,sizeof(void*)) = port; luaL_setmetatable(s,pd->lua_typename); return 1; } static int l_connect(lua_State *s) { int type_i = lua_upvalueindex(1); enum port_type type = lua_tointeger(s,type_i); struct port_descriptor *pd = &descriptors[type]; struct port *port = *(void**) luaL_checkudata(s,1,pd->lua_typename); const char *dst = luaL_checkstring(s,2); const char *src = jack_port_name(port->jack_port); if (type == AUDIO_INPUT || type == MIDI_INPUT) { const char *tmp = dst; dst = src; src = tmp; } if (jack_connect(client,src,dst) != 0) { luaL_error(s,"failed to connect ports '%s' and '%s'",src,dst); } return 0; } static int l_peers(lua_State *s) { int type_i = lua_upvalueindex(1); enum port_type type = lua_tointeger(s,type_i); struct port_descriptor *pd = &descriptors[type]; const char *pattern = luaL_optstring(s,1,""); const char *typename = pd->peer_jack_typename; unsigned long flags = pd->peer_jack_flags; const char **list = jack_get_ports(client,pattern,typename,flags); if (list == NULL) { luaL_error(s,"failed to list ports"); } lua_newtable(s); int tbl = lua_gettop(s); for (int i = 0; list[i] != NULL; i++) { const char *name = list[i]; jack_port_t *handle = jack_port_by_name(client,name); if (jack_port_is_mine(client,handle)) { continue; } lua_pushstring(s,list[i]); lua_rawseti(s,tbl,i + 1); } jack_free(list); lua_settop(s,tbl); return 1; } static int l_callback(lua_State *s) { struct port *port = *(void**) luaL_checkudata(s,1,descriptors[MIDI_INPUT].lua_typename); luaL_checktype(s,2,LUA_TFUNCTION); lua_pushvalue(s,2); port->fn = luaL_ref(s,LUA_REGISTRYINDEX); return 1; } static int l_out(lua_State *s) { struct port *port = *(void**) luaL_checkudata(s,1,descriptors[AUDIO_INPUT].lua_typename); *(void**) lua_newuserdata(s,sizeof(void*)) = port->chan; luaL_setmetatable(s,MELISMA_OUTPUT_CHANNEL_TYPENAME); return 1; } static int load_ports(lua_State *s) { lua_newtable(s); int root = lua_gettop(s); { /* AUDIO */ lua_newtable(s); int audio = lua_gettop(s); lua_pushinteger(s,AUDIO_OUTPUT); lua_pushcclosure(s,l_peers,1); lua_setfield(s,audio,"destinations"); lua_pushinteger(s,AUDIO_INPUT); lua_pushcclosure(s,l_peers,1); lua_setfield(s,audio,"sources"); /* AUDIO INPUT */ { lua_newtable(s); int audio_input = lua_gettop(s); // TODO: Use mutil.h lua_pushcfunction(s,l_out); lua_setfield(s,audio_input,"__ml_out"); luaL_newmetatable(s,descriptors[AUDIO_INPUT].lua_typename); int audio_input_mt = lua_gettop(s); lua_pushvalue(s,audio_input); lua_setfield(s,audio_input_mt,"__index"); lua_pushinteger(s,AUDIO_INPUT); lua_pushcclosure(s,l_open,1); lua_setfield(s,audio_input,"open"); lua_pushinteger(s,AUDIO_INPUT); lua_pushcclosure(s,l_connect,1); lua_setfield(s,audio_input,"connect"); lua_pushvalue(s,audio_input); lua_setfield(s,audio,"input"); } /* AUDIO OUTPUT */ { lua_newtable(s); int audio_output = lua_gettop(s); luaL_newmetatable(s,descriptors[AUDIO_OUTPUT].lua_typename); int audio_output_mt = lua_gettop(s); lua_pushvalue(s,audio_output); lua_setfield(s,audio_output_mt,"__index"); lua_pushinteger(s,AUDIO_OUTPUT); lua_pushcclosure(s,l_open,1); lua_setfield(s,audio_output,"open"); lua_pushinteger(s,AUDIO_OUTPUT); lua_pushcclosure(s,l_connect,1); lua_setfield(s,audio_output,"connect"); lua_pushvalue(s,audio_output); lua_setfield(s,audio,"output"); } lua_pushvalue(s,audio); lua_setfield(s,root,"audio"); } /* MIDI */ { lua_newtable(s); int midi = lua_gettop(s); lua_pushinteger(s,MIDI_OUTPUT); lua_pushcclosure(s,l_peers,1); lua_setfield(s,midi,"destinations"); lua_pushinteger(s,MIDI_INPUT); lua_pushcclosure(s,l_peers,1); lua_setfield(s,midi,"sources"); /* MIDI INPUT */ { lua_newtable(s); int midi_input = lua_gettop(s); luaL_newmetatable(s,descriptors[MIDI_INPUT].lua_typename); int midi_input_mt = lua_gettop(s); lua_pushvalue(s,midi_input); lua_setfield(s,midi_input_mt,"__index"); lua_pushinteger(s,MIDI_INPUT); lua_pushcclosure(s,l_open,1); lua_setfield(s,midi_input,"open"); lua_pushinteger(s,MIDI_INPUT); lua_pushcclosure(s,l_connect,1); lua_setfield(s,midi_input,"connect"); lua_pushinteger(s,MIDI_INPUT); lua_pushcclosure(s,l_callback,1); lua_setfield(s,midi_input,"callback"); lua_pushvalue(s,midi_input); lua_setfield(s,midi,"input"); } /* MIDI OUTPUT */ { lua_newtable(s); int midi_output = lua_gettop(s); luaL_newmetatable(s,descriptors[MIDI_OUTPUT].lua_typename); int midi_output_mt = lua_gettop(s); lua_pushvalue(s,midi_output); lua_setfield(s,midi_output_mt,"__index"); lua_pushinteger(s,MIDI_OUTPUT); lua_pushcclosure(s,l_open,1); lua_setfield(s,midi_output,"open"); lua_pushinteger(s,MIDI_OUTPUT); lua_pushcclosure(s,l_connect,1); lua_setfield(s,midi_output,"connect"); lua_pushvalue(s,midi_output); lua_setfield(s,midi,"output"); } lua_pushvalue(s,midi); lua_setfield(s,root,"midi"); } lua_settop(s,root); return 1; } static void set_jack_buffers(enum port_type type, size_t n) { struct port *port = descriptors[type].list; while (port) { port->jack_buf = jack_port_get_buffer(port->jack_port,n); port = port->next; } } static void schedule_midi_event(unsigned long t, int fn, int status, int data1, int data2) { extern lua_State *state; lua_newtable(state); int tbl = lua_absindex(state,lua_gettop(state)); lua_pushinteger(state,status); lua_setfield(state,tbl,"status"); lua_pushinteger(state,data1); lua_setfield(state,tbl,"data1"); lua_pushinteger(state,data2); lua_setfield(state,tbl,"data2"); int arg = luaL_ref(state,LUA_REGISTRYINDEX); ml_schedule_event(t,fn,arg); } static void flush_midi_events(void) { struct port *port = descriptors[MIDI_INPUT].list; for (; port; port = port->next) { if (port->fn == LUA_NOREF) { continue; } size_t n = jack_midi_get_event_count(port->jack_buf); for (size_t i = 0; i < n; i++) { jack_midi_event_t e; jack_midi_event_get(&e,port->jack_buf,i); schedule_midi_event(e.time,port->fn,e.buffer[0],e.buffer[1],e.buffer[2]); } } } static int process_callback(jack_nframes_t n, void *args) { set_jack_buffers(AUDIO_INPUT,n); set_jack_buffers(MIDI_INPUT,n); set_jack_buffers(AUDIO_OUTPUT,n); set_jack_buffers(MIDI_OUTPUT,n); unsigned long t0 = jack_last_frame_time(client); unsigned long t = 0; unsigned long dt = 0; ml_event_id id; flush_midi_events(); while ((id = next_event(n - t, &dt)) != NO_EVENT) { proc_graph(dt); dispatch_event(id); t += dt; t0 += dt; } proc_graph(dt); return 0; } static int xrun_callback(void *args) { fprintf(stderr,"XRUN\n"); return 0; } void init_ports(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_ports); lua_setfield(s,preload,"melisma.port"); lua_settop(s,top); jack_set_process_callback(client,process_callback,NULL); jack_set_xrun_callback(client,xrun_callback,NULL); }