Writing Buffers Content Tips

What is the best way to fill the contents of buffer and reading them in real time. Is there something I can do to make it sound a bit more interesting, assuming my one single buffer was more interesting musically, sounded more rich but when I am scaling it up to four buffers it creates clicks and cracks but nothing in between like in the single buffer case which I have tried. Code is below, and any tips appreciated.

//The buffs and control signals writing in the formers.
c = {Bus.control(s, 1)}!4;
b = {Buffer.alloc(s, 44100, 1)}!4;

//Input is a set of control device parameters but any between -1.0 and 1.0 will do.
[14, 15, 16, 17].collect{
	|el, i|
	~gamepad.elements[el].action = {|...args|
		c[i].set(args[0].range(-1.0, 1.0));
	};
};

//The synths, writing and reading consequently:
(
s.waitForBoot{
SynthDef(\record_buf, { arg out=0, input=0, bufnum=0, gate = 1, speed=1, offset=0.5, mul=1, dur = 2;
    EnvGen.kr( Env.sine, gate, dur ) * BufWr.kr(In.kr(input, 1) *  mul - offset, bufnum, Phasor.kr( gate, speed, 0, BufFrames.kr(bufnum) ), loop:1);
}).add;

s.sync;

SynthDef(\play_buf, { arg out = 0, bufnum = 0, rate = 1;
    var playbuf;
    playbuf = BufRd.ar(1, bufnum, LFSaw.ar(BufDur.ir(bufnum).reciprocal * rate * 2).range(0, BufFrames.ir(bufnum)) );
	playbuf = LeakDC.ar(HPF.ar(playbuf, 60), 0.995);
    Out.ar(out, playbuf.dup);
}).add;
}
)

(
~rec = Group();
~play = Group();
[0, 1, 2, 3].collect{ |i|
	Synth(\record_buf, [\input, c[i].index, \bufnum, b[i].bufnum, \speed, 1], target:~rec);
	Synth(\play_buf, [\out, 0, \bufnum, b[i].bufnum, \rate, 12], target:~play);
}
)

Thanks

When live-recording and looping, to avoid clicks, you have to make sure the play head never crosses over the record head or vice versa.

This is guaranteed if they are moving at the same rate.

If they are moving at different rates, there will inevitably be a collision = click.

So you cannot loop continuously with different playback and recording rates unless you’re ok with clicks.

It’s possible with some simple linear algebra to determine whether a playback segment will collide with the record head, or to calculate “good” boundaries. I wrote this up for some students awhile back; unfortunately the forum does not allow PDF uploads but I’ll use my web server. (Note, I need to revise the Pure Data section, but you’re not using PD so it doesn’t matter.) This is oriented toward granular synthesis but you could adapt it for other purposes.

https://www.dewdrop-world.net/download/circular-buffer.pdf

hjh

It sounds great alas couldn’t access the content of the link provided here.

Thanks for letting me know. I’ll have to follow up with my web provider.

It is quite strange that I just tried going to the parent directory, Index of /download , and it did display a listing, and I could open the PDF from there. ???

Anyway… I can summarize the main points.

If the buffer-writer is recording at rechead (in samples), and you want to play a segment of the recorded audio for dur seconds, at rate r, then – if the starting time of that segment is between rechead and rechead + (dur * samplerate * (1 - r)), then the playback and recording heads will cross = click. If the starting time of that segment is outside of that danger zone, then, you’re fine.

For example, if you have a 5 second buffer, currently recording at 3.0 seconds (I’ll just use seconds for this example – for BufWr/BufRd phase, multiply by the sample rate), and you want to choose a 1-second segment with rate = 2, then:

  • dur * (1 - r) = 1 * (1 - 2) = -1
  • rechead + that = 3 + -1 = 2
  • So a segment start time between 2 and 3 will fail. E.g, 2.2. At the start of the segment, rechead = 3, playhead = 2.2. Playhead < rechead. At the end of the segment, rechead = 4, playhead = 4.2. Playhead > rechead = crossed over = click.
  • And a segment start time < 2 would be OK. E.g, 1.8. At the start of the segment, rechead = 3, playhead = 1.8. Playhead < rechead. At the end of the segment, rechead = 4, playhead = 3.8. Playhead < rechead = didn’t cross over = no click.

