diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a23a6f1..9d71e9d1 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 296ea901..1a763ebb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,6 +77,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..22ec0b5d --- /dev/null +++ b/examples/channel_routing.rs @@ -0,0 +1,47 @@ +//! 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 (mut controller, router) = SignalGenerator::new(sample_rate, 440.0, Function::Triangle) + .amplify(0.1) + .channel_router(2, vec![vec![0.0f32, 0.0f32]]); + + 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.map(0, 0, left_level)?; + controller.map(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.map(0, 0, left_level)?; + controller.map(0, 1, right_level)?; + println!("Left: {left_level:.04}, Right: {right_level:.04}"); + } + _ => continue, + } + } + + Ok(()) +} diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs new file mode 100644 index 00000000..8ae9677a --- /dev/null +++ b/src/source/channel_router.rs @@ -0,0 +1,306 @@ +// Channel router types and implementation. + +use crate::{Sample, Source}; +use std::{ + cmp::min, + error::Error, + fmt, + sync::mpsc::{channel, Receiver, Sender}, +}; + +/// A matrix to map inputs to outputs according to a gain +/// +/// A two-dimensional matrix of `f32`s: +/// - The first dimension is respective to the input channels +/// - The second is respective to the output channels +/// +/// Thus, if a value at `map[1,1]` is 0.2, this signifies that the signal on +/// channel 1 should be mixed into channel 1 with a coefficient of 0.2. +pub type ChannelMap = Vec>; +// doing this as Vec> would require feature=experimental, so I decided +// to just use a channel to do updates. +// +// Doing it as a HashMap<(u16,u16), f32> is an option too but there's a penalty hashing these +// values, there's ways to speed that up though. It'd be great if the object upgraded its +// implementation if it got sufficiently big. + +// pub fn empty_channel_map(inputs: u16, outputs: u16) -> ChannelMap { +// vec![vec![0.0f32; outputs.into()]; inputs.into()] +// } + +/// Internal function that builds a [`ChannelRouter`] object. +pub fn channel_router( + input: I, + channel_count: u16, + channel_map: ChannelMap, +) -> (ChannelRouterController, ChannelRouterSource) +where + I: Source, + I::Item: Sample, +{ + ChannelRouterSource::new(input, channel_count, channel_map) +} + +struct ChannelRouterMessage(usize, usize, f32); + +/// `ChannelRouterController::map()` returns this error if the router source has been dropped. +#[derive(Debug, Eq, PartialEq)] +pub struct ChannelRouterControllerError {} + +impl fmt::Display for ChannelRouterControllerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "") + } +} + +impl Error for ChannelRouterControllerError {} + +/// A controller type that sends gain updates to a corresponding [`ChannelRouterSource`]. +#[derive(Debug, Clone)] +pub struct ChannelRouterController { + sender: Sender, +} + +impl ChannelRouterController { + /// Set or update the gain setting for a channel mapping. + /// + /// A channel from the input may be routed to any number of channels in the output, and a + /// channel in the output may be a mix of any number of channels in the input. + /// + /// Successive calls to `map` with the same `from` and `to` arguments will replace the + /// previous gain value with the new one. + pub fn map( + &mut self, + from: u16, + to: u16, + gain: f32, + ) -> Result<(), ChannelRouterControllerError> { + if self + .sender + .send(ChannelRouterMessage(from as usize, to as usize, gain)) + .is_err() + { + Err(ChannelRouterControllerError {}) + } else { + Ok(()) + } + } +} + +/// A source for extracting, reordering, mixing and duplicating audio between +/// channels. +#[derive(Debug)] +pub struct ChannelRouterSource +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_channel: u16, + + /// The number of output channels + channel_count: u16, + + /// The current input audio frame + input_buffer: Vec, + + /// Communication channel with the controller + receiver: Receiver, +} + +impl ChannelRouterSource +where + I: Source, + I::Item: Sample, +{ + /// Creates a new [`ChannelRouter`]. + /// + /// The new `ChannelRouter` will read samples from `input` and will mix and map them according + /// to `channel_mappings` into its output samples. + /// + /// # Panics + /// + /// - if `channel_count` is not equal to `channel_map`'s second dimension + /// - if `input.channels()` is not equal to `channel_map`'s first dimension + pub fn new( + input: I, + channel_count: u16, + channel_map: ChannelMap, + ) -> (ChannelRouterController, Self) { + assert!(channel_count as usize == channel_map[0].len()); + assert!(input.channels() as usize == channel_map.len()); + + let (tx, rx) = channel(); + + let controller = ChannelRouterController { sender: tx }; + let source = Self { + input, + channel_map, + current_channel: channel_count, + // this will cause the input buffer to fill on first call to next() + channel_count, + // channel_count is redundant, it's implicit in the channel_map dimensions + // but maybe it's saving us some time, we do check this value a lot. + input_buffer: vec![], + receiver: rx, + }; + + (controller, source) + } + + /// 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 ChannelRouterSource +where + I: Source, + I::Item: Sample, +{ + #[inline] + fn current_span_len(&self) -> Option { + self.input.current_span_len() + } + + #[inline] + fn channels(&self) -> u16 { + self.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 ChannelRouterSource +where + I: Source, + I::Item: Sample, +{ + type Item = I::Item; + + #[inline] + fn next(&mut self) -> Option { + if self.current_channel >= self.channel_count { + // We've reached the end of the frame, time to grab another one from the input + let input_channels = self.input.channels() as usize; + + // This might be too fussy, a source should never break a frame in the middle of an + // audio frame. + let samples_to_take = min( + input_channels, + self.input.current_span_len().unwrap_or(usize::MAX), + ); + + // fill the input buffer. If the input is exhausted and returning None this will make + // the input buffer zero length + self.input_buffer = self.inner_mut().take(samples_to_take).collect(); + + self.current_channel = 0; + + for change in self.receiver.try_iter() { + self.channel_map[change.0][change.1] = change.2; + } + } + + // Find the output sample for current_channel + let retval = self + .input_buffer + .iter() + .zip(&self.channel_map) + .map(|(in_sample, input_gains)| { + // the way this works, the input_buffer need not be totally full, the router will + // work with whatever samples are available and the missing samples will be assumed + // to be equilibrium. + let gain = input_gains[self.current_channel as usize]; + in_sample.amplify(gain) + }) + .reduce(|a, b| a.saturating_add(b)); + + self.current_channel += 1; + retval + } + + #[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![vec![0.5f32], vec![0.5f32]]; + + let (_, test_source) = ChannelRouterSource::new(input, 1, map); + let v1: Vec = test_source.take(4).collect(); + assert_eq!(v1.len(), 2); + assert_eq!(v1[0], 1u16); + assert_eq!(v1[1], 5u16); + } + + #[test] + fn test_upmix() { + let input = SamplesBuffer::new(1, 1, [0i16, -10, 10, 20, -20, -50, -30, 40]); + let map = vec![vec![1.0f32, 0.5f32, 2.0f32]]; + let (_, test_source) = ChannelRouterSource::new(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 initial_map = vec![vec![1.0f32], vec![1.0f32]]; + let (mut controller, mut source) = ChannelRouterSource::new(input, 1, initial_map); + let v1: Vec = source.by_ref().take(2).collect(); + assert_eq!(v1.len(), 2); + assert_eq!(v1[0], 0i16); + assert_eq!(v1[1], -2i16); + + let r1 = controller.map(0, 0, 0.0f32); + let r2 = controller.map(1, 0, 2.0f32); + assert_eq!(r1, Ok(())); + assert_eq!(r2, Ok(())); + + let v2: Vec = source.take(3).collect(); + assert_eq!(v2.len(), 2); + + assert_eq!(v2[0], 4i16); + assert_eq!(v2[1], -6i16); + } +} diff --git a/src/source/mod.rs b/src/source/mod.rs index 605057e8..e264ccdf 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -11,6 +11,7 @@ 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, ChannelRouterController, ChannelRouterSource}; pub use self::channel_volume::ChannelVolume; pub use self::chirp::{chirp, Chirp}; pub use self::crossfade::Crossfade; @@ -47,6 +48,7 @@ mod agc; mod amplify; mod blt; mod buffered; +mod channel_router; mod channel_volume; mod chirp; mod crossfade; @@ -346,6 +348,103 @@ where ) } + /// Creates a [`ChannelRouter`] that can mix input channels together and + /// assign them to new channels. + #[inline] + fn channel_router( + self, + channel_count: u16, + channel_map: ChannelMap, + ) -> (ChannelRouterController, ChannelRouterSource) + where + Self: Sized, + { + channel_router::channel_router(self, channel_count, channel_map) + } + + /// Creates a one-channel output [`ChannelRouter`] that extracts the channel `channel` from + /// this sound. + #[inline] + fn extract_channel(self, channel: u16) -> ChannelRouterSource + where + Self: Sized, + { + self.extract_channels(vec![channel]) + } + + /// Creates a [`ChannelRouter`] 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) -> ChannelRouterSource + 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[input_channel as usize][output_channel] = 1.0f32; + } + channel_router::channel_router(self, output_count, mapping).1 + } + + /// Creates a [`ChannelRouter`] that mixes all of the input channels to mono with full gain. + #[inline] + fn mono(self) -> ChannelRouterSource + where + Self: Sized, + { + let mapping: ChannelMap = vec![vec![1.0f32]; self.channels().into()]; + channel_router::channel_router(self, 1, mapping).1 + } + + /// Creates a [`ChannelRouter`] 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) -> ChannelRouterSource + where + Self: Sized, + { + let mapping: ChannelMap = vec![vec![0.5f32, 0.5f32]]; + channel_router::channel_router(self, 2, mapping).1 + } + + /// Creates a [`ChannelRouter`] 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) -> ChannelRouterSource + where + Self: Sized, + { + let three_db_down = std::f32::consts::FRAC_1_SQRT_2; + let mapping: ChannelMap = vec![ + vec![1.0f32, 0.0f32], + vec![0.0f32, 1.0f32], + vec![three_db_down, three_db_down], + vec![0.0f32, 0.0f32], + vec![three_db_down, 0.0f32], + vec![0.0f32, three_db_down], + ]; + channel_router::channel_router(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.