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
|
||||
|
||||
#include <cstdint>
|
||||
@@ -11,22 +10,19 @@ public:
|
||||
virtual void set_led_state(uint8_t note, uint8_t channel, uint8_t velocity) = 0;
|
||||
virtual void clear_all() = 0;
|
||||
|
||||
// Helper function to map MIDI note to LED index
|
||||
virtual uint8_t note_to_index(uint8_t note) {
|
||||
return note;
|
||||
}
|
||||
};
|
||||
|
||||
// LED state structure
|
||||
struct LedState {
|
||||
uint8_t note; // Launchpad note
|
||||
uint8_t channel; // LED channel (1-3)
|
||||
uint8_t velocity; // Color/brightness (0-127)
|
||||
uint32_t timestamp; // When state was set
|
||||
bool active; // Current on/off state
|
||||
uint8_t note;
|
||||
uint8_t channel;
|
||||
uint8_t velocity;
|
||||
uint32_t timestamp;
|
||||
bool active;
|
||||
};
|
||||
|
||||
// Default stub implementation
|
||||
class DefaultLedStub : public LedStub {
|
||||
private:
|
||||
static const uint8_t NUM_LEDS = 10;
|
||||
@@ -1,9 +1,7 @@
|
||||
// components/midi/midi_transport.h
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <functional>
|
||||
|
||||
struct MidiEvent {
|
||||
enum Type {
|
||||
@@ -29,21 +27,19 @@ public:
|
||||
~UsbMidiTransport();
|
||||
|
||||
bool begin();
|
||||
void task();
|
||||
void update();
|
||||
|
||||
// Event queue for communication with controller task
|
||||
QueueHandle_t get_event_queue() const { return event_queue; }
|
||||
// Callback registration
|
||||
void on_midi_receive(std::function<void(const MidiEvent&)> callback);
|
||||
|
||||
// Diagnostic logging
|
||||
void log_incoming(const char* source, const MidiEvent& event);
|
||||
// Send MIDI
|
||||
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:
|
||||
QueueHandle_t event_queue;
|
||||
std::function<void(const MidiEvent&)> receive_callback;
|
||||
bool initialized;
|
||||
|
||||
// MIDI packet parsing
|
||||
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
|
||||
|
||||
#include <cstdint>
|
||||
@@ -9,23 +8,19 @@ public:
|
||||
|
||||
virtual void begin() = 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 set_debounce_time(uint32_t time_ms) = 0;
|
||||
};
|
||||
|
||||
// Switch state structure
|
||||
struct SwitchState {
|
||||
uint8_t id; // Switch identifier
|
||||
uint8_t gpio_pin; // GPIO pin (if applicable)
|
||||
bool current_state; // Current pressed state
|
||||
bool previous_state; // Previous state (for debounce)
|
||||
uint32_t last_change_time; // Timestamp of last state change
|
||||
uint32_t debounce_time; // Debounce time in ms
|
||||
uint8_t id;
|
||||
uint8_t gpio_pin;
|
||||
bool current_state;
|
||||
bool previous_state;
|
||||
uint32_t last_change_time;
|
||||
uint32_t debounce_time;
|
||||
};
|
||||
|
||||
// Default stub implementation
|
||||
class DefaultSwitchStub : public SwitchStub {
|
||||
private:
|
||||
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 "hal/led_stub.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char* TAG = "led_stub";
|
||||
#include "led_stub.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
DefaultLedStub::DefaultLedStub() : initialized(false) {
|
||||
for (int i = 0; i < NUM_LEDS; i++) {
|
||||
@@ -16,7 +13,7 @@ DefaultLedStub::DefaultLedStub() : initialized(false) {
|
||||
|
||||
void DefaultLedStub::begin() {
|
||||
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) {
|
||||
@@ -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].velocity = velocity;
|
||||
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,
|
||||
velocity > 0 ? "ON" : "OFF");
|
||||
} 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].velocity = 0;
|
||||
}
|
||||
ESP_LOGI(TAG, "All LEDs cleared");
|
||||
Serial.println("[LED] All LEDs cleared");
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
// components/hal/switch_stub.cpp
|
||||
#include "hal/switch_stub.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char* TAG = "switch_stub";
|
||||
#include "switch_stub.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
DefaultSwitchStub::DefaultSwitchStub() : initialized(false) {
|
||||
for (int i = 0; i < NUM_SWITCHES; i++) {
|
||||
@@ -17,7 +14,7 @@ DefaultSwitchStub::DefaultSwitchStub() : initialized(false) {
|
||||
|
||||
void DefaultSwitchStub::begin() {
|
||||
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) {
|
||||
@@ -30,12 +27,12 @@ bool DefaultSwitchStub::is_pressed(uint8_t switch_id) {
|
||||
void DefaultSwitchStub::configure_switch(uint8_t switch_id, uint8_t gpio_pin) {
|
||||
if (switch_id >= NUM_SWITCHES) return;
|
||||
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) {
|
||||
for (int i = 0; i < NUM_SWITCHES; i++) {
|
||||
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