use std::time::Duration;
#[inline]
fn duration_to_rate(duration_secs: f64) -> f64 {
if duration_secs <= 0.0 {
0.0
} else {
1.0 / duration_secs
}
}
fn measures_to_duration_secs(
measures: f64,
tempo_map: Option<&crate::lighting::tempo::TempoMap>,
at_time: Duration,
) -> f64 {
if let Some(tm) = tempo_map {
let duration = tm.measures_to_duration(measures, at_time, 0.0);
duration.as_secs_f64()
} else {
let beats = measures * 4.0;
beats * 60.0 / crate::lighting::tempo::DEFAULT_BPM
}
}
fn beats_to_duration_secs(
beats: f64,
tempo_map: Option<&crate::lighting::tempo::TempoMap>,
at_time: Duration,
) -> f64 {
if let Some(tm) = tempo_map {
let duration = tm.beats_to_duration(beats, at_time, 0.0);
duration.as_secs_f64()
} else {
beats * 60.0 / crate::lighting::tempo::DEFAULT_BPM
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum TempoAwareValue {
Fixed(f64),
Measures(f64),
Beats(f64),
Seconds(f64),
}
impl TempoAwareValue {
pub fn to_rate(
&self,
tempo_map: Option<&crate::lighting::tempo::TempoMap>,
at_time: Duration,
) -> f64 {
match self {
TempoAwareValue::Fixed(rate) => *rate,
TempoAwareValue::Seconds(duration) => duration_to_rate(*duration),
TempoAwareValue::Measures(measures) => {
if *measures <= 0.0 {
return 0.0;
}
let duration_secs = measures_to_duration_secs(*measures, tempo_map, at_time);
duration_to_rate(duration_secs)
}
TempoAwareValue::Beats(beats) => {
if *beats <= 0.0 {
return 0.0;
}
let duration_secs = beats_to_duration_secs(*beats, tempo_map, at_time);
duration_to_rate(duration_secs)
}
}
}
#[inline]
pub fn to_cycles_per_second(
&self,
tempo_map: Option<&crate::lighting::tempo::TempoMap>,
at_time: Duration,
) -> f64 {
self.to_rate(tempo_map, at_time)
}
#[inline]
pub fn to_hz(
&self,
tempo_map: Option<&crate::lighting::tempo::TempoMap>,
at_time: Duration,
) -> f64 {
self.to_rate(tempo_map, at_time)
}
}
pub type TempoAwareSpeed = TempoAwareValue;
pub type TempoAwareFrequency = TempoAwareValue;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn duration_to_rate_positive() {
assert!((duration_to_rate(2.0) - 0.5).abs() < 1e-9);
}
#[test]
fn duration_to_rate_one_second() {
assert!((duration_to_rate(1.0) - 1.0).abs() < 1e-9);
}
#[test]
fn duration_to_rate_zero() {
assert_eq!(duration_to_rate(0.0), 0.0);
}
#[test]
fn duration_to_rate_negative() {
assert_eq!(duration_to_rate(-1.0), 0.0);
}
#[test]
fn value_fixed() {
let val = TempoAwareValue::Fixed(2.5);
assert!((val.to_rate(None, Duration::ZERO) - 2.5).abs() < 1e-9);
}
#[test]
fn value_seconds() {
let val = TempoAwareValue::Seconds(4.0);
assert!((val.to_rate(None, Duration::ZERO) - 0.25).abs() < 1e-9);
}
#[test]
fn value_seconds_zero() {
let val = TempoAwareValue::Seconds(0.0);
assert_eq!(val.to_rate(None, Duration::ZERO), 0.0);
}
#[test]
fn value_beats_no_tempo_map() {
let val = TempoAwareValue::Beats(1.0);
let rate = val.to_rate(None, Duration::ZERO);
assert!((rate - 2.0).abs() < 1e-9);
}
#[test]
fn value_beats_two_beats_no_tempo_map() {
let val = TempoAwareValue::Beats(2.0);
let rate = val.to_rate(None, Duration::ZERO);
assert!((rate - 1.0).abs() < 1e-9);
}
#[test]
fn value_beats_zero() {
let val = TempoAwareValue::Beats(0.0);
assert_eq!(val.to_rate(None, Duration::ZERO), 0.0);
}
#[test]
fn value_beats_negative() {
let val = TempoAwareValue::Beats(-1.0);
assert_eq!(val.to_rate(None, Duration::ZERO), 0.0);
}
#[test]
fn value_measures_no_tempo_map() {
let val = TempoAwareValue::Measures(1.0);
let rate = val.to_rate(None, Duration::ZERO);
assert!((rate - 0.5).abs() < 1e-9);
}
#[test]
fn value_measures_zero() {
let val = TempoAwareValue::Measures(0.0);
assert_eq!(val.to_rate(None, Duration::ZERO), 0.0);
}
#[test]
fn value_measures_negative() {
let val = TempoAwareValue::Measures(-1.0);
assert_eq!(val.to_rate(None, Duration::ZERO), 0.0);
}
#[test]
fn speed_alias_works() {
let speed = TempoAwareSpeed::Fixed(2.5);
assert!((speed.to_cycles_per_second(None, Duration::ZERO) - 2.5).abs() < 1e-9);
}
#[test]
fn frequency_alias_works() {
let freq = TempoAwareFrequency::Fixed(10.0);
assert!((freq.to_hz(None, Duration::ZERO) - 10.0).abs() < 1e-9);
}
#[test]
fn value_beats_with_tempo_map() {
use crate::lighting::tempo::{TempoMap, TimeSignature};
let tm = TempoMap::new(Duration::ZERO, 90.0, TimeSignature::new(4, 4), vec![]);
let val = TempoAwareValue::Beats(1.0);
let rate = val.to_rate(Some(&tm), Duration::ZERO);
assert!((rate - 1.5).abs() < 0.01);
}
#[test]
fn value_measures_with_tempo_map() {
use crate::lighting::tempo::{TempoMap, TimeSignature};
let tm = TempoMap::new(Duration::ZERO, 60.0, TimeSignature::new(4, 4), vec![]);
let val = TempoAwareValue::Measures(1.0);
let rate = val.to_rate(Some(&tm), Duration::ZERO);
assert!((rate - 0.25).abs() < 0.01);
}
#[test]
fn value_beats_with_tempo_map_via_hz() {
use crate::lighting::tempo::{TempoMap, TimeSignature};
let tm = TempoMap::new(Duration::ZERO, 60.0, TimeSignature::new(4, 4), vec![]);
let freq = TempoAwareFrequency::Beats(1.0);
let hz = freq.to_hz(Some(&tm), Duration::ZERO);
assert!((hz - 1.0).abs() < 0.01);
}
}