diff --git a/crates/audio/src/audio.rs b/crates/audio/src/audio.rs index f90a1ac95373c6f98bbf37c9e5b97721d04dbca1..174c77762dc92ff00a66f06ad6f27e5947b30580 100644 --- a/crates/audio/src/audio.rs +++ b/crates/audio/src/audio.rs @@ -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::(move |buffer| { let mut int_buffer: [i16; _] = buffer.map(|s| s.to_sample()); diff --git a/crates/audio/src/rodio_ext.rs b/crates/audio/src/rodio_ext.rs index eff9aec92d72e0b3cf835e18de3790a63f6bd3ea..e263dd99745738d1c7cf4ccfff580ec5d817f745 100644 --- a/crates/audio/src/rodio_ext.rs +++ b/crates/audio/src/rodio_ext.rs @@ -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(self, callback: F) -> ProcessBuffer where @@ -33,7 +43,8 @@ pub trait RodioExt: Source + Sized { channel_count: ChannelCount, sample_rate: SampleRate, ) -> UniformSourceIterator; - fn suspicious_stereo_to_mono(self) -> ToMono; + fn constant_samplerate(self, sample_rate: SampleRate) -> ConstantSampleRate; + fn possibly_disconnected_channels_to_mono(self) -> ToMono; } impl RodioExt for S { @@ -121,32 +132,88 @@ impl RodioExt for S { ) -> UniformSourceIterator { UniformSourceIterator::new(self, channel_count, sample_rate) } - fn suspicious_stereo_to_mono(self) -> ToMono { - ToMono { - input_channel_count: self.channels(), - inner: self, - mean: f32::EPSILON * 3.0, + fn constant_samplerate(self, sample_rate: SampleRate) -> ConstantSampleRate { + ConstantSampleRate::new(self, sample_rate) + } + fn possibly_disconnected_channels_to_mono(self) -> ToMono { + ToMono::new(self) + } +} + +pub struct ConstantSampleRate { + inner: SampleRateConverter, + channels: ChannelCount, + sample_rate: SampleRate, +} + +impl ConstantSampleRate { + 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 Iterator for ConstantSampleRate { + type Item = rodio::Sample; + + fn next(&mut self) -> Option { + self.inner.next() + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl Source for ConstantSampleRate { + fn current_span_len(&self) -> Option { + None + } + + fn channels(&self) -> ChannelCount { + self.channels + } + + fn sample_rate(&self) -> SampleRate { + self.sample_rate + } + + fn total_duration(&self) -> Option { + 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 { inner: S, input_channel_count: ChannelCount, + connected_channels: ChannelCount, /// running mean of second channel 'volume' - mean: f32, + means: [f32; MAX_CHANNELS], } -impl ToMono { - fn real_stereo(&self) -> bool { - dbg!(self.mean); - dbg!(self.mean >= f32::EPSILON * 3.0) - } +impl ToMono { + fn new(input: S) -> Self { + let channels = input + .channels() + .min(const { NonZero::::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 Source for ToMono { } } +fn update_mean(mean: &mut f32, sample: Sample) { + const HISTORY: f32 = 500.0; + *mean *= (HISTORY - 1.0) / HISTORY; + *mean += sample.abs() / HISTORY; +} + impl Iterator for ToMono { type Item = Sample; fn next(&mut self) -> Option { - 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) } }