use sim_lib_audio_graph_core::{PrepareConfig, ProcessBlock, Processor};
use crate::common::{input_sample, prepare_channels, prepared_output_channels};
pub trait NonlinearSampleProcessor: Clone + Send {
fn reset(&mut self);
fn process_sample(&mut self, input: f32) -> f32;
}
#[derive(Clone, Debug, PartialEq)]
pub struct TanhClipper {
drive: f32,
}
impl TanhClipper {
pub fn new(drive: f32) -> Self {
Self {
drive: drive.max(0.0),
}
}
}
impl NonlinearSampleProcessor for TanhClipper {
fn reset(&mut self) {}
fn process_sample(&mut self, input: f32) -> f32 {
(input * self.drive).tanh()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct OversamplingWrapper<P: NonlinearSampleProcessor> {
prototype: P,
processors: Vec<P>,
previous_inputs: Vec<f32>,
factor: u8,
}
impl<P: NonlinearSampleProcessor> OversamplingWrapper<P> {
pub fn new(processor: P, factor: u8) -> Self {
Self {
prototype: processor,
processors: Vec::new(),
previous_inputs: Vec::new(),
factor: factor.clamp(1, 16),
}
}
fn process_channel_sample(&mut self, channel: usize, input: f32) -> f32 {
let previous = self.previous_inputs[channel];
let mut output = 0.0;
for step in 1..=self.factor {
let t = step as f32 / self.factor as f32;
let upsampled = previous + (input - previous) * t;
output = self.processors[channel].process_sample(upsampled);
}
self.previous_inputs[channel] = input;
output
}
#[cfg(all(test, not(debug_assertions)))]
pub(crate) fn realtime_state_snapshot(&self) -> Vec<usize> {
vec![self.processors.capacity(), self.previous_inputs.capacity()]
}
}
impl OversamplingWrapper<TanhClipper> {
pub fn soft_clipper(drive: f32, factor: u8) -> Self {
Self::new(TanhClipper::new(drive), factor)
}
}
impl<P: NonlinearSampleProcessor> Processor for OversamplingWrapper<P> {
fn prepare(&mut self, cfg: PrepareConfig) {
prepare_channels(
&mut self.processors,
cfg.out_channels as usize,
self.prototype.clone(),
);
prepare_channels(&mut self.previous_inputs, cfg.out_channels as usize, 0.0);
}
fn reset(&mut self) {
self.previous_inputs.fill(0.0);
for processor in &mut self.processors {
processor.reset();
}
}
fn process(&mut self, block: &mut ProcessBlock<'_>) {
let channels =
prepared_output_channels(block, self.processors.len(), "OversamplingWrapper");
let frames = block.frames as usize;
for channel in 0..channels {
for frame in 0..frames {
let input = input_sample(block, channel, frame);
block.out_audio[channel][frame] = self.process_channel_sample(channel, input);
}
}
}
}
pub type OversampledSoftClipper = OversamplingWrapper<TanhClipper>;