How to keep convolution output stable

I have been trying to convolve two signals to produce an everlasting convolution, for example to simulate a reverb with an infinite tail, like in these examples. Ive tried to convolve a sinewave with whitenoise, but the whitenoise keeps jumping around, is there a way to get rid of this? Disregard that it doesnt sound like reverb at all, thats besides the point; Im trying to make a start so i can build further from there.
This is the first thing ive tried.

(
{
	Convolution.ar(SinOsc.ar(200),WhiteNoise.ar(),2**14) * 0.001 ! 2;
}.play;
)

The whitenoise jumps around.
Then ive tried to record whitenoise in a buffer so that that every fft frame is exactly the same.

b = Buffer.alloc(s,2**14);

(
{
	RecordBuf.ar(WhiteNoise.ar(),b,loop:0,doneAction:2)
}.play;
)

(
{
	Convolution.ar(SinOsc.ar(200),PlayBuf.ar(1,b,loop:1),2**14)*0.001!2;
}.play;
)

But now it just ends. So i change the fft framesize to 2**13. The buffer size stays the same.

(
{
	Convolution.ar(SinOsc.ar(200),PlayBuf.ar(1,b,loop:1),2**13)*0.001!2;
}.play;
)

It plays endlessly now, but the jumping is still there.
For every other kernel i try this happens too. Can someone explain why this happens and tell me how to keep the output “stable”?

It’s not totally clear (to me) what you want/expect to get out of the convolution, as a sound result. My understanding is that the Convolution UGen isn’t really doing “infinite convolution”, but rather convolution (and with relatively small FFT windows!) of two changing signals. I think it’s inevitable it will “jump around” at a regular rate, since the kernel signal is changing all the time. Convolution2 and the other variants use a fixed kernel, and this is limited to half the size of the FFT window (so quite short).

If you want to convolve with very long (though not infinite) impulse responses, you could maybe look into PartConv, I’ve used that with some very long responses.

You should use some kind of delay network for infinite reverb, a series of Comb filters is common. You may even wish to use LocalIn/Out, but the warned the block size will alter the delay.

When using PartConv, it’s convenient to use Buffer.loadCollection.

The idea was to create drones from convolved signals (not just to create reverb), so they have to be smooth. And it would be most convienient that theyd be infinite, so that i have the most flexibility.
That being said ive sort of found a solution, not very elegantly. I just use convolution2 (probably does work also with PartConv), for a fixed kernel and keep triggering the envelope of the input signal, so its sort of like an infinite convolution. I randomized the trigger frequency so it sounds less pulsy.

b = Buffer.alloc(s,2**16);
(
{
	RecordBuf.ar(WhiteNoise.ar(),b,loop:0,doneAction:2);
}.play;
)

(
{	Convolution2.ar(SinOsc.ar(200)*EnvGen.kr(Env.perc(0.01,1),Impulse.kr(LFNoise1.kr(2).range(1,3))),b,0,2**16)*0.001!2;
}.play;
)

I understand what you mean. At first i thought that was the case, since whitenoise changes alot. But when i tried it with a saw wave kernel it jumped almost as much. I thought that saw and sine waves have a static output, thats why i started doubting my first thought. But i guess they dont!

First: Xiph.Org Video Presentations: Digital Show & Tell – the best video introduction to the features of band-limited waveforms that I’ve ever seen. This will explain why “static output” is not exactly what you thought it was.

There are two ways in which it isn’t “static” here: (changed my mind midstream but forgot to delete that)

What if the saw’s wavelength isn’t an integer number of samples? Let’s say it’s 100.15 samples/cycle. So cycle 1 is y0, y1… y100. Cycle 2 will start at y101 but y101 != y0, y102 != y1 etc. Why? Because y101 is at time index 0.15 between the original first and second samples.

You hear continuity but each subsequent cycle samples the bandlimited wave shape with a slightly different subsample offset relative to any consistent reference point. The shape of the sampled data changes continuously, to represent an unchanging waveform (and you were convolving against these changing data).

“Infinite” convolution drone… the convolution behaves like a resonating body being excited by some source of audio energy (noise or other) – basically physical modeling. All stable resonating bodies eventually come to rest. To keep them sounding forever, you’d need to keep putting energy in (just like a violin string can produce sound forever as long as you never stop bowing it).

For longer convolution kernels, do use PartConv.

hjh

3 Likes

Thanks that makes sense!
Sorry to ask the questions to you now, but do you have any idea why the playbuf method in the example didnt work with 2 ** 14 and why i had to reduce it to 2 ** 13? I figured that if you pass a buffer thats exactly the specified framesize, it would loop seamlessly. If it would loop seamlessly it wouldnt jump so much anymore, since its using exactly the same fft frame everytime.

It’s not just about the fft size, it’s also whether or not the signal fits perfectly into the same number of samples, i.e., the signal may, per sample block, oscillate exactly, once, twice, thrice… But if it doesn’t fit exactly (a whole number), as @jamshark70 said, you will get a different sound each time.

That was the case for a running signal, but why does this also hold for a buffer? I recorded a buffer thats exactly 2 ** 14 samples long, there cant be more or less, because thats how much memory i allocated for the buffer. Doesnt it just play the buffer at audio rate for exaclty 2 ** 14 samples? Because the buffer and the convolution frame are both exactly 2 ** 14 samples long, doesnt that mean that they should just start and end together exactly at the same time? Why does this what you guys say also hold for playing back a buffer?

The Convolution unit’s help file appears to need some attention – it gives inconsistent advice.

  • Argument list says: “Size of FFT frame, must be a power of two (512, 1024, 2048, 4096 are standard choices). Convolution uses twice this number internally. Note that the convolution gets progressively more expensive to run for higher powers! The maximum value you can use is 2^16=16384.”

  • Code examples say: “// must have power of two framesize- FFT size will be sorted by Convolution to be double this // maximum is currently a=8192 for FFT of size 16384”

You reported a good result with 2**13 = 8192 and a bad result with 2**14 = 16384, which is inconsistent with the argument list description but which is consistent with the fine print in the code examples.

hjh