From c46a044cb416fa28b31d7af1c49758b29aa66918 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sun, 21 Aug 2022 17:01:43 +0200 Subject: [PATCH] Add audio output for the CPAL backend --- src/wrapper/standalone/backend/cpal.rs | 167 +++++++++++++++++++++++-- 1 file changed, 154 insertions(+), 13 deletions(-) diff --git a/src/wrapper/standalone/backend/cpal.rs b/src/wrapper/standalone/backend/cpal.rs index 15b6ee10..eb0ee5d3 100644 --- a/src/wrapper/standalone/backend/cpal.rs +++ b/src/wrapper/standalone/backend/cpal.rs @@ -1,5 +1,6 @@ use anyhow::{Context, Result}; -use cpal::{traits::*, Device, SampleFormat, StreamConfig}; +use cpal::{traits::*, Device, OutputCallbackInfo, Sample, SampleFormat, StreamConfig}; +use crossbeam::sync::{Parker, Unparker}; use super::super::config::WrapperConfig; use super::Backend; @@ -10,22 +11,70 @@ use crate::plugin::{AuxiliaryIOConfig, BusConfig, Plugin}; /// Uses CPAL for audio and midir for MIDI. pub struct Cpal { + config: WrapperConfig, bus_config: BusConfig, - input_device: Option, - input_config: Option, + + input: Option<(Device, StreamConfig, SampleFormat)>, + output_device: Device, output_config: StreamConfig, + output_sample_format: SampleFormat, // TODO: MIDI } impl Backend for Cpal { fn run( &mut self, - mut cb: impl FnMut(&mut Buffer, Transport, &[NoteEvent], &mut Vec) -> bool + cb: impl FnMut(&mut Buffer, Transport, &[NoteEvent], &mut Vec) -> bool + 'static + Send, ) { - // TODO: + // The CPAL audio devices may not accept floating point samples, so all of the actual audio + // handling and buffer management handles in the `build_*_data_callback()` functions defined + // below + + // This thread needs to be blocked until audio processing ends as CPAL processes the streams + // on another thread instead of blocking + // TODO: Move this to the output stream handling + // TODO: Input stream + // TODO: Block the main thread until this breaky thing + let parker = Parker::new(); + let unparker = parker.unparker().clone(); + + let error_cb = { + let unparker = unparker.clone(); + move |err| { + nih_error!("Error during playback: {err:#}"); + unparker.clone().unpark(); + } + }; + + let output_stream = match self.output_sample_format { + SampleFormat::I16 => self.output_device.build_output_stream( + &self.output_config, + self.build_output_data_callback::(unparker, cb), + error_cb, + ), + SampleFormat::U16 => self.output_device.build_output_stream( + &self.output_config, + self.build_output_data_callback::(unparker, cb), + error_cb, + ), + SampleFormat::F32 => self.output_device.build_output_stream( + &self.output_config, + self.build_output_data_callback::(unparker, cb), + error_cb, + ), + } + .expect("Fatal error creating the output stream"); + + // TODO: Wait a period before doing this when also reading the input + output_stream + .play() + .expect("Fatal error trying to start the output stream"); + + // Wait for the audio thread to exit + parker.park(); } } @@ -38,6 +87,7 @@ impl Cpal { // No input device is connected unless requested by the user to avoid feedback loops let input_device = config .input_device + .as_ref() .map(|name| -> Result { let device = host .input_devices() @@ -62,7 +112,7 @@ impl Cpal { }) .transpose()?; - let output_device = match config.output_device { + let output_device = match config.output_device.as_ref() { Some(name) => host .output_devices() .context("No audio output devices available")? @@ -91,9 +141,8 @@ impl Cpal { let requested_sample_rate = cpal::SampleRate(config.sample_rate as u32); let requested_buffer_size = cpal::BufferSize::Fixed(config.period_size); - let input_config = input_device - .as_ref() - .map(|device| -> Result { + let input = input_device + .map(|device| -> Result<(Device, StreamConfig, SampleFormat)> { let input_configs: Vec<_> = device .supported_input_configs() .context("Could not get supported audio input configurations")? @@ -122,11 +171,14 @@ impl Cpal { })?; // We already checked that these settings are valid - Ok(StreamConfig { + let input_config = StreamConfig { channels: input_config_range.channels(), sample_rate: requested_sample_rate, buffer_size: requested_buffer_size.clone(), - }) + }; + let input_sample_format = input_config_range.sample_format(); + + Ok((device, input_config, input_sample_format)) }) .transpose()?; @@ -160,13 +212,102 @@ impl Cpal { sample_rate: requested_sample_rate, buffer_size: requested_buffer_size, }; + let output_sample_format = output_config_range.sample_format(); Ok(Cpal { + config, bus_config, - input_device, - input_config, + + input, + output_device, output_config, + output_sample_format, }) } + + fn build_output_data_callback( + &self, + unparker: Unparker, + mut cb: impl FnMut(&mut Buffer, Transport, &[NoteEvent], &mut Vec) -> bool + + 'static + + Send, + ) -> impl FnMut(&mut [T], &OutputCallbackInfo) + Send + 'static { + // We'll receive interlaced input samples from CPAL. These need to converted to deinterlaced + // channels, processed, and then copied those back to an interlaced buffer for the output. + // This needs to be wrapped in a struct like this and boxed because the `channels` vectors + // need to live just as long as `buffer` when they get moved into the closure. FIXME: This + // is pretty nasty, come up with a cleaner alternative + let mut channels = vec![ + vec![0.0f32; self.config.period_size as usize]; + self.bus_config.num_output_channels as usize + ]; + let mut buffer = Buffer::default(); + unsafe { + buffer.with_raw_vec(|output_slices| { + // Pre-allocate enough storage, the pointers are set in the data callback because + // `channels` will have been moved between now and the next callback + output_slices.resize_with(channels.len(), || &mut []); + }) + } + + // TODO: MIDI input and output + let mut midi_input_events = Vec::with_capacity(1024); + let mut midi_output_events = Vec::with_capacity(1024); + + // Can't borrow from `self` in the callback + let config = self.config.clone(); + let mut num_processed_samples = 0; + + move |data, _info| { + // Things may have been moved in between callbacks, so these pointers need to be set up + // agian on each invocation + unsafe { + buffer.with_raw_vec(|output_slices| { + for (output_slice, channel) in output_slices.iter_mut().zip(channels.iter_mut()) + { + // SAFETY: `channels` is no longer used directly after this, and it outlives + // the data closure + *output_slice = &mut *(channel.as_mut_slice() as *mut [f32]); + } + }) + } + + let mut transport = Transport::new(config.sample_rate); + transport.pos_samples = Some(num_processed_samples); + transport.tempo = Some(config.tempo as f64); + transport.time_sig_numerator = Some(config.timesig_num as i32); + transport.time_sig_denominator = Some(config.timesig_denom as i32); + transport.playing = true; + + for channel in buffer.as_slice() { + channel.fill(0.0); + } + + midi_output_events.clear(); + if !cb( + &mut buffer, + transport, + &midi_input_events, + &mut midi_output_events, + ) { + // TODO: Some way to immediately terminate the stream here would be nice + unparker.unpark(); + return; + } + + // The buffer's samples need to be written to `data` in an interlaced format + for (output_sample, buffer_sample) in data.iter_mut().zip( + buffer + .iter_samples() + .flat_map(|channels| channels.into_iter()), + ) { + *output_sample = T::from(buffer_sample); + } + + // TODO: Handle MIDI output events + + num_processed_samples += buffer.len() as i64; + } + } }