Add automatic-volume to legacy (#51919)

Yara 🏳️‍⚧️ created

## Context

This allows using of Rodio's effects library within our home brewn audio
pipeline. The alternative would be inlining Rodio's effects which is
problematic from a legal stance. We would then have to make clear that
code is not owned by zed-industries while the code would be surrounded
by zed-industries owned code.

This adaptor does incur a slight performance penalty (copying into a
pre-allocated vec and back) however the impact will be immeasurably low.

There is no latency impact.

## How to Review

- Adds an adapter for Rodio effects
- Enables the adapter and effects only when the setting is enabled
-Makes the setting pub(crate) so we can use it from livekit playback

## Self-Review Checklist

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- Added Automatic volume control to calls

Change summary

crates/audio/src/audio.rs                            |  1 
crates/audio/src/audio_settings.rs                   |  6 
crates/livekit_client/src/livekit_client/playback.rs | 89 ++++++++++++++
3 files changed, 93 insertions(+), 3 deletions(-)

Detailed changes

crates/audio/src/audio.rs 🔗

@@ -8,6 +8,7 @@ pub const CHANNEL_COUNT: ChannelCount = nz!(2);
 
 mod audio_settings;
 pub use audio_settings::AudioSettings;
+pub use audio_settings::LIVE_SETTINGS;
 
 mod audio_pipeline;
 pub use audio_pipeline::{Audio, VoipParts};

crates/audio/src/audio_settings.rs 🔗

@@ -71,8 +71,8 @@ impl Settings for AudioSettings {
 }
 
 /// See docs on [LIVE_SETTINGS]
-pub(crate) struct LiveSettings {
-    pub(crate) auto_microphone_volume: AtomicBool,
+pub struct LiveSettings {
+    pub auto_microphone_volume: AtomicBool,
     pub(crate) auto_speaker_volume: AtomicBool,
     pub(crate) denoise: AtomicBool,
 }
@@ -128,7 +128,7 @@ impl LiveSettings {
 /// observer of SettingsStore. Needed because audio playback and recording are
 /// real time and must each run in a dedicated OS thread, therefore we can not
 /// use the background executor.
-pub(crate) static LIVE_SETTINGS: LiveSettings = LiveSettings {
+pub static LIVE_SETTINGS: LiveSettings = LiveSettings {
     auto_microphone_volume: AtomicBool::new(true),
     auto_speaker_volume: AtomicBool::new(true),
     denoise: AtomicBool::new(true),

crates/livekit_client/src/livekit_client/playback.rs 🔗

@@ -23,6 +23,8 @@ use livekit::webrtc::{
 use log::info;
 use parking_lot::Mutex;
 use rodio::Source;
+use rodio::conversions::SampleTypeConverter;
+use rodio::source::{AutomaticGainControlSettings, LimitSettings};
 use serde::{Deserialize, Serialize};
 use settings::Settings;
 use std::cell::RefCell;
@@ -380,6 +382,14 @@ impl AudioStack {
                         let ten_ms_buffer_size =
                             (config.channels() as u32 * config.sample_rate() / 100) as usize;
                         let mut buf: Vec<i16> = Vec::with_capacity(ten_ms_buffer_size);
+                        let mut rodio_effects = RodioEffectsAdaptor::new(buf.len())
+                            .automatic_gain_control(AutomaticGainControlSettings {
+                                target_level: 0.50,
+                                attack_time: Duration::from_secs(1),
+                                release_time: Duration::from_secs(0),
+                                absolute_max_gain: 5.0,
+                            })
+                            .limit(LimitSettings::live_performance());
 
                         let stream = device
                             .build_input_stream_raw(
@@ -411,6 +421,21 @@ impl AudioStack {
                                                     sample_rate,
                                                 )
                                                 .to_owned();
+
+                                            if audio::LIVE_SETTINGS
+                                                .auto_microphone_volume
+                                                .load(Ordering::Relaxed)
+                                            {
+                                                rodio_effects
+                                                    .inner_mut()
+                                                    .inner_mut()
+                                                    .fill_buffer_with(&sampled);
+                                                sampled.clear();
+                                                sampled.extend(SampleTypeConverter::<_, i16>::new(
+                                                    rodio_effects.by_ref(),
+                                                ));
+                                            }
+
                                             apm.lock()
                                                 .process_stream(
                                                     &mut sampled,
@@ -419,6 +444,7 @@ impl AudioStack {
                                                 )
                                                 .log_err();
                                             buf.clear();
+
                                             frame_tx
                                                 .try_send(TimestampedFrame {
                                                     frame: AudioFrame {
@@ -453,6 +479,69 @@ impl AudioStack {
     }
 }
 
+/// This allows using of Rodio's effects library within our home brewn audio
+/// pipeline. The alternative would be inlining Rodio's effects which is
+/// problematic from a legal stance. We would then have to make clear that code
+/// is not owned by zed-industries while the code would be surrounded by
+/// zed-industries owned code.
+///
+/// This adaptor does incur a slight performance penalty (copying into a
+/// pre-allocated vec and back) however the impact will be immeasurably low.
+///
+/// There is no latency impact.
+pub struct RodioEffectsAdaptor {
+    input: Vec<rodio::Sample>,
+    pos: usize,
+}
+
+impl RodioEffectsAdaptor {
+    // This implementation incorrect terminology confusing everyone. A normal
+    // audio frame consists of all samples for one moment in time (one for mono,
+    // two for stereo). Here a frame of audio refers to a 10ms buffer of samples.
+    fn new(samples_per_frame: usize) -> Self {
+        Self {
+            input: Vec::with_capacity(samples_per_frame),
+            pos: 0,
+        }
+    }
+
+    fn fill_buffer_with(&mut self, integer_samples: &[i16]) {
+        self.input.clear();
+        self.input.extend(SampleTypeConverter::<_, f32>::new(
+            integer_samples.iter().copied(),
+        ));
+        self.pos = 0;
+    }
+}
+
+impl Iterator for RodioEffectsAdaptor {
+    type Item = rodio::Sample;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let sample = self.input.get(self.pos)?;
+        self.pos += 1;
+        Some(*sample)
+    }
+}
+
+impl rodio::Source for RodioEffectsAdaptor {
+    fn current_span_len(&self) -> Option<usize> {
+        None
+    }
+
+    fn channels(&self) -> rodio::ChannelCount {
+        rodio::nz!(2)
+    }
+
+    fn sample_rate(&self) -> rodio::SampleRate {
+        rodio::nz!(48000)
+    }
+
+    fn total_duration(&self) -> Option<Duration> {
+        None
+    }
+}
+
 #[derive(Serialize, Deserialize, Debug)]
 pub struct Speaker {
     pub name: String,