From 184b3831c18e6cefc17fe343f9d10330d6ee84ab Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sun, 6 Nov 2022 23:08:04 +0100 Subject: [PATCH 01/34] Support buffer size selection Incompatible API change. --- src/stream.rs | 60 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/src/stream.rs b/src/stream.rs index 59a4c91..4f0464d 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -8,7 +8,7 @@ use crate::dynamic_mixer::{self, DynamicMixerController}; use crate::sink::Sink; use crate::source::Source; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use cpal::{Sample, SupportedStreamConfig}; +use cpal::{Sample, SampleFormat, StreamConfig, SupportedStreamConfig}; /// `cpal::Stream` container. Also see the more useful `OutputStreamHandle`. /// @@ -42,7 +42,7 @@ impl OutputStream { device: &cpal::Device, config: SupportedStreamConfig, ) -> Result<(Self, OutputStreamHandle), StreamError> { - let (mixer, _stream) = device.try_new_output_stream_config(config)?; + let (mixer, _stream) = device.try_new_output_stream_config(&config)?; _stream.play()?; let out = Self { mixer, _stream }; let handle = OutputStreamHandle { @@ -78,8 +78,8 @@ impl OutputStream { impl OutputStreamHandle { /// Plays a source with a device until it ends. pub fn play_raw(&self, source: S) -> Result<(), PlayError> - where - S: Source + Send + 'static, + where + S: Source + Send + 'static, { let mixer = self.mixer.upgrade().ok_or(PlayError::NoDevice)?; mixer.add(source); @@ -88,8 +88,8 @@ impl OutputStreamHandle { /// Plays a sound once. Returns a `Sink` that can be used to control the sound. pub fn play_once(&self, input: R) -> Result - where - R: Read + Seek + Send + Sync + 'static, + where + R: Read + Seek + Send + Sync + 'static, { let input = decoder::Decoder::new(input)?; let sink = Sink::try_new(self)?; @@ -192,28 +192,36 @@ impl error::Error for StreamError { pub(crate) trait CpalDeviceExt { fn new_output_stream_with_format( &self, - format: cpal::SupportedStreamConfig, + config: &cpal::StreamConfig, + sample_format: &cpal::SampleFormat, ) -> Result<(Arc>, cpal::Stream), cpal::BuildStreamError>; fn try_new_output_stream_config( &self, - config: cpal::SupportedStreamConfig, + config: &cpal::SupportedStreamConfig, + ) -> Result<(Arc>, cpal::Stream), StreamError>; + + fn try_new_output_stream( + &self, + config: &cpal::StreamConfig, + sample_format: &cpal::SampleFormat, ) -> Result<(Arc>, cpal::Stream), StreamError>; } impl CpalDeviceExt for cpal::Device { fn new_output_stream_with_format( &self, - format: cpal::SupportedStreamConfig, + config: &cpal::StreamConfig, + sample_format: &cpal::SampleFormat, ) -> Result<(Arc>, cpal::Stream), cpal::BuildStreamError> { let (mixer_tx, mut mixer_rx) = - dynamic_mixer::mixer::(format.channels(), format.sample_rate().0); + dynamic_mixer::mixer::(config.channels, config.sample_rate.0); let error_callback = |err| eprintln!("an error occurred on output stream: {}", err); - match format.sample_format() { + match sample_format { cpal::SampleFormat::F32 => self.build_output_stream::( - &format.config(), + &config, move |data, _| { data.iter_mut() .for_each(|d| *d = mixer_rx.next().unwrap_or(0f32)) @@ -240,7 +248,7 @@ impl CpalDeviceExt for cpal::Device { None, ), cpal::SampleFormat::I16 => self.build_output_stream::( - &format.config(), + &config, move |data, _| { data.iter_mut() .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i16)) @@ -280,7 +288,7 @@ impl CpalDeviceExt for cpal::Device { None, ), cpal::SampleFormat::U16 => self.build_output_stream::( - &format.config(), + &config, move |data, _| { data.iter_mut().for_each(|d| { *d = mixer_rx @@ -320,17 +328,31 @@ impl CpalDeviceExt for cpal::Device { ), _ => return Err(cpal::BuildStreamError::StreamConfigNotSupported), } - .map(|stream| (mixer_tx, stream)) + .map(|stream| (mixer_tx, stream)) } fn try_new_output_stream_config( &self, - config: SupportedStreamConfig, + config: &SupportedStreamConfig, ) -> Result<(Arc>, cpal::Stream), StreamError> { - self.new_output_stream_with_format(config).or_else(|err| { + self.new_output_stream_with_format(&config.config(), &config.sample_format()).or_else(|err| { // look through all supported formats to see if another works supported_output_formats(self)? - .find_map(|format| self.new_output_stream_with_format(format).ok()) + .find_map(|format| self.new_output_stream_with_format(&format.config(), &config.sample_format()).ok()) + // return original error if nothing works + .ok_or(StreamError::BuildStreamError(err)) + }) + } + + fn try_new_output_stream( + &self, + config: &StreamConfig, + sample_format: &SampleFormat, + ) -> Result<(Arc>, cpal::Stream), StreamError> { + self.new_output_stream_with_format(&config, &sample_format).or_else(|err| { + // look through all supported formats to see if another works + supported_output_formats(self)? + .find_map(|format| self.new_output_stream_with_format(&format.config(), &sample_format).ok()) // return original error if nothing works .ok_or(StreamError::BuildStreamError(err)) }) @@ -340,7 +362,7 @@ impl CpalDeviceExt for cpal::Device { /// All the supported output formats with sample rates fn supported_output_formats( device: &cpal::Device, -) -> Result, StreamError> { +) -> Result, StreamError> { const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); let mut supported: Vec<_> = device.supported_output_configs()?.collect(); From dd483902cb04105e38c261fae48aed71fef91447 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Mon, 14 Nov 2022 23:06:00 +0100 Subject: [PATCH 02/34] Make configurable buffer API public --- src/stream.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/stream.rs b/src/stream.rs index 4f0464d..d768197 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -31,7 +31,9 @@ impl OutputStream { device: &cpal::Device, ) -> Result<(Self, OutputStreamHandle), StreamError> { let default_config = device.default_output_config()?; - OutputStream::try_from_device_config(device, default_config) + OutputStream::try_from_device_config(device, + &default_config.config(), + &default_config.sample_format()) } /// Returns a new stream & handle using the given device and stream config. @@ -39,6 +41,21 @@ impl OutputStream { /// If the supplied `SupportedStreamConfig` is invalid for the device this function will /// fail to create an output stream and instead return a `StreamError` pub fn try_from_device_config( + device: &cpal::Device, + config: &StreamConfig, + sample_format: &SampleFormat, + ) -> Result<(Self, OutputStreamHandle), StreamError> { + let (mixer, _stream) = device.try_new_output_stream(&config, &sample_format)?; + _stream.play()?; + let out = Self { mixer, _stream }; + let handle = OutputStreamHandle { + mixer: Arc::downgrade(&out.mixer), + }; + Ok((out, handle)) + } + + + pub fn try_from_config( device: &cpal::Device, config: SupportedStreamConfig, ) -> Result<(Self, OutputStreamHandle), StreamError> { @@ -51,6 +68,7 @@ impl OutputStream { Ok((out, handle)) } + /// Return a new stream & handle using the default output device. /// /// On failure will fallback to trying any non-default output devices. From 5d42375270f4b56dc4ff49f325451b07332b6abe Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Mon, 14 Nov 2022 23:17:02 +0100 Subject: [PATCH 03/34] Correct API signatures THe idea was to add a new function. --- src/stream.rs | 46 +++++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/stream.rs b/src/stream.rs index d768197..28e4814 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -31,9 +31,7 @@ impl OutputStream { device: &cpal::Device, ) -> Result<(Self, OutputStreamHandle), StreamError> { let default_config = device.default_output_config()?; - OutputStream::try_from_device_config(device, - &default_config.config(), - &default_config.sample_format()) + OutputStream::try_from_device_config(device, &default_config) } /// Returns a new stream & handle using the given device and stream config. @@ -41,6 +39,19 @@ impl OutputStream { /// If the supplied `SupportedStreamConfig` is invalid for the device this function will /// fail to create an output stream and instead return a `StreamError` pub fn try_from_device_config( + device: &cpal::Device, + config: &SupportedStreamConfig, + ) -> Result<(Self, OutputStreamHandle), StreamError> { + let (mixer, _stream) = device.try_new_output_stream_config(&config)?; + _stream.play()?; + let out = Self { mixer, _stream }; + let handle = OutputStreamHandle { + mixer: Arc::downgrade(&out.mixer), + }; + Ok((out, handle)) + } + + pub fn try_from_config( device: &cpal::Device, config: &StreamConfig, sample_format: &SampleFormat, @@ -54,21 +65,6 @@ impl OutputStream { Ok((out, handle)) } - - pub fn try_from_config( - device: &cpal::Device, - config: SupportedStreamConfig, - ) -> Result<(Self, OutputStreamHandle), StreamError> { - let (mixer, _stream) = device.try_new_output_stream_config(&config)?; - _stream.play()?; - let out = Self { mixer, _stream }; - let handle = OutputStreamHandle { - mixer: Arc::downgrade(&out.mixer), - }; - Ok((out, handle)) - } - - /// Return a new stream & handle using the default output device. /// /// On failure will fallback to trying any non-default output devices. @@ -248,7 +244,7 @@ impl CpalDeviceExt for cpal::Device { None, ), cpal::SampleFormat::F64 => self.build_output_stream::( - &format.config(), + &config, move |data, _| { data.iter_mut() .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0f64)) @@ -257,7 +253,7 @@ impl CpalDeviceExt for cpal::Device { None, ), cpal::SampleFormat::I8 => self.build_output_stream::( - &format.config(), + &config, move |data, _| { data.iter_mut() .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i8)) @@ -275,7 +271,7 @@ impl CpalDeviceExt for cpal::Device { None, ), cpal::SampleFormat::I32 => self.build_output_stream::( - &format.config(), + &config, move |data, _| { data.iter_mut() .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i32)) @@ -284,7 +280,7 @@ impl CpalDeviceExt for cpal::Device { None, ), cpal::SampleFormat::I64 => self.build_output_stream::( - &format.config(), + &config, move |data, _| { data.iter_mut() .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i64)) @@ -293,7 +289,7 @@ impl CpalDeviceExt for cpal::Device { None, ), cpal::SampleFormat::U8 => self.build_output_stream::( - &format.config(), + &config, move |data, _| { data.iter_mut().for_each(|d| { *d = mixer_rx @@ -319,7 +315,7 @@ impl CpalDeviceExt for cpal::Device { None, ), cpal::SampleFormat::U32 => self.build_output_stream::( - &format.config(), + &config, move |data, _| { data.iter_mut().for_each(|d| { *d = mixer_rx @@ -332,7 +328,7 @@ impl CpalDeviceExt for cpal::Device { None, ), cpal::SampleFormat::U64 => self.build_output_stream::( - &format.config(), + &config, move |data, _| { data.iter_mut().for_each(|d| { *d = mixer_rx From 4c62dd3ed8f66cca2867e87f3d3d5a72cd34691a Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sun, 1 Oct 2023 18:44:56 +0200 Subject: [PATCH 04/34] Bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 921294e..a2ac375 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rodio" -version = "0.17.1" +version = "0.18.0" license = "MIT OR Apache-2.0" description = "Audio playback library" keywords = ["audio", "playback", "gamedev"] From 1a8d63ad2993bd29f756767ea3fb5ab44961ab46 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sun, 1 Oct 2023 23:17:43 +0200 Subject: [PATCH 05/34] Reduce API changes --- src/stream.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/stream.rs b/src/stream.rs index 28e4814..d01008a 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -31,7 +31,7 @@ impl OutputStream { device: &cpal::Device, ) -> Result<(Self, OutputStreamHandle), StreamError> { let default_config = device.default_output_config()?; - OutputStream::try_from_device_config(device, &default_config) + OutputStream::try_from_device_config(device, default_config) } /// Returns a new stream & handle using the given device and stream config. @@ -40,9 +40,9 @@ impl OutputStream { /// fail to create an output stream and instead return a `StreamError` pub fn try_from_device_config( device: &cpal::Device, - config: &SupportedStreamConfig, + config: SupportedStreamConfig, ) -> Result<(Self, OutputStreamHandle), StreamError> { - let (mixer, _stream) = device.try_new_output_stream_config(&config)?; + let (mixer, _stream) = device.try_new_output_stream_config(config)?; _stream.play()?; let out = Self { mixer, _stream }; let handle = OutputStreamHandle { @@ -92,8 +92,8 @@ impl OutputStream { impl OutputStreamHandle { /// Plays a source with a device until it ends. pub fn play_raw(&self, source: S) -> Result<(), PlayError> - where - S: Source + Send + 'static, + where + S: Source + Send + 'static, { let mixer = self.mixer.upgrade().ok_or(PlayError::NoDevice)?; mixer.add(source); @@ -102,8 +102,8 @@ impl OutputStreamHandle { /// Plays a sound once. Returns a `Sink` that can be used to control the sound. pub fn play_once(&self, input: R) -> Result - where - R: Read + Seek + Send + Sync + 'static, + where + R: Read + Seek + Send + Sync + 'static, { let input = decoder::Decoder::new(input)?; let sink = Sink::try_new(self)?; @@ -212,7 +212,7 @@ pub(crate) trait CpalDeviceExt { fn try_new_output_stream_config( &self, - config: &cpal::SupportedStreamConfig, + config: cpal::SupportedStreamConfig, ) -> Result<(Arc>, cpal::Stream), StreamError>; fn try_new_output_stream( @@ -347,7 +347,7 @@ impl CpalDeviceExt for cpal::Device { fn try_new_output_stream_config( &self, - config: &SupportedStreamConfig, + config: SupportedStreamConfig, ) -> Result<(Arc>, cpal::Stream), StreamError> { self.new_output_stream_with_format(&config.config(), &config.sample_format()).or_else(|err| { // look through all supported formats to see if another works @@ -376,7 +376,7 @@ impl CpalDeviceExt for cpal::Device { /// All the supported output formats with sample rates fn supported_output_formats( device: &cpal::Device, -) -> Result, StreamError> { +) -> Result, StreamError> { const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); let mut supported: Vec<_> = device.supported_output_configs()?.collect(); From c17712f74b1f41036e36580637ceea54650fe2ad Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sun, 1 Oct 2023 23:48:24 +0200 Subject: [PATCH 06/34] Reformat code --- src/stream.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stream.rs b/src/stream.rs index d01008a..124a5f8 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -342,7 +342,7 @@ impl CpalDeviceExt for cpal::Device { ), _ => return Err(cpal::BuildStreamError::StreamConfigNotSupported), } - .map(|stream| (mixer_tx, stream)) + .map(|stream| (mixer_tx, stream)) } fn try_new_output_stream_config( From dd1edbb11d10371f8da12e4e756402f1f7c4e994 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Tue, 24 Sep 2024 01:01:10 +0400 Subject: [PATCH 07/34] WIP, simplify output stream initialization --- examples/basic.rs | 2 +- examples/mix_multiple_sources.rs | 2 +- examples/music_flac.rs | 2 +- examples/music_m4a.rs | 2 +- examples/music_mp3.rs | 2 +- examples/music_ogg.rs | 2 +- examples/music_wav.rs | 2 +- examples/reverb.rs | 2 +- examples/spatial.rs | 2 +- examples/stereo.rs | 2 +- src/dynamic_mixer.rs | 42 +++++----- src/lib.rs | 6 +- src/sink.rs | 14 +++- src/source/mod.rs | 5 +- src/source/uniform.rs | 6 +- src/stream.rs | 135 ++++++++++++++++++------------- 16 files changed, 131 insertions(+), 97 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index d57d379..d12bd24 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -3,7 +3,7 @@ use std::thread; use std::time::Duration; fn main() { - let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap(); + let (_stream, stream_handle) = rodio::OutputStream::default().unwrap(); let file = std::fs::File::open("assets/beep.wav").unwrap(); let beep1 = stream_handle.play_once(BufReader::new(file)).unwrap(); diff --git a/examples/mix_multiple_sources.rs b/examples/mix_multiple_sources.rs index 3363d5f..f4d0acf 100644 --- a/examples/mix_multiple_sources.rs +++ b/examples/mix_multiple_sources.rs @@ -5,7 +5,7 @@ use std::time::Duration; fn main() { // Construct a dynamic controller and mixer, stream_handle, and sink. let (controller, mixer) = dynamic_mixer::mixer::(2, 44_100); - let (_stream, stream_handle) = OutputStream::try_default().unwrap(); + let (_stream, stream_handle) = OutputStream::default().unwrap(); let sink = Sink::try_new(&stream_handle).unwrap(); // Create four unique sources. The frequencies used here correspond diff --git a/examples/music_flac.rs b/examples/music_flac.rs index 580d19b..2b787b6 100644 --- a/examples/music_flac.rs +++ b/examples/music_flac.rs @@ -1,7 +1,7 @@ use std::io::BufReader; fn main() { - let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let (_stream, handle) = rodio::OutputStream::default().unwrap(); let sink = rodio::Sink::try_new(&handle).unwrap(); let file = std::fs::File::open("assets/music.flac").unwrap(); diff --git a/examples/music_m4a.rs b/examples/music_m4a.rs index 2c50f33..e62d825 100644 --- a/examples/music_m4a.rs +++ b/examples/music_m4a.rs @@ -1,7 +1,7 @@ use std::io::BufReader; fn main() { - let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let (_stream, handle) = rodio::OutputStream::default().unwrap(); let sink = rodio::Sink::try_new(&handle).unwrap(); let file = std::fs::File::open("assets/music.m4a").unwrap(); diff --git a/examples/music_mp3.rs b/examples/music_mp3.rs index f6ac371..20650ae 100644 --- a/examples/music_mp3.rs +++ b/examples/music_mp3.rs @@ -1,7 +1,7 @@ use std::io::BufReader; fn main() { - let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let (_stream, handle) = rodio::OutputStream::default().unwrap(); let sink = rodio::Sink::try_new(&handle).unwrap(); let file = std::fs::File::open("assets/music.mp3").unwrap(); diff --git a/examples/music_ogg.rs b/examples/music_ogg.rs index 6388715..8003283 100644 --- a/examples/music_ogg.rs +++ b/examples/music_ogg.rs @@ -1,7 +1,7 @@ use std::io::BufReader; fn main() { - let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let (_stream, handle) = rodio::OutputStream::default().unwrap(); let sink = rodio::Sink::try_new(&handle).unwrap(); let file = std::fs::File::open("assets/music.ogg").unwrap(); diff --git a/examples/music_wav.rs b/examples/music_wav.rs index f44631c..d713c82 100644 --- a/examples/music_wav.rs +++ b/examples/music_wav.rs @@ -1,7 +1,7 @@ use std::io::BufReader; fn main() { - let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let (_stream, handle) = rodio::OutputStream::default().unwrap(); let sink = rodio::Sink::try_new(&handle).unwrap(); let file = std::fs::File::open("assets/music.wav").unwrap(); diff --git a/examples/reverb.rs b/examples/reverb.rs index 883aa55..ea0dd9a 100644 --- a/examples/reverb.rs +++ b/examples/reverb.rs @@ -3,7 +3,7 @@ use std::io::BufReader; use std::time::Duration; fn main() { - let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let (_stream, handle) = rodio::OutputStream::default().unwrap(); let sink = rodio::Sink::try_new(&handle).unwrap(); let file = std::fs::File::open("assets/music.ogg").unwrap(); diff --git a/examples/spatial.rs b/examples/spatial.rs index b2e9513..0b22e9a 100644 --- a/examples/spatial.rs +++ b/examples/spatial.rs @@ -18,7 +18,7 @@ fn main() { let total_duration = iter_duration * 2 * repeats; - let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let (_stream, handle) = rodio::OutputStream::default().unwrap(); let mut positions = ([0., 0., 0.], [-1., 0., 0.], [1., 0., 0.]); let sink = rodio::SpatialSink::try_new(&handle, positions.0, positions.1, positions.2).unwrap(); diff --git a/examples/stereo.rs b/examples/stereo.rs index fb1ada5..601658c 100644 --- a/examples/stereo.rs +++ b/examples/stereo.rs @@ -2,7 +2,7 @@ use std::io::BufReader; fn main() { - let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); + let (_stream, handle) = rodio::OutputStream::default().unwrap(); let sink = rodio::Sink::try_new(&handle).unwrap(); let file = std::fs::File::open("assets/RL.ogg").unwrap(); diff --git a/src/dynamic_mixer.rs b/src/dynamic_mixer.rs index 045aed8..67ee252 100644 --- a/src/dynamic_mixer.rs +++ b/src/dynamic_mixer.rs @@ -41,7 +41,7 @@ where /// The input of the mixer. pub struct DynamicMixerController { has_pending: AtomicBool, - pending_sources: Mutex + Send>>>, + pending_sources: Mutex + Send>>>, channels: u16, sample_rate: u32, } @@ -54,13 +54,13 @@ where #[inline] pub fn add(&self, source: T) where - T: Source + Send + 'static, + T: Source + Send + 'static, { let uniform_source = UniformSourceIterator::new(source, self.channels, self.sample_rate); - self.pending_sources + let mut pending = self.pending_sources .lock() - .unwrap() - .push(Box::new(uniform_source) as Box<_>); + .unwrap(); + pending.push(Box::new(uniform_source) as Box<_>); self.has_pending.store(true, Ordering::SeqCst); // TODO: can we relax this ordering? } } @@ -68,7 +68,7 @@ where /// The output of the mixer. Implements `Source`. pub struct DynamicMixer { // The current iterator that produces samples. - current_sources: Vec + Send>>, + current_sources: Vec + Send>>, // The pending sounds. input: Arc>, @@ -77,10 +77,10 @@ pub struct DynamicMixer { sample_count: usize, // A temporary vec used in start_pending_sources. - still_pending: Vec + Send>>, + still_pending: Vec + Send>>, // A temporary vec used in sum_current_sources. - still_current: Vec + Send>>, + still_current: Vec + Send>>, } impl Source for DynamicMixer @@ -146,18 +146,16 @@ where // in-step with the modulo of the samples produced so far. Otherwise, the // sound will play on the wrong channels, e.g. left / right will be reversed. fn start_pending_sources(&mut self) { - let mut pending = self.input.pending_sources.lock().unwrap(); // TODO: relax ordering? - - for source in pending.drain(..) { - let in_step = self.sample_count % source.channels() as usize == 0; - + let mut pending = self.input.pending_sources.lock().unwrap(); + let mut i = 0; + while i < pending.len() { + let in_step = self.sample_count % pending[i].channels() as usize == 0; if in_step { - self.current_sources.push(source); + self.current_sources.push(pending.swap_remove(i)); } else { - self.still_pending.push(source); + i += 1; } } - std::mem::swap(&mut self.still_pending, &mut pending); let has_pending = !pending.is_empty(); self.input.has_pending.store(has_pending, Ordering::SeqCst); // TODO: relax ordering? @@ -165,15 +163,15 @@ where fn sum_current_sources(&mut self) -> S { let mut sum = S::zero_value(); - - for mut source in self.current_sources.drain(..) { - if let Some(value) = source.next() { + let mut i = 0; + while i < self.current_sources.len() { + if let Some(value) = self.current_sources[i].next() { sum = sum.saturating_add(value); - self.still_current.push(source); + i += 1; + } else { + self.current_sources.swap_remove(i); } } - std::mem::swap(&mut self.still_current, &mut self.current_sources); - sum } } diff --git a/src/lib.rs b/src/lib.rs index 51eefed..7685f57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ //! - Create an object that represents the streaming sound. It can be a sine wave, a buffer, a //! [`decoder`], etc. or even your own type that implements the [`Source`] trait. //! - Get an output stream handle to a physical device. For example, get a stream to the system's -//! default sound device with [`OutputStream::try_default()`] +//! default sound device with [`OutputStream::default()`] //! - Call [`.play_raw(source)`](OutputStreamHandle::play_raw) on the output stream handle. //! //! The [`play_raw`](OutputStreamHandle::play_raw) function expects the source to produce [`f32`]s, @@ -21,7 +21,7 @@ //! use rodio::{Decoder, OutputStream, source::Source}; //! //! // Get a output stream handle to the default physical sound device -//! let (_stream, stream_handle) = OutputStream::try_default().unwrap(); +//! let (_stream, stream_handle) = OutputStream::default().unwrap(); //! // Load a sound from a file, using a path relative to Cargo.toml //! let file = BufReader::new(File::open("examples/music.ogg").unwrap()); //! // Decode that sound file into a source @@ -51,7 +51,7 @@ //! use rodio::{Decoder, OutputStream, Sink}; //! use rodio::source::{SineWave, Source}; //! -//! let (_stream, stream_handle) = OutputStream::try_default().unwrap(); +//! let (_stream, stream_handle) = OutputStream::default().unwrap(); //! let sink = Sink::try_new(&stream_handle).unwrap(); //! //! // Add a dummy source of the sake of the example. diff --git a/src/sink.rs b/src/sink.rs index fac1ca6..9c844e0 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -11,7 +11,7 @@ use crate::stream::{OutputStreamHandle, PlayError}; use crate::{queue, source::Done, Sample, Source}; use cpal::FromSample; -/// Handle to an device that outputs sounds. +/// Handle to a device that outputs sounds. /// /// Dropping the `Sink` stops all sounds. You can use `detach` if you want the sounds to continue /// playing. @@ -71,7 +71,7 @@ impl Sink { f32: FromSample, S::Item: Sample + Send, { - // Wait for queue to flush then resume stopped playback + // Wait for the queue to flush then resume stopped playback if self.controls.stopped.load(Ordering::SeqCst) { if self.sound_count.load(Ordering::SeqCst) > 0 { self.sleep_until_end(); @@ -248,6 +248,7 @@ mod tests { use crate::buffer::SamplesBuffer; use crate::{Sink, Source}; use std::sync::atomic::Ordering; + use crossbeam_utils::atomic::AtomicCell; #[test] fn test_pause_and_stop() { @@ -308,6 +309,15 @@ mod tests { assert_eq!(queue_rx.next(), src.next()); } + #[test] + fn lab() { + + + let c = AtomicCell::new(0.0); + assert!(AtomicCell::::is_lock_free()); + c.load(); + } + #[test] fn test_volume() { let (sink, mut queue_rx) = Sink::new_idle(); diff --git a/src/source/mod.rs b/src/source/mod.rs index 2af8007..cbbe49a 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -75,7 +75,7 @@ mod zero; /// amplitude every 20µs). By doing so we obtain a list of numerical values, each value being /// called a *sample*. /// -/// Therefore a sound can be represented in memory by a frequency and a list of samples. The +/// Therefore, a sound can be represented in memory by a frequency and a list of samples. The /// frequency is expressed in hertz and corresponds to the number of samples that have been /// read per second. For example if we read one sample every 20µs, the frequency would be /// 50000 Hz. In reality, common values for the frequency are 44100, 48000 and 96000. @@ -96,7 +96,7 @@ mod zero; /// channel, then the second sample of the second channel, and so on. The same applies if you have /// more than two channels. The rodio library only supports this schema. /// -/// Therefore in order to represent a sound in memory in fact we need three characteristics: the +/// Therefore, in order to represent a sound in memory in fact we need three characteristics: the /// frequency, the number of channels, and the list of samples. /// /// ## The `Source` trait @@ -126,7 +126,6 @@ mod zero; /// In order to properly handle this situation, the `current_frame_len()` method should return /// the number of samples that remain in the iterator before the samples rate and number of /// channels can potentially change. -/// pub trait Source: Iterator where Self::Item: Sample, diff --git a/src/source/uniform.rs b/src/source/uniform.rs index 823cb5d..2c1e735 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -37,9 +37,9 @@ where target_sample_rate: u32, ) -> UniformSourceIterator { let total_duration = input.total_duration(); - let input = UniformSourceIterator::bootstrap(input, target_channels, target_sample_rate); + let input = Self::bootstrap(input, target_channels, target_sample_rate); - UniformSourceIterator { + Self { inner: Some(input), target_channels, target_sample_rate, @@ -99,7 +99,7 @@ where .iter; let mut input = - UniformSourceIterator::bootstrap(input, self.target_channels, self.target_sample_rate); + Self::bootstrap(input, self.target_channels, self.target_sample_rate); let value = input.next(); self.inner = Some(input); diff --git a/src/stream.rs b/src/stream.rs index 124a5f8..98105bc 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -1,18 +1,56 @@ +use std::{error, fmt}; use std::io::{Read, Seek}; +use std::iter::empty; use std::marker::Sync; use std::sync::{Arc, Weak}; -use std::{error, fmt}; + +use cpal::{PlayStreamError, Sample, SampleFormat, StreamConfig, SupportedStreamConfig}; +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::decoder; -use crate::dynamic_mixer::{self, DynamicMixerController}; +use crate::dynamic_mixer::DynamicMixerController; use crate::sink::Sink; use crate::source::Source; -use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use cpal::{Sample, SampleFormat, StreamConfig, SupportedStreamConfig}; + +type SampleSource = dyn Iterator; + +pub struct OutputHandle { + stream: cpal::Stream, + plug: SourceSocket, +} + +impl OutputHandle { + pub fn attach(&mut self, source: Box>) { + self.plug.source = source; + } + + pub fn play(&self) -> Result<(), PlayStreamError> { + self.stream.play() + } +} + +struct SourceSocket { + source: Box>, +} + +impl SourceSocket { + pub fn new() -> Self { + SourceSocket { source: Box::new(empty()) } + } +} + +impl Iterator for SourceSocket { + type Item = f32; + + fn next(&mut self) -> Option { + self.source.next() + } +} + /// `cpal::Stream` container. Also see the more useful `OutputStreamHandle`. /// -/// If this is dropped playback will end & attached `OutputStreamHandle`s will no longer work. +/// If this is dropped, playback will end & attached `OutputStreamHandle`s will no longer work. pub struct OutputStream { mixer: Arc>, _stream: cpal::Stream, @@ -27,53 +65,45 @@ pub struct OutputStreamHandle { impl OutputStream { /// Returns a new stream & handle using the given output device and the default output /// configuration. - pub fn try_from_device( + pub fn from_device( device: &cpal::Device, - ) -> Result<(Self, OutputStreamHandle), StreamError> { + ) -> Result { let default_config = device.default_output_config()?; - OutputStream::try_from_device_config(device, default_config) + OutputStream::from_device_config(device, default_config) } /// Returns a new stream & handle using the given device and stream config. /// /// If the supplied `SupportedStreamConfig` is invalid for the device this function will - /// fail to create an output stream and instead return a `StreamError` - pub fn try_from_device_config( + /// fail to create an output stream and instead return a `StreamError`. + pub fn from_device_config( device: &cpal::Device, config: SupportedStreamConfig, - ) -> Result<(Self, OutputStreamHandle), StreamError> { - let (mixer, _stream) = device.try_new_output_stream_config(config)?; - _stream.play()?; - let out = Self { mixer, _stream }; - let handle = OutputStreamHandle { - mixer: Arc::downgrade(&out.mixer), - }; - Ok((out, handle)) + ) -> Result { + let handle = device.try_new_output_stream_config(config)?; + handle.play()?; + Ok(handle) } - pub fn try_from_config( + pub fn from_config( device: &cpal::Device, config: &StreamConfig, sample_format: &SampleFormat, - ) -> Result<(Self, OutputStreamHandle), StreamError> { - let (mixer, _stream) = device.try_new_output_stream(&config, &sample_format)?; - _stream.play()?; - let out = Self { mixer, _stream }; - let handle = OutputStreamHandle { - mixer: Arc::downgrade(&out.mixer), - }; - Ok((out, handle)) + ) -> Result { + let handle = device.try_new_output_stream(&config, &sample_format)?; + handle.play()?; + Ok(handle) } /// Return a new stream & handle using the default output device. /// - /// On failure will fallback to trying any non-default output devices. - pub fn try_default() -> Result<(Self, OutputStreamHandle), StreamError> { + /// On failure will fall back to trying any non-default output devices. + pub fn default() -> Result { let default_device = cpal::default_host() .default_output_device() .ok_or(StreamError::NoDevice)?; - let default_stream = Self::try_from_device(&default_device); + let default_stream = Self::from_device(&default_device); default_stream.or_else(|original_err| { // default device didn't work, try other ones @@ -83,7 +113,7 @@ impl OutputStream { }; devices - .find_map(|d| Self::try_from_device(&d).ok()) + .find_map(|d| Self::from_device(&d).ok()) .ok_or(original_err) }) } @@ -93,7 +123,7 @@ impl OutputStreamHandle { /// Plays a source with a device until it ends. pub fn play_raw(&self, source: S) -> Result<(), PlayError> where - S: Source + Send + 'static, + S: Source + Send + 'static, { let mixer = self.mixer.upgrade().ok_or(PlayError::NoDevice)?; mixer.add(source); @@ -208,18 +238,18 @@ pub(crate) trait CpalDeviceExt { &self, config: &cpal::StreamConfig, sample_format: &cpal::SampleFormat, - ) -> Result<(Arc>, cpal::Stream), cpal::BuildStreamError>; + ) -> Result; fn try_new_output_stream_config( &self, config: cpal::SupportedStreamConfig, - ) -> Result<(Arc>, cpal::Stream), StreamError>; + ) -> Result; fn try_new_output_stream( &self, config: &cpal::StreamConfig, sample_format: &cpal::SampleFormat, - ) -> Result<(Arc>, cpal::Stream), StreamError>; + ) -> Result; } impl CpalDeviceExt for cpal::Device { @@ -227,18 +257,15 @@ impl CpalDeviceExt for cpal::Device { &self, config: &cpal::StreamConfig, sample_format: &cpal::SampleFormat, - ) -> Result<(Arc>, cpal::Stream), cpal::BuildStreamError> { - let (mixer_tx, mut mixer_rx) = - dynamic_mixer::mixer::(config.channels, config.sample_rate.0); - + ) -> Result { let error_callback = |err| eprintln!("an error occurred on output stream: {}", err); - + let mut samples = SourceSocket::new(); match sample_format { cpal::SampleFormat::F32 => self.build_output_stream::( &config, move |data, _| { data.iter_mut() - .for_each(|d| *d = mixer_rx.next().unwrap_or(0f32)) + .for_each(|d| *d = samples.next().unwrap_or(0f32)) }, error_callback, None, @@ -247,7 +274,7 @@ impl CpalDeviceExt for cpal::Device { &config, move |data, _| { data.iter_mut() - .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0f64)) + .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0f64)) }, error_callback, None, @@ -256,7 +283,7 @@ impl CpalDeviceExt for cpal::Device { &config, move |data, _| { data.iter_mut() - .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i8)) + .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i8)) }, error_callback, None, @@ -265,7 +292,7 @@ impl CpalDeviceExt for cpal::Device { &config, move |data, _| { data.iter_mut() - .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i16)) + .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i16)) }, error_callback, None, @@ -274,7 +301,7 @@ impl CpalDeviceExt for cpal::Device { &config, move |data, _| { data.iter_mut() - .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i32)) + .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i32)) }, error_callback, None, @@ -283,7 +310,7 @@ impl CpalDeviceExt for cpal::Device { &config, move |data, _| { data.iter_mut() - .for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i64)) + .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i64)) }, error_callback, None, @@ -292,7 +319,7 @@ impl CpalDeviceExt for cpal::Device { &config, move |data, _| { data.iter_mut().for_each(|d| { - *d = mixer_rx + *d = samples .next() .map(Sample::from_sample) .unwrap_or(u8::max_value() / 2) @@ -305,7 +332,7 @@ impl CpalDeviceExt for cpal::Device { &config, move |data, _| { data.iter_mut().for_each(|d| { - *d = mixer_rx + *d = samples .next() .map(Sample::from_sample) .unwrap_or(u16::max_value() / 2) @@ -318,7 +345,7 @@ impl CpalDeviceExt for cpal::Device { &config, move |data, _| { data.iter_mut().for_each(|d| { - *d = mixer_rx + *d = samples .next() .map(Sample::from_sample) .unwrap_or(u32::max_value() / 2) @@ -331,7 +358,7 @@ impl CpalDeviceExt for cpal::Device { &config, move |data, _| { data.iter_mut().for_each(|d| { - *d = mixer_rx + *d = samples .next() .map(Sample::from_sample) .unwrap_or(u64::max_value() / 2) @@ -342,13 +369,13 @@ impl CpalDeviceExt for cpal::Device { ), _ => return Err(cpal::BuildStreamError::StreamConfigNotSupported), } - .map(|stream| (mixer_tx, stream)) + .map(|stream| crate::stream::OutputHandle { stream, plug: samples }) } fn try_new_output_stream_config( &self, config: SupportedStreamConfig, - ) -> Result<(Arc>, cpal::Stream), StreamError> { + ) -> Result { self.new_output_stream_with_format(&config.config(), &config.sample_format()).or_else(|err| { // look through all supported formats to see if another works supported_output_formats(self)? @@ -362,7 +389,7 @@ impl CpalDeviceExt for cpal::Device { &self, config: &StreamConfig, sample_format: &SampleFormat, - ) -> Result<(Arc>, cpal::Stream), StreamError> { + ) -> Result { self.new_output_stream_with_format(&config, &sample_format).or_else(|err| { // look through all supported formats to see if another works supported_output_formats(self)? @@ -376,7 +403,7 @@ impl CpalDeviceExt for cpal::Device { /// All the supported output formats with sample rates fn supported_output_formats( device: &cpal::Device, -) -> Result, StreamError> { +) -> Result, StreamError> { const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); let mut supported: Vec<_> = device.supported_output_configs()?.collect(); From e82b208b7c694287fb212afa943cf2a651c35a7a Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Thu, 7 Nov 2024 01:15:58 +0400 Subject: [PATCH 08/34] WIP. Implementing OpenStreamBuilder --- Cargo.toml | 9 +- src/dynamic_mixer.rs | 22 ++-- src/lib.rs | 4 + src/sink.rs | 48 ++++---- src/stream.rs | 276 ++++++++++++++++++++++++++----------------- 5 files changed, 220 insertions(+), 139 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 39510ff..e29ccc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ cpal = "0.15" claxon = { version = "0.4.2", optional = true } hound = { version = "3.3.1", optional = true } lewton = { version = "0.10", optional = true } -minimp3_fixed = { version = "0.5.4", optional = true} +minimp3_fixed = { version = "0.5.4", optional = true } symphonia = { version = "0.5.2", optional = true, default-features = false } crossbeam-channel = { version = "0.5.8", optional = true } @@ -36,7 +36,12 @@ symphonia-vorbis = ["symphonia/vorbis"] symphonia-wav = ["symphonia/wav", "symphonia/pcm", "symphonia/adpcm"] [dev-dependencies] -quickcheck = "0.9.2" +quickcheck = "1.0.3" +criterion = "0.5.1" + +[[bench]] +name = "benchmark" +harness = false [[example]] name = "music_m4a" diff --git a/src/dynamic_mixer.rs b/src/dynamic_mixer.rs index 67ee252..022607e 100644 --- a/src/dynamic_mixer.rs +++ b/src/dynamic_mixer.rs @@ -1,5 +1,4 @@ //! Mixer that plays multiple sounds at the same time. - use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -31,8 +30,6 @@ where current_sources: Vec::with_capacity(16), input: input.clone(), sample_count: 0, - still_pending: vec![], - still_current: vec![], }; (input, output) @@ -75,12 +72,6 @@ pub struct DynamicMixer { // The number of samples produced so far. sample_count: usize, - - // A temporary vec used in start_pending_sources. - still_pending: Vec + Send>>, - - // A temporary vec used in sum_current_sources. - still_current: Vec + Send>>, } impl Source for DynamicMixer @@ -178,10 +169,23 @@ where #[cfg(test)] mod tests { + use std::cell::Cell; + use std::ops::Deref; + use std::sync::Arc; + use std::sync::atomic::{AtomicBool, AtomicU8}; + use std::sync::atomic::Ordering::{Acquire, Release}; use crate::buffer::SamplesBuffer; use crate::dynamic_mixer; use crate::source::Source; + #[test] + pub fn fff() { + let r = Arc::new(AtomicU8::new(12)); + let c = r.clone(); + r.store(44, Release); + assert_eq!(r.load(Acquire), c.load(Acquire)); + } + #[test] fn basic() { let (tx, mut rx) = dynamic_mixer::mixer(1, 48000); diff --git a/src/lib.rs b/src/lib.rs index 7685f57..bc09d0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,10 @@ //! [`decoder`], etc. or even your own type that implements the [`Source`] trait. //! - Get an output stream handle to a physical device. For example, get a stream to the system's //! default sound device with [`OutputStream::default()`] +//! FIXME Update documentation after the builder is complete //! - Call [`.play_raw(source)`](OutputStreamHandle::play_raw) on the output stream handle. //! +//! FIXME Update documentation after the builder is complete //! The [`play_raw`](OutputStreamHandle::play_raw) function expects the source to produce [`f32`]s, //! which may not be the case. If you get a compilation error, try calling //! [`.convert_samples()`](Source::convert_samples) on the source to fix it. @@ -39,6 +41,7 @@ //! In order to make it easier to control the playback, the rodio library also provides a type //! named [`Sink`] which represents an audio track. //! +//! FIXME Update documentation after the builder is complete //! Instead of playing the sound with [`play_raw`](OutputStreamHandle::play_raw), you can add it to //! a [`Sink`] instead. //! @@ -51,6 +54,7 @@ //! use rodio::{Decoder, OutputStream, Sink}; //! use rodio::source::{SineWave, Source}; //! +//! // FIXME Update documentation after the builder is complete //! let (_stream, stream_handle) = OutputStream::default().unwrap(); //! let sink = Sink::try_new(&stream_handle).unwrap(); //! diff --git a/src/sink.rs b/src/sink.rs index 9c844e0..ef45fba 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -1,15 +1,15 @@ -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; -use std::time::Duration; - -#[cfg(feature = "crossbeam-channel")] -use crossbeam_channel::Receiver; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; #[cfg(not(feature = "crossbeam-channel"))] use std::sync::mpsc::Receiver; +use std::time::Duration; -use crate::stream::{OutputStreamHandle, PlayError}; -use crate::{queue, source::Done, Sample, Source}; use cpal::FromSample; +#[cfg(feature = "crossbeam-channel")] +use crossbeam_channel::Receiver; + +use crate::{queue, Sample, Source, source::Done}; +use crate::stream::{OutputStreamHandle, PlayError}; /// Handle to a device that outputs sounds. /// @@ -245,10 +245,10 @@ impl Drop for Sink { #[cfg(test)] mod tests { - use crate::buffer::SamplesBuffer; + use std::sync::atomic::{AtomicBool, Ordering}; + use crate::{Sink, Source}; - use std::sync::atomic::Ordering; - use crossbeam_utils::atomic::AtomicCell; + use crate::buffer::SamplesBuffer; #[test] fn test_pause_and_stop() { @@ -257,7 +257,7 @@ mod tests { // assert_eq!(queue_rx.next(), Some(0.0)); let v = vec![10i16, -10, 20, -20, 30, -30]; - + v.iter().skip(123); // Low rate to ensure immediate control. sink.append(SamplesBuffer::new(1, 1, v.clone())); let mut src = SamplesBuffer::new(1, 1, v).convert_samples(); @@ -309,15 +309,6 @@ mod tests { assert_eq!(queue_rx.next(), src.next()); } - #[test] - fn lab() { - - - let c = AtomicCell::new(0.0); - assert!(AtomicCell::::is_lock_free()); - c.load(); - } - #[test] fn test_volume() { let (sink, mut queue_rx) = Sink::new_idle(); @@ -335,4 +326,21 @@ mod tests { assert_eq!(queue_rx.next(), src.next()); } } + + #[test] + fn lab() { + let (tx, rx) = std::sync::mpsc::sync_channel(1); + tx.send(12).unwrap(); + let ta = std::time::Instant::now(); + let flag = AtomicBool::new(false); + let n = 100_000_000; + // std::borrow::Cow::Owned(123); + for _ in 0..n { + if flag.load(Ordering::Acquire) { + rx.try_recv(); + } + } + let tb = std::time::Instant::now(); + dbg!((tb - ta) / n); + } } diff --git a/src/stream.rs b/src/stream.rs index 98105bc..e620c63 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -4,7 +4,7 @@ use std::iter::empty; use std::marker::Sync; use std::sync::{Arc, Weak}; -use cpal::{PlayStreamError, Sample, SampleFormat, StreamConfig, SupportedStreamConfig}; +use cpal::{BufferSize, ChannelCount, FrameCount, PlayStreamError, Sample, SampleFormat, SampleRate, StreamConfig, SupportedBufferSize, SupportedStreamConfig}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::decoder; @@ -12,16 +12,19 @@ use crate::dynamic_mixer::DynamicMixerController; use crate::sink::Sink; use crate::source::Source; -type SampleSource = dyn Iterator; +const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); + +type SampleSource = dyn Iterator + Send; pub struct OutputHandle { stream: cpal::Stream, - plug: SourceSocket, + // holder: SourcePlug, } impl OutputHandle { pub fn attach(&mut self, source: Box>) { - self.plug.source = source; + todo!(); + // self.holder.source = source; } pub fn play(&self) -> Result<(), PlayStreamError> { @@ -29,17 +32,17 @@ impl OutputHandle { } } -struct SourceSocket { +struct SourcePlug { source: Box>, } -impl SourceSocket { +impl SourcePlug { pub fn new() -> Self { - SourceSocket { source: Box::new(empty()) } + SourcePlug { source: Box::new(empty()) } } } -impl Iterator for SourceSocket { +impl Iterator for SourcePlug { type Item = f32; fn next(&mut self) -> Option { @@ -62,43 +65,124 @@ pub struct OutputStreamHandle { mixer: Weak>, } -impl OutputStream { - /// Returns a new stream & handle using the given output device and the default output - /// configuration. +#[derive(Copy, Clone, Debug)] +struct OutputStreamConfig { + pub channel_count: ChannelCount, + pub sample_rate: SampleRate, + pub buffer_size: BufferSize, + pub sample_format: SampleFormat, +} + +#[derive(Default)] +struct OutputStreamBuilder { + device: Option, + config: OutputStreamConfig, +} + +impl Default for OutputStreamConfig { + fn default() -> Self { + Self { + channel_count: 2, + sample_rate: HZ_44100, + buffer_size: BufferSize::Default, + sample_format: SampleFormat::I8, + } + } +} + +impl OutputStreamBuilder { pub fn from_device( - device: &cpal::Device, - ) -> Result { + device: cpal::Device, + ) -> Result { let default_config = device.default_output_config()?; - OutputStream::from_device_config(device, default_config) + Ok(Self::default().with_supported_config(&default_config)) } + pub fn from_default_device() -> Result { + let default_device = cpal::default_host() + .default_output_device() + .ok_or(StreamError::NoDevice)?; + Self::from_device(default_device) + } + + pub fn with_device(mut self, device: cpal::Device) -> OutputStreamBuilder { + self.device = Some(device); + self + } + + pub fn with_channels(mut self, channel_count: cpal::ChannelCount) -> OutputStreamBuilder { + assert!(channel_count > 0); + self.config.channel_count = channel_count; + self + } + + pub fn with_sample_rate(mut self, sample_rate: cpal::SampleRate) -> OutputStreamBuilder { + self.config.sample_rate = sample_rate; + self + } + + pub fn with_buffer_size(mut self, buffer_size: cpal::BufferSize) -> OutputStreamBuilder { + self.config.buffer_size = buffer_size; + self + } + + pub fn with_sample_format(mut self, sample_format: SampleFormat) -> OutputStreamBuilder { + self.config.sample_format = sample_format; + self + } + + pub fn with_supported_config(mut self, config: &cpal::SupportedStreamConfig) -> OutputStreamBuilder { + self.config = OutputStreamConfig { + channel_count: config.channels(), + sample_rate: config.sample_rate(), + // FIXME See how to best handle buffer_size preferences + buffer_size: clamp_supported_buffer_size(config.buffer_size(), 512), + sample_format: config.sample_format(), + ..self.config + }; + self + } + + pub fn with_config(mut self, config: &cpal::StreamConfig) -> OutputStreamBuilder { + self.config = OutputStreamConfig { + channel_count: config.channels, + sample_rate: config.sample_rate, + buffer_size: config.buffer_size, + ..self.config + }; + self + } + + pub fn open_stream(&self) -> Result { + let device = self.device.as_ref().expect("output device specified"); + OutputHandle::open(device, &self.config) + } + + /// FIXME Update documentation. /// Returns a new stream & handle using the given device and stream config. /// /// If the supplied `SupportedStreamConfig` is invalid for the device this function will /// fail to create an output stream and instead return a `StreamError`. - pub fn from_device_config( - device: &cpal::Device, - config: SupportedStreamConfig, - ) -> Result { - let handle = device.try_new_output_stream_config(config)?; - handle.play()?; - Ok(handle) - } - - pub fn from_config( - device: &cpal::Device, - config: &StreamConfig, - sample_format: &SampleFormat, - ) -> Result { - let handle = device.try_new_output_stream(&config, &sample_format)?; - handle.play()?; - Ok(handle) + pub fn try_open_stream(&self) -> Result { + let device = self.device.as_ref().expect("output device specified"); + OutputHandle::open(device, &self.config).or_else(|err| { + for supported_config in supported_output_configs(device)? { + let builder = Self::default().with_supported_config(&supported_config); + if let Ok(handle) = OutputHandle::open(device, &builder.config) { + return Ok(handle); + } + } + Err(err) + }) } + /// FIXME Update docs + /// /// Return a new stream & handle using the default output device. /// /// On failure will fall back to trying any non-default output devices. - pub fn default() -> Result { + /// FIXME update the function. + pub fn default_stream() -> Result { let default_device = cpal::default_host() .default_output_device() .ok_or(StreamError::NoDevice)?; @@ -119,6 +203,21 @@ impl OutputStream { } } +fn clamp_supported_buffer_size(buffer_size: &SupportedBufferSize, preferred_size: FrameCount) -> BufferSize { + todo!() +} + +impl From<&OutputStreamConfig> for StreamConfig { + fn from(config: &OutputStreamConfig) -> Self { + cpal::StreamConfig { + channels: config.channel_count, + sample_rate: config.sample_rate, + buffer_size: config.buffer_size, + } + } +} + +// TODO (refactoring) Move necessary conveniences to the builder? impl OutputStreamHandle { /// Plays a source with a device until it ends. pub fn play_raw(&self, source: S) -> Result<(), PlayError> @@ -232,45 +331,36 @@ impl error::Error for StreamError { } } -/// Extensions to `cpal::Device` -pub(crate) trait CpalDeviceExt { - fn new_output_stream_with_format( - &self, - config: &cpal::StreamConfig, - sample_format: &cpal::SampleFormat, - ) -> Result; +impl OutputHandle { + pub fn open(device: &cpal::Device, config: &OutputStreamConfig) -> Result { + Self::init_stream(device, config) + .map_err(|x| StreamError::from(x)) + .and_then(|stream| { + stream.play().map_err(|x| StreamError::from(x))?; + Ok(crate::stream::OutputHandle { stream }) + }) + } - fn try_new_output_stream_config( - &self, - config: cpal::SupportedStreamConfig, - ) -> Result; - - fn try_new_output_stream( - &self, - config: &cpal::StreamConfig, - sample_format: &cpal::SampleFormat, - ) -> Result; -} - -impl CpalDeviceExt for cpal::Device { - fn new_output_stream_with_format( - &self, - config: &cpal::StreamConfig, - sample_format: &cpal::SampleFormat, - ) -> Result { + fn init_stream( + device: &cpal::Device, + config: &OutputStreamConfig, + ) -> Result { let error_callback = |err| eprintln!("an error occurred on output stream: {}", err); - let mut samples = SourceSocket::new(); + let sample_format = config.sample_format; + let config = config.into(); + let mut samples = SourcePlug::new(); match sample_format { - cpal::SampleFormat::F32 => self.build_output_stream::( - &config, - move |data, _| { - data.iter_mut() - .for_each(|d| *d = samples.next().unwrap_or(0f32)) - }, - error_callback, - None, - ), - cpal::SampleFormat::F64 => self.build_output_stream::( + cpal::SampleFormat::F32 => + device.build_output_stream::( + &config, + move |data, _| { + data.iter_mut() + .for_each(|d| *d = samples.next().unwrap_or(0f32)) + }, + error_callback, + None, + ), + cpal::SampleFormat::F64 => device.build_output_stream::( &config, move |data, _| { data.iter_mut() @@ -279,7 +369,7 @@ impl CpalDeviceExt for cpal::Device { error_callback, None, ), - cpal::SampleFormat::I8 => self.build_output_stream::( + cpal::SampleFormat::I8 => device.build_output_stream::( &config, move |data, _| { data.iter_mut() @@ -288,7 +378,7 @@ impl CpalDeviceExt for cpal::Device { error_callback, None, ), - cpal::SampleFormat::I16 => self.build_output_stream::( + cpal::SampleFormat::I16 => device.build_output_stream::( &config, move |data, _| { data.iter_mut() @@ -297,7 +387,7 @@ impl CpalDeviceExt for cpal::Device { error_callback, None, ), - cpal::SampleFormat::I32 => self.build_output_stream::( + cpal::SampleFormat::I32 => device.build_output_stream::( &config, move |data, _| { data.iter_mut() @@ -306,7 +396,7 @@ impl CpalDeviceExt for cpal::Device { error_callback, None, ), - cpal::SampleFormat::I64 => self.build_output_stream::( + cpal::SampleFormat::I64 => device.build_output_stream::( &config, move |data, _| { data.iter_mut() @@ -315,7 +405,7 @@ impl CpalDeviceExt for cpal::Device { error_callback, None, ), - cpal::SampleFormat::U8 => self.build_output_stream::( + cpal::SampleFormat::U8 => device.build_output_stream::( &config, move |data, _| { data.iter_mut().for_each(|d| { @@ -328,7 +418,7 @@ impl CpalDeviceExt for cpal::Device { error_callback, None, ), - cpal::SampleFormat::U16 => self.build_output_stream::( + cpal::SampleFormat::U16 => device.build_output_stream::( &config, move |data, _| { data.iter_mut().for_each(|d| { @@ -341,7 +431,7 @@ impl CpalDeviceExt for cpal::Device { error_callback, None, ), - cpal::SampleFormat::U32 => self.build_output_stream::( + cpal::SampleFormat::U32 => device.build_output_stream::( &config, move |data, _| { data.iter_mut().for_each(|d| { @@ -354,7 +444,7 @@ impl CpalDeviceExt for cpal::Device { error_callback, None, ), - cpal::SampleFormat::U64 => self.build_output_stream::( + cpal::SampleFormat::U64 => device.build_output_stream::( &config, move |data, _| { data.iter_mut().for_each(|d| { @@ -367,45 +457,15 @@ impl CpalDeviceExt for cpal::Device { error_callback, None, ), - _ => return Err(cpal::BuildStreamError::StreamConfigNotSupported), + _ => Err(cpal::BuildStreamError::StreamConfigNotSupported), } - .map(|stream| crate::stream::OutputHandle { stream, plug: samples }) - } - - fn try_new_output_stream_config( - &self, - config: SupportedStreamConfig, - ) -> Result { - self.new_output_stream_with_format(&config.config(), &config.sample_format()).or_else(|err| { - // look through all supported formats to see if another works - supported_output_formats(self)? - .find_map(|format| self.new_output_stream_with_format(&format.config(), &config.sample_format()).ok()) - // return original error if nothing works - .ok_or(StreamError::BuildStreamError(err)) - }) - } - - fn try_new_output_stream( - &self, - config: &StreamConfig, - sample_format: &SampleFormat, - ) -> Result { - self.new_output_stream_with_format(&config, &sample_format).or_else(|err| { - // look through all supported formats to see if another works - supported_output_formats(self)? - .find_map(|format| self.new_output_stream_with_format(&format.config(), &sample_format).ok()) - // return original error if nothing works - .ok_or(StreamError::BuildStreamError(err)) - }) } } /// All the supported output formats with sample rates -fn supported_output_formats( +fn supported_output_configs( device: &cpal::Device, ) -> Result, StreamError> { - const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); - let mut supported: Vec<_> = device.supported_output_configs()?.collect(); supported.sort_by(|a, b| b.cmp_default_heuristics(a)); From fcfbabc607d624d1ee6551e48e7267ad5f301fa5 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sat, 9 Nov 2024 01:25:31 +0400 Subject: [PATCH 09/34] Revert to using mixer as default output --- src/stream.rs | 49 +++++++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/stream.rs b/src/stream.rs index e620c63..5a0e1fd 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -8,7 +8,7 @@ use cpal::{BufferSize, ChannelCount, FrameCount, PlayStreamError, Sample, Sample use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::decoder; -use crate::dynamic_mixer::DynamicMixerController; +use crate::dynamic_mixer::{mixer, DynamicMixer, DynamicMixerController}; use crate::sink::Sink; use crate::source::Source; @@ -16,22 +16,6 @@ const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); type SampleSource = dyn Iterator + Send; -pub struct OutputHandle { - stream: cpal::Stream, - // holder: SourcePlug, -} - -impl OutputHandle { - pub fn attach(&mut self, source: Box>) { - todo!(); - // self.holder.source = source; - } - - pub fn play(&self) -> Result<(), PlayStreamError> { - self.stream.play() - } -} - struct SourcePlug { source: Box>, } @@ -65,6 +49,19 @@ pub struct OutputStreamHandle { mixer: Weak>, } +pub struct OutputHandle { + stream: cpal::Stream, + mixer: Arc>, +} + +impl OutputHandle { + + pub fn play(&self) -> Result<(), PlayStreamError> { + self.stream.play() + } +} + + #[derive(Copy, Clone, Debug)] struct OutputStreamConfig { pub channel_count: ChannelCount, @@ -167,8 +164,7 @@ impl OutputStreamBuilder { let device = self.device.as_ref().expect("output device specified"); OutputHandle::open(device, &self.config).or_else(|err| { for supported_config in supported_output_configs(device)? { - let builder = Self::default().with_supported_config(&supported_config); - if let Ok(handle) = OutputHandle::open(device, &builder.config) { + if let Ok(handle) = Self::default().with_supported_config(&supported_config).open_stream() { return Ok(handle); } } @@ -182,12 +178,12 @@ impl OutputStreamBuilder { /// /// On failure will fall back to trying any non-default output devices. /// FIXME update the function. - pub fn default_stream() -> Result { + pub fn try_default_stream() -> Result { let default_device = cpal::default_host() .default_output_device() .ok_or(StreamError::NoDevice)?; - let default_stream = Self::from_device(&default_device); + let default_stream = Self::from_device(default_device).and_then(|x| x.open_stream()); default_stream.or_else(|original_err| { // default device didn't work, try other ones @@ -197,7 +193,7 @@ impl OutputStreamBuilder { }; devices - .find_map(|d| Self::from_device(&d).ok()) + .find_map(|d| Self::from_device(d).and_then(|x| x.open_stream()).ok()) .ok_or(original_err) }) } @@ -333,22 +329,23 @@ impl error::Error for StreamError { impl OutputHandle { pub fn open(device: &cpal::Device, config: &OutputStreamConfig) -> Result { - Self::init_stream(device, config) + let (controller, source) = mixer(config.channel_count, config.sample_rate.0); + Self::init_stream(device, config, source) .map_err(|x| StreamError::from(x)) .and_then(|stream| { - stream.play().map_err(|x| StreamError::from(x))?; - Ok(crate::stream::OutputHandle { stream }) + stream.play()?; + Ok(Self { stream, mixer: controller }) }) } fn init_stream( device: &cpal::Device, config: &OutputStreamConfig, + mut samples: DynamicMixer, ) -> Result { let error_callback = |err| eprintln!("an error occurred on output stream: {}", err); let sample_format = config.sample_format; let config = config.into(); - let mut samples = SourcePlug::new(); match sample_format { cpal::SampleFormat::F32 => device.build_output_stream::( From 7154439c4b48482fb3ad11ba9f7c496cd4b2cc02 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sat, 9 Nov 2024 01:26:41 +0400 Subject: [PATCH 10/34] Remove source plug prototype --- src/stream.rs | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/src/stream.rs b/src/stream.rs index 5a0e1fd..94e6c93 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -1,11 +1,10 @@ -use std::{error, fmt}; use std::io::{Read, Seek}; -use std::iter::empty; use std::marker::Sync; use std::sync::{Arc, Weak}; +use std::{error, fmt}; -use cpal::{BufferSize, ChannelCount, FrameCount, PlayStreamError, Sample, SampleFormat, SampleRate, StreamConfig, SupportedBufferSize, SupportedStreamConfig}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use cpal::{BufferSize, ChannelCount, FrameCount, PlayStreamError, Sample, SampleFormat, SampleRate, StreamConfig, SupportedBufferSize}; use crate::decoder; use crate::dynamic_mixer::{mixer, DynamicMixer, DynamicMixerController}; @@ -14,27 +13,6 @@ use crate::source::Source; const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); -type SampleSource = dyn Iterator + Send; - -struct SourcePlug { - source: Box>, -} - -impl SourcePlug { - pub fn new() -> Self { - SourcePlug { source: Box::new(empty()) } - } -} - -impl Iterator for SourcePlug { - type Item = f32; - - fn next(&mut self) -> Option { - self.source.next() - } -} - - /// `cpal::Stream` container. Also see the more useful `OutputStreamHandle`. /// /// If this is dropped, playback will end & attached `OutputStreamHandle`s will no longer work. @@ -50,12 +28,11 @@ pub struct OutputStreamHandle { } pub struct OutputHandle { - stream: cpal::Stream, + stream: cpal::Stream, mixer: Arc>, } impl OutputHandle { - pub fn play(&self) -> Result<(), PlayStreamError> { self.stream.play() } From e2c74a5ccc1681e58ab0c1bab78e0f82e4ba8019 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sat, 9 Nov 2024 02:13:04 +0400 Subject: [PATCH 11/34] Streamline default stream logic --- src/lib.rs | 8 ++-- src/sink.rs | 33 ++++---------- src/spatial_sink.rs | 4 +- src/stream.rs | 103 +++++++++++++++----------------------------- 4 files changed, 49 insertions(+), 99 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bc09d0e..183bdd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,10 +8,10 @@ //! - Get an output stream handle to a physical device. For example, get a stream to the system's //! default sound device with [`OutputStream::default()`] //! FIXME Update documentation after the builder is complete -//! - Call [`.play_raw(source)`](OutputStreamHandle::play_raw) on the output stream handle. +//! - Call [`.play_raw(source)`](OutputStream::play_raw) on the output stream handle. //! //! FIXME Update documentation after the builder is complete -//! The [`play_raw`](OutputStreamHandle::play_raw) function expects the source to produce [`f32`]s, +//! The [`play_raw`](OutputStream::play_raw) function expects the source to produce [`f32`]s, //! which may not be the case. If you get a compilation error, try calling //! [`.convert_samples()`](Source::convert_samples) on the source to fix it. //! @@ -42,7 +42,7 @@ //! named [`Sink`] which represents an audio track. //! //! FIXME Update documentation after the builder is complete -//! Instead of playing the sound with [`play_raw`](OutputStreamHandle::play_raw), you can add it to +//! Instead of playing the sound with [`play_raw`](OutputStream::play_raw), you can add it to //! a [`Sink`] instead. //! //! - Get a [`Sink`] to the output stream, and [`.append()`](Sink::append) your sound to it. @@ -135,4 +135,4 @@ pub use crate::decoder::Decoder; pub use crate::sink::Sink; pub use crate::source::Source; pub use crate::spatial_sink::SpatialSink; -pub use crate::stream::{OutputStream, OutputStreamHandle, PlayError, StreamError}; +pub use crate::stream::{OutputStream, PlayError, StreamError}; diff --git a/src/sink.rs b/src/sink.rs index ef45fba..03bc716 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -1,15 +1,15 @@ -use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; #[cfg(not(feature = "crossbeam-channel"))] use std::sync::mpsc::Receiver; +use std::sync::{Arc, Mutex}; use std::time::Duration; use cpal::FromSample; #[cfg(feature = "crossbeam-channel")] use crossbeam_channel::Receiver; -use crate::{queue, Sample, Source, source::Done}; -use crate::stream::{OutputStreamHandle, PlayError}; +use crate::stream::{OutputStream, PlayError}; +use crate::{queue, source::Done, Sample, Source}; /// Handle to a device that outputs sounds. /// @@ -36,9 +36,9 @@ struct Controls { impl Sink { /// Builds a new `Sink`, beginning playback on a stream. #[inline] - pub fn try_new(stream: &OutputStreamHandle) -> Result { + pub fn try_new(stream: &OutputStream) -> Result { let (sink, queue_rx) = Sink::new_idle(); - stream.play_raw(queue_rx)?; + stream.mixer().add(queue_rx); Ok(sink) } @@ -245,10 +245,10 @@ impl Drop for Sink { #[cfg(test)] mod tests { - use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::atomic::Ordering; - use crate::{Sink, Source}; use crate::buffer::SamplesBuffer; + use crate::{Sink, Source}; #[test] fn test_pause_and_stop() { @@ -257,7 +257,7 @@ mod tests { // assert_eq!(queue_rx.next(), Some(0.0)); let v = vec![10i16, -10, 20, -20, 30, -30]; - v.iter().skip(123); + // Low rate to ensure immediate control. sink.append(SamplesBuffer::new(1, 1, v.clone())); let mut src = SamplesBuffer::new(1, 1, v).convert_samples(); @@ -326,21 +326,4 @@ mod tests { assert_eq!(queue_rx.next(), src.next()); } } - - #[test] - fn lab() { - let (tx, rx) = std::sync::mpsc::sync_channel(1); - tx.send(12).unwrap(); - let ta = std::time::Instant::now(); - let flag = AtomicBool::new(false); - let n = 100_000_000; - // std::borrow::Cow::Owned(123); - for _ in 0..n { - if flag.load(Ordering::Acquire) { - rx.try_recv(); - } - } - let tb = std::time::Instant::now(); - dbg!((tb - ta) / n); - } } diff --git a/src/spatial_sink.rs b/src/spatial_sink.rs index 762bda9..c150636 100644 --- a/src/spatial_sink.rs +++ b/src/spatial_sink.rs @@ -5,7 +5,7 @@ use std::time::Duration; use cpal::FromSample; use crate::source::Spatial; -use crate::stream::{OutputStreamHandle, PlayError}; +use crate::stream::{OutputStream, PlayError}; use crate::{Sample, Sink, Source}; pub struct SpatialSink { @@ -22,7 +22,7 @@ struct SoundPositions { impl SpatialSink { /// Builds a new `SpatialSink`. pub fn try_new( - stream: &OutputStreamHandle, + stream: &OutputStream, emitter_position: [f32; 3], left_ear: [f32; 3], right_ear: [f32; 3], diff --git a/src/stream.rs b/src/stream.rs index 94e6c93..c7249a9 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -9,36 +9,23 @@ use cpal::{BufferSize, ChannelCount, FrameCount, PlayStreamError, Sample, Sample use crate::decoder; use crate::dynamic_mixer::{mixer, DynamicMixer, DynamicMixerController}; use crate::sink::Sink; -use crate::source::Source; const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); -/// `cpal::Stream` container. Also see the more useful `OutputStreamHandle`. +/// `cpal::Stream` container. Use `mixer()` method to control output. /// -/// If this is dropped, playback will end & attached `OutputStreamHandle`s will no longer work. +/// If this is dropped, playback will end, and the associated output stream will be disposed. pub struct OutputStream { - mixer: Arc>, - _stream: cpal::Stream, -} - -/// More flexible handle to a `OutputStream` that provides playback. -#[derive(Clone)] -pub struct OutputStreamHandle { - mixer: Weak>, -} - -pub struct OutputHandle { stream: cpal::Stream, mixer: Arc>, } -impl OutputHandle { - pub fn play(&self) -> Result<(), PlayStreamError> { - self.stream.play() +impl OutputStream { + pub fn mixer(&self) -> Arc> { + self.mixer.clone() } } - #[derive(Copy, Clone, Debug)] struct OutputStreamConfig { pub channel_count: ChannelCount, @@ -127,9 +114,9 @@ impl OutputStreamBuilder { self } - pub fn open_stream(&self) -> Result { + pub fn open_stream(&self) -> Result { let device = self.device.as_ref().expect("output device specified"); - OutputHandle::open(device, &self.config) + OutputStream::open(device, &self.config) } /// FIXME Update documentation. @@ -137,9 +124,9 @@ impl OutputStreamBuilder { /// /// If the supplied `SupportedStreamConfig` is invalid for the device this function will /// fail to create an output stream and instead return a `StreamError`. - pub fn try_open_stream(&self) -> Result { + pub fn try_open_stream(&self) -> Result { let device = self.device.as_ref().expect("output device specified"); - OutputHandle::open(device, &self.config).or_else(|err| { + OutputStream::open(device, &self.config).or_else(|err| { for supported_config in supported_output_configs(device)? { if let Ok(handle) = Self::default().with_supported_config(&supported_config).open_stream() { return Ok(handle); @@ -154,25 +141,18 @@ impl OutputStreamBuilder { /// Return a new stream & handle using the default output device. /// /// On failure will fall back to trying any non-default output devices. - /// FIXME update the function. - pub fn try_default_stream() -> Result { - let default_device = cpal::default_host() - .default_output_device() - .ok_or(StreamError::NoDevice)?; - - let default_stream = Self::from_device(default_device).and_then(|x| x.open_stream()); - - default_stream.or_else(|original_err| { - // default device didn't work, try other ones - let mut devices = match cpal::default_host().output_devices() { - Ok(d) => d, - Err(_) => return Err(original_err), - }; - - devices - .find_map(|d| Self::from_device(d).and_then(|x| x.open_stream()).ok()) - .ok_or(original_err) - }) + pub fn try_default_stream() -> Result { + Self::from_default_device() + .and_then(|x| x.open_stream()) + .or_else(|original_err| { + let mut devices = match cpal::default_host().output_devices() { + Ok(devices) => devices, + Err(_) => return Err(original_err), // TODO Report error? + }; + devices + .find_map(|d| Self::from_device(d).and_then(|x| x.try_open_stream()).ok()) + .ok_or(original_err) + }) } } @@ -180,6 +160,17 @@ fn clamp_supported_buffer_size(buffer_size: &SupportedBufferSize, preferred_size todo!() } +/// Plays a sound once. Returns a `Sink` that can be used to control the sound. +pub fn play(stream: &OutputStream, input: R) -> Result +where + R: Read + Seek + Send + Sync + 'static, +{ + let input = decoder::Decoder::new(input)?; + let sink = Sink::try_new(stream)?; + sink.append(input); + Ok(sink) +} + impl From<&OutputStreamConfig> for StreamConfig { fn from(config: &OutputStreamConfig) -> Self { cpal::StreamConfig { @@ -190,30 +181,6 @@ impl From<&OutputStreamConfig> for StreamConfig { } } -// TODO (refactoring) Move necessary conveniences to the builder? -impl OutputStreamHandle { - /// Plays a source with a device until it ends. - pub fn play_raw(&self, source: S) -> Result<(), PlayError> - where - S: Source + Send + 'static, - { - let mixer = self.mixer.upgrade().ok_or(PlayError::NoDevice)?; - mixer.add(source); - Ok(()) - } - - /// Plays a sound once. Returns a `Sink` that can be used to control the sound. - pub fn play_once(&self, input: R) -> Result - where - R: Read + Seek + Send + Sync + 'static, - { - let input = decoder::Decoder::new(input)?; - let sink = Sink::try_new(self)?; - sink.append(input); - Ok(sink) - } -} - /// An error occurred while attempting to play a sound. #[derive(Debug)] pub enum PlayError { @@ -304,8 +271,8 @@ impl error::Error for StreamError { } } -impl OutputHandle { - pub fn open(device: &cpal::Device, config: &OutputStreamConfig) -> Result { +impl OutputStream { + pub fn open(device: &cpal::Device, config: &OutputStreamConfig) -> Result { let (controller, source) = mixer(config.channel_count, config.sample_rate.0); Self::init_stream(device, config, source) .map_err(|x| StreamError::from(x)) @@ -436,7 +403,7 @@ impl OutputHandle { } } -/// All the supported output formats with sample rates +/// Return all formats supported by the device. fn supported_output_configs( device: &cpal::Device, ) -> Result, StreamError> { From dbd1d1f129b20422a52d196d024dc1f094972ddd Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sat, 9 Nov 2024 02:16:28 +0400 Subject: [PATCH 12/34] Remove Criterion dependency --- Cargo.toml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e29ccc4..c7392dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,11 +37,6 @@ symphonia-wav = ["symphonia/wav", "symphonia/pcm", "symphonia/adpcm"] [dev-dependencies] quickcheck = "1.0.3" -criterion = "0.5.1" - -[[bench]] -name = "benchmark" -harness = false [[example]] name = "music_m4a" From 5cfe61f929bcd747865cec2871d4e983873bbe5a Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sun, 10 Nov 2024 00:10:36 +0400 Subject: [PATCH 13/34] Update a basic example --- examples/basic.rs | 68 +++++++++++++++++++------------- examples/mix_multiple_sources.rs | 2 +- examples/music_flac.rs | 2 +- examples/music_m4a.rs | 2 +- examples/music_mp3.rs | 2 +- examples/music_ogg.rs | 2 +- examples/music_wav.rs | 2 +- examples/reverb.rs | 2 +- examples/stereo.rs | 2 +- src/dynamic_mixer.rs | 24 ++++++----- src/lib.rs | 4 +- src/sink.rs | 13 +++--- src/spatial_sink.rs | 13 +++--- src/stream.rs | 27 +++++++------ 14 files changed, 91 insertions(+), 74 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index d12bd24..20c5fdf 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -3,34 +3,46 @@ use std::thread; use std::time::Duration; fn main() { - let (_stream, stream_handle) = rodio::OutputStream::default().unwrap(); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream() + .expect("open default audio stream"); + let mixer = stream_handle.mixer(); - let file = std::fs::File::open("assets/beep.wav").unwrap(); - let beep1 = stream_handle.play_once(BufReader::new(file)).unwrap(); - beep1.set_volume(0.2); - println!("Started beep1"); + { + let file = std::fs::File::open("assets/beep.wav").unwrap(); + let sink = rodio::play(&mixer, BufReader::new(file)).unwrap(); + sink.set_volume(0.2); + println!("Started beep1"); + thread::sleep(Duration::from_millis(1500)); + sink.detach(); + } + { + let file = std::fs::File::open("assets/beep.wav").unwrap(); + let sink = rodio::play(&mixer, BufReader::new(file)).unwrap(); + sink.set_volume(0.2); + println!("Started beep1"); + thread::sleep(Duration::from_millis(1500)); + sink.detach(); + } - thread::sleep(Duration::from_millis(1500)); - - let file = std::fs::File::open("assets/beep2.wav").unwrap(); - let beep2 = stream_handle.play_once(BufReader::new(file)).unwrap(); - beep2.set_volume(0.3); - beep2.detach(); - println!("Started beep2"); - - thread::sleep(Duration::from_millis(1500)); - let file = std::fs::File::open("assets/beep3.ogg").unwrap(); - let beep3 = stream_handle.play_once(file).unwrap(); - beep3.set_volume(0.2); - println!("Started beep3"); - - thread::sleep(Duration::from_millis(1500)); - drop(beep1); - println!("Stopped beep1"); - - thread::sleep(Duration::from_millis(1500)); - drop(beep3); - println!("Stopped beep3"); - - thread::sleep(Duration::from_millis(1500)); + // let file = std::fs::File::open("assets/beep2.wav").unwrap(); + // let beep2 = stream_handle.play_once(BufReader::new(file)).unwrap(); + // beep2.set_volume(0.3); + // beep2.detach(); + // println!("Started beep2"); + // + // thread::sleep(Duration::from_millis(1500)); + // let file = std::fs::File::open("assets/beep3.ogg").unwrap(); + // let beep3 = stream_handle.play_once(file).unwrap(); + // beep3.set_volume(0.2); + // println!("Started beep3"); + // + // thread::sleep(Duration::from_millis(1500)); + // drop(beep1); + // println!("Stopped beep1"); + // + // thread::sleep(Duration::from_millis(1500)); + // drop(beep3); + // println!("Stopped beep3"); + // + // thread::sleep(Duration::from_millis(1500)); } diff --git a/examples/mix_multiple_sources.rs b/examples/mix_multiple_sources.rs index f4d0acf..4ffcc70 100644 --- a/examples/mix_multiple_sources.rs +++ b/examples/mix_multiple_sources.rs @@ -6,7 +6,7 @@ fn main() { // Construct a dynamic controller and mixer, stream_handle, and sink. let (controller, mixer) = dynamic_mixer::mixer::(2, 44_100); let (_stream, stream_handle) = OutputStream::default().unwrap(); - let sink = Sink::try_new(&stream_handle).unwrap(); + let sink = Sink::connect_new(&stream_handle).unwrap(); // Create four unique sources. The frequencies used here correspond // notes in the key of C and in octave 4: C4, or middle C on a piano, diff --git a/examples/music_flac.rs b/examples/music_flac.rs index 2b787b6..2d7d109 100644 --- a/examples/music_flac.rs +++ b/examples/music_flac.rs @@ -2,7 +2,7 @@ use std::io::BufReader; fn main() { let (_stream, handle) = rodio::OutputStream::default().unwrap(); - let sink = rodio::Sink::try_new(&handle).unwrap(); + let sink = rodio::Sink::connect_new(&handle).unwrap(); let file = std::fs::File::open("assets/music.flac").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/examples/music_m4a.rs b/examples/music_m4a.rs index e62d825..5a1fc3c 100644 --- a/examples/music_m4a.rs +++ b/examples/music_m4a.rs @@ -2,7 +2,7 @@ use std::io::BufReader; fn main() { let (_stream, handle) = rodio::OutputStream::default().unwrap(); - let sink = rodio::Sink::try_new(&handle).unwrap(); + let sink = rodio::Sink::connect_new(&handle).unwrap(); let file = std::fs::File::open("assets/music.m4a").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/examples/music_mp3.rs b/examples/music_mp3.rs index 20650ae..6204323 100644 --- a/examples/music_mp3.rs +++ b/examples/music_mp3.rs @@ -2,7 +2,7 @@ use std::io::BufReader; fn main() { let (_stream, handle) = rodio::OutputStream::default().unwrap(); - let sink = rodio::Sink::try_new(&handle).unwrap(); + let sink = rodio::Sink::connect_new(&handle).unwrap(); let file = std::fs::File::open("assets/music.mp3").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/examples/music_ogg.rs b/examples/music_ogg.rs index 8003283..5fd2553 100644 --- a/examples/music_ogg.rs +++ b/examples/music_ogg.rs @@ -2,7 +2,7 @@ use std::io::BufReader; fn main() { let (_stream, handle) = rodio::OutputStream::default().unwrap(); - let sink = rodio::Sink::try_new(&handle).unwrap(); + let sink = rodio::Sink::connect_new(&handle).unwrap(); let file = std::fs::File::open("assets/music.ogg").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/examples/music_wav.rs b/examples/music_wav.rs index d713c82..815fb73 100644 --- a/examples/music_wav.rs +++ b/examples/music_wav.rs @@ -2,7 +2,7 @@ use std::io::BufReader; fn main() { let (_stream, handle) = rodio::OutputStream::default().unwrap(); - let sink = rodio::Sink::try_new(&handle).unwrap(); + let sink = rodio::Sink::connect_new(&handle).unwrap(); let file = std::fs::File::open("assets/music.wav").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/examples/reverb.rs b/examples/reverb.rs index ea0dd9a..190c034 100644 --- a/examples/reverb.rs +++ b/examples/reverb.rs @@ -4,7 +4,7 @@ use std::time::Duration; fn main() { let (_stream, handle) = rodio::OutputStream::default().unwrap(); - let sink = rodio::Sink::try_new(&handle).unwrap(); + let sink = rodio::Sink::connect_new(&handle).unwrap(); let file = std::fs::File::open("assets/music.ogg").unwrap(); let source = rodio::Decoder::new(BufReader::new(file)).unwrap(); diff --git a/examples/stereo.rs b/examples/stereo.rs index 601658c..c3ee7f4 100644 --- a/examples/stereo.rs +++ b/examples/stereo.rs @@ -3,7 +3,7 @@ use std::io::BufReader; fn main() { let (_stream, handle) = rodio::OutputStream::default().unwrap(); - let sink = rodio::Sink::try_new(&handle).unwrap(); + let sink = rodio::Sink::connect_new(&handle).unwrap(); let file = std::fs::File::open("assets/RL.ogg").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/src/dynamic_mixer.rs b/src/dynamic_mixer.rs index 022607e..66e20cb 100644 --- a/src/dynamic_mixer.rs +++ b/src/dynamic_mixer.rs @@ -15,18 +15,18 @@ use crate::Sample; pub fn mixer( channels: u16, sample_rate: u32, -) -> (Arc>, DynamicMixer) +) -> (Arc>, MixerSource) where S: Sample + Send + 'static, { - let input = Arc::new(DynamicMixerController { + let input = Arc::new(Mixer { has_pending: AtomicBool::new(false), pending_sources: Mutex::new(Vec::new()), channels, sample_rate, }); - let output = DynamicMixer { + let output = MixerSource { current_sources: Vec::with_capacity(16), input: input.clone(), sample_count: 0, @@ -36,14 +36,14 @@ where } /// The input of the mixer. -pub struct DynamicMixerController { +pub struct Mixer { has_pending: AtomicBool, pending_sources: Mutex + Send>>>, channels: u16, sample_rate: u32, } -impl DynamicMixerController +impl Mixer where S: Sample + Send + 'static, { @@ -54,27 +54,25 @@ where T: Source + Send + 'static, { let uniform_source = UniformSourceIterator::new(source, self.channels, self.sample_rate); - let mut pending = self.pending_sources - .lock() - .unwrap(); + let mut pending = self.pending_sources.lock().unwrap(); pending.push(Box::new(uniform_source) as Box<_>); self.has_pending.store(true, Ordering::SeqCst); // TODO: can we relax this ordering? } } /// The output of the mixer. Implements `Source`. -pub struct DynamicMixer { +pub struct MixerSource { // The current iterator that produces samples. current_sources: Vec + Send>>, // The pending sounds. - input: Arc>, + input: Arc>, // The number of samples produced so far. sample_count: usize, } -impl Source for DynamicMixer +impl Source for MixerSource where S: Sample + Send + 'static, { @@ -99,7 +97,7 @@ where } } -impl Iterator for DynamicMixer +impl Iterator for MixerSource where S: Sample + Send + 'static, { @@ -128,7 +126,7 @@ where } } -impl DynamicMixer +impl MixerSource where S: Sample + Send + 'static, { diff --git a/src/lib.rs b/src/lib.rs index 183bdd8..a17d99b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,7 @@ //! //! // FIXME Update documentation after the builder is complete //! let (_stream, stream_handle) = OutputStream::default().unwrap(); -//! let sink = Sink::try_new(&stream_handle).unwrap(); +//! let sink = Sink::connect_new(&stream_handle).unwrap(); //! //! // Add a dummy source of the sake of the example. //! let source = SineWave::new(440.0).take_duration(Duration::from_secs_f32(0.25)).amplify(0.20); @@ -135,4 +135,4 @@ pub use crate::decoder::Decoder; pub use crate::sink::Sink; pub use crate::source::Source; pub use crate::spatial_sink::SpatialSink; -pub use crate::stream::{OutputStream, PlayError, StreamError}; +pub use crate::stream::{OutputStream, OutputStreamBuilder, play, PlayError, StreamError}; diff --git a/src/sink.rs b/src/sink.rs index 03bc716..8f210a1 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -10,6 +10,7 @@ use crossbeam_channel::Receiver; use crate::stream::{OutputStream, PlayError}; use crate::{queue, source::Done, Sample, Source}; +use crate::dynamic_mixer::Mixer; /// Handle to a device that outputs sounds. /// @@ -36,15 +37,15 @@ struct Controls { impl Sink { /// Builds a new `Sink`, beginning playback on a stream. #[inline] - pub fn try_new(stream: &OutputStream) -> Result { - let (sink, queue_rx) = Sink::new_idle(); - stream.mixer().add(queue_rx); - Ok(sink) + pub fn connect_new(mixer: &Mixer) -> Sink { + let (sink, source) = Sink::new(); + mixer.add(source); + sink } /// Builds a new `Sink`. #[inline] - pub fn new_idle() -> (Sink, queue::SourcesQueueOutput) { + pub fn new() -> (Sink, queue::SourcesQueueOutput) { let (queue_tx, queue_rx) = queue::queue(true); let sink = Sink { @@ -257,7 +258,7 @@ mod tests { // assert_eq!(queue_rx.next(), Some(0.0)); let v = vec![10i16, -10, 20, -20, 30, -30]; - + // Low rate to ensure immediate control. sink.append(SamplesBuffer::new(1, 1, v.clone())); let mut src = SamplesBuffer::new(1, 1, v).convert_samples(); diff --git a/src/spatial_sink.rs b/src/spatial_sink.rs index c150636..8d3a254 100644 --- a/src/spatial_sink.rs +++ b/src/spatial_sink.rs @@ -7,6 +7,7 @@ use cpal::FromSample; use crate::source::Spatial; use crate::stream::{OutputStream, PlayError}; use crate::{Sample, Sink, Source}; +use crate::dynamic_mixer::Mixer; pub struct SpatialSink { sink: Sink, @@ -21,20 +22,20 @@ struct SoundPositions { impl SpatialSink { /// Builds a new `SpatialSink`. - pub fn try_new( - stream: &OutputStream, + pub fn new( + stream: &Mixer, emitter_position: [f32; 3], left_ear: [f32; 3], right_ear: [f32; 3], - ) -> Result { - Ok(SpatialSink { - sink: Sink::try_new(stream)?, + ) -> SpatialSink { + SpatialSink { + sink: Sink::connect_new(stream), positions: Arc::new(Mutex::new(SoundPositions { emitter_position, left_ear, right_ear, })), - }) + } } /// Sets the position of the sound emitter in 3 dimensional space. diff --git a/src/stream.rs b/src/stream.rs index c7249a9..4df262f 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -7,7 +7,7 @@ use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::{BufferSize, ChannelCount, FrameCount, PlayStreamError, Sample, SampleFormat, SampleRate, StreamConfig, SupportedBufferSize}; use crate::decoder; -use crate::dynamic_mixer::{mixer, DynamicMixer, DynamicMixerController}; +use crate::dynamic_mixer::{mixer, MixerSource, Mixer}; use crate::sink::Sink; const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); @@ -17,11 +17,11 @@ const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); /// If this is dropped, playback will end, and the associated output stream will be disposed. pub struct OutputStream { stream: cpal::Stream, - mixer: Arc>, + mixer: Arc>, } impl OutputStream { - pub fn mixer(&self) -> Arc> { + pub fn mixer(&self) -> Arc> { self.mixer.clone() } } @@ -35,7 +35,7 @@ struct OutputStreamConfig { } #[derive(Default)] -struct OutputStreamBuilder { +pub struct OutputStreamBuilder { device: Option, config: OutputStreamConfig, } @@ -56,7 +56,9 @@ impl OutputStreamBuilder { device: cpal::Device, ) -> Result { let default_config = device.default_output_config()?; - Ok(Self::default().with_supported_config(&default_config)) + Ok(Self::default() + .with_device(device) + .with_supported_config(&default_config)) } pub fn from_default_device() -> Result { @@ -96,7 +98,7 @@ impl OutputStreamBuilder { self.config = OutputStreamConfig { channel_count: config.channels(), sample_rate: config.sample_rate(), - // FIXME See how to best handle buffer_size preferences + // FIXME See how to best handle buffer_size preferences? buffer_size: clamp_supported_buffer_size(config.buffer_size(), 512), sample_format: config.sample_format(), ..self.config @@ -147,7 +149,7 @@ impl OutputStreamBuilder { .or_else(|original_err| { let mut devices = match cpal::default_host().output_devices() { Ok(devices) => devices, - Err(_) => return Err(original_err), // TODO Report error? + Err(_) => return Err(original_err), // TODO Report the ignored error? }; devices .find_map(|d| Self::from_device(d).and_then(|x| x.try_open_stream()).ok()) @@ -157,16 +159,19 @@ impl OutputStreamBuilder { } fn clamp_supported_buffer_size(buffer_size: &SupportedBufferSize, preferred_size: FrameCount) -> BufferSize { - todo!() + BufferSize::Fixed(match buffer_size { + SupportedBufferSize::Range { min, max } => preferred_size.clamp(*min, *max), + SupportedBufferSize::Unknown => preferred_size + }) } /// Plays a sound once. Returns a `Sink` that can be used to control the sound. -pub fn play(stream: &OutputStream, input: R) -> Result +pub fn play(stream: &Mixer, input: R) -> Result where R: Read + Seek + Send + Sync + 'static, { let input = decoder::Decoder::new(input)?; - let sink = Sink::try_new(stream)?; + let sink = Sink::connect_new(stream); sink.append(input); Ok(sink) } @@ -285,7 +290,7 @@ impl OutputStream { fn init_stream( device: &cpal::Device, config: &OutputStreamConfig, - mut samples: DynamicMixer, + mut samples: MixerSource, ) -> Result { let error_callback = |err| eprintln!("an error occurred on output stream: {}", err); let sample_format = config.sample_format; From cabdda74a244d15e3c6c0fb2f428c2f171c8e4d3 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sun, 10 Nov 2024 02:34:49 +0400 Subject: [PATCH 14/34] Update tests and examples --- examples/basic.rs | 67 ++++++++++++++++---------------- examples/mix_multiple_sources.rs | 5 ++- examples/music_flac.rs | 5 ++- examples/music_m4a.rs | 5 ++- examples/music_mp3.rs | 5 ++- examples/music_ogg.rs | 5 ++- examples/music_wav.rs | 5 ++- examples/reverb.rs | 5 ++- examples/spatial.rs | 6 ++- examples/stereo.rs | 10 +++-- src/conversions/sample_rate.rs | 17 +++----- src/lib.rs | 14 ++++--- src/sink.rs | 9 ++--- src/spatial_sink.rs | 15 ++++--- src/stream.rs | 33 ++++++++-------- 15 files changed, 106 insertions(+), 100 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index 20c5fdf..787a90f 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,3 +1,5 @@ +use rodio::source::SineWave; +use rodio::Source; use std::io::BufReader; use std::thread; use std::time::Duration; @@ -7,42 +9,39 @@ fn main() { .expect("open default audio stream"); let mixer = stream_handle.mixer(); - { + let beep1 = { let file = std::fs::File::open("assets/beep.wav").unwrap(); let sink = rodio::play(&mixer, BufReader::new(file)).unwrap(); sink.set_volume(0.2); - println!("Started beep1"); - thread::sleep(Duration::from_millis(1500)); - sink.detach(); - } - { - let file = std::fs::File::open("assets/beep.wav").unwrap(); - let sink = rodio::play(&mixer, BufReader::new(file)).unwrap(); - sink.set_volume(0.2); - println!("Started beep1"); - thread::sleep(Duration::from_millis(1500)); - sink.detach(); - } + sink + }; + println!("Started beep1"); + thread::sleep(Duration::from_millis(1500)); - // let file = std::fs::File::open("assets/beep2.wav").unwrap(); - // let beep2 = stream_handle.play_once(BufReader::new(file)).unwrap(); - // beep2.set_volume(0.3); - // beep2.detach(); - // println!("Started beep2"); - // - // thread::sleep(Duration::from_millis(1500)); - // let file = std::fs::File::open("assets/beep3.ogg").unwrap(); - // let beep3 = stream_handle.play_once(file).unwrap(); - // beep3.set_volume(0.2); - // println!("Started beep3"); - // - // thread::sleep(Duration::from_millis(1500)); - // drop(beep1); - // println!("Stopped beep1"); - // - // thread::sleep(Duration::from_millis(1500)); - // drop(beep3); - // println!("Stopped beep3"); - // - // thread::sleep(Duration::from_millis(1500)); + { + let wave = SineWave::new(740.0) + .amplify(0.2) + .take_duration(Duration::from_secs(3)); + mixer.add(wave); + } + println!("Started beep2"); + thread::sleep(Duration::from_millis(1500)); + + let beep3 = { + let file = std::fs::File::open("assets/beep3.ogg").unwrap(); + let sink = rodio::play(&mixer, BufReader::new(file)).unwrap(); + sink.set_volume(0.2); + sink + }; + println!("Started beep3"); + thread::sleep(Duration::from_millis(1500)); + + drop(beep1); + println!("Stopped beep1"); + + thread::sleep(Duration::from_millis(1500)); + drop(beep3); + println!("Stopped beep3"); + + thread::sleep(Duration::from_millis(1500)); } diff --git a/examples/mix_multiple_sources.rs b/examples/mix_multiple_sources.rs index 4ffcc70..515cd73 100644 --- a/examples/mix_multiple_sources.rs +++ b/examples/mix_multiple_sources.rs @@ -5,8 +5,9 @@ use std::time::Duration; fn main() { // Construct a dynamic controller and mixer, stream_handle, and sink. let (controller, mixer) = dynamic_mixer::mixer::(2, 44_100); - let (_stream, stream_handle) = OutputStream::default().unwrap(); - let sink = Sink::connect_new(&stream_handle).unwrap(); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream() + .expect("open default audio stream"); + let sink = rodio::Sink::connect_new(&stream_handle.mixer()); // Create four unique sources. The frequencies used here correspond // notes in the key of C and in octave 4: C4, or middle C on a piano, diff --git a/examples/music_flac.rs b/examples/music_flac.rs index 2d7d109..3ae112a 100644 --- a/examples/music_flac.rs +++ b/examples/music_flac.rs @@ -1,8 +1,9 @@ use std::io::BufReader; fn main() { - let (_stream, handle) = rodio::OutputStream::default().unwrap(); - let sink = rodio::Sink::connect_new(&handle).unwrap(); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream() + .expect("open default audio stream"); + let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.flac").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/examples/music_m4a.rs b/examples/music_m4a.rs index 5a1fc3c..a94641b 100644 --- a/examples/music_m4a.rs +++ b/examples/music_m4a.rs @@ -1,8 +1,9 @@ use std::io::BufReader; fn main() { - let (_stream, handle) = rodio::OutputStream::default().unwrap(); - let sink = rodio::Sink::connect_new(&handle).unwrap(); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream() + .expect("open default audio stream"); + let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.m4a").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/examples/music_mp3.rs b/examples/music_mp3.rs index 6204323..dc8e9cd 100644 --- a/examples/music_mp3.rs +++ b/examples/music_mp3.rs @@ -1,8 +1,9 @@ use std::io::BufReader; fn main() { - let (_stream, handle) = rodio::OutputStream::default().unwrap(); - let sink = rodio::Sink::connect_new(&handle).unwrap(); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream() + .expect("open default audio stream"); + let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.mp3").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/examples/music_ogg.rs b/examples/music_ogg.rs index 5fd2553..562dfcb 100644 --- a/examples/music_ogg.rs +++ b/examples/music_ogg.rs @@ -1,8 +1,9 @@ use std::io::BufReader; fn main() { - let (_stream, handle) = rodio::OutputStream::default().unwrap(); - let sink = rodio::Sink::connect_new(&handle).unwrap(); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream() + .expect("open default audio stream"); + let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.ogg").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/examples/music_wav.rs b/examples/music_wav.rs index 815fb73..8e41598 100644 --- a/examples/music_wav.rs +++ b/examples/music_wav.rs @@ -1,8 +1,9 @@ use std::io::BufReader; fn main() { - let (_stream, handle) = rodio::OutputStream::default().unwrap(); - let sink = rodio::Sink::connect_new(&handle).unwrap(); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream() + .expect("open default audio stream"); + let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.wav").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); diff --git a/examples/reverb.rs b/examples/reverb.rs index 190c034..daca36a 100644 --- a/examples/reverb.rs +++ b/examples/reverb.rs @@ -3,8 +3,9 @@ use std::io::BufReader; use std::time::Duration; fn main() { - let (_stream, handle) = rodio::OutputStream::default().unwrap(); - let sink = rodio::Sink::connect_new(&handle).unwrap(); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream() + .expect("open default audio stream"); + let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.ogg").unwrap(); let source = rodio::Decoder::new(BufReader::new(file)).unwrap(); diff --git a/examples/spatial.rs b/examples/spatial.rs index 0b22e9a..aae2cad 100644 --- a/examples/spatial.rs +++ b/examples/spatial.rs @@ -18,9 +18,11 @@ fn main() { let total_duration = iter_duration * 2 * repeats; - let (_stream, handle) = rodio::OutputStream::default().unwrap(); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream() + .expect("open default audio stream"); + let mut positions = ([0., 0., 0.], [-1., 0., 0.], [1., 0., 0.]); - let sink = rodio::SpatialSink::try_new(&handle, positions.0, positions.1, positions.2).unwrap(); + let sink = rodio::SpatialSink::connect_new(&stream_handle.mixer(), positions.0, positions.1, positions.2); let file = std::fs::File::open("assets/music.ogg").unwrap(); let source = rodio::Decoder::new(BufReader::new(file)) diff --git a/examples/stereo.rs b/examples/stereo.rs index c3ee7f4..f6b3429 100644 --- a/examples/stereo.rs +++ b/examples/stereo.rs @@ -1,12 +1,16 @@ //! Plays a tone alternating between right and left ears, with right being first. use std::io::BufReader; +use rodio::Source; fn main() { - let (_stream, handle) = rodio::OutputStream::default().unwrap(); - let sink = rodio::Sink::connect_new(&handle).unwrap(); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream() + .expect("open default audio stream"); + let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/RL.ogg").unwrap(); - sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); + sink.append(rodio::Decoder::new(BufReader::new(file)) + .unwrap() + .amplify(0.2)); sink.sleep_until_end(); } diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index 21e11e5..47dbc30 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -243,8 +243,7 @@ impl ExactSizeIterator for SampleRateConverter where I: ExactSizeIterator, I::Item: Sample + Clone, -{ -} +{} #[cfg(test)] mod test { @@ -253,12 +252,6 @@ mod test { use cpal::SampleRate; use quickcheck::quickcheck; - // TODO: Remove once cpal 0.12.2 is released and the dependency is updated - // (cpal#483 implemented ops::Mul on SampleRate) - const fn multiply_rate(r: SampleRate, k: u32) -> SampleRate { - SampleRate(k * r.0) - } - quickcheck! { /// Check that resampling an empty input produces no output. fn empty(from: u32, to: u32, n: u16) -> () { @@ -289,9 +282,9 @@ mod test { /// Check that dividing the sample rate by k (integer) is the same as /// dropping a sample from each channel. fn divide_sample_rate(to: u32, k: u32, input: Vec, n: u16) -> () { + if k == 0 || n == 0 || to.checked_mul(k).is_none() { return; } let to = if to == 0 { return; } else { SampleRate(to) }; - let from = multiply_rate(to, k); - if k == 0 || n == 0 { return; } + let from = to * k; // Truncate the input, so it contains an integer number of frames. let input = { @@ -313,9 +306,9 @@ mod test { /// Check that, after multiplying the sample rate by k, every k-th /// sample in the output matches exactly with the input. fn multiply_sample_rate(from: u32, k: u32, input: Vec, n: u16) -> () { + if k == 0 || n == 0 || from.checked_mul(k).is_none() { return; } let from = if from == 0 { return; } else { SampleRate(from) }; - let to = multiply_rate(from, k); - if k == 0 || n == 0 { return; } + let to = from * k; // Truncate the input, so it contains an integer number of frames. let input = { diff --git a/src/lib.rs b/src/lib.rs index a17d99b..1fe68a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,14 +22,16 @@ //! use std::io::BufReader; //! use rodio::{Decoder, OutputStream, source::Source}; //! -//! // Get a output stream handle to the default physical sound device -//! let (_stream, stream_handle) = OutputStream::default().unwrap(); +//! // Get an output stream handle to the default physical sound device. +//! let stream_handle = rodio::OutputStreamBuilder::try_default_stream() +//! .expect("open default audio stream"); +//! let sink = rodio::Sink::connect_new(&stream_handle.mixer()); //! // Load a sound from a file, using a path relative to Cargo.toml //! let file = BufReader::new(File::open("examples/music.ogg").unwrap()); //! // Decode that sound file into a source //! let source = Decoder::new(file).unwrap(); //! // Play the sound directly on the device -//! stream_handle.play_raw(source.convert_samples()); +//! stream_handle.mixer().add(source.convert_samples()); //! //! // The sound plays in a separate audio thread, //! // so we need to keep the main thread alive while it's playing. @@ -54,9 +56,9 @@ //! use rodio::{Decoder, OutputStream, Sink}; //! use rodio::source::{SineWave, Source}; //! -//! // FIXME Update documentation after the builder is complete -//! let (_stream, stream_handle) = OutputStream::default().unwrap(); -//! let sink = Sink::connect_new(&stream_handle).unwrap(); +//! let stream_handle = rodio::OutputStreamBuilder::try_default_stream() +//! .expect("open default audio stream"); +//! let sink = rodio::Sink::connect_new(&stream_handle.mixer()); //! //! // Add a dummy source of the sake of the example. //! let source = SineWave::new(440.0).take_duration(Duration::from_secs_f32(0.25)).amplify(0.20); diff --git a/src/sink.rs b/src/sink.rs index 8f210a1..e7067cc 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -8,13 +8,12 @@ use cpal::FromSample; #[cfg(feature = "crossbeam-channel")] use crossbeam_channel::Receiver; -use crate::stream::{OutputStream, PlayError}; use crate::{queue, source::Done, Sample, Source}; use crate::dynamic_mixer::Mixer; /// Handle to a device that outputs sounds. /// -/// Dropping the `Sink` stops all sounds. You can use `detach` if you want the sounds to continue +/// Dropping the `Sink` stops all its sounds. You can use `detach` if you want the sounds to continue /// playing. pub struct Sink { queue_tx: Arc>, @@ -253,7 +252,7 @@ mod tests { #[test] fn test_pause_and_stop() { - let (sink, mut queue_rx) = Sink::new_idle(); + let (sink, mut queue_rx) = Sink::new(); // assert_eq!(queue_rx.next(), Some(0.0)); @@ -284,7 +283,7 @@ mod tests { #[test] fn test_stop_and_start() { - let (sink, mut queue_rx) = Sink::new_idle(); + let (sink, mut queue_rx) = Sink::new(); let v = vec![10i16, -10, 20, -20, 30, -30]; @@ -312,7 +311,7 @@ mod tests { #[test] fn test_volume() { - let (sink, mut queue_rx) = Sink::new_idle(); + let (sink, mut queue_rx) = Sink::new(); let v = vec![10i16, -10, 20, -20, 30, -30]; diff --git a/src/spatial_sink.rs b/src/spatial_sink.rs index 8d3a254..d2975ad 100644 --- a/src/spatial_sink.rs +++ b/src/spatial_sink.rs @@ -4,10 +4,9 @@ use std::time::Duration; use cpal::FromSample; -use crate::source::Spatial; -use crate::stream::{OutputStream, PlayError}; -use crate::{Sample, Sink, Source}; use crate::dynamic_mixer::Mixer; +use crate::source::Spatial; +use crate::{Sample, Sink, Source}; pub struct SpatialSink { sink: Sink, @@ -22,7 +21,7 @@ struct SoundPositions { impl SpatialSink { /// Builds a new `SpatialSink`. - pub fn new( + pub fn connect_new( stream: &Mixer, emitter_position: [f32; 3], left_ear: [f32; 3], @@ -69,10 +68,10 @@ impl SpatialSink { pos_lock.left_ear, pos_lock.right_ear, ) - .periodic_access(Duration::from_millis(10), move |i| { - let pos = positions.lock().unwrap(); - i.set_positions(pos.emitter_position, pos.left_ear, pos.right_ear); - }); + .periodic_access(Duration::from_millis(10), move |i| { + let pos = positions.lock().unwrap(); + i.set_positions(pos.emitter_position, pos.left_ear, pos.right_ear); + }); self.sink.append(source); } diff --git a/src/stream.rs b/src/stream.rs index 4df262f..6817e22 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -1,14 +1,13 @@ use std::io::{Read, Seek}; use std::marker::Sync; -use std::sync::{Arc, Weak}; +use std::sync::Arc; use std::{error, fmt}; -use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use cpal::{BufferSize, ChannelCount, FrameCount, PlayStreamError, Sample, SampleFormat, SampleRate, StreamConfig, SupportedBufferSize}; - use crate::decoder; -use crate::dynamic_mixer::{mixer, MixerSource, Mixer}; +use crate::dynamic_mixer::{mixer, Mixer, MixerSource}; use crate::sink::Sink; +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use cpal::{BufferSize, ChannelCount, FrameCount, Sample, SampleFormat, SampleRate, StreamConfig, SupportedBufferSize}; const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); @@ -16,7 +15,7 @@ const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); /// /// If this is dropped, playback will end, and the associated output stream will be disposed. pub struct OutputStream { - stream: cpal::Stream, + _stream: cpal::Stream, mixer: Arc>, } @@ -27,7 +26,7 @@ impl OutputStream { } #[derive(Copy, Clone, Debug)] -struct OutputStreamConfig { +pub struct OutputStreamConfig { pub channel_count: ChannelCount, pub sample_rate: SampleRate, pub buffer_size: BufferSize, @@ -98,8 +97,8 @@ impl OutputStreamBuilder { self.config = OutputStreamConfig { channel_count: config.channels(), sample_rate: config.sample_rate(), - // FIXME See how to best handle buffer_size preferences? - buffer_size: clamp_supported_buffer_size(config.buffer_size(), 512), + // In case of supported range limit buffer size to avoid unexpectedly long playback delays. + buffer_size: clamp_supported_buffer_size(config.buffer_size(), 1024), sample_format: config.sample_format(), ..self.config }; @@ -149,20 +148,22 @@ impl OutputStreamBuilder { .or_else(|original_err| { let mut devices = match cpal::default_host().output_devices() { Ok(devices) => devices, - Err(_) => return Err(original_err), // TODO Report the ignored error? + Err(_ignored) => return Err(original_err), }; devices - .find_map(|d| Self::from_device(d).and_then(|x| x.try_open_stream()).ok()) + .find_map(|d| Self::from_device(d) + .and_then(|x| x.try_open_stream()) + .ok()) .ok_or(original_err) }) } } fn clamp_supported_buffer_size(buffer_size: &SupportedBufferSize, preferred_size: FrameCount) -> BufferSize { - BufferSize::Fixed(match buffer_size { - SupportedBufferSize::Range { min, max } => preferred_size.clamp(*min, *max), - SupportedBufferSize::Unknown => preferred_size - }) + match buffer_size { + SupportedBufferSize::Range { min, max } => BufferSize::Fixed(preferred_size.clamp(*min, *max)), + SupportedBufferSize::Unknown => BufferSize::Default + } } /// Plays a sound once. Returns a `Sink` that can be used to control the sound. @@ -283,7 +284,7 @@ impl OutputStream { .map_err(|x| StreamError::from(x)) .and_then(|stream| { stream.play()?; - Ok(Self { stream, mixer: controller }) + Ok(Self { _stream: stream, mixer: controller }) }) } From 352c39987e7ec979f8f6c8e0102740d593806e84 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sun, 10 Nov 2024 15:06:33 +0400 Subject: [PATCH 15/34] Add example comments --- examples/basic.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/basic.rs b/examples/basic.rs index 787a90f..dc04138 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -10,6 +10,7 @@ fn main() { let mixer = stream_handle.mixer(); let beep1 = { + // Play a WAV file let file = std::fs::File::open("assets/beep.wav").unwrap(); let sink = rodio::play(&mixer, BufReader::new(file)).unwrap(); sink.set_volume(0.2); @@ -19,6 +20,7 @@ fn main() { thread::sleep(Duration::from_millis(1500)); { + // Generate sine wave let wave = SineWave::new(740.0) .amplify(0.2) .take_duration(Duration::from_secs(3)); @@ -28,6 +30,7 @@ fn main() { thread::sleep(Duration::from_millis(1500)); let beep3 = { + // Play an OGG file let file = std::fs::File::open("assets/beep3.ogg").unwrap(); let sink = rodio::play(&mixer, BufReader::new(file)).unwrap(); sink.set_volume(0.2); From c5850ab2ed35ea03bcfd23d24f74a0f81aaf2102 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sun, 10 Nov 2024 15:30:52 +0400 Subject: [PATCH 16/34] Sync Cargo.toml with master branch --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fb87025..60574ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ claxon = { version = "0.4.2", optional = true } hound = { version = "3.3.1", optional = true } lewton = { version = "0.10", optional = true } minimp3_fixed = { version = "0.5.4", optional = true} -symphonia = { version = "0.5.2", optional = true, default-features = false } +symphonia = { version = "0.5.4", optional = true, default-features = false } crossbeam-channel = { version = "0.5.8", optional = true } rand = { version = "0.8.5", features = ["small_rng"], optional = true } From 83c002500f33f007389096113985f53228c7ab5f Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sun, 10 Nov 2024 20:23:18 +0400 Subject: [PATCH 17/34] Update doc examples --- examples/automatic_gain_control.rs | 12 +++-- examples/basic.rs | 8 +-- examples/mix_multiple_sources.rs | 2 +- examples/noise_generator.rs | 27 +++++----- examples/seek_mp3.rs | 7 +-- examples/signal_generator.rs | 82 +++++++++++++----------------- src/dynamic_mixer.rs | 8 ++- src/sink.rs | 3 -- src/source/mod.rs | 32 +++++++----- src/source/speed.rs | 22 ++++---- src/spatial_sink.rs | 71 ++++---------------------- src/stream.rs | 48 +++++++++-------- 12 files changed, 136 insertions(+), 186 deletions(-) diff --git a/examples/automatic_gain_control.rs b/examples/automatic_gain_control.rs index 17603ac..9a57cac 100644 --- a/examples/automatic_gain_control.rs +++ b/examples/automatic_gain_control.rs @@ -8,8 +8,9 @@ use std::thread; use std::time::Duration; fn main() { - let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); - let sink = rodio::Sink::try_new(&handle).unwrap(); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream() + .expect("open default audio stream"); + let sink = rodio::Sink::connect_new(&stream_handle.mixer()); // Decode the sound file into a source let file = BufReader::new(File::open("assets/music.flac").unwrap()); @@ -24,14 +25,15 @@ fn main() { let agc_enabled = Arc::new(AtomicBool::new(true)); let agc_enabled_clone = agc_enabled.clone(); let controlled = agc_source.periodic_access(Duration::from_millis(5), move |agc_source| { + #[cfg(not(feature = "experimental"))] agc_source.set_enabled(agc_enabled_clone.load(Ordering::Relaxed)); }); // Add the source now equipped with automatic gain control and controlled via - // periodic_access to the sink for playback + // periodic_access to the sink for the playback. sink.append(controlled); - // after 5 seconds of playback disable automatic gain control using the + // After 5 seconds of playback disable automatic gain control using the // shared AtomicBool `agc_enabled`. You could do this from another part // of the program since `agc_enabled` is of type Arc which // is freely clone-able and move-able. @@ -41,6 +43,6 @@ fn main() { thread::sleep(Duration::from_secs(5)); agc_enabled.store(false, Ordering::Relaxed); - // Keep the program running until playback is complete + // Keep the program running until the playback is complete. sink.sleep_until_end(); } diff --git a/examples/basic.rs b/examples/basic.rs index dc04138..2a0e293 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -3,6 +3,8 @@ use rodio::Source; use std::io::BufReader; use std::thread; use std::time::Duration; +#[cfg(feature = "tracing")] +use tracing; fn main() { let stream_handle = rodio::OutputStreamBuilder::try_default_stream() @@ -10,7 +12,7 @@ fn main() { let mixer = stream_handle.mixer(); let beep1 = { - // Play a WAV file + // Play a WAV file. let file = std::fs::File::open("assets/beep.wav").unwrap(); let sink = rodio::play(&mixer, BufReader::new(file)).unwrap(); sink.set_volume(0.2); @@ -20,7 +22,7 @@ fn main() { thread::sleep(Duration::from_millis(1500)); { - // Generate sine wave + // Generate sine wave. let wave = SineWave::new(740.0) .amplify(0.2) .take_duration(Duration::from_secs(3)); @@ -30,7 +32,7 @@ fn main() { thread::sleep(Duration::from_millis(1500)); let beep3 = { - // Play an OGG file + // Play an OGG file. let file = std::fs::File::open("assets/beep3.ogg").unwrap(); let sink = rodio::play(&mixer, BufReader::new(file)).unwrap(); sink.set_volume(0.2); diff --git a/examples/mix_multiple_sources.rs b/examples/mix_multiple_sources.rs index 515cd73..92f1378 100644 --- a/examples/mix_multiple_sources.rs +++ b/examples/mix_multiple_sources.rs @@ -1,5 +1,5 @@ use rodio::source::{SineWave, Source}; -use rodio::{dynamic_mixer, OutputStream, Sink}; +use rodio::dynamic_mixer; use std::time::Duration; fn main() { diff --git a/examples/noise_generator.rs b/examples/noise_generator.rs index a72bcb7..e2ee301 100644 --- a/examples/noise_generator.rs +++ b/examples/noise_generator.rs @@ -1,34 +1,31 @@ //! Noise generator example. Use the "noise" feature to enable the noise generator sources. +use std::io::BufReader; + #[cfg(feature = "noise")] fn main() { use rodio::source::{pink, white, Source}; use std::thread; use std::time::Duration; - let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap(); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream() + .expect("open default audio stream"); let noise_duration = Duration::from_millis(1000); let interval_duration = Duration::from_millis(1500); - stream_handle - .play_raw( - white(cpal::SampleRate(48000)) - .amplify(0.1) - .take_duration(noise_duration), - ) - .unwrap(); + stream_handle.mixer() + .add(white(cpal::SampleRate(48000)) + .amplify(0.1) + .take_duration(noise_duration)); println!("Playing white noise"); thread::sleep(interval_duration); - stream_handle - .play_raw( - pink(cpal::SampleRate(48000)) - .amplify(0.1) - .take_duration(noise_duration), - ) - .unwrap(); + stream_handle.mixer() + .add(pink(cpal::SampleRate(48000)) + .amplify(0.1) + .take_duration(noise_duration)); println!("Playing pink noise"); thread::sleep(interval_duration); diff --git a/examples/seek_mp3.rs b/examples/seek_mp3.rs index 5042003..e79fa3a 100644 --- a/examples/seek_mp3.rs +++ b/examples/seek_mp3.rs @@ -2,8 +2,9 @@ use std::io::BufReader; use std::time::Duration; fn main() { - let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); - let sink = rodio::Sink::try_new(&handle).unwrap(); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream() + .expect("open default audio stream"); + let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.mp3").unwrap(); sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); @@ -16,7 +17,7 @@ fn main() { sink.sleep_until_end(); - // wont do anything since the sound has ended already + // This doesn't do anything since the sound has ended already. sink.try_seek(Duration::from_secs(5)).unwrap(); println!("seek example ended"); } diff --git a/examples/signal_generator.rs b/examples/signal_generator.rs index 08fd476..90330c1 100644 --- a/examples/signal_generator.rs +++ b/examples/signal_generator.rs @@ -5,79 +5,69 @@ fn main() { use std::thread; use std::time::Duration; - let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap(); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream() + .expect("open default audio stream"); let test_signal_duration = Duration::from_millis(1000); let interval_duration = Duration::from_millis(1500); + let sample_rate = cpal::SampleRate(48000); println!("Playing 1000 Hz tone"); - stream_handle - .play_raw( - SignalGenerator::new(cpal::SampleRate(48000), 1000.0, Function::Sine) - .amplify(0.1) - .take_duration(test_signal_duration), - ) - .unwrap(); + stream_handle.mixer().add( + SignalGenerator::new(sample_rate, 1000.0, Function::Sine) + .amplify(0.1) + .take_duration(test_signal_duration), + ); thread::sleep(interval_duration); println!("Playing 10,000 Hz tone"); - stream_handle - .play_raw( - SignalGenerator::new(cpal::SampleRate(48000), 10000.0, Function::Sine) - .amplify(0.1) - .take_duration(test_signal_duration), - ) - .unwrap(); + stream_handle.mixer().add( + SignalGenerator::new(sample_rate, 10000.0, Function::Sine) + .amplify(0.1) + .take_duration(test_signal_duration), + ); thread::sleep(interval_duration); println!("Playing 440 Hz Triangle Wave"); - stream_handle - .play_raw( - SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Triangle) - .amplify(0.1) - .take_duration(test_signal_duration), - ) - .unwrap(); + stream_handle.mixer().add( + SignalGenerator::new(sample_rate, 440.0, Function::Triangle) + .amplify(0.1) + .take_duration(test_signal_duration), + ); thread::sleep(interval_duration); println!("Playing 440 Hz Sawtooth Wave"); - stream_handle - .play_raw( - SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Sawtooth) - .amplify(0.1) - .take_duration(test_signal_duration), - ) - .unwrap(); + stream_handle.mixer().add( + SignalGenerator::new(sample_rate, 440.0, Function::Sawtooth) + .amplify(0.1) + .take_duration(test_signal_duration), + ); thread::sleep(interval_duration); println!("Playing 440 Hz Square Wave"); - stream_handle - .play_raw( - SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Square) - .amplify(0.1) - .take_duration(test_signal_duration), - ) - .unwrap(); + stream_handle.mixer().add( + SignalGenerator::new(sample_rate, 440.0, Function::Square) + .amplify(0.1) + .take_duration(test_signal_duration), + ); thread::sleep(interval_duration); println!("Playing 20-10000 Hz Sweep"); - stream_handle - .play_raw( - chirp( - cpal::SampleRate(48000), - 20.0, - 10000.0, - Duration::from_secs(1), - ) + stream_handle.mixer().add( + chirp( + sample_rate, + 20.0, + 10000.0, + Duration::from_secs(1), + ) .amplify(0.1) .take_duration(test_signal_duration), - ) - .unwrap(); + ); thread::sleep(interval_duration); } diff --git a/src/dynamic_mixer.rs b/src/dynamic_mixer.rs index 1577ccd..eddaa10 100644 --- a/src/dynamic_mixer.rs +++ b/src/dynamic_mixer.rs @@ -203,14 +203,12 @@ where #[cfg(test)] mod tests { - use std::cell::Cell; - use std::ops::Deref; - use std::sync::Arc; - use std::sync::atomic::{AtomicBool, AtomicU8}; - use std::sync::atomic::Ordering::{Acquire, Release}; use crate::buffer::SamplesBuffer; use crate::dynamic_mixer; use crate::source::Source; + use std::sync::atomic::Ordering::{Acquire, Release}; + use std::sync::atomic::AtomicU8; + use std::sync::Arc; #[test] pub fn fff() { diff --git a/src/sink.rs b/src/sink.rs index 47ff594..9dde4f5 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -1,6 +1,4 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -#[cfg(not(feature = "crossbeam-channel"))] -use std::sync::mpsc::Receiver; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -11,7 +9,6 @@ use crossbeam_channel::{Receiver, Sender}; use std::sync::mpsc::{Receiver, Sender}; use crate::source::SeekError; -use crate::stream::{OutputStreamHandle, PlayError}; use crate::{queue, source::Done, Sample, Source}; use crate::dynamic_mixer::Mixer; diff --git a/src/source/mod.rs b/src/source/mod.rs index 5a89763..d905e94 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -300,18 +300,26 @@ where /// /// # Example (Quick start) /// - /// ```rust + /// ```rust /// // Apply Automatic Gain Control to the source (AGC is on by default) + /// use rodio::source::{Source, SineWave}; + /// use rodio::Sink; + /// let source = SineWave::new(444.0); // An example. + /// let (sink, output) = Sink::new(); // An example. + /// /// let agc_source = source.automatic_gain_control(1.0, 4.0, 0.005, 5.0); /// /// // Get a handle to control the AGC's enabled state (optional) - /// let agc_control = agc_source.get_agc_control(); + /// #[cfg(feature = "experimental")] + /// { + /// let agc_control = agc_source.get_agc_control(); /// - /// // You can toggle AGC on/off at any time (optional) - /// agc_control.store(false, std::sync::atomic::Ordering::Relaxed); + /// // You can toggle AGC on/off at any time (optional) + /// agc_control.store(false, std::sync::atomic::Ordering::Relaxed); /// - /// // Add the AGC-controlled source to the sink - /// sink.append(agc_source); + /// // Add the AGC-controlled source to the sink + /// sink.append(agc_source); + /// } /// /// // Note: Using agc_control is optional. If you don't need to toggle AGC, /// // you can simply use the agc_source directly without getting agc_control. @@ -502,7 +510,7 @@ where /// /// This can get confusing when using [`get_pos()`](TrackPosition::get_pos) /// together with [`Source::try_seek()`] as the latter does take all - /// speedup's and delay's into account. Its recommended therefore to apply + /// speedup's and delay's into account. It's recommended therefore to apply /// track_position after speedup's and delay's. fn track_position(self) -> TrackPosition where @@ -558,7 +566,7 @@ where /// Attempts to seek to a given position in the current source. /// - /// As long as the duration of the source is known seek is guaranteed to saturate + /// As long as the duration of the source is known, seek is guaranteed to saturate /// at the end of the source. For example given a source that reports a total duration /// of 42 seconds calling `try_seek()` with 60 seconds as argument will seek to /// 42 seconds. @@ -581,8 +589,8 @@ where } // We might add decoders requiring new error types, without non_exhaustive -// this would break users builds -/// Occurs when try_seek fails because the underlying decoder has an error or +// this would break users' builds. +/// Occurs when `try_seek` fails because the underlying decoder has an error or /// does not support seeking. #[non_exhaustive] #[derive(Debug)] @@ -598,8 +606,8 @@ pub enum SeekError { #[cfg(feature = "wav")] /// The hound (wav) decoder ran into an issue HoundDecoder(std::io::Error), - // Prefer adding an enum variant to using this. Its meant for end users their - // own try_seek implementations + // Prefer adding an enum variant to using this. It's meant for end users their + // own `try_seek` implementations. /// Any other error probably in a custom Source Other(Box), } diff --git a/src/source/speed.rs b/src/source/speed.rs index 5287883..90a58e3 100644 --- a/src/source/speed.rs +++ b/src/source/speed.rs @@ -17,24 +17,26 @@ //!# use rodio::{Decoder, Sink, OutputStream, source::{Source, SineWave}}; //! //! // Get an output stream handle to the default physical sound device. -//! // Note that no sound will be played if _stream is dropped -//! let (_stream, stream_handle) = OutputStream::try_default().unwrap(); -//! // Load a sound from a file, using a path relative to Cargo.toml +//! // Note that no sound will be played if the _stream is dropped. +//! let stream_handle = rodio::OutputStreamBuilder::try_default_stream() +//! .expect("open default audio stream"); +//! // Load a sound from a file, using a path relative to `Cargo.toml` //! let file = BufReader::new(File::open("examples/music.ogg").unwrap()); //! // Decode that sound file into a source //! let source = Decoder::new(file).unwrap(); //! // Play the sound directly on the device 2x faster -//! stream_handle.play_raw(source.convert_samples().speed(2.0)); - +//! stream_handle.mixer().add(source.convert_samples().speed(2.0)); //! std::thread::sleep(std::time::Duration::from_secs(5)); //! ``` //! here is how you would do it using the sink -//! ``` +//!``` +//! use rodio::source::{Source, SineWave}; //! let source = SineWave::new(440.0) -//! .take_duration(Duration::from_secs_f32(20.25)) -//! .amplify(0.20); -//! -//! let sink = Sink::try_new(&stream_handle)?; +//! .take_duration(std::time::Duration::from_secs_f32(20.25)) +//! .amplify(0.20); +//! let stream_handle = rodio::OutputStreamBuilder::try_default_stream() +//! .expect("open default audio stream"); +//! let sink = rodio::Sink::connect_new(&stream_handle.mixer()); //! sink.set_speed(2.0); //! sink.append(source); //! std::thread::sleep(std::time::Duration::from_secs(5)); diff --git a/src/spatial_sink.rs b/src/spatial_sink.rs index d64767f..8f2e2e8 100644 --- a/src/spatial_sink.rs +++ b/src/spatial_sink.rs @@ -25,13 +25,13 @@ struct SoundPositions { impl SpatialSink { /// Builds a new `SpatialSink`. pub fn connect_new( - stream: &Mixer, + mixer: &Mixer, emitter_position: [f32; 3], left_ear: [f32; 3], right_ear: [f32; 3], ) -> SpatialSink { SpatialSink { - sink: Sink::connect_new(stream), + sink: Sink::connect_new(mixer), positions: Arc::new(Mutex::new(SoundPositions { emitter_position, left_ear, @@ -71,10 +71,10 @@ impl SpatialSink { pos_lock.left_ear, pos_lock.right_ear, ) - .periodic_access(Duration::from_millis(10), move |i| { - let pos = positions.lock().unwrap(); - i.set_positions(pos.emitter_position, pos.left_ear, pos.right_ear); - }); + .periodic_access(Duration::from_millis(10), move |i| { + let pos = positions.lock().unwrap(); + i.set_positions(pos.emitter_position, pos.left_ear, pos.right_ear); + }); self.sink.append(source); } @@ -96,20 +96,10 @@ impl SpatialSink { self.sink.set_volume(value); } - /// Changes the play speed of the sound. Does not adjust the samples, only the playback speed. + /// Gets the speed of the sound. /// - /// # Note: - /// 1. **Increasing the speed will increase the pitch by the same factor** - /// - If you set the speed to 0.5 this will halve the frequency of the sound - /// lowering its pitch. - /// - If you set the speed to 2 the frequency will double raising the - /// pitch of the sound. - /// 2. **Change in the speed affect the total duration inversely** - /// - If you set the speed to 0.5, the total duration will be twice as long. - /// - If you set the speed to 2 the total duration will be halve of what it - /// was. - /// - /// See [`Speed`] for details + /// The value `1.0` is the "normal" speed (unfiltered input). Any value other than `1.0` will + /// change the play speed of the sound. #[inline] pub fn speed(&self) -> f32 { self.sink.speed() @@ -148,14 +138,6 @@ impl SpatialSink { self.sink.is_paused() } - /// Removes all currently loaded `Source`s from the `SpatialSink` and pauses it. - /// - /// See `pause()` for information about pausing a `Sink`. - #[inline] - pub fn clear(&self) { - self.sink.clear(); - } - /// Stops the sink by emptying the queue. #[inline] pub fn stop(&self) { @@ -181,43 +163,8 @@ impl SpatialSink { } /// Returns the number of sounds currently in the queue. - #[allow(clippy::len_without_is_empty)] #[inline] pub fn len(&self) -> usize { self.sink.len() } - - /// Attempts to seek to a given position in the current source. - /// - /// This blocks between 0 and ~5 milliseconds. - /// - /// As long as the duration of the source is known seek is guaranteed to saturate - /// at the end of the source. For example given a source that reports a total duration - /// of 42 seconds calling `try_seek()` with 60 seconds as argument will seek to - /// 42 seconds. - /// - /// # Errors - /// This function will return [`SeekError::NotSupported`] if one of the underlying - /// sources does not support seeking. - /// - /// It will return an error if an implementation ran - /// into one during the seek. - /// - /// When seeking beyond the end of a source this - /// function might return an error if the duration of the source is not known. - pub fn try_seek(&self, pos: Duration) -> Result<(), SeekError> { - self.sink.try_seek(pos) - } - - /// Returns the position of the sound that's being played. - /// - /// This takes into account any speedup or delay applied. - /// - /// Example: if you apply a speedup of *2* to an mp3 decoder source and - /// [`get_pos()`](Sink::get_pos) returns *5s* then the position in the mp3 - /// recording is *10s* from its start. - #[inline] - pub fn get_pos(&self) -> Duration { - self.sink.get_pos() - } } diff --git a/src/stream.rs b/src/stream.rs index 213b4fc..65e8285 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -54,7 +54,8 @@ impl OutputStreamBuilder { pub fn from_device( device: cpal::Device, ) -> Result { - let default_config = device.default_output_config()?; + let default_config = device.default_output_config() + .map_err(StreamError::DefaultStreamConfigError)?; Ok(Self::default() .with_device(device) .with_supported_config(&default_config)) @@ -120,11 +121,10 @@ impl OutputStreamBuilder { OutputStream::open(device, &self.config) } - /// FIXME Update documentation. - /// Returns a new stream & handle using the given device and stream config. - /// - /// If the supplied `SupportedStreamConfig` is invalid for the device this function will - /// fail to create an output stream and instead return a `StreamError`. + /// Try to open a new output stream with the builder's current stream configuration. + /// Failing that attempt to open stream with other available configurations + /// provided by the device. + /// If all attempts to open stream have not succeeded returns initial error. pub fn try_open_stream(&self) -> Result { let device = self.device.as_ref().expect("output device specified"); OutputStream::open(device, &self.config).or_else(|err| { @@ -137,18 +137,23 @@ impl OutputStreamBuilder { }) } - /// FIXME Update docs - /// - /// Return a new stream & handle using the default output device. - /// - /// On failure will fall back to trying any non-default output devices. + /// Try to open a new output stream for the default output device with its default configuration. + /// Failing that attempt to open output stream with alternative configuration and/or non default + /// output devices. Returns stream for first tried configuration that succeeds. + /// If all attempts have not succeeded return the initial error. pub fn try_default_stream() -> Result { Self::from_default_device() .and_then(|x| x.open_stream()) .or_else(|original_err| { let mut devices = match cpal::default_host().output_devices() { Ok(devices) => devices, - Err(_ignored) => return Err(original_err), + Err(err) => { + #[cfg(feature = "tracing")] + tracing::error!("error getting list of output devices: {err}"); + #[cfg(not(feature = "tracing"))] + eprintln!("error getting list of output devices: {err}"); + return Err(original_err) + }, }; devices .find_map(|d| Self::from_device(d) @@ -226,10 +231,10 @@ pub enum StreamError { /// Could not start playing the stream, see [cpal::PlayStreamError] for /// details. PlayStreamError(cpal::PlayStreamError), - /// Failed to get the stream config for device the given device. See - /// [cpal::DefaultStreamConfigError] for details + /// Failed to get the stream config for the given device. See + /// [cpal::DefaultStreamConfigError] for details. DefaultStreamConfigError(cpal::DefaultStreamConfigError), - /// Error opening stream with OS. See [cpal::BuildStreamError] for details + /// Error opening stream with OS. See [cpal::BuildStreamError] for details. BuildStreamError(cpal::BuildStreamError), /// Could not list supported stream configs for device. Maybe it /// disconnected, for details see: [cpal::SupportedStreamConfigsError]. @@ -266,9 +271,9 @@ impl OutputStream { pub fn open(device: &cpal::Device, config: &OutputStreamConfig) -> Result { let (controller, source) = mixer(config.channel_count, config.sample_rate.0); Self::init_stream(device, config, source) - .map_err(|x| StreamError::from(x)) + .map_err(StreamError::BuildStreamError) .and_then(|stream| { - stream.play()?; + stream.play().map_err(StreamError::PlayStreamError)?; Ok(Self { _stream: stream, mixer: controller }) }) } @@ -280,9 +285,9 @@ impl OutputStream { ) -> Result { let error_callback = |err| { #[cfg(feature = "tracing")] - tracing::error!("an error occurred on output stream: {err}"); + tracing::error!("error initializing output stream: {err}"); #[cfg(not(feature = "tracing"))] - eprintln!("an error occurred on output stream: {err}"); + eprintln!("error initializing output stream: {err}"); }; let sample_format = config.sample_format; let config = config.into(); @@ -403,8 +408,9 @@ impl OutputStream { fn supported_output_configs( device: &cpal::Device, ) -> Result, StreamError> { - let mut supported: Vec<_> = device.supported_output_configs()?.collect(); - /// FIXME .map_err(StreamError::SupportedStreamConfigsError)? + let mut supported: Vec<_> = device.supported_output_configs() + .map_err(StreamError::SupportedStreamConfigsError)? + .collect(); supported.sort_by(|a, b| b.cmp_default_heuristics(a)); Ok(supported.into_iter().flat_map(|sf| { From 179d41a53e945ea0fa145ece80bf571487de9252 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Thu, 14 Nov 2024 03:08:50 +0400 Subject: [PATCH 18/34] Reformat code --- examples/automatic_gain_control.rs | 4 +- examples/basic.rs | 4 +- examples/mix_multiple_sources.rs | 6 +-- examples/music_flac.rs | 4 +- examples/music_m4a.rs | 4 +- examples/music_mp3.rs | 4 +- examples/music_ogg.rs | 4 +- examples/music_wav.rs | 4 +- examples/noise_generator.rs | 18 +++---- examples/reverb.rs | 4 +- examples/seek_mp3.rs | 4 +- examples/signal_generator.rs | 11 ++--- examples/spatial.rs | 11 +++-- examples/stereo.rs | 16 ++++--- src/conversions/sample_rate.rs | 3 +- src/dynamic_mixer.rs | 13 ++---- src/lib.rs | 2 +- src/sink.rs | 2 +- src/source/mod.rs | 4 +- src/source/uniform.rs | 3 +- src/stream.rs | 75 ++++++++++++++++++------------ 21 files changed, 109 insertions(+), 91 deletions(-) diff --git a/examples/automatic_gain_control.rs b/examples/automatic_gain_control.rs index 9a57cac..9a8b87e 100644 --- a/examples/automatic_gain_control.rs +++ b/examples/automatic_gain_control.rs @@ -8,8 +8,8 @@ use std::thread; use std::time::Duration; fn main() { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream() - .expect("open default audio stream"); + let stream_handle = + rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); let sink = rodio::Sink::connect_new(&stream_handle.mixer()); // Decode the sound file into a source diff --git a/examples/basic.rs b/examples/basic.rs index 2a0e293..e5c72c2 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -7,8 +7,8 @@ use std::time::Duration; use tracing; fn main() { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream() - .expect("open default audio stream"); + let stream_handle = + rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); let mixer = stream_handle.mixer(); let beep1 = { diff --git a/examples/mix_multiple_sources.rs b/examples/mix_multiple_sources.rs index 92f1378..9b145f9 100644 --- a/examples/mix_multiple_sources.rs +++ b/examples/mix_multiple_sources.rs @@ -1,12 +1,12 @@ -use rodio::source::{SineWave, Source}; use rodio::dynamic_mixer; +use rodio::source::{SineWave, Source}; use std::time::Duration; fn main() { // Construct a dynamic controller and mixer, stream_handle, and sink. let (controller, mixer) = dynamic_mixer::mixer::(2, 44_100); - let stream_handle = rodio::OutputStreamBuilder::try_default_stream() - .expect("open default audio stream"); + let stream_handle = + rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); let sink = rodio::Sink::connect_new(&stream_handle.mixer()); // Create four unique sources. The frequencies used here correspond diff --git a/examples/music_flac.rs b/examples/music_flac.rs index 3ae112a..da46581 100644 --- a/examples/music_flac.rs +++ b/examples/music_flac.rs @@ -1,8 +1,8 @@ use std::io::BufReader; fn main() { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream() - .expect("open default audio stream"); + let stream_handle = + rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.flac").unwrap(); diff --git a/examples/music_m4a.rs b/examples/music_m4a.rs index a94641b..edae3c7 100644 --- a/examples/music_m4a.rs +++ b/examples/music_m4a.rs @@ -1,8 +1,8 @@ use std::io::BufReader; fn main() { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream() - .expect("open default audio stream"); + let stream_handle = + rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.m4a").unwrap(); diff --git a/examples/music_mp3.rs b/examples/music_mp3.rs index dc8e9cd..4919a76 100644 --- a/examples/music_mp3.rs +++ b/examples/music_mp3.rs @@ -1,8 +1,8 @@ use std::io::BufReader; fn main() { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream() - .expect("open default audio stream"); + let stream_handle = + rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.mp3").unwrap(); diff --git a/examples/music_ogg.rs b/examples/music_ogg.rs index 562dfcb..d88d1a5 100644 --- a/examples/music_ogg.rs +++ b/examples/music_ogg.rs @@ -1,8 +1,8 @@ use std::io::BufReader; fn main() { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream() - .expect("open default audio stream"); + let stream_handle = + rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.ogg").unwrap(); diff --git a/examples/music_wav.rs b/examples/music_wav.rs index 8e41598..5dce303 100644 --- a/examples/music_wav.rs +++ b/examples/music_wav.rs @@ -1,8 +1,8 @@ use std::io::BufReader; fn main() { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream() - .expect("open default audio stream"); + let stream_handle = + rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.wav").unwrap(); diff --git a/examples/noise_generator.rs b/examples/noise_generator.rs index e2ee301..867200d 100644 --- a/examples/noise_generator.rs +++ b/examples/noise_generator.rs @@ -8,24 +8,26 @@ fn main() { use std::thread; use std::time::Duration; - let stream_handle = rodio::OutputStreamBuilder::try_default_stream() - .expect("open default audio stream"); + let stream_handle = + rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); let noise_duration = Duration::from_millis(1000); let interval_duration = Duration::from_millis(1500); - stream_handle.mixer() - .add(white(cpal::SampleRate(48000)) + stream_handle.mixer().add( + white(cpal::SampleRate(48000)) .amplify(0.1) - .take_duration(noise_duration)); + .take_duration(noise_duration), + ); println!("Playing white noise"); thread::sleep(interval_duration); - stream_handle.mixer() - .add(pink(cpal::SampleRate(48000)) + stream_handle.mixer().add( + pink(cpal::SampleRate(48000)) .amplify(0.1) - .take_duration(noise_duration)); + .take_duration(noise_duration), + ); println!("Playing pink noise"); thread::sleep(interval_duration); diff --git a/examples/reverb.rs b/examples/reverb.rs index daca36a..46e6c2a 100644 --- a/examples/reverb.rs +++ b/examples/reverb.rs @@ -3,8 +3,8 @@ use std::io::BufReader; use std::time::Duration; fn main() { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream() - .expect("open default audio stream"); + let stream_handle = + rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.ogg").unwrap(); diff --git a/examples/seek_mp3.rs b/examples/seek_mp3.rs index e79fa3a..4955ddf 100644 --- a/examples/seek_mp3.rs +++ b/examples/seek_mp3.rs @@ -2,8 +2,8 @@ use std::io::BufReader; use std::time::Duration; fn main() { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream() - .expect("open default audio stream"); + let stream_handle = + rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.mp3").unwrap(); diff --git a/examples/signal_generator.rs b/examples/signal_generator.rs index 90330c1..89c93fb 100644 --- a/examples/signal_generator.rs +++ b/examples/signal_generator.rs @@ -5,8 +5,8 @@ fn main() { use std::thread; use std::time::Duration; - let stream_handle = rodio::OutputStreamBuilder::try_default_stream() - .expect("open default audio stream"); + let stream_handle = + rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); let test_signal_duration = Duration::from_millis(1000); let interval_duration = Duration::from_millis(1500); @@ -59,12 +59,7 @@ fn main() { println!("Playing 20-10000 Hz Sweep"); stream_handle.mixer().add( - chirp( - sample_rate, - 20.0, - 10000.0, - Duration::from_secs(1), - ) + chirp(sample_rate, 20.0, 10000.0, Duration::from_secs(1)) .amplify(0.1) .take_duration(test_signal_duration), ); diff --git a/examples/spatial.rs b/examples/spatial.rs index aae2cad..bab8b22 100644 --- a/examples/spatial.rs +++ b/examples/spatial.rs @@ -18,11 +18,16 @@ fn main() { let total_duration = iter_duration * 2 * repeats; - let stream_handle = rodio::OutputStreamBuilder::try_default_stream() - .expect("open default audio stream"); + let stream_handle = + rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); let mut positions = ([0., 0., 0.], [-1., 0., 0.], [1., 0., 0.]); - let sink = rodio::SpatialSink::connect_new(&stream_handle.mixer(), positions.0, positions.1, positions.2); + let sink = rodio::SpatialSink::connect_new( + &stream_handle.mixer(), + positions.0, + positions.1, + positions.2, + ); let file = std::fs::File::open("assets/music.ogg").unwrap(); let source = rodio::Decoder::new(BufReader::new(file)) diff --git a/examples/stereo.rs b/examples/stereo.rs index f6b3429..15f4266 100644 --- a/examples/stereo.rs +++ b/examples/stereo.rs @@ -1,16 +1,18 @@ //! Plays a tone alternating between right and left ears, with right being first. -use std::io::BufReader; use rodio::Source; +use std::io::BufReader; fn main() { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream() - .expect("open default audio stream"); - let sink = rodio::Sink::connect_new(&stream_handle.mixer()); + let stream_handle = + rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); + let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/RL.ogg").unwrap(); - sink.append(rodio::Decoder::new(BufReader::new(file)) - .unwrap() - .amplify(0.2)); + sink.append( + rodio::Decoder::new(BufReader::new(file)) + .unwrap() + .amplify(0.2), + ); sink.sleep_until_end(); } diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index 8fe58ed..2fd26c5 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -249,7 +249,8 @@ impl ExactSizeIterator for SampleRateConverter where I: ExactSizeIterator, I::Item: Sample + Clone, -{} +{ +} #[cfg(test)] mod test { diff --git a/src/dynamic_mixer.rs b/src/dynamic_mixer.rs index eddaa10..450152f 100644 --- a/src/dynamic_mixer.rs +++ b/src/dynamic_mixer.rs @@ -12,10 +12,7 @@ use crate::Sample; /// added to the mixer will be converted to these values. /// /// After creating a mixer, you can add new sounds with the controller. -pub fn mixer( - channels: u16, - sample_rate: u32, -) -> (Arc>, MixerSource) +pub fn mixer(channels: u16, sample_rate: u32) -> (Arc>, MixerSource) where S: Sample + Send + 'static, { @@ -38,7 +35,7 @@ where /// The input of the mixer. pub struct Mixer { has_pending: AtomicBool, - pending_sources: Mutex + Send>>>, + pending_sources: Mutex + Send>>>, channels: u16, sample_rate: u32, } @@ -51,7 +48,7 @@ where #[inline] pub fn add(&self, source: T) where - T: Source + Send + 'static, + T: Source + Send + 'static, { let uniform_source = UniformSourceIterator::new(source, self.channels, self.sample_rate); let mut pending = self.pending_sources.lock().unwrap(); @@ -63,7 +60,7 @@ where /// The output of the mixer. Implements `Source`. pub struct MixerSource { // The current iterator that produces samples. - current_sources: Vec + Send>>, + current_sources: Vec + Send>>, // The pending sounds. input: Arc>, @@ -206,8 +203,8 @@ mod tests { use crate::buffer::SamplesBuffer; use crate::dynamic_mixer; use crate::source::Source; - use std::sync::atomic::Ordering::{Acquire, Release}; use std::sync::atomic::AtomicU8; + use std::sync::atomic::Ordering::{Acquire, Release}; use std::sync::Arc; #[test] diff --git a/src/lib.rs b/src/lib.rs index d8dc7c6..9c2428c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -155,4 +155,4 @@ pub use crate::decoder::Decoder; pub use crate::sink::Sink; pub use crate::source::Source; pub use crate::spatial_sink::SpatialSink; -pub use crate::stream::{OutputStream, OutputStreamBuilder, play, PlayError, StreamError}; +pub use crate::stream::{play, OutputStream, OutputStreamBuilder, PlayError, StreamError}; diff --git a/src/sink.rs b/src/sink.rs index 9dde4f5..7a0354d 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -8,9 +8,9 @@ use crossbeam_channel::{Receiver, Sender}; #[cfg(not(feature = "crossbeam-channel"))] use std::sync::mpsc::{Receiver, Sender}; +use crate::dynamic_mixer::Mixer; use crate::source::SeekError; use crate::{queue, source::Done, Sample, Source}; -use crate::dynamic_mixer::Mixer; /// Handle to a device that outputs sounds. /// diff --git a/src/source/mod.rs b/src/source/mod.rs index d905e94..33ca934 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -300,13 +300,13 @@ where /// /// # Example (Quick start) /// - /// ```rust + /// ```rust /// // Apply Automatic Gain Control to the source (AGC is on by default) /// use rodio::source::{Source, SineWave}; /// use rodio::Sink; /// let source = SineWave::new(444.0); // An example. /// let (sink, output) = Sink::new(); // An example. - /// + /// /// let agc_source = source.automatic_gain_control(1.0, 4.0, 0.005, 5.0); /// /// // Get a handle to control the AGC's enabled state (optional) diff --git a/src/source/uniform.rs b/src/source/uniform.rs index 287f581..7d25a8b 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -102,8 +102,7 @@ where .into_inner() .iter; - let mut input = - Self::bootstrap(input, self.target_channels, self.target_sample_rate); + let mut input = Self::bootstrap(input, self.target_channels, self.target_sample_rate); let value = input.next(); self.inner = Some(input); diff --git a/src/stream.rs b/src/stream.rs index 65e8285..3d1ae73 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -7,7 +7,10 @@ use crate::decoder; use crate::dynamic_mixer::{mixer, Mixer, MixerSource}; use crate::sink::Sink; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use cpal::{BufferSize, ChannelCount, FrameCount, Sample, SampleFormat, SampleRate, StreamConfig, SupportedBufferSize}; +use cpal::{ + BufferSize, ChannelCount, FrameCount, Sample, SampleFormat, SampleRate, StreamConfig, + SupportedBufferSize, +}; const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); @@ -51,10 +54,9 @@ impl Default for OutputStreamConfig { } impl OutputStreamBuilder { - pub fn from_device( - device: cpal::Device, - ) -> Result { - let default_config = device.default_output_config() + pub fn from_device(device: cpal::Device) -> Result { + let default_config = device + .default_output_config() .map_err(StreamError::DefaultStreamConfigError)?; Ok(Self::default() .with_device(device) @@ -94,7 +96,10 @@ impl OutputStreamBuilder { self } - pub fn with_supported_config(mut self, config: &cpal::SupportedStreamConfig) -> OutputStreamBuilder { + pub fn with_supported_config( + mut self, + config: &cpal::SupportedStreamConfig, + ) -> OutputStreamBuilder { self.config = OutputStreamConfig { channel_count: config.channels(), sample_rate: config.sample_rate(), @@ -129,7 +134,10 @@ impl OutputStreamBuilder { let device = self.device.as_ref().expect("output device specified"); OutputStream::open(device, &self.config).or_else(|err| { for supported_config in supported_output_configs(device)? { - if let Ok(handle) = Self::default().with_supported_config(&supported_config).open_stream() { + if let Ok(handle) = Self::default() + .with_supported_config(&supported_config) + .open_stream() + { return Ok(handle); } } @@ -152,22 +160,25 @@ impl OutputStreamBuilder { tracing::error!("error getting list of output devices: {err}"); #[cfg(not(feature = "tracing"))] eprintln!("error getting list of output devices: {err}"); - return Err(original_err) - }, + return Err(original_err); + } }; devices - .find_map(|d| Self::from_device(d) - .and_then(|x| x.try_open_stream()) - .ok()) + .find_map(|d| Self::from_device(d).and_then(|x| x.try_open_stream()).ok()) .ok_or(original_err) }) } } -fn clamp_supported_buffer_size(buffer_size: &SupportedBufferSize, preferred_size: FrameCount) -> BufferSize { +fn clamp_supported_buffer_size( + buffer_size: &SupportedBufferSize, + preferred_size: FrameCount, +) -> BufferSize { match buffer_size { - SupportedBufferSize::Range { min, max } => BufferSize::Fixed(preferred_size.clamp(*min, *max)), - SupportedBufferSize::Unknown => BufferSize::Default + SupportedBufferSize::Range { min, max } => { + BufferSize::Fixed(preferred_size.clamp(*min, *max)) + } + SupportedBufferSize::Unknown => BufferSize::Default, } } @@ -268,13 +279,19 @@ impl error::Error for StreamError { } impl OutputStream { - pub fn open(device: &cpal::Device, config: &OutputStreamConfig) -> Result { + pub fn open( + device: &cpal::Device, + config: &OutputStreamConfig, + ) -> Result { let (controller, source) = mixer(config.channel_count, config.sample_rate.0); Self::init_stream(device, config, source) .map_err(StreamError::BuildStreamError) .and_then(|stream| { stream.play().map_err(StreamError::PlayStreamError)?; - Ok(Self { _stream: stream, mixer: controller }) + Ok(Self { + _stream: stream, + mixer: controller, + }) }) } @@ -292,16 +309,15 @@ impl OutputStream { let sample_format = config.sample_format; let config = config.into(); match sample_format { - cpal::SampleFormat::F32 => - device.build_output_stream::( - &config, - move |data, _| { - data.iter_mut() - .for_each(|d| *d = samples.next().unwrap_or(0f32)) - }, - error_callback, - None, - ), + cpal::SampleFormat::F32 => device.build_output_stream::( + &config, + move |data, _| { + data.iter_mut() + .for_each(|d| *d = samples.next().unwrap_or(0f32)) + }, + error_callback, + None, + ), cpal::SampleFormat::F64 => device.build_output_stream::( &config, move |data, _| { @@ -407,8 +423,9 @@ impl OutputStream { /// Return all formats supported by the device. fn supported_output_configs( device: &cpal::Device, -) -> Result, StreamError> { - let mut supported: Vec<_> = device.supported_output_configs() +) -> Result, StreamError> { + let mut supported: Vec<_> = device + .supported_output_configs() .map_err(StreamError::SupportedStreamConfigsError)? .collect(); supported.sort_by(|a, b| b.cmp_default_heuristics(a)); From af925663217ff750e4cfebc55e8822dc2fedc909 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Thu, 14 Nov 2024 23:03:59 +0400 Subject: [PATCH 19/34] Cleanup Code reformatted. Removed experimental code. --- examples/noise_generator.rs | 2 -- src/dynamic_mixer.rs | 8 -------- 2 files changed, 10 deletions(-) diff --git a/examples/noise_generator.rs b/examples/noise_generator.rs index 867200d..0bd1ee5 100644 --- a/examples/noise_generator.rs +++ b/examples/noise_generator.rs @@ -1,7 +1,5 @@ //! Noise generator example. Use the "noise" feature to enable the noise generator sources. -use std::io::BufReader; - #[cfg(feature = "noise")] fn main() { use rodio::source::{pink, white, Source}; diff --git a/src/dynamic_mixer.rs b/src/dynamic_mixer.rs index 450152f..14c488c 100644 --- a/src/dynamic_mixer.rs +++ b/src/dynamic_mixer.rs @@ -207,14 +207,6 @@ mod tests { use std::sync::atomic::Ordering::{Acquire, Release}; use std::sync::Arc; - #[test] - pub fn fff() { - let r = Arc::new(AtomicU8::new(12)); - let c = r.clone(); - r.store(44, Release); - assert_eq!(r.load(Acquire), c.load(Acquire)); - } - #[test] fn basic() { let (tx, mut rx) = dynamic_mixer::mixer(1, 48000); From ebc673e315edff3b7c112d917d84a8b4cc392b7d Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Fri, 15 Nov 2024 00:19:43 +0400 Subject: [PATCH 20/34] Cleanup, remove unnecessay imports --- src/dynamic_mixer.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/dynamic_mixer.rs b/src/dynamic_mixer.rs index 14c488c..da5a493 100644 --- a/src/dynamic_mixer.rs +++ b/src/dynamic_mixer.rs @@ -203,9 +203,6 @@ mod tests { use crate::buffer::SamplesBuffer; use crate::dynamic_mixer; use crate::source::Source; - use std::sync::atomic::AtomicU8; - use std::sync::atomic::Ordering::{Acquire, Release}; - use std::sync::Arc; #[test] fn basic() { From b720273847ba180ddc8ba576f82bef1fe995f7c7 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Fri, 15 Nov 2024 03:11:48 +0400 Subject: [PATCH 21/34] Revert extra changes for merge request --- src/conversions/sample_rate.rs | 14 +++++--- src/dynamic_mixer.rs | 41 +++++++++++++++-------- src/source/mod.rs | 1 + src/source/uniform.rs | 7 ++-- src/spatial_sink.rs | 59 ++++++++++++++++++++++++++++++++-- src/stream.rs | 2 +- 6 files changed, 99 insertions(+), 25 deletions(-) diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index 2fd26c5..36aa5a5 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -259,6 +259,12 @@ mod test { use cpal::SampleRate; use quickcheck::quickcheck; + // TODO: Remove once cpal 0.12.2 is released and the dependency is updated + // (cpal#483 implemented ops::Mul on SampleRate) + const fn multiply_rate(r: SampleRate, k: u32) -> SampleRate { + SampleRate(k * r.0) + } + quickcheck! { /// Check that resampling an empty input produces no output. fn empty(from: u32, to: u32, n: u16) -> () { @@ -289,9 +295,9 @@ mod test { /// Check that dividing the sample rate by k (integer) is the same as /// dropping a sample from each channel. fn divide_sample_rate(to: u32, k: u32, input: Vec, n: u16) -> () { - if k == 0 || n == 0 || to.checked_mul(k).is_none() { return; } let to = if to == 0 { return; } else { SampleRate(to) }; - let from = to * k; + let from = multiply_rate(to, k); + if k == 0 || n == 0 { return; } // Truncate the input, so it contains an integer number of frames. let input = { @@ -313,9 +319,9 @@ mod test { /// Check that, after multiplying the sample rate by k, every k-th /// sample in the output matches exactly with the input. fn multiply_sample_rate(from: u32, k: u32, input: Vec, n: u16) -> () { - if k == 0 || n == 0 || from.checked_mul(k).is_none() { return; } let from = if from == 0 { return; } else { SampleRate(from) }; - let to = from * k; + let to = multiply_rate(from, k); + if k == 0 || n == 0 { return; } // Truncate the input, so it contains an integer number of frames. let input = { diff --git a/src/dynamic_mixer.rs b/src/dynamic_mixer.rs index da5a493..61c13ee 100644 --- a/src/dynamic_mixer.rs +++ b/src/dynamic_mixer.rs @@ -1,4 +1,5 @@ //! Mixer that plays multiple sounds at the same time. + use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -27,6 +28,8 @@ where current_sources: Vec::with_capacity(16), input: input.clone(), sample_count: 0, + still_pending: vec![], + still_current: vec![], }; (input, output) @@ -51,8 +54,10 @@ where T: Source + Send + 'static, { let uniform_source = UniformSourceIterator::new(source, self.channels, self.sample_rate); - let mut pending = self.pending_sources.lock().unwrap(); - pending.push(Box::new(uniform_source) as Box<_>); + self.pending_sources + .lock() + .unwrap() + .push(Box::new(uniform_source) as Box<_>); self.has_pending.store(true, Ordering::SeqCst); // TODO: can we relax this ordering? } } @@ -67,6 +72,12 @@ pub struct MixerSource { // The number of samples produced so far. sample_count: usize, + + // A temporary vec used in start_pending_sources. + still_pending: Vec + Send>>, + + // A temporary vec used in sum_current_sources. + still_current: Vec + Send>>, } impl Source for MixerSource @@ -168,16 +179,18 @@ where // in-step with the modulo of the samples produced so far. Otherwise, the // sound will play on the wrong channels, e.g. left / right will be reversed. fn start_pending_sources(&mut self) { - let mut pending = self.input.pending_sources.lock().unwrap(); - let mut i = 0; - while i < pending.len() { - let in_step = self.sample_count % pending[i].channels() as usize == 0; + let mut pending = self.input.pending_sources.lock().unwrap(); // TODO: relax ordering? + + for source in pending.drain(..) { + let in_step = self.sample_count % source.channels() as usize == 0; + if in_step { - self.current_sources.push(pending.swap_remove(i)); + self.current_sources.push(source); } else { - i += 1; + self.still_pending.push(source); } } + std::mem::swap(&mut self.still_pending, &mut pending); let has_pending = !pending.is_empty(); self.input.has_pending.store(has_pending, Ordering::SeqCst); // TODO: relax ordering? @@ -185,15 +198,15 @@ where fn sum_current_sources(&mut self) -> S { let mut sum = S::zero_value(); - let mut i = 0; - while i < self.current_sources.len() { - if let Some(value) = self.current_sources[i].next() { + + for mut source in self.current_sources.drain(..) { + if let Some(value) = source.next() { sum = sum.saturating_add(value); - i += 1; - } else { - self.current_sources.swap_remove(i); + self.still_current.push(source); } } + std::mem::swap(&mut self.still_current, &mut self.current_sources); + sum } } diff --git a/src/source/mod.rs b/src/source/mod.rs index 33ca934..4802e53 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -145,6 +145,7 @@ pub use self::noise::{pink, white, PinkNoise, WhiteNoise}; /// In order to properly handle this situation, the `current_frame_len()` method should return /// the number of samples that remain in the iterator before the samples rate and number of /// channels can potentially change. +/// pub trait Source: Iterator where Self::Item: Sample, diff --git a/src/source/uniform.rs b/src/source/uniform.rs index 7d25a8b..b9d146d 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -41,9 +41,9 @@ where target_sample_rate: u32, ) -> UniformSourceIterator { let total_duration = input.total_duration(); - let input = Self::bootstrap(input, target_channels, target_sample_rate); + let input = UniformSourceIterator::bootstrap(input, target_channels, target_sample_rate); - Self { + UniformSourceIterator { inner: Some(input), target_channels, target_sample_rate, @@ -102,7 +102,8 @@ where .into_inner() .iter; - let mut input = Self::bootstrap(input, self.target_channels, self.target_sample_rate); + let mut input = + UniformSourceIterator::bootstrap(input, self.target_channels, self.target_sample_rate); let value = input.next(); self.inner = Some(input); diff --git a/src/spatial_sink.rs b/src/spatial_sink.rs index 8f2e2e8..bddd679 100644 --- a/src/spatial_sink.rs +++ b/src/spatial_sink.rs @@ -96,10 +96,20 @@ impl SpatialSink { self.sink.set_volume(value); } - /// Gets the speed of the sound. + /// Changes the play speed of the sound. Does not adjust the samples, only the playback speed. /// - /// The value `1.0` is the "normal" speed (unfiltered input). Any value other than `1.0` will - /// change the play speed of the sound. + /// # Note: + /// 1. **Increasing the speed will increase the pitch by the same factor** + /// - If you set the speed to 0.5 this will halve the frequency of the sound + /// lowering its pitch. + /// - If you set the speed to 2 the frequency will double raising the + /// pitch of the sound. + /// 2. **Change in the speed affect the total duration inversely** + /// - If you set the speed to 0.5, the total duration will be twice as long. + /// - If you set the speed to 2 the total duration will be halve of what it + /// was. + /// + /// See [`Speed`] for details #[inline] pub fn speed(&self) -> f32 { self.sink.speed() @@ -138,6 +148,14 @@ impl SpatialSink { self.sink.is_paused() } + /// Removes all currently loaded `Source`s from the `SpatialSink` and pauses it. + /// + /// See `pause()` for information about pausing a `Sink`. + #[inline] + pub fn clear(&self) { + self.sink.clear(); + } + /// Stops the sink by emptying the queue. #[inline] pub fn stop(&self) { @@ -163,8 +181,43 @@ impl SpatialSink { } /// Returns the number of sounds currently in the queue. + #[allow(clippy::len_without_is_empty)] #[inline] pub fn len(&self) -> usize { self.sink.len() } + + /// Attempts to seek to a given position in the current source. + /// + /// This blocks between 0 and ~5 milliseconds. + /// + /// As long as the duration of the source is known seek is guaranteed to saturate + /// at the end of the source. For example given a source that reports a total duration + /// of 42 seconds calling `try_seek()` with 60 seconds as argument will seek to + /// 42 seconds. + /// + /// # Errors + /// This function will return [`SeekError::NotSupported`] if one of the underlying + /// sources does not support seeking. + /// + /// It will return an error if an implementation ran + /// into one during the seek. + /// + /// When seeking beyond the end of a source this + /// function might return an error if the duration of the source is not known. + pub fn try_seek(&self, pos: Duration) -> Result<(), SeekError> { + self.sink.try_seek(pos) + } + + /// Returns the position of the sound that's being played. + /// + /// This takes into account any speedup or delay applied. + /// + /// Example: if you apply a speedup of *2* to an mp3 decoder source and + /// [`get_pos()`](Sink::get_pos) returns *5s* then the position in the mp3 + /// recording is *10s* from its start. + #[inline] + pub fn get_pos(&self) -> Duration { + self.sink.get_pos() + } } diff --git a/src/stream.rs b/src/stream.rs index 3d1ae73..9437930 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -18,8 +18,8 @@ const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); /// /// If this is dropped, playback will end, and the associated output stream will be disposed. pub struct OutputStream { - _stream: cpal::Stream, mixer: Arc>, + _stream: cpal::Stream, } impl OutputStream { From 689670d66c4a4004ca9abfbc19394b28600e6691 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Fri, 15 Nov 2024 19:57:36 +0400 Subject: [PATCH 22/34] UPdate documentation Add an example for rodio::play() Remove experimental API from rustdocs. --- src/lib.rs | 20 ++++++++++++++++++++ src/source/mod.rs | 15 ++------------- src/spatial_sink.rs | 2 +- src/stream.rs | 4 ++-- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9c2428c..407785b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,26 @@ //! // so we need to keep the main thread alive while it's playing. //! std::thread::sleep(std::time::Duration::from_secs(5)); //! ``` +//! You can use [rodio::play()] to do the above +//! ```no_run +//! use std::fs::File; +//! use std::io::BufReader; +//! use rodio::{Decoder, OutputStream, source::Source}; +//! +//! // Get an output stream handle to the default physical sound device. +//! // Note that no sound will be played if _stream is dropped +//! let stream_handle = rodio::OutputStreamBuilder::try_default_stream() +//! .expect("open default audio stream"); +//! +//! // Load a sound from a file, using a path relative to Cargo.toml +//! let file = BufReader::new(File::open("examples/music.ogg").unwrap()); +//! rodio::play(&stream_handle.mixer(), file).unwrap(); +//! +//! // The sound plays in a separate audio thread, +//! // so we need to keep the main thread alive while it's playing. +//! std::thread::sleep(std::time::Duration::from_secs(5)); +//! ``` +//! //! //! ## Sink //! diff --git a/src/source/mod.rs b/src/source/mod.rs index 4802e53..ceac73b 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -310,20 +310,9 @@ where /// /// let agc_source = source.automatic_gain_control(1.0, 4.0, 0.005, 5.0); /// - /// // Get a handle to control the AGC's enabled state (optional) - /// #[cfg(feature = "experimental")] - /// { - /// let agc_control = agc_source.get_agc_control(); + /// // Add the AGC-controlled source to the sink + /// sink.append(agc_source); /// - /// // You can toggle AGC on/off at any time (optional) - /// agc_control.store(false, std::sync::atomic::Ordering::Relaxed); - /// - /// // Add the AGC-controlled source to the sink - /// sink.append(agc_source); - /// } - /// - /// // Note: Using agc_control is optional. If you don't need to toggle AGC, - /// // you can simply use the agc_source directly without getting agc_control. /// ``` #[inline] fn automatic_gain_control( diff --git a/src/spatial_sink.rs b/src/spatial_sink.rs index bddd679..4d54c70 100644 --- a/src/spatial_sink.rs +++ b/src/spatial_sink.rs @@ -5,7 +5,7 @@ use std::time::Duration; use cpal::FromSample; use crate::dynamic_mixer::Mixer; -use crate::source::Spatial; +use crate::source::{SeekError, Spatial}; use crate::{Sample, Sink, Source}; /// A sink that allows changing the position of the source and the listeners diff --git a/src/stream.rs b/src/stream.rs index 9437930..6653899 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -183,12 +183,12 @@ fn clamp_supported_buffer_size( } /// Plays a sound once. Returns a `Sink` that can be used to control the sound. -pub fn play(stream: &Mixer, input: R) -> Result +pub fn play(mixer: &Mixer, input: R) -> Result where R: Read + Seek + Send + Sync + 'static, { let input = decoder::Decoder::new(input)?; - let sink = Sink::connect_new(stream); + let sink = Sink::connect_new(mixer); sink.append(input); Ok(sink) } From 6a6e94c88ef0abced21cb42235b4394822c15136 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Fri, 15 Nov 2024 21:08:54 +0400 Subject: [PATCH 23/34] Rename dynamic_mixer.rs --- examples/mix_multiple_sources.rs | 4 ++-- src/lib.rs | 2 +- src/{dynamic_mixer.rs => mixer.rs} | 10 +++++----- src/sink.rs | 2 +- src/spatial_sink.rs | 2 +- src/stream.rs | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) rename src/{dynamic_mixer.rs => mixer.rs} (96%) diff --git a/examples/mix_multiple_sources.rs b/examples/mix_multiple_sources.rs index 9b145f9..c6b7691 100644 --- a/examples/mix_multiple_sources.rs +++ b/examples/mix_multiple_sources.rs @@ -1,10 +1,10 @@ -use rodio::dynamic_mixer; +use rodio::mixer; use rodio::source::{SineWave, Source}; use std::time::Duration; fn main() { // Construct a dynamic controller and mixer, stream_handle, and sink. - let (controller, mixer) = dynamic_mixer::mixer::(2, 44_100); + let (controller, mixer) = mixer::mixer::(2, 44_100); let stream_handle = rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); let sink = rodio::Sink::connect_new(&stream_handle.mixer()); diff --git a/src/lib.rs b/src/lib.rs index 407785b..e6b3cbe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -165,7 +165,7 @@ mod stream; pub mod buffer; pub mod decoder; -pub mod dynamic_mixer; +pub mod mixer; pub mod queue; pub mod source; pub mod static_buffer; diff --git a/src/dynamic_mixer.rs b/src/mixer.rs similarity index 96% rename from src/dynamic_mixer.rs rename to src/mixer.rs index 61c13ee..92b3e4e 100644 --- a/src/dynamic_mixer.rs +++ b/src/mixer.rs @@ -214,12 +214,12 @@ where #[cfg(test)] mod tests { use crate::buffer::SamplesBuffer; - use crate::dynamic_mixer; + use crate::mixer; use crate::source::Source; #[test] fn basic() { - let (tx, mut rx) = dynamic_mixer::mixer(1, 48000); + let (tx, mut rx) = mixer::mixer(1, 48000); tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10])); tx.add(SamplesBuffer::new(1, 48000, vec![5i16, 5, 5, 5])); @@ -235,7 +235,7 @@ mod tests { #[test] fn channels_conv() { - let (tx, mut rx) = dynamic_mixer::mixer(2, 48000); + let (tx, mut rx) = mixer::mixer(2, 48000); tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10])); tx.add(SamplesBuffer::new(1, 48000, vec![5i16, 5, 5, 5])); @@ -255,7 +255,7 @@ mod tests { #[test] fn rate_conv() { - let (tx, mut rx) = dynamic_mixer::mixer(1, 96000); + let (tx, mut rx) = mixer::mixer(1, 96000); tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10])); tx.add(SamplesBuffer::new(1, 48000, vec![5i16, 5, 5, 5])); @@ -274,7 +274,7 @@ mod tests { #[test] fn start_afterwards() { - let (tx, mut rx) = dynamic_mixer::mixer(1, 48000); + let (tx, mut rx) = mixer::mixer(1, 48000); tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10])); diff --git a/src/sink.rs b/src/sink.rs index 7a0354d..8dd5175 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -8,7 +8,7 @@ use crossbeam_channel::{Receiver, Sender}; #[cfg(not(feature = "crossbeam-channel"))] use std::sync::mpsc::{Receiver, Sender}; -use crate::dynamic_mixer::Mixer; +use crate::mixer::Mixer; use crate::source::SeekError; use crate::{queue, source::Done, Sample, Source}; diff --git a/src/spatial_sink.rs b/src/spatial_sink.rs index 4d54c70..6a4a0c9 100644 --- a/src/spatial_sink.rs +++ b/src/spatial_sink.rs @@ -4,7 +4,7 @@ use std::time::Duration; use cpal::FromSample; -use crate::dynamic_mixer::Mixer; +use crate::mixer::Mixer; use crate::source::{SeekError, Spatial}; use crate::{Sample, Sink, Source}; diff --git a/src/stream.rs b/src/stream.rs index 6653899..6f00b79 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use std::{error, fmt}; use crate::decoder; -use crate::dynamic_mixer::{mixer, Mixer, MixerSource}; +use crate::mixer::{mixer, Mixer, MixerSource}; use crate::sink::Sink; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::{ From 1af733ee8662c3c659303d8abd548b2d701201aa Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Fri, 15 Nov 2024 22:41:03 +0400 Subject: [PATCH 24/34] Cleanup: fix lint warnings --- src/lib.rs | 2 +- src/stream.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e6b3cbe..606359e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,10 +7,10 @@ //! [`decoder`], etc. or even your own type that implements the [`Source`] trait. //! - Get an output stream handle to a physical device. For example, get a stream to the system's //! default sound device with [`OutputStream::default()`] -//! FIXME Update documentation after the builder is complete //! - Call [`.play_raw(source)`](OutputStream::play_raw) on the output stream handle. //! //! FIXME Update documentation after the builder is complete +//! //! The [`play_raw`](OutputStream::play_raw) function expects the source to produce [`f32`]s, //! which may not be the case. If you get a compilation error, try calling //! [`.convert_samples()`](Source::convert_samples) on the source to fix it. diff --git a/src/stream.rs b/src/stream.rs index 6f00b79..1a912e6 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -106,7 +106,6 @@ impl OutputStreamBuilder { // In case of supported range limit buffer size to avoid unexpectedly long playback delays. buffer_size: clamp_supported_buffer_size(config.buffer_size(), 1024), sample_format: config.sample_format(), - ..self.config }; self } From 6f6385b0d56081c8eb7749973750eb9c6eb6ad0d Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sat, 16 Nov 2024 23:19:18 +0400 Subject: [PATCH 25/34] Add documentation for output stream public exports --- src/stream.rs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/stream.rs b/src/stream.rs index 1a912e6..364c6ca 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -14,8 +14,8 @@ use cpal::{ const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); -/// `cpal::Stream` container. Use `mixer()` method to control output. -/// +/// `cpal::Stream` container. +/// Use `mixer()` method to control output. /// If this is dropped, playback will end, and the associated output stream will be disposed. pub struct OutputStream { mixer: Arc>, @@ -23,6 +23,7 @@ pub struct OutputStream { } impl OutputStream { + /// Access the output stream's mixer. pub fn mixer(&self) -> Arc> { self.mixer.clone() } @@ -36,6 +37,9 @@ pub struct OutputStreamConfig { pub sample_format: SampleFormat, } +/// Convenience builder for audio output stream. +/// It provides methods to configure several parameters of the audio output and opening default +/// device. See examples for use-cases. #[derive(Default)] pub struct OutputStreamBuilder { device: Option, @@ -54,6 +58,7 @@ impl Default for OutputStreamConfig { } impl OutputStreamBuilder { + /// Sets default output stream parameters for given output audio device. pub fn from_device(device: cpal::Device) -> Result { let default_config = device .default_output_config() @@ -63,6 +68,7 @@ impl OutputStreamBuilder { .with_supported_config(&default_config)) } + /// Sets default output stream parameters for default output audio device. pub fn from_default_device() -> Result { let default_device = cpal::default_host() .default_output_device() @@ -70,32 +76,43 @@ impl OutputStreamBuilder { Self::from_device(default_device) } + /// Sets output audio device keeping all existing stream parameters intact. + /// This method is useful if you want to set other parameters yourself. + /// To also set parameters that are appropriate for the device use [Self::from_device()] instead. pub fn with_device(mut self, device: cpal::Device) -> OutputStreamBuilder { self.device = Some(device); self } + /// Sets number of output stream's channels. pub fn with_channels(mut self, channel_count: cpal::ChannelCount) -> OutputStreamBuilder { assert!(channel_count > 0); self.config.channel_count = channel_count; self } + /// Sets output stream's sample rate. pub fn with_sample_rate(mut self, sample_rate: cpal::SampleRate) -> OutputStreamBuilder { self.config.sample_rate = sample_rate; self } + /// Sets preferred output buffer size. + /// Larger buffer size causes longer playback delays. Buffer sizes that are too small + /// may cause higher CPU usage or playback interruptions. pub fn with_buffer_size(mut self, buffer_size: cpal::BufferSize) -> OutputStreamBuilder { self.config.buffer_size = buffer_size; self } + /// Select scalar type that will carry a sample. pub fn with_sample_format(mut self, sample_format: SampleFormat) -> OutputStreamBuilder { self.config.sample_format = sample_format; self } + /// Set available parameters from a CPAL supported config. You can ge list of + /// such configurations for an output device using [crate::stream::supported_output_configs()] pub fn with_supported_config( mut self, config: &cpal::SupportedStreamConfig, @@ -110,6 +127,7 @@ impl OutputStreamBuilder { self } + /// Set all output stream parameters at once from CPAL stream config. pub fn with_config(mut self, config: &cpal::StreamConfig) -> OutputStreamBuilder { self.config = OutputStreamConfig { channel_count: config.channels, @@ -120,6 +138,7 @@ impl OutputStreamBuilder { self } + /// Open output stream using parameters configured so far. pub fn open_stream(&self) -> Result { let device = self.device.as_ref().expect("output device specified"); OutputStream::open(device, &self.config) @@ -181,7 +200,8 @@ fn clamp_supported_buffer_size( } } -/// Plays a sound once. Returns a `Sink` that can be used to control the sound. +/// A convenience function. Plays a sound once. +/// Returns a `Sink` that can be used to control the sound. pub fn play(mixer: &Mixer, input: R) -> Result where R: Read + Seek + Send + Sync + 'static, @@ -278,6 +298,7 @@ impl error::Error for StreamError { } impl OutputStream { + /// Open output audio stream. pub fn open( device: &cpal::Device, config: &OutputStreamConfig, From 77c50cd9ce7a485bc838b91aea97110e0c9c97a5 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sun, 17 Nov 2024 18:54:13 +0400 Subject: [PATCH 26/34] Clarify API documentation --- src/stream.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stream.rs b/src/stream.rs index 364c6ca..e8a166c 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -58,7 +58,7 @@ impl Default for OutputStreamConfig { } impl OutputStreamBuilder { - /// Sets default output stream parameters for given output audio device. + /// Sets output device and its default parameters. pub fn from_device(device: cpal::Device) -> Result { let default_config = device .default_output_config() From 4101ae908790ece9feb203473f878901ec91f6db Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Wed, 20 Nov 2024 00:58:05 +0400 Subject: [PATCH 27/34] Remove unwrap and expect from example code --- examples/automatic_gain_control.rs | 11 ++++++----- examples/basic.rs | 16 +++++++++------- examples/mix_multiple_sources.rs | 8 +++++--- examples/music_flac.rs | 12 +++++++----- examples/music_m4a.rs | 12 +++++++----- examples/music_mp3.rs | 12 +++++++----- examples/music_ogg.rs | 12 +++++++----- examples/music_wav.rs | 12 +++++++----- examples/noise_generator.rs | 9 ++++++--- examples/reverb.rs | 12 +++++++----- examples/seek_mp3.rs | 18 ++++++++++-------- examples/signal_generator.rs | 9 ++++++--- examples/spatial.rs | 13 +++++++------ examples/stereo.rs | 17 ++++++++--------- src/source/speed.rs | 2 +- 15 files changed, 100 insertions(+), 75 deletions(-) diff --git a/examples/automatic_gain_control.rs b/examples/automatic_gain_control.rs index 9a8b87e..2c259d5 100644 --- a/examples/automatic_gain_control.rs +++ b/examples/automatic_gain_control.rs @@ -1,5 +1,6 @@ use rodio::source::Source; use rodio::Decoder; +use std::error::Error; use std::fs::File; use std::io::BufReader; use std::sync::atomic::{AtomicBool, Ordering}; @@ -7,14 +8,13 @@ use std::sync::Arc; use std::thread; use std::time::Duration; -fn main() { - let stream_handle = - rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); +fn main() -> Result<(), Box> { + let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); // Decode the sound file into a source - let file = BufReader::new(File::open("assets/music.flac").unwrap()); - let source = Decoder::new(file).unwrap(); + let file = BufReader::new(File::open("assets/music.flac")?); + let source = Decoder::new(file)?; // Apply automatic gain control to the source let agc_source = source.automatic_gain_control(1.0, 4.0, 0.005, 5.0); @@ -45,4 +45,5 @@ fn main() { // Keep the program running until the playback is complete. sink.sleep_until_end(); + Ok(()) } diff --git a/examples/basic.rs b/examples/basic.rs index e5c72c2..e45580f 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,20 +1,20 @@ use rodio::source::SineWave; use rodio::Source; +use std::error::Error; use std::io::BufReader; use std::thread; use std::time::Duration; #[cfg(feature = "tracing")] use tracing; -fn main() { - let stream_handle = - rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); +fn main() -> Result<(), Box> { + let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; let mixer = stream_handle.mixer(); let beep1 = { // Play a WAV file. - let file = std::fs::File::open("assets/beep.wav").unwrap(); - let sink = rodio::play(&mixer, BufReader::new(file)).unwrap(); + let file = std::fs::File::open("assets/beep.wav")?; + let sink = rodio::play(&mixer, BufReader::new(file))?; sink.set_volume(0.2); sink }; @@ -33,8 +33,8 @@ fn main() { let beep3 = { // Play an OGG file. - let file = std::fs::File::open("assets/beep3.ogg").unwrap(); - let sink = rodio::play(&mixer, BufReader::new(file)).unwrap(); + let file = std::fs::File::open("assets/beep3.ogg")?; + let sink = rodio::play(&mixer, BufReader::new(file))?; sink.set_volume(0.2); sink }; @@ -49,4 +49,6 @@ fn main() { println!("Stopped beep3"); thread::sleep(Duration::from_millis(1500)); + + Ok(()) } diff --git a/examples/mix_multiple_sources.rs b/examples/mix_multiple_sources.rs index c6b7691..a2f760f 100644 --- a/examples/mix_multiple_sources.rs +++ b/examples/mix_multiple_sources.rs @@ -1,12 +1,12 @@ use rodio::mixer; use rodio::source::{SineWave, Source}; +use std::error::Error; use std::time::Duration; -fn main() { +fn main() -> Result<(), Box> { // Construct a dynamic controller and mixer, stream_handle, and sink. let (controller, mixer) = mixer::mixer::(2, 44_100); - let stream_handle = - rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); // Create four unique sources. The frequencies used here correspond @@ -36,4 +36,6 @@ fn main() { // Sleep the thread until sink is empty. sink.sleep_until_end(); + + Ok(()) } diff --git a/examples/music_flac.rs b/examples/music_flac.rs index da46581..fd79d24 100644 --- a/examples/music_flac.rs +++ b/examples/music_flac.rs @@ -1,12 +1,14 @@ +use std::error::Error; use std::io::BufReader; -fn main() { - let stream_handle = - rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); +fn main() -> Result<(), Box> { + let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); - let file = std::fs::File::open("assets/music.flac").unwrap(); - sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); + let file = std::fs::File::open("assets/music.flac")?; + sink.append(rodio::Decoder::new(BufReader::new(file))?); sink.sleep_until_end(); + + Ok(()) } diff --git a/examples/music_m4a.rs b/examples/music_m4a.rs index edae3c7..ff4d52d 100644 --- a/examples/music_m4a.rs +++ b/examples/music_m4a.rs @@ -1,12 +1,14 @@ +use std::error::Error; use std::io::BufReader; -fn main() { - let stream_handle = - rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); +fn main() -> Result<(), Box> { + let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); - let file = std::fs::File::open("assets/music.m4a").unwrap(); - sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); + let file = std::fs::File::open("assets/music.m4a")?; + sink.append(rodio::Decoder::new(BufReader::new(file))?); sink.sleep_until_end(); + + Ok(()) } diff --git a/examples/music_mp3.rs b/examples/music_mp3.rs index 4919a76..1bd4a40 100644 --- a/examples/music_mp3.rs +++ b/examples/music_mp3.rs @@ -1,12 +1,14 @@ +use std::error::Error; use std::io::BufReader; -fn main() { - let stream_handle = - rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); +fn main() -> Result<(), Box> { + let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); - let file = std::fs::File::open("assets/music.mp3").unwrap(); - sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); + let file = std::fs::File::open("assets/music.mp3")?; + sink.append(rodio::Decoder::new(BufReader::new(file))?); sink.sleep_until_end(); + + Ok(()) } diff --git a/examples/music_ogg.rs b/examples/music_ogg.rs index d88d1a5..10b9e49 100644 --- a/examples/music_ogg.rs +++ b/examples/music_ogg.rs @@ -1,12 +1,14 @@ +use std::error::Error; use std::io::BufReader; -fn main() { - let stream_handle = - rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); +fn main() -> Result<(), Box> { + let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); - let file = std::fs::File::open("assets/music.ogg").unwrap(); - sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); + let file = std::fs::File::open("assets/music.ogg")?; + sink.append(rodio::Decoder::new(BufReader::new(file))?); sink.sleep_until_end(); + + Ok(()) } diff --git a/examples/music_wav.rs b/examples/music_wav.rs index 5dce303..2e582ab 100644 --- a/examples/music_wav.rs +++ b/examples/music_wav.rs @@ -1,12 +1,14 @@ +use std::error::Error; use std::io::BufReader; -fn main() { - let stream_handle = - rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); +fn main() -> Result<(), Box> { + let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); - let file = std::fs::File::open("assets/music.wav").unwrap(); - sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); + let file = std::fs::File::open("assets/music.wav")?; + sink.append(rodio::Decoder::new(BufReader::new(file))?); sink.sleep_until_end(); + + Ok(()) } diff --git a/examples/noise_generator.rs b/examples/noise_generator.rs index 0bd1ee5..b9ff905 100644 --- a/examples/noise_generator.rs +++ b/examples/noise_generator.rs @@ -1,13 +1,14 @@ //! Noise generator example. Use the "noise" feature to enable the noise generator sources. +use std::error::Error; + #[cfg(feature = "noise")] -fn main() { +fn main() -> Result<(), Box> { use rodio::source::{pink, white, Source}; use std::thread; use std::time::Duration; - let stream_handle = - rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; let noise_duration = Duration::from_millis(1000); let interval_duration = Duration::from_millis(1500); @@ -29,6 +30,8 @@ fn main() { println!("Playing pink noise"); thread::sleep(interval_duration); + + Ok(()) } #[cfg(not(feature = "noise"))] diff --git a/examples/reverb.rs b/examples/reverb.rs index 46e6c2a..c77c379 100644 --- a/examples/reverb.rs +++ b/examples/reverb.rs @@ -1,16 +1,18 @@ use rodio::Source; +use std::error::Error; use std::io::BufReader; use std::time::Duration; -fn main() { - let stream_handle = - rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); +fn main() -> Result<(), Box> { + let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); - let file = std::fs::File::open("assets/music.ogg").unwrap(); - let source = rodio::Decoder::new(BufReader::new(file)).unwrap(); + let file = std::fs::File::open("assets/music.ogg")?; + let source = rodio::Decoder::new(BufReader::new(file))?; let with_reverb = source.buffered().reverb(Duration::from_millis(40), 0.7); sink.append(with_reverb); sink.sleep_until_end(); + + Ok(()) } diff --git a/examples/seek_mp3.rs b/examples/seek_mp3.rs index 4955ddf..3be311f 100644 --- a/examples/seek_mp3.rs +++ b/examples/seek_mp3.rs @@ -1,23 +1,25 @@ +use std::error::Error; use std::io::BufReader; use std::time::Duration; -fn main() { - let stream_handle = - rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); +fn main() -> Result<(), Box> { + let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); - let file = std::fs::File::open("assets/music.mp3").unwrap(); - sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap()); + let file = std::fs::File::open("assets/music.mp3")?; + sink.append(rodio::Decoder::new(BufReader::new(file))?); std::thread::sleep(std::time::Duration::from_secs(2)); - sink.try_seek(Duration::from_secs(0)).unwrap(); + sink.try_seek(Duration::from_secs(0))?; std::thread::sleep(std::time::Duration::from_secs(2)); - sink.try_seek(Duration::from_secs(4)).unwrap(); + sink.try_seek(Duration::from_secs(4))?; sink.sleep_until_end(); // This doesn't do anything since the sound has ended already. - sink.try_seek(Duration::from_secs(5)).unwrap(); + sink.try_seek(Duration::from_secs(5))?; println!("seek example ended"); + + Ok(()) } diff --git a/examples/signal_generator.rs b/examples/signal_generator.rs index 89c93fb..c73731b 100644 --- a/examples/signal_generator.rs +++ b/examples/signal_generator.rs @@ -1,12 +1,13 @@ //! Test signal generator example. -fn main() { +use std::error::Error; + +fn main() -> Result<(), Box> { use rodio::source::{chirp, Function, SignalGenerator, Source}; use std::thread; use std::time::Duration; - let stream_handle = - rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; let test_signal_duration = Duration::from_millis(1000); let interval_duration = Duration::from_millis(1500); @@ -65,4 +66,6 @@ fn main() { ); thread::sleep(interval_duration); + + Ok(()) } diff --git a/examples/spatial.rs b/examples/spatial.rs index bab8b22..485829e 100644 --- a/examples/spatial.rs +++ b/examples/spatial.rs @@ -1,10 +1,11 @@ +use std::error::Error; use std::io::BufReader; use std::thread; use std::time::Duration; use rodio::Source; -fn main() { +fn main() -> Result<(), Box> { let iter_duration = Duration::from_secs(5); let iter_distance = 5.; @@ -18,8 +19,7 @@ fn main() { let total_duration = iter_duration * 2 * repeats; - let stream_handle = - rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); + let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; let mut positions = ([0., 0., 0.], [-1., 0., 0.], [1., 0., 0.]); let sink = rodio::SpatialSink::connect_new( @@ -29,9 +29,8 @@ fn main() { positions.2, ); - let file = std::fs::File::open("assets/music.ogg").unwrap(); - let source = rodio::Decoder::new(BufReader::new(file)) - .unwrap() + let file = std::fs::File::open("assets/music.ogg")?; + let source = rodio::Decoder::new(BufReader::new(file))? .repeat_infinite() .take_duration(total_duration); sink.append(source); @@ -57,4 +56,6 @@ fn main() { } } sink.sleep_until_end(); + + Ok(()) } diff --git a/examples/stereo.rs b/examples/stereo.rs index 15f4266..a908894 100644 --- a/examples/stereo.rs +++ b/examples/stereo.rs @@ -1,18 +1,17 @@ //! Plays a tone alternating between right and left ears, with right being first. + use rodio::Source; +use std::error::Error; use std::io::BufReader; -fn main() { - let stream_handle = - rodio::OutputStreamBuilder::try_default_stream().expect("open default audio stream"); +fn main() -> Result<(), Box> { + let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); - let file = std::fs::File::open("assets/RL.ogg").unwrap(); - sink.append( - rodio::Decoder::new(BufReader::new(file)) - .unwrap() - .amplify(0.2), - ); + let file = std::fs::File::open("assets/RL.ogg")?; + sink.append(rodio::Decoder::new(BufReader::new(file))?.amplify(0.2)); sink.sleep_until_end(); + + Ok(()) } diff --git a/src/source/speed.rs b/src/source/speed.rs index 90a58e3..4b7bac8 100644 --- a/src/source/speed.rs +++ b/src/source/speed.rs @@ -29,7 +29,7 @@ //! std::thread::sleep(std::time::Duration::from_secs(5)); //! ``` //! here is how you would do it using the sink -//!``` +//!```no_run //! use rodio::source::{Source, SineWave}; //! let source = SineWave::new(440.0) //! .take_duration(std::time::Duration::from_secs_f32(20.25)) From 0ba60217ffea11ce92b752cb2a2a80c4f6dc717d Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Thu, 21 Nov 2024 19:58:06 +0400 Subject: [PATCH 28/34] Hide OutputStreamConfig struct --- src/stream.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/stream.rs b/src/stream.rs index e8a166c..8e26a52 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -30,7 +30,7 @@ impl OutputStream { } #[derive(Copy, Clone, Debug)] -pub struct OutputStreamConfig { +struct OutputStreamConfig { pub channel_count: ChannelCount, pub sample_rate: SampleRate, pub buffer_size: BufferSize, @@ -298,8 +298,7 @@ impl error::Error for StreamError { } impl OutputStream { - /// Open output audio stream. - pub fn open( + fn open( device: &cpal::Device, config: &OutputStreamConfig, ) -> Result { From 95060b1909697afdc8e8f4908c5b3ecfdaa97cc7 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Thu, 21 Nov 2024 20:51:11 +0400 Subject: [PATCH 29/34] Stream initialization fix --- src/stream.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/stream.rs b/src/stream.rs index 8e26a52..57209b2 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -144,15 +144,16 @@ impl OutputStreamBuilder { OutputStream::open(device, &self.config) } - /// Try to open a new output stream with the builder's current stream configuration. + /// Try opening a new output stream with the builder's current stream configuration. /// Failing that attempt to open stream with other available configurations /// provided by the device. - /// If all attempts to open stream have not succeeded returns initial error. + /// If all attempts did not succeed returns initial error. pub fn try_open_stream(&self) -> Result { let device = self.device.as_ref().expect("output device specified"); OutputStream::open(device, &self.config).or_else(|err| { for supported_config in supported_output_configs(device)? { if let Ok(handle) = Self::default() + .with_device(device.clone()) .with_supported_config(&supported_config) .open_stream() { From 678df037cb86b0223348afaf353172b43df0bbee Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Thu, 21 Nov 2024 21:00:57 +0400 Subject: [PATCH 30/34] Custom output stream config example --- examples/custom_config.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 examples/custom_config.rs diff --git a/examples/custom_config.rs b/examples/custom_config.rs new file mode 100644 index 0000000..07b2f8d --- /dev/null +++ b/examples/custom_config.rs @@ -0,0 +1,35 @@ +use cpal::traits::HostTrait; +use cpal::{BufferSize, SampleFormat, SampleRate}; +use rodio::source::SineWave; +use rodio::Source; +use std::error::Error; +use std::thread; +use std::time::Duration; + +fn main() -> Result<(), Box> { + // You can use any other output device that can be queried from CPAL. + let default_device = cpal::default_host() + .default_output_device() + .ok_or("No default audio output device is found.")?; + let stream_handle = rodio::OutputStreamBuilder::from_device(default_device)? + // No need to set all parameters explicitly here, + // the defaults were set from the device's description. + .with_buffer_size(BufferSize::Fixed(256)) + .with_sample_rate(SampleRate(48_000)) + .with_sample_format(SampleFormat::F32) + // Note that the function below still tries alternative configs if the specified one fails. + // If you need to only use the exact specified configuration, + // then use OutputStreamBuilder::open_stream() instead. + .try_open_stream()?; + let mixer = stream_handle.mixer(); + + let wave = SineWave::new(740.0) + .amplify(0.1) + .take_duration(Duration::from_secs(1)); + mixer.add(wave); + + println!("Beep..."); + thread::sleep(Duration::from_millis(1500)); + + Ok(()) +} From d1d5726ce0a8e0b8799e3ce5673e63d1a4ff0a49 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Thu, 21 Nov 2024 21:43:24 +0400 Subject: [PATCH 31/34] Rename try_open_stream and try_default_stream --- examples/automatic_gain_control.rs | 2 +- examples/basic.rs | 2 +- examples/custom_config.rs | 2 +- examples/mix_multiple_sources.rs | 2 +- examples/music_flac.rs | 2 +- examples/music_m4a.rs | 2 +- examples/music_mp3.rs | 2 +- examples/music_ogg.rs | 2 +- examples/music_wav.rs | 2 +- examples/noise_generator.rs | 2 +- examples/reverb.rs | 2 +- examples/seek_mp3.rs | 2 +- examples/signal_generator.rs | 2 +- examples/spatial.rs | 2 +- examples/stereo.rs | 2 +- src/lib.rs | 6 +++--- src/source/speed.rs | 4 ++-- src/stream.rs | 18 +++++++++++------- 18 files changed, 31 insertions(+), 27 deletions(-) diff --git a/examples/automatic_gain_control.rs b/examples/automatic_gain_control.rs index 2c259d5..aae0ba6 100644 --- a/examples/automatic_gain_control.rs +++ b/examples/automatic_gain_control.rs @@ -9,7 +9,7 @@ use std::thread; use std::time::Duration; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); // Decode the sound file into a source diff --git a/examples/basic.rs b/examples/basic.rs index e45580f..fa3ef3e 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -8,7 +8,7 @@ use std::time::Duration; use tracing; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let mixer = stream_handle.mixer(); let beep1 = { diff --git a/examples/custom_config.rs b/examples/custom_config.rs index 07b2f8d..40b6ca4 100644 --- a/examples/custom_config.rs +++ b/examples/custom_config.rs @@ -20,7 +20,7 @@ fn main() -> Result<(), Box> { // Note that the function below still tries alternative configs if the specified one fails. // If you need to only use the exact specified configuration, // then use OutputStreamBuilder::open_stream() instead. - .try_open_stream()?; + .open_stream_or_fallback()?; let mixer = stream_handle.mixer(); let wave = SineWave::new(740.0) diff --git a/examples/mix_multiple_sources.rs b/examples/mix_multiple_sources.rs index a2f760f..55c3fb0 100644 --- a/examples/mix_multiple_sources.rs +++ b/examples/mix_multiple_sources.rs @@ -6,7 +6,7 @@ use std::time::Duration; fn main() -> Result<(), Box> { // Construct a dynamic controller and mixer, stream_handle, and sink. let (controller, mixer) = mixer::mixer::(2, 44_100); - let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); // Create four unique sources. The frequencies used here correspond diff --git a/examples/music_flac.rs b/examples/music_flac.rs index fd79d24..13013ba 100644 --- a/examples/music_flac.rs +++ b/examples/music_flac.rs @@ -2,7 +2,7 @@ use std::error::Error; use std::io::BufReader; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.flac")?; diff --git a/examples/music_m4a.rs b/examples/music_m4a.rs index ff4d52d..15b5843 100644 --- a/examples/music_m4a.rs +++ b/examples/music_m4a.rs @@ -2,7 +2,7 @@ use std::error::Error; use std::io::BufReader; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.m4a")?; diff --git a/examples/music_mp3.rs b/examples/music_mp3.rs index 1bd4a40..cb94159 100644 --- a/examples/music_mp3.rs +++ b/examples/music_mp3.rs @@ -2,7 +2,7 @@ use std::error::Error; use std::io::BufReader; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.mp3")?; diff --git a/examples/music_ogg.rs b/examples/music_ogg.rs index 10b9e49..74c3622 100644 --- a/examples/music_ogg.rs +++ b/examples/music_ogg.rs @@ -2,7 +2,7 @@ use std::error::Error; use std::io::BufReader; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.ogg")?; diff --git a/examples/music_wav.rs b/examples/music_wav.rs index 2e582ab..a29d929 100644 --- a/examples/music_wav.rs +++ b/examples/music_wav.rs @@ -2,7 +2,7 @@ use std::error::Error; use std::io::BufReader; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.wav")?; diff --git a/examples/noise_generator.rs b/examples/noise_generator.rs index b9ff905..577f9d0 100644 --- a/examples/noise_generator.rs +++ b/examples/noise_generator.rs @@ -8,7 +8,7 @@ fn main() -> Result<(), Box> { use std::thread; use std::time::Duration; - let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let noise_duration = Duration::from_millis(1000); let interval_duration = Duration::from_millis(1500); diff --git a/examples/reverb.rs b/examples/reverb.rs index c77c379..dc74e71 100644 --- a/examples/reverb.rs +++ b/examples/reverb.rs @@ -4,7 +4,7 @@ use std::io::BufReader; use std::time::Duration; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.ogg")?; diff --git a/examples/seek_mp3.rs b/examples/seek_mp3.rs index 3be311f..a78238b 100644 --- a/examples/seek_mp3.rs +++ b/examples/seek_mp3.rs @@ -3,7 +3,7 @@ use std::io::BufReader; use std::time::Duration; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/music.mp3")?; diff --git a/examples/signal_generator.rs b/examples/signal_generator.rs index c73731b..e746692 100644 --- a/examples/signal_generator.rs +++ b/examples/signal_generator.rs @@ -7,7 +7,7 @@ fn main() -> Result<(), Box> { use std::thread; use std::time::Duration; - let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let test_signal_duration = Duration::from_millis(1000); let interval_duration = Duration::from_millis(1500); diff --git a/examples/spatial.rs b/examples/spatial.rs index 485829e..31505ec 100644 --- a/examples/spatial.rs +++ b/examples/spatial.rs @@ -19,7 +19,7 @@ fn main() -> Result<(), Box> { let total_duration = iter_duration * 2 * repeats; - let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let mut positions = ([0., 0., 0.], [-1., 0., 0.], [1., 0., 0.]); let sink = rodio::SpatialSink::connect_new( diff --git a/examples/stereo.rs b/examples/stereo.rs index a908894..5b5ec50 100644 --- a/examples/stereo.rs +++ b/examples/stereo.rs @@ -5,7 +5,7 @@ use std::error::Error; use std::io::BufReader; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::try_default_stream()?; + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let file = std::fs::File::open("assets/RL.ogg")?; diff --git a/src/lib.rs b/src/lib.rs index 606359e..c46b506 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,7 +24,7 @@ //! //! // Get an output stream handle to the default physical sound device. //! // Note that no sound will be played if _stream is dropped -//! let stream_handle = rodio::OutputStreamBuilder::try_default_stream() +//! let stream_handle = rodio::OutputStreamBuilder::open_default_stream() //! .expect("open default audio stream"); //! let sink = rodio::Sink::connect_new(&stream_handle.mixer()); //! // Load a sound from a file, using a path relative to Cargo.toml @@ -46,7 +46,7 @@ //! //! // Get an output stream handle to the default physical sound device. //! // Note that no sound will be played if _stream is dropped -//! let stream_handle = rodio::OutputStreamBuilder::try_default_stream() +//! let stream_handle = rodio::OutputStreamBuilder::open_default_stream() //! .expect("open default audio stream"); //! //! // Load a sound from a file, using a path relative to Cargo.toml @@ -78,7 +78,7 @@ //! use rodio::source::{SineWave, Source}; //! //! // _stream must live as long as the sink -//! let stream_handle = rodio::OutputStreamBuilder::try_default_stream() +//! let stream_handle = rodio::OutputStreamBuilder::open_default_stream() //! .expect("open default audio stream"); //! let sink = rodio::Sink::connect_new(&stream_handle.mixer()); //! diff --git a/src/source/speed.rs b/src/source/speed.rs index 4b7bac8..8e278e9 100644 --- a/src/source/speed.rs +++ b/src/source/speed.rs @@ -18,7 +18,7 @@ //! //! // Get an output stream handle to the default physical sound device. //! // Note that no sound will be played if the _stream is dropped. -//! let stream_handle = rodio::OutputStreamBuilder::try_default_stream() +//! let stream_handle = rodio::OutputStreamBuilder::open_default_stream() //! .expect("open default audio stream"); //! // Load a sound from a file, using a path relative to `Cargo.toml` //! let file = BufReader::new(File::open("examples/music.ogg").unwrap()); @@ -34,7 +34,7 @@ //! let source = SineWave::new(440.0) //! .take_duration(std::time::Duration::from_secs_f32(20.25)) //! .amplify(0.20); -//! let stream_handle = rodio::OutputStreamBuilder::try_default_stream() +//! let stream_handle = rodio::OutputStreamBuilder::open_default_stream() //! .expect("open default audio stream"); //! let sink = rodio::Sink::connect_new(&stream_handle.mixer()); //! sink.set_speed(2.0); diff --git a/src/stream.rs b/src/stream.rs index 57209b2..8b31882 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -146,9 +146,9 @@ impl OutputStreamBuilder { /// Try opening a new output stream with the builder's current stream configuration. /// Failing that attempt to open stream with other available configurations - /// provided by the device. - /// If all attempts did not succeed returns initial error. - pub fn try_open_stream(&self) -> Result { + /// supported by the device. + /// If all attempts fail returns initial error. + pub fn open_stream_or_fallback(&self) -> Result { let device = self.device.as_ref().expect("output device specified"); OutputStream::open(device, &self.config).or_else(|err| { for supported_config in supported_output_configs(device)? { @@ -166,9 +166,9 @@ impl OutputStreamBuilder { /// Try to open a new output stream for the default output device with its default configuration. /// Failing that attempt to open output stream with alternative configuration and/or non default - /// output devices. Returns stream for first tried configuration that succeeds. - /// If all attempts have not succeeded return the initial error. - pub fn try_default_stream() -> Result { + /// output devices. Returns stream for first of the tried configurations that succeeds. + /// If all attempts fail return the initial error. + pub fn open_default_stream() -> Result { Self::from_default_device() .and_then(|x| x.open_stream()) .or_else(|original_err| { @@ -183,7 +183,11 @@ impl OutputStreamBuilder { } }; devices - .find_map(|d| Self::from_device(d).and_then(|x| x.try_open_stream()).ok()) + .find_map(|d| { + Self::from_device(d) + .and_then(|x| x.open_stream_or_fallback()) + .ok() + }) .ok_or(original_err) }) } From c23ec2c76d301881d244445c30a630243d460c51 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Thu, 21 Nov 2024 22:31:43 +0400 Subject: [PATCH 32/34] Update documentation --- src/lib.rs | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c46b506..ea0a3a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,19 +3,17 @@ //! The main concept of this library is the [`Source`] trait, which //! represents a sound (streaming or not). In order to play a sound, there are three steps: //! +//! - Get an output stream handle to a physical device. For example, get a stream to the system's +//! default sound device with [`OutputStreamBuilder::open_default_stream()`]. //! - Create an object that represents the streaming sound. It can be a sine wave, a buffer, a //! [`decoder`], etc. or even your own type that implements the [`Source`] trait. -//! - Get an output stream handle to a physical device. For example, get a stream to the system's -//! default sound device with [`OutputStream::default()`] -//! - Call [`.play_raw(source)`](OutputStream::play_raw) on the output stream handle. +//! - Add the source to the output stream using [`OutputStream::mixer()`](OutputStream::mixer) +//! on the output stream handle. //! -//! FIXME Update documentation after the builder is complete +//! The output stream expects the sources to produce [`f32`]s. In case the output sample format +//! is different use [`.convert_samples()`](Source::convert_samples) to adapt them. //! -//! The [`play_raw`](OutputStream::play_raw) function expects the source to produce [`f32`]s, -//! which may not be the case. If you get a compilation error, try calling -//! [`.convert_samples()`](Source::convert_samples) on the source to fix it. -//! -//! For example, here is how you would play an audio file: +//! Here is a complete example of how you would play an audio file: //! //! ```no_run //! use std::fs::File; @@ -23,7 +21,7 @@ //! use rodio::{Decoder, OutputStream, source::Source}; //! //! // Get an output stream handle to the default physical sound device. -//! // Note that no sound will be played if _stream is dropped +//! // Note that the playback stops when the stream_handle is dropped. //! let stream_handle = rodio::OutputStreamBuilder::open_default_stream() //! .expect("open default audio stream"); //! let sink = rodio::Sink::connect_new(&stream_handle.mixer()); @@ -38,14 +36,15 @@ //! // so we need to keep the main thread alive while it's playing. //! std::thread::sleep(std::time::Duration::from_secs(5)); //! ``` -//! You can use [rodio::play()] to do the above +//! +//! [rodio::play()] helps to simplify the above //! ```no_run //! use std::fs::File; //! use std::io::BufReader; //! use rodio::{Decoder, OutputStream, source::Source}; //! //! // Get an output stream handle to the default physical sound device. -//! // Note that no sound will be played if _stream is dropped +//! // Note that the playback stops when the stream_handle is dropped. //! let stream_handle = rodio::OutputStreamBuilder::open_default_stream() //! .expect("open default audio stream"); //! @@ -62,13 +61,11 @@ //! ## Sink //! //! In order to make it easier to control the playback, the rodio library also provides a type -//! named [`Sink`] which represents an audio track. +//! named [`Sink`] which represents an audio track. [`Sink`] plays its input sources sequentially, +//! one after another. To play sounds in simultaneously in parallel, use [`mixer::Mixer`] instead. //! -//! FIXME Update documentation after the builder is complete -//! Instead of playing the sound with [`play_raw`](OutputStream::play_raw), you can add it to -//! a [`Sink`] instead. -//! -//! - Get a [`Sink`] to the output stream, and [`.append()`](Sink::append) your sound to it. +//! To play a soung Create a [`Sink`] connect it to the output stream, +//! and [`.append()`](Sink::append) your sound to it. //! //! ```no_run //! use std::fs::File; From 5972eebc3a7f405c040e565a2924df8ad822f21d Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Thu, 21 Nov 2024 22:33:38 +0400 Subject: [PATCH 33/34] Update CHANGELOG.md --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea1e110..e5cbfb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Output audio stream buffer size can now be adjusted. + +### Changed +- Breaking: `OutputStreamBuilder` should now be used to initialize audio output stream. +- Breaking: `OutputStreamHandle` removed, use `OutputStream` and `OutputStream::mixer()` instead. +- Breaking: `DynamicMixerController` renamed to `Mixer`, `DynamicMixer` renamed to `MixerSource`. +- Breaking: `Sink::try_new` renamed to `connect_new` and does not return error anymore. + `Sink::new_idle` was renamed to `new`. + # Version 0.20.1 (2024-11-08) ### Fixed From bc8d1c7e050f98d88f39fb50152404467e51bfe4 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sun, 24 Nov 2024 12:32:00 +0100 Subject: [PATCH 34/34] remove unneeded pub from OutputStreamConfig --- src/stream.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/stream.rs b/src/stream.rs index 8b31882..246f0c9 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -31,10 +31,10 @@ impl OutputStream { #[derive(Copy, Clone, Debug)] struct OutputStreamConfig { - pub channel_count: ChannelCount, - pub sample_rate: SampleRate, - pub buffer_size: BufferSize, - pub sample_format: SampleFormat, + channel_count: ChannelCount, + sample_rate: SampleRate, + buffer_size: BufferSize, + sample_format: SampleFormat, } /// Convenience builder for audio output stream.