1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
use midir::MidiOutputConnection;

use super::Button;
use crate::OutputDevice;

pub use crate::protocols::double_buffering::*;
pub use crate::protocols::query::*;

#[allow(dead_code)] // to prevent "variant is never constructed" warning
enum GridMappingMode {
    Session,
    DrumRack,
}

/// The Launchpad Mini output connection handler.
pub struct Output {
    connection: MidiOutputConnection,
}

impl crate::OutputDevice for Output {
    const MIDI_CONNECTION_NAME: &'static str = "Launchy Mini output";
    const MIDI_DEVICE_KEYWORD: &'static str = "Launchpad Mini";

    fn from_connection(connection: MidiOutputConnection) -> Result<Self, crate::MidiError> {
        let mut self_ = Self { connection };
        self_.change_grid_mapping_mode(GridMappingMode::Session)?;
        Ok(self_)
    }

    fn send(&mut self, bytes: &[u8]) -> Result<(), crate::MidiError> {
        self.connection.send(bytes)?;
        Ok(())
    }
}

impl Output {
    /// Set a `button` to a certain `color`.
    ///
    /// For example to set the leftmost control button to yellow:
    /// ```no_run
    /// # use launchy::mini::{Output, Button, Color, DoubleBufferingBehavior};
    /// # use launchy::OutputDevice as _;
    /// # let output: launchy::mini::Output = unimplemented!();
    ///
    /// let button = Button::ControlButton { index: 0 };
    /// let color = Color::YELLOW;
    /// output.set_button(button, color, DoubleBufferingBehavior::Copy)?;
    /// # Ok::<(), launchy::MidiError>(())
    /// ```
    pub fn set_button(
        &mut self,
        button: Button,
        color: Color,
        d: DoubleBufferingBehavior,
    ) -> Result<(), crate::MidiError> {
        let light_code = make_color_code(color, d);

        match button {
            Button::GridButton { x, y } => {
                let button_code = y * 16 + x;
                self.send(&[0x90, button_code, light_code])?;
            }
            Button::ControlButton { index } => {
                let button_code = 104 + index;
                self.send(&[0xB0, button_code, light_code])?;
            }
        }

        Ok(())
    }

    /// In order to make maximum use of the original Launchpad's slow midi speeds, a rapid LED
    /// lighting mode was invented which allows the lighting of two leds in just a single message.
    /// To use this mode, simply start sending these message and the Launchpad will update the 8x8
    /// grid in left-to-right, top-to-bottom order, then the eight scene launch buttons in
    /// top-to-bottom order, and finally the eight Automap/Live buttons in left-to-right order
    /// (these are otherwise inaccessible using note-on messages). Overflowing data will be ignored.
    ///
    /// To leave the mode, simply send any other message. Sending another kind of message and then
    /// re-sending this message will reset the cursor to the top left of the grid.
    pub fn set_button_rapid(
        &mut self,
        color1: Color,
        dbb1: DoubleBufferingBehavior,
        color2: Color,
        dbb2: DoubleBufferingBehavior,
    ) -> Result<(), crate::MidiError> {
        self.send(&[
            0x92,
            make_color_code(color1, dbb1),
            make_color_code(color2, dbb2),
        ])
    }

    /// Turns on all LEDs to a certain brightness, dictated by the `brightness` parameter. According
    /// to the Launchpad documentation, sending this command resets various configuration settings -
    /// see `reset()` for more information. However, in my experience, that only sometimes happens.
    /// Weird.
    ///
    /// This function is primarily intended as a diagnostics tool to verify that the library and the
    /// device is working correctly.
    pub fn turn_on_all_leds(&mut self, brightness: Brightness) -> Result<(), crate::MidiError> {
        let brightness_code = match brightness {
            Brightness::Off => 0,
            Brightness::Low => 125,
            Brightness::Medium => 126,
            Brightness::Full => 127,
        };

        self.send(&[0xB0, 0, brightness_code])
    }

