Last active
December 24, 2024 16:09
Arturia MiniLab MKII RGB Lighting Feedback
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <windows.h> | |
#include <mmsystem.h> | |
#include <iostream> | |
#include <string> | |
#include <thread> | |
#include <atomic> | |
#include <unordered_map> | |
#include <memory> | |
#pragma comment(lib, "Winmm.lib") | |
const std::wstring REQUIRED_INPUT_DEVICE = L"Arturia loopMIDI Port"; | |
const std::wstring REQUIRED_OUTPUT_DEVICE = L"Arturia MiniLab mkII"; | |
struct ButtonState { | |
BYTE color; | |
std::atomic<bool> isMuted; | |
std::atomic<bool> isBlinking; | |
ButtonState(BYTE col, bool muted, bool blinking) | |
: color(col), isMuted(muted), isBlinking(blinking) {} | |
ButtonState() : ButtonState(0x01, false, false) {} | |
}; | |
std::unordered_map<int, std::unique_ptr<ButtonState>> buttonStates; | |
HMIDIOUT hMidiOut = nullptr; | |
std::atomic<bool> isRunning(true); | |
void SetButtonColor(int buttonIndex, BYTE color) { | |
std::cout << "Setting color for Button " << buttonIndex + 1 << " to 0x" << std::hex << (int)color << std::endl; | |
BYTE sysexMessage[] = { | |
0xF0, 0x00, 0x20, 0x6B, 0x7F, 0x42, 0x02, 0x00, | |
0x10, static_cast<BYTE>(0x70 + buttonIndex), color, 0xF7 | |
}; | |
MIDIHDR midiHdr; | |
memset(&midiHdr, 0, sizeof(MIDIHDR)); | |
midiHdr.lpData = reinterpret_cast<LPSTR>(sysexMessage); | |
midiHdr.dwBufferLength = sizeof(sysexMessage); | |
midiHdr.dwFlags = 0; | |
if (midiOutPrepareHeader(hMidiOut, &midiHdr, sizeof(MIDIHDR)) == MMSYSERR_NOERROR) { | |
midiOutLongMsg(hMidiOut, &midiHdr, sizeof(MIDIHDR)); | |
midiOutUnprepareHeader(hMidiOut, &midiHdr, sizeof(MIDIHDR)); | |
} | |
} | |
void BlinkButton(int buttonIndex, BYTE color) { | |
while (buttonStates[buttonIndex]->isBlinking && isRunning) { | |
SetButtonColor(buttonIndex, 0x00); // Turn off light | |
Sleep(500); | |
if (!buttonStates[buttonIndex]->isBlinking || !isRunning) break; | |
SetButtonColor(buttonIndex, color); // Turn on light with the specified color | |
Sleep(500); | |
} | |
} | |
void TestButtonColors(int buttonIndex) { | |
std::cout << "Testing colors for Button " << buttonIndex + 1 << "." << std::endl; | |
std::vector<BYTE> colors = { 0x00, 0x01, 0x10, 0x04, 0x05, 0x11, 0x14, 0x7F, 0x00}; | |
for (BYTE color : colors) { | |
SetButtonColor(buttonIndex, color); | |
Sleep(500); // Stay on each color for 1 second | |
} | |
// Restore to default color after the test | |
SetButtonColor(buttonIndex, buttonStates[buttonIndex]->color); | |
std::cout << "Finished testing colors for Button " << buttonIndex + 1 << "." << std::endl; | |
} | |
void HandleButtonPress(int buttonIndex, BYTE data2) { | |
switch (buttonIndex) { | |
case 0: // Button 1 | |
if (data2 == 127 && !buttonStates[buttonIndex]->isMuted) { // Mute message | |
std::cout << "Microphone " << buttonIndex + 1 << " muted. Starting blinking with color RED." << std::endl; | |
buttonStates[buttonIndex]->isMuted = true; | |
buttonStates[buttonIndex]->isBlinking = true; | |
std::thread(BlinkButton, buttonIndex, 0x01).detach(); | |
} | |
else if (data2 == 0 && buttonStates[buttonIndex]->isMuted) { // Unmute message | |
std::cout << "Microphone " << buttonIndex + 1 << " unmuted." << std::endl; | |
buttonStates[buttonIndex]->isMuted = false; | |
buttonStates[buttonIndex]->isBlinking = false; | |
SetButtonColor(buttonIndex, 0x01); // Restore original color | |
} | |
break; | |
case 1: // Button 2 | |
if (data2 == 127 && !buttonStates[buttonIndex]->isMuted) { // Mute message | |
std::cout << "Playstation " << buttonIndex + 1 << " muted. Starting blinking with color BLUE." << std::endl; | |
buttonStates[buttonIndex]->isMuted = true; | |
buttonStates[buttonIndex]->isBlinking = true; | |
std::thread(BlinkButton, buttonIndex, 0x10).detach(); | |
} | |
else if (data2 == 0 && buttonStates[buttonIndex]->isMuted) { // Unmute message | |
std::cout << "Playstation " << buttonIndex + 1 << " unmuted." << std::endl; | |
buttonStates[buttonIndex]->isMuted = false; | |
buttonStates[buttonIndex]->isBlinking = false; | |
SetButtonColor(buttonIndex, 0x10); // Restore original color | |
} | |
break; | |
case 2: // Button 3 | |
if (data2 == 127 && !buttonStates[buttonIndex]->isMuted) { // Mute message | |
std::cout << "Spotify " << buttonIndex + 1 << " muted. Starting blinking with color GREEN." << std::endl; | |
buttonStates[buttonIndex]->isMuted = true; | |
buttonStates[buttonIndex]->isBlinking = true; | |
std::thread(BlinkButton, buttonIndex, 0x04).detach(); | |
} | |
else if (data2 == 0 && buttonStates[buttonIndex]->isMuted) { // Unmute message | |
std::cout << "Spotify " << buttonIndex + 1 << " unmuted." << std::endl; | |
buttonStates[buttonIndex]->isMuted = false; | |
buttonStates[buttonIndex]->isBlinking = false; | |
SetButtonColor(buttonIndex, 0x04); // Restore original color | |
} | |
break; | |
case 3: // Button 4 | |
if (data2 == 127 && !buttonStates[buttonIndex]->isMuted) { // Mute message | |
std::cout << "Chrome " << buttonIndex + 1 << " muted. Starting blinking with color YELLOW." << std::endl; | |
buttonStates[buttonIndex]->isMuted = true; | |
buttonStates[buttonIndex]->isBlinking = true; | |
std::thread(BlinkButton, buttonIndex, 0x05).detach(); | |
} | |
else if (data2 == 0 && buttonStates[buttonIndex]->isMuted) { // Unmute message | |
std::cout << "Chrome " << buttonIndex + 1 << " unmuted." << std::endl; | |
buttonStates[buttonIndex]->isMuted = false; | |
buttonStates[buttonIndex]->isBlinking = false; | |
SetButtonColor(buttonIndex, 0x05); // Restore original color | |
} | |
break; | |
case 4: // Button 5 | |
if (data2 == 127 && !buttonStates[buttonIndex]->isMuted) { // Mute message | |
std::cout << "Console 2 " << buttonIndex + 1 << " muted. Starting blinking with color CYAN." << std::endl; | |
buttonStates[buttonIndex]->isMuted = true; | |
buttonStates[buttonIndex]->isBlinking = true; | |
std::thread(BlinkButton, buttonIndex, 0x14).detach(); | |
} | |
else if (data2 == 0 && buttonStates[buttonIndex]->isMuted) { // Unmute message | |
std::cout << "Console 2 " << buttonIndex + 1 << " unmuted." << std::endl; | |
buttonStates[buttonIndex]->isMuted = false; | |
buttonStates[buttonIndex]->isBlinking = false; | |
SetButtonColor(buttonIndex, 0x14); // Restore original color | |
} | |
break; | |
case 5: // Button 6 | |
if (data2 == 127 && !buttonStates[buttonIndex]->isMuted) { // Mute message | |
std::cout << "Default Channel " << buttonIndex + 1 << " muted. Starting blinking with color WHITE." << std::endl; | |
buttonStates[buttonIndex]->isMuted = true; | |
buttonStates[buttonIndex]->isBlinking = true; | |
std::thread(BlinkButton, buttonIndex, 0x7F).detach(); | |
} | |
else if (data2 == 0 && buttonStates[buttonIndex]->isMuted) { // Unmute message | |
std::cout << "Default Channel " << buttonIndex + 1 << " unmuted." << std::endl; | |
buttonStates[buttonIndex]->isMuted = false; | |
buttonStates[buttonIndex]->isBlinking = false; | |
SetButtonColor(buttonIndex, 0x7F); // Restore original color | |
} | |
break; | |
case 6: // Button 7 | |
if (data2 == 127 && !buttonStates[buttonIndex]->isMuted) { // Mute message | |
std::cout << "Game Channel " << buttonIndex + 1 << " muted. Starting blinking with color WHITE." << std::endl; | |
buttonStates[buttonIndex]->isMuted = true; | |
buttonStates[buttonIndex]->isBlinking = true; | |
std::thread(BlinkButton, buttonIndex, 0x7F).detach(); | |
} | |
else if (data2 == 0 && buttonStates[buttonIndex]->isMuted) { // Unmute message | |
std::cout << "Game Channel " << buttonIndex + 1 << " unmuted." << std::endl; | |
buttonStates[buttonIndex]->isMuted = false; | |
buttonStates[buttonIndex]->isBlinking = false; | |
SetButtonColor(buttonIndex, 0x7F); // Restore original color | |
} | |
break; | |
case 7: // Button 8 | |
if (data2 == 127 && !buttonStates[buttonIndex]->isMuted) { // Mute message | |
std::cout << "VOIP Channel " << buttonIndex + 1 << " muted. Starting blinking with color WHITE." << std::endl; | |
buttonStates[buttonIndex]->isMuted = true; | |
buttonStates[buttonIndex]->isBlinking = true; | |
std::thread(BlinkButton, buttonIndex, 0x7F).detach(); | |
} | |
else if (data2 == 0 && buttonStates[buttonIndex]->isMuted) { // Unmute message | |
std::cout << "Game Channel " << buttonIndex + 1 << " unmuted." << std::endl; | |
buttonStates[buttonIndex]->isMuted = false; | |
buttonStates[buttonIndex]->isBlinking = false; | |
SetButtonColor(buttonIndex, 0x7F); // Restore original color | |
} | |
break; | |
default: | |
std::cerr << "Unhandled button index: " << buttonIndex << std::endl; | |
break; | |
} | |
} | |
void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { | |
if (wMsg == MIM_DATA) { | |
BYTE status = dwParam1 & 0xFF; | |
BYTE data1 = (dwParam1 >> 8) & 0xFF; | |
BYTE data2 = (dwParam1 >> 16) & 0xFF; | |
if (status == 153 && data1 >= 48 && data1 <= 55) { // Buttons 1-8 | |
int buttonIndex = data1 - 48; | |
HandleButtonPress(buttonIndex, data2); | |
} | |
} | |
} | |
int main() { | |
bool hasLoopMIDI = false, hasMiniLab = false; | |
UINT numInputDevices = midiInGetNumDevs(); | |
for (UINT i = 0; i < numInputDevices; ++i) { | |
MIDIINCAPS caps; | |
if (midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS)) == MMSYSERR_NOERROR) { | |
if (std::wstring(caps.szPname) == REQUIRED_INPUT_DEVICE) hasLoopMIDI = true; | |
if (std::wstring(caps.szPname) == REQUIRED_OUTPUT_DEVICE) hasMiniLab = true; | |
} | |
} | |
if (!hasLoopMIDI || !hasMiniLab) { | |
std::cerr << "Required MIDI devices are missing. Exiting." << std::endl; | |
return -1; | |
} | |
std::vector<BYTE> defaultColors = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x06, 0x06 }; | |
for (int i = 0; i < 8; ++i) { | |
buttonStates[i] = std::make_unique<ButtonState>(defaultColors[i], false, false); | |
SetButtonColor(i, defaultColors[i]); | |
} | |
HMIDIIN hMidiIn = nullptr; | |
for (UINT i = 0; i < numInputDevices; ++i) { | |
MIDIINCAPS caps; | |
if (midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS)) == MMSYSERR_NOERROR) { | |
if (std::wstring(caps.szPname) == REQUIRED_INPUT_DEVICE) { | |
if (midiInOpen(&hMidiIn, i, (DWORD_PTR)MidiInProc, 0, CALLBACK_FUNCTION) == MMSYSERR_NOERROR) { | |
midiInStart(hMidiIn); | |
break; | |
} | |
} | |
} | |
} | |
UINT numOutputDevices = midiOutGetNumDevs(); | |
for (UINT i = 0; i < numOutputDevices; ++i) { | |
MIDIOUTCAPS caps; | |
if (midiOutGetDevCaps(i, &caps, sizeof(MIDIOUTCAPS)) == MMSYSERR_NOERROR) { | |
if (std::wstring(caps.szPname) == REQUIRED_OUTPUT_DEVICE) { | |
if (midiOutOpen(&hMidiOut, i, 0, 0, CALLBACK_NULL) == MMSYSERR_NOERROR) { | |
std::wcout << L"Opened MIDI output device: " << caps.szPname << std::endl; | |
} | |
break; | |
} | |
} | |
} | |
std::cout << "Press Enter to quit..." << std::endl; | |
std::cin.get(); | |
isRunning = false; | |
if (hMidiIn) { | |
midiInStop(hMidiIn); | |
midiInClose(hMidiIn); | |
} | |
if (hMidiOut) { | |
midiOutClose(hMidiOut); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment