@@ -175,8 +175,9 @@ impl Audio {
info!("Opened microphone: {:?}", stream.config());
let (replay, stream) = stream
- // .suspicious_stereo_to_mono()
- .constant_params(CHANNEL_COUNT, SAMPLE_RATE)
+ .possibly_disconnected_channels_to_mono()
+ .constant_samplerate(SAMPLE_RATE)
+ // .constant_params(CHANNEL_COUNT, SAMPLE_RATE)
.limit(LimitSettings::live_performance())
.process_buffer::<BUFFER_SIZE, _>(move |buffer| {
let mut int_buffer: [i16; _] = buffer.map(|s| s.to_sample());
@@ -1,5 +1,6 @@
use std::{
f32,
+ num::NonZero,
sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
@@ -9,12 +10,21 @@ use std::{
use crossbeam::queue::ArrayQueue;
use denoise::{Denoiser, DenoiserError};
-use rodio::{ChannelCount, Sample, SampleRate, Source, source::UniformSourceIterator};
+use log::warn;
+use rodio::{
+ ChannelCount, Sample, SampleRate, Source, conversions::SampleRateConverter, nz,
+ source::UniformSourceIterator,
+};
+
+const MAX_CHANNELS: usize = 8;
#[derive(Debug, thiserror::Error)]
#[error("Replay duration is too short must be >= 100ms")]
pub struct ReplayDurationTooShort;
+// These all require constant sources (so the span is infinitely long)
+// this is not guaranteed by rodio however we know it to be true in all our
+// applications. Rodio desperately needs a constant source concept.
pub trait RodioExt: Source + Sized {
fn process_buffer<const N: usize, F>(self, callback: F) -> ProcessBuffer<N, Self, F>
where
@@ -33,7 +43,8 @@ pub trait RodioExt: Source + Sized {
channel_count: ChannelCount,
sample_rate: SampleRate,
) -> UniformSourceIterator<Self>;
- fn suspicious_stereo_to_mono(self) -> ToMono<Self>;
+ fn constant_samplerate(self, sample_rate: SampleRate) -> ConstantSampleRate<Self>;
+ fn possibly_disconnected_channels_to_mono(self) -> ToMono<Self>;
}
impl<S: Source> RodioExt for S {
@@ -121,32 +132,88 @@ impl<S: Source> RodioExt for S {
) -> UniformSourceIterator<Self> {
UniformSourceIterator::new(self, channel_count, sample_rate)
}
- fn suspicious_stereo_to_mono(self) -> ToMono<Self> {
- ToMono {
- input_channel_count: self.channels(),
- inner: self,
- mean: f32::EPSILON * 3.0,
+ fn constant_samplerate(self, sample_rate: SampleRate) -> ConstantSampleRate<Self> {
+ ConstantSampleRate::new(self, sample_rate)
+ }
+ fn possibly_disconnected_channels_to_mono(self) -> ToMono<Self> {
+ ToMono::new(self)
+ }
+}
+
+pub struct ConstantSampleRate<S: Source> {
+ inner: SampleRateConverter<S>,
+ channels: ChannelCount,
+ sample_rate: SampleRate,
+}
+
+impl<S: Source> ConstantSampleRate<S> {
+ fn new(source: S, target_rate: SampleRate) -> Self {
+ let input_sample_rate = source.sample_rate();
+ let channels = source.channels();
+ let inner = SampleRateConverter::new(source, input_sample_rate, target_rate, channels);
+ Self {
+ inner,
+ channels,
+ sample_rate: target_rate,
}
}
}
+impl<S: Source> Iterator for ConstantSampleRate<S> {
+ type Item = rodio::Sample;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.inner.next()
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.inner.size_hint()
+ }
+}
+
+impl<S: Source> Source for ConstantSampleRate<S> {
+ fn current_span_len(&self) -> Option<usize> {
+ None
+ }
+
+ fn channels(&self) -> ChannelCount {
+ self.channels
+ }
+
+ fn sample_rate(&self) -> SampleRate {
+ self.sample_rate
+ }
+
+ fn total_duration(&self) -> Option<Duration> {
+ None // not supported (not used by us)
+ }
+}
+
+const TYPICAL_NOISE_FLOOR: Sample = 1e-3;
+
/// constant source, only works on a single span
pub struct ToMono<S> {
inner: S,
input_channel_count: ChannelCount,
+ connected_channels: ChannelCount,
/// running mean of second channel 'volume'
- mean: f32,
+ means: [f32; MAX_CHANNELS],
}
-impl<S> ToMono<S> {
- fn real_stereo(&self) -> bool {
- dbg!(self.mean);
- dbg!(self.mean >= f32::EPSILON * 3.0)
- }
+impl<S: Source> ToMono<S> {
+ fn new(input: S) -> Self {
+ let channels = input
+ .channels()
+ .min(const { NonZero::<u16>::new(MAX_CHANNELS as u16).unwrap() });
+ if channels < input.channels() {
+ warn!("Ignoring input channels {}..", channels.get());
+ }
- fn update_mean(&mut self, second_channel: Sample) {
- const HISTORY: f32 = 500.0;
- self.mean *= (HISTORY - 1.0) / HISTORY;
- self.mean += second_channel.abs() / HISTORY;
+ Self {
+ connected_channels: channels,
+ input_channel_count: channels,
+ inner: input,
+ means: [TYPICAL_NOISE_FLOOR; MAX_CHANNELS],
+ }
}
}
@@ -168,25 +235,31 @@ impl<S: Source> Source for ToMono<S> {
}
}
+fn update_mean(mean: &mut f32, sample: Sample) {
+ const HISTORY: f32 = 500.0;
+ *mean *= (HISTORY - 1.0) / HISTORY;
+ *mean += sample.abs() / HISTORY;
+}
+
impl<S: Source> Iterator for ToMono<S> {
type Item = Sample;
fn next(&mut self) -> Option<Self::Item> {
- match self.input_channel_count.get() {
- 1 => self.next(),
- 2 => {
- let first_channel = self.inner.next().unwrap();
- let second_channel = self.inner.next()?;
- self.update_mean(second_channel);
-
- if self.real_stereo() {
- Some((first_channel + second_channel) / 2.0)
- } else {
- Some(first_channel)
- }
+ let mut mono_sample = 0f32;
+ let mut active_channels = 0;
+ for channel in 0..self.input_channel_count.get() as usize {
+ let sample = self.inner.next()?;
+ mono_sample += sample;
+
+ update_mean(&mut self.means[channel], sample);
+ if self.means[channel] > TYPICAL_NOISE_FLOOR / 10.0 {
+ active_channels += 1;
}
- _ => todo!("unsupported channel count"),
}
+ mono_sample /= self.connected_channels.get() as f32;
+ self.connected_channels = NonZero::new(active_channels).unwrap_or(nz!(1));
+
+ Some(mono_sample)
}
}