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}