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}