/*
* 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);
}