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| {