use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
const FADE_SECONDS: f64 = 0.15;
const SLEEP_GLIDE_SECONDS: f64 = 0.1;
#[derive(Default)]
pub struct FadeControl {
audible: AtomicBool,
from_silence: AtomicBool,
silent: AtomicBool,
sleep_gain: AtomicU32,
sleep_snap: AtomicBool,
}
impl FadeControl {
pub fn new() -> Arc<Self> {
Arc::new(Self {
audible: AtomicBool::new(true),
sleep_gain: AtomicU32::new(1.0f32.to_bits()),
..Default::default()
})
}
pub fn fade_out(&self) {
self.audible.store(false, Ordering::Release);
}
pub fn fade_in(&self, from_silence: bool) {
self.silent.store(false, Ordering::Release);
if from_silence {
self.from_silence.store(true, Ordering::Release);
}
self.audible.store(true, Ordering::Release);
}
pub fn is_silent(&self) -> bool {
self.silent.load(Ordering::Acquire)
}
pub fn set_sleep_gain(&self, gain: f32, snap: bool) {
if snap {
self.sleep_snap.store(true, Ordering::Release);
}
self.sleep_gain
.store(gain.clamp(0.0, 1.0).to_bits(), Ordering::Release);
}
}
pub struct Fader {
control: Arc<FadeControl>,
pos: usize,
len: usize,
sleep: f32,
sleep_target: u32,
sleep_step: f32,
glide: f32,
}
impl Fader {
pub fn new(control: Arc<FadeControl>, sample_rate: f64) -> Self {
let len = ((sample_rate * FADE_SECONDS) as usize).max(1);
Self {
control,
pos: len,
len,
sleep: 1.0,
sleep_target: 1.0f32.to_bits(),
sleep_step: 0.0,
glide: ((sample_rate * SLEEP_GLIDE_SECONDS) as f32).max(1.0),
}
}
pub fn readable(&mut self, wanted: usize, channels: usize) -> usize {
if self.control.from_silence.swap(false, Ordering::AcqRel) {
self.pos = 0;
}
if self.control.audible.load(Ordering::Acquire) {
return wanted;
}
if self.pos == 0 {
self.control.silent.store(true, Ordering::Release);
}
wanted.min(self.pos * channels.max(1))
}
pub fn hold(&mut self) {
if !self.control.audible.load(Ordering::Acquire) {
self.pos = 0;
self.control.silent.store(true, Ordering::Release);
}
}
fn follow_sleep(&mut self) {
let bits = self.control.sleep_gain.load(Ordering::Acquire);
let snap = self.control.sleep_snap.swap(false, Ordering::AcqRel);
if bits == self.sleep_target && !snap {
return;
}
self.sleep_target = bits;
let target = f32::from_bits(bits);
if snap {
self.sleep = target;
self.sleep_step = 0.0;
} else {
self.sleep_step = (target - self.sleep) / self.glide;
}
}
pub fn apply(&mut self, samples: &mut [f32], channels: usize) {
self.follow_sleep();
let rising = self.control.audible.load(Ordering::Acquire);
let pausing = !((rising && self.pos == self.len) || (!rising && self.pos == 0));
let sleeping = self.sleep != 1.0 || self.sleep_step != 0.0;
if !pausing && !sleeping {
return;
}
let target = f32::from_bits(self.sleep_target);
for frame in samples.chunks_mut(channels.max(1)) {
if pausing {
self.pos = if rising {
(self.pos + 1).min(self.len)
} else {
self.pos.saturating_sub(1)
};
}
if self.sleep_step != 0.0 {
self.sleep += self.sleep_step;
if (self.sleep_step > 0.0 && self.sleep >= target)
|| (self.sleep_step < 0.0 && self.sleep <= target)
{
self.sleep = target;
self.sleep_step = 0.0;
}
}
let level = self.pos as f32 / self.len as f32;
let gain = level * level * self.sleep;
for s in frame {
*s *= gain;
}
}
if !rising && self.pos == 0 {
self.control.silent.store(true, Ordering::Release);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const RATE: f64 = 1000.0;
fn render(fader: &mut Fader, frames: usize) -> Vec<f32> {
let wanted = fader.readable(frames * 2, 2);
let mut out = vec![1.0f32; wanted];
fader.apply(&mut out, 2);
out
}
#[test]
fn a_fade_out_over_silence_is_silent_at_once() {
let control = FadeControl::new();
let mut fader = Fader::new(control.clone(), RATE);
fader.hold();
assert!(!control.is_silent(), "holding at full level fades nothing");
control.fade_out();
fader.hold();
assert!(control.is_silent());
assert_eq!(
fader.readable(64, 2),
0,
"nothing of the track plays after the pause"
);
}
#[test]
fn full_level_passes_samples_through() {
let control = FadeControl::new();
let mut fader = Fader::new(control, RATE);
let out = render(&mut fader, 64);
assert_eq!(out.len(), 128);
assert!(out.iter().all(|s| *s == 1.0));
}
#[test]
fn fade_out_stops_reading_at_silence() {
let control = FadeControl::new();
let mut fader = Fader::new(control.clone(), RATE);
control.fade_out();
let first = render(&mut fader, 100);
assert_eq!(first.len(), 200);
assert!(first.windows(2).all(|w| w[1] <= w[0]), "gain only falls");
assert!(!control.is_silent());
let second = render(&mut fader, 100);
assert_eq!(second.len(), 100, "only the rest of the ramp is read");
assert_eq!(*second.last().unwrap(), 0.0);
assert!(control.is_silent());
assert!(
render(&mut fader, 100).is_empty(),
"nothing read once silent"
);
}
#[test]
fn fade_in_from_stopped_starts_at_silence() {
let control = FadeControl::new();
let mut fader = Fader::new(control.clone(), RATE);
control.fade_in(true);
let out = render(&mut fader, 200);
assert!(out[0] < 0.01);
assert!(out.windows(2).all(|w| w[1] >= w[0]), "gain only rises");
assert_eq!(*out.last().unwrap(), 1.0);
}
#[test]
fn a_sleep_gain_glides_and_full_level_is_untouched() {
let control = FadeControl::new();
let mut fader = Fader::new(control.clone(), RATE);
assert!(render(&mut fader, 10).iter().all(|s| *s == 1.0));
control.set_sleep_gain(0.5, false);
let glide = render(&mut fader, 100); assert!(glide.windows(2).all(|w| w[1] <= w[0]), "only falls");
assert!(glide[0] < 1.0 && glide[0] > 0.99, "no step: {}", glide[0]);
assert!((glide.last().unwrap() - 0.5).abs() < 1e-4);
assert!(
render(&mut fader, 10)
.iter()
.all(|s| (*s - 0.5).abs() < 1e-6)
);
control.set_sleep_gain(1.0, true);
assert!(
render(&mut fader, 10).iter().all(|s| *s == 1.0),
"snapped back"
);
}
#[test]
fn resume_mid_fade_turns_around() {
let control = FadeControl::new();
let mut fader = Fader::new(control.clone(), RATE);
control.fade_out();
let down = render(&mut fader, 50);
control.fade_in(false);
let up = render(&mut fader, 10);
assert!(up[0] > *down.last().unwrap(), "rises from where it was");
assert!(!control.is_silent());
}
}