# How to Implement Real-Time Audio Level Monitoring with RMS in Meetily

> Implement real-time audio level monitoring using RMS in Meetily. Learn how Meetily captures audio, calculates levels, and sends data to the frontend with CPAL and Tauri events.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: how-to-guide
- Published: 2026-07-14

---

**Meetily provides a built-in RMS-based audio monitoring system that captures audio streams via CPAL, calculates root-mean-square and peak levels every 100ms, and emits normalized level data to the frontend through Tauri events.**

Meetily (Zackriya-Solutions/meetily) implements real-time audio level monitoring directly in its Tauri backend using Rust. The system leverages the CPAL audio library to capture raw samples from selected devices, computes RMS and peak values for each audio frame, and streams the results to the frontend via Tauri events. This architecture enables low-latency visualization of microphone and speaker levels without processing audio in the browser.

## Architecture Overview

The audio monitoring system resides in [`frontend/src-tauri/src/audio/level_monitor.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/level_monitor.rs) and operates as a background service within the Tauri application. It maintains thread-safe state containers for device names and active CPAL streams, ensuring that multiple audio sources can be monitored concurrently without blocking the main thread.

The monitor follows a producer-consumer pattern: CPAL stream callbacks produce raw audio data, a Tokio task consumes calculated levels every 100 milliseconds, and the Tauri event system broadcasts updates to the frontend.

## Core Implementation in [`level_monitor.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/level_monitor.rs)

### Initializing the Monitor

Create a new monitor instance using `AudioLevelMonitor::new()`, which initializes two thread-safe containers: one for tracking device names and another for storing active CPAL streams.

```rust
use meetily::audio::level_monitor::AudioLevelMonitor;

let monitor = AudioLevelMonitor::new();
// monitor.devices: Arc<Mutex<Vec<String>>>
// monitor.streams: Arc<Mutex<Vec<Stream>>>

```

### Starting the CPAL Streams

The `start_monitoring` method opens a CPAL host, resolves each device name using `find_device_by_name`, and builds dedicated streams via `create_level_stream`. This function supports both input and output devices, enabling monitoring of microphones and speakers simultaneously.

```rust
pub async fn start_monitoring(
    &mut self,
    app_handle: tauri::AppHandle,
    device_names: Vec<String>
) -> Result<(), Box<dyn std::error::Error>> {
    // Checks for existing sessions and stops them
    // Opens CPAL host and builds streams for each device
}

```

### RMS and Peak Calculation

