Skip to main content

quiver/
extended_io.rs

1//! Extended I/O
2//!
3//! This module provides extended I/O capabilities:
4//! - OSC (Open Sound Control) protocol support
5//! - VST/AU plugin wrapper infrastructure
6//! - Web Audio backend interface
7//!
8//! # OSC Support
9//!
10//! The OSC implementation provides a bridge between network OSC messages
11//! and the patch graph, allowing remote control of synthesizer parameters.
12//!
13//! # Plugin Wrapper
14//!
15//! The plugin wrapper infrastructure provides the foundation for building
16//! VST3/AU/LV2 plugins from Quiver patches.
17//!
18//! # Web Audio
19//!
20//! The Web Audio interface provides traits and structures for integrating
21//! Quiver with WebAssembly-based audio processing.
22
23use crate::io::AtomicF64;
24use crate::port::{GraphModule, PortDef, PortSpec, PortValues, SignalKind};
25use std::collections::HashMap;
26use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
27use std::sync::Arc;
28
29// ============================================================================
30// OSC Protocol Support
31// ============================================================================
32
33/// OSC message types
34#[derive(Debug, Clone)]
35pub enum OscValue {
36    /// 32-bit integer
37    Int(i32),
38    /// 32-bit float
39    Float(f32),
40    /// String
41    String(String),
42    /// Blob (binary data)
43    Blob(Vec<u8>),
44    /// True boolean
45    True,
46    /// False boolean
47    False,
48    /// Nil/null
49    Nil,
50    /// Infinitum/bang
51    Infinitum,
52    /// 64-bit integer
53    Long(i64),
54    /// 64-bit float
55    Double(f64),
56}
57
58impl OscValue {
59    /// Convert to f64 for CV use
60    pub fn to_f64(&self) -> Option<f64> {
61        match self {
62            OscValue::Int(v) => Some(*v as f64),
63            OscValue::Float(v) => Some(*v as f64),
64            OscValue::Long(v) => Some(*v as f64),
65            OscValue::Double(v) => Some(*v),
66            OscValue::True => Some(1.0),
67            OscValue::False => Some(0.0),
68            _ => None,
69        }
70    }
71
72    /// Convert to bool
73    pub fn to_bool(&self) -> Option<bool> {
74        match self {
75            OscValue::Int(v) => Some(*v != 0),
76            OscValue::Float(v) => Some(*v != 0.0),
77            OscValue::True => Some(true),
78            OscValue::False => Some(false),
79            _ => None,
80        }
81    }
82}
83
84/// An OSC message with address and arguments
85#[derive(Debug, Clone)]
86pub struct OscMessage {
87    /// OSC address pattern (e.g., "/synth/filter/cutoff")
88    pub address: String,
89    /// Message arguments
90    pub args: Vec<OscValue>,
91}
92
93impl OscMessage {
94    /// Create a new OSC message
95    pub fn new(address: impl Into<String>) -> Self {
96        Self {
97            address: address.into(),
98            args: Vec::new(),
99        }
100    }
101
102    /// Add an argument
103    pub fn with_arg(mut self, arg: OscValue) -> Self {
104        self.args.push(arg);
105        self
106    }
107
108    /// Add a float argument
109    pub fn with_float(self, value: f32) -> Self {
110        self.with_arg(OscValue::Float(value))
111    }
112
113    /// Add an int argument
114    pub fn with_int(self, value: i32) -> Self {
115        self.with_arg(OscValue::Int(value))
116    }
117
118    /// Get the first argument as f64
119    pub fn first_f64(&self) -> Option<f64> {
120        self.args.first().and_then(|v| v.to_f64())
121    }
122}
123
124/// OSC address pattern matcher implementing OSC 1.0 wildcard semantics.
125///
126/// Matching is **component-scoped** (`*` and `?` never cross a `/`), and
127/// supports character classes `[a-z]` with ranges, negation `[!...]`, and
128/// alternation `{foo,bar}`. Malformed patterns (an unterminated `[` or `{`,
129/// or an empty class) fail to match rather than silently swallowing the rest
130/// of the pattern.
131pub struct OscPattern {
132    /// Pattern split into path components (each free of `/`).
133    components: Vec<String>,
134}
135
136impl OscPattern {
137    /// Parse an OSC address pattern.
138    pub fn new(pattern: &str) -> Self {
139        let components = pattern
140            .split('/')
141            .filter(|s| !s.is_empty())
142            .map(String::from)
143            .collect();
144        Self { components }
145    }
146
147    /// Check if an address matches this pattern.
148    ///
149    /// Both sides are split into path components; each address component must
150    /// match the pattern component at the same position, and the component
151    /// counts must be equal (OSC has no cross-component `*`).
152    pub fn matches(&self, address: &str) -> bool {
153        let parts: Vec<&str> = address.split('/').filter(|s| !s.is_empty()).collect();
154        if parts.len() != self.components.len() {
155            return false;
156        }
157        self.components
158            .iter()
159            .zip(parts.iter())
160            .all(|(pat, part)| component_matches(pat, part))
161    }
162}
163
164/// Match a single path component against an OSC pattern component.
165fn component_matches(pattern: &str, text: &str) -> bool {
166    let pat: Vec<char> = pattern.chars().collect();
167    let txt: Vec<char> = text.chars().collect();
168    glob_match(&pat, &txt)
169}
170
171/// Recursive OSC glob matcher over the remaining pattern and text.
172fn glob_match(pat: &[char], text: &[char]) -> bool {
173    let Some((&c, rest)) = pat.split_first() else {
174        return text.is_empty();
175    };
176
177    match c {
178        '*' => {
179            // Match zero or more characters (never `/`, already split out).
180            (0..=text.len()).any(|i| glob_match(rest, &text[i..]))
181        }
182        '?' => !text.is_empty() && glob_match(rest, &text[1..]),
183        '[' => match parse_class(pat) {
184            Some((class, consumed)) => {
185                !text.is_empty()
186                    && class_matches(&class, text[0])
187                    && glob_match(&pat[consumed..], &text[1..])
188            }
189            // Malformed class => fail to match.
190            None => false,
191        },
192        '{' => match parse_alternation(pat) {
193            Some((alts, consumed)) => alts.iter().any(|alt| {
194                strip_prefix(alt, text).is_some_and(|rem| glob_match(&pat[consumed..], rem))
195            }),
196            // Malformed alternation => fail to match.
197            None => false,
198        },
199        _ => !text.is_empty() && text[0] == c && glob_match(rest, &text[1..]),
200    }
201}
202
203/// One entry of a character class.
204enum ClassItem {
205    Char(char),
206    Range(char, char),
207}
208
209/// A parsed `[...]` character class.
210struct CharClass {
211    negated: bool,
212    items: Vec<ClassItem>,
213}
214
215/// Parse a `[...]` class beginning at `pat[0] == '['`.
216///
217/// Returns the class and the number of pattern chars consumed (including the
218/// closing `]`), or `None` if the class is unterminated or empty (malformed).
219fn parse_class(pat: &[char]) -> Option<(CharClass, usize)> {
220    debug_assert_eq!(pat.first(), Some(&'['));
221    let mut i = 1;
222    let mut negated = false;
223    if matches!(pat.get(i), Some('!') | Some('^')) {
224        negated = true;
225        i += 1;
226    }
227
228    let mut items = Vec::new();
229    let mut closed = false;
230    while i < pat.len() {
231        if pat[i] == ']' {
232            closed = true;
233            i += 1;
234            break;
235        }
236        // A range `a-z`: a '-' between two chars, where the char after '-' is
237        // not the closing ']'.
238        if i + 2 < pat.len() && pat[i + 1] == '-' && pat[i + 2] != ']' {
239            items.push(ClassItem::Range(pat[i], pat[i + 2]));
240            i += 3;
241        } else {
242            items.push(ClassItem::Char(pat[i]));
243            i += 1;
244        }
245    }
246
247    if !closed || items.is_empty() {
248        return None;
249    }
250    Some((CharClass { negated, items }, i))
251}
252
253/// Test a character against a parsed class.
254fn class_matches(class: &CharClass, ch: char) -> bool {
255    let mut hit = false;
256    for item in &class.items {
257        match item {
258            ClassItem::Char(c) => {
259                if *c == ch {
260                    hit = true;
261                    break;
262                }
263            }
264            ClassItem::Range(a, b) => {
265                let (lo, hi) = if a <= b { (*a, *b) } else { (*b, *a) };
266                if ch >= lo && ch <= hi {
267                    hit = true;
268                    break;
269                }
270            }
271        }
272    }
273    hit ^ class.negated
274}
275
276/// Parse a `{a,b,c}` alternation beginning at `pat[0] == '{'`.
277///
278/// Returns the (literal) alternatives and the number of pattern chars consumed
279/// (including the closing `}`), or `None` if unterminated (malformed).
280fn parse_alternation(pat: &[char]) -> Option<(Vec<Vec<char>>, usize)> {
281    debug_assert_eq!(pat.first(), Some(&'{'));
282    let mut i = 1;
283    let mut alts = Vec::new();
284    let mut current = Vec::new();
285    let mut closed = false;
286    while i < pat.len() {
287        match pat[i] {
288            '}' => {
289                alts.push(current);
290                closed = true;
291                i += 1;
292                break;
293            }
294            ',' => {
295                alts.push(core::mem::take(&mut current));
296                i += 1;
297            }
298            c => {
299                current.push(c);
300                i += 1;
301            }
302        }
303    }
304    if !closed {
305        return None;
306    }
307    Some((alts, i))
308}
309
310/// If `text` starts with the literal `prefix`, return the remaining text.
311fn strip_prefix<'a>(prefix: &[char], text: &'a [char]) -> Option<&'a [char]> {
312    if text.len() >= prefix.len() && text[..prefix.len()] == *prefix {
313        Some(&text[prefix.len()..])
314    } else {
315        None
316    }
317}
318
319/// Binding between an OSC address and a parameter
320pub struct OscBinding {
321    /// OSC address pattern
322    pub pattern: OscPattern,
323    /// Target value
324    pub value: Arc<AtomicF64>,
325    /// Optional scale factor
326    pub scale: f64,
327    /// Optional offset
328    pub offset: f64,
329}
330
331impl OscBinding {
332    /// Create a new binding
333    pub fn new(pattern: &str, value: Arc<AtomicF64>) -> Self {
334        Self {
335            pattern: OscPattern::new(pattern),
336            value,
337            scale: 1.0,
338            offset: 0.0,
339        }
340    }
341
342    /// Set scale factor
343    pub fn with_scale(mut self, scale: f64) -> Self {
344        self.scale = scale;
345        self
346    }
347
348    /// Set offset
349    pub fn with_offset(mut self, offset: f64) -> Self {
350        self.offset = offset;
351        self
352    }
353
354    /// Apply a message to this binding
355    pub fn apply(&self, msg: &OscMessage) -> bool {
356        if !self.pattern.matches(&msg.address) {
357            return false;
358        }
359
360        if let Some(v) = msg.first_f64() {
361            self.value.set(v * self.scale + self.offset);
362            return true;
363        }
364
365        false
366    }
367}
368
369/// OSC receiver that routes messages to bindings
370pub struct OscReceiver {
371    /// Registered bindings
372    bindings: Vec<OscBinding>,
373    /// Counter for total messages received
374    message_count: AtomicU32,
375    /// Counter for messages that matched at least one binding
376    matched_count: AtomicU32,
377}
378
379impl OscReceiver {
380    /// Create a new OSC receiver
381    pub fn new() -> Self {
382        Self {
383            bindings: Vec::new(),
384            message_count: AtomicU32::new(0),
385            matched_count: AtomicU32::new(0),
386        }
387    }
388
389    /// Add a binding
390    pub fn add_binding(&mut self, binding: OscBinding) {
391        self.bindings.push(binding);
392    }
393
394    /// Create a binding for a parameter
395    pub fn bind(&mut self, pattern: &str, value: Arc<AtomicF64>) {
396        self.add_binding(OscBinding::new(pattern, value));
397    }
398
399    /// Create a scaled binding (e.g., 0-1 to 20-20000 Hz)
400    pub fn bind_scaled(&mut self, pattern: &str, value: Arc<AtomicF64>, scale: f64, offset: f64) {
401        self.add_binding(
402            OscBinding::new(pattern, value)
403                .with_scale(scale)
404                .with_offset(offset),
405        );
406    }
407
408    /// Process an OSC message
409    /// Returns true if at least one binding matched
410    pub fn handle_message(&self, msg: &OscMessage) -> bool {
411        self.message_count.fetch_add(1, Ordering::Relaxed);
412        let mut handled = false;
413        for binding in &self.bindings {
414            if binding.apply(msg) {
415                handled = true;
416            }
417        }
418        if handled {
419            self.matched_count.fetch_add(1, Ordering::Relaxed);
420        }
421        handled
422    }
423
424    /// Get the number of bindings
425    pub fn binding_count(&self) -> usize {
426        self.bindings.len()
427    }
428
429    /// Get the total number of messages received
430    pub fn message_count(&self) -> u32 {
431        self.message_count.load(Ordering::Relaxed)
432    }
433
434    /// Get the number of messages that matched at least one binding
435    pub fn matched_count(&self) -> u32 {
436        self.matched_count.load(Ordering::Relaxed)
437    }
438
439    /// Reset the message counters
440    pub fn reset_counters(&self) {
441        self.message_count.store(0, Ordering::Relaxed);
442        self.matched_count.store(0, Ordering::Relaxed);
443    }
444}
445
446impl Default for OscReceiver {
447    fn default() -> Self {
448        Self::new()
449    }
450}
451
452/// OSC input module for patch graphs
453pub struct OscInput {
454    /// Target value
455    value: Arc<AtomicF64>,
456    /// Port specification
457    spec: PortSpec,
458    /// OSC address (for documentation)
459    address: String,
460}
461
462impl OscInput {
463    /// Create a new OSC input
464    pub fn new(address: impl Into<String>, value: Arc<AtomicF64>, kind: SignalKind) -> Self {
465        Self {
466            value,
467            spec: PortSpec {
468                inputs: vec![],
469                outputs: vec![PortDef::new(0, "out", kind)],
470            },
471            address: address.into(),
472        }
473    }
474
475    /// Get the OSC address
476    pub fn address(&self) -> &str {
477        &self.address
478    }
479
480    /// Get reference to the value
481    pub fn value_ref(&self) -> &Arc<AtomicF64> {
482        &self.value
483    }
484}
485
486impl GraphModule for OscInput {
487    fn port_spec(&self) -> &PortSpec {
488        &self.spec
489    }
490
491    fn tick(&mut self, _inputs: &PortValues, outputs: &mut PortValues) {
492        outputs.set(0, self.value.get());
493    }
494
495    fn reset(&mut self) {}
496
497    fn set_sample_rate(&mut self, _: f64) {}
498
499    fn type_id(&self) -> &'static str {
500        "osc_input"
501    }
502}
503
504// ============================================================================
505// Plugin Wrapper Infrastructure
506// ============================================================================
507
508/// Plugin parameter definition
509#[derive(Debug, Clone)]
510pub struct PluginParameter {
511    /// Parameter ID
512    pub id: u32,
513    /// Display name
514    pub name: String,
515    /// Short name (for limited displays)
516    pub short_name: String,
517    /// Minimum value
518    pub min: f64,
519    /// Maximum value
520    pub max: f64,
521    /// Default value
522    pub default: f64,
523    /// Unit label (e.g., "Hz", "dB", "%")
524    pub unit: String,
525    /// Number of steps (0 = continuous)
526    pub steps: u32,
527}
528
529impl PluginParameter {
530    /// Create a new continuous parameter
531    pub fn new(id: u32, name: &str, min: f64, max: f64, default: f64) -> Self {
532        Self {
533            id,
534            name: name.to_string(),
535            short_name: name.chars().take(8).collect(),
536            min,
537            max,
538            default,
539            unit: String::new(),
540            steps: 0,
541        }
542    }
543
544    /// Set the unit label
545    pub fn with_unit(mut self, unit: &str) -> Self {
546        self.unit = unit.to_string();
547        self
548    }
549
550    /// Set the number of steps (for discrete parameters)
551    pub fn with_steps(mut self, steps: u32) -> Self {
552        self.steps = steps;
553        self
554    }
555
556    /// Set the short name
557    pub fn with_short_name(mut self, short_name: &str) -> Self {
558        self.short_name = short_name.to_string();
559        self
560    }
561
562    /// Normalize a value to 0.0-1.0 range
563    pub fn normalize(&self, value: f64) -> f64 {
564        (value - self.min) / (self.max - self.min)
565    }
566
567    /// Denormalize from 0.0-1.0 to parameter range
568    pub fn denormalize(&self, normalized: f64) -> f64 {
569        self.min + normalized * (self.max - self.min)
570    }
571
572    /// Quantize to steps (if discrete)
573    pub fn quantize(&self, value: f64) -> f64 {
574        if self.steps == 0 {
575            return value;
576        }
577        let step_size = (self.max - self.min) / self.steps as f64;
578        let steps = ((value - self.min) / step_size).round();
579        self.min + steps * step_size
580    }
581}
582
583/// Plugin audio bus configuration
584#[derive(Debug, Clone)]
585pub struct AudioBusConfig {
586    /// Number of input channels
587    pub inputs: u32,
588    /// Number of output channels
589    pub outputs: u32,
590    /// Bus name
591    pub name: String,
592}
593
594impl AudioBusConfig {
595    /// Create a stereo output configuration
596    pub fn stereo_out() -> Self {
597        Self {
598            inputs: 0,
599            outputs: 2,
600            name: "Main".to_string(),
601        }
602    }
603
604    /// Create a stereo I/O configuration
605    pub fn stereo_io() -> Self {
606        Self {
607            inputs: 2,
608            outputs: 2,
609            name: "Main".to_string(),
610        }
611    }
612
613    /// Create a mono output configuration
614    pub fn mono_out() -> Self {
615        Self {
616            inputs: 0,
617            outputs: 1,
618            name: "Main".to_string(),
619        }
620    }
621}
622
623/// Plugin metadata
624#[derive(Debug, Clone)]
625pub struct PluginInfo {
626    /// Plugin unique identifier
627    pub id: String,
628    /// Display name
629    pub name: String,
630    /// Vendor name
631    pub vendor: String,
632    /// Version string
633    pub version: String,
634    /// Plugin category
635    pub category: PluginCategory,
636    /// Whether the plugin is a synth (has no audio inputs)
637    pub is_synth: bool,
638    /// Supported sample rates (empty = any)
639    pub sample_rates: Vec<f64>,
640    /// Maximum block size (0 = any)
641    pub max_block_size: usize,
642    /// Latency in samples
643    pub latency: u32,
644}
645
646/// Plugin category
647#[derive(Debug, Clone, Copy, PartialEq, Eq)]
648pub enum PluginCategory {
649    Effect,
650    Instrument,
651    Analyzer,
652    Spatial,
653    Generator,
654    Other,
655}
656
657impl PluginInfo {
658    /// Create a new synth plugin info
659    pub fn synth(id: &str, name: &str, vendor: &str) -> Self {
660        Self {
661            id: id.to_string(),
662            name: name.to_string(),
663            vendor: vendor.to_string(),
664            version: "1.0.0".to_string(),
665            category: PluginCategory::Instrument,
666            is_synth: true,
667            sample_rates: vec![],
668            max_block_size: 0,
669            latency: 0,
670        }
671    }
672
673    /// Create a new effect plugin info
674    pub fn effect(id: &str, name: &str, vendor: &str) -> Self {
675        Self {
676            id: id.to_string(),
677            name: name.to_string(),
678            vendor: vendor.to_string(),
679            version: "1.0.0".to_string(),
680            category: PluginCategory::Effect,
681            is_synth: false,
682            sample_rates: vec![],
683            max_block_size: 0,
684            latency: 0,
685        }
686    }
687}
688
689/// Plugin wrapper for adapting Quiver patches to plugin formats
690pub struct PluginWrapper {
691    /// Plugin metadata
692    pub info: PluginInfo,
693    /// Audio bus configuration
694    pub bus_config: AudioBusConfig,
695    /// Parameter definitions
696    pub parameters: Vec<PluginParameter>,
697    /// Parameter values (atomic for thread-safe access)
698    pub param_values: Vec<Arc<AtomicF64>>,
699    /// Sample rate
700    pub sample_rate: f64,
701    /// Processing state
702    pub is_processing: AtomicBool,
703}
704
705impl PluginWrapper {
706    /// Create a new plugin wrapper
707    pub fn new(info: PluginInfo, bus_config: AudioBusConfig) -> Self {
708        Self {
709            info,
710            bus_config,
711            parameters: Vec::new(),
712            param_values: Vec::new(),
713            sample_rate: 44100.0,
714            is_processing: AtomicBool::new(false),
715        }
716    }
717
718    /// Add a parameter
719    pub fn add_parameter(&mut self, param: PluginParameter) -> Arc<AtomicF64> {
720        let value = Arc::new(AtomicF64::new(param.default));
721        self.param_values.push(value.clone());
722        self.parameters.push(param);
723        value
724    }
725
726    /// Get parameter count
727    pub fn parameter_count(&self) -> usize {
728        self.parameters.len()
729    }
730
731    /// Get parameter value by index
732    pub fn get_parameter(&self, index: usize) -> Option<f64> {
733        self.param_values.get(index).map(|v| v.get())
734    }
735
736    /// Set parameter value by index (normalized 0-1)
737    pub fn set_parameter_normalized(&self, index: usize, normalized: f64) {
738        if let (Some(param), Some(value)) =
739            (self.parameters.get(index), self.param_values.get(index))
740        {
741            let denormalized = param.denormalize(normalized.clamp(0.0, 1.0));
742            let quantized = param.quantize(denormalized);
743            value.set(quantized);
744        }
745    }
746
747    /// Set sample rate
748    pub fn set_sample_rate(&mut self, sample_rate: f64) {
749        self.sample_rate = sample_rate;
750    }
751
752    /// Start processing
753    pub fn start_processing(&self) {
754        self.is_processing.store(true, Ordering::SeqCst);
755    }
756
757    /// Stop processing
758    pub fn stop_processing(&self) {
759        self.is_processing.store(false, Ordering::SeqCst);
760    }
761
762    /// Check if processing is active
763    pub fn is_processing(&self) -> bool {
764        self.is_processing.load(Ordering::SeqCst)
765    }
766
767    /// Get the latency in samples
768    pub fn latency(&self) -> u32 {
769        self.info.latency
770    }
771
772    /// Set the latency in samples
773    pub fn set_latency(&mut self, samples: u32) {
774        self.info.latency = samples;
775    }
776}
777
778// ============================================================================
779// MIDI Support for Plugin Integration
780// ============================================================================
781
782/// MIDI message status bytes
783#[derive(Debug, Clone, Copy, PartialEq, Eq)]
784pub enum MidiStatus {
785    /// Note Off (channel 0-15)
786    NoteOff(u8),
787    /// Note On (channel 0-15)
788    NoteOn(u8),
789    /// Polyphonic Aftertouch (channel 0-15)
790    PolyPressure(u8),
791    /// Control Change (channel 0-15)
792    ControlChange(u8),
793    /// Program Change (channel 0-15)
794    ProgramChange(u8),
795    /// Channel Aftertouch (channel 0-15)
796    ChannelPressure(u8),
797    /// Pitch Bend (channel 0-15)
798    PitchBend(u8),
799    /// System message
800    System(u8),
801}
802
803impl MidiStatus {
804    /// Parse status byte
805    pub fn from_byte(byte: u8) -> Option<Self> {
806        let status = byte & 0xF0;
807        let channel = byte & 0x0F;
808        match status {
809            0x80 => Some(MidiStatus::NoteOff(channel)),
810            0x90 => Some(MidiStatus::NoteOn(channel)),
811            0xA0 => Some(MidiStatus::PolyPressure(channel)),
812            0xB0 => Some(MidiStatus::ControlChange(channel)),
813            0xC0 => Some(MidiStatus::ProgramChange(channel)),
814            0xD0 => Some(MidiStatus::ChannelPressure(channel)),
815            0xE0 => Some(MidiStatus::PitchBend(channel)),
816            0xF0..=0xFF => Some(MidiStatus::System(byte)),
817            _ => None,
818        }
819    }
820
821    /// Get the channel (0-15) or None for system messages
822    pub fn channel(&self) -> Option<u8> {
823        match self {
824            MidiStatus::NoteOff(ch)
825            | MidiStatus::NoteOn(ch)
826            | MidiStatus::PolyPressure(ch)
827            | MidiStatus::ControlChange(ch)
828            | MidiStatus::ProgramChange(ch)
829            | MidiStatus::ChannelPressure(ch)
830            | MidiStatus::PitchBend(ch) => Some(*ch),
831            MidiStatus::System(_) => None,
832        }
833    }
834}
835
836/// A MIDI message with timing information
837#[derive(Debug, Clone)]
838pub struct MidiMessage {
839    /// Sample offset within the current buffer
840    pub sample_offset: u32,
841    /// Status byte
842    pub status: MidiStatus,
843    /// Data byte 1 (note number, CC number, etc.)
844    pub data1: u8,
845    /// Data byte 2 (velocity, CC value, etc.)
846    pub data2: u8,
847}
848
849impl MidiMessage {
850    /// Create a Note On message
851    pub fn note_on(channel: u8, note: u8, velocity: u8) -> Self {
852        Self {
853            sample_offset: 0,
854            status: MidiStatus::NoteOn(channel & 0x0F),
855            data1: note & 0x7F,
856            data2: velocity & 0x7F,
857        }
858    }
859
860    /// Create a Note Off message
861    pub fn note_off(channel: u8, note: u8, velocity: u8) -> Self {
862        Self {
863            sample_offset: 0,
864            status: MidiStatus::NoteOff(channel & 0x0F),
865            data1: note & 0x7F,
866            data2: velocity & 0x7F,
867        }
868    }
869
870    /// Create a Control Change message
871    pub fn control_change(channel: u8, cc: u8, value: u8) -> Self {
872        Self {
873            sample_offset: 0,
874            status: MidiStatus::ControlChange(channel & 0x0F),
875            data1: cc & 0x7F,
876            data2: value & 0x7F,
877        }
878    }
879
880    /// Create a Pitch Bend message (value: -8192 to 8191)
881    pub fn pitch_bend(channel: u8, value: i16) -> Self {
882        let unsigned = (value + 8192).clamp(0, 16383) as u16;
883        Self {
884            sample_offset: 0,
885            status: MidiStatus::PitchBend(channel & 0x0F),
886            data1: (unsigned & 0x7F) as u8,
887            data2: ((unsigned >> 7) & 0x7F) as u8,
888        }
889    }
890
891    /// Set sample offset for sample-accurate timing
892    pub fn at_sample(mut self, offset: u32) -> Self {
893        self.sample_offset = offset;
894        self
895    }
896
897    /// Check if this is a Note On with non-zero velocity
898    pub fn is_note_on(&self) -> bool {
899        matches!(self.status, MidiStatus::NoteOn(_)) && self.data2 > 0
900    }
901
902    /// Check if this is a Note Off (or Note On with velocity 0)
903    pub fn is_note_off(&self) -> bool {
904        matches!(self.status, MidiStatus::NoteOff(_))
905            || (matches!(self.status, MidiStatus::NoteOn(_)) && self.data2 == 0)
906    }
907
908    /// Get the note number (0-127)
909    pub fn note(&self) -> u8 {
910        self.data1
911    }
912
913    /// Get the velocity (0-127)
914    pub fn velocity(&self) -> u8 {
915        self.data2
916    }
917
918    /// Convert note to frequency (A4 = 440Hz)
919    pub fn note_to_frequency(&self) -> f64 {
920        440.0 * 2.0_f64.powf((self.data1 as f64 - 69.0) / 12.0)
921    }
922
923    /// Convert note to V/Oct (0V = C4, 1V = C5, etc.)
924    pub fn note_to_volt_per_octave(&self) -> f64 {
925        (self.data1 as f64 - 60.0) / 12.0
926    }
927
928    /// Get pitch bend as -1.0 to 1.0 (for +/- 2 semitones typically)
929    pub fn pitch_bend_normalized(&self) -> f64 {
930        if !matches!(self.status, MidiStatus::PitchBend(_)) {
931            return 0.0;
932        }
933        let value = (self.data1 as i32) | ((self.data2 as i32) << 7);
934        (value - 8192) as f64 / 8192.0
935    }
936}
937
938/// MIDI event buffer for a processing block
939pub struct MidiBuffer {
940    /// Events sorted by sample offset
941    events: Vec<MidiMessage>,
942}
943
944impl MidiBuffer {
945    /// Create an empty MIDI buffer
946    pub fn new() -> Self {
947        Self { events: Vec::new() }
948    }
949
950    /// Create a buffer with capacity
951    pub fn with_capacity(capacity: usize) -> Self {
952        Self {
953            events: Vec::with_capacity(capacity),
954        }
955    }
956
957    /// Add an event
958    pub fn push(&mut self, event: MidiMessage) {
959        self.events.push(event);
960    }
961
962    /// Clear the buffer
963    pub fn clear(&mut self) {
964        self.events.clear();
965    }
966
967    /// Sort events by sample offset
968    pub fn sort(&mut self) {
969        self.events.sort_by_key(|e| e.sample_offset);
970    }
971
972    /// Get iterator over events
973    pub fn iter(&self) -> impl Iterator<Item = &MidiMessage> {
974        self.events.iter()
975    }
976
977    /// Get events at a specific sample offset
978    pub fn events_at(&self, sample: u32) -> impl Iterator<Item = &MidiMessage> {
979        self.events
980            .iter()
981            .filter(move |e| e.sample_offset == sample)
982    }
983
984    /// Get number of events
985    pub fn len(&self) -> usize {
986        self.events.len()
987    }
988
989    /// Check if empty
990    pub fn is_empty(&self) -> bool {
991        self.events.is_empty()
992    }
993}
994
995impl Default for MidiBuffer {
996    fn default() -> Self {
997        Self::new()
998    }
999}
1000
1001// ============================================================================
1002// Plugin Processor Trait
1003// ============================================================================
1004
1005/// Processing context passed to plugin processor
1006pub struct ProcessContext<'a> {
1007    /// Sample rate
1008    pub sample_rate: f64,
1009    /// Number of samples in this block
1010    pub num_samples: usize,
1011    /// Current transport position in samples (if available)
1012    pub transport_position: Option<u64>,
1013    /// Current tempo in BPM (if available)
1014    pub tempo: Option<f64>,
1015    /// Whether transport is playing
1016    pub is_playing: bool,
1017    /// MIDI input events
1018    pub midi_in: &'a MidiBuffer,
1019    /// MIDI output events
1020    pub midi_out: &'a mut MidiBuffer,
1021}
1022
1023/// Trait for plugin audio processors
1024///
1025/// This trait provides the complete interface for implementing audio plugins.
1026/// Implementations can be used with VST3, AU, or LV2 wrappers.
1027///
1028/// # Example
1029///
1030/// ```no_run
1031/// use quiver::prelude::*;
1032/// use quiver::extended_io::{PluginProcessor, ProcessContext};
1033///
1034/// struct MySynth {
1035///     patch: Patch,
1036///     sample_rate: f64,
1037/// }
1038///
1039/// impl PluginProcessor for MySynth {
1040///     fn initialize(&mut self, sample_rate: f64, _max_block_size: usize) {
1041///         self.sample_rate = sample_rate;
1042///     }
1043///
1044///     fn process(
1045///         &mut self,
1046///         _inputs: &[&[f32]],
1047///         outputs: &mut [&mut [f32]],
1048///         context: &mut ProcessContext,
1049///     ) {
1050///         // Handle MIDI
1051///         for event in context.midi_in.iter() {
1052///             if event.is_note_on() {
1053///                 // Trigger a note...
1054///             }
1055///         }
1056///
1057///         // Process audio
1058///         for i in 0..context.num_samples {
1059///             let (left, right) = self.patch.tick();
1060///             outputs[0][i] = left as f32;
1061///             outputs[1][i] = right as f32;
1062///         }
1063///     }
1064///
1065///     fn reset(&mut self) {
1066///         self.patch.reset();
1067///     }
1068///
1069///     fn set_parameter(&mut self, _id: u32, _value: f64) {}
1070///     fn get_parameter(&self, _id: u32) -> f64 { 0.0 }
1071/// }
1072/// ```
1073pub trait PluginProcessor: Send {
1074    /// Initialize the processor
1075    ///
1076    /// Called once when the plugin is instantiated or when sample rate changes.
1077    fn initialize(&mut self, sample_rate: f64, max_block_size: usize);
1078
1079    /// Process a block of audio
1080    ///
1081    /// `inputs`: Slice of input channel buffers (may be empty for synths)
1082    /// `outputs`: Slice of output channel buffers
1083    /// `context`: Processing context with timing and MIDI
1084    fn process(
1085        &mut self,
1086        inputs: &[&[f32]],
1087        outputs: &mut [&mut [f32]],
1088        context: &mut ProcessContext,
1089    );
1090
1091    /// Reset processor state
1092    ///
1093    /// Called when playback stops or when bypassed for a while.
1094    fn reset(&mut self);
1095
1096    /// Set a parameter value
1097    fn set_parameter(&mut self, id: u32, value: f64);
1098
1099    /// Get a parameter value
1100    fn get_parameter(&self, id: u32) -> f64;
1101
1102    /// Get the number of parameters
1103    fn parameter_count(&self) -> usize {
1104        0
1105    }
1106
1107    /// Get parameter info
1108    fn parameter_info(&self, _id: u32) -> Option<PluginParameter> {
1109        None
1110    }
1111
1112    /// Get tail length in samples (for reverbs, delays, etc.)
1113    fn tail_samples(&self) -> u32 {
1114        0
1115    }
1116
1117    /// Get latency in samples
1118    fn latency_samples(&self) -> u32 {
1119        0
1120    }
1121
1122    /// Handle state save (return serialized state)
1123    fn save_state(&self) -> Vec<u8> {
1124        Vec::new()
1125    }
1126
1127    /// Handle state load
1128    fn load_state(&mut self, _data: &[u8]) -> bool {
1129        false
1130    }
1131}
1132
1133// ============================================================================
1134// Web Audio Backend Interface
1135// ============================================================================
1136
1137/// Web Audio processor configuration
1138#[derive(Debug, Clone)]
1139pub struct WebAudioConfig {
1140    /// Number of input channels
1141    pub input_channels: u32,
1142    /// Number of output channels
1143    pub output_channels: u32,
1144    /// Sample rate (typically 44100 or 48000 for web)
1145    pub sample_rate: f64,
1146    /// Block size (typically 128 for Web Audio)
1147    pub block_size: usize,
1148}
1149
1150impl Default for WebAudioConfig {
1151    fn default() -> Self {
1152        Self {
1153            input_channels: 0,
1154            output_channels: 2,
1155            sample_rate: 44100.0,
1156            block_size: 128,
1157        }
1158    }
1159}
1160
1161/// Trait for Web Audio compatible processors
1162///
1163/// This trait provides the interface for WebAssembly-based audio processing.
1164/// Implementations can be compiled to WASM and used with AudioWorkletProcessor.
1165pub trait WebAudioProcessor: Send {
1166    /// Initialize the processor with the given configuration
1167    fn initialize(&mut self, config: &WebAudioConfig);
1168
1169    /// Process a block of audio
1170    ///
1171    /// `inputs`: Interleaved input samples (channels * block_size)
1172    /// `outputs`: Buffer for interleaved output samples
1173    /// Returns true to keep processing, false to end
1174    fn process(&mut self, inputs: &[f32], outputs: &mut [f32]) -> bool;
1175
1176    /// Handle a parameter change
1177    fn set_parameter(&mut self, name: &str, value: f64);
1178
1179    /// Get current parameter value
1180    fn get_parameter(&self, name: &str) -> Option<f64>;
1181
1182    /// Get all parameter names
1183    fn parameter_names(&self) -> Vec<String>;
1184
1185    /// Handle a message from the main thread
1186    fn handle_message(&mut self, _data: &[u8]) {}
1187}
1188
1189/// Web Audio worklet adapter
1190///
1191/// Adapts a Quiver patch for use as a Web Audio worklet.
1192pub struct WebAudioWorklet {
1193    /// Configuration
1194    config: WebAudioConfig,
1195    /// Parameter map
1196    parameters: HashMap<String, Arc<AtomicF64>>,
1197    /// Active state
1198    active: bool,
1199}
1200
1201impl WebAudioWorklet {
1202    /// Create a new worklet adapter
1203    pub fn new() -> Self {
1204        Self {
1205            config: WebAudioConfig::default(),
1206            parameters: HashMap::new(),
1207            active: false,
1208        }
1209    }
1210
1211    /// Add a parameter
1212    pub fn add_parameter(&mut self, name: &str, initial: f64) -> Arc<AtomicF64> {
1213        let value = Arc::new(AtomicF64::new(initial));
1214        self.parameters.insert(name.to_string(), value.clone());
1215        value
1216    }
1217
1218    /// Initialize with configuration
1219    pub fn initialize(&mut self, config: WebAudioConfig) {
1220        self.config = config;
1221        self.active = true;
1222    }
1223
1224    /// Get configuration
1225    pub fn config(&self) -> &WebAudioConfig {
1226        &self.config
1227    }
1228
1229    /// Check if active
1230    pub fn is_active(&self) -> bool {
1231        self.active
1232    }
1233
1234    /// Get a parameter value
1235    pub fn get_parameter(&self, name: &str) -> Option<f64> {
1236        self.parameters.get(name).map(|v| v.get())
1237    }
1238
1239    /// Set a parameter value
1240    pub fn set_parameter(&mut self, name: &str, value: f64) {
1241        if let Some(param) = self.parameters.get(name) {
1242            param.set(value);
1243        }
1244    }
1245}
1246
1247impl Default for WebAudioWorklet {
1248    fn default() -> Self {
1249        Self::new()
1250    }
1251}
1252
1253/// Web Audio block processor for AudioWorklet integration
1254///
1255/// This struct provides the sample-accurate processing required for
1256/// Web Audio's AudioWorkletProcessor. It handles the 128-sample render
1257/// quantum and provides efficient block processing.
1258///
1259/// # JavaScript Integration Example
1260///
1261/// ```javascript
1262/// // In your AudioWorkletProcessor
1263/// class QuiverProcessor extends AudioWorkletProcessor {
1264///   constructor() {
1265///     super();
1266///     this.engine = new QuiverEngine(sampleRate);
1267///     this.engine.load_patch(patchJson);
1268///     this.engine.compile();
1269///   }
1270///
1271///   process(inputs, outputs, parameters) {
1272///     const output = outputs[0];
1273///     const samples = this.engine.process_block(128);
1274///
1275///     // Deinterleave stereo output
1276///     for (let i = 0; i < 128; i++) {
1277///       output[0][i] = samples[i * 2];
1278///       output[1][i] = samples[i * 2 + 1];
1279///     }
1280///     return true;
1281///   }
1282/// }
1283/// ```
1284pub struct WebAudioBlockProcessor {
1285    /// Configuration
1286    config: WebAudioConfig,
1287    /// Left channel buffer
1288    left_buffer: Vec<f64>,
1289    /// Right channel buffer
1290    right_buffer: Vec<f64>,
1291    /// Interleaved output buffer (for f32)
1292    interleaved_buffer: Vec<f32>,
1293    /// Parameter map
1294    parameters: HashMap<String, Arc<AtomicF64>>,
1295    /// Active state
1296    active: bool,
1297}
1298
1299impl WebAudioBlockProcessor {
1300    /// Create a new block processor with default Web Audio config (128 samples)
1301    pub fn new() -> Self {
1302        Self::with_config(WebAudioConfig::default())
1303    }
1304
1305    /// Create a new block processor with custom configuration
1306    pub fn with_config(config: WebAudioConfig) -> Self {
1307        let block_size = config.block_size;
1308        Self {
1309            config,
1310            left_buffer: vec![0.0; block_size],
1311            right_buffer: vec![0.0; block_size],
1312            interleaved_buffer: vec![0.0; block_size * 2],
1313            parameters: HashMap::new(),
1314            active: false,
1315        }
1316    }
1317
1318    /// Add a parameter
1319    pub fn add_parameter(&mut self, name: &str, initial: f64) -> Arc<AtomicF64> {
1320        let value = Arc::new(AtomicF64::new(initial));
1321        self.parameters.insert(name.to_string(), value.clone());
1322        value
1323    }
1324
1325    /// Initialize/activate the processor
1326    pub fn activate(&mut self) {
1327        self.active = true;
1328    }
1329
1330    /// Deactivate the processor
1331    pub fn deactivate(&mut self) {
1332        self.active = false;
1333    }
1334
1335    /// Check if active
1336    pub fn is_active(&self) -> bool {
1337        self.active
1338    }
1339
1340    /// Get configuration
1341    pub fn config(&self) -> &WebAudioConfig {
1342        &self.config
1343    }
1344
1345    /// Get the block size
1346    pub fn block_size(&self) -> usize {
1347        self.config.block_size
1348    }
1349
1350    /// Get the sample rate
1351    pub fn sample_rate(&self) -> f64 {
1352        self.config.sample_rate
1353    }
1354
1355    /// Get a parameter value
1356    pub fn get_parameter(&self, name: &str) -> Option<f64> {
1357        self.parameters.get(name).map(|v| v.get())
1358    }
1359
1360    /// Set a parameter value
1361    pub fn set_parameter(&mut self, name: &str, value: f64) {
1362        if let Some(param) = self.parameters.get(name) {
1363            param.set(value);
1364        }
1365    }
1366
1367    /// Get all parameter names
1368    pub fn parameter_names(&self) -> Vec<String> {
1369        self.parameters.keys().cloned().collect()
1370    }
1371
1372    /// Process a block using a closure that generates samples
1373    ///
1374    /// The closure receives the sample index and should return (left, right).
1375    /// Returns a reference to the interleaved output buffer.
1376    pub fn process_with<F>(&mut self, mut generator: F) -> &[f32]
1377    where
1378        F: FnMut(usize) -> (f64, f64),
1379    {
1380        for i in 0..self.config.block_size {
1381            let (left, right) = generator(i);
1382            self.left_buffer[i] = left;
1383            self.right_buffer[i] = right;
1384        }
1385
1386        interleave_stereo(
1387            &self.left_buffer,
1388            &self.right_buffer,
1389            &mut self.interleaved_buffer,
1390        );
1391
1392        &self.interleaved_buffer
1393    }
1394
1395    /// Get the left channel buffer (for direct writing)
1396    pub fn left_buffer_mut(&mut self) -> &mut [f64] {
1397        &mut self.left_buffer
1398    }
1399
1400    /// Get the right channel buffer (for direct writing)
1401    pub fn right_buffer_mut(&mut self) -> &mut [f64] {
1402        &mut self.right_buffer
1403    }
1404
1405    /// Finalize and get interleaved output after writing to channel buffers
1406    pub fn finalize(&mut self) -> &[f32] {
1407        interleave_stereo(
1408            &self.left_buffer,
1409            &self.right_buffer,
1410            &mut self.interleaved_buffer,
1411        );
1412        &self.interleaved_buffer
1413    }
1414
1415    /// Clear all buffers
1416    pub fn clear(&mut self) {
1417        self.left_buffer.fill(0.0);
1418        self.right_buffer.fill(0.0);
1419        self.interleaved_buffer.fill(0.0);
1420    }
1421}
1422
1423impl Default for WebAudioBlockProcessor {
1424    fn default() -> Self {
1425        Self::new()
1426    }
1427}
1428
1429/// Convert f64 audio block to f32 for Web Audio
1430#[inline]
1431pub fn f64_to_f32_block(src: &[f64], dst: &mut [f32]) {
1432    let len = src.len().min(dst.len());
1433    for i in 0..len {
1434        dst[i] = src[i] as f32;
1435    }
1436}
1437
1438/// Convert f32 audio block to f64 from Web Audio
1439#[inline]
1440pub fn f32_to_f64_block(src: &[f32], dst: &mut [f64]) {
1441    let len = src.len().min(dst.len());
1442    for i in 0..len {
1443        dst[i] = src[i] as f64;
1444    }
1445}
1446
1447/// Interleave stereo channels for Web Audio
1448#[inline]
1449pub fn interleave_stereo(left: &[f64], right: &[f64], output: &mut [f32]) {
1450    let frames = left.len().min(right.len()).min(output.len() / 2);
1451    for i in 0..frames {
1452        output[i * 2] = left[i] as f32;
1453        output[i * 2 + 1] = right[i] as f32;
1454    }
1455}
1456
1457/// Deinterleave stereo channels from Web Audio
1458#[inline]
1459pub fn deinterleave_stereo(input: &[f32], left: &mut [f64], right: &mut [f64]) {
1460    let frames = (input.len() / 2).min(left.len()).min(right.len());
1461    for i in 0..frames {
1462        left[i] = input[i * 2] as f64;
1463        right[i] = input[i * 2 + 1] as f64;
1464    }
1465}
1466
1467#[cfg(test)]
1468mod tests {
1469    use super::*;
1470
1471    // OSC Tests
1472    #[test]
1473    fn test_osc_message() {
1474        let msg = OscMessage::new("/synth/filter/cutoff").with_float(0.75);
1475        assert_eq!(msg.address, "/synth/filter/cutoff");
1476        assert!((msg.first_f64().unwrap() - 0.75).abs() < 0.001);
1477    }
1478
1479    #[test]
1480    fn test_osc_pattern_literal() {
1481        let pattern = OscPattern::new("/synth/osc/pitch");
1482        assert!(pattern.matches("/synth/osc/pitch"));
1483        assert!(!pattern.matches("/synth/osc/volume"));
1484        assert!(!pattern.matches("/synth/osc"));
1485    }
1486
1487    #[test]
1488    fn test_osc_pattern_wildcard() {
1489        let pattern = OscPattern::new("/synth/*");
1490        // `*` matches within a single component...
1491        assert!(pattern.matches("/synth/osc"));
1492        assert!(pattern.matches("/synth/filter"));
1493        // ...but never crosses a component boundary (OSC has no cross-component `*`).
1494        assert!(!pattern.matches("/synth/filter/cutoff"));
1495        assert!(!pattern.matches("/synth"));
1496    }
1497
1498    #[test]
1499    fn test_osc_pattern_wildcard_partial_component() {
1500        let pattern = OscPattern::new("/synth/osc*");
1501        assert!(pattern.matches("/synth/osc"));
1502        assert!(pattern.matches("/synth/osc1"));
1503        assert!(pattern.matches("/synth/oscillator"));
1504        assert!(!pattern.matches("/synth/lfo"));
1505
1506        let mid = OscPattern::new("/a/f*r");
1507        assert!(mid.matches("/a/filter"));
1508        assert!(mid.matches("/a/fr"));
1509        assert!(!mid.matches("/a/filik"));
1510    }
1511
1512    #[test]
1513    fn test_osc_pattern_char_class_range() {
1514        let pattern = OscPattern::new("/ch[0-9]");
1515        assert!(pattern.matches("/ch0"));
1516        assert!(pattern.matches("/ch7"));
1517        assert!(!pattern.matches("/chx"));
1518        // A range char is not a literal set of {0, -, 9}.
1519        assert!(!pattern.matches("/ch-"));
1520
1521        let alpha = OscPattern::new("/[a-c]");
1522        assert!(alpha.matches("/a"));
1523        assert!(alpha.matches("/c"));
1524        assert!(!alpha.matches("/d"));
1525    }
1526
1527    #[test]
1528    fn test_osc_pattern_char_class_negation() {
1529        let pattern = OscPattern::new("/ch[!0-9]");
1530        assert!(pattern.matches("/chx"));
1531        assert!(!pattern.matches("/ch5"));
1532
1533        let neg = OscPattern::new("/[!abc]");
1534        assert!(neg.matches("/d"));
1535        assert!(!neg.matches("/a"));
1536    }
1537
1538    #[test]
1539    fn test_osc_pattern_alternation() {
1540        let pattern = OscPattern::new("/synth/{osc,lfo}");
1541        assert!(pattern.matches("/synth/osc"));
1542        assert!(pattern.matches("/synth/lfo"));
1543        assert!(!pattern.matches("/synth/vcf"));
1544
1545        // Alternation combined with surrounding literals.
1546        let mixed = OscPattern::new("/v{1,2}/gain");
1547        assert!(mixed.matches("/v1/gain"));
1548        assert!(mixed.matches("/v2/gain"));
1549        assert!(!mixed.matches("/v3/gain"));
1550    }
1551
1552    #[test]
1553    fn test_osc_pattern_malformed_fails() {
1554        // Unterminated class must fail to match, not swallow the rest.
1555        let unterminated_class = OscPattern::new("/ch[0-9");
1556        assert!(!unterminated_class.matches("/ch5"));
1557        assert!(!unterminated_class.matches("/ch"));
1558
1559        // Unterminated alternation must fail to match.
1560        let unterminated_alt = OscPattern::new("/synth/{osc,lfo");
1561        assert!(!unterminated_alt.matches("/synth/osc"));
1562
1563        // Empty class is malformed.
1564        let empty_class = OscPattern::new("/ch[]");
1565        assert!(!empty_class.matches("/ch"));
1566    }
1567
1568    #[test]
1569    fn test_osc_binding() {
1570        let value = Arc::new(AtomicF64::new(0.0));
1571        let binding = OscBinding::new("/test/param", value.clone()).with_scale(10.0);
1572
1573        let msg = OscMessage::new("/test/param").with_float(0.5);
1574        assert!(binding.apply(&msg));
1575        assert!((value.get() - 5.0).abs() < 0.001);
1576    }
1577
1578    #[test]
1579    fn test_osc_receiver() {
1580        let mut receiver = OscReceiver::new();
1581        let value = Arc::new(AtomicF64::new(0.0));
1582        receiver.bind("/synth/volume", value.clone());
1583
1584        let msg = OscMessage::new("/synth/volume").with_float(0.8);
1585        assert!(receiver.handle_message(&msg));
1586        assert!((value.get() - 0.8).abs() < 0.001);
1587
1588        let msg2 = OscMessage::new("/synth/pitch").with_float(0.5);
1589        assert!(!receiver.handle_message(&msg2));
1590    }
1591
1592    // Plugin Wrapper Tests
1593    #[test]
1594    fn test_plugin_parameter() {
1595        let param = PluginParameter::new(0, "Cutoff", 20.0, 20000.0, 1000.0).with_unit("Hz");
1596
1597        assert!((param.normalize(20.0) - 0.0).abs() < 0.001);
1598        assert!((param.normalize(20000.0) - 1.0).abs() < 0.001);
1599        assert!((param.denormalize(0.5) - 10010.0).abs() < 1.0);
1600    }
1601
1602    #[test]
1603    fn test_plugin_parameter_quantize() {
1604        let param = PluginParameter::new(0, "Steps", 0.0, 10.0, 5.0).with_steps(10);
1605
1606        // With 10 steps over 0-10 range, step size is 1.0
1607        // 0.5 rounds to 0.0 or 1.0
1608        assert!((param.quantize(0.4) - 0.0).abs() < 0.1);
1609        assert!((param.quantize(0.6) - 1.0).abs() < 0.1);
1610        assert!((param.quantize(4.7) - 5.0).abs() < 0.1);
1611        assert!((param.quantize(9.9) - 10.0).abs() < 0.1);
1612    }
1613
1614    #[test]
1615    fn test_plugin_wrapper() {
1616        let info = PluginInfo::synth("com.quiver.test", "Test Synth", "Quiver");
1617        let bus = AudioBusConfig::stereo_out();
1618
1619        let mut wrapper = PluginWrapper::new(info, bus);
1620        let cutoff =
1621            wrapper.add_parameter(PluginParameter::new(0, "Cutoff", 20.0, 20000.0, 1000.0));
1622
1623        assert_eq!(wrapper.parameter_count(), 1);
1624        assert!((wrapper.get_parameter(0).unwrap() - 1000.0).abs() < 0.001);
1625
1626        wrapper.set_parameter_normalized(0, 0.5);
1627        assert!((cutoff.get() - 10010.0).abs() < 1.0);
1628    }
1629
1630    // Web Audio Tests
1631    #[test]
1632    fn test_web_audio_config() {
1633        let config = WebAudioConfig::default();
1634        assert_eq!(config.output_channels, 2);
1635        assert_eq!(config.block_size, 128);
1636    }
1637
1638    #[test]
1639    fn test_web_audio_worklet() {
1640        let mut worklet = WebAudioWorklet::new();
1641        let freq = worklet.add_parameter("frequency", 440.0);
1642
1643        assert!((worklet.get_parameter("frequency").unwrap() - 440.0).abs() < 0.001);
1644
1645        worklet.set_parameter("frequency", 880.0);
1646        assert!((freq.get() - 880.0).abs() < 0.001);
1647    }
1648
1649    #[test]
1650    fn test_interleave_stereo() {
1651        let left = vec![1.0, 2.0, 3.0];
1652        let right = vec![4.0, 5.0, 6.0];
1653        let mut output = vec![0.0f32; 6];
1654
1655        interleave_stereo(&left, &right, &mut output);
1656
1657        assert!((output[0] - 1.0).abs() < 0.001);
1658        assert!((output[1] - 4.0).abs() < 0.001);
1659        assert!((output[2] - 2.0).abs() < 0.001);
1660        assert!((output[3] - 5.0).abs() < 0.001);
1661    }
1662
1663    #[test]
1664    fn test_deinterleave_stereo() {
1665        let input = vec![1.0f32, 4.0, 2.0, 5.0, 3.0, 6.0];
1666        let mut left = vec![0.0; 3];
1667        let mut right = vec![0.0; 3];
1668
1669        deinterleave_stereo(&input, &mut left, &mut right);
1670
1671        assert!((left[0] - 1.0).abs() < 0.001);
1672        assert!((left[1] - 2.0).abs() < 0.001);
1673        assert!((left[2] - 3.0).abs() < 0.001);
1674        assert!((right[0] - 4.0).abs() < 0.001);
1675        assert!((right[1] - 5.0).abs() < 0.001);
1676        assert!((right[2] - 6.0).abs() < 0.001);
1677    }
1678
1679    #[test]
1680    fn test_osc_value_to_f64() {
1681        assert!((OscValue::Int(42).to_f64().unwrap() - 42.0).abs() < 0.001);
1682        assert!((OscValue::Float(2.5).to_f64().unwrap() - 2.5).abs() < 0.01);
1683        assert!((OscValue::Long(100).to_f64().unwrap() - 100.0).abs() < 0.001);
1684        assert!((OscValue::Double(2.71).to_f64().unwrap() - 2.71).abs() < 0.001);
1685        assert!((OscValue::True.to_f64().unwrap() - 1.0).abs() < 0.001);
1686        assert!((OscValue::False.to_f64().unwrap() - 0.0).abs() < 0.001);
1687        assert!(OscValue::Nil.to_f64().is_none());
1688    }
1689
1690    #[test]
1691    fn test_osc_value_to_bool() {
1692        assert_eq!(OscValue::Int(1).to_bool(), Some(true));
1693        assert_eq!(OscValue::Int(0).to_bool(), Some(false));
1694        assert_eq!(OscValue::Float(1.0).to_bool(), Some(true));
1695        assert_eq!(OscValue::Float(0.0).to_bool(), Some(false));
1696        assert_eq!(OscValue::True.to_bool(), Some(true));
1697        assert_eq!(OscValue::False.to_bool(), Some(false));
1698        assert_eq!(OscValue::Nil.to_bool(), None);
1699    }
1700
1701    #[test]
1702    fn test_osc_message_with_int() {
1703        let msg = OscMessage::new("/test").with_int(42);
1704        assert_eq!(msg.args.len(), 1);
1705    }
1706
1707    #[test]
1708    fn test_osc_pattern_single_char() {
1709        let pattern = OscPattern::new("/a/?");
1710        assert!(pattern.matches("/a/b"));
1711        assert!(!pattern.matches("/a/bb"));
1712    }
1713
1714    #[test]
1715    fn test_osc_pattern_char_class() {
1716        let pattern = OscPattern::new("/[abc]");
1717        assert!(pattern.matches("/a"));
1718        assert!(pattern.matches("/b"));
1719        assert!(!pattern.matches("/d"));
1720    }
1721
1722    #[test]
1723    fn test_osc_binding_with_offset() {
1724        let value = Arc::new(AtomicF64::new(0.0));
1725        let binding = OscBinding::new("/test", value.clone())
1726            .with_scale(2.0)
1727            .with_offset(10.0);
1728
1729        let msg = OscMessage::new("/test").with_float(5.0);
1730        binding.apply(&msg);
1731        assert!((value.get() - 20.0).abs() < 0.001);
1732    }
1733
1734    #[test]
1735    fn test_osc_binding_non_matching() {
1736        let value = Arc::new(AtomicF64::new(0.0));
1737        let binding = OscBinding::new("/test", value.clone());
1738
1739        let msg = OscMessage::new("/other").with_float(5.0);
1740        assert!(!binding.apply(&msg));
1741    }
1742
1743    #[test]
1744    fn test_osc_receiver_bind_scaled() {
1745        let mut receiver = OscReceiver::new();
1746        let value = Arc::new(AtomicF64::new(0.0));
1747        receiver.bind_scaled("/test", value.clone(), 10.0, 5.0);
1748
1749        let msg = OscMessage::new("/test").with_float(1.0);
1750        receiver.handle_message(&msg);
1751        assert!((value.get() - 15.0).abs() < 0.001);
1752    }
1753
1754    #[test]
1755    fn test_osc_receiver_counters() {
1756        let mut receiver = OscReceiver::new();
1757        let value = Arc::new(AtomicF64::new(0.0));
1758        receiver.bind("/test", value.clone());
1759
1760        let msg = OscMessage::new("/test").with_float(1.0);
1761        receiver.handle_message(&msg);
1762
1763        assert_eq!(receiver.message_count(), 1);
1764        assert_eq!(receiver.matched_count(), 1);
1765        assert_eq!(receiver.binding_count(), 1);
1766
1767        receiver.reset_counters();
1768        assert_eq!(receiver.message_count(), 0);
1769    }
1770
1771    #[test]
1772    fn test_osc_receiver_default() {
1773        let receiver = OscReceiver::default();
1774        assert_eq!(receiver.binding_count(), 0);
1775    }
1776
1777    #[test]
1778    fn test_osc_input_module() {
1779        let value = Arc::new(AtomicF64::new(5.0));
1780        let mut input = OscInput::new("/test/param", value.clone(), SignalKind::CvUnipolar);
1781
1782        assert_eq!(input.address(), "/test/param");
1783        assert!((input.value_ref().get() - 5.0).abs() < 0.001);
1784
1785        let inputs = PortValues::new();
1786        let mut outputs = PortValues::new();
1787        input.tick(&inputs, &mut outputs);
1788
1789        assert!((outputs.get(0).unwrap() - 5.0).abs() < 0.001);
1790
1791        input.reset();
1792        input.set_sample_rate(48000.0);
1793        assert_eq!(input.type_id(), "osc_input");
1794    }
1795
1796    // MIDI Tests
1797    #[test]
1798    fn test_midi_status_parsing() {
1799        assert_eq!(MidiStatus::from_byte(0x90), Some(MidiStatus::NoteOn(0)));
1800        assert_eq!(MidiStatus::from_byte(0x95), Some(MidiStatus::NoteOn(5)));
1801        assert_eq!(MidiStatus::from_byte(0x80), Some(MidiStatus::NoteOff(0)));
1802        assert_eq!(
1803            MidiStatus::from_byte(0xB0),
1804            Some(MidiStatus::ControlChange(0))
1805        );
1806        assert_eq!(MidiStatus::from_byte(0xE0), Some(MidiStatus::PitchBend(0)));
1807        assert_eq!(MidiStatus::from_byte(0xF0), Some(MidiStatus::System(0xF0)));
1808    }
1809
1810    #[test]
1811    fn test_midi_status_channel() {
1812        let note_on = MidiStatus::NoteOn(5);
1813        assert_eq!(note_on.channel(), Some(5));
1814
1815        let system = MidiStatus::System(0xF0);
1816        assert_eq!(system.channel(), None);
1817    }
1818
1819    #[test]
1820    fn test_midi_note_on() {
1821        let msg = MidiMessage::note_on(0, 60, 100);
1822        assert!(msg.is_note_on());
1823        assert!(!msg.is_note_off());
1824        assert_eq!(msg.note(), 60);
1825        assert_eq!(msg.velocity(), 100);
1826    }
1827
1828    #[test]
1829    fn test_midi_note_off() {
1830        let msg = MidiMessage::note_off(0, 60, 0);
1831        assert!(!msg.is_note_on());
1832        assert!(msg.is_note_off());
1833    }
1834
1835    #[test]
1836    fn test_midi_note_on_zero_velocity() {
1837        // Note On with velocity 0 is treated as Note Off
1838        let msg = MidiMessage::note_on(0, 60, 0);
1839        assert!(!msg.is_note_on());
1840        assert!(msg.is_note_off());
1841    }
1842
1843    #[test]
1844    fn test_midi_control_change() {
1845        let msg = MidiMessage::control_change(0, 1, 64);
1846        assert_eq!(msg.data1, 1); // CC number
1847        assert_eq!(msg.data2, 64); // Value
1848    }
1849
1850    #[test]
1851    fn test_midi_pitch_bend() {
1852        // Center position (0)
1853        let msg = MidiMessage::pitch_bend(0, 0);
1854        let normalized = msg.pitch_bend_normalized();
1855        assert!(normalized.abs() < 0.001);
1856
1857        // Max positive
1858        let msg = MidiMessage::pitch_bend(0, 8191);
1859        let normalized = msg.pitch_bend_normalized();
1860        assert!((normalized - 1.0).abs() < 0.01);
1861
1862        // Max negative
1863        let msg = MidiMessage::pitch_bend(0, -8192);
1864        let normalized = msg.pitch_bend_normalized();
1865        assert!((normalized + 1.0).abs() < 0.01);
1866    }
1867
1868    #[test]
1869    fn test_midi_note_to_frequency() {
1870        let msg = MidiMessage::note_on(0, 69, 100); // A4
1871        assert!((msg.note_to_frequency() - 440.0).abs() < 0.01);
1872
1873        let msg = MidiMessage::note_on(0, 60, 100); // C4
1874        assert!((msg.note_to_frequency() - 261.63).abs() < 0.1);
1875    }
1876
1877    #[test]
1878    fn test_midi_note_to_volt_per_octave() {
1879        let msg = MidiMessage::note_on(0, 60, 100); // C4 = 0V
1880        assert!(msg.note_to_volt_per_octave().abs() < 0.001);
1881
1882        let msg = MidiMessage::note_on(0, 72, 100); // C5 = 1V
1883        assert!((msg.note_to_volt_per_octave() - 1.0).abs() < 0.001);
1884    }
1885
1886    #[test]
1887    fn test_midi_at_sample() {
1888        let msg = MidiMessage::note_on(0, 60, 100).at_sample(64);
1889        assert_eq!(msg.sample_offset, 64);
1890    }
1891
1892    #[test]
1893    fn test_midi_buffer() {
1894        let mut buffer = MidiBuffer::new();
1895        assert!(buffer.is_empty());
1896        assert_eq!(buffer.len(), 0);
1897
1898        buffer.push(MidiMessage::note_on(0, 60, 100).at_sample(0));
1899        buffer.push(MidiMessage::note_on(0, 64, 100).at_sample(32));
1900        buffer.push(MidiMessage::note_on(0, 67, 100).at_sample(64));
1901
1902        assert_eq!(buffer.len(), 3);
1903        assert!(!buffer.is_empty());
1904
1905        // Test events_at
1906        let at_0: Vec<_> = buffer.events_at(0).collect();
1907        assert_eq!(at_0.len(), 1);
1908        assert_eq!(at_0[0].note(), 60);
1909
1910        // Test clear
1911        buffer.clear();
1912        assert!(buffer.is_empty());
1913    }
1914
1915    #[test]
1916    fn test_midi_buffer_sort() {
1917        let mut buffer = MidiBuffer::with_capacity(10);
1918        buffer.push(MidiMessage::note_on(0, 60, 100).at_sample(64));
1919        buffer.push(MidiMessage::note_on(0, 64, 100).at_sample(0));
1920        buffer.push(MidiMessage::note_on(0, 67, 100).at_sample(32));
1921
1922        buffer.sort();
1923
1924        let events: Vec<_> = buffer.iter().collect();
1925        assert_eq!(events[0].sample_offset, 0);
1926        assert_eq!(events[1].sample_offset, 32);
1927        assert_eq!(events[2].sample_offset, 64);
1928    }
1929
1930    #[test]
1931    fn test_midi_buffer_default() {
1932        let buffer = MidiBuffer::default();
1933        assert!(buffer.is_empty());
1934    }
1935
1936    // Plugin Wrapper Extended Tests
1937    #[test]
1938    fn test_plugin_wrapper_latency() {
1939        let info = PluginInfo::effect("com.quiver.test", "Test Effect", "Quiver");
1940        let bus = AudioBusConfig::stereo_io();
1941
1942        let mut wrapper = PluginWrapper::new(info, bus);
1943        assert_eq!(wrapper.latency(), 0);
1944
1945        wrapper.set_latency(256);
1946        assert_eq!(wrapper.latency(), 256);
1947    }
1948
1949    #[test]
1950    fn test_plugin_wrapper_processing_state() {
1951        let info = PluginInfo::synth("com.quiver.test", "Test Synth", "Quiver");
1952        let bus = AudioBusConfig::stereo_out();
1953        let wrapper = PluginWrapper::new(info, bus);
1954
1955        assert!(!wrapper.is_processing());
1956        wrapper.start_processing();
1957        assert!(wrapper.is_processing());
1958        wrapper.stop_processing();
1959        assert!(!wrapper.is_processing());
1960    }
1961
1962    #[test]
1963    fn test_audio_bus_config() {
1964        let stereo_out = AudioBusConfig::stereo_out();
1965        assert_eq!(stereo_out.inputs, 0);
1966        assert_eq!(stereo_out.outputs, 2);
1967
1968        let stereo_io = AudioBusConfig::stereo_io();
1969        assert_eq!(stereo_io.inputs, 2);
1970        assert_eq!(stereo_io.outputs, 2);
1971
1972        let mono_out = AudioBusConfig::mono_out();
1973        assert_eq!(mono_out.inputs, 0);
1974        assert_eq!(mono_out.outputs, 1);
1975    }
1976
1977    // Web Audio Block Processor Tests
1978    #[test]
1979    fn test_web_audio_block_processor_new() {
1980        let processor = WebAudioBlockProcessor::new();
1981        assert_eq!(processor.block_size(), 128);
1982        assert!((processor.sample_rate() - 44100.0).abs() < 0.001);
1983        assert!(!processor.is_active());
1984    }
1985
1986    #[test]
1987    fn test_web_audio_block_processor_with_config() {
1988        let config = WebAudioConfig {
1989            input_channels: 0,
1990            output_channels: 2,
1991            sample_rate: 48000.0,
1992            block_size: 256,
1993        };
1994        let processor = WebAudioBlockProcessor::with_config(config);
1995        assert_eq!(processor.block_size(), 256);
1996        assert!((processor.sample_rate() - 48000.0).abs() < 0.001);
1997    }
1998
1999    #[test]
2000    fn test_web_audio_block_processor_activate() {
2001        let mut processor = WebAudioBlockProcessor::new();
2002        assert!(!processor.is_active());
2003
2004        processor.activate();
2005        assert!(processor.is_active());
2006
2007        processor.deactivate();
2008        assert!(!processor.is_active());
2009    }
2010
2011    #[test]
2012    fn test_web_audio_block_processor_parameters() {
2013        let mut processor = WebAudioBlockProcessor::new();
2014        let freq = processor.add_parameter("frequency", 440.0);
2015
2016        assert!((processor.get_parameter("frequency").unwrap() - 440.0).abs() < 0.001);
2017
2018        processor.set_parameter("frequency", 880.0);
2019        assert!((freq.get() - 880.0).abs() < 0.001);
2020
2021        let names = processor.parameter_names();
2022        assert!(names.contains(&"frequency".to_string()));
2023    }
2024
2025    #[test]
2026    fn test_web_audio_block_processor_process_with() {
2027        let mut processor = WebAudioBlockProcessor::new();
2028        let mut phase = 0.0;
2029
2030        let output = processor.process_with(|_i| {
2031            let sample = (phase * std::f64::consts::TAU).sin();
2032            phase += 440.0 / 44100.0;
2033            (sample, sample)
2034        });
2035
2036        // Output is interleaved stereo, 128 * 2 = 256 samples
2037        assert_eq!(output.len(), 256);
2038
2039        // First sample should be close to 0 (sin(0))
2040        assert!(output[0].abs() < 0.1);
2041    }
2042
2043    #[test]
2044    fn test_web_audio_block_processor_direct_buffer() {
2045        let mut processor = WebAudioBlockProcessor::new();
2046
2047        // Write directly to left buffer
2048        {
2049            let left = processor.left_buffer_mut();
2050            for (i, slot) in left.iter_mut().enumerate().take(128) {
2051                *slot = (i as f64) / 128.0;
2052            }
2053        }
2054
2055        // Write directly to right buffer
2056        {
2057            let right = processor.right_buffer_mut();
2058            for (i, slot) in right.iter_mut().enumerate().take(128) {
2059                *slot = 1.0 - (i as f64) / 128.0;
2060            }
2061        }
2062
2063        let output = processor.finalize();
2064
2065        // Check first sample pair
2066        assert!(output[0].abs() < 0.01); // left[0] = 0
2067        assert!((output[1] - 1.0).abs() < 0.01); // right[0] = 1
2068
2069        // Check last sample pair
2070        assert!((output[254] - 127.0 / 128.0).abs() < 0.01);
2071        assert!((output[255] - 1.0 / 128.0).abs() < 0.01);
2072    }
2073
2074    #[test]
2075    fn test_web_audio_block_processor_clear() {
2076        let mut processor = WebAudioBlockProcessor::new();
2077
2078        // Fill with non-zero values
2079        processor.process_with(|_| (1.0, 1.0));
2080
2081        // Clear
2082        processor.clear();
2083
2084        let output = processor.finalize();
2085        for sample in output {
2086            assert!(*sample < 0.001);
2087        }
2088    }
2089
2090    #[test]
2091    fn test_web_audio_block_processor_default() {
2092        let processor = WebAudioBlockProcessor::default();
2093        assert_eq!(processor.block_size(), 128);
2094    }
2095
2096    #[test]
2097    fn test_f64_to_f32_block() {
2098        let src = vec![0.5_f64, -0.5, 1.0, -1.0];
2099        let mut dst = vec![0.0_f32; 4];
2100
2101        f64_to_f32_block(&src, &mut dst);
2102
2103        assert!((dst[0] - 0.5).abs() < 0.001);
2104        assert!((dst[1] + 0.5).abs() < 0.001);
2105        assert!((dst[2] - 1.0).abs() < 0.001);
2106        assert!((dst[3] + 1.0).abs() < 0.001);
2107    }
2108
2109    #[test]
2110    fn test_f32_to_f64_block() {
2111        let src = vec![0.5_f32, -0.5, 1.0, -1.0];
2112        let mut dst = vec![0.0_f64; 4];
2113
2114        f32_to_f64_block(&src, &mut dst);
2115
2116        assert!((dst[0] - 0.5).abs() < 0.001);
2117        assert!((dst[1] + 0.5).abs() < 0.001);
2118        assert!((dst[2] - 1.0).abs() < 0.001);
2119        assert!((dst[3] + 1.0).abs() < 0.001);
2120    }
2121}