maolan-plugin-host 0.0.2

Out-of-process plugin host for Maolan DAW
Documentation
#![cfg_attr(windows, windows_subsystem = "windows")]

use std::time::Duration;

fn print_usage() {}

#[cfg(target_os = "linux")]
fn setup_parent_death_signal() {
    unsafe {
        let r = libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM, 0, 0, 0);
        if r != 0 {}
    }
}

#[cfg(target_os = "freebsd")]
fn setup_parent_death_signal() {
    unsafe {
        let sig: libc::c_int = libc::SIGTERM;
        let r = libc::procctl(
            libc::P_PID,
            0,
            libc::PROC_PDEATHSIG_CTL,
            &sig as *const _ as *mut libc::c_void,
        );
        if r != 0 {}
    }
}

#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
fn setup_parent_death_signal() {}

fn parse_log_level(args: &mut Vec<String>) -> Option<tracing::Level> {
    if let Some(pos) = args.iter().position(|a| a == "--log-level") {
        args.remove(pos);
        if pos < args.len() {
            let level_str = args.remove(pos);
            match level_str.as_str() {
                "none" => None,
                "info" => Some(tracing::Level::INFO),
                "warning" => Some(tracing::Level::WARN),
                "error" => Some(tracing::Level::ERROR),
                "debug" => Some(tracing::Level::DEBUG),
                _ => None,
            }
        } else {
            None
        }
    } else {
        None
    }
}

