use std::f32::consts::TAU;
use sim_lib_audio_graph_core::{PrepareConfig, ProcessBlock, Processor};
use crate::common::prepared_output_channels;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum BandlimitedWaveform {
Sine,
Saw,
Pulse {
duty: f32,
},
Triangle,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum BandlimitPolicy {
PolyBlep,
SineOnly,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OscillatorPolicy {
pub frequency_hz: f32,
pub amplitude: f32,
pub waveform: BandlimitedWaveform,
pub bandlimit: BandlimitPolicy,
}
impl OscillatorPolicy {
pub fn new(frequency_hz: f32, waveform: BandlimitedWaveform) -> Self {
let waveform = match waveform {
BandlimitedWaveform::Pulse { duty } => BandlimitedWaveform::Pulse {
duty: finite_or(duty, 0.5).clamp(0.01, 0.99),
},
other => other,
};
Self {
frequency_hz: finite_or(frequency_hz, 0.0).max(0.0),
amplitude: 1.0,
waveform,
bandlimit: BandlimitPolicy::PolyBlep,
}
}
pub fn with_amplitude(mut self, amplitude: f32) -> Self {
self.amplitude = finite_or(amplitude, 0.0);
self
}
pub fn with_bandlimit(mut self, bandlimit: BandlimitPolicy) -> Self {
self.bandlimit = bandlimit;
self
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BandlimitedOscillator {
policy: OscillatorPolicy,
sample_rate_hz: f32,
phases: Vec<f32>,
triangle_state: Vec<f32>,
}
impl BandlimitedOscillator {
pub fn new(policy: OscillatorPolicy) -> Self {
Self {
policy,
sample_rate_hz: 48_000.0,
phases: Vec::new(),
triangle_state: Vec::new(),
}
}
pub fn policy(&self) -> OscillatorPolicy {
self.policy
}
pub fn set_frequency_hz(&mut self, frequency_hz: f32) {
self.policy.frequency_hz = finite_or(frequency_hz, 0.0).max(0.0);
}
fn phase_increment(&self) -> f32 {
if self.sample_rate_hz <= 0.0 {
0.0
} else {
(self.policy.frequency_hz / self.sample_rate_hz).clamp(0.0, 0.499)
}
}
fn sample(&mut self, channel: usize, increment: f32) -> f32 {
let phase = self.phases[channel];
let value = match (self.policy.waveform, self.policy.bandlimit) {
(BandlimitedWaveform::Sine, _) => (TAU * phase).sin(),
(_, BandlimitPolicy::SineOnly) => 0.0,
(BandlimitedWaveform::Saw, BandlimitPolicy::PolyBlep) => {
2.0 * phase - 1.0 - poly_blep(phase, increment)
}
(BandlimitedWaveform::Pulse { duty }, BandlimitPolicy::PolyBlep) => {
let naive = if phase < duty { 1.0 } else { -1.0 };
naive + poly_blep(phase, increment)
- poly_blep((phase - duty).rem_euclid(1.0), increment)
}
(BandlimitedWaveform::Triangle, BandlimitPolicy::PolyBlep) => {
let naive = if phase < 0.5 { 1.0 } else { -1.0 };
let square = naive + poly_blep(phase, increment)
- poly_blep((phase - 0.5).rem_euclid(1.0), increment);
let leak = (1.0 - increment).clamp(0.0, 0.999_99);
let integrated = leak * self.triangle_state[channel] + square * increment * 4.0;
self.triangle_state[channel] = integrated.clamp(-1.2, 1.2);
self.triangle_state[channel]
}
};
self.phases[channel] = (phase + increment).rem_euclid(1.0);
value * self.policy.amplitude
}
#[cfg(test)]
pub(crate) fn realtime_state_snapshot(&self) -> [usize; 2] {
[self.phases.capacity(), self.triangle_state.capacity()]
}
}
impl Processor for BandlimitedOscillator {
fn prepare(&mut self, cfg: PrepareConfig) {
self.sample_rate_hz = cfg.sample_rate_hz.max(1) as f32;
self.phases.clear();
self.phases.resize(usize::from(cfg.out_channels), 0.0);
self.triangle_state.clear();
self.triangle_state
.resize(usize::from(cfg.out_channels), 0.0);
}
fn reset(&mut self) {
self.phases.fill(0.0);
self.triangle_state.fill(0.0);
}
fn process(&mut self, block: &mut ProcessBlock<'_>) {
let channels = prepared_output_channels(block, self.phases.len(), "BandlimitedOscillator");
let increment = self.phase_increment();
for frame in 0..block.frames as usize {
for channel in 0..channels {
block.out_audio[channel][frame] = self.sample(channel, increment);
}
}
}
}
fn poly_blep(phase: f32, increment: f32) -> f32 {
if increment <= f32::EPSILON {
return 0.0;
}
if phase < increment {
let x = phase / increment;
x + x - x * x - 1.0
} else if phase > 1.0 - increment {
let x = (phase - 1.0) / increment;
x * x + x + x + 1.0
} else {
0.0
}
}
fn finite_or(value: f32, fallback: f32) -> f32 {
if value.is_finite() { value } else { fallback }
}