use basedrop::Handle;
use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
BufferSize, Device, Host, SampleRate, Stream, StreamConfig,
};
use ringbuf::{Consumer, Producer};
use audio_processor_traits::{
AudioBuffer, AudioProcessor, AudioProcessorSettings, InterleavedAudioBuffer, MidiEventHandler,
};
use midi::{flush_midi_events, initialize_midi_host, MidiContext, MidiReference};
use crate::standalone_processor::{
StandaloneAudioOnlyProcessor, StandaloneProcessor, StandaloneProcessorImpl,
};
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(app, Some(handle))
}
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, None)
}
pub struct StandaloneHandles {
#[allow(unused)]
handle: std::thread::JoinHandle<()>,
#[allow(unused)]
midi_reference: MidiReference,
}
pub fn standalone_start(
mut app: impl StandaloneProcessor,
handle: Option<&Handle>,
) -> StandaloneHandles {
let _ = wisual_logger::try_init_from_env();
let (midi_reference, midi_context) = initialize_midi_host(&mut app, handle);
let handle = std::thread::Builder::new()
.name(String::from("audio_thread"))
.spawn(move || {
let host = cpal::default_host();
log::info!("Using host: {}", host.id().name());
let buffer_size = 512;
let sample_rate = {
#[cfg(not(target_os = "ios"))]
{
44100
}
#[cfg(target_os = "ios")]
{
48000
}
};
let accepts_input = app.options().accepts_input;
let input_tuple = if accepts_input {
Some(configure_input_device(&host, buffer_size, sample_rate))
} else {
None
};
let (output_device, output_config) =
configure_output_device(host, buffer_size, sample_rate);
let num_output_channels = output_config.channels.into();
let num_input_channels = input_tuple
.as_ref()
.map(|(_, input_config)| input_config.channels.into())
.unwrap_or(num_output_channels);
let settings = AudioProcessorSettings::new(
output_config.sample_rate.0 as f32,
num_input_channels,
num_output_channels,
buffer_size,
);
app.processor().prepare(settings);
audio_thread_run(
AudioThreadRunParams {
io_hints: AudioThreadIOHints {
buffer_size,
num_output_channels,
num_input_channels,
},
cpal_streams: AudioThreadCPalStreams {
output_config,
input_tuple,
output_device,
},
midi_context,
},
app,
);
})
.unwrap();
StandaloneHandles {
handle,
midi_reference,
}
}
#[derive(thiserror::Error, Debug)]
#[allow(clippy::enum_variant_names)]
enum AudioThreadError {
#[error("Failed to configure input stream")]
BuildInputStreamError(cpal::BuildStreamError),
#[error("Failed to configure output stream")]
BuildOutputStreamError(cpal::BuildStreamError),
#[error("Failed to start input stream")]
InputStreamError(cpal::PlayStreamError),
#[error("Failed to start output stream")]
OutputStreamError(cpal::PlayStreamError),
}
struct AudioThreadIOHints {
buffer_size: usize,
num_output_channels: usize,
num_input_channels: usize,
}
struct AudioThreadCPalStreams {
output_config: StreamConfig,
input_tuple: Option<(Device, StreamConfig)>,
output_device: Device,
}
struct AudioThreadRunParams {
midi_context: Option<MidiContext>,
io_hints: AudioThreadIOHints,
cpal_streams: AudioThreadCPalStreams,
}
fn audio_thread_run(params: AudioThreadRunParams, app: impl StandaloneProcessor) {
let AudioThreadRunParams {
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 =
move || -> Result<(Option<cpal::Stream>, cpal::Stream), AudioThreadError> {
let buffer = ringbuf::RingBuffer::new((buffer_size * 10) as usize);
let (producer, consumer) = buffer.split();
let input_stream = build_input_stream(input_tuple, producer)?;
let output_stream = build_output_stream(
app,
midi_context,
num_output_channels,
num_input_channels,
consumer,
output_device,
output_config,
)?;
Ok((input_stream, output_stream))
};
match build_streams() {
Ok((input_stream, output_stream)) => {
log::info!("Audio streams starting on audio-thread");
let play = || -> Result<(), AudioThreadError> {
if let Some(input_stream) = &input_stream {
input_stream
.play()
.map_err(AudioThreadError::InputStreamError)?;
}
output_stream
.play()
.map_err(AudioThreadError::OutputStreamError)?;
Ok(())
};
if let Err(err) = play() {
log::error!("Audio-thread failed to start with {}", err);
return;
}
log::info!("Audio streams started");
std::thread::park();
}
Err(err) => {
log::error!("Audio-thread failed to start with {}", err);
}
}
}
fn build_output_stream(
mut app: impl StandaloneProcessor,
mut midi_context: Option<MidiContext>,
num_output_channels: usize,
num_input_channels: usize,
mut input_consumer: Consumer<f32>,
output_device: Device,
output_config: StreamConfig,
) -> Result<Stream, AudioThreadError> {
log::info!(
"num_input_channels={} num_output_channels={} sample_rate={}",
num_input_channels,
num_output_channels,
output_config.sample_rate.0
);
let output_stream = output_device
.build_output_stream(
&output_config,
move |data: &mut [f32], _output_info: &cpal::OutputCallbackInfo| {
output_stream_with_context(
midi_context.as_mut(),
&mut app,
num_input_channels,
num_output_channels,
&mut input_consumer,
data,
);
},
|err| {
log::error!("Playback error: {:?}", err);
},
)
.map_err(AudioThreadError::BuildOutputStreamError)?;
Ok(output_stream)
}
fn build_input_stream(
input_tuple: Option<(Device, StreamConfig)>,
mut producer: Producer<f32>,
) -> Result<Option<Stream>, AudioThreadError> {
let input_stream = input_tuple.as_ref().map(|(input_device, input_config)| {
input_device
.build_input_stream(
input_config,
move |data: &[f32], _input_info: &cpal::InputCallbackInfo| {
input_stream_callback(&mut producer, data)
},
|err| {
log::error!("Input error: {:?}", err);
},
)
.map_err(AudioThreadError::BuildInputStreamError)
});
let input_stream = if let Some(input_stream) = input_stream {
Some(input_stream?)
} else {
None
};
Ok(input_stream)
}
fn configure_input_device(
host: &Host,
buffer_size: usize,
sample_rate: usize,
) -> (cpal::Device, StreamConfig) {
let input_device = host.default_input_device().unwrap();
let supported_configs = input_device.supported_input_configs().unwrap();
let mut supports_stereo = false;
for config in supported_configs {
log::info!(" INPUT Supported config: {:?}", config);
if config.channels() > 1 {
supports_stereo = true;
}
}
let input_config = input_device.default_input_config().unwrap();
let mut input_config: StreamConfig = input_config.into();
input_config.channels = if supports_stereo { 2 } else { 1 };
input_config.sample_rate = SampleRate(sample_rate as u32);
input_config.buffer_size = BufferSize::Fixed(buffer_size as u32);
#[cfg(target_os = "ios")]
{
input_config.buffer_size = BufferSize::Default;
}
log::info!(
"Using input name={} sample_rate={} buffer_size={:?}",
input_device.name().unwrap(),
sample_rate,
input_config.buffer_size
);
(input_device, input_config)
}
fn configure_output_device(
host: Host,
buffer_size: usize,
sample_rate: usize,
) -> (cpal::Device, StreamConfig) {
let output_device = host.default_output_device().unwrap();
for config in output_device.supported_input_configs().unwrap() {
log::info!(" OUTPUT Supported config: {:?}", config);
}
let output_config = output_device.default_output_config().unwrap();
let mut output_config: StreamConfig = output_config.into();
output_config.channels = output_device
.supported_input_configs()
.unwrap()
.map(|config| config.channels())
.max()
.unwrap_or(2)
.min(2);
output_config.sample_rate = SampleRate(sample_rate as u32);
output_config.buffer_size = BufferSize::Fixed(buffer_size as u32);
#[cfg(target_os = "ios")]
{
output_config.buffer_size = BufferSize::Default;
}
log::info!(
"Using output name={} sample_rate={} buffer_size={:?}",
output_device.name().unwrap(),
sample_rate,
output_config.buffer_size
);
(output_device, output_config)
}
fn input_stream_callback(producer: &mut Producer<f32>, data: &[f32]) {
for sample in data {
while producer.push(*sample).is_err() {}
}
}
fn output_stream_with_context<Processor: StandaloneProcessor>(
midi_context: Option<&mut MidiContext>,
processor: &mut Processor,
num_input_channels: usize,
num_output_channels: usize,
consumer: &mut Consumer<f32>,
data: &mut [f32],
) {
let mut audio_buffer = InterleavedAudioBuffer::new(num_output_channels, data);
for frame in audio_buffer.frames_mut() {
if num_input_channels == num_output_channels {
for sample in frame {
if let Some(input_sample) = consumer.pop() {
*sample = input_sample;
} else {
}
}
} else if let Some(input_sample) = consumer.pop() {
for sample in frame {
*sample = input_sample
}
} else {
break;
}
}
flush_midi_events(midi_context, processor);
processor.processor().process(&mut audio_buffer);
}
#[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);
}
}
};
}