Re-Sync MIDI Event


#1

question about Psync, and about how quant works in general.

I have three patterns in a Ppar that play are rhythmically synchronised initially, but then I change the \delta of one of them, momentarily, which desynchronises it from the other two patterns.

I want to be able to resynchronise the pattern using a midi note on message coming from a pad on my LPD8.

what is the architecture here? I am looking at Psync and quant, but it isn’t quite clicking how I should be structuring my Pdefns, etc.

code is here.


#2

A bunch of these Pattern synchronization questions have been coming up lately! They’re hard because they can require some deep-ish scheduling mojo to solve correctly.

For starters, I don’t think Psync or quant will really solve the problem for you? Or — quant might, but if so I’m not quite getting my head around it either.

I think your challenge is something like this: at some point, you push a button on your LPD8, which means your patterns should sync back up. Lets assume all three patterns are waiting to play their next even - meaning, a “next()” for each of those patterns is scheduled at some point in the future. Moreover, since you’re tweaking deltas, they can be each scheduled at different TIMES in the future. The best you could do here is set up a condition for each pattern where - once the button is pushed, the next time each of those patterns is next()'d, they start waiting again until some FURTHER point in the future where they all re-awake at the same time.

There are two downsides to this - the first is, the soonest you can get back in sync is once ALL of your patterns have woken up - this could be bad if some of them have long-ish deltas. The second downside: this would be sort of a pain in the butt to engineer without some deep pattern hacking. However, it would be possible.

Here are a few things you might try:

  1. The \timingOffset key allows you to bump the scheduled times of events forward and backward in time (iirc they can only go back in time as much as the \latency key, though you can always just make that big too…). You could really easily use Pbindef's, and change your \timingOffset from the outside. When you want to re-sync, just set them all back to 0 - the next event from each pattern will be an offset one, but after that scheduling will be back to synced again.
  2. Skip Ppar and just run each of your Pbind's on a separate TempoClock. Then, you have access to things like TempoClock:setTempoAtBeat and TempoClock:beats_, which should give you the ability to dynamically change the running speed of each pattern. I’m not TOTALLY sure how TempoClock works when you e.g. fast-forward to a later point in time by setting beats to a higher value. If you want to avoid weird timing problems, I think you have to schedule beats changes on the requisite TempoClock - so it fast-forwards itself. Ideally, fast-forwarding beats would cause it to play scheduled events, so your streams would “catch up”. I don’t recall having tested this theory so who knows :slight_smile:. The only tricky thing about not using Ppar is you’ll have to play all three at the same time, but that’s not so bad.
  3. This seems to work:
Pdef(\dur, { Pbind(
	\dur, Pseq([1], 10) ++ Pwrand([1, 1.5], [0.8, 0.2], inf)
)
});

(
Ppar([
	Pbind() <> Pdef(\dur),
	Pbind() <> Pdef(\dur),
	Pbind() <> Pdef(\dur),
]).trace.play;
)

These will each drift out of sync after the first ten events - but, if you re-define Pdef(\dur), they re-sync soon after (after each of their next events play). This is where quant comes in (since it controls where new Pdef definitions take over for the old one), but in some quick testing, I didn’t need to set a quant to get a musically reasonable result.

Post about how you solved the problem, especially since these kinds of issues have been such a hot topic lately!


#3

First, a sidenote – do the patterns have to be in Ppar? Rejiggering their timing will be extremely/incredibly/maybe impossibly hard within Ppar, but actually not that difficult if they are playing in separate stream players.

I think sometimes people use Ppar when it is not actually necessary.

Then…

Back in the day, I had an idea about playing notes on a MIDI keyboard, and then hitting a MIDI button to stop recording notes and seamlessly hand them over to some algorithmic process. I ended up abandoning this idea because…

If you hit the MIDI triggers in time with the music that you’re hearing, then it’s late compared to the time that the patterns evaluate. You hit the controller at the time when you hear the barlines, but sclang’s clock is at barline + 200 ms.

The best I could have done would be to skip any MIDI notes in the gap at the beginning of the metric cycle, and “catch up” with the next note to sound after the clock time. But at that time I wasn’t clever enough, and it wasn’t important enough to what I wanted to do, so I dropped it.

