#include #include #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) #define luax_register(L, f) luaL_setfuncs(L, f, 0) #else #define luax_len(L, i) (int) lua_objlen(L, i) #define luax_register(L, f) luaL_register(L, NULL, f) #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); link_state_t* state = (link_state_t*)lua_newuserdata(L, sizeof(link_state_t)); 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); return 1; } static int l_create_session_state(lua_State* L) { abl_link_session_state* session_state = (abl_link_session_state*)lua_newuserdata(L, sizeof(abl_link_session_state)); *session_state = abl_link_create_session_state(); luaL_getmetatable(L, "abletonlink.session_state"); lua_setmetatable(L, -2); 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) { link_state_t* state = checklinkstate(L); return state->link; } static abl_link_session_state checksessionstate(lua_State *L, int ud) { abl_link_session_state* session_state = (abl_link_session_state*)luaL_checkudata(L, ud, "abletonlink.session_state"); luaL_argcheck(L, session_state != NULL, 1, "`session_state' expected"); luaL_argcheck(L, session_state->impl != NULL, 1, "`session_state' is already destroyed"); return *session_state; } static int l_destroy_link(lua_State* L) { 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; } static int l_is_enabled(lua_State* L) { abl_link link = checklink(L); bool enabled = abl_link_is_enabled(link); lua_pushboolean(L, enabled); return 1; } static int l_enable(lua_State* L) { abl_link link = checklink(L); bool enable = lua_isnoneornil(L, 2) ? true : lua_toboolean(L, 2); abl_link_enable(link, enable); return 0; } static int l_is_start_stop_sync_enabled(lua_State* L) { abl_link link = checklink(L); bool enabled = abl_link_is_start_stop_sync_enabled(link); lua_pushboolean(L, enabled); return 1; } static int l_enable_start_stop_sync(lua_State* L) { abl_link link = checklink(L); bool enable = lua_isnoneornil(L, 2) ? true : lua_toboolean(L, 2); abl_link_enable_start_stop_sync(link, enable); return 0; } static int l_num_peers(lua_State* L) { abl_link link = checklink(L); uint64_t peers = abl_link_num_peers(link); lua_pushinteger(L, peers); return 1; } static int l_clock_micros(lua_State* L) { abl_link link = checklink(L); int64_t micros = abl_link_clock_micros(link); lua_pushinteger(L, micros); return 1; } static int l_capture_audio_session_state(lua_State* L) { abl_link link = checklink(L); abl_link_session_state session_state = checksessionstate(L, 2); abl_link_capture_audio_session_state(link, session_state); return 0; } static int l_commit_audio_session_state(lua_State* L) { abl_link link = checklink(L); abl_link_session_state session_state = checksessionstate(L, 2); abl_link_commit_audio_session_state(link, session_state); return 0; } static int l_capture_app_session_state(lua_State* L) { abl_link link = checklink(L); abl_link_session_state session_state = checksessionstate(L, 2); abl_link_capture_app_session_state(link, session_state); return 0; } 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); 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}, { "enable", l_enable }, { "is_start_stop_sync_enabled", l_is_start_stop_sync_enabled }, { "enable_start_stop_sync", l_enable_start_stop_sync}, { "num_peers", l_num_peers }, { "clock_micros", l_clock_micros }, { "capture_audio_session_state", l_capture_audio_session_state }, { "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 }, }; static int l_destroy_session_state(lua_State* L) { abl_link_session_state* session_state = (abl_link_session_state*)luaL_checkudata(L, 1, "abletonlink.session_state"); luaL_argcheck(L, session_state != NULL, 1, "`session_state' expected"); if (session_state->impl) { abl_link_destroy_session_state(*session_state); session_state->impl = NULL; } return 0; } static int l_tempo(lua_State* L) { abl_link_session_state session_state = checksessionstate(L, 1); double bpm = abl_link_tempo(session_state); lua_pushnumber(L, bpm); return 1; } static int l_set_tempo(lua_State* L) { abl_link_session_state session_state = checksessionstate(L, 1); double bpm = luaL_checknumber(L, 2); int64_t at_time = luaL_checkinteger(L, 3); abl_link_set_tempo(session_state, bpm, at_time); return 0; } static int l_beat_at_time(lua_State* L) { abl_link_session_state session_state = checksessionstate(L, 1); int64_t time = luaL_checkinteger(L, 2); double quantum = luaL_checknumber(L, 3); double beat = abl_link_beat_at_time(session_state, time, quantum); lua_pushnumber(L, beat); return 1; } static int l_phase_at_time(lua_State* L) { abl_link_session_state session_state = checksessionstate(L, 1); int64_t time = luaL_checkinteger(L, 2); double quantum = luaL_checknumber(L, 3); double phase = abl_link_phase_at_time(session_state, time, quantum); lua_pushnumber(L, phase); return 1; } static int l_time_at_beat(lua_State* L) { abl_link_session_state session_state = checksessionstate(L, 1); double beat = luaL_checknumber(L, 2); double quantum = luaL_checknumber(L, 3); int64_t time = abl_link_time_at_beat(session_state, beat, quantum); lua_pushinteger(L, time); return 1; } static int l_request_beat_at_time(lua_State* L) { abl_link_session_state session_state = checksessionstate(L, 1); double beat = luaL_checknumber(L, 2); int64_t time = luaL_checkinteger(L, 3); double quantum = luaL_checknumber(L, 4); abl_link_request_beat_at_time(session_state, beat, time, quantum); return 0; } static int l_force_beat_at_time(lua_State* L) { abl_link_session_state session_state = checksessionstate(L, 1); double beat = luaL_checknumber(L, 2); int64_t time = luaL_checkinteger(L, 3); double quantum = luaL_checknumber(L, 4); abl_link_force_beat_at_time(session_state, beat, time, quantum); return 0; } static int l_is_playing(lua_State* L) { abl_link_session_state session_state = checksessionstate(L, 1); bool playing = abl_link_is_playing(session_state); lua_pushboolean(L, playing); return 1; } static int l_set_is_playing(lua_State* L) { abl_link_session_state session_state = checksessionstate(L, 1); bool playing = lua_toboolean(L, 2); int64_t time = luaL_checkinteger(L, 3); abl_link_set_is_playing(session_state, playing, time); return 0; } static int l_time_for_is_playing(lua_State* L) { abl_link_session_state session_state = checksessionstate(L, 1); int64_t time = abl_link_time_for_is_playing(session_state); lua_pushinteger(L, time); return 1; } static int l_request_beat_at_start_playing_time(lua_State* L) { abl_link_session_state session_state = checksessionstate(L, 1); double beat = luaL_checknumber(L, 2); double quantum = luaL_checknumber(L, 3); abl_link_request_beat_at_start_playing_time(session_state, beat, quantum); return 0; } static int l_set_is_playing_and_request_beat_at_time(lua_State* L) { abl_link_session_state session_state = checksessionstate(L, 1); bool playing = lua_toboolean(L, 2); int64_t time = luaL_checkinteger(L, 3); double beat = luaL_checknumber(L, 4); double quantum = luaL_checknumber(L, 5); abl_link_set_is_playing_and_request_beat_at_time(session_state, playing, time, beat, quantum); return 0; } static const luaL_Reg abl_link_session_state_r[] = { { "destroy", l_destroy_session_state }, { "tempo", l_tempo }, { "set_tempo", l_set_tempo }, { "beat_at_time", l_beat_at_time }, { "phase_at_time", l_phase_at_time }, { "time_at_beat", l_time_at_beat }, { "request_beat_at_time", l_request_beat_at_time }, { "force_beat_at_time", l_force_beat_at_time }, { "is_playing", l_is_playing }, { "set_is_playing", l_set_is_playing }, { "time_for_is_playing", l_time_for_is_playing }, { "request_beat_at_start_playing_time", l_request_beat_at_start_playing_time }, { "set_is_playing_and_request_beat_at_time", l_set_is_playing_and_request_beat_at_time }, { "__gc", l_destroy_session_state }, { NULL, NULL }, }; static const luaL_Reg abletonlink[] = { { "create", l_create_link }, { "create_session_state", l_create_session_state }, { NULL, NULL }, }; int luaopen_abletonlink(lua_State* L) { luaL_newmetatable(L, "abletonlink.link"); lua_getmetatable(L, -1); // m.__index = m lua_pushvalue(L, -1); lua_setfield(L, -1, "__index"); // register luax_register(L, abl_link_r); lua_pop(L, 1); luaL_newmetatable(L, "abletonlink.session_state"); lua_getmetatable(L, -1); // m.__index = m lua_pushvalue(L, -1); lua_setfield(L, -1, "__index"); // register luax_register(L, abl_link_session_state_r); lua_pop(L, 1); lua_newtable(L); lua_pushstring(L, "v1.1.0"); lua_setfield(L, -2, "_VERSION"); luax_register(L, abletonlink); return 1; }