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}