From 70c236c7ac495b9ccb82716ffba06cb3fe5fa44c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 12:18:22 +0200 Subject: [PATCH 1/9] docs: fixes spatial source & sink description (was wrong & missing) --- src/source/channel_volume.rs | 1 - src/source/spatial.rs | 5 +++-- src/spatial_sink.rs | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/source/channel_volume.rs b/src/source/channel_volume.rs index 87ee9919..30962113 100644 --- a/src/source/channel_volume.rs +++ b/src/source/channel_volume.rs @@ -82,7 +82,6 @@ where #[inline] fn next(&mut self) -> Option { - // return value let ret = self .current_sample .map(|sample| sample.amplify(self.channel_volumes[self.current_channel])); diff --git a/src/source/spatial.rs b/src/source/spatial.rs index 2976703b..940147bf 100644 --- a/src/source/spatial.rs +++ b/src/source/spatial.rs @@ -5,8 +5,9 @@ use crate::{Sample, Source}; use super::SeekError; -/// Combines channels in input into a single mono source, then plays that mono sound -/// to each channel at the volume given for that channel. +/// A simple spatial audio source. The underlying source is transformed to Mono +/// and then played in stereo. The left and right channel's volume are amplified +/// differently depending on the distance of the left and right ear to the source. #[derive(Clone)] pub struct Spatial where diff --git a/src/spatial_sink.rs b/src/spatial_sink.rs index 9fc9affb..cc148f71 100644 --- a/src/spatial_sink.rs +++ b/src/spatial_sink.rs @@ -8,6 +8,9 @@ use crate::source::{SeekError, Spatial}; use crate::stream::{OutputStreamHandle, PlayError}; use crate::{Sample, Sink, Source}; +/// A sink that allows changing the position of the source and the listeners +/// ears while playing. The sources played are then transformed to give a simple +/// spatial effect. See [`Spatial`] for details. pub struct SpatialSink { sink: Sink, positions: Arc>, From 22d90e71d8d635bb777f5c0f9ca95be843b02ff3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:52:19 +0200 Subject: [PATCH 2/9] Docs: Added where missing corrected where wrong Specifically the documentation for - UniformSourceIterator was incomplete it did not mention that it can change the sample type --- src/decoder/mod.rs | 12 ++++++++- src/decoder/symphonia.rs | 4 +++ src/source/crossfade.rs | 5 ++++ src/source/done.rs | 4 ++- src/source/empty.rs | 2 ++ src/source/empty_callback.rs | 6 +++++ src/source/mod.rs | 17 +++++++++++-- src/source/pausable.rs | 8 +++++- src/source/position.rs | 1 + src/source/samples_converter.rs | 9 +++---- src/source/skippable.rs | 7 +++++- src/source/spatial.rs | 1 + src/source/stoppable.rs | 3 ++- src/source/take.rs | 3 +++ src/source/uniform.rs | 10 +++++--- src/source/zero.rs | 6 ++++- src/stream.rs | 44 +++++++++++++-------------------- 17 files changed, 98 insertions(+), 44 deletions(-) diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 1ae4cbab..8a3883ee 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -23,6 +23,7 @@ mod mp3; #[cfg(feature = "symphonia")] mod read_seek_source; #[cfg(feature = "symphonia")] +/// Symphonia decoders types pub mod symphonia; #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] mod vorbis; @@ -36,11 +37,15 @@ pub struct Decoder(DecoderImpl) where R: Read + Seek; +/// Source of audio samples from decoding a file that never ends. When the +/// end of the file is reached the decoder starts again from the beginning. +/// +/// Supports MP3, WAV, Vorbis and Flac. pub struct LoopedDecoder(DecoderImpl) where R: Read + Seek; -// can not really reduce the size of the VorbisDecoder. There are not any +// Cannot really reduce the size of the VorbisDecoder. There are not any // arrays just a lot of struct fields. #[allow(clippy::large_enum_variant)] enum DecoderImpl @@ -239,6 +244,10 @@ where #[cfg(not(feature = "symphonia"))] Err(DecoderError::UnrecognizedFormat) } + + /// Builds a new looped decoder. + /// + /// Attempts to automatically detect the format of the source of data. pub fn new_looped(data: R) -> Result, DecoderError> { Self::new(data).map(LoopedDecoder::new) } @@ -329,6 +338,7 @@ where } } +#[allow(missing_docs)] // Reason: will be removed, see: #612 #[derive(Debug)] pub enum Mp4Type { Mp4, diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index bf967256..31099177 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -210,12 +210,16 @@ impl Source for SymphoniaDecoder { #[derive(Debug, thiserror::Error)] pub enum SeekError { + /// Could not get next packet while refining seek position #[error("Could not get next packet while refining seek position: {0:?}")] Refining(symphonia::core::errors::Error), + /// Format reader failed to seek #[error("Format reader failed to seek: {0:?}")] BaseSeek(symphonia::core::errors::Error), + /// Decoding failed retrying on the next packet failed #[error("Decoding failed retrying on the next packet failed: {0:?}")] Retrying(symphonia::core::errors::Error), + /// Decoding failed on multiple consecutive packets #[error("Decoding failed on multiple consecutive packets: {0:?}")] Decoding(symphonia::core::errors::Error), } diff --git a/src/source/crossfade.rs b/src/source/crossfade.rs index a8ea20c7..03c21178 100644 --- a/src/source/crossfade.rs +++ b/src/source/crossfade.rs @@ -27,6 +27,11 @@ where input_fadeout.mix(input_fadein) } +/// Mixes one sound fading out with another sound fading in for the given +/// duration. +/// +/// Only the crossfaded portion (beginning of fadeout, beginning of fadein) is +/// covered. pub type Crossfade = Mix, FadeIn>>; #[cfg(test)] diff --git a/src/source/done.rs b/src/source/done.rs index dc6d6760..152233c1 100644 --- a/src/source/done.rs +++ b/src/source/done.rs @@ -6,7 +6,7 @@ use crate::{Sample, Source}; use super::SeekError; -/// When the inner source is empty this decrements an `AtomicUsize`. +/// When the inner source is empty this decrements a `AtomicUsize`. #[derive(Debug, Clone)] pub struct Done { input: I, @@ -15,6 +15,8 @@ pub struct Done { } impl Done { + /// When the inner source is empty the AtomicUsize passed in is decremented. + /// If it was zero it will overflow negatively. #[inline] pub fn new(input: I, signal: Arc) -> Done { Done { diff --git a/src/source/empty.rs b/src/source/empty.rs index 3c8f4722..d7b12980 100644 --- a/src/source/empty.rs +++ b/src/source/empty.rs @@ -17,6 +17,8 @@ impl Default for Empty { } impl Empty { + /// An empty source that immediately ends without ever returning a sample to + /// play #[inline] pub fn new() -> Empty { Empty(PhantomData) diff --git a/src/source/empty_callback.rs b/src/source/empty_callback.rs index 1752c366..cb74adfc 100644 --- a/src/source/empty_callback.rs +++ b/src/source/empty_callback.rs @@ -13,9 +13,15 @@ pub struct EmptyCallback { impl EmptyCallback { #[inline] + /// Create an empty source which executes a callback function. + /// Example use-case: + /// + /// Detect and do something when the source before this one has ended. pub fn new(callback: Box) -> EmptyCallback { EmptyCallback { + #[allow(missing_docs)] // See: https://github.com/RustAudio/rodio/issues/615 phantom_data: PhantomData, + #[allow(missing_docs)] // See: https://github.com/RustAudio/rodio/issues/615 callback, } } diff --git a/src/source/mod.rs b/src/source/mod.rs index 1f9f40d9..dcb9f228 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -360,6 +360,9 @@ where stoppable::stoppable(self) } + /// Adds a method [`Skippable::skip`] for skipping this source. Skipping + /// makes Source::next() return None. Which in turn makes the Sink skip to + /// the next source. fn skippable(self) -> Skippable where Self: Sized, @@ -374,7 +377,7 @@ where /// in the position returned by [`get_pos`](TrackPosition::get_pos). /// /// This can get confusing when using [`get_pos()`](TrackPosition::get_pos) - /// together with [`Source::try_seek()`] as the the latter does take all + /// together with [`Source::try_seek()`] as the latter does take all /// speedup's and delay's into account. Its recommended therefore to apply /// track_position after speedup's and delay's. fn track_position(self) -> TrackPosition @@ -455,22 +458,32 @@ 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 +/// does not support seeking. #[non_exhaustive] #[derive(Debug, thiserror::Error)] pub enum SeekError { - #[error("Streaming is not supported by source: {underlying_source}")] + /// On of the underlying sources does not support seeking + #[error("Seeking is not supported by source: {underlying_source}")] NotSupported { underlying_source: &'static str }, #[cfg(feature = "symphonia")] + /// The symphonia decoder ran into an issue #[error("Error seeking: {0}")] SymphoniaDecoder(#[from] crate::decoder::symphonia::SeekError), #[cfg(feature = "wav")] #[error("Error seeking in wav source: {0}")] + /// 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 + /// Any other error probably in a custom Source #[error("An error occurred")] Other(Box), } impl SeekError { + /// Will the source remain playing at its position before the seek or is it + /// broken? pub fn source_intact(&self) -> bool { match self { SeekError::NotSupported { .. } => true, diff --git a/src/source/pausable.rs b/src/source/pausable.rs index c41dfc1a..74b20601 100644 --- a/src/source/pausable.rs +++ b/src/source/pausable.rs @@ -4,7 +4,7 @@ use crate::{Sample, Source}; use super::SeekError; -/// Internal function that builds a `Pausable` object. +/// Builds a `Pausable` object. pub fn pausable(source: I, paused: bool) -> Pausable where I: Source, @@ -22,6 +22,12 @@ where } } +/// Wraps a source and makes it pausable by calling [`Pausable::set_paused`] on +/// this object. When the source is paused it returns zero value samples. +/// +/// You can usually still use this from another source wrapping this one by +/// calling `inner_mut` on it. Similarly this provides [`Pausable::inner`] and +/// mutable/destructing variants for accessing the underlying source. #[derive(Clone, Debug)] pub struct Pausable { input: I, diff --git a/src/source/position.rs b/src/source/position.rs index b8acc414..2ab92aac 100644 --- a/src/source/position.rs +++ b/src/source/position.rs @@ -17,6 +17,7 @@ pub fn track_position(source: I) -> TrackPosition { } } +/// Tracks the elapsed duration since the start of the underlying source. #[derive(Debug)] pub struct TrackPosition { input: I, diff --git a/src/source/samples_converter.rs b/src/source/samples_converter.rs index 84beb4c8..568e01b7 100644 --- a/src/source/samples_converter.rs +++ b/src/source/samples_converter.rs @@ -6,11 +6,8 @@ use cpal::{FromSample, Sample as CpalSample}; use super::SeekError; -/// An iterator that reads from a `Source` and converts the samples to a specific rate and -/// channels count. -/// -/// It implements `Source` as well, but all the data is guaranteed to be in a single frame whose -/// channels and samples rate have been passed to `new`. +/// Wrap the input and lazily converts the samples it provides to the type +/// specified by the generic parameter D #[derive(Clone)] pub struct SamplesConverter { inner: I, @@ -18,6 +15,8 @@ pub struct SamplesConverter { } impl SamplesConverter { + /// Wrap the input and lazily converts the samples it provides to the type + /// specified by the generic parameter D #[inline] pub fn new(input: I) -> SamplesConverter { SamplesConverter { diff --git a/src/source/skippable.rs b/src/source/skippable.rs index 400a4fec..082bc016 100644 --- a/src/source/skippable.rs +++ b/src/source/skippable.rs @@ -5,7 +5,9 @@ use crate::Source; use super::SeekError; -/// Internal function that builds a `Skippable` object. +/// Wrap the source in a skippable. It allows ending the current source early by +/// calling [`Skippable::skip`]. If this source is in a queue such as the Sink +/// ending the source early is equal to skipping the source. pub fn skippable(source: I) -> Skippable { Skippable { input: source, @@ -13,6 +15,9 @@ pub fn skippable(source: I) -> Skippable { } } +/// Wrap the source in a skippable. It allows ending the current source early by +/// calling [`Skippable::skip`]. If this source is in a queue such as the Sink +/// ending the source early is equal to skipping the source. #[derive(Clone, Debug)] pub struct Skippable { input: I, diff --git a/src/source/spatial.rs b/src/source/spatial.rs index 940147bf..954e65f4 100644 --- a/src/source/spatial.rs +++ b/src/source/spatial.rs @@ -29,6 +29,7 @@ where I: Source, I::Item: Sample, { + /// Builds a new `SpatialSink`, beginning playback on a stream. pub fn new( input: I, emitter_position: [f32; 3], diff --git a/src/source/stoppable.rs b/src/source/stoppable.rs index 71ba8d2f..7cadd1c2 100644 --- a/src/source/stoppable.rs +++ b/src/source/stoppable.rs @@ -4,7 +4,7 @@ use crate::{Sample, Source}; use super::SeekError; -/// Internal function that builds a `Stoppable` object. +/// This is the same as [`skippable`](crate::source::skippable) see its docs pub fn stoppable(source: I) -> Stoppable { Stoppable { input: source, @@ -12,6 +12,7 @@ pub fn stoppable(source: I) -> Stoppable { } } +/// This is the same as [`Skippable`](crate::source::Skippable) see its docs #[derive(Clone, Debug)] pub struct Stoppable { input: I, diff --git a/src/source/take.rs b/src/source/take.rs index 980b6920..c6196994 100644 --- a/src/source/take.rs +++ b/src/source/take.rs @@ -91,10 +91,13 @@ where self.input } + /// Make the truncated source end with a FadeOut. The fadeout covers the + /// entire length of the take source. pub fn set_filter_fadeout(&mut self) { self.filter = Some(DurationFilter::FadeOut); } + /// Remove any filter set. pub fn clear_filter(&mut self) { self.filter = None; } diff --git a/src/source/uniform.rs b/src/source/uniform.rs index fcaee5c5..b9d146d1 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -8,11 +8,11 @@ use crate::{Sample, Source}; use super::SeekError; -/// An iterator that reads from a `Source` and converts the samples to a specific rate and -/// channels count. +/// An iterator that reads from a `Source` and converts the samples to a +/// specific type, sample-rate and channels count. /// -/// It implements `Source` as well, but all the data is guaranteed to be in a single frame whose -/// channels and samples rate have been passed to `new`. +/// It implements `Source` as well, but all the data is guaranteed to be in a +/// single frame whose channels and samples rate have been passed to `new`. #[derive(Clone)] pub struct UniformSourceIterator where @@ -32,6 +32,8 @@ where I::Item: Sample, D: Sample, { + /// Wrap a `Source` and lazily convert its samples to a specific type, + /// sample-rate and channels count. #[inline] pub fn new( input: I, diff --git a/src/source/zero.rs b/src/source/zero.rs index 5337a90c..82c38d66 100644 --- a/src/source/zero.rs +++ b/src/source/zero.rs @@ -5,7 +5,9 @@ use crate::{Sample, Source}; use super::SeekError; -/// An infinite source that produces zero. +/// An source that produces samples with value zero (silence). Depending on if +/// it where created with [`Zero::new`] or [`Zero::new_samples`] it can be never +/// ending or finite. #[derive(Clone, Debug)] pub struct Zero { channels: u16, @@ -15,6 +17,7 @@ pub struct Zero { } impl Zero { + /// Create a new source that never ends and produces total silence. #[inline] pub fn new(channels: u16, sample_rate: u32) -> Zero { Zero { @@ -24,6 +27,7 @@ impl Zero { marker: PhantomData, } } + /// Create a new source that never ends and produces total silence. #[inline] pub fn new_samples(channels: u16, sample_rate: u32, num_samples: usize) -> Zero { Zero { diff --git a/src/stream.rs b/src/stream.rs index bdd0eef1..f81da3d5 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -29,7 +29,9 @@ impl OutputStream { pub fn try_from_device( device: &cpal::Device, ) -> Result<(Self, OutputStreamHandle), StreamError> { - let default_config = device.default_output_config()?; + let default_config = device + .default_output_config() + .map_err(StreamError::DefaultStreamConfigError)?; OutputStream::try_from_device_config(device, default_config) } @@ -42,7 +44,7 @@ impl OutputStream { config: SupportedStreamConfig, ) -> Result<(Self, OutputStreamHandle), StreamError> { let (mixer, _stream) = device.try_new_output_stream_config(config)?; - _stream.play()?; + _stream.play().map_err(StreamError::PlayStreamError)?; let out = Self { mixer, _stream }; let handle = OutputStreamHandle { mixer: Arc::downgrade(&out.mixer), @@ -130,39 +132,24 @@ impl error::Error for PlayError { } } +/// Errors that might occur when interfacing with audio output. #[derive(Debug)] 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 DefaultStreamConfigError(cpal::DefaultStreamConfigError), + /// 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]. SupportedStreamConfigsError(cpal::SupportedStreamConfigsError), + /// Could not find any output device NoDevice, } -impl From for StreamError { - fn from(err: cpal::DefaultStreamConfigError) -> Self { - Self::DefaultStreamConfigError(err) - } -} - -impl From for StreamError { - fn from(err: cpal::SupportedStreamConfigsError) -> Self { - Self::SupportedStreamConfigsError(err) - } -} - -impl From for StreamError { - fn from(err: cpal::BuildStreamError) -> Self { - Self::BuildStreamError(err) - } -} - -impl From for StreamError { - fn from(err: cpal::PlayStreamError) -> Self { - Self::PlayStreamError(err) - } -} - impl fmt::Display for StreamError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { @@ -342,7 +329,10 @@ fn supported_output_formats( ) -> Result, StreamError> { const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); - let mut supported: Vec<_> = device.supported_output_configs()?.collect(); + 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 e116660568974eb1b4038aef224df254abb791b4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 23:45:24 +0200 Subject: [PATCH 3/9] docs: add missing + fix docs in wrong place --- src/decoder/symphonia.rs | 3 ++- src/source/blt.rs | 1 + src/source/channel_volume.rs | 7 +++++-- src/source/empty_callback.rs | 4 ++-- src/source/mod.rs | 5 ++++- src/source/stoppable.rs | 4 ++-- 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index 31099177..06380c7a 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -208,6 +208,7 @@ impl Source for SymphoniaDecoder { } } +/// Error returned when the try_seek implementation of the symphonia decoder fails. #[derive(Debug, thiserror::Error)] pub enum SeekError { /// Could not get next packet while refining seek position @@ -225,7 +226,7 @@ pub enum SeekError { } impl SymphoniaDecoder { - /// note frame offset must be set after + /// Note frame offset must be set after fn refine_position(&mut self, seek_res: SeekedTo) -> Result<(), source::SeekError> { let mut samples_to_pass = seek_res.required_ts - seek_res.actual_ts; let packet = loop { diff --git a/src/source/blt.rs b/src/source/blt.rs index 853b6974..c7be7310 100644 --- a/src/source/blt.rs +++ b/src/source/blt.rs @@ -54,6 +54,7 @@ where } } +/// This applies an audio filter, it can be a high or low pass filter. #[derive(Clone, Debug)] pub struct BltFilter { input: I, diff --git a/src/source/channel_volume.rs b/src/source/channel_volume.rs index 30962113..89412372 100644 --- a/src/source/channel_volume.rs +++ b/src/source/channel_volume.rs @@ -25,6 +25,9 @@ where I: Source, I::Item: Sample, { + /// Wrap the input source and make it mono. Play that mono sound to each + /// channel at the volume set by the user. The volume can be changed using + /// [`ChannelVolume::set_volume`]. pub fn new(mut input: I, channel_volumes: Vec) -> ChannelVolume where I: Source, @@ -48,8 +51,8 @@ where } } - /// Sets the volume for a given channel number. Will panic if channel number - /// was invalid. + /// Sets the volume for a given channel number. Will panic if channel number + /// is invalid. pub fn set_volume(&mut self, channel: usize, volume: f32) { self.channel_volumes[channel] = volume; } diff --git a/src/source/empty_callback.rs b/src/source/empty_callback.rs index cb74adfc..ad1c66b8 100644 --- a/src/source/empty_callback.rs +++ b/src/source/empty_callback.rs @@ -7,7 +7,9 @@ use super::SeekError; /// An empty source which executes a callback function pub struct EmptyCallback { + #[allow(missing_docs)] // See: https://github.com/RustAudio/rodio/issues/615 pub phantom_data: PhantomData, + #[allow(missing_docs)] // See: https://github.com/RustAudio/rodio/issues/615 pub callback: Box, } @@ -19,9 +21,7 @@ impl EmptyCallback { /// Detect and do something when the source before this one has ended. pub fn new(callback: Box) -> EmptyCallback { EmptyCallback { - #[allow(missing_docs)] // See: https://github.com/RustAudio/rodio/issues/615 phantom_data: PhantomData, - #[allow(missing_docs)] // See: https://github.com/RustAudio/rodio/issues/615 callback, } } diff --git a/src/source/mod.rs b/src/source/mod.rs index dcb9f228..42c43ed6 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -465,7 +465,10 @@ where pub enum SeekError { /// On of the underlying sources does not support seeking #[error("Seeking is not supported by source: {underlying_source}")] - NotSupported { underlying_source: &'static str }, + NotSupported { + /// The source that did not support seek + underlying_source: &'static str, + }, #[cfg(feature = "symphonia")] /// The symphonia decoder ran into an issue #[error("Error seeking: {0}")] diff --git a/src/source/stoppable.rs b/src/source/stoppable.rs index 7cadd1c2..88078706 100644 --- a/src/source/stoppable.rs +++ b/src/source/stoppable.rs @@ -4,7 +4,7 @@ use crate::{Sample, Source}; use super::SeekError; -/// This is the same as [`skippable`](crate::source::skippable) see its docs +/// This is the same as [`skippable`](crate::source::skippable) see its docs pub fn stoppable(source: I) -> Stoppable { Stoppable { input: source, @@ -12,7 +12,7 @@ pub fn stoppable(source: I) -> Stoppable { } } -/// This is the same as [`Skippable`](crate::source::Skippable) see its docs +/// This is the same as [`Skippable`](crate::source::Skippable) see its docs #[derive(Clone, Debug)] pub struct Stoppable { input: I, From 58b61f66ffa138aadfcf103e15c31fd546132bbd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2024 23:35:06 +0200 Subject: [PATCH 4/9] Doc/Api note that a returning None means the end of the sound --- src/source/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/source/mod.rs b/src/source/mod.rs index 42c43ed6..1e85270b 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -114,7 +114,8 @@ mod zero; /// - The number of channels can be retrieved with `channels`. /// - The frequency can be retrieved with `sample_rate`. /// - The list of values can be retrieved by iterating on the source. The `Source` trait requires -/// that the `Iterator` trait be implemented as well. +/// that the `Iterator` trait be implemented as well. When a `Source` returns None the +/// sound has ended. /// /// # Frames /// From 1a7b8e5e8fdc8777bc97360d29e52103b0e0dd49 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 28 Sep 2024 02:41:44 +0200 Subject: [PATCH 5/9] adds optional tracing --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index aa75167a..087fa561 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,10 +17,13 @@ lewton = { version = "0.10", optional = true } minimp3_fixed = { version = "0.5.4", optional = true} symphonia = { version = "0.5.4", optional = true, default-features = false } crossbeam-channel = { version = "0.5.8", optional = true } + thiserror = "1.0.49" +tracing = { version = "0.1.40", optional = true } [features] default = ["flac", "vorbis", "wav", "mp3"] +tracing = ["dep:tracing"] flac = ["claxon"] vorbis = ["lewton"] From 95a466e03a7997ce91b114dc1062e48c5a306756 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 28 Sep 2024 02:44:58 +0200 Subject: [PATCH 6/9] use tracing (if enabled) for stream error + sink fmt --- src/sink.rs | 2 +- src/stream.rs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/sink.rs b/src/sink.rs index 71e278dc..148a1588 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -131,7 +131,7 @@ impl Sink { .periodic_access(Duration::from_millis(5), move |src| { if controls.stopped.load(Ordering::SeqCst) { src.stop(); - *controls.position.lock().unwrap() = Duration::ZERO; + *controls.position.lock().unwrap() = Duration::ZERO; } { let mut to_clear = controls.to_clear.lock().unwrap(); diff --git a/src/stream.rs b/src/stream.rs index f81da3d5..dd491244 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -195,7 +195,12 @@ impl CpalDeviceExt for cpal::Device { let (mixer_tx, mut mixer_rx) = dynamic_mixer::mixer::(format.channels(), format.sample_rate().0); - let error_callback = |err| eprintln!("an error occurred on output stream: {err}"); + let error_callback = |err| { + #[cfg(feature = "tracing")] + tracing::error!("an error occurred on output stream: {err}"); + #[cfg(not(feature = "tracing"))] + eprintln!("an error occurred on output stream: {err}"); + }; match format.sample_format() { cpal::SampleFormat::F32 => self.build_output_stream::( From a4e12d09b3e06ae09d8a761c49e28f8d76fcad8f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 28 Sep 2024 13:59:15 +0200 Subject: [PATCH 7/9] add section on features, decribe tracing feature --- src/lib.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 6f6c505b..8052b64c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,6 +101,15 @@ //! to avoid adding extra crates to your binary. //! See the [available feature flags](https://docs.rs/crate/rodio/latest/features) for all options. //! +//! ## Optional Features +//! +//! Rodio provides several optional features that are guarded with feature gates. +//! +//! ### Feature "tracing" +//! +//! The "tracing" feature replaces the print to stderr when a stream error happens with a +//! recording an error event with tracing. +//! //! ## How it works under the hood //! //! Rodio spawns a background thread that is dedicated to reading from the sources and sending From 97befac520781e72b7af8b81c524235db201ecc6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 29 Sep 2024 01:19:36 +0200 Subject: [PATCH 8/9] Adds benchmarks for effects and type conversions Benchmarks use the music.wav file, we use *divan* as benchmark harnass. The time needed to load the wav file is excluded from the benchmark by preparing the data into a special test Source. That source also enables converting between formats. In the future *divan* will add support for structured (json) output. Then we could integrate with the bencher service to generate benchmark reports for all PR's and keep a timeseries of performance. --- Cargo.toml | 10 +++++ benches/conversions.rs | 21 +++++++++++ benches/effects.rs | 48 ++++++++++++++++++++++++ benches/shared.rs | 83 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 benches/conversions.rs create mode 100644 benches/effects.rs create mode 100644 benches/shared.rs diff --git a/Cargo.toml b/Cargo.toml index 087fa561..1a0035bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,16 @@ quickcheck = "0.9.2" rstest = "0.18.2" rstest_reuse = "0.6.0" approx = "0.5.1" +dasp_sample = "0.11.0" +divan = "0.1.14" + +[[bench]] +name = "effects" +harness = false + +[[bench]] +name = "conversions" +harness = false [[example]] name = "music_m4a" diff --git a/benches/conversions.rs b/benches/conversions.rs new file mode 100644 index 00000000..b5c89d19 --- /dev/null +++ b/benches/conversions.rs @@ -0,0 +1,21 @@ +use cpal::FromSample; +use divan::Bencher; +use rodio::Source; + +mod shared; +use shared::TestSource; + +fn main() { + divan::main(); +} + +#[divan::bench(types = [i16, u16, f32])] +fn from_i16_to>(bencher: Bencher) { + bencher + .with_inputs(|| TestSource::music_wav()) + .bench_values(|source| { + source + .convert_samples::() + .for_each(divan::black_box_drop) + }) +} diff --git a/benches/effects.rs b/benches/effects.rs new file mode 100644 index 00000000..5f100112 --- /dev/null +++ b/benches/effects.rs @@ -0,0 +1,48 @@ +use std::time::Duration; + +use divan::Bencher; +use rodio::Source; + +mod shared; +use shared::TestSource; + +fn main() { + divan::main(); +} + +#[divan::bench] +fn reverb(bencher: Bencher) { + bencher + .with_inputs(|| TestSource::music_wav()) + .bench_values(|source| { + source + .buffered() + .reverb(Duration::from_secs_f32(0.05), 0.3) + .for_each(divan::black_box_drop) + }) +} + +#[divan::bench] +fn high_pass(bencher: Bencher) { + bencher + .with_inputs(|| TestSource::music_wav().to_f32s()) + .bench_values(|source| source.high_pass(200).for_each(divan::black_box_drop)) +} + +#[divan::bench] +fn fade_out(bencher: Bencher) { + bencher + .with_inputs(|| TestSource::music_wav()) + .bench_values(|source| { + source + .fade_out(Duration::from_secs(5)) + .for_each(divan::black_box_drop) + }) +} + +#[divan::bench] +fn amplify(bencher: Bencher) { + bencher + .with_inputs(|| TestSource::music_wav().to_f32s()) + .bench_values(|source| source.amplify(0.8).for_each(divan::black_box_drop)) +} diff --git a/benches/shared.rs b/benches/shared.rs new file mode 100644 index 00000000..0feff6a4 --- /dev/null +++ b/benches/shared.rs @@ -0,0 +1,83 @@ +use std::io::Cursor; +use std::time::Duration; +use std::vec; + +use rodio::Source; + +pub struct TestSource { + samples: vec::IntoIter, + channels: u16, + sample_rate: u32, + total_duration: Duration, +} + +impl Iterator for TestSource { + type Item = T; + + fn next(&mut self) -> Option { + self.samples.next() + } +} + +impl ExactSizeIterator for TestSource { + fn len(&self) -> usize { + self.samples.len() + } +} + +impl Source for TestSource { + fn current_frame_len(&self) -> Option { + None // forever + } + + fn channels(&self) -> u16 { + self.channels + } + + fn sample_rate(&self) -> u32 { + self.sample_rate + } + + fn total_duration(&self) -> Option { + Some(self.total_duration) + } +} + +impl TestSource { + pub fn music_wav() -> Self { + let data = include_bytes!("../assets/music.wav"); + let cursor = Cursor::new(data); + + let duration = Duration::from_secs(10); + let sound = rodio::Decoder::new(cursor) + .expect("music.wav is correctly encoded & wav is supported") + .take_duration(duration); + + TestSource { + channels: sound.channels(), + sample_rate: sound.sample_rate(), + total_duration: duration, + samples: sound.into_iter().collect::>().into_iter(), + } + } + + #[allow(unused, reason = "not everything from shared is used in all libs")] + pub fn to_f32s(self) -> TestSource { + let TestSource { + samples, + channels, + sample_rate, + total_duration, + } = self; + let samples = samples + .map(|s| cpal::Sample::from_sample(s)) + .collect::>() + .into_iter(); + TestSource { + samples, + channels, + sample_rate, + total_duration, + } + } +} From 2e5fc2e312e571823cf3a7f5b3158096abbdf190 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 29 Sep 2024 02:00:59 +0200 Subject: [PATCH 9/9] fmt --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8052b64c..bcb05fcb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,7 +101,7 @@ //! to avoid adding extra crates to your binary. //! See the [available feature flags](https://docs.rs/crate/rodio/latest/features) for all options. //! -//! ## Optional Features +//! ## Optional Features //! //! Rodio provides several optional features that are guarded with feature gates. //!