use crate::core::fixed::fixed::{Q15, Q8_8};
use crate::core::fixed::units::{PitchDelta, RetrigMul};
use crate::tracker::period::FrequencyType;
#[inline]
pub const fn lfo_speed_from_nibble_64(speed_nibble: u8) -> Q8_8 {
Q8_8::from_ratio((speed_nibble & 0x0F) as i16, 64)
}
#[inline]
pub const fn lfo_depth_from_nibble_16(depth_nibble: u8) -> Q15 {
Q15::from_ratio((depth_nibble & 0x0F) as i32, 16)
}
#[inline]
pub const fn it_vibrato_depth_from_nibble_4(depth_nibble: u8) -> PitchDelta {
PitchDelta::from_ratio((depth_nibble & 0x0F) as i16, 4)
}
#[inline]
pub const fn it_panbrello_depth_from_nibble_4(depth_nibble: u8) -> Q15 {
Q15::from_ratio((depth_nibble & 0x0F) as i32, 4)
}
#[inline]
pub const fn vibrato_depth_from_nibble_16(depth_nibble: u8) -> PitchDelta {
PitchDelta::from_ratio((depth_nibble & 0x0F) as i16, 16)
}
#[inline]
pub fn xm_vibrato_depth(
depth_nibble: u8,
freq_type: FrequencyType,
protracker: bool,
) -> PitchDelta {
let n = (depth_nibble & 0x0F) as i16;
match freq_type {
FrequencyType::LinearFrequencies => PitchDelta::from_ratio(n, 8),
FrequencyType::AmigaFrequencies => {
let shift = if protracker { 7 } else { 5 };
PitchDelta::from_q8_8_i16((255 * n) >> shift)
}
}
}
#[inline]
const fn retrig_mul_common(nibble: u8, nibble6_num: i16, nibble6_den: i16) -> RetrigMul {
match nibble {
6 => RetrigMul::from_ratio(nibble6_num, nibble6_den),
7 => RetrigMul::from_ratio(1, 2),
0xE => RetrigMul::from_ratio(3, 2),
0xF => RetrigMul::from_ratio(2, 1),
_ => RetrigMul::UNITY,
}
}
#[inline]
pub const fn retrig_mul_ft2(nibble: u8) -> RetrigMul {
retrig_mul_common(nibble, 11, 16)
}
#[inline]
pub const fn retrig_mul_s3m(nibble: u8) -> RetrigMul {
retrig_mul_common(nibble, 5, 8)
}
#[inline]
pub const fn retrig_volume_delta_q15(nibble: u8) -> Q15 {
match nibble {
1 => Q15::from_ratio(-1, 64),
2 => Q15::from_ratio(-2, 64),
3 => Q15::from_ratio(-4, 64),
4 => Q15::from_ratio(-8, 64),
5 => Q15::from_ratio(-16, 64),
9 => Q15::from_ratio(1, 64),
0xA => Q15::from_ratio(2, 64),
0xB => Q15::from_ratio(4, 64),
0xC => Q15::from_ratio(8, 64),
0xD => Q15::from_ratio(16, 64),
_ => Q15::ZERO,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lfo_speed_nibble_quantization() {
let mut prev = i16::MIN;
for n in 0..16 {
let q = lfo_speed_from_nibble_64(n);
assert!(q.raw() > prev);
prev = q.raw();
}
}
#[test]
fn it_panbrello_saturates_at_full_swing() {
assert_eq!(it_panbrello_depth_from_nibble_4(0), Q15::ZERO);
assert_eq!(it_panbrello_depth_from_nibble_4(4), Q15::ONE);
assert_eq!(it_panbrello_depth_from_nibble_4(15), Q15::ONE);
}
#[test]
fn xm_vibrato_depth_amiga_matches_ft2_peak() {
for n in 0u8..16 {
let expect = ((255i32 * n as i32) >> 5) as i16;
assert_eq!(
xm_vibrato_depth(n, FrequencyType::AmigaFrequencies, false).as_q8_8_i16(),
expect,
"amiga vibrato depth nibble {n}"
);
}
assert_eq!(
xm_vibrato_depth(15, FrequencyType::AmigaFrequencies, false).as_q8_8_i16(),
119
);
}
#[test]
fn xm_vibrato_depth_protracker_is_quarter_of_ft2() {
for n in 0u8..16 {
let expect = ((255i32 * n as i32) >> 7) as i16;
assert_eq!(
xm_vibrato_depth(n, FrequencyType::AmigaFrequencies, true).as_q8_8_i16(),
expect,
"protracker vibrato depth nibble {n}"
);
}
assert_eq!(
xm_vibrato_depth(15, FrequencyType::AmigaFrequencies, true).as_q8_8_i16(),
29
);
for n in 0u8..16 {
assert_eq!(
xm_vibrato_depth(n, FrequencyType::LinearFrequencies, true).as_q8_8_i16(),
xm_vibrato_depth(n, FrequencyType::LinearFrequencies, false).as_q8_8_i16(),
);
}
}
#[test]
fn xm_vibrato_depth_linear_is_double_old_scale() {
for n in 0u8..16 {
assert_eq!(
xm_vibrato_depth(n, FrequencyType::LinearFrequencies, false).as_q8_8_i16(),
PitchDelta::from_ratio(n as i16, 8).as_q8_8_i16(),
);
assert_eq!(
xm_vibrato_depth(n, FrequencyType::LinearFrequencies, false).as_q8_8_i16(),
2 * vibrato_depth_from_nibble_16(n).as_q8_8_i16(),
);
}
}
#[test]
fn retrig_mul_ft2_known_values() {
assert_eq!(retrig_mul_ft2(7), RetrigMul::from_ratio(1, 2));
assert_eq!(retrig_mul_ft2(0xF), RetrigMul::from_ratio(2, 1));
}
#[test]
fn retrig_volume_delta_signs() {
for (neg, pos) in [(1u8, 9u8), (2, 0xA), (3, 0xB), (4, 0xC), (5, 0xD)] {
assert_eq!(
retrig_volume_delta_q15(neg).raw(),
-retrig_volume_delta_q15(pos).raw()
);
}
assert_eq!(retrig_volume_delta_q15(0), Q15::ZERO);
}
}