# How Meetily Implements RMS-Based Ducking for Professional Audio Mixing

> Meetily's audio mixer uses RMS-based ducking to automatically lower system audio when voice is detected. Ensure clear dialogue and prevent clipping with intelligent gain staging.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: deep-dive
- Published: 2026-07-15

---

**Meetily's audio mixer uses RMS (Root-Mean-Square) calculations on 50ms microphone windows to detect speech activity, automatically reducing system audio gain to 0.60 (a 40% reduction) whenever voice is detected, ensuring clear dialogue prominence while preventing clipping through intelligent gain staging.**

Meetily is an open-source meeting recording tool that synchronizes microphone and system audio streams in real-time. The **RMS-based ducking** algorithm implemented in its Rust audio pipeline ensures that voice content remains audible above background music or notification sounds, producing broadcast-quality mixed recordings without manual level adjustments.

## Audio Pipeline Architecture

The ducking system operates within a multi-stage pipeline that synchronizes asynchronous audio sources before applying dynamic gain control.

### Ring Buffer Synchronization

The `AudioMixerRingBuffer` in [`frontend/src-tauri/src/audio/pipeline.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/pipeline.rs) manages time-alignment between microphone and system audio streams. This lock-free `VecDeque` implementation buffers incoming samples until both sources contain sufficient data for a complete mixing window.

The ring buffer maintains a 400ms safety limit, dropping old samples when buffers exceed this threshold to prevent memory bloat. When both buffers contain at least `window_size_samples` (approximately 50ms), the `extract_window` method returns aligned slices (`mic_window`, `sys_window`), zero-padding any missing data to prevent artifacts.

### Mixer Components

Meetily provides two implementations of the ducking algorithm:

- **`ProfessionalAudioMixer`**: Located in [`pipeline.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/pipeline.rs), this simpler mixer handles basic RMS detection and applies fixed ducking factors.
- **`FFmpegAudioMixer`**: Found in [`frontend/src-tauri/src/audio/ffmpeg_mixer.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/ffmpeg_mixer.rs), this full-featured variant includes adaptive buffering and specialized gap-handling for Bluetooth devices while implementing the identical RMS-based ducking logic.

## The RMS-Based Ducking Algorithm

The ducking mechanism follows a six-step process to analyze microphone energy and adjust system audio levels dynamically.

### RMS Calculation and Speech Detection

For each 50ms window, the mixer calculates the microphone's RMS level to determine speech presence. The implementation in [`pipeline.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/pipeline.rs) uses the standard RMS formula:

```rust
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 algorithm compares this value against a hard-coded `SPEECH_THRESHOLD = 0.01`. When the calculated RMS exceeds this threshold, the mixer identifies active speech and triggers the ducking mechanism.

### Dynamic Gain Adjustment

Upon speech detection, the mixer applies the `system_ducking` factor (default **0.60**) to the system audio stream, reducing its volume by 40%. When the microphone is silent, system audio plays at full gain (1.0).

The gain calculation occurs in the `mix_window` method:

```rust
let mic_rms = calculate_rms(mic);
let is_speech = mic_rms > 0.01;
let system_gain = if is_speech { self.system_ducking } else { 1.0 };

```

### Mixing and Clipping Protection

The final mixing stage combines both streams with clamping to prevent digital distortion. The mixer computes each output sample as:

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

```

This clamping ensures the output remains within the valid -1.0 to +1.0 range. The `FFmpegAudioMixer` implementation adds proportional soft-scaling for any over-range sums that might occur during high-amplitude moments.

## Code Implementation Examples

### Core Ducking Logic in pipeline.rs

The following excerpt from [`frontend/src-tauri/src/audio/pipeline.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/pipeline.rs) demonstrates the complete ducking algorithm:

```rust
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()
}

```

### FFmpeg-Style Mixer Usage

For custom tooling or advanced use cases, the `FFmpegAudioMixer` provides a configurable interface:

```rust
use meetily::frontend::src_tauri::src::audio::ffmpeg_mixer::FFmpegAudioMixer;
use meetily::frontend::src_tauri::src::audio::device_detection::InputDeviceKind;

let mut mixer = FFmpegAudioMixer::new(
    "Built‑in Microphone".into(),
    InputDeviceKind::Wired,
    "BlackHole".into(),
    InputDeviceKind::Wired,
    48_000,
);

// Push raw chunks as they arrive
mixer.push_mic(mic_chunk);
mixer.push_system(system_chunk);

// Extract processed window with ducking applied
if let Some(mixed) = mixer.pop_mixed() {
    send_to_transcriber(mixed);
}

```

### Pipeline Initialization

The ducking system initializes through the `AudioPipelineManager`, which coordinates the ring buffer, mixer, and VAD components:

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

```

## Summary

- **RMS-based ducking** in Meetily analyzes microphone energy using 50ms windows to detect speech activity in real-time.
- The `ProfessionalAudioMixer` and `FFmpegAudioMixer` both implement a threshold of **0.01 RMS** with a default ducking factor of **0.60** (40% attenuation).
- The `AudioMixerRingBuffer` maintains synchronization between asynchronous mic and system streams using a 400ms safety buffer.
- All mixed output undergoes clamping to **-1.0…+1.0** to prevent hard clipping, with optional soft-scaling in the FFmpeg implementation.
- The complete implementation resides in [`frontend/src-tauri/src/audio/pipeline.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/pipeline.rs) and [`frontend/src-tauri/src/audio/ffmpeg_mixer.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/ffmpeg_mixer.rs).

## Frequently Asked Questions

### What is RMS-based ducking and why does Meetily use it?

**RMS-based ducking** measures the Root-Mean-Square energy of audio windows to objectively quantify loudness. Meetily uses this method rather than simple peak detection because RMS better correlates with perceived loudness, ensuring that brief audio spikes don't trigger unnecessary volume reductions while genuine speech consistently activates the ducking mechanism.

### How does Meetily prevent audio clipping during mixing?

The mixer applies a `clamp(-1.0, 1.0)` operation to every output sample immediately after summing the mic and system channels. The `FFmpegAudioMixer` additionally implements soft-scaling that proportionally reduces the entire window when the sum exceeds unity, preserving the audio's dynamic character while eliminating hard digital distortion.

### What is the difference between ProfessionalAudioMixer and FFmpegAudioMixer?

The `ProfessionalAudioMixer` in [`pipeline.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/pipeline.rs) provides a straightforward implementation suitable for standard wired audio devices, while `FFmpegAudioMixer` in [`ffmpeg_mixer.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/ffmpeg_mixer.rs) offers enhanced buffering logic specifically designed to handle Bluetooth audio gaps and variable latency. Both implement identical RMS-based ducking algorithms and produce identical gain reduction behavior.

### Can the ducking threshold or gain values be customized?

Currently, the `SPEECH_THRESHOLD` (0.01) and `system_ducking` factor (0.60) are hard-coded constants in the Rust source. To modify these values, you must edit the constants in [`frontend/src-tauri/src/audio/pipeline.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/pipeline.rs) (for the simple mixer) or [`ffmpeg_mixer.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/ffmpeg_mixer.rs) (for the adaptive mixer) and recompile the application. Future versions may expose these parameters through the Tauri configuration interface.