diff --git a/README.md b/README.md index ccc96fea..8586483f 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,14 @@ [![Crates.io Downloads](https://img.shields.io/crates/d/rodio.svg)](https://crates.io/crates/rodio) [![Build Status](https://github.com/RustAudio/rodio/workflows/CI/badge.svg)](https://github.com/RustAudio/rodio/actions) +> [!WARNING] +> We are currently rewriting Rodio's core audio engine. The current state should +> be working but under documented. This is expected to take at least a month. +> For the latest unreleased work pre engine rewrite see [this +> checkout](https://github.com/RustAudio/rodio/tree/c5f1e94290922fe4ee963ce6f85754ea6ba36243). +> To follow our progress see the [tracking +> issue](https://github.com/RustAudio/rodio/issues/901) + Rust playback library. Playback is handled by [cpal](https://github.com/RustAudio/cpal). Format decoding is handled by [Symphonia](https://github.com/pdeljanov/Symphonia) by default, or by optional format-specific decoders: diff --git a/src/const_source.rs b/src/const_source.rs new file mode 100644 index 00000000..5b6c04de --- /dev/null +++ b/src/const_source.rs @@ -0,0 +1,147 @@ +//! A sound source that has it's sample rate and number of channels fixed at compile time. +//! Some practical examples: +//! - An effect performed by a neural network trained on a specific channel +//! count and sample rate +//! - A custom audio source streaming in at a pre-determined fixed sample rate. +//! We use this in our VoIP example. +//! +//! A const pipeline _may_ also be optimized more by the compiler as many +//! branches are known at compile time, like when converting from one channel +//! count to another. +use std::num::NonZeroU16; +use std::num::NonZeroU32; +use std::time::Duration; + +use crate::ChannelCount; +use crate::FixedSource; +use crate::Sample; +use crate::SampleRate; +use crate::Source as DynamicSource; // will be renamed to this upstream + +/// A source which sample rate and channel count are fixed at compile time. +pub trait ConstSource: Iterator { + /// Returns the sample rate which is the first generic (SR) of a ConstSource + fn sample_rate(&self) -> SampleRate { + const { NonZeroU32::new(SR).expect("SampleRate must be > 0") } + } + /// Returns the channel count which is the second generic (CH) of a ConstSource + fn channels(&self) -> ChannelCount { + const { NonZeroU16::new(CH).expect("Channel count must be > 0") } + } + + #[doc = include_str!("docs/total_duration.md")] + fn total_duration(&self) -> Option; + + /// Use this const source as if it's a dynamic source. You generally do not + /// want to do this since there are less effects for dynamic sources and + /// those that are available can not be implemented as efficient. This is + /// therefore purely provided for backwards compatibility. + fn into_dynamic_source(self) -> IntoDynamicSource + where + Self: Sized, + { + IntoDynamicSource { inner: self } + } + + /// Use this const source as if it's a fixed source which is generally + /// easier to work with since it drops the generics. The same effects are + /// available for both. + /// + /// # Example + /// + /// ```rust + /// struct CustomEffect(S); + /// + /// fn apply_custom_effect(source: S) -> CustomEffect { + /// CustomEffect(source) + /// } + /// + /// let source = Silence() + /// ``` + fn into_fixed_source(self) -> IntoDynamicSource + where + Self: Sized, + { + IntoDynamicSource { inner: self } + } +} + +/// A `DynamicSource` converted from a `ConstSource`. Useful for passing to old +/// APIs that do not accept a `ConstSource` nor `FixedSource`. +#[derive(Clone)] +pub struct IntoDynamicSource +where + S: ConstSource, +{ + inner: S, +} + +impl Iterator for IntoDynamicSource +where + S: ConstSource, +{ + type Item = Sample; + + fn next(&mut self) -> Option { + self.inner.next() + } +} + +impl DynamicSource for IntoDynamicSource +where + S: ConstSource, +{ + fn current_span_len(&self) -> Option { + None + } + + fn channels(&self) -> ChannelCount { + const { NonZeroU16::new(CH).expect("channel count must be larger then zero") } + } + + fn sample_rate(&self) -> SampleRate { + const { NonZeroU32::new(SR).expect("sample rate must be larger then zero") } + } + + fn total_duration(&self) -> Option { + self.inner.total_duration() + } +} + +/// A `FixedSource` converted from a `ConstSource`. Useful for passing to APIs +/// that do not accept a `ConstSource`. +#[derive(Clone)] +pub struct IntoFixedSource +where + S: ConstSource, +{ + inner: S, +} + +impl Iterator for IntoFixedSource +where + S: ConstSource, +{ + type Item = Sample; + + fn next(&mut self) -> Option { + self.inner.next() + } +} + +impl FixedSource for IntoFixedSource +where + S: ConstSource, +{ + fn channels(&self) -> ChannelCount { + const { NonZeroU16::new(CH).expect("channel count must be larger then zero") } + } + + fn sample_rate(&self) -> SampleRate { + const { NonZeroU32::new(SR).expect("sample rate must be larger then zero") } + } + + fn total_duration(&self) -> Option { + self.inner.total_duration() + } +} diff --git a/src/docs/total_duration.md b/src/docs/total_duration.md new file mode 100644 index 00000000..e05826cd --- /dev/null +++ b/src/docs/total_duration.md @@ -0,0 +1,6 @@ +Returns the total duration of this source, if known. + +`None` indicates at the same time "infinite" or "unknown". + +Note this value is free to change. For example if you removed an item from a +queue the total_duration lowers. diff --git a/src/fixed_source.rs b/src/fixed_source.rs index 6cc4ffee..8ed63609 100644 --- a/src/fixed_source.rs +++ b/src/fixed_source.rs @@ -2,7 +2,7 @@ //! channel count. use std::time::Duration; -use crate::{ChannelCount, Sample, SampleRate}; +use crate::{ChannelCount, ConstSource, Sample, SampleRate}; /// Similar to `Source`, something that can produce interleaved samples for a /// fixed amount of channels at a fixed sample rate. Those parameters never @@ -16,4 +16,109 @@ pub trait FixedSource: Iterator { /// /// `None` indicates at the same time "infinite" or "unknown". fn total_duration(&self) -> Option; + + /// Tries to convert from a fixed source to a const one assuming + /// the parameters already match. If they do not this returns an error. + /// + /// If the parameters do not match you can resample using: + /// [`with_sample_rate`](Self::with_sample_rate) and + /// [`with_channel_count`](Self::with_channel_count). + fn try_into_const_source( + self, + ) -> Result, ParameterMismatch> + where + Self: Sized, + { + if self.channels().get() != CH || self.sample_rate().get() != SR { + Err(ParameterMismatch { + sample_rate: self.sample_rate(), + channel_count: self.channels(), + }) + } else { + Ok(IntoConstSource(self)) + } + } + + /// Use this fixed source as if it's a dynamic source. You generally do not + /// want to do this since there are less effects for dynamic sources and + /// those that are available can not be implemented as efficient. This is + /// therefore purely provided for backwards compatibility. + fn into_dynamic_source(self) -> IntoDynamicSource + where + Self: Sized, + { + IntoDynamicSource(self) + } +} + +/// A [`ConstSource`] adapted from a [`FixedSource`]. +pub struct IntoConstSource(S); + +impl ConstSource + for IntoConstSource +{ + fn total_duration(&self) -> Option { + self.0.total_duration() + } +} + +impl Iterator for IntoConstSource { + type Item = Sample; + + fn next(&mut self) -> Option { + self.0.next() + } +} + +/// Error that occurs when a [`FixedSource`] can not be converted into a +/// [`ConstSource`] with a certain sample rate and channel count. +#[derive(Debug)] +pub struct ParameterMismatch { + sample_rate: SampleRate, + channel_count: ChannelCount, +} + +impl std::error::Error for ParameterMismatch {} + +impl std::fmt::Display for ParameterMismatch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.sample_rate.get() == SR && self.channel_count.get() == CH { + unreachable!("ParameterMismatch error can only occur when params mismatch"); + } else if self.sample_rate.get() == SR && self.channel_count.get() != CH { + f.write_fmt(format_args!("Fixed source's channel count: {}, does not match target const source's channel count: {}", self.channel_count.get(), CH)) + } else if self.sample_rate.get() != SR && self.channel_count.get() != CH { + f.write_fmt(format_args!("Fixed source's sample rate and channel count ({}, {}) do not match target const source's sample rate and channel count ({} {})", self.sample_rate.get(), self.channel_count.get(), SR, CH)) + } else { + f.write_fmt(format_args!("Fixed source's sample rate : {}, does not match target const source's sample rate: {}", self.sample_rate.get(), SR)) + } + } +} + +/// A [`DynamicSource`] adapted from a [`FixedSource`]. +pub struct IntoDynamicSource(S); + +impl crate::DynamicSource for IntoDynamicSource { + fn current_span_len(&self) -> Option { + None + } + + fn channels(&self) -> ChannelCount { + self.0.channels() + } + + fn sample_rate(&self) -> SampleRate { + self.0.sample_rate() + } + + fn total_duration(&self) -> Option { + self.0.total_duration() + } +} + +impl Iterator for IntoDynamicSource { + type Item = crate::Sample; + + fn next(&mut self) -> Option { + self.0.next() + } } diff --git a/src/generators.rs b/src/generators.rs new file mode 100644 index 00000000..c0fed4a5 --- /dev/null +++ b/src/generators.rs @@ -0,0 +1,15 @@ +//! Sources that produce sound without consuming anything + +// Note we make everything be it effects or generators available under / + +mod silence; + +/// Generators with both the sample rate and channel count known at compile time. +pub mod const_source { + pub use super::silence::const_source::Silence; +} + +/// Generators where the sample rate and channel may be passed in at runtime +pub mod fixed_source { + pub use super::silence::fixed_source::Silence; +} diff --git a/src/generators/silence.rs b/src/generators/silence.rs new file mode 100644 index 00000000..6e91cd84 --- /dev/null +++ b/src/generators/silence.rs @@ -0,0 +1,121 @@ +pub mod fixed_source { + use crate::{ChannelCount, SampleRate}; + use crate::{FixedSource, nz}; + + /// A source producing an infinite amount of Silence. Like all generators you + /// probably want to limit the duration of this source. + /// + /// # Example + /// Padding a [`TakeDuration`](crate::effects::fixed_source::TakeDuration) to + /// guarantee an exact playtime: + /// + /// ```rust,no_run + /// # use std::time::Duration; + /// # use rodio_experiments::nz; + /// # use rodio_experiments::generators::fixed_source::function::SineWave; + /// # use rodio_experiments::generators::fixed_source::Silence; + /// # use rodio_experiments::ConstSource; + /// # use rodio_experiments::fixed_source::FixedSourceExt; + /// # fn main() -> Result<(), Box> { + /// # let unknown_length = SineWave::new(440.0, nz!(44100)); + /// let silence = Silence::new(nz!(44100)); + /// let two_seconds = unknown_length + /// .try_chain_source(silence)? + /// .take_duration(Duration::from_secs(2)); + /// # Ok(()) + /// # } + /// ``` + pub struct Silence { + sample_rate: SampleRate, + } + + impl Silence { + /// Create an infinite silence + pub fn new(sample_rate: SampleRate) -> Self { + Self { sample_rate } + } + } + + impl FixedSource for Silence { + fn channels(&self) -> ChannelCount { + nz!(1) + } + + fn sample_rate(&self) -> SampleRate { + self.sample_rate + } + + fn total_duration(&self) -> Option { + None + } + } + + impl Iterator for Silence { + type Item = crate::Sample; + + fn next(&mut self) -> Option { + Some(0.0) + } + + fn size_hint(&self) -> (usize, Option) { + (usize::MAX, None) + } + } +} + +pub mod const_source { + use crate::ConstSource; + + /// A source producing an infinite amount of Silence. Like all generators you + /// probably want to limit the duration of this source. + /// + /// # Example + /// Padding a [`TakeDuration`](crate::effects::const_source::TakeDuration) to + /// guarantee an exact playtime: + /// + /// ```rust,no_run + /// # use std::time::Duration; + /// # use rodio_experiments::nz; + /// # use rodio_experiments::generators::const_source::function::SineWave; + /// # use rodio_experiments::generators::const_source::Silence; + /// # use rodio_experiments::ConstSource; + /// # use rodio_experiments::fixed_source::FixedSourceExt; + /// # let unknown_length = SineWave::new(440.0); + /// let silence: Silence<44100> = Silence::new(); + /// let two_seconds = unknown_length + /// .chain_source(silence) + /// .take_duration(Duration::from_secs(2)); + /// ``` + pub struct Silence; + + impl Silence { + /// Create an infinite silence + pub fn new() -> Self { + Self + } + } + + impl Default for Silence { + fn default() -> Self { + Self + } + } + + impl ConstSource for Silence { + fn total_duration(&self) -> Option { + None + } + } + + impl Iterator for Silence { + type Item = crate::Sample; + + fn next(&mut self) -> Option { + Some(0.0) + } + + fn size_hint(&self) -> (usize, Option) { + (usize::MAX, None) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index dbd6174c..7225b799 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -226,8 +226,12 @@ mod wav_output; pub mod buffer; pub mod conversions; pub mod decoder; -#[cfg(feature = "experimental")] + pub mod fixed_source; +pub mod const_source; + +pub mod generators; + pub mod math; #[cfg(feature = "recording")] /// Microphone input support for audio recording. @@ -239,8 +243,8 @@ pub mod static_buffer; pub use crate::common::{BitDepth, ChannelCount, Float, Sample, SampleRate, DEFAULT_SAMPLE_RATE}; pub use crate::decoder::Decoder; -#[cfg(feature = "experimental")] pub use crate::fixed_source::FixedSource; +pub use crate::const_source::ConstSource; pub use crate::player::Player; pub use crate::source::Source; pub use crate::spatial_player::SpatialPlayer; @@ -250,3 +254,5 @@ pub use crate::stream::{play, DeviceSinkBuilder, DeviceSinkError, MixerDeviceSin pub use crate::wav_output::wav_to_file; #[cfg(feature = "wav_output")] pub use crate::wav_output::wav_to_writer; + +pub use Source as DynamicSource; diff --git a/src/source/mod.rs b/src/source/mod.rs index b8ea8553..22257890 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -217,9 +217,7 @@ pub trait Source: Iterator { /// Returns the rate at which the source should be played. In number of samples per second. fn sample_rate(&self) -> SampleRate; - /// Returns the total duration of this source, if known. - /// - /// `None` indicates at the same time "infinite" or "unknown". + #[doc = include_str!("../docs/total_duration.md")] fn total_duration(&self) -> Option; /// Stores the source in a buffer in addition to returning it. This iterator can be cloned.