#[derive(Clone, Copy, Debug)]
pub struct Event {
pub sample_offset: u32,
pub body: EventBody,
}
#[derive(Clone, Copy, Debug)]
pub enum EventBody {
NoteOn {
group: u8,
channel: u8,
note: u8,
velocity: u8,
},
NoteOff {
group: u8,
channel: u8,
note: u8,
velocity: u8,
},
Aftertouch {
group: u8,
channel: u8,
note: u8,
pressure: u8,
},
ChannelPressure {
group: u8,
channel: u8,
pressure: u8,
},
ControlChange {
group: u8,
channel: u8,
cc: u8,
value: u8,
},
PitchBend {
group: u8,
channel: u8,
value: u16,
},
ProgramChange {
group: u8,
channel: u8,
program: u8,
},
NoteOn2 {
group: u8,
channel: u8,
note: u8,
velocity: u16,
attribute_type: u8,
attribute: u16,
},
NoteOff2 {
group: u8,
channel: u8,
note: u8,
velocity: u16,
attribute_type: u8,
attribute: u16,
},
PolyPressure2 {
group: u8,
channel: u8,
note: u8,
pressure: u32,
},
PerNoteCC {
group: u8,
channel: u8,
note: u8,
cc: u8,
value: u32,
registered: bool,
},
PerNotePitchBend {
group: u8,
channel: u8,
note: u8,
value: u32,
},
PerNoteManagement {
group: u8,
channel: u8,
note: u8,
flags: u8,
},
ControlChange2 {
group: u8,
channel: u8,
cc: u8,
value: u32,
},
ChannelPressure2 {
group: u8,
channel: u8,
pressure: u32,
},
PitchBend2 {
group: u8,
channel: u8,
value: u32,
},
ProgramChange2 {
group: u8,
channel: u8,
program: u8,
bank: Option<(u8, u8)>,
},
RegisteredController {
group: u8,
channel: u8,
bank: u8,
index: u8,
value: u32,
},
AssignableController {
group: u8,
channel: u8,
bank: u8,
index: u8,
value: u32,
},
ParamChange {
id: u32,
value: f64,
},
ParamMod {
id: u32,
note_id: i32,
value: f64,
},
Transport(TransportInfo),
SysEx {
pool_offset: u32,
len: u32,
},
}
#[derive(Clone, Copy, Debug, Default)]
pub struct TransportInfo {
pub playing: bool,
pub recording: bool,
pub tempo: f64,
pub time_sig_num: u8,
pub time_sig_den: u8,
pub position_samples: i64,
pub position_seconds: f64,
pub position_beats: f64,
pub bar_start_beats: f64,
pub loop_active: bool,
pub loop_start_beats: f64,
pub loop_end_beats: f64,
}
impl TransportInfo {
#[must_use]
pub fn for_screenshot() -> Self {
Self {
playing: true,
tempo: 120.0,
time_sig_num: 4,
time_sig_den: 4,
position_beats: 4.0,
..Self::default()
}
}
}
pub const EVENT_LIST_PREALLOC: usize = 256;
pub const SYSEX_POOL_PREALLOC: usize = 128 * 1024;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PushError {
PoolFull,
}
impl core::fmt::Display for PushError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::PoolFull => f.write_str("SysEx byte pool is full"),
}
}
}
impl std::error::Error for PushError {}
#[derive(Clone, Debug, Default)]
pub struct EventList {
events: Vec<Event>,
sysex_pool: Vec<u8>,
}
impl EventList {
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
events: Vec::with_capacity(capacity),
sysex_pool: Vec::with_capacity(SYSEX_POOL_PREALLOC),
}
}
pub fn push(&mut self, event: Event) {
self.events.push(event);
}
pub fn push_sysex(&mut self, sample_offset: u32, data: &[u8]) -> Result<(), PushError> {
let pool_offset = self.sysex_pool.len();
if pool_offset + data.len() > self.sysex_pool.capacity() {
return Err(PushError::PoolFull);
}
self.sysex_pool.extend_from_slice(data);
#[allow(clippy::cast_possible_truncation)]
self.events.push(Event {
sample_offset,
body: EventBody::SysEx {
pool_offset: pool_offset as u32,
len: data.len() as u32,
},
});
Ok(())
}
#[must_use]
pub fn sysex_bytes(&self, body: &EventBody) -> &[u8] {
match body {
EventBody::SysEx { pool_offset, len } => {
let start = *pool_offset as usize;
let end = start + (*len as usize);
&self.sysex_pool[start..end]
}
_ => &[],
}
}
pub fn clear(&mut self) {
self.events.clear();
self.sysex_pool.clear();
}
pub fn sort(&mut self) {
self.events.sort_by_key(|e| e.sample_offset);
}
pub fn iter(&self) -> impl Iterator<Item = &Event> {
self.events.iter()
}
#[must_use]
pub fn get(&self, index: usize) -> Option<&Event> {
self.events.get(index)
}
#[must_use]
pub fn len(&self) -> usize {
self.events.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
#[must_use]
pub fn sysex_pool_used(&self) -> usize {
self.sysex_pool.len()
}
#[must_use]
pub fn sysex_pool_capacity(&self) -> usize {
self.sysex_pool.capacity()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_sysex_round_trip() {
let mut list = EventList::with_capacity(8);
let payload = b"\x7E\x00\x06\x01"; list.push_sysex(42, payload).expect("pool has room");
assert_eq!(list.len(), 1);
let event = list.iter().next().expect("one event");
assert_eq!(event.sample_offset, 42);
assert!(matches!(event.body, EventBody::SysEx { .. }));
assert_eq!(list.sysex_bytes(&event.body), payload);
assert_eq!(list.sysex_pool_used(), payload.len());
}
#[test]
fn push_sysex_two_messages_carve_pool_independently() {
let mut list = EventList::with_capacity(8);
let a = b"\x01\x02\x03";
let b = b"\x04\x05\x06\x07";
list.push_sysex(0, a).unwrap();
list.push_sysex(1, b).unwrap();
let collected: Vec<_> = list.iter().collect();
assert_eq!(list.sysex_bytes(&collected[0].body), a);
assert_eq!(list.sysex_bytes(&collected[1].body), b);
assert_eq!(list.sysex_pool_used(), a.len() + b.len());
}
#[test]
fn push_sysex_pool_full_is_recoverable() {
let mut list = EventList::with_capacity(8);
let big = vec![0u8; SYSEX_POOL_PREALLOC];
list.push_sysex(0, &big)
.expect("first fill is exactly the pool");
let err = list.push_sysex(1, b"\x00").unwrap_err();
assert_eq!(err, PushError::PoolFull);
assert_eq!(list.len(), 1);
assert_eq!(list.sysex_pool_used(), SYSEX_POOL_PREALLOC);
}
#[test]
fn clear_preserves_pool_capacity() {
let mut list = EventList::with_capacity(8);
let cap_before = list.sysex_pool_capacity();
list.push_sysex(0, b"\x00\x01\x02").unwrap();
list.clear();
assert!(list.is_empty());
assert_eq!(list.sysex_pool_used(), 0);
assert_eq!(list.sysex_pool_capacity(), cap_before);
}
#[test]
fn sort_preserves_sysex_offsets() {
let mut list = EventList::with_capacity(8);
let early = b"\x10\x11";
let late = b"\x20\x21\x22";
list.push_sysex(100, late).unwrap();
list.push_sysex(0, early).unwrap();
list.sort();
let collected: Vec<_> = list.iter().collect();
assert_eq!(collected[0].sample_offset, 0);
assert_eq!(list.sysex_bytes(&collected[0].body), early);
assert_eq!(collected[1].sample_offset, 100);
assert_eq!(list.sysex_bytes(&collected[1].body), late);
}
#[test]
fn sysex_bytes_returns_empty_for_non_sysex() {
let list = EventList::with_capacity(8);
let body = EventBody::NoteOn {
group: 0,
channel: 0,
note: 60,
velocity: 100,
};
assert!(list.sysex_bytes(&body).is_empty());
}
}