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/// Configuration of one VST3 audio bus.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
58pub struct AudioBusConfig {
59    /// Number of channels advertised for this bus.
60    pub channel_count: usize,
61    /// Whether the bus is currently active in the component.
62    pub active: bool,
63}
64
65/// Current audio-bus configuration, preserving every VST3 bus index.
66#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
67pub struct AudioBusLayout {
68    /// Input buses in VST3 bus-index order.
69    pub inputs: Vec<AudioBusConfig>,
70    /// Output buses in VST3 bus-index order.
71    pub outputs: Vec<AudioBusConfig>,
72}
73
74/// Sample storage for one VST3 audio bus.
75#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
76pub struct AudioBusBuffer {
77    /// Whether this bus was active when the buffer set was created.
78    ///
79    /// Processing validates this against the plug-in's current activation state. Recreate the
80    /// buffer set after changing bus activation.
81    pub active: bool,
82    /// Samples indexed `[channel][sample]`. Inactive buses retain their advertised channels so
83    /// their bus index and shape are never lost; their inputs are ignored and outputs silenced.
84    #[serde(with = "crate::process_isolation::audio_codec")]
85    pub channels: Vec<Vec<f32>>,
86}
87
88impl AudioBusBuffer {
89    /// Allocate a silent bus with the requested shape.
90    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/// Bus-aware audio buffers preserving every input/output bus as a distinct slot.
99#[derive(Debug, Clone, PartialEq)]
100pub struct BusAudioBuffers {
101    /// Input buses in VST3 bus-index order.
102    pub inputs: Vec<AudioBusBuffer>,
103    /// Output buses in VST3 bus-index order.
104    pub outputs: Vec<AudioBusBuffer>,
105    /// Sample rate in Hz.
106    pub sample_rate: f64,
107    /// Nominal number of samples per channel.
108    pub block_size: usize,
109}
110
111impl BusAudioBuffers {
112    /// Allocate silent buffers from a previously queried [`AudioBusLayout`].
113    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    /// Clear all input and output buses to silence.
126    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/// Audio level information for a single channel
136#[derive(Debug, Clone, Copy)]
137pub struct ChannelLevel {
138    /// Peak level (0.0 to 1.0, where 1.0 = 0dB)
139    pub peak: f32,
140    /// RMS level (0.0 to 1.0)
141    pub rms: f32,
142    /// Peak hold level (0.0 to 1.0)
143    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    /// Convert peak level to decibels
158    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    /// Convert RMS level to decibels
167    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    /// Check if the signal is clipping (> 0dB)
176    pub fn is_clipping(&self) -> bool {
177        self.peak > 1.0
178    }
179}
180
181/// Audio level information for all channels
182#[derive(Debug, Clone)]
183pub struct AudioLevels {
184    /// Level information for each channel
185    pub channels: Vec<ChannelLevel>,
186}
187
188impl AudioLevels {
189    /// Create new audio levels for the given number of channels
190    pub fn new(channel_count: usize) -> Self {
191        Self {
192            channels: vec![ChannelLevel::default(); channel_count],
193        }
194    }
195
196    /// Update levels from audio buffers
197    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            // Calculate peak
204            let peak = buffer.iter().map(|&x| x.abs()).fold(0.0f32, f32::max);
205
206            // Calculate RMS (guard against a zero-length channel buffer → 0/0 = NaN).
207            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            // Update channel levels
215            let channel = &mut self.channels[i];
216            channel.peak = peak;
217            channel.rms = rms;
218
219            // Update peak hold if necessary
220            if peak > channel.peak_hold {
221                channel.peak_hold = peak;
222            }
223        }
224    }
225
226    /// Update levels from active output buses, preserving bus/channel order.
227    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    /// Reset peak hold values
258    pub fn reset_peak_hold(&mut self) {
259        for channel in &mut self.channels {
260            channel.peak_hold = channel.peak;
261        }
262    }
263
264    /// Check if any channel is clipping
265    pub fn is_clipping(&self) -> bool {
266        self.channels.iter().any(|ch| ch.is_clipping())
267    }
268}
269
270/// A VST3 speaker arrangement: a bitmask where each set bit is one channel (so the channel
271/// count is the number of set bits). Wraps the SDK's `SpeakerArrangement` (a `u64` bitmask);
272/// use the named constants or [`from_raw`](Self::from_raw).
273#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
274pub struct SpeakerArrangement(pub u64);
275
276impl SpeakerArrangement {
277    /// No channels (`kEmpty`).
278    pub const EMPTY: Self = Self(0);
279    /// Mono (`kMono` = front-center).
280    pub const MONO: Self = Self(0x0008_0000);
281    /// Stereo L/R (`kStereo`).
282    pub const STEREO: Self = Self(0x3);
283    /// Stereo surround Ls/Rs (`kStereoSurround`).
284    pub const STEREO_SURROUND: Self = Self(0x30);
285
286    /// Wrap a raw VST3 `SpeakerArrangement` bitmask.
287    pub fn from_raw(bits: u64) -> Self {
288        Self(bits)
289    }
290
291    /// The raw VST3 bitmask.
292    pub fn raw(self) -> u64 {
293        self.0
294    }
295
296    /// Number of channels in this arrangement (the count of set bits).
297    pub fn channel_count(self) -> usize {
298        self.0.count_ones() as usize
299    }
300}
301
302/// The kind of data a VST3 bus carries: PCM audio or events (MIDI). Maps to the SDK's
303/// `MediaTypes` (`kAudio` / `kEvent`).
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
305pub enum MediaType {
306    /// Audio (PCM sample) buses (`kAudio`).
307    Audio,
308    /// Event / MIDI buses (`kEvent`).
309    Event,
310}
311
312/// Which side of the plugin a bus sits on: input or output. Maps to the SDK's
313/// `BusDirections` (`kInput` / `kOutput`).
314#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
315pub enum BusDirection {
316    /// An input bus (`kInput`).
317    Input,
318    /// An output bus (`kOutput`).
319    Output,
320}
321
322/// The speaker arrangements of a plugin's audio input and output buses.
323#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
324pub struct BusArrangements {
325    /// Arrangement of each audio input bus, in bus-index order.
326    pub inputs: Vec<SpeakerArrangement>,
327    /// Arrangement of each audio output bus, in bus-index order.
328    pub outputs: Vec<SpeakerArrangement>,
329}
330
331/// A single-channel peak meter with falling ballistics and a timed peak-hold marker —
332/// the behaviour a level meter UI wants but [`AudioLevels`]'s sticky `peak_hold` doesn't give.
333///
334/// Time is **injected** ([`push`](Self::push) takes `now: Instant`) so the meter is
335/// deterministic and independent of any clock — pass `Instant::now()` from real code, or
336/// synthetic instants in tests. Feed it the per-block peak amplitude; read [`level`](Self::level)
337/// for the falling meter value and [`peak_hold`](Self::peak_hold) for the held marker.
338///
339/// ```
340/// use std::time::{Duration, Instant};
341/// use vst3_host::audio::PeakMeter;
342///
343/// let mut meter = PeakMeter::new(20.0, Duration::from_secs(2)); // 20 dB/s fall, 2 s hold
344/// let t0 = Instant::now();
345/// meter.push(0.8, t0);
346/// assert_eq!(meter.level(), 0.8);
347/// // After silence the displayed level falls but the hold marker stays put (within the window).
348/// meter.push(0.0, t0 + Duration::from_millis(100));
349/// assert!(meter.level() < 0.8 && meter.level() > 0.0);
350/// assert_eq!(meter.peak_hold(), 0.8);
351/// ```
352#[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    /// Below this the level snaps to exactly 0.0 (≈ -100 dB), so a meter fully empties
364    /// instead of asymptotically approaching zero forever.
365    const SILENCE: f32 = 1e-5;
366
367    /// Create a meter that falls at `fall_db_per_sec` decibels per second and holds the peak
368    /// marker for `hold` before it, too, begins to fall. A typical UI meter uses ~20 dB/s and
369    /// a 1–3 second hold.
370    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    /// Linear gain after falling for `dt`, e.g. `10^(-(dB/s · dt)/20)`.
382    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    /// Update with a new block's peak amplitude (`0.0..`) observed at `now`. The displayed
388    /// level rises instantly to a louder peak and decays toward quieter input; the hold marker
389    /// latches the loudest value and only starts falling once `hold` has elapsed since it was set.
390    pub fn push(&mut self, block_peak: f32, now: std::time::Instant) {
391        // Treat non-finite input (NaN/±inf from a misbehaving plugin) as silence so it can't
392        // permanently poison the meter — `inf * decay` stays inf and would never fall.
393        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            // New loudest value — latch it and restart the hold timer.
410            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            // Hold window expired — the marker falls at the same ballistic, never below `level`.
417            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    /// The current falling-meter level (`0.0..`).
427    pub fn level(&self) -> f32 {
428        self.level
429    }
430
431    /// The held peak marker (`0.0..`).
432    pub fn peak_hold(&self) -> f32 {
433        self.peak_hold
434    }
435
436    /// Reset the meter to silence.
437    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/// A moving-window RMS estimator over the most recent `N` samples.
446///
447/// Unlike [`AudioLevels`]'s per-block RMS (which resets every buffer), this gives a smooth
448/// level over a fixed time window regardless of block size — feed it samples or whole blocks
449/// and read [`rms`](Self::rms). The window length in samples is `window_secs · sample_rate`.
450///
451/// ```
452/// use vst3_host::audio::RmsWindow;
453///
454/// let mut rms = RmsWindow::new(4);
455/// for _ in 0..4 { rms.push_sample(0.5); }
456/// assert!((rms.rms() - 0.5).abs() < 1e-6); // constant 0.5 → RMS 0.5
457/// ```
458#[derive(Debug, Clone)]
459pub struct RmsWindow {
460    capacity: usize,
461    squares: std::collections::VecDeque<f32>,
462    // f64 accumulator so a meter running for the lifetime of a stream (millions of
463    // add/subtract cycles) doesn't drift from f32 rounding error.
464    sum: f64,
465}
466
467impl RmsWindow {
468    /// Create a window holding the most recent `window_samples` samples (minimum 1).
469    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    /// Create a window sized for `window_secs` of audio at `sample_rate` Hz.
479    ///
480    /// The sample count is clamped: a non-finite or absurd duration/rate would otherwise saturate
481    /// to `usize::MAX` and panic in `VecDeque::with_capacity`.
482    pub fn from_duration(window_secs: f32, sample_rate: f64) -> Self {
483        const MAX_WINDOW_SAMPLES: f64 = (1u64 << 26) as f64; // ~23 min at 48 kHz; 256 MiB of f32
484        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    /// Add one sample, evicting the oldest if the window is full.
494    pub fn push_sample(&mut self, sample: f32) {
495        // Treat a non-finite square as silence. The running sum is an accumulator: a single NaN
496        // (or an overflow to infinity from a huge sample) poisons it permanently, because evicting
497        // that entry subtracts NaN again. `rms()`'s `max(0.0)` guard then reports NaN as 0.0, so
498        // the meter would read digital silence for the rest of its life while audio flows.
499        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    /// Add a whole block of samples.
511    pub fn push_block(&mut self, block: &[f32]) {
512        for &s in block {
513            self.push_sample(s);
514        }
515    }
516
517    /// Current RMS over the samples in the window (`0.0` when empty).
518    pub fn rms(&self) -> f32 {
519        if self.squares.is_empty() {
520            return 0.0;
521        }
522        // Guard against tiny negative drift from float subtraction.
523        (self.sum.max(0.0) / self.squares.len() as f64).sqrt() as f32
524    }
525
526    /// Number of samples currently in the window.
527    pub fn len(&self) -> usize {
528        self.squares.len()
529    }
530
531    /// Whether the window holds no samples yet.
532    pub fn is_empty(&self) -> bool {
533        self.squares.is_empty()
534    }
535
536    /// Drop all samples.
537    pub fn clear(&mut self) {
538        self.squares.clear();
539        self.sum = 0.0;
540    }
541}
542
543/// Audio processing configuration
544#[derive(Debug, Clone, Copy)]
545pub struct AudioConfig {
546    /// Sample rate in Hz
547    pub sample_rate: f64,
548    /// Block size in samples
549    pub block_size: usize,
550    /// Number of input channels
551    pub input_channels: usize,
552    /// Number of output channels
553    pub output_channels: usize,
554    /// Transport tempo in beats per minute, advertised to plugins in the host
555    /// `ProcessContext` (drives tempo-synced DSP such as LFOs and synced delays).
556    pub tempo: f64,
557    /// Time signature numerator (beats per bar), advertised in the `ProcessContext`.
558    pub time_sig_numerator: i32,
559    /// Time signature denominator (note value of one beat), advertised in the
560    /// `ProcessContext`.
561    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
578/// Audio stream trait for controlling playback.
579///
580/// Deliberately **not** `Send`: real backends (cpal among them) make their stream handle
581/// thread-affine — construction, `play`/`pause` and teardown must all happen on the thread
582/// that opened the device. Implementations that *are* movable simply also implement `Send`;
583/// nothing here takes that away.
584pub trait AudioStream {
585    /// Start playback
586    fn play(&self) -> Result<(), Box<dyn std::error::Error>>;
587
588    /// Pause playback
589    fn pause(&self) -> Result<(), Box<dyn std::error::Error>>;
590}
591
592/// Audio backend trait for creating audio streams
593#[allow(clippy::type_complexity)] // Box<dyn FnMut...> callbacks are intrinsic to the API
594pub trait AudioBackend: Send + Sync {
595    /// The stream type this backend produces. Not required to be `Send` — see [`AudioStream`].
596    type Stream: AudioStream + 'static;
597    /// The device type this backend uses
598    type Device: Send + Sync;
599    /// The error type this backend returns
600    type Error: std::error::Error + Send + Sync + 'static;
601
602    /// Enumerate available output devices
603    fn enumerate_output_devices(&self) -> Result<Vec<Self::Device>, Self::Error>;
604
605    /// Enumerate available input devices
606    fn enumerate_input_devices(&self) -> Result<Vec<Self::Device>, Self::Error>;
607
608    /// Get the default output device
609    fn default_output_device(&self) -> Option<Self::Device>;
610
611    /// Get the default input device
612    fn default_input_device(&self) -> Option<Self::Device>;
613
614    /// Create an output stream
615    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    /// Create an input stream
624    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
633/// Write deinterleaved channel buffers to a 32-bit float WAV file (`WAVE_FORMAT_IEEE_FLOAT`).
634///
635/// `channels[ch][frame]`; all channels must be the same length. Used by offline rendering
636/// (e.g. [`crate::simple::render_to_wav`]) and audio export. No external dependency.
637pub 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()); // IEEE float
659    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    // Interleave channels frame by frame.
667    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
680/// Read a WAV file written as 32-bit float (`WAVE_FORMAT_IEEE_FLOAT`) or 16-bit PCM, returning
681/// deinterleaved channels (`channels[ch][frame]`) and the sample rate. The inverse of
682/// [`write_wav`]; used to feed a recorded signal into a plugin's input.
683pub 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    // Walk chunks to find fmt and data (handles extra chunks before data).
691    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); // chunks are word-aligned
713    }
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
743/// A source that fills a plugin's input buffers each block — a generated test signal or a
744/// preloaded audio file — so effects can be auditioned/rendered with a known input.
745pub trait InputSource: Send {
746    /// Fill `inputs[ch][..frames]` with the next block of audio at `sample_rate`.
747    fn fill(&mut self, inputs: &mut [Vec<f32>], frames: usize, sample_rate: f64);
748}
749
750/// A host-synthesized input signal (no capture device needed). Carries its own cursor so blocks
751/// are continuous across calls.
752#[derive(Debug, Clone)]
753pub enum SignalSource {
754    /// Silence (all zeros).
755    Silence,
756    /// A sine tone at `freq` Hz and linear `amplitude` (0..1).
757    Sine {
758        /// Frequency in Hz.
759        freq: f32,
760        /// Linear amplitude (0..1).
761        amplitude: f32,
762        /// Running phase in radians (cursor; start at 0.0).
763        phase: f64,
764    },
765    /// White noise with linear `amplitude` (0..1).
766    WhiteNoise {
767        /// Linear amplitude (0..1).
768        amplitude: f32,
769        /// xorshift RNG state (cursor; seed non-zero).
770        rng: u64,
771    },
772    /// A preloaded multi-channel sample (e.g. from [`read_wav`]), played from `pos`.
773    Wav {
774        /// Channel samples (`samples[ch][frame]`).
775        samples: std::sync::Arc<Vec<Vec<f32>>>,
776        /// Playback cursor (frame index).
777        pos: usize,
778        /// Loop back to the start at the end instead of going silent.
779        looping: bool,
780    },
781}
782
783impl SignalSource {
784    /// A sine tone.
785    pub fn sine(freq: f32, amplitude: f32) -> Self {
786        SignalSource::Sine {
787            freq,
788            amplitude,
789            phase: 0.0,
790        }
791    }
792    /// White noise (deterministic from a fixed seed).
793    pub fn white_noise(amplitude: f32) -> Self {
794        SignalSource::WhiteNoise {
795            amplitude,
796            rng: 0x9E37_79B9_7F4A_7C15,
797        }
798    }
799    /// A preloaded WAV/sample buffer.
800    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                    // xorshift64
841                    let mut x = *rng;
842                    x ^= x << 13;
843                    x ^= x >> 7;
844                    x ^= x << 17;
845                    *rng = x;
846                    // Map to [-1, 1) then scale.
847                    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); // IEEE float
903        assert_eq!(u16::from_le_bytes([bytes[22], bytes[23]]), 2); // channels
904        assert_eq!(
905            u32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]),
906            48_000
907        );
908        // 4 frames * 2 ch * 4 bytes = 32 bytes of data; file = 44-byte header + 32.
909        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        // Both channels get the same (mono) signal.
946        assert_eq!(inputs[0], inputs[1]);
947        // Non-trivial signal (not all zero).
948        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]); // zero-pads past the end
967    }
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]); // wraps
975    }
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        // Arbitrary 5.1-ish mask: 6 set bits → 6 channels.
999        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        // A louder block snaps both up immediately.
1031        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        // 0.5 s of silence: 20 dB/s → -10 dB ≈ 0.316 linear. Level fell; hold latched.
1043        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        // Past the 1 s hold window, with continued silence the marker starts falling too.
1059        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        // A long gap of silence fully empties the meter (snaps to exactly 0).
1073        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        // Push three zeros: the loud samples are evicted, RMS returns to 0.
1094        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        // 10 ms at 48 kHz = 480 samples.
1113        let r = RmsWindow::from_duration(0.01, 48_000.0);
1114        assert_eq!(r.capacity, 480);
1115    }
1116
1117    /// The running sum is an accumulator, so a single non-finite square would poison it for the
1118    /// life of the window — and `rms()`'s `max(0.0)` guard renders NaN as `0.0`, i.e. the meter
1119    /// silently reads digital silence forever while full-scale audio flows through it.
1120    #[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        // Same through the block API, with the poison still inside the window.
1134        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    /// A non-finite or absurd duration/rate saturates to `usize::MAX` and panics inside
1140    /// `VecDeque::with_capacity`; `from_duration` takes plain `f32`/`f64` from the caller.
1141    #[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}