use std::sync::mpsc::{channel, Receiver, Sender};
use basedrop::Handle;
use cpal::traits::DeviceTrait;
use cpal::{BufferSize, ChannelCount, SampleRate, StreamConfig};
use audio_processor_traits::{AudioProcessor, MidiEventHandler};
use crate::standalone_processor::{
StandaloneAudioOnlyProcessor, StandaloneProcessor, StandaloneProcessorImpl,
};
use self::error::AudioThreadError;
#[cfg(feature = "midi")]
use self::midi::{initialize_midi_host, MidiReference};
pub use self::mock_cpal::virtual_host::{VirtualHost, VirtualHostDevice, VirtualHostStream};
pub use self::options::AudioIOMode;
mod audio_thread;
mod error;
mod input_handling;
pub mod mock_cpal;
mod options;
mod output_handling;
#[cfg(feature = "midi")]
mod midi;
pub fn audio_processor_start_with_midi<
Processor: AudioProcessor<SampleType = f32> + MidiEventHandler + Send + 'static,
>(
audio_processor: Processor,
handle: &Handle,
) -> StandaloneHandles {
let app = StandaloneProcessorImpl::new(audio_processor);
standalone_start_with::<StandaloneProcessorImpl<Processor>, cpal::Host>(
app,
StandaloneStartOptions {
handle: Some(handle.clone()),
..StandaloneStartOptions::<cpal::Host>::default()
},
)
}
pub fn audio_processor_start<Processor: AudioProcessor<SampleType = f32> + Send + 'static>(
audio_processor: Processor,
) -> StandaloneHandles {
let app = StandaloneAudioOnlyProcessor::new(audio_processor, Default::default());
standalone_start(app)
}
#[derive(Debug)]
pub struct ResolvedStandaloneConfiguration {
host: String,
input_configuration: Option<IOConfiguration>,
output_configuration: IOConfiguration,
}
impl ResolvedStandaloneConfiguration {
pub fn host(&self) -> &str {
&self.host
}
pub fn input_configuration(&self) -> &Option<IOConfiguration> {
&self.input_configuration
}
pub fn output_configuration(&self) -> &IOConfiguration {
&self.output_configuration
}
}
#[derive(Debug)]
pub struct IOConfiguration {
name: String,
buffer_size: BufferSize,
sample_rate: SampleRate,
num_channels: ChannelCount,
}
impl IOConfiguration {
pub fn new(device: &impl DeviceTrait, config: &StreamConfig) -> IOConfiguration {
IOConfiguration {
name: device.name().unwrap(),
sample_rate: config.sample_rate,
buffer_size: config.buffer_size,
num_channels: config.channels,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn buffer_size(&self) -> &BufferSize {
&self.buffer_size
}
pub fn sample_rate(&self) -> SampleRate {
self.sample_rate
}
pub fn num_channels(&self) -> ChannelCount {
self.num_channels
}
}
pub struct StandaloneHandles {
configuration: ResolvedStandaloneConfiguration,
handle: Option<std::thread::JoinHandle<()>>,
stop_signal_tx: Sender<()>,
errors_rx: Receiver<AudioThreadError>,
#[cfg(feature = "midi")]
#[allow(unused)]
midi_reference: MidiReference,
}
impl Drop for StandaloneHandles {
fn drop(&mut self) {
if let Some(handle) = self.handle.take() {
let _ = self.stop_signal_tx.send(());
handle.join().unwrap();
}
log::info!("Cleaning-up standalone handles");
}
}
impl StandaloneHandles {
pub fn configuration(&self) -> &ResolvedStandaloneConfiguration {
&self.configuration
}
pub fn errors_rx(&self) -> &Receiver<AudioThreadError> {
&self.errors_rx
}
}
pub struct StandaloneStartOptions<Host: cpal::traits::HostTrait> {
pub host: Host,
pub host_name: String,
pub handle: Option<Handle>,
}
impl Default for StandaloneStartOptions<cpal::Host> {
fn default() -> Self {
let host = cpal::default_host();
let host_name = host.id().name().to_string();
Self {
host,
host_name,
handle: Some(audio_garbage_collector::handle().clone()),
}
}
}
pub fn standalone_start<SP: StandaloneProcessor>(app: SP) -> StandaloneHandles {
standalone_start_with::<SP, cpal::Host>(app, StandaloneStartOptions::<cpal::Host>::default())
}
pub fn standalone_start_with<
SP: StandaloneProcessor,
Host: cpal::traits::HostTrait + Send + 'static,
>(
#[allow(unused_mut)] mut app: SP,
options: StandaloneStartOptions<Host>,
) -> StandaloneHandles {
let StandaloneStartOptions {
host,
host_name,
handle,
} = options;
let _ = wisual_logger::try_init_from_env();
#[cfg(feature = "midi")]
let (midi_reference, midi_context) = initialize_midi_host(&mut app, handle.as_ref());
#[cfg(not(feature = "midi"))]
std::mem::forget(handle); let (errors_tx, errors_rx) = channel();
let (configuration_tx, configuration_rx) = channel();
let (stop_signal_tx, stop_signal_rx) = channel();
let handle = std::thread::Builder::new()
.name(String::from("audio_thread"))
.spawn(move || {
std::panic::set_hook(Box::new(|panic_info| {
log::error!("Audio-thread panicked: {:?}", panic_info);
}));
let result = audio_thread::audio_thread_main(
host,
host_name,
app,
#[cfg(feature = "midi")]
midi_context,
configuration_tx,
stop_signal_rx,
errors_tx.clone(),
);
if let Err(err) = result {
log::error!("Audio-thread failed with {}", err);
let _ = errors_tx.send(err);
}
})
.unwrap();
let configuration = configuration_rx
.recv()
.expect("Failed to receive cpal configuration message");
log::info!("Received configuration:\n {:#?}\n", configuration);
StandaloneHandles {
configuration,
handle: Some(handle),
stop_signal_tx,
errors_rx,
#[cfg(feature = "midi")]
midi_reference,
}
}
pub fn standalone_start_for_test(
standalone_processor: impl StandaloneProcessor,
) -> StandaloneHandles {
log::warn!("Starting testing CPAL virtual host");
standalone_start_with(
standalone_processor,
StandaloneStartOptions {
host: VirtualHost::default(),
host_name: "Test Host".to_string(),
handle: Some(audio_garbage_collector::handle().clone()),
},
)
}
#[macro_export]
macro_rules! standalone_start_for_env {
($standalone_processor:ident) => {{
#[cfg(test)]
{
::audio_processor_standalone::standalone_cpal::standalone_start_for_test(
$standalone_processor,
)
}
#[cfg(not(test))]
{
::audio_processor_standalone::standalone_cpal::standalone_start($standalone_processor)
}
}};
}
#[macro_export]
macro_rules! generic_standalone_run {
($t: ident) => {
match ::std::env::var("GUI") {
Ok(value) if value == "true" => {
use ::audio_processor_traits::parameters::{
AudioProcessorHandleProvider, AudioProcessorHandleRef,
};
let handle: AudioProcessorHandleRef =
AudioProcessorHandleProvider::generic_handle(&$t);
let _audio_handles = ::audio_processor_standalone::audio_processor_start($t);
::audio_processor_standalone_gui::open(handle);
}
_ => {
::audio_processor_standalone::audio_processor_main($t);
}
}
};
}
#[cfg(test)]
mod test {
use audio_processor_traits::NoopAudioProcessor;
use super::*;
#[test]
fn test_standalone_start_and_stop_processor() {
let _ = wisual_logger::try_init_from_env();
let processor = NoopAudioProcessor::default();
let processor = StandaloneAudioOnlyProcessor::new(processor, Default::default());
let handles = standalone_start_for_test(processor);
drop(handles);
}
}