Trigger Pbind / patterns with external clock?

hi,
i am trying to sync pbind / patterns with a external midi clock (hardware).
i was not able to get any tight results without drifts with MIDISyncClock http://quark.sccode.org/ddwMIDI/Help/MIDISyncClock.html

(
~myPattern = Pbind(
    \intrument, \default,
    \dur, 1,
    \amp, 1
).play(MIDISyncClock, quant:4);
)

my attempts to sync and control TempoClock.tempo by MIDIdef.sysrt (with the calculated delta ms of the ticks)
where very prone to other language or gui events and less reliable than MIDISyncClock.

but using the external midi ticks (96th) directly as a clock works quite well.

(
~myPattern = Pbind(
    \intrument, \default,
    \dur, 1,
    \amp, 1
).asStream;
)
(
var count = 0;
MIDIdef.sysrt(\mymidiclock, {arg ...args;
    switch(args[2],
        8, {// midi ticks
            if((count%12==0), { // speedlimit: 96th/24 = 4th
                ~myPattern.next(Event.new).play           
            });
            count = count +1;
        }
    );
});
)

i would like to be able to switch between internal and external clock and keep the full functionality of pbind / patterns like, the timing, Ppar etc…
is there another way to clock patterns without converting them to streams ?

thanks in advance for any hints

MIDISyncClock is my work.

I’m afraid it isn’t very well tested because I don’t own an external device that sends MIDI ticks. I have tested it in the past by sending MIDI clock messages from another sclang instance. In principle there’s no reason why it should be any worse than reading ticks directly.

Is it 96 ppq? Or 96 pulses per bar?

Few things off the top of my head:

  • “Using Event Streams with MIDISyncClock” – this section is no longer necessary. You don’t have to make these hacks anymore. I changed MIDISyncClock some months ago to make that unnecessary. (sccode.org has an old version of the help file.) https://github.com/jamshark70/ddwMIDI/commit/bb3c90c32f5999d74cefd87fdd4cbb6d7139b71b

  • The new, SCDoc-format help file adds a note: “Set the server’s latency as low as possible. MIDI clock messages assume the receiving device will play instantaneously; there is no compensation for OSC messaging latency.” s.latency is 200 ms by default, so, if you haven’t explicitly set latency, every event is probably 200 ms late. You might try s.latency = nil.

  • The new version of the help file also recommends to initialize MIDIClient and MIDISyncClock first, and only after that starting the clock source. MIDI ticks do not send any position within the bar. The only way to be sure barlines are synced is if MIDISyncClock is listening for start/stop/tick messages before the external clock actually sends them.

hjh

i really had an old version of your quark. i installed it for some reasons by hand.

it runs now much much better. thank you !
with buffersize set to 64 and s.latency set to zero i measure something like 9ms latency

there is one error i experience:
Pdef’s throw an “Message ‘timeToNextBeat’ not understood.” error.

(
SynthDef(\clicks,{|freq=5000|
	Out.ar(0,
		SinOsc.ar(3000!2, 0, 0.5) * EnvGen.kr(Env.perc(0.001,0.01),doneAction: 2)
	);
}).add
)
(
Pdef(\test2,
	Pbind(\instrument, \clicks,\dur, 1/2)
)
)
Pdef(\test2).play(MIDISyncClock, quant: 1)
Pdef(\test2).stop

