Using BufRd with looping Phasor, change buffers at loop point?


#1

I’m using a single Phasor, which loops between 0 and 1, to simultaneously index many buffers (with potentially different sampling rates) and use the indexed values to control other UGens.

Each set of buffers contains information for a certain amount of time, say 1 second, which matches how long it takes for the Phasor to loop. During that time, I prepare the next set of buffers so that they’re ready to be loaded and indexed when the previous ones are finished, (hopefully) seamlessly moving from one to the next without any clicks or hiccups.

I understand that the loading and setting of the new buffers is a language-side job, so my question is: what’s the best way to make sure that synth.set-ing the new buffers happens exactly when the Phasor loops back to 0?

I tried using Changed.ar as so:

SynthDef(\timePhasor, {
	|out = 3, dur = 1, loopPoint = 1, t_manualTrig = 0, startPoint = 0|

	var sig = Phasor.ar(
		t_manualTrig,
		1/(dur*SampleRate.ir()),
		startPoint,
		//set loopPoint to 1 for looping, inf for no looping
		loopPoint,
		startPoint
	);

	var trigStart = Changed.ar(sig, 0.01);
	SendTrig.ar(trigStart, 0, sig);

	Out.ar(out, sig.clip(0, 1));
}).add;

and, while it sort of works, the value of sig when the message is sent changes ever so slightly every time, e.g. 2.2455536964117e-005, 2.2437186999014e-005, 2.2418837033911e-005 etc.

Is there a better way to hot-swap the buffers so that the last sample of the previous buffer is reliably followed by the first sample of the next buffer?


#2

As I get it this would rather be built into the SynthDef where the BufRd (or similar) resides. When such a synth gets the jump to 0 (e.g. via a bus) it would trigger a switch to the next buffer.

If the buffers aren’t faded themselves you’d have to build in a crossfade, that would be the normal way to avoid that. We don’t have the whole picture, but if I’d build such a setup I’d expect to face two different crossfade tasks:
when looping the buffers and when switching. You could consider using Wouter Snoei’s PlayBufCF (wslib quark) or DX UGens (miSCellaneous_lib quark) for that.


#3

Thanks for the answer!

This would be ideal, as it eliminates all the timing issues that come with having to send OSC from the server to the language to signal the loop point, and back to the server to change to the new buffer.

The only issue is I don’t know how this is possible. Take this simple Synthdef for example:

SynthDef(\sine, {
	arg out=0, amp, frq, pan;

	var frqVal = BufRd.kr(1, frq, In.ar(~timerBus) * BufFrames.kr(frq));
	var ampVal = BufRd.kr(1, amp, In.ar(~timerBus) * BufFrames.kr(amp));
	var panVal = BufRd.kr(1, pan, In.ar(~timerBus) * BufFrames.kr(pan));

	var sig = SinOsc.ar(frqVal) * ampVal;

	Out.ar(out, Pan2.ar(sig, panVal));
}).add;

The values for the amp, freq, and pan arguments are the buffers that are to be indexed, and I was simply thinking of updating them with synth.set(\amp, newAmpBuf). How would the synth itself, once instatiated on the server, change the bufnum of the second argument to BufRd?

My best guess is that there would have to be two buffer arguments associated with every parameter (something like amp1, amp2). At any one time, one of the arguments will hold the buffer currently being used, while the other will point to the buffer that should be used when the phasor loops again, swapping roles between them every time there is a change. But how would the actual change happen using UGens on the server?

I don’t think that’s necessary if subsequent buffers have continuous value streams (remember that buffers are only used to control UGens, not for audio).

For example, say that I want a linear ramp that goes from 0.0 to 1.5 over three seconds. Assuming that each buffer has a duration of 1 second, the first would go from 0 to 0.5, the second from 0.5 to 1.0, and the third from 1.0 to 1.5. As long as the changes happen at the right time, I don’t think that it’s worth the trouble of crossfading between them.


#4

Consider Latch, then latent setting becomes manifest with the trigger from a switch to 0.

// something like this, untested

var in = In.ar(~timerBus);
var trig = in < 1e-7;

frq = Latch.ar(in, trig);
amp = ...    
pan = ...    

It was not clear for me that buffers have continous value streams, then of course you don’t need to crossfade.