launchkey_sdk/launchkey/
colors.rs

1use wmidi::U7;
2
3/// A wrapper type representing a valid index (0–127) into the Launchkey's color palette.
4/// Used to assign predefined colors to pads and other elements.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct ColorPaletteIndex(U7);
7
8impl ColorPaletteIndex {
9    /// Creates a new [`PaletteIndex`] with a value clamped to the range [0, 127].
10    fn clamp_new(value: u8) -> Self {
11        Self(U7::clamp_new(value))
12    }
13
14    /// Creates a new [`ColorPaletteIndex`] if the value is within the valid range [0, 127].
15    pub fn try_from(value: u8) -> Result<Self, String> {
16        U7::try_from(value).map(Self).map_err(|_| {
17            format!(
18                "Invalid color index: {}. Must be between 0 and 127.",
19                value
20            )
21        })
22    }
23
24    /// Converts the [`ColorPaletteIndex`] to its raw `u8` value.
25    pub fn as_u8(self) -> u8 {
26        self.0.into()
27    }
28}
29
30/// Represents an RGB color with component values limited to the range [0, 127].
31/// Used for setting custom colors on the Launchkey device.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub struct Color {
34    pub r: U7,
35    pub g: U7,
36    pub b: U7,
37}
38
39impl Color {
40    /// Tries to create a new [`Color`] if all RGB values are within the valid range [0, 127].
41    pub fn try_new(r: u8, g: u8, b: u8) -> Result<Self, String> {
42        let r = U7::try_from(r).map_err(|_| format!("Invalid R value: {}. Must be between 0 and 127.", r))?;
43        let g = U7::try_from(g).map_err(|_| format!("Invalid G value: {}. Must be between 0 and 127.", g))?;
44        let b = U7::try_from(b).map_err(|_| format!("Invalid B value: {}. Must be between 0 and 127.", b))?;
45        Ok(Self { r, g, b })
46    }
47
48    /// Creates a new [`Color`] with clamped values within the valid range [0, 127].
49    /// This is useful for internal use where invalid values might slip through.
50    pub(crate) fn clamp_new(r: u8, g: u8, b: u8) -> Self {
51        Self {
52            r: U7::clamp_new(r),
53            g: U7::clamp_new(g),
54            b: U7::clamp_new(b),
55        }
56    }
57
58    /// Creates a new [`Color`] by converting RGB values from the full range [0, 255] to [0, 127].
59    /// Automatically scales down the values.
60    pub fn from_full_range(r: u8, g: u8, b: u8) -> Self {
61        Self {
62            r: U7::from_u8_lossy((r as f32 * 127.0 / 255.0) as u8),
63            g: U7::from_u8_lossy((g as f32 * 127.0 / 255.0) as u8),
64            b: U7::from_u8_lossy((b as f32 * 127.0 / 255.0) as u8),
65        }
66    }
67
68    /// Creates a new [`Color`] by converting RGB values from the color palette range [97, 255] to [0, 127].
69    /// Ensures input values are at least 97, but doesn't clamp the upper bound.
70    pub fn from_color_palette_range(r: u8, g: u8, b: u8) -> Self {
71        Self::map_rgb(r.max(97), g.max(97), b.max(97), 97, 255)
72    }
73
74    /// Creates a new [`Color`] by converting RGB values from the specified input range to [0, 127].
75    pub fn map_rgb(r: u8, g: u8, b: u8, in_min: u8, in_max: u8) -> Self {
76        let out_min = 0;
77        let out_max = 127;
78
79        let map_value = |value: u8| -> U7 {
80            let mapped = ((value as f32 - in_min as f32) / (in_max as f32 - in_min as f32)
81                * (out_max as f32 - out_min as f32)
82                + out_min as f32) as u8;
83            U7::from_u8_lossy(mapped)
84        };
85
86        Self {
87            r: map_value(r),
88            g: map_value(g),
89            b: map_value(b),
90        }
91    }
92}
93
94/// Enum representing a set of common named colors supported by the Launchkey palette.
95/// These are mapped to fixed RGB values and palette indices.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum CommonColor {
98    Off,
99    DarkGray,
100    LightGray,
101    White,
102    DimRed,
103    NormalRed,
104    BrightRed,
105    DimOrange,
106    NormalOrange,
107    BrightOrange,
108    DimYellow,
109    NormalYellow,
110    BrightYellow,
111    DimGreen,
112    NormalGreen,
113    BrightGreen,
114    DimCyan,
115    NormalCyan,
116    BrightCyan,
117    DimBlue,
118    NormalBlue,
119    BrightBlue,
120    DimPurple,
121    NormalPurple,
122    BrightPurple,
123    DimPink,
124    NormalPink,
125    BrightPink,
126}
127
128impl CommonColor {
129    /// Converts the [`CommonColor`] to a [`ColorPaletteIndex`].
130    pub fn to_palette_index(self) -> ColorPaletteIndex {
131        let index = match self {
132            CommonColor::Off => 0x00,
133            CommonColor::DarkGray => 0x01,
134            CommonColor::LightGray => 0x02,
135            CommonColor::White => 0x03,
136            CommonColor::DimRed => 0x07,
137            CommonColor::NormalRed => 0x06,
138            CommonColor::BrightRed => 0x05,
139            CommonColor::DimOrange => 0x0B,
140            CommonColor::NormalOrange => 0x0A,
141            CommonColor::BrightOrange => 0x09,
142            CommonColor::DimYellow => 0x0F,
143            CommonColor::NormalYellow => 0x0E,
144            CommonColor::BrightYellow => 0x0D,
145            CommonColor::DimGreen => 0x17,
146            CommonColor::NormalGreen => 0x16,
147            CommonColor::BrightGreen => 0x15,
148            CommonColor::DimCyan => 0x27,
149            CommonColor::NormalCyan => 0x26,
150            CommonColor::BrightCyan => 0x25,
151            CommonColor::DimBlue => 0x2F,
152            CommonColor::NormalBlue => 0x2E,
153            CommonColor::BrightBlue => 0x2D,
154            CommonColor::DimPurple => 0x33,
155            CommonColor::NormalPurple => 0x32,
156            CommonColor::BrightPurple => 0x31,
157            CommonColor::DimPink => 0x37,
158            CommonColor::NormalPink => 0x36,
159            CommonColor::BrightPink => 0x35,
160        };
161        ColorPaletteIndex::clamp_new(index)
162    }
163
164    /// Converts the [`CommonColor`] to a [`Color`].
165    pub fn to_color(self) -> Color {
166        match self {
167            CommonColor::Off => Color::from_color_palette_range(97, 97, 97),
168            CommonColor::DarkGray => Color::from_color_palette_range(179, 179, 179),
169            CommonColor::LightGray => Color::from_color_palette_range(221, 221, 221),
170            CommonColor::White => Color::from_color_palette_range(255, 255, 255),
171            CommonColor::DimRed => Color::from_color_palette_range(179, 97, 97),
172            CommonColor::NormalRed => Color::from_color_palette_range(221, 97, 97),
173            CommonColor::BrightRed => Color::from_color_palette_range(255, 97, 97),
174            CommonColor::DimOrange => Color::from_color_palette_range(179, 118, 97),
175            CommonColor::NormalOrange => Color::from_color_palette_range(221, 140, 97),
176            CommonColor::BrightOrange => Color::from_color_palette_range(255, 179, 97),
177            CommonColor::DimYellow => Color::from_color_palette_range(179, 179, 97),
178            CommonColor::NormalYellow => Color::from_color_palette_range(221, 221, 97),
179            CommonColor::BrightYellow => Color::from_color_palette_range(255, 255, 97),
180            CommonColor::DimGreen => Color::from_color_palette_range(97, 179, 97),
181            CommonColor::NormalGreen => Color::from_color_palette_range(97, 221, 97),
182            CommonColor::BrightGreen => Color::from_color_palette_range(97, 255, 97),
183            CommonColor::DimCyan => Color::from_color_palette_range(97, 161, 179),
184            CommonColor::NormalCyan => Color::from_color_palette_range(97, 199, 221),
185            CommonColor::BrightCyan => Color::from_color_palette_range(97, 238, 255),
186            CommonColor::DimBlue => Color::from_color_palette_range(97, 97, 179),
187            CommonColor::NormalBlue => Color::from_color_palette_range(97, 97, 221),
188            CommonColor::BrightBlue => Color::from_color_palette_range(97, 97, 255),
189            CommonColor::DimPurple => Color::from_color_palette_range(118, 97, 179),
190            CommonColor::NormalPurple => Color::from_color_palette_range(129, 97, 221),
191            CommonColor::BrightPurple => Color::from_color_palette_range(161, 97, 255),
192            CommonColor::DimPink => Color::from_color_palette_range(179, 97, 179),
193            CommonColor::NormalPink => Color::from_color_palette_range(221, 97, 221),
194            CommonColor::BrightPink => Color::from_color_palette_range(255, 97, 255),
195        }
196    }
197}
198
199impl Into<ColorPaletteIndex> for CommonColor {
200    fn into(self) -> ColorPaletteIndex {
201        self.to_palette_index()
202    }
203}
204
205impl Into<Color> for CommonColor {
206    fn into(self) -> Color {
207        self.to_color()
208    }
209}
210
211// Trait to add the `clamp_new` functionality to U7
212pub trait U7Ext {
213    fn clamp_new(value: u8) -> U7;
214}
215
216impl U7Ext for U7 {
217    fn clamp_new(value: u8) -> U7 {
218        // Clamp the value to the range [0, 127]
219        U7::from_u8_lossy(value.min(U7::MAX.into()))
220    }
221}