Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88fee54f96 | ||
|
|
868c5eda82 | ||
|
|
d7f9bb16fd | ||
|
|
b3f4eb1a8b | ||
|
|
3226986709 | ||
|
|
e1507d5d7c | ||
|
|
3c5e0b07ae | ||
|
|
9d1a2fdc87 | ||
|
|
7c93839f94 | ||
|
|
907279e1ea | ||
|
|
df1f678845 | ||
|
|
b859527169 | ||
|
|
ad977d3b09 | ||
|
|
acd3cc51fe | ||
|
|
eb5d66d29b | ||
|
|
354f18efe7 | ||
|
|
eda2a959dc | ||
|
|
53752365b8 | ||
|
|
ea45c927e7 | ||
|
|
72496d2daf | ||
|
|
1a70cbe870 | ||
|
|
ec0cdc9438 | ||
|
|
9d2872a513 | ||
|
|
c83a500b3c | ||
|
|
a483eebd84 | ||
|
|
06479f0ca2 | ||
|
|
66058a6722 | ||
|
|
4ddb9a00bf | ||
|
|
c44bfb033b | ||
|
|
0d22769cf6 | ||
|
|
9197294365 | ||
|
|
2cf49b8deb | ||
|
|
f6c0a01a14 | ||
|
|
f0b6df1477 | ||
|
|
8bde8efaff | ||
|
|
08d6fc8701 |
@@ -0,0 +1,467 @@
|
|||||||
|
# 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:
|
||||||
|
|
||||||
|
1. **Configure Loopy Pro**: Enable MIDI Clock output targeting JOC Midi
|
||||||
|
2. **Device Recognition**: Loopy Pro detects the VID/PID (0x1235/0x0103)
|
||||||
|
3. **Visual Feedback**: Pixel 6 pulses white on each MIDI Clock beat (0xF8)
|
||||||
|
4. **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`)
|
||||||
|
|
||||||
|
```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
|
||||||
|
|
||||||
|
1. **Flash Firmware**
|
||||||
|
```bash
|
||||||
|
platformio run --target upload
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Open Serial Monitor** (115200 baud)
|
||||||
|
- Shows startup sequence
|
||||||
|
- MIDI activity diagnostics
|
||||||
|
- System status
|
||||||
|
|
||||||
|
3. **Available Commands** (type in serial)
|
||||||
|
- `help` - Show all commands
|
||||||
|
- `dump` - Display button states
|
||||||
|
- `probe` - Hardware diagnostics
|
||||||
|
- `ledon`/`ledoff` - Turn all LEDs on/off
|
||||||
|
- `ledtest` - Color cycle test
|
||||||
|
- `exp` - Show expression pedal ADC/value
|
||||||
|
- `usb` - USB status
|
||||||
|
- `gpiotest` - Raw GPIO diagnostics
|
||||||
|
- `miditest` - Simulate MIDI input
|
||||||
|
- `padtest` - Test individual pads
|
||||||
|
- `mapping` - Show current pad mapping
|
||||||
|
|
||||||
|
### MIDI Configuration in Loopy Pro
|
||||||
|
|
||||||
|
1. **System Settings**
|
||||||
|
- Name: "JOC Midi"
|
||||||
|
- Manufacturer: "JOC"
|
||||||
|
- Model: "JOC Midi"
|
||||||
|
|
||||||
|
2. **MIDI Setup**
|
||||||
|
- Port 1: Enabled
|
||||||
|
- Input Channel: All
|
||||||
|
- Output Channel: 1 (or preferred)
|
||||||
|
|
||||||
|
3. **Sync Configuration**
|
||||||
|
- Sync Master: LOOPY (if Loopy Pro is master)
|
||||||
|
- Clock Output: Enabled
|
||||||
|
- Clock Targets: JOC Midi
|
||||||
|
|
||||||
|
### Operation
|
||||||
|
|
||||||
|
1. **Button Presses**
|
||||||
|
- Press any button to send corresponding CC message
|
||||||
|
- Pixel color changes to match button state
|
||||||
|
|
||||||
|
2. **Expression Pedal**
|
||||||
|
- Connect foot pedal to ADC input
|
||||||
|
- Calibrate min (heel) and max (toe) positions
|
||||||
|
- Watch pixel 5 for pedal value feedback
|
||||||
|
|
||||||
|
3. **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
|
||||||
|
|
||||||
|
1. **Add New CC Mappings**
|
||||||
|
```cpp
|
||||||
|
// 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};
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Custom LED Patterns**
|
||||||
|
```cpp
|
||||||
|
// 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
|
||||||
|
|
||||||
|
1. **Modify Beat Detection**
|
||||||
|
```cpp
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Change Pulse Animation**
|
||||||
|
```cpp
|
||||||
|
// 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
|
||||||
|
|
||||||
|
1. **Change ADC Pin**
|
||||||
|
```cpp
|
||||||
|
// In expression_pedal.h/expression_pedal.cpp
|
||||||
|
static const uint8_t EXP_PEDAL_PIN = 4; // or another GPIO
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Adjust Calibration**
|
||||||
|
```cpp
|
||||||
|
// In main.cpp
|
||||||
|
default exp_pedal.cal_min(0); // Set based on testing
|
||||||
|
default exp_pedal.cal_max(1023); // Set based on testing
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Change MIDI CC**
|
||||||
|
```cpp
|
||||||
|
// 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
|
||||||
|
|
||||||
|
1. **Map Pads Differently**
|
||||||
|
```cpp
|
||||||
|
// 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}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Add Special Functions**
|
||||||
|
```cpp
|
||||||
|
// 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**:
|
||||||
|
1. Check Loopy Pro sync settings
|
||||||
|
2. Verify MIDI Clock target is JOC Midi
|
||||||
|
3. Examine `[CLK] !! NO MIDI CLOCK RECEIVED !!` in serial
|
||||||
|
|
||||||
|
#### Issue: Buttons not sending MIDI
|
||||||
|
**Cause**: Shift register issues
|
||||||
|
**Solution**:
|
||||||
|
1. Run `probe` command
|
||||||
|
2. Check wiring and pin connections
|
||||||
|
3. Verify shift register operation
|
||||||
|
|
||||||
|
#### Issue: Expression pedal unresponsive
|
||||||
|
**Cause**: ADC calibration incorrect
|
||||||
|
**Solution**:
|
||||||
|
1. Use `exp` command to see raw ADC values
|
||||||
|
2. Adjust calibration min/max values
|
||||||
|
3. Check voltage range and connection
|
||||||
|
|
||||||
|
#### Issue: USB connection drops
|
||||||
|
**Cause**: Re-enumeration issues
|
||||||
|
**Solution**:
|
||||||
|
1. Check mounted() status in `[MIDI] USB mounted:` messages
|
||||||
|
2. Ensure proper VID/PID values in build
|
||||||
|
3. Verify TinyUSB initialization
|
||||||
|
|
||||||
|
## Build Instructions
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
1. **PlatformIO IDE** or
|
||||||
|
2. **Arduino CLI** with ESP32 core support
|
||||||
|
|
||||||
|
### Quick Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using PlatformIO
|
||||||
|
platformio run
|
||||||
|
|
||||||
|
# Using Arduino CLI
|
||||||
|
cd your-project
|
||||||
|
arduino-cli compile --builder chitrak/micropython-builder --fqbn esp32-s3-devkitc-1 .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Upload
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using PlatformIO
|
||||||
|
platformio upload
|
||||||
|
|
||||||
|
# Using Arduino CLI
|
||||||
|
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32-s3-devkitc-1 .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting Build Issues
|
||||||
|
|
||||||
|
#### Common Build Errors
|
||||||
|
|
||||||
|
1. **Adafruit TinyUSB conflicts**
|
||||||
|
- Solution: Use the provided `extra_script.py` with `--allow-multiple-definition`
|
||||||
|
- Ensure build_flags match exactly
|
||||||
|
|
||||||
|
2. **Memory Issues**
|
||||||
|
- Solution: Reduce debug output, use release build type
|
||||||
|
- Check stack sizes in platformio.ini
|
||||||
|
|
||||||
|
3. **Pin Conflicts**
|
||||||
|
- Solution: Adjust GPIO pins in configuration files
|
||||||
|
- Verify all components use different pins
|
||||||
|
|
||||||
|
#### Hardware Issues
|
||||||
|
|
||||||
|
1. **LED Driver Problems**
|
||||||
|
- Solution: Test with simple color output commands
|
||||||
|
- Check wiring and power supply
|
||||||
|
|
||||||
|
2. **Button Issues**
|
||||||
|
- Solution: Use `probe` command regularly
|
||||||
|
- Ensure proper pull-up/pull-down configurations
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
### Planned Features
|
||||||
|
|
||||||
|
1. **USB MIDI Sysex Support**
|
||||||
|
- Launchpad X programmer mode
|
||||||
|
- Bank select and patch change
|
||||||
|
|
||||||
|
2. **Advanced Expression Pedal**
|
||||||
|
- Rotary encoding support
|
||||||
|
- Multiple pedal modes (CC1/CC4)
|
||||||
|
|
||||||
|
3. **Enhanced LED Effects**
|
||||||
|
- Breathing animations
|
||||||
|
- SOS patterns for diagnostics
|
||||||
|
- Battery level indicator
|
||||||
|
|
||||||
|
4. **Additional MIDI Functions**
|
||||||
|
- Pitch Bend support
|
||||||
|
- Aftertouch
|
||||||
|
- Poly Pressure
|
||||||
|
|
||||||
|
### Custom Configuration Examples
|
||||||
|
|
||||||
|
#### For Loopy Pro DJs
|
||||||
|
```cpp
|
||||||
|
// 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
|
||||||
|
```cpp
|
||||||
|
// CC-based interaction
|
||||||
|
static const uint8_t EXTERNAL_CC_TARGET = 1;
|
||||||
|
send_cc(EXTERNAL_CC_TARGET, 7, value); // Volume
|
||||||
|
```
|
||||||
|
|
||||||
|
#### For Recording Studios
|
||||||
|
```cpp
|
||||||
|
// 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:
|
||||||
|
1. Check the project documentation
|
||||||
|
2. Review serial output for diagnostic messages
|
||||||
|
3. Use available testing commands
|
||||||
|
4. Submit issues with complete build logs if encountering problems
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class UsbMidiTransport;
|
||||||
|
|
||||||
|
class ExpressionPedal {
|
||||||
|
public:
|
||||||
|
ExpressionPedal(uint8_t adc_pin = 5);
|
||||||
|
|
||||||
|
void begin();
|
||||||
|
void update(UsbMidiTransport& midi);
|
||||||
|
uint8_t get_value() const { return current_value; }
|
||||||
|
uint16_t get_raw() const { return current_raw; }
|
||||||
|
void set_cal_min();
|
||||||
|
void set_cal_max();
|
||||||
|
uint16_t get_cal_min() const { return cal_min; }
|
||||||
|
uint16_t get_cal_max() const { return cal_max; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t adc_pin;
|
||||||
|
uint8_t current_value;
|
||||||
|
uint8_t last_sent_value;
|
||||||
|
uint8_t midi_channel;
|
||||||
|
uint8_t midi_cc;
|
||||||
|
|
||||||
|
static const uint8_t HYSTERESIS = 3;
|
||||||
|
static const uint16_t READ_INTERVAL_MS = 5;
|
||||||
|
uint32_t last_read_time;
|
||||||
|
|
||||||
|
uint16_t current_raw;
|
||||||
|
uint16_t cal_min;
|
||||||
|
uint16_t cal_max;
|
||||||
|
|
||||||
|
uint8_t adc_to_midi(uint16_t adc_value);
|
||||||
|
};
|
||||||
@@ -3,6 +3,10 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
extern volatile uint32_t midi_tick_count;
|
||||||
|
extern volatile uint16_t last_spp_position;
|
||||||
|
extern volatile bool spp_valid;
|
||||||
|
|
||||||
struct MidiEvent {
|
struct MidiEvent {
|
||||||
enum Type {
|
enum Type {
|
||||||
NOTE_ON,
|
NOTE_ON,
|
||||||
|
|||||||
+14
-6
@@ -1,6 +1,8 @@
|
|||||||
#include "app_task.h"
|
#include "app_task.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
extern volatile uint8_t beats_per_bar;
|
||||||
|
|
||||||
AppTask::AppTask(LedStub* led, SwitchStub* sw, UsbMidiTransport* midi)
|
AppTask::AppTask(LedStub* led, SwitchStub* sw, UsbMidiTransport* midi)
|
||||||
: led_driver(led), switch_driver(sw), midi_transport(midi) {
|
: led_driver(led), switch_driver(sw), midi_transport(midi) {
|
||||||
|
|
||||||
@@ -36,11 +38,9 @@ void AppTask::update() {
|
|||||||
bool is_pressed = switch_driver->is_pressed(i);
|
bool is_pressed = switch_driver->is_pressed(i);
|
||||||
|
|
||||||
if (is_pressed && !last_switch_state[i]) {
|
if (is_pressed && !last_switch_state[i]) {
|
||||||
Serial.printf("[APP] Switch %d pressed\n", i);
|
|
||||||
process_switch_event(i, true);
|
process_switch_event(i, true);
|
||||||
last_switch_state[i] = true;
|
last_switch_state[i] = true;
|
||||||
} else if (!is_pressed && last_switch_state[i]) {
|
} else if (!is_pressed && last_switch_state[i]) {
|
||||||
Serial.printf("[APP] Switch %d released\n", i);
|
|
||||||
process_switch_event(i, false);
|
process_switch_event(i, false);
|
||||||
last_switch_state[i] = false;
|
last_switch_state[i] = false;
|
||||||
}
|
}
|
||||||
@@ -126,6 +126,18 @@ void AppTask::process_midi_event(const MidiEvent& event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AppTask::process_switch_event(uint8_t switch_id, bool pressed) {
|
void AppTask::process_switch_event(uint8_t switch_id, bool pressed) {
|
||||||
|
// Time signature combo: hold pad 10 (switch 9) + press pad 1/2/3
|
||||||
|
if (switch_id <= 2 && switch_driver->is_pressed(9)) {
|
||||||
|
if (pressed) {
|
||||||
|
switch (switch_id) {
|
||||||
|
case 0: beats_per_bar = 4; Serial.println("[APP] Time sig: 4/4"); break;
|
||||||
|
case 1: beats_per_bar = 3; Serial.println("[APP] Time sig: 3/4"); break;
|
||||||
|
case 2: beats_per_bar = 6; Serial.println("[APP] Time sig: 6/4"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return; // suppress CC in combo mode
|
||||||
|
}
|
||||||
|
|
||||||
for (uint8_t i = 0; i < NUM_PADS; i++) {
|
for (uint8_t i = 0; i < NUM_PADS; i++) {
|
||||||
if (pad_mapping[i].physical_switch == switch_id) {
|
if (pad_mapping[i].physical_switch == switch_id) {
|
||||||
uint8_t channel = pad_mapping[i].midi_channel;
|
uint8_t channel = pad_mapping[i].midi_channel;
|
||||||
@@ -136,10 +148,6 @@ void AppTask::process_switch_event(uint8_t switch_id, bool pressed) {
|
|||||||
if (pressed) {
|
if (pressed) {
|
||||||
midi_transport->send_cc(channel, cc_num, value);
|
midi_transport->send_cc(channel, cc_num, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[APP] Switch %d -> Ch%d CC%d Val%d (%s)\n",
|
|
||||||
switch_id, channel, cc_num, value,
|
|
||||||
pressed ? "PRESS" : "RELEASE");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#include "expression_pedal.h"
|
||||||
|
#include "midi_transport.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
ExpressionPedal::ExpressionPedal(uint8_t adc_pin)
|
||||||
|
: adc_pin(adc_pin)
|
||||||
|
, current_value(0)
|
||||||
|
, last_sent_value(255)
|
||||||
|
, midi_channel(1)
|
||||||
|
, midi_cc(4)
|
||||||
|
, last_read_time(0)
|
||||||
|
, current_raw(0)
|
||||||
|
, cal_min(36)
|
||||||
|
, cal_max(1180)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExpressionPedal::begin() {
|
||||||
|
Serial.printf("[EXP] Expression pedal on ADC1_CH3 (GPIO%d)\n", adc_pin);
|
||||||
|
analogReadResolution(12);
|
||||||
|
pinMode(adc_pin, INPUT);
|
||||||
|
delay(10);
|
||||||
|
current_raw = analogRead(adc_pin);
|
||||||
|
current_value = adc_to_midi(current_raw);
|
||||||
|
last_sent_value = current_value;
|
||||||
|
Serial.printf("[EXP] Starting: raw ADC=%d -> MIDI=%d (cal: %d-%d)\n",
|
||||||
|
current_raw, current_value, cal_min, cal_max);
|
||||||
|
Serial.printf("[EXP] Move pedal full range to see raw values\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExpressionPedal::update(UsbMidiTransport& midi) {
|
||||||
|
uint32_t now = millis();
|
||||||
|
if (now - last_read_time < READ_INTERVAL_MS) return;
|
||||||
|
last_read_time = now;
|
||||||
|
|
||||||
|
current_raw = analogRead(adc_pin);
|
||||||
|
uint8_t new_value = adc_to_midi(current_raw);
|
||||||
|
|
||||||
|
if (new_value > current_value + HYSTERESIS ||
|
||||||
|
new_value + HYSTERESIS < current_value ||
|
||||||
|
(new_value != current_value && (new_value == 0 || new_value == 127))) {
|
||||||
|
current_value = new_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_value != last_sent_value) {
|
||||||
|
last_sent_value = current_value;
|
||||||
|
midi.send_cc(midi_channel, midi_cc, current_value);
|
||||||
|
Serial.printf("[EXP] CC%d: %d (raw ADC: %d)\n", midi_cc, current_value, current_raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExpressionPedal::set_cal_min() {
|
||||||
|
cal_min = current_raw;
|
||||||
|
if (cal_min >= cal_max) cal_max = cal_min + 1;
|
||||||
|
Serial.printf("[EXP] cal_min set to %d (ADC raw)\n", cal_min);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExpressionPedal::set_cal_max() {
|
||||||
|
cal_max = current_raw;
|
||||||
|
if (cal_max <= cal_min) cal_min = cal_max - 1;
|
||||||
|
Serial.printf("[EXP] cal_max set to %d (ADC raw)\n", cal_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ExpressionPedal::adc_to_midi(uint16_t adc_value) {
|
||||||
|
if (adc_value <= cal_min) return 0;
|
||||||
|
if (adc_value >= cal_max) return 127;
|
||||||
|
uint32_t span = cal_max - cal_min;
|
||||||
|
uint32_t offset = adc_value - cal_min;
|
||||||
|
return (uint8_t)(offset * 127 / span);
|
||||||
|
}
|
||||||
+32
-7
@@ -142,6 +142,35 @@ static uint32_t velocity_to_color(uint8_t velocity) {
|
|||||||
return launchpad_palette[velocity];
|
return launchpad_palette[velocity];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const uint32_t pad_base_colors[10] = {
|
||||||
|
0x0000FF, // 0: Blue
|
||||||
|
0x0000FF, // 1: Blue
|
||||||
|
0xFF6600, // 2: Orange
|
||||||
|
0xFF6600, // 3: Orange
|
||||||
|
0xFF6600, // 4: Orange
|
||||||
|
0xFF0000, // 5: Red
|
||||||
|
0xFFFFFF, // 6: White
|
||||||
|
0xFFBF00, // 7: Amber
|
||||||
|
0x9900FF, // 8: Purple
|
||||||
|
0x9900FF, // 9: Purple
|
||||||
|
};
|
||||||
|
|
||||||
|
static void apply_pad_color(uint8_t index, uint8_t velocity) {
|
||||||
|
if (index >= 10) return;
|
||||||
|
uint32_t base = pad_base_colors[index];
|
||||||
|
uint8_t r = (base >> 16) & 0xFF;
|
||||||
|
uint8_t g = (base >> 8) & 0xFF;
|
||||||
|
uint8_t b = base & 0xFF;
|
||||||
|
|
||||||
|
if (velocity == 0) {
|
||||||
|
r = (uint16_t)r * 20 / 255;
|
||||||
|
g = (uint16_t)g * 20 / 255;
|
||||||
|
b = (uint16_t)b * 20 / 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
mux_ptr->set_led_color(index, r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
DefaultLedStub::DefaultLedStub() : initialized(false) {
|
DefaultLedStub::DefaultLedStub() : initialized(false) {
|
||||||
for (int i = 0; i < NUM_LEDS; i++) {
|
for (int i = 0; i < NUM_LEDS; i++) {
|
||||||
led_states[i].active = false;
|
led_states[i].active = false;
|
||||||
@@ -203,18 +232,14 @@ void DefaultLedStub::begin() {
|
|||||||
void DefaultLedStub::set_led_state(uint8_t note, uint8_t channel, uint8_t velocity, int8_t led_index) {
|
void DefaultLedStub::set_led_state(uint8_t note, uint8_t channel, uint8_t velocity, int8_t led_index) {
|
||||||
if (!initialized || !mux_ptr) return;
|
if (!initialized || !mux_ptr) return;
|
||||||
|
|
||||||
// Direct index mode (used by CC feedback)
|
// Direct index mode (used by CC feedback / pad colors)
|
||||||
if (led_index >= 0 && led_index < NUM_LEDS) {
|
if (led_index >= 0 && led_index < NUM_LEDS) {
|
||||||
led_states[led_index].active = (velocity > 0);
|
led_states[led_index].active = (velocity > 0);
|
||||||
led_states[led_index].note = note;
|
led_states[led_index].note = note;
|
||||||
led_states[led_index].channel = channel;
|
led_states[led_index].channel = channel;
|
||||||
led_states[led_index].velocity = velocity;
|
led_states[led_index].velocity = velocity;
|
||||||
led_states[led_index].timestamp = millis();
|
led_states[led_index].timestamp = millis();
|
||||||
uint32_t color = velocity_to_color(velocity);
|
apply_pad_color(led_index, velocity);
|
||||||
uint8_t r = (color >> 16) & 0xFF;
|
|
||||||
uint8_t g = (color >> 8) & 0xFF;
|
|
||||||
uint8_t b = color & 0xFF;
|
|
||||||
mux_ptr->set_led_color(led_index, r, g, b);
|
|
||||||
mux_ptr->show();
|
mux_ptr->show();
|
||||||
Serial.printf("[LED] Set LED %d: note=%d ch=%d vel=%d\n", led_index, note, channel, velocity);
|
Serial.printf("[LED] Set LED %d: note=%d ch=%d vel=%d\n", led_index, note, channel, velocity);
|
||||||
return;
|
return;
|
||||||
@@ -265,7 +290,7 @@ void DefaultLedStub::clear_all() {
|
|||||||
led_states[i].note = 0;
|
led_states[i].note = 0;
|
||||||
led_states[i].channel = 0;
|
led_states[i].channel = 0;
|
||||||
led_states[i].timestamp = 0;
|
led_states[i].timestamp = 0;
|
||||||
mux_ptr->set_led_color(i, 0, 0, 0);
|
apply_pad_color(i, 0);
|
||||||
}
|
}
|
||||||
mux_ptr->show();
|
mux_ptr->show();
|
||||||
Serial.println("[LED] All cleared");
|
Serial.println("[LED] All cleared");
|
||||||
|
|||||||
+54
-2
@@ -10,6 +10,7 @@
|
|||||||
#include "led_stub.h"
|
#include "led_stub.h"
|
||||||
#include "switch_stub.h"
|
#include "switch_stub.h"
|
||||||
#include "app_task.h"
|
#include "app_task.h"
|
||||||
|
#include "expression_pedal.h"
|
||||||
|
|
||||||
PixelStompMux mux(12, 10, 11, 9);
|
PixelStompMux mux(12, 10, 11, 9);
|
||||||
|
|
||||||
@@ -18,14 +19,46 @@ DefaultSwitchStub switch_driver;
|
|||||||
UsbMidiTransport midi_transport;
|
UsbMidiTransport midi_transport;
|
||||||
|
|
||||||
AppTask controller(&led_driver, &switch_driver, &midi_transport);
|
AppTask controller(&led_driver, &switch_driver, &midi_transport);
|
||||||
|
ExpressionPedal exp_pedal(4);
|
||||||
|
|
||||||
|
volatile uint32_t flash_latency = 55; // ms delay to compensate for Loopy Pro rendering
|
||||||
|
volatile uint8_t beats_per_bar = 4; // change to match your project's time signature
|
||||||
|
|
||||||
TaskHandle_t midi_task_handle = NULL;
|
TaskHandle_t midi_task_handle = NULL;
|
||||||
|
|
||||||
void midi_task(void* parameter) {
|
void midi_task(void* parameter) {
|
||||||
Serial.println("[TASK] MIDI task started on core 0");
|
Serial.println("[TASK] MIDI task started on core 0");
|
||||||
|
|
||||||
|
// Beat timing fixes for precise beat alignment - sync to real MIDI time, not hardcoded tempo
|
||||||
|
// UINT32_MAX ensures tick 0 (first 0xF8 after START) triggers a flash
|
||||||
|
uint32_t last_beat = UINT32_MAX;
|
||||||
|
uint32_t flash_start = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
midi_transport.update();
|
midi_transport.update();
|
||||||
|
exp_pedal.update(midi_transport);
|
||||||
|
|
||||||
|
uint32_t tick = midi_tick_count;
|
||||||
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
uint32_t current_beat = tick / 24;
|
||||||
|
|
||||||
|
if (current_beat != last_beat) {
|
||||||
|
last_beat = current_beat;
|
||||||
|
flash_start = now + flash_latency;
|
||||||
|
// All beats flash white
|
||||||
|
mux.set_led_color(6, 255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flash_start > 0 && now >= flash_start) {
|
||||||
|
mux.show();
|
||||||
|
if (now - flash_start >= 50) {
|
||||||
|
mux.set_led_color(6, 20, 20, 20);
|
||||||
|
mux.show();
|
||||||
|
flash_start = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
vTaskDelay(1);
|
vTaskDelay(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,6 +116,19 @@ void handle_serial_command(const String& cmd) {
|
|||||||
mux.set_led_color(1, 255, 255, 255);
|
mux.set_led_color(1, 255, 255, 255);
|
||||||
mux.show();
|
mux.show();
|
||||||
Serial.println("[CMD] Pixel 1 WHITE (max brightness)");
|
Serial.println("[CMD] Pixel 1 WHITE (max brightness)");
|
||||||
|
} else if (cmd == "exp") {
|
||||||
|
Serial.printf("[CMD] EXP ADC=%d MIDI=%d\n",
|
||||||
|
exp_pedal.get_raw(), exp_pedal.get_value());
|
||||||
|
} else if (cmd == "latency") {
|
||||||
|
Serial.printf("[CMD] Current flash latency: %d ms\n", flash_latency);
|
||||||
|
} else if (cmd.startsWith("latency ")) {
|
||||||
|
int val = atoi(cmd.c_str() + 8);
|
||||||
|
if (val >= 0 && val <= 500) {
|
||||||
|
flash_latency = val;
|
||||||
|
Serial.printf("[CMD] Flash latency set to %d ms\n", flash_latency);
|
||||||
|
} else {
|
||||||
|
Serial.println("[CMD] Latency must be 0-500 ms");
|
||||||
|
}
|
||||||
} else if (cmd == "usb") {
|
} else if (cmd == "usb") {
|
||||||
Serial.printf("[CMD] USB mounted: %s\n", TinyUSBDevice.mounted() ? "YES" : "NO");
|
Serial.printf("[CMD] USB mounted: %s\n", TinyUSBDevice.mounted() ? "YES" : "NO");
|
||||||
Serial.printf("[CMD] USB ready: %s\n", TinyUSBDevice.ready() ? "YES" : "NO");
|
Serial.printf("[CMD] USB ready: %s\n", TinyUSBDevice.ready() ? "YES" : "NO");
|
||||||
@@ -126,6 +172,9 @@ void handle_serial_command(const String& cmd) {
|
|||||||
Serial.println(" read - raw button read");
|
Serial.println(" read - raw button read");
|
||||||
Serial.println(" red/green/blue - solid colour");
|
Serial.println(" red/green/blue - solid colour");
|
||||||
Serial.println(" pixel0/pixel1 - single pixel test");
|
Serial.println(" pixel0/pixel1 - single pixel test");
|
||||||
|
Serial.println(" exp - expression pedal ADC/MIDI value");
|
||||||
|
Serial.println(" latency - show current flash latency");
|
||||||
|
Serial.println(" latency N - set flash latency to N ms (0-500)");
|
||||||
Serial.println(" usb - USB connection status and descriptor info");
|
Serial.println(" usb - USB connection status and descriptor info");
|
||||||
Serial.println(" gpiotest - raw GPIO pin diagnostic");
|
Serial.println(" gpiotest - raw GPIO pin diagnostic");
|
||||||
Serial.println(" rawled - bit-bang WS2812 (no library)");
|
Serial.println(" rawled - bit-bang WS2812 (no library)");
|
||||||
@@ -328,7 +377,7 @@ void setup() {
|
|||||||
|
|
||||||
Serial.println("=================================");
|
Serial.println("=================================");
|
||||||
Serial.println(" Loopy MIDI Controller v0.1");
|
Serial.println(" Loopy MIDI Controller v0.1");
|
||||||
Serial.println(" Phase 1: USB MIDI");
|
Serial.println(" Phase 1: USB MIDI + Expression Pedal");
|
||||||
Serial.println(" Board: ESP32-S3-WROOM-1");
|
Serial.println(" Board: ESP32-S3-WROOM-1");
|
||||||
Serial.println("=================================");
|
Serial.println("=================================");
|
||||||
|
|
||||||
@@ -346,6 +395,9 @@ void setup() {
|
|||||||
Serial.println("[INIT] Initializing USB MIDI...");
|
Serial.println("[INIT] Initializing USB MIDI...");
|
||||||
midi_transport.begin();
|
midi_transport.begin();
|
||||||
|
|
||||||
|
Serial.println("[INIT] Initializing Expression Pedal...");
|
||||||
|
exp_pedal.begin();
|
||||||
|
|
||||||
Serial.println("[INIT] Registering MIDI callbacks...");
|
Serial.println("[INIT] Registering MIDI callbacks...");
|
||||||
controller.begin();
|
controller.begin();
|
||||||
|
|
||||||
@@ -377,5 +429,5 @@ void loop() {
|
|||||||
handle_serial_command(cmd);
|
handle_serial_command(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(10);
|
delay(1);
|
||||||
}
|
}
|
||||||
|
|||||||
+63
-16
@@ -2,6 +2,11 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "Adafruit_TinyUSB.h"
|
#include "Adafruit_TinyUSB.h"
|
||||||
|
|
||||||
|
volatile uint32_t midi_tick_count = 0;
|
||||||
|
volatile uint16_t last_spp_position = 0;
|
||||||
|
volatile bool spp_valid = false;
|
||||||
|
extern volatile uint8_t beats_per_bar;
|
||||||
|
|
||||||
static Adafruit_USBD_MIDI usb_midi;
|
static Adafruit_USBD_MIDI usb_midi;
|
||||||
|
|
||||||
UsbMidiTransport::UsbMidiTransport() : initialized(false) {
|
UsbMidiTransport::UsbMidiTransport() : initialized(false) {
|
||||||
@@ -48,25 +53,68 @@ void UsbMidiTransport::update() {
|
|||||||
TinyUSBDevice.mounted() ? "YES" : "NO");
|
TinyUSBDevice.mounted() ? "YES" : "NO");
|
||||||
}
|
}
|
||||||
|
|
||||||
while (usb_midi.available()) {
|
if (usb_midi.available()) {
|
||||||
uint8_t packet[4];
|
uint8_t packet[4];
|
||||||
if (usb_midi.readPacket(packet)) {
|
if (usb_midi.readPacket(packet)) {
|
||||||
MidiEvent event;
|
uint8_t cin = packet[0] & 0x0F;
|
||||||
parse_midi_packet(packet, 4, event);
|
if (cin == 0x0F) {
|
||||||
|
if (packet[1] == 0xF8) {
|
||||||
|
midi_tick_count++;
|
||||||
|
} else if (packet[1] == 0xFA) {
|
||||||
|
if (spp_valid) {
|
||||||
|
midi_tick_count = last_spp_position * 6;
|
||||||
|
spp_valid = false;
|
||||||
|
Serial.printf("[CLK] START at SPP=%d -> tick %d\n", last_spp_position, midi_tick_count);
|
||||||
|
} else {
|
||||||
|
midi_tick_count = 0xFFFFFFFF;
|
||||||
|
Serial.println("[CLK] START (no SPP) - next F8 = tick 0");
|
||||||
|
}
|
||||||
|
} else if (packet[1] == 0xFB) {
|
||||||
|
// CONTINUE - same as START for our purposes
|
||||||
|
if (spp_valid) {
|
||||||
|
midi_tick_count = last_spp_position * 6;
|
||||||
|
spp_valid = false;
|
||||||
|
Serial.printf("[CLK] CONTINUE at SPP=%d -> tick %d\n", last_spp_position, midi_tick_count);
|
||||||
|
} else {
|
||||||
|
Serial.println("[CLK] CONTINUE (no SPP)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (cin == 0x03 && packet[1] == 0xF2) {
|
||||||
|
// Song Position Pointer
|
||||||
|
uint16_t prev_spp = last_spp_position;
|
||||||
|
last_spp_position = (packet[3] << 7) | packet[2];
|
||||||
|
spp_valid = true;
|
||||||
|
Serial.printf("[CLK] SPP=%d\n", last_spp_position);
|
||||||
|
|
||||||
const char* type_str = "UNK";
|
// Auto-detect time signature from SPP delta (sent at bar boundaries)
|
||||||
switch (event.type) {
|
if (prev_spp > 0 && last_spp_position > prev_spp) {
|
||||||
case MidiEvent::NOTE_ON: type_str = "NOTE_ON"; break;
|
uint16_t delta = last_spp_position - prev_spp;
|
||||||
case MidiEvent::NOTE_OFF: type_str = "NOTE_OFF"; break;
|
if (delta >= 8 && delta <= 64 && delta % 4 == 0) {
|
||||||
case MidiEvent::CONTROL_CHANGE: type_str = "CC"; break;
|
uint8_t detected = delta / 4;
|
||||||
case MidiEvent::PROGRAM_CHANGE: type_str = "PC"; break;
|
if (detected >= 2 && detected <= 16 && detected != beats_per_bar) {
|
||||||
case MidiEvent::PITCH_BEND: type_str = "PB"; break;
|
beats_per_bar = detected;
|
||||||
default: break;
|
Serial.printf("[CLK] Auto-detected %d/4 time from SPP delta=%d\n", detected, delta);
|
||||||
}
|
}
|
||||||
Serial.printf("[MIDI IN] Ch:%d %s:%d:%d\n", event.channel, type_str, event.data1, event.data2);
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MidiEvent event;
|
||||||
|
parse_midi_packet(packet, 4, event);
|
||||||
|
|
||||||
if (receive_callback) {
|
const char* type_str = "UNK";
|
||||||
receive_callback(event);
|
switch (event.type) {
|
||||||
|
case MidiEvent::NOTE_ON: type_str = "NOTE_ON"; break;
|
||||||
|
case MidiEvent::NOTE_OFF: type_str = "NOTE_OFF"; break;
|
||||||
|
case MidiEvent::CONTROL_CHANGE: type_str = "CC"; break;
|
||||||
|
case MidiEvent::PROGRAM_CHANGE: type_str = "PC"; break;
|
||||||
|
case MidiEvent::PITCH_BEND: type_str = "PB"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
Serial.printf("[MIDI IN] Ch:%d %s:%d:%d\n", event.channel, type_str, event.data1, event.data2);
|
||||||
|
|
||||||
|
if (receive_callback) {
|
||||||
|
receive_callback(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +142,6 @@ void UsbMidiTransport::send_cc(uint8_t channel, uint8_t cc, uint8_t value) {
|
|||||||
if (!initialized) return;
|
if (!initialized) return;
|
||||||
uint8_t packet[4] = {0x0B, (uint8_t)(0xB0 | (channel - 1)), cc, value};
|
uint8_t packet[4] = {0x0B, (uint8_t)(0xB0 | (channel - 1)), cc, value};
|
||||||
usb_midi.writePacket(packet);
|
usb_midi.writePacket(packet);
|
||||||
Serial.printf("[MIDI OUT] Ch:%d CC:%d:%d\n", channel, cc, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UsbMidiTransport::is_connected() {
|
bool UsbMidiTransport::is_connected() {
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ DefaultSwitchStub::DefaultSwitchStub() : initialized(false) {
|
|||||||
switch_states[i].current_state = false;
|
switch_states[i].current_state = false;
|
||||||
switch_states[i].previous_state = false;
|
switch_states[i].previous_state = false;
|
||||||
switch_states[i].last_change_time = 0;
|
switch_states[i].last_change_time = 0;
|
||||||
switch_states[i].debounce_time = 50;
|
switch_states[i].debounce_time = 5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user