use crate::buffer::Buffer;
use crate::sample::Sample;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BypassState {
Active,
RampingToBypassed,
Bypassed,
RampingToActive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BypassAction {
Passthrough,
Process,
ProcessAndCrossfade,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CrossfadeCurve {
#[default]
Linear,
EqualPower,
SCurve,
}
impl CrossfadeCurve {
#[inline]
pub fn gains<S: Sample>(&self, t: f64) -> (S, S) {
let (wet, dry) = match self {
CrossfadeCurve::Linear => (1.0 - t, t),
CrossfadeCurve::EqualPower => {
let angle = t * std::f64::consts::FRAC_PI_2;
(angle.cos(), angle.sin())
}
CrossfadeCurve::SCurve => {
let smooth = t * t * (3.0 - 2.0 * t); (1.0 - smooth, smooth)
}
};
(S::from_f64(wet), S::from_f64(dry))
}
}
pub struct BypassHandler {
state: BypassState,
ramp_position: u32,
ramp_samples: u32,
curve: CrossfadeCurve,
}
impl BypassHandler {
pub fn new(ramp_samples: u32, curve: CrossfadeCurve) -> Self {
Self {
state: BypassState::Active,
ramp_position: 0,
ramp_samples,
curve,
}
}
#[inline]
pub fn state(&self) -> BypassState {
self.state
}
#[inline]
pub fn is_ramping(&self) -> bool {
matches!(
self.state,
BypassState::RampingToBypassed | BypassState::RampingToActive
)
}
#[inline]
pub fn is_bypassed(&self) -> bool {
self.state == BypassState::Bypassed
}
#[inline]
pub fn is_active(&self) -> bool {
self.state == BypassState::Active
}
#[inline]
pub fn ramp_samples(&self) -> u32 {
self.ramp_samples
}
pub fn set_ramp_samples(&mut self, samples: u32) {
self.ramp_samples = samples;
}
pub fn set_curve(&mut self, curve: CrossfadeCurve) {
self.curve = curve;
}
pub fn begin(&mut self, bypassed: bool) -> BypassAction {
self.set_bypass(bypassed);
match self.state {
BypassState::Bypassed => BypassAction::Passthrough,
BypassState::Active => BypassAction::Process,
BypassState::RampingToBypassed | BypassState::RampingToActive => {
BypassAction::ProcessAndCrossfade
}
}
}
pub fn finish<S: Sample>(&mut self, buffer: &mut Buffer<S>) {
let num_samples = buffer.num_samples();
self.apply_crossfade(buffer, num_samples);
}
fn set_bypass(&mut self, bypassed: bool) {
if self.ramp_samples == 0 {
let target = if bypassed {
BypassState::Bypassed
} else {
BypassState::Active
};
if self.state != target {
self.state = target;
self.ramp_position = 0;
}
return;
}
match (self.state, bypassed) {
(BypassState::Active, true) => {
self.state = BypassState::RampingToBypassed;
self.ramp_position = 0;
}
(BypassState::RampingToBypassed, false) => {
self.state = BypassState::RampingToActive;
}
(BypassState::Bypassed, false) => {
self.state = BypassState::RampingToActive;
self.ramp_position = self.ramp_samples;
}
(BypassState::RampingToActive, true) => {
self.state = BypassState::RampingToBypassed;
}
_ => {}
}
}
fn apply_crossfade<S: Sample>(&mut self, buffer: &mut Buffer<S>, num_samples: usize) {
if self.ramp_samples == 0 {
return;
}
let num_channels = buffer.num_input_channels().min(buffer.num_output_channels());
if num_channels == 0 {
return;
}
let ramp_samples_f = self.ramp_samples as f64;
let ramping_to_bypass = self.state == BypassState::RampingToBypassed;
for sample_idx in 0..num_samples {
let t = (self.ramp_position as f64) / ramp_samples_f;
let (wet_gain, dry_gain): (S, S) = self.curve.gains(t);
for ch in 0..num_channels {
let dry = buffer.input(ch)[sample_idx];
let wet = buffer.output(ch)[sample_idx];
buffer.output(ch)[sample_idx] = wet * wet_gain + dry * dry_gain;
}
if ramping_to_bypass {
self.ramp_position = (self.ramp_position + 1).min(self.ramp_samples);
} else {
self.ramp_position = self.ramp_position.saturating_sub(1);
}
}
if ramping_to_bypass && self.ramp_position >= self.ramp_samples {
self.state = BypassState::Bypassed;
} else if !ramping_to_bypass && self.ramp_position == 0 {
self.state = BypassState::Active;
}
}
}
impl Default for BypassHandler {
fn default() -> Self {
Self::new(64, CrossfadeCurve::Linear)
}
}