@@ -10,7 +10,8 @@ thisProcess.openUDPPort(6789); // from tidal
1010// ~dirt.start(57120);
1111// };
1212// )
13-
13+ //
14+ // ~dirt.soundLibrary.getEvent('matrix_bass', 1)
1415
1516
1617// for sending MIDI to ableton
@@ -85,10 +86,59 @@ MIDIClient.init;
8586};
8687)
8788
88- // OSCdef.trace(false);
89+ // for sending MIDI to fluidsynth
90+ // fluidsynth -a coreaudio -m coremidi -v -o midi.autoconnect=1 -o synth.midi-bank-select=mma ~/Downloads/FluidR3_GM_GS.sf2
91+ (
92+ ~channel_lru = LinkedList .fill(16 , {arg i; i});
93+ ~inst_channels = TwoWayIdentityDictionary .new;
94+ ~sema = Semaphore (1 );
95+
96+ MIDIClient .init;
97+ ~m1 = MIDIOut .newByName("IAC Driver" , "Bus 1" ).latency_(0 );
98+
99+ ~release_all = {arg vel=0 ;
100+ [~m1 ].do{arg port; 128 .do{arg note; 16 .do{arg chan;
101+ port.noteOff(chan, note, vel)}}}
102+ };
103+ ~midi_send = {arg inst, pitch, vel;
104+ var channel;
105+ ~sema .wait;
106+ // check if this instrument has a channel
107+ channel = ~inst_channels .at(inst);
108+ channel.isNil.if{
109+ // if not get least recently used channel
110+ channel = ~channel_lru .popFirst;
111+ // and change the bank+program
112+ ((inst>128 )&&(inst<=256 ))||(inst>=265 ).if{
113+ ~m1 .control(channel, 0 , 1 ); //drum
114+ ~m1 .control(channel, 32 , 0 );
115+ ~m1 .program(channel, inst-128 ); //program
116+ }{
117+ ~m1 .control(channel, 0 , 0 ); //melodic
118+ ~m1 .control(channel, 32 , 0 );
119+ ~m1 .program(channel, inst-1 ); //program
120+ };
121+ // TODO: anonymous instruments?
122+
123+ }{
124+ ~channel_lru .remove(channel)
125+ };
126+ // send the event
127+ (vel>0 ).if{
128+ ~m1 .noteOn(channel, pitch, vel);
129+ }{
130+ ~m1 .noteOff(channel, pitch);
131+ };
132+ // update the inst / channel mappings
133+ ~channel_lru .add(channel);
134+ ~inst_channels .remove(channel);
135+ ~inst_channels [inst] = channel;
136+ ~sema .signal;
137+ };
138+ )
89139
90140(
91- ~oLatency = 0.1 ; // must match value at tidal boot
141+ ~oLatency = 0.1 ; // should match value at tidal boot
92142~ncLatency = ~oLatency -0.02 ; // slightly shorter than oLatency
93143~last_t = Process .elapsedTime;
94144
@@ -101,11 +151,12 @@ OSCdef(\from_tidal, {
101151 var args = Dictionary .newFrom(msg[1 ..]);
102152 var delta = args[\delta ] * (args[\legato ]?1 );
103153 // `time` is the start time
104- // args[\delta] is the duration
105- \tidal_feed .postln;
106- time.postln;
154+ // delta is the duration
155+
156+ // \tidal_feed.postln;
157+ // time.postln;
107158 // [time - Process.elapsedTime].postln;
108- args.postln;
159+ // args.postln;
109160
110161 ((args[\ncreset ]?0 ) > 0 ).if{
111162 \reset .postln;
@@ -117,14 +168,17 @@ OSCdef(\from_tidal, {
117168
118169 // note-on events should arrive from tidal in the order they happen
119170 // but note-offs do not, since they are implicit in duration of notes
120- // the only guarantee is nothing needs to happen sooner than tidal's
121- // oLatency parameter
171+ // the only guarantee is they won't needs to happen sooner than tidal's
172+ // oLatency parameter, since that's the soonest a new note-on can occur
122173 // so we can schedule all note-on and note-offs as soon as they arrive
123174 // (without all sub-events filled in, possibly)
124175
125176 // anthing scheduled sooner than ncLatency (e.g. 100ms)
126177 // is 'frozen' and ready to (query_)feed notochord.
127178
179+ // so we will schedule notochord processing to happen ~ncLatency
180+ // before the note-on,
181+ // ensuring the event gets updated before it is time to play it
128182 ~schedule_events .(
129183 time, delta, args[\ncinst ], args[\ncpitch ], args[\ncvel ]
130184 );
@@ -134,21 +188,21 @@ OSCdef(\from_tidal, {
134188
135189// create event handle,
136190// schedule processing by notochord,
137- // and schedule triggering the event
191+ // and schedule triggering the note-on event and corresponding note-off
138192~schedule_events = {
139193 arg time, delta, inst, pitch, vel;
140194
141195 var handle = ~event_idx .asSymbol;
142196 var event = (inst: inst?(-1 ), pitch: pitch?(-1 ), vel: vel?(-1 ));
143197 ~events [handle] = event;
144198 ~event_idx = ~event_idx +1 ;
145- ("scheduling event " ++~event_idx ).postln;
199+ ("scheduling event " ++handle ).postln;
146200
147201 // schedule notochord processing
148202 SystemClock .schedAbs(time-~ncLatency , {
149203 // compute dt from previous event
150204 event[\time ] = time - ~last_t ;
151- \ processing .postln; event.postln;
205+ ( " processing event " ++handle) .postln; event.postln;
152206 ~last_t = time;
153207 (event[\time ]<0 ).if{"warning: dt < 0" .postln};
154208
@@ -173,7 +227,7 @@ OSCdef(\from_tidal, {
173227 // completed the event (if it needed a query to notochord)
174228 SystemClock .schedAbs(time, {
175229 // send to the synthesizer (MIDI bus or hacked dirth synth?)
176- \note_on .postln; event.postln;
230+ ( "starting note " ++handle) .postln; event.postln;
177231 ~any_missing .(event).if{
178232 "incomplete note-on" .postln;
179233 }{
@@ -186,7 +240,7 @@ OSCdef(\from_tidal, {
186240 // in the future
187241 SystemClock .schedAbs(time+delta, {
188242 // send to the synthesizer (MIDI bus or hacked dirth synth?)
189- \note_off .postln; event.postln;
243+ ( "ending note " ++handle) .postln; event.postln;
190244 ~any_missing .(event).if{
191245 "incomplete note-off" .postln;
192246 }{
@@ -227,6 +281,8 @@ OSCdef(\from_python, {
227281 var args = Dictionary .newFrom(msg[1 ..]);
228282 var event = ~events [args[\handle ]];
229283
284+ ("updating event " ++args[\handle ]).postln;
285+
230286 // update the event
231287 event[\inst ] = args[\instrument ];
232288 event[\pitch ] = args[\pitch ];
0 commit comments