implement simple get/setters
s-ol
9 months ago
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 | } |
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) |