1use crate::buffer::delay_line::{DelayLine, InterpolationType};
4use crate::drc::LookaheadDetector;
5use crate::filter::ParametricEqualizer3Band;
6use crate::gain::Gain;
7use crate::safety;
8use crate::{Compressor, CompressorConfig};
9use std::error::Error;
10use std::fmt;
11
12const LIMITER_DELAY_HEADROOM: usize = 8;
14
15#[derive(Debug, Clone, Copy, PartialEq)]
16pub struct AudioSpec {
17 pub sample_rate: f32,
18 pub channels: usize,
19}
20
21impl AudioSpec {
22 pub fn new(sample_rate: f32, channels: usize) -> Result<Self, EngineError> {
23 if !sample_rate.is_finite() || sample_rate <= 0.0 {
24 return Err(EngineError::InvalidSampleRate(sample_rate));
25 }
26 if channels == 0 {
27 return Err(EngineError::NoChannels);
28 }
29 Ok(Self {
30 sample_rate,
31 channels,
32 })
33 }
34}
35
36#[derive(Debug, Clone, PartialEq)]
37pub enum EngineError {
38 InvalidSampleRate(f32),
39 NoChannels,
40 ChannelCountMismatch { expected: usize, actual: usize },
41 UnequalChannelLengths,
42}
43
44impl fmt::Display for EngineError {
45 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
46 match self {
47 Self::InvalidSampleRate(value) => write!(formatter, "Invalid sample rate: {value}"),
48 Self::NoChannels => write!(formatter, "Audio requires at least one channel"),
49 Self::ChannelCountMismatch { expected, actual } => {
50 write!(formatter, "Expected {expected} channels, received {actual}")
51 }
52 Self::UnequalChannelLengths => write!(formatter, "All channels in a block must match"),
53 }
54 }
55}
56
57impl Error for EngineError {}
58
59pub struct AudioBlock<'a> {
61 pub(crate) channels: &'a mut [&'a mut [f32]],
62 frames: usize,
63}
64
65impl<'a> AudioBlock<'a> {
66 pub fn new(channels: &'a mut [&'a mut [f32]]) -> Result<Self, EngineError> {
67 let Some(first_channel) = channels.first() else {
68 return Err(EngineError::NoChannels);
69 };
70 let frames = first_channel.len();
71 if channels.iter().any(|channel| channel.len() != frames) {
72 return Err(EngineError::UnequalChannelLengths);
73 }
74 Ok(Self { channels, frames })
75 }
76
77 pub fn channels(&self) -> usize {
78 self.channels.len()
79 }
80
81 pub fn frames(&self) -> usize {
82 self.frames
83 }
84
85 pub fn channel(&self, index: usize) -> Option<&[f32]> {
86 self.channels.get(index).map(|channel| &**channel as &[f32])
87 }
88
89 pub fn channel_mut(&mut self, index: usize) -> Option<&mut [f32]> {
90 self.channels.get_mut(index).map(|channel| &mut **channel)
91 }
92}
93
94pub trait AudioProcessor {
96 fn prepare(&mut self, spec: AudioSpec) -> Result<(), EngineError>;
97 fn process(&mut self, block: &mut AudioBlock<'_>) -> Result<(), EngineError>;
98 fn reset(&mut self);
99 fn latency_samples(&self) -> usize {
100 0
101 }
102 fn flush(&mut self, _block: &mut AudioBlock<'_>) -> Result<usize, EngineError> {
105 Ok(0)
106 }
107}
108
109pub struct ProcessorChain {
110 processors: Vec<Box<dyn AudioProcessor>>,
111 tail_frames_remaining: usize,
113}
114
115impl ProcessorChain {
116 pub fn new() -> Self {
117 Self {
118 processors: Vec::new(),
119 tail_frames_remaining: 0,
120 }
121 }
122
123 pub fn push<P: AudioProcessor + 'static>(&mut self, processor: P) {
124 self.processors.push(Box::new(processor));
125 }
126
127 fn process_stages(&mut self, block: &mut AudioBlock<'_>) -> Result<(), EngineError> {
133 for processor in &mut self.processors {
134 processor.process(block)?;
135 }
136 safety::sanitize_block(block);
138 Ok(())
139 }
140}
141
142impl Default for ProcessorChain {
143 fn default() -> Self {
144 Self::new()
145 }
146}
147
148impl AudioProcessor for ProcessorChain {
149 fn prepare(&mut self, spec: AudioSpec) -> Result<(), EngineError> {
150 for processor in &mut self.processors {
151 processor.prepare(spec)?;
152 }
153 self.tail_frames_remaining = self.latency_samples();
154 Ok(())
155 }
156
157 fn process(&mut self, block: &mut AudioBlock<'_>) -> Result<(), EngineError> {
158 self.process_stages(block)?;
159 self.tail_frames_remaining = self.latency_samples();
161 Ok(())
162 }
163
164 fn reset(&mut self) {
165 for processor in &mut self.processors {
166 processor.reset();
167 }
168 self.tail_frames_remaining = self.latency_samples();
169 }
170
171 fn latency_samples(&self) -> usize {
172 self.processors
173 .iter()
174 .map(|processor| processor.latency_samples())
175 .sum()
176 }
177
178 fn flush(&mut self, block: &mut AudioBlock<'_>) -> Result<usize, EngineError> {
189 if self.tail_frames_remaining == 0 {
190 return Ok(0);
191 }
192
193 let frames_flushed = block.frames().min(self.tail_frames_remaining);
194 if frames_flushed == 0 {
195 return Ok(0);
196 }
197
198 for channel_index in 0..block.channels() {
200 block
201 .channel_mut(channel_index)
202 .expect("validated channel count")
203 .fill(0.0);
204 }
205 self.process_stages(block)?;
206 self.tail_frames_remaining -= frames_flushed;
207 Ok(frames_flushed)
208 }
209}
210
211pub struct GainProcessor {
212 gain_db: f32,
213 gain_linear: f32,
214 channels: usize,
215}
216
217impl GainProcessor {
218 pub fn new(gain_db: f32) -> Self {
219 Self {
220 gain_db,
221 gain_linear: Gain::db_to_linear(gain_db),
222 channels: 0,
223 }
224 }
225
226 pub fn set_gain_db(&mut self, gain_db: f32) {
227 self.gain_db = gain_db;
228 self.gain_linear = Gain::db_to_linear(gain_db);
229 }
230}
231
232impl AudioProcessor for GainProcessor {
233 fn prepare(&mut self, spec: AudioSpec) -> Result<(), EngineError> {
234 self.channels = spec.channels;
235 Ok(())
236 }
237
238 fn process(&mut self, block: &mut AudioBlock<'_>) -> Result<(), EngineError> {
239 if block.channels() != self.channels {
240 return Err(EngineError::ChannelCountMismatch {
241 expected: self.channels,
242 actual: block.channels(),
243 });
244 }
245 for ch in 0..block.channels() {
246 let channel = block.channel_mut(ch).expect("validated channel count");
247 Gain::apply_linear(channel, self.gain_linear);
248 }
249 Ok(())
250 }
251
252 fn reset(&mut self) {}
253}
254
255pub struct EqualizerProcessor {
256 eqs: Vec<ParametricEqualizer3Band>,
257 low_freq: f32,
258 low_gain_db: f32,
259 mid_freq: f32,
260 mid_q: f32,
261 mid_gain_db: f32,
262 high_freq: f32,
263 high_gain_db: f32,
264}
265
266impl EqualizerProcessor {
267 pub fn new(
268 low_freq: f32,
269 low_gain_db: f32,
270 mid_freq: f32,
271 mid_q: f32,
272 mid_gain_db: f32,
273 high_freq: f32,
274 high_gain_db: f32,
275 ) -> Self {
276 Self {
277 eqs: Vec::new(),
278 low_freq,
279 low_gain_db,
280 mid_freq,
281 mid_q,
282 mid_gain_db,
283 high_freq,
284 high_gain_db,
285 }
286 }
287}
288
289impl AudioProcessor for EqualizerProcessor {
290 fn prepare(&mut self, spec: AudioSpec) -> Result<(), EngineError> {
291 self.eqs = (0..spec.channels)
292 .map(|_| {
293 let mut eq = ParametricEqualizer3Band::new(spec.sample_rate);
294 eq.set_low_band(self.low_freq, self.low_gain_db);
295 eq.set_mid_band(self.mid_freq, self.mid_q, self.mid_gain_db);
296 eq.set_high_band(self.high_freq, self.high_gain_db);
297 eq
298 })
299 .collect();
300 Ok(())
301 }
302
303 fn process(&mut self, block: &mut AudioBlock<'_>) -> Result<(), EngineError> {
304 if block.channels() != self.eqs.len() {
305 return Err(EngineError::ChannelCountMismatch {
306 expected: self.eqs.len(),
307 actual: block.channels(),
308 });
309 }
310 for (index, eq) in self.eqs.iter_mut().enumerate() {
311 let channel = block.channel_mut(index).expect("validated channel count");
312 eq.process_buffer(channel);
313 }
314 Ok(())
315 }
316
317 fn reset(&mut self) {
318 for eq in &mut self.eqs {
319 eq.reset();
320 }
321 }
322}
323
324pub struct CompressorProcessor {
325 config: CompressorConfig,
326 processors: Vec<Compressor>,
327}
328
329impl CompressorProcessor {
330 pub fn new(config: CompressorConfig) -> Self {
331 Self {
332 config,
333 processors: Vec::new(),
334 }
335 }
336}
337
338impl AudioProcessor for CompressorProcessor {
339 fn prepare(&mut self, spec: AudioSpec) -> Result<(), EngineError> {
340 self.processors = (0..spec.channels)
341 .map(|_| Compressor::new(spec.sample_rate, self.config.clone()))
342 .collect();
343 Ok(())
344 }
345
346 fn process(&mut self, block: &mut AudioBlock<'_>) -> Result<(), EngineError> {
347 if block.channels() != self.processors.len() {
348 return Err(EngineError::ChannelCountMismatch {
349 expected: self.processors.len(),
350 actual: block.channels(),
351 });
352 }
353 for (index, processor) in self.processors.iter_mut().enumerate() {
354 let channel = block
355 .channel_mut(index)
356 .expect("channel count was validated");
357 processor.process_buffer(channel);
358 }
359 Ok(())
360 }
361
362 fn reset(&mut self) {
363 for processor in &mut self.processors {
364 processor.reset();
365 }
366 }
367}
368
369pub struct LimiterProcessor {
375 ceiling_db: f32,
376 release_ms: f32,
377 lookahead_ms: f32,
378 detector: Option<LookaheadDetector>,
379 delays: Vec<DelayLine>,
380 ceiling_linear: f32,
381 latency_samples: usize,
382 pending_flush_samples: usize,
383}
384
385impl LimiterProcessor {
386 pub fn new(ceiling_db: f32, release_ms: f32, lookahead_ms: f32) -> Self {
387 Self {
388 ceiling_db,
389 release_ms,
390 lookahead_ms,
391 detector: None,
392 delays: Vec::new(),
393 ceiling_linear: Gain::db_to_linear(ceiling_db.min(0.0)),
394 latency_samples: 0,
395 pending_flush_samples: 0,
396 }
397 }
398
399 fn process_frame(&mut self, block: &mut AudioBlock<'_>, frame: usize) {
402 let mut frame_peak = 0.0_f32;
403 for channel_index in 0..self.delays.len() {
404 let sample = block
405 .channel(channel_index)
406 .expect("validated channel count")[frame];
407 frame_peak = frame_peak.max(sample.abs());
408 }
409
410 let detector = self.detector.as_mut().expect("limiter prepared");
411 let gain = detector.process_input_peak(frame_peak);
412 let delay_samples = self.latency_samples as f32;
413
414 for (channel_index, delay) in self.delays.iter_mut().enumerate() {
415 let input = block
416 .channel(channel_index)
417 .expect("validated channel count")[frame];
418 delay.push(input);
419 let delayed = delay.read_delay(delay_samples, InterpolationType::None);
420 let limited = (delayed * gain).clamp(-self.ceiling_linear, self.ceiling_linear);
421 block
422 .channel_mut(channel_index)
423 .expect("validated channel count")[frame] = limited;
424 }
425 }
426}
427
428impl AudioProcessor for LimiterProcessor {
429 fn prepare(&mut self, spec: AudioSpec) -> Result<(), EngineError> {
430 let detector = LookaheadDetector::new(
431 spec.sample_rate,
432 self.ceiling_db,
433 self.release_ms,
434 self.lookahead_ms,
435 );
436 self.latency_samples = detector.latency_samples();
437 self.ceiling_linear = detector.ceiling_linear();
438 self.delays = (0..spec.channels)
439 .map(|_| DelayLine::new(self.latency_samples + LIMITER_DELAY_HEADROOM))
440 .collect();
441 self.detector = Some(detector);
442 self.pending_flush_samples = self.latency_samples;
443 Ok(())
444 }
445
446 fn process(&mut self, block: &mut AudioBlock<'_>) -> Result<(), EngineError> {
447 if block.channels() != self.delays.len() {
448 return Err(EngineError::ChannelCountMismatch {
449 expected: self.delays.len(),
450 actual: block.channels(),
451 });
452 }
453 self.pending_flush_samples = self.latency_samples;
454 for frame in 0..block.frames() {
455 self.process_frame(block, frame);
456 }
457 Ok(())
458 }
459
460 fn reset(&mut self) {
461 for delay in &mut self.delays {
462 delay.clear();
463 }
464 if let Some(detector) = self.detector.as_mut() {
465 detector.reset();
466 }
467 self.pending_flush_samples = self.latency_samples;
468 }
469
470 fn latency_samples(&self) -> usize {
471 self.latency_samples
472 }
473
474 fn flush(&mut self, block: &mut AudioBlock<'_>) -> Result<usize, EngineError> {
475 if block.channels() != self.delays.len() {
476 return Err(EngineError::ChannelCountMismatch {
477 expected: self.delays.len(),
478 actual: block.channels(),
479 });
480 }
481 if self.pending_flush_samples == 0 {
482 return Ok(0);
483 }
484
485 let frames_to_flush = block.frames().min(self.pending_flush_samples);
486 for channel_index in 0..self.delays.len() {
487 block
488 .channel_mut(channel_index)
489 .expect("validated channel count")
490 .fill(0.0);
491 }
492 for frame in 0..frames_to_flush {
494 self.process_frame(block, frame);
495 }
496 self.pending_flush_samples -= frames_to_flush;
497 Ok(frames_to_flush)
498 }
499}
500
501#[cfg(test)]
502mod tests {
503 use super::*;
504 use crate::drc::DetectionMode;
505
506 const SAMPLE_RATE: f32 = 44_100.0;
507 const CHANNELS: usize = 1;
508 const LIMITER_CEILING_DB: f32 = -1.0;
509 const LIMITER_RELEASE_MS: f32 = 50.0;
510 const LIMITER_LOOKAHEAD_MS: f32 = 2.0;
511 const TEST_SIGNAL_FRAMES: usize = 2_048;
512
513 fn audio_spec() -> AudioSpec {
514 AudioSpec::new(SAMPLE_RATE, CHANNELS).expect("spec audio valid")
515 }
516
517 fn limiter_latency_samples() -> usize {
518 (LIMITER_LOOKAHEAD_MS * 0.001 * SAMPLE_RATE).round() as usize
519 }
520
521 fn limiter() -> LimiterProcessor {
522 LimiterProcessor::new(LIMITER_CEILING_DB, LIMITER_RELEASE_MS, LIMITER_LOOKAHEAD_MS)
523 }
524
525 fn transparent_compressor() -> CompressorProcessor {
526 CompressorProcessor::new(CompressorConfig {
527 threshold_db: 0.0,
528 ratio: 1.0,
529 knee_db: 0.0,
530 attack_ms: 1.0,
531 release_ms: 10.0,
532 makeup_gain_db: 0.0,
533 mode: DetectionMode::Peak,
534 })
535 }
536
537 fn full_mastering_chain() -> ProcessorChain {
538 let mut chain = ProcessorChain::new();
539 chain.push(GainProcessor::new(-3.0));
540 chain.push(CompressorProcessor::new(CompressorConfig {
541 threshold_db: -12.0,
542 ratio: 3.0,
543 knee_db: 4.0,
544 attack_ms: 5.0,
545 release_ms: 80.0,
546 makeup_gain_db: 0.0,
547 mode: DetectionMode::Peak,
548 }));
549 chain.push(limiter());
550 chain.prepare(audio_spec()).expect("prepare chain");
551 chain
552 }
553
554 fn limiter_chain(with_prefix_processor: bool) -> ProcessorChain {
556 let mut chain = ProcessorChain::new();
557 if with_prefix_processor {
558 chain.push(GainProcessor::new(0.0));
559 }
560 chain.push(transparent_compressor());
561 chain.push(limiter());
562 chain.prepare(audio_spec()).expect("prepare chain");
563 chain
564 }
565
566 fn process_block(chain: &mut ProcessorChain, samples: &mut [f32]) {
567 let mut channel_views: Vec<&mut [f32]> = vec![samples];
568 let mut block = AudioBlock::new(&mut channel_views).expect("block valid");
569 chain.process(&mut block).expect("process block");
570 }
571
572 fn flush_all(chain: &mut ProcessorChain, block_size: usize) -> Vec<f32> {
574 let mut tail = Vec::new();
575 loop {
576 let mut samples = vec![0.0_f32; block_size];
577 let mut channel_views: Vec<&mut [f32]> = vec![samples.as_mut_slice()];
578 let mut block = AudioBlock::new(&mut channel_views).expect("flush block");
579 let flushed = chain.flush(&mut block).expect("flush");
580 if flushed == 0 {
581 break;
582 }
583 tail.extend_from_slice(&samples[..flushed]);
584 }
585 tail
586 }
587
588 fn test_signal(frames: usize) -> Vec<f32> {
589 (0..frames)
590 .map(|index| {
591 let time = index as f32 / SAMPLE_RATE;
592 (2.0 * std::f32::consts::PI * 220.0 * time).sin() * 0.3
593 })
594 .collect()
595 }
596
597 fn render_with_block_size(block_size: usize) -> Vec<f32> {
599 let input = test_signal(TEST_SIGNAL_FRAMES);
600 let mut chain = full_mastering_chain();
601 let mut output = Vec::with_capacity(TEST_SIGNAL_FRAMES + limiter_latency_samples());
602
603 for chunk in input.chunks(block_size) {
604 let mut samples = chunk.to_vec();
605 process_block(&mut chain, &mut samples);
606 output.extend_from_slice(&samples);
607 }
608 output.extend(flush_all(&mut chain, block_size));
609 output
610 }
611
612 #[test]
613 fn flush_drains_limiter_tail_regardless_of_prefix_processors() {
614 let latency = limiter_latency_samples();
615 let mut tails = Vec::new();
616
617 for with_prefix_processor in [false, true] {
618 let mut chain = limiter_chain(with_prefix_processor);
619 let mut impulse = vec![0.0_f32; 64];
620 impulse[0] = 0.5;
621 process_block(&mut chain, &mut impulse);
622 tails.push(flush_all(&mut chain, latency.max(1)));
623 }
624
625 assert_eq!(
626 tails[0].len(),
627 latency,
628 "tail harus sepanjang latency limiter"
629 );
630 assert_eq!(tails[1].len(), latency);
631
632 for (plain, prefixed) in tails[0].iter().zip(tails[1].iter()) {
634 assert!((plain - prefixed).abs() < 1e-6);
635 }
636
637 let impulse_survives = tails[0]
638 .iter()
639 .any(|sample| (sample.abs() - 0.5).abs() < 1e-3);
640 assert!(
641 impulse_survives,
642 "tail harus memuat impulse 0.5 yang tertahan lookahead"
643 );
644 }
645
646 #[test]
647 fn flush_drains_the_tail_of_every_latency_stage() {
648 let mut chain = ProcessorChain::new();
649 chain.push(LimiterProcessor::new(
650 LIMITER_CEILING_DB,
651 LIMITER_RELEASE_MS,
652 1.0,
653 ));
654 chain.push(limiter());
655 chain.prepare(audio_spec()).expect("prepare chain");
656
657 let expected_latency = chain.latency_samples();
658 assert_eq!(
659 expected_latency,
660 chain.latency_samples(),
661 "total latency = jumlah latency setiap stage"
662 );
663
664 let mut impulse = vec![0.0_f32; 32];
665 impulse[0] = 0.5;
666 process_block(&mut chain, &mut impulse);
667
668 let tail = flush_all(&mut chain, expected_latency + 16);
669 assert_eq!(
670 tail.len(),
671 expected_latency,
672 "flush harus meniriskan tail dari semua stage berlatency"
673 );
674 let impulse_survives = tail.iter().any(|sample| (sample.abs() - 0.5).abs() < 1e-3);
675 assert!(impulse_survives, "impulse harus keluar dari kedua limiter");
676 }
677
678 #[test]
679 fn flush_returns_zero_when_chain_has_no_latency() {
680 let mut chain = ProcessorChain::new();
681 chain.push(GainProcessor::new(0.0));
682 chain.prepare(audio_spec()).expect("prepare chain");
683
684 let mut samples = vec![0.1_f32; 16];
685 process_block(&mut chain, &mut samples);
686
687 assert_eq!(chain.latency_samples(), 0);
688 assert!(flush_all(&mut chain, 16).is_empty());
689 }
690
691 #[test]
692 fn block_size_does_not_change_output() {
693 let reference = render_with_block_size(TEST_SIGNAL_FRAMES);
694
695 for block_size in [64, 256, 1_024] {
696 let rendered = render_with_block_size(block_size);
697
698 assert_eq!(
699 rendered.len(),
700 reference.len(),
701 "panjang output berbeda pada blok {block_size}"
702 );
703 for (frame, (expected, actual)) in reference.iter().zip(rendered.iter()).enumerate() {
704 assert!(
705 (expected - actual).abs() < 1e-6,
706 "blok {block_size}: frame {frame} berbeda ({expected} vs {actual})"
707 );
708 }
709 }
710 }
711
712 #[test]
713 fn limiter_links_channels_to_the_loudest_peak() {
714 let spec = AudioSpec::new(SAMPLE_RATE, 2).expect("spec stereo");
715 let mut left = vec![0.1_f32; 512];
716 let mut right = vec![0.5_f32; 512];
717 let mut channel_views: Vec<&mut [f32]> = vec![&mut left, &mut right];
718 let mut block = AudioBlock::new(&mut channel_views).expect("block");
719
720 let mut limiter = LimiterProcessor::new(-12.0, 50.0, LIMITER_LOOKAHEAD_MS);
721 limiter.prepare(spec).expect("prepare limiter");
722 limiter.process(&mut block).expect("process");
723
724 let ceiling = Gain::db_to_linear(-12.0);
725 let expected_gain = ceiling / 0.5;
726 let steady_frame = 400;
727
728 assert!(
729 (right[steady_frame] - 0.5 * expected_gain).abs() < 1e-3,
730 "channel kanan: {}",
731 right[steady_frame]
732 );
733 assert!(
734 (left[steady_frame] - 0.1 * expected_gain).abs() < 1e-3,
735 "channel kiri: {}",
736 left[steady_frame]
737 );
738 let ratio = left[steady_frame] / right[steady_frame];
740 assert!((ratio - 0.2).abs() < 1e-3, "rasio kiri/kanan {ratio}");
741 }
742
743 #[test]
744 fn limiter_processor_ceiling_holds_for_percussive_input() {
745 let mut samples = vec![0.0_f32; 1_000];
746 for index in [100, 300, 301, 700] {
747 samples[index] = 0.95;
748 }
749 let mut channel_views: Vec<&mut [f32]> = vec![&mut samples];
750 let mut block = AudioBlock::new(&mut channel_views).expect("block");
751
752 let mut limiter =
753 LimiterProcessor::new(LIMITER_CEILING_DB, LIMITER_RELEASE_MS, LIMITER_LOOKAHEAD_MS);
754 limiter.prepare(audio_spec()).expect("prepare limiter");
755 limiter.process(&mut block).expect("process");
756
757 let peak = samples
758 .iter()
759 .fold(0.0_f32, |acc, sample| acc.max(sample.abs()));
760 assert!(
761 peak <= Gain::db_to_linear(LIMITER_CEILING_DB) + 1e-6,
762 "peak {peak} melampaui ceiling"
763 );
764 }
765}