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

Commit efad23d

Browse files
fluidsynth support
1 parent b653c86 commit efad23d

1 file changed

Lines changed: 70 additions & 14 deletions

File tree

examples/notochord/tidalcycles/tidal-notochord.scd

Lines changed: 70 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)