use sim_lib_audio_graph_core::{PrepareConfig, ProcessBlock, Processor};
use crate::common::{input_sample, output_channels, prepare_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
}
}
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 = output_channels(block);
if self.processors.len() < channels {
self.processors.resize(channels, self.prototype.clone());
self.previous_inputs.resize(channels, 0.0);
}
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>;