Convolution#
Convolution is an operation that takes two functions and outputs a new function.
In that sense, it is very similar to addition or multiplication.
Given two function
The same is true for the convolution of these two functions:
But what does the convolution operator
Discrete Convolution#
Let us start with two arrays of numbers which represent discrete functions
We can use component-wise multiplication to generate a new function:
[1,2,3,4] * [-1,3,-1,3] // [ -1, 6, -3, 12 ], y_1[k] * y_2[k]
In that case,
The convolution is defined very differently. The idea is to mirrow the second function and to compute the weighted sum of a sliding window.

Fig. 25 Visualization of the computation of a convolution of two discrete signals (arrays of numbers).#
Given
Interestingly, the lengths of the signals do not have to match, and the new length of the new signal is equal to the sum of the length of the original signals minus 2.
The following sclang
function computes the convolution of two discrete function:
(
~convolve = {arg a, b;
var a_pad, b_pad, result, val;
a_pad = Array.fill(b.size-1, 0) ++ a;
b_pad = b.reverse ++ Array.fill(a.size-1, 0);
result = [];
for(0, a.size+b.size-2, {
val = (a_pad * b_pad).sum;
result = result ++ [val];
b_pad = b_pad.rotate(1);
});
result;
};
)
~convolve.([1,2,3,4], [-1,3,-1,3]); // [ -1, 1, 2, 6, 15, 5, 12 ]
Convolution of Discrete Signals
Given two discrete and finite signals
where
Since we are most interested in peridic functions, a more useful definition is given by the circular discrete convolution.
In that case, we
Circular Discrete Convolution
Given two discrete signals
where

Fig. 26 Visualization of the computation of a convalution of two discrete signals (arrays of numbers) where the second is (infinite) and periodic.#
The circular convolution of a finite signal with a infinite periodic discrete function is an infinite periodic funciton.
This in our code we do not construct an Array
but a new function.
(
~cconvolve = {arg a, b;
var func = {arg n;
(a *.s (b.reverse.rotate).rotate(n)).sum;
};
func;
};
f = ~cconvolve.([1,2,3,4], [-1,3,-1,3]);
y = Array.fill(12, {arg i; f.(i);});
y // [ 14, 6, 14, 6, 14, 6, 14, 6, 14, 6, 14, 6 ]
)
The sclang
code is relatively short.
cconvolve
returns the function, i.e.,
Note that the convolution is like multiplying two functions, but one is mirrored and shifted.
Since the convolution is the sum of these similarity measures, it tells us something about the similarity of the two functions too.
In our example, we can see that
If both signals are discrete but infinite (and defined on
Continuous Convolution#
If
This looks intimidating but there is nothing to fear.
Instead of a sum over an index
Rules#
Imagine the function
Let us illustrate some more examples.
In the following plot each row represents one convolution.
In the first and second column you find the functions

Fig. 27 Different continuous convolutions. Functions of the first two columns are convoluted resulting in the third column.#
The plots show us some effects of the convolution:
Convolving a function
with an impulse function just copies .Convolving
with a shifted impulse function makes a shifted copy of .Convolving
with a scaled impulse function scales .Convilving
with a scaled, shifted impulse function scales and shifts .Convolving with an impulse trainsuch (last row) adds a shifted original to the output for each impulse.
As many copies will be superimposed as there are nonzero impulses.
Copies will be shifted (delayed) by the position of the impulse in the impulse train function.
Shifted copies will be scaled by the amplitude of the impulses.
Shifted, scaled copies of the original are summed, smearing the result.
Convolution Theorem
Let
and
Multiplying in the time domain convolves in the frequency domain and multiplying in the frequency domain convolves in the time domain. Therefore, to compute the convolution one can do so by compute the Fouier tranform beforehand. In fact, this is how the unit generators, such as Convolution, are implemented.
The Convolution Unit Generator#
We can compute a discrete convolution on the audio server using the Convolution unit generator. Let’s try to contruct a triangle wave by convoluting two pulse waves.
({
var n, sig;
n = 512;
sig = LFPulse.ar(s.sampleRate/n);
sig = Convolution.ar(sig, sig, 2*n) * n.reciprocal;
sig
}.plot(512*5/s.sampleRate);
)

Fig. 28 Convoluting two pulse waves results in a triangle wave.#
As you can see, we get the expected result!
({
var n, sig, kernel;
n = 512;
sig = LFSaw.ar(s.sampleRate/n);
kernel = LFSaw.ar(s.sampleRate/n);
sig = Convolution.ar(sig, kernel, 2*n) * n.reciprocal;
sig
}.plot(512*5/s.sampleRate);
)

Fig. 29 Convoluting two sawtooth waves results in parabola-like wave.#
To compute ‘perfect’ convolution in the time domain, one requires all function values beforehand. This would make real-time processing impossible! Therefore, Convolution computes a windowed Fourier transform, i.e., the Fouier tranform of a short snipped of the functions (in and the kernel). Based on that, it computes the convolution of those snippets. To do so, the unit generator has to fill a buffer of a specific size (twice the number of framesize). Before the convolution can start, the buffer has to be filled; therefore, the result is zero at the beginning. The larger the buffer, the more expensive the operation gets and the more zeros there are at the beginning. If one period of the signals fits approximately into the buffer, the result will look approximately similar to a ‘perfect’ convolution.