maolan-plugin-host 0.0.2

Out-of-process plugin host for Maolan DAW
Documentation
#![cfg(all(test, windows))]

use maolan_plugin_protocol::events::EventPair;
use maolan_plugin_protocol::protocol::*;
use maolan_plugin_protocol::shm::ShmMapping;
use std::process::{Command, Stdio};
use std::sync::atomic::Ordering;
use std::time::{Duration, Instant};

#[test]
fn surge_xt_load_and_process() {
    let surge_path = r"C:\Program Files\Common Files\CLAP\Surge Synth Team\Surge XT.clap";
    if !std::path::Path::new(surge_path).exists() {
        return;
    }

    let plugin_spec = format!("{surge_path}::org.surge-synth-team.surge-xt");
    let instance_id = "test-surge-xt-windows";
    let pid = std::process::id();
    let shm_name = format!("/maolan-{pid}-{instance_id}");

    let mapping = ShmMapping::create(&shm_name, SHM_SIZE).expect("create shm");
    unsafe {
        init_shm_layout(mapping.as_ptr(), mapping.size());
    }

    let mut events = EventPair::new().expect("create event pair");

    let host_bin = std::env::var("CARGO_BIN_EXE_maolan-plugin-host")
        .map(std::path::PathBuf::from)
        .unwrap_or_else(|_| {
            std::env::current_exe()
                .unwrap()
                .parent()
                .unwrap()
                .join("maolan-plugin-host.exe")
        });

    let mut cmd = Command::new(&host_bin);
    cmd.arg("clap")
        .arg(&plugin_spec)
        .arg(&shm_name)
        .arg(instance_id)
        .arg(events.daw_to_host_name())
        .arg(events.host_to_daw_name())
        .stdin(Stdio::null())
        .stdout(Stdio::inherit())
        .stderr(Stdio::inherit());

    let mut child = cmd.spawn().expect("spawn plugin host");
    events.close_daw_unused();

    let header = unsafe { header_ref(mapping.as_ptr()) };
    let start = Instant::now();
    let ready = loop {
        if header.ready.load(Ordering::Acquire) != 0 {
            break true;
        }
        if start.elapsed() >= Duration::from_secs(15) {
            break false;
        }
        std::thread::sleep(Duration::from_millis(10));
    };
    assert!(ready, "plugin host did not signal ready");

    let name = unsafe {
        let mut n = None;
        for _ in 0..50 {
            n = read_plugin_name_from_scratch(mapping.as_ptr());
            if n.is_some() {
                break;
            }
            std::thread::sleep(Duration::from_millis(10));
        }
        n
    };
    println!("plugin name: {name:?}");

    let ptr = mapping.as_ptr();
    let block_size = 256usize;
    let channels = 2usize;
    unsafe {
        let h = header_mut(ptr);
        h.block_size.store(block_size as u32, Ordering::Release);
        h.num_input_channels
            .store(channels as u32, Ordering::Release);
        h.num_output_channels
            .store(channels as u32, Ordering::Release);
        let ts = transport_mut(ptr);
        ts.sample_rate_hz = 48000.0;
    }

    for ch in 0..channels {
        let plane = unsafe { audio_channel_ptr(ptr, ch, 0) };
        for s in 0..block_size {
            unsafe {
                *plane.add(s) = (s as f32) / (block_size as f32);
            }
        }
    }

    events.signal_host().expect("signal host");
    events
        .wait_host(Duration::from_secs(10))
        .expect("host should complete");

    for ch in 0..channels {
        let plane =
            unsafe { std::slice::from_raw_parts(audio_channel_ptr(ptr, ch, 1), block_size) };
        for (i, &sample) in plane.iter().enumerate() {
            assert!(
                sample.is_finite(),
                "output ch={ch} sample={i} is not finite: {sample}"
            );
        }
    }

    header.shutdown_request.store(1, Ordering::Release);
    let _ = events.signal_host();

    let _ = child.wait();
    let _ = ShmMapping::unlink(&shm_name);
}