## Sound Synthesis (1/ Oscillators)

By Vermeille on Monday 25 March 2013, 23:42 - C++ - Permalink

## Introduction

### Motivation

Yesterday, I was at BeMyApp contest, where we had to develop something about music. After a stupid lyrics generators using markov chains and an uninteresting game in html5, I decided to code a synthesizer (A dream I had for many years, since I'm really interested in sound synthesis).

## Theory

### Reminders

Okay, let's start with some sound theory.

Sound is mechanical wave propagating in the air. If those waves are periodic, it's a note. Taking a fundamental frequency (mostly 440 Hz, we can define our notes with the following formula:

As Fourier tells us, every periodic wave can be described in terms of a sum of sinus functions.

The wave with the lowest is named "fundamental", and multiples of this frequency are called "harmonics".

Let's see our 5 primitive waveforms:

### Sinus

The simplest wave is then a sinus: pure signal, without any harmonics. The formula is:

### Square

A square waveform, in sense of Fourier is an infinite serie summing all odd frequencies multiple of the harmonic.

Hopefully, we won't need to code such a formula. Electrically, we do this by commutating a DC generator, and we will do almost the same programatically.

### Sawtooth

A sawtooth is defined as a sum of all even harmonics

Electrically, I think that this is achieved with some RLC circuits (please, if you know, confirm it). Programatically, we don't need to code such a formula.

### Triangle

A triangle is the sum of odd harmonics, multiplying every th harmonic by and rolling off the harmonics by the inverse square of their relative frequency to the fundamental (thx Wikipedia :D)

### Whitenoise

It's just random numbers guys.

### Recap

## Synthesis systems

### Additive synthesis

This system is directly inspired from Fourier's series: take a lot of sinus, and add them to make a complex sound.

### Substractive synthesis

Take complex sounds (like the 5 ones defined above), and then apply some filters to treat them.

### FM synthesis

This kind of synthesis has almost an unpredictable result, and works like radio does: the timbre of a simple waveform is changed by frequency modulating it with a modulating frequency that is also in the audio range, resulting in a more complex waveform and a different-sounding tone.

### Wavetable synthesis

This one may be though like a combinations of others: take some sounds, and play them each for few milliseconds. The result can really be crazy. This is the one we will implement, because it allows killing acid sounds without efforts.

## We are in a digital world

### A word about your sound card

Since we're using a computer, we're dealing with discrete values. We will send our sound to the sound card in the form of many `int16`

, so we have to be able to give the amplitude of our signal considering sampling rate and the frequency. In most systems, the default sampling rate is 44100 Hz, it means that you have to feed your sound card with 44100 "values" per second (for each channel, that's why, assuming stereo, I'll duplicate each value).

### Generating waveforms

Enter the world of algorithms. Since I wrote my synthesizer both in Haskell and C++, I'll show, for each part, the language which is the easier to understand. If you never read haskell, list are constructed with `:`

, don't bother about infinite lists and recursions (allowed by laziness), and everything should be okay.

I need to introduce two magic numbers widely used in the code: 44100 is the default sampling rate we target, (-32767, 32768) are `short_min`

and `short_max`

```
-- takes a frequency and returns the number of data needed to fill a period
getPeriod :: Float -> Int
getPeriod freq = freq * 2 * pi / 44100
-- takes a number of milliseconds and returns the corresponding number of data
msToDatas :: Int -> Int
msToDatas ms = truncate $ 44100.0 / (1000.0 / fromIntegral ms)
```

#### Sine

It's really straigthforward, generate the ith value using the period, and recurse on the (i+1)th value.

```
sinW :: Float -> Int -> [Int16]
sinW freq i = sinW' i (getPeriod freq)
sinW' :: Int -> Float -> [Int16]
sinW' i period =
let new = (round $ (* 32767.0) $ sin (fromIntegral i * period)) in
new:new:sinW' (i + 1) period
```

#### Square

The square waveform is .

```
squareW :: Float -> Int -> [Int16]
squareW freq i = squareW' i (getPeriod freq)
squareW' :: Int -> Float -> [Int16]
squareW' i period = let new = (round . (* 32767.0) . fromIntegral . sign
$ sin (fromIntegral i * period)) in
new:new:squareW' (i + 1) period
sign x | x < 0 = -1
| otherwise = 1
```

#### Sawtooth

Algorithmically, this is simple : `samplesPerWavelength`

is the number of data to play during one period, `ampStep`

is the "amount of amplitude" to add between each step. Start at `int16_min`

, add `ampStep`

until we reach `int16_max`

, then restart.

```
sawW :: Int -> Float -> [Int16]
sawW i period = sawW' i period (-32767)
sawW' :: Int -> Float -> Int -> [Int16]
sawW' i period tempSample
| i < samplesPerWavelength =
let new = fromIntegral tempSample in
new:new:sawW' (i + 1) period (tempSample + ampStep period)
| otherwise = (-32767):(-32767):sawW' 0 period (-32767 + ampStep period)
where
samplesPerWavelength :: Int
samplesPerWavelength = truncate $ 44100.0 / period
ampStep :: Int
ampStep = (44100 * 2) `div` samplesPerWavelength period
```

#### Triangle

This looks like the sawtooth: `samplesPerWavelength`

is the number of data to play during one period, `ampStep`

is the "amount of amplitude" to add or substract between each step. Start at `int16_min`

, add `ampStep`

until we reach `int16_max`

, then substract `ampStep`

until we reach `int16_min`

etc...

```
triW :: Float -> Int-> [Int16]
triW period i = triW' period (-32766)
$ (44100 * 3)
`div` (samplesPerWavelength period)
where
samplesPerWavelength :: Float -> Int
samplesPerWavelength freq = truncate $ 44100.0 / freq
triW' :: Float -> Int -> Int -> [Int16]
triW' period tempSample ampStep
| abs tempSample > 32767 =
let new = fromIntegral $ tempSample + ampStep in
new:new:triW' period (tempSample - ampStep) (-ampStep)
| otherwise =
fromIntegral tempSample
:fromIntegral tempSample:triW' period (tempSample + ampStep) ampStep
```

### Playing notes

Okay, now you can play frequencies. You should create a simple array which allows a note number to be converted to a frequency, and you'll be able to play notes.

### Wavetable Synthesis

As I promised, here, we do some wavetable synthesis: just play some waveforms a few milliseconds.

```
int main()
{
double freqs[] = {
#define X(A) A,
# include "freqs.def"
#undef X
};
srand(time(nullptr));
// One theme I composed for Subliminal AEon
int note = 0;
int song[] = {
48, 55, 56, 48, 51, 55, 50, 53,
48, 55, 56, 48, 51, 55, 50, 53,
47, 50, 51, 55, 51, 50, 51, 48,
47, 50, 51, 55, 51, 50, 51, 48
};
while (true)
{
for (size_t i = 0; i < 5; ++i)
{
// Second parameter is the number or milliseconds to play
Sinus(freqs[song[note]], 6);
Square(freqs[song[note]], 5);
Sinus(freqs[song[note]], 7);
Saw(freqs[song[note]], 3);
Triangle(freqs[song[note]], 3);
Sinus(freqs[song[note]], 7);
White(2);
Sinus(freqs[song[note]], 7);
}
note = (note + 1) % (sizeof (song) / sizeof (song[0]));
}
return (0);
}
```

And just play that using

`./a.out | aplay -c 2 -f U16_LE -r 44100`

You may have to adjust parameters of `aplay`

depending on your architectures

## Going further

### New waves

As an example, you can now merge waveforms by adding or multiplying them to create additionnal waveforms. Feel free to create whatever you want when merging.

```
triPlusSquare :: Float -> [Int16]
triPlusSquareW freq = zipWith addW (triW freq) (squareW (freq * 2))
addW :: Int16 -> Int16 -> Int16
addW x y = (x `div` 2 + y `div` 2)
```

### Filters

It sounds really acid. We will give us the possibility (in the next article :D) to use Fourier's transform to use {low,high}-pass filters.

## Conclusion

I was really proud to create my own synthesizer, you should do it too :D !

## Comments

I love the simple random white noise one. You can make a few NES and Atari 2600 effects easily with it.

One piece of advice: if you generate waveforms using floating-point, use double if you can, especially if you are doing a for loop.