use std::sync::mpsc::{Receiver, Sender};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::StreamConfig;
use audio_processor_traits::{AudioContext, AudioProcessor, AudioProcessorSettings};
use crate::standalone_cpal::output_handling::BuildOutputStreamParams;
use crate::StandaloneProcessor;
#[cfg(feature = "midi")]
use super::midi::MidiContext;
use super::{
error::AudioThreadError, input_handling, options, output_handling, IOConfiguration,
ResolvedStandaloneConfiguration,
};
pub fn audio_thread_main<SP: StandaloneProcessor, Host: HostTrait>(
host: Host,
host_name: String,
mut app: SP,
#[cfg(feature = "midi")] midi_context: Option<MidiContext>,
configuration_tx: Sender<ResolvedStandaloneConfiguration>,
stop_signal_rx: Receiver<()>,
errors_tx: Sender<AudioThreadError>,
) -> Result<(), AudioThreadError> {
log::info!("Using host={}", host_name);
let buffer_size = 512;
let sample_rate = {
#[cfg(not(target_os = "ios"))]
{
44100
}
#[cfg(target_os = "ios")]
{
48000
}
};
let options = app.options();
let accepts_input = options.accepts_input;
let input_tuple = if accepts_input {
Some(options::configure_input_device(
&host,
options,
buffer_size,
sample_rate,
))
} else {
None
};
let (output_device, output_config) =
options::configure_output_device(host, options, buffer_size, sample_rate)?;
let num_output_channels = output_config.channels.into();
let num_input_channels = input_tuple
.as_ref()
.and_then(|t| t.as_ref().ok().map(|(_, input_config)| input_config))
.map(|input_config| input_config.channels.into())
.unwrap_or(num_output_channels);
let input_tuple = if let Some(input_tuple) = input_tuple {
input_tuple.map(Some)
} else {
Ok(None)
}?;
let settings = AudioProcessorSettings::new(
output_config.sample_rate.0 as f32,
num_input_channels,
num_output_channels,
buffer_size,
);
let mut context = AudioContext::from(settings);
log::info!("Preparing processor {:?}", settings);
app.processor().prepare(&mut context);
log::info!("Sending back configuration");
configuration_tx
.send(ResolvedStandaloneConfiguration {
host: host_name,
input_configuration: input_tuple
.as_ref()
.map(|(input_device, config)| IOConfiguration::new(input_device, config)),
output_configuration: IOConfiguration::new(&output_device, &output_config),
})
.expect("Failed to send configuration message");
let cpal_streams: AudioThreadCPalStreams<Host::Device> = AudioThreadCPalStreams {
output_config,
input_tuple,
output_device,
};
let run_params: AudioThreadRunParams<Host::Device> = AudioThreadRunParams {
io_hints: AudioThreadIOHints {
buffer_size,
num_output_channels,
num_input_channels,
},
cpal_streams,
#[cfg(feature = "midi")]
midi_context,
};
let streams = audio_thread_run_processor(run_params, app, errors_tx)?;
let _ = stop_signal_rx.recv();
drop(streams);
Ok(())
}
#[derive(Debug)]
struct AudioThreadIOHints {
buffer_size: usize,
num_output_channels: usize,
num_input_channels: usize,
}
#[derive(Debug)]
struct AudioThreadCPalStreams<D: DeviceTrait> {
output_config: StreamConfig,
input_tuple: Option<(D, StreamConfig)>,
output_device: D,
}
struct AudioThreadRunParams<D: DeviceTrait> {
#[cfg(feature = "midi")]
midi_context: Option<MidiContext>,
io_hints: AudioThreadIOHints,
cpal_streams: AudioThreadCPalStreams<D>,
}
fn audio_thread_run_processor<D: DeviceTrait>(
params: AudioThreadRunParams<D>,
app: impl StandaloneProcessor,
errors_tx: Sender<AudioThreadError>,
) -> Result<(Option<D::Stream>, D::Stream), AudioThreadError> {
log::info!(
"Starting audio streams\n params.io_hints={:#?}\n",
params.io_hints
);
let AudioThreadRunParams {
#[cfg(feature = "midi")]
midi_context,
io_hints,
cpal_streams,
} = params;
let AudioThreadIOHints {
buffer_size,
num_output_channels,
num_input_channels,
} = io_hints;
let AudioThreadCPalStreams {
output_config,
input_tuple,
output_device,
} = cpal_streams;
let build_streams = {
let errors_tx = errors_tx.clone();
move || -> Result<(Option<D::Stream>, D::Stream), AudioThreadError> {
let buffer = ringbuf::RingBuffer::new(buffer_size * 10);
let (producer, consumer) = buffer.split();
let input_stream = input_tuple
.map(|(input_device, input_config)| {
input_handling::build_input_stream(
input_device,
input_config,
producer,
errors_tx.clone(),
)
})
.map_or(Ok(None), |v| v.map(Some))?;
let audio_context = AudioContext::default();
let output_stream = output_handling::build_output_stream(
BuildOutputStreamParams {
app,
#[cfg(feature = "midi")]
midi_context,
audio_context,
num_output_channels,
num_input_channels,
input_consumer: consumer,
output_device,
output_config,
},
errors_tx,
)?;
Ok((input_stream, output_stream))
}
};
match build_streams() {
Ok((input_stream, output_stream)) => {
log::info!("Audio streams starting on audio-thread");
let play = || -> Result<(Option<D::Stream>, D::Stream), AudioThreadError> {
if let Some(input_stream) = &input_stream {
input_stream
.play()
.map_err(AudioThreadError::InputPlayStreamError)?;
}
output_stream
.play()
.map_err(AudioThreadError::OutputPlayStreamError)?;
Ok((input_stream, output_stream))
};
match play() {
Ok(streams) => {
log::info!("Audio streams started");
Ok(streams)
}
Err(err) => {
log::error!("Audio-thread failed to start with {}", err);
Err(err)
}
}
}
Err(err) => {
log::error!("Audio-thread failed to start with {}", err);
Err(err)
}
}
}