Simple Sine

Playing a simple tone in Rust

Let’s use Rust to generate an exceedingly simple musical note and save it to a wav file. The very simplest tone possible is created by playing a simple sine wave.

Create a new Cargo project and add the wav crate to make it easier to create a wav file.:

wav = "1.0.0"

Edit our src/main.rs. Import std::fs::File:

use std::fs::File;

Now we want to set a constant:

const SAMPLE_RATE: usize = 44_100;

Some very basic audio theory

Playing audio through a computer is simply a case of streaming a bunch of numbers through to the audio device. The audio device uses these numbers to determine how what position a speaker cone should be in. If we shake the speaker cone back and forwards quickly enough it will create a vibration, and that vibration will make a sound.

The SAMPLE_RATE we are using is the number of times per second that we can move the speaker cone. Typically that number will be 44,100 samples every second.

To play a note at a given frequency, we want to create a repeated pattern that will be played a certain times per second. To play a middle A, that number is 440. (Some argue it should be 432hz.) So if we want to play a pure middle A we are going to want to play a sine wave that completes each cycle 440 times per second.

Main

Let’s code up our main function.

We need to keep track of the current position we are in the sine:

fn main() {}
    let mut phase = 0.0;

and the frequency of the tone we want to play:

    let freq = 440.0;

and the phase_delta:

    let phase_delta = freq / sample_rate.0 as f32;

The phase delta is the amount we need to update the phase for each sample to ensure that we repeat the cycle freq number of times every sample_rate samples.

We are going to generate one seconds worth of sound, so we need to generate 44,100 samples (our sample rate).

let mut sine = Vec::with_capacity(44_100);

Then, for each element in the slice we calculate the sine at the current phase. We multiply the phase by TAU (2 times π) since Sine actually repeats every 2π.

We then update our phase by adding the phase_delta to it. We take the remainder from 1.0 to ensure our phase just repeats between 0 and 1.

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

That’s our sound generated. We now need to save it to a file. You can see the docs for wav to see what each parameter sent to the header means:

    let mut out_file = File::create("simple_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();

Then cargo run and we should have a file that when played sounds like this:

simple_sine.wav