Skip to content

Commit 2833196

Browse files
committed
Link - add initial support for accessing Link API from Tau and Spider
* Add support for Tau to talk to the Link NIF and provide OSC API endpoints for comms from Spider. * Add new TauApi class to Spider that gives access to the new Link API in Tau * Create new time/beat access/change functions in Spider Core and move all direct ThreadLocal access logic to them (this should make it easier to integrate Link support to the timing system) * Remove some dead code * Update logging in various places * Remove unused State Waiter logic which was created as a core part of TimeState but never completed. This will be revisited when TimeState is re-implemented as an Erlang NIF.
1 parent b8aad6e commit 2833196

29 files changed

+964
-484
lines changed

app/external/sp_link/CMakeLists.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ elseif(MSVC)
1919
if(DEFINED ENV{GITHUB_ACTION})
2020
set(ERLANG_INCLUDE_PATH "C:/Program Files/erl10.7/usr/include" CACHE PATH "Path to erlang includes")
2121
else()
22-
set(ERLANG_INCLUDE_PATH "C:/Program Files/erl-23.0/usr/include" CACHE PATH "Path to erlang includes")
22+
set(ERLANG_INCLUDE_PATH "C:/Program Files/erl-24.0/usr/include" CACHE PATH "Path to erlang includes")
2323
endif()
2424
endif(APPLE)
2525

@@ -72,4 +72,3 @@ elseif(UNIX)
7272
add_definitions(-DLINUX=1 -DNDEBUG=1)
7373
target_link_libraries(libsp_link Ableton::Link)
7474
endif(MSVC)
75-

app/external/sp_link/external_libs/link/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
cmake_minimum_required(VERSION 3.0)
22
project(Link)
33

4+
set(ERLANG_INCLUDE_PATH "/usr/local/lib/erlang/usr/include" CACHE PATH "Path to erlang includes")
5+
46
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
57

68
# ___ _ _
@@ -60,4 +62,3 @@ if(CMAKE_BUILD_TYPE)
6062
else()
6163
message(" Build type: Set by IDE")
6264
endif()
63-

app/external/sp_link/src/sp_link.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,8 @@ int sp_link_get_time_at_beat(double beat, double quantum, ErlNifSInt64* micros)
240240
return -1;
241241
}
242242

243+
std::cout << "get time at beat: " << beat << ", " << quantum << std::endl;
244+
243245
auto state = g_link->captureAppSessionState();
244246
*micros = state.timeAtBeat(beat, quantum).count();
245247
return 0;
@@ -306,9 +308,7 @@ int sp_link_get_current_time_microseconds(ErlNifSInt64* micros)
306308
}
307309

308310
*micros = g_link->clock().micros().count();
311+
312+
std::cout << "current_time from c++ " << *micros << std::endl;
309313
return 0;
310314
}
311-
312-
313-
314-

app/external/sp_midi/CMakeLists.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ elseif(MSVC)
2222
if(DEFINED ENV{GITHUB_ACTION})
2323
set(ERLANG_INCLUDE_PATH "C:/Program Files/erl10.7/usr/include" CACHE PATH "Path to erlang includes")
2424
else()
25-
set(ERLANG_INCLUDE_PATH "C:/Program Files/erl-23.0/usr/include" CACHE PATH "Path to erlang includes")
25+
set(ERLANG_INCLUDE_PATH "C:/Program Files/erl-24.0/usr/include" CACHE PATH "Path to erlang includes")
2626
endif()
2727
endif(APPLE)
2828

@@ -84,4 +84,3 @@ elseif(UNIX)
8484
include_directories(${ERLANG_INCLUDE_PATH})
8585
target_link_libraries(libsp_midi pthread ${ALSA_LIBRARY} dl rtmidi)
8686
endif(MSVC)
87-

