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

> Learn how to add support for a new audio device platform in Meetily's modular system. Implement CPAL device enumeration and integrate into the discovery flow seamlessly.

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

---

**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`](https://github.com/Zackriya-Solutions/meetily/blob/main/mod.rs), and wire it into the discovery flow in [`discovery.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/configuration.rs)** – Defines the `AudioDevice` struct used across all platforms to standardize device metadata (name, type, ID).
- **[`discovery.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/discovery.rs)** – The orchestrator that calls the appropriate platform-specific enumeration function based on the compile target.
- **Platform modules** ([`windows.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/windows.rs), [`macos.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/macos.rs), [`linux.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/freebsd.rs) or [`solaris.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/solaris.rs)). This module must expose a public function with the following signature:

```rust
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.

```rust
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:

```rust
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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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:

```rust
#[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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/Cargo.toml) if your platform requires additional system libraries. Use conditional compilation to avoid pulling in unnecessary dependencies on other platforms:

```toml
[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`](https://github.com/Zackriya-Solutions/meetily/blob/main/windows.rs)** – A full-featured implementation including the `get_windows_device` resolution helper and complex stream configuration handling for WASAPI devices.
- **[`macos.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/macos.rs)** – A minimal implementation that relies solely on CPAL’s default host abstraction without additional resolution helpers.
- **[`linux.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/mod.rs) using `#[cfg(target_os = "...")]` conditional compilation blocks.
- **Wire** the new function into [`discovery.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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.