The point is:

  • If you set s.latency = nil, then pattern evaluation and sound are as close as possible in time (easier to handle MIDI triggers), but notes’ onset times will be quantized to the audio driver buffer size = inexact timing = maybe groove if you’re lucky, but maybe just garbage.

  • If you set s.latency to a number that is too small, you get a lot of late messages and notes’ onset times are still quantized.

  • If you set s.latency to a number that is big enough for good timing, then you have the problem that live MIDI triggers will be later than the processing that they’re supposed to control.

hjh


#4

part of me feels that what I need is a Pattern that inserts one Psync value into the Pdefn (\quaver_delta) stream, and then returns it to its previous state.

but even if such a Pattern existed, I’m not sure exactly what the Psync would be syncing to.

Ima dive a bit deeper into TempoClock, see what I can come up with.

edit: here is a code block to give you a sense of how I’m structuring things so far. I don’t think the .quant method is really doing anything at the moment.

Pdefn (\bar_length) {
	var length = 2.20116;
	Pseq (length.asArray, inf);
};

Pdefn (\root_freq) {
	var freq = 87.307057858251; // F
	Pseq (freq.asArray, inf);
};

Pdefn (\quaver_delta) {
	var amount, min, max, dif, dur;
	amount = Pdefn (\knob_3) / 127;   // grabs the cc value
	amount = amount ** 2.reciprocal;  // shapes the curve
	min = Pdefn (\bar_length) / 16;   // when at minimum, the pattern plays semiquavers
	max = Pdefn (\root_freq) * 3 / 2; // when at maximum, the pattern plays a pitch
	max = max.reciprocal;             // length, not rate
	dif = min - max;
	dif = dif * amount;
	dur = min - dif;
};

Pdef (\quavers) {
	Pbind (
		\instrument, \tri_drum,
		\delta, Pdefn (\quaver_delta),
		\length, Pkey (\delta) / 16,
	);
}.quant_ (2.20116);

// ~~~~~~~~~~~~~~~

MIDIdef.cc (\a_sweat_lpd8_control) {  //  function to execute when recieving a cc msg
	arg ...args;
	var k, v, p;
	k = args[1];
	v = args[0];
	p = Pseq (v.asArray, inf);

	switch (k)
	{ 3 }  {
		Pdefn (\knob_3) { p };
	}

	// ("k " ++ k ++ ", v " ++ v).postln;
}.permanent_ (true);


#5

First off, scztt said in the first reply that Psync is really not the object you need for this. He’s correct about that. (One sign of possible misunderstanding is when you say “inserts one Psync value”… Psync is an event pattern – it requires events as input and outputs events – it’s not compatible with “values.”)

Psync help:

Psync behaves somewhat like Pfindur – it has a maxdur argument that limits the total duration of the event stream.

The difference is in what happens if the event pattern stops on its own before maxdur is reached. If the total duration of the event pattern is shorter than the given maximum duration:

  • Pfindur simply ends: no further time manipulation.
  • Psync inserts a rest to round the total duration up to the nearest multiple of the given quant.

I do kind of get the reasoning – you’re thinking about synchronizing things and Psync sounds related. But it’s not the droid you’re looking for.

The sync itself wouldn’t be too hard in a Prout wrapper. General form:

~generalPatternWrapper = { |pattern|
	Prout { |inval|
		var stream = pattern.asStream,
		out;
		while {
			out = stream.next(inval);
			out.notNil
		} {
			inval = out.yield;
		};
		inval
	};
};

For sync, you would insert logic into either the condition or loop-body functions to detect that sync is needed, and then yield an Event.silent. (Into the condition function, if you want to sync first and then get the next output value; into the body function if you want to get the output value and then sync before yielding the output. I’ll write the first way.)

~syncWrapper = { |pattern|
	Prout { |inval|
		var stream = pattern.asStream,
		out;
		while {
			if( **sync is needed** ) {
				**clear sync flag**;
				inval = Event.silent( **next sync point** - thisThread.beats).yield;
			};
			out = stream.next(inval);
			out.notNil
		} {
			inval = out.yield;
		};
		inval
	};
};

That leaves:

  • **sync is needed** – you’ll have to set some flag, and then clear it.
  • **next sync point** – I’m not going to guess right now – that’s up to you.

