# Meetily Ring Buffer Implementation: Synchronizing Microphone and System Audio Streams

> Discover Meetily's ring buffer implementation for synchronizing audio streams. Learn how dual VecDeque buffers and zero-padding ensure precise microphone and system audio alignment.

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

---

**Meetily uses a custom `AudioMixerRingBuffer` struct in [`frontend/src-tauri/src/audio/pipeline.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/pipeline.rs) to align microphone and system audio streams before mixing, employing dual `VecDeque<f32>` buffers with a fixed 50 ms window and zero-padding to handle jitter.**

Meetily solves the challenging problem of synchronizing independent audio capture sources through a specialized ring buffer implementation. When capturing both microphone input and system audio playback simultaneously, timing drift and OS-level jitter can cause misalignment. The `AudioMixerRingBuffer` struct in the Tauri-based Rust backend ensures temporal alignment by buffering streams until matching windows are available for mixing.

## How the Ring Buffer Implementation Works in Meetily

### The AudioMixerRingBuffer Structure

The ring buffer implementation centers on the `AudioMixerRingBuffer` struct defined at lines 16-23 in [`frontend/src-tauri/src/audio/pipeline.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/pipeline.rs). This structure maintains two independent `VecDeque<f32>` buffers—`mic_buffer` and `system_buffer`—that accumulate samples from their respective audio sources. Each buffer is configured with a fixed `window_size_samples` representing approximately 50 ms of audio, and a `max_buffer_size` capped at eight windows (roughly 400 ms) to prevent memory overflow during jitter spikes, particularly on macOS Core Audio systems.

### Buffer Management and Overflow Protection

Incoming audio chunks arrive asynchronously through the `add_samples()` method, which accepts a `DeviceType` enum (`Microphone` or `System`) and routes the `Vec<f32>` samples to the appropriate deque. The implementation includes automatic overflow protection: when either buffer exceeds `max_buffer_size`, the oldest samples are dropped from the front using `pop_front()`, and warnings are logged to alert developers of potential capture issues.

```rust
fn add_samples(&mut self, device_type: DeviceType, samples: Vec<f32>) {
    match device_type {
        DeviceType::Microphone => self.mic_buffer.extend(samples),
        DeviceType::System => self.system_buffer.extend(samples),
    }
    // Trim overflow, emit warnings
    while self.mic_buffer.len() > self.max_buffer_size {
        self.mic_buffer.pop_front();
    }
    while self.system_buffer.len() > self.max_buffer_size {
        self.system_buffer.pop_front();
    }
}

```

### Synchronization Logic and Window Extraction

The `can_mix()` method determines readiness by checking if either buffer contains at least one full window of samples. When true, `extract_window()` drains a complete window from both buffers simultaneously. If one stream has insufficient data, the implementation applies zero-padding to prevent audio artifacts, ensuring both returned vectors contain exactly the same number of samples. This returns an `Option<(Vec<f32>, Vec<f32>)>` containing the synchronized microphone and system windows.

```rust
fn can_mix(&self) -> bool {
    self.mic_buffer.len() >= self.window_size_samples ||
    self.system_buffer.len() >= self.window_size_samples
}

fn extract_window(&mut self) -> Option<(Vec<f32>, Vec<f32>)> {
    if !self.can_mix() { return None; }
    // Drain a full window or zero-pad if insufficient data
    // ... returns (mic_window, sys_window) ...
}

```

## Integrating the Ring Buffer with the Audio Pipeline

The `AudioPipeline` struct orchestrates the mixing process using the ring buffer. Within the `run()` method (lines 17-24), captured chunks are fed into the ring buffer via `add_samples()`. A loop continuously checks `can_mix()`, and upon success, extracts aligned windows and passes them to the `ProfessionalAudioMixer`. The mixed output then feeds into the Voice Activity Detection (VAD) pipeline and the recording manager, ensuring perfectly synchronized transcription of both audio sources.

