How Meetily's Professional Audio Mixer Applies RMS-Based Ducking: A Technical Deep Dive

Meetily synchronizes microphone and system audio streams in a lock‑free ring buffer, calculates the Root‑Mean‑Square (RMS) energy of each 50 ms microphone window to detect speech activity, and automatically attenuates system audio gain to 0.60 (a 40 % reduction) when speech is present while preventing digital clipping through sample clamping and soft scaling.

Meetily, an open‑source AI meeting assistant by Zackriya‑Solutions, records both microphone input and system audio simultaneously to create complete meeting archives. To ensure voice remains intelligible over background music or notifications, the Rust codebase implements professional RMS‑based ducking that dynamically adjusts stream levels in real time. This article examines the precise algorithms and data structures found in pipeline.rs and ffmpeg_mixer.rs that make this broadcast‑grade mixing possible.

Audio Mixing Architecture Overview

The audio pipeline centers on two complementary implementations that both enforce the same RMS‑based ducking semantics.

AudioMixerRingBuffer and ProfessionalAudioMixer live in frontend/src-tauri/src/audio/pipeline.rs and provide a lightweight, single‑file solution. They use a fixed‑size VecDeque to align incoming mic and system samples into synchronized 50 ms windows before mixing.

FFmpegAudioMixer in frontend/src-tauri/src/audio/ffmpeg_mixer.rs offers a full‑featured alternative with separate per‑source buffers, configurable parameters, and additional gap handling for Bluetooth devices. Both implementations share identical ducking logic: when microphone RMS exceeds the speech threshold, system gain is reduced; otherwise, it remains at full volume.

The RMS-Based Ducking Algorithm Step-by-Step

Window Extraction and Synchronization

Before any level detection occurs, the AudioMixerRingBuffer ensures temporal alignment between the two asynchronous capture streams.

When both buffers contain at least window_size_samples (equivalent to 50 ms at the current sample rate), the extract_window method returns aligned slices (mic_window, system_window). The ring buffer pads missing data with silence to avoid artifacts and drops samples older than a 400 ms safety limit to prevent unbounded memory growth.

// Conceptual flow from pipeline.rs
let (mic_window, sys_window) = ring_buffer.extract_window();

RMS Calculation and Speech Detection

With aligned windows available, the mixer calculates the microphone's energy using the standard RMS formula.

The calculate_rms function (or its inline equivalent in ProfessionalAudioMixer) computes the square root of the mean squared amplitude:

fn calculate_rms(samples: &[f32]) -> f32 {
    let sum_squares: f32 = samples.iter().map(|s| s * s).sum();
    (sum_squares / samples.len() as f32).sqrt()
}

The resulting value is compared against SPEECH_THRESHOLD = 0.01. An RMS above this constant indicates active speech, triggering the ducking behavior.

Dynamic Gain Reduction

When speech is detected, the mixer applies a hard‑coded ducking factor to the system audio stream.

  • Speech present (rms > 0.01): system_gain = self.system_ducking (default 0.60, approximately ‑4.4 dB)
  • Silence: system_gain = 1.0 (0 dB, full volume)

The microphone stream may also be scaled by mic_ducking (typically 1.0) to balance the mix. This gain structure ensures the microphone remains the focal point without completely muting system sounds.

Mixed Output and Clipping Prevention

The final mix combines the scaled samples and enforces hard limits to prevent digital distortion.

For each sample pair, the algorithm computes:

let mixed = (mic_sample * self.mic_ducking) + (system_sample * system_gain);
result.push(mixed.clamp(-1.0, 1.0));

The clamp operation prevents values outside the normalized ‑1.0 to +1.0 range. In the ProfessionalAudioMixer implementation, an additional soft‑scaling step proportionally reduces the entire window if the sum exceeds unity, preserving the dynamic relationships between samples while eliminating harsh clipping.

Implementation in pipeline.rs

The primary mixing logic resides in frontend/src-tauri/src/audio/pipeline.rs, which defines both the synchronization primitive and the ducking mixer.

The AudioPipeline struct orchestrates the flow: it receives raw AudioChunks from the capture layer (microphone and system), pushes them into the AudioMixerRingBuffer, extracts synchronized windows, and delegates mixing to ProfessionalAudioMixer::mix_window. The resulting audio is then forwarded to both the VAD → Whisper transcription pipeline and the WAV recorder via recording_sender_for_mixed.

