1use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
2
3pub const MAGIC: u32 = 0x4D41_4F4C;
5
6pub const VERSION: u32 = 4;
11
12pub const MAX_CHANNELS: usize = 32;
14
15pub const NUM_BUSES: usize = 2;
17
18pub const MAX_BLOCK_SIZE: usize = 4096;
20
21pub const RING_CAPACITY: usize = 4096;
23
24pub const MAX_MIDI_PORTS: usize = 16;
27
28pub const HEADER_SIZE: usize = 256;
30pub const CONTROL_SIZE: usize = 256;
31pub 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>();
33pub const MIDI_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<MidiEvent>();
35pub const MIDI_PORT_RING_SIZE: usize = {
37 let raw = 8 + MIDI_RING_SIZE; (raw + 15) & !15 };
40pub const TRANSPORT_SIZE: usize = 256;
41pub const SCRATCH_SIZE: usize = 65536;
42
43pub const CONTROL_OFFSET: usize = HEADER_SIZE;
46pub const AUDIO_OFFSET: usize = HEADER_SIZE + CONTROL_SIZE;
48pub const PARAM_RING_OFFSET: usize = AUDIO_OFFSET + AUDIO_BUFFER_SIZE;
50pub const ECHO_RING_OFFSET: usize = PARAM_RING_OFFSET + PARAM_RING_SIZE;
52pub const ECHO_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<ParameterEvent>();
53pub const MIDI_IN_RINGS_OFFSET: usize = {
55 let end = ECHO_RING_OFFSET + ECHO_RING_SIZE;
56 (end + 255) & !255
57};
58pub const MIDI_IN_RINGS_SIZE: usize = MAX_MIDI_PORTS * MIDI_PORT_RING_SIZE;
59pub const MIDI_OUT_RINGS_OFFSET: usize = MIDI_IN_RINGS_OFFSET + MIDI_IN_RINGS_SIZE;
61pub const MIDI_OUT_RINGS_SIZE: usize = MAX_MIDI_PORTS * MIDI_PORT_RING_SIZE;
62pub const TRANSPORT_OFFSET: usize = {
64 let end = MIDI_OUT_RINGS_OFFSET + MIDI_OUT_RINGS_SIZE;
65 (end + 255) & !255
67};
68pub const SCRATCH_OFFSET: usize = TRANSPORT_OFFSET + TRANSPORT_SIZE;
70
71pub const LAYOUT_SIZE: usize = SCRATCH_OFFSET + SCRATCH_SIZE;
73
74pub const SHM_SIZE: usize = 4 * 1024 * 1024;
76
77pub const PARAM_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET;
79pub const PARAM_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 4;
80pub const ECHO_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET + 8;
81pub const ECHO_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 12;
82pub const GUI_MODE_OFFSET: usize = CONTROL_OFFSET + 16;
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
86pub enum GuiMode {
87 #[default]
89 Embedded = 0,
90 Floating = 1,
92}
93
94impl GuiMode {
95 pub fn from_u32(value: u32) -> Self {
96 match value {
97 1 => GuiMode::Floating,
98 _ => GuiMode::Embedded,
99 }
100 }
101
102 pub fn as_u32(self) -> u32 {
103 self as u32
104 }
105}
106
107pub const PARAM_EVENT_VALUE: u32 = 0;
110pub const PARAM_EVENT_MOD: u32 = 1;
111pub const PARAM_EVENT_GESTURE_BEGIN: u32 = 2;
112pub const PARAM_EVENT_GESTURE_END: u32 = 3;
113
114#[repr(C, align(16))]
116#[derive(Clone, Copy, Debug, Default)]
117pub struct ParameterEvent {
118 pub param_index: u32,
119 pub value: f32,
120 pub sample_offset: u32,
121 pub event_kind: u32,
122}
123
124#[repr(C, align(16))]
126#[derive(Clone, Copy, Debug, Default)]
127pub struct MidiEvent {
128 pub sample_offset: u32,
129 pub data: [u8; 3],
130 pub channel: u8,
131 pub flags: u16,
132 pub _pad: u16,
133}
134
135#[repr(C, align(256))]
137#[derive(Clone, Copy, Debug)]
138pub struct TransportState {
139 pub playhead_sample: u64,
140 pub tempo: f64,
141 pub numerator: u32,
142 pub denominator: u32,
143 pub flags: u32,
144 pub sample_rate_hz: f64,
145 _pad: [u8; 256 - 40],
146}
147
148impl Default for TransportState {
149 fn default() -> Self {
150 Self {
151 playhead_sample: 0,
152 tempo: 120.0,
153 numerator: 4,
154 denominator: 4,
155 flags: 0,
156 sample_rate_hz: 0.0,
157 _pad: [0; 256 - 40],
158 }
159 }
160}
161
162#[repr(C, align(256))]
164pub struct ShmHeader {
165 pub magic: u32,
166 pub version: u32,
167 pub flags: u32,
168 pub ready: AtomicU32,
169 pub heartbeat: AtomicU32,
170 pub error_code: u32,
171 pub shutdown_request: AtomicU32,
172 pub tasks_issued: AtomicU32,
173 pub tasks_completed: AtomicU32,
174 pub block_size: AtomicU32,
175 pub num_input_channels: AtomicU32,
176 pub num_output_channels: AtomicU32,
177 pub midi_in_port_count: AtomicU32,
179 pub midi_out_port_count: AtomicU32,
181 pub request_type: AtomicU32,
185 pub request_status: AtomicU32,
187 pub scratch_size: AtomicU32,
189 pub parent_window: AtomicU64,
191 pub state_dirty: AtomicU32,
193 _pad: [u8; 256 - 84],
194}
195
196impl ShmHeader {
197 pub fn parent_window_usize(&self) -> usize {
199 self.parent_window.load(Ordering::Acquire) as usize
200 }
201
202 pub fn set_parent_window(&self, window: usize) {
205 self.parent_window.store(window as u64, Ordering::Release);
206 }
207
208 fn gui_mode_atomic(&self) -> &AtomicU32 {
209 unsafe {
212 let base = self as *const Self as *const u8;
213 &*(base.add(GUI_MODE_OFFSET) as *const AtomicU32)
214 }
215 }
216
217 pub fn gui_mode(&self) -> GuiMode {
219 GuiMode::from_u32(self.gui_mode_atomic().load(Ordering::Acquire))
220 }
221
222 pub fn set_gui_mode(&self, mode: GuiMode) {
224 self.gui_mode_atomic()
225 .store(mode.as_u32(), Ordering::Release);
226 }
227}
228
229impl Default for ShmHeader {
230 fn default() -> Self {
231 Self {
232 magic: MAGIC,
233 version: VERSION,
234 flags: 0,
235 ready: AtomicU32::new(0),
236 heartbeat: AtomicU32::new(0),
237 error_code: 0,
238 shutdown_request: AtomicU32::new(0),
239 tasks_issued: AtomicU32::new(0),
240 tasks_completed: AtomicU32::new(0),
241 block_size: AtomicU32::new(0),
242 num_input_channels: AtomicU32::new(0),
243 num_output_channels: AtomicU32::new(0),
244 midi_in_port_count: AtomicU32::new(0),
245 midi_out_port_count: AtomicU32::new(0),
246 request_type: AtomicU32::new(0),
247 request_status: AtomicU32::new(0),
248 scratch_size: AtomicU32::new(0),
249 parent_window: AtomicU64::new(0),
250 state_dirty: AtomicU32::new(0),
251 _pad: [0; 256 - 84],
252 }
253 }
254}
255
256pub unsafe fn init_shm_layout(ptr: *mut u8, size: usize) {
263 unsafe {
264 std::ptr::write_bytes(ptr, 0, size);
265 let header = ptr as *mut ShmHeader;
266 std::ptr::write(header, ShmHeader::default());
267 }
268}
269
270pub unsafe fn header_ref(ptr: *mut u8) -> &'static ShmHeader {
275 unsafe { &*(ptr as *mut ShmHeader) }
276}
277
278pub unsafe fn header_mut(ptr: *mut u8) -> &'static mut ShmHeader {
283 unsafe { &mut *(ptr as *mut ShmHeader) }
284}
285
286pub unsafe fn audio_ptr(ptr: *mut u8) -> *mut f32 {
291 unsafe { ptr.add(AUDIO_OFFSET) as *mut f32 }
292}
293
294pub unsafe fn audio_channel_ptr(ptr: *mut u8, channel: usize, bus: usize) -> *mut f32 {
302 let plane_size = MAX_BLOCK_SIZE * std::mem::size_of::<f32>();
303 let offset = AUDIO_OFFSET + (channel * NUM_BUSES + bus) * plane_size;
304 unsafe { ptr.add(offset) as *mut f32 }
305}
306
307pub unsafe fn param_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
312 unsafe { ptr.add(PARAM_RING_OFFSET) as *mut ParameterEvent }
313}
314
315pub unsafe fn param_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
320 unsafe {
321 (
322 ptr.add(PARAM_WRITE_IDX_OFFSET) as *mut AtomicU32,
323 ptr.add(PARAM_READ_IDX_OFFSET) as *mut AtomicU32,
324 )
325 }
326}
327
328pub unsafe fn echo_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
333 unsafe { ptr.add(ECHO_RING_OFFSET) as *mut ParameterEvent }
334}
335
336pub unsafe fn echo_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
341 unsafe {
342 (
343 ptr.add(ECHO_WRITE_IDX_OFFSET) as *mut AtomicU32,
344 ptr.add(ECHO_READ_IDX_OFFSET) as *mut AtomicU32,
345 )
346 }
347}
348
349const fn midi_port_ring_offset(base_offset: usize, port: usize) -> usize {
350 base_offset + port * MIDI_PORT_RING_SIZE
351}
352
353pub unsafe fn midi_in_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
358 unsafe {
359 let base = ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port));
360 (base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
361 }
362}
363
364pub unsafe fn midi_in_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
369 unsafe { ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port) + 8) as *mut MidiEvent }
370}
371
372pub unsafe fn midi_out_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
377 unsafe {
378 let base = ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port));
379 (base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
380 }
381}
382
383pub unsafe fn midi_out_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
388 unsafe { ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port) + 8) as *mut MidiEvent }
389}
390
391pub unsafe fn transport_ref(ptr: *mut u8) -> &'static TransportState {
396 unsafe { &*(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
397}
398
399pub unsafe fn transport_mut(ptr: *mut u8) -> &'static mut TransportState {
404 unsafe { &mut *(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
405}
406
407pub unsafe fn scratch_ptr(ptr: *mut u8) -> *mut u8 {
412 unsafe { ptr.add(SCRATCH_OFFSET) }
413}
414
415pub unsafe fn write_plugin_name_to_scratch(ptr: *mut u8, name: &str) {
421 unsafe {
422 let scratch = scratch_ptr(ptr);
423 let bytes = name.as_bytes();
424 let len = bytes.len().min(SCRATCH_SIZE - 4);
425 std::ptr::write_unaligned(scratch as *mut u32, len as u32);
426 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(4), len);
427 }
428}
429
430pub unsafe fn read_plugin_name_from_scratch(ptr: *mut u8) -> Option<String> {
435 unsafe {
436 let scratch = scratch_ptr(ptr);
437 let len = std::ptr::read_unaligned(scratch as *mut u32) as usize;
438 if len == 0 || len > SCRATCH_SIZE - 4 {
439 return None;
440 }
441 let bytes = std::slice::from_raw_parts(scratch.add(4), len);
442 String::from_utf8(bytes.to_vec()).ok()
443 }
444}
445
446pub const PORT_COUNTS_MAGIC: u32 = 0x504F_5254; const PORT_COUNTS_OFFSET: usize = 1024;
451
452pub unsafe fn write_port_counts_to_scratch(
457 ptr: *mut u8,
458 audio_in: u32,
459 audio_out: u32,
460 midi_in: u32,
461 midi_out: u32,
462) {
463 unsafe {
464 let dest = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
465 std::ptr::write_unaligned(dest as *mut u32, PORT_COUNTS_MAGIC);
466 std::ptr::write_unaligned(dest.add(4) as *mut u32, audio_in);
467 std::ptr::write_unaligned(dest.add(8) as *mut u32, audio_out);
468 std::ptr::write_unaligned(dest.add(12) as *mut u32, midi_in);
469 std::ptr::write_unaligned(dest.add(16) as *mut u32, midi_out);
470 }
471}
472
473pub unsafe fn read_port_counts_from_scratch(ptr: *mut u8) -> Option<(u32, u32, u32, u32)> {
478 unsafe {
479 let src = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
480 let magic = std::ptr::read_unaligned(src as *mut u32);
481 if magic != PORT_COUNTS_MAGIC {
482 return None;
483 }
484 let audio_in = std::ptr::read_unaligned(src.add(4) as *mut u32);
485 let audio_out = std::ptr::read_unaligned(src.add(8) as *mut u32);
486 let midi_in = std::ptr::read_unaligned(src.add(12) as *mut u32);
487 let midi_out = std::ptr::read_unaligned(src.add(16) as *mut u32);
488 Some((audio_in, audio_out, midi_in, midi_out))
489 }
490}
491
492pub const FILE_REFS_MAGIC: u32 = 0x4649_4C45; const FILE_REFS_OFFSET: usize = 2048;
497
498const FILE_REFS_MAX_SIZE: usize = SCRATCH_SIZE - FILE_REFS_OFFSET;
500
501pub type FileReference = (u32, String);
503
504pub unsafe fn write_file_references_to_scratch(
511 ptr: *mut u8,
512 refs: &[FileReference],
513) -> Result<(), String> {
514 unsafe {
515 let mut dest = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
516 let mut remaining = FILE_REFS_MAX_SIZE;
517 if remaining < 8 {
518 return Err("scratch too small for file references".to_string());
519 }
520 std::ptr::write_unaligned(dest as *mut u32, FILE_REFS_MAGIC);
521 dest = dest.add(4);
522 remaining -= 4;
523 let count = refs.len().min(u32::MAX as usize) as u32;
524 std::ptr::write_unaligned(dest as *mut u32, count);
525 dest = dest.add(4);
526 remaining -= 4;
527 for (index, path) in refs.iter().take(count as usize) {
528 if remaining < 8 {
529 return Err("scratch overflow writing file references".to_string());
530 }
531 std::ptr::write_unaligned(dest as *mut u32, *index);
532 dest = dest.add(4);
533 remaining -= 4;
534 let bytes = path.as_bytes();
535 let len = bytes
536 .len()
537 .min(u32::MAX as usize)
538 .min(remaining.saturating_sub(4));
539 if len < bytes.len() {
540 return Err("scratch overflow writing file references".to_string());
541 }
542 std::ptr::write_unaligned(dest as *mut u32, len as u32);
543 dest = dest.add(4);
544 remaining -= 4;
545 std::ptr::copy_nonoverlapping(bytes.as_ptr(), dest, len);
546 dest = dest.add(len);
547 remaining -= len;
548 }
549 Ok(())
550 }
551}
552
553pub unsafe fn read_file_references_from_scratch(ptr: *mut u8) -> Option<Vec<FileReference>> {
558 unsafe {
559 let mut src = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
560 let mut remaining = FILE_REFS_MAX_SIZE;
561 if remaining < 8 {
562 return None;
563 }
564 let magic = std::ptr::read_unaligned(src as *mut u32);
565 if magic != FILE_REFS_MAGIC {
566 return None;
567 }
568 src = src.add(4);
569 remaining -= 4;
570 let count = std::ptr::read_unaligned(src as *mut u32) as usize;
571 src = src.add(4);
572 remaining -= 4;
573 let mut refs = Vec::with_capacity(count);
574 for _ in 0..count {
575 if remaining < 8 {
576 return None;
577 }
578 let index = std::ptr::read_unaligned(src as *mut u32);
579 src = src.add(4);
580 remaining -= 4;
581 let len = std::ptr::read_unaligned(src as *mut u32) as usize;
582 src = src.add(4);
583 remaining -= 4;
584 if len > remaining {
585 return None;
586 }
587 let bytes = std::slice::from_raw_parts(src, len);
588 let path = String::from_utf8(bytes.to_vec()).ok()?;
589 refs.push((index, path));
590 src = src.add(len);
591 remaining -= len;
592 }
593 Some(refs)
594 }
595}
596
597pub unsafe fn write_resource_directory_to_scratch(ptr: *mut u8, path: &str) -> Result<(), String> {
603 unsafe {
604 let scratch = scratch_ptr(ptr);
605 let bytes = path.as_bytes();
606 let len = bytes.len().min(SCRATCH_SIZE - 8);
607 if len < bytes.len() {
608 return Err("resource directory path too long".to_string());
609 }
610 std::ptr::write_unaligned(scratch as *mut u32, FILE_REFS_MAGIC);
611 std::ptr::write_unaligned(scratch.add(4) as *mut u32, len as u32);
612 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(8), len);
613 Ok(())
614 }
615}
616
617pub unsafe fn read_resource_directory_from_scratch(ptr: *mut u8) -> Option<String> {
622 unsafe {
623 let scratch = scratch_ptr(ptr);
624 let magic = std::ptr::read_unaligned(scratch as *mut u32);
625 if magic != FILE_REFS_MAGIC {
626 return None;
627 }
628 let len = std::ptr::read_unaligned(scratch.add(4) as *mut u32) as usize;
629 if len == 0 || len > SCRATCH_SIZE - 8 {
630 return None;
631 }
632 let bytes = std::slice::from_raw_parts(scratch.add(8), len);
633 String::from_utf8(bytes.to_vec()).ok()
634 }
635}
636
637pub const REQUEST_LV2_CONTROL_PORTS: u32 = 8;
639
640pub const REQUEST_CLAP_PARAMETERS: u32 = 9;
642
643pub const FILE_REF_UPDATE_MAGIC: u32 = 0x5550_4441; pub unsafe fn write_file_reference_update_to_scratch(
652 ptr: *mut u8,
653 index: u32,
654 path: &str,
655) -> Result<(), String> {
656 unsafe {
657 let scratch = scratch_ptr(ptr);
658 let bytes = path.as_bytes();
659 let len = bytes.len().min(SCRATCH_SIZE - 12);
660 if len < bytes.len() {
661 return Err("file-reference update path too long".to_string());
662 }
663 std::ptr::write_unaligned(scratch as *mut u32, FILE_REF_UPDATE_MAGIC);
664 std::ptr::write_unaligned(scratch.add(4) as *mut u32, index);
665 std::ptr::write_unaligned(scratch.add(8) as *mut u32, len as u32);
666 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(12), len);
667 Ok(())
668 }
669}
670
671pub unsafe fn read_file_reference_update_from_scratch(ptr: *mut u8) -> Option<(u32, String)> {
676 unsafe {
677 let scratch = scratch_ptr(ptr);
678 let magic = std::ptr::read_unaligned(scratch as *mut u32);
679 if magic != FILE_REF_UPDATE_MAGIC {
680 return None;
681 }
682 let index = std::ptr::read_unaligned(scratch.add(4) as *mut u32);
683 let len = std::ptr::read_unaligned(scratch.add(8) as *mut u32) as usize;
684 if len == 0 || len > SCRATCH_SIZE - 12 {
685 return None;
686 }
687 let bytes = std::slice::from_raw_parts(scratch.add(12), len);
688 let path = String::from_utf8(bytes.to_vec()).ok()?;
689 Some((index, path))
690 }
691}
692
693const _: () = assert!(std::mem::size_of::<ShmHeader>() == 256);
696const _: () = assert!(std::mem::align_of::<ShmHeader>() == 256);
697const _: () = assert!(std::mem::size_of::<ParameterEvent>() == 16);
698const _: () = assert!(std::mem::align_of::<ParameterEvent>() == 16);
699const _: () = assert!(std::mem::size_of::<MidiEvent>() == 16);
700const _: () = assert!(std::mem::align_of::<MidiEvent>() == 16);
701const _: () = assert!(std::mem::size_of::<TransportState>() == 256);
702const _: () = assert!(std::mem::align_of::<TransportState>() == 256);
703const _: () = assert!(LAYOUT_SIZE <= SHM_SIZE);
704
705pub fn wait_for_ready(header: &ShmHeader, timeout: std::time::Duration) -> bool {
707 let start = std::time::Instant::now();
708 while header.ready.load(Ordering::Acquire) == 0 {
709 if start.elapsed() >= timeout {
710 return false;
711 }
712 std::thread::yield_now();
713 }
714 true
715}