diff --git a/Cargo.lock b/Cargo.lock index 2ee8b99..b41d8b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,9 +174,9 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.2.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" +checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" dependencies = [ "jobserver", "libc", diff --git a/src/audio/cpal.rs b/src/audio/cpal.rs index 8dd2f44..bc1aae5 100644 --- a/src/audio/cpal.rs +++ b/src/audio/cpal.rs @@ -12,7 +12,6 @@ // this program. If not, see . // use std::{ - any::type_name, collections::HashMap, error::Error, fmt, @@ -22,8 +21,12 @@ use std::{ }, }; -use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use tracing::{error, info, span, Level}; +use cpal::{ + traits::{DeviceTrait, HostTrait, StreamTrait}, + Stream, +}; +use hound::SampleFormat; +use tracing::{debug, error, info, span, Level}; use crate::{ playsync::CancelHandle, @@ -41,6 +44,10 @@ pub struct Device { host_id: cpal::HostId, /// The underlying cpal device. device: cpal::Device, + /// Supports i32. + supports_i32: bool, + /// Supports f32. + supports_f32: bool, } impl fmt::Display for Device { @@ -75,11 +82,42 @@ impl Device { let mut devices: Vec = Vec::new(); for host_id in cpal::available_hosts() { - let host_devices = cpal::host_from_id(host_id)?.devices()?; + let host_devices = match cpal::host_from_id(host_id)?.devices() { + Ok(host_devices) => host_devices, + Err(e) => { + error!( + err = e.to_string(), + host = host_id.name(), + "Unable to list devices for host" + ); + continue; + } + }; for device in host_devices { let mut max_channels = 0; + + let output_configs = device.supported_output_configs(); + if let Err(e) = output_configs { + debug!( + err = e.to_string(), + host = host_id.name(), + device = device.name().unwrap_or_default(), + "Error getting output configs" + ); + continue; + } + + let mut supports_f32 = false; + let mut supports_i32 = false; + for output_config in device.supported_output_configs()? { + if output_config.sample_format().is_float() { + supports_f32 = true; + } + if output_config.sample_format().is_int() { + supports_i32 = true; + } if max_channels < output_config.channels() { max_channels = output_config.channels(); } @@ -91,6 +129,8 @@ impl Device { max_channels, host_id, device, + supports_f32, + supports_i32, }) } } @@ -121,48 +161,20 @@ impl super::Device for Device { cancel_handle: CancelHandle, play_barrier: Arc, ) -> Result<(), Box> { - match song.sample_format { - hound::SampleFormat::Int => { - self.play_format::(song, mappings, cancel_handle, play_barrier) - } - hound::SampleFormat::Float => { - self.play_format::(song, mappings, cancel_handle, play_barrier) - } - } - } -} - -impl Device { - /// Plays the given song using the specified format. - fn play_format( - &self, - song: Arc, - mappings: &HashMap>, - cancel_handle: CancelHandle, - play_barrier: Arc, - ) -> Result<(), Box> - where - S: songs::Sample, - { let span = span!(Level::INFO, "play song (cpal)"); let _enter = span.enter(); - let format_string = type_name::(); info!( - format = format_string, + format = if song.sample_format == SampleFormat::Float { + "float" + } else { + "int" + }, device = self.name, song = song.name, duration = song.duration_string(), "Playing song." ); - if self.max_channels < song.num_channels { - return Err(format!( - "Song {} requires {} channels, audio device {} only has {}", - song.name, song.num_channels, self.name, self.max_channels - ) - .into()); - } - let source = song.source::(mappings)?; let num_channels = *mappings .iter() @@ -170,22 +182,32 @@ impl Device { .max() .ok_or("no max channel found")?; + if self.max_channels < num_channels { + return Err(format!( + "{} channels requested for song {}, audio device {} only has {}", + num_channels, song.name, self.name, self.max_channels + ) + .into()); + } + let (tx, rx) = channel(); - let mut output_callback = Device::output_callback(source, tx, cancel_handle); play_barrier.wait(); - let output_stream = self.device.build_output_stream( - &cpal::StreamConfig { - channels: num_channels, - sample_rate: cpal::SampleRate(song.sample_rate), - buffer_size: cpal::BufferSize::Default, - }, - move |data, _| output_callback(data), - |err: cpal::StreamError| { - error!(err = err.to_string(), "Error during stream."); - }, - None, - )?; + let output_stream = if self.supports_i32 && song.sample_format == hound::SampleFormat::Int { + debug!("Playing i32->i32"); + self.build_stream::(song, mappings, num_channels, tx, cancel_handle)? + } else if self.supports_f32 && song.sample_format == hound::SampleFormat::Float { + debug!("Playing f32->f32"); + self.build_stream::(song, mappings, num_channels, tx, cancel_handle)? + } else if self.supports_i32 && song.sample_format == hound::SampleFormat::Float { + debug!("Playing f32->i32"); + self.build_stream::(song, mappings, num_channels, tx, cancel_handle)? + } else if self.supports_f32 && song.sample_format == hound::SampleFormat::Int { + debug!("Playing i32->f32"); + self.build_stream::(song, mappings, num_channels, tx, cancel_handle)? + } else { + return Err("Device does not support correct sample format for song".into()); + }; output_stream.play()?; // Wait for the read finish. @@ -193,7 +215,41 @@ impl Device { Ok(()) } +} + +impl Device { + /// Builds an output stream. + fn build_stream + 'static>( + &self, + song: Arc, + mappings: &HashMap>, + num_channels: u16, + tx: Sender<()>, + cancel_handle: CancelHandle, + ) -> Result> { + let stream_config = cpal::StreamConfig { + channels: num_channels, + sample_rate: cpal::SampleRate(song.sample_rate), + buffer_size: cpal::BufferSize::Default, + }; + let error_callback = |err: cpal::StreamError| { + error!(err = err.to_string(), "Error during stream."); + }; + let source = song.source::(mappings)?; + let mut output_callback = Device::output_callback::(source, tx, cancel_handle); + let stream = self.device.build_output_stream( + &stream_config, + move |data, _| output_callback(data), + error_callback, + None, + ); + + match stream { + Ok(stream) => Ok(stream), + Err(e) => Err(e.to_string().into()), + } + } // If the playback should stop, this sends on the provided Sender and returns true. This will // only return true and send if we're on a frame boundary. fn signal_stop( @@ -214,12 +270,12 @@ impl Device { } // Creates a callback function that fills the output device buffer. - fn output_callback( + fn output_callback>( mut source: songs::SongSource, tx: Sender<()>, cancel_handle: CancelHandle, - ) -> impl FnMut(&mut [S]) { - move |data: &mut [S]| { + ) -> impl FnMut(&mut [F]) { + move |data: &mut [F]| { let data_len = data.len(); let mut data_pos = 0; @@ -234,7 +290,7 @@ impl Device { match source.next() { Some(sample) => { - *data = sample; + *data = sample.to_sample::(); data_pos += 1; } None => {