Skip to main content

wavecraft_dev_server/audio/
server.rs

1//! Audio server for full-duplex audio I/O in dev mode.
2//!
3//! This module provides an audio server that captures microphone input,
4//! processes it through a `DevAudioProcessor` (typically an `FfiProcessor`
5//! loaded from the user's cdylib), and sends the processed audio to the
6//! output device (speakers/headphones). Meter data is communicated back
7//! via a callback channel.
8//!
9//! # Architecture
10//!
11//! ```text
12//! OS Mic → cpal input callback → deinterleave → FfiProcessor::process()
13//!                                                        │
14//!                                              ┌─────────┴──────────┐
15//!                                              │                    │
16//!                                         meter compute      interleave
17//!                                              │               → SPSC ring
18//!                                              ▼                    │
19//!                                        WebSocket broadcast        │
20//!                                                                   ▼
21//!                                              cpal output callback → Speakers
22//! ```
23
24mod device_setup;
25mod input_pipeline;
26mod metering;
27mod output_modifiers;
28mod output_routing;
29mod startup_wiring;
30
31use std::sync::Arc;
32
33use anyhow::Result;
34use cpal::{Device, Stream, StreamConfig};
35use wavecraft_processors::OscilloscopeFrameConsumer;
36use wavecraft_protocol::MeterUpdateNotification;
37
38use super::atomic_params::AtomicParameterBridge;
39use super::ffi_processor::DevAudioProcessor;
40
41/// Configuration for audio server.
42#[derive(Debug, Clone)]
43pub struct AudioConfig {
44    /// Desired sample rate (e.g., 44100.0). Falls back to system default.
45    pub sample_rate: f32,
46    /// Buffer size in samples.
47    pub buffer_size: u32,
48}
49
50/// Handle returned by `AudioServer::start()` that keeps both audio
51/// streams alive. Drop this handle to stop audio capture and playback.
52pub struct AudioHandle {
53    _input_stream: Stream,
54    _output_stream: Option<Stream>,
55}
56
57/// Audio server that processes OS input through a `DevAudioProcessor`
58/// and routes the processed audio to the output device.
59pub struct AudioServer {
60    processor: Box<dyn DevAudioProcessor>,
61    config: AudioConfig,
62    input_device: Device,
63    output_device: Device,
64    input_config: StreamConfig,
65    output_config: StreamConfig,
66    param_bridge: Arc<AtomicParameterBridge>,
67}
68
69impl AudioServer {
70    /// Create a new audio server with the given processor, config, and
71    /// parameter bridge for lock-free audio-thread parameter reads.
72    pub fn new(
73        processor: Box<dyn DevAudioProcessor>,
74        config: AudioConfig,
75        param_bridge: Arc<AtomicParameterBridge>,
76    ) -> Result<Self> {
77        let negotiated = device_setup::negotiate_default_devices_and_configs()?;
78
79        Ok(Self {
80            processor,
81            config,
82            input_device: negotiated.input_device,
83            output_device: negotiated.output_device,
84            input_config: negotiated.input_config,
85            output_config: negotiated.output_config,
86            param_bridge,
87        })
88    }
89
90    /// Start audio capture, processing, and playback.
91    ///
92    /// Returns an `AudioHandle` that keeps both streams alive, plus a
93    /// `MeterConsumer` for draining meter frames from a lock-free ring
94    /// buffer (RT-safe: no allocations on the audio thread).
95    ///
96    /// Drop the handle to stop audio.
97    pub fn start(
98        mut self,
99    ) -> Result<(
100        AudioHandle,
101        rtrb::Consumer<MeterUpdateNotification>,
102        OscilloscopeFrameConsumer,
103    )> {
104        // Set sample rate from the actual input device config
105        let actual_sample_rate = self.input_config.sample_rate.0 as f32;
106        self.processor.set_sample_rate(actual_sample_rate);
107
108        let processor = self.processor;
109        let buffer_size = self.config.buffer_size as usize;
110        let input_channels = self.input_config.channels as usize;
111        let output_channels = self.output_config.channels as usize;
112        let param_bridge = Arc::clone(&self.param_bridge);
113
114        startup_wiring::start_audio_io(startup_wiring::StartAudioIoContext {
115            input_device: &self.input_device,
116            input_config: &self.input_config,
117            output_device: &self.output_device,
118            output_config: &self.output_config,
119            processor,
120            buffer_size,
121            input_channels,
122            output_channels,
123            param_bridge,
124            actual_sample_rate,
125        })
126    }
127
128    /// Returns true if an output device is available for audio playback.
129    pub fn has_output(&self) -> bool {
130        true
131    }
132}