How Meetily's Audio Level Monitoring Provides Real-Time Feedback During Recording

Meetily captures audio samples from input devices using CPAL, calculates RMS and peak levels every 100ms, and pushes updates to the React frontend via Tauri's audio-levels event bus to render live meters.

Meetily is an open-source meeting recording application built with Tauri (Rust) and React. Its audio level monitoring system bridges the native audio layer with the UI, providing visual feedback during recording sessions by translating raw audio samples into device-specific level data.

Architecture of the Audio Level Pipeline

The monitoring system follows a six-stage pipeline that moves data from the hardware audio interface to the React component tree. This architecture ensures low-latency updates while maintaining thread safety across the Rust-JavaScript boundary.

Stage 1: Initiating Monitoring via Tauri Command

The process begins when the React frontend invokes the start_audio_level_monitoring command defined in frontend/src-tauri/src/lib.rs. This command accepts a vector of device names and forwards the request to the monitoring implementation.

#[tauri::command]
async fn start_audio_level_monitoring<R: Runtime>(
    app: AppHandle<R>,
    device_names: Vec<String>,
) -> Result<(), String> {
    audio::simple_level_monitor::start_monitoring(app, device_names)
        .await
        .map_err(|e| format!("Failed to start audio level monitoring: {}", e))
}

Stage 2: Capturing Audio with CPAL

Inside frontend/src-tauri/src/audio/level_monitor.rs, the system uses CPAL (Cross-Platform Audio Library) to discover and open streams for each requested input device. For every device, a CPAL stream is created with a callback that receives raw audio samples.

Before processing, the audio_to_mono helper function from frontend/src-tauri/src/audio/audio_processing.rs flattens multi-channel audio into mono. This ensures consistent level calculations regardless of whether the source is mono, stereo, or multi-channel.

Stage 3: Calculating RMS and Peak Levels

The stream callback processes samples through process_audio_levels to compute two key metrics:

  • RMS (Root-Mean-Square): The effective average level of the audio signal
  • Peak: The maximum amplitude value in the sample window

The system also sets an is_active flag when RMS exceeds 0.001, filtering out background noise.

Stage 4: Aggregating State and Scheduling Emission

Processed data flows into a shared Arc<Mutex<Vec<AudioLevelData>>> that collects updates from all active streams. A background task running on a 100ms interval reads this vector, clears it, and packages the data into an AudioLevelUpdate struct containing a Unix-epoch timestamp and the level vector.

Stage 5: Emitting Events to the Frontend

The aggregated update is emitted via Tauri's event bus using app_handle.emit("audio-levels", &update). This occurs in frontend/src-tauri/src/audio/level_monitor.rs:

let update = AudioLevelUpdate {
    timestamp: SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis() as u64,
    levels,
};

if let Err(e) = app_handle_clone.emit("audio-levels", &update) {
    error!("Failed to emit audio levels: {}", e);
}

Stage 6: Consuming Updates in the React UI

In frontend/src/components/DeviceSelection.tsx, the React component registers a listener for the audio-levels event using Tauri's listen API:

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

const [audioLevels, setAudioLevels] = useState<Map<string, AudioLevelData>>(new Map());

useEffect(() => {
  let unlisten: (() => void) | undefined;

  const setup = async () => {
    unlisten = await listen<AudioLevelUpdate>('audio-levels', event => {
      const map = new Map<string, AudioLevelData>();
      event.payload.levels.forEach(l => map.set(l.device_name, l));
      setAudioLevels(map);
    });
  };
  setup();

  return () => unlisten?.();
}, []);

The component converts the payload array into a Map<string, AudioLevelData> keyed by device name, enabling O(1) access when rendering individual meter components.

Thread Safety and State Management

The implementation uses several concurrency primitives to ensure safe operation:

  • Arc<Mutex<...>>: Device names and CPAL streams reside inside thread-safe containers, allowing the async background task to read and write while the main Tauri thread handles commands.
  • AtomicBool: The AUDIO_LEVEL_STATE.is_monitoring flag enables fast start/stop control without race conditions.
  • 100ms Tick: The fixed emission interval balances real-time responsiveness with CPU usage, preventing event flooding that could overwhelm the frontend.

Development Fallback with Synthetic Data

For scenarios where full audio hardware access isn't available (e.g., development or CI environments), frontend/src-tauri/src/audio/simple_level_monitor.rs provides a simplified implementation. This module generates synthetic level data on the same 100ms schedule and emits identical audio-levels events, ensuring the UI logic remains testable without actual audio hardware.

Frontend Integration Example

To start monitoring from the TypeScript frontend:

// Invoke the Tauri command with chosen device names
await invoke('start_audio_level_monitoring', {
  device_names: ['Built-in Microphone', 'BlackHole 2ch']
});

The audioLevels state map can then drive visual components:

{audioLevels.get('Built-in Microphone')?.is_active && (
  <div className="recording-indicator">Recording</div>
)}

Summary

  • Meetily's audio level monitoring uses CPAL to capture raw samples from selected input devices.
  • The Rust backend in level_monitor.rs calculates RMS and peak levels every 100ms.
  • Updates flow through Tauri's audio-levels event bus to the React frontend.
  • The DeviceSelection component maintains a Map of device levels in state for efficient rendering.
  • Thread-safe state management using Arc<Mutex<...>> and AtomicBool prevents race conditions.
  • A synthetic monitor in simple_level_monitor.rs supports development without hardware.

Frequently Asked Questions

How does Meetily calculate audio levels from raw samples?

The system computes RMS (root-mean-square) for effective average loudness and tracks peak amplitude for maximum values. Both metrics are calculated after converting multi-channel audio to mono using the audio_to_mono helper, ensuring consistent readings across different device configurations.

What is the latency between audio input and UI updates?

The pipeline delivers updates every 100ms via Tauri's event bus. This interval provides near real-time visual feedback while preventing excessive CPU usage or frontend re-renders that could impact performance during recording sessions.

Can the audio level monitoring run without actual hardware?

Yes. The repository includes frontend/src-tauri/src/audio/simple_level_monitor.rs, which generates synthetic level data and emits the same audio-levels events. This allows UI development and testing in environments without audio input devices.

How does the frontend distinguish between active and inactive audio sources?

Each AudioLevelData payload includes an is_active boolean field that is set to true when the RMS level exceeds 0.001. This threshold filters out background noise and silent inputs, allowing the UI to highlight only devices receiving actual audio signals.

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 →