1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
use crate::synthesis::wavetable::{SAWTOOTH_WAVETABLE, SQUARE_WAVETABLE, TRIANGLE_WAVETABLE, WAVETABLE};
/// Different waveform types for synthesis
///
/// All waveforms use band-limited wavetables to prevent aliasing at high frequencies.
/// This ensures clean audio quality across the entire frequency spectrum.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Waveform {
Sine,
Square,
Sawtooth,
Triangle,
}
impl Waveform {
/// Generate a sample for this waveform at a given phase (0.0 to 1.0)
///
/// All waveforms use pre-computed band-limited wavetables for high-quality,
/// alias-free synthesis. This is much faster than computing waveforms
/// mathematically and produces better audio quality.
#[inline(always)]
pub fn sample(&self, phase: f32) -> f32 {
match self {
Waveform::Sine => Self::sine(phase),
Waveform::Square => Self::square(phase),
Waveform::Sawtooth => Self::sawtooth(phase),
Waveform::Triangle => Self::triangle(phase),
}
}
/// Sine wave: smooth, pure tone (band-limited wavetable)
///
/// Sine waves are already band-limited (single harmonic), so no aliasing occurs.
#[inline(always)]
fn sine(phase: f32) -> f32 {
WAVETABLE.sample(phase)
}
/// Square wave: rich in odd harmonics, hollow sound (band-limited wavetable)
///
/// Uses additive synthesis with band-limited harmonics to prevent aliasing.
/// Sounds identical to a perfect square wave but without the harsh digital artifacts.
#[inline(always)]
fn square(phase: f32) -> f32 {
SQUARE_WAVETABLE.sample(phase)
}
/// Sawtooth wave: rich in all harmonics, buzzy/bright sound (band-limited wavetable)
///
/// Uses additive synthesis with band-limited harmonics to prevent aliasing.
/// Produces a clean, bright sound suitable for leads and basses.
#[inline(always)]
fn sawtooth(phase: f32) -> f32 {
SAWTOOTH_WAVETABLE.sample(phase)
}
/// Triangle wave: smooth, few harmonics (band-limited wavetable)
///
/// Uses additive synthesis with band-limited odd harmonics at 1/n² amplitude.
/// Produces a rounder, softer sound than square waves.
#[inline(always)]
fn triangle(phase: f32) -> f32 {
TRIANGLE_WAVETABLE.sample(phase)
}
/// Generate a sample for a frequency at a given sample clock and sample rate
#[inline(always)]
pub fn sample_at(&self, frequency: f32, sample_clock: f32, sample_rate: f32) -> f32 {
let phase = sample_clock * frequency / sample_rate;
self.sample(phase)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_waveforms() {
// Test that waveforms produce values in expected range
let waveforms = [
Waveform::Sine,
Waveform::Square,
Waveform::Sawtooth,
Waveform::Triangle,
];
for waveform in &waveforms {
for i in 0..100 {
let phase = i as f32 / 100.0;
let sample = waveform.sample(phase);
assert!(
sample >= -1.0 && sample <= 1.0,
"{:?} produced out of range sample: {}",
waveform,
sample
);
}
}
}
}