Couple of other things:

  • Do you want the pattern to reset at sync time? If so, just stream = pattern.asStream just after yielding the Event.silent.
  • What do you want to happen between requesting sync and the next barlines? Silence? It should keep going (out of sync) and then line up on the next barlines? Or it should try to figure out the metrical alignment of the next event and sync that?
  • As scztt said, for example, if barlines are every 4 beats, and you’re currently on beat 7, and the next event is scheduled for beat 10, and you want it to resync to beat 8, the above approach will not work by itself – because the pattern will not wake up until 10, and it’s too late to go back to 8. It might be okay not to have instantaneous resync.

hjh


#6

fantastic James - this has helped immensely, thank you. :pray:

I’ll report back if I get a neat solution working.


#7

edit 2: ok so nope, simply resetting with a .quant value doesn’t actually work every time. I am confuse


edit: ok so the simplest way to do this in this case (where it is fairly inconsequential to reset the Pbinds), the easiest and neatest solution is to simply:

Pdef (\parallel).quant (1.5).reset;

ok. so in lieu of @jamshark70’s comments, maybe it would be simpler to trigger the next event of the out of sync Pbind, with the next event of the Pbind we want to re-sync to?

for example, in the following code, is it possible to modify Pdef (\beat_one) to trigger the next Pdef (\quavers) event?

(
SynthDef (\drum) {
	arg freq = 330, pan = 0, amp = 1;
	var sig, env;
	env = Env.perc (0, 0.2, amp);
	env = EnvGen.kr (env, doneAction: 2);
	sig = LFTri.ar (freq);
	sig = Pan2.ar (sig, pan, env);
	Out.ar (0, sig);
}.add;

SynthDef (\kick) {
	arg len = 0.3, pan = 0, amp = 1, freq = 55;
	var sig, env_amp, env_freq;

	env_amp  = Env.perc (0.01, len - 0.01, amp);
	env_amp  = EnvGen.kr (env_amp, doneAction: 2);

	env_freq = Env.perc (0, 0.02, 1);
	env_freq = EnvGen.ar (env_freq);

	sig = SinOsc.ar (freq + (44100 ** env_freq), 0, env_amp);
	sig = Pan2.ar (sig, pan);

	Out.ar (0, sig);
}.add;

Pdefn (\bar_length) {
	var length = 1.5;
	Pseq (length.asArray, inf);
};

Pdef (\quavers) {
	Pbind (
		\instrument, \drum,
		\freq, 880,
		\delta, Pdefn (\bar_length) / pi,
		\amp, 1,
	);
};

Pdef (\beat_one) {
	Pbind (
		\instrument, \kick,
		\freq, 55,
		\delta, Pdefn (\bar_length),
		\amp, 1
	);
};

Pdef (\parallel) {
	Ppar ([ Pdef (\quavers), Pdef (\beat_one) ], inf);
};
)

Pdef (\parallel).trace.play;

#8

But, you don’t want to trigger only the one event, do you? You would like \quavers to continue on its own after that point, right?

A few things are still not clear to me about your use case. I can think of a number of things that could happen when you hit the MIDI trigger:

  1. The pattern might continue out of sync until the next barline, and then reset to the beginning of the pattern on that barline.

  2. Or, it might stop playing out of sync notes until the next barline (rest until the barline), and then start from the beginning of the pattern on that barline.

  3. Or, it might immediately determine the next note that would play if it were in sync, and begin playing from that point in the pattern at the right time.

I have no idea which of these you want.

1 or 2 are fairly straightforward with a rescheduling function – it’s fairly easy to transfer control to a different EventStreamPlayer at any time you like. Because Pdef manages the stream player internally, it would probably have to be a new method – but it’s not that hard. It just depends on the idea that a stream player is disposable – the stream carries the pattern’s state, not the stream player, so there is nothing precious or valuable about the player. Just toss it and schedule a new one.

+ EventPatternProxy {
	reschedule { |beats, doReset = false, clock|
		var stream;
		if(this.isPlaying.not) {
			this.play(clock, doReset, beats)
		} {
			stream = player.originalStream;
			player.stop;
			// omit 'player.event' for a TaskProxy equivalent
			player = player.class.new(stream, player.event)
			.play(clock ?? { player.clock }, doReset, beats);
		}
	}
}
// rescheduling demo
Pdef(\x, Pbind(\degree, Pn(Pseries(0, 1, 4), inf), \dur, 1)).play;

