How to Add Support for a New Audio Device Platform in Meetily’s Modular Audio System

To add support for a new audio device platform in Meetily, create a Rust module under frontend/src-tauri/src/audio/devices/platform/, implement the configure_*_audio function to enumerate devices using CPAL, conditionally expose it in mod.rs, and wire it into the discovery flow in discovery.rs without modifying core audio logic.

Meetily’s audio subsystem is deliberately split into platform-specific modules so the core code remains agnostic about the underlying OS. This architecture allows you to extend support to additional operating systems—such as FreeBSD, Solaris, or custom embedded platforms—while keeping the higher-level pipeline, recorder, and transcription logic untouched. By following the patterns established in the audio/devices crate, you can integrate a new audio backend that leverages the existing cpal abstraction layer.

Understanding Meetily’s Modular Audio Architecture

The audio device layer in Meetily follows a strict separation of concerns. Platform-specific code resides in frontend/src-tauri/src/audio/devices/platform/, while generic device discovery and configuration live in sibling modules.

Key components:

  • configuration.rs – Defines the AudioDevice struct used across all platforms to standardize device metadata (name, type, ID).
  • discovery.rs – The orchestrator that calls the appropriate platform-specific enumeration function based on the compile target.
  • Platform modules (windows.rs, macos.rs, linux.rs) – Each implements a configure_*_audio function that returns a Vec<AudioDevice> populated using host-specific APIs.

This design ensures that adding support for a new audio device platform requires changes only within the platform directory and the conditional compilation blocks in mod.rs.

Step 1: Create the Platform-Specific Module

Create a new file under frontend/src-tauri/src/audio/devices/platform/ named after your target platform (e.g., freebsd.rs or solaris.rs). This module must expose a public function with the following signature:

pub fn configure_platform_audio(host: &cpal::Host) -> anyhow::Result<Vec<AudioDevice>>

Replace platform with your OS identifier (e.g., configure_freebsd_audio). This function receives a cpal::Host reference and returns a vector of AudioDevice structs wrapped in an anyhow::Result.

Inside the function, enumerate input and output devices using CPAL’s HostTrait methods (input_devices() and output_devices()), instantiate AudioDevice objects using AudioDevice::new(), and collect them into the returned vector.

Step 2: Implement Device Enumeration

Use CPAL’s device traits to query the system’s audio hardware. For input devices (microphones), iterate over host.input_devices(). For system audio or loopback capture, iterate over host.output_devices() and filter for monitor sources or specific naming patterns.

use anyhow::Result;
use cpal::traits::{DeviceTrait, HostTrait};
use crate::audio::devices::configuration::{AudioDevice, DeviceType};

pub fn configure_freebsd_audio(host: &cpal::Host) -> Result<Vec<AudioDevice>> {
    let mut devices = Vec::new();

    // Enumerate microphone inputs
    for dev in host.input_devices()? {
        if let Ok(name) = dev.name() {
            devices.push(AudioDevice::new(name, DeviceType::Input));
        }
    }

    // Enumerate system audio outputs (monitor sources on FreeBSD contain "monitor")
    for dev in host.output_devices()? {
        if let Ok(name) = dev.name() {
            if name.contains("monitor") {
                devices.push(AudioDevice::new(name, DeviceType::Output));
            }
        }
    }

    Ok(devices)
}

This example demonstrates filtering for monitor devices—a pattern similar to the Linux implementation that handles PulseAudio monitor sources.

Step 3: Add the Device Resolution Helper (Optional)

Some platforms require special handling to resolve an AudioDevice into a usable cpal::Device with a compatible stream configuration. If your platform needs this (as Windows does with get_windows_device), implement a second helper function:

pub fn get_freebsd_device(
    audio_device: &AudioDevice,
) -> Result<(cpal::Device, cpal::SupportedStreamConfig)> {
    let host = cpal::default_host();

    match audio_device.device_type {
        DeviceType::Input => {
            for dev in host.input_devices()? {
                if let Ok(name) = dev.name() {
                    if name == audio_device.name {
                        return Ok((dev, dev.default_input_config()?));
                    }
                }
            }
        }
        DeviceType::Output => {
            for dev in host.output_devices()? {
                if let Ok(name) = dev.name() {
                    if name == audio_device.name {
                        return Ok((dev, dev.default_output_config()?));
                    }
                }
            }
        }
    }

    Err(anyhow::anyhow!("Device not found: {}", audio_device.name))
}

