git.s-ol.nu lua-abletonlink / 428b68c
implement simple get/setters s-ol 9 months ago
7 changed file(s) with 376 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 *.o
1 *.so
2 *.lib
3 *.dll
4
5 *.rock
0 [submodule "link"]
1 path = link
2 url = https://github.com/Ableton/link.git
0 abletonlink
1 ===========
2
3 Lightweight Lua wrapper of the Ableton Link C API ([abl_link][abl_link]).
4
5 API
6 ---
7
8 The module follows the C API very closely.
9 See the [`abl_link.h`][abl_link.h] for comments for the various methods.
10
11 The module table has three members:
12
13 - `abletonlink._VERSION`: a string like `"1.0.0"`
14 - `abletonlink.create(number bpm)`: creates an `abl_link` instance
15 - `abletonlink.create_session_state()`: creates an `abl_link_session_state` instance
16
17 `abl_link` instances have the following methods:
18
19 - `link:is_enabled() -> bool`
20 - `link:enable(bool?)` (defaults to true)
21 - `link:is_start_stop_sync_enabled() -> bool`
22 - `link:enable_start_stop_sync(bool?)` (defaults to true)
23 - `link:num_peers() -> int`
24 - `link:clock_micros() -> int`
25 - `link:capture_audio_session_state(session_state output)`
26 - `link:commit_audio_session_state(session_state input)`
27 - `link:capture_app_session_state(session_state output)`
28 - `link:commit_app_session_state(session_state input)`
29 - `link:destroy()`:
30 Destroys this instance.
31 Calling any other method on a destroyed instance causes an error.
32 This is automatically called in __gc.
33 - `link:set_*_callback` are not currently implemented.
34
35 `abl_link_session_state` instances have the following methods:
36
37 - `session_state:tempo() -> number`
38 - `session_state:set_tempo(number bpm, int at_time)`
39 - `session_state:beat_at_time(int time, number quantum) -> number`
40 - `session_state:phase_at_time(int time, number quantum) -> number`
41 - `session_state:time_at_beat(number beat, number quantum) -> int`
42 - `session_state:request_beat_at_time(number beat, int time, number quantum)`
43 - `session_state:force_beat_at_time(number beat, int time, number quantum)`
44 - `session_state:set_is_playing(bool, int at_time)`
45 - `session_state:is_playing() -> bool`
46 - `session_state:time_for_is_playing() -> int`
47 - `session_state:request_beat_at_start_playing_time(number beat, number quantum)`
48 - `session_state:set_is_playing_and_request_beat_at_time(bool, int time, number beat, number quantum)`
49 - `session_state:destroy()`:
50 Destroys this instance.
51 Calling any other method on a destroyed instance causes an error.
52 This is automatically called in __gc.
53
54 [abl_link]: https://github.com/Ableton/link/tree/master/extensions/abl_link
55 [abl_link.h]: https://github.com/Ableton/link/blob/master/extensions/abl_link/include/abl_link.h
0 rockspec_format = "3.0"
1 package = "abletonlink"
2 version = "1.0.0-1"
3 source = {
4 url = 'git+https://git.s-ol.nu/lua-abletonlink.git',
5 tag = 'v1.0.0',
6 }
7 description = {
8 summary = "Ableton Link bindings for Lua",
9 detailed = [[
10 Lightweight wrapper of the Ableton Link C API (abl_link).
11
12 https://github.com/Ableton/link/tree/master/extensions/abl_link
13 ]],
14 homepage = "https://git.s-ol.nu/lua-abletonlink/-/",
15 license = "GPL-2.0-or-later",
16 }
17
18 dependencies = {
19 "lua >= 5.1",
20 }
21 build = {
22 type = 'builtin',
23 modules = {
24 -- C module
25 abletonlink = {
26 sources = {
27 'abletonlink.c',
28 'link/extensions/abl_link/src/abl_link.cpp',
29 },
30 incdirs = {'link/include', 'link/extensions/abl_link/include'},
31 libraries = {'stdc++'},
32 },
33 },
34 platforms = {
35 linux = {
36 modules = {
37 abletonlink = {
38 defines = {'LINK_PLATFORM_LINUX'},
39 },
40 },
41 },
42 windows = {
43 modules = {
44 abletonlink = {
45 defines = {'LINK_PLATFORM_WINDOWS'},
46 },
47 },
48 },
49 macosx = {
50 modules = {
51 abletonlink = {
52 defines = {'LINK_PLATFORM_MACOSX'},
53 },
54 },
55 },
56 },
57 }
0 #include <lua.h>
1 #include <lauxlib.h>
2 #include "link/extensions/abl_link/include/abl_link.h"
3
4 #if LUA_VERSION_NUM > 501
5 #define luax_len(L, i) (int) lua_rawlen(L, i)
6 #define luax_register(L, f) luaL_setfuncs(L, f, 0)
7 #else
8 #define luax_len(L, i) (int) lua_objlen(L, i)
9 #define luax_register(L, f) luaL_register(L, NULL, f)
10 #define LUA_RIDX_MAINTHREAD 1
11 #endif
12
13 static int l_create_link(lua_State* L) {
14 double bpm = luaL_checknumber(L, 1);
15
16 abl_link* link = (abl_link*)lua_newuserdata(L, sizeof(abl_link));
17
18 *link = abl_link_create(bpm);
19
20 luaL_getmetatable(L, "abletonlink.link");
21 lua_setmetatable(L, -2);
22
23 return 1;
24 }
25
26 static abl_link checklink(lua_State *L) {
27 abl_link* link = (abl_link*)luaL_checkudata(L, 1, "abletonlink.link");
28 luaL_argcheck(L, link != NULL, 1, "`link' expected");
29 luaL_argcheck(L, link->impl != NULL, 1, "`link' is already destroyed");
30 return *link;
31 }
32
33 static int l_destroy_link(lua_State* L) {
34 abl_link* link = (abl_link*)luaL_checkudata(L, 1, "abletonlink.link");
35 luaL_argcheck(L, link != NULL, 1, "`link' expected");
36 if (link->impl) {
37 abl_link_destroy(*link);
38 link->impl = NULL;
39 }
40 return 0;
41 }
42
43 static int l_is_enabled(lua_State* L) {
44 abl_link link = checklink(L);
45 bool enabled = abl_link_is_enabled(link);
46 lua_pushboolean(L, enabled);
47 return 1;
48 }
49
50 static int l_enable(lua_State* L) {
51 abl_link link = checklink(L);
52 bool enable = lua_isnoneornil(L, 2) ? true : lua_toboolean(L, 2);
53 abl_link_enable(link, enable);
54 return 0;
55 }
56
57 static int l_is_start_stop_sync_enabled(lua_State* L) {
58 abl_link link = checklink(L);
59 bool enabled = abl_link_is_start_stop_sync_enabled(link);
60 lua_pushboolean(L, enabled);
61 return 1;
62 }
63
64 static int l_enable_start_stop_sync(lua_State* L) {
65 abl_link link = checklink(L);
66 bool enable = lua_isnoneornil(L, 2) ? true : lua_toboolean(L, 2);
67 abl_link_enable_start_stop_sync(link, enable);
68 return 0;
69 }
70
71 static int l_num_peers(lua_State* L) {
72 abl_link link = checklink(L);
73 uint64_t peers = abl_link_num_peers(link);
74 lua_pushinteger(L, peers);
75 return 1;
76 }
77
78 static int l_clock_micros(lua_State* L) {
79 abl_link link = checklink(L);
80 int64_t micros = abl_link_clock_micros(link);
81 lua_pushinteger(L, micros);
82 return 1;
83 }
84
85 static const luaL_Reg abl_link_r[] = {
86 { "destroy", l_destroy_link },
87 { "is_enabled", l_is_enabled},
88 { "enable", l_enable },
89 { "is_start_stop_sync_enabled", l_is_start_stop_sync_enabled },
90 { "enable_start_stop_sync", l_enable_start_stop_sync},
91 { "num_peers", l_num_peers },
92 { "clock_micros", l_clock_micros },
93 /*
94 { "capture_audio_session_state", l_capture_audio_session_state },
95 { "commit_audio_session_state", l_commit_audio_session_state },
96 { "capture_app_session_state", l_capture_app_session_state },
97 { "commit_app_session_state", l_commit_app_session_state },
98 */
99 { "__gc", l_destroy_link },
100 { NULL, NULL },
101 };
102
103 static abl_link_session_state* checksessionstate(lua_State *L) {
104 void *ud = luaL_checkudata(L, 1, "abletonlink.session_state");
105 luaL_argcheck(L, ud != NULL, 1, "`session_state' expected");
106 return (abl_link_session_state*)ud;
107 }
108
109 static int l_destroy_session_state(lua_State* L) {
110 abl_link_session_state* state = checksessionstate(L);
111 if (state->impl) {
112 abl_link_destroy_session_state(*state);
113 state->impl = NULL;
114 }
115 return 0;
116 }
117
118 static const luaL_Reg abl_link_session_state_r[] = {
119 { "destroy", l_destroy_session_state },
120 /*
121 { "tempo", l_tempo },
122 { "set_tempo", l_set_tempo },
123 { "beat_at_time", l_beat_at_time },
124 { "phase_at_time", l_phase_at_time },
125 { "time_at_beat", l_time_at_beat },
126 { "request_beat_at_time", l_request_beat_at_time },
127 { "force_beat_at_time", l_force_beat_at_time },
128 { "is_playing", l_is_playing },
129 { "set_is_playing", l_set_is_playing },
130 { "time_for_is_playing", l_time_for_is_playing },
131 { "request_beat_at_start_playing_time", l_request_beat_at_start_playing_time },
132 { "set_is_playing_and_request_beat_at_time", l_set_is_playing_and_request_beat_at_time },
133 */
134 { "__gc", l_destroy_session_state },
135 { NULL, NULL },
136 };
137
138 static const luaL_Reg abletonlink[] = {
139 { "create", l_create_link },
140 { NULL, NULL },
141 };
142
143 int luaopen_abletonlink(lua_State* L) {
144 luaL_newmetatable(L, "abletonlink.link");
145 lua_getmetatable(L, -1);
146
147 // m.__index = m
148 lua_pushvalue(L, -1);
149 lua_setfield(L, -1, "__index");
150
151 // register
152 luax_register(L, abl_link_r);
153 lua_pop(L, 1);
154
155 luaL_newmetatable(L, "abletonlink.session_state");
156 lua_getmetatable(L, -1);
157
158 // m.__index = m
159 lua_pushvalue(L, -1);
160 lua_setfield(L, -1, "__index");
161
162 // register
163 luax_register(L, abl_link_session_state_r);
164 lua_pop(L, 1);
165
166 lua_newtable(L);
167 lua_pushstring(L, "v1.0.0");
168 lua_setfield(L, -2, "_VERSION");
169 luax_register(L, abletonlink);
170 return 1;
171 }
(New empty file)
0 local assert = require 'luassert'
1 local say = require("say")
2 local function greater_than(state, arguments)
3 local min, val = arguments[1], arguments[2]
4 return min < val
5 end
6 assert:register('assertion', 'greater_than', greater_than, 'assertion.greater_than.positive', 'assertion.greater_than.negative')
7 say:set_namespace("en")
8 say:set("assertion.greater_than.positive", "Expected %s < %s")
9 say:set("assertion.greater_than.negative", "Expected %s >= %s")
10
11 describe("abletonlink library", function()
12 local link = require 'abletonlink'
13
14 it("exports a _VERSION", function()
15 -- asset.is.equal('1.0.0', link._VERSION)
16 end)
17
18 it("can create and destroy link objects", function()
19 local l = link.create(120)
20 l:destroy()
21
22 assert.has.error(function()
23 l:is_enabled()
24 end)
25 end)
26
27 it("can be enabled and disabled", function()
28 local l = link.create(120)
29 assert.is_false(l:is_enabled())
30 l:enable()
31 assert.is_true(l:is_enabled())
32
33 l:enable(false)
34 assert.is_false(l:is_enabled())
35 l:enable(true)
36 assert.is_true(l:is_enabled())
37 end)
38
39 it("start/stop sync can be enabled and disabled", function()
40 local l = link.create(120)
41 assert.is_false(l:is_start_stop_sync_enabled())
42 l:enable_start_stop_sync()
43 assert.is_true(l:is_start_stop_sync_enabled())
44
45 l:enable_start_stop_sync(false)
46 assert.is_false(l:is_start_stop_sync_enabled())
47 l:enable_start_stop_sync(true)
48 assert.is_true(l:is_start_stop_sync_enabled())
49 end)
50
51 test("num_peers()", function()
52 local a, b = link.create(120), link.create(120)
53 assert.equal(0, a:num_peers())
54 assert.equal(0, b:num_peers())
55
56 a:enable()
57 b:enable()
58
59 -- stall a bit for connections to succeed
60 for i=1,10000 do
61 i = math.random()
62 end
63
64 assert.greater_than(0, a:num_peers())
65 assert.greater_than(0, b:num_peers())
66 end)
67
68 test("clock_micros()", function()
69 local l = link.create(120)
70
71 local last = 0
72
73 for i=1,10 do
74 local next = l:clock_micros()
75 assert.is.number(next)
76 assert.is.greater_than(last, next)
77 last = next
78 end
79 end)
80 end)