1use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
2
3pub const MAGIC: u32 = 0x4D41_4F4C;
5
6pub const VERSION: u32 = 6;
13
14pub const MAX_CHANNELS: usize = 32;
16
17pub const NUM_BUSES: usize = 2;
19
20pub const MAX_BLOCK_SIZE: usize = 4096;
22
23pub const RING_CAPACITY: usize = 4096;
25
26pub const MAX_MIDI_PORTS: usize = 16;
29
30pub const HEADER_SIZE: usize = 256;
32pub const CONTROL_SIZE: usize = 256;
33pub 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>();
35pub const MIDI_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<MidiEvent>();
37pub const MIDI_PORT_RING_SIZE: usize = {
41 let raw = 16 + MIDI_RING_SIZE; (raw + 15) & !15 };
44pub const TRANSPORT_SIZE: usize = 256;
45pub const SCRATCH_SIZE: usize = 65536;
46
47pub const CONTROL_OFFSET: usize = HEADER_SIZE;
50pub const AUDIO_OFFSET: usize = HEADER_SIZE + CONTROL_SIZE;
52pub const PARAM_RING_OFFSET: usize = AUDIO_OFFSET + AUDIO_BUFFER_SIZE;
54pub const ECHO_RING_OFFSET: usize = PARAM_RING_OFFSET + PARAM_RING_SIZE;
56pub const ECHO_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<ParameterEvent>();
57pub const MIDI_IN_RINGS_OFFSET: usize = {
59 let end = ECHO_RING_OFFSET + ECHO_RING_SIZE;
60 (end + 255) & !255
61};
62pub const MIDI_IN_RINGS_SIZE: usize = MAX_MIDI_PORTS * MIDI_PORT_RING_SIZE;
63pub const MIDI_OUT_RINGS_OFFSET: usize = MIDI_IN_RINGS_OFFSET + MIDI_IN_RINGS_SIZE;
65pub const MIDI_OUT_RINGS_SIZE: usize = MAX_MIDI_PORTS * MIDI_PORT_RING_SIZE;
66pub const TRANSPORT_OFFSET: usize = {
68 let end = MIDI_OUT_RINGS_OFFSET + MIDI_OUT_RINGS_SIZE;
69 (end + 255) & !255
71};
72pub const SCRATCH_OFFSET: usize = TRANSPORT_OFFSET + TRANSPORT_SIZE;
74
75pub const LAYOUT_SIZE: usize = SCRATCH_OFFSET + SCRATCH_SIZE;
77
78pub const SHM_SIZE: usize = 4 * 1024 * 1024;
80
81pub const PARAM_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET;
83pub const PARAM_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 4;
84pub const ECHO_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET + 8;
85pub const ECHO_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 12;
86pub const GUI_MODE_OFFSET: usize = CONTROL_OFFSET + 16;
87pub const GUI_PARENT_API_OFFSET: usize = CONTROL_OFFSET + 20;
88
89#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
91pub enum GuiMode {
92 #[default]
94 Embedded = 0,
95 Floating = 1,
97}
98
99impl GuiMode {
100 pub fn from_u32(value: u32) -> Self {
101 match value {
102 1 => GuiMode::Floating,
103 _ => GuiMode::Embedded,
104 }
105 }
106
107 pub fn as_u32(self) -> u32 {
108 self as u32
109 }
110}
111
112#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
114pub enum GuiParentApi {
115 #[default]
117 None = 0,
118 X11 = 1,
120 Wayland = 2,
122}
123
124impl GuiParentApi {
125 pub fn from_u32(value: u32) -> Self {
126 match value {
127 1 => GuiParentApi::X11,
128 2 => GuiParentApi::Wayland,
129 _ => GuiParentApi::None,
130 }
131 }
132
133 pub fn as_u32(self) -> u32 {
134 self as u32
135 }
136}
137
138pub const PARAM_EVENT_VALUE: u32 = 0;
141pub const PARAM_EVENT_MOD: u32 = 1;
142pub const PARAM_EVENT_GESTURE_BEGIN: u32 = 2;
143pub const PARAM_EVENT_GESTURE_END: u32 = 3;
144
145#[repr(C, align(16))]
147#[derive(Clone, Copy, Debug, Default)]
148pub struct ParameterEvent {
149 pub param_index: u32,
150 pub value: f32,
151 pub sample_offset: u32,
152 pub event_kind: u32,
153}
154
155#[repr(C, align(16))]
157#[derive(Clone, Copy, Debug, Default)]
158pub struct MidiEvent {
159 pub sample_offset: u32,
160 pub data: [u8; 3],
161 pub channel: u8,
162 pub flags: u16,
163 pub _pad: u16,
164}
165
166#[repr(C, align(256))]
168#[derive(Clone, Copy, Debug)]
169pub struct TransportState {
170 pub playhead_sample: u64,
171 pub tempo: f64,
172 pub numerator: u32,
173 pub denominator: u32,
174 pub flags: u32,
175 pub sample_rate_hz: f64,
176 _pad: [u8; 256 - 40],
177}
178
179impl Default for TransportState {
180 fn default() -> Self {
181 Self {
182 playhead_sample: 0,
183 tempo: 120.0,
184 numerator: 4,
185 denominator: 4,
186 flags: 0,
187 sample_rate_hz: 0.0,
188 _pad: [0; 256 - 40],
189 }
190 }
191}
192
193#[repr(C, align(256))]
195pub struct ShmHeader {
196 pub magic: u32,
197 pub version: u32,
198 pub flags: u32,
199 pub ready: AtomicU32,
200 pub heartbeat: AtomicU32,
201 pub error_code: u32,
202 pub shutdown_request: AtomicU32,
203 pub tasks_issued: AtomicU32,
204 pub tasks_completed: AtomicU32,
205 pub block_size: AtomicU32,
206 pub num_input_channels: AtomicU32,
207 pub num_output_channels: AtomicU32,
208 pub midi_in_port_count: AtomicU32,
210 pub midi_out_port_count: AtomicU32,
212 pub request_type: AtomicU32,
217 pub request_status: AtomicU32,
219 pub scratch_size: AtomicU32,
221 pub parent_window: AtomicU64,
223 pub state_dirty: AtomicU32,
225 pub latency_samples: AtomicU32,
227 _pad: [u8; 256 - 88],
228}
229
230impl ShmHeader {
231 pub fn parent_window_usize(&self) -> usize {
233 self.parent_window.load(Ordering::Acquire) as usize
234 }
235
236 pub fn set_parent_window(&self, window: usize) {
239 self.parent_window.store(window as u64, Ordering::Release);
240 }
241
242 fn gui_parent_api_atomic(&self) -> &AtomicU32 {
243 unsafe {
246 let base = self as *const Self as *const u8;
247 &*(base.add(GUI_PARENT_API_OFFSET) as *const AtomicU32)
248 }
249 }
250
251 pub fn gui_parent_api(&self) -> GuiParentApi {
253 GuiParentApi::from_u32(self.gui_parent_api_atomic().load(Ordering::Acquire))
254 }
255
256 pub fn set_gui_parent_api(&self, api: GuiParentApi) {
258 self.gui_parent_api_atomic()
259 .store(api.as_u32(), Ordering::Release);
260 }
261
262 fn gui_mode_atomic(&self) -> &AtomicU32 {
263 unsafe {
266 let base = self as *const Self as *const u8;
267 &*(base.add(GUI_MODE_OFFSET) as *const AtomicU32)
268 }
269 }
270
271 pub fn gui_mode(&self) -> GuiMode {
273 GuiMode::from_u32(self.gui_mode_atomic().load(Ordering::Acquire))
274 }
275
276 pub fn set_gui_mode(&self, mode: GuiMode) {
278 self.gui_mode_atomic()
279 .store(mode.as_u32(), Ordering::Release);
280 }
281}
282
283impl Default for ShmHeader {
284 fn default() -> Self {
285 Self {
286 magic: MAGIC,
287 version: VERSION,
288 flags: 0,
289 ready: AtomicU32::new(0),
290 heartbeat: AtomicU32::new(0),
291 error_code: 0,
292 shutdown_request: AtomicU32::new(0),
293 tasks_issued: AtomicU32::new(0),
294 tasks_completed: AtomicU32::new(0),
295 block_size: AtomicU32::new(0),
296 num_input_channels: AtomicU32::new(0),
297 num_output_channels: AtomicU32::new(0),
298 midi_in_port_count: AtomicU32::new(0),
299 midi_out_port_count: AtomicU32::new(0),
300 request_type: AtomicU32::new(0),
301 request_status: AtomicU32::new(0),
302 scratch_size: AtomicU32::new(0),
303 parent_window: AtomicU64::new(0),
304 state_dirty: AtomicU32::new(0),
305 latency_samples: AtomicU32::new(0),
306 _pad: [0; 256 - 88],
307 }
308 }
309}
310
311pub unsafe fn init_shm_layout(ptr: *mut u8, size: usize) {
318 unsafe {
319 std::ptr::write_bytes(ptr, 0, size);
320 let header = ptr as *mut ShmHeader;
321 std::ptr::write(header, ShmHeader::default());
322 }
323}
324
325pub unsafe fn header_ref(ptr: *mut u8) -> &'static ShmHeader {
330 unsafe { &*(ptr as *mut ShmHeader) }
331}
332
333pub unsafe fn header_mut(ptr: *mut u8) -> &'static mut ShmHeader {
338 unsafe { &mut *(ptr as *mut ShmHeader) }
339}
340
341pub unsafe fn audio_ptr(ptr: *mut u8) -> *mut f32 {
346 unsafe { ptr.add(AUDIO_OFFSET) as *mut f32 }
347}
348
349pub unsafe fn audio_channel_ptr(ptr: *mut u8, channel: usize, bus: usize) -> *mut f32 {
357 let plane_size = MAX_BLOCK_SIZE * std::mem::size_of::<f32>();
358 let offset = AUDIO_OFFSET + (channel * NUM_BUSES + bus) * plane_size;
359 unsafe { ptr.add(offset) as *mut f32 }
360}
361
362pub unsafe fn param_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
367 unsafe { ptr.add(PARAM_RING_OFFSET) as *mut ParameterEvent }
368}
369
370pub unsafe fn param_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
375 unsafe {
376 (
377 ptr.add(PARAM_WRITE_IDX_OFFSET) as *mut AtomicU32,
378 ptr.add(PARAM_READ_IDX_OFFSET) as *mut AtomicU32,
379 )
380 }
381}
382
383pub unsafe fn echo_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
388 unsafe { ptr.add(ECHO_RING_OFFSET) as *mut ParameterEvent }
389}
390
391pub unsafe fn echo_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
396 unsafe {
397 (
398 ptr.add(ECHO_WRITE_IDX_OFFSET) as *mut AtomicU32,
399 ptr.add(ECHO_READ_IDX_OFFSET) as *mut AtomicU32,
400 )
401 }
402}
403
404const fn midi_port_ring_offset(base_offset: usize, port: usize) -> usize {
405 base_offset + port * MIDI_PORT_RING_SIZE
406}
407
408pub unsafe fn midi_in_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
413 unsafe {
414 let base = ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port));
415 (base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
416 }
417}
418
419pub unsafe fn midi_in_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
424 unsafe { ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port) + 16) as *mut MidiEvent }
425}
426
427pub unsafe fn midi_out_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
432 unsafe {
433 let base = ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port));
434 (base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
435 }
436}
437
438pub unsafe fn midi_out_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
443 unsafe { ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port) + 16) as *mut MidiEvent }
444}
445
446pub unsafe fn transport_ref(ptr: *mut u8) -> &'static TransportState {
451 unsafe { &*(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
452}
453
454pub unsafe fn transport_mut(ptr: *mut u8) -> &'static mut TransportState {
459 unsafe { &mut *(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
460}
461
462pub unsafe fn scratch_ptr(ptr: *mut u8) -> *mut u8 {
467 unsafe { ptr.add(SCRATCH_OFFSET) }
468}
469
470pub unsafe fn write_plugin_name_to_scratch(ptr: *mut u8, name: &str) {
476 unsafe {
477 let scratch = scratch_ptr(ptr);
478 let bytes = name.as_bytes();
479 let len = bytes.len().min(SCRATCH_SIZE - 4);
480 std::ptr::write_unaligned(scratch as *mut u32, len as u32);
481 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(4), len);
482 }
483}
484
485pub unsafe fn read_plugin_name_from_scratch(ptr: *mut u8) -> Option<String> {
490 unsafe {
491 let scratch = scratch_ptr(ptr);
492 let len = std::ptr::read_unaligned(scratch as *mut u32) as usize;
493 if len == 0 || len > SCRATCH_SIZE - 4 {
494 return None;
495 }
496 let bytes = std::slice::from_raw_parts(scratch.add(4), len);
497 String::from_utf8(bytes.to_vec()).ok()
498 }
499}
500
501pub const PORT_COUNTS_MAGIC: u32 = 0x504F_5254; const PORT_COUNTS_OFFSET: usize = 1024;
506
507pub unsafe fn write_port_counts_to_scratch(
512 ptr: *mut u8,
513 audio_in: u32,
514 audio_out: u32,
515 midi_in: u32,
516 midi_out: u32,
517) {
518 unsafe {
519 let dest = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
520 std::ptr::write_unaligned(dest as *mut u32, PORT_COUNTS_MAGIC);
521 std::ptr::write_unaligned(dest.add(4) as *mut u32, audio_in);
522 std::ptr::write_unaligned(dest.add(8) as *mut u32, audio_out);
523 std::ptr::write_unaligned(dest.add(12) as *mut u32, midi_in);
524 std::ptr::write_unaligned(dest.add(16) as *mut u32, midi_out);
525 }
526}
527
528pub unsafe fn read_port_counts_from_scratch(ptr: *mut u8) -> Option<(u32, u32, u32, u32)> {
533 unsafe {
534 let src = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
535 let magic = std::ptr::read_unaligned(src as *mut u32);
536 if magic != PORT_COUNTS_MAGIC {
537 return None;
538 }
539 let audio_in = std::ptr::read_unaligned(src.add(4) as *mut u32);
540 let audio_out = std::ptr::read_unaligned(src.add(8) as *mut u32);
541 let midi_in = std::ptr::read_unaligned(src.add(12) as *mut u32);
542 let midi_out = std::ptr::read_unaligned(src.add(16) as *mut u32);
543 Some((audio_in, audio_out, midi_in, midi_out))
544 }
545}
546
547pub const FILE_REFS_MAGIC: u32 = 0x4649_4C45; const FILE_REFS_OFFSET: usize = 2048;
552
553const FILE_REFS_MAX_SIZE: usize = SCRATCH_SIZE - FILE_REFS_OFFSET;
555
556pub type FileReference = (u32, String);
558
559pub unsafe fn write_file_references_to_scratch(
566 ptr: *mut u8,
567 refs: &[FileReference],
568) -> Result<(), String> {
569 unsafe {
570 let mut dest = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
571 let mut remaining = FILE_REFS_MAX_SIZE;
572 if remaining < 8 {
573 return Err("scratch too small for file references".to_string());
574 }
575 std::ptr::write_unaligned(dest as *mut u32, FILE_REFS_MAGIC);
576 dest = dest.add(4);
577 remaining -= 4;
578 let count = refs.len().min(u32::MAX as usize) as u32;
579 std::ptr::write_unaligned(dest as *mut u32, count);
580 dest = dest.add(4);
581 remaining -= 4;
582 for (index, path) in refs.iter().take(count as usize) {
583 if remaining < 8 {
584 return Err("scratch overflow writing file references".to_string());
585 }
586 std::ptr::write_unaligned(dest as *mut u32, *index);
587 dest = dest.add(4);
588 remaining -= 4;
589 let bytes = path.as_bytes();
590 let len = bytes
591 .len()
592 .min(u32::MAX as usize)
593 .min(remaining.saturating_sub(4));
594 if len < bytes.len() {
595 return Err("scratch overflow writing file references".to_string());
596 }
597 std::ptr::write_unaligned(dest as *mut u32, len as u32);
598 dest = dest.add(4);
599 remaining -= 4;
600 std::ptr::copy_nonoverlapping(bytes.as_ptr(), dest, len);
601 dest = dest.add(len);
602 remaining -= len;
603 }
604 Ok(())
605 }
606}
607
608pub unsafe fn read_file_references_from_scratch(ptr: *mut u8) -> Option<Vec<FileReference>> {
613 unsafe {
614 let mut src = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
615 let mut remaining = FILE_REFS_MAX_SIZE;
616 if remaining < 8 {
617 return None;
618 }
619 let magic = std::ptr::read_unaligned(src as *mut u32);
620 if magic != FILE_REFS_MAGIC {
621 return None;
622 }
623 src = src.add(4);
624 remaining -= 4;
625 let count = std::ptr::read_unaligned(src as *mut u32) as usize;
626 src = src.add(4);
627 remaining -= 4;
628 let mut refs = Vec::with_capacity(count);
629 for _ in 0..count {
630 if remaining < 8 {
631 return None;
632 }
633 let index = std::ptr::read_unaligned(src as *mut u32);
634 src = src.add(4);
635 remaining -= 4;
636 let len = std::ptr::read_unaligned(src as *mut u32) as usize;
637 src = src.add(4);
638 remaining -= 4;
639 if len > remaining {
640 return None;
641 }
642 let bytes = std::slice::from_raw_parts(src, len);
643 let path = String::from_utf8(bytes.to_vec()).ok()?;
644 refs.push((index, path));
645 src = src.add(len);
646 remaining -= len;
647 }
648 Some(refs)
649 }
650}
651
652pub unsafe fn write_resource_directory_to_scratch(ptr: *mut u8, path: &str) -> Result<(), String> {
658 unsafe {
659 let scratch = scratch_ptr(ptr);
660 let bytes = path.as_bytes();
661 let len = bytes.len().min(SCRATCH_SIZE - 8);
662 if len < bytes.len() {
663 return Err("resource directory path too long".to_string());
664 }
665 std::ptr::write_unaligned(scratch as *mut u32, FILE_REFS_MAGIC);
666 std::ptr::write_unaligned(scratch.add(4) as *mut u32, len as u32);
667 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(8), len);
668 Ok(())
669 }
670}
671
672pub unsafe fn read_resource_directory_from_scratch(ptr: *mut u8) -> Option<String> {
677 unsafe {
678 let scratch = scratch_ptr(ptr);
679 let magic = std::ptr::read_unaligned(scratch as *mut u32);
680 if magic != FILE_REFS_MAGIC {
681 return None;
682 }
683 let len = std::ptr::read_unaligned(scratch.add(4) as *mut u32) as usize;
684 if len == 0 || len > SCRATCH_SIZE - 8 {
685 return None;
686 }
687 let bytes = std::slice::from_raw_parts(scratch.add(8), len);
688 String::from_utf8(bytes.to_vec()).ok()
689 }
690}
691
692pub const REQUEST_LV2_CONTROL_PORTS: u32 = 8;
694
695pub const REQUEST_CLAP_PARAMETERS: u32 = 9;
697
698pub const REQUEST_LV2_MIDNAM: u32 = 10;
700
701pub const REQUEST_CLAP_NOTE_NAMES: u32 = 11;
703
704pub const REQUEST_CLAP_AUDIO_PORTS: u32 = 12;
706
707pub const FILE_REF_UPDATE_MAGIC: u32 = 0x5550_4441; pub unsafe fn write_file_reference_update_to_scratch(
716 ptr: *mut u8,
717 index: u32,
718 path: &str,
719) -> Result<(), String> {
720 unsafe {
721 let scratch = scratch_ptr(ptr);
722 let bytes = path.as_bytes();
723 let len = bytes.len().min(SCRATCH_SIZE - 12);
724 if len < bytes.len() {
725 return Err("file-reference update path too long".to_string());
726 }
727 std::ptr::write_unaligned(scratch as *mut u32, FILE_REF_UPDATE_MAGIC);
728 std::ptr::write_unaligned(scratch.add(4) as *mut u32, index);
729 std::ptr::write_unaligned(scratch.add(8) as *mut u32, len as u32);
730 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(12), len);
731 Ok(())
732 }
733}
734
735pub unsafe fn read_file_reference_update_from_scratch(ptr: *mut u8) -> Option<(u32, String)> {
740 unsafe {
741 let scratch = scratch_ptr(ptr);
742 let magic = std::ptr::read_unaligned(scratch as *mut u32);
743 if magic != FILE_REF_UPDATE_MAGIC {
744 return None;
745 }
746 let index = std::ptr::read_unaligned(scratch.add(4) as *mut u32);
747 let len = std::ptr::read_unaligned(scratch.add(8) as *mut u32) as usize;
748 if len == 0 || len > SCRATCH_SIZE - 12 {
749 return None;
750 }
751 let bytes = std::slice::from_raw_parts(scratch.add(12), len);
752 let path = String::from_utf8(bytes.to_vec()).ok()?;
753 Some((index, path))
754 }
755}
756
757const _: () = assert!(std::mem::size_of::<ShmHeader>() == 256);
760const _: () = assert!(std::mem::align_of::<ShmHeader>() == 256);
761const _: () = assert!(std::mem::size_of::<ParameterEvent>() == 16);
762const _: () = assert!(std::mem::align_of::<ParameterEvent>() == 16);
763const _: () = assert!(std::mem::size_of::<MidiEvent>() == 16);
764const _: () = assert!(std::mem::align_of::<MidiEvent>() == 16);
765const _: () = assert!(std::mem::size_of::<TransportState>() == 256);
766const _: () = assert!(std::mem::align_of::<TransportState>() == 256);
767const _: () = assert!(LAYOUT_SIZE <= SHM_SIZE);
768
769pub fn wait_for_ready(header: &ShmHeader, timeout: std::time::Duration) -> bool {
771 let start = std::time::Instant::now();
772 while header.ready.load(Ordering::Acquire) == 0 {
773 if start.elapsed() >= timeout {
774 return false;
775 }
776 std::thread::yield_now();
777 }
778 true
779}