Custom Midi Controller
June 12th 2025
The goal was to create a custom MIDI controller for Touchdesigner with a specific layout for my mixing operator workflow.
Components:
- Arduino Nano
- 74hc4051 Multiplexer
- 4x Rotary Switch
- 3x Arcade Gaming Buttons
- 4x On/Off Switch
Software Libraries:
- Control Surface
- Platform I/O
Notes
- Control surfaces examples: https://tttapa.github.io/Control-Surface/Doxygen/d4/de9/examples.html
- Switch / Push buttons needs to be connected to GND -> SWITCH -> 74HC4051
- 74HC4051 needs:
- VCC -> Vin
- GD -> GND,
- Z -> A7 or D7 (either analog for potentiometer or digital for switch)
- S0/S1/S2 connected to digital pins (e.g. 3, 4, 5)
- YX are inputs
Source code
main.cpp:
#include <Arduino_Helpers.h>
#include <AH/Hardware/ExtendedInputOutput/AnalogMultiplex.hpp>
#include <Control_Surface.h>
// Interface MIDI USB
USBMIDI_Interface midi;
CD74HC4051 mux {
6, // Digital D6 Pin
{5, 4, 3} // Address pins S0, S1, S2
};
CCButton btns[] {
{ mux.pin(0), { MIDI_CC::General_Purpose_Controller_1, Channel_2 } },
{ mux.pin(1), { MIDI_CC::General_Purpose_Controller_2, Channel_2 } },
{ mux.pin(2), { MIDI_CC::General_Purpose_Controller_3, Channel_2 } },
{ mux.pin(3), { MIDI_CC::General_Purpose_Controller_4, Channel_2 } },
{ mux.pin(4), { MIDI_CC::General_Purpose_Controller_5, Channel_2 } },
{ mux.pin(5), { MIDI_CC::General_Purpose_Controller_6, Channel_2 } },
{ mux.pin(6), { MIDI_CC::General_Purpose_Controller_7, Channel_2 } },
{ mux.pin(7), { MIDI_CC::General_Purpose_Controller_8, Channel_2 } },
};
// Déclaration des broches analogiques utilisées
const int switchPins[4] = {A0, A1, A2, A3};
// Paramètres de debounce (un par switch)
unsigned long lastChangeTime[4] = {0, 0, 0, 0};
const unsigned long debounceDelay = 150; // en ms
// Dernier index connu pour chaque switch
int lastIndex[4] = {-1, -1, -1, -1};
// Nombre de switchs
const int numSwitches = 4;
// Prototypes
int getSwitchIndex(int value);
int getSmoothedAnalog(int pin, int samples = 30);
int getSmoothedMuxAnalog(int pin, int samples = 20);
void setup() {
mux.begin();
Control_Surface.begin();
for (int i = 0; i < numSwitches; i++) {
pinMode(switchPins[i], INPUT);
}
}
int lastResultCCBtn[7] = {0, 0, 0, 0, 0, 0, 0};
void loop() {
unsigned long currentTime = millis();
for (int i = 0; i < numSwitches; i++) {
int analogValue = getSmoothedAnalog(switchPins[i]);
int index = getSwitchIndex(analogValue);
if (index != lastIndex[i] && index != -1 &&
(currentTime - lastChangeTime[i] > debounceDelay)) {
MIDIAddress address(i + 16, Channel_1); // CC16 = GP Controller 1
midi.sendControlChange(address, index);
lastIndex[i] = index;
lastChangeTime[i] = currentTime;
}
}
Control_Surface.loop();
}
int getSmoothedAnalog(int pin, int samples) {
long total = 0;
for (int i = 0; i < samples; i++) {
total += analogRead(pin);
delay(2);
}
return total / samples;
}
// Mapping des plages vers index 0-11
int getSwitchIndex(int value) {
if (value < 3120) return 0;
else if (value < 3300) return 1;
else if (value < 3400) return 2;
else if (value < 3600) return 3;
else if (value < 3660) return 4;
else if (value < 3730) return 5;
else if (value < 3780) return 6;
else if (value < 3820) return 7;
else if (value < 3860) return 8;
else if (value < 3900) return 9;
else if (value < 3933) return 10;
else return 11;
}
Platform.ini
[env:nanoatmega328]
platform = nordicnrf52
board = nano33ble
framework = arduino
monitor_speed = 57600
lib_deps = https://github.com/tttapa/Control-Surface.git#main
lib_ignore = MIDIUSB
Build / Upload commands:
[env:nanoatmega328]
platform = nordicnrf52
board = nano33ble
framework = arduino
monitor_speed = 57600
lib_deps = https://github.com/tttapa/Control-Surface.git#main
lib_ignore = MIDIUSB
Scanning value of multiplexer
#include <Arduino_Helpers.h>
#include <AH/Hardware/ExtendedInputOutput/AnalogMultiplex.hpp>
#include <Control_Surface.h>
CD74HC4051 mux {
A7,
{5, 4, 3}
};
void setup() {
Serial.begin(115200);
mux.begin();
}
void loop() {
for (pin_int_t pin = 0; pin < mux.getLength(); ++pin) {
Serial.print(mux.analogRead(pin));
Serial.print('\t');
}
delay(1000);
Serial.println();
}