├── .gitignore ├── main.cpp ├── CMakeLists.txt ├── inc ├── application.hpp └── midi.hpp ├── src ├── midi.cpp └── application.cpp └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *un~ 3 | main 4 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "inc/application.hpp" 2 | 3 | int main(void) 4 | { 5 | Application application; 6 | application.Init(); 7 | application.Run(); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project(LaunchkeyMiniMK3Midi) 3 | 4 | set(EXECUTABLE_NAME main) 5 | set(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}) 6 | 7 | INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/inc /usr/include) 8 | 9 | add_executable( ${EXECUTABLE_NAME} 10 | ./src/application.cpp 11 | ./src/midi.cpp 12 | ./main.cpp) 13 | 14 | SET(GCC_COVERAGE_COMPILE_FLAGS "-Wall -Wextra -std=c++20 -pedantic -lrtmidi") 15 | 16 | set(CMAKE_CXX_FLAGS ${GCC_COVERAGE_COMPILE_FLAGS}) 17 | 18 | target_link_libraries(main rtmidi) 19 | -------------------------------------------------------------------------------- /inc/application.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "midi.hpp" 4 | 5 | class Application 6 | { 7 | private: 8 | 9 | int midiOutPort = 0; 10 | int midiInPort = 0; 11 | int numColor = 0; 12 | int mode = 0; 13 | std::shared_ptr midiOut = nullptr; 14 | std::shared_ptr midiIn = nullptr; 15 | bool isPlaying = true; 16 | 17 | void 18 | InitMIDI(void); 19 | 20 | void 21 | ColorPicker(std::optional message); 22 | 23 | void 24 | Mario(void); 25 | 26 | void 27 | Disco(void); 28 | 29 | void 30 | Touch(std::optional message); 31 | 32 | void 33 | MIDILoop(void); 34 | 35 | void 36 | Brightness(void); 37 | 38 | public: 39 | 40 | Application(){}; 41 | ~Application(){}; 42 | 43 | void 44 | Init(void); 45 | 46 | void 47 | Run(void); 48 | }; 49 | -------------------------------------------------------------------------------- /src/midi.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool 4 | CompareMidiMessage(MidiMessage messageA, MidiMessage messageB) 5 | { 6 | return messageA.channel == messageB.channel && 7 | messageA.status == messageB.status && 8 | messageA.data1.value() == messageB.data1.value() && 9 | messageA.data2.value() == messageB.data2.value() ; 10 | } 11 | 12 | MidiIn::MidiIn() 13 | { 14 | midiIn = std::make_unique(); 15 | } 16 | 17 | std::vector& 18 | MidiIn::GetPorts(void) 19 | { 20 | ports.clear(); 21 | std::uint8_t numPorts = midiIn->getPortCount(); 22 | for (std::uint8_t i = 0; i < numPorts; ++i) 23 | ports.push_back(midiIn->getPortName(i)); 24 | return ports; 25 | } 26 | 27 | bool 28 | MidiIn::IsPortOpen(void) 29 | { 30 | return midiIn->isPortOpen(); 31 | } 32 | 33 | void 34 | MidiIn::OpenPort(std::uint8_t numPort) 35 | { 36 | unsigned int numPorts = midiIn->getPortCount(); 37 | if (numPort < numPorts) 38 | midiIn->openPort(numPort); 39 | else 40 | midiIn->openPort(0); 41 | 42 | midiIn->ignoreTypes(false, false, false); 43 | } 44 | 45 | void 46 | MidiIn::ClosePort(void) 47 | { 48 | midiIn->closePort(); 49 | } 50 | 51 | std::optional 52 | MidiIn::GetMidiMessage(void) 53 | { 54 | if (!midiIn->isPortOpen()) return std::nullopt; 55 | 56 | midiIn->getMessage(&message); 57 | if (message.size() == 0) 58 | return std::nullopt; 59 | 60 | MidiMessage midiMessage; 61 | if (message.at(0) < 0xF0) 62 | { 63 | midiMessage.status = MidiStatus(message.at(0) & 0xF0); 64 | midiMessage.channel = MidiChannel(message.at(0) & 0x0F); 65 | if (message.size() > 1) 66 | midiMessage.data1 = std::make_optional(message.at(1)); 67 | if (message.size() > 2) 68 | midiMessage.data2 = std::make_optional(message.at(2)); 69 | 70 | if (midiMessage.status == MidiStatus::CC) 71 | { 72 | CC.at(midiMessage.channel).at(*midiMessage.data1) = *midiMessage.data2; 73 | } 74 | } 75 | else 76 | { 77 | if (message.at(0) == MidiStatus::CLK) // skip clock sync 78 | return std::nullopt; 79 | } 80 | return std::make_optional(midiMessage); 81 | } 82 | 83 | double 84 | MidiIn::GetCCValue(std::uint8_t numCh, std::uint8_t numCtrl) 85 | { 86 | std::uint8_t val = CC.at(numCh).at(numCtrl); 87 | return val / 127.0; 88 | } 89 | 90 | MidiOut::MidiOut() 91 | { 92 | midiOut = std::make_unique(); 93 | } 94 | 95 | std::vector& 96 | MidiOut::GetPorts(void) 97 | { 98 | ports.clear(); 99 | std::uint8_t numPorts = midiOut->getPortCount(); 100 | for (std::uint8_t i = 0; i < numPorts; ++i) 101 | ports.push_back(midiOut->getPortName(i)); 102 | return ports; 103 | } 104 | 105 | void 106 | MidiOut::OpenPort(std::uint8_t numPort) 107 | { 108 | unsigned int numPorts = midiOut->getPortCount(); 109 | if (numPort < numPorts) 110 | midiOut->openPort(numPort); 111 | else 112 | midiOut->openPort(0); 113 | } 114 | 115 | void 116 | MidiOut::ClosePort(void) 117 | { 118 | midiOut->closePort(); 119 | } 120 | 121 | bool 122 | MidiOut::IsPortOpen(void) 123 | { 124 | return midiOut->isPortOpen(); 125 | } 126 | 127 | void 128 | MidiOut::SendMidiMessage(MidiMessage midiMessage) 129 | { 130 | if (!midiOut->isPortOpen()) return; 131 | message.clear(); 132 | 133 | message.push_back(midiMessage.status + midiMessage.channel); 134 | if (midiMessage.data1) 135 | message.push_back(*midiMessage.data1); 136 | if (midiMessage.data2) 137 | message.push_back(*midiMessage.data2); 138 | 139 | if (midiOut) 140 | midiOut->sendMessage(&message); 141 | } 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reverse enginneered MIDI implementation of Launchkey Mini MK3 by Novation 2 | ## Intro 3 | I've got Launchkey Mini to control my synthesizers and use it as controller for my VJ framework. 4 | Custom modes setup in Novation Components were useful but sometimes I needed more control over Launchkey Mini. 5 | Inspired by [this](https://www.partsnotincluded.com/how-to-control-the-leds-on-a-novation-launchkey-mini-ii/) article I tried to make something similar. 6 | Probably all of this knowledge is available in Launchkey Mini Programmer's Guide, but it's not publicly available. 7 | 8 | Companion spreadsheet is available [here](https://docs.google.com/spreadsheets/d/e/2PACX-1vQgwSu7S3ifJUJc8kXHBo6Be1NiIXhUXTK6S_oT_4rPPBQmic8yTu5OKbmn-la32DogcFcIzZE-TvMF/pubhtml). 9 | It contains data about selected midi messages, pad notes and colors. 10 | Especially colors sheet is useful because most of colors don't follow any pattern (or I couldn't see one). 11 | 12 | In source code you can find example application and structs for midi communication. 13 | Everything is done in C++ using [RtMidi](https://github.com/thestk/rtmidi) but it can be easily applied to any language and API since it's compatible with MIDI standard. 14 | This project uses C3 as middle-C convention (explained [here](https://computermusicresource.com/midikeys.html)), same as Novation. 15 | 16 | Good explanation of MIDI messages is available [here](https://users.cs.cf.ac.uk/Dave.Marshall/Multimedia/node158.html). 17 | Message examples in this tutorial will look like this: 18 | Channel|Status|Data1|Data2|Info 19 | -------|------|-----|-----|---- 20 | 1|Note On|60(C3)|127|Send C3 note on with 127 velocity on channel 1 21 | 22 | ## Compilation 23 | Standard CMake compilation. 24 | ```bash 25 | mkdir build 26 | cd build 27 | cmake .. 28 | make 29 | ``` 30 | 31 | ## Program usage 32 | This program is focused purely on DAW mode. When you launch example app you will be asked to select MIDI port of Launchkey Mini MK3. 33 | Choose **Launchkey Mini MK3 MIDI 2** for both input and output. 34 | This port is responsible for passing data in so-called DAW mode. 35 | If you need midi data from piano keys, custom mode knobs and pads, pitch bend and modulation then you should connect to port **Launchkey Mini MK3 MIDI 1**, not supported by this example code. 36 | 37 | After that you will be asked to pick one of following modes: 38 | * Color checker - simple mode which changes colors of all pads in session mode. You can change colors by pressing up and down arrow on Launchkey Mini. 39 | * Mario - simple animation showing Mario 40 | * Disco - each pad has randomized color from spectrum, changing every few seconds 41 | * Touch - randomized color each time you press a pad. You can change color mode by pressing up and down arrows, choosing normal, flashing or pulsing color. 42 | Press play button on Launchkey to safely exit application. 43 | 44 | ## MIDI implementation 45 | ### DAW mode 46 | First after opening midi port you have to enter DAW mode to gain more control over Launchkey Mini. 47 | This is done by sending Note On status on channel 16, note C-1 and velocity 127. 48 | If you want to exit DAW mode (probably on application quit) you have to send the same message but with 0 velocity. 49 | 50 | Channel|Status|Data1|Data2|Info 51 | -------|------|-----|-----|---- 52 | 16|Note On|12(C-1)|127|Enter DAW mode 53 | 16|Note On|12(C-1)|0|Exit DAW mode 54 | 55 | ### Buttons 56 | Some special buttons send CC messages which we can handle by getting following messages: 57 | 58 | Channel|Status|Data1|Data2|Info 59 | -------|------|-----|-----|---- 60 | 1|CC|104(G#6)|127/0|Up button press/release 61 | 1|CC|105(A6)|127/0|Down button press/release 62 | 1|CC|108(C7)|127/0|Shift button press/release 63 | 16|CC|103(G6)|127/0|Left button press/release 64 | 16|CC|102(F#6)|127/0|Right button press/release 65 | 16|CC|115(G7)|127/0|Play button press/release 66 | 16|CC|117(A7)|127/0|Record button press/release 67 | 68 | Shift + Octave buttons sends Program Change message 69 | Channel|Status|Data1|Info 70 | -------|------|-----|---- 71 | 1|Program Change|n|Sends program number 'n' 72 | 73 | ### Pad modes 74 | You can change pad mode by sending CC message on channel 16. 75 | It's the same thing as changing modes by holding shift and choosing mode with orange pads. 76 | 77 | edit 27.09.2021: as found by [riban-bw](https://github.com/riban-bw) there are few more undocumented modes. 78 | 79 | Modes 6-8 replace Custom mode as found [here](https://github.com/giezu/LaunchkeyMiniMK3/issues/1) until you reboot Launchkey Mini MK3. 80 | 81 | Channel|Status|Data1|Data2|Info 82 | -------|------|-----|-----|---- 83 | 16|CC|3(D#-2)|1|Drum pad mode 84 | 16|CC|3(D#-2)|2|Session pad mode 85 | 16|CC|3(D#-2)|5|Custom pad mode 86 | 16|CC|3(D#-2)|6|Drum 2 pad mode 87 | 16|CC|3(D#-2)|7|Toggle pad mode 88 | 16|CC|3(D#-2)|8|Program Change pad mode 89 | 90 | Device should reply with the same message. 91 | Keep in mind that custom mode requires listening on **Launchkey Mini MK3 MIDI 1** port. 92 | **Session** mode pads send note data on **channel 1**. 93 | **Drum** mode pads send note data on **channel 10**. 94 | 95 | ### Knob modes 96 | You can change knob mode by sending CC message on channel 16. 97 | It's the same thing as changing modes by holding shift and choosing mode with cyan pads. 98 | Channel|Status|Data1|Data2|Info 99 | -------|------|-----|-----|---- 100 | 16|CC|9(A-2)|1|Device knob mode 101 | 16|CC|9(A-2)|2|Volume knob mode 102 | 16|CC|9(A-2)|3|Pan knob mode 103 | 16|CC|9(A-2)|4|Sends A knob mode 104 | 16|CC|9(A-2)|5|Sends B knob mode 105 | 16|CC|9(A-2)|6|Custom knob mode 106 | 107 | Device should reply with the same message. 108 | Keep in mind that custom mode requires listening on **Launchkey Mini MK3 MIDI 1** port. 109 | Besides custom mode, other knob modes will always send CC message on channel 16, with data1 as knob number 21-28 (from left to right) and data2 as knob value (0-127). 110 | 111 | ### Color time! 112 | Coloring pads was the most time consuming thing. 113 | There are 128 colors available and writing down all of them wasn't easy. 114 | Colors 0-59 follow easy pattern, but the rest looks randomized. 115 | You can choose color mode by sending message to different channels. 116 | 117 | Channels 1, 2, 3 are used in **session** mode, for solid, flashing and pulsing pad respectively. 118 | 119 | Channels 10, 11, 12 are used in **drum** mode, for solid, flashing and pulsing pad respectively. 120 | 121 | Examples: 122 | Channel|Status|Data1|Data2|Info 123 | -------|------|-----|-----|---- 124 | **1**|Note On|96(C6)|9|Color top-left pad with **solid** orange in **session** mode 125 | **2**|Note On|96(C6)|21|Color top-left pad with **flashing** green in **session** mode 126 | **3**|Note On|103(G6)|49|Color top-right pad with **pulsing** purple in **session** mode 127 | **10**|Note On|96(C6)|9|Color top-left pad with **solid** orange in **drum** mode 128 | **11**|Note On|96(C6)|9|Color top-left pad with **flashing** orange in **drum** mode 129 | **12**|Note On|96(C6)|9|Color top-left pad with **pulsing** orange in **drum** mode 130 | 131 | Bonus: flashing pad toggles between previous color and current (flashing) color. This way you can send solid color followed by flashing color to flash between two different colors e.g. green/red/green/red... If you want to flash between disabled and color led you have to send black (0) solid color first. 132 | 133 | ### Other buttons (by [riban-bw](https://github.com/riban-bw)) 134 | You can color both Up and Down buttons by sending CC message on channel 1, 2, 3 (same as **session** color modes). 135 | Also Play and Record buttons react to these color modes but only white color is supported. 136 | 137 | Examples: 138 | Channel|Status|Data1|Data2|Info 139 | -------|------|-----|-----|---- 140 | **1**|CC|104(G#6)|9|Color Up pad with **solid** orange color 141 | **2**|CC|105(A6)|21|Color Down pad with **flashing** green color 142 | **3**|CC|105(A6)|49|Color Down pad with **pulsing** purple color 143 | **1**|CC|115(G7)|127|Play button with **solid** white 144 | **2**|CC|117(G7)|127|Record button with **flashing** white 145 | **3**|CC|115(G7)|127|Play button with **pulsing** white 146 | 147 | You can change **brightness** of Up, Down, Play, Pause buttons by sending CC message on channel 16. 148 | Possible brightness values: 0%, 25%, 50%, 75% and 100% 149 | 150 | Examples: 151 | Channel|Status|Data1|Data2|Info 152 | -------|------|-----|-----|---- 153 | 16|CC|104(G#6)|32|Up pad with 25% brightness 154 | 16|CC|105(A6)|48|Down pad with 50% brightness 155 | 16|CC|115(G7)|64|Play button with 75% brightness 156 | 16|CC|117(A7)|96|Record button with 100% brightness 157 | -------------------------------------------------------------------------------- /inc/midi.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | enum MidiNote 9 | { 10 | C_2 = 0, Db_2, D_2, Eb_2, E_2, F_2, Gb_2, G_2, Ab_2, A_2, Bb_2, B_2, 11 | C_1, Db_1, D_1, Eb_1, E_1, F_1, Gb_1, G_1, Ab_1, A_1, Bb_1, B_1, 12 | C0, Db0, D0, Eb0, E0, F0, Gb0, G0, Ab0, A0, Bb0, B0, 13 | C1, Db1, D1, Eb1, E1, F1, Gb1, G1, Ab1, A1, Bb1, B1, 14 | C2, Db2, D2, Eb2, E2, F2, Gb2, G2, Ab2, A2, Bb2, B2, 15 | C3, Db3, D3, Eb3, E3, F3, Gb3, G3, Ab3, A3, Bb3, B3, 16 | C4, Db4, D4, Eb4, E4, F4, Gb4, G4, Ab4, A4, Bb4, B4, 17 | C5, Db5, D5, Eb5, E5, F5, Gb5, G5, Ab5, A5, Bb5, B5, 18 | C6, Db6, D6, Eb6, E6, F6, Gb6, G6, Ab6, A6, Bb6, B6, 19 | C7, Db7, D7, Eb7, E7, F7, Gb7, G7, Ab7, A7, Bb7, B7, 20 | C8, Db8, D8, Eb8, E8, F8, Gb8, G8 21 | }; 22 | 23 | enum MidiStatus 24 | { 25 | NOF = 0x80, 26 | NON = 0x90, 27 | PKP = 0xA0, 28 | CC = 0xB0, 29 | PC = 0xC0, 30 | CP = 0xD0, 31 | PB = 0xE0, 32 | CLK = 0xF8, 33 | START = 0xFA, 34 | CONT = 0xFB, 35 | STOP = 0xFC, 36 | ACTSENS = 0xFE, 37 | SYSRES = 0xFF, 38 | }; 39 | 40 | enum MidiChannel 41 | { 42 | CH1 = 0x0, 43 | CH2 = 0x1, 44 | CH3 = 0x2, 45 | CH4 = 0x3, 46 | CH5 = 0x4, 47 | CH6 = 0x5, 48 | CH7 = 0x6, 49 | CH8 = 0x7, 50 | CH9 = 0x8, 51 | CH10 = 0x9, 52 | CH11 = 0xA, 53 | CH12 = 0xB, 54 | CH13 = 0xC, 55 | CH14 = 0xD, 56 | CH15 = 0xE, 57 | CH16 = 0xF 58 | }; 59 | 60 | struct MidiMessage 61 | { 62 | MidiChannel channel; 63 | MidiStatus status; 64 | std::optional data1 = std::nullopt; 65 | std::optional data2 = std::nullopt; 66 | }; 67 | 68 | bool 69 | CompareMidiMessage(MidiMessage messageA, MidiMessage messageB); 70 | 71 | namespace LaunchKey 72 | { 73 | const MidiMessage DAWModeOn = 74 | { 75 | .channel = MidiChannel::CH16, 76 | .status = MidiStatus::NON, 77 | .data1 = 0x0C, 78 | .data2 = 0x7F 79 | }; 80 | 81 | const MidiMessage DAWModeOff = 82 | { 83 | .channel = MidiChannel::CH16, 84 | .status = MidiStatus::NON, 85 | .data1 = 0x0C, 86 | .data2 = 0x00 87 | }; 88 | 89 | const MidiMessage SessionPadColor = 90 | { 91 | .channel = MidiChannel::CH1, 92 | .status = MidiStatus::NON, 93 | .data1 = 0x00, 94 | .data2 = 0x00 95 | }; 96 | 97 | const MidiMessage SessionPadColorFlash = 98 | { 99 | .channel = MidiChannel::CH2, 100 | .status = MidiStatus::NON, 101 | .data1 = 0x00, 102 | .data2 = 0x00 103 | }; 104 | 105 | const MidiMessage SessionPadColorPulse = 106 | { 107 | .channel = MidiChannel::CH3, 108 | .status = MidiStatus::NON, 109 | .data1 = 0x00, 110 | .data2 = 0x00 111 | }; 112 | 113 | const MidiMessage DrumPadColor = 114 | { 115 | .channel = MidiChannel::CH10, 116 | .status = MidiStatus::NON, 117 | .data1 = 0x00, 118 | .data2 = 0x00 119 | }; 120 | 121 | const MidiMessage DrumPadColorFlash = 122 | { 123 | .channel = MidiChannel::CH11, 124 | .status = MidiStatus::NON, 125 | .data1 = 0x00, 126 | .data2 = 0x00 127 | }; 128 | 129 | const MidiMessage DrumPadColorPulse = 130 | { 131 | .channel = MidiChannel::CH12, 132 | .status = MidiStatus::NON, 133 | .data1 = 0x00, 134 | .data2 = 0x00 135 | }; 136 | 137 | const MidiMessage PadBrightness = 138 | { 139 | .channel = MidiChannel::CH16, 140 | .status = MidiStatus::CC, 141 | .data1 = 0x00, 142 | .data2 = 0x60 143 | }; 144 | 145 | const MidiMessage PadModeToggle = 146 | { 147 | .channel = MidiChannel::CH16, 148 | .status = MidiStatus::CC, 149 | .data1 = 0x03, 150 | .data2 = 0x07 151 | }; 152 | 153 | const MidiMessage PadModePC = 154 | { 155 | .channel = MidiChannel::CH16, 156 | .status = MidiStatus::CC, 157 | .data1 = 0x03, 158 | .data2 = 0x08 159 | }; 160 | 161 | const MidiMessage PadModeDrum2 = 162 | { 163 | .channel = MidiChannel::CH16, 164 | .status = MidiStatus::CC, 165 | .data1 = 0x03, 166 | .data2 = 0x06 167 | }; 168 | 169 | const MidiMessage PadModeCustom = 170 | { 171 | .channel = MidiChannel::CH16, 172 | .status = MidiStatus::CC, 173 | .data1 = 0x03, 174 | .data2 = 0x05 175 | }; 176 | 177 | const MidiMessage PadModeDrum = 178 | { 179 | .channel = MidiChannel::CH16, 180 | .status = MidiStatus::CC, 181 | .data1 = 0x03, 182 | .data2 = 0x01 183 | }; 184 | 185 | const MidiMessage PadModeSession = 186 | { 187 | .channel = MidiChannel::CH16, 188 | .status = MidiStatus::CC, 189 | .data1 = 0x03, 190 | .data2 = 0x02 191 | }; 192 | 193 | const MidiMessage KnobModeVolume = 194 | { 195 | .channel = MidiChannel::CH16, 196 | .status = MidiStatus::CC, 197 | .data1 = 0x09, 198 | .data2 = 0x01 199 | }; 200 | 201 | const MidiMessage KnobModeDevice = 202 | { 203 | .channel = MidiChannel::CH16, 204 | .status = MidiStatus::CC, 205 | .data1 = 0x09, 206 | .data2 = 0x02 207 | }; 208 | 209 | const MidiMessage KnobModePan = 210 | { 211 | .channel = MidiChannel::CH16, 212 | .status = MidiStatus::CC, 213 | .data1 = 0x09, 214 | .data2 = 0x03 215 | }; 216 | 217 | const MidiMessage KnobModeSendsA = 218 | { 219 | .channel = MidiChannel::CH16, 220 | .status = MidiStatus::CC, 221 | .data1 = 0x09, 222 | .data2 = 0x04 223 | }; 224 | 225 | const MidiMessage KnobModeSendsB = 226 | { 227 | .channel = MidiChannel::CH16, 228 | .status = MidiStatus::CC, 229 | .data1 = 0x09, 230 | .data2 = 0x05 231 | }; 232 | 233 | const MidiMessage KnobModeCustom = 234 | { 235 | .channel = MidiChannel::CH16, 236 | .status = MidiStatus::CC, 237 | .data1 = 0x09, 238 | .data2 = 0x06 239 | }; 240 | 241 | const MidiMessage ArrUp = 242 | { 243 | .channel = MidiChannel::CH1, 244 | .status = MidiStatus::CC, 245 | .data1 = 0x68, 246 | .data2 = 0x7F 247 | }; 248 | 249 | const MidiMessage ArrDown = 250 | { 251 | .channel = MidiChannel::CH1, 252 | .status = MidiStatus::CC, 253 | .data1 = 0x69, 254 | .data2 = 0x7F 255 | }; 256 | 257 | const MidiMessage ArrLeft = 258 | { 259 | .channel = MidiChannel::CH16, 260 | .status = MidiStatus::CC, 261 | .data1 = 0x67, 262 | .data2 = 0x7F 263 | }; 264 | 265 | const MidiMessage ArrRight= 266 | { 267 | .channel = MidiChannel::CH16, 268 | .status = MidiStatus::CC, 269 | .data1 = 0x66, 270 | .data2 = 0x7F 271 | }; 272 | 273 | const MidiMessage Shift = 274 | { 275 | .channel = MidiChannel::CH1, 276 | .status = MidiStatus::CC, 277 | .data1 = 0x6C, 278 | .data2 = 0x7F 279 | }; 280 | 281 | const MidiMessage Play = 282 | { 283 | .channel = MidiChannel::CH16, 284 | .status = MidiStatus::CC, 285 | .data1 = 0x73, 286 | .data2 = 0x7F 287 | }; 288 | 289 | const MidiMessage Record = 290 | { 291 | .channel = MidiChannel::CH16, 292 | .status = MidiStatus::CC, 293 | .data1 = 0x75, 294 | .data2 = 0x7F 295 | }; 296 | 297 | enum DrumPads 298 | { 299 | DP1 = MidiNote::E1, 300 | DP2, 301 | DP3, 302 | DP4, 303 | DP5 = MidiNote::C2, 304 | DP6, 305 | DP7, 306 | DP8, 307 | DP9 = MidiNote::C1, 308 | DP10, 309 | DP11, 310 | DP12, 311 | DP13 = MidiNote::Ab1, 312 | DP14, 313 | DP15, 314 | DP16, 315 | }; 316 | 317 | enum SessionPads 318 | { 319 | SP1 = MidiNote::C6, 320 | SP2, 321 | SP3, 322 | SP4, 323 | SP5, 324 | SP6, 325 | SP7, 326 | SP8, 327 | SP9 = MidiNote::E7, 328 | SP10, 329 | SP11, 330 | SP12, 331 | SP13, 332 | SP14, 333 | SP15, 334 | SP16 335 | }; 336 | 337 | enum Knob 338 | { 339 | K1 = 21, 340 | K2, 341 | K3, 342 | K4, 343 | K5, 344 | K6, 345 | K7, 346 | K8 347 | }; 348 | 349 | // % of brightness -> 0, 25, 50, 75, 100% 350 | enum Brightness 351 | { 352 | P0 = 0x00, 353 | P25 = 0x20, 354 | P50 = 0x30, 355 | P75 = 0x40, 356 | P100 = 0x60 357 | }; 358 | } 359 | 360 | class MidiIn 361 | { 362 | private: 363 | 364 | // 16 channels - 120 controllers - value 365 | std::array, 16> CC = {}; 366 | std::unique_ptr midiIn = nullptr; 367 | std::vector message; 368 | std::vector ports; 369 | 370 | public: 371 | 372 | MidiIn(); 373 | 374 | std::vector& 375 | GetPorts(void); 376 | 377 | bool 378 | IsPortOpen(void); 379 | 380 | void 381 | OpenPort(std::uint8_t numPort); 382 | 383 | void 384 | ClosePort(void); 385 | 386 | std::optional 387 | GetMidiMessage(void); 388 | 389 | double 390 | GetCCValue(std::uint8_t numCh, std::uint8_t numCtrl); 391 | }; 392 | 393 | class MidiOut 394 | { 395 | private: 396 | 397 | std::unique_ptr midiOut = nullptr; 398 | std::vector message; 399 | std::vector ports; 400 | 401 | public: 402 | 403 | MidiOut(); 404 | 405 | std::vector& 406 | GetPorts(void); 407 | 408 | bool 409 | IsPortOpen(void); 410 | 411 | void 412 | OpenPort(std::uint8_t numPort); 413 | 414 | void 415 | ClosePort(void); 416 | 417 | void 418 | SendMidiMessage(MidiMessage midiMessage); 419 | }; 420 | -------------------------------------------------------------------------------- /src/application.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | void 6 | Application::InitMIDI(void) 7 | { 8 | midiOut = std::make_shared(); 9 | midiIn = std::make_shared(); 10 | 11 | std::cout << "Midi In: " << std::endl; 12 | auto ports = midiIn->GetPorts(); 13 | for (unsigned int i = 0; i < ports.size(); ++i) 14 | std::cout << "Port id: " << i << " name: " << ports.at(i) << std::endl; 15 | 16 | int n; 17 | std::cin >> n; 18 | 19 | midiIn->OpenPort(n); 20 | 21 | std::cout << "Midi Out: " << std::endl; 22 | ports = midiOut->GetPorts(); 23 | for (unsigned int i = 0; i < ports.size(); ++i) 24 | std::cout << "Port id: " << i << " name: " << ports.at(i) << std::endl; 25 | 26 | std::cin >> n; 27 | 28 | std::cout << "Mode: " << std::endl << 29 | "1: Color checker" << std::endl << 30 | "2: Mario" << std::endl << 31 | "3: Disco" << std::endl << 32 | "4: Touch" << std::endl << 33 | "5: Toggle" << std::endl << 34 | "6: PC" << std::endl << 35 | "7: Drum mode 2" << std::endl << 36 | "8: Brightness" << std::endl; 37 | 38 | std::cin >> mode; 39 | 40 | midiOut->OpenPort(n); 41 | midiOut->SendMidiMessage(LaunchKey::DAWModeOn); 42 | 43 | if (mode == 1 || mode == 2 || mode == 8) 44 | midiOut->SendMidiMessage(LaunchKey::PadModeSession); 45 | 46 | if (mode == 3 || mode == 4) 47 | midiOut->SendMidiMessage(LaunchKey::PadModeDrum); 48 | 49 | if (mode == 5) 50 | midiOut->SendMidiMessage(LaunchKey::PadModeToggle); 51 | 52 | if (mode == 6) 53 | midiOut->SendMidiMessage(LaunchKey::PadModePC); 54 | 55 | if (mode == 7) 56 | midiOut->SendMidiMessage(LaunchKey::PadModeDrum2); 57 | 58 | MidiMessage message = LaunchKey::Play; 59 | message.channel = MidiChannel::CH2; 60 | message.data2 = 0x01; 61 | midiOut->SendMidiMessage(message); 62 | } 63 | 64 | void 65 | Application::Init(void) 66 | { 67 | std::cout << "Play - exit" << std::endl; 68 | InitMIDI(); 69 | } 70 | 71 | void 72 | Application::ColorPicker(std::optional message) 73 | { 74 | if (!message) return; 75 | 76 | int tmp = numColor; 77 | if (CompareMidiMessage(*message, LaunchKey::ArrUp)) 78 | numColor++; 79 | else if (CompareMidiMessage(*message, LaunchKey::ArrDown)) 80 | numColor--; 81 | 82 | if (tmp == numColor) return; 83 | 84 | if (numColor < 0) numColor = 0; 85 | if (numColor > 127) numColor = 127; 86 | 87 | MidiMessage colMessage = LaunchKey::SessionPadColor; 88 | colMessage.data2 = numColor; 89 | for (int i = 0; i < 8; ++i) 90 | { 91 | colMessage.data1 = LaunchKey::SessionPads::SP1 + i; 92 | midiOut->SendMidiMessage(colMessage); 93 | } 94 | for (int i = 0; i < 8; ++i) 95 | { 96 | colMessage.data1 = LaunchKey::SessionPads::SP9 + i; 97 | midiOut->SendMidiMessage(colMessage); 98 | } 99 | } 100 | 101 | void 102 | Application::Mario(void) 103 | { 104 | static int numRow = 0; 105 | static const std::array, 16> mario {{ 106 | {0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0}, 107 | {0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0}, 108 | {0, 0, 0, 0, 11, 11, 11, 9, 9, 11, 9, 0, 0, 0, 0, 0}, 109 | {0, 0, 0, 11, 9, 11, 9, 9, 9, 11, 9, 9, 9, 0, 0, 0}, 110 | {0, 0, 0, 11, 9, 11, 11, 9, 9, 9, 11, 9, 9, 9, 0, 0}, 111 | {0, 0, 0, 11, 11, 9, 9, 9, 9, 11, 11, 11, 11, 0, 0, 0}, 112 | {0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0}, 113 | {0, 0, 0, 0, 11, 11, 5, 11, 11, 11, 0, 0, 0, 0, 0, 0}, 114 | {0, 0, 0, 11, 11, 11, 5, 11, 11, 5, 11, 11, 11, 0, 0, 0}, 115 | {0, 0, 11, 11, 11, 11, 5, 5, 5, 5, 11, 11, 11, 11, 0, 0}, 116 | {0, 0, 9, 9, 11, 5, 13, 5, 5, 13, 5, 11, 9, 9, 0, 0}, 117 | {0, 0, 9, 9, 9, 5, 5, 5, 5, 5, 5, 9, 9, 9, 0, 0}, 118 | {0, 0, 9, 9, 5, 5, 5, 5, 5, 5, 5, 5, 9, 9, 0, 0}, 119 | {0, 0, 0, 0, 5, 5, 5, 0, 0, 5, 5, 5, 0, 0, 0, 0}, 120 | {0, 0, 0, 11, 11, 11, 0, 0, 0, 0, 11, 11, 11, 0, 0, 0}, 121 | {0, 0, 11, 11, 11, 11, 0, 0, 0, 0, 11, 11, 11, 11, 0, 0} 122 | }}; 123 | 124 | static bool dir = true; 125 | 126 | MidiMessage colMessage = LaunchKey::SessionPadColor; 127 | for (int i = 0; i < 8; ++i) 128 | { 129 | colMessage.data1 = LaunchKey::SessionPads::SP1 + i; 130 | colMessage.data2 = mario[numRow][i+4]; 131 | midiOut->SendMidiMessage(colMessage); 132 | } 133 | 134 | for (int i = 0; i < 8; ++i) 135 | { 136 | colMessage.data1 = LaunchKey::SessionPads::SP9 + i; 137 | colMessage.data2 = mario[numRow+1][i+4]; 138 | midiOut->SendMidiMessage(colMessage); 139 | } 140 | 141 | numRow += dir ? 1 : -1; 142 | if (numRow == 14 || 143 | numRow == 0) 144 | dir = !dir; 145 | 146 | usleep(166667); 147 | } 148 | 149 | void 150 | Application::Disco(void) 151 | { 152 | static bool initialized = false; 153 | static std::array timers; 154 | 155 | if (!initialized) 156 | { 157 | for (int i = 0; i > 16; ++i) 158 | timers[i] = rand() % 100; 159 | initialized = true; 160 | } 161 | 162 | MidiMessage colMessage = LaunchKey::DrumPadColor; 163 | 164 | for (int i = 0; i < 4; ++i) 165 | { 166 | if (timers[i] <= 0) 167 | { 168 | colMessage.data1 = LaunchKey::DrumPads::DP1 + i; 169 | colMessage.data2 = (rand() % 14 + 1) * 4 + 1; 170 | midiOut->SendMidiMessage(colMessage); 171 | timers[i] = rand() % 5; 172 | } 173 | } 174 | 175 | for (int i = 0; i < 4; ++i) 176 | { 177 | if (timers[i+4] <= 0) 178 | { 179 | colMessage.data1 = LaunchKey::DrumPads::DP5 + i; 180 | colMessage.data2 = (rand() % 14 + 1) * 4 + 1; 181 | midiOut->SendMidiMessage(colMessage); 182 | timers[i+4] = rand() % 5; 183 | } 184 | } 185 | 186 | for (int i = 0; i < 4; ++i) 187 | { 188 | if (timers[i+8] <= 0) 189 | { 190 | colMessage.data1 = LaunchKey::DrumPads::DP9 + i; 191 | colMessage.data2 = (rand() % 14 + 1) * 4 + 1; 192 | midiOut->SendMidiMessage(colMessage); 193 | timers[i+8] = rand() % 5; 194 | } 195 | } 196 | 197 | for (int i = 0; i < 4; ++i) 198 | { 199 | if (timers[i+12] <= 0) 200 | { 201 | colMessage.data1 = LaunchKey::DrumPads::DP13 + i; 202 | colMessage.data2 = (rand() % 14 + 1) * 4 + 1; 203 | midiOut->SendMidiMessage(colMessage); 204 | timers[i+12] = rand() % 5; 205 | } 206 | } 207 | 208 | for (int i = 0; i < 16; ++i) 209 | timers[i]--; 210 | 211 | usleep(100000); 212 | } 213 | 214 | void 215 | Application::Touch(std::optional message) 216 | { 217 | if (!message) return; 218 | 219 | static int colorMode = 0; 220 | 221 | if (CompareMidiMessage(*message, LaunchKey::ArrUp)) 222 | colorMode++; 223 | else if (CompareMidiMessage(*message, LaunchKey::ArrDown)) 224 | colorMode--; 225 | 226 | if (colorMode < 0) colorMode = 0; 227 | if (colorMode > 2) colorMode = 2; 228 | 229 | if (message->channel == MidiChannel::CH10 && 230 | message->status == MidiStatus::NON && 231 | message->data2.value() != 0) 232 | { 233 | MidiMessage colMessage; 234 | 235 | if (colorMode == 0) 236 | colMessage = LaunchKey::DrumPadColor; 237 | else if (colorMode == 1) 238 | colMessage = LaunchKey::DrumPadColorFlash; 239 | else if (colorMode == 2) 240 | colMessage = LaunchKey::DrumPadColorPulse; 241 | 242 | colMessage.data1 = message->data1.value(); 243 | colMessage.data2 = rand() % 128; 244 | midiOut->SendMidiMessage(colMessage); 245 | } 246 | } 247 | 248 | void 249 | Application::Brightness(void) 250 | { 251 | static bool init = false; 252 | 253 | if (init) return; 254 | MidiMessage message = LaunchKey::PadBrightness; 255 | 256 | // up arrow 257 | message.data1 = 0x68; 258 | message.data2 = LaunchKey::Brightness::P50; 259 | midiOut->SendMidiMessage(message); 260 | 261 | // down arrow 262 | message.data1 = 0x69; 263 | message.data2 = LaunchKey::Brightness::P75; 264 | midiOut->SendMidiMessage(message); 265 | 266 | // record button 267 | message.data1 = 0x75; 268 | message.data2 = LaunchKey::Brightness::P100; 269 | midiOut->SendMidiMessage(message); 270 | 271 | message.status = MidiStatus::NON; 272 | 273 | init = true; 274 | } 275 | 276 | void 277 | Application::MIDILoop(void) 278 | { 279 | std::optional messageIn = midiIn->GetMidiMessage(); 280 | if (messageIn) 281 | { 282 | if (CompareMidiMessage(*messageIn, LaunchKey::Play)) 283 | isPlaying = false; 284 | 285 | if (!messageIn->data2) 286 | { 287 | std::cout << "channel: " << messageIn->channel 288 | << " status: " << messageIn->status 289 | << " data1: " << messageIn->data1.value() << std::endl; 290 | } 291 | else 292 | { 293 | std::cout << "channel: " << messageIn->channel 294 | << " status: " << messageIn->status 295 | << " data1: " << messageIn->data1.value() 296 | << " data2: " << messageIn->data2.value() << std::endl; 297 | } 298 | } 299 | 300 | if (mode == 1) 301 | ColorPicker(messageIn); 302 | 303 | else if (mode == 2) 304 | Mario(); 305 | 306 | // Disco 307 | else if (mode == 3) 308 | Disco(); 309 | 310 | // Touch 311 | else if (mode == 4) 312 | Touch(messageIn); 313 | 314 | else if (mode == 8) 315 | Brightness(); 316 | } 317 | 318 | void 319 | Application::Run(void) 320 | { 321 | do 322 | { 323 | MIDILoop(); 324 | } 325 | while (isPlaying); 326 | 327 | { 328 | MidiMessage colMessage = LaunchKey::DrumPadColor; 329 | colMessage.data2 = 0; 330 | for (int i = 0; i < 4; ++i) 331 | { 332 | colMessage.data1 = LaunchKey::DrumPads::DP1 + i; 333 | midiOut->SendMidiMessage(colMessage); 334 | } 335 | 336 | for (int i = 0; i < 4; ++i) 337 | { 338 | colMessage.data1 = LaunchKey::DrumPads::DP5 + i; 339 | midiOut->SendMidiMessage(colMessage); 340 | } 341 | 342 | for (int i = 0; i < 4; ++i) 343 | { 344 | colMessage.data1 = LaunchKey::DrumPads::DP9 + i; 345 | midiOut->SendMidiMessage(colMessage); 346 | } 347 | 348 | for (int i = 0; i < 4; ++i) 349 | { 350 | colMessage.data1 = LaunchKey::DrumPads::DP13 + i; 351 | midiOut->SendMidiMessage(colMessage); 352 | } 353 | } 354 | 355 | { 356 | MidiMessage colMessage = LaunchKey::SessionPadColor; 357 | colMessage.data2 = 0; 358 | for (int i = 0; i < 8; ++i) 359 | { 360 | colMessage.data1 = LaunchKey::SessionPads::SP1 + i; 361 | midiOut->SendMidiMessage(colMessage); 362 | } 363 | for (int i = 0; i < 8; ++i) 364 | { 365 | colMessage.data1 = LaunchKey::SessionPads::SP9 + i; 366 | midiOut->SendMidiMessage(colMessage); 367 | } 368 | } 369 | 370 | MidiMessage message = LaunchKey::Play; 371 | message.channel = MidiChannel::CH2; 372 | message.data2 = LaunchKey::Brightness::P0; 373 | midiOut->SendMidiMessage(message); 374 | 375 | midiOut->SendMidiMessage(LaunchKey::DAWModeOff); 376 | } 377 | 378 | --------------------------------------------------------------------------------