Skip to content
This repository was archived by the owner on Nov 23, 2023. It is now read-only.

Commit 02aa9bd

Browse files
added scd file for tidal
1 parent 919e2ec commit 02aa9bd

1 file changed

Lines changed: 214 additions & 0 deletions

File tree

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
(
2+
thisProcess.openUDPPort(6789); // from tidal
3+
~python = NetAddr.new("127.0.0.1", 9999); // to python
4+
)
5+
6+
7+
// for sending MIDI to ableton
8+
(
9+
MIDIClient.init;
10+
// MIDIClient.destinations
11+
~m1 = MIDIOut.newByName("IAC Driver", "Bus 1").latency_(0);
12+
~m2 = MIDIOut.newByName("IAC Driver", "Bus 2").latency_(0);
13+
~m3 = MIDIOut.newByName("IAC Driver", "Bus 3").latency_(0);
14+
~prog2portchan = { arg prog;
15+
case
16+
{prog==0}{"can't convert start token".postln}
17+
{prog<=128}{
18+
var group = (prog-1 /8).asInteger;
19+
var idx = (prog-1 %8).asInteger;
20+
var port = switch(group)
21+
{ 0}{case
22+
{idx<4}{~m1} //acoustic
23+
{idx<6}{~m2} //electric
24+
{true}{~m3} //harpsichord
25+
} //piano
26+
{ 1}{((idx<3)||(idx==5)).if{~m1}{~m2}} //chromatic perc
27+
{ 2}{(idx<4).if{~m1}{~m2}} //organ
28+
{ 3}{(idx<5).if{~m1}{~m2}} //guitar
29+
{ 4}{(idx<4).if{~m2}{~m1}} //bass
30+
{ 5}{(idx<5).if{~m1}{~m2}} //strings
31+
{ 6}{(idx<4).if{~m1}{~m2}} //ensemble
32+
{ 7}{(idx<3).if{~m1}{~m2}} //brass
33+
{ 8}{(idx<4).if{~m1}{~m2}} //reed
34+
{ 9}{(idx<3).if{~m1}{~m2}} //pipe
35+
{10}{(idx%2==0).if{~m1}{~m2}} //synth lead
36+
{11}{(idx<4).if{~m1}{~m2}} //synth pad
37+
{12}{~m1} //synth fx
38+
{13}{case //'ethnic'
39+
{idx<=3}{~m1}
40+
{idx==4}{~m2}
41+
{idx==5}{group=2;~m2}
42+
{idx==6}{group=5;~m1}
43+
{idx==7}{group=8;~m2}
44+
}
45+
{14}{~m1} //percussive
46+
{15}{~m1} //sound fx
47+
;
48+
// \melody.postln;
49+
(port:port, chan:group)
50+
}{(prog<=256)||(prog>=265)}{
51+
// \drum.postln;
52+
(port:~m2, chan:12)
53+
}{prog-257 < 8}{
54+
// \anon.postln;
55+
(port:~m1, chan:0)
56+
}
57+
};
58+
~release_all = {arg vel=0;
59+
[~m1, ~m2, ~m3].do{arg port; 128.do{arg note; 16.do{arg chan;
60+
port.noteOff(chan, note, vel)}}}
61+
};
62+
~midi_send = {arg inst, pitch, vel;
63+
var port_chan = ~prog2portchan.(inst);
64+
var port = port_chan[\port];
65+
var chan = port_chan[\chan];
66+
(vel>0).if{
67+
port.noteOn(chan, pitch, vel.max(1));
68+
}{
69+
port.noteOff(chan, pitch);
70+
}
71+
}
72+
)
73+
74+
OSCdef.trace(false);
75+
76+
77+
// ok so how to actually 'schedule' things?
78+
// SystemClock one-offs should do?
79+
// schedule notochord-processing of each event for time of
80+
// oLatency before occurence
81+
// also schedule the actual event to occur
82+
// and set up a handle for the OSC return handler to
83+
// fill in any predicted fields
84+
85+
(
86+
~oLatency = 0.1; // must match value at tidal boot
87+
~ncLatency = ~oLatency-0.02; // slightly shorter than oLatency
88+
~last_t = Process.elapsedTime;
89+
90+
~events = Dictionary.new;
91+
~event_idx = 0;
92+
93+
// handle OSC input from tidal
94+
OSCdef(\from_tidal, {
95+
arg msg, time, src;
96+
var args = Dictionary.newFrom(msg[1..]);
97+
// `time` is the start time
98+
// args[\delta] is the duration
99+
\from_tidal.postln;
100+
time.postln;
101+
// [time - Process.elapsedTime].postln;
102+
args.postln;
103+
104+
// note-on events should arrive from tidal in the order they happen
105+
// but note-offs do not, since they are implicit in duration of notes
106+
// the only guarantee is nothing needs to happen sooner than tidal's
107+
// oLatency parameter
108+
// so we can schedule all note-on and note-offs as soon as they arrive
109+
// (without all sub-events filled in, possibly)
110+
111+
// anthing scheduled sooner than ncLatency (e.g. 100ms)
112+
// is 'frozen' and ready to (query_)feed notochord.
113+
114+
// schedule note-on
115+
~schedule_event.(
116+
time, args[\ncinst], args[\ncpitch], args[\ncvel]
117+
);
118+
// schedule note-off
119+
~schedule_event.(
120+
time+args[\delta], args[\ncinst], args[\ncpitch], 0
121+
);
122+
123+
// pop those off in order of occurence, and call feed or query_feed
124+
// in the case of query_feed, pass in some handle which tells the
125+
// return OSCdef where to put the result in the scheduler
126+
127+
// finally pop completed events as they cross the current time,
128+
129+
130+
//when instrument, pitch or velocity is negative or nil,
131+
//query notochord
132+
133+
}, "/notochord/tidal_feed");
134+
135+
// create event handle,
136+
// schedule processing by notochord,
137+
// and schedule triggering the event
138+
~schedule_event = {
139+
arg time, inst, pitch, vel;
140+
141+
var handle = ~event_idx.asSymbol;
142+
var event = (inst:inst?(-1), pitch:pitch?(-1), vel:vel?(-1));
143+
~events[handle] = event;
144+
~event_idx = ~event_idx+1;
145+
("scheduling event "++~event_idx).postln;
146+
147+
// schedule notochord processing
148+
SystemClock.schedAbs(time-~ncLatency, {
149+
// compute dt from previous event
150+
event[\time] = time - ~last_t;
151+
\processing.postln; event.postln;
152+
~last_t = time;
153+
(event[\time]<0).if{"warning: dt < 0".postln};
154+
155+
// if any parts not determined
156+
(event.collect(_<0).values.reduce('||')).if{
157+
// query_feed
158+
// include handle to event for writing the result
159+
~python.sendMsg(
160+
"/notochord/query_feed", \handle, handle,
161+
*~event_to_query.(event)
162+
)
163+
}{
164+
// else feed
165+
~python.sendMsg(
166+
"/notochord/feed", *event.asPairs
167+
)
168+
}
169+
});
170+
171+
// schedule event
172+
SystemClock.schedAbs(time, {
173+
// send to the synthesizer (MIDI bus or hacked dirth synth?)
174+
\playing.postln; event.postln;
175+
});
176+
177+
};
178+
179+
// convert an event (constructed in schedule_event from data received from tidal)
180+
// to query keyword args for notochord
181+
~event_to_query = {
182+
arg event;
183+
var query = List[];
184+
(event[\inst]>=0).if{query.add(\fix_instrument); query.add(event[\inst])};
185+
(event[\pitch]>=0).if{query.add(\fix_pitch); query.add(event[\pitch])};
186+
(event[\vel]>=0).if{query.add(\fix_vel); query.add(event[\vel])};
187+
query.add(\fix_time); query.add(event[\time]);
188+
query
189+
};
190+
191+
192+
// handle OSC return from python
193+
OSCdef(\from_python, {
194+
arg msg, time, src;
195+
var args = Dictionary.newFrom(msg[1..]);
196+
var event = ~events[args[\handle]];
197+
198+
// update the event
199+
event[\inst] = args[\instrument];
200+
event[\pitch] = args[\pitch];
201+
event[\vel] = args[\velocity];
202+
203+
~events.removeAt(\handle);
204+
205+
206+
// TODO: spit a warning if it trying to update
207+
// an event which should have happened already)
208+
// (thisThread.seconds > ...).if{
209+
// "warning: updating past event".postln; event.postln};
210+
211+
212+
}, "notochord/query_return");
213+
214+
)

0 commit comments

Comments
 (0)