Overlapping sample player

Hello everyone

Something that was discussed during today’s meetup was how to create overlapping sample players. For example playing a cymbal sound back in an endless looping and relatively smooth way. I’ve struggled with this and would like to hear your suggestions for doing this.

I found this old synthdef of mine that kind of does this but not perfectly:

b = Buffer.read(s, "path/to/soundfile.wav".asAbsolutePath)

SynthDef(\looperOverlapping, {
	arg out=0, buffer=0, rate=1.0, trig=1, startPos=0.0, loopTime=1.0, overlapFactor=0.0;
	var bufferphasor, fade, phasorend, phasorstart, phasorrate, sig, times;

	phasorstart = startPos * SampleRate.ir();

	phasorend = phasorstart + (loopTime * 2 * SampleRate.ir());

	phasorrate = rate * BufRateScale.kr(buffer) * BufDur.kr(buffer).reciprocal / (phasorend-phasorstart/BufFrames.kr(buffer));
	bufferphasor = LFSaw.ar(phasorrate, iphase: [0, 1]);

	times = [0.01,0.98,0.01] * (1+overlapFactor);
	fade = IEnvGen.ar(
		Env([0,1,1,0], times),
		bufferphasor.linlin(-1.0,1.0,0.0,1.0) * times.sum

	sig = fade * BufRd.ar(
		numChannels: 2,
		bufnum: buffer,
		phase: bufferphasor.linlin(-1.0,1.0, phasorstart, phasorend),
		loop: 0,
		interpolation: 4

	// Sum the two overlapping players
	sig = sig.sum;

	Out.ar(bus:out, channelsArray:sig)

Synth(\looperOverlapping, [\buffer, b, \rate, 0.5, \overlapFactor, 0.0])
1 Like

Hi Mads,

You could take some inspiration from this Synth. It does exactly that (with a little bit more(*)):

My logic is here:

// loop 1
rel2=LocalIn.kr(1,0); // retrieve if the loop2 released at previous tock
trig1=rel2+Impulse.kr(0); // loop1's trigger is an initial trigger + the release of the loop2

// ..sweep and read
idx1=Sweep.ar(trig1, BufSampleRate.kr(bufnum1))+start1;
sig1=BufRd.ar(numChannels,  bufnum1, idx1, 0);// 1: mono, 2: stereo

// .. gate
rel1=Trig1.kr(idx1-end1,0); // release when end cue point is reached
gate1=SetResetFF.kr(trig1,rel1); // build a gate to be used in SelectX

// loop 2
trig2=rel1; // loop2's trigger is loop1's release


// ..sweep and read
idx2=Sweep.ar(trig2, BufSampleRate.kr(bufnum2)) + start2;
sig2=BufRd.ar(numChannels, bufnum2, idx2, 0);// 1: mono, 2: stereo

// .. gate
rel2=Trig1.kr(((1-gate1)*idx2)-end2,0); // release when end cue point is reached // ==> "(1-gate1)*idx2" : Limit analyse to the portion where loop2 is expected to play. Without this we face nonexpected starts

// signal

env=EnvGen.kr(Env.asr(atk,1,rel),gate,doneAction: 2);

Hop it helps.

(*) The little bit more is that it loops over randomly selected buffers, with start and end points randomly selected among cue points passed as a buffer.