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 = {
39 let raw = 8 + MIDI_RING_SIZE; (raw + 15) & !15 };
42pub const TRANSPORT_SIZE: usize = 256;
43pub const SCRATCH_SIZE: usize = 65536;
44
45pub const CONTROL_OFFSET: usize = HEADER_SIZE;
48pub const AUDIO_OFFSET: usize = HEADER_SIZE + CONTROL_SIZE;
50pub const PARAM_RING_OFFSET: usize = AUDIO_OFFSET + AUDIO_BUFFER_SIZE;
52pub const ECHO_RING_OFFSET: usize = PARAM_RING_OFFSET + PARAM_RING_SIZE;
54pub const ECHO_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<ParameterEvent>();
55pub const MIDI_IN_RINGS_OFFSET: usize = {
57 let end = ECHO_RING_OFFSET + ECHO_RING_SIZE;
58 (end + 255) & !255
59};
60pub const MIDI_IN_RINGS_SIZE: usize = MAX_MIDI_PORTS * MIDI_PORT_RING_SIZE;
61pub const MIDI_OUT_RINGS_OFFSET: usize = MIDI_IN_RINGS_OFFSET + MIDI_IN_RINGS_SIZE;
63pub const MIDI_OUT_RINGS_SIZE: usize = MAX_MIDI_PORTS * MIDI_PORT_RING_SIZE;
64pub const TRANSPORT_OFFSET: usize = {
66 let end = MIDI_OUT_RINGS_OFFSET + MIDI_OUT_RINGS_SIZE;
67 (end + 255) & !255
69};
70pub const SCRATCH_OFFSET: usize = TRANSPORT_OFFSET + TRANSPORT_SIZE;
72
73pub const LAYOUT_SIZE: usize = SCRATCH_OFFSET + SCRATCH_SIZE;
75
76pub const SHM_SIZE: usize = 4 * 1024 * 1024;
78
79pub const PARAM_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET;
81pub const PARAM_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 4;
82pub const ECHO_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET + 8;
83pub const ECHO_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 12;
84pub const GUI_MODE_OFFSET: usize = CONTROL_OFFSET + 16;
85pub const GUI_PARENT_API_OFFSET: usize = CONTROL_OFFSET + 20;
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
89pub enum GuiMode {
90 #[default]
92 Embedded = 0,
93 Floating = 1,
95}
96
97impl GuiMode {
98 pub fn from_u32(value: u32) -> Self {
99 match value {
100 1 => GuiMode::Floating,
101 _ => GuiMode::Embedded,
102 }
103 }
104
105 pub fn as_u32(self) -> u32 {
106 self as u32
107 }
108}
109
110#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
112pub enum GuiParentApi {
113 #[default]
115 None = 0,
116 X11 = 1,
118 Wayland = 2,
120}
121
122impl GuiParentApi {
123 pub fn from_u32(value: u32) -> Self {
124 match value {
125 1 => GuiParentApi::X11,
126 2 => GuiParentApi::Wayland,
127 _ => GuiParentApi::None,
128 }
129 }
130
131 pub fn as_u32(self) -> u32 {
132 self as u32
133 }
134}
135
136pub const PARAM_EVENT_VALUE: u32 = 0;
139pub const PARAM_EVENT_MOD: u32 = 1;
140pub const PARAM_EVENT_GESTURE_BEGIN: u32 = 2;
141pub const PARAM_EVENT_GESTURE_END: u32 = 3;
142
143#[repr(C, align(16))]
145#[derive(Clone, Copy, Debug, Default)]
146pub struct ParameterEvent {
147 pub param_index: u32,
148 pub value: f32,
149 pub sample_offset: u32,
150 pub event_kind: u32,
151}
152
153#[repr(C, align(16))]
155#[derive(Clone, Copy, Debug, Default)]
156pub struct MidiEvent {
157 pub sample_offset: u32,
158 pub data: [u8; 3],
159 pub channel: u8,
160 pub flags: u16,
161 pub _pad: u16,
162}
163
164#[repr(C, align(256))]
166#[derive(Clone, Copy, Debug)]
167pub struct TransportState {
168 pub playhead_sample: u64,
169 pub tempo: f64,
170 pub numerator: u32,
171 pub denominator: u32,
172 pub flags: u32,
173 pub sample_rate_hz: f64,
174 _pad: [u8; 256 - 40],
175}
176
177impl Default for TransportState {
178 fn default() -> Self {
179 Self {
180 playhead_sample: 0,
181 tempo: 120.0,
182 numerator: 4,
183 denominator: 4,
184 flags: 0,
185 sample_rate_hz: 0.0,
186 _pad: [0; 256 - 40],
187 }
188 }
189}
190
191#[repr(C, align(256))]
193pub struct ShmHeader {
194 pub magic: u32,
195 pub version: u32,
196 pub flags: u32,
197 pub ready: AtomicU32,
198 pub heartbeat: AtomicU32,
199 pub error_code: u32,
200 pub shutdown_request: AtomicU32,
201 pub tasks_issued: AtomicU32,
202 pub tasks_completed: AtomicU32,
203 pub block_size: AtomicU32,
204 pub num_input_channels: AtomicU32,
205 pub num_output_channels: AtomicU32,
206 pub midi_in_port_count: AtomicU32,
208 pub midi_out_port_count: AtomicU32,
210 pub request_type: AtomicU32,
215 pub request_status: AtomicU32,
217 pub scratch_size: AtomicU32,
219 pub parent_window: AtomicU64,
221 pub state_dirty: AtomicU32,
223 pub latency_samples: AtomicU32,
225 _pad: [u8; 256 - 88],
226}
227
228impl ShmHeader {
229 pub fn parent_window_usize(&self) -> usize {
231 self.parent_window.load(Ordering::Acquire) as usize
232 }
233
234 pub fn set_parent_window(&self, window: usize) {
237 self.parent_window.store(window as u64, Ordering::Release);
238 }
239
240 fn gui_parent_api_atomic(&self) -> &AtomicU32 {
241 unsafe {
244 let base = self as *const Self as *const u8;
245 &*(base.add(GUI_PARENT_API_OFFSET) as *const AtomicU32)
246 }
247 }
248
249 pub fn gui_parent_api(&self) -> GuiParentApi {
251 GuiParentApi::from_u32(self.gui_parent_api_atomic().load(Ordering::Acquire))
252 }
253
254 pub fn set_gui_parent_api(&self, api: GuiParentApi) {
256 self.gui_parent_api_atomic()
257 .store(api.as_u32(), Ordering::Release);
258 }
259
260 fn gui_mode_atomic(&self) -> &AtomicU32 {
261 unsafe {
264 let base = self as *const Self as *const u8;
265 &*(base.add(GUI_MODE_OFFSET) as *const AtomicU32)
266 }
267 }
268
269 pub fn gui_mode(&self) -> GuiMode {
271 GuiMode::from_u32(self.gui_mode_atomic().load(Ordering::Acquire))
272 }
273
274 pub fn set_gui_mode(&self, mode: GuiMode) {
276 self.gui_mode_atomic()
277 .store(mode.as_u32(), Ordering::Release);
278 }
279}
280
281impl Default for ShmHeader {
282 fn default() -> Self {
283 Self {
284 magic: MAGIC,
285 version: VERSION,
286 flags: 0,
287 ready: AtomicU32::new(0),
288 heartbeat: AtomicU32::new(0),
289 error_code: 0,
290 shutdown_request: AtomicU32::new(0),
291 tasks_issued: AtomicU32::new(0),
292 tasks_completed: AtomicU32::new(0),
293 block_size: AtomicU32::new(0),
294 num_input_channels: AtomicU32::new(0),
295 num_output_channels: AtomicU32::new(0),
296 midi_in_port_count: AtomicU32::new(0),
297 midi_out_port_count: AtomicU32::new(0),
298 request_type: AtomicU32::new(0),
299 request_status: AtomicU32::new(0),
300 scratch_size: AtomicU32::new(0),
301 parent_window: AtomicU64::new(0),
302 state_dirty: AtomicU32::new(0),
303 latency_samples: AtomicU32::new(0),
304 _pad: [0; 256 - 88],
305 }
306 }
307}
308
309pub unsafe fn init_shm_layout(ptr: *mut u8, size: usize) {
316 unsafe {
317 std::ptr::write_bytes(ptr, 0, size);
318 let header = ptr as *mut ShmHeader;
319 std::ptr::write(header, ShmHeader::default());
320 }
321}
322
323pub unsafe fn header_ref(ptr: *mut u8) -> &'static ShmHeader {
328 unsafe { &*(ptr as *mut ShmHeader) }
329}
330
331pub unsafe fn header_mut(ptr: *mut u8) -> &'static mut ShmHeader {
336 unsafe { &mut *(ptr as *mut ShmHeader) }
337}
338
339pub unsafe fn audio_ptr(ptr: *mut u8) -> *mut f32 {
344 unsafe { ptr.add(AUDIO_OFFSET) as *mut f32 }
345}
346
347pub unsafe fn audio_channel_ptr(ptr: *mut u8, channel: usize, bus: usize) -> *mut f32 {
355 let plane_size = MAX_BLOCK_SIZE * std::mem::size_of::<f32>();
356 let offset = AUDIO_OFFSET + (channel * NUM_BUSES + bus) * plane_size;
357 unsafe { ptr.add(offset) as *mut f32 }
358}
359
360pub unsafe fn param_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
365 unsafe { ptr.add(PARAM_RING_OFFSET) as *mut ParameterEvent }
366}
367
368pub unsafe fn param_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
373 unsafe {
374 (
375 ptr.add(PARAM_WRITE_IDX_OFFSET) as *mut AtomicU32,
376 ptr.add(PARAM_READ_IDX_OFFSET) as *mut AtomicU32,
377 )
378 }
379}
380
381pub unsafe fn echo_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
386 unsafe { ptr.add(ECHO_RING_OFFSET) as *mut ParameterEvent }
387}
388
389pub unsafe fn echo_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
394 unsafe {
395 (
396 ptr.add(ECHO_WRITE_IDX_OFFSET) as *mut AtomicU32,
397 ptr.add(ECHO_READ_IDX_OFFSET) as *mut AtomicU32,
398 )
399 }
400}
401
402const fn midi_port_ring_offset(base_offset: usize, port: usize) -> usize {
403 base_offset + port * MIDI_PORT_RING_SIZE
404}
405
406pub unsafe fn midi_in_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
411 unsafe {
412 let base = ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port));
413 (base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
414 }
415}
416
417pub unsafe fn midi_in_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
422 unsafe { ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port) + 8) as *mut MidiEvent }
423}
424
425pub unsafe fn midi_out_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
430 unsafe {
431 let base = ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port));
432 (base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
433 }
434}
435
436pub unsafe fn midi_out_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
441 unsafe { ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port) + 8) as *mut MidiEvent }
442}
443
444pub unsafe fn transport_ref(ptr: *mut u8) -> &'static TransportState {
449 unsafe { &*(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
450}
451
452pub unsafe fn transport_mut(ptr: *mut u8) -> &'static mut TransportState {
457 unsafe { &mut *(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
458}
459
460pub unsafe fn scratch_ptr(ptr: *mut u8) -> *mut u8 {
465 unsafe { ptr.add(SCRATCH_OFFSET) }
466}
467
468pub unsafe fn write_plugin_name_to_scratch(ptr: *mut u8, name: &str) {
474 unsafe {
475 let scratch = scratch_ptr(ptr);
476 let bytes = name.as_bytes();
477 let len = bytes.len().min(SCRATCH_SIZE - 4);
478 std::ptr::write_unaligned(scratch as *mut u32, len as u32);
479 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(4), len);
480 }
481}
482
483pub unsafe fn read_plugin_name_from_scratch(ptr: *mut u8) -> Option<String> {
488 unsafe {
489 let scratch = scratch_ptr(ptr);
490 let len = std::ptr::read_unaligned(scratch as *mut u32) as usize;
491 if len == 0 || len > SCRATCH_SIZE - 4 {
492 return None;
493 }
494 let bytes = std::slice::from_raw_parts(scratch.add(4), len);
495 String::from_utf8(bytes.to_vec()).ok()
496 }
497}
498
499pub const PORT_COUNTS_MAGIC: u32 = 0x504F_5254; const PORT_COUNTS_OFFSET: usize = 1024;
504
505pub unsafe fn write_port_counts_to_scratch(
510 ptr: *mut u8,
511 audio_in: u32,
512 audio_out: u32,
513 midi_in: u32,
514 midi_out: u32,
515) {
516 unsafe {
517 let dest = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
518 std::ptr::write_unaligned(dest as *mut u32, PORT_COUNTS_MAGIC);
519 std::ptr::write_unaligned(dest.add(4) as *mut u32, audio_in);
520 std::ptr::write_unaligned(dest.add(8) as *mut u32, audio_out);
521 std::ptr::write_unaligned(dest.add(12) as *mut u32, midi_in);
522 std::ptr::write_unaligned(dest.add(16) as *mut u32, midi_out);
523 }
524}
525
526pub unsafe fn read_port_counts_from_scratch(ptr: *mut u8) -> Option<(u32, u32, u32, u32)> {
531 unsafe {
532 let src = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
533 let magic = std::ptr::read_unaligned(src as *mut u32);
534 if magic != PORT_COUNTS_MAGIC {
535 return None;
536 }
537 let audio_in = std::ptr::read_unaligned(src.add(4) as *mut u32);
538 let audio_out = std::ptr::read_unaligned(src.add(8) as *mut u32);
539 let midi_in = std::ptr::read_unaligned(src.add(12) as *mut u32);
540 let midi_out = std::ptr::read_unaligned(src.add(16) as *mut u32);
541 Some((audio_in, audio_out, midi_in, midi_out))
542 }
543}
544
545pub const FILE_REFS_MAGIC: u32 = 0x4649_4C45; const FILE_REFS_OFFSET: usize = 2048;
550
551const FILE_REFS_MAX_SIZE: usize = SCRATCH_SIZE - FILE_REFS_OFFSET;
553
554pub type FileReference = (u32, String);
556
557pub unsafe fn write_file_references_to_scratch(
564 ptr: *mut u8,
565 refs: &[FileReference],
566) -> Result<(), String> {
567 unsafe {
568 let mut dest = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
569 let mut remaining = FILE_REFS_MAX_SIZE;
570 if remaining < 8 {
571 return Err("scratch too small for file references".to_string());
572 }
573 std::ptr::write_unaligned(dest as *mut u32, FILE_REFS_MAGIC);
574 dest = dest.add(4);
575 remaining -= 4;
576 let count = refs.len().min(u32::MAX as usize) as u32;
577 std::ptr::write_unaligned(dest as *mut u32, count);
578 dest = dest.add(4);
579 remaining -= 4;
580 for (index, path) in refs.iter().take(count as usize) {
581 if remaining < 8 {
582 return Err("scratch overflow writing file references".to_string());
583 }
584 std::ptr::write_unaligned(dest as *mut u32, *index);
585 dest = dest.add(4);
586 remaining -= 4;
587 let bytes = path.as_bytes();
588 let len = bytes
589 .len()
590 .min(u32::MAX as usize)
591 .min(remaining.saturating_sub(4));
592 if len < bytes.len() {
593 return Err("scratch overflow writing file references".to_string());
594 }
595 std::ptr::write_unaligned(dest as *mut u32, len as u32);
596 dest = dest.add(4);
597 remaining -= 4;
598 std::ptr::copy_nonoverlapping(bytes.as_ptr(), dest, len);
599 dest = dest.add(len);
600 remaining -= len;
601 }
602 Ok(())
603 }
604}
605
606pub unsafe fn read_file_references_from_scratch(ptr: *mut u8) -> Option<Vec<FileReference>> {
611 unsafe {
612 let mut src = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
613 let mut remaining = FILE_REFS_MAX_SIZE;
614 if remaining < 8 {
615 return None;
616 }
617 let magic = std::ptr::read_unaligned(src as *mut u32);
618 if magic != FILE_REFS_MAGIC {
619 return None;
620 }
621 src = src.add(4);
622 remaining -= 4;
623 let count = std::ptr::read_unaligned(src as *mut u32) as usize;
624 src = src.add(4);
625 remaining -= 4;
626 let mut refs = Vec::with_capacity(count);
627 for _ in 0..count {
628 if remaining < 8 {
629 return None;
630 }
631 let index = std::ptr::read_unaligned(src as *mut u32);
632 src = src.add(4);
633 remaining -= 4;
634 let len = std::ptr::read_unaligned(src as *mut u32) as usize;
635 src = src.add(4);
636 remaining -= 4;
637 if len > remaining {
638 return None;
639 }
640 let bytes = std::slice::from_raw_parts(src, len);
641 let path = String::from_utf8(bytes.to_vec()).ok()?;
642 refs.push((index, path));
643 src = src.add(len);
644 remaining -= len;
645 }
646 Some(refs)
647 }
648}
649
650pub unsafe fn write_resource_directory_to_scratch(ptr: *mut u8, path: &str) -> Result<(), String> {
656 unsafe {
657 let scratch = scratch_ptr(ptr);
658 let bytes = path.as_bytes();
659 let len = bytes.len().min(SCRATCH_SIZE - 8);
660 if len < bytes.len() {
661 return Err("resource directory path too long".to_string());
662 }
663 std::ptr::write_unaligned(scratch as *mut u32, FILE_REFS_MAGIC);
664 std::ptr::write_unaligned(scratch.add(4) as *mut u32, len as u32);
665 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(8), len);
666 Ok(())
667 }
668}
669
670pub unsafe fn read_resource_directory_from_scratch(ptr: *mut u8) -> Option<String> {
675 unsafe {
676 let scratch = scratch_ptr(ptr);
677 let magic = std::ptr::read_unaligned(scratch as *mut u32);
678 if magic != FILE_REFS_MAGIC {
679 return None;
680 }
681 let len = std::ptr::read_unaligned(scratch.add(4) as *mut u32) as usize;
682 if len == 0 || len > SCRATCH_SIZE - 8 {
683 return None;
684 }
685 let bytes = std::slice::from_raw_parts(scratch.add(8), len);
686 String::from_utf8(bytes.to_vec()).ok()
687 }
688}
689
690pub const REQUEST_LV2_CONTROL_PORTS: u32 = 8;
692
693pub const REQUEST_CLAP_PARAMETERS: u32 = 9;
695
696pub const REQUEST_LV2_MIDNAM: u32 = 10;
698
699pub const REQUEST_CLAP_NOTE_NAMES: u32 = 11;
701
702pub const REQUEST_CLAP_AUDIO_PORTS: u32 = 12;
704
705pub const FILE_REF_UPDATE_MAGIC: u32 = 0x5550_4441; pub unsafe fn write_file_reference_update_to_scratch(
714 ptr: *mut u8,
715 index: u32,
716 path: &str,
717) -> Result<(), String> {
718 unsafe {
719 let scratch = scratch_ptr(ptr);
720 let bytes = path.as_bytes();
721 let len = bytes.len().min(SCRATCH_SIZE - 12);
722 if len < bytes.len() {
723 return Err("file-reference update path too long".to_string());
724 }
725 std::ptr::write_unaligned(scratch as *mut u32, FILE_REF_UPDATE_MAGIC);
726 std::ptr::write_unaligned(scratch.add(4) as *mut u32, index);
727 std::ptr::write_unaligned(scratch.add(8) as *mut u32, len as u32);
728 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(12), len);
729 Ok(())
730 }
731}
732
733pub unsafe fn read_file_reference_update_from_scratch(ptr: *mut u8) -> Option<(u32, String)> {
738 unsafe {
739 let scratch = scratch_ptr(ptr);
740 let magic = std::ptr::read_unaligned(scratch as *mut u32);
741 if magic != FILE_REF_UPDATE_MAGIC {
742 return None;
743 }
744 let index = std::ptr::read_unaligned(scratch.add(4) as *mut u32);
745 let len = std::ptr::read_unaligned(scratch.add(8) as *mut u32) as usize;
746 if len == 0 || len > SCRATCH_SIZE - 12 {
747 return None;
748 }
749 let bytes = std::slice::from_raw_parts(scratch.add(12), len);
750 let path = String::from_utf8(bytes.to_vec()).ok()?;
751 Some((index, path))
752 }
753}
754
755const _: () = assert!(std::mem::size_of::<ShmHeader>() == 256);
758const _: () = assert!(std::mem::align_of::<ShmHeader>() == 256);
759const _: () = assert!(std::mem::size_of::<ParameterEvent>() == 16);
760const _: () = assert!(std::mem::align_of::<ParameterEvent>() == 16);
761const _: () = assert!(std::mem::size_of::<MidiEvent>() == 16);
762const _: () = assert!(std::mem::align_of::<MidiEvent>() == 16);
763const _: () = assert!(std::mem::size_of::<TransportState>() == 256);
764const _: () = assert!(std::mem::align_of::<TransportState>() == 256);
765const _: () = assert!(LAYOUT_SIZE <= SHM_SIZE);
766
767pub fn wait_for_ready(header: &ShmHeader, timeout: std::time::Duration) -> bool {
769 let start = std::time::Instant::now();
770 while header.ready.load(Ordering::Acquire) == 0 {
771 if start.elapsed() >= timeout {
772 return false;
773 }
774 std::thread::yield_now();
775 }
776 true
777}