Sampler clicking problem

I’ve developed a sampler that uses loop points in an audio file. It works really nicely, except there’s an audible click when moving between the attack portion of the sound and the loop portion of the sound. I’ve tried adding a short crossfade, but there’s still an audible discontinuity. Any ideas on how to avoid this problem? I’ve attached my code below, and also attached the audio file I’m referencing here.

b = Buffer.read(s, "D:/sample.58.Viola.arco.sulC.mf.C4C5.mono.wav");

// This sampler is for sustaining sounds for an arbitrary duration. You will need to provide loop points.
SynthDef(\sampler_loop, {
	var atk_dur, atk_env, atk_sig, fade_in_time, loop_env, loop_sig, playback_rate, sig, sigPos, t_trig;

	// This allows us to morph between sounds and have a clean sound envelope.
	fade_in_time = 0.01;
    
    playback_rate = \transpose.ir(1) * BufRateScale.kr(\buf.ir(0));
    atk_dur = \sample_loop1.ir(0) / \buf_sample_rate.ir(44100);

	// This resets the loop to the start position after the attack sound has played,
	// for a seamless transition to the looped sound.
    t_trig = EnvGen.kr(Env.new([0, 0, 1], [atk_dur - fade_in_time, 0], \lin));

    // The two signals that make up the sound
    atk_sig = PlayBuf.ar(1, \buf.ir(0), playback_rate, 1, 0, 0);
    loop_sig = BufRd.ar(1, \buf.ir(0), Phasor.ar(t_trig, playback_rate, \sample_loop1.ir(0), \sample_loop2.ir(1), \sample_loop1.ir(0)), 1.0, 2);

	// Envelopes
	// The attack envelope governs the attack sound, and the loop envelope will not open until
	// the attack is finishing up.
    atk_env = EnvGen.ar(Env([1, 1, 0], [atk_dur, fade_in_time], \lin));
    loop_env = EnvGen.ar(Env([0, 0, 1], [atk_dur, fade_in_time], \lin));
	atk_sig = atk_sig * atk_env;
	loop_sig = loop_sig * loop_env;
    
    // merge the attack and loop to form one signal
    sig = atk_sig + loop_sig;
    sig = sig.madd(\mul.kr(1), \add.kr(0));
    sig = Pan2.ar(sig, \pos.kr(0.0));
	Out.ar(\out.kr(0), sig);
}).add;

Synth(\sampler_loop, [\buf, b, \sample_loop1, 71331, \sample_loop2, 76067, \buf_sample_rate, 44100, \mul, 0.5]);

If you’re aiming for an equal power cross-fade, a straightforward way— ignoring the complexities of average loudness for more uncorrelated signals—is just to ensure that the sum of the squares of the gains is constant. This avoids the dips.

Two linear envelopes don’t do it. For example, following this logic, the midpoint of the cross-fade should have both gains at sqrt(1/2)​ (≈ 0.707) rather than 1/2 (0.5), as in linear interpolation (ignoring the scale factor).

Quick snippet/test code:

(
{
    var gain1, gain2, t, dur = 10; // Duration of crossfade

    t = Line.ar(0, 1, dur); // t goes from 0 to 1 over 'dur' seconds

    // Calculate the gains with 1/sqrt(2) scaling factor
    gain1 = sqrt((1 - t) * 0.5);
    gain2 = sqrt(t * 0.5);

    // Test the sum of the squares is CONSTANT
    (gain1.squared + gain2.squared).poll(label: \sumOfSquares); // constant
    (gain1 + gain2).poll(label: \sums); // oscilates

    // Test the envelopes with a crossfade between two oscillators in different speakers
    [SinOsc.ar(440, 0, gain1), SinOsc.ar(440, 0, gain2)]
}.scope;
)
1 Like