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