use core::time::Duration;
use crate::module::Module;
#[cfg(not(feature = "std"))]
mod no_std_math {
#[inline]
pub fn round_f64(x: f64) -> f64 {
if x >= 0.0 {
(x + 0.5) as i128 as f64
} else {
(x - 0.5) as i128 as f64
}
}
#[inline]
pub fn is_finite_f64(x: f64) -> bool {
if x != x {
return false;
}
x - x == 0.0
}
}
#[cfg(not(feature = "std"))]
use no_std_math::{is_finite_f64, round_f64};
#[cfg(feature = "std")]
#[inline]
fn round_f64(x: f64) -> f64 {
x.round()
}
#[cfg(feature = "std")]
#[inline]
fn is_finite_f64(x: f64) -> bool {
x.is_finite()
}
pub trait ModuleDuration {
fn duration(&self, song: usize) -> Duration;
}
impl ModuleDuration for Module {
fn duration(&self, song: usize) -> Duration {
compute_duration(self, song)
}
}
pub fn compute_duration(module: &Module, song: usize) -> Duration {
let song_u16 = song.min(u16::MAX as usize) as u16;
let mut total_secs: f64 = 0.0;
for e in &module.timeline_map.entries {
if e.song != song_u16 {
continue;
}
let bpm = e.bpm_at_row.max(1) as f64;
total_secs += e.speed_at_row as f64 * 2.5 / bpm;
}
secs_to_duration(total_secs)
}
#[inline]
fn secs_to_duration(secs: f64) -> Duration {
if !is_finite_f64(secs) || secs <= 0.0 {
return Duration::ZERO;
}
let nanos_total = round_f64(secs * 1_000_000_000.0);
if nanos_total >= (u64::MAX as f64) * 1_000_000_000.0 {
return Duration::new(u64::MAX, 999_999_999);
}
let nanos_total = nanos_total as u128;
let secs = (nanos_total / 1_000_000_000) as u64;
let nanos = (nanos_total % 1_000_000_000) as u32;
Duration::new(secs, nanos)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::daw::build_timeline::build_timeline_layer;
use crate::prelude::*;
use alloc::vec;
use alloc::vec::Vec;
fn row_with(n: usize, ge: Vec<GlobalEffect>) -> Row {
let mut r: Row = (0..n).map(|_| TrackUnit::default()).collect();
r[0].global_effects = ge;
r
}
fn empty_row(n: usize) -> Row {
(0..n).map(|_| TrackUnit::default()).collect()
}
fn build_module(pattern: Pattern, order: Vec<usize>) -> Module {
let mut m = Module::default();
let pattern_order = vec![order];
let patterns = vec![pattern];
build_timeline_layer(&mut m, &pattern_order, &patterns);
m
}
fn close_to(d: Duration, expected: Duration) -> bool {
let delta = d.abs_diff(expected);
delta < Duration::from_millis(1)
}
#[test]
fn empty_module_is_zero() {
let m = Module::default();
assert_eq!(m.duration(0), Duration::ZERO);
}
#[test]
fn out_of_range_song_is_zero() {
let m = build_module(vec![empty_row(4); 8], vec![0]);
assert_eq!(m.duration(42), Duration::ZERO);
}
#[test]
fn defaults_one_pattern_64_rows() {
let pat: Pattern = (0..64).map(|_| empty_row(4)).collect();
let m = build_module(pat, vec![0]);
let expected = Duration::from_secs_f64(64.0 * 6.0 * 2.5 / 125.0);
assert!(close_to(m.duration(0), expected), "got {:?}", m.duration(0));
}
#[test]
fn speed_change_doubles_time() {
let mut pat: Pattern = (0..64).map(|_| empty_row(4)).collect();
pat[0] = row_with(4, vec![GlobalEffect::Speed(12)]);
let m = build_module(pat, vec![0]);
let expected = Duration::from_secs_f64(64.0 * 12.0 * 2.5 / 125.0);
assert!(close_to(m.duration(0), expected));
}
#[test]
fn bpm_change_halves_time() {
let mut pat: Pattern = (0..64).map(|_| empty_row(4)).collect();
pat[0] = row_with(4, vec![GlobalEffect::Bpm(250)]);
let m = build_module(pat, vec![0]);
let expected = Duration::from_secs_f64(64.0 * 6.0 * 2.5 / 250.0);
assert!(close_to(m.duration(0), expected));
}
#[test]
fn position_jump_back_to_start_stops_at_one_pass() {
let mut pat: Pattern = (0..4).map(|_| empty_row(4)).collect();
pat[3] = row_with(4, vec![GlobalEffect::PositionJump(0)]);
let m = build_module(pat, vec![0]);
let expected = Duration::from_secs_f64(4.0 * 6.0 * 2.5 / 125.0);
assert!(close_to(m.duration(0), expected));
}
#[test]
fn pattern_break_skips_remaining_rows() {
let mut pat0: Pattern = (0..16).map(|_| empty_row(4)).collect();
pat0[7] = row_with(4, vec![GlobalEffect::PatternBreak(0)]);
let pat1: Pattern = (0..64).map(|_| empty_row(4)).collect();
let mut m = Module::default();
build_timeline_layer(&mut m, &[vec![0, 1]], &[pat0, pat1]);
let expected = Duration::from_secs_f64((8 + 64) as f64 * 6.0 * 2.5 / 125.0);
assert!(close_to(m.duration(0), expected));
}
#[test]
fn pattern_delay_multiplies_row_time() {
let mut pat: Pattern = (0..4).map(|_| empty_row(4)).collect();
pat[1] = row_with(
4,
vec![GlobalEffect::PatternDelay {
quantity: 3,
tempo: false,
}],
);
let m = build_module(pat, vec![0]);
let expected = Duration::from_secs_f64(7.0 * 6.0 * 2.5 / 125.0);
assert!(close_to(m.duration(0), expected));
}
#[test]
fn pattern_loop_runs_n_plus_one_times() {
let mut pat: Pattern = (0..4).map(|_| empty_row(4)).collect();
pat[0] = row_with(4, vec![GlobalEffect::PatternLoop(0)]);
pat[3] = row_with(4, vec![GlobalEffect::PatternLoop(2)]);
let m = build_module(pat, vec![0]);
let expected = Duration::from_secs_f64(12.0 * 6.0 * 2.5 / 125.0);
assert!(close_to(m.duration(0), expected));
}
#[test]
fn speed_zero_ends_song_when_quirk_on() {
let mut pat: Pattern = vec![empty_row(4); 4];
pat[2] = row_with(4, vec![GlobalEffect::Speed(0)]);
let m = build_module(pat.clone(), vec![0]);
let four_rows = Duration::from_secs_f64(4.0 * 6.0 * 2.5 / 125.0);
assert!(close_to(m.duration(0), four_rows));
let mut m = Module {
profile: CompatibilityProfile::pt(),
..Default::default()
};
build_timeline_layer(&mut m, &[vec![0]], &[pat]);
let three_rows = Duration::from_secs_f64(3.0 * 6.0 * 2.5 / 125.0);
assert!(close_to(m.duration(0), three_rows));
}
}