use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use crate::core::fixed::fixed::Q15;
use crate::core::fixed::units::EnvValue;
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug)]
pub struct EnvelopePoint {
pub frame: usize,
pub value: EnvValue,
}
impl EnvelopePoint {
pub fn lerp(a: &EnvelopePoint, b: &EnvelopePoint, pos: usize) -> EnvValue {
if pos <= a.frame {
a.value
} else if pos >= b.frame {
b.value
} else {
let num = (pos - a.frame) as i32;
let den = (b.frame - a.frame) as i32;
let p = Q15::from_ratio(num, den);
EnvValue::lerp(a.value, b.value, p)
}
}
}
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
pub struct Envelope {
pub enabled: bool,
pub point: Vec<EnvelopePoint>,
pub sustain_enabled: bool,
pub sustain_start_point: usize,
pub sustain_end_point: usize,
pub loop_enabled: bool,
pub loop_start_point: usize,
pub loop_end_point: usize,
}
impl Envelope {
pub fn is_valid(&self) -> bool {
self.point.len() >= 2
&& self.point.len() <= 12
&& self.sustain_start_point < self.point.len()
&& self.sustain_end_point < self.point.len()
&& self.loop_start_point < self.point.len()
&& self.loop_end_point < self.point.len()
&& self.loop_start_point <= self.loop_end_point
&& self.sustain_start_point <= self.sustain_end_point
}
pub fn loop_in_sustain(&self, frame: usize) -> usize {
if self.sustain_enabled
&& self.sustain_end_point < self.point.len()
&& self.sustain_start_point < self.point.len()
{
let sustain_end = self.point[self.sustain_end_point].frame;
if frame > sustain_end {
return self.point[self.sustain_start_point].frame;
}
}
frame
}
pub fn loop_in_loop(&self, frame: usize) -> usize {
if self.loop_enabled
&& self.loop_end_point < self.point.len()
&& self.loop_start_point < self.point.len()
{
let loop_end = self.point[self.loop_end_point].frame;
if frame > loop_end {
return self.point[self.loop_start_point].frame;
}
}
frame
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn ev(num: i32, den: i32) -> EnvValue {
EnvValue::from_q15(Q15::from_ratio(num, den))
}
#[test]
fn lerp_at_boundaries() {
let a = EnvelopePoint {
frame: 0,
value: ev(0, 1),
};
let b = EnvelopePoint {
frame: 10,
value: ev(1, 1),
};
assert_eq!(EnvelopePoint::lerp(&a, &b, 0), ev(0, 1));
assert_eq!(EnvelopePoint::lerp(&a, &b, 10), ev(1, 1));
let mid = EnvelopePoint::lerp(&a, &b, 5);
let drift = (mid.raw_q15().raw() - Q15::HALF.raw()).abs();
assert!(drift <= 4, "midpoint drift {} LSB > 4", drift);
}
#[test]
fn lerp_before_and_after() {
let a = EnvelopePoint {
frame: 5,
value: ev(0, 1),
};
let b = EnvelopePoint {
frame: 15,
value: ev(1, 1),
};
assert_eq!(EnvelopePoint::lerp(&a, &b, 0), ev(0, 1)); assert_eq!(EnvelopePoint::lerp(&a, &b, 20), ev(1, 1)); }
#[test]
fn loop_in_sustain_empty_points_no_panic() {
let e = Envelope {
sustain_enabled: true,
sustain_start_point: 5,
sustain_end_point: 10,
point: vec![], ..Default::default()
};
assert_eq!(e.loop_in_sustain(100), 100);
}
#[test]
fn loop_in_loop_empty_points_no_panic() {
let e = Envelope {
loop_enabled: true,
loop_start_point: 0,
loop_end_point: 5,
point: vec![], ..Default::default()
};
assert_eq!(e.loop_in_loop(100), 100);
}
#[test]
fn loop_in_sustain_wraps() {
let e = Envelope {
sustain_enabled: true,
sustain_start_point: 1,
sustain_end_point: 2,
point: vec![
EnvelopePoint {
frame: 0,
value: ev(0, 1),
},
EnvelopePoint {
frame: 10,
value: ev(1, 2),
},
EnvelopePoint {
frame: 20,
value: ev(1, 1),
},
],
..Default::default()
};
assert_eq!(e.loop_in_sustain(15), 15); assert_eq!(e.loop_in_sustain(25), 10); }
}