use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
pub const MAGIC: u32 = 0x4D41_4F4C;
pub const VERSION: u32 = 4;
pub const MAX_CHANNELS: usize = 32;
pub const NUM_BUSES: usize = 2;
pub const MAX_BLOCK_SIZE: usize = 4096;
pub const RING_CAPACITY: usize = 4096;
pub const MAX_MIDI_PORTS: usize = 16;
pub const HEADER_SIZE: usize = 256;
pub const CONTROL_SIZE: usize = 256;
pub 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>();
pub const MIDI_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<MidiEvent>();
pub const MIDI_PORT_RING_SIZE: usize = {
let raw = 8 + MIDI_RING_SIZE; (raw + 15) & !15 };
pub const TRANSPORT_SIZE: usize = 256;
pub const SCRATCH_SIZE: usize = 65536;
pub const CONTROL_OFFSET: usize = HEADER_SIZE;
pub const AUDIO_OFFSET: usize = HEADER_SIZE + CONTROL_SIZE;
pub const PARAM_RING_OFFSET: usize = AUDIO_OFFSET + AUDIO_BUFFER_SIZE;
pub const ECHO_RING_OFFSET: usize = PARAM_RING_OFFSET + PARAM_RING_SIZE;
pub const ECHO_RING_SIZE: usize = RING_CAPACITY * std::mem::size_of::<ParameterEvent>();
pub const MIDI_IN_RINGS_OFFSET: usize = {
let end = ECHO_RING_OFFSET + ECHO_RING_SIZE;
(end + 255) & !255
};
pub const MIDI_IN_RINGS_SIZE: usize = MAX_MIDI_PORTS * MIDI_PORT_RING_SIZE;
pub const MIDI_OUT_RINGS_OFFSET: usize = MIDI_IN_RINGS_OFFSET + MIDI_IN_RINGS_SIZE;
pub const MIDI_OUT_RINGS_SIZE: usize = MAX_MIDI_PORTS * MIDI_PORT_RING_SIZE;
pub const TRANSPORT_OFFSET: usize = {
let end = MIDI_OUT_RINGS_OFFSET + MIDI_OUT_RINGS_SIZE;
(end + 255) & !255
};
pub const SCRATCH_OFFSET: usize = TRANSPORT_OFFSET + TRANSPORT_SIZE;
pub const LAYOUT_SIZE: usize = SCRATCH_OFFSET + SCRATCH_SIZE;
pub const SHM_SIZE: usize = 4 * 1024 * 1024;
pub const PARAM_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET;
pub const PARAM_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 4;
pub const ECHO_WRITE_IDX_OFFSET: usize = CONTROL_OFFSET + 8;
pub const ECHO_READ_IDX_OFFSET: usize = CONTROL_OFFSET + 12;
pub const PARAM_EVENT_VALUE: u32 = 0;
pub const PARAM_EVENT_MOD: u32 = 1;
pub const PARAM_EVENT_GESTURE_BEGIN: u32 = 2;
pub const PARAM_EVENT_GESTURE_END: u32 = 3;
#[repr(C, align(16))]
#[derive(Clone, Copy, Debug, Default)]
pub struct ParameterEvent {
pub param_index: u32,
pub value: f32,
pub sample_offset: u32,
pub event_kind: u32,
}
#[repr(C, align(16))]
#[derive(Clone, Copy, Debug, Default)]
pub struct MidiEvent {
pub sample_offset: u32,
pub data: [u8; 3],
pub channel: u8,
pub flags: u16,
pub _pad: u16,
}
#[repr(C, align(256))]
#[derive(Clone, Copy, Debug)]
pub struct TransportState {
pub playhead_sample: u64,
pub tempo: f64,
pub numerator: u32,
pub denominator: u32,
pub flags: u32,
pub sample_rate_hz: f64,
_pad: [u8; 256 - 40],
}
impl Default for TransportState {
fn default() -> Self {
Self {
playhead_sample: 0,
tempo: 120.0,
numerator: 4,
denominator: 4,
flags: 0,
sample_rate_hz: 0.0,
_pad: [0; 256 - 40],
}
}
}
#[repr(C, align(256))]
pub struct ShmHeader {
pub magic: u32,
pub version: u32,
pub flags: u32,
pub ready: AtomicU32,
pub heartbeat: AtomicU32,
pub error_code: u32,
pub shutdown_request: AtomicU32,
pub tasks_issued: AtomicU32,
pub tasks_completed: AtomicU32,
pub block_size: AtomicU32,
pub num_input_channels: AtomicU32,
pub num_output_channels: AtomicU32,
pub midi_in_port_count: AtomicU32,
pub midi_out_port_count: AtomicU32,
pub request_type: AtomicU32,
pub request_status: AtomicU32,
pub scratch_size: AtomicU32,
pub parent_window: AtomicU64,
pub state_dirty: AtomicU32,
_pad: [u8; 256 - 84],
}
impl ShmHeader {
pub fn parent_window_usize(&self) -> usize {
self.parent_window.load(Ordering::Acquire) as usize
}
pub fn set_parent_window(&self, window: usize) {
self.parent_window.store(window as u64, Ordering::Release);
}
}
impl Default for ShmHeader {
fn default() -> Self {
Self {
magic: MAGIC,
version: VERSION,
flags: 0,
ready: AtomicU32::new(0),
heartbeat: AtomicU32::new(0),
error_code: 0,
shutdown_request: AtomicU32::new(0),
tasks_issued: AtomicU32::new(0),
tasks_completed: AtomicU32::new(0),
block_size: AtomicU32::new(0),
num_input_channels: AtomicU32::new(0),
num_output_channels: AtomicU32::new(0),
midi_in_port_count: AtomicU32::new(0),
midi_out_port_count: AtomicU32::new(0),
request_type: AtomicU32::new(0),
request_status: AtomicU32::new(0),
scratch_size: AtomicU32::new(0),
parent_window: AtomicU64::new(0),
state_dirty: AtomicU32::new(0),
_pad: [0; 256 - 84],
}
}
}
pub unsafe fn init_shm_layout(ptr: *mut u8, size: usize) {
unsafe {
std::ptr::write_bytes(ptr, 0, size);
let header = ptr as *mut ShmHeader;
std::ptr::write(header, ShmHeader::default());
}
}
pub unsafe fn header_ref(ptr: *mut u8) -> &'static ShmHeader {
unsafe { &*(ptr as *mut ShmHeader) }
}
pub unsafe fn header_mut(ptr: *mut u8) -> &'static mut ShmHeader {
unsafe { &mut *(ptr as *mut ShmHeader) }
}
pub unsafe fn audio_ptr(ptr: *mut u8) -> *mut f32 {
unsafe { ptr.add(AUDIO_OFFSET) as *mut f32 }
}
pub unsafe fn audio_channel_ptr(ptr: *mut u8, channel: usize, bus: usize) -> *mut f32 {
let plane_size = MAX_BLOCK_SIZE * std::mem::size_of::<f32>();
let offset = AUDIO_OFFSET + (channel * NUM_BUSES + bus) * plane_size;
unsafe { ptr.add(offset) as *mut f32 }
}
pub unsafe fn param_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
unsafe { ptr.add(PARAM_RING_OFFSET) as *mut ParameterEvent }
}
pub unsafe fn param_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
unsafe {
(
ptr.add(PARAM_WRITE_IDX_OFFSET) as *mut AtomicU32,
ptr.add(PARAM_READ_IDX_OFFSET) as *mut AtomicU32,
)
}
}
pub unsafe fn echo_ring_ptr(ptr: *mut u8) -> *mut ParameterEvent {
unsafe { ptr.add(ECHO_RING_OFFSET) as *mut ParameterEvent }
}
pub unsafe fn echo_indices(ptr: *mut u8) -> (*mut AtomicU32, *mut AtomicU32) {
unsafe {
(
ptr.add(ECHO_WRITE_IDX_OFFSET) as *mut AtomicU32,
ptr.add(ECHO_READ_IDX_OFFSET) as *mut AtomicU32,
)
}
}
const fn midi_port_ring_offset(base_offset: usize, port: usize) -> usize {
base_offset + port * MIDI_PORT_RING_SIZE
}
pub unsafe fn midi_in_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
unsafe {
let base = ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port));
(base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
}
}
pub unsafe fn midi_in_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
unsafe { ptr.add(midi_port_ring_offset(MIDI_IN_RINGS_OFFSET, port) + 8) as *mut MidiEvent }
}
pub unsafe fn midi_out_indices(ptr: *mut u8, port: usize) -> (*mut AtomicU32, *mut AtomicU32) {
unsafe {
let base = ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port));
(base as *mut AtomicU32, base.add(4) as *mut AtomicU32)
}
}
pub unsafe fn midi_out_ring_ptr(ptr: *mut u8, port: usize) -> *mut MidiEvent {
unsafe { ptr.add(midi_port_ring_offset(MIDI_OUT_RINGS_OFFSET, port) + 8) as *mut MidiEvent }
}
pub unsafe fn transport_ref(ptr: *mut u8) -> &'static TransportState {
unsafe { &*(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
}
pub unsafe fn transport_mut(ptr: *mut u8) -> &'static mut TransportState {
unsafe { &mut *(ptr.add(TRANSPORT_OFFSET) as *mut TransportState) }
}
pub unsafe fn scratch_ptr(ptr: *mut u8) -> *mut u8 {
unsafe { ptr.add(SCRATCH_OFFSET) }
}
pub unsafe fn write_plugin_name_to_scratch(ptr: *mut u8, name: &str) {
unsafe {
let scratch = scratch_ptr(ptr);
let bytes = name.as_bytes();
let len = bytes.len().min(SCRATCH_SIZE - 4);
std::ptr::write_unaligned(scratch as *mut u32, len as u32);
std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(4), len);
}
}
pub unsafe fn read_plugin_name_from_scratch(ptr: *mut u8) -> Option<String> {
unsafe {
let scratch = scratch_ptr(ptr);
let len = std::ptr::read_unaligned(scratch as *mut u32) as usize;
if len == 0 || len > SCRATCH_SIZE - 4 {
return None;
}
let bytes = std::slice::from_raw_parts(scratch.add(4), len);
String::from_utf8(bytes.to_vec()).ok()
}
}
pub const PORT_COUNTS_MAGIC: u32 = 0x504F_5254;
const PORT_COUNTS_OFFSET: usize = 1024;
pub unsafe fn write_port_counts_to_scratch(
ptr: *mut u8,
audio_in: u32,
audio_out: u32,
midi_in: u32,
midi_out: u32,
) {
unsafe {
let dest = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
std::ptr::write_unaligned(dest as *mut u32, PORT_COUNTS_MAGIC);
std::ptr::write_unaligned(dest.add(4) as *mut u32, audio_in);
std::ptr::write_unaligned(dest.add(8) as *mut u32, audio_out);
std::ptr::write_unaligned(dest.add(12) as *mut u32, midi_in);
std::ptr::write_unaligned(dest.add(16) as *mut u32, midi_out);
}
}
pub unsafe fn read_port_counts_from_scratch(ptr: *mut u8) -> Option<(u32, u32, u32, u32)> {
unsafe {
let src = scratch_ptr(ptr).add(PORT_COUNTS_OFFSET);
let magic = std::ptr::read_unaligned(src as *mut u32);
if magic != PORT_COUNTS_MAGIC {
return None;
}
let audio_in = std::ptr::read_unaligned(src.add(4) as *mut u32);
let audio_out = std::ptr::read_unaligned(src.add(8) as *mut u32);
let midi_in = std::ptr::read_unaligned(src.add(12) as *mut u32);
let midi_out = std::ptr::read_unaligned(src.add(16) as *mut u32);
Some((audio_in, audio_out, midi_in, midi_out))
}
}
pub const FILE_REFS_MAGIC: u32 = 0x4649_4C45;
const FILE_REFS_OFFSET: usize = 2048;
const FILE_REFS_MAX_SIZE: usize = SCRATCH_SIZE - FILE_REFS_OFFSET;
pub type FileReference = (u32, String);
pub unsafe fn write_file_references_to_scratch(
ptr: *mut u8,
refs: &[FileReference],
) -> Result<(), String> {
unsafe {
let mut dest = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
let mut remaining = FILE_REFS_MAX_SIZE;
if remaining < 8 {
return Err("scratch too small for file references".to_string());
}
std::ptr::write_unaligned(dest as *mut u32, FILE_REFS_MAGIC);
dest = dest.add(4);
remaining -= 4;
let count = refs.len().min(u32::MAX as usize) as u32;
std::ptr::write_unaligned(dest as *mut u32, count);
dest = dest.add(4);
remaining -= 4;
for (index, path) in refs.iter().take(count as usize) {
if remaining < 8 {
return Err("scratch overflow writing file references".to_string());
}
std::ptr::write_unaligned(dest as *mut u32, *index);
dest = dest.add(4);
remaining -= 4;
let bytes = path.as_bytes();
let len = bytes
.len()
.min(u32::MAX as usize)
.min(remaining.saturating_sub(4));
if len < bytes.len() {
return Err("scratch overflow writing file references".to_string());
}
std::ptr::write_unaligned(dest as *mut u32, len as u32);
dest = dest.add(4);
remaining -= 4;
std::ptr::copy_nonoverlapping(bytes.as_ptr(), dest, len);
dest = dest.add(len);
remaining -= len;
}
Ok(())
}
}
pub unsafe fn read_file_references_from_scratch(ptr: *mut u8) -> Option<Vec<FileReference>> {
unsafe {
let mut src = scratch_ptr(ptr).add(FILE_REFS_OFFSET);
let mut remaining = FILE_REFS_MAX_SIZE;
if remaining < 8 {
return None;
}
let magic = std::ptr::read_unaligned(src as *mut u32);
if magic != FILE_REFS_MAGIC {
return None;
}
src = src.add(4);
remaining -= 4;
let count = std::ptr::read_unaligned(src as *mut u32) as usize;
src = src.add(4);
remaining -= 4;
let mut refs = Vec::with_capacity(count);
for _ in 0..count {
if remaining < 8 {
return None;
}
let index = std::ptr::read_unaligned(src as *mut u32);
src = src.add(4);
remaining -= 4;
let len = std::ptr::read_unaligned(src as *mut u32) as usize;
src = src.add(4);
remaining -= 4;
if len > remaining {
return None;
}
let bytes = std::slice::from_raw_parts(src, len);
let path = String::from_utf8(bytes.to_vec()).ok()?;
refs.push((index, path));
src = src.add(len);
remaining -= len;
}
Some(refs)
}
}
pub unsafe fn write_resource_directory_to_scratch(ptr: *mut u8, path: &str) -> Result<(), String> {
unsafe {
let scratch = scratch_ptr(ptr);
let bytes = path.as_bytes();
let len = bytes.len().min(SCRATCH_SIZE - 8);
if len < bytes.len() {
return Err("resource directory path too long".to_string());
}
std::ptr::write_unaligned(scratch as *mut u32, FILE_REFS_MAGIC);
std::ptr::write_unaligned(scratch.add(4) as *mut u32, len as u32);
std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(8), len);
Ok(())
}
}
pub unsafe fn read_resource_directory_from_scratch(ptr: *mut u8) -> Option<String> {
unsafe {
let scratch = scratch_ptr(ptr);
let magic = std::ptr::read_unaligned(scratch as *mut u32);
if magic != FILE_REFS_MAGIC {
return None;
}
let len = std::ptr::read_unaligned(scratch.add(4) as *mut u32) as usize;
if len == 0 || len > SCRATCH_SIZE - 8 {
return None;
}
let bytes = std::slice::from_raw_parts(scratch.add(8), len);
String::from_utf8(bytes.to_vec()).ok()
}
}
pub const FILE_REF_UPDATE_MAGIC: u32 = 0x5550_4441;
pub unsafe fn write_file_reference_update_to_scratch(
ptr: *mut u8,
index: u32,
path: &str,
) -> Result<(), String> {
unsafe {
let scratch = scratch_ptr(ptr);
let bytes = path.as_bytes();
let len = bytes.len().min(SCRATCH_SIZE - 12);
if len < bytes.len() {
return Err("file-reference update path too long".to_string());
}
std::ptr::write_unaligned(scratch as *mut u32, FILE_REF_UPDATE_MAGIC);
std::ptr::write_unaligned(scratch.add(4) as *mut u32, index);
std::ptr::write_unaligned(scratch.add(8) as *mut u32, len as u32);
std::ptr::copy_nonoverlapping(bytes.as_ptr(), scratch.add(12), len);
Ok(())
}
}
pub unsafe fn read_file_reference_update_from_scratch(ptr: *mut u8) -> Option<(u32, String)> {
unsafe {
let scratch = scratch_ptr(ptr);
let magic = std::ptr::read_unaligned(scratch as *mut u32);
if magic != FILE_REF_UPDATE_MAGIC {
return None;
}
let index = std::ptr::read_unaligned(scratch.add(4) as *mut u32);
let len = std::ptr::read_unaligned(scratch.add(8) as *mut u32) as usize;
if len == 0 || len > SCRATCH_SIZE - 12 {
return None;
}
let bytes = std::slice::from_raw_parts(scratch.add(12), len);
let path = String::from_utf8(bytes.to_vec()).ok()?;
Some((index, path))
}
}
const _: () = assert!(std::mem::size_of::<ShmHeader>() == 256);
const _: () = assert!(std::mem::align_of::<ShmHeader>() == 256);
const _: () = assert!(std::mem::size_of::<ParameterEvent>() == 16);
const _: () = assert!(std::mem::align_of::<ParameterEvent>() == 16);
const _: () = assert!(std::mem::size_of::<MidiEvent>() == 16);
const _: () = assert!(std::mem::align_of::<MidiEvent>() == 16);
const _: () = assert!(std::mem::size_of::<TransportState>() == 256);
const _: () = assert!(std::mem::align_of::<TransportState>() == 256);
const _: () = assert!(LAYOUT_SIZE <= SHM_SIZE);
pub fn wait_for_ready(header: &ShmHeader, timeout: std::time::Duration) -> bool {
let start = std::time::Instant::now();
while header.ready.load(Ordering::Acquire) == 0 {
if start.elapsed() >= timeout {
return false;
}
std::thread::yield_now();
}
true
}