aboutsummaryrefslogtreecommitdiffstats
path: root/abletonlink.c
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2026-04-13 18:05:51 +0000
committers-ol <s+removethis@s-ol.nu>2026-04-13 18:05:51 +0000
commita98c602ceaafb67f479e2e917035b8ae443b042e (patch)
treefe0587afa529a14e676986bf96bc34f1cb266a48 /abletonlink.c
parentexamples/linkhut: fix phase display (diff)
downloadlua-abletonlink-a98c602ceaafb67f479e2e917035b8ae443b042e.tar.gz
lua-abletonlink-a98c602ceaafb67f479e2e917035b8ae443b042e.zip
implement callbacks
Diffstat (limited to 'abletonlink.c')
-rw-r--r--abletonlink.c236
1 files changed, 224 insertions, 12 deletions
diff --git a/abletonlink.c b/abletonlink.c
index 1e603fa..c4a280e 100644
--- a/abletonlink.c
+++ b/abletonlink.c
@@ -1,6 +1,7 @@
#include <lua.h>
#include <lauxlib.h>
#include "link/extensions/abl_link/include/abl_link.h"
+#include "tinycthread.h"
#if LUA_VERSION_NUM > 501
#define luax_len(L, i) (int) lua_rawlen(L, i)
@@ -11,12 +12,88 @@
#define LUA_RIDX_MAINTHREAD 1
#endif
+typedef struct {
+ enum {
+ EVENT_TYPE_NUM_PEERS,
+ EVENT_TYPE_TEMPO,
+ EVENT_TYPE_START_STOP,
+ } type;
+ union {
+ uint64_t num_peers;
+ double tempo;
+ bool start_stop;
+ };
+} event_t;
+
+#define NUM_EVENTS 64
+
+typedef struct {
+ event_t buffer[NUM_EVENTS];
+ event_t *head;
+ event_t *tail;
+ event_t *end;
+ bool full;
+} event_queue_t;
+
+void eq_init(event_queue_t *queue) {
+ queue->head = queue->buffer;
+ queue->tail = queue->buffer;
+ queue->end = queue->buffer + NUM_EVENTS;
+}
+
+static void eq_advance(event_queue_t *queue, event_t **ptr) {
+ (*ptr)++;
+ if (*ptr == queue->end) {
+ *ptr = queue->buffer;
+ }
+}
+
+void eq_push(event_queue_t *queue, event_t evt) {
+ if (queue->full) return;
+
+ *queue->head = evt;
+
+ eq_advance(queue, &queue->head);
+
+ if (queue->head == queue->tail) {
+ queue->full = true;
+ }
+}
+
+bool eq_pop(event_queue_t *queue, event_t *evt) {
+ if (!queue->full && queue->head == queue->tail) {
+ return false;
+ }
+
+ *evt = *(queue->tail);
+ eq_advance(queue, &queue->tail);
+
+ return true;
+}
+
+typedef struct {
+ abl_link link;
+
+ int cb_num_peers;
+ int cb_tempo;
+ int cb_start_stop;
+
+ mtx_t mutex;
+ event_queue_t queue;
+} link_state_t;
+
static int l_create_link(lua_State* L) {
double bpm = luaL_checknumber(L, 1);
- abl_link* link = (abl_link*)lua_newuserdata(L, sizeof(abl_link));
+ link_state_t* state = (link_state_t*)lua_newuserdata(L, sizeof(link_state_t));
- *link = abl_link_create(bpm);
+ state->link = abl_link_create(bpm);
+ state->cb_num_peers = LUA_NOREF;
+ state->cb_tempo = LUA_NOREF;
+ state->cb_start_stop = LUA_NOREF;
+
+ mtx_init(&state->mutex, mtx_plain);
+ eq_init(&state->queue);
luaL_getmetatable(L, "abletonlink.link");
lua_setmetatable(L, -2);
@@ -35,11 +112,16 @@ static int l_create_session_state(lua_State* L) {
return 1;
}
+static link_state_t *checklinkstate(lua_State *L) {
+ link_state_t* state = (link_state_t*)luaL_checkudata(L, 1, "abletonlink.link");
+ luaL_argcheck(L, state != NULL, 1, "`link' expected");
+ luaL_argcheck(L, state->link.impl != NULL, 1, "`link' is already destroyed");
+ return state;
+}
+
static abl_link checklink(lua_State *L) {
- abl_link* link = (abl_link*)luaL_checkudata(L, 1, "abletonlink.link");
- luaL_argcheck(L, link != NULL, 1, "`link' expected");
- luaL_argcheck(L, link->impl != NULL, 1, "`link' is already destroyed");
- return *link;
+ link_state_t* state = checklinkstate(L);
+ return state->link;
}
static abl_link_session_state checksessionstate(lua_State *L, int ud) {
@@ -50,12 +132,22 @@ static abl_link_session_state checksessionstate(lua_State *L, int ud) {
}
static int l_destroy_link(lua_State* L) {
- abl_link* link = (abl_link*)luaL_checkudata(L, 1, "abletonlink.link");
- luaL_argcheck(L, link != NULL, 1, "`link' expected");
- if (link->impl) {
- abl_link_destroy(*link);
- link->impl = NULL;
+ link_state_t* state = checklinkstate(L);
+
+ if (state->link.impl) {
+ abl_link_destroy(state->link);
+
+ luaL_unref(L, LUA_REGISTRYINDEX, state->cb_num_peers);
+ luaL_unref(L, LUA_REGISTRYINDEX, state->cb_tempo);
+ luaL_unref(L, LUA_REGISTRYINDEX, state->cb_start_stop);
+ state->cb_num_peers = LUA_NOREF;
+ state->cb_tempo = LUA_NOREF;
+ state->cb_start_stop = LUA_NOREF;
+
+ mtx_destroy(&state->mutex);
+ state->link.impl = NULL;
}
+
return 0;
}
@@ -125,10 +217,126 @@ static int l_capture_app_session_state(lua_State* L) {
static int l_commit_app_session_state(lua_State* L) {
abl_link link = checklink(L);
abl_link_session_state session_state = checksessionstate(L, 2);
- abl_link_commit_app_session_state(link, session_state);
+ abl_link_commit_app_session_state(link, session_state);
+ return 0;
+}
+
+static void cb_num_peers(uint64_t num_peers, void *context) {
+ link_state_t* state = (link_state_t *)context;
+ event_t evt;
+ evt.type = EVENT_TYPE_NUM_PEERS;
+ evt.num_peers = num_peers;
+ eq_push(&state->queue, evt);
+}
+static int l_set_num_peers_callback(lua_State* L) {
+ link_state_t* state = checklinkstate(L);
+
+ luaL_unref(L, LUA_REGISTRYINDEX, state->cb_num_peers);
+
+ if (lua_isnil(L, 2)) {
+ state->cb_num_peers = LUA_NOREF;
+ abl_link_set_num_peers_callback(state->link, NULL, NULL);
+ return 0;
+ }
+
+ luaL_checktype(L, 2, LUA_TFUNCTION);
+ lua_pushvalue(L, 2);
+ state->cb_num_peers = luaL_ref(L, LUA_REGISTRYINDEX);
+ abl_link_set_num_peers_callback(state->link, cb_num_peers, state);
return 0;
}
+static void cb_tempo(double tempo, void *context) {
+ link_state_t* state = (link_state_t *)context;
+ event_t evt;
+ evt.type = EVENT_TYPE_TEMPO;
+ evt.tempo = tempo;
+ eq_push(&state->queue, evt);
+}
+static int l_set_tempo_callback(lua_State* L) {
+ link_state_t* state = checklinkstate(L);
+
+ luaL_unref(L, LUA_REGISTRYINDEX, state->cb_tempo);
+
+ if (lua_isnil(L, 2)) {
+ state->cb_tempo = LUA_NOREF;
+ abl_link_set_tempo_callback(state->link, NULL, NULL);
+ return 0;
+ }
+
+ luaL_checktype(L, 2, LUA_TFUNCTION);
+ lua_pushvalue(L, 2);
+ state->cb_tempo = luaL_ref(L, LUA_REGISTRYINDEX);
+ abl_link_set_tempo_callback(state->link, cb_tempo, state);
+ return 0;
+}
+
+static void cb_start_stop(bool start_stop, void *context) {
+ link_state_t* state = (link_state_t *)context;
+ event_t evt;
+ evt.type = EVENT_TYPE_START_STOP;
+ evt.start_stop = start_stop;
+ eq_push(&state->queue, evt);
+}
+static int l_set_start_stop_callback(lua_State* L) {
+ link_state_t* state = checklinkstate(L);
+
+ luaL_unref(L, LUA_REGISTRYINDEX, state->cb_start_stop);
+
+ if (lua_isnil(L, 2)) {
+ state->cb_start_stop = LUA_NOREF;
+ abl_link_set_start_stop_callback(state->link, NULL, NULL);
+ return 0;
+ }
+
+ luaL_checktype(L, 2, LUA_TFUNCTION);
+ lua_pushvalue(L, 2);
+ state->cb_start_stop = luaL_ref(L, LUA_REGISTRYINDEX);
+ abl_link_set_start_stop_callback(state->link, cb_start_stop, state);
+ return 0;
+}
+
+static int l_pump_events(lua_State* L) {
+ link_state_t *state = checklinkstate(L);
+
+ bool was_full = state->queue.full;
+ state->queue.full = false;
+
+ event_t evt;
+ while (eq_pop(&state->queue, &evt)) {
+ mtx_lock(&state->mutex);
+
+ switch (evt.type) {
+ case EVENT_TYPE_NUM_PEERS:
+ if (state->cb_num_peers == LUA_NOREF) continue;
+
+ lua_rawgeti(L, LUA_REGISTRYINDEX, state->cb_num_peers);
+ lua_pushinteger(L, evt.num_peers);
+ break;
+
+ case EVENT_TYPE_TEMPO:
+ if (state->cb_tempo == LUA_NOREF) continue;
+
+ lua_rawgeti(L, LUA_REGISTRYINDEX, state->cb_tempo);
+ lua_pushnumber(L, evt.tempo);
+ break;
+
+ case EVENT_TYPE_START_STOP:
+ if (state->cb_start_stop == LUA_NOREF) continue;
+
+ lua_rawgeti(L, LUA_REGISTRYINDEX, state->cb_start_stop);
+ lua_pushinteger(L, evt.start_stop);
+ break;
+ }
+
+ mtx_unlock(&state->mutex);
+ lua_call(L, 1, 0);
+ }
+
+ lua_pushboolean(L, was_full);
+ return 1;
+}
+
static const luaL_Reg abl_link_r[] = {
{ "destroy", l_destroy_link },
{ "is_enabled", l_is_enabled},
@@ -141,6 +349,10 @@ static const luaL_Reg abl_link_r[] = {
{ "commit_audio_session_state", l_commit_audio_session_state },
{ "capture_app_session_state", l_capture_app_session_state },
{ "commit_app_session_state", l_commit_app_session_state },
+ { "set_num_peers_callback", l_set_num_peers_callback },
+ { "set_tempo_callback", l_set_tempo_callback },
+ { "set_start_stop_callback", l_set_start_stop_callback },
+ { "pump_events", l_pump_events },
{ "__gc", l_destroy_link },
{ NULL, NULL },
};