hjh

1 Like

Thank you for this. Now, instead of going the easy way me asking for some code, I tried to adapt my content using the demo from your link I found online. But I am not sure thought I am doing it the right way. Any tips?

HID.findAvailable[0];
~gamepad = HID.open(1356, 2508);
~gamepad.debug = false;

HIDFunc.trace(false)

c = {Bus.control(s, 1)}!4;
b = {Buffer.alloc(s, 44100*2, 1)}!4;
p = {Bus.control(s, 1)}!4;

(
[14, 15, 16, 17].collect{
	|el, i|
	~gamepad.elements[el].action = {|...args|
		c[i].set(args[0].range(-1.0, 1.0));
	};
};

//buttons L2 and R2 of Dualshock
~gamepad.elements[6].action = {|...args| ~rec.set(\speed, 1.0.rand2); (args[0] + "speed").postln};
~gamepad.elements[7].action = {|...args| ~play.set(\rate, rrand(0.1, 2.0)); (args[0] + "rate").postln};
)

(
SynthDef(\circularRec, { |inbus, bufnum, posbus|
var phase = Phasor.kr(rate: 1, start: 0, end: BufFrames.kr(bufnum));
BufWr.kr(In.kr(inbus, 1), bufnum, phase);
Out.kr(posbus, phase);
}).add;

SynthDef(\live_grains, { |out, gate = 1, bufnum, posbus, amp = 0.1,
tfreq = 20, overlap = 8, rateRand = 1.05, timeRand = 0.15|
var sr = BufSampleRate.kr(bufnum);
var bufdur = BufDur.kr(bufnum);
var phaseSec = In.ar(posbus, 1) / sr;
var trig = Impulse.ar(tfreq);
var dur = overlap / tfreq;
var rate = TExpRand.ar(rateRand.reciprocal, rateRand, trig);
var safeStart = phaseSec + (dur * (1 - rate));
var grainStart = min(phaseSec, safeStart) - TRand.ar(0, timeRand, trig);
var grains = GrainBuf.ar(2, trig, dur, bufnum, rate,
grainStart / bufdur);
var eg = EnvGen.kr(Env.asr(0.001, 1, 0.01), gate, doneAction: 2);
Out.ar(out, grains * (eg * amp));
}).add;
)

(
~rec = Group();
~play = Group();
[0, 1, 2, 3].collect{ |i|
	Synth(\circularRec, [
inbus: c[i].index,
bufnum: b[i].bufnum,
posbus: p[i].index],
		target:~rec);

Synth(\live_grains, [
bufnum: b[i].bufnum,
posbus: p[i].index,
amp: 0.4,
timeRand: 0.7,
rateRand: 1.5
], target: ~play,
	addAction: \addAfter);
}
)

If you’re playing audio from the buffer, it would be better to record at audio rate.

This is following the record head exactly (but allowing for pitch shifting).

For timestretching, you’d need to generate the playback phase from a separate Phasor or other, and use safeStart to check whether the grain will be OK or not (and then adjust if not).

hjh

1 Like

Thanks, this won’t make any sound rather a timely (1 second interval) impulse sound, probably because I am logging the controller’s data into a control bus, makes sense.

Ok, I see… but then granular synthesis is inappropriate.

One suggestion: Always record at rate 1.0. If recording and playback rates are both changing, then it becomes harder to control. Do all the time control with the playback rate.

If you want to loop play from the buffers at arbitrary rates, one thought might be to crossfade when you’re approaching a crossover point.

hjh