1use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
2
3pub const MAGIC: u32 = 0x4D41_4F4C;
5
6pub const VERSION: u32 = 3;
10
11pub const MAX_CHANNELS: usize = 32;
13
14pub const NUM_BUSES: usize = 2;
16
17pub const MAX_BLOCK_SIZE: usize = 4096;
19
20pub const RING_CAPACITY: usize = 4096;
22
23pub const HEADER_SIZE: usize = 256;
25pub const CONTROL_SIZE: usize = 256;
26pub const AUDIO_BUFFER_SIZE: usize = MAX_CHANNELS * NUM_BUSES * MAX_BLOCK_SIZE * 4; pub const PARAM_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<ParameterEvent>();
28pub const MIDI_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<MidiEvent>();
29pub const TRANSPORT_SIZE: usize = 256;
30pub const SCRATCH_SIZE: usize = 65536;
31
32pub const CONTROL_OFFSET: usize = HEADER_SIZE;
35pub const AUDIO_OFFSET: usize = HEADER_SIZE + CONTROL_SIZE;
37pub const PARAM_RING_OFFSET: usize = AUDIO_OFFSET + AUDIO_BUFFER_SIZE;
39pub const MIDI_RING_OFFSET: usize = PARAM_RING_OFFSET + PARAM_RING_SIZE;
41pub const ECHO_RING_OFFSET: usize = MIDI_RING_OFFSET + MIDI_RING_SIZE;
42pub const ECHO_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<ParameterEvent>();
43pub const MIDI_OUT_RING_OFFSET: usize = ECHO_RING_OFFSET + ECHO_RING_SIZE;
44pub const MIDI_OUT_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<MidiEvent>();
45pub const TRANSPORT_OFFSET: usize = {
47 let end = MIDI_OUT_RING_OFFSET + MIDI_OUT_RING_SIZE;
48 (end + 255) & !255
50};
51pub const SCRATCH_OFFSET: usize = TRANSPORT_OFFSET + TRANSPORT_SIZE;
53
54pub const LAYOUT_SIZE: usize = SCRATCH_OFFSET + SCRATCH_SIZE;
56
57pub const SHM_SIZE: usize = 4 * 1024 * 1024;
59
60pub const PARAM_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET;
62pub const PARAM_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 4;
63pub const MIDI_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET + 8;
64pub const MIDI_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 12;
65pub const ECHO_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET + 16;
66pub const ECHO_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 20;
67pub const MIDI_OUT_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET + 24;
68pub const MIDI_OUT_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 28;
69
70pub const PARAM_EVENT_VALUE: u32 = 0;
73pub const PARAM_EVENT_MOD: u32 = 1;
74pub const PARAM_EVENT_GESTURE_BEGIN: u32 = 2;
75pub const PARAM_EVENT_GESTURE_END: u32 = 3;
76
77#[repr(C, align(16))]
79#[derive(Clone, Copy, Debug, Default)]
80pub struct ParameterEvent {
81 pub param_index: u32,
82 pub value: f32,
83 pub sample_offset: u32,
84 pub event_kind: u32,
85}
86
87#[repr(C, align(16))]
89#[derive(Clone, Copy, Debug, Default)]
90pub struct MidiEvent {
91 pub sample_offset: u32,
92 pub data: [u8; 3],
93 pub channel: u8,
94 pub flags: u16,
95 pub _pad: u16,
96}
97
98#[repr(C, align(256))]
100#[derive(Clone, Copy, Debug)]
101pub struct TransportState {
102 pub playhead_sample: u64,
103 pub tempo: f64,
104 pub numerator: u32,
105 pub denominator: u32,
106 pub flags: u32,
107 pub sample_rate_hz: f64,
108 _pad: [u8; 256 - 40],
109}
110
111impl Default for TransportState {
112 fn default() -> Self {
113 Self {
114 playhead_sample: 0,
115 tempo: 120.0,
116 numerator: 4,
117 denominator: 4,
118 flags: 0,
119 sample_rate_hz: 0.0,
120 _pad: [0; 256 - 40],
121 }
122 }
123}
124
125#[repr(C, align(256))]
127pub struct ShmHeader {
128 pub magic: u32,
129 pub version: u32,
130 pub flags: u32,
131 pub ready: AtomicU32,
132 pub heartbeat: AtomicU32,
133 pub error_code: u32,
134 pub shutdown_request: AtomicU32,
135 pub tasks_issued: AtomicU32,
136 pub tasks_completed: AtomicU32,
137 pub block_size: AtomicU32,
138 pub num_input_channels: AtomicU32,
139 pub num_output_channels: AtomicU32,
140 pub request_type: AtomicU32,
143 pub request_status: AtomicU32,
145 pub scratch_size: AtomicU32,
147 pub parent_window: AtomicU64,
149 _pad: [u8; 256 - 72],
150}
151
152impl ShmHeader {
153 pub fn parent_window_usize(&self) -> usize {
155 self.parent_window.load(Ordering::Acquire) as usize
156 }
157
158 pub fn set_parent_window(&self, window: usize) {
161 self.parent_window.store(window as u64, Ordering::Release);
162 }
163}
164
165impl Default for ShmHeader {
166 fn default() -> Self {
167 Self {
168 magic: MAGIC,
169 version: VERSION,
170 flags: 0,
171 ready: AtomicU32::new(0),
172 heartbeat: AtomicU32::new(0),
173 error_code: 0,
174 shutdown_request: AtomicU32::new(0),
175 tasks_issued: AtomicU32::new(0),
176 tasks_completed: AtomicU32::new(0),
177 block_size: AtomicU32::new(0),
178 num_input_channels: AtomicU32::new(0),
179 num_output_channels: AtomicU32::new(0),
180 request_type: AtomicU32::new(0),
181 request_status: AtomicU32::new(0),
182 scratch_size: AtomicU32::new(0),
183 parent_window: AtomicU64::new(0),
184 _pad: [0; 256 - 72],
185 }
186 }
187}
188
189pub unsafe fn init_shm_layout(ptr: *mut u8, size: usize) {
196 unsafe {
197 std::ptr::write_bytes(ptr, 0, size);
198 let header = ptr as *mut ShmHeader;
199 std::ptr::write(header, ShmHeader::default());
200 }
201}
202
203pub unsafe fn header_ref(ptr: *mut u8) -> &'static ShmHeader {
208 unsafe { &*(ptr as *mut ShmHeader) }
209}
210
211pub unsafe fn header_mut(ptr: *mut u8) -> &'static mut ShmHeader {
216 unsafe { &mut *(ptr as *mut ShmHeader) }
217}
218
219pub unsafe fn audio_ptr(ptr: *mut u8) -> *mut f32 {
224 unsafe { ptr.add(AUDIO_OFFSET) as *mut f32 }
225}
226
227pub unsafe fn audio_channel_ptr(ptr: *mut u8, channel: usize, bus: usize) -> *mut f32 {
235 let plane_size = MAX_BLOCK_SIZE * std::mem::size_of::<f32>();
236 let offset = AUDIO_OFFSET + (channel * NUM_BUSES + bus) * plane_size;
237 unsafe { ptr.add(offset) as *mut f32 }
238}
239
240pub unsafe fn param_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
245 unsafe { ptr.add(PARAM_RING_OFFSET) as *mut ParameterEvent }
246}
247
248pub unsafe fn param_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
253 unsafe {
254 (
255 ptr.add(PARAM_WRITE_IDX_OFFSET) as *mut AtomicU32,
256 ptr.add(PARAM_READ_IDX_OFFSET) as *mut AtomicU32,
257 )
258 }
259}
260
261pub unsafe fn midi_ring_ptr(ptr: *mut u8) -> *mut MidiEvent {
266 unsafe { ptr.add(MIDI_RING_OFFSET) as *mut MidiEvent }
267}
268
269pub unsafe fn midi_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
274 unsafe {
275 (
276 ptr.add(MIDI_WRITE_IDX_OFFSET) as *mut AtomicU32,
277 ptr.add(MIDI_READ_IDX_OFFSET) as *mut AtomicU32,
278 )
279 }
280}
281
282pub unsafe fn echo_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
287 unsafe { ptr.add(ECHO_RING_OFFSET) as *mut ParameterEvent }
288}
289
290pub unsafe fn echo_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
295 unsafe {
296 (
297 ptr.add(ECHO_WRITE_IDX_OFFSET) as *mut AtomicU32,
298 ptr.add(ECHO_READ_IDX_OFFSET) as *mut AtomicU32,
299 )
300 }
301}
302
303pub unsafe fn midi_out_ring_ptr(ptr: *mut u8) -> *mut MidiEvent {
308 unsafe { ptr.add(MIDI_OUT_RING_OFFSET) as *mut MidiEvent }
309}
310
311pub unsafe fn midi_out_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
316 unsafe {
317 (
318 ptr.add(MIDI_OUT_WRITE_IDX_OFFSET) as *mut AtomicU32,
319 ptr.add(MIDI_OUT_READ_IDX_OFFSET) as *mut AtomicU32,
320 )
321 }
322}
323
324pub unsafe fn transport_ref(ptr: *mut u8) -> &'static TransportState {
329 unsafe { &*(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
330}
331
332pub unsafe fn transport_mut(ptr: *mut u8) -> &'static mut TransportState {
337 unsafe { &mut *(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
338}
339
340pub unsafe fn scratch_ptr(ptr: *mut u8) -> *mut u8 {
345 unsafe { ptr.add(SCRATCH_OFFSET) }
346}
347
348pub unsafe fn write_plugin_name_to_scratch(ptr: *mut u8, name: &str) {
354 unsafe {
355 let scratch = scratch_ptr(ptr);
356 let bytes = name.as_bytes();
357 let len = bytes.len().min(SCRATCH_SIZE - 4);
358 std::ptr::write_unaligned(scratch as *mut u32, len as u32);
359 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(4), len);
360 }
361}
362
363pub unsafe fn read_plugin_name_from_scratch(ptr: *mut u8) -> Option<String> {
368 unsafe {
369 let scratch = scratch_ptr(ptr);
370 let len = std::ptr::read_unaligned(scratch as *mut u32) as usize;
371 if len == 0 || len > SCRATCH_SIZE - 4 {
372 return None;
373 }
374 let bytes = std::slice::from_raw_parts(scratch.add(4), len);
375 String::from_utf8(bytes.to_vec()).ok()
376 }
377}
378
379pub const PORT_COUNTS_MAGIC: u32 = 0x504F_5254; const PORT_COUNTS_OFFSET: usize = 1024;
384
385pub unsafe fn write_port_counts_to_scratch(
390 ptr: *mut u8,
391 audio_in: u32,
392 audio_out: u32,
393 midi_in: u32,
394 midi_out: u32,
395) {
396 unsafe {
397 let dest = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
398 std::ptr::write_unaligned(dest as *mut u32, PORT_COUNTS_MAGIC);
399 std::ptr::write_unaligned(dest.add(4) as *mut u32, audio_in);
400 std::ptr::write_unaligned(dest.add(8) as *mut u32, audio_out);
401 std::ptr::write_unaligned(dest.add(12) as *mut u32, midi_in);
402 std::ptr::write_unaligned(dest.add(16) as *mut u32, midi_out);
403 }
404}
405
406pub unsafe fn read_port_counts_from_scratch(ptr: *mut u8) -> Option<(u32, u32, u32, u32)> {
411 unsafe {
412 let src = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
413 let magic = std::ptr::read_unaligned(src as *mut u32);
414 if magic != PORT_COUNTS_MAGIC {
415 return None;
416 }
417 let audio_in = std::ptr::read_unaligned(src.add(4) as *mut u32);
418 let audio_out = std::ptr::read_unaligned(src.add(8) as *mut u32);
419 let midi_in = std::ptr::read_unaligned(src.add(12) as *mut u32);
420 let midi_out = std::ptr::read_unaligned(src.add(16) as *mut u32);
421 Some((audio_in, audio_out, midi_in, midi_out))
422 }
423}
424
425pub const FILE_REFS_MAGIC: u32 = 0x4649_4C45; const FILE_REFS_OFFSET: usize = 2048;
430
431const FILE_REFS_MAX_SIZE: usize = SCRATCH_SIZE - FILE_REFS_OFFSET;
433
434pub type FileReference = (u32, String);
436
437pub unsafe fn write_file_references_to_scratch(
444 ptr: *mut u8,
445 refs: &[FileReference],
446) -> Result<(), String> {
447 unsafe {
448 let mut dest = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
449 let mut remaining = FILE_REFS_MAX_SIZE;
450 if remaining < 8 {
451 return Err("scratch too small for file references".to_string());
452 }
453 std::ptr::write_unaligned(dest as *mut u32, FILE_REFS_MAGIC);
454 dest = dest.add(4);
455 remaining -= 4;
456 let count = refs.len().min(u32::MAX as usize) as u32;
457 std::ptr::write_unaligned(dest as *mut u32, count);
458 dest = dest.add(4);
459 remaining -= 4;
460 for (index, path) in refs.iter().take(count as usize) {
461 if remaining < 8 {
462 return Err("scratch overflow writing file references".to_string());
463 }
464 std::ptr::write_unaligned(dest as *mut u32, *index);
465 dest = dest.add(4);
466 remaining -= 4;
467 let bytes = path.as_bytes();
468 let len = bytes
469 .len()
470 .min(u32::MAX as usize)
471 .min(remaining.saturating_sub(4));
472 if len < bytes.len() {
473 return Err("scratch overflow writing file references".to_string());
474 }
475 std::ptr::write_unaligned(dest as *mut u32, len as u32);
476 dest = dest.add(4);
477 remaining -= 4;
478 std::ptr::copy_nonoverlapping(bytes.as_ptr(), dest, len);
479 dest = dest.add(len);
480 remaining -= len;
481 }
482 Ok(())
483 }
484}
485
486pub unsafe fn read_file_references_from_scratch(ptr: *mut u8) -> Option<Vec<FileReference>> {
491 unsafe {
492 let mut src = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
493 let mut remaining = FILE_REFS_MAX_SIZE;
494 if remaining < 8 {
495 return None;
496 }
497 let magic = std::ptr::read_unaligned(src as *mut u32);
498 if magic != FILE_REFS_MAGIC {
499 return None;
500 }
501 src = src.add(4);
502 remaining -= 4;
503 let count = std::ptr::read_unaligned(src as *mut u32) as usize;
504 src = src.add(4);
505 remaining -= 4;
506 let mut refs = Vec::with_capacity(count);
507 for _ in 0..count {
508 if remaining < 8 {
509 return None;
510 }
511 let index = std::ptr::read_unaligned(src as *mut u32);
512 src = src.add(4);
513 remaining -= 4;
514 let len = std::ptr::read_unaligned(src as *mut u32) as usize;
515 src = src.add(4);
516 remaining -= 4;
517 if len > remaining {
518 return None;
519 }
520 let bytes = std::slice::from_raw_parts(src, len);
521 let path = String::from_utf8(bytes.to_vec()).ok()?;
522 refs.push((index, path));
523 src = src.add(len);
524 remaining -= len;
525 }
526 Some(refs)
527 }
528}
529
530pub unsafe fn write_resource_directory_to_scratch(ptr: *mut u8, path: &str) -> Result<(), String> {
536 unsafe {
537 let scratch = scratch_ptr(ptr);
538 let bytes = path.as_bytes();
539 let len = bytes.len().min(SCRATCH_SIZE - 8);
540 if len < bytes.len() {
541 return Err("resource directory path too long".to_string());
542 }
543 std::ptr::write_unaligned(scratch as *mut u32, FILE_REFS_MAGIC);
544 std::ptr::write_unaligned(scratch.add(4) as *mut u32, len as u32);
545 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(8), len);
546 Ok(())
547 }
548}
549
550pub unsafe fn read_resource_directory_from_scratch(ptr: *mut u8) -> Option<String> {
555 unsafe {
556 let scratch = scratch_ptr(ptr);
557 let magic = std::ptr::read_unaligned(scratch as *mut u32);
558 if magic != FILE_REFS_MAGIC {
559 return None;
560 }
561 let len = std::ptr::read_unaligned(scratch.add(4) as *mut u32) as usize;
562 if len == 0 || len > SCRATCH_SIZE - 8 {
563 return None;
564 }
565 let bytes = std::slice::from_raw_parts(scratch.add(8), len);
566 String::from_utf8(bytes.to_vec()).ok()
567 }
568}
569
570pub const FILE_REF_UPDATE_MAGIC: u32 = 0x5550_4441; pub unsafe fn write_file_reference_update_to_scratch(
579 ptr: *mut u8,
580 index: u32,
581 path: &str,
582) -> Result<(), String> {
583 unsafe {
584 let scratch = scratch_ptr(ptr);
585 let bytes = path.as_bytes();
586 let len = bytes.len().min(SCRATCH_SIZE - 12);
587 if len < bytes.len() {
588 return Err("file-reference update path too long".to_string());
589 }
590 std::ptr::write_unaligned(scratch as *mut u32, FILE_REF_UPDATE_MAGIC);
591 std::ptr::write_unaligned(scratch.add(4) as *mut u32, index);
592 std::ptr::write_unaligned(scratch.add(8) as *mut u32, len as u32);
593 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(12), len);
594 Ok(())
595 }
596}
597
598pub unsafe fn read_file_reference_update_from_scratch(ptr: *mut u8) -> Option<(u32, String)> {
603 unsafe {
604 let scratch = scratch_ptr(ptr);
605 let magic = std::ptr::read_unaligned(scratch as *mut u32);
606 if magic != FILE_REF_UPDATE_MAGIC {
607 return None;
608 }
609 let index = std::ptr::read_unaligned(scratch.add(4) as *mut u32);
610 let len = std::ptr::read_unaligned(scratch.add(8) as *mut u32) as usize;
611 if len == 0 || len > SCRATCH_SIZE - 12 {
612 return None;
613 }
614 let bytes = std::slice::from_raw_parts(scratch.add(12), len);
615 let path = String::from_utf8(bytes.to_vec()).ok()?;
616 Some((index, path))
617 }
618}
619
620const _: () = assert!(std::mem::size_of::<ShmHeader>() == 256);
623const _: () = assert!(std::mem::align_of::<ShmHeader>() == 256);
624const _: () = assert!(std::mem::size_of::<ParameterEvent>() == 16);
625const _: () = assert!(std::mem::align_of::<ParameterEvent>() == 16);
626const _: () = assert!(std::mem::size_of::<MidiEvent>() == 16);
627const _: () = assert!(std::mem::align_of::<MidiEvent>() == 16);
628const _: () = assert!(std::mem::size_of::<TransportState>() == 256);
629const _: () = assert!(std::mem::align_of::<TransportState>() == 256);
630const _: () = assert!(LAYOUT_SIZE <= SHM_SIZE);
631
632pub fn wait_for_ready(header: &ShmHeader, timeout: std::time::Duration) -> bool {
634 let start = std::time::Instant::now();
635 while header.ready.load(Ordering::Acquire) == 0 {
636 if start.elapsed() >= timeout {
637 return false;
638 }
639 std::thread::yield_now();
640 }
641 true
642}