use gled_midi::monitor::label;
use gled_model::asset::midi_controller::{MidiController, MidiControllerMapping, MidiTrigger};
use gled_protocol::midi::MidiMonitorEvent;
use super::list_state::{LearnRequest, ListState, ModeFilter};
use super::rules::{
Control, ControlKind, Plain, Rule, action_sentence, control_index, edit, message_name, text,
};
use crate::midi_learn::{LearnState, MidiLearnRequest, MidiLearnTarget};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Pending {
pub(crate) trigger: MidiTrigger,
pub(crate) mode: Option<u8>,
}
pub(crate) fn start_learning(
learn_state: &mut LearnState,
controller_id: gled_model::asset_id::AssetId<MidiController>,
request: LearnRequest,
state: &ListState,
) {
let target = match request {
LearnRequest::NewControls => MidiLearnTarget::NewControl(match state.filter {
ModeFilter::Mode(mode) => Some(mode),
ModeFilter::All | ModeFilter::Everywhere => None,
}),
LearnRequest::Control(control) => MidiLearnTarget::Control(control),
LearnRequest::ModeSwitch(mode) => MidiLearnTarget::ModeSwitch(mode),
};
learn_state.arm(MidiLearnRequest {
controller_id,
target,
});
}
pub(crate) enum Learned {
Pending(Pending),
Existing(usize),
Message(String),
}
pub(crate) fn take_captures(
learn_state: &mut LearnState,
controller_id: gled_model::asset_id::AssetId<MidiController>,
mapping: &mut MidiControllerMapping,
controls: &mut Vec<Control>,
) -> Vec<Learned> {
let mut learned = Vec::new();
while let Some(capture) = learn_state.pop_capture() {
if capture.controller_id != controller_id {
continue;
}
match capture.target {
MidiLearnTarget::NewControl(mode) => match control_index(controls, &capture.trigger) {
Some(control)
if super::rules::build_rules(mapping, controls)
.iter()
.any(|rule| rule.control == control && rule.mode == mode) =>
{
learned.push(Learned::Existing(control));
}
_ => learned.push(Learned::Pending(Pending {
trigger: capture.trigger,
mode,
})),
},
MidiLearnTarget::Control(control) => {
edit::relearn_control(mapping, controls, control, capture.trigger.clone());
learned.push(Learned::Message(format!(
"{} now sends {}",
controls
.get(control)
.map_or("The control", |control| control.name.as_str()),
message_name(&capture.trigger)
)));
}
MidiLearnTarget::ModeSwitch(mode) => {
let control = with_control(controls, &capture.trigger);
let before = edit::make_mode_switch(mapping, controls, control, mode);
let name = &controls[control].name;
let mode_name = mapping
.modes
.get(usize::from(mode))
.map_or("", |mode| mode.name.as_str());
learned.push(Learned::Message(match before {
Some(before) => {
format!("{name} now switches to {mode_name} (before it {before})")
}
None => format!("{name} now switches to {mode_name}"),
}));
}
}
}
learned
}
pub(crate) fn with_control(controls: &mut Vec<Control>, trigger: &MidiTrigger) -> usize {
if let Some(index) = control_index(controls, trigger) {
return index;
}
controls.push(new_control(trigger));
controls.len() - 1
}
pub(crate) fn new_control(trigger: &MidiTrigger) -> Control {
let kind = match trigger.status & 0xF0 {
0xE0 => ControlKind::Fader,
0xB0 => ControlKind::Knob,
_ => ControlKind::Button,
};
Control {
name: message_name(trigger),
trigger: trigger.clone(),
kind,
named_by_message: true,
}
}
pub(crate) fn hardware_message(
event: &MidiMonitorEvent,
mapping: &MidiControllerMapping,
controls: &[Control],
rules: &[Rule],
filter: ModeFilter,
) -> Option<(Option<usize>, String)> {
if event.label != label::MIDI {
return None;
}
let [status, data1, value] = match event.bytes.as_slice() {
[status, data1, value, ..] => [*status, *data1, *value],
_ => return None,
};
if status & 0xF0 == 0x80 || (status & 0xF0 == 0x90 && value == 0) {
return None;
}
let trigger = MidiTrigger {
status,
data1,
match_data1: true,
};
if mapping.input_bindings.iter().any(|binding| {
binding
.touch
.as_ref()
.is_some_and(|touch| touch.matches(status, data1))
}) {
return None;
}
let Some(control) = control_index(controls, &trigger) else {
return Some((
None,
format!(
"{} on {} does nothing yet — press Learn and move it to give it a rule",
message_name(&trigger),
event.port_name
),
));
};
let shown_mode = match filter {
ModeFilter::Mode(mode) => Some(mode),
ModeFilter::All | ModeFilter::Everywhere => None,
};
let rule = rules
.iter()
.filter(|rule| rule.control == control && rule.press.or(rule.release).is_some())
.min_by_key(|rule| (rule.mode != shown_mode, rule.mode.is_some()));
let name = &controls[control].name;
Some((
Some(control),
match rule.and_then(|rule| rule.press.or(rule.release)) {
Some(binding) => format!(
"{name} {}",
text(&action_sentence(
&mapping.input_bindings[binding].action,
&mapping.modes,
&Plain
))
),
None => format!("{name} does nothing yet"),
},
))
}