maolan_engine/plugins/
ipc.rs1use crate::audio::io::AudioIO;
4use crate::mutex::UnsafeMutex;
5use maolan_plugin_protocol::events::EventPair;
6use maolan_plugin_protocol::protocol::*;
7use maolan_plugin_protocol::shm::ShmMapping;
8use std::path::{Path, PathBuf};
9use std::process::{Child, Command, Stdio};
10use std::sync::Arc;
11use std::sync::atomic::Ordering;
12use std::time::{Duration, Instant};
13
14pub struct HostSpawnArgs<'a> {
16 pub host_binary: &'a Path,
17 pub format: &'a str,
18 pub plugin_spec: &'a str,
19 pub instance_id: &'a str,
20 pub extra_args: &'a [&'a str],
21}
22
23pub fn spawn_host(args: HostSpawnArgs) -> Result<(Child, ShmMapping, EventPair, String), String> {
25 let pid = std::process::id();
26 let shm_name = format!("/maolan-{pid}-{}", args.instance_id);
27
28 let mapping = ShmMapping::create(&shm_name, SHM_SIZE)
29 .map_err(|e| format!("failed to create shared memory: {e}"))?;
30 unsafe {
31 init_shm_layout(mapping.as_ptr(), mapping.size());
32 }
33
34 let mut events = EventPair::new().map_err(|e| format!("failed to create event pipes: {e}"))?;
35
36 let mut cmd = Command::new(args.host_binary);
37 cmd.arg(args.format)
38 .arg(args.plugin_spec)
39 .arg(&shm_name)
40 .arg(args.instance_id)
41 .stdin(Stdio::null())
42 .stdout(Stdio::null())
43 .stderr(Stdio::inherit());
44
45 for arg in args.extra_args {
46 cmd.arg(arg);
47 }
48
49 #[cfg(unix)]
50 {
51 cmd.arg(events.host_read_fd().to_string())
52 .arg(events.host_write_fd().to_string());
53 }
54 #[cfg(windows)]
55 {
56 cmd.arg(events.daw_to_host_name())
57 .arg(events.host_to_daw_name());
58 }
59
60 if std::env::args().any(|a| a == "--debug") {
61 cmd.arg("--debug");
62 }
63
64 let child = cmd
65 .spawn()
66 .map_err(|e| format!("failed to spawn {} host: {e}", args.format))?;
67
68 events.close_daw_unused();
69
70 Ok((child, mapping, events, shm_name))
71}
72
73pub fn wait_for_ready(header: &ShmHeader, timeout: Duration) -> bool {
75 let start = Instant::now();
76 while start.elapsed() < timeout {
77 if header.ready.load(Ordering::Acquire) != 0 {
78 return true;
79 }
80 std::thread::sleep(Duration::from_millis(5));
81 }
82 false
83}
84
85pub fn bypass_copy_inputs_to_outputs(inputs: &[Arc<AudioIO>], outputs: &[Arc<AudioIO>]) {
87 for (input, output) in inputs.iter().zip(outputs.iter()) {
88 let src = input.buffer.lock();
89 let dst = output.buffer.lock();
90 dst.fill(0.0);
91 for (d, s) in dst.iter_mut().zip(src.iter()) {
92 *d = *s;
93 }
94 *output.finished.lock() = true;
95 }
96 for output in outputs.iter().skip(inputs.len()) {
97 let dst = output.buffer.lock();
98 dst.fill(0.0);
99 *output.finished.lock() = true;
100 }
101}
102
103pub fn drop_host(
105 mapping: &Option<ShmMapping>,
106 events: &Option<EventPair>,
107 child: &UnsafeMutex<Option<Child>>,
108 shm_name: &str,
109) {
110 if let Some(mapping) = mapping
111 && let Some(events) = events
112 {
113 let header = unsafe { header_mut(mapping.as_ptr()) };
114 header.shutdown_request.store(1, Ordering::Release);
115 let _ = events.signal_host();
116 }
117 let mut child_opt = child.lock().take();
118 if let Some(mut child) = child_opt.take() {
119 let start = Instant::now();
120 while start.elapsed() < Duration::from_secs(2) {
121 if child.try_wait().map(|s| s.is_some()).unwrap_or(true) {
122 break;
123 }
124 std::thread::sleep(Duration::from_millis(10));
125 }
126 if child.try_wait().map(|s| s.is_none()).unwrap_or(false) {
127 let _ = child.kill();
128 }
129 }
130 let _ = ShmMapping::unlink(shm_name);
131}
132
133pub fn find_plugin_host_binary() -> Option<PathBuf> {
140 let exe_dir = std::env::current_exe()
141 .ok()
142 .and_then(|p| p.parent().map(PathBuf::from));
143
144 if let Some(ref dir) = exe_dir {
146 let candidate = dir.join("maolan-plugin-host");
147 if candidate.exists() {
148 return Some(candidate);
149 }
150 }
151
152 if let Ok(manifest) = std::env::var("CARGO_MANIFEST_DIR") {
154 let engine_root = Path::new(&manifest);
155 for profile in ["debug", "release"] {
156 let candidate = engine_root
157 .parent()
158 .unwrap_or(Path::new(""))
159 .join("daw")
160 .join("target")
161 .join(profile)
162 .join("maolan-plugin-host");
163 if candidate.exists() {
164 return Some(candidate);
165 }
166 }
167 }
168
169 if let Ok(path_var) = std::env::var("PATH") {
171 for dir in path_var.split(':') {
172 let candidate = Path::new(dir).join("maolan-plugin-host");
173 if candidate.exists() {
174 return Some(candidate);
175 }
176 }
177 }
178
179 None
180}
181
182pub unsafe fn copy_inputs_to_shm(inputs: &[Arc<AudioIO>], ptr: *mut u8, frames: usize) {
187 for (ch, input) in inputs.iter().enumerate() {
188 let src = input.buffer.lock();
189 let dst = unsafe { audio_channel_ptr(ptr, ch, 0) };
190 let len = frames.min(src.len());
191 unsafe {
192 std::ptr::copy_nonoverlapping(src.as_ptr(), dst, len);
193 }
194 }
195}
196
197pub unsafe fn copy_outputs_from_shm(outputs: &[Arc<AudioIO>], ptr: *mut u8, frames: usize) {
202 for (ch, output) in outputs.iter().enumerate() {
203 let dst = output.buffer.lock();
204 let src = unsafe { audio_channel_ptr(ptr, ch, 1) };
205 let len = frames.min(dst.len());
206 unsafe {
207 std::ptr::copy_nonoverlapping(src, dst.as_mut_ptr(), len);
208 }
209 *output.finished.lock() = true;
210 }
211}
212
213pub unsafe fn configure_shm_header(ptr: *mut u8, frames: usize, num_in: usize, num_out: usize) {
218 unsafe {
219 let h = header_mut(ptr);
220 h.block_size.store(frames as u32, Ordering::Release);
221 h.num_input_channels.store(num_in as u32, Ordering::Release);
222 h.num_output_channels
223 .store(num_out as u32, Ordering::Release);
224 }
225}
226
227#[macro_export]
230macro_rules! impl_ipc_processor_wrapper {
231 ($processor:ty) => {
232 impl $crate::mutex::UnsafeMutex<$processor> {
233 pub fn setup_audio_ports(&self) {
234 self.lock().setup_audio_ports();
235 }
236
237 pub fn audio_inputs(&self) -> &[std::sync::Arc<$crate::audio::io::AudioIO>] {
238 self.lock().audio_inputs()
239 }
240
241 pub fn audio_outputs(&self) -> &[std::sync::Arc<$crate::audio::io::AudioIO>] {
242 self.lock().audio_outputs()
243 }
244
245 pub fn main_audio_input_count(&self) -> usize {
246 self.lock().main_audio_input_count()
247 }
248
249 pub fn main_audio_output_count(&self) -> usize {
250 self.lock().main_audio_output_count()
251 }
252
253 pub fn midi_input_count(&self) -> usize {
254 self.lock().midi_input_count()
255 }
256
257 pub fn midi_output_count(&self) -> usize {
258 self.lock().midi_output_count()
259 }
260
261 pub fn set_bypassed(&self, bypassed: bool) {
262 self.lock().set_bypassed(bypassed);
263 }
264
265 pub fn name(&self) -> String {
266 self.lock().name().to_string()
267 }
268
269 pub fn run_host_callbacks_main_thread(&self) {
270 self.lock().run_host_callbacks_main_thread();
271 }
272
273 pub fn reconfigure_ports_if_needed(&self) -> Result<bool, String> {
274 self.lock().reconfigure_ports_if_needed()
275 }
276 }
277 };
278}