#![allow(clippy::cast_ptr_alignment)]
use std::ffi::c_void;
use truce_core::cast::{len_u32, sample_pos_i64};
use truce_core::events::{Event, EventBody, EventList, TransportInfo};
use truce_core::midi::{pitch_bend_from_bytes, pitch_bend_to_bytes};
use crate::urid::{Urid, UridMap};
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Atom {
pub size: u32,
pub type_: Urid,
}
#[repr(C)]
pub struct AtomSequenceBody {
pub unit: Urid,
pub pad: u32,
}
#[repr(C)]
pub struct AtomSequence {
pub atom: Atom,
pub body: AtomSequenceBody,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct AtomEventHeader {
time_frames: i64,
body: Atom,
}
pub struct AtomSequenceReader<'a> {
seq: *const AtomSequence,
urid: &'a UridMap,
}
impl<'a> AtomSequenceReader<'a> {
pub fn new(seq: *const AtomSequence, urid: &'a UridMap) -> Self {
Self { seq, urid }
}
pub fn for_each_midi(&self, mut f: impl FnMut(u32, &[u8])) {
if self.seq.is_null() || self.urid.midi_event == 0 {
return;
}
unsafe {
self.walk(|frame, ev_type, body_ptr, body_bytes| {
if ev_type == self.urid.midi_event {
let slice = core::slice::from_raw_parts(body_ptr, body_bytes);
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let frame_u32 = frame.max(0) as u32;
f(frame_u32, slice);
}
});
}
}
pub fn apply_time_position(&self, info: &mut TransportInfo) -> bool {
if self.seq.is_null() || self.urid.time_position == 0 {
return false;
}
let mut found = false;
unsafe {
self.walk(|_, ev_type, body_ptr, body_bytes| {
if ev_type != self.urid.atom_blank && ev_type != self.urid.atom_object {
return;
}
if !self.read_time_position(body_ptr, body_bytes, info) {
return;
}
found = true;
});
}
found
}
unsafe fn walk<F: FnMut(i64, Urid, *const u8, usize)>(&self, mut f: F) {
unsafe {
let seq = &*self.seq;
let body_size = seq.atom.size as usize;
if body_size < core::mem::size_of::<AtomSequenceBody>() {
return;
}
let data_size = body_size - core::mem::size_of::<AtomSequenceBody>();
let data_start = self
.seq
.cast::<u8>()
.add(core::mem::size_of::<AtomSequence>());
let mut offset = 0usize;
while offset + core::mem::size_of::<AtomEventHeader>() <= data_size {
let ev_ptr = data_start.add(offset).cast::<AtomEventHeader>();
let ev = *ev_ptr;
let body_bytes = ev.body.size as usize;
let total = core::mem::size_of::<AtomEventHeader>() + body_bytes;
let padded = (total + 7) & !7;
if offset + padded > data_size {
break;
}
let body_ptr = data_start.add(offset + core::mem::size_of::<AtomEventHeader>());
f(ev.time_frames, ev.body.type_, body_ptr, body_bytes);
offset += padded;
}
}
}
unsafe fn read_time_position(
&self,
body_ptr: *const u8,
body_bytes: usize,
info: &mut TransportInfo,
) -> bool {
unsafe {
let header_size = core::mem::size_of::<Urid>() * 2;
if body_bytes < header_size {
return false;
}
let otype = *body_ptr.add(core::mem::size_of::<Urid>()).cast::<Urid>();
if otype != self.urid.time_position {
return false;
}
let mut bar: Option<f64> = None;
let mut bar_beat: Option<f64> = None;
let mut beats_per_bar: Option<f64> = None;
let mut beat_direct: Option<f64> = None;
let mut offset = header_size;
while offset + core::mem::size_of::<Urid>() * 2 + core::mem::size_of::<Atom>()
<= body_bytes
{
let key = *body_ptr.add(offset).cast::<Urid>();
let value_header = body_ptr.add(offset + core::mem::size_of::<Urid>() * 2);
let value_atom = *value_header.cast::<Atom>();
let value_data = value_header.add(core::mem::size_of::<Atom>());
let value_size = value_atom.size as usize;
let entry_total =
core::mem::size_of::<Urid>() * 2 + core::mem::size_of::<Atom>() + value_size;
let padded = (entry_total + 7) & !7;
if let Some(v) = self.read_atom_number(value_atom.type_, value_data, value_size) {
if !v.is_finite() {
offset += padded;
if padded == 0 {
break;
}
continue;
}
if key == self.urid.time_beats_per_minute {
info.tempo = v;
} else if key == self.urid.time_bar {
bar = Some(v);
} else if key == self.urid.time_bar_beat {
bar_beat = Some(v);
} else if key == self.urid.time_beats_per_bar {
beats_per_bar = Some(v);
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let v_u8 = v.round().clamp(0.0, f64::from(u8::MAX)) as u8;
info.time_sig_num = v_u8;
} else if key == self.urid.time_beat {
beat_direct = Some(v);
} else if key == self.urid.time_frame {
info.position_samples = sample_pos_i64(v);
} else if key == self.urid.time_speed {
info.playing = v.abs() > 1e-9;
} else if key == self.urid.time_beat_unit {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let v_u8 = v.round().clamp(0.0, f64::from(u8::MAX)) as u8;
info.time_sig_den = v_u8;
}
}
offset += padded;
if padded == 0 {
break;
}
}
let bpb = beats_per_bar
.or({
if info.time_sig_num > 0 {
Some(f64::from(info.time_sig_num))
} else {
None
}
})
.unwrap_or(4.0);
if let Some(b) = bar {
info.bar_start_beats = b * bpb;
if let Some(bb) = bar_beat {
info.position_beats = info.bar_start_beats + bb;
} else if let Some(bd) = beat_direct {
info.position_beats = bd;
} else {
info.position_beats = info.bar_start_beats;
}
} else if let Some(bd) = beat_direct {
info.position_beats = bd;
} else if let Some(bb) = bar_beat {
info.position_beats = bb;
}
true
}
}
#[allow(clippy::cast_precision_loss)]
unsafe fn read_atom_number(
&self,
atom_type: Urid,
data: *const u8,
size: usize,
) -> Option<f64> {
unsafe {
if atom_type == self.urid.atom_float && size >= core::mem::size_of::<f32>() {
Some(f64::from(*data.cast::<f32>()))
} else if atom_type == self.urid.atom_double && size >= core::mem::size_of::<f64>() {
Some(*data.cast::<f64>())
} else if atom_type == self.urid.atom_int && size >= core::mem::size_of::<i32>() {
Some(f64::from(*data.cast::<i32>()))
} else if atom_type == self.urid.atom_long && size >= core::mem::size_of::<i64>() {
Some(*data.cast::<i64>() as f64)
} else if atom_type == self.urid.atom_bool && size >= core::mem::size_of::<i32>() {
Some(if *data.cast::<i32>() != 0 { 1.0 } else { 0.0 })
} else {
None
}
}
}
}
pub fn midi_bytes_to_event(sample_offset: u32, bytes: &[u8]) -> Option<Event> {
if bytes.is_empty() {
return None;
}
let status = bytes[0];
let channel = status & 0x0F;
let body = match status & 0xF0 {
0x80 if bytes.len() >= 3 => EventBody::NoteOff {
group: 0,
channel,
note: bytes[1] & 0x7F,
velocity: bytes[2] & 0x7F,
},
0x90 if bytes.len() >= 3 => {
let vel = bytes[2] & 0x7F;
if vel == 0 {
EventBody::NoteOff {
group: 0,
channel,
note: bytes[1] & 0x7F,
velocity: 0,
}
} else {
EventBody::NoteOn {
group: 0,
channel,
note: bytes[1] & 0x7F,
velocity: vel,
}
}
}
0xA0 if bytes.len() >= 3 => EventBody::Aftertouch {
group: 0,
channel,
note: bytes[1] & 0x7F,
pressure: bytes[2] & 0x7F,
},
0xB0 if bytes.len() >= 3 => EventBody::ControlChange {
group: 0,
channel,
cc: bytes[1] & 0x7F,
value: bytes[2] & 0x7F,
},
0xC0 if bytes.len() >= 2 => EventBody::ProgramChange {
group: 0,
channel,
program: bytes[1] & 0x7F,
},
0xD0 if bytes.len() >= 2 => EventBody::ChannelPressure {
group: 0,
channel,
pressure: bytes[1] & 0x7F,
},
0xE0 if bytes.len() >= 3 => EventBody::PitchBend {
group: 0,
channel,
value: pitch_bend_from_bytes(bytes[1], bytes[2]),
},
_ => return None,
};
Some(Event {
sample_offset,
body,
})
}
pub unsafe fn write_midi_out_sequence(out: *mut AtomSequence, events: &EventList, urid: &UridMap) {
unsafe {
if out.is_null() || urid.midi_event == 0 {
return;
}
let capacity = (*out).atom.size as usize;
let atom_size = core::mem::size_of::<Atom>();
let header_size = core::mem::size_of::<AtomSequenceBody>();
let body_start = out.cast::<u8>().add(atom_size + header_size);
let mut offset = 0usize;
(*out).atom.type_ = urid.atom_sequence;
(*out).body.unit = 0;
(*out).body.pad = 0;
for event in events.iter() {
if let EventBody::SysEx { .. } = &event.body {
let inner = events.sysex_bytes(&event.body);
let body_len = inner.len() + 2; let total = core::mem::size_of::<AtomEventHeader>() + body_len;
let padded = (total + 7) & !7;
if offset + padded > capacity {
break;
}
let ev_ptr = body_start.add(offset).cast::<AtomEventHeader>();
(*ev_ptr).time_frames = i64::from(event.sample_offset);
(*ev_ptr).body.size = len_u32(body_len);
(*ev_ptr).body.type_ = urid.midi_event;
let body_ptr = body_start.add(offset + core::mem::size_of::<AtomEventHeader>());
*body_ptr = 0xF0;
core::ptr::copy_nonoverlapping(inner.as_ptr(), body_ptr.add(1), inner.len());
*body_ptr.add(1 + inner.len()) = 0xF7;
for i in body_len..(padded - core::mem::size_of::<AtomEventHeader>()) {
*body_ptr.add(i) = 0;
}
offset += padded;
continue;
}
let mut buf = [0u8; 3];
let (n, frame) = match &event.body {
EventBody::NoteOn {
channel,
note,
velocity,
..
} => {
buf[0] = 0x90 | (channel & 0x0F);
buf[1] = note & 0x7F;
buf[2] = velocity & 0x7F;
(3, event.sample_offset)
}
EventBody::NoteOff {
channel,
note,
velocity,
..
} => {
buf[0] = 0x80 | (channel & 0x0F);
buf[1] = note & 0x7F;
buf[2] = velocity & 0x7F;
(3, event.sample_offset)
}
EventBody::ControlChange {
channel, cc, value, ..
} => {
buf[0] = 0xB0 | (channel & 0x0F);
buf[1] = cc & 0x7F;
buf[2] = value & 0x7F;
(3, event.sample_offset)
}
EventBody::Aftertouch {
channel,
note,
pressure,
..
} => {
buf[0] = 0xA0 | (channel & 0x0F);
buf[1] = note & 0x7F;
buf[2] = pressure & 0x7F;
(3, event.sample_offset)
}
EventBody::ChannelPressure {
channel, pressure, ..
} => {
buf[0] = 0xD0 | (channel & 0x0F);
buf[1] = pressure & 0x7F;
(2, event.sample_offset)
}
EventBody::PitchBend { channel, value, .. } => {
let (lsb, msb) = pitch_bend_to_bytes(*value);
buf[0] = 0xE0 | (channel & 0x0F);
buf[1] = lsb;
buf[2] = msb;
(3, event.sample_offset)
}
EventBody::ProgramChange {
channel, program, ..
} => {
buf[0] = 0xC0 | (channel & 0x0F);
buf[1] = program & 0x7F;
(2, event.sample_offset)
}
_ => continue,
};
let total = core::mem::size_of::<AtomEventHeader>() + n;
let padded = (total + 7) & !7;
if offset + padded > capacity {
break; }
let ev_ptr = body_start.add(offset).cast::<AtomEventHeader>();
(*ev_ptr).time_frames = i64::from(frame);
(*ev_ptr).body.size = len_u32(n);
(*ev_ptr).body.type_ = urid.midi_event;
let body_ptr = body_start.add(offset + core::mem::size_of::<AtomEventHeader>());
core::ptr::copy_nonoverlapping(buf.as_ptr(), body_ptr, n);
for i in n..(padded - core::mem::size_of::<AtomEventHeader>()) {
*body_ptr.add(i) = 0;
}
offset += padded;
}
(*out).atom.size = len_u32(header_size + offset);
}
}
pub unsafe fn write_time_position_sequence(
out: *mut AtomSequence,
info: &TransportInfo,
urid: &UridMap,
) {
unsafe {
if out.is_null() || urid.time_position == 0 || urid.atom_object == 0 {
return;
}
let capacity = (*out).atom.size as usize;
let atom_size = core::mem::size_of::<Atom>();
let body_header = core::mem::size_of::<AtomSequenceBody>();
let body_start = out.cast::<u8>().add(atom_size + body_header);
(*out).atom.type_ = urid.atom_sequence;
(*out).body.unit = 0;
(*out).body.pad = 0;
let ev_header_size = core::mem::size_of::<AtomEventHeader>();
let obj_header_size = core::mem::size_of::<Urid>() * 2;
let ev_ptr = body_start.cast::<AtomEventHeader>();
if ev_header_size + obj_header_size > capacity {
(*out).atom.size = len_u32(body_header);
return;
}
(*ev_ptr).time_frames = 0;
(*ev_ptr).body.type_ = urid.atom_object;
let obj_body_start = body_start.add(ev_header_size);
*obj_body_start.cast::<Urid>() = 0; *obj_body_start
.add(core::mem::size_of::<Urid>())
.cast::<Urid>() = urid.time_position;
let mut prop_offset = obj_header_size;
let prop_header_size = core::mem::size_of::<Urid>() * 2 + core::mem::size_of::<Atom>();
let mut write_typed = |key: Urid,
atom_type: Urid,
value_size: usize,
write_value: &dyn Fn(*mut u8)|
-> bool {
if key == 0 || atom_type == 0 {
return false;
}
let total = prop_header_size + value_size;
let padded = (total + 7) & !7;
if ev_header_size + prop_offset + padded > capacity {
return false;
}
let entry = obj_body_start.add(prop_offset);
*entry.cast::<Urid>() = key;
*entry.add(core::mem::size_of::<Urid>()).cast::<Urid>() = 0; let atom_hdr = entry.add(core::mem::size_of::<Urid>() * 2).cast::<Atom>();
(*atom_hdr).size = len_u32(value_size);
(*atom_hdr).type_ = atom_type;
let value_ptr = entry.add(prop_header_size);
write_value(value_ptr);
prop_offset += padded;
true
};
let bpb = if info.time_sig_num > 0 {
f64::from(info.time_sig_num)
} else {
4.0
};
#[allow(clippy::cast_possible_truncation)]
let bar_index = (info.bar_start_beats / bpb).round() as i64;
let bar_beat = info.position_beats - info.bar_start_beats;
let mut ok = true;
ok = ok
&& write_typed(urid.time_speed, urid.atom_double, 8, &|p| {
*p.cast::<f64>() = if info.playing { 1.0 } else { 0.0 };
});
ok = ok
&& write_typed(urid.time_beats_per_minute, urid.atom_double, 8, &|p| {
*p.cast::<f64>() = info.tempo;
});
ok = ok
&& write_typed(urid.time_bar_beat, urid.atom_float, 4, &|p| {
#[allow(clippy::cast_possible_truncation)]
let v = bar_beat as f32;
*p.cast::<f32>() = v;
});
ok = ok
&& write_typed(urid.time_bar, urid.atom_long, 8, &|p| {
*p.cast::<i64>() = bar_index;
});
ok = ok
&& write_typed(urid.time_frame, urid.atom_long, 8, &|p| {
*p.cast::<i64>() = info.position_samples;
});
ok = ok
&& write_typed(urid.time_beats_per_bar, urid.atom_int, 4, &|p| {
*p.cast::<i32>() = i32::from(info.time_sig_num);
});
ok = ok
&& write_typed(urid.time_beat_unit, urid.atom_int, 4, &|p| {
*p.cast::<i32>() = i32::from(info.time_sig_den);
});
if !ok {
return;
}
(*ev_ptr).body.size = len_u32(prop_offset);
let event_total = ev_header_size + prop_offset;
(*out).atom.size = len_u32(body_header + event_total);
}
}
const _: Option<*mut c_void> = None;
#[cfg(test)]
mod tests {
use super::*;
use crate::urid::UridMap;
fn test_urid_map() -> UridMap {
let mut u = UridMap::default();
u.midi_event = 1;
u.atom_sequence = 2;
u.atom_chunk = 3;
u.atom_blank = 4;
u.atom_object = 5;
u.atom_bool = 6;
u.atom_int = 7;
u.atom_long = 8;
u.atom_float = 9;
u.atom_double = 10;
u.time_position = 100;
u.time_bar = 101;
u.time_bar_beat = 102;
u.time_beat = 103;
u.time_beat_unit = 104;
u.time_beats_per_bar = 105;
u.time_beats_per_minute = 106;
u.time_frame = 107;
u.time_speed = 108;
u
}
#[test]
fn time_position_roundtrip() {
let urid = test_urid_map();
let mut buf = vec![0u8; 4096];
let seq = buf.as_mut_ptr().cast::<AtomSequence>();
unsafe {
(*seq).atom.size = len_u32(buf.len() - core::mem::size_of::<Atom>());
}
let source = TransportInfo {
playing: true,
recording: false,
tempo: 132.5,
time_sig_num: 7,
time_sig_den: 8,
position_samples: 48000,
position_seconds: 0.0,
position_beats: 16.25,
bar_start_beats: 14.0,
loop_active: false,
loop_start_beats: 0.0,
loop_end_beats: 0.0,
};
unsafe {
write_time_position_sequence(seq, &source, &urid);
}
let mut decoded = TransportInfo::default();
let reader = AtomSequenceReader::new(seq.cast_const(), &urid);
assert!(reader.apply_time_position(&mut decoded));
assert!(decoded.playing, "playing flag round-tripped");
assert!((decoded.tempo - source.tempo).abs() < 1e-9);
assert!((decoded.position_beats - source.position_beats).abs() < 1e-9);
assert!((decoded.bar_start_beats - source.bar_start_beats).abs() < 1e-9);
assert_eq!(decoded.position_samples, source.position_samples);
assert_eq!(decoded.time_sig_num, source.time_sig_num);
assert_eq!(decoded.time_sig_den, source.time_sig_den);
}
#[test]
fn midi_roundtrip() {
use truce_core::events::{Event, EventBody, EventList};
let urid = test_urid_map();
let mut buf = vec![0u8; 4096];
let seq = buf.as_mut_ptr().cast::<AtomSequence>();
unsafe {
(*seq).atom.size = len_u32(buf.len() - core::mem::size_of::<Atom>());
}
let mut source = EventList::default();
source.push(Event {
sample_offset: 0,
body: EventBody::NoteOn {
group: 0,
channel: 0,
note: 60,
velocity: 95,
},
});
source.push(Event {
sample_offset: 128,
body: EventBody::NoteOff {
group: 0,
channel: 0,
note: 60,
velocity: 0,
},
});
source.push(Event {
sample_offset: 256,
body: EventBody::ControlChange {
group: 0,
channel: 3,
cc: 7,
value: 64,
},
});
unsafe {
write_midi_out_sequence(seq, &source, &urid);
}
let reader = AtomSequenceReader::new(seq.cast_const(), &urid);
let mut decoded = Vec::new();
reader.for_each_midi(|sample_offset, bytes| {
if let Some(event) = midi_bytes_to_event(sample_offset, bytes) {
decoded.push(event);
}
});
assert_eq!(decoded.len(), source.len(), "all events round-tripped");
assert_eq!(decoded[0].sample_offset, 0);
assert_eq!(decoded[1].sample_offset, 128);
assert_eq!(decoded[2].sample_offset, 256);
match decoded[0].body {
EventBody::NoteOn {
channel,
note,
velocity,
..
} => {
assert_eq!(channel, 0);
assert_eq!(note, 60);
assert_eq!(velocity, 95);
}
_ => panic!("expected NoteOn at index 0, got {:?}", decoded[0].body),
}
match decoded[1].body {
EventBody::NoteOff { channel, note, .. } => {
assert_eq!(channel, 0);
assert_eq!(note, 60);
}
_ => panic!("expected NoteOff at index 1, got {:?}", decoded[1].body),
}
match decoded[2].body {
EventBody::ControlChange {
channel, cc, value, ..
} => {
assert_eq!(channel, 3);
assert_eq!(cc, 7);
assert_eq!(value, 64);
}
_ => panic!(
"expected ControlChange at index 2, got {:?}",
decoded[2].body
),
}
}
}