use std::f32::consts::PI;
use crate::synthesis::simd::{SIMD, SimdLanes, SimdWidth};
use wide::{f32x4, f32x8};
#[derive(Debug, Clone, Copy)]
pub struct Partial {
pub frequency_ratio: f32,
pub amplitude: f32,
pub phase_offset: f32,
}
impl Partial {
pub fn new(frequency_ratio: f32, amplitude: f32, phase_offset: f32) -> Self {
Self {
frequency_ratio,
amplitude,
phase_offset,
}
}
pub fn harmonic(harmonic_number: u32, amplitude: f32) -> Self {
Self {
frequency_ratio: harmonic_number as f32,
amplitude,
phase_offset: 0.0,
}
}
pub fn inharmonic(ratio: f32, amplitude: f32) -> Self {
Self {
frequency_ratio: ratio,
amplitude,
phase_offset: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct AdditiveSynth {
fundamental_freq: f32,
sample_rate: f32,
partials: Vec<Partial>,
phases: Vec<f32>,
}
impl AdditiveSynth {
pub fn new(fundamental_freq: f32, sample_rate: f32) -> Self {
Self {
fundamental_freq,
sample_rate,
partials: Vec::new(),
phases: Vec::new(),
}
}
pub fn add_partial(mut self, partial: Partial) -> Self {
self.phases.push(partial.phase_offset);
self.partials.push(partial);
self
}
pub fn with_partials(mut self, partials: Vec<Partial>) -> Self {
for partial in partials {
self = self.add_partial(partial);
}
self
}
pub fn set_frequency(&mut self, freq: f32) {
self.fundamental_freq = freq;
}
pub fn reset(&mut self) {
for (i, partial) in self.partials.iter().enumerate() {
self.phases[i] = partial.phase_offset;
}
}
#[inline]
pub fn sample(&mut self) -> f32 {
let mut output = 0.0;
for (i, partial) in self.partials.iter().enumerate() {
let freq = self.fundamental_freq * partial.frequency_ratio;
let sample = (self.phases[i] * 2.0 * PI).sin() * partial.amplitude;
output += sample;
let phase_increment = freq / self.sample_rate;
self.phases[i] += phase_increment;
if self.phases[i] >= 1.0 {
self.phases[i] -= self.phases[i].floor();
}
}
if !self.partials.is_empty() {
output / self.partials.len() as f32
} else {
0.0
}
}
pub fn generate(&mut self, length: usize) -> Vec<f32> {
if self.partials.len() >= 8 {
match SIMD.simd_width() {
SimdWidth::X8 => self.generate_simd_x8(length),
SimdWidth::X4 => self.generate_simd_x4(length),
SimdWidth::Scalar => (0..length).map(|_| self.sample()).collect(),
}
} else if self.partials.len() >= 4 {
match SIMD.simd_width() {
SimdWidth::X8 | SimdWidth::X4 => self.generate_simd_x4(length),
SimdWidth::Scalar => (0..length).map(|_| self.sample()).collect(),
}
} else {
(0..length).map(|_| self.sample()).collect()
}
}
#[inline]
fn generate_simd_x8(&mut self, length: usize) -> Vec<f32> {
let mut output = Vec::with_capacity(length);
let num_partials = self.partials.len();
let simd_partials = (num_partials / 8) * 8;
for _ in 0..length {
let mut sum = 0.0f32;
for chunk_start in (0..simd_partials).step_by(8) {
let phases = f32x8::from([
self.phases[chunk_start],
self.phases[chunk_start + 1],
self.phases[chunk_start + 2],
self.phases[chunk_start + 3],
self.phases[chunk_start + 4],
self.phases[chunk_start + 5],
self.phases[chunk_start + 6],
self.phases[chunk_start + 7],
]);
let amplitudes = f32x8::from([
self.partials[chunk_start].amplitude,
self.partials[chunk_start + 1].amplitude,
self.partials[chunk_start + 2].amplitude,
self.partials[chunk_start + 3].amplitude,
self.partials[chunk_start + 4].amplitude,
self.partials[chunk_start + 5].amplitude,
self.partials[chunk_start + 6].amplitude,
self.partials[chunk_start + 7].amplitude,
]);
let freq_ratios = f32x8::from([
self.partials[chunk_start].frequency_ratio,
self.partials[chunk_start + 1].frequency_ratio,
self.partials[chunk_start + 2].frequency_ratio,
self.partials[chunk_start + 3].frequency_ratio,
self.partials[chunk_start + 4].frequency_ratio,
self.partials[chunk_start + 5].frequency_ratio,
self.partials[chunk_start + 6].frequency_ratio,
self.partials[chunk_start + 7].frequency_ratio,
]);
let phase_radians = phases.mul(f32x8::splat(2.0 * PI));
let sine_vals = phase_radians.fast_sin();
let samples = sine_vals.mul(amplitudes);
let arr = samples.to_array();
sum += arr[0] + arr[1] + arr[2] + arr[3] + arr[4] + arr[5] + arr[6] + arr[7];
let fundamental = f32x8::splat(self.fundamental_freq);
let sample_rate = f32x8::splat(self.sample_rate);
let freqs = fundamental.mul(freq_ratios);
let phase_increments = freqs.div(sample_rate);
let new_phases = phases.add(phase_increments);
for i in 0..8 {
let mut phase = new_phases.to_array()[i];
if phase >= 1.0 {
phase -= phase.floor();
}
self.phases[chunk_start + i] = phase;
}
}
for i in simd_partials..num_partials {
let freq = self.fundamental_freq * self.partials[i].frequency_ratio;
let sample = (self.phases[i] * 2.0 * PI).sin() * self.partials[i].amplitude;
sum += sample;
let phase_increment = freq / self.sample_rate;
self.phases[i] += phase_increment;
if self.phases[i] >= 1.0 {
self.phases[i] -= self.phases[i].floor();
}
}
let normalized = if num_partials > 0 {
sum / num_partials as f32
} else {
0.0
};
output.push(normalized);
}
output
}
#[inline]
fn generate_simd_x4(&mut self, length: usize) -> Vec<f32> {
let mut output = Vec::with_capacity(length);
let num_partials = self.partials.len();
let simd_partials = (num_partials / 4) * 4;
for _ in 0..length {
let mut sum = 0.0f32;
for chunk_start in (0..simd_partials).step_by(4) {
let phases = f32x4::from([
self.phases[chunk_start],
self.phases[chunk_start + 1],
self.phases[chunk_start + 2],
self.phases[chunk_start + 3],
]);
let amplitudes = f32x4::from([
self.partials[chunk_start].amplitude,
self.partials[chunk_start + 1].amplitude,
self.partials[chunk_start + 2].amplitude,
self.partials[chunk_start + 3].amplitude,
]);
let freq_ratios = f32x4::from([
self.partials[chunk_start].frequency_ratio,
self.partials[chunk_start + 1].frequency_ratio,
self.partials[chunk_start + 2].frequency_ratio,
self.partials[chunk_start + 3].frequency_ratio,
]);
let phase_radians = phases.mul(f32x4::splat(2.0 * PI));
let sine_vals = phase_radians.fast_sin();
let samples = sine_vals.mul(amplitudes);
let arr = samples.to_array();
sum += arr[0] + arr[1] + arr[2] + arr[3];
let fundamental = f32x4::splat(self.fundamental_freq);
let sample_rate = f32x4::splat(self.sample_rate);
let freqs = fundamental.mul(freq_ratios);
let phase_increments = freqs.div(sample_rate);
let new_phases = phases.add(phase_increments);
for i in 0..4 {
let mut phase = new_phases.to_array()[i];
if phase >= 1.0 {
phase -= phase.floor();
}
self.phases[chunk_start + i] = phase;
}
}
for i in simd_partials..num_partials {
let freq = self.fundamental_freq * self.partials[i].frequency_ratio;
let sample = (self.phases[i] * 2.0 * PI).sin() * self.partials[i].amplitude;
sum += sample;
let phase_increment = freq / self.sample_rate;
self.phases[i] += phase_increment;
if self.phases[i] >= 1.0 {
self.phases[i] -= self.phases[i].floor();
}
}
let normalized = if num_partials > 0 {
sum / num_partials as f32
} else {
0.0
};
output.push(normalized);
}
output
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_partial_creation() {
let p = Partial::new(2.0, 0.5, 0.25);
assert_eq!(p.frequency_ratio, 2.0);
assert_eq!(p.amplitude, 0.5);
assert_eq!(p.phase_offset, 0.25);
}
#[test]
fn test_partial_harmonic() {
let p = Partial::harmonic(3, 0.7);
assert_eq!(p.frequency_ratio, 3.0);
assert_eq!(p.amplitude, 0.7);
assert_eq!(p.phase_offset, 0.0);
}
#[test]
fn test_partial_inharmonic() {
let p = Partial::inharmonic(2.76, 0.6);
assert_eq!(p.frequency_ratio, 2.76);
assert_eq!(p.amplitude, 0.6);
}
#[test]
fn test_additive_synth_creation() {
let synth = AdditiveSynth::new(440.0, 44100.0);
assert_eq!(synth.fundamental_freq, 440.0);
assert_eq!(synth.sample_rate, 44100.0);
assert_eq!(synth.partials.len(), 0);
}
#[test]
fn test_add_partial() {
let synth = AdditiveSynth::new(440.0, 44100.0)
.add_partial(Partial::harmonic(1, 1.0))
.add_partial(Partial::harmonic(2, 0.5));
assert_eq!(synth.partials.len(), 2);
assert_eq!(synth.phases.len(), 2);
}
#[test]
fn test_with_partials() {
let partials = vec![
Partial::harmonic(1, 1.0),
Partial::harmonic(2, 0.5),
Partial::harmonic(3, 0.33),
];
let synth = AdditiveSynth::new(440.0, 44100.0)
.with_partials(partials);
assert_eq!(synth.partials.len(), 3);
}
#[test]
fn test_sample_generation() {
let mut synth = AdditiveSynth::new(440.0, 44100.0)
.add_partial(Partial::harmonic(1, 1.0));
let sample = synth.sample();
assert!(sample >= -1.0 && sample <= 1.0);
}
#[test]
fn test_generate_length() {
let mut synth = AdditiveSynth::new(440.0, 44100.0)
.add_partial(Partial::harmonic(1, 1.0));
let samples = synth.generate(1000);
assert_eq!(samples.len(), 1000);
for &sample in &samples {
assert!(sample >= -1.0 && sample <= 1.0);
}
}
#[test]
fn test_set_frequency() {
let mut synth = AdditiveSynth::new(440.0, 44100.0)
.add_partial(Partial::harmonic(1, 1.0));
synth.set_frequency(880.0);
assert_eq!(synth.fundamental_freq, 880.0);
}
#[test]
fn test_reset_phases() {
let mut synth = AdditiveSynth::new(440.0, 44100.0)
.add_partial(Partial::new(1.0, 1.0, 0.25));
let initial_phase = synth.phases[0];
for _ in 0..500 {
synth.sample();
}
synth.reset();
assert_eq!(synth.phases[0], initial_phase);
}
#[test]
fn test_multiple_partials_sum() {
let mut synth = AdditiveSynth::new(440.0, 44100.0)
.add_partial(Partial::harmonic(1, 0.5))
.add_partial(Partial::harmonic(2, 0.5));
let samples = synth.generate(100);
for &sample in &samples {
assert!(sample >= -1.0 && sample <= 1.0);
}
}
#[test]
fn test_inharmonic_ratios() {
let mut synth = AdditiveSynth::new(220.0, 44100.0)
.add_partial(Partial::inharmonic(1.0, 1.0))
.add_partial(Partial::inharmonic(2.76, 0.6))
.add_partial(Partial::inharmonic(5.4, 0.4));
let samples = synth.generate(1000);
assert_eq!(samples.len(), 1000);
for &sample in &samples {
assert!(sample >= -1.0 && sample <= 1.0);
}
}
#[test]
fn test_phase_offset_affects_output() {
let mut synth1 = AdditiveSynth::new(440.0, 44100.0)
.add_partial(Partial::new(1.0, 1.0, 0.0));
let mut synth2 = AdditiveSynth::new(440.0, 44100.0)
.add_partial(Partial::new(1.0, 1.0, 0.5));
let sample1 = synth1.sample();
let sample2 = synth2.sample();
assert!((sample1 + sample2).abs() < 0.01);
}
}