Skip to main content

lavende_core/audio/
effects.rs

1pub mod volume {
2    use crate::audio::{
3        constants::{INT16_MAX_F, INT16_MIN_F},
4        effects::fade::FadeCurve,
5    };
6    pub struct VolumeEffect {
7        current_volume: f32,
8        target_volume: f32,
9        start_volume: f32,
10        fade_frames_total: usize,
11        fade_frames_elapsed: usize,
12        fade_active: bool,
13        fade_curve: FadeCurve,
14        limiter_softness: f32,
15        threshold_value: f32,
16        limit_headroom: f32,
17        limiter_lut: [f32; 1024],
18        channels: usize,
19    }
20    impl VolumeEffect {
21        pub fn new(volume: f32, sample_rate: u32, channels: usize) -> Self {
22            let limiter_threshold = 0.95_f32;
23            let limiter_softness = 0.4_f32;
24            let threshold_value = limiter_threshold * INT16_MAX_F;
25            let limit_headroom = INT16_MAX_F - threshold_value;
26            let mut limiter_lut = [0.0_f32; 1024];
27            for (i, val) in limiter_lut.iter_mut().enumerate() {
28                let overshoot = i as f32 / 1023.0 * 2.5;
29                *val = 1.0 - (-overshoot * limiter_softness).exp();
30            }
31            let fade_frames_total = sample_rate as usize;
32            Self {
33                current_volume: volume,
34                target_volume: volume,
35                start_volume: volume,
36                fade_frames_total,
37                fade_frames_elapsed: fade_frames_total,
38                fade_active: false,
39                fade_curve: FadeCurve::Sinusoidal,
40                limiter_softness,
41                threshold_value,
42                limit_headroom,
43                limiter_lut,
44                channels,
45            }
46        }
47        pub fn set_volume(&mut self, volume: f32) {
48            if (volume - self.target_volume).abs() < f32::EPSILON {
49                return;
50            }
51            self.start_volume = self.current_volume;
52            self.target_volume = volume;
53            self.fade_frames_elapsed = 0;
54            self.fade_active = self.fade_frames_total > 0;
55            if !self.fade_active {
56                self.current_volume = volume;
57            }
58        }
59        pub fn set_volume_instant(&mut self, volume: f32) {
60            self.current_volume = volume;
61            self.target_volume = volume;
62            self.start_volume = volume;
63            self.fade_active = false;
64            self.fade_frames_elapsed = self.fade_frames_total;
65        }
66        pub fn current_volume(&self) -> f32 {
67            self.current_volume
68        }
69        #[inline(always)]
70        fn apply_limiter(&self, value: f32) -> f32 {
71            let abs = value.abs();
72            if abs <= self.threshold_value || self.limit_headroom <= 0.0 {
73                return value;
74            }
75            let overshoot_raw = (abs - self.threshold_value) / self.limit_headroom;
76            let lut_idx = (overshoot_raw * 1023.0 / 2.5) as usize;
77            let softened = if lut_idx < 1024 {
78                self.limiter_lut[lut_idx]
79            } else {
80                1.0 - (-overshoot_raw * self.limiter_softness).exp()
81            };
82            let limited = self.threshold_value + self.limit_headroom * softened;
83            value.signum() * limited.min(INT16_MAX_F)
84        }
85        pub fn process(&mut self, frame: &mut [i16]) {
86            let sample_count = frame.len();
87            if sample_count == 0 {
88                return;
89            }
90            let (gain_start, gain_end) = if self.fade_active && self.fade_frames_total > 0 {
91                let frames = sample_count / self.channels;
92                let prev = self.fade_frames_elapsed;
93                let next = (prev + frames).min(self.fade_frames_total);
94                let t_start = prev as f32 / self.fade_frames_total as f32;
95                let t_end = next as f32 / self.fade_frames_total as f32;
96                let range = self.target_volume - self.start_volume;
97                let gs = self.start_volume + range * self.fade_curve.value(t_start);
98                let ge = self.start_volume + range * self.fade_curve.value(t_end);
99                self.fade_frames_elapsed = next;
100                if next >= self.fade_frames_total {
101                    self.fade_active = false;
102                    self.current_volume = self.target_volume;
103                } else {
104                    self.current_volume = ge;
105                }
106                (gs, ge)
107            } else {
108                let v = self.target_volume;
109                (v, v)
110            };
111            if !self.fade_active && (gain_start - 1.0).abs() < 0.0001 {
112                return;
113            }
114            let step = if sample_count > 1 {
115                (gain_end - gain_start) / (sample_count - 1) as f32
116            } else {
117                0.0
118            };
119            let mut gain = gain_start;
120            for s in frame.iter_mut() {
121                let scaled = *s as f32 * gain;
122                if scaled.abs() > self.threshold_value {
123                    let limited = self.apply_limiter(scaled);
124                    *s = limited.clamp(INT16_MIN_F, INT16_MAX_F) as i16;
125                } else {
126                    *s = scaled as i16;
127                }
128                gain += step;
129            }
130        }
131    }
132}
133pub mod tape {
134    use crate::config::player::TapeCurve;
135    struct TapeState {
136        start_rate: f32,
137        target_rate: f32,
138        duration_ms: f32,
139        elapsed_ms: f32,
140        curve: TapeCurve,
141    }
142    pub struct TapeEffect {
143        sample_rate: u32,
144        channels: usize,
145        current_rate: f32,
146        tape: Option<TapeState>,
147        ramp_completed: bool,
148        input_buffer: Vec<f32>,
149        read_pos: f64,
150    }
151    impl TapeEffect {
152        pub fn new(sample_rate: u32, channels: usize) -> Self {
153            let max_size = (sample_rate as usize * channels * 10).max(96000);
154            Self {
155                sample_rate,
156                channels,
157                current_rate: 1.0,
158                tape: None,
159                ramp_completed: false,
160                input_buffer: Vec::with_capacity(max_size),
161                read_pos: 0.0,
162            }
163        }
164        pub fn set_rate(&mut self, rate: f32) {
165            self.current_rate = rate.clamp(0.01, 2.0);
166            self.tape = None;
167            self.ramp_completed = false;
168        }
169        pub fn tape_to(&mut self, duration_ms: f32, is_start: bool, curve_type: TapeCurve) {
170            let target_rate = if is_start { 1.0 } else { 0.01 };
171            if duration_ms <= 0.0 {
172                self.current_rate = target_rate;
173                self.tape = None;
174                return;
175            }
176            self.tape = Some(TapeState {
177                start_rate: self.current_rate,
178                target_rate,
179                duration_ms,
180                elapsed_ms: 0.0,
181                curve: curve_type,
182            });
183            self.ramp_completed = false;
184        }
185        pub fn is_active(&self) -> bool {
186            self.tape.is_some() || (self.current_rate - 1.0).abs() > 0.001
187        }
188        pub fn is_ramping(&self) -> bool {
189            self.tape.is_some()
190        }
191        pub fn check_ramp_completed(&mut self) -> bool {
192            std::mem::replace(&mut self.ramp_completed, false)
193        }
194        pub fn process(&mut self, frame: &mut [i16]) {
195            if frame.is_empty() || !self.is_active() {
196                return;
197            }
198            let channels = self.channels;
199            for &s in frame.iter() {
200                self.input_buffer.push(s as f32 / 32767.0);
201            }
202            let mut out_idx = 0;
203            let sample_duration_ms = 1000.0 / self.sample_rate as f32;
204            while out_idx < frame.len() {
205                if let Some(state) = &mut self.tape {
206                    state.elapsed_ms += sample_duration_ms;
207                    let t = (state.elapsed_ms / state.duration_ms).min(1.0);
208                    let curve_t = state.curve.value(t);
209                    self.current_rate =
210                        state.start_rate + (state.target_rate - state.start_rate) * curve_t;
211                    if t >= 1.0 {
212                        self.current_rate = state.target_rate;
213                        self.tape = None;
214                        self.ramp_completed = true;
215                    }
216                }
217                if self.current_rate <= 0.01 && self.tape.is_none() {
218                    frame[out_idx..].fill(0);
219                    break;
220                }
221                let i_pos = (self.read_pos.floor() as usize / channels) * channels;
222                if i_pos + channels * 3 >= self.input_buffer.len() {
223                    frame[out_idx..].fill(0);
224                    break;
225                }
226                let frac = ((self.read_pos - i_pos as f64) / channels as f64) as f32;
227                for c in 0..channels {
228                    let p0 = if i_pos >= channels {
229                        self.input_buffer[i_pos - channels + c]
230                    } else {
231                        self.input_buffer[i_pos + c]
232                    };
233                    let p1 = self.input_buffer[i_pos + c];
234                    let p2 = self.input_buffer[i_pos + channels + c];
235                    let p3 = self.input_buffer[i_pos + channels * 2 + c];
236                    let val = 0.5
237                        * (2.0 * p1
238                            + (-p0 + p2) * frac
239                            + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * frac * frac
240                            + (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * frac * frac * frac);
241                    if out_idx < frame.len() {
242                        frame[out_idx] = (val * 32767.0).clamp(-32768.0, 32767.0).round() as i16;
243                        out_idx += 1;
244                    }
245                }
246                self.read_pos += self.current_rate as f64 * channels as f64;
247            }
248            if self.read_pos > (self.sample_rate as f64 * channels as f64) {
249                let integral = (self.read_pos.floor() as usize / channels) * channels;
250                self.input_buffer.copy_within(integral.., 0);
251                self.input_buffer
252                    .truncate(self.input_buffer.len() - integral);
253                self.read_pos -= integral as f64;
254            }
255        }
256    }
257}
258pub mod fade {
259    use crate::audio::constants::{INT16_MAX_F, INT16_MIN_F};
260    #[derive(Debug, Clone, Copy, PartialEq)]
261    pub enum FadeCurve {
262        Linear,
263        Sinusoidal,
264    }
265    impl FadeCurve {
266        pub fn value(self, t: f32) -> f32 {
267            match self {
268                FadeCurve::Linear => t,
269                FadeCurve::Sinusoidal => 0.5 * (1.0 - (t * std::f32::consts::PI).cos()),
270            }
271        }
272    }
273    pub struct FadeEffect {
274        current_gain: f32,
275        target_gain: f32,
276        start_gain: f32,
277        fade_samples_total: usize,
278        fade_samples_elapsed: usize,
279        fade_active: bool,
280        curve: FadeCurve,
281    }
282    impl FadeEffect {
283        pub fn new(initial_gain: f32, _channels: usize) -> Self {
284            Self {
285                current_gain: initial_gain,
286                target_gain: initial_gain,
287                start_gain: initial_gain,
288                fade_samples_total: 0,
289                fade_samples_elapsed: 0,
290                fade_active: false,
291                curve: FadeCurve::Sinusoidal,
292            }
293        }
294        pub fn set_gain(&mut self, gain: f32) {
295            self.current_gain = gain;
296            self.target_gain = gain;
297            self.start_gain = gain;
298            self.fade_active = false;
299        }
300        pub fn fade_to(
301            &mut self,
302            target: f32,
303            duration_ms: u64,
304            curve: FadeCurve,
305            sample_rate: u32,
306        ) {
307            if duration_ms == 0 {
308                self.set_gain(target);
309                return;
310            }
311            self.start_gain = self.current_gain;
312            self.target_gain = target;
313            self.fade_samples_total = (sample_rate as u64 * duration_ms / 1000) as usize;
314            self.fade_samples_elapsed = 0;
315            self.fade_active = self.fade_samples_total > 0;
316            self.curve = curve;
317        }
318        pub fn current_gain(&self) -> f32 {
319            self.current_gain
320        }
321        pub fn is_done(&self) -> bool {
322            !self.fade_active
323        }
324        pub fn process(&mut self, frame: &mut [i16]) {
325            let sample_count = frame.len();
326            if sample_count == 0 {
327                return;
328            }
329            if !self.fade_active && (self.current_gain - 1.0).abs() < 1e-5 {
330                return;
331            }
332            let (gain_start, gain_end) = if self.fade_active && self.fade_samples_total > 0 {
333                let prev = self.fade_samples_elapsed;
334                let next = (prev + sample_count).min(self.fade_samples_total);
335                let t0 = prev as f32 / self.fade_samples_total as f32;
336                let t1 = next as f32 / self.fade_samples_total as f32;
337                let range = self.target_gain - self.start_gain;
338                let gs = self.start_gain + range * self.curve.value(t0);
339                let ge = self.start_gain + range * self.curve.value(t1);
340                self.fade_samples_elapsed = next;
341                if next >= self.fade_samples_total {
342                    self.fade_active = false;
343                    self.current_gain = self.target_gain;
344                } else {
345                    self.current_gain = ge;
346                }
347                (gs, ge)
348            } else {
349                let g = self.current_gain;
350                (g, g)
351            };
352            let step = if sample_count > 1 {
353                (gain_end - gain_start) / (sample_count - 1) as f32
354            } else {
355                0.0
356            };
357            let mut gain = gain_start;
358            for s in frame.iter_mut() {
359                let out = (*s as f32 * gain).clamp(INT16_MIN_F, INT16_MAX_F);
360                *s = out.round() as i16;
361                gain += step;
362            }
363        }
364    }
365}
366pub mod crossfade {
367    use super::fade::FadeCurve;
368    use crate::audio::{
369        RingBuffer,
370        buffer::PooledBuffer,
371        constants::{HALF_PI, INT16_MAX_F, INT16_MIN_F},
372    };
373    use flume::Receiver;
374    pub struct CrossfadeController {
375        sample_rate: u32,
376        channels: usize,
377        bytes_per_ms: usize,
378        ring_buffer: Option<RingBuffer>,
379        next_rx: Option<Receiver<PooledBuffer>>,
380        active_fade: Option<CrossfadeState>,
381        target_buffer_bytes: usize,
382    }
383    struct CrossfadeState {
384        duration_ms: u64,
385        elapsed_ms: f32,
386        curve: FadeCurve,
387    }
388    impl CrossfadeController {
389        pub fn new(sample_rate: u32, channels: usize) -> Self {
390            let bytes_per_ms = (sample_rate as usize * channels * 2) / 1000;
391            Self {
392                sample_rate,
393                channels,
394                bytes_per_ms,
395                ring_buffer: None,
396                next_rx: None,
397                active_fade: None,
398                target_buffer_bytes: 0,
399            }
400        }
401        pub fn prepare(&mut self, rx: Receiver<PooledBuffer>, duration_ms: u64) {
402            self.clear();
403            let buffer_size = (duration_ms as usize * self.bytes_per_ms).max(8192);
404            self.ring_buffer = Some(RingBuffer::new(buffer_size));
405            self.target_buffer_bytes = buffer_size;
406            self.next_rx = Some(rx);
407        }
408        pub fn fill_buffer(&mut self) {
409            let Some(rx) = &self.next_rx else { return };
410            let Some(ring) = &mut self.ring_buffer else {
411                return;
412            };
413            while let Ok(pooled) = rx.try_recv() {
414                ring.write(crate::audio::buffer::as_byte_slice(&pooled));
415            }
416        }
417        pub fn is_ready(&self) -> bool {
418            let Some(ring) = &self.ring_buffer else {
419                return false;
420            };
421            ring.len()
422                >= (self.target_buffer_bytes * 8 / 10)
423                    .min(self.sample_rate as usize * self.channels * 2)
424        }
425        pub fn start_crossfade(&mut self, duration_ms: u64, curve: FadeCurve) -> bool {
426            if self.ring_buffer.is_none() || !self.is_ready() {
427                return false;
428            }
429            self.active_fade = Some(CrossfadeState {
430                duration_ms,
431                elapsed_ms: 0.0,
432                curve,
433            });
434            true
435        }
436        pub fn is_active(&self) -> bool {
437            self.active_fade.is_some()
438        }
439        pub fn clear(&mut self) {
440            self.ring_buffer = None;
441            self.next_rx = None;
442            self.active_fade = None;
443            self.target_buffer_bytes = 0;
444        }
445        pub fn process(&mut self, frame: &mut [i16]) -> bool {
446            let (elapsed, duration, curve) = match &self.active_fade {
447                Some(s) => (s.elapsed_ms, s.duration_ms as f32, s.curve),
448                None => return false,
449            };
450            let sample_count = frame.len();
451            let byte_count = sample_count * 2;
452            let next_bytes = if let Some(ring) = &mut self.ring_buffer {
453                ring.read(byte_count)
454            } else {
455                return false;
456            };
457            let Some(next_bytes) = next_bytes else {
458                return false;
459            };
460            let next_samples_raw = crate::audio::buffer::as_i16_slice(&next_bytes);
461            let chunk_ms =
462                (sample_count as f32 / self.channels as f32 / self.sample_rate as f32) * 1000.0;
463            let t_start = (elapsed / duration).min(1.0);
464            let t_end = ((elapsed + chunk_ms) / duration).min(1.0);
465            let (out_start, in_start) = fade_gains(t_start, curve);
466            let (out_end, in_end) = fade_gains(t_end, curve);
467            let step_out = if sample_count > 1 {
468                (out_end - out_start) / (sample_count - 1) as f32
469            } else {
470                0.0
471            };
472            let step_in = if sample_count > 1 {
473                (in_end - in_start) / (sample_count - 1) as f32
474            } else {
475                0.0
476            };
477            let mut g_out = out_start;
478            let mut g_in = in_start;
479            for (sample, &next_val) in frame.iter_mut().zip(next_samples_raw.iter()) {
480                let mixed = (*sample as f32 * g_out) + (next_val as f32 * g_in);
481                *sample = mixed.clamp(INT16_MIN_F, INT16_MAX_F) as i16;
482                g_out += step_out;
483                g_in += step_in;
484            }
485            let state = self.active_fade.as_mut().unwrap();
486            state.elapsed_ms += chunk_ms;
487            let finished = state.elapsed_ms >= state.duration_ms as f32;
488            if finished {
489                self.active_fade = None;
490            }
491            finished
492        }
493    }
494    fn fade_gains(t: f32, curve: FadeCurve) -> (f32, f32) {
495        let t = t.clamp(0.0, 1.0);
496        match curve {
497            FadeCurve::Linear => (1.0 - t, t),
498            FadeCurve::Sinusoidal => ((t * HALF_PI).cos(), (t * HALF_PI).sin()),
499        }
500    }
501}
502use crate::audio::buffer::PooledBuffer;
503use std::sync::atomic::{AtomicU8, AtomicU64};
504pub struct ProcessContext<'a> {
505    pub mix_buf: &'a mut [i32],
506    pub i: &'a mut usize,
507    pub out_len: usize,
508    pub vol: f32,
509    pub stash: &'a mut Vec<i16>,
510    pub rx: &'a flume::Receiver<PooledBuffer>,
511    pub state_atomic: &'a AtomicU8,
512    pub position_atomic: &'a AtomicU64,
513}
514pub trait TransitionEffect: Send {
515    fn process(&mut self, ctx: ProcessContext<'_>) -> bool;
516}