app/server/erlang/tau/Emakefile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
{"src/osc/*", [{outdir, "ebin/"}]}.
1+
{"src/osc/*", [{outdir, "ebin/"}]}.
22
{"src/tau_server/*", [{outdir, "ebin/"}]}.
3-
{"src/sp_midi/*", [{outdir, "ebin/"}]}.
3+
{"src/sp_midi/*", [{outdir, "ebin/"}]}.
4+
{"src/sp_link/*", [{outdir, "ebin/"}]}.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
-module(sp_link).
2+
-export([init_nif/1, deinit_nif/0, enable/1, is_enabled/0, set_tempo/2, get_tempo/0, get_num_peers/0,
3+
start_stop_sync_enable/1, is_start_stop_sync_enabled/0, set_is_playing/2, is_playing/0, get_time_for_is_playing/0,
4+
get_beat_at_time/2, get_phase_at_time/2, get_time_at_beat/2, request_beat_at_time/3, force_beat_at_time/3,
5+
request_beat_at_start_playing_time/2, set_is_playing_and_request_beat_at_time/4, set_callback_pid/1,
6+
get_current_time_microseconds/0, set_log_level/1]).
7+
-on_load(init/0).
8+
9+
-define(APPLICATION, tau).
10+
-define(LIBNAME, "libsp_link").
11+
12+
init() ->
13+
SoName = case code:priv_dir(?APPLICATION) of
14+
{error, bad_name} ->
15+
case filelib:is_dir(filename:join(["..", priv])) of
16+
true ->
17+
filename:join(["..", priv, ?LIBNAME]);
18+
_ ->
19+
filename:join([priv, ?LIBNAME])
20+
end;
21+
Dir ->
22+
filename:join(Dir, ?LIBNAME)
23+
end,
24+
erlang:load_nif(SoName, 0).
25+
26+
init_nif(_) ->
27+
exit(nif_library_not_loaded).
28+
deinit_nif() ->
29+
exit(nif_library_not_loaded).
30+
enable(_) ->
31+
exit(nif_library_not_loaded).
32+
is_enabled() ->
33+
exit(nif_library_not_loaded).
34+
set_tempo(_, _) ->
35+
exit(nif_library_not_loaded).
36+
get_tempo() ->
37+
exit(nif_library_not_loaded).
38+
get_num_peers() ->
39+
exit(nif_library_not_loaded).
40+
start_stop_sync_enable(_) ->
41+
exit(nif_library_not_loaded).
42+
is_start_stop_sync_enabled() ->
43+
exit(nif_library_not_loaded).
44+
set_is_playing(_, _) ->
45+
exit(nif_library_not_loaded).
46+
is_playing() ->
47+
exit(nif_library_not_loaded).
48+
get_time_for_is_playing() ->
49+
exit(nif_library_not_loaded).
50+
get_beat_at_time(_, _) ->
51+
exit(nif_library_not_loaded).
52+
get_phase_at_time(_, _) ->
53+
exit(nif_library_not_loaded).
54+
get_time_at_beat(_, _) ->
55+
exit(nif_library_not_loaded).
56+
request_beat_at_time(_, _, _) ->
57+
exit(nif_library_not_loaded).
58+
force_beat_at_time(_, _, _) ->
59+
exit(nif_library_not_loaded).
60+
request_beat_at_start_playing_time(_, _) ->
61+
exit(nif_library_not_loaded).
62+
set_is_playing_and_request_beat_at_time(_, _, _, _) ->
63+
exit(nif_library_not_loaded).
64+
set_callback_pid(_) ->
65+
exit(nif_library_not_loaded).
66+
get_current_time_microseconds() ->
67+
exit(nif_library_not_loaded).
68+
set_log_level(_) ->
69+
exit(nif_library_not_loaded).
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
-module(sp_link_test).
2+
-export([start/0, link_process/0, test_callbacks/0, test_tempo_changes_from_erlang/0]).
3+
4+
5+
link_process() ->
6+
receive
7+
{link_num_peers, Peers} when is_integer(Peers) ->
8+
io:format("Received callback num_peers message -> ~p peers~n", [Peers]);
9+
{link_tempo, Tempo} when is_float(Tempo) ->
10+
io:format("Received callback tempo message -> ~p bpm~n", [Tempo]);
11+
{link_start} ->
12+
io:format("Received callback start message-> ~n", []);
13+
{link_stop} ->
14+
io:format("Received callback stop message-> ~n", []);
15+
X ->
16+
io:format("Received something unexpected->~p~n", [X])
17+
18+
end,
19+
link_process().
20+
21+
test_callbacks() ->
22+
io:fwrite("Spawning and setting up Erlang Link callback process~n"),
23+
Pid = spawn(sp_link_test, link_process, []),
24+
sp_link:set_callback_pid(Pid),
25+
26+
io:fwrite("Now go into Ableton Live (or other Link enabled SW or device) and change tempo, settings, play / stop (make sure that Link and Start / stop sync are enabled there)~n"),
27+
io:fwrite("You should see the callbacks triggering~n"),
28+
29+
io:fwrite("Waiting 20 seconds for callbacks~n"),
30+
timer:sleep(20000).
31+
32+
33+
% Make sure the tempo is passed as a float. Otherwise, badarg
34+
test_tempo_changes_from_erlang() ->
35+
sp_link:set_tempo(99.0, 0),
36+
timer:sleep(2000),
37+
sp_link:set_tempo(212.0, 0),
38+
timer:sleep(2000),
39+
sp_link:set_tempo(67.5, 0),
40+
timer:sleep(2000),
41+
sp_link:set_tempo(20.0, 0),
42+
timer:sleep(2000),
43+
sp_link:set_tempo(500.99, 0),
44+
timer:sleep(2000),
45+
46+
GetTempo = sp_link:get_tempo(),
47+
io:fwrite("Getting the tempo to check. Got ~p~n", [GetTempo]).
48+
49+
50+
start() ->
51+
% cd("d:/projects/sp_link/src").
52+
compile:file(sp_link),
53+
54+
io:fwrite("Init and enabling Link~n"),
55+
sp_link:init_nif(120.0),
56+
sp_link:enable(true),
57+
58+
io:fwrite("Enabling Link start / stop synchronization~n"),
59+
sp_link:start_stop_sync_enable(true),
60+
61+
io:fwrite("Testing NIF function to return link clock in microseconds.~n"),
62+
Micros = sp_link:get_current_time_microseconds(),
63+
io:fwrite("Micros: ~p~n", [Micros]),
64+
65+
io:fwrite("Testing callbacks (comment if you do not want to test them anymore)~n"),
66+
test_callbacks(),
67+
68+
io:fwrite("Now we do some tempo changes and see if Ableton Link gets them~n"),
69+
test_tempo_changes_from_erlang(),
70+
71+
sp_link:enable(false),
72+
sp_link:deinit_nif().

