Freeing, recording and triggering synths from inside the same routine

I’m working on a new live electronics project. Without going into too much unnecessary detail, I’d like to record into a buffer and process it live. I’ve copied the initial idea from this video.

Because I’m keen to do a lot of different time-stretching, reverse etc. of the buffer, and because I’d like to have different phrases running through different synths, I’ve worked out I need a number of buffers. At the moment I’m working with 5, so that one buffer is recording while the other four are being used.

The question I’m confronted with now is how to have multiple synths running, each playing a different buffer, but then free each synth before the buffer it’s playing is overwritten to avoid weird artefacts, clicking etc. I figure this should all be part of the same Routine - something like this:

~synths = Array.new(5);

(
Tdef(\bufferAlternate, { 
        loop {

                5.do {
                        |i|
                          // free the running Synth
                        ~synths[i].set(\gate, 0);

                        // record to buffer
                        ~recordBuffers.set(\buf, ~bufs[i]);
                        ~bufLength.wait;
             
                        // trigger the synth when the buffer is finished recording
                        synths = synths.add(Synth(\granular, [buf: i]));


                };
                1.wait;
        }

}).play;
)

Here’s the full code I’m working with - I don’t know if it’s super relevant, but in case it’s helpful. Granular synth based pretty closely on an Alik Rustamoff video.

(
~numBufs = 5;
~bufLength = 15;
~bufs = ~numBufs.collect { |i| Buffer.alloc(s, s.sampleRate * ~bufLength, bufnum: i) };
~micBus = Bus.audio(s, 1);
~pointerBus = Bus.audio(s, 1);

SynthDef(\mic, {
        var sig = SoundIn.ar(\in.kr(0) * \amp.kr(0.dbamp));
//        var sig = In.ar(\in.kr(0));
        Out.ar(\out.kr(0), sig);
}).add;

SynthDef(\pointer, {
        arg buf = 0;
        var sig = Phasor.ar(0, BufRateScale.kr(buf), 0, BufFrames.kr(buf));
        Out.ar(\out.kr(0), sig);
}).add;

SynthDef(\rec, {
        var pointer = In.ar(\pointerIn.kr(0), 1);
        var sig = In.ar(\micIn.kr(0), 1);
        BufWr.ar(sig, \buf.kr(0), pointer);
}).add;


SynthDef(\granular, {
        arg tRate = 20, rate = 1, posRate = 1, tRateMF = 0, tRateMD = 0, rateMF = 0, rateMD = 0, posRateMF = 0, posRateMD = 0;
        var buf = \buf.kr(0, 0.5);
        var trig = Impulse.ar(tRate.lag(0.05));
        var bufFrames = BufFrames.ir(buf);
        var tRateMod = { SinOsc.ar(tRateMF, Rand(0.0, 2pi)) * tRateMD};
        var rateMod = { SinOsc.ar(rateMF, Rand(0.0, 2pi)) * rateMD};
        var posRateMod = { SinOsc.ar(posRateMF, Rand(0.0, 2pi)) * posRateMD};
        var phasor, sig, env;


        tRate = tRate + tRateMod.dup;
        posRate = posRate + posRateMod.dup;
        rate = rate + rateMod.dup;

        phasor = Phasor.ar(
                trig: 0.0,
                rate: posRate * BufRateScale.kr(buf),
                start: \startPos.kr(0.0, 0.5),
                end: \endPos.kr(1.0) * bufFrames
        );


        sig = GrainBuf.ar(
                numChannels: 1,
                trigger: trig,
                dur: tRate.reciprocal * \overlap.kr(2, 2),
                sndbuf: buf,
                rate: rate,
                pos: phasor / bufFrames,
                interp: 2,
                pan: 0,
                envbufnum: -1,
                maxGrains: 512
        );

        sig = sig * \amp.kr(-0.dbamp);

        Out.ar(\out.kr(0), sig);

}).add;

Tdef(\bufferAlternate, { 
        loop {

                5.do {
                        |i|
                        ~recordBuffers.set(\buf, ~bufs[i]);
                        ~bufLength.wait;

                };
                1.wait;
        }

});

Tdef(\synthBufAlternate, {
        loop {
                5.do {
                        |i|
                        var buf = (i+1);
                        ~synth.set(
                                \buf, ~bufs[buf],
                                \rate, [1, 0.5, 2, 0.25, 1.5, 0.75].choose,
                                \posRate, rrand(0.25, 5) * [-1, 1].choose,
                                \tRateMF, rrand(0.0, 5.0),
                                \tRateMD, rrand(0.0, 5.0),
                                \overlap, rrand(0.02, 20),
                                \startPos, rrand(0, 1),
                                \endPos, rrand(0, 1)

                        );
                        ~bufLength * ~numBufs.wait;
                };
                1.wait;
        }
});
)


