#[cfg(test)]
mod tests {
use crate::common::spsc::{
RT_STATUS_DEGRADE_MINIMAL, RT_STATUS_DEGRADE_REDUCED, RtStatusFlags,
};
use crate::dsp::adaptive::*;
use std::sync::atomic::Ordering;
fn rt_flags() -> RtStatusFlags {
RtStatusFlags::default()
}
fn above_threshold(budget_us: u64, ratio: f32) -> u64 {
((budget_us as f32 * ratio).ceil() as u64) + 1
}
fn below_threshold(budget_us: u64, ratio: f32) -> u64 {
((budget_us as f32 * ratio).floor() as u64).saturating_sub(1)
}
#[test]
fn mode_off_no_transitions() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Off);
let budget = 1000;
for _ in 0..10 {
adaptive.update(above_threshold(budget, 0.99), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Full);
assert_eq!(flags.degrade_transitions_total.load(Ordering::Relaxed), 0);
}
#[test]
fn conservative_full_to_reduced() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Reduced);
assert!(flags.check_flag(RT_STATUS_DEGRADE_REDUCED));
assert!(!flags.check_flag(RT_STATUS_DEGRADE_MINIMAL));
assert_eq!(flags.degrade_transitions_total.load(Ordering::Relaxed), 1);
}
#[test]
fn conservative_full_stays_full_if_not_consecutive() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
adaptive.update(below_threshold(budget, 0.69), budget, 48000, &flags); adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
assert_eq!(adaptive.state(), AdaptiveState::Full); assert_eq!(flags.degrade_transitions_total.load(Ordering::Relaxed), 0);
}
#[test]
fn conservative_reduced_to_minimal() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Reduced);
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.86), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Minimal);
assert!(flags.check_flag(RT_STATUS_DEGRADE_REDUCED));
assert!(flags.check_flag(RT_STATUS_DEGRADE_MINIMAL));
assert_eq!(flags.degrade_transitions_total.load(Ordering::Relaxed), 2);
}
#[test]
fn conservative_reduced_recovers_to_full() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Reduced);
for _ in 0..5 {
adaptive.update(below_threshold(budget, 0.34), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Full);
assert!(!flags.check_flag(RT_STATUS_DEGRADE_REDUCED));
assert!(!flags.check_flag(RT_STATUS_DEGRADE_MINIMAL));
assert_eq!(flags.degrade_transitions_total.load(Ordering::Relaxed), 2);
}
#[test]
fn conservative_reduced_recovery_resets_on_intermediate() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Reduced);
for _ in 0..3 {
adaptive.update(below_threshold(budget, 0.34), budget, 48000, &flags);
}
adaptive.update(budget / 2, budget, 48000, &flags);
for _ in 0..5 {
adaptive.update(below_threshold(budget, 0.34), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Full);
}
#[test]
fn minimal_recovers_to_reduced() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.86), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Minimal);
assert_eq!(flags.degrade_transitions_total.load(Ordering::Relaxed), 2);
for _ in 0..5 {
adaptive.update(below_threshold(budget, 0.42), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Reduced);
assert!(flags.check_flag(RT_STATUS_DEGRADE_REDUCED));
assert!(!flags.check_flag(RT_STATUS_DEGRADE_MINIMAL));
assert_eq!(flags.degrade_transitions_total.load(Ordering::Relaxed), 3);
}
#[test]
fn aggressive_full_to_reduced_lower_threshold() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Aggressive);
let budget = 1000;
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.56), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Reduced);
}
#[test]
fn aggressive_reduced_to_minimal() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Aggressive);
let budget = 1000;
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.56), budget, 48000, &flags);
}
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Minimal);
}
#[test]
fn set_mode_resets_state() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.86), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Minimal);
adaptive.set_mode(AdaptiveComputeMode::Off, &flags);
assert_eq!(adaptive.state(), AdaptiveState::Full);
assert!(!adaptive.is_crossfading());
assert!(!flags.check_flag(RT_STATUS_DEGRADE_REDUCED));
assert!(!flags.check_flag(RT_STATUS_DEGRADE_MINIMAL));
}
#[test]
fn wavenet_effective_layers_full() {
let adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
assert_eq!(adaptive.wavenet_effective_layers(8), 8);
assert_eq!(adaptive.wavenet_effective_layers(4), 4);
}
#[test]
fn wavenet_effective_layers_reduced() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Reduced);
assert_eq!(adaptive.wavenet_effective_layers(8), 6);
assert_eq!(adaptive.wavenet_effective_layers(4), 3);
assert_eq!(adaptive.wavenet_effective_layers(1), 1);
}
#[test]
fn wavenet_effective_layers_minimal() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.86), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Minimal);
assert_eq!(adaptive.wavenet_effective_layers(8), 4);
assert_eq!(adaptive.wavenet_effective_layers(1), 1);
}
#[test]
fn lstm_effective_layers() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
assert_eq!(adaptive.lstm_effective_layers(2), 2);
assert_eq!(adaptive.lstm_effective_layers(1), 1);
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert_eq!(adaptive.lstm_effective_layers(2), 1);
assert_eq!(adaptive.lstm_effective_layers(1), 1);
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.86), budget, 48000, &flags);
}
assert_eq!(adaptive.lstm_effective_layers(2), 0);
}
#[test]
fn crossfade_starts_on_transition() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
assert!(!adaptive.is_crossfading());
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert!(adaptive.is_crossfading());
}
#[test]
fn crossfade_completes() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert!(adaptive.is_crossfading());
for _ in 0..25 {
adaptive.crossfade_multiplier(48000, 64);
}
assert!(!adaptive.is_crossfading());
}
#[test]
fn test_crossfade_prev_state_and_multipliers() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
assert_eq!(adaptive.prev_state(), AdaptiveState::Full);
assert_eq!(adaptive.state(), AdaptiveState::Full);
assert_eq!(adaptive.current_crossfade_multiplier(), 0.0);
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert_eq!(adaptive.prev_state(), AdaptiveState::Full);
assert_eq!(adaptive.state(), AdaptiveState::Reduced);
assert!(adaptive.is_crossfading());
let total_layers = 16;
let old_layers =
adaptive.wavenet_effective_layers_for_state(adaptive.prev_state(), total_layers);
let new_layers =
adaptive.wavenet_effective_layers_for_state(adaptive.state(), total_layers);
assert_eq!(old_layers, 16); assert_eq!(new_layers, 12);
let m0 = adaptive.current_crossfade_multiplier();
assert_eq!(m0, 0.0);
let m0_again = adaptive.current_crossfade_multiplier();
assert_eq!(m0_again, 0.0);
adaptive.crossfade_multiplier(48000, 512);
let m1 = adaptive.current_crossfade_multiplier();
assert!(m1 > 0.0 && m1 < 1.0);
adaptive.crossfade_multiplier(48000, 1500);
assert!(!adaptive.is_crossfading());
assert_eq!(adaptive.current_crossfade_multiplier(), 1.0);
}
#[test]
fn crossfade_linearity_midpoint() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert!(adaptive.is_crossfading());
for _ in 0..12 {
adaptive.crossfade_multiplier(48000, 64);
}
let progress = adaptive.current_crossfade_multiplier();
assert!(
(progress - 0.5).abs() < 1e-6,
"expected progress ≈ 0.5 at midpoint, got {progress}"
);
}
#[test]
fn crossfade_linearity_near_end() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert!(adaptive.is_crossfading());
for _ in 0..21 {
adaptive.crossfade_multiplier(48000, 64);
}
adaptive.crossfade_multiplier(48000, 39);
assert!(adaptive.is_crossfading());
let progress = adaptive.current_crossfade_multiplier();
assert!(
progress > 0.9,
"expected progress > 0.9 at 90% of total, got {progress}"
);
}
#[test]
fn crossfade_rebased_on_rapid_consecutive_transitions() {
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
let budget = 1000;
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert!(adaptive.is_crossfading());
assert_eq!(adaptive.state(), AdaptiveState::Reduced);
assert_eq!(adaptive.prev_state(), AdaptiveState::Full);
let mut prev_multiplier = 0.0;
for _ in 0..9 {
prev_multiplier = adaptive.crossfade_multiplier(48000, 64);
}
assert!(adaptive.is_crossfading());
assert!(
(prev_multiplier - 1.0 / 3.0).abs() < 0.01,
"expected multiplier ≈ 1/3, got {}",
prev_multiplier
);
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.86), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Minimal);
assert_eq!(adaptive.prev_state(), AdaptiveState::Full);
assert!(adaptive.is_crossfading());
let m_after = adaptive.current_crossfade_multiplier();
let step = (m_after - prev_multiplier).abs();
assert!(
step < 0.05,
"envelope discontinuity after rapid re-base: \
prev={prev_multiplier:.4}, after={m_after:.4}, step={step:.4}"
);
}
#[test]
fn prev_state_invariant_under_chained_crossfades() {
let budget = 1000;
{
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Reduced);
assert_eq!(adaptive.prev_state(), AdaptiveState::Full);
assert!(adaptive.is_crossfading());
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.86), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Minimal);
assert_eq!(adaptive.prev_state(), AdaptiveState::Full);
assert!(adaptive.is_crossfading());
}
{
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Reduced);
assert_eq!(adaptive.prev_state(), AdaptiveState::Full);
assert!(adaptive.is_crossfading());
adaptive.crossfade_multiplier(48000, 512);
for _ in 0..5 {
adaptive.update(below_threshold(budget, 0.34), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Full);
assert_eq!(adaptive.prev_state(), AdaptiveState::Full);
assert!(adaptive.is_crossfading());
}
{
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert_eq!(adaptive.prev_state(), AdaptiveState::Full);
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.86), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Minimal);
assert_eq!(adaptive.prev_state(), AdaptiveState::Full);
assert!(adaptive.is_crossfading());
for _ in 0..5 {
adaptive.update(below_threshold(budget, 0.42), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Reduced);
assert_eq!(adaptive.prev_state(), AdaptiveState::Full);
assert!(adaptive.is_crossfading());
}
{
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert_eq!(adaptive.prev_state(), AdaptiveState::Full);
assert_eq!(adaptive.state(), AdaptiveState::Reduced);
adaptive.crossfade_multiplier(48000, 1600);
assert!(!adaptive.is_crossfading());
assert_eq!(adaptive.current_crossfade_multiplier(), 1.0);
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.86), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Minimal);
assert_eq!(adaptive.prev_state(), AdaptiveState::Reduced);
assert!(adaptive.is_crossfading());
}
{
let flags = rt_flags();
let mut adaptive = AdaptiveCompute::new(AdaptiveComputeMode::Conservative);
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.71), budget, 48000, &flags);
}
assert_eq!(adaptive.prev_state(), AdaptiveState::Full);
for _ in 0..12 {
adaptive.crossfade_multiplier(48000, 64);
}
let m_before = adaptive.current_crossfade_multiplier();
assert!((m_before - 0.5).abs() < 0.02);
for _ in 0..3 {
adaptive.update(above_threshold(budget, 0.86), budget, 48000, &flags);
}
assert_eq!(adaptive.state(), AdaptiveState::Minimal);
assert_eq!(adaptive.prev_state(), AdaptiveState::Full);
let m_after = adaptive.current_crossfade_multiplier();
assert!(
(m_after - m_before).abs() < 0.05,
"envelope discontinuity after chained re-base: \
before={m_before:.4}, after={m_after:.4}"
);
}
}
}