mod builder;
mod handle;
pub use builder::*;
pub use handle::*;
use crate::{
Decibels, Mix, Parameter,
command::{ValueChangeCommand, read_commands_into_parameters},
command_writers_and_readers,
effect::Effect,
frame::Frame,
info::Info,
};
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DistortionKind {
HardClip,
SoftClip,
}
impl Default for DistortionKind {
fn default() -> Self {
Self::HardClip
}
}
struct Distortion {
command_readers: CommandReaders,
kind: DistortionKind,
drive: Parameter<Decibels>,
mix: Parameter<Mix>,
}
impl Effect for Distortion {
fn on_start_processing(&mut self) {
if let Some(kind) = self.command_readers.set_kind.read() {
self.kind = kind;
}
read_commands_into_parameters!(self, drive, mix);
}
fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) {
self.drive.update(dt * input.len() as f64, info);
self.mix.update(dt * input.len() as f64, info);
let num_frames = input.len();
for (i, frame) in input.iter_mut().enumerate() {
let time_in_chunk = (i + 1) as f64 / num_frames as f64;
let drive = self.drive.interpolated_value(time_in_chunk).as_amplitude();
let mix = self.mix.interpolated_value(time_in_chunk).0.clamp(0.0, 1.0);
let mut output = *frame * drive;
output = match self.kind {
DistortionKind::HardClip => {
Frame::new(output.left.clamp(-1.0, 1.0), output.right.clamp(-1.0, 1.0))
}
DistortionKind::SoftClip => Frame::new(
output.left / (1.0 + output.left.abs()),
output.right / (1.0 + output.right.abs()),
),
};
output /= drive;
*frame = output * mix.sqrt() + *frame * (1.0 - mix).sqrt()
}
}
}
command_writers_and_readers! {
set_kind: DistortionKind,
set_drive: ValueChangeCommand<Decibels>,
set_mix: ValueChangeCommand<Mix>,
}