When is BufRd "done?"


I can’t quite seem to find the right pieces that will allow me to play through part of a buffer once with a modulating rate. PlayBuf’s rate is modulatable but doesn’t accept an end-point. So using BufRd… I can get a single play-through using Line but it isn’t modulatable. A Phasor is modulatable but loops with no way to make it one-shot. I tried using an Envelope (for both PlayBuf and as the phase for BufRd) but it’s fixed-length and I have no idea how long to make it. I stumbled across https://doc.sccode.org/Classes/Done.html and it indicates that BufRd triggers a DoneAction which I thought I might be able to use to kill the looping Phasor but I’m pretty sure (empirically and reading source code) that BufRd doesn’t trigger a DoneAction.

Any thoughts are appreciated.

If it’s one shot, the solution is to use an envelope. There is two kind of envelope:

  • a gated one where you have to release yourself the synth
  • a non gated one where you specify the duration at creation

Here is the synthdef i use often to play samples. Maybe it’s not what you want to do but starting from this, it will be easier to clarify your goal.
In this case, the envelope is a gated one (Env.adsr) and the pattern take care of releasing the synth after 2 beats (\dur = 2 * \legato = 1)

SynthDef(\playersec, { arg out=0, amp=0.1, gate=1, pan=0, freq=200, bufnum, speed=1, pos=0, doneAction=2, loop=0, trigger=1, velamp=1;
	// pos in seconds
	var sig;
	var sig1;
	//speed = speed * ( SinOsc.kr(1.3) * 1 + 1 ); // modulate rate ?
	sig = PlayBuf.ar(2, bufnum, BufRateScale.kr(bufnum) * speed, trigger, startPos: (pos*BufSampleRate.kr(bufnum)), doneAction:doneAction, loop: loop);
	sig = sig * EnvGen.ar(\adsr.kr(Env.adsr(0.001,0,1,0.01)),gate,doneAction:doneAction);
	Out.ar(out, sig * \gain.kr(1) * velamp);

~mybuffer = Buffer.readChannel(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav", channels:[0,0]); 

		\instrument, \playersec,
		\bufnum, ~mybuffer,
		\legato, 1,
		\dur, 2,
		\gain, 1,

Thanks, but I since I want to play with a variable rate, think of a slowly accelerating fast-forward or rewind, I can neither use a fixed-duration envelope nor know when to close the gate of a sustaining envelope. That seems to rule out using PlayBuf since, yea, an Envelope is the only way I know to get it to play a section of a buffer. That leaves me with BufRd but I can’t figure out how to make a one-shot-variable-rate-line-like ugen.

If I understand correctly, you want to play a buffer with a variable rate, but once the read head hit a specific position in the buffer, you want the synth to stop automatically ?

If the rate is constant, this is a lot simpler, so do you want a dynamic rate (for example a rate modulated by a SinOSc) ?

Like this?

	var rate = LFNoise2.kr(50).exprange(0.5, 2);
	var line = Integrator.kr(rate);
	var done = line >= 200;
}.plot(duration: 0.5);  // too long, proves it's really stopping


1 Like

Yes, perfect.

Here’s an accelerating fast-forward / rewind using it. It still needs to incorporate BufRateScale but works great when the system and buffer’s sample rate match. Then add Done so I can send an OSC message back to the client letting it know too.

SynthDef.new(\ffrw, {
	|bufn, startPos=0, endPos=0, pan=0, amp=0.5, out=0|
	var dir, rate, phase, done, sig;

	// +1 forward, -1 backward
	dir = ( ( endPos - startPos ) > 0 ) * 2 - 1;
	rate = EnvGen.ar(Env([1,8],[20],[6])) * dir;
	phase = Integrator.ar(rate, add: startPos).poll(2);
	done = FreeSelf.kr( ( ( phase - endPos ) * dir ) >= 0);

	sig = BufRd.ar(1, bufn, phase);
	sig = Pan2.ar(sig, pan, amp);
	Out.ar(0, sig);

Synth(\ffrw, [\bufn, ~buf, \startPos, ~buf.numFrames / 8, \endPos, 2 * ~buf.numFrames / 8])
Synth(\ffrw, [\bufn, ~buf, \startPos, 2 * ~buf.numFrames / 8, \endPos, ~buf.numFrames / 8])

Thank you

Simply multiply rate by BufRateScale before integrating.