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;
82
83pub const PARAM_EVENT_VALUE: u32 = 0;
86pub const PARAM_EVENT_MOD: u32 = 1;
87pub const PARAM_EVENT_GESTURE_BEGIN: u32 = 2;
88pub const PARAM_EVENT_GESTURE_END: u32 = 3;
89
90#[repr(C, align(16))]
92#[derive(Clone, Copy, Debug, Default)]
93pub struct ParameterEvent {
94 pub param_index: u32,
95 pub value: f32,
96 pub sample_offset: u32,
97 pub event_kind: u32,
98}
99
100#[repr(C, align(16))]
102#[derive(Clone, Copy, Debug, Default)]
103pub struct MidiEvent {
104 pub sample_offset: u32,
105 pub data: [u8; 3],
106 pub channel: u8,
107 pub flags: u16,
108 pub _pad: u16,
109}
110
111#[repr(C, align(256))]
113#[derive(Clone, Copy, Debug)]
114pub struct TransportState {
115 pub playhead_sample: u64,
116 pub tempo: f64,
117 pub numerator: u32,
118 pub denominator: u32,
119 pub flags: u32,
120 pub sample_rate_hz: f64,
121 _pad: [u8; 256 - 40],
122}
123
124impl Default for TransportState {
125 fn default() -> Self {
126 Self {
127 playhead_sample: 0,
128 tempo: 120.0,
129 numerator: 4,
130 denominator: 4,
131 flags: 0,
132 sample_rate_hz: 0.0,
133 _pad: [0; 256 - 40],
134 }
135 }
136}
137
138#[repr(C, align(256))]
140pub struct ShmHeader {
141 pub magic: u32,
142 pub version: u32,
143 pub flags: u32,
144 pub ready: AtomicU32,
145 pub heartbeat: AtomicU32,
146 pub error_code: u32,
147 pub shutdown_request: AtomicU32,
148 pub tasks_issued: AtomicU32,
149 pub tasks_completed: AtomicU32,
150 pub block_size: AtomicU32,
151 pub num_input_channels: AtomicU32,
152 pub num_output_channels: AtomicU32,
153 pub midi_in_port_count: AtomicU32,
155 pub midi_out_port_count: AtomicU32,
157 pub request_type: AtomicU32,
160 pub request_status: AtomicU32,
162 pub scratch_size: AtomicU32,
164 pub parent_window: AtomicU64,
166 pub state_dirty: AtomicU32,
168 _pad: [u8; 256 - 84],
169}
170
171impl ShmHeader {
172 pub fn parent_window_usize(&self) -> usize {
174 self.parent_window.load(Ordering::Acquire) as usize
175 }
176
177 pub fn set_parent_window(&self, window: usize) {
180 self.parent_window.store(window as u64, Ordering::Release);
181 }
182}
183
184impl Default for ShmHeader {
185 fn default() -> Self {
186 Self {
187 magic: MAGIC,
188 version: VERSION,
189 flags: 0,
190 ready: AtomicU32::new(0),
191 heartbeat: AtomicU32::new(0),
192 error_code: 0,
193 shutdown_request: AtomicU32::new(0),
194 tasks_issued: AtomicU32::new(0),
195 tasks_completed: AtomicU32::new(0),
196 block_size: AtomicU32::new(0),
197 num_input_channels: AtomicU32::new(0),
198 num_output_channels: AtomicU32::new(0),
199 midi_in_port_count: AtomicU32::new(0),
200 midi_out_port_count: AtomicU32::new(0),
201 request_type: AtomicU32::new(0),
202 request_status: AtomicU32::new(0),
203 scratch_size: AtomicU32::new(0),
204 parent_window: AtomicU64::new(0),
205 state_dirty: AtomicU32::new(0),
206 _pad: [0; 256 - 84],
207 }
208 }
209}
210
211pub unsafe fn init_shm_layout(ptr: *mut u8, size: usize) {
218 unsafe {
219 std::ptr::write_bytes(ptr, 0, size);
220 let header = ptr as *mut ShmHeader;
221 std::ptr::write(header, ShmHeader::default());
222 }
223}
224
225pub unsafe fn header_ref(ptr: *mut u8) -> &'static ShmHeader {
230 unsafe { &*(ptr as *mut ShmHeader) }
231}
232
233pub unsafe fn header_mut(ptr: *mut u8) -> &'static mut ShmHeader {
238 unsafe { &mut *(ptr as *mut ShmHeader) }
239}
240
241pub unsafe fn audio_ptr(ptr: *mut u8) -> *mut f32 {
246 unsafe { ptr.add(AUDIO_OFFSET) as *mut f32 }
247}
248
249pub unsafe fn audio_channel_ptr(ptr: *mut u8, channel: usize, bus: usize) -> *mut f32 {
257 let plane_size = MAX_BLOCK_SIZE * std::mem::size_of::<f32>();
258 let offset = AUDIO_OFFSET + (channel * NUM_BUSES + bus) * plane_size;
259 unsafe { ptr.add(offset) as *mut f32 }
260}
261
262pub unsafe fn param_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
267 unsafe { ptr.add(PARAM_RING_OFFSET) as *mut ParameterEvent }
268}
269
270pub unsafe fn param_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
275 unsafe {
276 (
277 ptr.add(PARAM_WRITE_IDX_OFFSET) as *mut AtomicU32,
278 ptr.add(PARAM_READ_IDX_OFFSET) as *mut AtomicU32,
279 )
280 }
281}
282
283pub unsafe fn echo_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
288 unsafe { ptr.add(ECHO_RING_OFFSET) as *mut ParameterEvent }
289}
290
291pub unsafe fn echo_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
296 unsafe {
297 (
298 ptr.add(ECHO_WRITE_IDX_OFFSET) as *mut AtomicU32,
299 ptr.add(ECHO_READ_IDX_OFFSET) as *mut AtomicU32,
300 )
301 }
302}
303
304const fn midi_port_ring_offset(base_offset: usize, port: usize) -> usize {
305 base_offset + port * MIDI_PORT_RING_SIZE
306}
307
308pub unsafe fn midi_in_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
313 unsafe {
314 let base = ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port));
315 (base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
316 }
317}
318
319pub unsafe fn midi_in_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
324 unsafe { ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port) + 8) as *mut MidiEvent }
325}
326
327pub unsafe fn midi_out_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
332 unsafe {
333 let base = ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port));
334 (base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
335 }
336}
337
338pub unsafe fn midi_out_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
343 unsafe { ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port) + 8) as *mut MidiEvent }
344}
345
346pub unsafe fn transport_ref(ptr: *mut u8) -> &'static TransportState {
351 unsafe { &*(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
352}
353
354pub unsafe fn transport_mut(ptr: *mut u8) -> &'static mut TransportState {
359 unsafe { &mut *(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
360}
361
362pub unsafe fn scratch_ptr(ptr: *mut u8) -> *mut u8 {
367 unsafe { ptr.add(SCRATCH_OFFSET) }
368}
369
370pub unsafe fn write_plugin_name_to_scratch(ptr: *mut u8, name: &str) {
376 unsafe {
377 let scratch = scratch_ptr(ptr);
378 let bytes = name.as_bytes();
379 let len = bytes.len().min(SCRATCH_SIZE - 4);
380 std::ptr::write_unaligned(scratch as *mut u32, len as u32);
381 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(4), len);
382 }
383}
384
385pub unsafe fn read_plugin_name_from_scratch(ptr: *mut u8) -> Option<String> {
390 unsafe {
391 let scratch = scratch_ptr(ptr);
392 let len = std::ptr::read_unaligned(scratch as *mut u32) as usize;
393 if len == 0 || len > SCRATCH_SIZE - 4 {
394 return None;
395 }
396 let bytes = std::slice::from_raw_parts(scratch.add(4), len);
397 String::from_utf8(bytes.to_vec()).ok()
398 }
399}
400
401pub const PORT_COUNTS_MAGIC: u32 = 0x504F_5254; const PORT_COUNTS_OFFSET: usize = 1024;
406
407pub unsafe fn write_port_counts_to_scratch(
412 ptr: *mut u8,
413 audio_in: u32,
414 audio_out: u32,
415 midi_in: u32,
416 midi_out: u32,
417) {
418 unsafe {
419 let dest = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
420 std::ptr::write_unaligned(dest as *mut u32, PORT_COUNTS_MAGIC);
421 std::ptr::write_unaligned(dest.add(4) as *mut u32, audio_in);
422 std::ptr::write_unaligned(dest.add(8) as *mut u32, audio_out);
423 std::ptr::write_unaligned(dest.add(12) as *mut u32, midi_in);
424 std::ptr::write_unaligned(dest.add(16) as *mut u32, midi_out);
425 }
426}
427
428pub unsafe fn read_port_counts_from_scratch(ptr: *mut u8) -> Option<(u32, u32, u32, u32)> {
433 unsafe {
434 let src = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
435 let magic = std::ptr::read_unaligned(src as *mut u32);
436 if magic != PORT_COUNTS_MAGIC {
437 return None;
438 }
439 let audio_in = std::ptr::read_unaligned(src.add(4) as *mut u32);
440 let audio_out = std::ptr::read_unaligned(src.add(8) as *mut u32);
441 let midi_in = std::ptr::read_unaligned(src.add(12) as *mut u32);
442 let midi_out = std::ptr::read_unaligned(src.add(16) as *mut u32);
443 Some((audio_in, audio_out, midi_in, midi_out))
444 }
445}
446
447pub const FILE_REFS_MAGIC: u32 = 0x4649_4C45; const FILE_REFS_OFFSET: usize = 2048;
452
453const FILE_REFS_MAX_SIZE: usize = SCRATCH_SIZE - FILE_REFS_OFFSET;
455
456pub type FileReference = (u32, String);
458
459pub unsafe fn write_file_references_to_scratch(
466 ptr: *mut u8,
467 refs: &[FileReference],
468) -> Result<(), String> {
469 unsafe {
470 let mut dest = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
471 let mut remaining = FILE_REFS_MAX_SIZE;
472 if remaining < 8 {
473 return Err("scratch too small for file references".to_string());
474 }
475 std::ptr::write_unaligned(dest as *mut u32, FILE_REFS_MAGIC);
476 dest = dest.add(4);
477 remaining -= 4;
478 let count = refs.len().min(u32::MAX as usize) as u32;
479 std::ptr::write_unaligned(dest as *mut u32, count);
480 dest = dest.add(4);
481 remaining -= 4;
482 for (index, path) in refs.iter().take(count as usize) {
483 if remaining < 8 {
484 return Err("scratch overflow writing file references".to_string());
485 }
486 std::ptr::write_unaligned(dest as *mut u32, *index);
487 dest = dest.add(4);
488 remaining -= 4;
489 let bytes = path.as_bytes();
490 let len = bytes
491 .len()
492 .min(u32::MAX as usize)
493 .min(remaining.saturating_sub(4));
494 if len < bytes.len() {
495 return Err("scratch overflow writing file references".to_string());
496 }
497 std::ptr::write_unaligned(dest as *mut u32, len as u32);
498 dest = dest.add(4);
499 remaining -= 4;
500 std::ptr::copy_nonoverlapping(bytes.as_ptr(), dest, len);
501 dest = dest.add(len);
502 remaining -= len;
503 }
504 Ok(())
505 }
506}
507
508pub unsafe fn read_file_references_from_scratch(ptr: *mut u8) -> Option<Vec<FileReference>> {
513 unsafe {
514 let mut src = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
515 let mut remaining = FILE_REFS_MAX_SIZE;
516 if remaining < 8 {
517 return None;
518 }
519 let magic = std::ptr::read_unaligned(src as *mut u32);
520 if magic != FILE_REFS_MAGIC {
521 return None;
522 }
523 src = src.add(4);
524 remaining -= 4;
525 let count = std::ptr::read_unaligned(src as *mut u32) as usize;
526 src = src.add(4);
527 remaining -= 4;
528 let mut refs = Vec::with_capacity(count);
529 for _ in 0..count {
530 if remaining < 8 {
531 return None;
532 }
533 let index = std::ptr::read_unaligned(src as *mut u32);
534 src = src.add(4);
535 remaining -= 4;
536 let len = std::ptr::read_unaligned(src as *mut u32) as usize;
537 src = src.add(4);
538 remaining -= 4;
539 if len > remaining {
540 return None;
541 }
542 let bytes = std::slice::from_raw_parts(src, len);
543 let path = String::from_utf8(bytes.to_vec()).ok()?;
544 refs.push((index, path));
545 src = src.add(len);
546 remaining -= len;
547 }
548 Some(refs)
549 }
550}
551
552pub unsafe fn write_resource_directory_to_scratch(ptr: *mut u8, path: &str) -> Result<(), String> {
558 unsafe {
559 let scratch = scratch_ptr(ptr);
560 let bytes = path.as_bytes();
561 let len = bytes.len().min(SCRATCH_SIZE - 8);
562 if len < bytes.len() {
563 return Err("resource directory path too long".to_string());
564 }
565 std::ptr::write_unaligned(scratch as *mut u32, FILE_REFS_MAGIC);
566 std::ptr::write_unaligned(scratch.add(4) as *mut u32, len as u32);
567 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(8), len);
568 Ok(())
569 }
570}
571
572pub unsafe fn read_resource_directory_from_scratch(ptr: *mut u8) -> Option<String> {
577 unsafe {
578 let scratch = scratch_ptr(ptr);
579 let magic = std::ptr::read_unaligned(scratch as *mut u32);
580 if magic != FILE_REFS_MAGIC {
581 return None;
582 }
583 let len = std::ptr::read_unaligned(scratch.add(4) as *mut u32) as usize;
584 if len == 0 || len > SCRATCH_SIZE - 8 {
585 return None;
586 }
587 let bytes = std::slice::from_raw_parts(scratch.add(8), len);
588 String::from_utf8(bytes.to_vec()).ok()
589 }
590}
591
592pub const FILE_REF_UPDATE_MAGIC: u32 = 0x5550_4441; pub unsafe fn write_file_reference_update_to_scratch(
601 ptr: *mut u8,
602 index: u32,
603 path: &str,
604) -> Result<(), String> {
605 unsafe {
606 let scratch = scratch_ptr(ptr);
607 let bytes = path.as_bytes();
608 let len = bytes.len().min(SCRATCH_SIZE - 12);
609 if len < bytes.len() {
610 return Err("file-reference update path too long".to_string());
611 }
612 std::ptr::write_unaligned(scratch as *mut u32, FILE_REF_UPDATE_MAGIC);
613 std::ptr::write_unaligned(scratch.add(4) as *mut u32, index);
614 std::ptr::write_unaligned(scratch.add(8) as *mut u32, len as u32);
615 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(12), len);
616 Ok(())
617 }
618}
619
620pub unsafe fn read_file_reference_update_from_scratch(ptr: *mut u8) -> Option<(u32, String)> {
625 unsafe {
626 let scratch = scratch_ptr(ptr);
627 let magic = std::ptr::read_unaligned(scratch as *mut u32);
628 if magic != FILE_REF_UPDATE_MAGIC {
629 return None;
630 }
631 let index = std::ptr::read_unaligned(scratch.add(4) as *mut u32);
632 let len = std::ptr::read_unaligned(scratch.add(8) as *mut u32) as usize;
633 if len == 0 || len > SCRATCH_SIZE - 12 {
634 return None;
635 }
636 let bytes = std::slice::from_raw_parts(scratch.add(12), len);
637 let path = String::from_utf8(bytes.to_vec()).ok()?;
638 Some((index, path))
639 }
640}
641
642const _: () = assert!(std::mem::size_of::<ShmHeader>() == 256);
645const _: () = assert!(std::mem::align_of::<ShmHeader>() == 256);
646const _: () = assert!(std::mem::size_of::<ParameterEvent>() == 16);
647const _: () = assert!(std::mem::align_of::<ParameterEvent>() == 16);
648const _: () = assert!(std::mem::size_of::<MidiEvent>() == 16);
649const _: () = assert!(std::mem::align_of::<MidiEvent>() == 16);
650const _: () = assert!(std::mem::size_of::<TransportState>() == 256);
651const _: () = assert!(std::mem::align_of::<TransportState>() == 256);
652const _: () = assert!(LAYOUT_SIZE <= SHM_SIZE);
653
654pub fn wait_for_ready(header: &ShmHeader, timeout: std::time::Duration) -> bool {
656 let start = std::time::Instant::now();
657 while header.ready.load(Ordering::Acquire) == 0 {
658 if start.elapsed() >= timeout {
659 return false;
660 }
661 std::thread::yield_now();
662 }
663 true
664}