use std::f32::consts::PI;
use std::sync::Arc;
use crate::context::BaseAudioContext;
use crate::node::TABLE_LENGTH_USIZE;
#[derive(Debug, Default, Clone)]
pub struct PeriodicWaveOptions {
pub real: Option<Vec<f32>>,
pub imag: Option<Vec<f32>>,
pub disable_normalization: bool,
}
#[derive(Debug, Clone, Default)]
pub struct PeriodicWave {
wavetable: Arc<Vec<f32>>,
}
impl PeriodicWave {
pub fn new<C: BaseAudioContext>(_context: &C, options: PeriodicWaveOptions) -> Self {
let PeriodicWaveOptions {
real,
imag,
disable_normalization,
} = options;
let (real, imag) = match (real, imag) {
(Some(r), Some(i)) => {
assert_eq!(
r.len(),
i.len(),
"IndexSizeError - `real` and `imag` length should be equal"
);
assert!(
r.len() >= 2,
"IndexSizeError - `real` and `imag` length should at least 2"
);
(r, i)
}
(Some(r), None) => {
assert!(
r.len() >= 2,
"IndexSizeError - `real` and `imag` length should at least 2"
);
let len = r.len();
(r, vec![0.; len])
}
(None, Some(i)) => {
assert!(
i.len() >= 2,
"IndexSizeError - `real` and `imag` length should at least 2"
);
let len = i.len();
(vec![0.; len], i)
}
_ => (vec![0., 0.], vec![0., 1.]),
};
let normalize = !disable_normalization;
let wavetable = Self::generate_wavetable(&real, &imag, normalize, TABLE_LENGTH_USIZE);
Self {
wavetable: Arc::new(wavetable),
}
}
pub(crate) fn as_slice(&self) -> &[f32] {
&self.wavetable[..]
}
fn generate_wavetable(reals: &[f32], imags: &[f32], normalize: bool, size: usize) -> Vec<f32> {
let mut wavetable = Vec::with_capacity(size);
let pi_2 = 2. * PI;
for i in 0..size {
let mut sample = 0.;
let phase = pi_2 * i as f32 / size as f32;
for j in 1..reals.len() {
let freq = j as f32;
let real = reals[j];
let imag = imags[j];
let rad = phase * freq;
let contrib = real * rad.cos() + imag * rad.sin();
sample += contrib;
}
wavetable.push(sample);
}
if normalize {
Self::normalize(&mut wavetable);
}
wavetable
}
fn normalize(wavetable: &mut [f32]) {
let mut max = 0.;
for sample in wavetable.iter() {
let abs = sample.abs();
if abs > max {
max = abs;
}
}
if max > 0. {
let norm_factor = 1. / max;
for sample in wavetable.iter_mut() {
*sample *= norm_factor;
}
}
}
}
#[cfg(test)]
mod tests {
use float_eq::assert_float_eq;
use std::f32::consts::PI;
use crate::context::AudioContext;
use crate::node::{TABLE_LENGTH_F32, TABLE_LENGTH_USIZE};
use super::{PeriodicWave, PeriodicWaveOptions};
#[test]
#[should_panic]
fn fails_to_build_when_only_real_is_defined_and_too_short() {
let context = AudioContext::default();
let options = PeriodicWaveOptions {
real: Some(vec![0.]),
imag: None,
disable_normalization: false,
};
let _periodic_wave = PeriodicWave::new(&context, options);
}
#[test]
#[should_panic]
fn fails_to_build_when_only_imag_is_defined_and_too_short() {
let context = AudioContext::default();
let options = PeriodicWaveOptions {
real: None,
imag: Some(vec![0.]),
disable_normalization: false,
};
let _periodic_wave = PeriodicWave::new(&context, options);
}
#[test]
#[should_panic]
fn fails_to_build_when_imag_and_real_not_equal_length() {
let context = AudioContext::default();
let options = PeriodicWaveOptions {
real: Some(vec![0., 0., 0.]),
imag: Some(vec![0., 0.]),
disable_normalization: false,
};
let _periodic_wave = PeriodicWave::new(&context, options);
}
#[test]
#[should_panic]
fn fails_to_build_when_imag_and_real_too_shorts() {
let context = AudioContext::default();
let options = PeriodicWaveOptions {
real: Some(vec![0.]),
imag: Some(vec![0.]),
disable_normalization: false,
};
let _periodic_wave = PeriodicWave::new(&context, options);
}
#[test]
fn wavetable_generate_sine() {
let reals = [0., 0.];
let imags = [0., 1.];
let result = PeriodicWave::generate_wavetable(&reals, &imags, true, TABLE_LENGTH_USIZE);
let mut expected = Vec::new();
for i in 0..TABLE_LENGTH_USIZE {
let sample = (i as f32 / TABLE_LENGTH_F32 * 2. * PI).sin();
expected.push(sample);
}
assert_float_eq!(result[..], expected[..], abs_all <= 1e-6);
}
#[test]
fn wavetable_generate_2f_not_norm() {
let reals = [0., 0., 0.];
let imags = [0., 0.5, 0.5];
let result = PeriodicWave::generate_wavetable(&reals, &imags, false, TABLE_LENGTH_USIZE);
let mut expected = Vec::new();
for i in 0..TABLE_LENGTH_USIZE {
let mut sample = 0.;
sample += 0.5 * (1. * i as f32 / TABLE_LENGTH_F32 * 2. * PI).sin();
sample += 0.5 * (2. * i as f32 / TABLE_LENGTH_F32 * 2. * PI).sin();
expected.push(sample);
}
assert_float_eq!(result[..], expected[..], abs_all <= 1e-6);
}
#[test]
fn normalize() {
{
let mut signal = [-0.5, 0.2];
PeriodicWave::normalize(&mut signal);
let expected = [-1., 0.4];
assert_float_eq!(signal[..], expected[..], abs_all <= 0.);
}
{
let mut signal = [0.5, -0.2];
PeriodicWave::normalize(&mut signal);
let expected = [1., -0.4];
assert_float_eq!(signal[..], expected[..], abs_all <= 0.);
}
}
#[test]
fn wavetable_generate_2f_norm() {
let reals = [0., 0., 0.];
let imags = [0., 0.5, 0.5];
let result = PeriodicWave::generate_wavetable(&reals, &imags, true, TABLE_LENGTH_USIZE);
let mut expected = Vec::new();
for i in 0..TABLE_LENGTH_USIZE {
let mut sample = 0.;
sample += 0.5 * (1. * i as f32 / TABLE_LENGTH_F32 * 2. * PI).sin();
sample += 0.5 * (2. * i as f32 / TABLE_LENGTH_F32 * 2. * PI).sin();
expected.push(sample);
}
PeriodicWave::normalize(&mut expected);
assert_float_eq!(result[..], expected[..], abs_all <= 1e-6);
}
}