diff options
| author | Ludvig Gunne Lindström <ludviggunnelindstrom@gmail.com> | 2026-07-17 11:42:57 +0200 |
|---|---|---|
| committer | Ludvig Gunne Lindström <ludviggunnelindstrom@gmail.com> | 2026-11-08 14:07:53 +0100 |
| commit | 98b3c66ebc79597f5b54fff132eaafbebf581248 (patch) | |
| tree | f999ec466ae3ac635653d1fe0a6a6dd1a2f2c3b0 /src | |
| download | melisma-main.tar.gz | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/event.c | 374 | ||||
| -rw-r--r-- | src/event.h | 32 | ||||
| -rw-r--r-- | src/graph.c | 466 | ||||
| -rw-r--r-- | src/graph.h | 32 | ||||
| -rw-r--r-- | src/io.c | 98 | ||||
| -rw-r--r-- | src/io.h | 26 | ||||
| -rw-r--r-- | src/main.c | 252 | ||||
| -rw-r--r-- | src/mem.h | 36 | ||||
| -rw-r--r-- | src/port.c | 546 | ||||
| -rw-r--r-- | src/port.h | 26 | ||||
| -rw-r--r-- | src/ring.c | 158 | ||||
| -rw-r--r-- | src/util.c | 33 | ||||
| -rw-r--r-- | src/util.h | 26 | ||||
| -rw-r--r-- | src/version.c | 62 |
14 files changed, 2167 insertions, 0 deletions
diff --git a/src/event.c b/src/event.c new file mode 100644 index 0000000..2d5a41f --- /dev/null +++ b/src/event.c @@ -0,0 +1,374 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#include <stdbool.h> + +#include <jack/jack.h> + +#include <lua.h> +#include <lauxlib.h> + +#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; + } +} diff --git a/src/event.h b/src/event.h new file mode 100644 index 0000000..dd80d80 --- /dev/null +++ b/src/event.h @@ -0,0 +1,32 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#ifndef EVENT_H_INCLUDED +#define EVENT_H_INCLUDED + +#include <lua.h> + +#include "melisma.h" + +#define NO_EVENT ((ml_event_id) -1) + +void init_events(lua_State *s); +ml_event_id next_event(unsigned long tmax, unsigned long *t); +void dispatch_event(ml_event_id id); + +#endif diff --git a/src/graph.c b/src/graph.c new file mode 100644 index 0000000..0e1a41e --- /dev/null +++ b/src/graph.c @@ -0,0 +1,466 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <stdbool.h> + +#include <lua.h> +#include <lauxlib.h> + +#include "melisma.h" +#include "graph.h" +#include "mem.h" + +#ifndef BUFFER_SIZE +#define BUFFER_SIZE 4096 +#endif + +struct ml_channel +{ + ml_node *node; + enum {IN,OUT} dir; + ml_channel *src; + ml_channel *next; + size_t buf; + int refs; +}; + +struct ml_node +{ + ml_proc_fn proc; + void *userp; + ml_channel *chans[2]; + ml_node *next; + bool visited; + bool cycle; + char *label; +}; + +struct chunk +{ + char *label; + ml_proc_fn proc; + ml_proc_args args; + struct chunk *next; + float *data[]; +}; + +struct free_list +{ + size_t buf; + struct free_list *next; +}; + +static ml_node *root; +static ml_node *active; +static ml_node *active_tail; +static struct free_list *free_list; +static size_t counter; +static float *buffers; +static struct chunk *graph; + +ml_node *ml_create_node(ml_proc_fn proc, void *userp) +{ + ml_node *node = scalloc(1,sizeof(*node)); + node->proc = proc; + node->userp = userp; + return node; +} + +void ml_label_node(ml_node *node, const char *fmt, ...) +{ + va_list ap_alloc,ap_print; + + va_start(ap_alloc,fmt); + va_copy(ap_print,ap_alloc); + + size_t len = 1 + vsnprintf(NULL,0,fmt,ap_alloc); + va_end(ap_alloc); + + char *buf = smalloc(len); + vsnprintf(buf,len,fmt,ap_print); + + node->label = buf; +} + +void *ml_get_userp(ml_node *node) +{ + return node->userp; +} + +ml_channel *add_channel(ml_node *node, int dir) +{ + ml_channel *chan = scalloc(1,sizeof(*chan)); + chan->dir = dir; + chan->node = node; + + if (node->chans[dir] == NULL) + { + node->chans[dir] = chan; + } + else + { + ml_channel *insert = node->chans[dir]; + while (insert->next) + { + insert = insert->next; + } + insert->next = chan; + } + + return chan; +} + +ml_channel *ml_add_input(ml_node *node) +{ + return add_channel(node,IN); +} + +ml_channel *ml_add_output(ml_node *node) +{ + return add_channel(node,OUT); +} + +static ml_channel *get_channel(ml_node *node, size_t i, int dir) +{ + ml_channel *chan = node->chans[dir]; + + while (i > 0) + { + if (chan == NULL) + { + break; + } + + i--; + chan = chan->next; + } + + return chan; +} + +ml_channel *ml_get_input(ml_node *node, size_t i) +{ + return get_channel(node,i,IN); +} + +ml_channel *ml_get_output(ml_node *node, size_t i) +{ + return get_channel(node,i,OUT); +} + +static size_t num_channels(ml_node *node, int dir) +{ + ml_channel *chan = node->chans[dir]; + size_t n = 0; + + while (chan != NULL) + { + n++; + chan = chan->next; + } + + return n; +} + +size_t ml_num_inputs(ml_node *node) +{ + return num_channels(node,IN); +} + +size_t ml_num_outputs(ml_node *node) +{ + return num_channels(node,OUT); +} + +void ml_connect(ml_channel *src, ml_channel *dst) +{ + assert(src->dir == OUT); + assert(dst->dir == IN); + src->refs++; + dst->src = src; +} + +ml_node *ml_get_root_node(void) +{ + return root; +} + +void init_graph(lua_State *s) +{ + root = ml_create_node(NULL,NULL); + ml_label_node(root,"root"); + + luaL_newmetatable(s,MELISMA_NODE_TYPENAME); + luaL_newmetatable(s,MELISMA_INPUT_CHANNEL_TYPENAME); + luaL_newmetatable(s,MELISMA_OUTPUT_CHANNEL_TYPENAME); +} + +static const char *topo_sort(ml_node *node) +{ + if (node->visited) + { + return NULL; + } + + if (node->cycle) + { + return "cycle in audio graph detected"; + } + + node->visited = true; + node->cycle = true; + + ml_channel *in = node->chans[IN]; + + while (in != NULL) + { + if (in->src == NULL) + { + return "disconnected input channel in audio graph detected"; + } + + const char *err = topo_sort(in->src->node); + + if (err != NULL) + { + return err; + } + + in = in->next; + } + + if (active_tail) + { + active_tail->next = node; + } + else + { + active = node; + } + + active_tail = node; + node->cycle = false; + + return NULL; +} + +static size_t alloc_buf(void) +{ + if (free_list) + { + struct free_list *head = free_list; + size_t buf = head->buf; + free_list = head->next; + free(head); + return buf; + } + + return counter++; +} + +static void free_buf(size_t buf) +{ + struct free_list *head = scalloc(1,sizeof(*head)); + head->buf = buf; + head->next = free_list; + free_list = head; +} + +static void assign_bufs() +{ + ml_node *node = active; + + while (node) + { + ml_channel *in, *out; + in = node->chans[IN]; + out = node->chans[OUT]; + + while (out) + { + out->buf = alloc_buf(); + out = out->next; + } + + while (in) + { + if (--in->src->refs == 0) + { + free_buf(in->src->buf); + } + in = in->next; + } + + node = node->next; + } +} + +static void make_chunks(void) +{ + struct chunk *chunk, *tail = NULL; + struct ml_node *node = active; + + while (node) + { + size_t icnt = ml_num_inputs(node); + size_t ocnt = ml_num_outputs(node); + size_t nchans = icnt + ocnt; + size_t size = sizeof(*chunk) + sizeof(chunk->data[0]) * nchans; + + chunk = scalloc(1,size); + chunk->label = node->label ? strdup(node->label) : NULL; + chunk->proc = node->proc; + chunk->args.userp = node->userp; + chunk->args.icnt = icnt; + chunk->args.ocnt = ocnt; + chunk->args.in = chunk->data; + chunk->args.out = chunk->data + icnt; + + ml_channel *in, *out; + size_t i = 0; + + in = node->chans[IN]; + + while (in != NULL) + { + chunk->data[i++] = &buffers[BUFFER_SIZE*in->src->buf]; + in = in->next; + } + + out = node->chans[OUT]; + + while (out != NULL) + { + chunk->data[i++] = &buffers[BUFFER_SIZE*out->buf]; + out = out->next; + } + + assert(i == nchans); + + if (tail == NULL) + { + graph = chunk; + } + else + { + tail->next = chunk; + } + + tail = chunk; + node = node->next; + } +} + +const char *build_graph(void) +{ + const char *err = topo_sort(root); + + if (err != NULL) + { + return err; + } + + assign_bufs(); + + buffers = malloc(sizeof(*buffers)*BUFFER_SIZE*counter); + + make_chunks(); + + return NULL; +} + +static void proc_once(size_t n) +{ + struct chunk *c = graph; + while (c) + { + if (c->proc) + { + c->args.nframes = n; + c->proc(&c->args); + } + + c = c->next; + } +} + +void proc_graph(size_t n) +{ + while (n > 0) + { + size_t m = n > BUFFER_SIZE ? BUFFER_SIZE : n; + proc_once(m); + n -= m; + } +} + +static void add_to_layout(lua_State *s, int layout, ml_node *node) +{ + int top = lua_absindex(s,lua_gettop(s)); + + lua_pushlightuserdata(s,node); + lua_gettable(s,layout); + + if (!lua_isnil(s,-1)) + { + return; + } + + lua_newtable(s); + int tbl = lua_absindex(s,lua_gettop(s)); + + if (node->label) + { + lua_pushstring(s,node->label); + lua_setfield(s,tbl,"label"); + } + + lua_newtable(s); + int list = lua_absindex(s,lua_gettop(s)); + ml_channel *in = node->chans[IN]; + int i = 1; + + while (in) + { + add_to_layout(s,layout,in->src->node); + lua_pushlightuserdata(s,in->src->node); + lua_rawseti(s,list,i++); + in = in->next; + } + + lua_pushvalue(s,list); + lua_setfield(s,tbl,"inputs"); + + lua_pushlightuserdata(s,node); + lua_pushvalue(s,tbl); + lua_settable(s,layout); + + lua_settop(s,top); +} + +int l_graph_layout(lua_State *s) +{ + lua_newtable(s); + int layout = lua_absindex(s,lua_gettop(s)); + add_to_layout(s,layout,root); + lua_pushvalue(s,layout); + return 1; +} diff --git a/src/graph.h b/src/graph.h new file mode 100644 index 0000000..d0f8c28 --- /dev/null +++ b/src/graph.h @@ -0,0 +1,32 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#ifndef GRAPH_H_INCLUDED +#define GRAPH_H_INCLUDED + +#include "melisma.h" + +#include <lua.h> + +void init_graph(lua_State *s); +const char *build_graph(void); +void proc_graph(size_t n); + +int l_graph_layout(lua_State *s); + +#endif diff --git a/src/io.c b/src/io.c new file mode 100644 index 0000000..3f60279 --- /dev/null +++ b/src/io.c @@ -0,0 +1,98 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#include <stdbool.h> + +#include "io.h" +#include "mem.h" + +struct io +{ + ml_io_handler hdl; + void *args; + bool removed; +}; + +static struct io *ios; +static struct pollfd *pollfds; +static size_t num_fds; + +void ml_add_io_handler(int fd, int flags, ml_io_handler hdl, void *args) +{ + size_t i = num_fds++; + ios = srealloc(ios,sizeof(*ios)*num_fds); + pollfds = srealloc(pollfds,sizeof(*pollfds)*num_fds); + + ios[i] = (struct io) { + .hdl = hdl, + .args = args, + }; + + pollfds[i] = (struct pollfd) { + .fd = fd, + .events = flags, + }; +} + +void ml_remove_io_handler(int fd) +{ + for (size_t i = 0; i < num_fds; i++) + { + if (pollfds[i].fd == fd) + { + ios[i].removed = true; + return; + } + } +} + +static bool remove_fds(void) +{ + for (size_t i = 0; i < num_fds; i++) + { + if (ios[i].removed) + { + num_fds--; + ios[i] = ios[num_fds]; + pollfds[i] = pollfds[num_fds]; + return true; + } + } + + return false; +} + +void io_loop(void) +{ + while (poll(pollfds,num_fds,-1) >= 0) + { + for (size_t i = 0; i < num_fds; i++) + { + struct pollfd *p = &pollfds[i]; + struct io *io = &ios[i]; + + if ((p->revents & p->events) && io->hdl) + { + io->hdl(p->fd,p->revents,io->args); + } + } + + while (remove_fds()) + ; + } +} diff --git a/src/io.h b/src/io.h new file mode 100644 index 0000000..22fa8df --- /dev/null +++ b/src/io.h @@ -0,0 +1,26 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#ifndef IO_H_INCLUDED +#define IO_H_INCLUDED + +#include "melisma.h" + +void io_loop(void); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..b1224a0 --- /dev/null +++ b/src/main.c @@ -0,0 +1,252 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <getopt.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> + +#include <lua.h> +#include <lualib.h> +#include <lauxlib.h> + +#include <jack/jack.h> + +#include "melisma.h" +#include "graph.h" +#include "util.h" +#include "event.h" +#include "port.h" +#include "mem.h" +#include "io.h" + +jack_client_t *client; +lua_State *state; + +static const char *program = NULL; +static const char *path = NULL; +static const char *client_name = "melisma"; +static char **script_argv = NULL; + +static void usage(FILE *f) +{ + fprintf(f,"Usage: %s [OPTION...] [FILE] [ARG...]\n",program); + fprintf(f,"Options:\n"); + fprintf(f,"\t-h Show this help message.\n"); + fprintf(f,"\t-c NAME Set Jack client name to NAME.\n"); + fprintf(f,"\t-C PATH Change working directory to PATH.\n"); +} + +static void change_cwd(const char *path) +{ + if (chdir(path) < 0) + { + fprintf(stderr,"%s: %s: %s\n", + program,path,strerror(errno)); + exit(EXIT_FAILURE); + } +} + +static void parse_args(int argc, char **argv) +{ + program = argv[0]; + + char c; + while ((c = getopt(argc, argv, "hc:C:")) != -1) + { + switch (c) + { + case 'h': + usage(stdout); + exit(EXIT_SUCCESS); + case 'c': + client_name = optarg; + break; + case 'C': + change_cwd(optarg); + break; + default: + usage(stderr); + exit(EXIT_FAILURE); + } + } + + if (optind < argc && strcmp(argv[optind],"-") != 0) + { + path = argv[optind]; + } + + script_argv = &argv[optind + 1]; +} + +static int l_sample_rate(lua_State *s) +{ + lua_pushinteger(s,ml_sample_rate()); + return 1; +} + +static int load_internal_module(lua_State *s) +{ + lua_newtable(s); + int tbl = lua_absindex(s,lua_gettop(s)); + + lua_pushcfunction(s,l_graph_layout); + lua_setfield(s,tbl,"layout"); + lua_pushcfunction(s,l_sample_rate); + lua_setfield(s,tbl,"samplerate"); + + lua_newtable(s); + int argv_tbl = lua_gettop(s); + + int i = 1; + for (; *script_argv; script_argv++) + { + lua_pushstring(s,*script_argv); + lua_rawseti(s,argv_tbl,i++); + } + + lua_settop(s,argv_tbl); + lua_setfield(s,tbl,"argv"); + + lua_settop(s,tbl); + return 1; +} + +static void register_internal_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_internal_module); + lua_setfield(s,preload,"melisma.internal"); + + lua_settop(s,top); +} + +static char *read_stdin(void) +{ + size_t len = 0; + size_t cap = 0; + char *buf = NULL; + + for (;;) + { + char c = fgetc(stdin); + + if (len == cap) + { + cap = cap ? 2 * cap : 1024; + buf = srealloc(buf, cap); + } + + buf[len++] = c == EOF ? 0 : c; + + if (c == EOF) + { + break; + } + } + + return buf; +} + +int main(int argc, char **argv) +{ + int exitc = EXIT_SUCCESS; + + parse_args(argc,argv); + + client = jack_client_open(client_name,0,NULL); + + if (client == NULL) + { + fprintf(stderr,"Failed to open Jack client '%s'\n",client_name); + exit(EXIT_FAILURE); + } + + state = luaL_newstate(); + luaL_openlibs(state); + register_internal_module(state); + + init_graph(state); + init_events(state); + init_ports(state); + + char *text = NULL; + if (path == NULL) + { + text = read_stdin(); + if (luaL_loadstring(state,text) != LUA_OK) + { + const char *err = lua_tostring(state,-1); + fprintf(stderr,"%s\n",err); + exitc = EXIT_FAILURE; + goto cleanup; + } + } + else + { + if (luaL_loadfile(state,path) != LUA_OK) + { + const char *err = lua_tostring(state,-1); + fprintf(stderr,"%s\n",err); + exitc = EXIT_FAILURE; + goto cleanup; + } + } + + if (lua_pcall_with_backtrace(state,0,LUA_MULTRET) != LUA_OK) + { + const char *err = lua_tostring(state,-1); + fprintf(stderr,"%s\n",err); + exitc = EXIT_FAILURE; + goto cleanup; + } + + const char *err = build_graph(); + + if (err != NULL) + { + fprintf(stderr,"Error: %s\n",err); + exitc = EXIT_FAILURE; + goto cleanup; + } + + jack_activate(client); + + ml_add_io_handler(STDIN_FILENO,POLLIN,NULL,NULL); + io_loop(); + +cleanup: + free(text); + lua_close(state); + jack_client_close(client); + exit(exitc); +} + +unsigned long ml_sample_rate(void) +{ + return jack_get_sample_rate(client); +} diff --git a/src/mem.h b/src/mem.h new file mode 100644 index 0000000..0269213 --- /dev/null +++ b/src/mem.h @@ -0,0 +1,36 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#ifndef MEM_H_INCLUDED +#define MEM_H_INCLUDED + +#include <assert.h> +#include <stdlib.h> + +#define safe(func, ...)\ + ({\ + void *ptr = func(__VA_ARGS__);\ + assert(ptr && #func " failed");\ + ptr;\ + }) + +#define smalloc(...) safe(malloc, __VA_ARGS__) +#define scalloc(...) safe(calloc, __VA_ARGS__) +#define srealloc(...) safe(realloc, __VA_ARGS__) + +#endif 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 <https://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <assert.h> + +#include <lua.h> +#include <lauxlib.h> + +#include <jack/jack.h> +#include <jack/midiport.h> + +#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); +} diff --git a/src/port.h b/src/port.h new file mode 100644 index 0000000..7885d5e --- /dev/null +++ b/src/port.h @@ -0,0 +1,26 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#ifndef PORT_H +#define PORT_H + +#include <lua.h> + +void init_ports(lua_State *s); + +#endif diff --git a/src/ring.c b/src/ring.c new file mode 100644 index 0000000..0bb1553 --- /dev/null +++ b/src/ring.c @@ -0,0 +1,158 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#include "mem.h" +#include "melisma/ring.h" + +struct ml_ring +{ + _Atomic size_t head; + _Atomic size_t tail; + size_t len; + uint8_t data[]; +}; + + +ml_ring *ml_ring_init(size_t len) +{ + size_t size = sizeof(ml_ring) + len; + ml_ring *r = scalloc(1,size); + r->len = len; + return r; +} + +void ml_ring_free(ml_ring *r) +{ + free(r); +} + +int ml_ring_empty(ml_ring *r) +{ + size_t head = atomic_load_explicit(&r->head,memory_order_relaxed); + size_t tail = atomic_load_explicit(&r->tail,memory_order_acquire); + return tail == head; +} + +int ml_ring_has_space(ml_ring *r, size_t len) +{ + size_t head = atomic_load_explicit(&r->head,memory_order_acquire); + size_t tail = atomic_load_explicit(&r->tail,memory_order_relaxed); + size_t used = tail >= head ? tail - head : r->len - head + tail; + size_t free = r->len - 1 - used; + return free >= len; +} + +int ml_ring_put(ml_ring *r, uint8_t c) +{ + size_t tail = atomic_load_explicit(&r->tail,memory_order_relaxed); + size_t head = atomic_load_explicit(&r->head,memory_order_acquire); + size_t new_tail = (tail + 1) % r->len; + + if (new_tail == head) + { + return -1; + } + + r->data[tail] = c; + atomic_store_explicit(&r->tail,new_tail,memory_order_release); + + return 0; +} + +int ml_ring_get(ml_ring *r, uint8_t *c) +{ + size_t tail = atomic_load_explicit(&r->tail,memory_order_acquire); + size_t head = atomic_load_explicit(&r->head,memory_order_relaxed); + size_t new_head = (head + 1) % r->len; + + if (head == tail) + { + return -1; + } + + *c = r->data[head]; + atomic_store_explicit(&r->head,new_head,memory_order_release); + + return 0; +} + +size_t ml_ring_write(ml_ring *r, const void *ptr, size_t len) +{ + size_t l = 0; + const uint8_t *uptr = ptr; + + for (; l < len; l++) + { + if (ml_ring_put(r,uptr[l]) < 0) + { + break; + } + } + + return l; +} + +size_t ml_ring_read(ml_ring *r, void *ptr, size_t len) +{ + size_t l = 0; + uint8_t *uptr = ptr; + + for (; l < len; l++) + { + if (ml_ring_get(r,&uptr[l]) < 0) + { + break; + } + } + + return l; +} + +void ml_ring_put_blocking(ml_ring *r, uint8_t c) +{ + while (ml_ring_put(r,c) < 0) + ; +} + +uint8_t ml_ring_get_blocking(ml_ring *r) +{ + uint8_t c; + while (ml_ring_get(r,&c) < 0) + ; + return c; +} + +void ml_ring_write_blocking(ml_ring *r, const void *ptr, size_t len) +{ + const uint8_t *uptr = ptr; + + while (len--) + { + ml_ring_put_blocking(r,*(uptr++)); + } +} + +void ml_ring_read_blocking(ml_ring *r, void *ptr, size_t len) +{ + uint8_t *uptr = ptr; + + while (len--) + { + *(uptr++) = ml_ring_get_blocking(r); + } +} diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..1b25ae5 --- /dev/null +++ b/src/util.c @@ -0,0 +1,33 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <lauxlib.h> +#include "util.h" + +int lua_pcall_with_backtrace(lua_State *s, int n, int r) +{ + int f = lua_gettop(s) - n; + lua_getglobal(s,"debug"); + lua_getfield(s,-1,"traceback"); + lua_remove(s,-2); + lua_insert(s,f); + int res = lua_pcall(s,n,r,f); + lua_remove(s,f); + return res; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..4851f07 --- /dev/null +++ b/src/util.h @@ -0,0 +1,26 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#ifndef UTIL_H_INCLUDED +#define UTIL_H_INCLUDED + +#include <lua.h> + +int lua_pcall_with_backtrace(lua_State *s, int n, int r); + +#endif diff --git a/src/version.c b/src/version.c new file mode 100644 index 0000000..2213bc6 --- /dev/null +++ b/src/version.c @@ -0,0 +1,62 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include "melisma.h" +#include <lauxlib.h> + +struct semver +{ + int major; + int minor; + int patch; +}; + +static void parse(struct semver *out, const char *str) +{ + const char *tok; + char buf[32]; + + strcpy(buf, str); + + assert((tok = strtok(buf, "."))); + out->major = atoi(tok); + + assert((tok = strtok(NULL, "."))); + out->minor = atoi(tok); + + assert((tok = strtok(NULL, "."))); + out->patch = atoi(tok); +} + +void ml__check_version(const char *s) +{ + extern lua_State *state; + struct semver melisma, module; + + parse(&melisma,MELISMA_VERSION); + parse(&module,s); + + if (melisma.major != module.major) + { + luaL_error(state,"Mismatching versions (melisma: %s, module: %s)", + MELISMA_VERSION,s); + } +} |
