use xmrs::prelude::*;
#[derive(Clone)]
pub struct StateEnvelope<'a> {
env: &'a Envelope,
default_value: f32,
pub enabled: bool,
pub value: f32,
pub counter: usize,
}
impl<'a> StateEnvelope<'a> {
pub fn new(env: &'a Envelope, default_value: f32) -> Self {
let enabled = env.enabled;
Self {
env,
default_value,
enabled,
value: default_value,
counter: 0,
}
}
pub fn has_volume_envelope(&self) -> bool {
self.env.enabled || self.enabled
}
pub fn reset(&mut self) {
self.enabled = self.env.enabled;
self.value = self.default_value;
self.counter = 0;
}
pub fn tick(&mut self, sustained: bool) {
let num_points = self.env.point.len();
if num_points == 0 {
self.value = 0.0;
return;
}
if num_points == 1 {
self.value = self.env.point[0].value.min(1.0);
return;
}
if sustained {
self.counter = self.env.loop_in_sustain(self.counter);
} else {
self.counter = self.env.loop_in_loop(self.counter);
}
let last_idx = num_points - 1;
if self.counter >= self.env.point[last_idx].frame {
self.value = self.env.point[last_idx].value;
self.counter = self.counter.saturating_add(1);
return;
}
for i in 1..num_points {
let prev_point = &self.env.point[i - 1];
let curr_point = &self.env.point[i];
if self.counter == prev_point.frame {
self.value = prev_point.value;
break;
}
if self.counter <= curr_point.frame {
self.value = EnvelopePoint::lerp(prev_point, curr_point, self.counter);
break;
}
if prev_point.frame >= curr_point.frame {
self.value = prev_point.value;
break;
}
}
self.counter += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use alloc::vec::Vec;
fn make_envelope(points: Vec<(usize, f32)>) -> Envelope {
Envelope {
enabled: true,
point: points
.into_iter()
.map(|(frame, value)| EnvelopePoint { frame, value })
.collect(),
..Default::default()
}
}
#[test]
fn envelope_clamps_to_last_point_when_counter_exceeds_end() {
let env = make_envelope(vec![(0, 1.0), (10, 0.5), (20, 0.0)]);
let mut state = StateEnvelope::new(&env, 1.0);
state.counter = 50; state.tick(false);
assert_eq!(state.value, 0.0, "should clamp to last point value");
}
#[test]
fn envelope_clamps_to_last_point_nonzero_tail() {
let env = make_envelope(vec![(0, 0.0), (10, 0.3)]);
let mut state = StateEnvelope::new(&env, 1.0);
state.counter = 100;
state.tick(false);
assert!(
(state.value - 0.3).abs() < 1e-6,
"expected 0.3, got {}",
state.value
);
}
#[test]
fn envelope_position_set_past_end_does_not_stay_at_default() {
let env = make_envelope(vec![(0, 1.0), (50, 0.0)]);
let mut state = StateEnvelope::new(&env, 1.0);
state.counter = 999;
state.tick(false);
assert_eq!(state.value, 0.0);
}
#[test]
fn envelope_counter_at_last_point_exactly() {
let env = make_envelope(vec![(0, 1.0), (10, 0.5), (20, 0.25)]);
let mut state = StateEnvelope::new(&env, 1.0);
state.counter = 20;
state.tick(false);
assert!(
(state.value - 0.25).abs() < 1e-6,
"expected 0.25, got {}",
state.value
);
}
}