Create a kick

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.

The timber 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:

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.

Plotting a signal using gnuplot

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:

decay1

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:

decay2

This looks much better. Let’s move on to the actual timber tone.

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.

Putting the timber together

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.

The click

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;
}

Putting things together

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.

A basic rhythm

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