Patterns Synced to CV Clock

the feeling I am getting from this thread is that patterns represent a powerful way to organise and sequence audio events.

my main problem with patterns is synchronisation - I want to be able to synchronise with a cv clock at a hardware input.

at the moment, I can collect the interval between clock ticks using a synth with Timer.ar that sends back a OSC message using SendTrig.ar:

(
SynthDef (\interval_getter) {
	arg bus = 0, thresh = 0.5;
	var sig, val;
	sig = SoundIn.ar (bus);
	sig = Compander.ar (1, sig, thresh, 10, 1, 0.01, 0.01);
	sig = Trig1.ar (sig, 0.01);
	val = Timer.ar (sig);
	sig = (val > 0.05) * sig;
	val = Timer.ar (sig);
	SendTrig.ar (sig, 0, val);
}.add;

~osc_listener = OSCFunc ({
	arg interval;
	Synth (\triDing);
	interval.postln;
}, '/tr', s.addr);
)

it’s not super pretty, but it works.

but, the OSC message is super late and together with sclang latency, it is not tenable to trigger either patterns or individual synths from inside the listener.

I could clock directly from a bus, but that would limit me to server side sequencing.

is there a way to use the powerful laziness of patterns with CV clock?

I’m not sure what it would be that I would be building - potentially a Clock subclass that listens to an input channel and, when triggered, synchronously evaluates events that land on that beat, while scheduling those events that land between this beat and the next using the interval time between the last beat and this one?

such a clock would work similarly to an analog sequencer module, always waiting for the next CV impulse to push forward a step, but would retain all of the potential complexity and power of patterns.

is this possible?

This is way outside anything I do so this is really spitballing.

But if I was to approach this I would probably use demand side UGens that send out my patterns, that were clocked to Timer. And then use patterns to find buffers that were read by the demand side UGens. And then you could use the clock as a trigger for checking those buffers in some way. So that clock should send that signal to an audio bus that everything else checks (make sure the clock is at the beginning of that bus).

So conceptually the idea would be that patterns send updates on some regular basis for the next clock cycle (whatever that is - 1/4 note, 1/8 note - whatever) - and your synths check the new buffers on the next clock cycle.

Also, remember that you can dynamically create synths whenever (and those synths can sync themselves to the clock). So you could just create sequence patterns dynamically (maybe using the functional composition patterns), and trigger when those are created using patterns.

But my guess is that you’re going to have to play around with this.

Also you may not need to use audio rate for control rate stuff - control rate may be fast enough. That will make your life easier in some ways.

Sure, here’s a simplified example: a trigger from server and an (untimed) stream of Events. As always, latency is your friend, you must invlove latency to get exact timing, though you can shorten it down to 0.05 sec or so.

(
p = Pbind(
    \instrument, \test,
    \midinote, Pseq((60..67), inf), 
    \pos, Pseq([-1, 1], inf)
);

// we just need untimed next events (an event stream)
a = p.asStream;

SynthDef(\test, { |out = 0, freq = 400, pos = 0, amp = 0.1|
    OffsetOut.ar(out, Pan2.ar(SinOsc.ar(freq, 0, amp), pos) * EnvGen.ar(Env.perc, doneAction: 2))
}).add;

SynthDef(\trig, { |trigRate = 5|
    SendReply.ar(Impulse.ar(trigRate), '/trig', WhiteNoise.ar());
}).add;

~osc_listener = OSCFunc( { |msg|
    msg[3].postln; // you can even use server data in the pattern
    s.makeBundle(0.2, { a.next(()).play }); // empty Event () has to be passed for next Event
}, '/trig');
)

// start 

x = Synth(\trig)

x.set(\trigRate, 10)

// stop

x.free

BTW I’ve developed a whole system for using server data in the language (HS / PHS, HSpar / PHSpar). Probably it isn’t easily applicable here as you want especially the timing driven by server whereas HS/PHS etc. are designed for taking over arbitrary other data from server …
However your idea should be easily possible with bundling as above. I’ve defined fixed-length envelopes here, which is easier, with gated envelopes you would have to define releasing too.

Daniel

2 Likes

legennd. thank you.

ima try get this going when I get home :rocket:

But … yet this is not a perfectly timed solution. Still you have the uncertainty of the time, which the messages need from server to language! So to get really good precision you would have to define a latency for this too, send a time stamp or a time difference with the trigger to lang and use this data there together with the lang-to-server latency for the final timing. I don’t have time to work out an example for this at the moment, sorry.

But this all is doable and with latency you can get sufficiently good timing also in combination with synths started in parallel. With “sufficiently good” I mean the maximum you can get with RT sequencing. Due to a RT calibration of timing which depends on the hardware driver’s blocksize we don’t get sample-accurate lang-based timing in SC. I’ve given an example for this some years ago, but can’t find it in the archives now (the underlying problems seem to be very complex and are beyond my knowledge). Will repost on occasion, I think it’s an important point: sample-accurate lang-based timing (as with patterns) is only possible in NRT mode.

1 Like