Recording and playing back in real time

I need some help from Live-Performers!
I am trying to do a live recording (into a buffer) of sections of a live performance (say about 6 seconds each time), so that afterwards I can do other stuff to the recorded buffers (hall, pitch shifting etc.). I want to have this operation saved in a synth, so that I can start new synths for doing this whenever I want during the performance. I have tried this with LocalBuf:

(
SynthDef(\rec, {
	var sig, buf = LocalBuf.new(SampleRate.ir * 6, 2).clear;
	BufWr.ar(SoundIn.ar([0, 1]), buf, Phasor.ar(1, BufRateScale.kr(buf) * 1, 0, BufFrames.kr(buf)));
	sig = PlayBuf.ar(2, buf, 1, Impulse.kr(0.1));
	Out.ar(0, sig ! 2)
}).add
)

but this is some how acting wired for me! The playback happens a couple of times at the beginning of synth-creation, and then it is gone. I want it to be around, until I delete the synth manually (how, I don’t know yet either).
Any tips, and helps is highly appreciated!

Here is what I do in a similar situation:

Allocate 2 buffers of say 30 seconds.
Simultaneously loop-record your live signal into the 2 buffers. Buffer 1 starts recording at 0 seconds(relative to the buffer), Buffer 2 starts recording 15 seconds into the buffer. I use RecordBuf fo this.

Now you can always get the last 15 seconds of performance from one of the 2 buffers without discontinuities: Let’s say you start recording into the buffers as described above and then 33 seconds later you want a buffer of the last 6 seconds, ie. from 27-33 seconds relative to the start time. Buffer 1 is no good because it will have a discontinuity since it loops back every 30 seconds, but Buffer 2 is good, because it loops back at 15 seconds, 45 seconds, 75 seconds etc and thus have no discontinuity for the given timeslot.

To actually get the last 6 seconds, first you determine which buffer does not have any discontinuity using modulo (%) and .div calculations, then you allocate a buffer of 6 seconds and use .copyData to get the last 6 seconds from one of the loop-recording buffers.

There is a potential timing issue with this method as the Server clock and the language clock(s) are not totally synced. Retriggering RecordBuf relative to the language clock on every record cycle can partially fix this.

1 Like

Here is the code:

(
s.waitForBoot{
	~testBuf = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");
	~bufSize = 30;
	~buf = 2.collect{Buffer.alloc(s, ~bufSize * s.sampleRate, 2)};	
	
	SynthDef.new(\bufrec, {
		var buf = \buf.kr(0);
		// var sig = SoundIn.ar([0, 1]);
		var sig = PlayBuf.ar(1, ~testBuf, loop: 1)!2;
		RecordBuf.ar(sig, buf, \offset.kr(0), \reclev.kr(1).varlag(0.3),
			\prelev.kr(0).varlag(0.3), \run.kr(1), \loop.kr(0), \trig.tr(0) );
	}).add;
	
	s.sync;
	~bufRec = ~buf.collect{|n|Synth(\bufrec, [\buf, n ])};
	
	~bufID = {|start = 0, end = 5, size = 30|
		case
		{ start.div(size) == end.div(size)} { 0 }
		{ (start + (size/2)).div(size) == (end + (size/2)).div(size)} { 1 }
	};
	
	t = TempoClock(1);
	
	Pdef(\bufReset,
		Ppar([
			Pbind(
				\type, \set,
				\id, ~bufRec[0],
				\args, #[\trig],
				\trig, 1,
				\dur, ~bufSize
			),
			Pbind(
				\type, \set,
				\id, ~bufRec[1],
				\args, #[\trig],
				\trig, 1,
				\dur, Pseq([~bufSize/2, Pseq([~bufSize], inf)], 1)
		)]
	)).play(t);
	
	~getChunk = {|start = 0, end = 5, buf|
		var i = ~bufID.(start, end, ~bufSize).debug(\i);
		if (i.notNil)
		{
			{
				~buf[i].copyData(
					buf,
					srcStartAt: (start + (~bufSize/2 * i)) % ~bufSize  * s.sampleRate,
					numSamples: (end - start) * s.sampleRate
				);
				s.sync;
			}.fork;
		}
	}
};
)

// wait 6 seconds or more. Now you can do:
~myBuf = Buffer.alloc(s, 6 * s.sampleRate, 2); // allocate a buffer of the desired length;
~getChunk.(t.beats - 6, t.beats, ~myBuf); // copy the last 6 seconds of audio to the buffer
~myBuf.play;

// you can reuse ~myBuf if you need another 6 seconds chunk later

~getChunk.(t.beats - 6, t.beats, ~myBuf); // copy the last 6 seconds of audio to the buffer

~myBuf.play;
1 Like

Sorry for all the replies, just wanted to explain what I see as the options for using live input and buffers.

If you want to be able to get the last x seconds of SoundIn without the possibility of a discontinuity and you want it now, ie. not x seconds from not, I can only see two possible options: either you record into one buffer which is at least as long as your entire performance OR use two loop-recording buffers like in my code above.

On the other hand, if you ok waiting x seconds for the buffer to fill, things get a little easier. While there probably is a way to achieve this with LocalBuf I would split the buffer-recording and the buffer manipulation into different synthdefs. One thing to remember is that you
can simultaneously record into a buffer and play it back, at first you will hear no sound as the buffer is filling up. After 2 seconds in the example below, you will hear the delay fx.

(
SynthDef(\fillBuffer, {|buf|
	var free = Env([0, 0], [BufFrames.kr(buf) / SampleRate.ir]).kr(2); // freeing the synth after filling it
	RecordBuf.ar(SoundIn.ar([0, 1]), buf);
}).add;

SynthDef(\delay, {|buf|
	var sig = PlayBuf.ar(2, buf, loop: \loop.kr(1)); // super simple example just for demonstration
	Out.ar(0, CombC.ar(sig, decaytime: 4))
}).add
)

(
{
	b = Buffer.alloc(s, 2 * s.sampleRate, 2);
	s.sync;
	Synth(\fillBuffer, [buf: b]); // fill the buffer
	Synth(\delay, [buf: b]) // start using the buffer right away, after 2 seconds you will hear the delay
}.fork
)

If you want to use many different ‘chunk-durations’ and don’t want to allocate a new buffer each time, you could also allocate one buffer of the maximum duration you are going to need and reuse this buffer for everything by only playing the first x-seconds of the buffer as needed. In this case you won’t need the {…s.sync… }.fork structure as the buffer has been pre-allocated and there is nothing to wait for.

1 Like