1use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
2
3pub const MAGIC: u32 = 0x4D41_4F4C;
5
6pub const VERSION: u32 = 7;
16
17pub const MAX_CHANNELS: usize = 32;
19
20pub const NUM_BUSES: usize = 2;
22
23pub const MAX_BLOCK_SIZE: usize = 4096;
25
26pub const RING_CAPACITY: usize = 4096;
28
29pub const MAX_MIDI_PORTS: usize = 16;
32
33pub const HEADER_SIZE: usize = 256;
35pub const CONTROL_SIZE: usize = 256;
36pub 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>();
38pub const MIDI_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<MidiEvent>();
40pub const MIDI_PORT_RING_SIZE: usize = {
44 let raw = 16 + MIDI_RING_SIZE; (raw + 15) & !15 };
47pub const TRANSPORT_SIZE: usize = 256;
48pub const SCRATCH_SIZE: usize = 65536;
49
50pub const CONTROL_OFFSET: usize = HEADER_SIZE;
53pub const AUDIO_OFFSET: usize = HEADER_SIZE + CONTROL_SIZE;
55pub const PARAM_RING_OFFSET: usize = AUDIO_OFFSET + AUDIO_BUFFER_SIZE;
57pub const ECHO_RING_OFFSET: usize = PARAM_RING_OFFSET + PARAM_RING_SIZE;
59pub const ECHO_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<ParameterEvent>();
60pub const MIDI_IN_RINGS_OFFSET: usize = {
62 let end = ECHO_RING_OFFSET + ECHO_RING_SIZE;
63 (end + 255) & !255
64};
65pub const MIDI_IN_RINGS_SIZE: usize = MAX_MIDI_PORTS * MIDI_PORT_RING_SIZE;
66pub const MIDI_OUT_RINGS_OFFSET: usize = MIDI_IN_RINGS_OFFSET + MIDI_IN_RINGS_SIZE;
68pub const MIDI_OUT_RINGS_SIZE: usize = MAX_MIDI_PORTS * MIDI_PORT_RING_SIZE;
69pub const TRANSPORT_OFFSET: usize = {
71 let end = MIDI_OUT_RINGS_OFFSET + MIDI_OUT_RINGS_SIZE;
72 (end + 255) & !255
74};
75pub const SCRATCH_OFFSET: usize = TRANSPORT_OFFSET + TRANSPORT_SIZE;
77
78pub const LAYOUT_SIZE: usize = SCRATCH_OFFSET + SCRATCH_SIZE;
80
81pub const SHM_SIZE: usize = 4 * 1024 * 1024;
83
84pub const PARAM_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET;
86pub const PARAM_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 4;
87pub const ECHO_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET + 8;
88pub const ECHO_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 12;
89pub const GUI_MODE_OFFSET: usize = CONTROL_OFFSET + 16;
90pub const GUI_PARENT_API_OFFSET: usize = CONTROL_OFFSET + 20;
91
92#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
94pub enum GuiMode {
95 #[default]
97 Embedded = 0,
98 Floating = 1,
100}
101
102impl GuiMode {
103 pub fn from_u32(value: u32) -> Self {
104 match value {
105 1 => GuiMode::Floating,
106 _ => GuiMode::Embedded,
107 }
108 }
109
110 pub fn as_u32(self) -> u32 {
111 self as u32
112 }
113}
114
115#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
117pub enum GuiParentApi {
118 #[default]
120 None = 0,
121 X11 = 1,
123 Wayland = 2,
125}
126
127impl GuiParentApi {
128 pub fn from_u32(value: u32) -> Self {
129 match value {
130 1 => GuiParentApi::X11,
131 2 => GuiParentApi::Wayland,
132 _ => GuiParentApi::None,
133 }
134 }
135
136 pub fn as_u32(self) -> u32 {
137 self as u32
138 }
139}
140
141pub const PARAM_EVENT_VALUE: u32 = 0;
144pub const PARAM_EVENT_MOD: u32 = 1;
145pub const PARAM_EVENT_GESTURE_BEGIN: u32 = 2;
146pub const PARAM_EVENT_GESTURE_END: u32 = 3;
147
148#[repr(C, align(16))]
150#[derive(Clone, Copy, Debug, Default)]
151pub struct ParameterEvent {
152 pub param_index: u32,
153 pub value: f32,
154 pub sample_offset: u32,
155 pub event_kind: u32,
156}
157
158#[repr(C, align(16))]
160#[derive(Clone, Copy, Debug, Default)]
161pub struct MidiEvent {
162 pub sample_offset: u32,
163 pub data: [u8; 3],
164 pub channel: u8,
165 pub flags: u16,
166 pub _pad: u16,
167}
168
169#[repr(C, align(256))]
171#[derive(Clone, Copy, Debug)]
172pub struct TransportState {
173 pub playhead_sample: u64,
174 pub tempo: f64,
175 pub numerator: u32,
176 pub denominator: u32,
177 pub flags: u32,
178 pub sample_rate_hz: f64,
179 _pad: [u8; 256 - 40],
180}
181
182impl Default for TransportState {
183 fn default() -> Self {
184 Self {
185 playhead_sample: 0,
186 tempo: 120.0,
187 numerator: 4,
188 denominator: 4,
189 flags: 0,
190 sample_rate_hz: 0.0,
191 _pad: [0; 256 - 40],
192 }
193 }
194}
195
196#[repr(C, align(256))]
198pub struct ShmHeader {
199 pub magic: u32,
200 pub version: u32,
201 pub flags: u32,
202 pub ready: AtomicU32,
203 pub heartbeat: AtomicU32,
204 pub error_code: u32,
205 pub shutdown_request: AtomicU32,
206 pub tasks_issued: AtomicU32,
207 pub tasks_completed: AtomicU32,
208 pub block_size: AtomicU32,
209 pub num_input_channels: AtomicU32,
210 pub num_output_channels: AtomicU32,
211 pub midi_in_port_count: AtomicU32,
213 pub midi_out_port_count: AtomicU32,
215 pub request_type: AtomicU32,
220 pub request_status: AtomicU32,
222 pub scratch_size: AtomicU32,
224 pub parent_window: AtomicU64,
226 pub state_dirty: AtomicU32,
228 pub latency_samples: AtomicU32,
230 _pad: [u8; 256 - 88],
231}
232
233impl ShmHeader {
234 pub fn parent_window_usize(&self) -> usize {
236 self.parent_window.load(Ordering::Acquire) as usize
237 }
238
239 pub fn set_parent_window(&self, window: usize) {
242 self.parent_window.store(window as u64, Ordering::Release);
243 }
244
245 fn gui_parent_api_atomic(&self) -> &AtomicU32 {
246 unsafe {
249 let base = self as *const Self as *const u8;
250 &*(base.add(GUI_PARENT_API_OFFSET) as *const AtomicU32)
251 }
252 }
253
254 pub fn gui_parent_api(&self) -> GuiParentApi {
256 GuiParentApi::from_u32(self.gui_parent_api_atomic().load(Ordering::Acquire))
257 }
258
259 pub fn set_gui_parent_api(&self, api: GuiParentApi) {
261 self.gui_parent_api_atomic()
262 .store(api.as_u32(), Ordering::Release);
263 }
264
265 fn gui_mode_atomic(&self) -> &AtomicU32 {
266 unsafe {
269 let base = self as *const Self as *const u8;
270 &*(base.add(GUI_MODE_OFFSET) as *const AtomicU32)
271 }
272 }
273
274 pub fn gui_mode(&self) -> GuiMode {
276 GuiMode::from_u32(self.gui_mode_atomic().load(Ordering::Acquire))
277 }
278
279 pub fn set_gui_mode(&self, mode: GuiMode) {
281 self.gui_mode_atomic()
282 .store(mode.as_u32(), Ordering::Release);
283 }
284}
285
286impl Default for ShmHeader {
287 fn default() -> Self {
288 Self {
289 magic: MAGIC,
290 version: VERSION,
291 flags: 0,
292 ready: AtomicU32::new(0),
293 heartbeat: AtomicU32::new(0),
294 error_code: 0,
295 shutdown_request: AtomicU32::new(0),
296 tasks_issued: AtomicU32::new(0),
297 tasks_completed: AtomicU32::new(0),
298 block_size: AtomicU32::new(0),
299 num_input_channels: AtomicU32::new(0),
300 num_output_channels: AtomicU32::new(0),
301 midi_in_port_count: AtomicU32::new(0),
302 midi_out_port_count: AtomicU32::new(0),
303 request_type: AtomicU32::new(0),
304 request_status: AtomicU32::new(0),
305 scratch_size: AtomicU32::new(0),
306 parent_window: AtomicU64::new(0),
307 state_dirty: AtomicU32::new(0),
308 latency_samples: AtomicU32::new(0),
309 _pad: [0; 256 - 88],
310 }
311 }
312}
313
314pub unsafe fn init_shm_layout(ptr: *mut u8, size: usize) {
321 unsafe {
322 std::ptr::write_bytes(ptr, 0, size);
323 let header = ptr as *mut ShmHeader;
324 std::ptr::write(header, ShmHeader::default());
325 }
326}
327
328pub unsafe fn header_ref(ptr: *mut u8) -> &'static ShmHeader {
333 unsafe { &*(ptr as *mut ShmHeader) }
334}
335
336pub unsafe fn header_mut(ptr: *mut u8) -> &'static mut ShmHeader {
341 unsafe { &mut *(ptr as *mut ShmHeader) }
342}
343
344pub unsafe fn audio_ptr(ptr: *mut u8) -> *mut f32 {
349 unsafe { ptr.add(AUDIO_OFFSET) as *mut f32 }
350}
351
352pub unsafe fn audio_channel_ptr(ptr: *mut u8, channel: usize, bus: usize) -> *mut f32 {
360 let plane_size = MAX_BLOCK_SIZE * std::mem::size_of::<f32>();
361 let offset = AUDIO_OFFSET + (channel * NUM_BUSES + bus) * plane_size;
362 unsafe { ptr.add(offset) as *mut f32 }
363}
364
365pub unsafe fn param_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
370 unsafe { ptr.add(PARAM_RING_OFFSET) as *mut ParameterEvent }
371}
372
373pub unsafe fn param_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
378 unsafe {
379 (
380 ptr.add(PARAM_WRITE_IDX_OFFSET) as *mut AtomicU32,
381 ptr.add(PARAM_READ_IDX_OFFSET) as *mut AtomicU32,
382 )
383 }
384}
385
386pub unsafe fn echo_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
391 unsafe { ptr.add(ECHO_RING_OFFSET) as *mut ParameterEvent }
392}
393
394pub unsafe fn echo_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
399 unsafe {
400 (
401 ptr.add(ECHO_WRITE_IDX_OFFSET) as *mut AtomicU32,
402 ptr.add(ECHO_READ_IDX_OFFSET) as *mut AtomicU32,
403 )
404 }
405}
406
407const fn midi_port_ring_offset(base_offset: usize, port: usize) -> usize {
408 base_offset + port * MIDI_PORT_RING_SIZE
409}
410
411pub unsafe fn midi_in_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
416 unsafe {
417 let base = ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port));
418 (base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
419 }
420}
421
422pub unsafe fn midi_in_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
427 unsafe { ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port) + 16) as *mut MidiEvent }
428}
429
430pub unsafe fn midi_out_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
435 unsafe {
436 let base = ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port));
437 (base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
438 }
439}
440
441pub unsafe fn midi_out_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
446 unsafe { ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port) + 16) as *mut MidiEvent }
447}
448
449pub unsafe fn transport_ref(ptr: *mut u8) -> &'static TransportState {
454 unsafe { &*(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
455}
456
457pub unsafe fn transport_mut(ptr: *mut u8) -> &'static mut TransportState {
462 unsafe { &mut *(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
463}
464
465pub unsafe fn scratch_ptr(ptr: *mut u8) -> *mut u8 {
470 unsafe { ptr.add(SCRATCH_OFFSET) }
471}
472
473pub unsafe fn write_plugin_name_to_scratch(ptr: *mut u8, name: &str) {
479 unsafe {
480 let scratch = scratch_ptr(ptr);
481 let bytes = name.as_bytes();
482 let len = bytes.len().min(SCRATCH_SIZE - 4);
483 std::ptr::write_unaligned(scratch as *mut u32, len as u32);
484 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(4), len);
485 }
486}
487
488pub unsafe fn read_plugin_name_from_scratch(ptr: *mut u8) -> Option<String> {
493 unsafe {
494 let scratch = scratch_ptr(ptr);
495 let len = std::ptr::read_unaligned(scratch as *mut u32) as usize;
496 if len == 0 || len > SCRATCH_SIZE - 4 {
497 return None;
498 }
499 let bytes = std::slice::from_raw_parts(scratch.add(4), len);
500 String::from_utf8(bytes.to_vec()).ok()
501 }
502}
503
504pub const PORT_COUNTS_MAGIC: u32 = 0x504F_5254; const PORT_COUNTS_OFFSET: usize = 1024;
509
510pub unsafe fn write_port_counts_to_scratch(
515 ptr: *mut u8,
516 audio_in: u32,
517 audio_out: u32,
518 midi_in: u32,
519 midi_out: u32,
520) {
521 unsafe {
522 let dest = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
523 std::ptr::write_unaligned(dest as *mut u32, PORT_COUNTS_MAGIC);
524 std::ptr::write_unaligned(dest.add(4) as *mut u32, audio_in);
525 std::ptr::write_unaligned(dest.add(8) as *mut u32, audio_out);
526 std::ptr::write_unaligned(dest.add(12) as *mut u32, midi_in);
527 std::ptr::write_unaligned(dest.add(16) as *mut u32, midi_out);
528 }
529}
530
531pub unsafe fn read_port_counts_from_scratch(ptr: *mut u8) -> Option<(u32, u32, u32, u32)> {
536 unsafe {
537 let src = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
538 let magic = std::ptr::read_unaligned(src as *mut u32);
539 if magic != PORT_COUNTS_MAGIC {
540 return None;
541 }
542 let audio_in = std::ptr::read_unaligned(src.add(4) as *mut u32);
543 let audio_out = std::ptr::read_unaligned(src.add(8) as *mut u32);
544 let midi_in = std::ptr::read_unaligned(src.add(12) as *mut u32);
545 let midi_out = std::ptr::read_unaligned(src.add(16) as *mut u32);
546 Some((audio_in, audio_out, midi_in, midi_out))
547 }
548}
549
550pub const FILE_REFS_MAGIC: u32 = 0x4649_4C45; const FILE_REFS_OFFSET: usize = 2048;
555
556const FILE_REFS_MAX_SIZE: usize = SCRATCH_SIZE - FILE_REFS_OFFSET;
558
559pub type ResourceFile = (u32, String);
562
563pub unsafe fn write_resource_files_to_scratch(
570 ptr: *mut u8,
571 files: &[ResourceFile],
572) -> Result<(), String> {
573 unsafe {
574 let mut dest = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
575 let mut remaining = FILE_REFS_MAX_SIZE;
576 if remaining < 8 {
577 return Err("scratch too small for resource files".to_string());
578 }
579 std::ptr::write_unaligned(dest as *mut u32, FILE_REFS_MAGIC);
580 dest = dest.add(4);
581 remaining -= 4;
582 let count = files.len().min(u32::MAX as usize) as u32;
583 std::ptr::write_unaligned(dest as *mut u32, count);
584 dest = dest.add(4);
585 remaining -= 4;
586 for (index, path) in files.iter().take(count as usize) {
587 if remaining < 8 {
588 return Err("scratch overflow writing resource files".to_string());
589 }
590 std::ptr::write_unaligned(dest as *mut u32, *index);
591 dest = dest.add(4);
592 remaining -= 4;
593 let bytes = path.as_bytes();
594 let len = bytes
595 .len()
596 .min(u32::MAX as usize)
597 .min(remaining.saturating_sub(4));
598 if len < bytes.len() {
599 return Err("scratch overflow writing resource files".to_string());
600 }
601 std::ptr::write_unaligned(dest as *mut u32, len as u32);
602 dest = dest.add(4);
603 remaining -= 4;
604 std::ptr::copy_nonoverlapping(bytes.as_ptr(), dest, len);
605 dest = dest.add(len);
606 remaining -= len;
607 }
608 Ok(())
609 }
610}
611
612pub unsafe fn read_resource_files_from_scratch(ptr: *mut u8) -> Option<Vec<ResourceFile>> {
617 unsafe {
618 let mut src = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
619 let mut remaining = FILE_REFS_MAX_SIZE;
620 if remaining < 8 {
621 return None;
622 }
623 let magic = std::ptr::read_unaligned(src as *mut u32);
624 if magic != FILE_REFS_MAGIC {
625 return None;
626 }
627 src = src.add(4);
628 remaining -= 4;
629 let count = std::ptr::read_unaligned(src as *mut u32) as usize;
630 src = src.add(4);
631 remaining -= 4;
632 let mut files = Vec::with_capacity(count);
633 for _ in 0..count {
634 if remaining < 8 {
635 return None;
636 }
637 let index = std::ptr::read_unaligned(src as *mut u32);
638 src = src.add(4);
639 remaining -= 4;
640 let len = std::ptr::read_unaligned(src as *mut u32) as usize;
641 src = src.add(4);
642 remaining -= 4;
643 if len > remaining {
644 return None;
645 }
646 let bytes = std::slice::from_raw_parts(src, len);
647 let path = String::from_utf8(bytes.to_vec()).ok()?;
648 files.push((index, path));
649 src = src.add(len);
650 remaining -= len;
651 }
652 Some(files)
653 }
654}
655
656pub unsafe fn write_resource_directory_to_scratch(
662 ptr: *mut u8,
663 path: &str,
664 is_shared: bool,
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("resource directory path too long".to_string());
672 }
673 std::ptr::write_unaligned(scratch as *mut u32, FILE_REFS_MAGIC);
674 std::ptr::write_unaligned(scratch.add(4) as *mut u32, len as u32);
675 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(8), len);
676 std::ptr::write_unaligned(scratch.add(8 + len) as *mut u32, is_shared as u32);
677 Ok(())
678 }
679}
680
681pub unsafe fn read_resource_directory_from_scratch(ptr: *mut u8) -> Option<(String, bool)> {
686 unsafe {
687 let scratch = scratch_ptr(ptr);
688 let magic = std::ptr::read_unaligned(scratch as *mut u32);
689 if magic != FILE_REFS_MAGIC {
690 return None;
691 }
692 let len = std::ptr::read_unaligned(scratch.add(4) as *mut u32) as usize;
693 if len == 0 || len > SCRATCH_SIZE - 12 {
694 return None;
695 }
696 let bytes = std::slice::from_raw_parts(scratch.add(8), len);
697 let path = String::from_utf8(bytes.to_vec()).ok()?;
698 let is_shared = std::ptr::read_unaligned(scratch.add(8 + len) as *mut u32) != 0;
699 Some((path, is_shared))
700 }
701}
702
703pub const REQUEST_COLLECT_RESOURCES: u32 = 6;
706
707pub const REQUEST_RESOURCE_FILES: u32 = 7;
710
711pub const REQUEST_LV2_CONTROL_PORTS: u32 = 8;
713
714pub const REQUEST_CLAP_PARAMETERS: u32 = 9;
716
717pub const REQUEST_LV2_MIDNAM: u32 = 10;
719
720pub const REQUEST_CLAP_NOTE_NAMES: u32 = 11;
722
723pub const REQUEST_CLAP_AUDIO_PORTS: u32 = 12;
725
726const _: () = assert!(std::mem::size_of::<ShmHeader>() == 256);
729const _: () = assert!(std::mem::align_of::<ShmHeader>() == 256);
730const _: () = assert!(std::mem::size_of::<ParameterEvent>() == 16);
731const _: () = assert!(std::mem::align_of::<ParameterEvent>() == 16);
732const _: () = assert!(std::mem::size_of::<MidiEvent>() == 16);
733const _: () = assert!(std::mem::align_of::<MidiEvent>() == 16);
734const _: () = assert!(std::mem::size_of::<TransportState>() == 256);
735const _: () = assert!(std::mem::align_of::<TransportState>() == 256);
736const _: () = assert!(LAYOUT_SIZE <= SHM_SIZE);
737
738pub fn wait_for_ready(header: &ShmHeader, timeout: std::time::Duration) -> bool {
740 let start = std::time::Instant::now();
741 while header.ready.load(Ordering::Acquire) == 0 {
742 if start.elapsed() >= timeout {
743 return false;
744 }
745 std::thread::yield_now();
746 }
747 true
748}