Sample accurate BufRd playback only once?

In the code below I record sound input triggered by keyDownAction until a keyUpAction. I store the number of frames recorded by launching a Phasor object simultaneously which is freed on keyUpAction (this all works fine). I would like to be able to play back the exact number of frames I recorded once with BufRd but am struggling with how to do so. If I use Phasor to play from beginning of the buffer to the frame I stopped recording I can play exactly what I recorded, ending where the Phasor stopped, but it loops that segment forever. If I switch to Line (or use an Env) I appear to have to somehow specify duration in seconds. I am unsure how to convert frames to seconds without potentially risking the accuracy of the playback that I am trying to preserve. Is there a way to play back my recording from the beginning of the buffer to the frame at which I stopped recording exactly and only once?

Below please find the Phasor version that loops forever but otherwise does what I want. Any suggestions would be greatly appreciated.

s.boot;

~buf = Buffer.alloc(s, s.sampleRate * 60, 1);
~dur = Bus.control(s, 1);

(
SynthDef(\record, {|amp = 0.9, buf, in = 0, off = 0, rate = 1, recManualBus, start = 0, trig = 0|
    var env, sig, phase;

    sig = SoundIn.ar(in, amp);
    phase = Phasor.ar(trig, BufRateScale.kr(buf) * rate, start, BufFrames.kr(buf));
    BufWr.ar(sig, buf, phase);
    Out.kr(recManualBus, phase);
    FreeSelf.kr(off);
}).add;

SynthDef(\bufRd, {|amp = 0.9, buf, end, loop, out, rate = 1, start = 0, trig = 0|
    var phase, play, sig;
    
    phase = Phasor.ar(trig, BufRateScale.kr(buf) * rate, start, end);
    sig = BufRd.ar(1, buf, phase, loop);
    play = sig * amp;
    Out.ar(out, Pan2.ar(play));
}).add;
)


(
var keyDown = False;

w = Window.new("keyTrigRec", Rect(50, 50, 275, 275));
w.background_(Color.white);

w.view.keyDownAction = { arg view, char, modifiers, unicode, keycode;
    if(keycode == 49, {
        if( keyDown == False, {
            "start recording".postln;
            w.background_(Color.red);
            // start the record synth
            x = Synth(\record, [\trig, 1, \buf, ~buf, \recManualBus, ~dur.index ]);
            // block repeated triggers from space bar
            keyDown = True;
        });
    });
};

w.view.keyUpAction = { arg view, char, modifiers, unicode, keycode;
    if(keycode == 49, {
        "stop recording".postln;
        w.background_(Color.green);
        fork {
            x.set(\off, 1); // stop recording
            // check position of phasor
            ~dur.get({ |recDur|
                ~numFrames = recDur;
                ("numFrames recorded:" + ~numFrames).postln;
            });
            s.sync;
            ~dur.set(0.0); // reset bus for next recording
        };
        keyDown = False;
    });
};
w.front;
)

// play recording
y = Synth(\bufRd, [\buf, ~buf, \end, ~numFrames, \loop, 0, \trig, 1 ]);

One hidden assumption here is that “stopping” means “stopping playback,” rather than “stopping the sound.” If you think of it in terms of the latter, then it doesn’t matter whether buffer-reading phase stays within the keydown/keyup range – it can keep running freely.

How, then, to stop the sound? I would do it the normal way – with an envelope.

If an envelope is stopping the sound, then you’re free to use a one-directional free-running phase generator, such as Sweep.

SynthDef(\bufRd, {|amp = 0.9, buf, end, loop, out, rate = 1, start = 0, trig = 0|
	var phase, play, sig;

	// this will eventually get bigger than 'end'
	phase = start + (Sweep.ar(trig, BufRateScale.kr(buf) * rate) * BufSampleRate.kr(buf));
	
	// but the phase being too big is OK
	// because we will use that to close an envelope's gate
	var eg = EnvGen.ar(Env.asr(0.001,  1, 0.001),
		gate: phase < end
		// , doneAction: 2 ... maybe?
		// Unclear why your original synth doesn't release
	);
	sig = BufRd.ar(1, buf, phase, loop);
	play = sig * (amp * eg);
	Out.ar(out, Pan2.ar(play));
}).add;

(Late edit: Needed another pair of parentheses in the phase expression.)

hjh

1 Like

Hi James!

I really appreciate this response, thank you! Do you think there is a better way to handle the recording SynthDef also? If so I would be interested to hear any thoughts you may have along those lines.

Thank you again for your help, though I have been using SC for more than a decade I almost never record with it so this has been a learning experience for me.

Casey

If you’re recording continuously into a circular buffer, then your recording synth is fine.

But I just noticed that the bufRd synth that I posted doesn’t handle playback of a segment that will wrap around the circular buffer’s boundary.

The logic will be simpler if you require end to be greater than start.

frame 0                 buf end
|                       |
                   ^start       ^end

… and then set loop = 1. For a circular buffer, I think loop = 1 in the argument list (set it as a default value).

hjh