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, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
58pub struct AudioBusConfig {
59 pub channel_count: usize,
61 pub active: bool,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
67pub struct AudioBusLayout {
68 pub inputs: Vec<AudioBusConfig>,
70 pub outputs: Vec<AudioBusConfig>,
72}
73
74#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
76pub struct AudioBusBuffer {
77 pub active: bool,
82 #[serde(with = "crate::process_isolation::audio_codec")]
85 pub channels: Vec<Vec<f32>>,
86}
87
88impl AudioBusBuffer {
89 pub fn new(channel_count: usize, block_size: usize, active: bool) -> Self {
91 Self {
92 active,
93 channels: vec![vec![0.0; block_size]; channel_count],
94 }
95 }
96}
97
98#[derive(Debug, Clone, PartialEq)]
100pub struct BusAudioBuffers {
101 pub inputs: Vec<AudioBusBuffer>,
103 pub outputs: Vec<AudioBusBuffer>,
105 pub sample_rate: f64,
107 pub block_size: usize,
109}
110
111impl BusAudioBuffers {
112 pub fn new(layout: &AudioBusLayout, block_size: usize, sample_rate: f64) -> Self {
114 let make = |config: &AudioBusConfig| {
115 AudioBusBuffer::new(config.channel_count, block_size, config.active)
116 };
117 Self {
118 inputs: layout.inputs.iter().map(make).collect(),
119 outputs: layout.outputs.iter().map(make).collect(),
120 sample_rate,
121 block_size,
122 }
123 }
124
125 pub fn clear(&mut self) {
127 for bus in self.inputs.iter_mut().chain(&mut self.outputs) {
128 for channel in &mut bus.channels {
129 channel.fill(0.0);
130 }
131 }
132 }
133}
134
135#[derive(Debug, Clone, Copy)]
137pub struct ChannelLevel {
138 pub peak: f32,
140 pub rms: f32,
142 pub peak_hold: f32,
144}
145
146impl Default for ChannelLevel {
147 fn default() -> Self {
148 Self {
149 peak: 0.0,
150 rms: 0.0,
151 peak_hold: 0.0,
152 }
153 }
154}
155
156impl ChannelLevel {
157 pub fn peak_db(&self) -> f32 {
159 if self.peak <= 0.0 {
160 -f32::INFINITY
161 } else {
162 20.0 * self.peak.log10()
163 }
164 }
165
166 pub fn rms_db(&self) -> f32 {
168 if self.rms <= 0.0 {
169 -f32::INFINITY
170 } else {
171 20.0 * self.rms.log10()
172 }
173 }
174
175 pub fn is_clipping(&self) -> bool {
177 self.peak > 1.0
178 }
179}
180
181#[derive(Debug, Clone)]
183pub struct AudioLevels {
184 pub channels: Vec<ChannelLevel>,
186}
187
188impl AudioLevels {
189 pub fn new(channel_count: usize) -> Self {
191 Self {
192 channels: vec![ChannelLevel::default(); channel_count],
193 }
194 }
195
196 pub fn update_from_buffers(&mut self, buffers: &[Vec<f32>]) {
198 for (i, buffer) in buffers.iter().enumerate() {
199 if i >= self.channels.len() {
200 break;
201 }
202
203 let peak = buffer.iter().map(|&x| x.abs()).fold(0.0f32, f32::max);
205
206 let sum_squares: f32 = buffer.iter().map(|&x| x * x).sum();
208 let rms = if buffer.is_empty() {
209 0.0
210 } else {
211 (sum_squares / buffer.len() as f32).sqrt()
212 };
213
214 let channel = &mut self.channels[i];
216 channel.peak = peak;
217 channel.rms = rms;
218
219 if peak > channel.peak_hold {
221 channel.peak_hold = peak;
222 }
223 }
224 }
225
226 pub fn update_from_bus_buffers(&mut self, buses: &[AudioBusBuffer]) {
228 let mut level_index = 0usize;
229 for channel in buses
230 .iter()
231 .filter(|bus| bus.active)
232 .flat_map(|bus| &bus.channels)
233 {
234 let Some(level) = self.channels.get_mut(level_index) else {
235 break;
236 };
237 let peak = channel
238 .iter()
239 .map(|sample| sample.abs())
240 .fold(0.0, f32::max);
241 let sum_squares: f32 = channel.iter().map(|sample| sample * sample).sum();
242 level.peak = peak;
243 level.rms = if channel.is_empty() {
244 0.0
245 } else {
246 (sum_squares / channel.len() as f32).sqrt()
247 };
248 level.peak_hold = level.peak_hold.max(peak);
249 level_index += 1;
250 }
251 for level in self.channels.iter_mut().skip(level_index) {
252 level.peak = 0.0;
253 level.rms = 0.0;
254 }
255 }
256
257 pub fn reset_peak_hold(&mut self) {
259 for channel in &mut self.channels {
260 channel.peak_hold = channel.peak;
261 }
262 }
263
264 pub fn is_clipping(&self) -> bool {
266 self.channels.iter().any(|ch| ch.is_clipping())
267 }
268}
269
270#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
274pub struct SpeakerArrangement(pub u64);
275
276impl SpeakerArrangement {
277 pub const EMPTY: Self = Self(0);
279 pub const MONO: Self = Self(0x0008_0000);
281 pub const STEREO: Self = Self(0x3);
283 pub const STEREO_SURROUND: Self = Self(0x30);
285
286 pub fn from_raw(bits: u64) -> Self {
288 Self(bits)
289 }
290
291 pub fn raw(self) -> u64 {
293 self.0
294 }
295
296 pub fn channel_count(self) -> usize {
298 self.0.count_ones() as usize
299 }
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
305pub enum MediaType {
306 Audio,
308 Event,
310}
311
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
315pub enum BusDirection {
316 Input,
318 Output,
320}
321
322#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
324pub struct BusArrangements {
325 pub inputs: Vec<SpeakerArrangement>,
327 pub outputs: Vec<SpeakerArrangement>,
329}
330
331#[derive(Debug, Clone)]
353pub struct PeakMeter {
354 fall_db_per_sec: f32,
355 hold: std::time::Duration,
356 level: f32,
357 peak_hold: f32,
358 peak_hold_at: Option<std::time::Instant>,
359 last: Option<std::time::Instant>,
360}
361
362impl PeakMeter {
363 const SILENCE: f32 = 1e-5;
366
367 pub fn new(fall_db_per_sec: f32, hold: std::time::Duration) -> Self {
371 Self {
372 fall_db_per_sec: fall_db_per_sec.max(0.0),
373 hold,
374 level: 0.0,
375 peak_hold: 0.0,
376 peak_hold_at: None,
377 last: None,
378 }
379 }
380
381 fn decay(&self, dt: std::time::Duration) -> f32 {
383 let db = self.fall_db_per_sec * dt.as_secs_f32();
384 10f32.powf(-db / 20.0)
385 }
386
387 pub fn push(&mut self, block_peak: f32, now: std::time::Instant) {
391 let block_peak = if block_peak.is_finite() {
394 block_peak.max(0.0)
395 } else {
396 0.0
397 };
398 let decay = match self.last {
399 Some(prev) => self.decay(now.saturating_duration_since(prev)),
400 None => 1.0,
401 };
402
403 self.level = (self.level * decay).max(block_peak);
404 if self.level < Self::SILENCE {
405 self.level = 0.0;
406 }
407
408 if block_peak >= self.peak_hold {
409 self.peak_hold = block_peak;
411 self.peak_hold_at = Some(now);
412 } else if self
413 .peak_hold_at
414 .is_some_and(|at| now.saturating_duration_since(at) > self.hold)
415 {
416 self.peak_hold = (self.peak_hold * decay).max(self.level);
418 if self.peak_hold < Self::SILENCE {
419 self.peak_hold = 0.0;
420 }
421 }
422
423 self.last = Some(now);
424 }
425
426 pub fn level(&self) -> f32 {
428 self.level
429 }
430
431 pub fn peak_hold(&self) -> f32 {
433 self.peak_hold
434 }
435
436 pub fn reset(&mut self) {
438 self.level = 0.0;
439 self.peak_hold = 0.0;
440 self.peak_hold_at = None;
441 self.last = None;
442 }
443}
444
445#[derive(Debug, Clone)]
459pub struct RmsWindow {
460 capacity: usize,
461 squares: std::collections::VecDeque<f32>,
462 sum: f64,
465}
466
467impl RmsWindow {
468 pub fn new(window_samples: usize) -> Self {
470 let capacity = window_samples.max(1);
471 Self {
472 capacity,
473 squares: std::collections::VecDeque::with_capacity(capacity),
474 sum: 0.0,
475 }
476 }
477
478 pub fn from_duration(window_secs: f32, sample_rate: f64) -> Self {
483 const MAX_WINDOW_SAMPLES: f64 = (1u64 << 26) as f64; let samples = window_secs.max(0.0) as f64 * sample_rate;
485 let samples = if samples.is_finite() {
486 samples.round().clamp(1.0, MAX_WINDOW_SAMPLES) as usize
487 } else {
488 1
489 };
490 Self::new(samples)
491 }
492
493 pub fn push_sample(&mut self, sample: f32) {
495 let sq = sample * sample;
500 let sq = if sq.is_finite() { sq } else { 0.0 };
501 if self.squares.len() == self.capacity {
502 if let Some(old) = self.squares.pop_front() {
503 self.sum -= old as f64;
504 }
505 }
506 self.squares.push_back(sq);
507 self.sum += sq as f64;
508 }
509
510 pub fn push_block(&mut self, block: &[f32]) {
512 for &s in block {
513 self.push_sample(s);
514 }
515 }
516
517 pub fn rms(&self) -> f32 {
519 if self.squares.is_empty() {
520 return 0.0;
521 }
522 (self.sum.max(0.0) / self.squares.len() as f64).sqrt() as f32
524 }
525
526 pub fn len(&self) -> usize {
528 self.squares.len()
529 }
530
531 pub fn is_empty(&self) -> bool {
533 self.squares.is_empty()
534 }
535
536 pub fn clear(&mut self) {
538 self.squares.clear();
539 self.sum = 0.0;
540 }
541}
542
543#[derive(Debug, Clone, Copy)]
545pub struct AudioConfig {
546 pub sample_rate: f64,
548 pub block_size: usize,
550 pub input_channels: usize,
552 pub output_channels: usize,
554 pub tempo: f64,
557 pub time_sig_numerator: i32,
559 pub time_sig_denominator: i32,
562}
563
564impl Default for AudioConfig {
565 fn default() -> Self {
566 Self {
567 sample_rate: 44100.0,
568 block_size: 512,
569 input_channels: 0,
570 output_channels: 2,
571 tempo: 120.0,
572 time_sig_numerator: 4,
573 time_sig_denominator: 4,
574 }
575 }
576}
577
578pub trait AudioStream {
585 fn play(&self) -> Result<(), Box<dyn std::error::Error>>;
587
588 fn pause(&self) -> Result<(), Box<dyn std::error::Error>>;
590}
591
592#[allow(clippy::type_complexity)] pub trait AudioBackend: Send + Sync {
595 type Stream: AudioStream + 'static;
597 type Device: Send + Sync;
599 type Error: std::error::Error + Send + Sync + 'static;
601
602 fn enumerate_output_devices(&self) -> Result<Vec<Self::Device>, Self::Error>;
604
605 fn enumerate_input_devices(&self) -> Result<Vec<Self::Device>, Self::Error>;
607
608 fn default_output_device(&self) -> Option<Self::Device>;
610
611 fn default_input_device(&self) -> Option<Self::Device>;
613
614 fn create_output_stream(
616 &self,
617 device: &Self::Device,
618 config: AudioConfig,
619 data_callback: Box<dyn FnMut(&mut [f32]) + Send>,
620 error_callback: Box<dyn FnMut(Self::Error) + Send>,
621 ) -> Result<Self::Stream, Self::Error>;
622
623 fn create_input_stream(
625 &self,
626 device: &Self::Device,
627 config: AudioConfig,
628 data_callback: Box<dyn FnMut(&[f32]) + Send>,
629 error_callback: Box<dyn FnMut(Self::Error) + Send>,
630 ) -> Result<Self::Stream, Self::Error>;
631}
632
633pub fn write_wav<P: AsRef<std::path::Path>>(
638 path: P,
639 channels: &[Vec<f32>],
640 sample_rate: u32,
641) -> crate::error::Result<()> {
642 use crate::error::Error;
643 use std::io::Write;
644
645 let num_channels = channels.len().max(1) as u16;
646 let frames = channels.iter().map(|c| c.len()).min().unwrap_or(0);
647 let bits_per_sample: u16 = 32;
648 let block_align = num_channels * (bits_per_sample / 8);
649 let byte_rate = sample_rate * block_align as u32;
650 let data_size = (frames * num_channels as usize * (bits_per_sample / 8) as usize) as u32;
651
652 let mut buf: Vec<u8> = Vec::with_capacity(44 + data_size as usize);
653 buf.extend_from_slice(b"RIFF");
654 buf.extend_from_slice(&(36 + data_size).to_le_bytes());
655 buf.extend_from_slice(b"WAVE");
656 buf.extend_from_slice(b"fmt ");
657 buf.extend_from_slice(&16u32.to_le_bytes());
658 buf.extend_from_slice(&3u16.to_le_bytes()); buf.extend_from_slice(&num_channels.to_le_bytes());
660 buf.extend_from_slice(&sample_rate.to_le_bytes());
661 buf.extend_from_slice(&byte_rate.to_le_bytes());
662 buf.extend_from_slice(&block_align.to_le_bytes());
663 buf.extend_from_slice(&bits_per_sample.to_le_bytes());
664 buf.extend_from_slice(b"data");
665 buf.extend_from_slice(&data_size.to_le_bytes());
666 for f in 0..frames {
668 for ch in channels {
669 buf.extend_from_slice(&ch[f].to_le_bytes());
670 }
671 }
672
673 let mut file =
674 std::fs::File::create(path).map_err(|e| Error::Other(format!("create wav: {e}")))?;
675 file.write_all(&buf)
676 .map_err(|e| Error::Other(format!("write wav: {e}")))?;
677 Ok(())
678}
679
680pub fn read_wav<P: AsRef<std::path::Path>>(path: P) -> crate::error::Result<(Vec<Vec<f32>>, u32)> {
684 use crate::error::Error;
685 let data = std::fs::read(path).map_err(|e| Error::Other(format!("read wav: {e}")))?;
686 let err = |m: &str| Error::Other(format!("invalid wav: {m}"));
687 if data.len() < 44 || &data[0..4] != b"RIFF" || &data[8..12] != b"WAVE" {
688 return Err(err("not a RIFF/WAVE file"));
689 }
690 let (mut fmt_tag, mut channels, mut sample_rate, mut bits) = (0u16, 0u16, 0u32, 0u16);
692 let mut data_range: Option<(usize, usize)> = None;
693 let mut pos = 12;
694 while pos + 8 <= data.len() {
695 let id = &data[pos..pos + 4];
696 let size = u32::from_le_bytes([data[pos + 4], data[pos + 5], data[pos + 6], data[pos + 7]])
697 as usize;
698 let body = pos + 8;
699 if id == b"fmt " && body + 16 <= data.len() {
700 fmt_tag = u16::from_le_bytes([data[body], data[body + 1]]);
701 channels = u16::from_le_bytes([data[body + 2], data[body + 3]]);
702 sample_rate = u32::from_le_bytes([
703 data[body + 4],
704 data[body + 5],
705 data[body + 6],
706 data[body + 7],
707 ]);
708 bits = u16::from_le_bytes([data[body + 14], data[body + 15]]);
709 } else if id == b"data" {
710 data_range = Some((body, (body + size).min(data.len())));
711 }
712 pos = body + size + (size & 1); }
714 let (ds, de) = data_range.ok_or_else(|| err("no data chunk"))?;
715 if channels == 0 {
716 return Err(err("zero channels"));
717 }
718 let nch = channels as usize;
719 let mut out: Vec<Vec<f32>> = vec![Vec::new(); nch];
720 let bytes = &data[ds..de];
721 match (fmt_tag, bits) {
722 (3, 32) => {
723 for (i, frame) in bytes.chunks_exact(4 * nch).enumerate() {
724 let _ = i;
725 for (ch, s) in frame.chunks_exact(4).enumerate() {
726 out[ch].push(f32::from_le_bytes([s[0], s[1], s[2], s[3]]));
727 }
728 }
729 }
730 (1, 16) => {
731 for frame in bytes.chunks_exact(2 * nch) {
732 for (ch, s) in frame.chunks_exact(2).enumerate() {
733 let v = i16::from_le_bytes([s[0], s[1]]) as f32 / 32768.0;
734 out[ch].push(v);
735 }
736 }
737 }
738 _ => return Err(err("unsupported format (need 32-bit float or 16-bit PCM)")),
739 }
740 Ok((out, sample_rate))
741}
742
743pub trait InputSource: Send {
746 fn fill(&mut self, inputs: &mut [Vec<f32>], frames: usize, sample_rate: f64);
748}
749
750#[derive(Debug, Clone)]
753pub enum SignalSource {
754 Silence,
756 Sine {
758 freq: f32,
760 amplitude: f32,
762 phase: f64,
764 },
765 WhiteNoise {
767 amplitude: f32,
769 rng: u64,
771 },
772 Wav {
774 samples: std::sync::Arc<Vec<Vec<f32>>>,
776 pos: usize,
778 looping: bool,
780 },
781}
782
783impl SignalSource {
784 pub fn sine(freq: f32, amplitude: f32) -> Self {
786 SignalSource::Sine {
787 freq,
788 amplitude,
789 phase: 0.0,
790 }
791 }
792 pub fn white_noise(amplitude: f32) -> Self {
794 SignalSource::WhiteNoise {
795 amplitude,
796 rng: 0x9E37_79B9_7F4A_7C15,
797 }
798 }
799 pub fn wav(samples: Vec<Vec<f32>>, looping: bool) -> Self {
801 SignalSource::Wav {
802 samples: std::sync::Arc::new(samples),
803 pos: 0,
804 looping,
805 }
806 }
807}
808
809impl InputSource for SignalSource {
810 fn fill(&mut self, inputs: &mut [Vec<f32>], frames: usize, sample_rate: f64) {
811 for ch in inputs.iter_mut() {
812 if ch.len() < frames {
813 ch.resize(frames, 0.0);
814 }
815 }
816 match self {
817 SignalSource::Silence => {
818 for ch in inputs.iter_mut() {
819 for s in &mut ch[..frames] {
820 *s = 0.0;
821 }
822 }
823 }
824 SignalSource::Sine {
825 freq,
826 amplitude,
827 phase,
828 } => {
829 let step = std::f64::consts::TAU * *freq as f64 / sample_rate.max(1.0);
830 for f in 0..frames {
831 let v = (phase.sin() as f32) * *amplitude;
832 for ch in inputs.iter_mut() {
833 ch[f] = v;
834 }
835 *phase = (*phase + step) % std::f64::consts::TAU;
836 }
837 }
838 SignalSource::WhiteNoise { amplitude, rng } => {
839 for f in 0..frames {
840 let mut x = *rng;
842 x ^= x << 13;
843 x ^= x >> 7;
844 x ^= x << 17;
845 *rng = x;
846 let unit = ((x >> 11) as f64 / (1u64 << 53) as f64) as f32 * 2.0 - 1.0;
848 let v = unit * *amplitude;
849 for ch in inputs.iter_mut() {
850 ch[f] = v;
851 }
852 }
853 }
854 SignalSource::Wav {
855 samples,
856 pos,
857 looping,
858 } => {
859 let total = samples.iter().map(|c| c.len()).max().unwrap_or(0);
860 for f in 0..frames {
861 let p = *pos + f;
862 let src_idx = if total == 0 {
863 None
864 } else if p < total {
865 Some(p)
866 } else if *looping {
867 Some(p % total)
868 } else {
869 None
870 };
871 for (ci, ch) in inputs.iter_mut().enumerate() {
872 ch[f] = match src_idx {
873 Some(i) => samples
874 .get(ci % samples.len().max(1))
875 .and_then(|c| c.get(i))
876 .copied()
877 .unwrap_or(0.0),
878 None => 0.0,
879 };
880 }
881 }
882 *pos += frames;
883 }
884 }
885 }
886}
887
888#[cfg(test)]
889mod wav_tests {
890 use super::*;
891
892 #[test]
893 fn write_wav_has_correct_header_and_size() {
894 let ch = vec![vec![0.0f32, 0.5, -0.5, 1.0], vec![0.1, 0.2, 0.3, 0.4]];
895 let path = std::env::temp_dir().join(format!("vh_write_wav_{}.wav", std::process::id()));
896 write_wav(&path, &ch, 48_000).unwrap();
897 let bytes = std::fs::read(&path).unwrap();
898 let _ = std::fs::remove_file(&path);
899
900 assert_eq!(&bytes[0..4], b"RIFF");
901 assert_eq!(&bytes[8..12], b"WAVE");
902 assert_eq!(u16::from_le_bytes([bytes[20], bytes[21]]), 3); assert_eq!(u16::from_le_bytes([bytes[22], bytes[23]]), 2); assert_eq!(
905 u32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]),
906 48_000
907 );
908 assert_eq!(bytes.len(), 44 + 32);
910 }
911
912 #[test]
913 fn write_then_read_wav_round_trips() {
914 let ch = vec![vec![0.0f32, 0.5, -0.5, 1.0], vec![0.1, 0.2, 0.3, 0.4]];
915 let path = std::env::temp_dir().join(format!("vh_rw_{}.wav", std::process::id()));
916 write_wav(&path, &ch, 44_100).unwrap();
917 let (back, sr) = read_wav(&path).unwrap();
918 let _ = std::fs::remove_file(&path);
919 assert_eq!(sr, 44_100);
920 assert_eq!(back.len(), 2);
921 for (a, b) in ch.iter().zip(back.iter()) {
922 for (x, y) in a.iter().zip(b.iter()) {
923 assert!((x - y).abs() < 1e-6, "{x} vs {y}");
924 }
925 }
926 }
927}
928
929#[cfg(test)]
930mod signal_tests {
931 use super::*;
932
933 #[test]
934 fn sine_starts_at_zero_and_stays_in_amplitude() {
935 let mut src = SignalSource::sine(1000.0, 0.5);
936 let mut inputs = vec![vec![0.0f32; 256], vec![0.0f32; 256]];
937 src.fill(&mut inputs, 256, 48_000.0);
938 assert!(inputs[0][0].abs() < 1e-6, "sine should start at phase 0");
939 for ch in &inputs {
940 assert!(
941 ch.iter().all(|s| s.abs() <= 0.5 + 1e-6),
942 "exceeds amplitude"
943 );
944 }
945 assert_eq!(inputs[0], inputs[1]);
947 assert!(inputs[0].iter().any(|s| s.abs() > 0.1));
949 }
950
951 #[test]
952 fn noise_is_bounded_and_varied() {
953 let mut src = SignalSource::white_noise(0.25);
954 let mut inputs = vec![vec![0.0f32; 512]];
955 src.fill(&mut inputs, 512, 48_000.0);
956 assert!(inputs[0].iter().all(|s| s.abs() <= 0.25 + 1e-6));
957 let first = inputs[0][0];
958 assert!(inputs[0].iter().any(|&s| s != first), "noise should vary");
959 }
960
961 #[test]
962 fn wav_source_advances_and_zero_pads() {
963 let mut src = SignalSource::wav(vec![vec![1.0, 2.0, 3.0]], false);
964 let mut inputs = vec![vec![0.0f32; 5]];
965 src.fill(&mut inputs, 5, 48_000.0);
966 assert_eq!(inputs[0], vec![1.0, 2.0, 3.0, 0.0, 0.0]); }
968
969 #[test]
970 fn wav_source_loops() {
971 let mut src = SignalSource::wav(vec![vec![1.0, 2.0]], true);
972 let mut inputs = vec![vec![0.0f32; 5]];
973 src.fill(&mut inputs, 5, 48_000.0);
974 assert_eq!(inputs[0], vec![1.0, 2.0, 1.0, 2.0, 1.0]); }
976}
977
978#[cfg(test)]
979mod speaker_arrangement_tests {
980 use super::*;
981
982 #[test]
983 fn channel_counts_match_bitmask() {
984 assert_eq!(SpeakerArrangement::EMPTY.channel_count(), 0);
985 assert_eq!(SpeakerArrangement::MONO.channel_count(), 1);
986 assert_eq!(SpeakerArrangement::STEREO.channel_count(), 2);
987 assert_eq!(SpeakerArrangement::STEREO_SURROUND.channel_count(), 2);
988 }
989
990 #[test]
991 fn raw_round_trips() {
992 let bits = SpeakerArrangement::STEREO.raw();
993 assert_eq!(bits, 0x3);
994 assert_eq!(
995 SpeakerArrangement::from_raw(bits),
996 SpeakerArrangement::STEREO
997 );
998 assert_eq!(SpeakerArrangement::from_raw(0b111111).channel_count(), 6);
1000 }
1001
1002 #[test]
1003 fn media_type_and_bus_direction_serde_round_trip() {
1004 for mt in [MediaType::Audio, MediaType::Event] {
1005 let json = serde_json::to_string(&mt).expect("serialize MediaType");
1006 let back: MediaType = serde_json::from_str(&json).expect("deserialize MediaType");
1007 assert_eq!(mt, back);
1008 }
1009 for dir in [BusDirection::Input, BusDirection::Output] {
1010 let json = serde_json::to_string(&dir).expect("serialize BusDirection");
1011 let back: BusDirection = serde_json::from_str(&json).expect("deserialize BusDirection");
1012 assert_eq!(dir, back);
1013 }
1014 }
1015}
1016
1017#[cfg(test)]
1018mod meter_tests {
1019 use super::*;
1020 use std::time::{Duration, Instant};
1021
1022 #[test]
1023 fn peak_meter_rises_instantly_and_holds() {
1024 let mut m = PeakMeter::new(20.0, Duration::from_secs(2));
1025 let t0 = Instant::now();
1026 m.push(0.7, t0);
1027 assert_eq!(m.level(), 0.7);
1028 assert_eq!(m.peak_hold(), 0.7);
1029
1030 m.push(0.9, t0 + Duration::from_millis(10));
1032 assert_eq!(m.level(), 0.9);
1033 assert_eq!(m.peak_hold(), 0.9);
1034 }
1035
1036 #[test]
1037 fn peak_meter_level_falls_but_hold_latches() {
1038 let mut m = PeakMeter::new(20.0, Duration::from_secs(3));
1039 let t0 = Instant::now();
1040 m.push(1.0, t0);
1041
1042 m.push(0.0, t0 + Duration::from_millis(500));
1044 let lvl = m.level();
1045 assert!(
1046 lvl < 1.0 && lvl > 0.0,
1047 "level should be mid-fall, got {lvl}"
1048 );
1049 assert!((lvl - 0.316).abs() < 0.02, "≈-10 dB expected, got {lvl}");
1050 assert_eq!(m.peak_hold(), 1.0, "hold must latch within its window");
1051 }
1052
1053 #[test]
1054 fn peak_meter_hold_falls_after_window() {
1055 let mut m = PeakMeter::new(20.0, Duration::from_secs(1));
1056 let t0 = Instant::now();
1057 m.push(1.0, t0);
1058 m.push(0.0, t0 + Duration::from_millis(1500));
1060 assert!(
1061 m.peak_hold() < 1.0,
1062 "hold should fall after the window expired, got {}",
1063 m.peak_hold()
1064 );
1065 }
1066
1067 #[test]
1068 fn peak_meter_reaches_silence_floor() {
1069 let mut m = PeakMeter::new(60.0, Duration::from_millis(0));
1070 let t0 = Instant::now();
1071 m.push(0.5, t0);
1072 m.push(0.0, t0 + Duration::from_secs(10));
1074 assert_eq!(m.level(), 0.0);
1075 assert_eq!(m.peak_hold(), 0.0);
1076 }
1077
1078 #[test]
1079 fn rms_window_constant_signal() {
1080 let mut r = RmsWindow::new(8);
1081 for _ in 0..8 {
1082 r.push_sample(0.5);
1083 }
1084 assert!((r.rms() - 0.5).abs() < 1e-6);
1085 assert_eq!(r.len(), 8);
1086 }
1087
1088 #[test]
1089 fn rms_window_slides_and_evicts() {
1090 let mut r = RmsWindow::new(3);
1091 r.push_block(&[1.0, 1.0, 1.0]);
1092 assert!((r.rms() - 1.0).abs() < 1e-6);
1093 r.push_block(&[0.0, 0.0, 0.0]);
1095 assert_eq!(r.len(), 3);
1096 assert!(
1097 r.rms() < 1e-6,
1098 "window should have slid to silence, got {}",
1099 r.rms()
1100 );
1101 }
1102
1103 #[test]
1104 fn rms_window_empty_is_zero() {
1105 let r = RmsWindow::new(16);
1106 assert!(r.is_empty());
1107 assert_eq!(r.rms(), 0.0);
1108 }
1109
1110 #[test]
1111 fn rms_window_from_duration_sizes_correctly() {
1112 let r = RmsWindow::from_duration(0.01, 48_000.0);
1114 assert_eq!(r.capacity, 480);
1115 }
1116
1117 #[test]
1121 fn rms_window_is_not_latched_by_a_non_finite_sample() {
1122 for poison in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, 1.9e19] {
1123 let mut w = RmsWindow::new(1);
1124 w.push_sample(poison);
1125 w.push_sample(0.5);
1126 assert!(
1127 (w.rms() - 0.5).abs() < 1e-6,
1128 "a {poison} sample latched the meter: rms = {}",
1129 w.rms()
1130 );
1131 }
1132
1133 let mut w = RmsWindow::new(4);
1135 w.push_block(&[f32::NAN, 0.5, 0.5, 0.5]);
1136 assert!(w.rms().is_finite() && w.rms() > 0.0, "rms = {}", w.rms());
1137 }
1138
1139 #[test]
1142 fn rms_window_from_duration_survives_absurd_input() {
1143 for (secs, rate) in [
1144 (1.0f32, f64::INFINITY),
1145 (f32::INFINITY, 48_000.0f64),
1146 (f32::NAN, 48_000.0),
1147 (1.0, 1e30),
1148 (1.0, -48_000.0),
1149 (-1.0, 48_000.0),
1150 ] {
1151 let w = RmsWindow::from_duration(secs, rate);
1152 assert!(
1153 w.capacity >= 1,
1154 "capacity {} for ({secs}, {rate})",
1155 w.capacity
1156 );
1157 }
1158 }
1159}