moont-live 1.0.0

Real-time CM-32L MIDI sink using ALSA
// Copyright (C) 2021-2026 Geoff Hill <geoff@geoffhill.org>
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 2.1 of the License, or (at
// your option) any later version. Read COPYING.LESSER.txt for details.

use alsa::pcm::{Access, Format, HwParams, PCM};
use alsa::{Direction, ValueOr};

pub struct AudioOut {
    pcm: PCM,
}

impl AudioOut {
    pub fn open(device: &str, period_frames: u64) -> AudioOut {
        let pcm = PCM::new(device, Direction::Playback, false)
            .expect("failed to open ALSA PCM device");

        {
            let hwp = HwParams::any(&pcm)
                .expect("failed to get ALSA hardware params");
            hwp.set_access(Access::RWInterleaved)
                .expect("failed to set access mode");
            hwp.set_format(Format::s16())
                .expect("failed to set format S16_LE");
            hwp.set_channels(2).expect("failed to set channels to 2");
            hwp.set_rate(moont::SAMPLE_RATE, ValueOr::Nearest)
                .expect("failed to set sample rate");
            hwp.set_period_size(period_frames as i64, ValueOr::Nearest)
                .expect("failed to set period size");
            hwp.set_periods(4, ValueOr::Nearest)
                .expect("failed to set period count");
            pcm.hw_params(&hwp)
                .expect("failed to apply hardware params");

            let rate = hwp.get_rate().unwrap();
            if rate != moont::SAMPLE_RATE {
                eprintln!(
                    "Warning: requested {}Hz, got {}Hz",
                    moont::SAMPLE_RATE,
                    rate
                );
            }
        }

        AudioOut { pcm }
    }

    pub fn writei(&self, buf: &[i16]) {
        let io = self.pcm.io_i16().expect("failed to get PCM I/O");
        match io.writei(buf) {
            Ok(_) => {}
            Err(e) => {
                self.pcm
                    .try_recover(e, true)
                    .expect("failed to recover from xrun");
                let _ = io.writei(buf);
            }
        }
    }
}