Skip to main content

rescale_core/
lib.rs

1//! Core DSP Engine & ReScaler in Pure Rust (Zero Dependency)
2
3pub 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        // Siapkan buffer input stereo
130        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        // 1. Tulis via WavStreamWriter per block 256 frame
139        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        // 2. Baca via WavStreamReader per block 128 frame
158        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        // Sinyal pendek dengan impulse di awal
202        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        // Flush tail latency
208        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        // Sisa delay output harus bernilai ~0.8 bukan 0
215        assert!((flush_block.channel(0).unwrap()[0] - 0.8).abs() < 0.2);
216
217        // Flush kedua harus mengembalikan 0 frame
218        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]; // panjang berbeda
229        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        // Test channel count mismatch pada processor
242        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)); // ~2x linear gain (+6dB)
263        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        // Semantik read_delay: relatif terhadap sampel terakhir yang di-push.
328        // delay 1.5 = interpolasi x[n-1] dan x[n-2] = (10 + 0) / 2.
329        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        // DC sampai Nyquist inklusif: len / 2 + 1 bin
495        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}