app/server/erlang/tau/src/tau.app.src

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111
{mod,{tau_server_app,[]}},
1212
{env, [{enabled, true},
1313
{midi_enabled, true},
14+
{link_enabled, true},
1415
{in_port, 4560}, % sane defaults for the ports
1516
{api_port, 51240},
1617
{cue_host, {127,0,0,1}},
17-
{cue_port, 51235},
18+
{spider_port, 51235},
1819
{internal, true}
1920
]},
2021
{applications, [kernel,stdlib,sasl]}

app/server/erlang/tau/src/tau_server/tau_server.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818

1919

2020
%% API for launching as an OTP application from the command line
21-
%% "erl -tau_server api_port $API_PORT in_port $IN_PORT cue_port $CUE_PORT \
21+
%% "erl -tau api_port $API_PORT in_port $IN_PORT spider_port $SPIDER_PORT \
2222
%% -s tau_server start"
2323
start() ->
24-
%% note that this will dispatch using the 'mod' entry in tau_server.app
24+
%% note that this will dispatch using the 'mod' entry in tau.app
2525
{ok, _} = application:ensure_all_started(?APPLICATION),
2626
ok.

app/server/erlang/tau/src/tau_server/tau_server_api.erl

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414

1515
-module(tau_server_api).
1616

17-
-export([start_link/2]).
17+
-export([start_link/3]).
1818

1919
%% internal
20-
-export([init/3, loop/1]).
20+
-export([init/4, loop/1]).
2121