This function locates the device by name and returns a tuple containing the cpal::Device and its SupportedStreamConfig, preferably using f32 format with 2-channel stereo when available.

Step 4: Register the Module with Conditional Compilation

Edit frontend/src-tauri/src/audio/devices/platform/mod.rs to conditionally compile your new module. Add a cfg block for your target OS that declares the module and re-exports its public functions:

#[cfg(target_os = "freebsd")]
pub mod freebsd;

#[cfg(target_os = "freebsd")]
pub use freebsd::{configure_freebsd_audio, get_freebsd_device};

This mirrors the existing blocks for Windows, macOS, and Linux, ensuring your code only compiles on the appropriate target.

Step 5: Integrate with the Discovery Flow

The generic device discovery code in frontend/src-tauri/src/audio/devices/discovery.rs calls the appropriate configure_*_audio function based on the active target. Update the discovery logic to invoke your new function:

  • If the file uses a match statement on std::env::consts::OS, add a new arm for your platform.
  • If it uses a generic alias pattern, ensure the platform module re-exports configure_freebsd_audio as the expected interface name.

The discovery module expects a Vec<AudioDevice> return, so as long as your function signature matches, the integration requires minimal changes.

Step 6: Configure Dependencies and Testing

Update frontend/src-tauri/Cargo.toml if your platform requires additional system libraries. Use conditional compilation to avoid pulling in unnecessary dependencies on other platforms:

[target.'cfg(target_os = "freebsd")'.dependencies]
some-freebsd-audio-crate = "0.1"

Add unit tests to verify your implementation. Create a #[cfg(target_os = "freebsd")] test block that asserts configure_freebsd_audio returns at least one valid AudioDevice when hardware is present. Run the existing test suite to ensure your changes do not break cross-platform builds.

Reference Implementations by Platform

Meetily provides three reference implementations that demonstrate different complexity levels:

  • windows.rs – A full-featured implementation including the get_windows_device resolution helper and complex stream configuration handling for WASAPI devices.
  • macos.rs – A minimal implementation that relies solely on CPAL’s default host abstraction without additional resolution helpers.
  • linux.rs – Demonstrates handling of PulseAudio-specific monitor sources and special device naming conventions for system audio capture.

Study these files to determine which pattern best fits your target platform’s requirements.

Summary

  • Create a new platform module under frontend/src-tauri/src/audio/devices/platform/ with a configure_*_audio function that enumerates devices using CPAL.
  • Implement device enumeration by iterating over host.input_devices() and host.output_devices(), filtering for relevant hardware, and constructing AudioDevice objects.
  • Optionally add a get_*_device helper if your platform needs custom stream configuration resolution beyond CPAL defaults.
  • Register the module in mod.rs using #[cfg(target_os = "...")] conditional compilation blocks.
  • Wire the new function into discovery.rs to ensure the rest of the application can discover devices on your platform.
  • Test your implementation with platform-specific unit tests and update Cargo.toml with any required conditional dependencies.

Frequently Asked Questions

Do I need to modify the core audio recording logic to add a new platform?

No. Meetily’s modular design isolates platform-specific code in the audio/devices/platform/ directory. As long as your implementation returns the standard Vec<AudioDevice> and follows the configure_*_audio signature, the existing recorder and transcription pipeline will work without modification.

What if my platform uses a custom audio backend instead of CPAL?

If your target platform requires a native API not supported by CPAL (e.g., a proprietary embedded audio stack), you can still implement the configure_*_audio function by directly interfacing with that API. Simply construct AudioDevice objects manually using the data returned by your native calls. The AudioDevice struct in configuration.rs only requires a name and device type, making it agnostic to the underlying capture mechanism.

How does Meetily handle system audio (loopback) capture on different platforms?

System audio capture is handled differently per OS. On Linux, the code filters output devices for names containing "monitor" (PulseAudio monitor sources). On Windows, it may use WASAPI loopback devices. When adding a new platform, identify how that OS exposes system audio—whether through monitor devices, special APIs, or virtual drivers—and apply similar filtering logic in your enumeration function.

Can I test my platform implementation without physical hardware?

Yes. You can test the enumeration logic using virtual audio devices or null drivers available on your target platform. Additionally, structure your unit tests to mock the cpal::Host responses or verify error handling paths when no devices are present. The anyhow::Result return type allows you to test both success and failure scenarios gracefully.

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 →