From 98b3c66ebc79597f5b54fff132eaafbebf581248 Mon Sep 17 00:00:00 2001 From: Ludvig Gunne Lindström Date: Fri, 17 Jul 2026 11:42:57 +0200 Subject: init --- src/port.c | 546 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 546 insertions(+) create mode 100644 src/port.c (limited to 'src/port.c') diff --git a/src/port.c b/src/port.c new file mode 100644 index 0000000..fb487e3 --- /dev/null +++ b/src/port.c @@ -0,0 +1,546 @@ +/* + * 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); +} -- cgit v1.3.1