14 KiB
Loopy MIDI Controller - USB MIDI Foot Controller
A compact ESP32-S3 based USB MIDI foot controller with hardware acceleration, designed to work seamlessly with Loopy Pro.
Overview
This project implements a USB MIDI device that accepts foot pedal inputs and converts them to MIDI Continuous Controller (CC) messages. It's designed to be a reliable replacement for the MIDI-to-USB adapters used with Loopy Pro, offering low latency (1-5ms) and USB compatibility without Bluetooth interference.
Key Features:
- USB MIDI Interface: Uses ESP32-S3's native USB MIDI support for stable, reliable connectivity
- Expression Pedal Support: Read analog input with configurable calibration
- Launchpad X Compatible: Per-pad LED colors matching Novation Launchpad hardware
- Low Latency Design: 1-5ms response time for responsive control
- MIDI Clock Sync: Pixel 6 pulses in time with Loopy Pro tempo
- Hardware Acceleration: Uses multiple cores efficiently without cross-core LED issues
Hardware Requirements
PCB/Layout Notes
- ESP32-S3-WROOM-1 microcontroller
- Daisy-chained 74HC165 shift registers for button inputs (16 buttons total)
- 2x RMT WS2812C LED drivers for 10 programmable RGB pixels
- ADC on GPIO4 for expression pedal input
- WS2812 external LED for boot animation (GPIO12)
Required Components
- ESP32-S3 development board (ESP32-S3-WROOM-1 preferred)
- 74HC165 shift register (2x for 16 buttons)
- WS2812C LED strip (10 pixels)
- PWM/ DAC capable for expression pedal
- Level shifters if needed for input voltages
Software Architecture
Core Design
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ USB MIDI │ │ MIDI Task │ │ LED Driver │
│ (Core 1) │ │ (Core 0) │ │ (Core 0) │
│ - Loopy Pro │◄──►│ - Clock Parity │◄──►│ - Pixel Pulse │
│ recognition │ │ Detection │ │ on beats │
│ - Note/CC │ │ - MIDI update │◄──►│ - Button LED │
│ processing │ │ processing │ │ feedback │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ Expression │
│ Pedal (Core 0)│
│ - ADC Read │
│ - CC 4 output │
└─────────────────┘
Key Subsystems
1. USB MIDI Transport (Core 1)
- Adafruit TinyUSB library integration
- Proper VID/PID spoofing (Novation Launchpad X: 0x1235/0x0103)
- MIDI event parsing and routing to Core 0
- Re-enumeration support for stable device connection
2. MIDI Task (Core 0)
- Core 0 exclusive for all FastLED operations (prevents crashes)
- MIDI clock (0xF8) detection and counting
- Beat detection using tick count / 24 (24 PPQN)
- Pixel 6 pulsing on every MIDI beat
- Button-to-CC message conversion
3. LED Driver (Core 0)
- Per-pad color configuration (Launchpad X compatibility)
- Velocity-based color mapping
- Dim off-state for power efficiency
- Launchpad-style startup animation
4. Expression Pedal (Core 0)
- ADC input on GPIO4 (calibrated min/max)
- CC 4 output on MIDI channel 1
- Hysteresis filtering for stable readings
- 5ms update interval for responsive control
5. Switch Driver (Core 0)
- 16 buttons via daisy-chained 74HC165
- 5ms debounce for reliable input
- Direct button-to-CC mapping
- Per-pad LED feedback
Configuration Options
MIDI Mapping
The controller uses a Launchpad X-style mapping for intuitive control:
| Pad | Hardware Button | MIDI Channel | Note/Pitch | CC | Default Color |
|---|---|---|---|---|---|
| 0-1 | 0-1 | 1 | 36-45 | - | Blue (0x0000FF) |
| 2-4 | 2-4 | 1 | 36-45 | - | Orange (0xFF6600) |
| 5 | 5 | 1 | 36 | - | Red (0xFF0000) |
| 6 | 6 | 1 | 36 | - | White (0xFFFFFF) |
| 7 | 7 | 1 | 36 | - | Amber (0xFFBF00) |
| 8-9 | 8-9 | 1 | 36-37 | - | Purple (0x9900FF) |
For Loopy Pro Users:
- Buttons generate CC messages (not Note On/Off)
- CC values: 2-11 for buttons 0-9
- CC 4 for expression pedal
- Pixel colors provide visual feedback
MIDI Clock Synchronization
Pixel 6 provides visual feedback synchronized to Loopy Pro's tempo:
- Configure Loopy Pro: Enable MIDI Clock output targeting JOC Midi
- Device Recognition: Loopy Pro detects the VID/PID (0x1235/0x0103)
- Visual Feedback: Pixel 6 pulses white on each MIDI Clock beat (0xF8)
- Pulse Animation: Quadratic fade (80ms duration) between beats
Troubleshooting: If pixel 6 doesn't pulse:
- Verify Loopy Pro has MIDI Clock enabled
- Ensure JOC Midi is the clock target
- Check MIDI device permissions
- Verify MIDI input in Loopy Pro shows "JOC Midi"
PlatformIO Configuration
Build Options (platformio.ini)
[env]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
build_type = release
monitor_speed = 115200
upload_speed = 921600
build_flags =
-DCORE_DEBUG_LEVEL=0
-DARDUINO_USB_MODE=0
-DARDUINO_USB_LAUNCHER_MODE=0
lib_deps =
adafruit/[email protected]
https://github.com/FastLED/FastLED/archive/refs/tags/3.6.0.zip
extra_scripts =
pre_build.py
extra_script.py
Build Flags Explanation
-DARDUINO_USB_MODE=0: USB in CDC/MIDI mode (no virtual serial)-DARDUINO_USB_LAUNCHER_MODE=0: No USB launcher mode-DCORE_DEBUG_LEVEL=0: Disable debug output--allow-multiple-definition: Required for Adafruit TinyUSB compatibility
Build Scripts
pre_build.py: Patches board definitions with Launchpad X VID/PID
extra_script.py: Ensures proper TinyUSB linking order
Usage
Initial Setup
- Flash Firmware
platformio run --target upload
-
Open Serial Monitor (115200 baud)
- Shows startup sequence
- MIDI activity diagnostics
- System status
-
Available Commands (type in serial)
help- Show all commandsdump- Display button statesprobe- Hardware diagnosticsledon/ledoff- Turn all LEDs on/offledtest- Color cycle testexp- Show expression pedal ADC/valueusb- USB statusgpiotest- Raw GPIO diagnosticsmiditest- Simulate MIDI inputpadtest- Test individual padsmapping- Show current pad mapping
MIDI Configuration in Loopy Pro
-
System Settings
- Name: "JOC Midi"
- Manufacturer: "JOC"
- Model: "JOC Midi"
-
MIDI Setup
- Port 1: Enabled
- Input Channel: All
- Output Channel: 1 (or preferred)
-
Sync Configuration
- Sync Master: LOOPY (if Loopy Pro is master)
- Clock Output: Enabled
- Clock Targets: JOC Midi
Operation
-
Button Presses
- Press any button to send corresponding CC message
- Pixel color changes to match button state
-
Expression Pedal
- Connect foot pedal to ADC input
- Calibrate min (heel) and max (toe) positions
- Watch pixel 5 for pedal value feedback
-
Visual Feedback
- Buttons: Color indicates CC state
- Expression pedal: Pixel 5 brightness reflects CC value
- Sync: Pixel 6 pulses with MIDI Clock
Customization
Adding New MIDI Functions
- Add New CC Mappings
// In switch_stub.h or app_task.cpp
static const uint8_t BUTTONS_TO_CC[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
- Custom LED Patterns
// In led_stub.cpp
void apply_custom_color(uint8_t index, uint8_t velocity) {
if (index == 6) { // Example: Custom color for expression pedal
mux_ptr->set_led_color(index, 0, 255, 0); // Green
return;
}
apply_pad_color(index, velocity);
}
Changing MIDI Clock Behavior
- Modify Beat Detection
// In main.cpp, midi_task
uint32_t beat_interval = 24; // PPQN for MIDI Clock
if (tick / beat_interval != last_beat) {
last_beat = tick / beat_interval;
// Trigger pulse
}
- Change Pulse Animation
// In main.cpp, midi_task
float pulse_speed = 1.0f / 1.0; // Adjust pulse speed
mux.set_led_color(6, v * pulse_speed, v * pulse_speed, v * pulse_speed);
Modifying Expression Pedal
- Change ADC Pin
// In expression_pedal.h/expression_pedal.cpp
static const uint8_t EXP_PEDAL_PIN = 4; // or another GPIO
- Adjust Calibration
// In main.cpp
default exp_pedal.cal_min(0); // Set based on testing
default exp_pedal.cal_max(1023); // Set based on testing
- Change MIDI CC
// In expression_pedal.cpp
send_cc(1, 4, value); // Keep existing
// Or change:
send_cc(1, 7, value); // Volume instead of expression
Custom Pad Layout
- Map Pads Differently
// In app_task.h/app_task.cpp
#define PAD_NOTE_MAPPING {36, 37, 38, 39, 40, 41, 42, 43, 44, 45}
#define PAD_CC_MAPPING {2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
- Add Special Functions
// In app_task.cpp
void process_special_button(uint8_t led_index) {
if (led_index == 5) { // Expression pedal button
// Toggle pedal mode
}
if (led_index == 6) { // MIDI Clock toggle
// Toggle clock visualization
}
}
Testing and Diagnostics
Serial Commands for Debugging
| Command | Description |
|---|---|
probe |
Test all buttons and LEDs |
gpiotest |
Check GPIO pin states |
ledtest |
Cycle through all LED colors |
miditest |
Simulate MIDI input for testing |
dump |
Show current button states |
Troubleshooting Common Issues
Issue: Pixel 6 doesn't pulse
Cause: MIDI Clock not arriving Solution:
- Check Loopy Pro sync settings
- Verify MIDI Clock target is JOC Midi
- Examine
[CLK] !! NO MIDI CLOCK RECEIVED !!in serial
Issue: Buttons not sending MIDI
Cause: Shift register issues Solution:
- Run
probecommand - Check wiring and pin connections
- Verify shift register operation
Issue: Expression pedal unresponsive
Cause: ADC calibration incorrect Solution:
- Use
expcommand to see raw ADC values - Adjust calibration min/max values
- Check voltage range and connection
Issue: USB connection drops
Cause: Re-enumeration issues Solution:
- Check mounted() status in
[MIDI] USB mounted:messages - Ensure proper VID/PID values in build
- Verify TinyUSB initialization
Build Instructions
Prerequisites
- PlatformIO IDE or
- Arduino CLI with ESP32 core support
Quick Build
# Using PlatformIO
platformio run
# Using Arduino CLI
cd your-project
arduino-cli compile --builder chitrak/micropython-builder --fqbn esp32-s3-devkitc-1 .
Upload
# Using PlatformIO
platformio upload
# Using Arduino CLI
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32-s3-devkitc-1 .
Troubleshooting Build Issues
Common Build Errors
-
Adafruit TinyUSB conflicts
- Solution: Use the provided
extra_script.pywith--allow-multiple-definition - Ensure build_flags match exactly
- Solution: Use the provided
-
Memory Issues
- Solution: Reduce debug output, use release build type
- Check stack sizes in platformio.ini
-
Pin Conflicts
- Solution: Adjust GPIO pins in configuration files
- Verify all components use different pins
Hardware Issues
-
LED Driver Problems
- Solution: Test with simple color output commands
- Check wiring and power supply
-
Button Issues
- Solution: Use
probecommand regularly - Ensure proper pull-up/pull-down configurations
- Solution: Use
Future Enhancements
Planned Features
-
USB MIDI Sysex Support
- Launchpad X programmer mode
- Bank select and patch change
-
Advanced Expression Pedal
- Rotary encoding support
- Multiple pedal modes (CC1/CC4)
-
Enhanced LED Effects
- Breathing animations
- SOS patterns for diagnostics
- Battery level indicator
-
Additional MIDI Functions
- Pitch Bend support
- Aftertouch
- Poly Pressure
Custom Configuration Examples
For Loopy Pro DJs
// Loopy-specific CC mappings
static const uint8_t LOOPY_CC_MAP[10] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
For External Controllers
// CC-based interaction
static const uint8_t EXTERNAL_CC_TARGET = 1;
send_cc(EXTERNAL_CC_TARGET, 7, value); // Volume
For Recording Studios
// Note-based for drum triggers
static const uint8_t DRUM_NOTES[10] = {36, 38, 40, 42, 44, 45, 47, 48, 50, 52};
License
This project is provided as-is with no explicit license. The code is intended for educational and personal use. Modifications and distributions should respect original authors' intentions where specified.
Acknowledgments
- Novation Launchpad: Inspiration for layout and color scheme
- ESP32-S3: Powerful microcontroller with native USB MIDI
- Adafruit TinyUSB: Reliable USB MIDI stack
- FastLED: Efficient LED control library
- Contributors: All who tested and provided feedback
Contact
For issues or questions:
- Check the project documentation
- Review serial output for diagnostic messages
- Use available testing commands
- Submit issues with complete build logs if encountering problems