Skip to main content

maolan_widgets/
controller.rs

1use crate::midi::{
2    MIDI_CHANNELS, PIANO_NRPN_KIND_ALL, PIANO_RPN_KIND_ALL, PianoControllerLane,
3    PianoControllerPoint, PianoNrpnKind, PianoRpnKind,
4};
5use iced::Color;
6use std::collections::HashSet;
7
8pub fn controller_color(controller: u8, channel: u8) -> Color {
9    let h = (controller as f32 / 127.0).clamp(0.0, 1.0);
10    let c = (channel as f32 / 15.0).clamp(0.0, 1.0);
11    Color {
12        r: 0.3 + 0.5 * h,
13        g: 0.85 - 0.45 * h,
14        b: 0.25 + 0.45 * (1.0 - c),
15        a: 0.85,
16    }
17}
18
19pub fn controller_lane_line_count(lane: PianoControllerLane) -> usize {
20    match lane {
21        PianoControllerLane::Controller => 128,
22        PianoControllerLane::Velocity => 128,
23        PianoControllerLane::Rpn => PIANO_RPN_KIND_ALL.len(),
24        PianoControllerLane::Nrpn => PIANO_NRPN_KIND_ALL.len(),
25        PianoControllerLane::SysEx => 1,
26        PianoControllerLane::MpePitchBend
27        | PianoControllerLane::MpePressure
28        | PianoControllerLane::MpeTimbre => 128,
29    }
30}
31
32pub fn controller_row_for_lane(lane: PianoControllerLane, controller: u8) -> Option<usize> {
33    match lane {
34        PianoControllerLane::Controller => Some(usize::from(127_u8.saturating_sub(controller))),
35        PianoControllerLane::Velocity => None,
36        PianoControllerLane::Rpn => match controller {
37            101 => Some(0),
38            100 => Some(1),
39            6 | 38 | 96 | 97 => Some(2),
40            _ => None,
41        },
42        PianoControllerLane::Nrpn => match controller {
43            99 => Some(0),
44            98 => Some(1),
45            6 | 38 | 96 | 97 => Some(2),
46            _ => None,
47        },
48        PianoControllerLane::SysEx => None,
49        PianoControllerLane::MpePitchBend
50        | PianoControllerLane::MpePressure
51        | PianoControllerLane::MpeTimbre => None,
52    }
53}
54
55pub fn rpn_param(kind: PianoRpnKind) -> (u8, u8) {
56    match kind {
57        PianoRpnKind::PitchBendSensitivity => (0, 0),
58        PianoRpnKind::FineTuning => (0, 1),
59        PianoRpnKind::CoarseTuning => (0, 2),
60    }
61}
62
63pub fn nrpn_param(kind: PianoNrpnKind) -> (u8, u8) {
64    match kind {
65        PianoNrpnKind::Brightness => (1, 8),
66        PianoNrpnKind::VibratoRate => (1, 9),
67        PianoNrpnKind::VibratoDepth => (1, 10),
68    }
69}
70
71pub fn rpn_row_for_param(msb: u8, lsb: u8) -> Option<usize> {
72    PIANO_RPN_KIND_ALL
73        .iter()
74        .position(|kind| rpn_param(*kind) == (msb, lsb))
75}
76
77pub fn nrpn_row_for_param(msb: u8, lsb: u8) -> Option<usize> {
78    PIANO_NRPN_KIND_ALL
79        .iter()
80        .position(|kind| nrpn_param(*kind) == (msb, lsb))
81}
82
83pub fn sysex_preview(data: &[u8]) -> String {
84    let mut parts = data
85        .iter()
86        .take(6)
87        .map(|b| format!("{b:02X}"))
88        .collect::<Vec<_>>();
89    if data.len() > 6 {
90        parts.push("...".to_string());
91    }
92    parts.join(" ")
93}
94
95pub fn lane_controller_events(
96    lane: PianoControllerLane,
97    controllers: &[PianoControllerPoint],
98) -> Vec<(usize, usize)> {
99    match lane {
100        PianoControllerLane::Controller => controllers
101            .iter()
102            .enumerate()
103            .filter_map(|(idx, ctrl)| {
104                controller_row_for_lane(lane, ctrl.controller).map(|row| (idx, row))
105            })
106            .collect(),
107        PianoControllerLane::Velocity => vec![],
108        PianoControllerLane::SysEx => vec![],
109        PianoControllerLane::MpePitchBend
110        | PianoControllerLane::MpePressure
111        | PianoControllerLane::MpeTimbre => vec![],
112        PianoControllerLane::Rpn => {
113            let mut ordered: Vec<usize> = (0..controllers.len()).collect();
114            ordered.sort_unstable_by_key(|idx| (controllers[*idx].sample, *idx));
115            let mut current_msb: [Option<u8>; MIDI_CHANNELS] = [None; MIDI_CHANNELS];
116            let mut current_lsb: [Option<u8>; MIDI_CHANNELS] = [None; MIDI_CHANNELS];
117            let mut out = Vec::new();
118            for idx in ordered {
119                let ctrl = &controllers[idx];
120                let channel = usize::from(ctrl.channel.min((MIDI_CHANNELS - 1) as u8));
121                match ctrl.controller {
122                    101 => current_msb[channel] = Some(ctrl.value),
123                    100 => current_lsb[channel] = Some(ctrl.value),
124                    6 => {
125                        if let (Some(msb), Some(lsb)) = (current_msb[channel], current_lsb[channel])
126                            && let Some(row) = rpn_row_for_param(msb, lsb)
127                        {
128                            out.push((idx, row));
129                        }
130                    }
131                    _ => {}
132                }
133            }
134            out
135        }
136        PianoControllerLane::Nrpn => {
137            let mut ordered: Vec<usize> = (0..controllers.len()).collect();
138            ordered.sort_unstable_by_key(|idx| (controllers[*idx].sample, *idx));
139            let mut current_msb: [Option<u8>; MIDI_CHANNELS] = [None; MIDI_CHANNELS];
140            let mut current_lsb: [Option<u8>; MIDI_CHANNELS] = [None; MIDI_CHANNELS];
141            let mut out = Vec::new();
142            for idx in ordered {
143                let ctrl = &controllers[idx];
144                let channel = usize::from(ctrl.channel.min((MIDI_CHANNELS - 1) as u8));
145                match ctrl.controller {
146                    99 => current_msb[channel] = Some(ctrl.value),
147                    98 => current_lsb[channel] = Some(ctrl.value),
148                    6 => {
149                        if let (Some(msb), Some(lsb)) = (current_msb[channel], current_lsb[channel])
150                            && let Some(row) = nrpn_row_for_param(msb, lsb)
151                        {
152                            out.push((idx, row));
153                        }
154                    }
155                    _ => {}
156                }
157            }
158            out
159        }
160    }
161}
162
163pub fn populated_controller_ccs(controllers: &[PianoControllerPoint]) -> HashSet<u8> {
164    controllers.iter().map(|ctrl| ctrl.controller).collect()
165}
166
167pub fn populated_controller_rows(
168    lane: PianoControllerLane,
169    controllers: &[PianoControllerPoint],
170) -> HashSet<usize> {
171    lane_controller_events(lane, controllers)
172        .into_iter()
173        .map(|(_, row)| row)
174        .collect()
175}
176
177pub fn cc_name(cc: u8) -> &'static str {
178    match cc {
179        0 => "Bank Select",
180        1 => "Modulation Wheel",
181        2 => "Breath Controller",
182        4 => "Foot Controller",
183        5 => "Portamento Time",
184        6 => "Data Entry MSB",
185        7 => "Channel Volume",
186        8 => "Balance",
187        10 => "Pan",
188        11 => "Expression Controller",
189        12 => "Effect Control 1",
190        13 => "Effect Control 2",
191        16 => "General Purpose Controller 1",
192        17 => "General Purpose Controller 2",
193        18 => "General Purpose Controller 3",
194        19 => "General Purpose Controller 4",
195        32 => "Bank Select LSB",
196        33 => "Modulation Wheel LSB",
197        34 => "Breath Controller LSB",
198        36 => "Foot Controller LSB",
199        37 => "Portamento Time LSB",
200        38 => "Data Entry LSB",
201        39 => "Channel Volume LSB",
202        40 => "Balance LSB",
203        42 => "Pan LSB",
204        43 => "Expression Controller LSB",
205        44 => "Effect Control 1 LSB",
206        45 => "Effect Control 2 LSB",
207        48 => "General Purpose Controller 1 LSB",
208        49 => "General Purpose Controller 2 LSB",
209        50 => "General Purpose Controller 3 LSB",
210        51 => "General Purpose Controller 4 LSB",
211        64 => "Sustain Pedal",
212        65 => "Portamento",
213        66 => "Sostenuto",
214        67 => "Soft Pedal",
215        68 => "Legato Footswitch",
216        69 => "Hold 2",
217        70 => "Sound Controller 1",
218        71 => "Sound Controller 2",
219        72 => "Sound Controller 3",
220        73 => "Sound Controller 4",
221        74 => "Sound Controller 5",
222        75 => "Sound Controller 6",
223        76 => "Sound Controller 7",
224        77 => "Sound Controller 8",
225        78 => "Sound Controller 9",
226        79 => "Sound Controller 10",
227        80 => "General Purpose Controller 5",
228        81 => "General Purpose Controller 6",
229        82 => "General Purpose Controller 7",
230        83 => "General Purpose Controller 8",
231        84 => "Portamento Control",
232        91 => "Effects 1 Depth",
233        92 => "Effects 2 Depth",
234        93 => "Effects 3 Depth",
235        94 => "Effects 4 Depth",
236        95 => "Effects 5 Depth",
237        96 => "Data Increment",
238        97 => "Data Decrement",
239        98 => "NRPN LSB",
240        99 => "NRPN MSB",
241        100 => "RPN LSB",
242        101 => "RPN MSB",
243        120 => "All Sound Off",
244        121 => "Reset All Controllers",
245        122 => "Local Control",
246        123 => "All Notes Off",
247        124 => "Omni Mode Off",
248        125 => "Omni Mode On",
249        126 => "Mono Mode On",
250        127 => "Poly Mode On",
251        _ => "Undefined",
252    }
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq)]
256pub struct ControllerKindOption(pub u8);
257
258impl std::fmt::Display for ControllerKindOption {
259    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260        write!(f, "CC{:03} {}", self.0, cc_name(self.0))
261    }
262}
263
264#[cfg(test)]
265mod tests {
266    use super::*;
267
268    #[test]
269    fn sysex_preview_truncates_long_messages() {
270        assert_eq!(
271            sysex_preview(&[0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7, 0x00]),
272            "F0 7E 7F 09 01 F7 ..."
273        );
274    }
275
276    #[test]
277    fn populated_rows_follow_rpn_mapping() {
278        let controllers = vec![
279            PianoControllerPoint {
280                sample: 0,
281                controller: 101,
282                value: 0,
283                channel: 0,
284            },
285            PianoControllerPoint {
286                sample: 0,
287                controller: 100,
288                value: 1,
289                channel: 0,
290            },
291            PianoControllerPoint {
292                sample: 1,
293                controller: 6,
294                value: 64,
295                channel: 0,
296            },
297        ];
298
299        let rows = populated_controller_rows(PianoControllerLane::Rpn, &controllers);
300        assert!(rows.contains(&1));
301    }
302}