use gled_model::asset::midi_controller::{
MidiEffectParameter, MidiInputAction, MidiSceneTarget, MidiValueSource, MidiValueSourceKind,
};
use strum::IntoEnumIterator;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ValueKind {
OnOff,
Level,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SourceInfo {
pub(crate) kind: MidiValueSourceKind,
pub(crate) group: &'static str,
pub(crate) label: &'static str,
pub(crate) phrase: &'static str,
pub(crate) value: ValueKind,
}
pub(crate) fn source_info(kind: MidiValueSourceKind) -> SourceInfo {
use MidiValueSourceKind as K;
use ValueKind::{Level, OnOff};
let (group, label, phrase, value) = match kind {
K::SceneActive => ("Scene", "Scene is on", "whether {target} {@is on}", OnOff),
K::SceneFlashed => (
"Scene",
"Scene is flashed",
"whether {target} {@is flashed}",
OnOff,
),
K::SceneSelected => (
"Scene",
"Scene is selected",
"whether {target} {@is selected}",
OnOff,
),
K::SceneOpacity => (
"Scene",
"Scene opacity",
"{@the opacity of} {target}",
Level,
),
K::SceneOpacityLive => (
"Scene",
"Scene opacity (live)",
"{@the live opacity of} {target}",
Level,
),
K::SceneInputDimmer => (
"Scene",
"Scene input dimmer",
"{@the input dimmer of} {target}",
Level,
),
K::SceneBeatOffset => (
"Scene",
"Scene beat offset",
"{@the beat offset of} {target}",
Level,
),
K::SceneIgnoreMainDimmer => (
"Scene",
"Scene ignores main dimmer",
"whether {target} {@ignores the main dimmer}",
OnOff,
),
K::SceneSetOffsetOnFlash => (
"Scene",
"Scene sets offset on flash",
"whether {target} {@sets its offset on flash}",
OnOff,
),
K::SelectedSceneOpacity => (
"Selected scene",
"Selected scene opacity",
"{@the opacity of the selected scene}",
Level,
),
K::SelectedSceneOpacityLive => (
"Selected scene",
"Selected scene opacity (live)",
"{@the live opacity of the selected scene}",
Level,
),
K::SelectedSceneDistributed => (
"Selected scene",
"Which scene is selected (0–127)",
"{@which scene is selected} (spread 0–127)",
Level,
),
K::SelectedSceneInputDimmer => (
"Selected scene",
"Selected scene input dimmer",
"{@the input dimmer of the selected scene}",
Level,
),
K::SelectedSceneBeatOffset => (
"Selected scene",
"Selected scene beat offset",
"{@the beat offset of the selected scene}",
Level,
),
K::SelectedSceneIgnoreMainDimmer => (
"Selected scene",
"Selected scene ignores main dimmer",
"whether the selected scene {@ignores the main dimmer}",
OnOff,
),
K::SelectedSceneSetOffsetOnFlash => (
"Selected scene",
"Selected scene sets offset on flash",
"whether the selected scene {@sets its offset on flash}",
OnOff,
),
K::SelectedEffectDistributed => (
"Animation",
"Which animation is selected (0–127)",
"{@which animation is selected} (spread 0–127)",
Level,
),
K::EffectSelected => (
"Animation",
"Animation is selected",
"whether {animation} {@is selected}",
OnOff,
),
K::SelectedSceneEffectLive => (
"Animation",
"Animation value (live)",
"{@the live} {parameter} of {effect} in the selected scene",
Level,
),
K::SceneEffectSettingF32 => (
"Animation",
"Animation setting",
"{@the value of} {setting} of {effect} in {target}",
Level,
),
K::SceneEffectOpacity => (
"Animation",
"Animation opacity",
"{@the opacity of} {effect} in {target}",
Level,
),
K::SceneEffectColorShift => (
"Animation",
"Animation color shift",
"{@the color shift of} {effect} in {target}",
Level,
),
K::SceneEffectBeatProgression => (
"Animation",
"Animation beat progression",
"{@the beat progression of} {effect} in {target}",
Level,
),
K::SceneEffectBeatOffset => (
"Animation",
"Animation beat offset",
"{@the beat offset of} {effect} in {target}",
Level,
),
K::SceneEffectSpeedExponent => (
"Animation",
"Animation speed",
"{@the speed of} {effect} in {target}",
Level,
),
K::SceneEffectLayout => (
"Animation",
"Animation layout",
"{@the layout of} {effect} in {target}",
Level,
),
K::MainDimmer => ("Global", "Main dimmer", "{@the main dimmer}", Level),
K::Blackout => ("Global", "Blackout", "{@blackout} {blackout}", OnOff),
K::BeatFlankPulse => ("Global", "Beat pulse", "{@the beat}: {beat}", OnOff),
K::ModeActive => (
"Global",
"Mode is active",
"whether mode {mode} {@is active}",
OnOff,
),
};
SourceInfo {
kind,
group,
label,
phrase,
value,
}
}
pub(crate) fn sources() -> impl Iterator<Item = SourceInfo> {
MidiValueSourceKind::iter().map(source_info)
}
pub(crate) fn matching_source(action: &MidiInputAction) -> Option<MidiValueSource> {
use MidiInputAction as A;
use MidiValueSource as S;
let selected = |target: &MidiSceneTarget| *target == MidiSceneTarget::Selected;
Some(match action {
A::SetMainDimmer | A::SetMainDimmerRelative { .. } => S::MainDimmer,
A::SetBlackout | A::ToggleBlackout => S::Blackout {
inverted: false,
blink: false,
},
A::Tap => S::BeatFlankPulse {
start_beat: 0.0,
end_beat: 0.5,
blackout_blink_value: None,
every_beat: true,
},
A::SelectSceneDistributed => S::SelectedSceneDistributed,
A::SelectSceneEffectDistributed => S::SelectedEffectDistributed,
A::SelectScene { target } => S::SceneSelected {
target: target.clone(),
},
A::SelectMode { mode } => S::ModeActive { mode: *mode },
A::SelectSceneEffect { index } => S::EffectSelected { index: *index },
A::ToggleSceneActive { target } | A::SetSceneActive { target } => S::SceneActive {
target: target.clone(),
},
A::FlashScene { target } => S::SceneFlashed {
target: target.clone(),
},
A::SetSceneOpacity { target } if selected(target) => S::SelectedSceneOpacity,
A::SetSceneOpacity { target } => S::SceneOpacity {
target: target.clone(),
},
A::SetSceneInputDimmer { target } if selected(target) => S::SelectedSceneInputDimmer,
A::SetSceneInputDimmer { target } => S::SceneInputDimmer {
target: target.clone(),
},
A::SetSceneBeatOffset { target } if selected(target) => S::SelectedSceneBeatOffset,
A::SetSceneBeatOffset { target } => S::SceneBeatOffset {
target: target.clone(),
},
A::SetSceneIgnoreMainDimmer { target } if selected(target) => {
S::SelectedSceneIgnoreMainDimmer
}
A::SetSceneIgnoreMainDimmer { target } => S::SceneIgnoreMainDimmer {
target: target.clone(),
},
A::SetSceneSetOffsetOnFlash { target } if selected(target) => {
S::SelectedSceneSetOffsetOnFlash
}
A::SetSceneSetOffsetOnFlash { target } => S::SceneSetOffsetOnFlash {
target: target.clone(),
},
A::SetSceneEffectSettingF32 {
target,
effect,
setting_index,
} => S::SceneEffectSettingF32 {
target: target.clone(),
effect: *effect,
setting_index: *setting_index,
},
A::SetSceneEffectOpacity { target, effect } => S::SceneEffectOpacity {
target: target.clone(),
effect: *effect,
},
A::SetSceneEffectColorShift { target, effect } => S::SceneEffectColorShift {
target: target.clone(),
effect: *effect,
},
A::SetSceneEffectBeatProgression { target, effect } => S::SceneEffectBeatProgression {
target: target.clone(),
effect: *effect,
},
A::SetSceneEffectBeatOffset { target, effect } => S::SceneEffectBeatOffset {
target: target.clone(),
effect: *effect,
},
A::SetSceneEffectSpeedExponent { target, effect } => S::SceneEffectSpeedExponent {
target: target.clone(),
effect: *effect,
},
A::SetSceneEffectLayout { target, effect } => S::SceneEffectLayout {
target: target.clone(),
effect: *effect,
},
A::SetSpeedAdd
| A::SetSpeedMultiply
| A::SetSpeedRelative { .. }
| A::SelectSceneRelative { .. }
| A::SelectSceneRow { .. }
| A::SelectSceneColumn { .. }
| A::SelectSceneEffectRelative { .. } => return None,
})
}
pub(crate) fn live_source(source: MidiValueSource) -> MidiValueSource {
use MidiEffectParameter as P;
use MidiValueSource as S;
let selected = |target: &MidiSceneTarget| *target == MidiSceneTarget::Selected;
let live = |effect, parameter| S::SelectedSceneEffectLive { effect, parameter };
match source {
S::SelectedSceneOpacity => S::SelectedSceneOpacityLive,
S::SceneOpacity { target } => S::SceneOpacityLive { target },
S::SceneEffectOpacity { target, effect } if selected(&target) => live(effect, P::Opacity),
S::SceneEffectColorShift { target, effect } if selected(&target) => {
live(effect, P::ColorShift)
}
S::SceneEffectBeatProgression { target, effect } if selected(&target) => {
live(effect, P::BeatProgression)
}
S::SceneEffectBeatOffset { target, effect } if selected(&target) => {
live(effect, P::BeatOffset)
}
S::SceneEffectSettingF32 {
target,
effect,
setting_index,
} if selected(&target) => match P::setting(setting_index) {
Some(parameter) => live(effect, parameter),
None => S::SceneEffectSettingF32 {
target,
effect,
setting_index,
},
},
other => other,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_toggle_light_shows_whether_its_scene_is_on() {
let target = MidiSceneTarget::Row { col: 2 };
assert_eq!(
matching_source(&MidiInputAction::ToggleSceneActive {
target: target.clone()
}),
Some(MidiValueSource::SceneActive { target })
);
}
#[test]
fn a_tap_light_pulses_on_every_beat() {
assert!(matches!(
matching_source(&MidiInputAction::Tap),
Some(MidiValueSource::BeatFlankPulse {
every_beat: true,
..
})
));
}
}