MIDISyncClock


A hack to try to slave sc to external MIDI clock messages.


A singleton. No instance methods; the entire interface is through the class.


Limitations: All events are quantized to the nearest tick.

Timing may not be reliable in high-pressure situations.

Written in sc, not in c++, so it may be CPU hungry.

Beats counter is OK. MIDISyncClock.seconds returns elapsed seconds since clock start.


The clock responds to three kinds of MIDI clock messages:

clock: Advances the clock by one tick (1/24 beat). The number of ticks per beat is configurable.

start: Reset all counters to 0. This ensures that the MIDISyncClock will be synced with the MIDI clock.

stop: Clear the queue. Since the clock is not running and times on the queue will be invalid once

the clock restarts, the queue should be emptied.


Methods:


*init(argBeats, argSeconds)


Initialize the clock. If MIDIClient is not initialized, this method will do it and connect the sources sequentially to MIDIIn.


Note that MIDIIn.sysrt receives clock messages ONLY from the port assigned to MIDIIn inport 0. If your clock source is not at MIDIClient.sources[0], make sure to connect it manually.


*schedAbs(when, task)


Schedule a task on a specific beat.


*sched(when, task, adjustment = 0)


Schedule a task for "when" beats from now. The adjustment argument is used internally to maintain correct timing of event streams. Normally you should not use this argument.


*play(task, when)


Play a task based on a quantization factor specified as "when." This uses [TimeSpec] objects in my library. Normal sc syntax (when = a simple number specifying beats to round up) will work as usual.


*clear


Empty the queue.


*tempo

*beatDur


Estimates of the tempo, and duration of one beat. There is some jitter in receiving MIDI messages; therefore these numbers can never be exact.



Using Event Streams with MIDISyncClock:


Note, these instructions are no longer valid due to changes in the class library. I'll figure out how to make it work later but don't have time at the moment.


To play Pbinds and such, you'll need to make some hacks to EventStreamPlayer and Event. These changes have not been approved by jmc (and I expect he'll have issues with them), so don't count on them. But they do make the clock work!


EventStreamPlayer - add the following before the "clock.play..." line:


event = event.copy.put(\clock, clock);


Event - in the schedBundle and schedBundleArray functions:


near the bottom, change the following line:

thisThread.clock.sched(sustain) { 

to read:

(~clock ? thisThread.clock).sched(sustain) { 



Example:


MIDIClient.init;

MIDIIn.connect(0, MIDIClient.sources[0]);  // or which device holds the clock


MIDISyncClock.init(0, 0); // 0 beats, 0 secs


// start sending MIDI clock messages


s.boot;


t = Task({

var synth;

loop {

#[60, 62, 64, 65, 67, 65, 64, 62].do({ |note|

s.makeBundle(0.2, {

synth = Synth(\default, [freq: note.midicps])

});

s.makeBundle(0.4, {

synth.release;

});

0.25.wait;

});

}

}).play(MIDISyncClock, quant: 1);


SynthDescLib.global.read;


t = Pbind(\midinote, Pseq([60, 62, 64, 65, 67, 65, 64, 62], inf),

\delta, 0.25, \dur, 0.25, \instrument, \default)

.play(MIDISyncClock, quant:4);


// play with the tempo of the midi clock. sc should adjust accordingly.


t.stop;