use gled_model::asset::midi_controller::{
MidiControllerMapping, MidiInputAction, MidiOutputBindingKind,
};
use super::build::Rule;
use super::controls::Control;
use super::numbers::{
Slot, action_numbers, action_shape, output_numbers, set_output_number, source_shape,
};
use super::sentence::Numbers;
const MIN_MEMBERS: usize = 4;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum Place {
Action,
Feedback(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Numbering {
Same,
Counting { offset: i32 },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RuleGroup {
pub(crate) rules: Vec<usize>,
pub(crate) numbers: Vec<i32>,
pub(crate) counting: Vec<(Place, Slot, i32)>,
}
impl RuleGroup {
pub(crate) fn numbering(&self, place: Place, slot: Slot) -> Numbering {
self.counting
.iter()
.find(|(counted_place, counted_slot, _)| {
*counted_place == place && *counted_slot == slot
})
.map_or(Numbering::Same, |(_, _, offset)| Numbering::Counting {
offset: *offset,
})
}
}
pub(crate) struct GroupNumbers<'a> {
pub(crate) group: &'a RuleGroup,
pub(crate) place: Place,
}
impl Numbers for GroupNumbers<'_> {
fn numbering(&self, slot: Slot) -> Numbering {
self.group.numbering(self.place, slot)
}
}
pub(crate) fn rule_numbers(mapping: &MidiControllerMapping, rule: &Rule) -> Vec<(Place, Slot, u8)> {
let mut numbers = Vec::new();
if let Some(binding) = rule.press.or(rule.release) {
numbers.extend(
action_numbers(&mapping.input_bindings[binding].action)
.into_iter()
.map(|(slot, number)| (Place::Action, slot, number)),
);
}
for (position, output) in rule.feedback.iter().enumerate() {
numbers.extend(
output_numbers(&mapping.output_bindings[*output].kind)
.into_iter()
.map(|(slot, number)| (Place::Feedback(position), slot, number)),
);
}
numbers
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Series {
Numbers,
Channels,
}
impl Series {
fn key(self, control: &Control) -> (u8, u8) {
let channel = control.trigger.status & 0x0F;
match self {
Self::Numbers => (channel, control.trigger.data1),
Self::Channels => (control.trigger.data1, channel),
}
}
fn follows(self, previous: &Control, next: &Control) -> bool {
let (previous_outer, previous_inner) = self.key(previous);
let (outer, inner) = self.key(next);
outer == previous_outer && u16::from(inner) == u16::from(previous_inner) + 1
}
}
pub(crate) fn find_groups(
mapping: &MidiControllerMapping,
controls: &[Control],
rules: &[Rule],
) -> Vec<RuleGroup> {
let mut groups = Vec::new();
for series in [Series::Numbers, Series::Channels] {
let grouped: Vec<usize> = groups
.iter()
.flat_map(|group: &RuleGroup| group.rules.iter().copied())
.collect();
let mut order: Vec<usize> = (0..rules.len())
.filter(|rule| !grouped.contains(rule) && groupable(mapping, &rules[*rule]))
.collect();
order.sort_by_key(|&rule| {
let control = &controls[rules[rule].control];
(
rules[rule].mode,
control.trigger.status & 0xF0,
series.key(control),
)
});
let mut run: Vec<usize> = Vec::new();
for rule in order {
if !run.is_empty() && !continues(mapping, controls, rules, series, &run, rule) {
groups.extend(finish(mapping, controls, rules, &run));
run.clear();
}
run.push(rule);
}
groups.extend(finish(mapping, controls, rules, &run));
}
groups
}
fn groupable(mapping: &MidiControllerMapping, rule: &Rule) -> bool {
match rule.press.or(rule.release) {
Some(binding) => !matches!(
mapping.input_bindings[binding].action,
MidiInputAction::SelectMode { .. }
),
None => false,
}
}
fn name_number(control: &Control) -> Option<(&str, i32)> {
let name = control.name.trim_end();
let digits = name.len() - name.trim_end_matches(|ch: char| ch.is_ascii_digit()).len();
if digits == 0 || digits == name.len() || control.named_by_message {
return None;
}
let (text, number) = name.split_at(name.len() - digits);
Some((text.trim_end(), number.parse().ok()?))
}
pub(crate) fn control_number(control: &Control) -> Option<i32> {
name_number(control).map(|(_, number)| number)
}
pub(crate) fn action_numbers_of(mapping: &MidiControllerMapping, rule: &Rule) -> Vec<(Slot, u8)> {
rule_numbers(mapping, rule)
.into_iter()
.filter(|(place, _, _)| *place == Place::Action)
.map(|(_, slot, value)| (slot, value))
.collect()
}
fn continues(
mapping: &MidiControllerMapping,
controls: &[Control],
rules: &[Rule],
series: Series,
run: &[usize],
rule: usize,
) -> bool {
let first = &rules[run[0]];
let last = &rules[run[run.len() - 1]];
let candidate = &rules[rule];
let last_control = &controls[last.control];
let control = &controls[candidate.control];
if candidate.mode != first.mode
|| control.kind != last_control.kind
|| control.trigger.status & 0xF0 != last_control.trigger.status & 0xF0
|| !series.follows(last_control, control)
|| !same_shape(mapping, first, candidate)
{
return false;
}
match (name_number(last_control), name_number(control)) {
(Some((last_text, last_number)), Some((text, number))) => {
if text != last_text || number != last_number + 1 {
return false;
}
}
(None, None) => {}
_ => return false,
}
let mut members: Vec<usize> = run.to_vec();
members.push(rule);
counting(mapping, controls, rules, &members).is_some()
}
fn finish(
mapping: &MidiControllerMapping,
controls: &[Control],
rules: &[Rule],
run: &[usize],
) -> Option<RuleGroup> {
if run.len() < MIN_MEMBERS {
return None;
}
let (numbers, counting) = counting(mapping, controls, rules, run)?;
Some(RuleGroup {
rules: run.to_vec(),
numbers,
counting,
})
}
fn counting(
mapping: &MidiControllerMapping,
controls: &[Control],
rules: &[Rule],
members: &[usize],
) -> Option<(Vec<i32>, Vec<(Place, Slot, i32)>)> {
let numbers: Vec<i32> = members
.iter()
.enumerate()
.map(|(position, &rule)| {
name_number(&controls[rules[rule].control])
.map_or(position as i32 + 1, |(_, number)| number)
})
.collect();
let values: Vec<Vec<(Place, Slot, u8)>> = members
.iter()
.map(|&rule| rule_numbers(mapping, &rules[rule]))
.collect();
let mut counted = Vec::new();
for (index, &(place, slot, first)) in values[0].iter().enumerate() {
let column: Vec<i32> = values
.iter()
.map(|member| i32::from(member[index].2))
.collect();
if column.iter().all(|&value| value == i32::from(first)) {
continue;
}
let offset = column[0] + 1 - numbers[0];
let counts = column
.iter()
.zip(&numbers)
.all(|(&value, &number)| value + 1 - number == offset);
if !counts {
return None;
}
counted.push((place, slot, offset));
}
Some((numbers, counted))
}
fn same_shape(mapping: &MidiControllerMapping, a: &Rule, b: &Rule) -> bool {
let input = |index: Option<usize>| index.map(|index| &mapping.input_bindings[index]);
let same_binding = |x: Option<usize>, y: Option<usize>| match (input(x), input(y)) {
(Some(x), Some(y)) => {
action_shape(&x.action) == action_shape(&y.action)
&& x.touch.is_some() == y.touch.is_some()
}
(None, None) => true,
_ => false,
};
same_binding(a.press, b.press)
&& same_binding(a.release, b.release)
&& a.feedback.len() == b.feedback.len()
&& a.feedback.iter().zip(&b.feedback).all(|(&x, &y)| {
same_feedback(
&mapping.output_bindings[x].kind,
&mapping.output_bindings[y].kind,
)
})
}
fn same_feedback(a: &MidiOutputBindingKind, b: &MidiOutputBindingKind) -> bool {
match (a, b) {
(MidiOutputBindingKind::Value(a), MidiOutputBindingKind::Value(b)) => {
a.min == b.min
&& a.max == b.max
&& a.active_value == b.active_value
&& source_shape(&a.source) == source_shape(&b.source)
}
(MidiOutputBindingKind::Meter(a), MidiOutputBindingKind::Meter(b)) => {
source_shape(&a.source) == source_shape(&b.source)
}
(MidiOutputBindingKind::SceneColorValue(a), MidiOutputBindingKind::SceneColorValue(b)) => {
let shape = |color: &gled_model::asset::midi_controller::MidiSceneColorValueOutput| {
let mut kind = MidiOutputBindingKind::SceneColorValue(
gled_model::asset::midi_controller::MidiSceneColorValueOutput {
status: 0,
data1: 0,
..color.clone()
},
);
for (slot, _) in output_numbers(&kind.clone()) {
set_output_number(&mut kind, slot, 0);
}
kind
};
a.mapping_name == b.mapping_name && shape(a) == shape(b)
}
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ui::windows::midi_controllers::rules::{
build::build_rules,
controls::controls_of,
test_mappings::{apc40, p1x},
};
fn group_names(mapping: &MidiControllerMapping) -> Vec<(String, Option<u8>)> {
let controls = controls_of(mapping);
let rules = build_rules(mapping, &controls);
find_groups(mapping, &controls, &rules)
.iter()
.map(|group| {
let first = &controls[rules[group.rules[0]].control].name;
let last = &controls[rules[group.rules[group.rules.len() - 1]].control].name;
(format!("{first}–{last}"), rules[group.rules[0]].mode)
})
.collect()
}
#[test]
fn the_p1x_strips_are_groups() {
let groups = group_names(&p1x());
for expected in [
("Rec 1–Rec 8", Some(0)),
("S 1–S 8", Some(0)),
("Select 1–Select 8", Some(0)),
("Fader 1–Fader 8", Some(0)),
("Select 1–Select 8", Some(1)),
("Fader 5–Fader 8", Some(1)),
] {
assert!(
groups.contains(&(expected.0.to_owned(), expected.1)),
"{expected:?} in {groups:?}"
);
}
assert_eq!(groups.len(), 6, "{groups:?}");
}
#[test]
fn faders_5_to_8_count_their_setting_from_an_offset() {
let mapping = p1x();
let controls = controls_of(&mapping);
let rules = build_rules(&mapping, &controls);
let groups = find_groups(&mapping, &controls, &rules);
let faders = groups
.iter()
.find(|group| controls[rules[group.rules[0]].control].name == "Fader 5")
.unwrap_or_else(|| panic!("no group from Fader 5"));
assert_eq!(faders.numbers, [5, 6, 7, 8]);
assert_eq!(
faders.numbering(Place::Action, Slot::Setting),
Numbering::Counting { offset: -4 }
);
}
#[test]
fn a_row_of_pads_is_a_group_with_the_row_the_same() {
let mapping = apc40();
let controls = controls_of(&mapping);
let rules = build_rules(&mapping, &controls);
let groups = find_groups(&mapping, &controls, &rules);
let top_row = groups
.iter()
.find(|group| controls[rules[group.rules[0]].control].name == "Note 32")
.unwrap_or_else(|| panic!("no group from Note 32"));
assert_eq!(top_row.rules.len(), 8);
assert_eq!(top_row.numbering(Place::Action, Slot::Row), Numbering::Same);
assert_eq!(
top_row.numbering(Place::Action, Slot::Column),
Numbering::Counting { offset: 0 }
);
}
}