Skip to main content

gled_model/asset/
midi_controller.rs

1use super::AssetTrait;
2use serde::{Deserialize, Serialize};
3
4use crate::asset::scene::color::SceneInstanceColor;
5
6#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
7pub struct MidiController {
8    pub mapping: MidiControllerMapping,
9}
10
11impl AssetTrait for MidiController {
12    const DIR_NAME: &'static str = "midi_controllers";
13    const NAME: &'static str = "MIDI Controller";
14    const SHOW_NAME_IF_SELECTED: bool = true;
15}
16
17#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
18pub struct MidiControllerMapping {
19    pub input_bindings: Vec<MidiInputBinding>,
20    pub output_bindings: Vec<MidiOutputBinding>,
21    pub color_mappings: Vec<MidiNamedSceneColorMapping>,
22}
23
24#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
25pub struct MidiInputBinding {
26    pub name: String,
27    pub trigger: MidiTrigger,
28    pub action: MidiInputAction,
29}
30
31impl Default for MidiInputBinding {
32    fn default() -> Self {
33        Self {
34            name: "Input".to_owned(),
35            trigger: MidiTrigger::default(),
36            action: MidiInputAction::Tap,
37        }
38    }
39}
40
41#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
42pub struct MidiTrigger {
43    pub status: u8,
44    pub data1: u8,
45    pub match_data1: bool,
46}
47
48impl Default for MidiTrigger {
49    fn default() -> Self {
50        Self {
51            status: 0,
52            data1: 0,
53            match_data1: true,
54        }
55    }
56}
57
58#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
59pub enum MidiInputAction {
60    Tap,
61    SetMainDimmer,
62    SetBlackout,
63    SetSpeedAdd,
64    SetSpeedMultiply,
65    SelectSceneDistributed,
66    SelectScene {
67        target: MidiSceneTarget,
68    },
69    ToggleSceneActive {
70        target: MidiSceneTarget,
71    },
72    SetSceneActive {
73        target: MidiSceneTarget,
74    },
75    FlashScene {
76        target: MidiSceneTarget,
77    },
78    SetSceneOpacity {
79        target: MidiSceneTarget,
80    },
81    SetSceneInputDimmer {
82        target: MidiSceneTarget,
83    },
84    SetSceneBeatOffset {
85        target: MidiSceneTarget,
86    },
87    SetSceneIgnoreMainDimmer {
88        target: MidiSceneTarget,
89    },
90    SetSceneSetOffsetOnFlash {
91        target: MidiSceneTarget,
92    },
93    SetSceneEffectSettingF32 {
94        target: MidiSceneTarget,
95        effect_index: u8,
96        setting_index: u8,
97    },
98    SetSceneEffectOpacity {
99        target: MidiSceneTarget,
100        effect_index: u8,
101    },
102    SetSceneEffectColorShift {
103        target: MidiSceneTarget,
104        effect_index: u8,
105    },
106    SetSceneEffectBeatProgression {
107        target: MidiSceneTarget,
108        effect_index: u8,
109    },
110    SetSceneEffectBeatOffset {
111        target: MidiSceneTarget,
112        effect_index: u8,
113    },
114    SetSceneEffectSpeedExponent {
115        target: MidiSceneTarget,
116        effect_index: u8,
117    },
118    SetSceneEffectGroupIndex {
119        target: MidiSceneTarget,
120        effect_index: u8,
121    },
122}
123
124#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
125pub enum MidiSceneTarget {
126    #[default]
127    Selected,
128    Quick {
129        index: u8,
130    },
131    Grid {
132        row: u8,
133        col: u8,
134    },
135}
136
137#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
138pub struct MidiOutputBinding {
139    pub name: String,
140    pub kind: MidiOutputBindingKind,
141}
142
143impl Default for MidiOutputBinding {
144    fn default() -> Self {
145        Self {
146            name: "Output".to_owned(),
147            kind: MidiOutputBindingKind::Value(MidiValueOutput::default()),
148        }
149    }
150}
151
152#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
153pub enum MidiOutputBindingKind {
154    Value(MidiValueOutput),
155    SceneColorValue(MidiSceneColorValueOutput),
156}
157
158impl Default for MidiOutputBindingKind {
159    fn default() -> Self {
160        Self::Value(MidiValueOutput::default())
161    }
162}
163
164#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
165pub struct MidiValueOutput {
166    pub status: u8,
167    pub data1: u8,
168    pub min: u8,
169    pub max: u8,
170    pub active_value: u8,
171    pub source: MidiValueSource,
172}
173
174impl Default for MidiValueOutput {
175    fn default() -> Self {
176        Self {
177            status: 176,
178            data1: 0,
179            min: 0,
180            max: 127,
181            active_value: 127,
182            source: MidiValueSource::SelectedSceneOpacity,
183        }
184    }
185}
186
187#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)]
188pub enum MidiValueSource {
189    #[default]
190    SelectedSceneOpacity,
191    SelectedSceneDistributed,
192    SelectedSceneInputDimmer,
193    SelectedSceneBeatOffset,
194    SelectedSceneIgnoreMainDimmer,
195    SelectedSceneSetOffsetOnFlash,
196    MainDimmer,
197    BeatFlankPulse {
198        start_beat: f32,
199        end_beat: f32,
200        blackout_blink_value: Option<u8>,
201    },
202    Blackout {
203        inverted: bool,
204        blink: bool,
205    },
206    SceneOpacity {
207        target: MidiSceneTarget,
208    },
209    SceneInputDimmer {
210        target: MidiSceneTarget,
211    },
212    SceneBeatOffset {
213        target: MidiSceneTarget,
214    },
215    SceneIgnoreMainDimmer {
216        target: MidiSceneTarget,
217    },
218    SceneSetOffsetOnFlash {
219        target: MidiSceneTarget,
220    },
221    SceneActive {
222        target: MidiSceneTarget,
223    },
224    SceneFlashed {
225        target: MidiSceneTarget,
226    },
227    SceneEffectSettingF32 {
228        target: MidiSceneTarget,
229        effect_index: u8,
230        setting_index: u8,
231    },
232    SceneEffectOpacity {
233        target: MidiSceneTarget,
234        effect_index: u8,
235    },
236    SceneEffectColorShift {
237        target: MidiSceneTarget,
238        effect_index: u8,
239    },
240    SceneEffectBeatProgression {
241        target: MidiSceneTarget,
242        effect_index: u8,
243    },
244    SceneEffectBeatOffset {
245        target: MidiSceneTarget,
246        effect_index: u8,
247    },
248    SceneEffectSpeedExponent {
249        target: MidiSceneTarget,
250        effect_index: u8,
251    },
252    SceneEffectGroupIndex {
253        target: MidiSceneTarget,
254        effect_index: u8,
255    },
256}
257
258impl Eq for MidiValueSource {}
259
260#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
261pub enum MidiColorSource {
262    SceneColor { target: MidiSceneTarget },
263}
264
265impl Default for MidiColorSource {
266    fn default() -> Self {
267        Self::SceneColor {
268            target: MidiSceneTarget::Selected,
269        }
270    }
271}
272
273#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
274pub struct MidiSceneColorValueOutput {
275    pub source: MidiColorSource,
276    pub mapping_name: String,
277    pub status: u8,
278    pub data1: u8,
279}
280
281impl Default for MidiSceneColorValueOutput {
282    fn default() -> Self {
283        Self {
284            source: MidiColorSource::default(),
285            mapping_name: String::new(),
286            status: 144,
287            data1: 0,
288        }
289    }
290}
291
292#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
293pub struct MidiNamedSceneColorMapping {
294    pub name: String,
295    pub active: MidiSceneColorMessageMap,
296    pub inactive: MidiSceneColorMessageMap,
297    pub flashed: MidiSceneColorMessageMap,
298}
299
300impl Default for MidiNamedSceneColorMapping {
301    fn default() -> Self {
302        let default_map = MidiSceneColorMessageMap::default();
303        Self {
304            name: "Color Mapping".to_owned(),
305            active: default_map.clone(),
306            inactive: default_map.clone(),
307            flashed: default_map,
308        }
309    }
310}
311
312#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
313pub struct MidiSceneColorMessage {
314    pub value: u8,
315}
316
317#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
318pub struct MidiSceneColorMessageMap {
319    pub red: MidiSceneColorMessage,
320    pub green: MidiSceneColorMessage,
321    pub blue: MidiSceneColorMessage,
322    pub white: MidiSceneColorMessage,
323    pub orange: MidiSceneColorMessage,
324    pub yellow: MidiSceneColorMessage,
325    pub purple: MidiSceneColorMessage,
326    pub pink: MidiSceneColorMessage,
327    pub black: MidiSceneColorMessage,
328}
329
330impl Default for MidiSceneColorMessageMap {
331    fn default() -> Self {
332        Self {
333            red: MidiSceneColorMessage { value: 5 },
334            green: MidiSceneColorMessage { value: 21 },
335            blue: MidiSceneColorMessage { value: 45 },
336            white: MidiSceneColorMessage { value: 3 },
337            orange: MidiSceneColorMessage { value: 9 },
338            yellow: MidiSceneColorMessage { value: 13 },
339            purple: MidiSceneColorMessage { value: 49 },
340            pink: MidiSceneColorMessage { value: 53 },
341            black: MidiSceneColorMessage { value: 0 },
342        }
343    }
344}
345
346impl MidiSceneColorMessageMap {
347    pub fn message_for_color(&self, color: SceneInstanceColor) -> &MidiSceneColorMessage {
348        match color {
349            SceneInstanceColor::Red => &self.red,
350            SceneInstanceColor::Green => &self.green,
351            SceneInstanceColor::Blue => &self.blue,
352            SceneInstanceColor::White => &self.white,
353            SceneInstanceColor::Orange => &self.orange,
354            SceneInstanceColor::Yellow => &self.yellow,
355            SceneInstanceColor::Purple => &self.purple,
356            SceneInstanceColor::Pink => &self.pink,
357            SceneInstanceColor::Black => &self.black,
358        }
359    }
360}
361
362/// A port name without the number some systems append, such as
363/// `"APC40 [2]"` → `"APC40"`, so a controller keeps its mapping when it is
364/// plugged in again.
365pub fn normalize_controller_key(port_name: &str) -> String {
366    let trimmed = port_name.trim();
367    if let Some(prefix) = trimmed.strip_suffix(']')
368        && let Some((base, suffix)) = prefix.rsplit_once(" [")
369        && !base.is_empty()
370        && !suffix.is_empty()
371        && suffix.chars().all(|ch| ch.is_ascii_digit())
372    {
373        return base.to_owned();
374    }
375
376    trimmed.to_owned()
377}