Convert to PlatformIO/Arduino format
- Replace ESP-IDF structure with PlatformIO (platformio.ini) - Move source files to src/ and include/ - Move libraries to lib/ directory - Replace FreeRTOS with Arduino task/vTaskDelay - Use Arduino MIDI library instead of raw TinyUSB - Dual-core: MIDI on core 0, controller on core 1
This commit is contained in:
@@ -1,7 +0,0 @@
|
|||||||
# Top-level CMake file for ESP-IDF project
|
|
||||||
cmake_minimum_required(VERSION 3.16)
|
|
||||||
|
|
||||||
set(EXTRA_COMPONENT_DIRS "components")
|
|
||||||
|
|
||||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
|
||||||
project(loopy_midi_controller)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
idf_component_register(SRCS "app_task.cpp"
|
|
||||||
INCLUDE_DIRS "."
|
|
||||||
REQUIRES midi hal)
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
// components/controller/app_task.cpp
|
|
||||||
#include "controller/app_task.h"
|
|
||||||
#include "midi/midi_transport.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
|
|
||||||
static const char* TAG = "app_task";
|
|
||||||
|
|
||||||
// Simple pad mapping table (Phase 1 - modifiable)
|
|
||||||
struct PadMapping {
|
|
||||||
uint8_t physical_switch; // 0-9
|
|
||||||
uint8_t midi_channel; // 1-3
|
|
||||||
uint8_t midi_note; // Note number (configurable)
|
|
||||||
uint8_t led_index; // LED index (0-9)
|
|
||||||
};
|
|
||||||
|
|
||||||
static PadMapping pad_mapping[] = {
|
|
||||||
{0, 1, 0, 0}, // Switch 0 -> Channel 1, Note 0, LED 0
|
|
||||||
{1, 1, 1, 1}, // Switch 1 -> Channel 1, Note 1, LED 1
|
|
||||||
{2, 1, 2, 2}, // Switch 2 -> Channel 1, Note 2, LED 2
|
|
||||||
{3, 1, 3, 3}, // Switch 3 -> Channel 1, Note 3, LED 3
|
|
||||||
{4, 1, 4, 4}, // Switch 4 -> Channel 1, Note 4, LED 4
|
|
||||||
{5, 1, 5, 5}, // Switch 5 -> Channel 1, Note 5, LED 5
|
|
||||||
{6, 1, 6, 6}, // Switch 6 -> Channel 1, Note 6, LED 6
|
|
||||||
{7, 1, 7, 7}, // Switch 7 -> Channel 1, Note 7, LED 7
|
|
||||||
{8, 1, 8, 8}, // Switch 8 -> Channel 1, Note 8, LED 8
|
|
||||||
{9, 1, 9, 9}, // Switch 9 -> Channel 1, Note 9, LED 9
|
|
||||||
};
|
|
||||||
|
|
||||||
static const uint8_t NUM_PADS = sizeof(pad_mapping) / sizeof(pad_mapping[0]);
|
|
||||||
|
|
||||||
BaseType_t app_task(void* parameters) {
|
|
||||||
AppTaskParams* params = (AppTaskParams*)parameters;
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Controller task started");
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
// Check for MIDI events
|
|
||||||
MidiEvent midi_event;
|
|
||||||
if (xQueueReceive(params->midi_queue, &midi_event, 0) == pdPASS) {
|
|
||||||
app_process_midi_event(midi_event, params->led_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for switch events (Phase 1 stub)
|
|
||||||
for (uint8_t i = 0; i < NUM_PADS; i++) {
|
|
||||||
bool is_pressed = params->switch_driver->is_pressed(i);
|
|
||||||
static bool last_state[10] = {false};
|
|
||||||
|
|
||||||
if (is_pressed && !last_state[i]) {
|
|
||||||
// Switch press detected
|
|
||||||
app_process_switch_event(i, true);
|
|
||||||
last_state[i] = true;
|
|
||||||
} else if (!is_pressed && last_state[i]) {
|
|
||||||
// Switch release detected
|
|
||||||
app_process_switch_event(i, false);
|
|
||||||
last_state[i] = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(10)); // 10ms task period
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void app_process_midi_event(const MidiEvent& event, LedStub* led_driver) {
|
|
||||||
// Convert MIDI event to LED command
|
|
||||||
// This is where we map MIDI to LED state
|
|
||||||
uint8_t led_index = -1;
|
|
||||||
uint8_t midi_channel = event.channel;
|
|
||||||
uint8_t midi_note = event.data1;
|
|
||||||
uint8_t midi_velocity = event.data2;
|
|
||||||
|
|
||||||
// Find matching LED index from pad mapping
|
|
||||||
for (uint8_t i = 0; i < NUM_PADS; i++) {
|
|
||||||
if (pad_mapping[i].midi_channel == midi_channel &&
|
|
||||||
pad_mapping[i].midi_note == midi_note) {
|
|
||||||
led_index = pad_mapping[i].led_index;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (led_index != 255) {
|
|
||||||
// Trigger LED state change
|
|
||||||
led_driver->set_led_state(
|
|
||||||
pad_mapping[led_index].midi_note,
|
|
||||||
pad_mapping[led_index].midi_channel,
|
|
||||||
event.type == MidiEvent::NOTE_ON ? midi_velocity : 0
|
|
||||||
);
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "MIDI PROCESSED: Channel %d Note %d Velocity %d -> LED %d",
|
|
||||||
midi_channel, midi_note, midi_velocity, led_index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void app_process_switch_event(uint8_t switch_id, bool pressed) {
|
|
||||||
// Convert switch event to MIDI event
|
|
||||||
MidiEvent midi_event;
|
|
||||||
|
|
||||||
// Find mapping for this switch
|
|
||||||
for (uint8_t i = 0; i < NUM_PADS; i++) {
|
|
||||||
if (pad_mapping[i].physical_switch == switch_id) {
|
|
||||||
midi_event.channel = pad_mapping[i].midi_channel;
|
|
||||||
midi_event.data1 = pad_mapping[i].midi_note;
|
|
||||||
midi_event.data2 = pressed ? 127 : 0; // Full velocity for press
|
|
||||||
midi_event.type = pressed ? MidiEvent::NOTE_ON : MidiEvent::NOTE_OFF;
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "SWITCH EVENT: Switch %d -> Channel %d Note %d Velocity %d",
|
|
||||||
switch_id, midi_event.channel, midi_event.data1, midi_event.data2);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// components/controller/app_task.h
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/queue.h>
|
|
||||||
#include "midi/midi_transport.h"
|
|
||||||
#include "hal/led_stub.h"
|
|
||||||
#include "hal/switch_stub.h"
|
|
||||||
|
|
||||||
// Application task parameters
|
|
||||||
struct AppTaskParams {
|
|
||||||
LedStub* led_driver;
|
|
||||||
SwitchStub* switch_driver;
|
|
||||||
QueueHandle_t midi_queue;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Application task function
|
|
||||||
BaseType_t app_task(void* parameters);
|
|
||||||
|
|
||||||
// Application state management
|
|
||||||
void app_process_midi_event(const MidiEvent& event, LedStub* led_driver);
|
|
||||||
void app_process_switch_event(uint8_t switch_id, bool pressed);
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
idf_component_register(SRCS "led_stub.cpp" "switch_stub.cpp"
|
|
||||||
INCLUDE_DIRS "."
|
|
||||||
REQUIRES )
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
idf_component_register(SRCS "midi_transport.cpp"
|
|
||||||
INCLUDE_DIRS "."
|
|
||||||
REQUIRES driver)
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
// components/midi/midi_transport.cpp
|
|
||||||
#include "midi/midi_transport.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "tusb.h"
|
|
||||||
|
|
||||||
static const char* TAG = "midi_transport";
|
|
||||||
|
|
||||||
UsbMidiTransport::UsbMidiTransport() : event_queue(nullptr), initialized(false) {
|
|
||||||
}
|
|
||||||
|
|
||||||
UsbMidiTransport::~UsbMidiTransport() {
|
|
||||||
if (event_queue != NULL) {
|
|
||||||
vQueueDelete(event_queue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UsbMidiTransport::begin() {
|
|
||||||
// Create event queue
|
|
||||||
event_queue = xQueueCreate(32, sizeof(MidiEvent));
|
|
||||||
if (event_queue == NULL) {
|
|
||||||
ESP_LOGE(TAG, "Failed to create event queue");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize TinyUSB
|
|
||||||
tusb_init();
|
|
||||||
|
|
||||||
initialized = true;
|
|
||||||
ESP_LOGI(TAG, "USB MIDI transport initialized");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UsbMidiTransport::task() {
|
|
||||||
if (!initialized) return;
|
|
||||||
|
|
||||||
// TinyUSB device task handling
|
|
||||||
tuh_task();
|
|
||||||
|
|
||||||
// Check for MIDI data on the USB host interface
|
|
||||||
uint8_t cable_num;
|
|
||||||
uint8_t midi_packet[4];
|
|
||||||
|
|
||||||
while (tud_midi_available()) {
|
|
||||||
if (tud_midi_packet_read(midi_packet)) {
|
|
||||||
MidiEvent event;
|
|
||||||
parse_midi_packet(midi_packet, 4, event);
|
|
||||||
|
|
||||||
log_incoming("USB", event);
|
|
||||||
|
|
||||||
if (xQueueSend(event_queue, &event, 0) != pdPASS) {
|
|
||||||
ESP_LOGW(TAG, "Failed to queue MIDI event (queue full)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void UsbMidiTransport::log_incoming(const char* source, const MidiEvent& event) {
|
|
||||||
const char* type_str;
|
|
||||||
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 = "PITCH_BEND"; break;
|
|
||||||
default: type_str = "UNKNOWN"; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "MIDI IN: %s Ch:%d %s:%d:%d",
|
|
||||||
source, event.channel, type_str, event.data1, event.data2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UsbMidiTransport::parse_midi_packet(const uint8_t* buffer, uint32_t size, MidiEvent& event) {
|
|
||||||
if (size < 4) return;
|
|
||||||
|
|
||||||
// USB MIDI packet format: [cable_num | CIN], [status], [data1], [data2]
|
|
||||||
uint8_t cin = buffer[0] & 0x0F;
|
|
||||||
uint8_t status = buffer[1];
|
|
||||||
uint8_t type = status & 0xF0;
|
|
||||||
uint8_t channel = (status & 0x0F) + 1; // Convert to 1-16 range
|
|
||||||
|
|
||||||
event.channel = channel;
|
|
||||||
event.data1 = buffer[2];
|
|
||||||
event.data2 = buffer[3];
|
|
||||||
|
|
||||||
switch (cin) {
|
|
||||||
case 0x8: // Note Off
|
|
||||||
event.type = MidiEvent::NOTE_OFF;
|
|
||||||
break;
|
|
||||||
case 0x9: // Note On
|
|
||||||
event.type = MidiEvent::NOTE_ON;
|
|
||||||
if (event.data2 == 0) {
|
|
||||||
event.type = MidiEvent::NOTE_OFF;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 0xB: // Control Change
|
|
||||||
event.type = MidiEvent::CONTROL_CHANGE;
|
|
||||||
break;
|
|
||||||
case 0xC: // Program Change
|
|
||||||
event.type = MidiEvent::PROGRAM_CHANGE;
|
|
||||||
break;
|
|
||||||
case 0xE: // Pitch Bend
|
|
||||||
event.type = MidiEvent::PITCH_BEND;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Try to infer from status byte
|
|
||||||
switch (type) {
|
|
||||||
case 0x80: event.type = MidiEvent::NOTE_OFF; break;
|
|
||||||
case 0x90: event.type = MidiEvent::NOTE_ON; break;
|
|
||||||
case 0xB0: event.type = MidiEvent::CONTROL_CHANGE; break;
|
|
||||||
case 0xC0: event.type = MidiEvent::PROGRAM_CHANGE; break;
|
|
||||||
case 0xE0: event.type = MidiEvent::PITCH_BEND; break;
|
|
||||||
default: event.type = MidiEvent::NOTE_ON; break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void usb_midi_task(void* pvParameters) {
|
|
||||||
UsbMidiTransport* transport = static_cast<UsbMidiTransport*>(pvParameters);
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "USB MIDI task started");
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
transport->task();
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include "midi_transport.h"
|
||||||
|
#include "led_stub.h"
|
||||||
|
#include "switch_stub.h"
|
||||||
|
|
||||||
|
struct PadMapping {
|
||||||
|
uint8_t physical_switch;
|
||||||
|
uint8_t midi_channel;
|
||||||
|
uint8_t midi_note;
|
||||||
|
uint8_t led_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AppTask {
|
||||||
|
public:
|
||||||
|
AppTask(LedStub* led, SwitchStub* sw, UsbMidiTransport* midi);
|
||||||
|
|
||||||
|
void begin();
|
||||||
|
void update();
|
||||||
|
|
||||||
|
private:
|
||||||
|
LedStub* led_driver;
|
||||||
|
SwitchStub* switch_driver;
|
||||||
|
UsbMidiTransport* midi_transport;
|
||||||
|
|
||||||
|
static const uint8_t NUM_PADS = 10;
|
||||||
|
PadMapping pad_mapping[NUM_PADS];
|
||||||
|
bool last_switch_state[NUM_PADS];
|
||||||
|
|
||||||
|
void process_midi_event(const MidiEvent& event);
|
||||||
|
void process_switch_event(uint8_t switch_id, bool pressed);
|
||||||
|
};
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// components/hal/led_stub.h
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -11,22 +10,19 @@ public:
|
|||||||
virtual void set_led_state(uint8_t note, uint8_t channel, uint8_t velocity) = 0;
|
virtual void set_led_state(uint8_t note, uint8_t channel, uint8_t velocity) = 0;
|
||||||
virtual void clear_all() = 0;
|
virtual void clear_all() = 0;
|
||||||
|
|
||||||
// Helper function to map MIDI note to LED index
|
|
||||||
virtual uint8_t note_to_index(uint8_t note) {
|
virtual uint8_t note_to_index(uint8_t note) {
|
||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// LED state structure
|
|
||||||
struct LedState {
|
struct LedState {
|
||||||
uint8_t note; // Launchpad note
|
uint8_t note;
|
||||||
uint8_t channel; // LED channel (1-3)
|
uint8_t channel;
|
||||||
uint8_t velocity; // Color/brightness (0-127)
|
uint8_t velocity;
|
||||||
uint32_t timestamp; // When state was set
|
uint32_t timestamp;
|
||||||
bool active; // Current on/off state
|
bool active;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Default stub implementation
|
|
||||||
class DefaultLedStub : public LedStub {
|
class DefaultLedStub : public LedStub {
|
||||||
private:
|
private:
|
||||||
static const uint8_t NUM_LEDS = 10;
|
static const uint8_t NUM_LEDS = 10;
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
// components/midi/midi_transport.h
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <functional>
|
||||||
#include <freertos/queue.h>
|
|
||||||
|
|
||||||
struct MidiEvent {
|
struct MidiEvent {
|
||||||
enum Type {
|
enum Type {
|
||||||
@@ -29,21 +27,19 @@ public:
|
|||||||
~UsbMidiTransport();
|
~UsbMidiTransport();
|
||||||
|
|
||||||
bool begin();
|
bool begin();
|
||||||
void task();
|
void update();
|
||||||
|
|
||||||
// Event queue for communication with controller task
|
// Callback registration
|
||||||
QueueHandle_t get_event_queue() const { return event_queue; }
|
void on_midi_receive(std::function<void(const MidiEvent&)> callback);
|
||||||
|
|
||||||
// Diagnostic logging
|
// Send MIDI
|
||||||
void log_incoming(const char* source, const MidiEvent& event);
|
void send_note_on(uint8_t channel, uint8_t note, uint8_t velocity);
|
||||||
|
void send_note_off(uint8_t channel, uint8_t note, uint8_t velocity);
|
||||||
|
void send_cc(uint8_t channel, uint8_t cc, uint8_t value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QueueHandle_t event_queue;
|
std::function<void(const MidiEvent&)> receive_callback;
|
||||||
bool initialized;
|
bool initialized;
|
||||||
|
|
||||||
// MIDI packet parsing
|
|
||||||
void parse_midi_packet(const uint8_t* buffer, uint32_t size, MidiEvent& event);
|
void parse_midi_packet(const uint8_t* buffer, uint32_t size, MidiEvent& event);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Task function for USB MIDI processing
|
|
||||||
void usb_midi_task(void* pvParameters);
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// components/hal/switch_stub.h
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -9,23 +8,19 @@ public:
|
|||||||
|
|
||||||
virtual void begin() = 0;
|
virtual void begin() = 0;
|
||||||
virtual bool is_pressed(uint8_t switch_id) = 0;
|
virtual bool is_pressed(uint8_t switch_id) = 0;
|
||||||
|
|
||||||
// Configuration methods
|
|
||||||
virtual void configure_switch(uint8_t switch_id, uint8_t gpio_pin) = 0;
|
virtual void configure_switch(uint8_t switch_id, uint8_t gpio_pin) = 0;
|
||||||
virtual void set_debounce_time(uint32_t time_ms) = 0;
|
virtual void set_debounce_time(uint32_t time_ms) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Switch state structure
|
|
||||||
struct SwitchState {
|
struct SwitchState {
|
||||||
uint8_t id; // Switch identifier
|
uint8_t id;
|
||||||
uint8_t gpio_pin; // GPIO pin (if applicable)
|
uint8_t gpio_pin;
|
||||||
bool current_state; // Current pressed state
|
bool current_state;
|
||||||
bool previous_state; // Previous state (for debounce)
|
bool previous_state;
|
||||||
uint32_t last_change_time; // Timestamp of last state change
|
uint32_t last_change_time;
|
||||||
uint32_t debounce_time; // Debounce time in ms
|
uint32_t debounce_time;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Default stub implementation
|
|
||||||
class DefaultSwitchStub : public SwitchStub {
|
class DefaultSwitchStub : public SwitchStub {
|
||||||
private:
|
private:
|
||||||
static const uint8_t NUM_SWITCHES = 10;
|
static const uint8_t NUM_SWITCHES = 10;
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
#include "app_task.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
AppTask::AppTask(LedStub* led, SwitchStub* sw, UsbMidiTransport* midi)
|
||||||
|
: led_driver(led), switch_driver(sw), midi_transport(midi) {
|
||||||
|
|
||||||
|
// Default pad mapping
|
||||||
|
for (uint8_t i = 0; i < NUM_PADS; i++) {
|
||||||
|
pad_mapping[i].physical_switch = i;
|
||||||
|
pad_mapping[i].midi_channel = 1;
|
||||||
|
pad_mapping[i].midi_note = i;
|
||||||
|
pad_mapping[i].led_index = i;
|
||||||
|
last_switch_state[i] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppTask::begin() {
|
||||||
|
Serial.println("[APP] Controller task started");
|
||||||
|
|
||||||
|
// Register MIDI callback
|
||||||
|
midi_transport->on_midi_receive([this](const MidiEvent& event) {
|
||||||
|
process_midi_event(event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppTask::update() {
|
||||||
|
// Poll switches
|
||||||
|
for (uint8_t i = 0; i < NUM_PADS; i++) {
|
||||||
|
bool is_pressed = switch_driver->is_pressed(i);
|
||||||
|
|
||||||
|
if (is_pressed && !last_switch_state[i]) {
|
||||||
|
process_switch_event(i, true);
|
||||||
|
last_switch_state[i] = true;
|
||||||
|
} else if (!is_pressed && last_switch_state[i]) {
|
||||||
|
process_switch_event(i, false);
|
||||||
|
last_switch_state[i] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppTask::process_midi_event(const MidiEvent& event) {
|
||||||
|
uint8_t led_index = 0xFF;
|
||||||
|
uint8_t midi_channel = event.channel;
|
||||||
|
uint8_t midi_note = event.data1;
|
||||||
|
uint8_t midi_velocity = event.data2;
|
||||||
|
|
||||||
|
// Find matching LED index from pad mapping
|
||||||
|
for (uint8_t i = 0; i < NUM_PADS; i++) {
|
||||||
|
if (pad_mapping[i].midi_channel == midi_channel &&
|
||||||
|
pad_mapping[i].midi_note == midi_note) {
|
||||||
|
led_index = pad_mapping[i].led_index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (led_index < NUM_PADS) {
|
||||||
|
led_driver->set_led_state(
|
||||||
|
pad_mapping[led_index].midi_note,
|
||||||
|
pad_mapping[led_index].midi_channel,
|
||||||
|
event.type == MidiEvent::NOTE_ON ? midi_velocity : 0
|
||||||
|
);
|
||||||
|
|
||||||
|
Serial.printf("[APP] MIDI -> LED: Ch%d Note%d Vel%d -> LED%d\n",
|
||||||
|
midi_channel, midi_note, midi_velocity, led_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppTask::process_switch_event(uint8_t switch_id, bool pressed) {
|
||||||
|
// Find mapping for this switch
|
||||||
|
for (uint8_t i = 0; i < NUM_PADS; i++) {
|
||||||
|
if (pad_mapping[i].physical_switch == switch_id) {
|
||||||
|
uint8_t channel = pad_mapping[i].midi_channel;
|
||||||
|
uint8_t note = pad_mapping[i].midi_note;
|
||||||
|
uint8_t velocity = pressed ? 127 : 0;
|
||||||
|
|
||||||
|
if (pressed) {
|
||||||
|
midi_transport->send_note_on(channel, note, velocity);
|
||||||
|
} else {
|
||||||
|
midi_transport->send_note_off(channel, note, velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[APP] Switch %d -> Ch%d Note%d Vel%d (%s)\n",
|
||||||
|
switch_id, channel, note, velocity,
|
||||||
|
pressed ? "PRESS" : "RELEASE");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
// components/hal/led_stub.cpp
|
#include "led_stub.h"
|
||||||
#include "hal/led_stub.h"
|
#include <Arduino.h>
|
||||||
#include "esp_log.h"
|
|
||||||
|
|
||||||
static const char* TAG = "led_stub";
|
|
||||||
|
|
||||||
DefaultLedStub::DefaultLedStub() : initialized(false) {
|
DefaultLedStub::DefaultLedStub() : initialized(false) {
|
||||||
for (int i = 0; i < NUM_LEDS; i++) {
|
for (int i = 0; i < NUM_LEDS; i++) {
|
||||||
@@ -16,7 +13,7 @@ DefaultLedStub::DefaultLedStub() : initialized(false) {
|
|||||||
|
|
||||||
void DefaultLedStub::begin() {
|
void DefaultLedStub::begin() {
|
||||||
initialized = true;
|
initialized = true;
|
||||||
ESP_LOGI(TAG, "LED stub initialized (GPIO pins not configured yet)");
|
Serial.println("[LED] Stub initialized (GPIO pins not configured yet)");
|
||||||
}
|
}
|
||||||
|
|
||||||
void DefaultLedStub::set_led_state(uint8_t note, uint8_t channel, uint8_t velocity) {
|
void DefaultLedStub::set_led_state(uint8_t note, uint8_t channel, uint8_t velocity) {
|
||||||
@@ -29,13 +26,13 @@ void DefaultLedStub::set_led_state(uint8_t note, uint8_t channel, uint8_t veloci
|
|||||||
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].active = (velocity > 0);
|
led_states[led_index].active = (velocity > 0);
|
||||||
led_states[led_index].timestamp = 0;
|
led_states[led_index].timestamp = millis();
|
||||||
|
|
||||||
ESP_LOGI(TAG, "LED STATE: Note %d -> LED %d Channel %d Velocity %d (%s)",
|
Serial.printf("[LED] Note %d -> LED %d Ch %d Vel %d (%s)\n",
|
||||||
note, led_index, channel, velocity,
|
note, led_index, channel, velocity,
|
||||||
velocity > 0 ? "ON" : "OFF");
|
velocity > 0 ? "ON" : "OFF");
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "LED index out of range: %d (Note: %d)", led_index, note);
|
Serial.printf("[LED] Index out of range: %d (Note: %d)\n", led_index, note);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,5 +41,5 @@ void DefaultLedStub::clear_all() {
|
|||||||
led_states[i].active = false;
|
led_states[i].active = false;
|
||||||
led_states[i].velocity = 0;
|
led_states[i].velocity = 0;
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "All LEDs cleared");
|
Serial.println("[LED] All LEDs cleared");
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
// components/hal/switch_stub.cpp
|
#include "switch_stub.h"
|
||||||
#include "hal/switch_stub.h"
|
#include <Arduino.h>
|
||||||
#include "esp_log.h"
|
|
||||||
|
|
||||||
static const char* TAG = "switch_stub";
|
|
||||||
|
|
||||||
DefaultSwitchStub::DefaultSwitchStub() : initialized(false) {
|
DefaultSwitchStub::DefaultSwitchStub() : initialized(false) {
|
||||||
for (int i = 0; i < NUM_SWITCHES; i++) {
|
for (int i = 0; i < NUM_SWITCHES; i++) {
|
||||||
@@ -17,7 +14,7 @@ DefaultSwitchStub::DefaultSwitchStub() : initialized(false) {
|
|||||||
|
|
||||||
void DefaultSwitchStub::begin() {
|
void DefaultSwitchStub::begin() {
|
||||||
initialized = true;
|
initialized = true;
|
||||||
ESP_LOGI(TAG, "Switch stub initialized (GPIO pins not configured yet)");
|
Serial.println("[SW] Stub initialized (GPIO pins not configured yet)");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DefaultSwitchStub::is_pressed(uint8_t switch_id) {
|
bool DefaultSwitchStub::is_pressed(uint8_t switch_id) {
|
||||||
@@ -30,12 +27,12 @@ bool DefaultSwitchStub::is_pressed(uint8_t switch_id) {
|
|||||||
void DefaultSwitchStub::configure_switch(uint8_t switch_id, uint8_t gpio_pin) {
|
void DefaultSwitchStub::configure_switch(uint8_t switch_id, uint8_t gpio_pin) {
|
||||||
if (switch_id >= NUM_SWITCHES) return;
|
if (switch_id >= NUM_SWITCHES) return;
|
||||||
switch_states[switch_id].gpio_pin = gpio_pin;
|
switch_states[switch_id].gpio_pin = gpio_pin;
|
||||||
ESP_LOGI(TAG, "Switch %d configured to GPIO %d", switch_id, gpio_pin);
|
Serial.printf("[SW] Switch %d configured to GPIO %d\n", switch_id, gpio_pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DefaultSwitchStub::set_debounce_time(uint32_t time_ms) {
|
void DefaultSwitchStub::set_debounce_time(uint32_t time_ms) {
|
||||||
for (int i = 0; i < NUM_SWITCHES; i++) {
|
for (int i = 0; i < NUM_SWITCHES; i++) {
|
||||||
switch_states[i].debounce_time = time_ms;
|
switch_states[i].debounce_time = time_ms;
|
||||||
}
|
}
|
||||||
ESP_LOGI(TAG, "Debounce time set to %lu ms", time_ms);
|
Serial.printf("[SW] Debounce time set to %lu ms\n", time_ms);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#include "midi_transport.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <USB.h>
|
||||||
|
#include <USBMIDI.h>
|
||||||
|
|
||||||
|
// TinyUSB MIDI interface
|
||||||
|
static USBMIDI MIDI;
|
||||||
|
|
||||||
|
UsbMidiTransport::UsbMidiTransport() : initialized(false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
UsbMidiTransport::~UsbMidiTransport() {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UsbMidiTransport::begin() {
|
||||||
|
USB.begin();
|
||||||
|
MIDI.begin(MIDI_CHANNEL_OMNI);
|
||||||
|
initialized = true;
|
||||||
|
Serial.println("[MIDI] USB MIDI transport initialized");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UsbMidiTransport::update() {
|
||||||
|
if (!initialized) return;
|
||||||
|
|
||||||
|
// Check for incoming MIDI
|
||||||
|
if (MIDI.read()) {
|
||||||
|
MidiEvent event;
|
||||||
|
midi_message_t msg = MIDI.getMessage();
|
||||||
|
|
||||||
|
event.channel = msg.channel;
|
||||||
|
event.data1 = msg.data1;
|
||||||
|
event.data2 = msg.data2;
|
||||||
|
event.timestamp = millis();
|
||||||
|
|
||||||
|
// Map Arduino MIDI types to our types
|
||||||
|
switch (msg.type) {
|
||||||
|
case midi::NoteOn:
|
||||||
|
event.type = (msg.data2 > 0) ? MidiEvent::NOTE_ON : MidiEvent::NOTE_OFF;
|
||||||
|
break;
|
||||||
|
case midi::NoteOff:
|
||||||
|
event.type = MidiEvent::NOTE_OFF;
|
||||||
|
break;
|
||||||
|
case midi::ControlChange:
|
||||||
|
event.type = MidiEvent::CONTROL_CHANGE;
|
||||||
|
break;
|
||||||
|
case midi::ProgramChange:
|
||||||
|
event.type = MidiEvent::PROGRAM_CHANGE;
|
||||||
|
break;
|
||||||
|
case midi::PitchBend:
|
||||||
|
event.type = MidiEvent::PITCH_BEND;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return; // Unknown type, skip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diagnostic logging
|
||||||
|
const char* type_str = "UNK";
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Call callback
|
||||||
|
if (receive_callback) {
|
||||||
|
receive_callback(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UsbMidiTransport::on_midi_receive(std::function<void(const MidiEvent&)> callback) {
|
||||||
|
receive_callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UsbMidiTransport::send_note_on(uint8_t channel, uint8_t note, uint8_t velocity) {
|
||||||
|
if (!initialized) return;
|
||||||
|
MIDI.sendNoteOn(note, velocity, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UsbMidiTransport::send_note_off(uint8_t channel, uint8_t note, uint8_t velocity) {
|
||||||
|
if (!initialized) return;
|
||||||
|
MIDI.sendNoteOff(note, velocity, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UsbMidiTransport::send_cc(uint8_t channel, uint8_t cc, uint8_t value) {
|
||||||
|
if (!initialized) return;
|
||||||
|
MIDI.sendControlChange(cc, value, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UsbMidiTransport::parse_midi_packet(const uint8_t* buffer, uint32_t size, MidiEvent& event) {
|
||||||
|
// Not used with Arduino MIDI library - kept for reference
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
idf_component_register(SRCS "../main.cpp"
|
|
||||||
INCLUDE_DIRS "."
|
|
||||||
REQUIRES controller midi hal)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
## IDF Component Manager Manifest File
|
|
||||||
dependencies:
|
|
||||||
idf:
|
|
||||||
version: ">=5.0.0"
|
|
||||||
espressif/esp_tinyusb:
|
|
||||||
version: ">=1.0.0"
|
|
||||||
rules:
|
|
||||||
- if: "target == esp32s3"
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
; PlatformIO project configuration
|
||||||
|
; ESP32-S3 Loopy Pro Launchpad-Compatible Foot Controller
|
||||||
|
|
||||||
|
[env:esp32s3]
|
||||||
|
platform = espressif32
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
framework = arduino
|
||||||
|
|
||||||
|
; USB settings for MIDI
|
||||||
|
build_flags =
|
||||||
|
-DARDUINO_USB_MODE=1
|
||||||
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
-DUSE_TINYUSB
|
||||||
|
|
||||||
|
; Serial monitoring
|
||||||
|
monitor_speed = 115200
|
||||||
|
|
||||||
|
; Partition scheme with more space
|
||||||
|
board_build.partitions = default_8MB.csv
|
||||||
|
board_build.arduino.memory_type = qio_opi
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# ESP32-S3 Configuration
|
|
||||||
CONFIG_IDF_TARGET="esp32s3"
|
|
||||||
|
|
||||||
# USB Configuration
|
|
||||||
CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Ashley Strahle"
|
|
||||||
CONFIG_TINYUSB_DESC_PRODUCT_STRING="Loopy Foot Controller"
|
|
||||||
CONFIG_TINYUSB_DESC_CDC_STRING="Loopy Foot Controller"
|
|
||||||
CONFIG_TINYUSB_MIDI_ENABLED=y
|
|
||||||
CONFIG_TINYUSB_MIDI_RX_BUFSIZE=64
|
|
||||||
CONFIG_TINYUSB_MIDI_TX_BUFSIZE=64
|
|
||||||
|
|
||||||
# FreeRTOS
|
|
||||||
CONFIG_FREERTOS_HZ=1000
|
|
||||||
|
|
||||||
# Log level
|
|
||||||
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Loopy MIDI Controller - Phase 1
|
||||||
|
// ESP32-S3 USB MIDI Foot Controller
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "midi_transport.h"
|
||||||
|
#include "led_stub.h"
|
||||||
|
#include "switch_stub.h"
|
||||||
|
#include "app_task.h"
|
||||||
|
|
||||||
|
// Hardware instances
|
||||||
|
DefaultLedStub led_driver;
|
||||||
|
DefaultSwitchStub switch_driver;
|
||||||
|
UsbMidiTransport midi_transport;
|
||||||
|
|
||||||
|
// Controller task
|
||||||
|
AppTask controller(&led_driver, &switch_driver, &midi_transport);
|
||||||
|
|
||||||
|
// FreeRTOS task handles
|
||||||
|
TaskHandle_t midi_task_handle = NULL;
|
||||||
|
|
||||||
|
// MIDI processing task (runs on core 0)
|
||||||
|
void midi_task(void* parameter) {
|
||||||
|
Serial.println("[TASK] MIDI task started on core 0");
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
midi_transport.update();
|
||||||
|
vTaskDelay(1); // Yield to other tasks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
Serial.println("=============================");
|
||||||
|
Serial.println("Loopy MIDI Controller v0.1");
|
||||||
|
Serial.println("Phase 1: USB MIDI");
|
||||||
|
Serial.println("=============================");
|
||||||
|
|
||||||
|
// Initialize hardware stubs
|
||||||
|
led_driver.begin();
|
||||||
|
switch_driver.begin();
|
||||||
|
|
||||||
|
// Initialize MIDI transport
|
||||||
|
midi_transport.begin();
|
||||||
|
|
||||||
|
// Initialize controller
|
||||||
|
controller.begin();
|
||||||
|
|
||||||
|
// Create MIDI task on core 0 (high priority)
|
||||||
|
xTaskCreatePinnedToCore(
|
||||||
|
midi_task,
|
||||||
|
"midi_task",
|
||||||
|
4096,
|
||||||
|
NULL,
|
||||||
|
3,
|
||||||
|
&midi_task_handle,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
Serial.println("[INIT] All systems ready");
|
||||||
|
Serial.println("=============================");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// Controller task runs on core 1 (main Arduino loop)
|
||||||
|
controller.update();
|
||||||
|
delay(10); // 10ms loop period
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user