2222
%% sys module callbacks
2323
-export([system_continue/3, system_terminate/4, system_code_change/4,
@@ -70,12 +70,12 @@
7070

7171

7272
%% supervisor compliant start function
73-
start_link(CueServer, MIDIServer) ->
73+
start_link(CueServer, MIDIServer, LinkServer) ->
7474
%% synchronous start of the child process
75-
proc_lib:start_link(?MODULE, init, [self(), CueServer, MIDIServer]).
75+
proc_lib:start_link(?MODULE, init, [self(), CueServer, MIDIServer, LinkServer]).
7676

7777

78-
init(Parent, CueServer, MIDIServer) ->
78+
init(Parent, CueServer, MIDIServer, LinkServer) ->
7979
register(?SERVER, self()),
8080
APIPort = application:get_env(?APPLICATION, api_port, undefined),
8181
io:format("~n"
@@ -98,8 +98,10 @@ init(Parent, CueServer, MIDIServer) ->
9898
api_socket => APISocket,
9999
cue_server => CueServer,
100100
midi_server => MIDIServer,
101+
link_server => LinkServer,
101102
tag_map => #{}
102103
},
104+
send_to_cue({tau_ready}, State),
103105
loop(State).
104106

105107
loop(State) ->
@@ -112,15 +114,16 @@ loop(State) ->
112114
NewState = do_bundle(Time, X, State),
113115
?MODULE:loop(NewState);
114116
{cmd, ["/ping"]} ->
115-
debug("sending /pong to ~p ~p ~n", [Ip, Port]),
117+
debug("sending! /pong to ~p ~p ~n", [Ip, Port]),
116118
PongBin = osc:encode(["/pong"]),
117119
ok = gen_udp:send(APISocket, Ip, Port, PongBin),
118120
?MODULE:loop(State);
119-
{cmd, ["/midi", OSC]} ->
121+
{cmd, ["/midi", OSC]=Cmd} ->
122+
debug_cmd(Cmd),
120123
MIDIServer = maps:get(midi_server, State),
121124
MIDIServer ! {send, OSC},
122125
?MODULE:loop(State);
123-
{cmd, ["/midi_flush"]=Cmd} ->
126+
{cmd, ["/midi-flush"]=Cmd} ->
124127
debug_cmd(Cmd),
125128
MIDIServer = maps:get(midi_server, State),
126129
MIDIServer ! {flush},
@@ -142,9 +145,33 @@ loop(State) ->
142145
debug_cmd(Cmd),
143146
send_to_cue({midi_enabled, Flag =:= 1}, State),
144147
?MODULE:loop(State);
148+
{cmd, ["/api-rpc", UUID, "/link-get-current-time"]=Cmd} ->
149+
debug_cmd(Cmd),
150+
send_to_link({link_rpc, UUID, get_current_time}, State),
151+
?MODULE:loop(State);
152+
153+
{cmd, ["/api-rpc", UUID, "/link-get-beat-at-time", Time, Quantum]=Cmd} ->
154+
debug_cmd(Cmd),
155+
send_to_link({link_rpc, UUID, get_beat_at_time, Time, Quantum}, State),
156+
?MODULE:loop(State);
157+
158+
{cmd, ["/api-rpc", UUID, "/link-get-time-at-beat", Beat, Quantum]=Cmd} ->
159+
debug_cmd(Cmd),
160+
send_to_link({link_rpc, UUID, get_time_at_beat, Beat, Quantum}, State),
161+
?MODULE:loop(State);
162+
163+
{cmd, ["/api-rpc", UUID, "/link-get-tempo"]=Cmd} ->
164+
debug_cmd(Cmd),
165+
send_to_link({link_rpc, UUID, get_tempo}, State),
166+
?MODULE:loop(State);
167+
168+
{cmd, ["/api-rpc", UUID, "/link-get-num-peers"]=Cmd} ->
169+
debug_cmd(Cmd),
170+
send_to_link({link_rpc, UUID, get_num_peers}, State),
171+
?MODULE:loop(State);
145172

146173
{cmd, Cmd} ->
147-
log("Unknown command: \"~s\"~n", [Cmd]),
174+
log("Unknown command:: ~p~n", [Cmd]),
148175
?MODULE:loop(State)
149176
catch
150177
Class:Term:Trace ->
@@ -162,6 +189,12 @@ loop(State) ->
162189
?MODULE:loop(State)
163190
end.
164191

192+
send_to_link(Message, State) ->
193+
LinkServer = maps:get(link_server, State),
194+
LinkServer ! Message,
195+
ok.
196+
197+
165198
send_to_cue(Message, State) ->
166199
CueServer = maps:get(cue_server, State),
167200
CueServer ! Message,
@@ -173,13 +206,13 @@ debug_cmd([Cmd|Args]) ->
173206
do_bundle(Time, [{_,Bin}|T], State) ->
174207
NewState =
175208
try osc:decode(Bin) of
176-
{cmd, ["/send_after", Host, Port , OSC]} ->
209+
{cmd, ["/send-after", Host, Port , OSC]} ->
177210
schedule_cmd("default", Time, Host, Port, OSC, State);
178-
{cmd, ["/send_after_tagged", Tag, Host, Port, OSC]} ->
211+
{cmd, ["/send-after-tagged", Tag, Host, Port, OSC]} ->
179212
schedule_cmd(Tag, Time, Host, Port, OSC, State);
180-
{cmd, ["/midi_at", Cmd]} ->
213+
{cmd, ["/midi-at", Cmd]} ->
181214
schedule_midi("default", Time, Cmd, State);
182-
{cmd, ["/midi_at_tagged", Tag, Cmd]} ->
215+
{cmd, ["/midi-at-tagged", Tag, Cmd]} ->
183216
schedule_midi(Tag, Time, Cmd, State);
184217
Other ->
185218
log("Unexpected bundle content:~p~n", [Other]),

0 commit comments

Comments
 (0)