#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss
)]
#[inline]
#[must_use]
pub fn norm_7bit(v: u8) -> f32 {
debug_assert!(
v <= 127,
"norm_7bit: {v} > 127 (high bit is the MIDI status flag)",
);
f32::from(v) / 127.0
}
#[inline]
#[must_use]
pub fn denorm_7bit(v: f32) -> u8 {
debug_assert!(
!v.is_nan(),
"denorm_7bit: NaN input — caller's normalized value is uninitialized?",
);
(v.clamp(0.0, 1.0) * 127.0).round() as u8
}
#[inline]
#[must_use]
pub fn norm_pitch_bend(raw: u16) -> f32 {
debug_assert!(
raw <= 16383,
"norm_pitch_bend: raw {raw} > 16383 — caller didn't mask LSB|MSB<<7?",
);
(f32::from(raw) - 8192.0) / 8192.0
}
#[inline]
#[must_use]
pub fn denorm_pitch_bend(v: f32) -> u16 {
debug_assert!(
!v.is_nan(),
"denorm_pitch_bend: NaN input — caller's normalized value is uninitialized?",
);
let raw = (v.clamp(-1.0, 1.0) * 8192.0 + 8192.0).round();
(raw as u16).min(16383)
}
#[inline]
#[must_use]
pub fn pitch_bend_to_bytes(raw: u16) -> (u8, u8) {
debug_assert!(raw <= 16383, "pitch_bend_to_bytes: raw {raw} > 16383");
let lsb = (raw & 0x7F) as u8;
let msb = ((raw >> 7) & 0x7F) as u8;
(lsb, msb)
}
#[inline]
#[must_use]
pub fn pitch_bend_from_bytes(lsb: u8, msb: u8) -> u16 {
(u16::from(msb & 0x7F) << 7) | u16::from(lsb & 0x7F)
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
#[test]
fn norm_7bit_endpoints() {
assert_eq!(norm_7bit(0), 0.0);
assert_eq!(norm_7bit(127), 1.0);
assert!((norm_7bit(64) - (64.0 / 127.0)).abs() < f32::EPSILON);
}
#[test]
fn denorm_7bit_endpoints() {
assert_eq!(denorm_7bit(0.0), 0);
assert_eq!(denorm_7bit(1.0), 127);
assert_eq!(denorm_7bit(0.5), 64); }
#[test]
fn denorm_7bit_clamps() {
assert_eq!(denorm_7bit(-0.5), 0);
assert_eq!(denorm_7bit(2.0), 127);
assert_eq!(denorm_7bit(f32::INFINITY), 127);
assert_eq!(denorm_7bit(f32::NEG_INFINITY), 0);
}
#[test]
fn round_trip_7bit_all_codes() {
for raw in 0u8..=127 {
assert_eq!(denorm_7bit(norm_7bit(raw)), raw);
}
}
#[test]
fn norm_pitch_bend_endpoints() {
assert_eq!(norm_pitch_bend(0), -1.0);
assert_eq!(norm_pitch_bend(8192), 0.0);
let max_pos = norm_pitch_bend(16383);
assert!((max_pos - 8191.0_f32 / 8192.0_f32).abs() < f32::EPSILON);
}
#[test]
fn denorm_pitch_bend_endpoints() {
assert_eq!(denorm_pitch_bend(-1.0), 0);
assert_eq!(denorm_pitch_bend(0.0), 8192);
assert_eq!(denorm_pitch_bend(1.0), 16383);
}
#[test]
fn round_trip_pitch_bend_all_codes() {
for raw in 0u16..=16383 {
let v = norm_pitch_bend(raw);
let back = denorm_pitch_bend(v);
assert_eq!(back, raw, "raw={raw}, v={v}");
}
}
#[test]
fn pitch_bend_byte_split_round_trip() {
for raw in 0u16..=16383 {
let (lsb, msb) = pitch_bend_to_bytes(raw);
assert!(lsb < 128 && msb < 128);
assert_eq!(pitch_bend_from_bytes(lsb, msb), raw);
}
}
#[test]
fn pitch_bend_from_bytes_masks_high_bit() {
assert_eq!(pitch_bend_from_bytes(0xFF, 0xFF), 16383);
assert_eq!(pitch_bend_from_bytes(0x80, 0x80), 0);
}
}