fn main() {
    setup_parent_death_signal();

    let mut args: Vec<String> = std::env::args().collect();

    if args.len() < 2 {
        print_usage();
        std::process::exit(1);
    }

    if args[1] == "--scan" {
        let mut format = None;
        let mut path = None;
        let mut output = None;
        let mut i = 2;
        while i < args.len() {
            match args[i].as_str() {
                "--format" => {
                    i += 1;
                    if i < args.len() {
                        format = Some(args[i].clone());
                    }
                }
                "--path" => {
                    i += 1;
                    if i < args.len() {
                        path = Some(args[i].clone());
                    }
                }
                "--output" => {
                    i += 1;
                    if i < args.len() {
                        output = Some(args[i].clone());
                    }
                }
                _ => {}
            }
            i += 1;
        }
        let format = format.unwrap_or_else(|| {
            print_usage();
            std::process::exit(1);
        });
        let path = path.unwrap_or_else(|| "--system".to_string());
        let code = maolan_plugin_host::scan::run_scan(&format, &path, output.as_deref());
        std::process::exit(code);
    }

    let log_level = parse_log_level(&mut args);

    let format = args[1].clone();

    let expected_args = match format.as_str() {
        "clap" | "null" | "__test__" => 7,
        "vst3" | "lv2" => 11,
        _ => {
            print_usage();
            std::process::exit(4);
        }
    };

    if args.len() != expected_args {
        print_usage();
        std::process::exit(1);
    }

    let plugin_spec = args[2].clone();
    let shm_name = args[3].clone();
    let instance_id = args[4].clone();

    #[cfg(unix)]
    let d2h_fd: i32 = args[5].parse().unwrap_or(-1);
    #[cfg(unix)]
    let h2d_fd: i32 = args[6].parse().unwrap_or(-1);

    #[cfg(unix)]
    let sample_rate: f64 = if args.len() > 7 {
        args[7].parse().unwrap_or(48000.0)
    } else {
        48000.0
    };
    #[cfg(unix)]
    let buffer_size: usize = if args.len() > 8 {
        args[8].parse().unwrap_or(256)
    } else {
        256
    };
    #[cfg(unix)]
    let num_inputs: usize = if args.len() > 9 {
        args[9].parse().unwrap_or(2)
    } else {
        2
    };
    #[cfg(unix)]
    let num_outputs: usize = if args.len() > 10 {
        args[10].parse().unwrap_or(2)
    } else {
        2
    };

    let (plugin_path, _plugin_id) = if format == "clap" {
        if let Some(pos) = plugin_spec.rfind("::") {
            (
                plugin_spec[..pos].to_string(),
                plugin_spec[pos + 2..].to_string(),
            )
        } else if let Some(pos) = plugin_spec.rfind('#') {
            (
                plugin_spec[..pos].to_string(),
                plugin_spec[pos + 1..].to_string(),
            )
        } else {
            (plugin_spec.clone(), String::new())
        }
    } else {
        (plugin_spec.clone(), String::new())
    };

    let level = log_level.unwrap_or(tracing::Level::INFO);
    tracing_subscriber::fmt()
        .with_writer(std::io::stderr)
        .with_max_level(level)
        .init();

    match format.as_str() {
        "vst3" => {
            #[cfg(unix)]
            {
                if d2h_fd < 0 || h2d_fd < 0 {
                    std::process::exit(3);
                }
                let events =
                    unsafe { maolan_plugin_protocol::events::EventPair::from_fds(d2h_fd, h2d_fd) };
                let mapping = match maolan_plugin_protocol::shm::ShmMapping::open_existing(
                    &shm_name,
                    maolan_plugin_protocol::protocol::SHM_SIZE,
                ) {
                    Ok(m) => m,
                    Err(_e) => {
                        std::process::exit(2);
                    }
                };
                {
                    let header =
                        unsafe { maolan_plugin_protocol::protocol::header_mut(mapping.as_ptr()) };
                    header.ready.store(1, std::sync::atomic::Ordering::Release);
                }
                maolan_plugin_host::vst3_lv2_host::run_vst3(
                    maolan_plugin_host::vst3_lv2_host::Vst3RunArgs {
                        plugin_path: &plugin_spec,
                        mapping,
                        events,
                        instance_id: &instance_id,
                        sample_rate,
                        buffer_size,
                        num_inputs,
                        num_outputs,
                    },
                );
                return;
            }
            #[cfg(not(unix))]
            {
                std::process::exit(4);
            }
        }
        #[cfg(unix)]
        "lv2" => {
            if d2h_fd < 0 || h2d_fd < 0 {
                std::process::exit(3);
            }
            let events =
                unsafe { maolan_plugin_protocol::events::EventPair::from_fds(d2h_fd, h2d_fd) };
            let mapping = match maolan_plugin_protocol::shm::ShmMapping::open_existing(
                &shm_name,
                maolan_plugin_protocol::protocol::SHM_SIZE,
            ) {
                Ok(m) => m,
                Err(_e) => {
                    std::process::exit(2);
                }
            };
            {
                let header =
                    unsafe { maolan_plugin_protocol::protocol::header_mut(mapping.as_ptr()) };
                header.ready.store(1, std::sync::atomic::Ordering::Release);
            }
            maolan_plugin_host::vst3_lv2_host::run_lv2(
                &plugin_spec,
                mapping,
                events,
                &instance_id,
                sample_rate,
                buffer_size,
            );
            return;
        }
        #[cfg(not(unix))]
        "lv2" => {
            std::process::exit(4);
        }
        _ => {}
    }

    #[cfg(unix)]
    let events = {
        if d2h_fd < 0 || h2d_fd < 0 {
            std::process::exit(3);
        }
        unsafe { maolan_plugin_host::events::EventPair::from_fds(d2h_fd, h2d_fd) }
    };
    #[cfg(windows)]
    let events = maolan_plugin_host::events::EventPair::from_names(&args[5], &args[6])
        .unwrap_or_else(|e| {
            std::process::exit(3);
        });

    let runtime = match maolan_plugin_host::host::HostRuntime::attach(
        &shm_name,
        events,
        format.clone(),
        plugin_spec.clone(),
        instance_id.clone(),
    ) {
        Ok(rt) => rt,
        Err(_e) => {
            std::process::exit(2);
        }
    };

    match plugin_path.as_str() {
        "__test__" => runtime.write_test_magic(),
        "__crash__" => {
            runtime.signal_ready();
            std::process::abort();
        }
        "__hang__" => {
            runtime.signal_ready();
            loop {
                std::thread::sleep(Duration::from_secs(60));
            }
        }
        _ => {}
    }

    match format.as_str() {
        "null" => {
            runtime.signal_ready();
            runtime.run_null_plugin();
        }
        "clap" => runtime.run_clap_plugin(),
        _ => {
            runtime.signal_ready();
            runtime.run_until_shutdown();
        }
    }

    runtime.shutdown();
}