Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions src/engine/hardware.rs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
use super::*;
#[cfg(target_os = "linux")]
use crate::hw::alsa::{HwDriver, HwOptions, MidiHub};
#[cfg(target_os = "macos")]
use crate::hw::coreaudio::{HwDriver, HwOptions, MidiHub};
#[cfg(unix)]
use crate::hw::jack::JackRuntime;
#[cfg(target_os = "windows")]
Expand All@@ -16,6 +18,8 @@ use crate::hw::traits::HwWorkerDriver;
use crate::hw::wasapi::{self, HwDriver};
#[cfg(target_os = "linux")]
use crate::workers::alsa_worker::HwWorker;
#[cfg(target_os = "macos")]
use crate::workers::coreaudio_worker::HwWorker;
#[cfg(target_os = "freebsd")]
use crate::workers::oss_worker::HwWorker;
#[cfg(target_os = "openbsd")]
Expand All@@ -29,6 +33,7 @@ use crate::{
message::{Action, Message},
};
#[cfg(unix)]
#[cfg(all(unix, not(target_os = "macos")))]
use std::fs::read_dir;
use std::sync::Arc;
use tokio::sync::mpsc::channel;
Expand All@@ -41,7 +46,7 @@ impl Engine {
devices
}

#[cfg(unix)]
#[cfg(all(unix, not(target_os = "macos")))]
pub(crate) fn discover_midi_hw_devices_from_dir(path: &str, prefixes: &[&str]) -> Vec<String> {
let devices = read_dir(path)
.map(|rd| {
Expand All@@ -67,6 +72,10 @@ impl Engine {
let devices = Self::discover_midi_hw_devices_from_dir("/dev/snd", &["midiC"]);
#[cfg(target_os = "openbsd")]
let devices = Self::discover_midi_hw_devices_from_dir("/dev", &["midi"]);
// macOS exposes MIDI endpoints through CoreMIDI rather than device
// nodes, and no CoreMIDI backend exists yet.
#[cfg(target_os = "macos")]
let devices = Self::finalize_midi_hw_devices(Vec::new());
#[cfg(target_os = "windows")]
let devices = {
let mut devices = wasapi::list_midi_input_devices();
Expand All@@ -83,7 +92,12 @@ impl Engine {
bits: i32,
hw_opts: HwOptions,
) -> Result<HwDriver, String> {
#[cfg(any(target_os = "windows", target_os = "freebsd", target_os = "linux"))]
#[cfg(any(
target_os = "windows",
target_os = "freebsd",
target_os = "linux",
target_os = "macos"
))]
{
HwDriver::new_with_options(device, _input_device, sample_rate_hz, bits, hw_opts)
.map_err(|e| e.to_string())
Expand All@@ -104,6 +118,8 @@ impl Engine {
let label = "OSS";
#[cfg(target_os = "openbsd")]
let label = "sndio";
#[cfg(target_os = "macos")]
let label = "CoreAudio";
label
}

Expand Down
2 changes: 2 additions & 0 deletions src/engine/mod.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,8 @@ pub fn parse_automation_lanes(

#[cfg(target_os = "linux")]
use crate::hw::alsa::{HwDriver, MidiHub};
#[cfg(target_os = "macos")]
use crate::hw::coreaudio::{HwDriver, MidiHub};
#[cfg(unix)]
use crate::hw::jack::JackRuntime;
#[cfg(target_os = "freebsd")]
Expand Down
2 changes: 2 additions & 0 deletions src/engine/runtime.rs
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
use super::*;
#[cfg(target_os = "linux")]
use crate::hw::alsa::MidiHub;
#[cfg(target_os = "macos")]
use crate::hw::coreaudio::MidiHub;
#[cfg(target_os = "freebsd")]
use crate::hw::oss::MidiHub;
#[cfg(target_os = "openbsd")]
Expand Down
3 changes: 3 additions & 0 deletions src/hw/common.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,9 @@ pub fn channel_balance_gain(ch_count: usize, channel_idx: usize, balance: f32) -
}
}

// Fallback meter for the native drivers. macOS has no native driver yet, but
// the helper stays available to tests so its coverage does not vary by platform.
#[cfg(any(not(target_os = "macos"), test))]
pub fn output_meter_linear(port_count: usize, gain: f32, balance: f32) -> Vec<f32> {
(0..port_count)
.map(|channel_idx| 0.0 * gain * channel_balance_gain(port_count, channel_idx, balance))
Expand Down
2 changes: 2 additions & 0 deletions src/hw/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,8 @@ pub const HW_PROFILE_ENV: &str = "MAOLAN_HW_PROFILE";
pub const OSS_ASSIST_AUTONOMOUS_ENV: &str = "MAOLAN_OSS_ASSIST_AUTONOMOUS";
#[cfg(target_os = "linux")]
pub const ALSA_ASSIST_AUTONOMOUS_ENV: &str = "MAOLAN_ALSA_ASSIST_AUTONOMOUS";
#[cfg(target_os = "macos")]
pub const COREAUDIO_ASSIST_AUTONOMOUS_ENV: &str = "MAOLAN_COREAUDIO_ASSIST_AUTONOMOUS";
#[cfg(target_os = "openbsd")]
pub const SNDIO_ASSIST_AUTONOMOUS_ENV: &str = "MAOLAN_SNDIO_ASSIST_AUTONOMOUS";
#[cfg(target_os = "windows")]
Expand Down
147 changes: 147 additions & 0 deletions src/hw/coreaudio.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
//! macOS hardware backend.
//!
//! Maolan has no native CoreAudio driver yet. This module exists so the engine
//! builds and runs on macOS, where audio I/O goes through JACK (see
//! [`crate::hw::jack`]) rather than a native device. Opening a native device
//! reports that clearly instead of failing to compile.
//!
//! MIDI and the option struct are platform-neutral and are re-exported from the
//! shared modules, exactly as the other backends do.

use crate::audio::io::AudioIO;
use crate::hw::traits::{HwDevice, HwWorkerDriver};
use std::sync::Arc;

pub use super::midi_hub::MidiHub;
pub use super::options::HwOptions;

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

pub const UNSUPPORTED: &str =
"Maolan has no native CoreAudio backend yet; run a JACK server and select a JACK device";

/// Placeholder native device. [`HwDriver::new_with_options`] always fails, so a
/// value of this type is never constructed; it exists to satisfy the backend
/// surface the engine and [`crate::workers::hw_worker::HwWorker`] expect.
#[derive(Debug)]
pub struct HwDriver {
_private: (),
}

impl HwDriver {
pub fn new_with_options(
_device: &str,
_input_device: Option<&str>,
_sample_rate_hz: i32,
_bits: i32,
_options: HwOptions,
) -> Result<Self, String> {
Err(UNSUPPORTED.to_string())
}
}

impl HwDriver {
pub fn input_channels(&self) -> usize {
0
}

pub fn output_channels(&self) -> usize {
0
}

pub fn sample_rate(&self) -> i32 {
0
}

pub fn cycle_samples(&self) -> usize {
0
}

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

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

pub fn latency_ranges(&self) -> ((usize, usize), (usize, usize)) {
((0, 0), (0, 0))
}

pub fn input_port(&self, _idx: usize) -> Option<Arc<AudioIO>> {
None
}

pub fn output_port(&self, _idx: usize) -> Option<Arc<AudioIO>> {
None
}

pub fn close_fds(&mut self) {}

pub fn set_playing(&mut self, _playing: bool) {}

pub fn set_output_gain_balance(&mut self, _gain: f32, _balance: f32) {}
}

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

fn sample_rate(&self) -> i32 {
Self::sample_rate(self)
}

fn close_fds(&mut self) {
Self::close_fds(self)
}

fn set_playing(&mut self, playing: bool) {
Self::set_playing(self, playing)
}

fn set_output_gain_balance(&mut self, gain: f32, balance: f32) {
Self::set_output_gain_balance(self, gain, balance)
}

fn run_cycle_for_worker(&mut self) -> Result<(), String> {
Err(UNSUPPORTED.to_string())
}

fn run_assist_step_for_worker(&mut self) -> Result<bool, String> {
Err(UNSUPPORTED.to_string())
}
}

impl HwDevice for HwDriver {
fn input_channels(&self) -> usize {
Self::input_channels(self)
}

fn output_channels(&self) -> usize {
Self::output_channels(self)
}

fn sample_rate(&self) -> i32 {
Self::sample_rate(self)
}

fn latency_ranges(&self) -> ((usize, usize), (usize, usize)) {
Self::latency_ranges(self)
}
}

crate::impl_hw_midi_hub_traits!(MidiHub);
8 changes: 4 additions & 4 deletions src/hw/midi_hub.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -196,7 +196,7 @@ struct MidiInputDevice {
parser: MidiParser,
}

#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "macos"))]
#[derive(Debug)]
struct MidiInputWaiter {
kq: i32,
Expand All@@ -205,7 +205,7 @@ struct MidiInputWaiter {
wake_write_fd: RawFd,
}

#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "macos"))]
impl MidiInputWaiter {
fn new() -> Result<Self, String> {
let kq = unsafe { libc::kqueue() };
Expand DownExpand Up@@ -325,14 +325,14 @@ impl MidiInputWaiter {
}
}

#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "macos"))]
impl Drop for MidiInputWaiter {
fn drop(&mut self) {
self.close();
}
}

#[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "macos"))]
unsafe impl Send for MidiInputWaiter {}

#[cfg(target_os = "linux")]
Expand Down
4 changes: 4 additions & 0 deletions src/hw/mod.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,15 +3,19 @@ pub mod alsa;
pub mod common;
pub mod config;
pub mod convert_policy;
#[cfg(target_os = "macos")]
pub mod coreaudio;
pub mod error_fmt;
#[cfg(unix)]
pub mod jack;
#[cfg(not(target_os = "macos"))]
pub mod latency;
#[cfg(unix)]
pub mod midi_hub;
pub mod options;
#[cfg(target_os = "freebsd")]
pub mod oss;
#[cfg(not(target_os = "macos"))]
pub mod ports;
#[cfg(target_os = "openbsd")]
pub mod sndio;
Expand Down
18 changes: 18 additions & 0 deletions src/workers/coreaudio_worker.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
use super::hw_worker::Backend;
use crate::hw::config;
use crate::hw::coreaudio;

#[derive(Debug)]
pub struct CoreAudioBackend;

impl Backend for CoreAudioBackend {
type Driver = coreaudio::HwDriver;
type MidiHub = coreaudio::MidiHub;

const LABEL: &'static str = "CoreAudio";
const WORKER_THREAD_NAME: &'static str = "coreaudio-worker";
const ASSIST_THREAD_NAME: &'static str = "coreaudio-assist";
const ASSIST_AUTONOMOUS_ENV: &'static str = config::COREAUDIO_ASSIST_AUTONOMOUS_ENV;
}

pub type HwWorker = super::hw_worker::HwWorker<CoreAudioBackend>;
4 changes: 4 additions & 0 deletions src/workers/hw_worker.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -80,6 +80,10 @@ impl<B: Backend> HwWorker<B> {
unsafe {
libc::pthread_set_name_np(thread, c_name.as_ptr());
}
#[cfg(target_os = "macos")]
unsafe {
let _ = libc::pthread_setname_np(c_name.as_ptr());
}

let param = unsafe {
let mut p = std::mem::zeroed::<libc::sched_param>();
Expand Down
2 changes: 2 additions & 0 deletions src/workers/mod.rs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
#[cfg(target_os = "linux")]
pub mod alsa_worker;
#[cfg(target_os = "macos")]
pub mod coreaudio_worker;
pub mod hw_worker;
#[cfg(target_os = "freebsd")]
pub mod oss_worker;
Expand Down