In a recent project, I wanted to do convolution against a time-varying signal and be able to crossfade the amount of convolution. Convolution.ar is all-on, all the time, so I decided to do it with PV_Mul instead. The trick was to prepare a buffer with the frequency-domain equivalent of 1.0, so that when the convolution is “faded out,” the spectrum gets multiplied by this identity operand.
Sharing bc it shows something about using FFT buffers and FFTTrigger.
(
s.waitForBoot {
var fftSize = 2048; // makes a big difference to the end result!
var cond = CondVar.new;
// sendCollection + wait needs to fork
fork {
~unity = Buffer.sendCollection(s,
[1, 1] ++ Array.fill(fftSize div: 2 - 1, [1, 0]).flat,
action: { cond.signalAll }
);
};
cond.wait;
~srcBus = Bus.audio(s, 2);
Pdef(\src, Pbind(
\degree, Pwhite(-7, 14, inf),
\dur, Pexprand(0.1, 0.8, inf),
\legato, Pexprand(0.6, 4.0, inf),
\amp, Pexprand(0.06, 0.18, inf),
\amp, 1,
\out, ~srcBus
)).play;
~fx = { |inbus, unity|
var hop = 0.5;
var sig = In.ar(inbus, 2);
var fft = FFT(Array.fill(2, { LocalBuf(fftSize) }), sig, hop);
// replace 'kernel' with any other signal
// I'm just using a noisy FM pair here
var kernel = SinOsc.ar(0,
phase: (Phasor.ar(0, LFDNoise3.kr(0.8).exprange(10, 200) * SampleDur.ir, 0, 1) * 2pi
+ (45 * SinOsc.ar(LFDNoise3.kr(0.8).exprange(30, 1000)))
) % 2pi
) * 0.2; // empirically this needs to be scaled down
var kFFT = FFT(LocalBuf(fftSize), kernel, hop);
var unityFFT = FFTTrigger(unity, hop, polar: 0);
// note, do not put unityFFT first, or it will get overwritten
// xfade is reversed for this reason
var xfadeKernel = PV_XFade(kFFT, unityFFT, MouseX.kr(1, 0));
var convo = PV_Mul(fft, xfadeKernel);
IFFT(convo) * 0.1
}.play(s.defaultGroup, addAction: \addToTail, args: [
unity: ~unity, inbus: ~srcBus
]);
~fx.onFree { Pdef(\src).stop; ~unity.free; ~srcBus.free };
};
)
hjh