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,
142 pub request_status: AtomicU32,
144 pub scratch_size: AtomicU32,
146 pub parent_window: AtomicU64,
148 _pad: [u8; 256 - 72],
149}
150
151impl ShmHeader {
152 pub fn parent_window_usize(&self) -> usize {
154 self.parent_window.load(Ordering::Acquire) as usize
155 }
156
157 pub fn set_parent_window(&self, window: usize) {
160 self.parent_window.store(window as u64, Ordering::Release);
161 }
162}
163
164impl Default for ShmHeader {
165 fn default() -> Self {
166 Self {
167 magic: MAGIC,
168 version: VERSION,
169 flags: 0,
170 ready: AtomicU32::new(0),
171 heartbeat: AtomicU32::new(0),
172 error_code: 0,
173 shutdown_request: AtomicU32::new(0),
174 tasks_issued: AtomicU32::new(0),
175 tasks_completed: AtomicU32::new(0),
176 block_size: AtomicU32::new(0),
177 num_input_channels: AtomicU32::new(0),
178 num_output_channels: AtomicU32::new(0),
179 request_type: AtomicU32::new(0),
180 request_status: AtomicU32::new(0),
181 scratch_size: AtomicU32::new(0),
182 parent_window: AtomicU64::new(0),
183 _pad: [0; 256 - 72],
184 }
185 }
186}
187
188pub unsafe fn init_shm_layout(ptr: *mut u8, size: usize) {
195 unsafe {
196 std::ptr::write_bytes(ptr, 0, size);
197 let header = ptr as *mut ShmHeader;
198 std::ptr::write(header, ShmHeader::default());
199 }
200}
201
202pub unsafe fn header_ref(ptr: *mut u8) -> &'static ShmHeader {
207 unsafe { &*(ptr as *mut ShmHeader) }
208}
209
210pub unsafe fn header_mut(ptr: *mut u8) -> &'static mut ShmHeader {
215 unsafe { &mut *(ptr as *mut ShmHeader) }
216}
217
218pub unsafe fn audio_ptr(ptr: *mut u8) -> *mut f32 {
223 unsafe { ptr.add(AUDIO_OFFSET) as *mut f32 }
224}
225
226pub unsafe fn audio_channel_ptr(ptr: *mut u8, channel: usize, bus: usize) -> *mut f32 {
234 let plane_size = MAX_BLOCK_SIZE * std::mem::size_of::<f32>();
235 let offset = AUDIO_OFFSET + (channel * NUM_BUSES + bus) * plane_size;
236 unsafe { ptr.add(offset) as *mut f32 }
237}
238
239pub unsafe fn param_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
244 unsafe { ptr.add(PARAM_RING_OFFSET) as *mut ParameterEvent }
245}
246
247pub unsafe fn param_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
252 unsafe {
253 (
254 ptr.add(PARAM_WRITE_IDX_OFFSET) as *mut AtomicU32,
255 ptr.add(PARAM_READ_IDX_OFFSET) as *mut AtomicU32,
256 )
257 }
258}
259
260pub unsafe fn midi_ring_ptr(ptr: *mut u8) -> *mut MidiEvent {
265 unsafe { ptr.add(MIDI_RING_OFFSET) as *mut MidiEvent }
266}
267
268pub unsafe fn midi_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
273 unsafe {
274 (
275 ptr.add(MIDI_WRITE_IDX_OFFSET) as *mut AtomicU32,
276 ptr.add(MIDI_READ_IDX_OFFSET) as *mut AtomicU32,
277 )
278 }
279}
280
281pub unsafe fn echo_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
286 unsafe { ptr.add(ECHO_RING_OFFSET) as *mut ParameterEvent }
287}
288
289pub unsafe fn echo_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
294 unsafe {
295 (
296 ptr.add(ECHO_WRITE_IDX_OFFSET) as *mut AtomicU32,
297 ptr.add(ECHO_READ_IDX_OFFSET) as *mut AtomicU32,
298 )
299 }
300}
301
302pub unsafe fn midi_out_ring_ptr(ptr: *mut u8) -> *mut MidiEvent {
307 unsafe { ptr.add(MIDI_OUT_RING_OFFSET) as *mut MidiEvent }
308}
309
310pub unsafe fn midi_out_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
315 unsafe {
316 (
317 ptr.add(MIDI_OUT_WRITE_IDX_OFFSET) as *mut AtomicU32,
318 ptr.add(MIDI_OUT_READ_IDX_OFFSET) as *mut AtomicU32,
319 )
320 }
321}
322
323pub unsafe fn transport_ref(ptr: *mut u8) -> &'static TransportState {
328 unsafe { &*(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
329}
330
331pub unsafe fn transport_mut(ptr: *mut u8) -> &'static mut TransportState {
336 unsafe { &mut *(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
337}
338
339pub unsafe fn scratch_ptr(ptr: *mut u8) -> *mut u8 {
344 unsafe { ptr.add(SCRATCH_OFFSET) }
345}
346
347const _: () = assert!(std::mem::size_of::<ShmHeader>() == 256);
350const _: () = assert!(std::mem::align_of::<ShmHeader>() == 256);
351const _: () = assert!(std::mem::size_of::<ParameterEvent>() == 16);
352const _: () = assert!(std::mem::align_of::<ParameterEvent>() == 16);
353const _: () = assert!(std::mem::size_of::<MidiEvent>() == 16);
354const _: () = assert!(std::mem::align_of::<MidiEvent>() == 16);
355const _: () = assert!(std::mem::size_of::<TransportState>() == 256);
356const _: () = assert!(std::mem::align_of::<TransportState>() == 256);
357const _: () = assert!(LAYOUT_SIZE <= SHM_SIZE);
358
359pub fn wait_for_ready(header: &ShmHeader, timeout: std::time::Duration) -> bool {
361 let start = std::time::Instant::now();
362 while header.ready.load(Ordering::Acquire) == 0 {
363 if start.elapsed() >= timeout {
364 return false;
365 }
366 std::thread::yield_now();
367 }
368 true
369}