Skip to main content

vst3_host/
audio.rs

1//! Audio types and utilities for VST3 host
2
3/// Audio buffers for plugin processing
4#[derive(Debug)]
5pub struct AudioBuffers {
6    /// Input audio buffers, indexed `[channel][sample]`.
7    pub inputs: Vec<Vec<f32>>,
8    /// Output audio buffers, indexed `[channel][sample]`.
9    pub outputs: Vec<Vec<f32>>,
10    /// Sample rate in Hz
11    pub sample_rate: f64,
12    /// Number of samples per buffer
13    pub block_size: usize,
14}
15
16impl AudioBuffers {
17    /// Create new audio buffers
18    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    /// Clear all buffers to silence
36    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    /// Get the number of input channels
46    pub fn input_channels(&self) -> usize {
47        self.inputs.len()
48    }
49
50    /// Get the number of output channels
51    pub fn output_channels(&self) -> usize {
52        self.outputs.len()
53    }
54}
55
56/// Audio level information for a single channel
57#[derive(Debug, Clone, Copy)]
58pub struct ChannelLevel {
59    /// Peak level (0.0 to 1.0, where 1.0 = 0dB)
60    pub peak: f32,
61    /// RMS level (0.0 to 1.0)
62    pub rms: f32,
63    /// Peak hold level (0.0 to 1.0)
64    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    /// Convert peak level to decibels
79    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    /// Convert RMS level to decibels
88    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    /// Check if the signal is clipping (> 0dB)
97    pub fn is_clipping(&self) -> bool {
98        self.peak > 1.0
99    }
100}
101
102/// Audio level information for all channels
103#[derive(Debug, Clone)]
104pub struct AudioLevels {
105    /// Level information for each channel
106    pub channels: Vec<ChannelLevel>,
107}
108
109impl AudioLevels {
110    /// Create new audio levels for the given number of channels
111    pub fn new(channel_count: usize) -> Self {
112        Self {
113            channels: vec![ChannelLevel::default(); channel_count],
114        }
115    }
116
117    /// Update levels from audio buffers
118    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            // Calculate peak
125            let peak = buffer.iter().map(|&x| x.abs()).fold(0.0f32, f32::max);
126
127            // Calculate RMS (guard against a zero-length channel buffer → 0/0 = NaN).
128            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            // Update channel levels
136            let channel = &mut self.channels[i];
137            channel.peak = peak;
138            channel.rms = rms;
139
140            // Update peak hold if necessary
141            if peak > channel.peak_hold {
142                channel.peak_hold = peak;
143            }
144        }
145    }
146
147    /// Reset peak hold values
148    pub fn reset_peak_hold(&mut self) {
149        for channel in &mut self.channels {
150            channel.peak_hold = channel.peak;
151        }
152    }
153
154    /// Check if any channel is clipping
155    pub fn is_clipping(&self) -> bool {
156        self.channels.iter().any(|ch| ch.is_clipping())
157    }
158}
159
160/// A VST3 speaker arrangement: a bitmask where each set bit is one channel (so the channel
161/// count is the number of set bits). Wraps the SDK's `SpeakerArrangement` (a `u64` bitmask);
162/// use the named constants or [`from_raw`](Self::from_raw).
163#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
164pub struct SpeakerArrangement(pub u64);
165
166impl SpeakerArrangement {
167    /// No channels (`kEmpty`).
168    pub const EMPTY: Self = Self(0);
169    /// Mono (`kMono` = front-center).
170    pub const MONO: Self = Self(0x0008_0000);
171    /// Stereo L/R (`kStereo`).
172    pub const STEREO: Self = Self(0x3);
173    /// Stereo surround Ls/Rs (`kStereoSurround`).
174    pub const STEREO_SURROUND: Self = Self(0x30);
175
176    /// Wrap a raw VST3 `SpeakerArrangement` bitmask.
177    pub fn from_raw(bits: u64) -> Self {
178        Self(bits)
179    }
180
181    /// The raw VST3 bitmask.
182    pub fn raw(self) -> u64 {
183        self.0
184    }
185
186    /// Number of channels in this arrangement (the count of set bits).
187    pub fn channel_count(self) -> usize {
188        self.0.count_ones() as usize
189    }
190}
191
192/// The kind of data a VST3 bus carries: PCM audio or events (MIDI). Maps to the SDK's
193/// `MediaTypes` (`kAudio` / `kEvent`).
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
195pub enum MediaType {
196    /// Audio (PCM sample) buses (`kAudio`).
197    Audio,
198    /// Event / MIDI buses (`kEvent`).
199    Event,
200}
201
202/// Which side of the plugin a bus sits on: input or output. Maps to the SDK's
203/// `BusDirections` (`kInput` / `kOutput`).
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
205pub enum BusDirection {
206    /// An input bus (`kInput`).
207    Input,
208    /// An output bus (`kOutput`).
209    Output,
210}
211
212/// The speaker arrangements of a plugin's audio input and output buses.
213#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
214pub struct BusArrangements {
215    /// Arrangement of each audio input bus, in bus-index order.
216    pub inputs: Vec<SpeakerArrangement>,
217    /// Arrangement of each audio output bus, in bus-index order.
218    pub outputs: Vec<SpeakerArrangement>,
219}
220
221/// A single-channel peak meter with falling ballistics and a timed peak-hold marker —
222/// the behaviour a level meter UI wants but [`AudioLevels`]'s sticky `peak_hold` doesn't give.
223///
224/// Time is **injected** ([`push`](Self::push) takes `now: Instant`) so the meter is
225/// deterministic and independent of any clock — pass `Instant::now()` from real code, or
226/// synthetic instants in tests. Feed it the per-block peak amplitude; read [`level`](Self::level)
227/// for the falling meter value and [`peak_hold`](Self::peak_hold) for the held marker.
228///
229/// ```
230/// use std::time::{Duration, Instant};
231/// use vst3_host::audio::PeakMeter;
232///
233/// let mut meter = PeakMeter::new(20.0, Duration::from_secs(2)); // 20 dB/s fall, 2 s hold
234/// let t0 = Instant::now();
235/// meter.push(0.8, t0);
236/// assert_eq!(meter.level(), 0.8);
237/// // After silence the displayed level falls but the hold marker stays put (within the window).
238/// meter.push(0.0, t0 + Duration::from_millis(100));
239/// assert!(meter.level() < 0.8 && meter.level() > 0.0);
240/// assert_eq!(meter.peak_hold(), 0.8);
241/// ```
242#[derive(Debug, Clone)]
243pub struct PeakMeter {
244    fall_db_per_sec: f32,
245    hold: std::time::Duration,
246    level: f32,
247    peak_hold: f32,
248    peak_hold_at: Option<std::time::Instant>,
249    last: Option<std::time::Instant>,
250}
251
252impl PeakMeter {
253    /// Below this the level snaps to exactly 0.0 (≈ -100 dB), so a meter fully empties
254    /// instead of asymptotically approaching zero forever.
255    const SILENCE: f32 = 1e-5;
256
257    /// Create a meter that falls at `fall_db_per_sec` decibels per second and holds the peak
258    /// marker for `hold` before it, too, begins to fall. A typical UI meter uses ~20 dB/s and
259    /// a 1–3 second hold.
260    pub fn new(fall_db_per_sec: f32, hold: std::time::Duration) -> Self {
261        Self {
262            fall_db_per_sec: fall_db_per_sec.max(0.0),
263            hold,
264            level: 0.0,
265            peak_hold: 0.0,
266            peak_hold_at: None,
267            last: None,
268        }
269    }
270
271    /// Linear gain after falling for `dt`, e.g. `10^(-(dB/s · dt)/20)`.
272    fn decay(&self, dt: std::time::Duration) -> f32 {
273        let db = self.fall_db_per_sec * dt.as_secs_f32();
274        10f32.powf(-db / 20.0)
275    }
276
277    /// Update with a new block's peak amplitude (`0.0..`) observed at `now`. The displayed
278    /// level rises instantly to a louder peak and decays toward quieter input; the hold marker
279    /// latches the loudest value and only starts falling once `hold` has elapsed since it was set.
280    pub fn push(&mut self, block_peak: f32, now: std::time::Instant) {
281        // Treat non-finite input (NaN/±inf from a misbehaving plugin) as silence so it can't
282        // permanently poison the meter — `inf * decay` stays inf and would never fall.
283        let block_peak = if block_peak.is_finite() {
284            block_peak.max(0.0)
285        } else {
286            0.0
287        };
288        let decay = match self.last {
289            Some(prev) => self.decay(now.saturating_duration_since(prev)),
290            None => 1.0,
291        };
292
293        self.level = (self.level * decay).max(block_peak);
294        if self.level < Self::SILENCE {
295            self.level = 0.0;
296        }
297
298        if block_peak >= self.peak_hold {
299            // New loudest value — latch it and restart the hold timer.
300            self.peak_hold = block_peak;
301            self.peak_hold_at = Some(now);
302        } else if self
303            .peak_hold_at
304            .is_some_and(|at| now.saturating_duration_since(at) > self.hold)
305        {
306            // Hold window expired — the marker falls at the same ballistic, never below `level`.
307            self.peak_hold = (self.peak_hold * decay).max(self.level);
308            if self.peak_hold < Self::SILENCE {
309                self.peak_hold = 0.0;
310            }
311        }
312
313        self.last = Some(now);
314    }
315
316    /// The current falling-meter level (`0.0..`).
317    pub fn level(&self) -> f32 {
318        self.level
319    }
320
321    /// The held peak marker (`0.0..`).
322    pub fn peak_hold(&self) -> f32 {
323        self.peak_hold
324    }
325
326    /// Reset the meter to silence.
327    pub fn reset(&mut self) {
328        self.level = 0.0;
329        self.peak_hold = 0.0;
330        self.peak_hold_at = None;
331        self.last = None;
332    }
333}
334
335/// A moving-window RMS estimator over the most recent `N` samples.
336///
337/// Unlike [`AudioLevels`]'s per-block RMS (which resets every buffer), this gives a smooth
338/// level over a fixed time window regardless of block size — feed it samples or whole blocks
339/// and read [`rms`](Self::rms). The window length in samples is `window_secs · sample_rate`.
340///
341/// ```
342/// use vst3_host::audio::RmsWindow;
343///
344/// let mut rms = RmsWindow::new(4);
345/// for _ in 0..4 { rms.push_sample(0.5); }
346/// assert!((rms.rms() - 0.5).abs() < 1e-6); // constant 0.5 → RMS 0.5
347/// ```
348#[derive(Debug, Clone)]
349pub struct RmsWindow {
350    capacity: usize,
351    squares: std::collections::VecDeque<f32>,
352    // f64 accumulator so a meter running for the lifetime of a stream (millions of
353    // add/subtract cycles) doesn't drift from f32 rounding error.
354    sum: f64,
355}
356
357impl RmsWindow {
358    /// Create a window holding the most recent `window_samples` samples (minimum 1).
359    pub fn new(window_samples: usize) -> Self {
360        let capacity = window_samples.max(1);
361        Self {
362            capacity,
363            squares: std::collections::VecDeque::with_capacity(capacity),
364            sum: 0.0,
365        }
366    }
367
368    /// Create a window sized for `window_secs` of audio at `sample_rate` Hz.
369    pub fn from_duration(window_secs: f32, sample_rate: f64) -> Self {
370        Self::new((window_secs.max(0.0) as f64 * sample_rate).round() as usize)
371    }
372
373    /// Add one sample, evicting the oldest if the window is full.
374    pub fn push_sample(&mut self, sample: f32) {
375        let sq = sample * sample;
376        if self.squares.len() == self.capacity {
377            if let Some(old) = self.squares.pop_front() {
378                self.sum -= old as f64;
379            }
380        }
381        self.squares.push_back(sq);
382        self.sum += sq as f64;
383    }
384
385    /// Add a whole block of samples.
386    pub fn push_block(&mut self, block: &[f32]) {
387        for &s in block {
388            self.push_sample(s);
389        }
390    }
391
392    /// Current RMS over the samples in the window (`0.0` when empty).
393    pub fn rms(&self) -> f32 {
394        if self.squares.is_empty() {
395            return 0.0;
396        }
397        // Guard against tiny negative drift from float subtraction.
398        (self.sum.max(0.0) / self.squares.len() as f64).sqrt() as f32
399    }
400
401    /// Number of samples currently in the window.
402    pub fn len(&self) -> usize {
403        self.squares.len()
404    }
405
406    /// Whether the window holds no samples yet.
407    pub fn is_empty(&self) -> bool {
408        self.squares.is_empty()
409    }
410
411    /// Drop all samples.
412    pub fn clear(&mut self) {
413        self.squares.clear();
414        self.sum = 0.0;
415    }
416}
417
418/// Audio processing configuration
419#[derive(Debug, Clone, Copy)]
420pub struct AudioConfig {
421    /// Sample rate in Hz
422    pub sample_rate: f64,
423    /// Block size in samples
424    pub block_size: usize,
425    /// Number of input channels
426    pub input_channels: usize,
427    /// Number of output channels
428    pub output_channels: usize,
429    /// Transport tempo in beats per minute, advertised to plugins in the host
430    /// `ProcessContext` (drives tempo-synced DSP such as LFOs and synced delays).
431    pub tempo: f64,
432    /// Time signature numerator (beats per bar), advertised in the `ProcessContext`.
433    pub time_sig_numerator: i32,
434    /// Time signature denominator (note value of one beat), advertised in the
435    /// `ProcessContext`.
436    pub time_sig_denominator: i32,
437}
438
439impl Default for AudioConfig {
440    fn default() -> Self {
441        Self {
442            sample_rate: 44100.0,
443            block_size: 512,
444            input_channels: 0,
445            output_channels: 2,
446            tempo: 120.0,
447            time_sig_numerator: 4,
448            time_sig_denominator: 4,
449        }
450    }
451}
452
453/// Audio stream trait for controlling playback
454pub trait AudioStream: Send {
455    /// Start playback
456    fn play(&self) -> Result<(), Box<dyn std::error::Error>>;
457
458    /// Pause playback
459    fn pause(&self) -> Result<(), Box<dyn std::error::Error>>;
460}
461
462/// Audio backend trait for creating audio streams
463#[allow(clippy::type_complexity)] // Box<dyn FnMut...> callbacks are intrinsic to the API
464pub trait AudioBackend: Send + Sync {
465    /// The stream type this backend produces
466    type Stream: AudioStream + Send + 'static;
467    /// The device type this backend uses
468    type Device: Send + Sync;
469    /// The error type this backend returns
470    type Error: std::error::Error + Send + Sync + 'static;
471
472    /// Enumerate available output devices
473    fn enumerate_output_devices(&self) -> Result<Vec<Self::Device>, Self::Error>;
474
475    /// Enumerate available input devices
476    fn enumerate_input_devices(&self) -> Result<Vec<Self::Device>, Self::Error>;
477
478    /// Get the default output device
479    fn default_output_device(&self) -> Option<Self::Device>;
480
481    /// Get the default input device
482    fn default_input_device(&self) -> Option<Self::Device>;
483
484    /// Create an output stream
485    fn create_output_stream(
486        &self,
487        device: &Self::Device,
488        config: AudioConfig,
489        data_callback: Box<dyn FnMut(&mut [f32]) + Send>,
490        error_callback: Box<dyn FnMut(Self::Error) + Send>,
491    ) -> Result<Self::Stream, Self::Error>;
492
493    /// Create an input stream
494    fn create_input_stream(
495        &self,
496        device: &Self::Device,
497        config: AudioConfig,
498        data_callback: Box<dyn FnMut(&[f32]) + Send>,
499        error_callback: Box<dyn FnMut(Self::Error) + Send>,
500    ) -> Result<Self::Stream, Self::Error>;
501}
502
503/// Write deinterleaved channel buffers to a 32-bit float WAV file (`WAVE_FORMAT_IEEE_FLOAT`).
504///
505/// `channels[ch][frame]`; all channels must be the same length. Used by offline rendering
506/// (e.g. [`crate::simple::render_to_wav`]) and audio export. No external dependency.
507pub fn write_wav<P: AsRef<std::path::Path>>(
508    path: P,
509    channels: &[Vec<f32>],
510    sample_rate: u32,
511) -> crate::error::Result<()> {
512    use crate::error::Error;
513    use std::io::Write;
514
515    let num_channels = channels.len().max(1) as u16;
516    let frames = channels.iter().map(|c| c.len()).min().unwrap_or(0);
517    let bits_per_sample: u16 = 32;
518    let block_align = num_channels * (bits_per_sample / 8);
519    let byte_rate = sample_rate * block_align as u32;
520    let data_size = (frames * num_channels as usize * (bits_per_sample / 8) as usize) as u32;
521
522    let mut buf: Vec<u8> = Vec::with_capacity(44 + data_size as usize);
523    buf.extend_from_slice(b"RIFF");
524    buf.extend_from_slice(&(36 + data_size).to_le_bytes());
525    buf.extend_from_slice(b"WAVE");
526    buf.extend_from_slice(b"fmt ");
527    buf.extend_from_slice(&16u32.to_le_bytes());
528    buf.extend_from_slice(&3u16.to_le_bytes()); // IEEE float
529    buf.extend_from_slice(&num_channels.to_le_bytes());
530    buf.extend_from_slice(&sample_rate.to_le_bytes());
531    buf.extend_from_slice(&byte_rate.to_le_bytes());
532    buf.extend_from_slice(&block_align.to_le_bytes());
533    buf.extend_from_slice(&bits_per_sample.to_le_bytes());
534    buf.extend_from_slice(b"data");
535    buf.extend_from_slice(&data_size.to_le_bytes());
536    // Interleave channels frame by frame.
537    for f in 0..frames {
538        for ch in channels {
539            buf.extend_from_slice(&ch[f].to_le_bytes());
540        }
541    }
542
543    let mut file =
544        std::fs::File::create(path).map_err(|e| Error::Other(format!("create wav: {e}")))?;
545    file.write_all(&buf)
546        .map_err(|e| Error::Other(format!("write wav: {e}")))?;
547    Ok(())
548}
549
550/// Read a WAV file written as 32-bit float (`WAVE_FORMAT_IEEE_FLOAT`) or 16-bit PCM, returning
551/// deinterleaved channels (`channels[ch][frame]`) and the sample rate. The inverse of
552/// [`write_wav`]; used to feed a recorded signal into a plugin's input.
553pub fn read_wav<P: AsRef<std::path::Path>>(path: P) -> crate::error::Result<(Vec<Vec<f32>>, u32)> {
554    use crate::error::Error;
555    let data = std::fs::read(path).map_err(|e| Error::Other(format!("read wav: {e}")))?;
556    let err = |m: &str| Error::Other(format!("invalid wav: {m}"));
557    if data.len() < 44 || &data[0..4] != b"RIFF" || &data[8..12] != b"WAVE" {
558        return Err(err("not a RIFF/WAVE file"));
559    }
560    // Walk chunks to find fmt and data (handles extra chunks before data).
561    let (mut fmt_tag, mut channels, mut sample_rate, mut bits) = (0u16, 0u16, 0u32, 0u16);
562    let mut data_range: Option<(usize, usize)> = None;
563    let mut pos = 12;
564    while pos + 8 <= data.len() {
565        let id = &data[pos..pos + 4];
566        let size = u32::from_le_bytes([data[pos + 4], data[pos + 5], data[pos + 6], data[pos + 7]])
567            as usize;
568        let body = pos + 8;
569        if id == b"fmt " && body + 16 <= data.len() {
570            fmt_tag = u16::from_le_bytes([data[body], data[body + 1]]);
571            channels = u16::from_le_bytes([data[body + 2], data[body + 3]]);
572            sample_rate = u32::from_le_bytes([
573                data[body + 4],
574                data[body + 5],
575                data[body + 6],
576                data[body + 7],
577            ]);
578            bits = u16::from_le_bytes([data[body + 14], data[body + 15]]);
579        } else if id == b"data" {
580            data_range = Some((body, (body + size).min(data.len())));
581        }
582        pos = body + size + (size & 1); // chunks are word-aligned
583    }
584    let (ds, de) = data_range.ok_or_else(|| err("no data chunk"))?;
585    if channels == 0 {
586        return Err(err("zero channels"));
587    }
588    let nch = channels as usize;
589    let mut out: Vec<Vec<f32>> = vec![Vec::new(); nch];
590    let bytes = &data[ds..de];
591    match (fmt_tag, bits) {
592        (3, 32) => {
593            for (i, frame) in bytes.chunks_exact(4 * nch).enumerate() {
594                let _ = i;
595                for (ch, s) in frame.chunks_exact(4).enumerate() {
596                    out[ch].push(f32::from_le_bytes([s[0], s[1], s[2], s[3]]));
597                }
598            }
599        }
600        (1, 16) => {
601            for frame in bytes.chunks_exact(2 * nch) {
602                for (ch, s) in frame.chunks_exact(2).enumerate() {
603                    let v = i16::from_le_bytes([s[0], s[1]]) as f32 / 32768.0;
604                    out[ch].push(v);
605                }
606            }
607        }
608        _ => return Err(err("unsupported format (need 32-bit float or 16-bit PCM)")),
609    }
610    Ok((out, sample_rate))
611}
612
613/// A source that fills a plugin's input buffers each block — a generated test signal or a
614/// preloaded audio file — so effects can be auditioned/rendered with a known input.
615pub trait InputSource: Send {
616    /// Fill `inputs[ch][..frames]` with the next block of audio at `sample_rate`.
617    fn fill(&mut self, inputs: &mut [Vec<f32>], frames: usize, sample_rate: f64);
618}
619
620/// A host-synthesized input signal (no capture device needed). Carries its own cursor so blocks
621/// are continuous across calls.
622#[derive(Debug, Clone)]
623pub enum SignalSource {
624    /// Silence (all zeros).
625    Silence,
626    /// A sine tone at `freq` Hz and linear `amplitude` (0..1).
627    Sine {
628        /// Frequency in Hz.
629        freq: f32,
630        /// Linear amplitude (0..1).
631        amplitude: f32,
632        /// Running phase in radians (cursor; start at 0.0).
633        phase: f64,
634    },
635    /// White noise with linear `amplitude` (0..1).
636    WhiteNoise {
637        /// Linear amplitude (0..1).
638        amplitude: f32,
639        /// xorshift RNG state (cursor; seed non-zero).
640        rng: u64,
641    },
642    /// A preloaded multi-channel sample (e.g. from [`read_wav`]), played from `pos`.
643    Wav {
644        /// Channel samples (`samples[ch][frame]`).
645        samples: std::sync::Arc<Vec<Vec<f32>>>,
646        /// Playback cursor (frame index).
647        pos: usize,
648        /// Loop back to the start at the end instead of going silent.
649        looping: bool,
650    },
651}
652
653impl SignalSource {
654    /// A sine tone.
655    pub fn sine(freq: f32, amplitude: f32) -> Self {
656        SignalSource::Sine {
657            freq,
658            amplitude,
659            phase: 0.0,
660        }
661    }
662    /// White noise (deterministic from a fixed seed).
663    pub fn white_noise(amplitude: f32) -> Self {
664        SignalSource::WhiteNoise {
665            amplitude,
666            rng: 0x9E37_79B9_7F4A_7C15,
667        }
668    }
669    /// A preloaded WAV/sample buffer.
670    pub fn wav(samples: Vec<Vec<f32>>, looping: bool) -> Self {
671        SignalSource::Wav {
672            samples: std::sync::Arc::new(samples),
673            pos: 0,
674            looping,
675        }
676    }
677}
678
679impl InputSource for SignalSource {
680    fn fill(&mut self, inputs: &mut [Vec<f32>], frames: usize, sample_rate: f64) {
681        for ch in inputs.iter_mut() {
682            if ch.len() < frames {
683                ch.resize(frames, 0.0);
684            }
685        }
686        match self {
687            SignalSource::Silence => {
688                for ch in inputs.iter_mut() {
689                    for s in &mut ch[..frames] {
690                        *s = 0.0;
691                    }
692                }
693            }
694            SignalSource::Sine {
695                freq,
696                amplitude,
697                phase,
698            } => {
699                let step = std::f64::consts::TAU * *freq as f64 / sample_rate.max(1.0);
700                for f in 0..frames {
701                    let v = (phase.sin() as f32) * *amplitude;
702                    for ch in inputs.iter_mut() {
703                        ch[f] = v;
704                    }
705                    *phase = (*phase + step) % std::f64::consts::TAU;
706                }
707            }
708            SignalSource::WhiteNoise { amplitude, rng } => {
709                for f in 0..frames {
710                    // xorshift64
711                    let mut x = *rng;
712                    x ^= x << 13;
713                    x ^= x >> 7;
714                    x ^= x << 17;
715                    *rng = x;
716                    // Map to [-1, 1) then scale.
717                    let unit = ((x >> 11) as f64 / (1u64 << 53) as f64) as f32 * 2.0 - 1.0;
718                    let v = unit * *amplitude;
719                    for ch in inputs.iter_mut() {
720                        ch[f] = v;
721                    }
722                }
723            }
724            SignalSource::Wav {
725                samples,
726                pos,
727                looping,
728            } => {
729                let total = samples.iter().map(|c| c.len()).max().unwrap_or(0);
730                for f in 0..frames {
731                    let p = *pos + f;
732                    let src_idx = if total == 0 {
733                        None
734                    } else if p < total {
735                        Some(p)
736                    } else if *looping {
737                        Some(p % total)
738                    } else {
739                        None
740                    };
741                    for (ci, ch) in inputs.iter_mut().enumerate() {
742                        ch[f] = match src_idx {
743                            Some(i) => samples
744                                .get(ci % samples.len().max(1))
745                                .and_then(|c| c.get(i))
746                                .copied()
747                                .unwrap_or(0.0),
748                            None => 0.0,
749                        };
750                    }
751                }
752                *pos += frames;
753            }
754        }
755    }
756}
757
758#[cfg(test)]
759mod wav_tests {
760    use super::*;
761
762    #[test]
763    fn write_wav_has_correct_header_and_size() {
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("vh_write_wav_test.wav");
766        write_wav(&path, &ch, 48_000).unwrap();
767        let bytes = std::fs::read(&path).unwrap();
768        let _ = std::fs::remove_file(&path);
769
770        assert_eq!(&bytes[0..4], b"RIFF");
771        assert_eq!(&bytes[8..12], b"WAVE");
772        assert_eq!(u16::from_le_bytes([bytes[20], bytes[21]]), 3); // IEEE float
773        assert_eq!(u16::from_le_bytes([bytes[22], bytes[23]]), 2); // channels
774        assert_eq!(
775            u32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]),
776            48_000
777        );
778        // 4 frames * 2 ch * 4 bytes = 32 bytes of data; file = 44-byte header + 32.
779        assert_eq!(bytes.len(), 44 + 32);
780    }
781
782    #[test]
783    fn write_then_read_wav_round_trips() {
784        let ch = vec![vec![0.0f32, 0.5, -0.5, 1.0], vec![0.1, 0.2, 0.3, 0.4]];
785        let path = std::env::temp_dir().join(format!("vh_rw_{}.wav", std::process::id()));
786        write_wav(&path, &ch, 44_100).unwrap();
787        let (back, sr) = read_wav(&path).unwrap();
788        let _ = std::fs::remove_file(&path);
789        assert_eq!(sr, 44_100);
790        assert_eq!(back.len(), 2);
791        for (a, b) in ch.iter().zip(back.iter()) {
792            for (x, y) in a.iter().zip(b.iter()) {
793                assert!((x - y).abs() < 1e-6, "{x} vs {y}");
794            }
795        }
796    }
797}
798
799#[cfg(test)]
800mod signal_tests {
801    use super::*;
802
803    #[test]
804    fn sine_starts_at_zero_and_stays_in_amplitude() {
805        let mut src = SignalSource::sine(1000.0, 0.5);
806        let mut inputs = vec![vec![0.0f32; 256], vec![0.0f32; 256]];
807        src.fill(&mut inputs, 256, 48_000.0);
808        assert!(inputs[0][0].abs() < 1e-6, "sine should start at phase 0");
809        for ch in &inputs {
810            assert!(
811                ch.iter().all(|s| s.abs() <= 0.5 + 1e-6),
812                "exceeds amplitude"
813            );
814        }
815        // Both channels get the same (mono) signal.
816        assert_eq!(inputs[0], inputs[1]);
817        // Non-trivial signal (not all zero).
818        assert!(inputs[0].iter().any(|s| s.abs() > 0.1));
819    }
820
821    #[test]
822    fn noise_is_bounded_and_varied() {
823        let mut src = SignalSource::white_noise(0.25);
824        let mut inputs = vec![vec![0.0f32; 512]];
825        src.fill(&mut inputs, 512, 48_000.0);
826        assert!(inputs[0].iter().all(|s| s.abs() <= 0.25 + 1e-6));
827        let first = inputs[0][0];
828        assert!(inputs[0].iter().any(|&s| s != first), "noise should vary");
829    }
830
831    #[test]
832    fn wav_source_advances_and_zero_pads() {
833        let mut src = SignalSource::wav(vec![vec![1.0, 2.0, 3.0]], false);
834        let mut inputs = vec![vec![0.0f32; 5]];
835        src.fill(&mut inputs, 5, 48_000.0);
836        assert_eq!(inputs[0], vec![1.0, 2.0, 3.0, 0.0, 0.0]); // zero-pads past the end
837    }
838
839    #[test]
840    fn wav_source_loops() {
841        let mut src = SignalSource::wav(vec![vec![1.0, 2.0]], true);
842        let mut inputs = vec![vec![0.0f32; 5]];
843        src.fill(&mut inputs, 5, 48_000.0);
844        assert_eq!(inputs[0], vec![1.0, 2.0, 1.0, 2.0, 1.0]); // wraps
845    }
846}
847
848#[cfg(test)]
849mod speaker_arrangement_tests {
850    use super::*;
851
852    #[test]
853    fn channel_counts_match_bitmask() {
854        assert_eq!(SpeakerArrangement::EMPTY.channel_count(), 0);
855        assert_eq!(SpeakerArrangement::MONO.channel_count(), 1);
856        assert_eq!(SpeakerArrangement::STEREO.channel_count(), 2);
857        assert_eq!(SpeakerArrangement::STEREO_SURROUND.channel_count(), 2);
858    }
859
860    #[test]
861    fn raw_round_trips() {
862        let bits = SpeakerArrangement::STEREO.raw();
863        assert_eq!(bits, 0x3);
864        assert_eq!(
865            SpeakerArrangement::from_raw(bits),
866            SpeakerArrangement::STEREO
867        );
868        // Arbitrary 5.1-ish mask: 6 set bits → 6 channels.
869        assert_eq!(SpeakerArrangement::from_raw(0b111111).channel_count(), 6);
870    }
871
872    #[test]
873    fn media_type_and_bus_direction_serde_round_trip() {
874        for mt in [MediaType::Audio, MediaType::Event] {
875            let json = serde_json::to_string(&mt).expect("serialize MediaType");
876            let back: MediaType = serde_json::from_str(&json).expect("deserialize MediaType");
877            assert_eq!(mt, back);
878        }
879        for dir in [BusDirection::Input, BusDirection::Output] {
880            let json = serde_json::to_string(&dir).expect("serialize BusDirection");
881            let back: BusDirection = serde_json::from_str(&json).expect("deserialize BusDirection");
882            assert_eq!(dir, back);
883        }
884    }
885}
886
887#[cfg(test)]
888mod meter_tests {
889    use super::*;
890    use std::time::{Duration, Instant};
891
892    #[test]
893    fn peak_meter_rises_instantly_and_holds() {
894        let mut m = PeakMeter::new(20.0, Duration::from_secs(2));
895        let t0 = Instant::now();
896        m.push(0.7, t0);
897        assert_eq!(m.level(), 0.7);
898        assert_eq!(m.peak_hold(), 0.7);
899
900        // A louder block snaps both up immediately.
901        m.push(0.9, t0 + Duration::from_millis(10));
902        assert_eq!(m.level(), 0.9);
903        assert_eq!(m.peak_hold(), 0.9);
904    }
905
906    #[test]
907    fn peak_meter_level_falls_but_hold_latches() {
908        let mut m = PeakMeter::new(20.0, Duration::from_secs(3));
909        let t0 = Instant::now();
910        m.push(1.0, t0);
911
912        // 0.5 s of silence: 20 dB/s → -10 dB ≈ 0.316 linear. Level fell; hold latched.
913        m.push(0.0, t0 + Duration::from_millis(500));
914        let lvl = m.level();
915        assert!(
916            lvl < 1.0 && lvl > 0.0,
917            "level should be mid-fall, got {lvl}"
918        );
919        assert!((lvl - 0.316).abs() < 0.02, "≈-10 dB expected, got {lvl}");
920        assert_eq!(m.peak_hold(), 1.0, "hold must latch within its window");
921    }
922
923    #[test]
924    fn peak_meter_hold_falls_after_window() {
925        let mut m = PeakMeter::new(20.0, Duration::from_secs(1));
926        let t0 = Instant::now();
927        m.push(1.0, t0);
928        // Past the 1 s hold window, with continued silence the marker starts falling too.
929        m.push(0.0, t0 + Duration::from_millis(1500));
930        assert!(
931            m.peak_hold() < 1.0,
932            "hold should fall after the window expired, got {}",
933            m.peak_hold()
934        );
935    }
936
937    #[test]
938    fn peak_meter_reaches_silence_floor() {
939        let mut m = PeakMeter::new(60.0, Duration::from_millis(0));
940        let t0 = Instant::now();
941        m.push(0.5, t0);
942        // A long gap of silence fully empties the meter (snaps to exactly 0).
943        m.push(0.0, t0 + Duration::from_secs(10));
944        assert_eq!(m.level(), 0.0);
945        assert_eq!(m.peak_hold(), 0.0);
946    }
947
948    #[test]
949    fn rms_window_constant_signal() {
950        let mut r = RmsWindow::new(8);
951        for _ in 0..8 {
952            r.push_sample(0.5);
953        }
954        assert!((r.rms() - 0.5).abs() < 1e-6);
955        assert_eq!(r.len(), 8);
956    }
957
958    #[test]
959    fn rms_window_slides_and_evicts() {
960        let mut r = RmsWindow::new(3);
961        r.push_block(&[1.0, 1.0, 1.0]);
962        assert!((r.rms() - 1.0).abs() < 1e-6);
963        // Push three zeros: the loud samples are evicted, RMS returns to 0.
964        r.push_block(&[0.0, 0.0, 0.0]);
965        assert_eq!(r.len(), 3);
966        assert!(
967            r.rms() < 1e-6,
968            "window should have slid to silence, got {}",
969            r.rms()
970        );
971    }
972
973    #[test]
974    fn rms_window_empty_is_zero() {
975        let r = RmsWindow::new(16);
976        assert!(r.is_empty());
977        assert_eq!(r.rms(), 0.0);
978    }
979
980    #[test]
981    fn rms_window_from_duration_sizes_correctly() {
982        // 10 ms at 48 kHz = 480 samples.
983        let r = RmsWindow::from_duration(0.01, 48_000.0);
984        assert_eq!(r.capacity, 480);
985    }
986}