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,
184 pub request_status: AtomicU32,
186 pub scratch_size: AtomicU32,
188 pub parent_window: AtomicU64,
190 pub state_dirty: AtomicU32,
192 _pad: [u8; 256 - 84],
193}
194
195impl ShmHeader {
196 pub fn parent_window_usize(&self) -> usize {
198 self.parent_window.load(Ordering::Acquire) as usize
199 }
200
201 pub fn set_parent_window(&self, window: usize) {
204 self.parent_window.store(window as u64, Ordering::Release);
205 }
206
207 fn gui_mode_atomic(&self) -> &AtomicU32 {
208 unsafe {
211 let base = self as *const Self as *const u8;
212 &*(base.add(GUI_MODE_OFFSET) as *const AtomicU32)
213 }
214 }
215
216 pub fn gui_mode(&self) -> GuiMode {
218 GuiMode::from_u32(self.gui_mode_atomic().load(Ordering::Acquire))
219 }
220
221 pub fn set_gui_mode(&self, mode: GuiMode) {
223 self.gui_mode_atomic()
224 .store(mode.as_u32(), Ordering::Release);
225 }
226}
227
228impl Default for ShmHeader {
229 fn default() -> Self {
230 Self {
231 magic: MAGIC,
232 version: VERSION,
233 flags: 0,
234 ready: AtomicU32::new(0),
235 heartbeat: AtomicU32::new(0),
236 error_code: 0,
237 shutdown_request: AtomicU32::new(0),
238 tasks_issued: AtomicU32::new(0),
239 tasks_completed: AtomicU32::new(0),
240 block_size: AtomicU32::new(0),
241 num_input_channels: AtomicU32::new(0),
242 num_output_channels: AtomicU32::new(0),
243 midi_in_port_count: AtomicU32::new(0),
244 midi_out_port_count: AtomicU32::new(0),
245 request_type: AtomicU32::new(0),
246 request_status: AtomicU32::new(0),
247 scratch_size: AtomicU32::new(0),
248 parent_window: AtomicU64::new(0),
249 state_dirty: AtomicU32::new(0),
250 _pad: [0; 256 - 84],
251 }
252 }
253}
254
255pub unsafe fn init_shm_layout(ptr: *mut u8, size: usize) {
262 unsafe {
263 std::ptr::write_bytes(ptr, 0, size);
264 let header = ptr as *mut ShmHeader;
265 std::ptr::write(header, ShmHeader::default());
266 }
267}
268
269pub unsafe fn header_ref(ptr: *mut u8) -> &'static ShmHeader {
274 unsafe { &*(ptr as *mut ShmHeader) }
275}
276
277pub unsafe fn header_mut(ptr: *mut u8) -> &'static mut ShmHeader {
282 unsafe { &mut *(ptr as *mut ShmHeader) }
283}
284
285pub unsafe fn audio_ptr(ptr: *mut u8) -> *mut f32 {
290 unsafe { ptr.add(AUDIO_OFFSET) as *mut f32 }
291}
292
293pub unsafe fn audio_channel_ptr(ptr: *mut u8, channel: usize, bus: usize) -> *mut f32 {
301 let plane_size = MAX_BLOCK_SIZE * std::mem::size_of::<f32>();
302 let offset = AUDIO_OFFSET + (channel * NUM_BUSES + bus) * plane_size;
303 unsafe { ptr.add(offset) as *mut f32 }
304}
305
306pub unsafe fn param_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
311 unsafe { ptr.add(PARAM_RING_OFFSET) as *mut ParameterEvent }
312}
313
314pub unsafe fn param_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
319 unsafe {
320 (
321 ptr.add(PARAM_WRITE_IDX_OFFSET) as *mut AtomicU32,
322 ptr.add(PARAM_READ_IDX_OFFSET) as *mut AtomicU32,
323 )
324 }
325}
326
327pub unsafe fn echo_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
332 unsafe { ptr.add(ECHO_RING_OFFSET) as *mut ParameterEvent }
333}
334
335pub unsafe fn echo_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
340 unsafe {
341 (
342 ptr.add(ECHO_WRITE_IDX_OFFSET) as *mut AtomicU32,
343 ptr.add(ECHO_READ_IDX_OFFSET) as *mut AtomicU32,
344 )
345 }
346}
347
348const fn midi_port_ring_offset(base_offset: usize, port: usize) -> usize {
349 base_offset + port * MIDI_PORT_RING_SIZE
350}
351
352pub unsafe fn midi_in_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
357 unsafe {
358 let base = ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port));
359 (base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
360 }
361}
362
363pub unsafe fn midi_in_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
368 unsafe { ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port) + 8) as *mut MidiEvent }
369}
370
371pub unsafe fn midi_out_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
376 unsafe {
377 let base = ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port));
378 (base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
379 }
380}
381
382pub unsafe fn midi_out_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
387 unsafe { ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port) + 8) as *mut MidiEvent }
388}
389
390pub unsafe fn transport_ref(ptr: *mut u8) -> &'static TransportState {
395 unsafe { &*(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
396}
397
398pub unsafe fn transport_mut(ptr: *mut u8) -> &'static mut TransportState {
403 unsafe { &mut *(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
404}
405
406pub unsafe fn scratch_ptr(ptr: *mut u8) -> *mut u8 {
411 unsafe { ptr.add(SCRATCH_OFFSET) }
412}
413
414pub unsafe fn write_plugin_name_to_scratch(ptr: *mut u8, name: &str) {
420 unsafe {
421 let scratch = scratch_ptr(ptr);
422 let bytes = name.as_bytes();
423 let len = bytes.len().min(SCRATCH_SIZE - 4);
424 std::ptr::write_unaligned(scratch as *mut u32, len as u32);
425 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(4), len);
426 }
427}
428
429pub unsafe fn read_plugin_name_from_scratch(ptr: *mut u8) -> Option<String> {
434 unsafe {
435 let scratch = scratch_ptr(ptr);
436 let len = std::ptr::read_unaligned(scratch as *mut u32) as usize;
437 if len == 0 || len > SCRATCH_SIZE - 4 {
438 return None;
439 }
440 let bytes = std::slice::from_raw_parts(scratch.add(4), len);
441 String::from_utf8(bytes.to_vec()).ok()
442 }
443}
444
445pub const PORT_COUNTS_MAGIC: u32 = 0x504F_5254; const PORT_COUNTS_OFFSET: usize = 1024;
450
451pub unsafe fn write_port_counts_to_scratch(
456 ptr: *mut u8,
457 audio_in: u32,
458 audio_out: u32,
459 midi_in: u32,
460 midi_out: u32,
461) {
462 unsafe {
463 let dest = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
464 std::ptr::write_unaligned(dest as *mut u32, PORT_COUNTS_MAGIC);
465 std::ptr::write_unaligned(dest.add(4) as *mut u32, audio_in);
466 std::ptr::write_unaligned(dest.add(8) as *mut u32, audio_out);
467 std::ptr::write_unaligned(dest.add(12) as *mut u32, midi_in);
468 std::ptr::write_unaligned(dest.add(16) as *mut u32, midi_out);
469 }
470}
471
472pub unsafe fn read_port_counts_from_scratch(ptr: *mut u8) -> Option<(u32, u32, u32, u32)> {
477 unsafe {
478 let src = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
479 let magic = std::ptr::read_unaligned(src as *mut u32);
480 if magic != PORT_COUNTS_MAGIC {
481 return None;
482 }
483 let audio_in = std::ptr::read_unaligned(src.add(4) as *mut u32);
484 let audio_out = std::ptr::read_unaligned(src.add(8) as *mut u32);
485 let midi_in = std::ptr::read_unaligned(src.add(12) as *mut u32);
486 let midi_out = std::ptr::read_unaligned(src.add(16) as *mut u32);
487 Some((audio_in, audio_out, midi_in, midi_out))
488 }
489}
490
491pub const FILE_REFS_MAGIC: u32 = 0x4649_4C45; const FILE_REFS_OFFSET: usize = 2048;
496
497const FILE_REFS_MAX_SIZE: usize = SCRATCH_SIZE - FILE_REFS_OFFSET;
499
500pub type FileReference = (u32, String);
502
503pub unsafe fn write_file_references_to_scratch(
510 ptr: *mut u8,
511 refs: &[FileReference],
512) -> Result<(), String> {
513 unsafe {
514 let mut dest = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
515 let mut remaining = FILE_REFS_MAX_SIZE;
516 if remaining < 8 {
517 return Err("scratch too small for file references".to_string());
518 }
519 std::ptr::write_unaligned(dest as *mut u32, FILE_REFS_MAGIC);
520 dest = dest.add(4);
521 remaining -= 4;
522 let count = refs.len().min(u32::MAX as usize) as u32;
523 std::ptr::write_unaligned(dest as *mut u32, count);
524 dest = dest.add(4);
525 remaining -= 4;
526 for (index, path) in refs.iter().take(count as usize) {
527 if remaining < 8 {
528 return Err("scratch overflow writing file references".to_string());
529 }
530 std::ptr::write_unaligned(dest as *mut u32, *index);
531 dest = dest.add(4);
532 remaining -= 4;
533 let bytes = path.as_bytes();
534 let len = bytes
535 .len()
536 .min(u32::MAX as usize)
537 .min(remaining.saturating_sub(4));
538 if len < bytes.len() {
539 return Err("scratch overflow writing file references".to_string());
540 }
541 std::ptr::write_unaligned(dest as *mut u32, len as u32);
542 dest = dest.add(4);
543 remaining -= 4;
544 std::ptr::copy_nonoverlapping(bytes.as_ptr(), dest, len);
545 dest = dest.add(len);
546 remaining -= len;
547 }
548 Ok(())
549 }
550}
551
552pub unsafe fn read_file_references_from_scratch(ptr: *mut u8) -> Option<Vec<FileReference>> {
557 unsafe {
558 let mut src = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
559 let mut remaining = FILE_REFS_MAX_SIZE;
560 if remaining < 8 {
561 return None;
562 }
563 let magic = std::ptr::read_unaligned(src as *mut u32);
564 if magic != FILE_REFS_MAGIC {
565 return None;
566 }
567 src = src.add(4);
568 remaining -= 4;
569 let count = std::ptr::read_unaligned(src as *mut u32) as usize;
570 src = src.add(4);
571 remaining -= 4;
572 let mut refs = Vec::with_capacity(count);
573 for _ in 0..count {
574 if remaining < 8 {
575 return None;
576 }
577 let index = std::ptr::read_unaligned(src as *mut u32);
578 src = src.add(4);
579 remaining -= 4;
580 let len = std::ptr::read_unaligned(src as *mut u32) as usize;
581 src = src.add(4);
582 remaining -= 4;
583 if len > remaining {
584 return None;
585 }
586 let bytes = std::slice::from_raw_parts(src, len);
587 let path = String::from_utf8(bytes.to_vec()).ok()?;
588 refs.push((index, path));
589 src = src.add(len);
590 remaining -= len;
591 }
592 Some(refs)
593 }
594}
595
596pub unsafe fn write_resource_directory_to_scratch(ptr: *mut u8, path: &str) -> Result<(), String> {
602 unsafe {
603 let scratch = scratch_ptr(ptr);
604 let bytes = path.as_bytes();
605 let len = bytes.len().min(SCRATCH_SIZE - 8);
606 if len < bytes.len() {
607 return Err("resource directory path too long".to_string());
608 }
609 std::ptr::write_unaligned(scratch as *mut u32, FILE_REFS_MAGIC);
610 std::ptr::write_unaligned(scratch.add(4) as *mut u32, len as u32);
611 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(8), len);
612 Ok(())
613 }
614}
615
616pub unsafe fn read_resource_directory_from_scratch(ptr: *mut u8) -> Option<String> {
621 unsafe {
622 let scratch = scratch_ptr(ptr);
623 let magic = std::ptr::read_unaligned(scratch as *mut u32);
624 if magic != FILE_REFS_MAGIC {
625 return None;
626 }
627 let len = std::ptr::read_unaligned(scratch.add(4) as *mut u32) as usize;
628 if len == 0 || len > SCRATCH_SIZE - 8 {
629 return None;
630 }
631 let bytes = std::slice::from_raw_parts(scratch.add(8), len);
632 String::from_utf8(bytes.to_vec()).ok()
633 }
634}
635
636pub const FILE_REF_UPDATE_MAGIC: u32 = 0x5550_4441; pub unsafe fn write_file_reference_update_to_scratch(
645 ptr: *mut u8,
646 index: u32,
647 path: &str,
648) -> Result<(), String> {
649 unsafe {
650 let scratch = scratch_ptr(ptr);
651 let bytes = path.as_bytes();
652 let len = bytes.len().min(SCRATCH_SIZE - 12);
653 if len < bytes.len() {
654 return Err("file-reference update path too long".to_string());
655 }
656 std::ptr::write_unaligned(scratch as *mut u32, FILE_REF_UPDATE_MAGIC);
657 std::ptr::write_unaligned(scratch.add(4) as *mut u32, index);
658 std::ptr::write_unaligned(scratch.add(8) as *mut u32, len as u32);
659 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(12), len);
660 Ok(())
661 }
662}
663
664pub unsafe fn read_file_reference_update_from_scratch(ptr: *mut u8) -> Option<(u32, String)> {
669 unsafe {
670 let scratch = scratch_ptr(ptr);
671 let magic = std::ptr::read_unaligned(scratch as *mut u32);
672 if magic != FILE_REF_UPDATE_MAGIC {
673 return None;
674 }
675 let index = std::ptr::read_unaligned(scratch.add(4) as *mut u32);
676 let len = std::ptr::read_unaligned(scratch.add(8) as *mut u32) as usize;
677 if len == 0 || len > SCRATCH_SIZE - 12 {
678 return None;
679 }
680 let bytes = std::slice::from_raw_parts(scratch.add(12), len);
681 let path = String::from_utf8(bytes.to_vec()).ok()?;
682 Some((index, path))
683 }
684}
685
686const _: () = assert!(std::mem::size_of::<ShmHeader>() == 256);
689const _: () = assert!(std::mem::align_of::<ShmHeader>() == 256);
690const _: () = assert!(std::mem::size_of::<ParameterEvent>() == 16);
691const _: () = assert!(std::mem::align_of::<ParameterEvent>() == 16);
692const _: () = assert!(std::mem::size_of::<MidiEvent>() == 16);
693const _: () = assert!(std::mem::align_of::<MidiEvent>() == 16);
694const _: () = assert!(std::mem::size_of::<TransportState>() == 256);
695const _: () = assert!(std::mem::align_of::<TransportState>() == 256);
696const _: () = assert!(LAYOUT_SIZE <= SHM_SIZE);
697
698pub fn wait_for_ready(header: &ShmHeader, timeout: std::time::Duration) -> bool {
700 let start = std::time::Instant::now();
701 while header.ready.load(Ordering::Acquire) == 0 {
702 if start.elapsed() >= timeout {
703 return false;
704 }
705 std::thread::yield_now();
706 }
707 true
708}