Previous: Police siren
In this chapter, we are going to synthesize a kick sound as the first instrument in our drum kit.
A kick is the drum that gives a low frequency thump, and is generally used to mark the beat in electronic music.
The typical way to synthesize a kick with additive synthesis is to add two components together: the click and the timber.
The click is a short impulse that adds some punch to the attack. The timber is a longer, trailing sound with a more distinct tone.
We will start by synthesizing the timber. The easiest way is to produce a signal whose frequency sweeps from the mid frequencies to low in a fraction of a second, with an exponentially decreasing amplitude to simulate a natural decay.
To do this, we will need to generate two control signals that we will feed into
an osc
node: the frequency and the amplitude.
Let’s start with the amplitude: we want to generate an exponentially decreasing
signal. We will use the func
module which allows us to generate arbitrary
mathematical functions (check its specification with sndc -h func
).
func
takes a mathematical expression in its expr
input and will generate an
output buffer with that function. The usual mathematical functions such as
exp
, sin
and cos
are available, and we can use the following built-in
parameters:
$s
is a parameter that will go from 0 to 1.$t
is the time, or $s
multiplied by the duration of the buffer.$n
is the sample number.We can also of course specify the duration of the total output, its sampling rate, its interpolation type and so on.
Let’s create a SNDC file named kick.sndc
, containing a simple exponentially
decreasing signal:
timber_decay: func {
expr: "exp(-$t)";
duration: 1;
sampling: 100;
}
We’ve set the sampling rate to 100 since we don’t need too many samples: it’s a control signal, not an audio signal.
At that point, we would very much like to know what that signal looks like so we can tweak it better.
In order to do this, we will dump that buffer into a file and plot it using
gnuplot
. We will use the print
module, which is pretty straightforward:
timber_decay: func {
expr: "exp(-$t)";
duration: 1;
sampling: 100;
}
p: print {
in: timber_decay.out;
file: "decay.dat";
}
Let’s run this with sndc
:
$ sndc kick.sndc
We should now see a decay.dat
file. Let’s plot it with gnuplot
:
$ gnuplot
gnuplot> plot "decay.dat" with lines
You should see the following graph:
We can see that 100 samples were generated, and it does look like a decreasing exponential, but it’s not going to cut it: it decreases too slowly. We need to increase the decay rate. Also, the signal starts at 1. An amplitude of 1 is the maximum a sound card can take before saturating. Since we are going to add other signals to the timber, we should reduce the amplitude. We can start with 0.5, like so:
timber_decay: func {
expr: "0.5 * exp(-6 * $t)";
duration: 1;
sampling: 100;
}
p: print {
in: timber_decay.out;
file: "decay.dat";
}
Plotting the above with the previous method, we now see:
This looks much better. Let’s move on to the actual timber tone.
We will start with a simple tone like we have seen a couple of times before in this tutorial:
timber: osc {
function: "sin";
duration: 1;
freq: 135;
}
What we want now is to vary the frequency and sweep from about 135Hz down to
35Hz (those are totally empirical values, I spend a bit of time tweaking until I
was satisfied with those). We will use the func
module again for this, and we
will start with a naive linear sweep, going from 135Hz down to 35Hz in 1 second:
sweep: func {
expr: "(135 - 35) * (1 - $s) + 35";
duration: 1;
sampling: 100;
}
timber: osc {
function: "sin";
duration: 1;
freq: sweep.out;
}
This doesn’t sound right: we need to sweep much faster. In fact, the main problem is that the human hearing is logarithmic by nature, so to do a sweep that sounds linear, we actually need to decrease the frequency exponentially:
sweep: func {
expr: "(135 - 35) * exp(-10 * $s) + 35";
duration: 1;
sampling: 100;
}
timber: osc {
function: "sin";
duration: 1;
freq: sweep.out;
}
Whoa, this sounds much better, like a very fat kick (if your headphones let you hear those infra basses). It still needs some work though: it’s trailing too much (of course, it’s not decaying at all…) and it could be a bit snappier.
We will just plug the decay we generated above right into the amplitude
input
of the timber signal:
timber_decay: func {
expr: "0.5 * exp(-6 * $t)";
duration: 1;
sampling: 100;
}
sweep: func {
expr: "(135 - 35) * exp(-10 * $s) + 35";
duration: 1;
sampling: 100;
}
timber: osc {
function: "sin";
duration: 1;
freq: sweep.out;
amplitude: timber_decay.out;
}
It’s really taking shape now. Let’s add a bit more snap to it.
To add a bit of snap, we will simply generate a short sound and mix it with the timber. A simple sine tone at 100Hz, exponentially decaying like we have done above will do.
click_decay: func {
expr: "exp(-40 * $t)";
duration: 1;
sampling: 100;
}
click: osc {
function: "sin";
duration: 1;
freq: 100;
amplitude: click_decay.out;
}
Finally, we just mix both the timber and click together:
click_decay: func {
expr: "exp(-40 * $t)";
duration: 1;
sampling: 100;
}
click: osc {
function: "sin";
duration: 1;
freq: 100;
amplitude: click_decay.out;
}
timber_decay: func {
expr: "0.5 * exp(-6 * $t)";
duration: 1;
sampling: 100;
}
sweep: func {
expr: "(135 - 35) * exp(-10 * $s) + 35";
duration: 1;
sampling: 100;
}
timber: osc {
function: "sin";
duration: 1;
freq: sweep.out;
amplitude: timber_decay.out;
}
kick: mix {
input0: click.out;
gain0: 0.2;
input1: timber.out;
gain1: 1;
}
You may now experiment with the different parameters and see how they influence
the sound. Try changing the function
input of the timber
node to something
like saw
or square
for a more “hardcore” kick.
Try changing the parameters in the timber_decay
’s expr
input to make a
fatter, longer kick or a dryer one, try changing the sweep’s start and end
frequencies and so on.
Experiment with the osc
’s p_offset
input: by shifting the period, the signal
can start higher on a sine crest, making the sound a lot snappier.
Let’s have some fun and use the drumbox
module to make a simple rhythm.
The drumbox
module takes in a bpm
, a bunch of samples and their associated
patterns. Samples are given via the sample<n>
inputs and patterns via the
seq<n>
inputs.
The way we define a sequence is via a string: a “x” will play the associated
sample at that beat, and a “-” will skip that beat. By default, there are 4
divisions in a beat (there will be 4 “x” or “-” for every beat) but this can be
changed via the divs
input. Note that all other characters are ignored so we
can layout the string in an easy to read way. Let’s add the following node at
the bottom of our kick.sndc
file:
rhythm: drumbox {
bpm: 120;
sample0: kick.out;
seq0: "x--- x--- x--- x-x-";
}
Let’s keep expanding our drum kit with more instruments to make more interesting rhythm patterns.
Next: Create a snare