alsa-rs

repository·master·Indexed 41 days ago

https://github.com/diwic/alsa-rs

Thin but safe Rust bindings for the Linux ALSA (Advanced Linux Sound Architecture) library. Version 0.12.1 provides wrappers for audio playback, recording, mixer control, MIDI, and the HCtl API. It features RAII resource management, support for non-blocking operations via polling, and a direct module for low-latency audio using memory-mapped (MMAP) kernel DMA buffers.

Tokens
13.6K
Snippets
57
Records
67
Agent score
50%

What's inside alsa-rs

  1. Overview of alsa-rs capabilities

    master

    The alsa crate provides thin, safe Rust wrappers for the ALSA (Advanced Linux Sound Architecture) library. While the library is still evolving, it currently supports the following core audio functionalities:

    • Audio Playback: Managed via the pcm module.
    • Audio Recording
    • Mixer controls
    • HCtl API: Includes JACK detection (see hctl module docs).
    • Raw MIDI and UMP
    • MIDI sequencer
    • Ctl API
    • Device name hints: Managed via the device_name module.
    • Enumerations: Iterators for cards, devices, and other entities.
    • Polling/Waiting: Support for non-blocking operations across most APIs.
  2. Understand the alsa-rs API design and patterns

    master

    The alsa crate follows several key patterns to ensure safety and ease of use:

    • 1-to-1 Mapping: Most functions map directly to their alsa-lib C counterparts. You can consult the official ALSA documentation for detailed behavior of the underlying C functions.
    • RAII (Resource Acquisition Is Initialization): Structs automatically manage their lifecycle. When a struct (like PCM) is dropped, the corresponding ALSA close/free function (like snd_pcm_close) is called automatically.
    • Buffer I/O: To perform read/write operations on audio buffers, use the io_* methods. These return a specialized struct used for reading, writing, or performing mmap operations if supported by the driver.
    • Error Handling: Functions that can fail in the C library return a Result in Rust.
    • Enumeration: You can iterate over cards, devices, and other hardware entities using standard Rust Iterator implementations on the provided structs.
    • Async and Polling: Many structs implement poll::Descriptors, allowing them to be integrated with async frameworks. For synchronous non-blocking needs, you can use the wait methods.
  3. Convert between Decibels and MilliBels

    master

    The MilliBel struct is a wrapper around an i64 used to represent millibels (1/100th of a decibel). This is the unit ALSA uses for its dB-related functions.

    • MilliBel::from_db(f32): Converts a standard decibel value to MilliBel.
    • MilliBel::to_db(): Converts the value to a f32 decibel representation.
    • It implements standard arithmetic operators (Add, Sub, etc.) for easy calculation.
    let db_val = MilliBel::from_db(-6.0);
    let raw_val = db_val.0; // Access the underlying i64
    println!("Decibels: {}", db_val.to_db());
  4. How to receive MIDI input events using `Input`

    master

    To read events from the sequencer, you must first obtain an Input object by calling seq.input().

    Important Constraints:

    • Only one Input instance may exist at a time for a given Seq instance. The Seq object tracks this and will panic if you attempt to create a second Input while the first is still in scope.
    • The Input object provides access to event_input, event_input_pending, and set_input_buffer_size.
    • While an Event obtained via event_input is alive, you must not call any methods on Input that modify the internal input buffer (as the event might hold a reference to it, such as for Sysex messages).
    let seq = Seq::open(None, None, false)?;
    {
        let mut input = seq.input();
        if input.event_input_pending(false)? > 0 {
            let mut event = input.event_input()?;
            // Process event...
        }
    }
  5. Use Direction to specify Playback or Capture

    master

    The Direction enum is used to specify whether an operation involves audio output or input. It provides helper methods to map common terminology to the ALSA concepts of Playback and Capture.

    • Direction::Playback: Audio output.
    • Direction::Capture: Audio input.

    Use Direction::output() to get Playback and Direction::input() to get Capture.

    use alsa::Direction;
    
    let output = Direction::output(); // Returns Direction::Playback
    let input = Direction::input();   // Returns Direction::Capture
  6. Manage MIDI Queue Control with EvQueueControl

    master

    The EvQueueControl<T> struct is a generic wrapper for sending control messages to an ALSA sequencer queue. The type T depends on the specific EventType being used:

    • EvQueueControl<()>: Used for Start, Continue, Stop, Clock, and QueueSkew.
    • EvQueueControl<i32>: Used for Tempo (where value is the tempo).
    • EvQueueControl<u32>: Used for SyncPos, Tick, and SetposTick (where value is the position).
    • EvQueueControl<time::Duration>: Used for SetposTime (where value is the time duration).
  7. Use Direct PCM MMAP for low-latency audio

    master

    The direct module allows bypassing alsa-lib to read and write directly into memory-mapped kernel memory (DMA buffers). This provides minimum overhead and maximum RT-safety by avoiding syscalls and memory allocations during streaming.

    Requirements & Limitations:

    • Hardware Devices Only: This only works with hardware device strings (e.g., "hw:xxx"). It will not work with plugins like PulseAudio.
    • Access Mode: The PCM must be configured with pcm::Access::MMapInterleaved.
    • Architecture Support: Direct memory reading of status is supported on x86, PowerPC, and Alpha. For other architectures, use SyncPtrStatus which uses a kernel syscall.

    To use this mode, open a pcm::PCM and then call direct_mmap_playback::<S>() or direct_mmap_capture::<S>() where S is your sample type (e.g., i16).

    // Example setup for Mmap Playback
    let pcm = PCM::open(&*CString::new("hw:0").unwrap(), Direction::Playback, false).unwrap();
    let hwp = HwParams::any(&pcm).unwrap();
    hwp.set_channels(2).unwrap();
    hwp.set_rate(44100, ValueOr::Nearest).unwrap();
    hwp.set_format(Format::s16()).unwrap();
    hwp.set_access(Access::MMapInterleaved).unwrap();
    pcm.hw_params(&hwp).unwrap();
    
    let mut m = pcm.direct_mmap_playback::<i16>().unwrap();
  8. Use Round to specify mixer rounding modes

    master

    The Round enum is used in certain mixer-related calls to specify how values should be rounded.

    • Round::Floor: Round down (towards negative infinity).
    • Round::Ceil: Round up (towards positive infinity).
  9. Use ValueOr to restrict hardware parameter searches

    master

    When submitting values for hardware parameters, ValueOr defines the strategy to use if the exact requested value is unavailable. It determines in which direction the system should search for the nearest available value.

    • ValueOr::Less: The value set is the submitted value, or less.
    • ValueOr::Nearest: The value set is the submitted value, or the nearest.
    • ValueOr::Greater: The value set is the submitted value, or greater.
  10. Use the HCtl API for mixer control and jack detection

    master

    The HCtl (Hardware Control) API provides access to ALSA mixer controls and hardware jack detection. You can use it to iterate over hardware elements, read their values (like volume or mute status), and check if audio jacks are plugged in.

    To use HCtl, you typically follow this lifecycle:

    1. Open the hardware control interface using HCtl::new(name, nonblock) or HCtl::from_card(card, nonblock).
    2. Load the configuration using .load() (this is required after opening).
    3. Iterate or Find elements using .elem_iter() or .find_elem(id).
    4. Read/Write values using the Elem object returned by the iterator.
    for a in ::alsa::card::Iter::new().map(|x| x.unwrap()) {
        use std::ffi::CString;
        use alsa::hctl::HCtl;
        // Open the hardware control for the card
        let h = HCtl::open(&CString::new(format!("hw:{}", a.get_index())).unwrap(), false).unwrap();
        h.load().unwrap();
    
        for b in h.elem_iter() {
            use alsa::ctl::ElemIface;
            let id = b.get_id().unwrap();
            // Filter for card-level elements
            if id.get_interface() != ElemIface::Card { continue; }
            
            let name = id.get_name().unwrap();
            // Example: Detecting Jack status
            if name.ends_with(" Jack") {
                let status = if b.read().unwrap().get_boolean(0).unwrap() { "plugged in" } else { "unplugged" };
                println!("{} is {}", &name[..name.len()-5], status);
            }
        }
    }
  11. Create MIDI events with `Event::new` and `Event::new_ext`

    master

    Events are created using two different methods depending on whether they carry variable-length data.

    1. Event::new<D: EventData>(t: EventType, data: &D): Use this for fixed-length events (like EvNote or EvCtrl).
    2. Event::new_ext<D: Into<Cow<'a, [u8]>>>(t: EventType, data: D): Use this for variable-length events such as Sysex, Bounce, or UsrVar types. This method requires a lifetime 'a because the event may reference an external buffer.

    To decouple an event from its original buffer (e.g., to move it to a 'static lifetime), call .into_owned().

    // Fixed length event
    let note_event = Event::new(EventType::NoteOn, &note_data);
    
    // Variable length event (Sysex)
    let sysex_data = vec![0x00, 0x01, 0x02];
    let sysex_event = Event::new_ext(EventType::Sysex, sysex_data);
  12. Configure MIDI parameters with Params

    master

    The Params struct allows you to inspect and modify the current configuration of a Rawmidi device. You can manage settings such as the minimum available buffer size, active sensing, read modes, and clock types.

    let mut params = midi.params_current()?;
    
    // Get current settings
    let mode = params.get_read_mode()?;
    let clock = params.get_clock_type()?;
    
    // Set new settings
    params.set_read_mode(ReadMode::Timestamp)?;
    params.set_clock_type(Clock::Monotonic)?;
    
    // Apply parameters back to the device
    midi.params(&params)?;