diff --git a/CHANGELOG.md b/CHANGELOG.md index f2db5982..5bdab1f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Adds a function to write a `Source` to a `wav` file, see `output_to_wav`. - Output audio stream buffer size can now be adjusted. +- A `ChannelRouterSource` that can mix, re-order and extract channels from a + multi-channel source. + - Several `Source` trait helper functions including `extract_channel`, + `extract_channels`, `mono`, `mono_to_stereo`, and `downmix_51`. + - A `ChannelRouterController` type to modify the `ChannelRouterSource` + across thread boundaries. - Sources for directly generating square waves, triangle waves, square waves, and sawtooths have been added. - An interface for defining `SignalGenerator` patterns with an `fn`, see diff --git a/Cargo.toml b/Cargo.toml index 4e756567..b529bfce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,10 @@ required-features = ["symphonia-isomp4", "symphonia-aac"] name = "noise_generator" required-features = ["noise"] +[[example]] +name = "channel_routing" + [[example]] name = "into_file" required-features = ["wav", "mp3"] + diff --git a/examples/channel_routing.rs b/examples/channel_routing.rs new file mode 100644 index 00000000..a1624c4f --- /dev/null +++ b/examples/channel_routing.rs @@ -0,0 +1,45 @@ +//! Channel router example + +use std::io::Read; +use std::{error::Error, io}; + +fn main() -> Result<(), Box> { + use rodio::source::{Function, SignalGenerator, Source}; + + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; + + let sample_rate: u32 = 48000; + + let (controller, router) = SignalGenerator::new(sample_rate, 440.0, Function::Triangle) + .amplify(0.1) + .channel_router(2, &vec![]); + + println!("Control left and right levels separately:"); + println!("q: left+\na: left-\nw: right+\ns: right-\nx: quit"); + + stream_handle.mixer().add(router); + + let (mut left_level, mut right_level) = (0.5f32, 0.5f32); + controller.set_map(&vec![(0, 0, left_level), (0, 1, right_level)])?; + println!("Left: {left_level:.04}, Right: {right_level:.04}"); + + let bytes = io::stdin().bytes(); + for chr in bytes { + match chr.unwrap() { + b'q' => left_level += 0.1, + b'a' => left_level -= 0.1, + b'w' => right_level += 0.1, + b's' => right_level -= 0.1, + b'x' => break, + b'\n' => { + left_level = left_level.clamp(0.0, 1.0); + right_level = right_level.clamp(0.0, 1.0); + controller.set_map(&vec![(0, 0, left_level), (0, 1, right_level)])?; + println!("Left: {left_level:.04}, Right: {right_level:.04}"); + } + _ => continue, + } + } + + Ok(()) +} diff --git a/src/mixer.rs b/src/mixer.rs index b1e58f42..c0c5d7d4 100644 --- a/src/mixer.rs +++ b/src/mixer.rs @@ -7,7 +7,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; -/// Builds a new mixer. +/// Builds a new mixer that sums corresponding channels from incoming streams. /// /// You can choose the characteristics of the output thanks to this constructor. All the sounds /// added to the mixer will be converted to these values. @@ -116,7 +116,7 @@ where // let mut encounterd_err = None; // // for source in &mut self.current_sources { - // let pos = /* source.playback_pos() */ todo!(); + // let pos = /* source.playback_pos() */ // if let Err(e) = source.try_seek(pos) { // encounterd_err = Some(e); // break; diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs new file mode 100644 index 00000000..df3eebf3 --- /dev/null +++ b/src/source/channel_router.rs @@ -0,0 +1,312 @@ +// Channel router types and implementation. + +use crate::{ChannelCount, Sample, Source}; +use dasp_sample::Sample as DaspSample; +use std::fmt::{Debug, Formatter}; +use std::{ + error::Error, + fmt, + sync::mpsc::{channel, Receiver, Sender}, +}; + +/// Weighted connection between an input and an output channel. +/// (source_channel, target_channel, gain) +// Alternatively this can be a struct but map construction becomes more verbose. +pub type ChannelLink = (ChannelCount, ChannelCount, f32); + +/// An input channels to output channels mapping. +pub type ChannelMap = Vec; + +/// Function that builds a [`ChannelMixer`] object. +/// The new `ChannelMixer` will read samples from `input` and will mix and map them according +/// to `channel_mappings` into its output samples. +pub fn channel_mixer( + input: I, + out_channels_count: u16, + channel_map: &ChannelMap, +) -> (ChannelMixer, ChannelMixerSource) +where + I: Source, + I::Item: Sample, +{ + let (tx, rx) = channel(); + let controller = ChannelMixer { + sender: tx, + out_channels_count, + }; + let source = ChannelMixerSource { + input, + channel_map: vec![], + // this will cause the input buffer to fill on first call to next() + current_out_channel: out_channels_count, + out_channel_count: out_channels_count, + // I::Item::zero_value() zero value is not 0 for some sample types, + // so have to use an option. + output_frame: vec![None; out_channels_count.into()], + receiver: rx, + }; + // TODO Return an error here? Requires to change API. Alternatively, + // map can be set separately, or this can panic. + controller + .set_map(channel_map) + .expect("set channel mixer map"); + + (controller, source) +} + +/// `ChannelRouterController::map()` returns this error if the router source has been dropped. +#[derive(Debug, Eq, PartialEq)] +pub enum ChannelMixerError { + ConfigError, + SendError, +} + +impl fmt::Display for ChannelMixerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "") + } +} + +impl Error for ChannelMixerError {} + +/// A controller type that sends gain updates to a corresponding [`ChannelMixerSource`]. +#[derive(Debug, Clone)] +pub struct ChannelMixer { + sender: Sender, + out_channels_count: ChannelCount, +} + +impl ChannelMixer { + /// Set or update the gain setting for a channel mapping. + pub fn set_map(&self, channel_map: &ChannelMap) -> Result<(), ChannelMixerError> { + let mut new_map = channel_map.clone(); + self.prepare_map(&mut new_map)?; + self.sender + .send(new_map) + .map_err(|_| ChannelMixerError::SendError) + } + + fn prepare_map(&self, new_channel_map: &mut ChannelMap) -> Result<(), ChannelMixerError> { + if !new_channel_map + .iter() + .all(|(_from, to, _gain)| to < &self.out_channels_count) + { + return Err(ChannelMixerError::ConfigError); + } + new_channel_map.retain(|(_from, _to, gain)| *gain != 0.0); + new_channel_map.sort_by(|a, b| a.0.cmp(&b.0)); + Ok(()) + } +} + +/// A source for extracting, reordering, mixing and duplicating audio between +/// channels. +pub struct ChannelMixerSource +where + I: Source, + I::Item: Sample, +{ + /// Input [`Source`] + input: I, + + /// Mapping of input to output channels. + channel_map: ChannelMap, + + /// The output channel that [`next()`] will return next. + current_out_channel: ChannelCount, + + /// The number of output channels. + out_channel_count: ChannelCount, + + /// The current input audio frame. + output_frame: Vec>, + + /// Communication channel with the controller. + receiver: Receiver, +} + +impl Debug for ChannelMixerSource +where + I: Source, + I::Item: Sample, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("ChannelMixerSource") + .field("channel_map", &self.channel_map) + .field("channel_count", &self.out_channel_count) + .field("current_channel", &self.current_out_channel) + .finish() + } +} + +impl ChannelMixerSource +where + I: Source, + I::Item: Sample, +{ + /// Destroys this router and returns the underlying source. + #[inline] + pub fn into_inner(self) -> I { + self.input + } + + /// Get mutable access to the inner source. + #[inline] + pub fn inner_mut(&mut self) -> &mut I { + &mut self.input + } +} + +impl Source for ChannelMixerSource +where + I: Source, + I::Item: Sample, +{ + #[inline] + fn current_span_len(&self) -> Option { + self.input.current_span_len() + } + + #[inline] + fn channels(&self) -> ChannelCount { + self.out_channel_count + } + + #[inline] + fn sample_rate(&self) -> u32 { + self.input.sample_rate() + } + + #[inline] + fn total_duration(&self) -> Option { + self.input.total_duration() + } +} + +impl Iterator for ChannelMixerSource +where + I: Source, + I::Item: Sample, +{ + type Item = I::Item; + + #[inline] + fn next(&mut self) -> Option { + if self.current_out_channel >= self.out_channel_count { + // An improvement idea: one may want to update the mapping when incoming channel count changes. + // TODO This condition is normally false. Use an atomic to speedup polling? + if let Some(map_update) = self.receiver.try_iter().last() { + self.channel_map = map_update; + } + + self.current_out_channel = 0; + self.output_frame.fill(None); + let input_channels = self.input.channels(); + let mut link_index = 0; + let mut in_channel_count = 0; + let input = &mut self.input; + for (input_channel, sample) in input.take(input_channels as usize).enumerate() { + while link_index < self.channel_map.len() { + let (from_channel, to_channel, weight) = self.channel_map[link_index]; + if from_channel > input_channel as ChannelCount { + break; + } + if from_channel == input_channel as ChannelCount { + let amplified = sample.amplify(weight); + let c = &mut self.output_frame[to_channel as usize]; + // This could be simpler if samples had a way to get additive zero (0, or 0.0). + *c = Some(c.map_or(amplified, |x| x.saturating_add(amplified))); + } + link_index += 1; + } + in_channel_count += 1; + } + if in_channel_count < input_channels { + return None; + } + } + let sample = self.output_frame[self.current_out_channel as usize]; + self.current_out_channel += 1; + Some(sample.unwrap_or(I::Item::zero_value())) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.input.size_hint() + } +} + +#[cfg(test)] +mod tests { + use crate::buffer::SamplesBuffer; + use crate::source::channel_router::*; + + #[test] + fn test_stereo_to_mono() { + let input = SamplesBuffer::new(2, 1, [0u16, 2u16, 4u16, 6u16]); + let map = vec![(0, 0, 0.5f32), (1, 0, 0.5f32)]; + + let (_, test_source) = channel_mixer(input, 1, &map); + let v1: Vec = test_source.take(4).collect(); + assert_eq!(v1, [1u16, 5u16]); + } + + #[test] + fn test_upmix() { + let input = SamplesBuffer::new(1, 1, [0i16, -10, 10, 20, -20, -50, -30, 40]); + let map = vec![(0, 0, 1.0f32), (0, 1, 0.5f32), (0, 2, 2.0f32)]; + let (_, test_source) = channel_mixer(input, 3, &map); + assert_eq!(test_source.channels(), 3); + let v1: Vec = test_source.take(1000).collect(); + assert_eq!(v1.len(), 24); + assert_eq!( + v1, + [ + 0i16, 0, 0, -10, -5, -20, 10, 5, 20, 20, 10, 40, -20, -10, -40, -50, -25, -100, + -30, -15, -60, 40, 20, 80 + ] + ); + } + + #[test] + fn test_updates() { + let input = SamplesBuffer::new(2, 1, [0i16, 0i16, -1i16, -1i16, 1i16, 2i16, -4i16, -3i16]); + let mut map = vec![(0, 0, 1.0f32), (1, 0, 1.0f32)]; + let (controller, mut source) = channel_mixer(input, 1, &map); + let v1: Vec = source.by_ref().take(2).collect(); + assert_eq!(v1, vec![0i16, -2i16]); + + map[0].2 = 0.0f32; + map[1].2 = 2.0f32; + assert!(controller.set_map(&map).is_ok()); + + let v2: Vec = source.take(3).collect(); + assert_eq!(v2, vec![4i16, -6i16]); + } + + #[test] + fn test_arbitrary_mixing() { + let input = SamplesBuffer::new(4, 1, [10i16, 100, 300, 700, 1100, 1300, 1705].repeat(4)); + // 4 to 3 channels. + let map = vec![ + // Intentionally left 1 without mapping to test the case. + (2, 0, 1.0f32), + (3, 0, 0.1f32), + (3, 1, 0.3f32), + (0, 1, 0.7f32), + (0, 2, 0.6f32), + // For better diagnostics this should be rejected, currently it is ignored. + (17, 0, 321.5f32), + ]; + let (_controller, mut source) = channel_mixer(input, 3, &map); + let v1: Vec = source.by_ref().collect(); + assert_eq!(v1.len(), 21); + assert_eq!( + v1, + vec![ + 370i16, 217, 6, 1706, 773, 660, 810, 400, 60, 20, 940, 780, 1230, 600, 180, 130, + 1283, 1023, 1470, 1001, 420 + ] + ); + } +} diff --git a/src/source/mod.rs b/src/source/mod.rs index 605057e8..0fbcc0e0 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -3,14 +3,11 @@ use core::fmt; use core::time::Duration; -use crate::common::{ChannelCount, SampleRate}; -use crate::Sample; -use dasp_sample::FromSample; - pub use self::agc::AutomaticGainControl; pub use self::amplify::Amplify; pub use self::blt::BltFilter; pub use self::buffered::Buffered; +pub use self::channel_router::{ChannelMap, ChannelMixer, ChannelMixerSource}; pub use self::channel_volume::ChannelVolume; pub use self::chirp::{chirp, Chirp}; pub use self::crossfade::Crossfade; @@ -42,11 +39,15 @@ pub use self::take::TakeDuration; pub use self::triangle::TriangleWave; pub use self::uniform::UniformSourceIterator; pub use self::zero::Zero; +use crate::common::{ChannelCount, SampleRate}; +use crate::Sample; +use dasp_sample::FromSample; mod agc; mod amplify; mod blt; mod buffered; +mod channel_router; mod channel_volume; mod chirp; mod crossfade; @@ -346,6 +347,103 @@ where ) } + /// Creates a [`ChannelMixerSource`] that can mix input channels together and + /// assign them to new channels. + #[inline] + fn channel_router( + self, + channel_count: u16, + channel_map: &ChannelMap, + ) -> (ChannelMixer, ChannelMixerSource) + where + Self: Sized, + { + channel_router::channel_mixer(self, channel_count, channel_map) + } + + /// Creates a one-channel output [`ChannelMixerSource`] that extracts the channel `channel` from + /// this sound. + #[inline] + fn extract_channel(self, channel: u16) -> ChannelMixerSource + where + Self: Sized, + { + self.extract_channels(vec![channel]) + } + + /// Creates a [`ChannelMixerSource`] that reorders this sound's channels according to the order of + /// the channels in the `channels` parameter. + /// + /// # Panics + /// + /// - length of `channels` exceeds `u16::MAX`. + #[inline] + fn extract_channels(self, channels: Vec) -> ChannelMixerSource + where + Self: Sized, + { + assert!( + channels.len() < u16::MAX.into(), + "`channels` excessive length" + ); + let mut mapping = ChannelMap::new(); + let output_count = channels.len() as u16; + for (output_channel, input_channel) in channels.into_iter().enumerate() { + mapping.push((input_channel, output_channel as ChannelCount, 1.0f32)); + } + channel_router::channel_mixer(self, output_count, &mapping).1 + } + + /// Creates a [`ChannelMixerSource`] that mixes all of the input channels to mono with full gain. + #[inline] + fn mono(self) -> ChannelMixerSource + where + Self: Sized, + { + let mapping: ChannelMap = (0..self.channels()).map(|oc| (0, oc, 1.0f32)).collect(); + channel_router::channel_mixer(self, 1, &mapping).1 + } + + /// Creates a [`ChannelMixerSource`] that splits a mono channel into two stereo channels, at half + /// their original gain. + /// + /// # Panics + /// + /// - `self.channels()` is not equal to 1 + #[inline] + fn mono_to_stereo(self) -> ChannelMixerSource + where + Self: Sized, + { + let mapping: ChannelMap = vec![(0, 1, 0.5f32), (0, 1, 0.5f32)]; + channel_router::channel_mixer(self, 2, &mapping).1 + } + + /// Creates a [`ChannelMixerSource`] that mixes a 5.1 source in SMPTE channel order (L, R, C, Lfe, Ls, + /// Rs) into a stereo mix, with gain coefficients per [ITU-BS.775-1](itu_775). + /// + /// [itu_775]: https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.775-1-199407-S!!PDF-E.pdf + /// + /// # Panics + /// + /// - `self.channels()` is not equal to 6 + #[inline] + fn downmix_51(self) -> ChannelMixerSource + where + Self: Sized, + { + let three_db_down = std::f32::consts::FRAC_1_SQRT_2; + let mapping: ChannelMap = vec![ + (0, 0, 1f32), + (1, 1, 1f32), + (2, 0, three_db_down), + (2, 1, three_db_down), + (4, 0, three_db_down), + (5, 1, three_db_down), + ]; + channel_router::channel_mixer(self, 6, &mapping).1 + } + /// Mixes this sound fading out with another sound fading in for the given duration. /// /// Only the crossfaded portion (beginning of self, beginning of other) is returned.