if i evaluate Pdef(\test2 a second time while playing it throws that error.

Pbindef’s behave like expected.

Pbindef(\test1,\instrument, \clicks,\dur, 1/2);
Pbindef(\test1).play(MIDISyncClock, quant: 4);
Pbindef(\test1).stop;

here is the complete error message

ERROR: Message 'timeToNextBeat' not understood.
RECEIVER:
class MIDISyncClock (0x107daa0) {
  instance variables [19]
    name : Symbol 'MIDISyncClock'
    nextclass : instance of Meta_MIDISysDataDispatcher (0x16e4820, size=19, set=5)
    superclass : Symbol 'Object'
    subclasses : nil
    methods : nil
    instVarNames : nil
    classVarNames : instance of SymbolArray (0x107db60, size=15, set=3)
    iprototype : nil
    cprototype : instance of Array (0x107dbe0, size=15, set=4)
    constNames : nil
    constValues : nil
    instanceFormat : Integer 0
    instanceFlags : Integer 0
    classIndex : Integer 615
    classFlags : Integer 0
    maxSubclassIndex : Integer 615
    filenameSymbol : Symbol '/root/.local/share/SuperCollider/downloaded-quarks/ddwMIDI/MIDISyncClock.sc'
    charPos : Integer 176
    classVarIndex : Integer 130
}
ARGS:
   Float 1.000000   00000000 3FF00000

PROTECTED CALL STACK:
	Meta_MethodError:new	0xffb160
		arg this = DoesNotUnderstandError
		arg what = nil
		arg receiver = MIDISyncClock
	Meta_DoesNotUnderstandError:new	0xffc320
		arg this = DoesNotUnderstandError
		arg receiver = MIDISyncClock
		arg selector = timeToNextBeat
		arg args = [ 1 ]
	Object:doesNotUnderstand	0xb13c20
		arg this = MIDISyncClock
		arg selector = timeToNextBeat
		arg args = nil
	EventPatternProxy:constrainStream	0x12afd20
		arg this = Pdef('test2')
		arg stream = a Routine
		arg newStream = a Routine
		arg inval = (  )
		arg cleanup = an EventStreamCleanup
		var delta = nil
		var tolerance = nil
		var quantBeat = 0
		var catchUp = nil
		var deltaTillCatchUp = nil
		var forwardTime = nil
		var quant = 1
	EventPatternProxy:embedInStream	0x12af960
		arg this = Pdef('test2')
		arg inval = (  )
		arg embed = true
		arg default = nil
		var outval = ( 'instrument': clicks, 'msgFunc': a Function, 'dur': 0.5, 'amp': 0.1,
  'server': localhost, 'sustain': 0.4, 'isPlaying': true, 'freq': 261.6255653006, 'hasGate': false,
  'id': [ 1209 ] )
		var count = 0
		var pat = a Pbind
		var test = true
		var resetTest = nil
		var stream = a Routine
		var cleanup = an EventStreamCleanup
	Routine:prStart	0x1591a60
		arg this = a Routine
		arg inval = (  )

CALL STACK:
	DoesNotUnderstandError:reportError
		arg this = <instance of DoesNotUnderstandError>
	Nil:handleError
		arg this = nil
		arg error = <instance of DoesNotUnderstandError>
	Thread:handleError
		arg this = <instance of Routine>
		arg error = <instance of DoesNotUnderstandError>
	Object:throw
		arg this = <instance of DoesNotUnderstandError>
	Object:doesNotUnderstand
		arg this = <instance of Meta_MIDISyncClock>
		arg selector = 'timeToNextBeat'
		arg args = [*1]
	EventPatternProxy:constrainStream
		arg this = <instance of Pdef>
		arg stream = <instance of Routine>
		arg newStream = <instance of Routine>
		arg inval = <instance of Event>
		arg cleanup = <instance of EventStreamCleanup>
		var delta = nil
		var tolerance = nil
		var quantBeat = 0
		var catchUp = nil
		var deltaTillCatchUp = nil
		var forwardTime = nil
		var quant = 1
	EventPatternProxy:embedInStream
		arg this = <instance of Pdef>
		arg inval = <instance of Event>
		arg embed = true
		arg default = nil
		var outval = <instance of Event>
		var count = 0
		var pat = <instance of Pbind>
		var test = true
		var resetTest = nil
		var stream = <instance of Routine>
		var cleanup = <instance of EventStreamCleanup>
	Routine:prStart
		arg this = <instance of Routine>
		arg inval = <instance of Event>
^^ The preceding error dump is for ERROR: Message 'timeToNextBeat' not understood.
RECEIVER: MIDISyncClock

That is very good to hear! I had run some reasonably acceptable tests, but hadn’t gotten as far as a real-world test with external hardware, so I wasn’t sure how it would actually fare.

That’s a quick fix – I just pushed that method into the https://github.com/jamshark70/ddwMIDI.git repository. Let me know if it works.

hjh

it works !
thank you very much.this is helping me a lot