Hello.
Here is a little update of my 3 potentiometers and 3 faders NRPN external midi controller for the CQ range mixer.
-it connect to the CQ via USB midi.
-it use 1 arduino pro micro (leonardo)
-3 potentiometers and 3 faders.
-wiring diagram:
-Arduino sketch:
#include <USB-MIDI.h>
#include <EEPROM.h>
USBMIDI_CREATE_DEFAULT_INSTANCE();
const byte numPots = 6;
const byte potPins[numPots] = {A0, A1, A2, A3, A7, A8};
const int EEPROM_START_ADDRESS = 0;
int currentPotValue[numPots] = {0, 0, 0, 0, 0, 0};
int lastPotValue[numPots] = {0, 0, 0, 0, 0, 0};
const int tolerance = 2;
const unsigned int DEFAULT_NRPN[numPots] = {8193, 8253, 8192, 8220, 8252, 8222};
unsigned int nrpnParameters[numPots];
const byte midiChannel = 1;
// Define the CC numbers used for configuration
const byte CC_CONFIG_MSB_START = 10; // CC 10-15 pour les MSB des 6 pots
const byte CC_CONFIG_LSB_START = 20; // CC 20-25 pour les LSB des 6 pots
void setup() {
MIDI.begin(midiChannel);
MIDI.setHandleControlChange(onControlChange);
// Serial.begin(9600);
loadNRPNConfig();
// ✨ NOUVEAU : Envoi de la configuration enregistrée au démarrage
// Le navigateur doit être prêt à recevoir cette donnée après la connexion.
sendCurrentConfigToHost();
}
void loop() {
for (byte i = 0; i < numPots; i++) {
currentPotValue[i] = analogRead(potPins[i]);
if (abs(currentPotValue[i] - lastPotValue[i]) > tolerance) {
unsigned int mappedValue = map(currentPotValue[i], 0, 1023, 0, 16383);
sendNRPNValue(midiChannel, nrpnParameters[i], mappedValue);
lastPotValue[i] = currentPotValue[i];
}
}
delay(10);
MIDI.read();
}
// Fonction de rappel appelée lorsque l'Arduino reçoit un message Control Change
void onControlChange(byte channel, byte control, byte value) {
if (channel == midiChannel) {
// Vérifie si c'est un message de configuration MSB (CC 10-15)
if (control >= CC_CONFIG_MSB_START && control < (CC_CONFIG_MSB_START + numPots)) {
byte potIndex = control - CC_CONFIG_MSB_START;
nrpnParameters[potIndex] = (nrpnParameters[potIndex] & 0x007F) | ((unsigned int)value << 7);
}
// Vérifie si c'est un message de configuration LSB (CC 20-25)
else if (control >= CC_CONFIG_LSB_START && control < (CC_CONFIG_LSB_START + numPots)) {
byte potIndex = control - CC_CONFIG_LSB_START;
nrpnParameters[potIndex] = (nrpnParameters[potIndex] & 0xFF80) | value;
saveNRPNConfig(potIndex, nrpnParameters[potIndex]);
}
}
}
// Fonction utilitaire pour envoyer un message NRPN complet
void sendNRPNValue(byte channel, unsigned int parameterNumber, unsigned int value) {
MIDI.beginNrpn(parameterNumber, channel);
MIDI.sendNrpnValue(value, channel);
}
// --- Fonctions EEPROM (inchangées) ---
void loadNRPNConfig() {
unsigned int checkValue = 0xFFFF;
EEPROM.get(EEPROM_START_ADDRESS, checkValue);
if (checkValue == 0xFFFF) {
for (byte i = 0; i < numPots; i++) {
nrpnParameters[i] = DEFAULT_NRPN[i];
saveNRPNConfig(i, nrpnParameters[i]);
}
// Serial.println("EEPROM non initialisée. Chargement et écriture des valeurs par défaut.");
} else {
for (byte i = 0; i < numPots; i++) {
int address = EEPROM_START_ADDRESS + (i * sizeof(unsigned int));
EEPROM.get(address, nrpnParameters[i]);
}
// Serial.println("EEPROM chargée avec les valeurs enregistrées.");
}
}
void saveNRPNConfig(byte potIndex, unsigned int nrpnValue) {
int address = EEPROM_START_ADDRESS + (potIndex * sizeof(unsigned int));
EEPROM.put(address, nrpnValue);
}
// ✨ NOUVELLE FONCTION : Envoie la config NRPN actuelle au logiciel hôte (le navigateur)
void sendCurrentConfigToHost() {
// L'Arduino va s'envoyer ses propres valeurs enregistrées au navigateur.
for (byte i = 0; i < numPots; i++) {
unsigned int nrpnValue = nrpnParameters[i];
// Split 14-bit NRPN into MSB and LSB
const byte msb_param = (nrpnValue >> 7) & 0x7F;
const byte lsb_param = nrpnValue & 0x7F;
// Use the custom CCs for configuration (CC 10-15 and CC 20-25)
const byte cc_msb_number = CC_CONFIG_MSB_START + i;
const byte cc_lsb_number = CC_CONFIG_LSB_START + i;
// Envoi du MSB
MIDI.sendControlChange(cc_msb_number, msb_param, midiChannel);
// Envoi du LSB (déclenchera l'affichage sur l'interface après réception complète)
MIDI.sendControlChange(cc_lsb_number, lsb_param, midiChannel);
}
}
-build your own enclosure, I do not provide 3d files directly here but on demand via MP (for free) for this version only:
-once the Arduino sketch is upload, you can program your midi device directly online at this address:
-https://cq-faders.netlify.app/
If the CQ is connected to the same PC while programming, there is a “learn function” to auto_copy the NRPN value of the CQ fader your move.
Otherwise, choose it via the drop/down menu.
With Mac route midi with midipipe, and with IOS route midi with midiMittr.
Enjoy and report/feedback if you like it.


