A Tuneful Sine

In the last post we generated a very simple note using a sine wave. Whilst some techno artists would disagree, it’s not very interesting having a tune consist of just a single note. So lets generate some different notes for our little tune.

Notes

So we know that to play an A, we can generate a sine wave at 440hz (repeating 440 times per second). Now, to generate an A# we need to play our sine wave at 466.16hz - 26.16hz faster than the A.

To play a B (one semitone higher than a A#) we need to play at 493.88hz. This is 27.71hz faster than A#.

The gaps between the notes are not linear!

The formula

The formula for calculating the frequency of each note is:

$$ f = 440 \cdot 2^{\frac{(note - 69)}{12}} $$

We can use the following rust code:

pub fn midi_note_to_freq(note: u32) -> f32 {
    440 * 2.0f32.powf((note as f32 - 69.0) / 12.0)
}

The note parameter is the midi note number. According to the MIDI standard, middle A is 69, A# is 70, B 71 and so on.

The tune

Then let’s update our main function to generate the tune.

As before:

fn main() {
    let mut phase = 0.0;

We’ll play the following notes (B, G, A#, E):

    let notes = [71, 67, 70, 64];

Then we need to allocate enough capacity to generate half a second per note in our list:

    let mut sine = Vec::with_capacity(SAMPLE_RATE * notes.len() / 2);

Now we need to go through each of our notes, get the frequency we want to play and use this to update our phase delte.

    for note in notes {
        let freq = midi_note_to_freq(note);
        let phase_delta = freq / SAMPLE_RATE as f32;

Then we generate our sine as before:

        for _ in 0..SAMPLE_RATE / 2 {
            sine.push((phase * std::f32::consts::TAU).sin());
            phase = (phase + phase_delta) % 1.0;
        }
    }

The rest is the same as before, just saving that tune to a wav file:

    let mut out_file = File::create("tuneful_sine.wav").unwrap();
    let header = wav::Header::new(
        wav::header::WAV_FORMAT_IEEE_FLOAT,
        1,
        SAMPLE_RATE as u32,
        32,
    );

    wav::write(
        header,
        &wav::bit_depth::BitDepth::ThirtyTwoFloat(sine),
        &mut out_file,
    )
    .unwrap();
}

And there we have a beautiful melody…

tuneful_sine.wav