// oops, didn't 'quant' to barline
// so, reschedule for the next bar and reset
Pdef(\x).reschedule(Pdef(\x).clock.nextBar, doReset: true);

// or you can do something odd like skip one beat
Pdef(\x).reschedule(Pdef(\x).player.nextBeat + 1);

Pdef(\x).stop;

3 is more difficult because you need to be able to scan the pattern’s contents to find the next note to play. Pdef does have an outset method which (I think) is supposed to do something like that, but I haven’t tried it.

The technique to use might be different depending on the behavior you want – so, at this point, I would suggest that flailing around with the code has not been a wholly successful strategy so far, and it would be better to clarify the desired behavior.

hjh


#9

edit: ok so I am just going to flesh out H James’ solution for the sake of completeness.

the + EventPatternProxy code block in the above post extends the EventPatternProxy class, and as such needs to be saved in an .sc file in the extensions folder (see File → Open user support directory). once the file is saved, the class library needs to be recompiled (Language → Recompile Class Library, or ⇧⌘L).

from the Writing Classes documentation:

Methods may be added to Classes in separate files. This is equivalent to Categories in Objective-C. By convention, the file name starts with a lower case letter: the name of the method or feature that the methods are supporting.

also, you need to be using TempoClock to set your tempo, which is sensible to do anyhow, and easy enough. The tempo is given in beats per second, and by addressing the default TempoClock like so: TempoClock.tempo_ (0.8); will cause \delta values in any Pbinds you have will be multiplied by the beat length (ie. 1.25 seconds in this case, to give a default tempo of 48 bpm).

TempoClock gives you options to specify beats per bar, etc. but the following example shows how you can use the .nextTimeOnGrid method to simply snap to the beat, eschewing the need for bars altogether:

(
SynthDef (\kick) {
	var amp_env, freq_env, sig;
	amp_env = EnvGen.kr (Env.perc (0.01, 1, 0.5, -8), doneAction: 2);
	freq_env = 3675 ** EnvGen.kr (Env.perc (0, 0.02)) + 55;
	sig = SinOsc.ar (freq_env) * amp_env;
	sig = Pan2.ar (sig);
	Out.ar (0, sig);
}.add;

SynthDef (\tri_drum) {
	arg freq = 660;
	var sig, env;
	env = Env.perc (0.01, 0.8, 0.2, -8);
	env = EnvGen.kr (env, doneAction: 2);
	sig = LFTri.ar (freq) * env * PinkNoise.ar (0.8, 0.2);
	sig = Pan2.ar (sig);
	Out.ar (0, sig);
}.add;

Pdef (\one_kick) {
	Pbind (
		\instrument, \kick,
		\delta, Pseq ([ 3, 3, 2 ] / 8, inf),
	);
};

Pdef (\snare) {
	Pbind (
		\instrument, \tri_drum,
		\delta, Pdefn (\snare_delta),
		\midinote, Pseq ((73!8) ++ (72!8) ++ (75!8), inf),
	);
};

Pdefn (\snare_delta) { { 1.yield }.loop };
)

(
TempoClock.tempo_ (0.8);
Pdef (\one_kick).play;
Pdef (\snare).play;
)

// push snare out of time
Pdefn (\snare_delta) { rrand (1.0, 2.0).yield; { 1.yield }.loop };

// reschedule snare to the next grid line:
Pdef (\snare).reschedule (Pdef (\snare).clock.nextTimeOnGrid);

// reschedule snare half way between grid lines:
Pdef (\snare).reschedule (Pdef (\snare).clock.nextTimeOnGrid + 0.5);


correct.

this is the solution I am aiming for presently.

this would be great, but not required for the current use case.

brilliant - this is my homework tonight. many many thanks :pray::pray::pray:


#10

Ah, thanks for explaining about class extensions.

The one thing I would add is, if you are rescheduling a Pdef for the barline, probably a good idea to use the doReset: true flag. You’ll probably hit the key to reschedule in the middle of the bar – which means rescheduling will put the next event (middle of the bar) on the bar line. But if you reset at the same time as rescheduling, then the note/rest at the beginning of the bar goes on to the barline.

hjh