// process: record buffer, trigger synth. Before recording over buffer, free the synth.


// final routine
(
Routine({ 

~micGroup = Group.new;
~pointerGroup = Group.after(~micGroup);
~recGroup = Group.after(~pointerGroup);
~granGroup = Group.after(~recGroup);
Synth(\mic, [in: 0, out: ~micBus], ~micGroup);
Synth(\pointer, [buf: ~bufs[0], out: ~pointerBus], ~pointerGroup);
~recordBuffers = Synth(\rec, [pointerIn: ~pointerBus, micIn: ~micBus, buf: ~bufs[0]], ~recGroup);

Tdef(\bufferAlternate).play;
~bufLength.wait;
~synth = Synth(\granular, [bufs: ~bufs[0]]);
~bufLength.wait;
Tdef(\synthBufAlternate).play;

}).play;
)

I’m super grateful for any help.

Cheers,
Jordan

When I am doing this I make more buffers than I will ever need, and then write to and read from them in sequence such that they are never being written to when being played.

So, rather than making 5 buffers, I might make 10:

~buffers = List.fill(10, {Buffer.alloc(s, 44100, 1)});

~bufSeq = Pseq(~buffers.collect{|buf| buf.bufnum}, inf).asStream;

then, every time I need a new buffer, I just ask the stream for the next buffer:

~bufSeq.next;

As long as your loop doesn’t move too quickly, you never overwrite your buffer while playing.

Sam

2 Likes

To add to what Sam said:

You can predict the times when each buffer is recording and when it’s playable. In your example, with 5 buffers of 15 seconds each:

  • Buffer 0 will be recording during 0-15 sec, and playable during 15-75 sec.
  • Buffer 1 recording 15-30 sec, playable 30-90 sec.
  • Buffer 2 recording 30-45 sec, playable 45-105 sec.

etc.

So if you know when a synth will start, and how long it will play, then you also know which buffers will be safe for it to use.

Here, if you know that each processing synth will play for a maximum of 30 seconds, and you’re currently recording buffer i, then i-1 and i-2 are definitely safe and the other two might be safe now, but maybe not safe at the end of that synth.

hjh

Thanks to both of you for the help!

Long term, I would like for SC to randomly but safely use the buffers in a random order and for a random amount of time - the idea is to have the computer “improvising” alongside me with input from my guitar playing (albeit in a very low-level, non-machine-learning kind of way). So that some audio is looped for say, 45 seconds, while another phrase comes and goes.

I’m wondering if there’s some way for SC to know when a Buffer is being recorded to, or being read from, so that it can automatically choose the right buffer or free the right synths? Something like:

if (~bufs[4].isBeingRead,
 {// do nothing},
 { // write to ~bufs[4] });

Does that make sense? Is that kind of thing somehow possible?

Thanks again!
Jordan

There’s no built-in function for that – it isn’t SC’s job to dictate how you use the buffers.

But, because your code decides which buffer is recording at what times, you can arrange this into a predictable pattern! … as in my previous post. None of it is by chance, because it’s under your control.

When the pattern is predictable, then it’s always known which buffers are safe to use for playback, and for how long.

hjh