maolan-engine 0.2.15

Audio engine for the Maolan DAW with audio/MIDI tracks, routing, export, and out-of-process CLAP/VST3/LV2 plugin support
Documentation
use super::{Audio, MidiHub, OSSChannel};
use crate::audio::io::AudioIO;
use crate::hw::common;
use crate::hw::latency;
use crate::hw::options::HwOptions;
use crate::hw::traits::HwWorkerDriver;
use std::sync::{
    Arc, Mutex,
    atomic::{AtomicBool, Ordering},
};

#[derive(Debug)]
pub struct HwDriver {
    capture: Audio,
    playback: Audio,
    nperiods: usize,
    sync_mode: bool,
    input_latency_frames: usize,
    output_latency_frames: usize,
    playing: Arc<AtomicBool>,
    stop_requested: Arc<AtomicBool>,
    assist_lock: Arc<Mutex<()>>,
}

impl Default for HwOptions {
    fn default() -> Self {
        Self {
            exclusive: false,
            period_frames: 1024,
            nperiods: 1,
            ignore_hwbuf: false,
            sync_mode: false,
            input_latency_frames: 0,
            output_latency_frames: 0,
        }
    }
}

impl HwDriver {
    pub fn new(path: &str, rate: i32, bits: i32) -> std::io::Result<Self> {
        Self::new_with_options(path, None, rate, bits, HwOptions::default())
    }

    pub fn new_with_options(
        playback_path: &str,
        capture_path: Option<&str>,
        rate: i32,
        bits: i32,
        options: HwOptions,
    ) -> std::io::Result<Self> {
        let playing = Arc::new(AtomicBool::new(false));
        let stop_requested = Arc::new(AtomicBool::new(false));
        let capture_path = capture_path.unwrap_or(playback_path);
        let sync_key = if capture_path == playback_path {
            playback_path.to_string()
        } else {
            format!("{capture_path}|{playback_path}")
        };
        let capture = Audio::new(
            capture_path,
            &sync_key,
            rate,
            bits,
            true,
            options,
            playing.clone(),
        )
        .map_err(|e| {
            std::io::Error::other(format!("Failed to open OSS input '{capture_path}': {e}"))
        })?;
        let playback = Audio::new(
            playback_path,
            &sync_key,
            rate,
            bits,
            false,
            options,
            playing.clone(),
        )
        .map_err(|e| {
            std::io::Error::other(format!("Failed to open OSS output '{playback_path}': {e}"))
        })?;
        Ok(Self {
            capture,
            playback,
            nperiods: options.nperiods.max(1),
            sync_mode: options.sync_mode,
            input_latency_frames: options.input_latency_frames,
            output_latency_frames: options.output_latency_frames,
            playing,
            stop_requested,
            assist_lock: Arc::new(Mutex::new(())),
        })
    }

    pub fn input_fd(&self) -> i32 {
        self.capture.fd()
    }

    pub fn output_fd(&self) -> i32 {
        self.playback.fd()
    }

    pub fn input_channels(&self) -> usize {
        self.capture.channels.len()
    }

    pub fn output_channels(&self) -> usize {
        self.playback.channels.len()
    }

    pub fn sample_rate(&self) -> i32 {
        self.playback.rate
    }

    pub fn cycle_samples(&self) -> usize {
        self.playback.chsamples
    }

    pub fn sample_bits(&self) -> i32 {
        self.playback.sample_bits()
    }

    pub fn frame_size_bytes(&self) -> usize {
        self.playback.frame_size_bytes()
    }

    pub fn input_port(&self, idx: usize) -> Option<Arc<AudioIO>> {
        self.capture.channels.get(idx).cloned()
    }

    pub fn output_port(&self, idx: usize) -> Option<Arc<AudioIO>> {
        self.playback.channels.get(idx).cloned()
    }

    pub fn set_output_gain_balance(&mut self, gain: f32, balance: f32) {
        self.playback.output_gain_linear = gain;
        self.playback.output_balance = balance;
    }

    pub fn output_meter_linear(&self, gain: f32, balance: f32) -> Vec<f32> {
        common::output_meter_linear(&self.playback.channels, gain, balance)
    }

    pub fn start_input_trigger(&self) -> std::io::Result<()> {
        self.capture.start_trigger()
    }

    pub fn start_output_trigger(&self) -> std::io::Result<()> {
        self.playback.start_trigger()
    }

    pub fn channel(&mut self) -> OSSChannel<'_> {
        OSSChannel {
            capture: &mut self.capture,
            playback: &mut self.playback,
            stop_requested: &self.stop_requested,
        }
    }

    fn run_cycle_with_assist(&mut self) -> std::io::Result<()> {
        let assist_lock = self.assist_lock.clone();
        let _guard = assist_lock.lock().expect("OSS assist mutex poisoned");
        self.channel().run_cycle()
    }

    fn run_assist_step(&mut self) -> std::io::Result<bool> {
        let assist_lock = self.assist_lock.clone();
        self.channel().run_assist_step_with_lock(&assist_lock)
    }

    pub fn latency_ranges(&self) -> ((usize, usize), (usize, usize)) {
        latency::latency_ranges(
            self.cycle_samples(),
            self.nperiods,
            self.sync_mode,
            self.input_latency_frames,
            self.output_latency_frames,
        )
    }

    pub fn set_playing(&mut self, playing: bool) {
        self.playing.store(playing, Ordering::Relaxed);
        if playing {
            let _ = self.playback.start_trigger();
        } else {
            let _ = self.playback.stop_trigger();
            self.playback.force_silence_now();
        }
    }
}

impl HwWorkerDriver for HwDriver {
    fn cycle_samples(&self) -> usize {
        self.cycle_samples()
    }

    fn sample_rate(&self) -> i32 {
        self.sample_rate()
    }

    fn run_cycle_for_worker(&mut self) -> Result<(), String> {
        self.run_cycle_with_assist().map_err(|e| e.to_string())
    }

    fn run_assist_step_for_worker(&mut self) -> Result<bool, String> {
        self.run_assist_step().map_err(|e| e.to_string())
    }

    fn request_stop(&mut self) {
        self.stop_requested.store(true, Ordering::Release);
        let _ = self.playback.stop_trigger();
    }
}

crate::impl_hw_device_for_driver!(HwDriver);
crate::impl_hw_midi_hub_traits!(MidiHub);