use std::f32::consts::PI;
use std::sync::Arc;
pub const DEFAULT_TABLE_SIZE: usize = 2048;
#[derive(Clone, Debug)]
pub struct Wavetable {
table: Arc<Vec<f32>>,
}
impl Wavetable {
pub fn from_fn<F>(size: usize, f: F) -> Self
where
F: Fn(f32) -> f32,
{
let mut table = Vec::with_capacity(size);
let size_recip = 1.0 / (size as f32);
for i in 0..size {
let phase = (i as f32) * size_recip;
table.push(f(phase));
}
Self {
table: Arc::new(table),
}
}
pub fn from_samples(samples: Vec<f32>) -> Self {
Self {
table: Arc::new(samples),
}
}
pub fn from_harmonics(size: usize, harmonics: &[(usize, f32)]) -> Self {
let mut table = vec![0.0; size];
const TWO_PI: f32 = 2.0 * PI;
let size_recip = 1.0 / (size as f32);
for &(harmonic, amplitude) in harmonics {
let harmonic_f32 = harmonic as f32;
for (i, sample) in table.iter_mut().enumerate() {
let phase = (i as f32) * size_recip;
*sample += amplitude * (phase * harmonic_f32 * TWO_PI).sin();
}
}
let max_amp = table.iter().map(|&x| x.abs()).fold(0.0f32, |a, b| a.max(b));
if max_amp > 0.0 {
for sample in &mut table {
*sample /= max_amp;
}
}
Self {
table: Arc::new(table),
}
}
pub fn sine() -> Self {
Self::from_fn(DEFAULT_TABLE_SIZE, |phase| (phase * 2.0 * PI).sin())
}
pub fn saw_bandlimited() -> Self {
let harmonics: Vec<(usize, f32)> = (1..32).map(|n| (n, 1.0 / n as f32)).collect();
Self::from_harmonics(DEFAULT_TABLE_SIZE, &harmonics)
}
pub fn square_bandlimited() -> Self {
let harmonics: Vec<(usize, f32)> = (0..16)
.map(|i| {
let n = 2 * i + 1; (n, 1.0 / n as f32)
})
.collect();
Self::from_harmonics(DEFAULT_TABLE_SIZE, &harmonics)
}
pub fn triangle_bandlimited() -> Self {
let harmonics: Vec<(usize, f32)> = (0..16)
.map(|i| {
let n = 2 * i + 1; let sign = if i % 2 == 0 { 1.0 } else { -1.0 }; (n, sign / (n * n) as f32)
})
.collect();
Self::from_harmonics(DEFAULT_TABLE_SIZE, &harmonics)
}
pub fn triangle() -> Self {
Self::from_fn(DEFAULT_TABLE_SIZE, |phase| {
if phase < 0.5 {
4.0 * phase - 1.0
} else {
-4.0 * phase + 3.0
}
})
}
pub fn pwm(duty_cycle: f32) -> Self {
Self::from_fn(DEFAULT_TABLE_SIZE, move |phase| {
if phase < duty_cycle { 1.0 } else { -1.0 }
})
}
#[inline(always)]
pub fn sample(&self, phase: f32) -> f32 {
let phase = phase.fract();
let table_size = self.table.len();
let table_pos = phase * (table_size as f32);
let index = table_pos as usize;
let frac = table_pos - index as f32;
let sample1 = unsafe { *self.table.get_unchecked(index) };
let sample2 = unsafe { *self.table.get_unchecked((index + 1) & (table_size - 1)) };
sample1 + (sample2 - sample1) * frac
}
#[inline(always)]
pub fn sample_at(&self, frequency: f32, time: f32) -> f32 {
let phase = time * frequency;
self.sample(phase)
}
pub fn fill_buffer_simd(
&self,
buffer: &mut [f32],
start_phase: f32,
phase_increment: f32,
) -> f32 {
self.fill_buffer_real_simd(buffer, start_phase, phase_increment)
}
#[inline(always)]
fn fill_buffer_real_simd(
&self,
buffer: &mut [f32],
start_phase: f32,
phase_increment: f32,
) -> f32 {
use crate::synthesis::simd::SIMD;
let mut phase = start_phase;
let table_size = self.table.len();
let table_size_f32 = table_size as f32;
let mask = table_size - 1;
const SIMD_WIDTH: usize = 8;
let num_chunks = buffer.len() / SIMD_WIDTH;
let remainder_start = num_chunks * SIMD_WIDTH;
let mut samples1 = [0.0f32; SIMD_WIDTH];
let mut samples2 = [0.0f32; SIMD_WIDTH];
let mut fracs = [0.0f32; SIMD_WIDTH];
for chunk_idx in 0..num_chunks {
let chunk_start = chunk_idx * SIMD_WIDTH;
let chunk = &mut buffer[chunk_start..chunk_start + SIMD_WIDTH];
for i in 0..SIMD_WIDTH {
let p = (phase + (i as f32) * phase_increment).fract();
let table_pos = p * table_size_f32;
let index = table_pos as usize;
let frac = table_pos - index as f32;
samples1[i] = unsafe { *self.table.get_unchecked(index) };
samples2[i] = unsafe { *self.table.get_unchecked((index + 1) & mask) };
fracs[i] = frac;
}
SIMD.lerp_buffers(chunk, &samples1, &samples2, &fracs);
phase += (SIMD_WIDTH as f32) * phase_increment;
}
for sample in buffer.iter_mut().skip(remainder_start) {
*sample = self.sample(phase);
phase += phase_increment;
}
phase.fract()
}
#[allow(dead_code)]
#[inline(always)]
fn fill_buffer_scalar(
&self,
buffer: &mut [f32],
start_phase: f32,
phase_increment: f32,
) -> f32 {
let mut phase = start_phase;
for sample in buffer {
*sample = self.sample(phase);
phase += phase_increment;
}
phase.fract()
}
pub fn len(&self) -> usize {
self.table.len()
}
pub fn is_empty(&self) -> bool {
self.table.is_empty()
}
}
impl Default for Wavetable {
fn default() -> Self {
Self::sine()
}
}
lazy_static::lazy_static! {
pub static ref WAVETABLE: Wavetable = Wavetable::sine();
pub static ref SAWTOOTH_WAVETABLE: Wavetable = Wavetable::saw_bandlimited();
pub static ref SQUARE_WAVETABLE: Wavetable = Wavetable::square_bandlimited();
pub static ref TRIANGLE_WAVETABLE: Wavetable = Wavetable::triangle_bandlimited();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wavetable_sine_accuracy() {
let table = Wavetable::sine();
let test_phases = [0.0, 0.25, 0.5, 0.75, 1.0];
for &phase in &test_phases {
let table_value = table.sample(phase);
let exact_value = (phase * 2.0 * PI).sin();
assert!(
(table_value - exact_value).abs() < 0.01,
"Phase {}: table={}, exact={}, error={}",
phase,
table_value,
exact_value,
(table_value - exact_value).abs()
);
}
}
#[test]
fn test_wavetable_wrapping() {
let table = Wavetable::sine();
assert!((table.sample(0.0) - table.sample(1.0)).abs() < 0.01);
assert!((table.sample(0.25) - table.sample(1.25)).abs() < 0.01);
assert!((table.sample(0.5) - table.sample(2.5)).abs() < 0.01);
}
#[test]
fn test_wavetable_continuity() {
let table = Wavetable::sine();
for i in 0..100 {
let phase1 = i as f32 / 100.0;
let phase2 = (i + 1) as f32 / 100.0;
let val1 = table.sample(phase1);
let val2 = table.sample(phase2);
assert!(
(val1 - val2).abs() < 0.1,
"Discontinuity detected between {} and {}",
phase1,
phase2
);
}
}
#[test]
fn test_sample_at() {
let table = Wavetable::sine();
let sample = table.sample_at(440.0, 0.0);
assert!(sample.abs() < 0.01);
let quarter_period = 1.0 / (440.0 * 4.0);
let sample = table.sample_at(440.0, quarter_period);
assert!((sample - 1.0).abs() < 0.1);
}
#[test]
fn test_from_fn() {
let wt = Wavetable::from_fn(1024, |phase| phase * 2.0 - 1.0);
assert!((wt.sample(0.0) - (-1.0)).abs() < 0.01);
assert!(wt.sample(0.5).abs() < 0.1);
assert!((wt.sample(0.99) - 1.0).abs() < 0.1);
}
#[test]
fn test_from_samples() {
let samples = vec![0.0, 1.0, 0.0, -1.0];
let wt = Wavetable::from_samples(samples);
assert_eq!(wt.len(), 4);
assert!((wt.sample(0.0) - 0.0).abs() < 0.01);
assert!((wt.sample(0.25) - 1.0).abs() < 0.1);
}
#[test]
fn test_from_harmonics() {
let wt = Wavetable::from_harmonics(1024, &[(1, 1.0)]);
let sine_wt = Wavetable::sine();
assert!((wt.sample(0.0) - sine_wt.sample(0.0)).abs() < 0.01);
assert!((wt.sample(0.25) - sine_wt.sample(0.25)).abs() < 0.01);
}
#[test]
fn test_band_limited_waveforms() {
let saw = Wavetable::saw_bandlimited();
let square = Wavetable::square_bandlimited();
for i in 0..10 {
let phase = i as f32 / 10.0;
assert!(saw.sample(phase).abs() <= 1.0);
assert!(square.sample(phase).abs() <= 1.0);
}
}
#[test]
fn test_pwm() {
let wt = Wavetable::pwm(0.25);
assert!(wt.sample(0.1) > 0.5);
assert!(wt.sample(0.5) < -0.5);
assert!(wt.sample(0.9) < -0.5);
}
}