novation_remote_25sl/
lib.rs

1//! A simple crate for converting the MIDI output of the Novation ReMOTE 25SL into user-friendly
2//! rust-esque types.
3
4pub extern crate pitch_calc;
5pub use pitch_calc::{Letter, LetterOctave};
6
7/// The name of the ports on which the `25SL` emits MIDI input values.
8pub const MIDI_INPUT_PORT_PREFIX: &'static str = "ReMOTE SL";
9
10/// The MIDI input ports on which the `25SL` emits MIDI input values.
11#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
12pub enum InputPort {
13    /// Receives keyboard note events as well as `Pitch` and `Mod` `Control` events.
14    A,
15    /// Receives all other `Control` events.
16    B,
17    /// Receives messages about newly loaded presets.
18    C,
19}
20
21/// All possible events that might be emitted from the ReMOTE 25SL.
22#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
23pub enum Event {
24    Control(Control),
25    Key(State, LetterOctave, u8),
26}
27
28/// Note events emitted from key presses.
29#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
30pub enum State {
31    /// The note was pressed.
32    On,
33    /// The note was released.
34    Off
35}
36
37/// Most controls on the 25SL come in 8 strips.
38#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
39#[repr(u8)]
40pub enum Oct { A, B, C, D, E, F, G, H }
41
42/// The 4 distinct rows on which `Button`s are placed.
43#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
44pub enum ButtonRow {
45    TopLeft,
46    BottomLeft,
47    TopRight,
48    BottomRight,
49}
50
51/// Axes on which the `TouchPad` can output values.
52#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
53pub enum Axis { X, Y }
54
55/// The left and right sides of the controller.
56#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
57pub enum Side {
58    Left,
59    Right,
60}
61
62/// The page up and down buttons.
63#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
64pub enum Page {
65    Up,
66    Down,
67}
68
69/// The four buttons on the upper left hand side of the controller from top to bottom.
70#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
71pub enum LeftButton { A, B, C, D }
72
73/// The three buttons on the upper right hand side of the controller from top to bottom.
74#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
75pub enum RightButton { A, B, C }
76
77/// Media playback-style control buttons.
78#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
79pub enum Playback {
80    Previous,
81    Next,
82    Stop,
83    Play,
84    Loop,
85    Record,
86}
87
88/// Controller events.
89#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
90pub enum Control {
91
92    /// A magnitude in the direction in which the dial was turned.
93    ///
94    /// Values may range from -64 to 64 (exclusive).
95    RotaryDial(Oct, i8),
96
97    /// The value to which the slider was set.
98    ///
99    /// Values range from `0` to `127` (inclusive).
100    RotarySlider(Oct, u8),
101
102    /// The value to which the slider was set.
103    ///
104    /// Values range from `0` to `127` (inclusive).
105    VerticalSlider(Oct, u8),
106
107    /// The force with which the pad was pressed.
108    ///
109    /// Values range from `0` to `127` (inclusive).
110    PressurePad(Oct, u8),
111
112    /// A button was pressed on the given row.
113    Button(ButtonRow, Oct, State),
114
115    /// The position on the touch pad that was pressed.
116    ///
117    /// Values range from `0` to `127` (inclusive).
118    TouchPad(Axis, u8),
119
120    /// The position of the pitch bender.
121    ///
122    /// Ranges from -64 to 64 (exclusive).
123    Pitch(i8),
124
125    /// The position of the modulation bender.
126    ///
127    /// Values range from 0 to 127 (inclusive).
128    Mod(u8),
129
130    /// The page up and down buttons on the top left and right of the controller..
131    Page(Side, Page, State),
132
133    /// The four buttons on the upper left hand side of the controller.
134    LeftButton(LeftButton, State),
135
136    /// The three buttons on the upper right hand side of the controller.
137    RightButton(RightButton, State),
138
139    /// Media playback-style control buttons.
140    Playback(Playback, State),
141}
142
143
144impl Oct {
145
146    /// Create an `Oct` from the given value where 0 == A, 1 == B, etc.
147    fn from_u8(n: u8) -> Option<Self> {
148        match n {
149            0 => Some(Oct::A),
150            1 => Some(Oct::B),
151            2 => Some(Oct::C),
152            3 => Some(Oct::D),
153            4 => Some(Oct::E),
154            5 => Some(Oct::F),
155            6 => Some(Oct::G),
156            7 => Some(Oct::H),
157            _ => None,
158        }
159    }
160
161}
162
163
164impl InputPort {
165
166    /// Determine the `InputPort` from its name.
167    pub fn from_name(name: &str) -> Option<Self> {
168        if name.starts_with(MIDI_INPUT_PORT_PREFIX) {
169            match name.chars().last() {
170                Some('0') => Some(InputPort::A),
171                Some('1') => Some(InputPort::B),
172                Some('2') => Some(InputPort::C),
173                _ => None,
174            }
175        } else {
176            None
177        }
178    }
179
180}
181
182
183impl Event {
184
185    /// Produce an `Event` from the given MIDI input port number and the MIDI message itself.
186    pub fn from_midi(port: InputPort, msg: &[u8]) -> Option<Self> {
187        match port {
188
189            // Receive keyboard note events and pitch/mod bend values.
190            InputPort::A => match msg.len() {
191                3 => match (msg[0], msg[1], msg[2]) {
192
193                    // Pitch bend.
194                    (224, 0, pitch) => Some(Control::Pitch(pitch as i8 - 64).into()),
195
196                    // Modulation bend.
197                    (176, 1, modulation) => Some(Control::Mod(modulation).into()),
198
199                    // Notes pressed on the keyboard.
200                    (state, step, velocity) => {
201                        let letter_octave = pitch_calc::Step(step as f32).to_letter_octave();
202                        let note = match state {
203                            144 => Some(State::On),
204                            128 => Some(State::Off),
205                            _ => None,
206                        };
207                        note.map(|note| Event::Key(note, letter_octave, velocity))
208                    },
209
210                },
211                _ => None,
212            },
213
214            // Receive control events.
215            InputPort::B => match msg.len() {
216                3 => match (msg[0], msg[1], msg[2]) {
217
218                    // Rotary dialers.
219                    (176, n @ 56...63, value) => {
220                        let oct = Oct::from_u8(n - 56).unwrap();
221                        let value = if value > 64 { -(value as i8 - 64) } else { value as i8 };
222                        Some(Control::RotaryDial(oct, value).into())
223                    },
224
225                    // Rotary sliders.
226                    (176, n @ 8...15, value) => {
227                        let oct = Oct::from_u8(n - 8).unwrap();
228                        Some(Control::RotarySlider(oct, value).into())
229                    },
230
231                    // Vertical sliders.
232                    (176, n @ 16...23, value) => {
233                        let oct = Oct::from_u8(n - 16).unwrap();
234                        Some(Control::VerticalSlider(oct, value).into())
235                    },
236
237                    // Pressure pads.
238                    (144, n @ 36...43, velocity) => {
239                        let oct = Oct::from_u8(n - 36).unwrap();
240                        Some(Control::PressurePad(oct, velocity).into())
241                    },
242
243                    // Touch pad.
244                    (176, axis @ 68...69, value) => {
245                        let axis = if axis == 68 { Axis::X } else { Axis::Y };
246                        Some(Control::TouchPad(axis, value).into())
247                    },
248
249
250                    ///////////////////
251                    ///// Buttons /////
252                    ///////////////////
253
254                    // Top left row buttons.
255                    (176, n @ 24...31, state) => {
256                        let oct = Oct::from_u8(n - 24).unwrap();
257                        let state = if state == 0 { State::Off } else { State::On };
258                        Some(Control::Button(ButtonRow::TopLeft, oct, state).into())
259                    },
260
261                    // Bottom left row buttons.
262                    (176, n @ 32...39, state) => {
263                        let oct = Oct::from_u8(n - 32).unwrap();
264                        let state = if state == 0 { State::Off } else { State::On };
265                        Some(Control::Button(ButtonRow::BottomLeft, oct, state).into())
266                    },
267
268                    // Top right row buttons.
269                    (176, n @ 40...47, state) => {
270                        let oct = Oct::from_u8(n - 40).unwrap();
271                        let state = if state == 0 { State::Off } else { State::On };
272                        Some(Control::Button(ButtonRow::TopRight, oct, state).into())
273                    },
274
275                    // Bottom right row buttons.
276                    (176, n @ 48...55, state) => {
277                        let oct = Oct::from_u8(n - 48).unwrap();
278                        let state = if state == 0 { State::Off } else { State::On };
279                        Some(Control::Button(ButtonRow::BottomRight, oct, state).into())
280                    },
281
282                    // Page up and down.
283                    (176, n @ 88...91, state) => {
284                        let (side, page) = match n {
285                            88 => (Side::Left, Page::Up),
286                            89 => (Side::Left, Page::Down),
287                            90 => (Side::Right, Page::Up),
288                            91 => (Side::Right, Page::Down),
289                            _ => unreachable!(),
290                        };
291                        let state = if state == 0 { State::Off } else { State::On };
292                        Some(Control::Page(side, page, state).into())
293                    },
294
295                    // Left-hand side buttons.
296                    (176, n @ 80...83, state) => {
297                        let button = match n {
298                            80 => LeftButton::A,
299                            81 => LeftButton::B,
300                            82 => LeftButton::C,
301                            83 => LeftButton::D,
302                            _ => unreachable!(),
303                        };
304                        let state = if state == 0 { State::Off } else { State::On };
305                        Some(Control::LeftButton(button, state).into())
306                    },
307
308                    // Right-hand side buttons.
309                    (176, n @ 85...87, state) => {
310                        let button = match n {
311                            85 => RightButton::A,
312                            86 => RightButton::B,
313                            87 => RightButton::C,
314                            _ => unreachable!(),
315                        };
316                        let state = if state == 0 { State::Off } else { State::On };
317                        Some(Control::RightButton(button, state).into())
318                    },
319
320                    // Playback buttons.
321                    (176, n @ 72...77, state) => {
322                        let playback = match n {
323                            72 => Playback::Previous,
324                            73 => Playback::Next,
325                            74 => Playback::Stop,
326                            75 => Playback::Play,
327                            76 => Playback::Record,
328                            77 => Playback::Loop,
329                            _ => unreachable!(),
330                        };
331                        let state = if state == 0 { State::Off } else { State::On };
332                        Some(Control::Playback(playback, state).into())
333                    },
334
335                    _ => None,
336
337                },
338                _ => None,
339            },
340
341            // Receive preset state loaded from the controller.
342            InputPort::C => {
343                None
344            },
345        }
346    }
347
348}
349
350
351impl From<Control> for Event {
352    fn from(control: Control) -> Self {
353        Event::Control(control)
354    }
355}