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…