use std::time::Duration;
use super::super::effects::{BlendMode, EffectLayer, EffectType};
use super::super::tempo::TempoMap;
#[derive(Clone)]
pub(crate) struct ParseContext {
pub tempo_map: Option<TempoMap>,
pub cue_time: Duration,
pub offset_secs: f64,
pub unshifted_score_time: Option<Duration>,
pub score_measure: Option<u32>,
pub measure_offset: u32,
}
#[derive(Debug, Clone)]
pub struct LightShow {
pub name: String,
pub cues: Vec<Cue>,
pub tempo_map: Option<crate::lighting::tempo::TempoMap>,
}
#[derive(Debug, Clone)]
pub struct Sequence {
pub cues: Vec<Cue>,
pub bpm: f64,
}
impl Sequence {
pub fn duration(&self) -> Duration {
if self.cues.is_empty() {
return Duration::ZERO;
}
let mut max_completion_time = Duration::ZERO;
for cue in &self.cues {
for effect in &cue.effects {
let effect_duration = effect.total_duration();
let completion_time = cue.time + effect_duration;
if completion_time > max_completion_time {
max_completion_time = completion_time;
}
}
}
max_completion_time
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum SequenceLoop {
Once,
Loop, PingPong,
Random,
Count(usize), }
#[derive(Debug, Clone)]
pub(crate) struct UnexpandedSequenceCue {
pub time: Duration,
pub effects: Vec<Effect>,
pub layer_commands: Vec<LayerCommand>,
pub stop_sequences: Vec<String>,
pub sequence_references: Vec<(String, Option<SequenceLoop>)>, }
#[derive(Debug, Clone)]
pub struct Cue {
pub time: Duration,
pub effects: Vec<Effect>,
pub layer_commands: Vec<LayerCommand>,
pub stop_sequences: Vec<String>, pub start_sequences: Vec<String>, }
#[derive(Debug, Clone)]
pub struct Effect {
pub groups: Vec<String>,
pub effect_type: EffectType,
pub layer: Option<EffectLayer>,
pub blend_mode: Option<BlendMode>,
pub up_time: Option<Duration>,
pub hold_time: Option<Duration>,
pub down_time: Option<Duration>,
pub sequence_name: Option<String>, }
impl Effect {
pub fn total_duration(&self) -> Duration {
if let EffectType::Dimmer { duration, .. } = &self.effect_type {
return *duration;
}
let effect_duration = self.effect_type.duration();
let hold = self.hold_time.unwrap_or(effect_duration);
self.up_time.unwrap_or(Duration::ZERO) + hold + self.down_time.unwrap_or(Duration::ZERO)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LayerCommandType {
Clear,
Release,
Freeze,
Unfreeze,
Master,
}
#[derive(Debug, Clone)]
pub struct LayerCommand {
pub command_type: LayerCommandType,
pub layer: Option<EffectLayer>, pub fade_time: Option<Duration>,
pub intensity: Option<f64>,
pub speed: Option<f64>,
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
use crate::lighting::effects::{
ChaseDirection, ChasePattern, Color, CycleDirection, CycleTransition, DimmerCurve,
TempoAwareFrequency, TempoAwareSpeed,
};
fn static_effect(duration: Duration) -> Effect {
Effect {
groups: vec![],
effect_type: EffectType::Static {
parameters: HashMap::new(),
duration,
},
layer: None,
blend_mode: None,
up_time: None,
hold_time: None,
down_time: None,
sequence_name: None,
}
}
fn timed_effect(
effect_type: EffectType,
up: Option<Duration>,
hold: Option<Duration>,
down: Option<Duration>,
) -> Effect {
Effect {
groups: vec![],
effect_type,
layer: None,
blend_mode: None,
up_time: up,
hold_time: hold,
down_time: down,
sequence_name: None,
}
}
#[test]
fn effect_static_with_duration() {
let e = static_effect(Duration::from_secs(5));
assert_eq!(e.total_duration(), Duration::from_secs(5));
}
#[test]
fn effect_static_with_duration_and_up_down() {
let e = timed_effect(
EffectType::Static {
parameters: HashMap::new(),
duration: Duration::from_secs(5),
},
Some(Duration::from_secs(1)),
None, Some(Duration::from_secs(2)),
);
assert_eq!(e.total_duration(), Duration::from_secs(8));
}
#[test]
fn effect_dimmer_uses_effect_duration() {
let e = timed_effect(
EffectType::Dimmer {
start_level: 0.0,
end_level: 1.0,
duration: Duration::from_secs(3),
curve: DimmerCurve::Linear,
},
None,
None,
None,
);
assert_eq!(e.total_duration(), Duration::from_secs(3));
}
#[test]
fn effect_strobe_with_duration() {
let e = timed_effect(
EffectType::Strobe {
frequency: TempoAwareFrequency::Fixed(10.0),
duration: Duration::from_secs(5),
},
None,
None,
None,
);
assert_eq!(e.total_duration(), Duration::from_secs(5));
}
#[test]
fn effect_pulse_with_duration() {
let e = timed_effect(
EffectType::Pulse {
base_level: 0.0,
pulse_amplitude: 1.0,
frequency: TempoAwareFrequency::Fixed(2.0),
duration: Duration::from_secs(5),
},
None,
None,
None,
);
assert_eq!(e.total_duration(), Duration::from_secs(5));
}
#[test]
fn effect_color_cycle_with_duration() {
let e = timed_effect(
EffectType::ColorCycle {
colors: vec![Color::new(255, 0, 0)],
speed: TempoAwareSpeed::Fixed(1.0),
direction: CycleDirection::Forward,
transition: CycleTransition::Fade,
duration: Duration::from_secs(5),
},
None,
None,
None,
);
assert_eq!(e.total_duration(), Duration::from_secs(5));
}
#[test]
fn effect_chase_with_duration() {
let e = timed_effect(
EffectType::Chase {
pattern: ChasePattern::Linear,
speed: TempoAwareSpeed::Fixed(1.0),
direction: ChaseDirection::LeftToRight,
transition: CycleTransition::Snap,
duration: Duration::from_secs(5),
},
None,
None,
None,
);
assert_eq!(e.total_duration(), Duration::from_secs(5));
}
#[test]
fn effect_rainbow_with_duration() {
let e = timed_effect(
EffectType::Rainbow {
speed: TempoAwareSpeed::Fixed(0.5),
saturation: 1.0,
brightness: 1.0,
duration: Duration::from_secs(5),
},
None,
None,
None,
);
assert_eq!(e.total_duration(), Duration::from_secs(5));
}
#[test]
fn effect_with_up_hold_down() {
let e = timed_effect(
EffectType::Strobe {
frequency: TempoAwareFrequency::Fixed(10.0),
duration: Duration::from_secs(5),
},
Some(Duration::from_secs(1)),
Some(Duration::from_secs(3)),
Some(Duration::from_secs(1)),
);
assert_eq!(e.total_duration(), Duration::from_secs(5));
}
#[test]
fn sequence_empty() {
let seq = Sequence {
cues: vec![],
bpm: 120.0,
};
assert_eq!(seq.duration(), Duration::ZERO);
}
#[test]
fn sequence_single_timed_cue() {
let seq = Sequence {
cues: vec![Cue {
time: Duration::from_secs(2),
effects: vec![static_effect(Duration::from_secs(3))],
layer_commands: vec![],
stop_sequences: vec![],
start_sequences: vec![],
}],
bpm: 120.0,
};
assert_eq!(seq.duration(), Duration::from_secs(5));
}
#[test]
fn sequence_max_completion_across_cues() {
let seq = Sequence {
cues: vec![
Cue {
time: Duration::from_secs(0),
effects: vec![static_effect(Duration::from_secs(3))],
layer_commands: vec![],
stop_sequences: vec![],
start_sequences: vec![],
},
Cue {
time: Duration::from_secs(5),
effects: vec![static_effect(Duration::from_secs(10))],
layer_commands: vec![],
stop_sequences: vec![],
start_sequences: vec![],
},
],
bpm: 120.0,
};
assert_eq!(seq.duration(), Duration::from_secs(15));
}
#[test]
fn sequence_mixed_short_and_long() {
let seq = Sequence {
cues: vec![Cue {
time: Duration::from_secs(1),
effects: vec![
static_effect(Duration::from_secs(1)), static_effect(Duration::from_secs(4)), ],
layer_commands: vec![],
stop_sequences: vec![],
start_sequences: vec![],
}],
bpm: 120.0,
};
assert_eq!(seq.duration(), Duration::from_secs(5));
}
#[test]
fn layer_command_type_equality() {
assert_eq!(LayerCommandType::Clear, LayerCommandType::Clear);
assert_ne!(LayerCommandType::Clear, LayerCommandType::Release);
assert_ne!(LayerCommandType::Freeze, LayerCommandType::Unfreeze);
}
}