```rust
// Inside AudioPipeline::run()
self.ring_buffer.add_samples(chunk.device_type.clone(), chunk.data);
while self.ring_buffer.can_mix() {
    if let Some((mic_window, sys_window)) = self.ring_buffer.extract_window() {
        let mixed = self.mixer.mix_window(&mic_window, &sys_window);
        // Send to VAD / recorder ...
    }
}

```

## Practical Code Examples

### Initializing the Ring Buffer

Create a new ring buffer instance by specifying the sample rate. The constructor automatically calculates the window size and maximum buffer capacity.

```rust
use meetily::audio::pipeline::AudioMixerRingBuffer;

// Initialize for 48kHz audio capture
let mut ring_buffer = AudioMixerRingBuffer::new(48_000);

```

### Feeding Audio Streams

Push samples from capture callbacks into the appropriate buffer using the `DeviceType` enum to distinguish sources.

```rust
use meetily::audio::recording_state::DeviceType;

// Feed microphone samples
ring_buffer.add_samples(DeviceType::Microphone, mic_chunk);

// Feed system (desktop) samples
ring_buffer.add_samples(DeviceType::System, system_chunk);

```

### Extracting and Mixing Synchronized Windows

Always check `can_mix()` before extraction. The returned windows are guaranteed to be the same length, with zero-padding applied to shorter streams.

```rust
if ring_buffer.can_mix() {
    if let Some((mic_win, sys_win)) = ring_buffer.extract_window() {
        // `mic_win` and `sys_win` now contain exactly the same number of samples
        let mixed = mixer.mix_window(&mic_win, &sys_win);
        // Process mixed audio for VAD or WAV recording...
    }
}

```

## Summary

- **Dual Buffer Architecture**: `AudioMixerRingBuffer` uses separate `VecDeque<f32>` buffers for microphone and system audio to handle asynchronous arrival times.
- **Jitter Absorption**: The implementation enforces a 50 ms mixing window with an 800 ms maximum capacity (8 windows) to accommodate system jitter without losing data.
- **Automatic Overflow Handling**: The `add_samples()` method routes data by `DeviceType` and automatically trims excess samples, logging warnings when buffers exceed safe limits.
- **Zero-Padding Synchronization**: The `extract_window()` method ensures temporal alignment by zero-padding deficient streams, enabling artifact-free mixing in `ProfessionalAudioMixer`.
- **Pipeline Integration**: Located in [`frontend/src-tauri/src/audio/pipeline.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/pipeline.rs), this ring buffer implementation feeds the VAD-driven transcription pipeline and mixed-audio recorder.

## Frequently Asked Questions

### Why does Meetily use a ring buffer instead of simple timestamp matching?

Timestamp matching alone fails when audio drivers deliver chunks at irregular intervals or when system load causes jitter. The ring buffer implementation accumulates samples into fixed windows, allowing the `extract_window()` method to wait until both streams have sufficient data, effectively decoupling the capture timing from the processing logic according to the source code in [`pipeline.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/pipeline.rs).

### How does the ring buffer handle when one audio source stops producing data?

If one stream's buffer falls below `window_size_samples` while the other continues receiving data, `can_mix()` will eventually return true for the full buffer while the empty buffer remains short. The `extract_window()` method detects this imbalance and applies zero-padding to the deficient side, preventing pipeline stalls while maintaining audio continuity for the transcription workflow.

### What is the performance impact of the 50ms buffering delay?

The 50ms window introduces minimal latency while providing enough samples for the Voice Activity Detection (VAD) algorithm to analyze audio context. As implemented in the Meetily source code, this trade-off ensures reliable synchronization without perceptible delay, as the 400ms maximum buffer safely absorbs typical macOS Core Audio jitter without blocking the capture threads.

### Can the ring buffer size be adjusted for different latency requirements?

Yes. The `new()` constructor in [`frontend/src-tauri/src/audio/pipeline.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/pipeline.rs) calculates `window_size_samples` from the sample rate and window duration, and `max_buffer_size` is set to eight times that value. Developers can modify these constants to tune the buffer for lower latency or higher jitter tolerance, though the current defaults are optimized for typical meeting transcription scenarios.