    /// Launchpad controls the brightness of its LEDs by continually switching them on and off
    /// faster than the eye can see: a technique known as multiplexing. This command provides a way
    /// of altering the proportion of time for which the LEDs are on while they are in low- and
    /// medium-brightness modes. This proportion is known as the duty cycle.
    ///
    /// Manipulating this is useful for fade effects, for adjusting contrast, and for creating
    /// custom palettes.
    ///
    /// The default duty cycle is 1/5 meaning that low-brightness LEDs are on for only every fifth
    /// multiplex pass, and medium-brightness LEDs are on for two passes in every five. Generally,
    /// lower duty cycles (numbers closer to zero) will increase contrast between different
    /// brightness settings but will also increase flicker; higher ones will eliminate flicker, but
    /// will also reduce contrast. Note that using less simple ratios (such as 3/17 or 2/11) can
    /// also increase perceived flicker.
    ///
    /// If you are particularly sensitive to strobing lights, please use this command with care when
    /// working with large areas of low-brightness LEDs: in particular, avoid duty cycles of 1/8 or
    /// less.
    pub fn set_duty_cycle(
        &mut self,
        numerator: u8,
        denominator: u8,
    ) -> Result<(), crate::MidiError> {
        assert!(numerator >= 1);
        assert!(numerator <= 16);
        assert!(denominator >= 3);
        assert!(denominator <= 18);

        if numerator < 9 {
            self.send(&[0xB0, 30, 16 * (numerator - 1) + (denominator - 3)])
        } else {
            self.send(&[0xB0, 31, 16 * (numerator - 9) + (denominator - 3)])
        }
    }

    /// This method controls the double buffering mode on the Launchpad. See the module
    /// documentation for an explanation on double buffering.
    ///
    /// The default state is no flashing; the first buffer is both the update and the displayed
    /// buffer: In this mode, any LED data written to Launchpad is displayed instantly. Sending this
    /// message also resets the flash timer, so it can be used to resynchronise the flash rates of
    /// all the Launchpads connected to a system.
    ///
    /// - If `copy` is set, copy the LED states from the new displayed buffer to the new updating
    ///   buffer.
    /// - If `flash` is set, continually flip displayed buffers to make selected LEDs flash.
    /// - `updated`: the new updated buffer
    /// - `displayed`: the new displayed buffer
    pub fn control_double_buffering(&mut self, d: DoubleBuffering) -> Result<(), crate::MidiError> {
        let last_byte = 0b00100000
            | ((d.copy as u8) << 4)
            | ((d.flash as u8) << 3)
            | ((d.edited_buffer as u8) << 2)
            | d.displayed_buffer as u8;

        self.send(&[0xB0, 0, last_byte])
    }

    pub fn scroll_text(
        &mut self,
        text: &[u8],
        color: Color,
        should_loop: bool,
    ) -> Result<(), crate::MidiError> {
        let color_code = make_color_code_loopable(color, should_loop);

        let bytes = &[&[240, 0, 32, 41, 9, color_code], text, &[247]].concat();

        return self.send(bytes);
    }

    pub fn request_device_inquiry(&mut self, query: DeviceIdQuery) -> Result<(), crate::MidiError> {
        request_device_inquiry(self, query)
    }

    pub fn request_version_inquiry(&mut self) -> Result<(), crate::MidiError> {
        request_version_inquiry(self)
    }

    fn change_grid_mapping_mode(&mut self, mode: GridMappingMode) -> Result<(), crate::MidiError> {
        let mode = match mode {
            GridMappingMode::Session => 0,
            GridMappingMode::DrumRack => 1,
        };
        self.send(&[240, 0, 32, 41, 2, 24, 34, mode, 247])
    }

    // -----------------------------
    // Shorthand functions:
    // -----------------------------

    /// All LEDs are turned off, and the mapping mode, buffer settings, and duty cycle are reset to
    /// their default values.
    pub fn reset(&mut self) -> Result<(), crate::MidiError> {
        self.turn_on_all_leds(Brightness::Off)
    }

    pub fn set_all_buttons(&mut self, color: Color, dbb: DoubleBufferingBehavior) -> Result<(), crate::MidiError> {
        for _ in 0..40 {
            self.set_button_rapid(color, dbb, color, dbb)?;
        }

        Ok(())
    }

    pub fn light(&mut self, button: Button, color: Color) -> Result<(), crate::MidiError> {
        self.set_button(button, color, DoubleBufferingBehavior::Copy)
    }

    pub fn light_all(&mut self, color: Color) -> Result<(), crate::MidiError> {
        self.set_all_buttons(color, DoubleBufferingBehavior::Copy)
    }
}