Skip to main content

atome/
lib.rs

1//! A real-time audio engine over cpal.
2//!
3//! [`AudioEngine`] owns every stream and decides what reaches which device. It
4//! is given a list of inputs and a list of outputs, both described as
5//! [`AtomeDevice`]s, and builds an [`InputClass`] or [`OutputClass`] for each.
6//!
7//! # Routing
8//!
9//! An input with no routing feeds every output. An input that names outputs
10//! feeds only those, so a talkback microphone can reach the monitors without
11//! also reaching the main mix.
12//!
13//! # Plugins
14//!
15//! A [`Plugin`] attaches at one of three levels, and where it attaches is what
16//! decides how much audio it hears:
17//!
18//! | Attached to | Hears |
19//! |---|---|
20//! | An input's `AtomeDevice` | that input alone, before routing |
21//! | An output's `AtomeDevice` | what that device plays, after mixing |
22//! | [`AudioEngine::new`] directly | everything |
23//!
24//! The shortest one to write is an [internal
25//! plugin](plugins::internal) — a Rust function compiled in, needing no
26//! feature and nothing installed on the machine:
27//!
28//! ```
29//! use atome::Plugin;
30//!
31//! let quieter = Plugin::internal("-6 dB", |buffer: &mut [f32], _channels| {
32//!     for sample in buffer {
33//!         *sample *= 0.5;
34//!     }
35//! });
36//! ```
37//!
38//! VST 3 and Audio Units are hosted too, each behind its own feature. See
39//! [`plugins`] for the full table.
40//!
41//! ```no_run
42//! use atome::{device::AtomeDevice, AudioEngine};
43//! use atome::output::{OutputType, SampleRate};
44//!
45//! let mic = AtomeDevice::default_input(OutputType::CoreAudio)
46//!     .expect("no input device");
47//! let speakers = AtomeDevice::default_output(OutputType::CoreAudio)
48//!     .expect("no output device");
49//!
50//! let engine = AudioEngine::<f32>::new(
51//!     vec![mic],
52//!     vec![speakers],
53//!     SampleRate::Hz48k,
54//!     vec![2],
55//!     Some(512),
56//!     vec![],
57//! )?;
58//! # Ok::<(), cpal::Error>(())
59//! ```
60
61use cpal::{Error, ErrorKind};
62
63pub mod device;
64pub mod import;
65pub mod input;
66pub mod output;
67pub mod plugins;
68
69pub use device::{AtomeDevice, Direction};
70pub use input::InputClass;
71pub use output::{OutputClass, SampleRate, SampleType};
72pub use plugins::Plugin;
73
74/// An input, its stream, and the outputs it feeds.
75pub struct EngineInput<S: SampleType> {
76    device: AtomeDevice,
77    input: InputClass<S>,
78    /// Indices into [`AudioEngine::outputs`], resolved once at construction so
79    /// the audio path never has to match a name against a list.
80    routes: Vec<usize>,
81}
82
83impl<S: SampleType> EngineInput<S> {
84    pub fn device(&self) -> &AtomeDevice {
85        &self.device
86    }
87
88    pub fn input(&self) -> &InputClass<S> {
89        &self.input
90    }
91
92    pub fn input_mut(&mut self) -> &mut InputClass<S> {
93        &mut self.input
94    }
95
96    /// Which outputs this input feeds, by index.
97    pub fn routes(&self) -> &[usize] {
98        &self.routes
99    }
100}
101
102/// An output and its stream.
103pub struct EngineOutput<S: SampleType> {
104    device: AtomeDevice,
105    output: OutputClass<S>,
106}
107
108impl<S: SampleType> EngineOutput<S> {
109    pub fn device(&self) -> &AtomeDevice {
110        &self.device
111    }
112
113    pub fn output(&self) -> &OutputClass<S> {
114        &self.output
115    }
116
117    pub fn output_mut(&mut self) -> &mut OutputClass<S> {
118        &mut self.output
119    }
120}
121
122/// The main audio engine: every stream, and the routing between them.
123pub struct AudioEngine<S: SampleType> {
124    inputs: Vec<EngineInput<S>>,
125    outputs: Vec<EngineOutput<S>>,
126    sample_rate: SampleRate,
127    buffer_size: Option<i32>,
128    /// Applied to everything, whichever device it came from or goes to.
129    plugins: Vec<Plugin>,
130}
131
132impl<S: SampleType> AudioEngine<S> {
133    /// Builds an engine over the given devices.
134    ///
135    /// `output_channels` gives one channel count per output device, in the same
136    /// order — `[2, 2, 5]` for two stereo pairs and a five-channel rig. It is a
137    /// list rather than one number because devices on one engine genuinely
138    /// differ, and pairing them off by position is checked rather than assumed:
139    /// a list of the wrong length is an error, not a silent truncation.
140    ///
141    /// Inputs take their channel count from the hardware instead, since a
142    /// capture device gives what it has.
143    ///
144    /// Nothing is started here. Streams are built but paused, exactly as cpal
145    /// leaves them.
146    ///
147    /// # Errors
148    ///
149    /// - A device in `inputs` that is not an input, or in `outputs` that is not
150    ///   an output
151    /// - `output_channels` not the same length as `outputs`
152    /// - An input routed to a name that matches no output
153    pub fn new(
154        inputs: Vec<AtomeDevice>,
155        outputs: Vec<AtomeDevice>,
156        sample_rate: SampleRate,
157        output_channels: Vec<u16>,
158        buffer_size: Option<i32>,
159        plugins: Vec<Plugin>,
160    ) -> Result<Self, Error> {
161        if output_channels.len() != outputs.len() {
162            return Err(Error::with_message(
163                ErrorKind::InvalidInput,
164                format!(
165                    "{} output devices but {} channel counts",
166                    outputs.len(),
167                    output_channels.len()
168                ),
169            ));
170        }
171
172        for device in &outputs {
173            if device.direction() != Direction::Output {
174                return Err(Error::with_message(
175                    ErrorKind::InvalidInput,
176                    format!("{} is an input device, listed as an output", device.name()),
177                ));
178            }
179        }
180
181        for device in &inputs {
182            if device.direction() != Direction::Input {
183                return Err(Error::with_message(
184                    ErrorKind::InvalidInput,
185                    format!("{} is an output device, listed as an input", device.name()),
186                ));
187            }
188        }
189
190        // Outputs first: an input's routing names them, so they have to exist
191        // before it can be resolved.
192        let built_outputs: Vec<EngineOutput<S>> = outputs
193            .into_iter()
194            .zip(output_channels)
195            .map(|(device, channels)| {
196                let output = OutputClass::new(
197                    Some(device.device().clone()),
198                    device.host(),
199                    channels,
200                    sample_rate,
201                    buffer_size,
202                );
203
204                EngineOutput { device, output }
205            })
206            .collect();
207
208        let names: Vec<String> = built_outputs
209            .iter()
210            .map(|output| output.device.name())
211            .collect();
212
213        let built_inputs = inputs
214            .into_iter()
215            .map(|device| {
216                let routes = resolve_routes(&device, &names)?;
217
218                // The callback is a placeholder: carrying captured audio to the
219                // routed outputs is section 2.3's remaining work, and needs a
220                // lock-free hand-off rather than anything that can be done from
221                // inside the audio callback.
222                let mut input = InputClass::new(
223                    Some(device.device().clone()),
224                    device.host(),
225                    sample_rate,
226                    buffer_size,
227                    |_captured: &[S]| {},
228                );
229
230                // The same routing the indices above describe, as devices, so
231                // an `InputClass` driven on its own knows where it is going
232                // without asking the engine.
233                if device.routing().is_some() {
234                    let devices = routes
235                        .iter()
236                        .map(|index| built_outputs[*index].device.device().clone())
237                        .collect();
238                    input.set_routing(Some(devices));
239                }
240
241                Ok(EngineInput {
242                    device,
243                    input,
244                    routes,
245                })
246            })
247            .collect::<Result<Vec<_>, Error>>()?;
248
249        Ok(AudioEngine {
250            inputs: built_inputs,
251            outputs: built_outputs,
252            sample_rate,
253            buffer_size,
254            plugins,
255        })
256    }
257
258    pub fn inputs(&self) -> &[EngineInput<S>] {
259        &self.inputs
260    }
261
262    pub fn inputs_mut(&mut self) -> &mut [EngineInput<S>] {
263        &mut self.inputs
264    }
265
266    pub fn outputs(&self) -> &[EngineOutput<S>] {
267        &self.outputs
268    }
269
270    pub fn outputs_mut(&mut self) -> &mut [EngineOutput<S>] {
271        &mut self.outputs
272    }
273
274    pub fn sample_rate(&self) -> SampleRate {
275        self.sample_rate
276    }
277
278    pub fn buffer_size(&self) -> Option<i32> {
279        self.buffer_size
280    }
281
282    /// The plugins applied to everything.
283    pub fn plugins(&self) -> &[Plugin] {
284        &self.plugins
285    }
286
287    /// Applies the plugins attached to input `index`, in place.
288    ///
289    /// This is the input's own chain and nothing else — the engine-wide chain
290    /// runs later, in [`apply_engine_plugins`](Self::apply_engine_plugins), and
291    /// the destination's chain later still. Called before routing, so an input
292    /// heard by several outputs is processed once rather than once per
293    /// destination.
294    ///
295    /// Does nothing if that input has no plugins.
296    pub fn apply_input_plugins(&mut self, index: usize, buffer: &mut [S]) -> Result<(), Error> {
297        let input = self
298            .inputs
299            .get_mut(index)
300            .ok_or_else(|| unknown(index, "input"))?;
301
302        let channels = input.input.channels();
303        for plugin in input.device.plugins_mut() {
304            plugin.apply(buffer, channels)?;
305        }
306
307        Ok(())
308    }
309
310    /// Applies the plugins attached to output `index`, in place.
311    ///
312    /// The last chain to run, and the narrowest: it hears what this device is
313    /// about to play and nothing that goes anywhere else.
314    pub fn apply_output_plugins(&mut self, index: usize, buffer: &mut [S]) -> Result<(), Error> {
315        let output = self
316            .outputs
317            .get_mut(index)
318            .ok_or_else(|| unknown(index, "output"))?;
319
320        let channels = output.output.channels();
321        for plugin in output.device.plugins_mut() {
322            plugin.apply(buffer, channels)?;
323        }
324
325        Ok(())
326    }
327
328    /// Applies the engine-wide plugins, in place.
329    ///
330    /// These were handed to [`new`](Self::new) directly rather than attached to
331    /// a device, so they hear everything — every input, on its way to every
332    /// output. `channels` says how `buffer` is laid out, since the engine's
333    /// devices do not agree on one count.
334    pub fn apply_engine_plugins(
335        &mut self,
336        buffer: &mut [S],
337        channels: u16,
338    ) -> Result<(), Error> {
339        for plugin in &mut self.plugins {
340            plugin.apply(buffer, channels)?;
341        }
342
343        Ok(())
344    }
345
346    /// Runs every chain that applies to audio captured on input `index` and
347    /// bound for output `to`, in the order they belong in.
348    ///
349    /// The order is the point of having three levels: the input's own
350    /// processing happens where the audio is still one source, the engine's in
351    /// the middle, and the destination's last, when it is what that device will
352    /// actually play.
353    pub fn apply_plugins(
354        &mut self,
355        index: usize,
356        to: usize,
357        buffer: &mut [S],
358        channels: u16,
359    ) -> Result<(), Error> {
360        self.apply_input_plugins(index, buffer)?;
361        self.apply_engine_plugins(buffer, channels)?;
362        self.apply_output_plugins(to, buffer)
363    }
364}
365
366impl<S: SampleType> std::fmt::Debug for EngineInput<S> {
367    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368        formatter
369            .debug_struct("EngineInput")
370            .field("device", &self.device.name())
371            .field("channels", &self.input.channels())
372            .field("plugins", &self.device.plugins().len())
373            .field("routes", &self.routes)
374            .finish()
375    }
376}
377
378impl<S: SampleType> std::fmt::Debug for EngineOutput<S> {
379    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380        formatter
381            .debug_struct("EngineOutput")
382            .field("device", &self.device.name())
383            .field("channels", &self.output.channels())
384            .field("plugins", &self.device.plugins().len())
385            .finish()
386    }
387}
388
389/// Reports the wiring rather than the streams: which devices, how many channels
390/// each, and what routes where. None of the cpal types underneath have `Debug`,
391/// and none of them would say anything useful if they did.
392impl<S: SampleType> std::fmt::Debug for AudioEngine<S> {
393    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394        formatter
395            .debug_struct("AudioEngine")
396            .field("sample_rate", &self.sample_rate)
397            .field("buffer_size", &self.buffer_size)
398            .field("inputs", &self.inputs)
399            .field("outputs", &self.outputs)
400            .field("plugins", &self.plugins.len())
401            .finish()
402    }
403}
404
405/// Turns an input's routing names into indices into the output list.
406///
407/// Resolved once, here, so that the audio path is an index lookup rather than a
408/// string comparison, and so a name that matches nothing is caught while there
409/// is still somewhere sensible to report it.
410fn resolve_routes(device: &AtomeDevice, outputs: &[String]) -> Result<Vec<usize>, Error> {
411    let Some(routing) = device.routing() else {
412        // No routing named: this input feeds everything.
413        return Ok((0..outputs.len()).collect());
414    };
415
416    routing
417        .iter()
418        .map(|wanted| {
419            outputs
420                .iter()
421                .position(|name| name == wanted)
422                .ok_or_else(|| {
423                    Error::with_message(
424                        ErrorKind::InvalidInput,
425                        format!(
426                            "{} is routed to {wanted:?}, which is not one of the outputs",
427                            device.name()
428                        ),
429                    )
430                })
431        })
432        .collect()
433}
434
435fn unknown(index: usize, what: &str) -> Error {
436    Error::with_message(
437        ErrorKind::InvalidInput,
438        format!("no {what} at index {index}"),
439    )
440}