Rust Audio Programming: Oscillator – Build a Sine Wave [PART 1]

YuriiYurii
6 min read

Hey there 👋!

Whether you’re a seasoned rustacean, a curious audio hacker, or just someone who thinks synths are cool — welcome aboard!

This is the Rust Audio Programming series, and today we’re diving into the basics of audio programming by building our very first sine wave — the audio equivalent of “hello, world”.

⚙️ This tutorial assumes you already have the Rust toolchain installed (via rustup). If not, check out the official install guide — setting it up is out of scope for this post.

So what digital sound is?

Sound, at its core, is a vibration in the air — pressure waves that travel to our ears. Inside, these vibrations are turned into signals that our brains understand as sound.

In the digital world, we sample these continuous waves at discrete intervals — usually thousands of times per second — and store the values as numbers.

This is known as digital audio. For example, CD-quality audio samples the waveform 44,100 times per second (44.1 kHz), capturing its shape with enough detail that it sounds smooth and natural to the human ear.

Enter the waveform

Every sound you hear — a piano note, a bird chirp, a voice — has a unique waveform. The shape of this waveform determines the character or timbre of the sound.

Waves are building blocks of synthetic sound, and they’re what we’ll be working with in this series.

What’s a note, really?

When we talk about a musical note, we're really talking about a specific frequency of vibration — how fast the air is wiggling back and forth. Each full wiggle is called a cycle.

That frequency is measured in hertz (Hz), or cycles per second. So if a sound wave vibrates 440 times in one second, it has a frequency of 440 Hz — which just happens to be the pitch of the musical note A4.

The Science of Music — Kaitlin Bove Music

Different notes are just different frequencies:

  • A lower note? Slower vibrations (lower Hz).

  • A higher note? Faster vibrations (higher Hz).

So when we generate sound in code, we’re not saying play A4 we’re saying “generate a waveform that cycles at 440 Hz.”.

Let’s try it out!

First, let’s set up a fresh Rust project for our experiment. Open your terminal and run:

cargo new sine_wave && cd sine_wave

💡 These examples use Unix-style commands (Linux/macOS). If you’re on Windows… well, you know the drill — adjust accordingly.

Before jumping into code, let’s take a second to understand what we’re actually generating.

We’re going to produce a 2-second sine wave that plays the musical note A4 — which has a frequency of 440 Hz. That means the waveform will complete 440 full cycles every second.

To represent that wave digitally, we’ll sample it — break it down into thousands of tiny values we can save to a file.

We’ll use a sample rate of 44100 Hz (standard CD quality), meaning we’ll capture 44100 samples per second. For 2 seconds of audio: 44100 samples/sec × 2 sec = 88200 samples.

To generate each sample, we’ll use the classic sine wave equation: sample = amplitude × sin(2π × frequency × time), where:

  • frequency is 440.0 (A4)

  • time is the current time in seconds for each sample (t / sample_rate)

  • amplitude controls the volume — we’ll use the max for 16-bit audio

🧠 Isn’t the sine formula usually written differently?

In math you’ll often see it as A × sin(2πft + φ), where φ is the phase offset.

In our case, we’re starting the wave right at the beginning of its cycle, so we just set phase to zero — and skip it entirely.

Now let’s generate our very first sine wave and save it as a .wav file we can actually play and analyze.

To do that, we’ll use a single dependency: hound.

Run this in your terminal:

cargo add hound@3.5

hound is a simple Rust library for reading and writing WAV files.

Open src/main.rs and replace everything with this:

use std::f32::consts::PI;

fn main() {
    // WAV file settings
    let spec = hound::WavSpec {
        channels: 1,        // mono
        sample_rate: 44100, // samples per second
        bits_per_sample: 16,
        sample_format: hound::SampleFormat::Int,
    };
    // Create a WAV writer
    let mut writer = hound::WavWriter::create("sine.wav", spec).expect("Failed to create WAV file");
    // Sine wave parameters
    let freq_hz = 440.0; // frequency (A4)
    let duration_secs = 2.0; // 2 seconds
    let amplitude = i16::MAX as f32; // max volume

    let sample_rate = spec.sample_rate as f32;
    let total_samples = (sample_rate * duration_secs) as usize;

    for t in 0..total_samples {
        let time = t as f32 / sample_rate;
        let sample = (amplitude * (2.0 * PI * freq_hz * time).sin()) as i16;
        writer.write_sample(sample).unwrap();
    }

    writer.finalize().unwrap();
    println!("✅ Sine wave written to 'sine.wav'");
}

What’s happening in the code?

Let’s break down the essential parts:

1. WAV file setup

let spec = hound::WavSpec {
    channels: 1,
    sample_rate: 44100,
    bits_per_sample: 16,
    sample_format: hound::SampleFormat::Int,
};

We configure the .wav file to be:

  • Mono audio (channels: 1)

  • 44.1 kHz sample rate (standard quality)

  • 16-bit samples (i.e., each sample is stored as a signed 16-bit integer)

2. Main parameters

let freq_hz = 440.0;
let duration_secs = 2.0;
let amplitude = i16::MAX as f32;

We want a 440 Hz sine wave (A4 note) that lasts 2 seconds, at maximum volume.

3. Generate samples

for t in 0..total_samples {
    let time = t as f32 / sample_rate;
    let sample = (amplitude * (2.0 * PI * freq_hz * time).sin()) as i16;
    writer.write_sample(sample).unwrap();
}

writer.finalize().unwrap();

We:

  • Loop through each sample

  • Use the sine wave formula to calculate its value

  • Write the value to the WAV writer’s internal buffer

Then, in the next step:

  • Call .finalize() to flush the buffer and write everything to the file

Now let’s actually run the code and hear what we’ve created:

cargo run

You should see:

✅ Sine wave written to 'sine.wav'

That means your .wav file was successfully generated! Now go ahead and play sine.wav using your favourite audio player.

If everything worked, you should hear a clean, steady tone — no clicks, no weird noise. That’s a pure 440 Hz sine wave, also known as concert A.

Let’s see the sound

To visualize the waveform, open sine.wav in Audacity — a free, open-source audio editor that makes it easy to zoom in and inspect waveforms.

You should see a smooth, repeating sine wave — but heads up, it might not look like much at first.

Audacity will probably show it as a solid block by default. Zoom in horizontally until you can see the individual wave cycles — that’s your sine wave in action!

🏁 That’s a wrap for [PART 1]

Congrats — you just generated your first sine wave from scratch using Rust. 🎉

You now understand:

  • What digital sound is

  • How sampling works

  • How to generate individual audio samples using math

Not bad for one article, huh?

🔜 What’s next?

In the next part of this series, we’ll build on what we’ve made by changing the note over time — like playing a simple melody with just a sine wave.

It sounds straightforward… but we’ll run into some interesting behavior that’s worth a closer look 👀

Along the way, we’ll improve the math behind our waveform generation and start shaping the code into something more flexible and reusable.

▶️ Continue to Part 2:

Coming soon: Rust Audio Programming: Oscillator – Handle Frequency Changes Smoothly [PART 2]

💾 Source code from this article:

View on GitHub →

0
Subscribe to my newsletter

Read articles from Yurii directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Yurii
Yurii

original rustafarian from Kyiv, Ukraine