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:
2026-06-23 12:55:12 +00:00
parent 458cb5060f
commit 9078001404
20 changed files with 341 additions and 355 deletions
-7
View File
@@ -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)
-3
View File
@@ -1,3 +0,0 @@
idf_component_register(SRCS "app_task.cpp"
INCLUDE_DIRS "."
REQUIRES midi hal)
-110
View File
@@ -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;
}
}
}
-22
View File
@@ -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);
-3
View File
@@ -1,3 +0,0 @@
idf_component_register(SRCS "led_stub.cpp" "switch_stub.cpp"
INCLUDE_DIRS "."
REQUIRES )
-3
View File
@@ -1,3 +0,0 @@
idf_component_register(SRCS "midi_transport.cpp"
INCLUDE_DIRS "."
REQUIRES driver)
-127
View File
@@ -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));
}
}
+33
View File
@@ -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;
+88
View File
@@ -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);
} }
+97
View File
@@ -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
}
-3
View File
@@ -1,3 +0,0 @@
idf_component_register(SRCS "../main.cpp"
INCLUDE_DIRS "."
REQUIRES controller midi hal)
-8
View File
@@ -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"
+20
View File
@@ -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
-16
View File
@@ -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
+69
View File
@@ -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
}