From f4977038d945f2ac177c96cf040b4e30e1acefcf Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 7 Dec 2024 13:24:30 -0800 Subject: [PATCH 01/30] Adding a ChannelRouter source Implementation in progress. --- src/source/channel_router.rs | 43 ++++++++++++++++++++++++++++++++++++ src/source/mod.rs | 2 ++ 2 files changed, 45 insertions(+) create mode 100644 src/source/channel_router.rs diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs new file mode 100644 index 00000000..0b345f28 --- /dev/null +++ b/src/source/channel_router.rs @@ -0,0 +1,43 @@ +use std::collections::HashMap; + +use crate::{Sample, Source}; + + + +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct InputOutputPair(u16, u16); + + +#[derive(Clone, Debug)] +pub struct ChannelRouter +where + I: Source, + I::Item: Sample, +{ + input: I, + channel_mappings: HashMap, + current_channel: u16, + input_buffer: Vec, +} + +impl ChannelRouter where + I: Source, + I::Item: Sample,{ + + pub fn new(input: I, channel_count: u16, channel_mappings: HashMap) -> Self { + Self { + input, + channel_mappings, + current_channel: 0u16, + input_buffer: vec![::zero_value(); channel_count.into() ], + } + } +} + +impl Iterator for ChannelRouter where I: Source, I::Item: Sample { + type Item = I::Item; + + fn next(&mut self) -> Option { + todo!() + } +} diff --git a/src/source/mod.rs b/src/source/mod.rs index 04dc7777..64609b2a 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::ChannelRouter; 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; From cfdea360d7aea79cc186c2ba656ff9dc65fd8dfc Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 7 Dec 2024 14:09:18 -0800 Subject: [PATCH 02/30] Implementation continues --- src/source/channel_router.rs | 89 +++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 11 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 0b345f28..388173bb 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -1,13 +1,10 @@ -use std::collections::HashMap; +use std::{cmp::min, collections::HashMap}; use crate::{Sample, Source}; - - #[derive(Clone, Hash, Eq, PartialEq, Debug)] pub struct InputOutputPair(u16, u16); - #[derive(Clone, Debug)] pub struct ChannelRouter where @@ -17,27 +14,97 @@ where input: I, channel_mappings: HashMap, current_channel: u16, + channel_count: u16, input_buffer: Vec, } -impl ChannelRouter where +impl ChannelRouter +where I: Source, - I::Item: Sample,{ - - pub fn new(input: I, channel_count: u16, channel_mappings: HashMap) -> Self { + I::Item: Sample, +{ + pub fn new( + input: I, + channel_count: u16, + channel_mappings: HashMap, + ) -> Self { Self { input, channel_mappings, current_channel: 0u16, - input_buffer: vec![::zero_value(); channel_count.into() ], + channel_count, + input_buffer: vec![::zero_value(); channel_count.into()], } } } -impl Iterator for ChannelRouter where I: Source, I::Item: Sample { +impl Source for ChannelRouter +where + I: Source, + I::Item: Sample, +{ + fn current_frame_len(&self) -> Option { + self.input.current_frame_len() + } + + fn channels(&self) -> u16 { + self.channel_count + } + + fn sample_rate(&self) -> u32 { + self.input.sample_rate() + } + + fn total_duration(&self) -> Option { + self.input.total_duration() + } +} + +impl ChannelRouter +where + I: Source, + I::Item: Sample, +{ + fn apply_gains(&self, output_channel: u16) -> I::Item { + self.input_buffer + .iter() + .enumerate() + .map(|(input_channel, in_sample)| { + let pair = InputOutputPair(input_channel as u16, output_channel); + let gain = self.channel_mappings.get(&pair).unwrap_or(&0.0f32); + in_sample.amplify(*gain) + }) + .reduce(|a, b| a.saturating_add(b)) + .unwrap_or(::zero_value()) + } +} + +impl Iterator for ChannelRouter +where + I: Source, + I::Item: Sample, +{ type Item = I::Item; fn next(&mut self) -> Option { - todo!() + if self.current_channel >= self.channel_count { + let input_channels = self.input.channels() as usize; + let samples_to_take = min( + input_channels, + self.input.current_frame_len().unwrap_or(usize::MAX), + ); + + self.input_buffer = self.input.by_ref().take(samples_to_take).collect(); + + self.current_channel = 0; + } + + if self.input_buffer.len() == 0 { + None + } else { + let retval = self.apply_gains(self.current_channel); + self.current_channel += 1; + Some(retval) + } } } From 121105ebd910e651c8fc4774fe4c438c6ab59f4f Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 7 Dec 2024 14:18:54 -0800 Subject: [PATCH 03/30] Simplified handling of frame endings --- src/source/channel_router.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 388173bb..b3f7fb52 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -65,17 +65,17 @@ where I: Source, I::Item: Sample, { - fn apply_gains(&self, output_channel: u16) -> I::Item { + /// Create an output sample from the current input and the channel gain map + fn render_output(&self) -> Option { self.input_buffer .iter() .enumerate() .map(|(input_channel, in_sample)| { - let pair = InputOutputPair(input_channel as u16, output_channel); + let pair = InputOutputPair(input_channel as u16, self.current_channel); let gain = self.channel_mappings.get(&pair).unwrap_or(&0.0f32); in_sample.amplify(*gain) }) .reduce(|a, b| a.saturating_add(b)) - .unwrap_or(::zero_value()) } } @@ -99,12 +99,8 @@ where self.current_channel = 0; } - if self.input_buffer.len() == 0 { - None - } else { - let retval = self.apply_gains(self.current_channel); - self.current_channel += 1; - Some(retval) - } + let retval = self.render_output(); + self.current_channel += 1; + retval } } From edc683bb654c6f05df0170e5d5577a4bda44956a Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 7 Dec 2024 14:50:05 -0800 Subject: [PATCH 04/30] More implementation, added mod functions --- src/source/channel_router.rs | 40 +++++++++++++++++++++++++++--------- src/source/mod.rs | 10 ++++++++- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index b3f7fb52..f67c6675 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -2,9 +2,22 @@ use std::{cmp::min, collections::HashMap}; use crate::{Sample, Source}; +pub type ChannelMap = HashMap; + +pub fn channel_router(input: I, channel_count: u16, channel_map: ChannelMap) -> ChannelRouter +where + I: Source, + I::Item: Sample, +{ + ChannelRouter::new(input, channel_count, channel_map) +} + +/// A tuple for describing the source and destination channel for a gain setting. #[derive(Clone, Hash, Eq, PartialEq, Debug)] pub struct InputOutputPair(u16, u16); +/// A source for extracting, reordering mixing and duplicating audio between +/// channels. #[derive(Clone, Debug)] pub struct ChannelRouter where @@ -12,7 +25,7 @@ where I::Item: Sample, { input: I, - channel_mappings: HashMap, + channel_map: HashMap, current_channel: u16, channel_count: u16, input_buffer: Vec, @@ -23,19 +36,26 @@ where I: Source, I::Item: Sample, { - pub fn new( - input: I, - channel_count: u16, - channel_mappings: HashMap, - ) -> Self { + /// 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. + pub fn new(input: I, channel_count: u16, channel_map: ChannelMap) -> Self { Self { input, - channel_mappings, - current_channel: 0u16, + channel_map, + current_channel: channel_count, // this will cause the input buffer to fill on first + // call to next() channel_count, - input_buffer: vec![::zero_value(); channel_count.into()], + input_buffer: vec![::zero_value(); 0], } } + + /// Add or update the gain setting for a channel mapping. + pub fn map(&mut self, from: u16, to: u16, gain: f32) -> () { + let k = InputOutputPair(from, to); + self.channel_map.insert(k, gain); + } } impl Source for ChannelRouter @@ -72,7 +92,7 @@ where .enumerate() .map(|(input_channel, in_sample)| { let pair = InputOutputPair(input_channel as u16, self.current_channel); - let gain = self.channel_mappings.get(&pair).unwrap_or(&0.0f32); + let gain = self.channel_map.get(&pair).unwrap_or(&0.0f32); in_sample.amplify(*gain) }) .reduce(|a, b| a.saturating_add(b)) diff --git a/src/source/mod.rs b/src/source/mod.rs index 64609b2a..c6c77717 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -11,7 +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::ChannelRouter; +pub use self::channel_router::{ChannelRouter, ChannelMap}; pub use self::channel_volume::ChannelVolume; pub use self::chirp::{chirp, Chirp}; pub use self::crossfade::Crossfade; @@ -348,6 +348,14 @@ 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) -> ChannelRouter + where Self: Sized { + channel_router::channel_router(self, channel_count, channel_map) + } + /// 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. From 6037f67dcaeba1460989654862c17c76a8660004 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 7 Dec 2024 15:06:43 -0800 Subject: [PATCH 05/30] Some documentation --- src/source/channel_router.rs | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index f67c6675..a8b4f143 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -2,8 +2,15 @@ use std::{cmp::min, collections::HashMap}; use crate::{Sample, Source}; +/// A tuple for describing the source and destination channel for a gain setting. +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct InputOutputPair(u16, u16); + +/// A [`HashMap`] for defining a connection between an input channel and output channel, and the +/// gain to apply to that connection. pub type ChannelMap = HashMap; +/// Internal function that builds a [`ChannelRouter`] object. pub fn channel_router(input: I, channel_count: u16, channel_map: ChannelMap) -> ChannelRouter where I: Source, @@ -12,10 +19,6 @@ where ChannelRouter::new(input, channel_count, channel_map) } -/// A tuple for describing the source and destination channel for a gain setting. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct InputOutputPair(u16, u16); - /// A source for extracting, reordering mixing and duplicating audio between /// channels. #[derive(Clone, Debug)] @@ -24,10 +27,19 @@ where I: Source, I::Item: Sample, { + /// Input [`Source`] input: I, - channel_map: HashMap, - current_channel: u16, + + /// 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, } @@ -45,9 +57,9 @@ where input, channel_map, current_channel: channel_count, // this will cause the input buffer to fill on first - // call to next() + // call to next() channel_count, - input_buffer: vec![::zero_value(); 0], + input_buffer: vec![], } } @@ -85,7 +97,10 @@ where I: Source, I::Item: Sample, { - /// Create an output sample from the current input and the channel gain map + /// Renders an output sample. + /// + /// Create an output sample from the current input audio frame, the `current_channel` of the + /// output and the channel gain map. fn render_output(&self) -> Option { self.input_buffer .iter() From b45b936fd1de95087f2849b5234a80c1bc387ae5 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sat, 7 Dec 2024 16:11:12 -0800 Subject: [PATCH 06/30] Flatted-out next a little Undid an unnecesary refactor. --- src/source/channel_router.rs | 56 ++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index a8b4f143..b5eecd4f 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -31,12 +31,12 @@ where input: I, /// Mapping of input to output channels - channel_map: ChannelMap, - + channel_map: ChannelMap, + /// The output channel that [`next()`] will return next. - current_channel: u16, - - /// The number of output channels + current_channel: u16, + + /// The number of output channels channel_count: u16, /// The current input audio frame @@ -68,6 +68,18 @@ where let k = InputOutputPair(from, to); self.channel_map.insert(k, gain); } + + /// Destroys this router and returns the underlying source. + #[inline] + pub fn into_inner(self) -> I { + self.input + } + + /// Get mutable access to the iterator + #[inline] + pub fn inner_mut(&mut self) -> &mut I { + &mut self.input + } } impl Source for ChannelRouter @@ -92,28 +104,6 @@ where } } -impl ChannelRouter -where - I: Source, - I::Item: Sample, -{ - /// Renders an output sample. - /// - /// Create an output sample from the current input audio frame, the `current_channel` of the - /// output and the channel gain map. - fn render_output(&self) -> Option { - self.input_buffer - .iter() - .enumerate() - .map(|(input_channel, in_sample)| { - let pair = InputOutputPair(input_channel as u16, self.current_channel); - let gain = self.channel_map.get(&pair).unwrap_or(&0.0f32); - in_sample.amplify(*gain) - }) - .reduce(|a, b| a.saturating_add(b)) - } -} - impl Iterator for ChannelRouter where I: Source, @@ -134,7 +124,17 @@ where self.current_channel = 0; } - let retval = self.render_output(); + let retval = self + .input_buffer + .iter() + .enumerate() + .map(|(input_channel, in_sample)| { + let pair = InputOutputPair(input_channel as u16, self.current_channel); + let gain = self.channel_map.get(&pair).unwrap_or(&0.0f32); + in_sample.amplify(*gain) + }) + .reduce(|a, b| a.saturating_add(b)); + self.current_channel += 1; retval } From 51b1f4be759cd5a05ed2fd1e717fc3a9e0892b5e Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 8 Dec 2024 11:23:08 -0800 Subject: [PATCH 07/30] rusfmt and typo --- src/source/channel_router.rs | 2 +- src/source/mod.rs | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index b5eecd4f..1c39e1ac 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -19,7 +19,7 @@ where ChannelRouter::new(input, channel_count, channel_map) } -/// A source for extracting, reordering mixing and duplicating audio between +/// A source for extracting, reordering, mixing and duplicating audio between /// channels. #[derive(Clone, Debug)] pub struct ChannelRouter diff --git a/src/source/mod.rs b/src/source/mod.rs index c6c77717..182a27aa 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -11,7 +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::{ChannelRouter, ChannelMap}; +pub use self::channel_router::{ChannelMap, ChannelRouter}; pub use self::channel_volume::ChannelVolume; pub use self::chirp::{chirp, Chirp}; pub use self::crossfade::Crossfade; @@ -348,11 +348,13 @@ where ) } - /// Creates a [`ChannelRouter`] that can mix input channels together and + /// 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) -> ChannelRouter - where Self: Sized { + fn channel_router(self, channel_count: u16, channel_map: ChannelMap) -> ChannelRouter + where + Self: Sized, + { channel_router::channel_router(self, channel_count, channel_map) } From 67b16a1368c10fc885737113798504afdc49883a Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 8 Dec 2024 11:44:17 -0800 Subject: [PATCH 08/30] Typos and added methods, also documentation --- src/source/channel_router.rs | 37 +++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 1c39e1ac..78511325 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -56,17 +56,29 @@ where Self { input, channel_map, - current_channel: channel_count, // this will cause the input buffer to fill on first - // call to next() + current_channel: channel_count, + // this will cause the input buffer to fill on first call to next() channel_count, input_buffer: vec![], } } - /// Add or update the gain setting for a channel mapping. - pub fn map(&mut self, from: u16, to: u16, gain: f32) -> () { + /// 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 `route` with the same `from` and `to` arguments will replace the + /// previous gain value with the new one. + pub fn route(&mut self, from: u16, to: u16, gain: f32) -> () { + let k = InputOutputPair(from, to); + _ = self.channel_map.insert(k, gain); + } + + /// Delete an existing mapping from `from` to `to` if it exists. + pub fn unroute(&mut self, from: u16, to: u16) -> () { let k = InputOutputPair(from, to); - self.channel_map.insert(k, gain); + _ = self.channel_map.remove(&k); } /// Destroys this router and returns the underlying source. @@ -75,7 +87,7 @@ where self.input } - /// Get mutable access to the iterator + /// Get mutable access to the inner source. #[inline] pub fn inner_mut(&mut self) -> &mut I { &mut self.input @@ -113,22 +125,33 @@ where 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_frame_len().unwrap_or(usize::MAX), ); - self.input_buffer = self.input.by_ref().take(samples_to_take).collect(); + // 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; } + // Find the output sample for current_channel let retval = self .input_buffer .iter() + // if input_buffer is empty, retval will be None .enumerate() .map(|(input_channel, in_sample)| { + // 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 pair = InputOutputPair(input_channel as u16, self.current_channel); let gain = self.channel_map.get(&pair).unwrap_or(&0.0f32); in_sample.amplify(*gain) From f7d8220361fbfa6d8869bc0d7df294919bc3a3b9 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 8 Dec 2024 11:45:19 -0800 Subject: [PATCH 09/30] clippy --- src/source/channel_router.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 78511325..9eaf0bbd 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -70,13 +70,13 @@ where /// /// Successive calls to `route` with the same `from` and `to` arguments will replace the /// previous gain value with the new one. - pub fn route(&mut self, from: u16, to: u16, gain: f32) -> () { + pub fn route(&mut self, from: u16, to: u16, gain: f32) { let k = InputOutputPair(from, to); _ = self.channel_map.insert(k, gain); } /// Delete an existing mapping from `from` to `to` if it exists. - pub fn unroute(&mut self, from: u16, to: u16) -> () { + pub fn unroute(&mut self, from: u16, to: u16) { let k = InputOutputPair(from, to); _ = self.channel_map.remove(&k); } From 1204fdf7a34447c5e414101e0b4e5cdc5a8d13b0 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 8 Dec 2024 11:48:19 -0800 Subject: [PATCH 10/30] Inline everything! --- src/source/channel_router.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 9eaf0bbd..cbc0e1d3 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -99,18 +99,22 @@ where I: Source, I::Item: Sample, { + #[inline] fn current_frame_len(&self) -> Option { self.input.current_frame_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() } @@ -123,6 +127,7 @@ where { 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 @@ -161,4 +166,9 @@ where self.current_channel += 1; retval } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.input.size_hint() + } } From 9d434215e616362756483c30c72c89856d721e40 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Sun, 8 Dec 2024 12:14:41 -0800 Subject: [PATCH 11/30] Added extract_channels and extract_channel sources --- src/source/channel_router.rs | 2 +- src/source/mod.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index cbc0e1d3..050a3373 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -4,7 +4,7 @@ use crate::{Sample, Source}; /// A tuple for describing the source and destination channel for a gain setting. #[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct InputOutputPair(u16, u16); +pub struct InputOutputPair(pub u16, pub u16); /// A [`HashMap`] for defining a connection between an input channel and output channel, and the /// gain to apply to that connection. diff --git a/src/source/mod.rs b/src/source/mod.rs index 182a27aa..24d83ad7 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -3,6 +3,7 @@ use core::fmt; use core::time::Duration; +use channel_router::InputOutputPair; use cpal::FromSample; use crate::Sample; @@ -358,6 +359,40 @@ where 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) -> ChannelRouter + 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) -> ChannelRouter + 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() { + let k = channel_router::InputOutputPair(input_channel, output_channel as u16); + _ = mapping.insert(k, 1.0f32); + } + channel_router::channel_router(self, output_count, mapping) + } + /// 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. From 77763ae3750976d80971e5bd89e32404a51a1666 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 15:35:13 -0800 Subject: [PATCH 12/30] Gains implemented as an atomic array of f32s --- src/source/channel_router.rs | 43 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 050a3373..8056bbb1 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -1,14 +1,11 @@ -use std::{cmp::min, collections::HashMap}; - use crate::{Sample, Source}; +use std::cmp::min; -/// A tuple for describing the source and destination channel for a gain setting. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct InputOutputPair(pub u16, pub u16); +#[cfg(feature = "experimental")] +pub type ChannelMap = Vec>; -/// A [`HashMap`] for defining a connection between an input channel and output channel, and the -/// gain to apply to that connection. -pub type ChannelMap = HashMap; +#[cfg(not(feature = "experimental"))] +pub type ChannelMap = Vec>; /// Internal function that builds a [`ChannelRouter`] object. pub fn channel_router(input: I, channel_count: u16, channel_map: ChannelMap) -> ChannelRouter @@ -21,7 +18,7 @@ where /// A source for extracting, reordering, mixing and duplicating audio between /// channels. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct ChannelRouter where I: Source, @@ -52,7 +49,14 @@ where /// /// 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) -> Self { + assert!(channel_count as usize == channel_map[0].len()); + assert!(input.channels() as usize == channel_map.len()); Self { input, channel_map, @@ -70,15 +74,12 @@ where /// /// Successive calls to `route` with the same `from` and `to` arguments will replace the /// previous gain value with the new one. - pub fn route(&mut self, from: u16, to: u16, gain: f32) { - let k = InputOutputPair(from, to); - _ = self.channel_map.insert(k, gain); - } - - /// Delete an existing mapping from `from` to `to` if it exists. - pub fn unroute(&mut self, from: u16, to: u16) { - let k = InputOutputPair(from, to); - _ = self.channel_map.remove(&k); + /// + /// _This is an experimental feature._ + #[cfg(feature = "experimental")] + pub fn mix(&mut self, from: u16, to: u16, gain: f32) { + _ = self.channel_map[from as usize][to as usize] + .store(gain, std::sync::atomic::Ordering::Relaxed); } /// Destroys this router and returns the underlying source. @@ -157,9 +158,9 @@ where // 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 pair = InputOutputPair(input_channel as u16, self.current_channel); - let gain = self.channel_map.get(&pair).unwrap_or(&0.0f32); - in_sample.amplify(*gain) + let gain = self.channel_map[input_channel][self.current_channel as usize] + .load(std::sync::atomic::Ordering::Relaxed); + in_sample.amplify(gain) }) .reduce(|a, b| a.saturating_add(b)); From 3f16c2589c5ade2ce9c7a9b611548f80839afefa Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 15:55:12 -0800 Subject: [PATCH 13/30] Mutex-implemented, but need to clean this up Can't try to grab a lock on the audio thread. --- src/source/channel_router.rs | 36 ++++++++++++++---------------------- src/source/mod.rs | 12 +++++------- 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 8056bbb1..f82bbb45 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -1,25 +1,22 @@ use crate::{Sample, Source}; -use std::cmp::min; +use std::{cmp::min, sync::Mutex}; -#[cfg(feature = "experimental")] -pub type ChannelMap = Vec>; - -#[cfg(not(feature = "experimental"))] pub type ChannelMap = Vec>; /// Internal function that builds a [`ChannelRouter`] object. -pub fn channel_router(input: I, channel_count: u16, channel_map: ChannelMap) -> ChannelRouter +pub fn channel_router(input: I, channel_count: u16, channel_map: ChannelMap) -> ChannelRouterSource where I: Source, I::Item: Sample, { - ChannelRouter::new(input, channel_count, channel_map) + ChannelRouterSource::new(input, channel_count, channel_map) } + /// A source for extracting, reordering, mixing and duplicating audio between /// channels. #[derive(Debug)] -pub struct ChannelRouter +pub struct ChannelRouterSource where I: Source, I::Item: Sample, @@ -28,8 +25,8 @@ where input: I, /// Mapping of input to output channels - channel_map: ChannelMap, - + channel_map: Mutex, + /// The output channel that [`next()`] will return next. current_channel: u16, @@ -40,7 +37,7 @@ where input_buffer: Vec, } -impl ChannelRouter +impl ChannelRouterSource where I: Source, I::Item: Sample, @@ -59,7 +56,7 @@ where assert!(input.channels() as usize == channel_map.len()); Self { input, - channel_map, + channel_map: Mutex::new(channel_map), current_channel: channel_count, // this will cause the input buffer to fill on first call to next() channel_count, @@ -72,14 +69,10 @@ where /// 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 `route` with the same `from` and `to` arguments will replace the + /// Successive calls to `mix` with the same `from` and `to` arguments will replace the /// previous gain value with the new one. - /// - /// _This is an experimental feature._ - #[cfg(feature = "experimental")] pub fn mix(&mut self, from: u16, to: u16, gain: f32) { - _ = self.channel_map[from as usize][to as usize] - .store(gain, std::sync::atomic::Ordering::Relaxed); + self.channel_map.lock().unwrap()[from as usize][to as usize] = gain; } /// Destroys this router and returns the underlying source. @@ -95,7 +88,7 @@ where } } -impl Source for ChannelRouter +impl Source for ChannelRouterSource where I: Source, I::Item: Sample, @@ -121,7 +114,7 @@ where } } -impl Iterator for ChannelRouter +impl Iterator for ChannelRouterSource where I: Source, I::Item: Sample, @@ -158,8 +151,7 @@ where // 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 = self.channel_map[input_channel][self.current_channel as usize] - .load(std::sync::atomic::Ordering::Relaxed); + let gain = self.channel_map.lock().unwrap()[input_channel][self.current_channel as usize]; in_sample.amplify(gain) }) .reduce(|a, b| a.saturating_add(b)); diff --git a/src/source/mod.rs b/src/source/mod.rs index 24d83ad7..9d589a7b 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -3,7 +3,6 @@ use core::fmt; use core::time::Duration; -use channel_router::InputOutputPair; use cpal::FromSample; use crate::Sample; @@ -12,7 +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, ChannelRouter}; +pub use self::channel_router::{ChannelMap, ChannelRouterSource}; pub use self::channel_volume::ChannelVolume; pub use self::chirp::{chirp, Chirp}; pub use self::crossfade::Crossfade; @@ -352,7 +351,7 @@ 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) -> ChannelRouter + fn channel_router(self, channel_count: u16, channel_map: ChannelMap) -> ChannelRouterSource where Self: Sized, { @@ -362,7 +361,7 @@ where /// Creates a one-channel output [`ChannelRouter`] that extracts the channel `channel` from /// this sound. #[inline] - fn extract_channel(self, channel: u16) -> ChannelRouter + fn extract_channel(self, channel: u16) -> ChannelRouterSource where Self: Sized, { @@ -376,7 +375,7 @@ where /// /// - length of `channels` exceeds `u16::MAX`. #[inline] - fn extract_channels(self, channels: Vec) -> ChannelRouter + fn extract_channels(self, channels: Vec) -> ChannelRouterSource where Self: Sized, { @@ -387,8 +386,7 @@ where let mut mapping = ChannelMap::new(); let output_count = channels.len() as u16; for (output_channel, input_channel) in channels.into_iter().enumerate() { - let k = channel_router::InputOutputPair(input_channel, output_channel as u16); - _ = mapping.insert(k, 1.0f32); + mapping[input_channel as usize][output_channel as usize] = 1.0f32; } channel_router::channel_router(self, output_count, mapping) } From d39bbf2d0225af397d82f8764250fec94567ee24 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 17:31:19 -0800 Subject: [PATCH 14/30] Implemented updates with a mpsc::channel --- src/source/channel_router.rs | 75 +++++++++++++++++++++++++++--------- src/source/mod.rs | 10 +++-- 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index f82bbb45..f234098e 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -1,10 +1,17 @@ use crate::{Sample, Source}; -use std::{cmp::min, sync::Mutex}; +use std::{ + cmp::min, + sync::mpsc::{channel, Receiver, Sender}, +}; pub type ChannelMap = Vec>; /// Internal function that builds a [`ChannelRouter`] object. -pub fn channel_router(input: I, channel_count: u16, channel_map: ChannelMap) -> ChannelRouterSource +pub fn channel_router( + input: I, + channel_count: u16, + channel_map: ChannelMap, +) -> (ChannelRouterController, ChannelRouterSource) where I: Source, I::Item: Sample, @@ -12,6 +19,29 @@ where ChannelRouterSource::new(input, channel_count, channel_map) } +struct ChannelRouterMessage(usize, usize, f32); + +/// 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 `mix` 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) { + if let Err(_) = self.sender + .send(ChannelRouterMessage(from as usize, to as usize, gain)) { + todo!("Probably shouldn't panic here"); + } + } +} /// A source for extracting, reordering, mixing and duplicating audio between /// channels. @@ -25,8 +55,8 @@ where input: I, /// Mapping of input to output channels - channel_map: Mutex, - + channel_map: ChannelMap, + /// The output channel that [`next()`] will return next. current_channel: u16, @@ -35,6 +65,9 @@ where /// The current input audio frame input_buffer: Vec, + + /// Communication channel with the controller + receiver: Receiver, } impl ChannelRouterSource @@ -51,28 +84,28 @@ where /// /// - 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) -> Self { + 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()); - Self { + + let (tx, rx) = channel(); + + let controller = ChannelRouterController { sender: tx }; + let source = Self { input, - channel_map: Mutex::new(channel_map), + channel_map, current_channel: channel_count, // this will cause the input buffer to fill on first call to next() channel_count, input_buffer: vec![], - } - } + receiver: rx, + }; - /// 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 `mix` with the same `from` and `to` arguments will replace the - /// previous gain value with the new one. - pub fn mix(&mut self, from: u16, to: u16, gain: f32) { - self.channel_map.lock().unwrap()[from as usize][to as usize] = gain; + (controller, source) } /// Destroys this router and returns the underlying source. @@ -139,6 +172,10 @@ where 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 @@ -151,7 +188,7 @@ where // 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 = self.channel_map.lock().unwrap()[input_channel][self.current_channel as usize]; + let gain = self.channel_map[input_channel][self.current_channel as usize]; in_sample.amplify(gain) }) .reduce(|a, b| a.saturating_add(b)); diff --git a/src/source/mod.rs b/src/source/mod.rs index 9d589a7b..beebe84d 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -11,7 +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, ChannelRouterSource}; +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; @@ -351,7 +351,11 @@ 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) -> ChannelRouterSource + fn channel_router( + self, + channel_count: u16, + channel_map: ChannelMap, + ) -> (ChannelRouterController, ChannelRouterSource) where Self: Sized, { @@ -388,7 +392,7 @@ where for (output_channel, input_channel) in channels.into_iter().enumerate() { mapping[input_channel as usize][output_channel as usize] = 1.0f32; } - channel_router::channel_router(self, output_count, mapping) + channel_router::channel_router(self, output_count, mapping).1 } /// Mixes this sound fading out with another sound fading in for the given duration. From 45c4688bb4c936dbd50fb7e46b2e6fabcb75ffb1 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 17:31:44 -0800 Subject: [PATCH 15/30] rustfmt --- src/source/channel_router.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index f234098e..3cfc2c6c 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -36,10 +36,12 @@ impl ChannelRouterController { /// Successive calls to `mix` 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) { - if let Err(_) = self.sender - .send(ChannelRouterMessage(from as usize, to as usize, gain)) { - todo!("Probably shouldn't panic here"); - } + if let Err(_) = self + .sender + .send(ChannelRouterMessage(from as usize, to as usize, gain)) + { + todo!("Probably shouldn't panic here"); + } } } From 1662db29655779e400322005e8e52196f5e3e630 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 19:02:53 -0800 Subject: [PATCH 16/30] Added more router conveniences --- src/source/mod.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/source/mod.rs b/src/source/mod.rs index beebe84d..ceb75ac1 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -395,6 +395,56 @@ where 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. From 9b361b02c28de5ff20960367632facd0bfbc9078 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 19:04:40 -0800 Subject: [PATCH 17/30] Added some comments and stubbed-out tests --- src/source/channel_router.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 3cfc2c6c..6ec867d4 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -1,3 +1,5 @@ +// Channel router types and implementation. + use crate::{Sample, Source}; use std::{ cmp::min, @@ -5,6 +7,16 @@ use std::{ }; 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( @@ -103,6 +115,8 @@ where current_channel: channel_count, // this will cause the input buffer to fill on first call to next() channel_count, + // we don't need to store channel count, it's implicit in the channel_map dimentions + // but maybe it's saving us some time, we do check this value a lot. input_buffer: vec![], receiver: rx, }; @@ -184,13 +198,12 @@ where let retval = self .input_buffer .iter() - // if input_buffer is empty, retval will be None - .enumerate() - .map(|(input_channel, in_sample)| { + .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 = self.channel_map[input_channel][self.current_channel as usize]; + let gain = input_gains[self.current_channel as usize]; in_sample.amplify(gain) }) .reduce(|a, b| a.saturating_add(b)); @@ -204,3 +217,10 @@ where self.input.size_hint() } } + +#[cfg(test)] +mod tests { + + #[test] + fn test() {} +} From 562147763e5f9f0e127fcb8f42223663bafd673c Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 19:27:47 -0800 Subject: [PATCH 18/30] Added some static tests --- src/source/channel_router.rs | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 6ec867d4..83da5f9f 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -115,7 +115,7 @@ where current_channel: channel_count, // this will cause the input buffer to fill on first call to next() channel_count, - // we don't need to store channel count, it's implicit in the channel_map dimentions + // 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, @@ -220,7 +220,35 @@ where #[cfg(test)] mod tests { + use crate::buffer::SamplesBuffer; + use crate::source::channel_router::*; #[test] - fn 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 + ] + ); + } } From ca2ee9d1175cbe65ce0852b3d80b85eb3fc684d3 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 19:43:04 -0800 Subject: [PATCH 19/30] Added description to changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 107ba47e..428544a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - 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, traingle waves, square waves and sawtooths have been added. - An interface for defining `SignalGenerator` patterns with an `fn`, see From f521000ce1b1ce01fde4fb7c4d939b62420ee603 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 20:03:39 -0800 Subject: [PATCH 20/30] Test of the controller --- src/source/channel_router.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 83da5f9f..cea3c678 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -222,7 +222,7 @@ where 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]); @@ -251,4 +251,27 @@ mod tests { ] ); } + + #[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); + + controller.map(0, 0, 0.0f32); + controller.map(1, 0, 2.0f32); + + let v2: Vec = source.take(3).collect(); + assert_eq!(v2.len(), 2); + + assert_eq!(v2[0], 4i16); + assert_eq!(v2[1], -6i16); + } } From 9ae7119664334b3f9fc6215ac2bfed4f87240787 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 20:04:04 -0800 Subject: [PATCH 21/30] rustfmt --- src/source/channel_router.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index cea3c678..8616d8f7 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -222,7 +222,7 @@ where 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]); @@ -255,10 +255,7 @@ mod tests { #[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 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); From 0fe726ce22c73ec3ea5071f8269c5ba545463ea5 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 20:10:46 -0800 Subject: [PATCH 22/30] Docstring for CI --- src/source/channel_router.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 8616d8f7..c5afd365 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -6,6 +6,14 @@ use std::{ 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. From 08789de6a20066a0be2933419665f2f51c4dfee0 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 20:14:51 -0800 Subject: [PATCH 23/30] For the pickier ubuntu-latest clippy --- src/source/channel_router.rs | 6 ++---- src/source/mod.rs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index c5afd365..04484363 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -12,7 +12,7 @@ use std::{ /// - 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 +/// 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 @@ -56,9 +56,7 @@ impl ChannelRouterController { /// Successive calls to `mix` 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) { - if let Err(_) = self - .sender - .send(ChannelRouterMessage(from as usize, to as usize, gain)) + if self.sender.send(ChannelRouterMessage(from as usize, to as usize, gain)).is_err() { todo!("Probably shouldn't panic here"); } diff --git a/src/source/mod.rs b/src/source/mod.rs index ceb75ac1..670743b2 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -390,7 +390,7 @@ where 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 as usize] = 1.0f32; + mapping[input_channel as usize][output_channel] = 1.0f32; } channel_router::channel_router(self, output_count, mapping).1 } From 8cc8c6e00125e78c48f9e12a6df4f5623a698c18 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 20:25:22 -0800 Subject: [PATCH 24/30] Removing the todo and addressing clippy --- src/source/channel_router.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 04484363..7d56ad85 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -41,6 +41,9 @@ where struct ChannelRouterMessage(usize, usize, f32); +/// `ChannelRouterController::map()` returns this error if the router source has been dropped. +pub struct ChannelRouterControllerError {} + /// A controller type that sends gain updates to a corresponding [`ChannelRouterSource`]. #[derive(Debug, Clone)] pub struct ChannelRouterController { @@ -55,10 +58,20 @@ impl ChannelRouterController { /// /// Successive calls to `mix` 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) { - if self.sender.send(ChannelRouterMessage(from as usize, to as usize, gain)).is_err() + 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() { - todo!("Probably shouldn't panic here"); + Err(ChannelRouterControllerError {}) + } else { + Ok(()) } } } From 084fb7800bdea110d16f8b97b6847b26b0e51439 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 20:30:25 -0800 Subject: [PATCH 25/30] Additional tests and impl for tests --- src/source/channel_router.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 7d56ad85..394f4db2 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -42,6 +42,7 @@ where struct ChannelRouterMessage(usize, usize, f32); /// `ChannelRouterController::map()` returns this error if the router source has been dropped. +#[derive(Debug, Eq, PartialEq)] pub struct ChannelRouterControllerError {} /// A controller type that sends gain updates to a corresponding [`ChannelRouterSource`]. @@ -281,8 +282,10 @@ mod tests { assert_eq!(v1[0], 0i16); assert_eq!(v1[1], -2i16); - controller.map(0, 0, 0.0f32); - controller.map(1, 0, 2.0f32); + 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); From c9cc895af9599ca183bd5dcc75ce0298f1aba73f Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Mon, 9 Dec 2024 21:56:00 -0800 Subject: [PATCH 26/30] Update channel_router.rs Typo --- src/source/channel_router.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 394f4db2..3044c65f 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -57,7 +57,7 @@ impl ChannelRouterController { /// 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 `mix` with the same `from` and `to` arguments will replace the + /// 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 a6f0873f1ba532ab0eb0ffc049edd11660ad3c6e Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Tue, 10 Dec 2024 10:15:21 -0800 Subject: [PATCH 27/30] Added a channel_routing example --- Cargo.toml | 3 +++ examples/channel_routing.rs | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 examples/channel_routing.rs diff --git a/Cargo.toml b/Cargo.toml index 599cb059..681540f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,3 +74,6 @@ required-features = ["symphonia-isomp4", "symphonia-aac"] [[example]] name = "noise_generator" required-features = ["noise"] + +[[example]] +name = "channel_routing" diff --git a/examples/channel_routing.rs b/examples/channel_routing.rs new file mode 100644 index 00000000..458fedfb --- /dev/null +++ b/examples/channel_routing.rs @@ -0,0 +1,43 @@ +//! Channel router example + +use std::error::Error; + +fn main() -> Result<(), Box> { + use rodio::source::{Function, SignalGenerator, Source}; + use std::thread; + use std::time::Duration; + + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; + + // let test_signal_duration = Duration::from_millis(1000); + let interval_duration = Duration::from_millis(100); + let sample_rate = cpal::SampleRate(48000); + + let (mut controller, router) = SignalGenerator::new(sample_rate, 1000.0, Function::Triangle) + .amplify(0.1) + .channel_router(2, vec![vec![0.0f32, 0.0f32]]); + + println!("Playing 1000Hz tone"); + + stream_handle.mixer().add(router); + + for i in 0..1000 { + thread::sleep(interval_duration); + let n = i % 20; + match n { + 0 => println!("Left speaker ramp up"), + 1..10 => { + _ = controller.map(0, 0, n as f32 / 10.0); + _ = controller.map(0, 1, 0f32); + } + 10 => println!("Right speaker ramp up"), + 11..20 => { + _ = controller.map(0, 0, 0.0f32); + _ = controller.map(0, 1, (n - 10) as f32 / 10.0); + } + _ => {} + } + } + + Ok(()) +} From d0bdd4574fc743ab00875c9e48743fb4e7d92731 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Tue, 10 Dec 2024 11:16:59 -0800 Subject: [PATCH 28/30] Made channel_routing example interactive --- examples/channel_routing.rs | 48 +++++++++++++++++++++--------------- src/source/channel_router.rs | 11 +++++++-- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/examples/channel_routing.rs b/examples/channel_routing.rs index 458fedfb..04cb9fd3 100644 --- a/examples/channel_routing.rs +++ b/examples/channel_routing.rs @@ -1,41 +1,49 @@ //! Channel router example -use std::error::Error; +use std::io::prelude::*; +use std::{error::Error, io}; fn main() -> Result<(), Box> { use rodio::source::{Function, SignalGenerator, Source}; - use std::thread; - use std::time::Duration; + // use std::thread; + // use std::time::Duration; let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; // let test_signal_duration = Duration::from_millis(1000); - let interval_duration = Duration::from_millis(100); + // let interval_duration = Duration::from_millis(100); let sample_rate = cpal::SampleRate(48000); - let (mut controller, router) = SignalGenerator::new(sample_rate, 1000.0, Function::Triangle) + 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!("Playing 1000Hz tone"); + println!("Control left and right levels separately:"); + println!("q: left+\na: left-\nw: right+\ns: right-\nx: quit"); stream_handle.mixer().add(router); - for i in 0..1000 { - thread::sleep(interval_duration); - let n = i % 20; - match n { - 0 => println!("Left speaker ramp up"), - 1..10 => { - _ = controller.map(0, 0, n as f32 / 10.0); - _ = controller.map(0, 1, 0f32); + 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}"); } - 10 => println!("Right speaker ramp up"), - 11..20 => { - _ = controller.map(0, 0, 0.0f32); - _ = controller.map(0, 1, (n - 10) as f32 / 10.0); - } - _ => {} + _ => continue, } } diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 3044c65f..0752fee7 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -2,8 +2,7 @@ use crate::{Sample, Source}; use std::{ - cmp::min, - sync::mpsc::{channel, Receiver, Sender}, + cmp::min, error::Error, fmt, sync::mpsc::{channel, Receiver, Sender} }; /// A matrix to map inputs to outputs according to a gain @@ -45,6 +44,14 @@ struct ChannelRouterMessage(usize, usize, f32); #[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 { From 276f23f901ff57206b9c88e899244d6a3df75a44 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Tue, 10 Dec 2024 11:17:48 -0800 Subject: [PATCH 29/30] rustfmt --- src/source/channel_router.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 0752fee7..66cef330 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -2,7 +2,10 @@ use crate::{Sample, Source}; use std::{ - cmp::min, error::Error, fmt, sync::mpsc::{channel, Receiver, Sender} + cmp::min, + error::Error, + fmt, + sync::mpsc::{channel, Receiver, Sender}, }; /// A matrix to map inputs to outputs according to a gain From 5ed8da286cbba02a0e652d6622e34ff6f4570681 Mon Sep 17 00:00:00 2001 From: Jamie Hardt Date: Wed, 1 Jan 2025 11:26:26 -0800 Subject: [PATCH 30/30] Test fixes --- Cargo.toml | 1 + examples/channel_routing.rs | 2 +- src/source/channel_router.rs | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 17c8644f..1a763ebb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,3 +83,4 @@ name = "channel_routing" [[example]] name = "into_file" required-features = ["wav", "mp3"] + diff --git a/examples/channel_routing.rs b/examples/channel_routing.rs index 10fbfc0c..22ec0b5d 100644 --- a/examples/channel_routing.rs +++ b/examples/channel_routing.rs @@ -8,7 +8,7 @@ fn main() -> Result<(), Box> { let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sample_rate = cpal::SampleRate(48000); + let sample_rate: u32 = 48000; let (mut controller, router) = SignalGenerator::new(sample_rate, 440.0, Function::Triangle) .amplify(0.1) diff --git a/src/source/channel_router.rs b/src/source/channel_router.rs index 66cef330..8ae9677a 100644 --- a/src/source/channel_router.rs +++ b/src/source/channel_router.rs @@ -173,8 +173,8 @@ where I::Item: Sample, { #[inline] - fn current_frame_len(&self) -> Option { - self.input.current_frame_len() + fn current_span_len(&self) -> Option { + self.input.current_span_len() } #[inline] @@ -210,7 +210,7 @@ where // audio frame. let samples_to_take = min( input_channels, - self.input.current_frame_len().unwrap_or(usize::MAX), + self.input.current_span_len().unwrap_or(usize::MAX), ); // fill the input buffer. If the input is exhausted and returning None this will make