How Meetily Provides Real-Time Device Level Feedback to the UI

Meetily captures raw audio samples from input devices using CPAL, computes RMS and peak levels in Rust, and transmits updates to the React frontend every 100 milliseconds via Tauri's audio-levels event bus.

The open-source meeting assistant from Zackriya-Solutions implements real-time device level monitoring to help users verify microphone input before joining calls. By bridging Rust's low-level audio processing capabilities with a TypeScript React frontend, the system delivers sub-100ms latency for visual feedback while maintaining thread-safe state management across the native boundary.

The Audio Level Monitoring Pipeline

The architecture follows a six-stage pipeline that moves from device selection to UI rendering. First, the frontend invokes the start_audio_level_monitoring command defined in frontend/src-tauri/src/lib.rs, passing a list of target device names. This command initializes the monitor in audio/level_monitor.rs or falls back to audio/simple_level_monitor.rs during development.

Next, the monitor creates CPAL input streams for each device. In the stream callbacks, raw multi-channel samples pass through audio_to_mono (defined in frontend/src-tauri/src/audio/audio_processing.rs) before process_audio_levels calculates RMS and peak values. These results populate a shared Arc<Mutex<Vec<AudioLevelData>>> that aggregates data from all active streams.

Every 100 milliseconds, a background task locks the mutex, drains the vector, and packages the data into an AudioLevelUpdate struct containing a Unix-epoch timestamp and the device-level array. The task emits this payload via app_handle.emit("audio-levels", &update), which traverses Tauri's event bus to the frontend.

On the UI side, the DeviceSelection.tsx component registers a listener using listen<AudioLevelUpdate>('audio-levels', ...). Each incoming event updates a local Map<string, AudioLevelData> keyed by device name, triggering React re-renders that display live meters or activity indicators based on the is_active and RMS values.

Backend Implementation: Capturing and Processing Audio

The core monitoring logic resides in frontend/src-tauri/src/audio/level_monitor.rs. This module leverages the CPAL (Cross-Platform Audio Library) crate to enumerate and open input devices. For each requested device name, the monitor constructs an input stream with a callback that receives interleaved audio samples.

// Conceptual flow from level_monitor.rs
let stream = device.build_input_stream(
    &config,
    move |data: &[f32], _: &_| {
        let mono = audio_to_mono(data, channels);
        let levels = process_audio_levels(&mono);
        // Push to shared state...
    },
    err_fn,
    None,
)?;

The audio_to_mono function averages multi-channel samples into a single mono stream, ensuring consistent level calculations regardless of input configuration. Then process_audio_levels computes the root-mean-square (RMS) and peak amplitude. The system sets is_active to true when RMS exceeds 0.001, filtering background noise while maintaining responsiveness.

For development scenarios where hardware access is unavailable, frontend/src-tauri/src/audio/simple_level_monitor.rs provides a mock implementation. It generates synthetic level data on the same 100ms cadence and emits identical audio-levels events, allowing UI development without physical audio devices.

Emitting Real-Time Events from Rust

Thread safety is critical when audio callbacks run on CPAL's threads while the main Tauri thread handles commands. The monitor stores device names and CPAL streams within Arc<Mutex<...>> structures, enabling safe concurrent access. An AtomicBool named AUDIO_LEVEL_STATE.is_monitoring provides lock-free start/stop control that prevents race conditions during rapid toggle operations.

The background emission task runs on an async interval:

// Emission logic from level_monitor.rs
let update = AudioLevelUpdate {
    timestamp: SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis() as u64,
    levels: current_levels,
};

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

This pattern ensures that the frontend receives a complete snapshot of all device levels every 100 milliseconds, even if individual streams process samples at different rates. The AudioLevelUpdate struct acts as a serialization boundary, converting Rust vectors into JSON payloads that Tauri automatically deserializes for JavaScript consumption.

Consuming Levels in the React Frontend

The DeviceSelection.tsx component manages the subscription lifecycle using React hooks. It imports the listen function from @tauri-apps/api/event and registers the handler inside a useEffect block:

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?.();
}, []);

This approach maintains a Map that preserves device identity without requiring array searches on every update. When rendering, the component reads from audioLevels to set meter heights, change colors when is_active is true, or display numeric RMS values. The cleanup function removes the listener when the component unmounts, preventing memory leaks in the Tauri event bus.

Thread Safety and Performance Optimizations

Several implementation details ensure the system remains responsive under load:

  • Atomic monitoring flag: The is_monitoring atomic boolean in AUDIO_LEVEL_STATE allows instantaneous start/stop checks without blocking audio threads.
  • Mutex isolation: The Arc<Mutex<Vec<AudioLevelData>>> separates high-frequency audio callbacks from the slower 100ms emission cycle, preventing buffer overflow during processing spikes.
  • Mono conversion: By flattening multi-channel audio before analysis, the system guarantees consistent RMS values across stereo microphones and mono headsets alike.
  • Threshold filtering: The 0.001 RMS threshold eliminates visual noise from silent channels while preserving sensitivity to actual speech.

Summary

  • Meetily uses CPAL in frontend/src-tauri/src/audio/level_monitor.rs to capture raw audio from selected input devices.
  • The audio_to_mono and process_audio_levels functions calculate RMS and peak values, storing results in a thread-safe Arc<Mutex<Vec<...>>> structure.
  • A background task emits AudioLevelUpdate payloads via the audio-levels Tauri event every 100 milliseconds.
  • The React frontend in DeviceSelection.tsx listens for these events and maintains a Map of device levels to render real-time visual feedback.
  • An AtomicBool flag and careful mutex usage prevent race conditions during rapid start/stop operations.

Frequently Asked Questions

How does Meetily calculate audio levels from raw samples?

Meetily processes raw audio samples through the process_audio_levels function in frontend/src-tauri/src/audio/level_monitor.rs. First, audio_to_mono converts multi-channel interleaved data into a mono stream by averaging channels. Then the function calculates the root-mean-square (RMS) amplitude and tracks the peak absolute value over the sample buffer. An is_active boolean is set to true when RMS exceeds 0.001, indicating detectable audio input above background noise.

What is the difference between the full monitor and simple level monitor?

The full implementation in frontend/src-tauri/src/audio/level_monitor.rs opens actual CPAL streams to hardware devices and processes real audio samples. The simplified version in frontend/src-tauri/src/audio/simple_level_monitor.rs generates synthetic level data programmatically on the same 100ms schedule. Both emit identical audio-levels events, allowing frontend development and testing without physical microphones or audio interfaces.

How frequently does the UI receive audio level updates?

The UI receives updates every 100 milliseconds. This cadence is controlled by a background async task in the Rust monitor that reads the shared state buffer, clears it, and emits the audio-levels event. This interval balances responsiveness with CPU usage, providing smooth visual feedback without overwhelming the JavaScript event loop.

Why does Meetily convert multi-channel audio to mono before processing?

Multi-channel audio (stereo, quad, or surround) contains interleaved samples that would produce inconsistent level readings if analyzed directly. The audio_to_mono function in frontend/src-tauri/src/audio/audio_processing.rs averages all channels into a single mono stream, ensuring that RMS and peak calculations remain consistent regardless of whether the user connects a mono headset or stereo microphone. This standardization prevents volume meters from appearing artificially low when only one channel carries signal.

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 →