use super::v2::{ControlChangeV2, NoteOnV2};
use super::{ControlChange, FramePos, NoteEvent};
pub const PITCH_BEND_CENTER: u32 = 0x80000000;
#[must_use]
#[inline]
pub fn velocity_7_to_16(v: u8) -> u16 {
if v == 0 {
return 0;
}
let v = v as u16;
(v << 9) | (v << 2) | (v >> 5)
}
#[must_use]
#[inline]
pub fn velocity_16_to_7(v: u16) -> u8 {
(v >> 9) as u8
}
#[must_use]
#[inline]
pub fn cc_7_to_32(v: u8) -> u32 {
let v = v as u32;
(v << 25) | (v << 18) | (v << 11) | (v << 4) | (v >> 3)
}
#[must_use]
#[inline]
pub fn cc_32_to_7(v: u32) -> u8 {
(v >> 25) as u8
}
#[must_use]
#[inline]
pub fn pitch_bend_14_to_32(v: u16) -> u32 {
let v = v as u32;
(v << 18) | (v << 4) | (v >> 10)
}
#[must_use]
#[inline]
pub fn pitch_bend_32_to_14(v: u32) -> u16 {
(v >> 18) as u16
}
#[must_use]
#[inline]
pub fn note_event_to_v2(event: &NoteEvent) -> NoteOnV2 {
NoteOnV2 {
position: event.position,
note: event.note,
velocity: velocity_7_to_16(event.velocity),
channel: event.channel,
attribute_type: 0,
attribute_data: 0,
}
}
#[must_use]
#[inline]
pub fn note_on_v2_to_event(event: &NoteOnV2, duration: FramePos) -> NoteEvent {
NoteEvent {
position: event.position,
duration,
note: event.note,
velocity: velocity_16_to_7(event.velocity),
channel: event.channel,
}
}
#[must_use]
#[inline]
pub fn cc_to_v2(cc: &ControlChange) -> ControlChangeV2 {
ControlChangeV2 {
position: cc.position,
controller: cc.controller,
value: cc_7_to_32(cc.value),
channel: cc.channel,
}
}
#[must_use]
#[inline]
pub fn cc_v2_to_cc(cc: &ControlChangeV2) -> ControlChange {
ControlChange {
position: cc.position,
controller: cc.controller,
value: cc_32_to_7(cc.value),
channel: cc.channel,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn velocity_7_to_16_zero() {
assert_eq!(velocity_7_to_16(0), 0);
}
#[test]
fn velocity_7_to_16_max() {
let v16 = velocity_7_to_16(127);
assert!(v16 > 65000, "max velocity should be near 65535, got {v16}");
}
#[test]
fn velocity_roundtrip() {
for v in 0..=127u8 {
let v16 = velocity_7_to_16(v);
let back = velocity_16_to_7(v16);
assert_eq!(back, v, "roundtrip failed for velocity {v}");
}
}
#[test]
fn cc_roundtrip() {
for v in 0..=127u8 {
let v32 = cc_7_to_32(v);
let back = cc_32_to_7(v32);
assert_eq!(back, v, "roundtrip failed for CC value {v}");
}
}
#[test]
fn pitch_bend_roundtrip() {
for v in [0u16, 1, 8192, 16383] {
let v32 = pitch_bend_14_to_32(v);
let back = pitch_bend_32_to_14(v32);
assert_eq!(back, v, "roundtrip failed for pitch bend {v}");
}
}
#[test]
fn pitch_bend_center() {
let center_14 = 8192u16;
let v32 = pitch_bend_14_to_32(center_14);
let back = pitch_bend_32_to_14(v32);
assert_eq!(back, center_14);
}
#[test]
fn note_event_to_v2_and_back() {
let event = NoteEvent {
position: 1000,
duration: 500,
note: 60,
velocity: 100,
channel: 0,
};
let v2 = note_event_to_v2(&event);
assert_eq!(v2.note, 60);
assert_eq!(v2.channel, 0);
let back = note_on_v2_to_event(&v2, 500);
assert_eq!(back.note, 60);
assert_eq!(back.velocity, 100);
assert_eq!(back.duration, 500);
}
#[test]
fn cc_to_v2_and_back() {
let cc = ControlChange {
position: 0,
controller: 7,
value: 100,
channel: 0,
};
let v2 = cc_to_v2(&cc);
assert_eq!(v2.controller, 7);
let back = cc_v2_to_cc(&v2);
assert_eq!(back.controller, 7);
assert_eq!(back.value, 100);
}
#[test]
fn cc_7_to_32_max() {
let v32 = cc_7_to_32(127);
assert!(
v32 > 4_000_000_000,
"max CC should be near u32::MAX, got {v32}"
);
}
#[test]
fn pitch_bend_center_constant() {
assert_eq!(PITCH_BEND_CENTER, 0x80000000);
}
}