1use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
2
3pub const MAGIC: u32 = 0x4D41_4F4C;
5
6pub const VERSION: u32 = 5;
12
13pub const MAX_CHANNELS: usize = 32;
15
16pub const NUM_BUSES: usize = 2;
18
19pub const MAX_BLOCK_SIZE: usize = 4096;
21
22pub const RING_CAPACITY: usize = 4096;
24
25pub const MAX_MIDI_PORTS: usize = 16;
28
29pub const HEADER_SIZE: usize = 256;
31pub const CONTROL_SIZE: usize = 256;
32pub 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>();
34pub const MIDI_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<MidiEvent>();
36pub const MIDI_PORT_RING_SIZE: usize = {
38 let raw = 8 + MIDI_RING_SIZE; (raw + 15) & !15 };
41pub const TRANSPORT_SIZE: usize = 256;
42pub const SCRATCH_SIZE: usize = 65536;
43
44pub const CONTROL_OFFSET: usize = HEADER_SIZE;
47pub const AUDIO_OFFSET: usize = HEADER_SIZE + CONTROL_SIZE;
49pub const PARAM_RING_OFFSET: usize = AUDIO_OFFSET + AUDIO_BUFFER_SIZE;
51pub const ECHO_RING_OFFSET: usize = PARAM_RING_OFFSET + PARAM_RING_SIZE;
53pub const ECHO_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<ParameterEvent>();
54pub const MIDI_IN_RINGS_OFFSET: usize = {
56 let end = ECHO_RING_OFFSET + ECHO_RING_SIZE;
57 (end + 255) & !255
58};
59pub const MIDI_IN_RINGS_SIZE: usize = MAX_MIDI_PORTS * MIDI_PORT_RING_SIZE;
60pub const MIDI_OUT_RINGS_OFFSET: usize = MIDI_IN_RINGS_OFFSET + MIDI_IN_RINGS_SIZE;
62pub const MIDI_OUT_RINGS_SIZE: usize = MAX_MIDI_PORTS * MIDI_PORT_RING_SIZE;
63pub const TRANSPORT_OFFSET: usize = {
65 let end = MIDI_OUT_RINGS_OFFSET + MIDI_OUT_RINGS_SIZE;
66 (end + 255) & !255
68};
69pub const SCRATCH_OFFSET: usize = TRANSPORT_OFFSET + TRANSPORT_SIZE;
71
72pub const LAYOUT_SIZE: usize = SCRATCH_OFFSET + SCRATCH_SIZE;
74
75pub const SHM_SIZE: usize = 4 * 1024 * 1024;
77
78pub const PARAM_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET;
80pub const PARAM_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 4;
81pub const ECHO_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET + 8;
82pub const ECHO_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 12;
83pub const GUI_MODE_OFFSET: usize = CONTROL_OFFSET + 16;
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
87pub enum GuiMode {
88 #[default]
90 Embedded = 0,
91 Floating = 1,
93}
94
95impl GuiMode {
96 pub fn from_u32(value: u32) -> Self {
97 match value {
98 1 => GuiMode::Floating,
99 _ => GuiMode::Embedded,
100 }
101 }
102
103 pub fn as_u32(self) -> u32 {
104 self as u32
105 }
106}
107
108pub const PARAM_EVENT_VALUE: u32 = 0;
111pub const PARAM_EVENT_MOD: u32 = 1;
112pub const PARAM_EVENT_GESTURE_BEGIN: u32 = 2;
113pub const PARAM_EVENT_GESTURE_END: u32 = 3;
114
115#[repr(C, align(16))]
117#[derive(Clone, Copy, Debug, Default)]
118pub struct ParameterEvent {
119 pub param_index: u32,
120 pub value: f32,
121 pub sample_offset: u32,
122 pub event_kind: u32,
123}
124
125#[repr(C, align(16))]
127#[derive(Clone, Copy, Debug, Default)]
128pub struct MidiEvent {
129 pub sample_offset: u32,
130 pub data: [u8; 3],
131 pub channel: u8,
132 pub flags: u16,
133 pub _pad: u16,
134}
135
136#[repr(C, align(256))]
138#[derive(Clone, Copy, Debug)]
139pub struct TransportState {
140 pub playhead_sample: u64,
141 pub tempo: f64,
142 pub numerator: u32,
143 pub denominator: u32,
144 pub flags: u32,
145 pub sample_rate_hz: f64,
146 _pad: [u8; 256 - 40],
147}
148
149impl Default for TransportState {
150 fn default() -> Self {
151 Self {
152 playhead_sample: 0,
153 tempo: 120.0,
154 numerator: 4,
155 denominator: 4,
156 flags: 0,
157 sample_rate_hz: 0.0,
158 _pad: [0; 256 - 40],
159 }
160 }
161}
162
163#[repr(C, align(256))]
165pub struct ShmHeader {
166 pub magic: u32,
167 pub version: u32,
168 pub flags: u32,
169 pub ready: AtomicU32,
170 pub heartbeat: AtomicU32,
171 pub error_code: u32,
172 pub shutdown_request: AtomicU32,
173 pub tasks_issued: AtomicU32,
174 pub tasks_completed: AtomicU32,
175 pub block_size: AtomicU32,
176 pub num_input_channels: AtomicU32,
177 pub num_output_channels: AtomicU32,
178 pub midi_in_port_count: AtomicU32,
180 pub midi_out_port_count: AtomicU32,
182 pub request_type: AtomicU32,
186 pub request_status: AtomicU32,
188 pub scratch_size: AtomicU32,
190 pub parent_window: AtomicU64,
192 pub state_dirty: AtomicU32,
194 pub latency_samples: AtomicU32,
196 _pad: [u8; 256 - 88],
197}
198
199impl ShmHeader {
200 pub fn parent_window_usize(&self) -> usize {
202 self.parent_window.load(Ordering::Acquire) as usize
203 }
204
205 pub fn set_parent_window(&self, window: usize) {
208 self.parent_window.store(window as u64, Ordering::Release);
209 }
210
211 fn gui_mode_atomic(&self) -> &AtomicU32 {
212 unsafe {
215 let base = self as *const Self as *const u8;
216 &*(base.add(GUI_MODE_OFFSET) as *const AtomicU32)
217 }
218 }
219
220 pub fn gui_mode(&self) -> GuiMode {
222 GuiMode::from_u32(self.gui_mode_atomic().load(Ordering::Acquire))
223 }
224
225 pub fn set_gui_mode(&self, mode: GuiMode) {
227 self.gui_mode_atomic()
228 .store(mode.as_u32(), Ordering::Release);
229 }
230}
231
232impl Default for ShmHeader {
233 fn default() -> Self {
234 Self {
235 magic: MAGIC,
236 version: VERSION,
237 flags: 0,
238 ready: AtomicU32::new(0),
239 heartbeat: AtomicU32::new(0),
240 error_code: 0,
241 shutdown_request: AtomicU32::new(0),
242 tasks_issued: AtomicU32::new(0),
243 tasks_completed: AtomicU32::new(0),
244 block_size: AtomicU32::new(0),
245 num_input_channels: AtomicU32::new(0),
246 num_output_channels: AtomicU32::new(0),
247 midi_in_port_count: AtomicU32::new(0),
248 midi_out_port_count: AtomicU32::new(0),
249 request_type: AtomicU32::new(0),
250 request_status: AtomicU32::new(0),
251 scratch_size: AtomicU32::new(0),
252 parent_window: AtomicU64::new(0),
253 state_dirty: AtomicU32::new(0),
254 latency_samples: AtomicU32::new(0),
255 _pad: [0; 256 - 88],
256 }
257 }
258}
259
260pub unsafe fn init_shm_layout(ptr: *mut u8, size: usize) {
267 unsafe {
268 std::ptr::write_bytes(ptr, 0, size);
269 let header = ptr as *mut ShmHeader;
270 std::ptr::write(header, ShmHeader::default());
271 }
272}
273
274pub unsafe fn header_ref(ptr: *mut u8) -> &'static ShmHeader {
279 unsafe { &*(ptr as *mut ShmHeader) }
280}
281
282pub unsafe fn header_mut(ptr: *mut u8) -> &'static mut ShmHeader {
287 unsafe { &mut *(ptr as *mut ShmHeader) }
288}
289
290pub unsafe fn audio_ptr(ptr: *mut u8) -> *mut f32 {
295 unsafe { ptr.add(AUDIO_OFFSET) as *mut f32 }
296}
297
298pub unsafe fn audio_channel_ptr(ptr: *mut u8, channel: usize, bus: usize) -> *mut f32 {
306 let plane_size = MAX_BLOCK_SIZE * std::mem::size_of::<f32>();
307 let offset = AUDIO_OFFSET + (channel * NUM_BUSES + bus) * plane_size;
308 unsafe { ptr.add(offset) as *mut f32 }
309}
310
311pub unsafe fn param_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
316 unsafe { ptr.add(PARAM_RING_OFFSET) as *mut ParameterEvent }
317}
318
319pub unsafe fn param_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
324 unsafe {
325 (
326 ptr.add(PARAM_WRITE_IDX_OFFSET) as *mut AtomicU32,
327 ptr.add(PARAM_READ_IDX_OFFSET) as *mut AtomicU32,
328 )
329 }
330}
331
332pub unsafe fn echo_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
337 unsafe { ptr.add(ECHO_RING_OFFSET) as *mut ParameterEvent }
338}
339
340pub unsafe fn echo_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
345 unsafe {
346 (
347 ptr.add(ECHO_WRITE_IDX_OFFSET) as *mut AtomicU32,
348 ptr.add(ECHO_READ_IDX_OFFSET) as *mut AtomicU32,
349 )
350 }
351}
352
353const fn midi_port_ring_offset(base_offset: usize, port: usize) -> usize {
354 base_offset + port * MIDI_PORT_RING_SIZE
355}
356
357pub unsafe fn midi_in_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
362 unsafe {
363 let base = ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port));
364 (base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
365 }
366}
367
368pub unsafe fn midi_in_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
373 unsafe { ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port) + 8) as *mut MidiEvent }
374}
375
376pub unsafe fn midi_out_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
381 unsafe {
382 let base = ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port));
383 (base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
384 }
385}
386
387pub unsafe fn midi_out_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
392 unsafe { ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port) + 8) as *mut MidiEvent }
393}
394
395pub unsafe fn transport_ref(ptr: *mut u8) -> &'static TransportState {
400 unsafe { &*(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
401}
402
403pub unsafe fn transport_mut(ptr: *mut u8) -> &'static mut TransportState {
408 unsafe { &mut *(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
409}
410
411pub unsafe fn scratch_ptr(ptr: *mut u8) -> *mut u8 {
416 unsafe { ptr.add(SCRATCH_OFFSET) }
417}
418
419pub unsafe fn write_plugin_name_to_scratch(ptr: *mut u8, name: &str) {
425 unsafe {
426 let scratch = scratch_ptr(ptr);
427 let bytes = name.as_bytes();
428 let len = bytes.len().min(SCRATCH_SIZE - 4);
429 std::ptr::write_unaligned(scratch as *mut u32, len as u32);
430 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(4), len);
431 }
432}
433
434pub unsafe fn read_plugin_name_from_scratch(ptr: *mut u8) -> Option<String> {
439 unsafe {
440 let scratch = scratch_ptr(ptr);
441 let len = std::ptr::read_unaligned(scratch as *mut u32) as usize;
442 if len == 0 || len > SCRATCH_SIZE - 4 {
443 return None;
444 }
445 let bytes = std::slice::from_raw_parts(scratch.add(4), len);
446 String::from_utf8(bytes.to_vec()).ok()
447 }
448}
449
450pub const PORT_COUNTS_MAGIC: u32 = 0x504F_5254; const PORT_COUNTS_OFFSET: usize = 1024;
455
456pub unsafe fn write_port_counts_to_scratch(
461 ptr: *mut u8,
462 audio_in: u32,
463 audio_out: u32,
464 midi_in: u32,
465 midi_out: u32,
466) {
467 unsafe {
468 let dest = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
469 std::ptr::write_unaligned(dest as *mut u32, PORT_COUNTS_MAGIC);
470 std::ptr::write_unaligned(dest.add(4) as *mut u32, audio_in);
471 std::ptr::write_unaligned(dest.add(8) as *mut u32, audio_out);
472 std::ptr::write_unaligned(dest.add(12) as *mut u32, midi_in);
473 std::ptr::write_unaligned(dest.add(16) as *mut u32, midi_out);
474 }
475}
476
477pub unsafe fn read_port_counts_from_scratch(ptr: *mut u8) -> Option<(u32, u32, u32, u32)> {
482 unsafe {
483 let src = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
484 let magic = std::ptr::read_unaligned(src as *mut u32);
485 if magic != PORT_COUNTS_MAGIC {
486 return None;
487 }
488 let audio_in = std::ptr::read_unaligned(src.add(4) as *mut u32);
489 let audio_out = std::ptr::read_unaligned(src.add(8) as *mut u32);
490 let midi_in = std::ptr::read_unaligned(src.add(12) as *mut u32);
491 let midi_out = std::ptr::read_unaligned(src.add(16) as *mut u32);
492 Some((audio_in, audio_out, midi_in, midi_out))
493 }
494}
495
496pub const FILE_REFS_MAGIC: u32 = 0x4649_4C45; const FILE_REFS_OFFSET: usize = 2048;
501
502const FILE_REFS_MAX_SIZE: usize = SCRATCH_SIZE - FILE_REFS_OFFSET;
504
505pub type FileReference = (u32, String);
507
508pub unsafe fn write_file_references_to_scratch(
515 ptr: *mut u8,
516 refs: &[FileReference],
517) -> Result<(), String> {
518 unsafe {
519 let mut dest = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
520 let mut remaining = FILE_REFS_MAX_SIZE;
521 if remaining < 8 {
522 return Err("scratch too small for file references".to_string());
523 }
524 std::ptr::write_unaligned(dest as *mut u32, FILE_REFS_MAGIC);
525 dest = dest.add(4);
526 remaining -= 4;
527 let count = refs.len().min(u32::MAX as usize) as u32;
528 std::ptr::write_unaligned(dest as *mut u32, count);
529 dest = dest.add(4);
530 remaining -= 4;
531 for (index, path) in refs.iter().take(count as usize) {
532 if remaining < 8 {
533 return Err("scratch overflow writing file references".to_string());
534 }
535 std::ptr::write_unaligned(dest as *mut u32, *index);
536 dest = dest.add(4);
537 remaining -= 4;
538 let bytes = path.as_bytes();
539 let len = bytes
540 .len()
541 .min(u32::MAX as usize)
542 .min(remaining.saturating_sub(4));
543 if len < bytes.len() {
544 return Err("scratch overflow writing file references".to_string());
545 }
546 std::ptr::write_unaligned(dest as *mut u32, len as u32);
547 dest = dest.add(4);
548 remaining -= 4;
549 std::ptr::copy_nonoverlapping(bytes.as_ptr(), dest, len);
550 dest = dest.add(len);
551 remaining -= len;
552 }
553 Ok(())
554 }
555}
556
557pub unsafe fn read_file_references_from_scratch(ptr: *mut u8) -> Option<Vec<FileReference>> {
562 unsafe {
563 let mut src = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
564 let mut remaining = FILE_REFS_MAX_SIZE;
565 if remaining < 8 {
566 return None;
567 }
568 let magic = std::ptr::read_unaligned(src as *mut u32);
569 if magic != FILE_REFS_MAGIC {
570 return None;
571 }
572 src = src.add(4);
573 remaining -= 4;
574 let count = std::ptr::read_unaligned(src as *mut u32) as usize;
575 src = src.add(4);
576 remaining -= 4;
577 let mut refs = Vec::with_capacity(count);
578 for _ in 0..count {
579 if remaining < 8 {
580 return None;
581 }
582 let index = std::ptr::read_unaligned(src as *mut u32);
583 src = src.add(4);
584 remaining -= 4;
585 let len = std::ptr::read_unaligned(src as *mut u32) as usize;
586 src = src.add(4);
587 remaining -= 4;
588 if len > remaining {
589 return None;
590 }
591 let bytes = std::slice::from_raw_parts(src, len);
592 let path = String::from_utf8(bytes.to_vec()).ok()?;
593 refs.push((index, path));
594 src = src.add(len);
595 remaining -= len;
596 }
597 Some(refs)
598 }
599}
600
601pub unsafe fn write_resource_directory_to_scratch(ptr: *mut u8, path: &str) -> Result<(), String> {
607 unsafe {
608 let scratch = scratch_ptr(ptr);
609 let bytes = path.as_bytes();
610 let len = bytes.len().min(SCRATCH_SIZE - 8);
611 if len < bytes.len() {
612 return Err("resource directory path too long".to_string());
613 }
614 std::ptr::write_unaligned(scratch as *mut u32, FILE_REFS_MAGIC);
615 std::ptr::write_unaligned(scratch.add(4) as *mut u32, len as u32);
616 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(8), len);
617 Ok(())
618 }
619}
620
621pub unsafe fn read_resource_directory_from_scratch(ptr: *mut u8) -> Option<String> {
626 unsafe {
627 let scratch = scratch_ptr(ptr);
628 let magic = std::ptr::read_unaligned(scratch as *mut u32);
629 if magic != FILE_REFS_MAGIC {
630 return None;
631 }
632 let len = std::ptr::read_unaligned(scratch.add(4) as *mut u32) as usize;
633 if len == 0 || len > SCRATCH_SIZE - 8 {
634 return None;
635 }
636 let bytes = std::slice::from_raw_parts(scratch.add(8), len);
637 String::from_utf8(bytes.to_vec()).ok()
638 }
639}
640
641pub const REQUEST_LV2_CONTROL_PORTS: u32 = 8;
643
644pub const REQUEST_CLAP_PARAMETERS: u32 = 9;
646
647pub const REQUEST_LV2_MIDNAM: u32 = 10;
649
650pub const REQUEST_CLAP_NOTE_NAMES: u32 = 11;
652
653pub const FILE_REF_UPDATE_MAGIC: u32 = 0x5550_4441; pub unsafe fn write_file_reference_update_to_scratch(
662 ptr: *mut u8,
663 index: u32,
664 path: &str,
665) -> Result<(), String> {
666 unsafe {
667 let scratch = scratch_ptr(ptr);
668 let bytes = path.as_bytes();
669 let len = bytes.len().min(SCRATCH_SIZE - 12);
670 if len < bytes.len() {
671 return Err("file-reference update path too long".to_string());
672 }
673 std::ptr::write_unaligned(scratch as *mut u32, FILE_REF_UPDATE_MAGIC);
674 std::ptr::write_unaligned(scratch.add(4) as *mut u32, index);
675 std::ptr::write_unaligned(scratch.add(8) as *mut u32, len as u32);
676 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(12), len);
677 Ok(())
678 }
679}
680
681pub unsafe fn read_file_reference_update_from_scratch(ptr: *mut u8) -> Option<(u32, String)> {
686 unsafe {
687 let scratch = scratch_ptr(ptr);
688 let magic = std::ptr::read_unaligned(scratch as *mut u32);
689 if magic != FILE_REF_UPDATE_MAGIC {
690 return None;
691 }
692 let index = std::ptr::read_unaligned(scratch.add(4) as *mut u32);
693 let len = std::ptr::read_unaligned(scratch.add(8) as *mut u32) as usize;
694 if len == 0 || len > SCRATCH_SIZE - 12 {
695 return None;
696 }
697 let bytes = std::slice::from_raw_parts(scratch.add(12), len);
698 let path = String::from_utf8(bytes.to_vec()).ok()?;
699 Some((index, path))
700 }
701}
702
703const _: () = assert!(std::mem::size_of::<ShmHeader>() == 256);
706const _: () = assert!(std::mem::align_of::<ShmHeader>() == 256);
707const _: () = assert!(std::mem::size_of::<ParameterEvent>() == 16);
708const _: () = assert!(std::mem::align_of::<ParameterEvent>() == 16);
709const _: () = assert!(std::mem::size_of::<MidiEvent>() == 16);
710const _: () = assert!(std::mem::align_of::<MidiEvent>() == 16);
711const _: () = assert!(std::mem::size_of::<TransportState>() == 256);
712const _: () = assert!(std::mem::align_of::<TransportState>() == 256);
713const _: () = assert!(LAYOUT_SIZE <= SHM_SIZE);
714
715pub fn wait_for_ready(header: &ShmHeader, timeout: std::time::Duration) -> bool {
717 let start = std::time::Instant::now();
718 while header.ready.load(Ordering::Acquire) == 0 {
719 if start.elapsed() >= timeout {
720 return false;
721 }
722 std::thread::yield_now();
723 }
724 true
725}