Skip to content

Commit 935dc04

Browse files
committed
Progress so far...
1 parent 7180752 commit 935dc04

File tree

18 files changed

+608
-105
lines changed

18 files changed

+608
-105
lines changed

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/"}]}.

app/server/erlang/tau/ebin/tau.app

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
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}},
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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
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}},

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

Lines changed: 28 additions & 5 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) ->
@@ -143,6 +145,21 @@ loop(State) ->
143145
send_to_cue({midi_enabled, Flag =:= 1}, State),
144146
?MODULE:loop(State);
145147

148+
{cmd, ["/api_rpc", UUID, "/link_current_time"]=Cmd} ->
149+
debug_cmd(Cmd),
150+
send_to_link({link_rpc, UUID, 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_tempo"]=Cmd} ->
159+
debug_cmd(Cmd),
160+
send_to_link({link_rpc, UUID, get_tempo}, State),
161+
?MODULE:loop(State);
162+
146163
{cmd, Cmd} ->
147164
log("Unknown command: \"~s\"~n", [Cmd]),
148165
?MODULE:loop(State)
@@ -162,6 +179,12 @@ loop(State) ->
162179
?MODULE:loop(State)
163180
end.
164181

182+
send_to_link(Message, State) ->
183+
LinkServer = maps:get(link_server, State),
184+
LinkServer ! Message,
185+
ok.
186+
187+
165188
send_to_cue(Message, State) ->
166189
CueServer = maps:get(cue_server, State),
167190
CueServer ! Message,

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

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ init(Parent) ->
4747
Internal = application:get_env(?APPLICATION, internal, true),
4848
Enabled = application:get_env(?APPLICATION, enabled, true),
4949
MIDIEnabled = application:get_env(?APPLICATION, midi_enabled, true),
50+
LinkEnabled = application:get_env(?APPLICATION, link_enabled, true),
5051
io:format("~n"
5152
"+--------------------------------------+~n"
5253
" This is the Sonic Pi OSC Server ~n"
@@ -74,11 +75,13 @@ init(Parent) ->
7475
State = #{parent => Parent,
7576
enabled => Enabled,
7677
midi_enabled => MIDIEnabled,
78+
link_enabled => LinkEnabled,
7779
cue_host => CueHost,
7880
cue_port => CuePort,
7981
internal => Internal,
8082
in_port => InPort,
8183
in_socket => InSocket
84+
8285
},
8386
loop(State).
8487

@@ -90,13 +93,71 @@ loop(State) ->
9093
CueHost = maps:get(cue_host, State),
9194
CuePort = maps:get(cue_port, State),
9295
InSocket = maps:get(in_socket, State),
93-
forward_midi_cue(CueHost, CuePort, InSocket, Path, Args),
96+
forward_internal_cue(CueHost, CuePort, InSocket, Path, Args),
9497
?MODULE:loop(State);
9598
#{midi_enabled := false} ->
9699
debug("MIDI cue forwarding disabled - ignored: ~p~n", [{Path, Args}]),
97100
?MODULE:loop(State)
98101
end;
99102

103+
{link, num_peers, NumPeers} ->
104+
case State of
105+
#{link_enabled := true,
106+
cue_host := CueHost,
107+
cue_port := CuePort,
108+
in_socket := InSocket} ->
109+
forward_internal_cue(CueHost, CuePort, InSocket, "/link/num-peers", [NumPeers]),
110+
?MODULE:loop(State);
111+
_ ->
112+
debug("Link cue forwarding disabled - ignored num_peers change ~n", []),
113+
?MODULE:loop(State)
114+
end;
115+
116+
117+
{link, tempo_change, Tempo} ->
118+
case State of
119+
#{link_enabled := true,
120+
cue_host := CueHost,
121+
cue_port := CuePort,
122+
in_socket := InSocket} ->
123+
forward_internal_cue(CueHost, CuePort, InSocket, "/link/tempo-change", [Tempo]),
124+
?MODULE:loop(State);
125+
_ ->
126+
debug("Link cue forwarding disabled - ignored tempo change ~n", []),
127+
?MODULE:loop(State)
128+
end;
129+
130+
{link, start} ->
131+
case State of
132+
#{link_enabled := true,
133+
cue_host := CueHost,
134+
cue_port := CuePort,
135+
in_socket := InSocket} ->
136+
forward_internal_cue(CueHost, CuePort, InSocket, "/link/start", []),
137+
?MODULE:loop(State);
138+
_ ->
139+
debug("Link cue forwarding disabled - ignored start message ~n", []),
140+
?MODULE:loop(State)
141+
end;
142+
143+
144+
{link, stop} ->
145+
case State of
146+
#{link_enabled := true,
147+
cue_host := CueHost,
148+
cue_port := CuePort,
149+
in_socket := InSocket} ->
150+
forward_internal_cue(CueHost, CuePort, InSocket, "/link/stop", []),
151+
?MODULE:loop(State);
152+
_ ->
153+
debug("Link cue forwarding disabled - ignored stop message ~n", []),
154+
?MODULE:loop(State)
155+
end;
156+
157+
{api_reply, UUID, Response} ->
158+
send_api_reply(State, UUID, Response),
159+
?MODULE:loop(State);
160+
100161
{update_midi_ports, Ins, Outs} ->
101162
CueHost = maps:get(cue_host, State),
102163
CuePort = maps:get(cue_port, State),
@@ -183,9 +244,9 @@ loop(State) ->
183244
send_forward(maps:get(in_socket, State), Time, Data),
184245
?MODULE:loop(State);
185246

