// Simpler saturations for Actuate
// Based off the Duro Console Saturations
// Ardura 2023
use nih_plug::params::enums::Enum;
use serde::{Deserialize, Serialize};
use std::f32::consts::PI;
#[derive(Clone, Enum, PartialEq, Serialize, Deserialize)]
pub enum SaturationType {
Tape,
Clip,
SinPow,
Subtle,
Sine,
}
#[derive(Clone, PartialEq)]
pub(crate) struct Saturation {
sat_type: SaturationType,
}
impl Saturation {
pub fn new() -> Self {
Saturation {
sat_type: SaturationType::Tape,
}
}
pub fn set_type(&mut self, new_type: SaturationType) {
self.sat_type = new_type;
}
// Process our saturations - amount from 0 to 1
pub fn process(&mut self, input_l: f32, input_r: f32, amount: f32) -> (f32, f32) {
let output_l: f32;
let output_r: f32;
let idrive = if amount == 0.0 { 0.0001 } else { amount };
match self.sat_type {
SaturationType::Tape => {
// Define the transfer curve for the tape saturation effect
// 1.0 addition and powf were added to make it more pronounced
let transfer = |x: f32| -> f32 { (x * (10.0 * idrive + 1.0)).tanh() };
// Apply the transfer curve to the input sample
output_l = transfer(input_l);
output_r = transfer(input_r);
}
SaturationType::Clip => {
let clipped = input_l.signum();
// Mix clipped signal with original
output_l = input_l * (1.0 - amount) + clipped * amount;
output_r = input_r * (1.0 - amount) + clipped * amount;
}
SaturationType::SinPow => {
let transfer = |x: f32| -> f32 { (x * (idrive)).sin().powf(2.0) };
output_l = transfer(input_l);
output_r = transfer(input_r);
}
SaturationType::Subtle => {
let transfer = |x: f32| -> f32 { ((idrive * (idrive * PI * x).cos()) / 4.0) + x };
output_l = transfer(input_l);
output_r = transfer(input_r);
}
SaturationType::Sine => {
let transfer = |x: f32| -> f32 { x.signum() * (x.abs() + idrive).sin() };
output_l = transfer(input_l);
output_r = transfer(input_r);
}
}
(output_l, output_r)
}
}