1pub mod buffer;
4pub mod convert;
5pub mod drc;
6pub mod engine;
7pub mod fft;
8pub mod filter;
9pub mod gain;
10pub mod limiter;
11pub mod normalize;
12pub mod safety;
13pub mod tui;
14pub mod wav;
15
16pub use buffer::{DelayLine, FeedbackDelay, InterpolationType, RingBuffer};
17pub use convert::SampleConvert;
18pub use drc::{
19 BrickwallLimiter, Compressor, CompressorConfig, DetectionMode, EnvelopeFollower,
20 LookaheadDetector, SlidingMax,
21};
22pub use engine::{
23 AudioBlock, AudioProcessor, AudioSpec, CompressorProcessor, EngineError, EqualizerProcessor,
24 GainProcessor, LimiterProcessor, ProcessorChain,
25};
26pub use fft::{Complex, Fft, Window, WindowType};
27pub use filter::{BiquadCoefficients, BiquadFilter, ParametricEqualizer3Band};
28pub use gain::Gain;
29pub use limiter::Limiter;
30pub use normalize::Normalizer;
31pub use tui::{MenuItem, TerminalCanvas, TuiMenu};
32pub use wav::{
33 DitherMode, WavHeader, WavReader, WavSampleFormat, WavStreamReader, WavStreamWriter,
34 WavWriteOptions, WavWriter,
35};
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40 use std::io::Cursor;
41
42 #[test]
43 fn test_gain_and_normalize() {
44 let mut buffer = vec![0.1, -0.2, 0.4, -0.5];
45
46 Gain::apply_db(&mut buffer, 6.0206);
47 assert!((buffer[3] - (-1.0)).abs() < 1e-3);
48
49 Normalizer::normalize_to_peak(&mut buffer, 0.8);
50 let peak = Normalizer::find_peak(&buffer);
51 assert!((peak - 0.8).abs() < 1e-4);
52 }
53
54 #[test]
55 fn test_audio_block_and_processor_chain() {
56 let spec = AudioSpec::new(48_000.0, 2).expect("valid audio specification");
57 let mut left = vec![0.25, -0.25];
58 let mut right = vec![0.5, -0.5];
59 let mut channels: Vec<&mut [f32]> = vec![&mut left, &mut right];
60 let mut block = AudioBlock::new(&mut channels).expect("matching channel lengths");
61
62 let config = CompressorConfig {
63 threshold_db: 0.0,
64 ratio: 1.0,
65 knee_db: 0.0,
66 attack_ms: 1.0,
67 release_ms: 10.0,
68 makeup_gain_db: 0.0,
69 mode: DetectionMode::Peak,
70 };
71 let mut chain = ProcessorChain::new();
72 chain.push(CompressorProcessor::new(config));
73 chain.prepare(spec).expect("processors prepare");
74 chain.process(&mut block).expect("processors run");
75
76 assert_eq!(block.frames(), 2);
77 assert_eq!(block.channels(), 2);
78 assert!((left[0] - 0.25).abs() < 1e-6);
79 assert!((right[0] - 0.5).abs() < 1e-6);
80 }
81
82 #[test]
83 fn test_wav_write_and_read_roundtrip() {
84 let sample_rate = 44100;
85 let freq = 440.0;
86 let num_samples = 4410;
87 let mut channel_l = Vec::with_capacity(num_samples);
88 let mut channel_r = Vec::with_capacity(num_samples);
89
90 for i in 0..num_samples {
91 let t = i as f32 / sample_rate as f32;
92 let sample = (2.0 * std::f32::consts::PI * freq * t).sin() * 0.75;
93 channel_l.push(sample);
94 channel_r.push(-sample);
95 }
96
97 let channels = vec![channel_l.clone(), channel_r.clone()];
98
99 let mut wav_bytes = Vec::new();
100 WavWriter::write_to_stream(
101 &mut wav_bytes,
102 &channels,
103 sample_rate,
104 WavSampleFormat::Pcm16Bit,
105 )
106 .expect("Failed to write WAV stream");
107
108 let mut cursor = Cursor::new(wav_bytes);
109 let (header, decoded_channels) =
110 WavReader::read_from_stream(&mut cursor).expect("Failed to read WAV stream");
111
112 assert_eq!(header.num_channels, 2);
113 assert_eq!(header.sample_rate, 44100);
114 assert_eq!(header.num_samples_per_channel, num_samples);
115 assert_eq!(decoded_channels.len(), 2);
116
117 for i in 0..num_samples {
118 assert!((decoded_channels[0][i] - channels[0][i]).abs() < 1e-4);
119 assert!((decoded_channels[1][i] - channels[1][i]).abs() < 1e-4);
120 }
121 }
122
123 #[test]
124 fn test_wav_stream_reader_and_writer_block_roundtrip() {
125 let sample_rate = 48000;
126 let num_channels = 2;
127 let total_frames = 1024;
128
129 let mut in_l = Vec::with_capacity(total_frames);
131 let mut in_r = Vec::with_capacity(total_frames);
132 for i in 0..total_frames {
133 let t = i as f32 / sample_rate as f32;
134 in_l.push((2.0 * std::f32::consts::PI * 300.0 * t).sin() * 0.8);
135 in_r.push((2.0 * std::f32::consts::PI * 600.0 * t).sin() * 0.5);
136 }
137
138 let mut cursor = Cursor::new(Vec::new());
140 let options = WavWriteOptions::new(WavSampleFormat::Pcm24Bit, DitherMode::None);
141 let mut stream_writer =
142 WavStreamWriter::new(&mut cursor, num_channels, sample_rate, options)
143 .expect("init stream writer");
144
145 let chunk_size = 256;
146 for start in (0..total_frames).step_by(chunk_size) {
147 let end = (start + chunk_size).min(total_frames);
148 let mut left_slice = in_l[start..end].to_vec();
149 let mut right_slice = in_r[start..end].to_vec();
150 let mut slices: Vec<&mut [f32]> = vec![&mut left_slice, &mut right_slice];
151 let block = AudioBlock::new(&mut slices).expect("block create");
152 stream_writer.write_block(&block).expect("write block");
153 }
154 let written_header = stream_writer.finalize().expect("finalize stream writer");
155 assert_eq!(written_header.num_samples_per_channel, total_frames);
156
157 cursor.set_position(0);
159 let mut stream_reader = WavStreamReader::new(cursor).expect("init stream reader");
160 assert_eq!(stream_reader.total_frames(), total_frames);
161 assert_eq!(stream_reader.header().format, WavSampleFormat::Pcm24Bit);
162
163 let read_chunk_size = 128;
164 let mut out_l = Vec::new();
165 let mut out_r = Vec::new();
166
167 let mut buf_l = vec![0.0_f32; read_chunk_size];
168 let mut buf_r = vec![0.0_f32; read_chunk_size];
169
170 while !stream_reader.is_finished() {
171 let mut slices: Vec<&mut [f32]> = vec![&mut buf_l, &mut buf_r];
172 let mut block = AudioBlock::new(&mut slices).expect("block create");
173 let frames_read = stream_reader.read_block(&mut block).expect("read block");
174 if frames_read == 0 {
175 break;
176 }
177 out_l.extend_from_slice(&buf_l[..frames_read]);
178 out_r.extend_from_slice(&buf_r[..frames_read]);
179 }
180
181 assert_eq!(out_l.len(), total_frames);
182 assert_eq!(out_r.len(), total_frames);
183 for i in 0..total_frames {
184 assert!((out_l[i] - in_l[i]).abs() < 1e-4);
185 assert!((out_r[i] - in_r[i]).abs() < 1e-4);
186 }
187 }
188
189 #[test]
190 fn test_limiter_processor_flush_tail() {
191 let sample_rate = 44100.0;
192 let spec = AudioSpec::new(sample_rate, 1).expect("valid spec");
193 let lookahead_ms = 2.0;
194
195 let mut limiter_proc = LimiterProcessor::new(-1.0, 20.0, lookahead_ms);
196 limiter_proc.prepare(spec).expect("limiter prepare");
197
198 let latency = limiter_proc.latency_samples();
199 assert!(latency > 0);
200
201 let mut impulse = vec![0.8_f32; 100];
203 let mut slices: Vec<&mut [f32]> = vec![&mut impulse];
204 let mut block = AudioBlock::new(&mut slices).expect("block");
205 limiter_proc.process(&mut block).expect("process");
206
207 let mut flush_buf = vec![0.0_f32; latency + 10];
209 let mut flush_slices: Vec<&mut [f32]> = vec![&mut flush_buf];
210 let mut flush_block = AudioBlock::new(&mut flush_slices).expect("flush block");
211 let flushed = limiter_proc.flush(&mut flush_block).expect("limiter flush");
212
213 assert_eq!(flushed, latency);
214 assert!((flush_block.channel(0).unwrap()[0] - 0.8).abs() < 0.2);
216
217 let flushed_again = limiter_proc.flush(&mut flush_block).expect("second flush");
219 assert_eq!(flushed_again, 0);
220 }
221
222 #[test]
223 fn test_engine_error_validation_and_channel_mismatch() {
224 assert!(AudioSpec::new(0.0, 2).is_err());
225 assert!(AudioSpec::new(44100.0, 0).is_err());
226
227 let mut ch1 = vec![0.0; 10];
228 let mut ch2 = vec![0.0; 12]; let mut channels = vec![ch1.as_mut_slice(), ch2.as_mut_slice()];
230 assert_eq!(
231 AudioBlock::new(&mut channels).err(),
232 Some(EngineError::UnequalChannelLengths)
233 );
234
235 let mut empty_channels: Vec<&mut [f32]> = Vec::new();
236 assert_eq!(
237 AudioBlock::new(&mut empty_channels).err(),
238 Some(EngineError::NoChannels)
239 );
240
241 let spec_stereo = AudioSpec::new(48000.0, 2).unwrap();
243 let mut gain_proc = GainProcessor::new(0.0);
244 gain_proc.prepare(spec_stereo).unwrap();
245
246 let mut mono_buf = vec![0.5; 8];
247 let mut mono_slices = vec![mono_buf.as_mut_slice()];
248 let mut mono_block = AudioBlock::new(&mut mono_slices).unwrap();
249 assert!(matches!(
250 gain_proc.process(&mut mono_block),
251 Err(EngineError::ChannelCountMismatch {
252 expected: 2,
253 actual: 1
254 })
255 ));
256 }
257
258 #[test]
259 fn test_equalizer_and_gain_processors_in_chain() {
260 let spec = AudioSpec::new(44100.0, 2).expect("spec");
261 let mut chain = ProcessorChain::new();
262 chain.push(GainProcessor::new(6.0206)); chain.push(EqualizerProcessor::new(
264 100.0, 0.0, 1000.0, 1.0, 0.0, 8000.0, 0.0,
265 ));
266 chain.prepare(spec).expect("prepare chain");
267
268 let mut left = vec![0.25; 100];
269 let mut right = vec![0.4; 100];
270 let mut slices: Vec<&mut [f32]> = vec![&mut left, &mut right];
271 let mut block = AudioBlock::new(&mut slices).expect("block");
272
273 chain.process(&mut block).expect("process chain");
274 assert!((left[50] - 0.5).abs() < 1e-3);
275 assert!((right[50] - 0.8).abs() < 1e-3);
276 }
277
278 #[test]
279 fn test_float_wav_lossless() {
280 let sample_rate = 48000;
281 let samples = vec![vec![0.0, 0.123456, -0.654321, 0.99999]];
282
283 let mut wav_bytes = Vec::new();
284 WavWriter::write_to_stream(
285 &mut wav_bytes,
286 &samples,
287 sample_rate,
288 WavSampleFormat::Pcm32BitFloat,
289 )
290 .expect("Failed to write 32-bit float WAV");
291
292 let mut cursor = Cursor::new(wav_bytes);
293 let (_, decoded) =
294 WavReader::read_from_stream(&mut cursor).expect("Failed to read 32-bit float WAV");
295
296 for i in 0..samples[0].len() {
297 assert_eq!(samples[0][i], decoded[0][i]);
298 }
299 }
300
301 #[test]
302 fn test_ring_buffer() {
303 let mut ring = RingBuffer::new(4);
304 ring.push(10.0);
305 ring.push(20.0);
306 ring.push(30.0);
307
308 assert_eq!(ring.read_past(0), 30.0);
309 assert_eq!(ring.read_past(1), 20.0);
310 assert_eq!(ring.read_past(2), 10.0);
311
312 ring.push(40.0);
313 ring.push(50.0);
314 assert_eq!(ring.read_past(0), 50.0);
315 assert_eq!(ring.read_past(1), 40.0);
316 assert_eq!(ring.read_past(2), 30.0);
317 assert_eq!(ring.read_past(3), 20.0);
318 }
319
320 #[test]
321 fn test_fractional_delay_interpolation() {
322 let mut delay = DelayLine::new(10);
323 delay.push(0.0);
324 delay.push(10.0);
325 delay.push(20.0);
326
327 let val_linear = delay.read_delay(1.5, InterpolationType::Linear);
330 assert!((val_linear - 5.0).abs() < 1e-4);
331 }
332
333 #[test]
334 fn test_feedback_delay_echo() {
335 let mut echo = FeedbackDelay::new(100, 2.0, 0.5, 1.0);
336 let mut impulse = vec![1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
337
338 echo.process_buffer(&mut impulse);
339
340 assert_eq!(impulse[0], 0.0);
341 assert_eq!(impulse[1], 0.0);
342 assert_eq!(impulse[2], 1.0);
343 assert_eq!(impulse[3], 0.0);
344 assert_eq!(impulse[4], 0.5);
345 }
346
347 #[test]
348 fn test_biquad_low_pass_attenuation() {
349 let sample_rate = 44100.0;
350 let lpf_coeffs =
351 BiquadCoefficients::low_pass(sample_rate, 500.0, std::f32::consts::FRAC_1_SQRT_2);
352 let mut filter = BiquadFilter::new(lpf_coeffs);
353
354 let high_freq = 10000.0;
355 let mut high_freq_signal: Vec<f32> = (0..500)
356 .map(|i| (2.0 * std::f32::consts::PI * high_freq * (i as f32) / sample_rate).sin())
357 .collect();
358
359 filter.process_buffer(&mut high_freq_signal);
360
361 let output_peak = Normalizer::find_peak(&high_freq_signal[100..]);
362 assert!(
363 output_peak < 0.1,
364 "High frequency must be attenuated by LPF"
365 );
366 }
367
368 #[test]
369 fn test_parametric_equalizer_boost() {
370 let sample_rate = 44100.0;
371 let mut eq = ParametricEqualizer3Band::new(sample_rate);
372 eq.set_mid_band(1000.0, 1.0, 6.0);
373
374 let mid_freq = 1000.0;
375 let mut signal: Vec<f32> = (0..1000)
376 .map(|i| (2.0 * std::f32::consts::PI * mid_freq * (i as f32) / sample_rate).sin() * 0.5)
377 .collect();
378
379 eq.process_buffer(&mut signal);
380
381 let output_peak = Normalizer::find_peak(&signal[200..]);
382 assert!(
383 (output_peak - 1.0).abs() < 0.15,
384 "Mid band should be boosted by ~6dB"
385 );
386 }
387
388 #[test]
389 fn test_dynamic_compressor_reduction() {
390 let sample_rate = 44100.0;
391 let config = CompressorConfig {
392 threshold_db: -12.0,
393 ratio: 4.0,
394 knee_db: 0.0,
395 attack_ms: 1.0,
396 release_ms: 50.0,
397 makeup_gain_db: 0.0,
398 mode: DetectionMode::Peak,
399 };
400 let mut comp = Compressor::new(sample_rate, config);
401
402 let mut hot_signal = vec![0.8_f32; 1000];
403 comp.process_buffer(&mut hot_signal);
404
405 let steady_output = hot_signal[500];
406 assert!(steady_output < 0.45 && steady_output > 0.3);
407 }
408
409 #[test]
410 fn test_brickwall_lookahead_limiter_ceiling() {
411 let sample_rate = 44100.0;
412 let ceiling_db = -1.0;
413 let mut limiter = BrickwallLimiter::new(sample_rate, ceiling_db, 20.0, 3.0);
414
415 let mut extreme_signal: Vec<f32> = (0..500)
416 .map(|i| (2.0 * std::f32::consts::PI * 200.0 * (i as f32) / sample_rate).sin() * 2.5)
417 .collect();
418
419 limiter.process_buffer(&mut extreme_signal);
420
421 let output_peak = Normalizer::find_peak(&extreme_signal);
422 let max_allowed = Gain::db_to_linear(ceiling_db) + 1e-4;
423 assert!(
424 output_peak <= max_allowed,
425 "Limiter ceiling breached: peak was {}",
426 output_peak
427 );
428 }
429
430 #[test]
431 fn test_fft_forward_and_inverse_roundtrip() {
432 let n = 256;
433 let original: Vec<Complex> = (0..n)
434 .map(|i| {
435 let t = i as f32 / n as f32;
436 Complex::new((2.0 * std::f32::consts::PI * 5.0 * t).sin(), 0.0)
437 })
438 .collect();
439
440 let mut buffer = original.clone();
441
442 Fft::forward(&mut buffer);
443 Fft::inverse(&mut buffer);
444
445 for i in 0..n {
446 assert!((buffer[i].re - original[i].re).abs() < 1e-5);
447 assert!(buffer[i].im.abs() < 1e-5);
448 }
449 }
450
451 #[test]
452 fn test_fft_dominant_frequency_detection() {
453 let sample_rate = 44100.0;
454 let fft_size = 1024;
455 let target_freq = 1720.0;
456
457 let mut buffer: Vec<Complex> = (0..fft_size)
458 .map(|i| {
459 let t = (i as f32) / sample_rate;
460 Complex::new((2.0 * std::f32::consts::PI * target_freq * t).sin(), 0.0)
461 })
462 .collect();
463
464 let mut time_domain: Vec<f32> = buffer.iter().map(|c| c.re).collect();
465 Window::apply(&mut time_domain, WindowType::Hann);
466 for (c, &w) in buffer.iter_mut().zip(time_domain.iter()) {
467 c.re = w;
468 }
469
470 Fft::forward(&mut buffer);
471
472 let (detected_freq, _) = Fft::find_dominant_frequency(&buffer, sample_rate);
473 let freq_resolution = sample_rate / (fft_size as f32);
474
475 assert!(
476 (detected_freq - target_freq).abs() <= freq_resolution,
477 "Detected freq: {}, Target freq: {}",
478 detected_freq,
479 target_freq
480 );
481 }
482
483 #[test]
484 fn test_fft_helpers_accept_empty_input() {
485 let empty: Vec<Complex> = Vec::new();
486 assert!(Fft::compute_magnitude_spectrum(&empty).is_empty());
487 assert_eq!(Fft::find_dominant_frequency(&empty, 44100.0), (0.0, 0.0));
488 }
489
490 #[test]
491 fn test_fft_magnitude_spectrum_has_nyquist_bin() {
492 let mut buffer = vec![Complex::new(0.0, 0.0); 8];
493 Fft::forward(&mut buffer);
494 assert_eq!(Fft::compute_magnitude_spectrum(&buffer).len(), 5);
496 }
497
498 #[test]
499 #[should_panic(expected = "FFT size must be a power of two")]
500 fn test_fft_forward_rejects_non_power_of_two() {
501 let mut buffer = vec![Complex::new(0.0, 0.0); 100];
502 Fft::forward(&mut buffer);
503 }
504
505 #[test]
506 fn test_full_mastering_chain_pipeline() {
507 let sample_rate = 44100.0;
508 let num_samples = 4410;
509
510 let mut audio_channel: Vec<f32> = (0..num_samples)
511 .map(|i| {
512 let t = i as f32 / sample_rate;
513 let f1 = (2.0 * std::f32::consts::PI * 100.0 * t).sin() * 0.4;
514 let f2 = (2.0 * std::f32::consts::PI * 1000.0 * t).sin() * 0.4;
515 let f3 = (2.0 * std::f32::consts::PI * 8000.0 * t).sin() * 0.4;
516 f1 + f2 + f3
517 })
518 .collect();
519
520 let mut eq = ParametricEqualizer3Band::new(sample_rate);
521 eq.set_low_band(150.0, 2.0);
522 eq.set_high_band(6000.0, -3.0);
523 eq.process_buffer(&mut audio_channel);
524
525 let comp_config = CompressorConfig {
526 threshold_db: -10.0,
527 ratio: 3.0,
528 knee_db: 4.0,
529 attack_ms: 5.0,
530 release_ms: 80.0,
531 makeup_gain_db: 2.0,
532 mode: DetectionMode::Rms,
533 };
534 let mut comp = Compressor::new(sample_rate, comp_config);
535 comp.process_buffer(&mut audio_channel);
536
537 let mut limiter = BrickwallLimiter::new(sample_rate, -0.5, 30.0, 2.0);
538 limiter.process_buffer(&mut audio_channel);
539
540 let final_peak = Normalizer::find_peak(&audio_channel);
541 let max_ceiling = Gain::db_to_linear(-0.5) + 1e-4;
542 assert!(final_peak <= max_ceiling);
543
544 let stereo_channels = vec![audio_channel.clone(), audio_channel];
545 let mut wav_bytes = Vec::new();
546 WavWriter::write_to_stream(
547 &mut wav_bytes,
548 &stereo_channels,
549 sample_rate as u32,
550 WavSampleFormat::Pcm24Bit,
551 )
552 .expect("Failed to write mastering output to WAV");
553
554 let mut cursor = Cursor::new(wav_bytes);
555 let (header, decoded_channels) =
556 WavReader::read_from_stream(&mut cursor).expect("Failed to read mastered WAV");
557
558 assert_eq!(header.num_channels, 2);
559 assert_eq!(header.format, WavSampleFormat::Pcm24Bit);
560 assert_eq!(decoded_channels[0].len(), num_samples);
561 }
562}