// From pipeline.rs - the core mixing method
fn mix(&mut self, mic: &[f32], system: &[f32]) -> Vec<f32> {
    let mic_rms = calculate_rms(mic);
    let is_speech = mic_rms > 0.01;               // speech threshold
    let system_gain = if is_speech { self.system_ducking } else { 1.0 };

    mic.iter()
        .zip(system.iter())
        .map(|(&m, &s)| (m * self.mic_ducking + s * system_gain).clamp(-1.0, 1.0))
        .collect()
}

Advanced Ducking in ffmpeg_mixer.rs

For scenarios requiring more robust buffer management (such as handling Bluetooth audio gaps), frontend/src-tauri/src/audio/ffmpeg_mixer.rs provides FFmpegAudioMixer.

This implementation maintains independent resampling buffers for each input source and applies the identical RMS detection and ducking logic, but adds configurable latency compensation and drift correction. The ducking factor remains 0.60 during speech activity, demonstrating that the RMS‑based approach is consistent across both the simple and advanced mixer variants.

Integration Example

To utilize this system, the Tauri backend initializes an AudioPipelineManager which wires together the capture devices, ring buffer, mixer, and recording sink:

let mut manager = AudioPipelineManager::default();
manager.start(
    state.clone(),
    transcription_sender,
    /* target_chunk_duration_ms */ 0,
    /* sample_rate */ 48000,
    Some(recording_sender),          // receive mixed audio for WAV files
    mic_device_name,
    mic_device_kind,
    system_device_name,
    system_device_kind,
)?;

The manager spawns an async AudioPipeline::run loop that continues processing until the sender channel closes, guaranteeing all buffered audio flushes to disk before shutdown.

Summary

  • Meetily uses a lock‑free AudioMixerRingBuffer in pipeline.rs to synchronize microphone and system audio into 50 ms aligned windows.
  • RMS detection calculates the energy of each microphone window; values above 0.01 trigger ducking.
  • Ducking attenuation reduces system audio gain to 0.60 (40 % reduction) during speech, restoring full volume during silence.
  • Clip protection uses clamp(-1.0, 1.0) and soft‑scaling to keep mixed output within valid floating‑point ranges.
  • Two implementations exist: the lightweight ProfessionalAudioMixer for standard use, and FFmpegAudioMixer for advanced device handling with identical ducking semantics.

Frequently Asked Questions

What is RMS-based ducking in audio processing?

RMS‑based ducking is a dynamic range compression technique that measures the Root‑Mean‑Square energy of a control signal (like a microphone) to automatically reduce the volume of another audio stream (like system audio) when speech is detected. Meetily implements this in Rust by calculating the RMS of 50 ms microphone windows and attenuating system gain by 40 % whenever the energy exceeds the 0.01 threshold.

Why does Meetily use a 50ms window size for audio mixing?

The 50 ms window size strikes a balance between responsive ducking and stable level detection. Windows shorter than 20 ms capture transient noise that could trigger false ducking, while windows longer than 100 ms introduce noticeable latency in the ducking response. The 50 ms duration aligns with the ring buffer implementation in pipeline.rs and matches typical speech syllable durations for natural‑sounding gain changes.

How does Meetily prevent audio clipping when mixing multiple streams?

The mixer prevents clipping through a two‑stage protection mechanism: first, it applies clamp(-1.0, 1.0) to every summed sample to enforce hard limits; second, the ProfessionalAudioMixer performs a soft‑scaling pass that proportionally reduces the entire window if the peak exceeds unity gain. This combination eliminates digital distortion while preserving the relative dynamics between microphone and system audio.

What is the difference between ProfessionalAudioMixer and FFmpegAudioMixer?

ProfessionalAudioMixer (in pipeline.rs) is a streamlined implementation using a shared ring buffer for straightforward use cases. FFmpegAudioMixer (in ffmpeg_mixer.rs) provides a more robust architecture with separate per‑source buffers, configurable parameters, and specialized handling for Bluetooth device gaps and clock drift. Both apply identical RMS‑based ducking logic with the same 0.60 gain reduction factor during speech activity.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →