use super::common::*;
#[cfg(test)]
use crate::lighting::effects::*;
use crate::lighting::engine::EffectEngine;
use crate::lighting::parser::parse_light_shows;
use crate::lighting::timeline::LightingTimeline;
use std::collections::HashMap;
use std::time::Duration;
#[test]
fn test_crossfade_multiplier_calculation() {
let mut effect = EffectInstance::new(
"test".to_string(),
EffectType::Static {
parameters: HashMap::new(),
duration: Duration::from_secs(5),
},
vec!["test_fixture".to_string()],
None,
None,
None,
);
effect.up_time = Some(Duration::from_secs(2));
effect.hold_time = Some(Duration::from_secs(8));
effect.down_time = None;
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(0)),
0.0
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(1)),
0.5
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(2)),
1.0
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(5)),
1.0
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(10)),
1.0
);
effect.up_time = None;
effect.hold_time = Some(Duration::from_secs(8));
effect.down_time = Some(Duration::from_secs(2));
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(8)),
1.0
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(9)),
0.5
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(10)),
0.0
);
effect.up_time = Some(Duration::from_secs(1));
effect.hold_time = Some(Duration::from_secs(8));
effect.down_time = Some(Duration::from_secs(1));
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(0)),
0.0
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_millis(500)),
0.5
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(1)),
1.0
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(5)),
1.0
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(9)),
1.0
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_millis(9500)),
0.5
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(10)),
0.0
);
}
#[test]
fn test_crossfade_multiplier_no_up_time_no_hold_time() {
let mut effect = EffectInstance::new(
"test".to_string(),
EffectType::Static {
parameters: HashMap::new(),
duration: Duration::from_secs(5),
},
vec!["test_fixture".to_string()],
None,
None,
None,
);
effect.up_time = Some(Duration::from_secs(0)); effect.hold_time = Some(Duration::from_secs(0)); effect.down_time = Some(Duration::from_secs(2));
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(0)),
1.0
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_millis(500)),
0.75
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(1)),
0.5
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_millis(1500)),
0.25
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(2)),
0.0
);
assert_eq!(
effect.calculate_crossfade_multiplier(Duration::from_secs(3)),
0.0
);
}
#[test]
fn test_static_effect_crossfade_comprehensive() {
let mut engine = EffectEngine::new();
let mut channels = HashMap::new();
channels.insert("red".to_string(), 1);
channels.insert("green".to_string(), 2);
channels.insert("blue".to_string(), 3);
let fixture = FixtureInfo::new(
"test_fixture".to_string(),
1,
1,
"RGB_Par".to_string(),
channels,
None,
);
engine.register_fixture(fixture);
let mut parameters = HashMap::new();
parameters.insert("red".to_string(), 1.0);
parameters.insert("green".to_string(), 0.0);
parameters.insert("blue".to_string(), 0.0);
let mut static_effect = create_effect_with_timing(
"static_test".to_string(),
EffectType::Static {
parameters,
duration: Duration::from_secs(10), },
vec!["test_fixture".to_string()],
EffectLayer::Background,
BlendMode::Replace,
Some(Duration::from_secs(1)), Some(Duration::from_secs(1)), );
static_effect.hold_time = Some(Duration::from_secs(8));
engine.start_effect(static_effect).unwrap();
let commands = engine.update(Duration::from_secs(0), None).unwrap();
let red_cmd = commands.iter().find(|cmd| cmd.channel == 1).unwrap();
assert_eq!(red_cmd.value, 0);
let commands = engine.update(Duration::from_millis(500), None).unwrap();
let red_cmd = commands.iter().find(|cmd| cmd.channel == 1).unwrap();
assert!(red_cmd.value > 0 && red_cmd.value < 255);
let commands = engine.update(Duration::from_millis(1500), None).unwrap(); let red_cmd = commands.iter().find(|cmd| cmd.channel == 1).unwrap();
assert_eq!(red_cmd.value, 255);
let commands = engine.update(Duration::from_secs(7), None).unwrap(); let red_cmd = commands.iter().find(|cmd| cmd.channel == 1).unwrap();
assert_eq!(red_cmd.value, 255);
let commands = engine.update(Duration::from_millis(500), None).unwrap(); let red_cmd = commands.iter().find(|cmd| cmd.channel == 1).unwrap();
assert!(red_cmd.value > 0 && red_cmd.value < 255);
let commands = engine.update(Duration::from_millis(500), None).unwrap(); let red_cmd = commands.iter().find(|cmd| cmd.channel == 1);
if let Some(red_cmd) = red_cmd {
assert_eq!(red_cmd.value, 0); }
}
#[test]
fn test_color_cycle_effect_crossfade() {
let mut engine = EffectEngine::new();
let mut channels = HashMap::new();
channels.insert("red".to_string(), 1);
channels.insert("green".to_string(), 2);
channels.insert("blue".to_string(), 3);
let fixture = FixtureInfo::new(
"test_fixture".to_string(),
1,
1,
"RGB_Par".to_string(),
channels,
None,
);
engine.register_fixture(fixture);
let mut cycle_effect = create_effect_with_timing(
"cycle_test".to_string(),
EffectType::ColorCycle {
colors: vec![
Color {
r: 255,
g: 0,
b: 0,
w: None,
}, Color {
r: 0,
g: 255,
b: 0,
w: None,
}, Color {
r: 0,
g: 0,
b: 255,
w: None,
}, ],
speed: TempoAwareSpeed::Fixed(1.0), direction: CycleDirection::Forward,
transition: CycleTransition::Snap,
duration: Duration::from_secs(11),
},
vec!["test_fixture".to_string()],
EffectLayer::Background,
BlendMode::Replace,
Some(Duration::from_secs(1)), Some(Duration::from_secs(1)), );
cycle_effect.hold_time = Some(Duration::from_secs(9));
engine.start_effect(cycle_effect).unwrap();
let commands = engine.update(Duration::from_millis(500), None).unwrap();
let active_channel = commands.iter().find(|cmd| cmd.value > 0);
assert!(active_channel.is_some());
let active_channel = active_channel.unwrap();
assert!(active_channel.value > 0 && active_channel.value < 255);
let commands = engine.update(Duration::from_millis(1500), None).unwrap(); let active_channel = commands.iter().find(|cmd| cmd.value > 0);
assert!(active_channel.is_some());
let active_channel = active_channel.unwrap();
assert_eq!(active_channel.value, 255);
let _commands = engine.update(Duration::from_secs(7), None).unwrap(); }
#[test]
fn test_pulse_effect_crossfade() {
let mut engine = EffectEngine::new();
let mut channels = HashMap::new();
channels.insert("red".to_string(), 1);
channels.insert("green".to_string(), 2);
channels.insert("blue".to_string(), 3);
let fixture = FixtureInfo::new(
"test_fixture".to_string(),
1,
1,
"RGB_Par".to_string(),
channels,
None,
);
engine.register_fixture(fixture);
let mut pulse_effect = create_effect_with_timing(
"pulse_test".to_string(),
EffectType::Pulse {
base_level: 0.5,
pulse_amplitude: 0.5,
frequency: TempoAwareFrequency::Fixed(2.0), duration: Duration::from_secs(5),
},
vec!["test_fixture".to_string()],
EffectLayer::Midground,
BlendMode::Overlay,
Some(Duration::from_secs(1)), Some(Duration::from_secs(1)), );
pulse_effect.hold_time = Some(Duration::from_secs(3));
engine.start_effect(pulse_effect).unwrap();
let commands = engine.update(Duration::from_millis(500), None).unwrap();
assert!(commands.is_empty());
let commands = engine.update(Duration::from_secs(2), None).unwrap();
assert!(commands.is_empty());
let commands = engine.update(Duration::from_millis(2000), None).unwrap(); assert!(commands.is_empty());
let commands = engine.update(Duration::from_millis(500), None).unwrap(); assert!(commands.is_empty()); }
#[test]
fn test_rainbow_effect_crossfade() {
let mut engine = EffectEngine::new();
let mut channels = HashMap::new();
channels.insert("red".to_string(), 1);
channels.insert("green".to_string(), 2);
channels.insert("blue".to_string(), 3);
let fixture = FixtureInfo::new(
"test_fixture".to_string(),
1,
1,
"RGB_Par".to_string(),
channels,
None,
);
engine.register_fixture(fixture);
let mut rainbow_effect = create_effect_with_timing(
"rainbow_test".to_string(),
EffectType::Rainbow {
speed: TempoAwareSpeed::Fixed(1.0), saturation: 1.0,
brightness: 1.0,
duration: Duration::from_secs(5),
},
vec!["test_fixture".to_string()],
EffectLayer::Background,
BlendMode::Replace,
Some(Duration::from_secs(1)), Some(Duration::from_secs(1)), );
rainbow_effect.hold_time = Some(Duration::from_secs(3));
engine.start_effect(rainbow_effect).unwrap();
let commands = engine.update(Duration::from_millis(500), None).unwrap();
let active_cmd = commands.iter().find(|cmd| cmd.value > 0).unwrap();
assert!(active_cmd.value > 0 && active_cmd.value < 255);
let commands = engine.update(Duration::from_secs(2), None).unwrap();
let active_cmd = commands.iter().find(|cmd| cmd.value > 0).unwrap();
assert!(active_cmd.value > 200);
let commands = engine.update(Duration::from_millis(2000), None).unwrap(); let active_cmd = commands.iter().find(|cmd| cmd.value > 0).unwrap();
assert!(active_cmd.value > 0 && active_cmd.value < 255);
let commands = engine.update(Duration::from_millis(500), None).unwrap(); assert!(commands.is_empty()); }
#[test]
fn test_dsl_crossfade_integration() {
let content = r#"show "DSL Crossfade Test" {
@00:00.000
front_wash: static color: "blue", up_time: 2s, down_time: 1s, hold_time: 5s
}"#;
let result = parse_light_shows(content);
assert!(result.is_ok());
let shows = result.unwrap();
let show = shows.get("DSL Crossfade Test").unwrap();
let effect = &show.cues[0].effects[0];
assert_eq!(effect.up_time, Some(Duration::from_secs(2)));
assert_eq!(effect.down_time, Some(Duration::from_secs(1)));
let mut engine = EffectEngine::new();
let mut channels = HashMap::new();
channels.insert("red".to_string(), 1);
channels.insert("green".to_string(), 2);
channels.insert("blue".to_string(), 3);
let fixture = FixtureInfo::new(
"front_wash".to_string(),
1,
1,
"RGB_Par".to_string(),
channels,
Some(20.0),
);
engine.register_fixture(fixture);
let effect_instance = LightingTimeline::create_effect_instance(effect, show.cues[0].time);
assert_eq!(effect_instance.up_time, Some(Duration::from_secs(2)));
assert_eq!(effect_instance.down_time, Some(Duration::from_secs(1)));
engine.start_effect(effect_instance).unwrap();
let commands = engine.update(Duration::from_millis(0), None).unwrap();
if let Some(blue_cmd) = commands.iter().find(|cmd| cmd.channel == 3) {
assert_eq!(blue_cmd.value, 0); }
let commands = engine.update(Duration::from_millis(1000), None).unwrap();
if let Some(blue_cmd) = commands.iter().find(|cmd| cmd.channel == 3) {
assert!(blue_cmd.value > 100 && blue_cmd.value < 150); }
let commands = engine.update(Duration::from_millis(1000), None).unwrap(); if let Some(blue_cmd) = commands.iter().find(|cmd| cmd.channel == 3) {
assert_eq!(blue_cmd.value, 255); }
let commands = engine.update(Duration::from_millis(5000), None).unwrap(); if let Some(blue_cmd) = commands.iter().find(|cmd| cmd.channel == 3) {
assert_eq!(blue_cmd.value, 255); }
let commands = engine.update(Duration::from_millis(1000), None).unwrap(); assert!(
commands.is_empty() || commands.iter().all(|cmd| cmd.value == 0),
"Effect should end with no commands or all zeros (not permanent)"
);
}
#[test]
fn test_static_effect_crossfade() {
let mut engine = EffectEngine::new();
let mut channels = HashMap::new();
channels.insert("red".to_string(), 1);
channels.insert("green".to_string(), 2);
channels.insert("blue".to_string(), 3);
let fixture = FixtureInfo::new(
"test_fixture".to_string(),
1,
1,
"RGB_Par".to_string(),
channels,
Some(20.0),
);
engine.register_fixture(fixture);
let mut parameters = HashMap::new();
parameters.insert("red".to_string(), 0.0);
parameters.insert("green".to_string(), 0.0);
parameters.insert("blue".to_string(), 1.0);
let mut static_effect = create_effect_with_timing(
"static_blue".to_string(),
EffectType::Static {
parameters,
duration: Duration::from_secs(3),
},
vec!["test_fixture".to_string()],
EffectLayer::Background,
BlendMode::Replace,
Some(Duration::from_secs(1)), Some(Duration::from_secs(1)), );
static_effect.hold_time = Some(Duration::from_secs(1));
engine.start_effect(static_effect).unwrap();
let commands = engine.update(Duration::from_millis(0), None).unwrap();
let blue_cmd = commands.iter().find(|cmd| cmd.channel == 3).unwrap();
assert_eq!(blue_cmd.value, 0);
let commands = engine.update(Duration::from_millis(500), None).unwrap();
let blue_cmd = commands.iter().find(|cmd| cmd.channel == 3).unwrap();
assert_eq!(blue_cmd.value, 127);
let commands = engine.update(Duration::from_millis(500), None).unwrap(); let blue_cmd = commands.iter().find(|cmd| cmd.channel == 3).unwrap();
assert_eq!(blue_cmd.value, 255);
let commands = engine.update(Duration::from_millis(1500), None).unwrap(); let blue_cmd = commands.iter().find(|cmd| cmd.channel == 3).unwrap();
assert_eq!(blue_cmd.value, 127);
let commands = engine.update(Duration::from_millis(500), None).unwrap(); assert!(commands.is_empty()); }