Inside each CPAL stream callback, the system converts multi-channel audio to mono using `audio_to_mono` (from [`audio_processing.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/audio_processing.rs)), then calculates RMS and peak values:

```rust
// Excerpt from create_level_stream callback
let rms = (samples.iter().map(|s| s * s).sum::<f32>() / samples.len() as f32).sqrt();
let peak = samples.iter().fold(0.0_f32, |m, &s| m.max(s.abs()));

let data = AudioLevelData {
    device_name: device_name.clone(),
    device_type: device_type.to_string(),
    rms_level: rms,
    peak_level: peak,
    is_active: rms > 0.001,
};
level_data.lock().await.push(data);

```

The RMS value represents the root-mean-square of the audio signal, providing a perceptually accurate measure of loudness normalized between 0.0 and 1.0.

### Emitting Updates to the Frontend

A background Tokio task wakes every 100 milliseconds, drains the collected level data from the shared vector, and emits it via Tauri's event system:

```rust
// Inside the emission task
let mut interval = tokio::time::interval(Duration::from_millis(100));
loop {
    interval.tick().await;
    let mut levels = level_data.lock().await;
    if !levels.is_empty() {
        let update = AudioLevelUpdate {
            timestamp: chrono::Utc::now().to_rfc3339(),
            levels: levels.drain(..).collect(),
        };
        app_handle.emit("audio-levels", &update).unwrap();
    }
}

```

## Device Discovery and Stream Management

The `find_device_by_name` function iterates through both input and output devices in the default CPAL host to locate requested devices by name. This dual-mode capability allows the monitor to track system audio outputs as well as microphone inputs.

When stopping the monitor via `stop_monitoring`, the system clears the `is_monitoring` flag, drops all CPAL streams (which automatically stops audio capture), and empties the device list, ensuring clean resource cleanup.

## Frontend Integration

The frontend listens for the `"audio-levels"` event using Tauri's event API. Each payload contains an `AudioLevelUpdate` struct with a timestamp and vector of `AudioLevelData` objects containing `device_name`, `rms_level`, `peak_level`, and `is_active` fields.

```typescript
import { listen } from '@tauri-apps/api/event';
import type { AudioLevelUpdate } from '@/types';

listen<AudioLevelUpdate>('audio-levels', (event) => {
  const { timestamp, levels } = event.payload;
  
  levels.forEach((level) => {
    const rmsPercentage = (level.rms_level * 100).toFixed(1);
    const bar = document.getElementById(`meter-${level.device_name}`);
    if (bar) {
      bar.style.width = `${rmsPercentage}%`;
    }
  });
});

```

## Backend Commands

Expose the monitoring functionality to the frontend by registering Tauri commands in [`frontend/src-tauri/src/lib.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/lib.rs):

```rust
#[tauri::command]
pub async fn start_audio_monitor(
    app: tauri::AppHandle,
    device_names: Vec<String>,
) -> Result<(), String> {
    let mut monitor = meetily::audio::level_monitor::AudioLevelMonitor::new();
    monitor
        .start_monitoring(app, device_names)
        .await
        .map_err(|e| e.to_string())
}

#[tauri::command]
pub async fn stop_audio_monitor() -> Result<(), String> {
    AUDIO_LEVEL_MONITOR
        .stop_monitoring()
        .await
        .map_err(|e| e.to_string())
}

```

Store the monitor instance globally (e.g., via `lazy_static` or Tauri state management) to maintain persistence across command calls.

## Summary

- **RMS calculation** occurs inside CPAL stream callbacks in [`frontend/src-tauri/src/audio/level_monitor.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/level_monitor.rs), computing the square root of the mean squared samples for perceptually accurate level detection.
- **Update frequency** is fixed at 100 milliseconds via a Tokio interval task that batches and emits level data through the `"audio-levels"` Tauri event.
- **Dual device support** enables monitoring of both input (microphone) and output (speaker) devices through the same `find_device_by_name` implementation.
- **Resource cleanup** happens automatically when calling `stop_monitoring`, which drops CPAL streams and clears internal state vectors.

## Frequently Asked Questions

### What is RMS and why does Meetily use it for audio levels?

RMS (Root-Mean-Square) calculates the square root of the average of squared audio samples, providing a mathematical representation of signal energy that correlates closely with human perception of loudness. Meetily uses RMS values normalized between 0.0 and 1.0 to drive visual level meters that accurately reflect perceived volume rather than instantaneous peak values.

### How often does Meetily emit audio level updates to the frontend?

The backend emits updates every 100 milliseconds via a Tokio async task that drains the accumulated `AudioLevelData` vector and broadcasts it through the `"audio-levels"` Tauri event. This interval balances real-time responsiveness with processing overhead.

### Can I monitor both input and output devices simultaneously in Meetily?

Yes. The `find_device_by_name` function searches both input and output device lists in the CPAL host, and `start_monitoring` accepts multiple device names in a single vector. You can mix microphone and speaker devices in the same monitoring session, with each source reporting independent RMS and peak levels.

### How do I properly stop the audio monitoring to release system resources?

Call the `stop_monitoring` method on your `AudioLevelMonitor` instance, which clears the internal `is_monitoring` flag, drops all active CPAL streams, and empties the device list. In the Tauri command layer, ensure you maintain a global or state-managed instance of the monitor so that `stop_monitoring` affects the same object that started the streams.