Sampling in real time

I have a live-situation where I have to record some live signals (coming from microphones) into separate buffers allocated dynamically. The signals are very short pulses from a percussionist. I need the samples to be named automatically in a folder, so that later I can use them for generating other sounds. Since I don’t have many experience with handling live signals and live-sampling in SC, I would be very grateful to any suggestions how I could do this.

One way to do this:

  1. Allocate a buffer of maximum length for each mic source, or use one buffer for all if if you don’t have simultaneous recordings going on.
  2. Define a List or Array for each source, ie. ~perc1 = List.new etc.
  3. Trigger the start of the recording. Here it would be useful to know how you will trigger the start of a recording. Is it a ‘manual’ trigger (you pushing a button) or a gated trigger (ie. recording starts when signal surpasses a threshold)?
  4. Trigger the end of the recording. On this trigger the recording stops AND
  5. A buffer of length equal to (last trig - previous trig) is allocated and added to the List/Array and using the Buffer.copyData method, the portion between the two tiggers are copied into the newly allocated buffer.

You would access the buffer (later) by indexing into the List or Array, ie. ~perc1[0], ~perc1[1], ~perc1.last. If you plan to reuse the buffers after quitting SC, you would also need to write the array of buffers to disk as these are erased from memory when you quit SC.

You could alternatively allocate all the buffers by maximum length before performance starts, ie.

~perc1 = Array.fill(20, { Buffer.alloc(s, s.sampleRate * 4) }) // max time = 4 seconds

and record straight into these buffers. In this case you will need to keep track of when the buffer recording ended not to play the empty space at the end each buffer, so maybe something like:

~perc1 = Array.fill(20, {[Buffer.alloc(s, s.sampleRate * 4), nil] }) // nil will be replaced with the endtime in seconds (or samples)

1 Like

Here is some code. Let me know if you have problems understanding what is going on.

(
SynthDef(\recBuf, {
	var sig = SoundIn.ar(0);
	var amplitude = Amplitude.ar(sig, \atk.kr(0.001), \rel.kr(0.1));
	var onTrig = Trig.ar(amplitude > \onthresh.kr(0.1));
	var phasor = Phasor.ar(onTrig, 1, 0, inf);
	var offTrig = Trig.ar(amplitude < \offthresh.kr(0.05));
	var doneTrig = offTrig + ( phasor > (\maxTime.kr(4) * SampleRate.ir) ) * SetResetFF.ar(onTrig);
	var rec = BufWr.ar(sig, \buf.kr(0), phasor, loop:1);
	FreeSelf.kr(doneTrig);
	SendReply.ar(doneTrig, '/reply', phasor);
}).add;
)

(
s.newBufferAllocators;
b = Buffer.alloc(s, s.sampleRate * 4, 2);
l = List.new; // holds new buffers
o = OSCFunc({ |msg|	
	{
		var numSamples = msg[3].postln;
		var newbuf = Buffer.alloc(s, numSamples, 2).postln;
		s.sync;
		b.copyData(newbuf, numSamples: numSamples);
		s.sync;
		l.add(newbuf);
		b.zero
	}.fork;
}, '/reply');
)

Synth(\recBuf, [\buf, b]) // run this and speak/sing into your mic, when you see action in the postwindow, the recording has ended. Run again to create more buffers.

// now index into l

l[0].play;
l[1].play;
l.lastplay;
2 Likes

One potential issue is catching the beginning of percussion sound with very fast attack - you will have to test it to see if something like a clave, with a very short attack and release is captured in full.

1 Like

Have you considered, allocating a large buffer, using something like FluidNoveltyFeature (flucoma) to produce an impulse for each ‘hit’, on hit, sequentially store the current phase index in a separate buffer using Dbufwr, then when it comes to play back, you just play between the indexes? This way you don’t need to save them as separate buffers on disk. Also, you don’t have to wait for the recording to finish before you access the samples.

3 Likes

This sounds very interesting. I suppose FluidNoveltyFeature is some kind of plugin not in the default library? Where can I find it?

Found something:
https://learn.flucoma.org/reference/noveltyslice/

should only find out how it works! :grinning:

Thanks for your thorough answer. I don’t have the time to trigger the end of the sampling. The processing of the recorded samples (hits) will start in the piece while the percussionist is still producing further “hits”!

It just triggers when something ‘novel’ happens, you can try the same approach with Coyote

My code example automatically defines the end of the sample when the level drops beneath the threshold and can easily be adapted to automatically create new hits without any user interaction. No matter how you approach it, you won’t be able to process a hit right when a hit is recognized, you are going to need a short time to record the rest of the hit.

1 Like

Something to keep in mind if you are using the Amplitude ugen: Amplitude: incorrect results if input signal is not at the same rate · Issue #3095 · supercollider/supercollider · GitHub