1#[derive(Debug)]
5pub struct AudioBuffers {
6 pub inputs: Vec<Vec<f32>>,
8 pub outputs: Vec<Vec<f32>>,
10 pub sample_rate: f64,
12 pub block_size: usize,
14}
15
16impl AudioBuffers {
17 pub fn new(
19 input_channels: usize,
20 output_channels: usize,
21 block_size: usize,
22 sample_rate: f64,
23 ) -> Self {
24 let inputs = vec![vec![0.0; block_size]; input_channels];
25 let outputs = vec![vec![0.0; block_size]; output_channels];
26
27 Self {
28 inputs,
29 outputs,
30 sample_rate,
31 block_size,
32 }
33 }
34
35 pub fn clear(&mut self) {
37 for buffer in &mut self.inputs {
38 buffer.fill(0.0);
39 }
40 for buffer in &mut self.outputs {
41 buffer.fill(0.0);
42 }
43 }
44
45 pub fn input_channels(&self) -> usize {
47 self.inputs.len()
48 }
49
50 pub fn output_channels(&self) -> usize {
52 self.outputs.len()
53 }
54}
55
56#[derive(Debug, Clone, Copy)]
58pub struct ChannelLevel {
59 pub peak: f32,
61 pub rms: f32,
63 pub peak_hold: f32,
65}
66
67impl Default for ChannelLevel {
68 fn default() -> Self {
69 Self {
70 peak: 0.0,
71 rms: 0.0,
72 peak_hold: 0.0,
73 }
74 }
75}
76
77impl ChannelLevel {
78 pub fn peak_db(&self) -> f32 {
80 if self.peak <= 0.0 {
81 -f32::INFINITY
82 } else {
83 20.0 * self.peak.log10()
84 }
85 }
86
87 pub fn rms_db(&self) -> f32 {
89 if self.rms <= 0.0 {
90 -f32::INFINITY
91 } else {
92 20.0 * self.rms.log10()
93 }
94 }
95
96 pub fn is_clipping(&self) -> bool {
98 self.peak > 1.0
99 }
100}
101
102#[derive(Debug, Clone)]
104pub struct AudioLevels {
105 pub channels: Vec<ChannelLevel>,
107}
108
109impl AudioLevels {
110 pub fn new(channel_count: usize) -> Self {
112 Self {
113 channels: vec![ChannelLevel::default(); channel_count],
114 }
115 }
116
117 pub fn update_from_buffers(&mut self, buffers: &[Vec<f32>]) {
119 for (i, buffer) in buffers.iter().enumerate() {
120 if i >= self.channels.len() {
121 break;
122 }
123
124 let peak = buffer.iter().map(|&x| x.abs()).fold(0.0f32, f32::max);
126
127 let sum_squares: f32 = buffer.iter().map(|&x| x * x).sum();
129 let rms = if buffer.is_empty() {
130 0.0
131 } else {
132 (sum_squares / buffer.len() as f32).sqrt()
133 };
134
135 let channel = &mut self.channels[i];
137 channel.peak = peak;
138 channel.rms = rms;
139
140 if peak > channel.peak_hold {
142 channel.peak_hold = peak;
143 }
144 }
145 }
146
147 pub fn reset_peak_hold(&mut self) {
149 for channel in &mut self.channels {
150 channel.peak_hold = channel.peak;
151 }
152 }
153
154 pub fn is_clipping(&self) -> bool {
156 self.channels.iter().any(|ch| ch.is_clipping())
157 }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
164pub struct SpeakerArrangement(pub u64);
165
166impl SpeakerArrangement {
167 pub const EMPTY: Self = Self(0);
169 pub const MONO: Self = Self(0x0008_0000);
171 pub const STEREO: Self = Self(0x3);
173 pub const STEREO_SURROUND: Self = Self(0x30);
175
176 pub fn from_raw(bits: u64) -> Self {
178 Self(bits)
179 }
180
181 pub fn raw(self) -> u64 {
183 self.0
184 }
185
186 pub fn channel_count(self) -> usize {
188 self.0.count_ones() as usize
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
194pub struct BusArrangements {
195 pub inputs: Vec<SpeakerArrangement>,
197 pub outputs: Vec<SpeakerArrangement>,
199}
200
201#[derive(Debug, Clone)]
223pub struct PeakMeter {
224 fall_db_per_sec: f32,
225 hold: std::time::Duration,
226 level: f32,
227 peak_hold: f32,
228 peak_hold_at: Option<std::time::Instant>,
229 last: Option<std::time::Instant>,
230}
231
232impl PeakMeter {
233 const SILENCE: f32 = 1e-5;
236
237 pub fn new(fall_db_per_sec: f32, hold: std::time::Duration) -> Self {
241 Self {
242 fall_db_per_sec: fall_db_per_sec.max(0.0),
243 hold,
244 level: 0.0,
245 peak_hold: 0.0,
246 peak_hold_at: None,
247 last: None,
248 }
249 }
250
251 fn decay(&self, dt: std::time::Duration) -> f32 {
253 let db = self.fall_db_per_sec * dt.as_secs_f32();
254 10f32.powf(-db / 20.0)
255 }
256
257 pub fn push(&mut self, block_peak: f32, now: std::time::Instant) {
261 let block_peak = if block_peak.is_finite() {
264 block_peak.max(0.0)
265 } else {
266 0.0
267 };
268 let decay = match self.last {
269 Some(prev) => self.decay(now.saturating_duration_since(prev)),
270 None => 1.0,
271 };
272
273 self.level = (self.level * decay).max(block_peak);
274 if self.level < Self::SILENCE {
275 self.level = 0.0;
276 }
277
278 if block_peak >= self.peak_hold {
279 self.peak_hold = block_peak;
281 self.peak_hold_at = Some(now);
282 } else if self
283 .peak_hold_at
284 .is_some_and(|at| now.saturating_duration_since(at) > self.hold)
285 {
286 self.peak_hold = (self.peak_hold * decay).max(self.level);
288 if self.peak_hold < Self::SILENCE {
289 self.peak_hold = 0.0;
290 }
291 }
292
293 self.last = Some(now);
294 }
295
296 pub fn level(&self) -> f32 {
298 self.level
299 }
300
301 pub fn peak_hold(&self) -> f32 {
303 self.peak_hold
304 }
305
306 pub fn reset(&mut self) {
308 self.level = 0.0;
309 self.peak_hold = 0.0;
310 self.peak_hold_at = None;
311 self.last = None;
312 }
313}
314
315#[derive(Debug, Clone)]
329pub struct RmsWindow {
330 capacity: usize,
331 squares: std::collections::VecDeque<f32>,
332 sum: f64,
335}
336
337impl RmsWindow {
338 pub fn new(window_samples: usize) -> Self {
340 let capacity = window_samples.max(1);
341 Self {
342 capacity,
343 squares: std::collections::VecDeque::with_capacity(capacity),
344 sum: 0.0,
345 }
346 }
347
348 pub fn from_duration(window_secs: f32, sample_rate: f64) -> Self {
350 Self::new((window_secs.max(0.0) as f64 * sample_rate).round() as usize)
351 }
352
353 pub fn push_sample(&mut self, sample: f32) {
355 let sq = sample * sample;
356 if self.squares.len() == self.capacity {
357 if let Some(old) = self.squares.pop_front() {
358 self.sum -= old as f64;
359 }
360 }
361 self.squares.push_back(sq);
362 self.sum += sq as f64;
363 }
364
365 pub fn push_block(&mut self, block: &[f32]) {
367 for &s in block {
368 self.push_sample(s);
369 }
370 }
371
372 pub fn rms(&self) -> f32 {
374 if self.squares.is_empty() {
375 return 0.0;
376 }
377 (self.sum.max(0.0) / self.squares.len() as f64).sqrt() as f32
379 }
380
381 pub fn len(&self) -> usize {
383 self.squares.len()
384 }
385
386 pub fn is_empty(&self) -> bool {
388 self.squares.is_empty()
389 }
390
391 pub fn clear(&mut self) {
393 self.squares.clear();
394 self.sum = 0.0;
395 }
396}
397
398#[derive(Debug, Clone, Copy)]
400pub struct AudioConfig {
401 pub sample_rate: f64,
403 pub block_size: usize,
405 pub input_channels: usize,
407 pub output_channels: usize,
409 pub tempo: f64,
412 pub time_sig_numerator: i32,
414 pub time_sig_denominator: i32,
417}
418
419impl Default for AudioConfig {
420 fn default() -> Self {
421 Self {
422 sample_rate: 44100.0,
423 block_size: 512,
424 input_channels: 0,
425 output_channels: 2,
426 tempo: 120.0,
427 time_sig_numerator: 4,
428 time_sig_denominator: 4,
429 }
430 }
431}
432
433pub trait AudioStream: Send {
435 fn play(&self) -> Result<(), Box<dyn std::error::Error>>;
437
438 fn pause(&self) -> Result<(), Box<dyn std::error::Error>>;
440}
441
442#[allow(clippy::type_complexity)] pub trait AudioBackend: Send + Sync {
445 type Stream: AudioStream + Send + 'static;
447 type Device: Send + Sync;
449 type Error: std::error::Error + Send + Sync + 'static;
451
452 fn enumerate_output_devices(&self) -> Result<Vec<Self::Device>, Self::Error>;
454
455 fn enumerate_input_devices(&self) -> Result<Vec<Self::Device>, Self::Error>;
457
458 fn default_output_device(&self) -> Option<Self::Device>;
460
461 fn default_input_device(&self) -> Option<Self::Device>;
463
464 fn create_output_stream(
466 &self,
467 device: &Self::Device,
468 config: AudioConfig,
469 data_callback: Box<dyn FnMut(&mut [f32]) + Send>,
470 error_callback: Box<dyn FnMut(Self::Error) + Send>,
471 ) -> Result<Self::Stream, Self::Error>;
472
473 fn create_input_stream(
475 &self,
476 device: &Self::Device,
477 config: AudioConfig,
478 data_callback: Box<dyn FnMut(&[f32]) + Send>,
479 error_callback: Box<dyn FnMut(Self::Error) + Send>,
480 ) -> Result<Self::Stream, Self::Error>;
481}
482
483pub fn write_wav<P: AsRef<std::path::Path>>(
488 path: P,
489 channels: &[Vec<f32>],
490 sample_rate: u32,
491) -> crate::error::Result<()> {
492 use crate::error::Error;
493 use std::io::Write;
494
495 let num_channels = channels.len().max(1) as u16;
496 let frames = channels.iter().map(|c| c.len()).min().unwrap_or(0);
497 let bits_per_sample: u16 = 32;
498 let block_align = num_channels * (bits_per_sample / 8);
499 let byte_rate = sample_rate * block_align as u32;
500 let data_size = (frames * num_channels as usize * (bits_per_sample / 8) as usize) as u32;
501
502 let mut buf: Vec<u8> = Vec::with_capacity(44 + data_size as usize);
503 buf.extend_from_slice(b"RIFF");
504 buf.extend_from_slice(&(36 + data_size).to_le_bytes());
505 buf.extend_from_slice(b"WAVE");
506 buf.extend_from_slice(b"fmt ");
507 buf.extend_from_slice(&16u32.to_le_bytes());
508 buf.extend_from_slice(&3u16.to_le_bytes()); buf.extend_from_slice(&num_channels.to_le_bytes());
510 buf.extend_from_slice(&sample_rate.to_le_bytes());
511 buf.extend_from_slice(&byte_rate.to_le_bytes());
512 buf.extend_from_slice(&block_align.to_le_bytes());
513 buf.extend_from_slice(&bits_per_sample.to_le_bytes());
514 buf.extend_from_slice(b"data");
515 buf.extend_from_slice(&data_size.to_le_bytes());
516 for f in 0..frames {
518 for ch in channels {
519 buf.extend_from_slice(&ch[f].to_le_bytes());
520 }
521 }
522
523 let mut file =
524 std::fs::File::create(path).map_err(|e| Error::Other(format!("create wav: {e}")))?;
525 file.write_all(&buf)
526 .map_err(|e| Error::Other(format!("write wav: {e}")))?;
527 Ok(())
528}
529
530pub fn read_wav<P: AsRef<std::path::Path>>(path: P) -> crate::error::Result<(Vec<Vec<f32>>, u32)> {
534 use crate::error::Error;
535 let data = std::fs::read(path).map_err(|e| Error::Other(format!("read wav: {e}")))?;
536 let err = |m: &str| Error::Other(format!("invalid wav: {m}"));
537 if data.len() < 44 || &data[0..4] != b"RIFF" || &data[8..12] != b"WAVE" {
538 return Err(err("not a RIFF/WAVE file"));
539 }
540 let (mut fmt_tag, mut channels, mut sample_rate, mut bits) = (0u16, 0u16, 0u32, 0u16);
542 let mut data_range: Option<(usize, usize)> = None;
543 let mut pos = 12;
544 while pos + 8 <= data.len() {
545 let id = &data[pos..pos + 4];
546 let size = u32::from_le_bytes([data[pos + 4], data[pos + 5], data[pos + 6], data[pos + 7]])
547 as usize;
548 let body = pos + 8;
549 if id == b"fmt " && body + 16 <= data.len() {
550 fmt_tag = u16::from_le_bytes([data[body], data[body + 1]]);
551 channels = u16::from_le_bytes([data[body + 2], data[body + 3]]);
552 sample_rate = u32::from_le_bytes([
553 data[body + 4],
554 data[body + 5],
555 data[body + 6],
556 data[body + 7],
557 ]);
558 bits = u16::from_le_bytes([data[body + 14], data[body + 15]]);
559 } else if id == b"data" {
560 data_range = Some((body, (body + size).min(data.len())));
561 }
562 pos = body + size + (size & 1); }
564 let (ds, de) = data_range.ok_or_else(|| err("no data chunk"))?;
565 if channels == 0 {
566 return Err(err("zero channels"));
567 }
568 let nch = channels as usize;
569 let mut out: Vec<Vec<f32>> = vec![Vec::new(); nch];
570 let bytes = &data[ds..de];
571 match (fmt_tag, bits) {
572 (3, 32) => {
573 for (i, frame) in bytes.chunks_exact(4 * nch).enumerate() {
574 let _ = i;
575 for (ch, s) in frame.chunks_exact(4).enumerate() {
576 out[ch].push(f32::from_le_bytes([s[0], s[1], s[2], s[3]]));
577 }
578 }
579 }
580 (1, 16) => {
581 for frame in bytes.chunks_exact(2 * nch) {
582 for (ch, s) in frame.chunks_exact(2).enumerate() {
583 let v = i16::from_le_bytes([s[0], s[1]]) as f32 / 32768.0;
584 out[ch].push(v);
585 }
586 }
587 }
588 _ => return Err(err("unsupported format (need 32-bit float or 16-bit PCM)")),
589 }
590 Ok((out, sample_rate))
591}
592
593pub trait InputSource: Send {
596 fn fill(&mut self, inputs: &mut [Vec<f32>], frames: usize, sample_rate: f64);
598}
599
600#[derive(Debug, Clone)]
603pub enum SignalSource {
604 Silence,
606 Sine {
608 freq: f32,
610 amplitude: f32,
612 phase: f64,
614 },
615 WhiteNoise {
617 amplitude: f32,
619 rng: u64,
621 },
622 Wav {
624 samples: std::sync::Arc<Vec<Vec<f32>>>,
626 pos: usize,
628 looping: bool,
630 },
631}
632
633impl SignalSource {
634 pub fn sine(freq: f32, amplitude: f32) -> Self {
636 SignalSource::Sine {
637 freq,
638 amplitude,
639 phase: 0.0,
640 }
641 }
642 pub fn white_noise(amplitude: f32) -> Self {
644 SignalSource::WhiteNoise {
645 amplitude,
646 rng: 0x9E37_79B9_7F4A_7C15,
647 }
648 }
649 pub fn wav(samples: Vec<Vec<f32>>, looping: bool) -> Self {
651 SignalSource::Wav {
652 samples: std::sync::Arc::new(samples),
653 pos: 0,
654 looping,
655 }
656 }
657}
658
659impl InputSource for SignalSource {
660 fn fill(&mut self, inputs: &mut [Vec<f32>], frames: usize, sample_rate: f64) {
661 for ch in inputs.iter_mut() {
662 if ch.len() < frames {
663 ch.resize(frames, 0.0);
664 }
665 }
666 match self {
667 SignalSource::Silence => {
668 for ch in inputs.iter_mut() {
669 for s in &mut ch[..frames] {
670 *s = 0.0;
671 }
672 }
673 }
674 SignalSource::Sine {
675 freq,
676 amplitude,
677 phase,
678 } => {
679 let step = std::f64::consts::TAU * *freq as f64 / sample_rate.max(1.0);
680 for f in 0..frames {
681 let v = (phase.sin() as f32) * *amplitude;
682 for ch in inputs.iter_mut() {
683 ch[f] = v;
684 }
685 *phase = (*phase + step) % std::f64::consts::TAU;
686 }
687 }
688 SignalSource::WhiteNoise { amplitude, rng } => {
689 for f in 0..frames {
690 let mut x = *rng;
692 x ^= x << 13;
693 x ^= x >> 7;
694 x ^= x << 17;
695 *rng = x;
696 let unit = ((x >> 11) as f64 / (1u64 << 53) as f64) as f32 * 2.0 - 1.0;
698 let v = unit * *amplitude;
699 for ch in inputs.iter_mut() {
700 ch[f] = v;
701 }
702 }
703 }
704 SignalSource::Wav {
705 samples,
706 pos,
707 looping,
708 } => {
709 let total = samples.iter().map(|c| c.len()).max().unwrap_or(0);
710 for f in 0..frames {
711 let p = *pos + f;
712 let src_idx = if total == 0 {
713 None
714 } else if p < total {
715 Some(p)
716 } else if *looping {
717 Some(p % total)
718 } else {
719 None
720 };
721 for (ci, ch) in inputs.iter_mut().enumerate() {
722 ch[f] = match src_idx {
723 Some(i) => samples
724 .get(ci % samples.len().max(1))
725 .and_then(|c| c.get(i))
726 .copied()
727 .unwrap_or(0.0),
728 None => 0.0,
729 };
730 }
731 }
732 *pos += frames;
733 }
734 }
735 }
736}
737
738#[cfg(test)]
739mod wav_tests {
740 use super::*;
741
742 #[test]
743 fn write_wav_has_correct_header_and_size() {
744 let ch = vec![vec![0.0f32, 0.5, -0.5, 1.0], vec![0.1, 0.2, 0.3, 0.4]];
745 let path = std::env::temp_dir().join("vh_write_wav_test.wav");
746 write_wav(&path, &ch, 48_000).unwrap();
747 let bytes = std::fs::read(&path).unwrap();
748 let _ = std::fs::remove_file(&path);
749
750 assert_eq!(&bytes[0..4], b"RIFF");
751 assert_eq!(&bytes[8..12], b"WAVE");
752 assert_eq!(u16::from_le_bytes([bytes[20], bytes[21]]), 3); assert_eq!(u16::from_le_bytes([bytes[22], bytes[23]]), 2); assert_eq!(
755 u32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]),
756 48_000
757 );
758 assert_eq!(bytes.len(), 44 + 32);
760 }
761
762 #[test]
763 fn write_then_read_wav_round_trips() {
764 let ch = vec![vec![0.0f32, 0.5, -0.5, 1.0], vec![0.1, 0.2, 0.3, 0.4]];
765 let path = std::env::temp_dir().join(format!("vh_rw_{}.wav", std::process::id()));
766 write_wav(&path, &ch, 44_100).unwrap();
767 let (back, sr) = read_wav(&path).unwrap();
768 let _ = std::fs::remove_file(&path);
769 assert_eq!(sr, 44_100);
770 assert_eq!(back.len(), 2);
771 for (a, b) in ch.iter().zip(back.iter()) {
772 for (x, y) in a.iter().zip(b.iter()) {
773 assert!((x - y).abs() < 1e-6, "{x} vs {y}");
774 }
775 }
776 }
777}
778
779#[cfg(test)]
780mod signal_tests {
781 use super::*;
782
783 #[test]
784 fn sine_starts_at_zero_and_stays_in_amplitude() {
785 let mut src = SignalSource::sine(1000.0, 0.5);
786 let mut inputs = vec![vec![0.0f32; 256], vec![0.0f32; 256]];
787 src.fill(&mut inputs, 256, 48_000.0);
788 assert!(inputs[0][0].abs() < 1e-6, "sine should start at phase 0");
789 for ch in &inputs {
790 assert!(
791 ch.iter().all(|s| s.abs() <= 0.5 + 1e-6),
792 "exceeds amplitude"
793 );
794 }
795 assert_eq!(inputs[0], inputs[1]);
797 assert!(inputs[0].iter().any(|s| s.abs() > 0.1));
799 }
800
801 #[test]
802 fn noise_is_bounded_and_varied() {
803 let mut src = SignalSource::white_noise(0.25);
804 let mut inputs = vec![vec![0.0f32; 512]];
805 src.fill(&mut inputs, 512, 48_000.0);
806 assert!(inputs[0].iter().all(|s| s.abs() <= 0.25 + 1e-6));
807 let first = inputs[0][0];
808 assert!(inputs[0].iter().any(|&s| s != first), "noise should vary");
809 }
810
811 #[test]
812 fn wav_source_advances_and_zero_pads() {
813 let mut src = SignalSource::wav(vec![vec![1.0, 2.0, 3.0]], false);
814 let mut inputs = vec![vec![0.0f32; 5]];
815 src.fill(&mut inputs, 5, 48_000.0);
816 assert_eq!(inputs[0], vec![1.0, 2.0, 3.0, 0.0, 0.0]); }
818
819 #[test]
820 fn wav_source_loops() {
821 let mut src = SignalSource::wav(vec![vec![1.0, 2.0]], true);
822 let mut inputs = vec![vec![0.0f32; 5]];
823 src.fill(&mut inputs, 5, 48_000.0);
824 assert_eq!(inputs[0], vec![1.0, 2.0, 1.0, 2.0, 1.0]); }
826}
827
828#[cfg(test)]
829mod speaker_arrangement_tests {
830 use super::*;
831
832 #[test]
833 fn channel_counts_match_bitmask() {
834 assert_eq!(SpeakerArrangement::EMPTY.channel_count(), 0);
835 assert_eq!(SpeakerArrangement::MONO.channel_count(), 1);
836 assert_eq!(SpeakerArrangement::STEREO.channel_count(), 2);
837 assert_eq!(SpeakerArrangement::STEREO_SURROUND.channel_count(), 2);
838 }
839
840 #[test]
841 fn raw_round_trips() {
842 let bits = SpeakerArrangement::STEREO.raw();
843 assert_eq!(bits, 0x3);
844 assert_eq!(
845 SpeakerArrangement::from_raw(bits),
846 SpeakerArrangement::STEREO
847 );
848 assert_eq!(SpeakerArrangement::from_raw(0b111111).channel_count(), 6);
850 }
851}
852
853#[cfg(test)]
854mod meter_tests {
855 use super::*;
856 use std::time::{Duration, Instant};
857
858 #[test]
859 fn peak_meter_rises_instantly_and_holds() {
860 let mut m = PeakMeter::new(20.0, Duration::from_secs(2));
861 let t0 = Instant::now();
862 m.push(0.7, t0);
863 assert_eq!(m.level(), 0.7);
864 assert_eq!(m.peak_hold(), 0.7);
865
866 m.push(0.9, t0 + Duration::from_millis(10));
868 assert_eq!(m.level(), 0.9);
869 assert_eq!(m.peak_hold(), 0.9);
870 }
871
872 #[test]
873 fn peak_meter_level_falls_but_hold_latches() {
874 let mut m = PeakMeter::new(20.0, Duration::from_secs(3));
875 let t0 = Instant::now();
876 m.push(1.0, t0);
877
878 m.push(0.0, t0 + Duration::from_millis(500));
880 let lvl = m.level();
881 assert!(
882 lvl < 1.0 && lvl > 0.0,
883 "level should be mid-fall, got {lvl}"
884 );
885 assert!((lvl - 0.316).abs() < 0.02, "≈-10 dB expected, got {lvl}");
886 assert_eq!(m.peak_hold(), 1.0, "hold must latch within its window");
887 }
888
889 #[test]
890 fn peak_meter_hold_falls_after_window() {
891 let mut m = PeakMeter::new(20.0, Duration::from_secs(1));
892 let t0 = Instant::now();
893 m.push(1.0, t0);
894 m.push(0.0, t0 + Duration::from_millis(1500));
896 assert!(
897 m.peak_hold() < 1.0,
898 "hold should fall after the window expired, got {}",
899 m.peak_hold()
900 );
901 }
902
903 #[test]
904 fn peak_meter_reaches_silence_floor() {
905 let mut m = PeakMeter::new(60.0, Duration::from_millis(0));
906 let t0 = Instant::now();
907 m.push(0.5, t0);
908 m.push(0.0, t0 + Duration::from_secs(10));
910 assert_eq!(m.level(), 0.0);
911 assert_eq!(m.peak_hold(), 0.0);
912 }
913
914 #[test]
915 fn rms_window_constant_signal() {
916 let mut r = RmsWindow::new(8);
917 for _ in 0..8 {
918 r.push_sample(0.5);
919 }
920 assert!((r.rms() - 0.5).abs() < 1e-6);
921 assert_eq!(r.len(), 8);
922 }
923
924 #[test]
925 fn rms_window_slides_and_evicts() {
926 let mut r = RmsWindow::new(3);
927 r.push_block(&[1.0, 1.0, 1.0]);
928 assert!((r.rms() - 1.0).abs() < 1e-6);
929 r.push_block(&[0.0, 0.0, 0.0]);
931 assert_eq!(r.len(), 3);
932 assert!(
933 r.rms() < 1e-6,
934 "window should have slid to silence, got {}",
935 r.rms()
936 );
937 }
938
939 #[test]
940 fn rms_window_empty_is_zero() {
941 let r = RmsWindow::new(16);
942 assert!(r.is_empty());
943 assert_eq!(r.rms(), 0.0);
944 }
945
946 #[test]
947 fn rms_window_from_duration_sizes_correctly() {
948 let r = RmsWindow::from_duration(0.01, 48_000.0);
950 assert_eq!(r.capacity, 480);
951 }
952}