186-
{udp_error, Port, econnreset} ->
247+
{udp_error, _Port, econnreset} ->
187248
%% Should not happen, but can happen anyway on Windows
188-
debug(2, "got UDP ECONNRESET for port ~p- ignored~n", [Port]),
249+
debug(2, "got UDP ECONNRESET - ignored~n", []),
189250
?MODULE:loop(State);
190251

191252
{system, From, Request} ->
@@ -201,9 +262,17 @@ loop(State) ->
201262
Port = maps:get(cue_port, State),
202263
send_udp(Socket, Host, Port, Bin),
203264
?MODULE:loop(State);
265+
{tau_ready} ->
266+
Bin = osc:encode(["/tau-ready"]),
267+
Socket = maps:get(in_socket, State),
268+
Host = maps:get(cue_host, State),
269+
Port = maps:get(cue_port, State),
270+
send_udp(Socket, Host, Port, Bin),
271+
?MODULE:loop(State);
204272
Any ->
205273
log("Cue Server got unexpected message: ~p~n", [Any]),
206274
?MODULE:loop(State)
275+
207276
end.
208277

209278

@@ -246,12 +315,20 @@ update_midi_out_ports(CueHost, CuePort, InSocket, Args) ->
246315
debug("forwarded new MIDI outs to ~p:~p~n", [CueHost, CuePort]),
247316
ok.
248317

249-
forward_midi_cue(CueHost, CuePort, InSocket, Path, Args) ->
250-
Bin = osc:encode(["/midi-cue", "erlang", Path | Args]),
318+
send_api_reply(State, UUID, Args) ->
319+
CueHost = maps:get(cue_host, State),
320+
CuePort = maps:get(cue_port, State),
321+
InSocket = maps:get(in_socket, State),
322+
Bin = osc:encode(["/tau_api_reply", "erlang", UUID | Args]),
323+
%% debug("send api reply ~p:~p~n", ToEncode),
251324
send_udp(InSocket, CueHost, CuePort, Bin),
252-
debug("forwarded MIDI OSC cue to ~p:~p~n", [CueHost, CuePort]),
253325
ok.
254326

327+
forward_internal_cue(CueHost, CuePort, InSocket, Path, Args) ->
328+
Bin = osc:encode(["/internal-cue", "erlang", Path | Args]),
329+
send_udp(InSocket, CueHost, CuePort, Bin),
330+
debug("forwarded internal OSC cue to ~p:~p~n", [CueHost, CuePort]),
331+
ok.
255332

256333
forward_cue(CueHost, CuePort, InSocket, Ip, Port, Cmd) ->
257334
Bin = osc:encode(["/external-osc-cue", inet:ntoa(Ip), Port] ++ Cmd),

0 commit comments

Comments
 (0)