Done Ugen with BufRd

Hi guys,
I’m making some experiments to get notification from the server when an audiofile playback is finished. Long story short: I’m able to obtain the desired effect with PlayBuf Ugen but not with BufRd.

Below some code to reproduce my reasonings…

// 1. start the server
s.boot;
// 2. load a mono buffer
b = Buffer.readChannel(s, "/path/to/my/mono/audiofile.wav", channels:0);

// 3. define an OSC function ready to catch the Done signal
(
OSCdef(\check_if_finished_new, {
	| msg, time |
	"playback Done!".postln;
	// acoustic feedback to help understanding
	{Out.ar(0, EnvGen.ar(Env.perc(0.0, 0.1), doneAction:2) * SinOsc.ar(1000)!2 * 0.75)}.play;
},'/tr', s.addr);
);

// 4. define a 'playBuf' synth as a first example
(
SynthDef(\playbuf_and_sendTrig, {
	|out=0, t_trig=1, buf, rate=1, loop=0|
	var sig = PlayBuf.ar(1, buf, BufRateScale.kr(buf)*rate, t_trig, loop:loop, doneAction:0);
	var ctrl = Done.kr( sig );
	SendTrig.kr( ctrl );
	Out.ar(out, sig!2);
}).add;
)

// 5. instantiate the synth with loop set to 0.
// You will experience a new Done message when the audio file is finished, then silence.
x = Synth(\playbuf_and_sendTrig, [\buf, b, \loop, 0]);

// You obviously need to manually restart the audiofile playback.
// You will see a new Done message at the end of every reproduction as long as you re-trigger the synth (which is fine!)
x.set(\t_trig, 1);

// free the synth when you are happy
x.free;


// Question: what would happen if the loop is set? You will not see any Done message triggered!!!
// Deduction: the playback must finish in order to the Done Ugen to properly trigger its message.
x = Synth(\playbuf_and_sendTrig, [\buf, b, \loop, 1]);
// free the synth when you are happy
x.free;

From the Done documentation i read that Done can also be used with the BufRd Ugen, so let’s make a new experiment.

// 6. define a BufRd synth (the BufRd is controlled by a Phasor Ugen).
(
SynthDef(\bufRd_and_sendTrig, {
	|out=0, t_trig=1, buf, rate=1, loop=0|
	var phase = Phasor.ar(t_trig, BufRateScale.kr(buf)*rate, 0, BufFrames.ir(buf));
	var sig = BufRd.ar(1, buf, phase, loop:loop);
	var ctrl = Done.kr( sig );
	SendTrig.kr( ctrl );
	Out.ar(out, sig!2);
}).add;
)

// 7. instantiate the synth
y = Synth(\bufRd_and_sendTrig, [\buf, b, \loop, 0]);

// you will experience a continuously playing audio (because the phasor is keep increasing and wrappint its value).
// But you will also see (only) an initial "Done message" correctly sent back from the server to the language.

// No more message will be sent back from the server, even if I forse the trigger to restart from its beginning
y.set(\t_trig, 1);

Why? Is there something I’m loosing along the way?

Thank you so much for your help.

Think about the range of this Phasor: 0 <= x < buffer frames.

This will never go outside the buffer’s bounds – therefore, never trigger Done.

So you’d need to expand the range so that it can go out of bounds.

hjh

1 Like

Thank you @jamshark70 for your suggestion.

I’ve tried your solution and now my synth look something like this

(
SynthDef(\bufRd_and_sendTrig_mod, {
	|out=0, t_trig=1, buf, rate=1, loop=0|
	var phase = Phasor.ar(t_trig, BufRateScale.kr(buf)*rate, 0, BufFrames.ir(buf)+1024);
	var sig = BufRd.ar(1, buf, phase, loop:loop);
	var ctrl = Done.kr( sig );
	SendTrig.kr( ctrl );
	Out.ar(out, sig!2);
}).add;
)

I’ve hardcoded a tail value to be added to the end parameter for the Phasor Ugen.
I’ve tested different tail values but it doesn’t seem to work.
Maybe something related to the BufRd and Phasor communication?

You can also consider using the .onFree method and avoid the need for SendTrig and the OSCdef.

1 Like

You could also derive the done ctrl directly from phase.

(
SynthDef(\bufRd_and_sendTrig_mod, {
	|out=0, t_trig=1, buf, rate=1, loop=0|
	var notloop = loop <= 0;
	var frames = BufFrames.kr(buf);
	var upperBound = frames + (1024 * notloop);
	var phase = Phasor.ar(t_trig, BufRateScale.kr(buf) * rate, 0, upperBound);
	var sig = BufRd.ar(1, buf, phase, loop:loop);
	var ctrl = phase >= upperBound;
	SendTrig.kr( ctrl );
	Out.ar(out, sig!2);
}).add;
)

I guess it’s a matter of perspective… “PlayBuf and BufRd both play buffer contents, so they should both have compatible ‘done’ behavior” is reasonable, but the fact that PlayBuf has a doneAction and BufRd doesn’t suggests that this isn’t the design principle. Another possible design principle is, “PlayBuf must handle ‘done’ because you can’t derive it from phase, while BufRd doesn’t have to because you have access to the phase” – not what you expected in this case, but also reasonable.

hjh

1 Like