use crate::error::ParseError;
pub const VERSION_V2: u8 = 5;
pub const VERSION_V3: u32 = 7;
pub const VERSION_V4: u32 = 17;
pub const V2_LEN: usize = 9;
pub const V3_LEN: usize = 24;
pub const V4_LEN: usize = 92;
pub const V4_LEN_LONG: usize = 108;
const V2_DYNAMICS_ENABLE: usize = 3;
const V2_VELOCITY_TO_AMPLITUDE: usize = 4;
const V2_VELOCITY_TO_TIMBRE: usize = 5;
pub const VELOCITY_LEVELS: u8 = 3;
pub fn velocity_level(depth: u8) -> Option<u8> {
match depth {
0 => Some(0),
1 | 2 => Some(1),
3 => Some(VELOCITY_LEVELS - 1),
_ => None,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StyV2 {
pub raw: [u8; V2_LEN],
}
impl StyV2 {
pub fn parse(payload: &[u8]) -> Result<StyV2, ParseError> {
let raw = payload.try_into().map_err(|_| {
ParseError::AssertFail(format!(
"sty section is {} bytes, not the {V2_LEN} a v2 preset is",
payload.len()
))
})?;
Ok(StyV2 { raw })
}
pub fn dynamics_enabled(&self) -> bool {
self.raw[V2_DYNAMICS_ENABLE] != 0
}
pub fn velocity_to_amplitude(&self) -> u8 {
self.raw[V2_VELOCITY_TO_AMPLITUDE]
}
pub fn velocity_to_timbre(&self) -> u8 {
self.raw[V2_VELOCITY_TO_TIMBRE]
}
}
const V3_DYNAMICS_ENABLE: usize = 4;
const V3_DYNAMICS_RESPONSE: usize = 12;
const V3_DYNAMICS_CURVE: usize = 14;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StyV3 {
pub raw: [u8; V3_LEN],
}
impl StyV3 {
pub fn parse(payload: &[u8]) -> Result<StyV3, ParseError> {
let raw = payload.try_into().map_err(|_| {
ParseError::AssertFail(format!(
"sty section is {} bytes, not the {V3_LEN} a v3 preset is",
payload.len()
))
})?;
Ok(StyV3 { raw })
}
pub fn dynamics_enabled(&self) -> bool {
self.raw[V3_DYNAMICS_ENABLE] != 0
}
pub fn dynamics_curve(&self) -> u8 {
self.raw[V3_DYNAMICS_CURVE]
}
pub fn dynamics_response(&self) -> u8 {
self.raw[V3_DYNAMICS_RESPONSE]
}
}
const V4_DYNAMICS_ENABLE: usize = 3;
const V4_DYNAMICS_CURVE: usize = 4;
pub const DYNAMICS_CURVE_NONE: u8 = 6;
const V4_DYNAMICS_RESPONSE: usize = 85;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EqBand {
pub frequency: u16,
pub gain: i32,
pub q: i32,
}
const EQ_BAND_STRIDE: usize = 12;
const EQ_BAND_BODY: usize = 10;
const EQ_AT: usize = 45;
pub const EQ_BANDS: usize = 3;
const _: () = assert!(EQ_AT + (EQ_BANDS - 1) * EQ_BAND_STRIDE + EQ_BAND_BODY <= V4_LEN);
const _: () = assert!(V4_DYNAMICS_RESPONSE + 3 <= V4_LEN);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StyV4 {
pub raw: Vec<u8>,
}
impl StyV4 {
pub fn parse(payload: &[u8]) -> Result<StyV4, ParseError> {
if payload.len() != V4_LEN && payload.len() != V4_LEN_LONG {
return Err(ParseError::AssertFail(format!(
"sty section is {} bytes, not the {V4_LEN} or {V4_LEN_LONG} a v4 preset is",
payload.len()
)));
}
Ok(StyV4 {
raw: payload.to_vec(),
})
}
pub fn dynamics_enabled(&self) -> bool {
self.raw[V4_DYNAMICS_ENABLE] != 0
}
pub fn dynamics_curve(&self) -> Option<u8> {
match self.raw[V4_DYNAMICS_CURVE] {
DYNAMICS_CURVE_NONE => None,
curve => Some(curve),
}
}
pub fn dynamics_response(&self) -> [u8; 3] {
std::array::from_fn(|i| self.raw[V4_DYNAMICS_RESPONSE + i])
}
pub fn eq(&self) -> [EqBand; EQ_BANDS] {
std::array::from_fn(|i| {
let b = &self.raw[EQ_AT + i * EQ_BAND_STRIDE..][..EQ_BAND_BODY];
EqBand {
frequency: u16::from_be_bytes([b[0], b[1]]),
gain: i32::from_be_bytes([b[2], b[3], b[4], b[5]]),
q: i32::from_be_bytes([b[6], b[7], b[8], b[9]]),
}
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Sty {
V2(StyV2),
V3(StyV3),
V4(StyV4),
}
impl Sty {
pub fn parse_wide(version: u32, payload: &[u8]) -> Result<Sty, ParseError> {
match version {
VERSION_V3 => StyV3::parse(payload).map(Sty::V3),
VERSION_V4 => StyV4::parse(payload).map(Sty::V4),
v => Err(ParseError::AssertFail(format!(
"sty section version {v} has no preset layout derived from a specimen"
))),
}
}
pub fn raw(&self) -> &[u8] {
match self {
Sty::V2(s) => &s.raw,
Sty::V3(s) => &s.raw,
Sty::V4(s) => &s.raw,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn v2_reads_the_dynamics_enable_and_both_velocity_depths() {
let off = StyV2::parse(&[0, 1, 0, 0, 1, 1, 0, 0, 0]).unwrap();
let on = StyV2::parse(&[0, 1, 0, 1, 0, 2, 0, 0, 0]).unwrap();
assert!(!off.dynamics_enabled());
assert_eq!(off.velocity_to_amplitude(), 1);
assert_eq!(off.velocity_to_timbre(), 1);
assert!(on.dynamics_enabled());
assert_eq!(on.velocity_to_amplitude(), 0);
assert_eq!(on.velocity_to_timbre(), 2);
}
#[test]
fn v3_reads_its_dynamics_group() {
let mut raw = [0u8; V3_LEN];
raw[V3_DYNAMICS_RESPONSE] = 127;
raw[V3_DYNAMICS_CURVE] = 2;
let off = Sty::parse_wide(VERSION_V3, &raw).unwrap();
let Sty::V3(off) = off else {
panic!("not a v3 preset");
};
assert!(!off.dynamics_enabled());
assert_eq!(off.dynamics_curve(), 2);
assert_eq!(off.dynamics_response(), 127);
raw[V3_DYNAMICS_ENABLE] = 43;
raw[V3_DYNAMICS_RESPONSE] = 74;
raw[V3_DYNAMICS_CURVE] = 1;
let on = StyV3::parse(&raw).unwrap();
assert!(on.dynamics_enabled());
assert_eq!(on.dynamics_curve(), 1);
assert_eq!(on.dynamics_response(), 74);
}
#[test]
fn a_velocity_depth_quantises_onto_three_levels() {
assert_eq!(
[0, 1, 2, 3].map(velocity_level),
[Some(0), Some(1), Some(1), Some(VELOCITY_LEVELS - 1)]
);
assert_eq!(velocity_level(4), None);
assert_eq!(velocity_level(u8::MAX), None);
}
#[test]
fn a_short_v3_preset_is_refused() {
assert!(StyV3::parse(&[0; V3_LEN - 1]).is_err());
assert!(Sty::parse_wide(VERSION_V3, &[0; V3_LEN + 1]).is_err());
}
#[test]
fn a_short_v2_preset_is_refused() {
assert!(StyV2::parse(&[0; V2_LEN - 1]).is_err());
}
#[test]
fn both_v4_widths_are_accepted_under_one_version() {
assert!(Sty::parse_wide(VERSION_V4, &[0; V4_LEN]).is_ok());
assert!(Sty::parse_wide(VERSION_V4, &[0; V4_LEN_LONG]).is_ok());
assert!(Sty::parse_wide(VERSION_V4, &[0; V4_LEN + 1]).is_err());
}
#[test]
fn an_unknown_wide_version_is_refused_rather_than_guessed() {
assert!(Sty::parse_wide(VERSION_V3 + 1, &[0; V3_LEN]).is_err());
}
#[test]
fn v4_dynamics_reads_its_enable_curve_and_response() {
let mut raw = [0u8; V4_LEN];
raw[V4_DYNAMICS_CURVE] = DYNAMICS_CURVE_NONE;
raw[V4_DYNAMICS_RESPONSE..][..3].fill(127);
let off = StyV4::parse(&raw).unwrap();
assert!(!off.dynamics_enabled());
assert_eq!(off.dynamics_curve(), None);
assert_eq!(off.dynamics_response(), [127; 3]);
raw[V4_DYNAMICS_ENABLE] = 1;
raw[V4_DYNAMICS_CURVE] = 1;
raw[V4_DYNAMICS_RESPONSE..][..3].copy_from_slice(&[74, 82, 90]);
let on = StyV4::parse(&raw).unwrap();
assert!(on.dynamics_enabled());
assert_eq!(on.dynamics_curve(), Some(1));
assert_eq!(on.dynamics_response(), [74, 82, 90]);
}
#[test]
fn v4_eq_bands_read_frequency_gain_and_q() {
let mut raw = [0u8; V4_LEN];
for (i, (f, g, q)) in [(3000u16, -50i32, 10i32), (4000, -50, 10), (5000, 0, 0)]
.into_iter()
.enumerate()
{
let at = EQ_AT + i * EQ_BAND_STRIDE;
raw[at..at + 2].copy_from_slice(&f.to_be_bytes());
raw[at + 2..at + 6].copy_from_slice(&g.to_be_bytes());
raw[at + 6..at + 10].copy_from_slice(&q.to_be_bytes());
}
let bands = StyV4::parse(&raw).unwrap().eq();
assert_eq!(bands[0].frequency, 3000);
assert_eq!(bands[1].gain, -50);
assert_eq!(
bands[2],
EqBand {
frequency: 5000,
gain: 0,
q: 0
}
);
}
}