use gled_model::asset::midi_controller::MidiControllerMapping;
use super::controls::{Control, control_index, output_control};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Rule {
pub(crate) control: usize,
pub(crate) mode: Option<u8>,
pub(crate) press: Option<usize>,
pub(crate) release: Option<usize>,
pub(crate) feedback: Vec<usize>,
}
impl Rule {
fn new(control: usize, mode: Option<u8>) -> Self {
Self {
control,
mode,
press: None,
release: None,
feedback: Vec::new(),
}
}
pub(crate) fn while_held(&self, mapping: &MidiControllerMapping) -> bool {
match (self.press, self.release) {
(Some(press), Some(release)) => {
mapping.input_bindings[press].action == mapping.input_bindings[release].action
}
_ => false,
}
}
}
pub(crate) fn build_rules(mapping: &MidiControllerMapping, controls: &[Control]) -> Vec<Rule> {
let inputs = &mapping.input_bindings;
let mut rules: Vec<Rule> = Vec::new();
for (index, binding) in inputs.iter().enumerate() {
let Some(control) = control_index(controls, &binding.trigger) else {
continue;
};
let same_action = |other: Option<usize>| {
other.is_some_and(|other| inputs[other].action == binding.action)
};
if binding.trigger.status & 0xF0 == 0x80 {
let pair = rules.iter_mut().find(|rule| {
rule.control == control
&& rule.mode == binding.mode
&& rule.release.is_none()
&& same_action(rule.press)
});
match pair {
Some(rule) => rule.release = Some(index),
None => rules.push(Rule {
release: Some(index),
..Rule::new(control, binding.mode)
}),
}
} else {
let pair = rules.iter_mut().find(|rule| {
rule.control == control
&& rule.mode == binding.mode
&& rule.press.is_none()
&& same_action(rule.release)
});
match pair {
Some(rule) => rule.press = Some(index),
None => rules.push(Rule {
press: Some(index),
..Rule::new(control, binding.mode)
}),
}
}
}
for (index, binding) in mapping.output_bindings.iter().enumerate() {
let Some(control) = output_control(controls, &binding.kind) else {
continue;
};
let rule = rules
.iter_mut()
.find(|rule| rule.control == control && rule.mode == binding.mode);
match rule {
Some(rule) => rule.feedback.push(index),
None => rules.push(Rule {
feedback: vec![index],
..Rule::new(control, binding.mode)
}),
}
}
rules
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ui::windows::midi_controllers::rules::{
controls::controls_of,
test_mappings::{apc40, p1x},
};
fn assert_each_binding_in_one_rule(mapping: &MidiControllerMapping) {
let controls = controls_of(mapping);
let rules = build_rules(mapping, &controls);
let mut inputs = vec![0; mapping.input_bindings.len()];
let mut outputs = vec![0; mapping.output_bindings.len()];
for rule in &rules {
for index in rule.press.iter().chain(&rule.release) {
inputs[*index] += 1;
}
for index in &rule.feedback {
outputs[*index] += 1;
}
}
assert!(inputs.iter().all(|count| *count == 1), "inputs {inputs:?}");
assert!(
outputs.iter().all(|count| *count == 1),
"outputs {outputs:?}"
);
}
#[test]
fn every_binding_of_the_fixtures_belongs_to_one_rule() {
assert_each_binding_in_one_rule(&p1x());
assert_each_binding_in_one_rule(&apc40());
}
fn rule_of<'a>(
rules: &'a [Rule],
controls: &[Control],
name: &str,
mode: Option<u8>,
) -> &'a Rule {
rules
.iter()
.find(|rule| controls[rule.control].name == name && rule.mode == mode)
.unwrap_or_else(|| panic!("no rule for {name} in {mode:?}"))
}
#[test]
fn a_flash_on_press_and_release_is_one_rule_while_held() {
let mapping = p1x();
let controls = controls_of(&mapping);
let rules = build_rules(&mapping, &controls);
let rule = rule_of(&rules, &controls, "S 1", Some(0));
assert!(rule.while_held(&mapping));
assert_eq!(rule.feedback.len(), 1);
}
#[test]
fn a_fader_rule_holds_its_motor_fader_and_meter() {
let mapping = p1x();
let controls = controls_of(&mapping);
let rules = build_rules(&mapping, &controls);
let rule = rule_of(&rules, &controls, "Fader 3", Some(1));
assert!(rule.press.is_some());
assert_eq!(rule.feedback.len(), 2);
}
#[test]
fn a_pad_bound_to_its_release_is_a_rule_on_release() {
let mapping = apc40();
let controls = controls_of(&mapping);
let rules = build_rules(&mapping, &controls);
let rule = rule_of(&rules, &controls, "Note 32", None);
assert_eq!(rule.press, None);
assert!(rule.release.is_some());
assert_eq!(rule.feedback.len(), 1);
}
#[test]
fn the_p1x_has_one_rule_per_control_and_mode() {
let mapping = p1x();
let controls = controls_of(&mapping);
let rules = build_rules(&mapping, &controls);
assert_eq!(rules.len(), 6 + 33 + 18);
}
}