1use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
2
3pub const MAGIC: u32 = 0x4D41_4F4C;
5
6pub const VERSION: u32 = 3;
10
11pub const MAX_CHANNELS: usize = 32;
13
14pub const NUM_BUSES: usize = 2;
16
17pub const MAX_BLOCK_SIZE: usize = 4096;
19
20pub const RING_CAPACITY: usize = 4096;
22
23pub const HEADER_SIZE: usize = 256;
25pub const CONTROL_SIZE: usize = 256;
26pub 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>();
28pub const MIDI_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<MidiEvent>();
29pub const TRANSPORT_SIZE: usize = 256;
30pub const SCRATCH_SIZE: usize = 65536;
31
32pub const CONTROL_OFFSET: usize = HEADER_SIZE;
35pub const AUDIO_OFFSET: usize = HEADER_SIZE + CONTROL_SIZE;
37pub const PARAM_RING_OFFSET: usize = AUDIO_OFFSET + AUDIO_BUFFER_SIZE;
39pub const MIDI_RING_OFFSET: usize = PARAM_RING_OFFSET + PARAM_RING_SIZE;
41pub const ECHO_RING_OFFSET: usize = MIDI_RING_OFFSET + MIDI_RING_SIZE;
42pub const ECHO_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<ParameterEvent>();
43pub const MIDI_OUT_RING_OFFSET: usize = ECHO_RING_OFFSET + ECHO_RING_SIZE;
44pub const MIDI_OUT_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<MidiEvent>();
45pub const TRANSPORT_OFFSET: usize = {
47 let end = MIDI_OUT_RING_OFFSET + MIDI_OUT_RING_SIZE;
48 (end + 255) & !255
50};
51pub const SCRATCH_OFFSET: usize = TRANSPORT_OFFSET + TRANSPORT_SIZE;
53
54pub const LAYOUT_SIZE: usize = SCRATCH_OFFSET + SCRATCH_SIZE;
56
57pub const SHM_SIZE: usize = 4 * 1024 * 1024;
59
60pub const PARAM_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET;
62pub const PARAM_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 4;
63pub const MIDI_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET + 8;
64pub const MIDI_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 12;
65pub const ECHO_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET + 16;
66pub const ECHO_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 20;
67pub const MIDI_OUT_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET + 24;
68pub const MIDI_OUT_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 28;
69
70pub const PARAM_EVENT_VALUE: u32 = 0;
73pub const PARAM_EVENT_MOD: u32 = 1;
74pub const PARAM_EVENT_GESTURE_BEGIN: u32 = 2;
75pub const PARAM_EVENT_GESTURE_END: u32 = 3;
76
77#[repr(C, align(16))]
79#[derive(Clone, Copy, Debug, Default)]
80pub struct ParameterEvent {
81 pub param_index: u32,
82 pub value: f32,
83 pub sample_offset: u32,
84 pub event_kind: u32,
85}
86
87#[repr(C, align(16))]
89#[derive(Clone, Copy, Debug, Default)]
90pub struct MidiEvent {
91 pub sample_offset: u32,
92 pub data: [u8; 3],
93 pub channel: u8,
94 pub flags: u16,
95 pub _pad: u16,
96}
97
98#[repr(C, align(256))]
100#[derive(Clone, Copy, Debug)]
101pub struct TransportState {
102 pub playhead_sample: u64,
103 pub tempo: f64,
104 pub numerator: u32,
105 pub denominator: u32,
106 pub flags: u32,
107 pub sample_rate_hz: f64,
108 _pad: [u8; 256 - 40],
109}
110
111impl Default for TransportState {
112 fn default() -> Self {
113 Self {
114 playhead_sample: 0,
115 tempo: 120.0,
116 numerator: 4,
117 denominator: 4,
118 flags: 0,
119 sample_rate_hz: 0.0,
120 _pad: [0; 256 - 40],
121 }
122 }
123}
124
125#[repr(C, align(256))]
127pub struct ShmHeader {
128 pub magic: u32,
129 pub version: u32,
130 pub flags: u32,
131 pub ready: AtomicU32,
132 pub heartbeat: AtomicU32,
133 pub error_code: u32,
134 pub shutdown_request: AtomicU32,
135 pub tasks_issued: AtomicU32,
136 pub tasks_completed: AtomicU32,
137 pub block_size: AtomicU32,
138 pub num_input_channels: AtomicU32,
139 pub num_output_channels: AtomicU32,
140 pub request_type: AtomicU32,
143 pub request_status: AtomicU32,
145 pub scratch_size: AtomicU32,
147 pub parent_window: AtomicU64,
149 pub state_dirty: AtomicU32,
151 _pad: [u8; 256 - 76],
152}
153
154impl ShmHeader {
155 pub fn parent_window_usize(&self) -> usize {
157 self.parent_window.load(Ordering::Acquire) as usize
158 }
159
160 pub fn set_parent_window(&self, window: usize) {
163 self.parent_window.store(window as u64, Ordering::Release);
164 }
165}
166
167impl Default for ShmHeader {
168 fn default() -> Self {
169 Self {
170 magic: MAGIC,
171 version: VERSION,
172 flags: 0,
173 ready: AtomicU32::new(0),
174 heartbeat: AtomicU32::new(0),
175 error_code: 0,
176 shutdown_request: AtomicU32::new(0),
177 tasks_issued: AtomicU32::new(0),
178 tasks_completed: AtomicU32::new(0),
179 block_size: AtomicU32::new(0),
180 num_input_channels: AtomicU32::new(0),
181 num_output_channels: AtomicU32::new(0),
182 request_type: AtomicU32::new(0),
183 request_status: AtomicU32::new(0),
184 scratch_size: AtomicU32::new(0),
185 parent_window: AtomicU64::new(0),
186 state_dirty: AtomicU32::new(0),
187 _pad: [0; 256 - 76],
188 }
189 }
190}
191
192pub unsafe fn init_shm_layout(ptr: *mut u8, size: usize) {
199 unsafe {
200 std::ptr::write_bytes(ptr, 0, size);
201 let header = ptr as *mut ShmHeader;
202 std::ptr::write(header, ShmHeader::default());
203 }
204}
205
206pub unsafe fn header_ref(ptr: *mut u8) -> &'static ShmHeader {
211 unsafe { &*(ptr as *mut ShmHeader) }
212}
213
214pub unsafe fn header_mut(ptr: *mut u8) -> &'static mut ShmHeader {
219 unsafe { &mut *(ptr as *mut ShmHeader) }
220}
221
222pub unsafe fn audio_ptr(ptr: *mut u8) -> *mut f32 {
227 unsafe { ptr.add(AUDIO_OFFSET) as *mut f32 }
228}
229
230pub unsafe fn audio_channel_ptr(ptr: *mut u8, channel: usize, bus: usize) -> *mut f32 {
238 let plane_size = MAX_BLOCK_SIZE * std::mem::size_of::<f32>();
239 let offset = AUDIO_OFFSET + (channel * NUM_BUSES + bus) * plane_size;
240 unsafe { ptr.add(offset) as *mut f32 }
241}
242
243pub unsafe fn param_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
248 unsafe { ptr.add(PARAM_RING_OFFSET) as *mut ParameterEvent }
249}
250
251pub unsafe fn param_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
256 unsafe {
257 (
258 ptr.add(PARAM_WRITE_IDX_OFFSET) as *mut AtomicU32,
259 ptr.add(PARAM_READ_IDX_OFFSET) as *mut AtomicU32,
260 )
261 }
262}
263
264pub unsafe fn midi_ring_ptr(ptr: *mut u8) -> *mut MidiEvent {
269 unsafe { ptr.add(MIDI_RING_OFFSET) as *mut MidiEvent }
270}
271
272pub unsafe fn midi_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
277 unsafe {
278 (
279 ptr.add(MIDI_WRITE_IDX_OFFSET) as *mut AtomicU32,
280 ptr.add(MIDI_READ_IDX_OFFSET) as *mut AtomicU32,
281 )
282 }
283}
284
285pub unsafe fn echo_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
290 unsafe { ptr.add(ECHO_RING_OFFSET) as *mut ParameterEvent }
291}
292
293pub unsafe fn echo_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
298 unsafe {
299 (
300 ptr.add(ECHO_WRITE_IDX_OFFSET) as *mut AtomicU32,
301 ptr.add(ECHO_READ_IDX_OFFSET) as *mut AtomicU32,
302 )
303 }
304}
305
306pub unsafe fn midi_out_ring_ptr(ptr: *mut u8) -> *mut MidiEvent {
311 unsafe { ptr.add(MIDI_OUT_RING_OFFSET) as *mut MidiEvent }
312}
313
314pub unsafe fn midi_out_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
319 unsafe {
320 (
321 ptr.add(MIDI_OUT_WRITE_IDX_OFFSET) as *mut AtomicU32,
322 ptr.add(MIDI_OUT_READ_IDX_OFFSET) as *mut AtomicU32,
323 )
324 }
325}
326
327pub unsafe fn transport_ref(ptr: *mut u8) -> &'static TransportState {
332 unsafe { &*(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
333}
334
335pub unsafe fn transport_mut(ptr: *mut u8) -> &'static mut TransportState {
340 unsafe { &mut *(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
341}
342
343pub unsafe fn scratch_ptr(ptr: *mut u8) -> *mut u8 {
348 unsafe { ptr.add(SCRATCH_OFFSET) }
349}
350
351pub unsafe fn write_plugin_name_to_scratch(ptr: *mut u8, name: &str) {
357 unsafe {
358 let scratch = scratch_ptr(ptr);
359 let bytes = name.as_bytes();
360 let len = bytes.len().min(SCRATCH_SIZE - 4);
361 std::ptr::write_unaligned(scratch as *mut u32, len as u32);
362 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(4), len);
363 }
364}
365
366pub unsafe fn read_plugin_name_from_scratch(ptr: *mut u8) -> Option<String> {
371 unsafe {
372 let scratch = scratch_ptr(ptr);
373 let len = std::ptr::read_unaligned(scratch as *mut u32) as usize;
374 if len == 0 || len > SCRATCH_SIZE - 4 {
375 return None;
376 }
377 let bytes = std::slice::from_raw_parts(scratch.add(4), len);
378 String::from_utf8(bytes.to_vec()).ok()
379 }
380}
381
382pub const PORT_COUNTS_MAGIC: u32 = 0x504F_5254; const PORT_COUNTS_OFFSET: usize = 1024;
387
388pub unsafe fn write_port_counts_to_scratch(
393 ptr: *mut u8,
394 audio_in: u32,
395 audio_out: u32,
396 midi_in: u32,
397 midi_out: u32,
398) {
399 unsafe {
400 let dest = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
401 std::ptr::write_unaligned(dest as *mut u32, PORT_COUNTS_MAGIC);
402 std::ptr::write_unaligned(dest.add(4) as *mut u32, audio_in);
403 std::ptr::write_unaligned(dest.add(8) as *mut u32, audio_out);
404 std::ptr::write_unaligned(dest.add(12) as *mut u32, midi_in);
405 std::ptr::write_unaligned(dest.add(16) as *mut u32, midi_out);
406 }
407}
408
409pub unsafe fn read_port_counts_from_scratch(ptr: *mut u8) -> Option<(u32, u32, u32, u32)> {
414 unsafe {
415 let src = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
416 let magic = std::ptr::read_unaligned(src as *mut u32);
417 if magic != PORT_COUNTS_MAGIC {
418 return None;
419 }
420 let audio_in = std::ptr::read_unaligned(src.add(4) as *mut u32);
421 let audio_out = std::ptr::read_unaligned(src.add(8) as *mut u32);
422 let midi_in = std::ptr::read_unaligned(src.add(12) as *mut u32);
423 let midi_out = std::ptr::read_unaligned(src.add(16) as *mut u32);
424 Some((audio_in, audio_out, midi_in, midi_out))
425 }
426}
427
428pub const FILE_REFS_MAGIC: u32 = 0x4649_4C45; const FILE_REFS_OFFSET: usize = 2048;
433
434const FILE_REFS_MAX_SIZE: usize = SCRATCH_SIZE - FILE_REFS_OFFSET;
436
437pub type FileReference = (u32, String);
439
440pub unsafe fn write_file_references_to_scratch(
447 ptr: *mut u8,
448 refs: &[FileReference],
449) -> Result<(), String> {
450 unsafe {
451 let mut dest = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
452 let mut remaining = FILE_REFS_MAX_SIZE;
453 if remaining < 8 {
454 return Err("scratch too small for file references".to_string());
455 }
456 std::ptr::write_unaligned(dest as *mut u32, FILE_REFS_MAGIC);
457 dest = dest.add(4);
458 remaining -= 4;
459 let count = refs.len().min(u32::MAX as usize) as u32;
460 std::ptr::write_unaligned(dest as *mut u32, count);
461 dest = dest.add(4);
462 remaining -= 4;
463 for (index, path) in refs.iter().take(count as usize) {
464 if remaining < 8 {
465 return Err("scratch overflow writing file references".to_string());
466 }
467 std::ptr::write_unaligned(dest as *mut u32, *index);
468 dest = dest.add(4);
469 remaining -= 4;
470 let bytes = path.as_bytes();
471 let len = bytes
472 .len()
473 .min(u32::MAX as usize)
474 .min(remaining.saturating_sub(4));
475 if len < bytes.len() {
476 return Err("scratch overflow writing file references".to_string());
477 }
478 std::ptr::write_unaligned(dest as *mut u32, len as u32);
479 dest = dest.add(4);
480 remaining -= 4;
481 std::ptr::copy_nonoverlapping(bytes.as_ptr(), dest, len);
482 dest = dest.add(len);
483 remaining -= len;
484 }
485 Ok(())
486 }
487}
488
489pub unsafe fn read_file_references_from_scratch(ptr: *mut u8) -> Option<Vec<FileReference>> {
494 unsafe {
495 let mut src = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
496 let mut remaining = FILE_REFS_MAX_SIZE;
497 if remaining < 8 {
498 return None;
499 }
500 let magic = std::ptr::read_unaligned(src as *mut u32);
501 if magic != FILE_REFS_MAGIC {
502 return None;
503 }
504 src = src.add(4);
505 remaining -= 4;
506 let count = std::ptr::read_unaligned(src as *mut u32) as usize;
507 src = src.add(4);
508 remaining -= 4;
509 let mut refs = Vec::with_capacity(count);
510 for _ in 0..count {
511 if remaining < 8 {
512 return None;
513 }
514 let index = std::ptr::read_unaligned(src as *mut u32);
515 src = src.add(4);
516 remaining -= 4;
517 let len = std::ptr::read_unaligned(src as *mut u32) as usize;
518 src = src.add(4);
519 remaining -= 4;
520 if len > remaining {
521 return None;
522 }
523 let bytes = std::slice::from_raw_parts(src, len);
524 let path = String::from_utf8(bytes.to_vec()).ok()?;
525 refs.push((index, path));
526 src = src.add(len);
527 remaining -= len;
528 }
529 Some(refs)
530 }
531}
532
533pub unsafe fn write_resource_directory_to_scratch(ptr: *mut u8, path: &str) -> Result<(), String> {
539 unsafe {
540 let scratch = scratch_ptr(ptr);
541 let bytes = path.as_bytes();
542 let len = bytes.len().min(SCRATCH_SIZE - 8);
543 if len < bytes.len() {
544 return Err("resource directory path too long".to_string());
545 }
546 std::ptr::write_unaligned(scratch as *mut u32, FILE_REFS_MAGIC);
547 std::ptr::write_unaligned(scratch.add(4) as *mut u32, len as u32);
548 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(8), len);
549 Ok(())
550 }
551}
552
553pub unsafe fn read_resource_directory_from_scratch(ptr: *mut u8) -> Option<String> {
558 unsafe {
559 let scratch = scratch_ptr(ptr);
560 let magic = std::ptr::read_unaligned(scratch as *mut u32);
561 if magic != FILE_REFS_MAGIC {
562 return None;
563 }
564 let len = std::ptr::read_unaligned(scratch.add(4) as *mut u32) as usize;
565 if len == 0 || len > SCRATCH_SIZE - 8 {
566 return None;
567 }
568 let bytes = std::slice::from_raw_parts(scratch.add(8), len);
569 String::from_utf8(bytes.to_vec()).ok()
570 }
571}
572
573pub const FILE_REF_UPDATE_MAGIC: u32 = 0x5550_4441; pub unsafe fn write_file_reference_update_to_scratch(
582 ptr: *mut u8,
583 index: u32,
584 path: &str,
585) -> Result<(), String> {
586 unsafe {
587 let scratch = scratch_ptr(ptr);
588 let bytes = path.as_bytes();
589 let len = bytes.len().min(SCRATCH_SIZE - 12);
590 if len < bytes.len() {
591 return Err("file-reference update path too long".to_string());
592 }
593 std::ptr::write_unaligned(scratch as *mut u32, FILE_REF_UPDATE_MAGIC);
594 std::ptr::write_unaligned(scratch.add(4) as *mut u32, index);
595 std::ptr::write_unaligned(scratch.add(8) as *mut u32, len as u32);
596 std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(12), len);
597 Ok(())
598 }
599}
600
601pub unsafe fn read_file_reference_update_from_scratch(ptr: *mut u8) -> Option<(u32, String)> {
606 unsafe {
607 let scratch = scratch_ptr(ptr);
608 let magic = std::ptr::read_unaligned(scratch as *mut u32);
609 if magic != FILE_REF_UPDATE_MAGIC {
610 return None;
611 }
612 let index = std::ptr::read_unaligned(scratch.add(4) as *mut u32);
613 let len = std::ptr::read_unaligned(scratch.add(8) as *mut u32) as usize;
614 if len == 0 || len > SCRATCH_SIZE - 12 {
615 return None;
616 }
617 let bytes = std::slice::from_raw_parts(scratch.add(12), len);
618 let path = String::from_utf8(bytes.to_vec()).ok()?;
619 Some((index, path))
620 }
621}
622
623const _: () = assert!(std::mem::size_of::<ShmHeader>() == 256);
626const _: () = assert!(std::mem::align_of::<ShmHeader>() == 256);
627const _: () = assert!(std::mem::size_of::<ParameterEvent>() == 16);
628const _: () = assert!(std::mem::align_of::<ParameterEvent>() == 16);
629const _: () = assert!(std::mem::size_of::<MidiEvent>() == 16);
630const _: () = assert!(std::mem::align_of::<MidiEvent>() == 16);
631const _: () = assert!(std::mem::size_of::<TransportState>() == 256);
632const _: () = assert!(std::mem::align_of::<TransportState>() == 256);
633const _: () = assert!(LAYOUT_SIZE <= SHM_SIZE);
634
635pub fn wait_for_ready(header: &ShmHeader, timeout: std::time::Duration) -> bool {
637 let start = std::time::Instant::now();
638 while header.ready.load(Ordering::Acquire) == 0 {
639 if start.elapsed() >= timeout {
640 return false;
641 }
642 std::thread::yield_now();
643 }
644 true
645}