launchkey_sdk/launchkey/
commands.rs

1use crate::launchkey::bitmap::LaunchkeyBitmap;
2use crate::launchkey::colors::{Color, ColorPaletteIndex};
3use crate::launchkey::constants::{
4    LaunchKeySku, BITMAP_HEADER_BYTE, BUTTON_BRIGHTNESS_OVERRIDE_CHANNEL, CC_CH7,
5    CONFIGURE_DISPLAY_COMMAND, CUSTOM_COLOR_COMMAND, DISABLE_DRUM_DAW_MODE, ENABLE_DRUM_DAW_MODE,
6    ENCODER_MODE_CC, FADER_MODE_CC, PAD_MODE_CC, SET_SCREEN_TEXT_COMMAND, SYSEX_TERMINATOR,
7};
8use crate::launchkey::modes::encoder_mode::EncoderMode;
9use crate::launchkey::modes::fader_mode::FaderMode;
10use crate::launchkey::modes::pad_mode::PadMode;
11use crate::launchkey::surface::buttons::{Brightness, LaunchKeyButton};
12use crate::launchkey::surface::display::{
13    Arrangement, ContextualDisplayTarget, DisplayConfig, DisplayTarget, GlobalDisplayTarget,
14};
15use crate::launchkey::surface::pads::{LEDMode, PadInMode};
16use std::ops::Deref;
17
18/// Represents a high-level Launchkey command that can be converted into a MIDI message.
19/// These are used to control pads, buttons, displays, and other device features.
20#[derive(Debug, Clone)]
21pub enum LaunchkeyCommand {
22    SetDrumDAWMode(bool),
23    SetPadMode(PadMode),
24    SetEncoderMode(EncoderMode),
25    SetFaderMode(FaderMode),
26    SetButtonBrightness {
27        launch_key_button: LaunchKeyButton,
28        brightness: Brightness,
29    },
30    SetPadColor {
31        pad_in_mode: PadInMode,
32        mode: LEDMode,
33        color_palette_index: ColorPaletteIndex,
34    },
35    SetPadCustomColor {
36        pad_in_mode: PadInMode,
37        color: Color,
38    },
39    SetScreenTextGlobal {
40        target: GlobalDisplayTarget,
41        arrangement: Arrangement,
42    },
43    SetScreenTextContextual {
44        target: ContextualDisplayTarget,
45        text: String,
46    },
47    SendScreenBitmap {
48        target: GlobalDisplayTarget,
49        bitmap: Box<LaunchkeyBitmap>,
50    },
51}
52
53impl LaunchkeyCommand {
54    /// Converts the [`LaunchkeyCommand`] into a MIDI byte sequence appropriate for the specific SKU.
55    pub(crate) fn as_bytes(&self, sku: &LaunchKeySku) -> Vec<u8> {
56        let header = sku.sys_ex_header();
57        match self {
58            LaunchkeyCommand::SetDrumDAWMode(enable) => {
59                if *enable {
60                    ENABLE_DRUM_DAW_MODE.into()
61                } else {
62                    DISABLE_DRUM_DAW_MODE.into()
63                }
64            }
65            LaunchkeyCommand::SetPadMode(mode) => vec![CC_CH7, PAD_MODE_CC, mode.to_value()],
66            LaunchkeyCommand::SetEncoderMode(mode) => {
67                vec![CC_CH7, ENCODER_MODE_CC, mode.to_value()]
68            }
69            LaunchkeyCommand::SetFaderMode(mode) => vec![CC_CH7, FADER_MODE_CC, mode.to_value()],
70            LaunchkeyCommand::SetButtonBrightness {
71                launch_key_button,
72                brightness,
73            } => {
74                vec![
75                    BUTTON_BRIGHTNESS_OVERRIDE_CHANNEL,
76                    (*launch_key_button).to_value(),
77                    brightness.value(),
78                ]
79            }
80            // Commands for pad LEDs
81            LaunchkeyCommand::SetPadColor {
82                pad_in_mode,
83                mode,
84                color_palette_index,
85            } => {
86                let channel = mode.to_midi_channel(*pad_in_mode);
87                vec![
88                    channel,
89                    (*pad_in_mode).to_index(),
90                    color_palette_index.as_u8(),
91                ]
92            }
93            LaunchkeyCommand::SetPadCustomColor { pad_in_mode, color } => {
94                let mut data = header.to_vec();
95                data.extend_from_slice(&[
96                    CUSTOM_COLOR_COMMAND.0,
97                    CUSTOM_COLOR_COMMAND.1,
98                    (*pad_in_mode).to_index(),
99                    color.r.into(),
100                    color.g.into(),
101                    color.b.into(),
102                ]);
103                data.push(SYSEX_TERMINATOR);
104                data
105            }
106
107            // Commands for screen control
108            LaunchkeyCommand::SetScreenTextGlobal {
109                target,
110                arrangement,
111            } => {
112                let mut data: Vec<u8> = Vec::new();
113                // Start with configuring the display for the arrangement
114                data.extend(self.configure_display(
115                    (*target).into(),
116                    DisplayConfig::Arrangement(arrangement.clone()),
117                    sku,
118                ));
119
120                // Handle different arrangement types
121                match arrangement {
122                    Arrangement::NameValue(name, value) => {
123                        data.extend(self.set_screen_text(
124                            (*target).into(),
125                            0,
126                            name.to_string(),
127                            sku,
128                        ));
129                        data.extend(self.set_screen_text(
130                            (*target).into(),
131                            1,
132                            value.to_string(),
133                            sku,
134                        ));
135                    }
136                    Arrangement::TitleNameValue(title, name, value) => {
137                        data.extend(self.set_screen_text(
138                            (*target).into(),
139                            0,
140                            title.to_string(),
141                            sku,
142                        ));
143                        data.extend(self.set_screen_text(
144                            (*target).into(),
145                            1,
146                            name.to_string(),
147                            sku,
148                        ));
149                        data.extend(self.set_screen_text(
150                            (*target).into(),
151                            2,
152                            value.to_string(),
153                            sku,
154                        ));
155                    }
156                    Arrangement::TitleEightNames(title, names) => {
157                        data.extend(self.set_screen_text(
158                            (*target).into(),
159                            0,
160                            title.to_string(),
161                            sku,
162                        ));
163                        for (i, name) in names.iter().enumerate() {
164                            data.extend(self.set_screen_text(
165                                (*target).into(),
166                                (i + 1) as u8,
167                                name.to_string(),
168                                sku,
169                            ));
170                        }
171                    }
172                    Arrangement::NameNumericValue(name) => {
173                        data.extend(self.set_screen_text(
174                            (*target).into(),
175                            0,
176                            name.to_string(),
177                            sku,
178                        ));
179                    }
180                }
181
182                // Finalize with configuring the display for triggering
183                data.extend(self.configure_display((*target).into(), DisplayConfig::Trigger, sku));
184
185                data
186            }
187            LaunchkeyCommand::SetScreenTextContextual { target, text } => {
188                self.set_screen_text((*target).into(), 0, text.to_string(), sku)
189            }
190            LaunchkeyCommand::SendScreenBitmap { target, bitmap } => {
191                let mut data = header.to_vec();
192                data.extend_from_slice(&[BITMAP_HEADER_BYTE, (*target).into()]);
193                data.extend_from_slice(bitmap.deref().as_ref());
194                data.push(SYSEX_TERMINATOR);
195                data
196            }
197        }
198    }
199
200    /// Internal helper to create a SysEx message configuring the screen for a target and config.
201    fn configure_display(
202        &self,
203        target: DisplayTarget,
204        config: DisplayConfig,
205        sku: &LaunchKeySku,
206    ) -> Vec<u8> {
207        let mut data = sku.sys_ex_header().to_vec();
208        data.extend_from_slice(&[
209            CONFIGURE_DISPLAY_COMMAND,
210            target.into(),
211            config.clone().into(),
212        ]);
213        data.push(SYSEX_TERMINATOR);
214        data
215    }
216
217    /// Internal helper to create a SysEx message that sets text on a specific display field.
218    fn set_screen_text(
219        &self,
220        target: DisplayTarget,
221        field: u8,
222        text: String,
223        sku: &LaunchKeySku,
224    ) -> Vec<u8> {
225        let mut data = sku.sys_ex_header().to_vec();
226        data.extend_from_slice(&[SET_SCREEN_TEXT_COMMAND, target.into(), field]);
227        data.extend(text.as_bytes());
228        data.push(SYSEX_TERMINATOR);
229        data
230    }
231}