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}