You want to build your own Arduino MIDI controller — with buttons, potentiometers, and LEDs — and you want a straightforward, step-by-step guide that actually works. This tutorial walks you through every connection, every line of code, and every design decision so you can create a custom MIDI controller that sends real MIDI messages to your DAW, synthesizer, or VST plugin.
Why Build an Arduino MIDI Controller?
Commercial MIDI controllers cost anywhere from fifty to several hundred dollars, and they rarely have exactly the layout you want. An Arduino MIDI controller puts you in control: you decide how many buttons, knobs, and LEDs you need, what they do, and how they look. It’s a perfect weekend project that teaches you digital input handling, analog-to-digital conversion, and the MIDI protocol — all while producing something you can actually use.
This project uses an Arduino Leonardo (or a clone like the Pro Micro) because it supports native USB MIDI — no additional serial-to-MIDI converter required. You’ll wire up four buttons, four potentiometers, and four LEDs. Once you understand the pattern, scaling up to 8, 16, or more controls is trivial.
Parts List: What You Need
Before wiring anything, gather these components:
| Component | Quantity | Notes |
|---|---|---|
| Arduino Leonardo (or Pro Micro) | 1 | Must be ATmega32u4 for native USB MIDI |
| 10kΩ potentiometers | 4 | Any linear taper potentiometer works |
| Tactile push buttons | 4 | Momentary, normally open |
| LEDs (any color) | 4 | Standard 5mm or 3mm |
| 220Ω resistors | 4 | For LED current limiting |
| 10kΩ resistors | 4 | Pull-down resistors for buttons |
| Breadboard | 1 | Half-size is enough |
| Jumper wires | ~20 | M-M and M-F for breadboard |
| USB cable | 1 | Micro USB or USB-C depending on board |
Total cost: roughly $15–20 if you already have an Arduino. If you’re starting from scratch, grab a Leonardo clone for under $10.
Wiring the Arduino MIDI Controller
The wiring splits into three sections: buttons, potentiometers, and LEDs. Wire them one group at a time and test each group before moving on.
Button Wiring
Each button needs a pull-down resistor to keep the input LOW when the button isn’t pressed. Connect as follows:
- Button pin 1 → 5V (via a small jumper)
- Button pin 2 → Arduino digital pin + one leg of a 10kΩ resistor
- Other leg of 10kΩ resistor → GND
Do this for all four buttons using digital pins 2, 3, 4, and 5.
Potentiometer Wiring
Potentiometers have three terminals. The outer two connect to 5V and GND; the middle wiper connects to an analog input.
- Left terminal → GND
- Center terminal → Arduino analog pin (A0, A1, A2, A3)
- Right terminal → 5V
Double-check your wiring: swapping the outer terminals just reverses the direction of the knob, which is easy to fix in software.
LED Wiring
LEDs connect to digital PWM-capable pins through current-limiting resistors. Any pin on the Leonardo can do digital output, but PWM pins (3, 5, 6, 9, 10, 11) let you control brightness if you want visual feedback.
- LED anode (long leg) → 220Ω resistor → Arduino pin (6, 9, 10, 11)
- LED cathode (short leg) → GND
Here’s the full pin mapping:
| Function | Arduino Pin |
|---|---|
| Button 1 | D2 |
| Button 2 | D3 |
| Button 3 | D4 |
| Button 4 | D5 |
| Potentiometer 1 | A0 |
| Potentiometer 2 | A1 |
| Potentiometer 3 | A2 |
| Potentiometer 4 | A3 |
| LED 1 | D6 |
| LED 2 | D9 |
| LED 3 | D10 |
| LED 4 | D11 |
Programming the Arduino for USB MIDI
The Leonardo’s ATmega32u4 can act as a USB MIDI device without any extra hardware. The Arduino IDE includes the MIDIUSB library, which handles all the protocol details.
Install the Library
- Open Arduino IDE
- Go to Sketch → Include Library → Manage Libraries
- Search for
MIDIUSB - Install the official library by Arduino
The Code
Copy this sketch. It reads all four buttons and potentiometers every 10 milliseconds and sends MIDI Control Change (CC) messages. Each control maps to a specific CC number — you can change these to match your DAW or synthesizer.
#include <MIDIUSB.h>
// Pin definitions
const int buttonPins[] = {2, 3, 4, 5};
const int potPins[] = {A0, A1, A2, A3};
const int ledPins[] = {6, 9, 10, 11};
// MIDI CC numbers for each control
const int buttonCC[] = {1, 2, 3, 4};
const int potCC[] = {5, 6, 7, 8};
// State tracking
bool lastButtonState[4] = {HIGH, HIGH, HIGH, HIGH};
int lastPotValue[4] = {0, 0, 0, 0};
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 10;
void setup() {
for (int i = 0; i < 4; i++) {
pinMode(buttonPins[i], INPUT);
pinMode(ledPins[i], OUTPUT);
digitalWrite(ledPins[i], LOW);
}
// Initialize serial for debugging
Serial.begin(9600);
}
void loop() {
unsigned long currentMillis = millis();
// Read buttons with debouncing
if (currentMillis - lastDebounceTime >= debounceDelay) {
for (int i = 0; i < 4; i++) {
bool reading = digitalRead(buttonPins[i]);
if (reading != lastButtonState[i]) {
lastDebounceTime = currentMillis;
lastButtonState[i] = reading;
if (reading == HIGH) {
// Button pressed: send CC 127
controlChange(0, buttonCC[i], 127);
MidiUSB.flush();
digitalWrite(ledPins[i], HIGH);
} else {
// Button released: send CC 0
controlChange(0, buttonCC[i], 0);
MidiUSB.flush();
digitalWrite(ledPins[i], LOW);
}
}
}
}
// Read potentiometers
for (int i = 0; i < 4; i++) {
int raw = analogRead(potPins[i]);
int scaled = map(raw, 0, 1023, 0, 127);
// Only send if value changed by more than 1 (avoid flooding)
if (abs(scaled - lastPotValue[i]) > 1) {
lastPotValue[i] = scaled;
controlChange(0, potCC[i], scaled);
MidiUSB.flush();
}
}
delay(5); // Small delay to avoid overwhelming the USB bus
}
void controlChange(byte channel, byte control, byte value) {
midiEventPacket_t event = {0x0B, 0xB0 | channel, control, value};
MidiUSB.sendMIDI(event);
}
How the Code Works
- Debouncing: The
lastDebounceTimecheck prevents false triggers from mechanical button bounce. - Potentiometer mapping:
analogRead()returns 0–1023, but MIDI CC values are 0–127. Themap()function scales it perfectly. - Change detection: The potentiometer code only sends a new MIDI message when the value changes by more than 1. This keeps your USB traffic minimal and prevents your DAW from slowing down.
- LED feedback: LEDs mirror the button state — on when pressed, off when released.
Testing Your Arduino MIDI Controller
Before connecting to your DAW, test with a MIDI monitor tool:
- Windows: Use MIDI-OX
- macOS: Use the built-in Audio MIDI Setup or download MIDI Monitor
- Linux:
aseqdump -lfollowed byaseqdump -p <port>
When you press a button or turn a knob, you should see MIDI CC messages appearing. Each movement triggers a message like:
TIMESTAMP IN PORT STATUS DATA1 DATA2
00714466 1 B 1 5 64
The B status byte means Control Change. The first data byte is the CC number, the second is the value (0–127).
Integrating with a DAW or Synth
Your Arduino MIDI controller appears as a standard MIDI device. Here’s how to use it in popular DAWs:
Ableton Live
- Go to Preferences → Link, Tempo & MIDI
- Under Control Surface, choose “None” and select your Arduino as the Input
- Map controls by right-clicking a parameter and selecting “MIDI Learn”
FL Studio
- Open Options → MIDI Settings
- Find your Arduino in the Input list and enable it
- Right-click any knob or fader, select “Link to controller”, then move your physical control
Reaper
- Open Preferences → MIDI Devices
- Enable your Arduino as a control input
- Use the “Learn” button in Reaper’s parameter modulation window
Expanding Your Design
Once the basic Arduino MIDI controller works, you can easily scale it up:
Add More Controls
- Use analog multiplexers (CD4051) to read 8+ potentiometers with only 3 analog pins
- Matrix your buttons to handle 16 or more with just 8 digital I/O pins
Add Visual Feedback
- Use RGB LEDs (WS2812B) for color-coded feedback — red for mute, green for active
- Add a small OLED display to show current CC values. See our guide on OLED Display Arduino for wiring details.
Add Velocity Sensitivity
- Replace buttons with force-sensitive resistors (FSR) for velocity-sensitive pads
- Read the analog value from the FSR and map it directly to MIDI note velocity
Wireless MIDI
- Add a Bluetooth Module Arduino for wireless control
- Use an ESP8266 for WiFi-based MIDI over UDP (OSC protocol)
Troubleshooting Common Issues
No MIDI device detected:
- Make sure you’re using a Leonardo or Pro Micro (Uno and Mega don’t support native USB MIDI)
- Press the reset button on your Arduino while it’s connected — it should re-enumerate
Buttons trigger multiple times:
- Your debounce delay might be too short. Increase it to 20–30ms
- Check pull-down resistor connections — floating inputs can cause ghost presses
Potentiometers jump erratically:
- Noisy potentiometers need a small capacitor (100nF) between the wiper and GND
- Add a
constrain(raw, 0, 1023)before mapping to filter out extreme readings
MIDI messages flood the DAW:
- Increase the dead zone threshold in the potentiometer reading code (change
> 1to> 3) - Add a longer
delay()— 10ms works well for most applications
Going Further: Real Projects Using This Controller
Professional musicians and hobbyists alike use custom Arduino MIDI controllers for everything from VST automation to modular synth control. You can adapt this project for:
- Lighting control: Map the CCs to DMX lights via a bridge
- DJ controllers: Use buttons for cue points and pots for filter sweeps
- Game controllers: Map to MIDI notes in games like Guitar Hero or Synthesia
- Accessibility tools: Create custom input devices for people with limited mobility
For more inspiration, check out our 555 Timer IC: Build a Simple LED Flasher project — it uses similar wiring principles for timing applications.
Conclusion
Building an Arduino MIDI controller from scratch is one of the most rewarding electronics projects you can tackle. You learn about debouncing, analog reading, the MIDI protocol, and USB HID emulation — all while creating a genuinely useful device. Start with four buttons and four pots, get it working with your DAW, then expand from there. The code and wiring in this guide give you a solid foundation; what you build on top is up to you.