diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index e79e189..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -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) diff --git a/components/controller/CMakeLists.txt b/components/controller/CMakeLists.txt deleted file mode 100644 index c1f58b4..0000000 --- a/components/controller/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -idf_component_register(SRCS "app_task.cpp" - INCLUDE_DIRS "." - REQUIRES midi hal) diff --git a/components/controller/app_task.cpp b/components/controller/app_task.cpp deleted file mode 100644 index 1f76ca6..0000000 --- a/components/controller/app_task.cpp +++ /dev/null @@ -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; - } - } -} \ No newline at end of file diff --git a/components/controller/app_task.h b/components/controller/app_task.h deleted file mode 100644 index 0ba5fa2..0000000 --- a/components/controller/app_task.h +++ /dev/null @@ -1,22 +0,0 @@ -// components/controller/app_task.h -#pragma once - -#include -#include -#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); diff --git a/components/hal/CMakeLists.txt b/components/hal/CMakeLists.txt deleted file mode 100644 index a08ca51..0000000 --- a/components/hal/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -idf_component_register(SRCS "led_stub.cpp" "switch_stub.cpp" - INCLUDE_DIRS "." - REQUIRES ) diff --git a/components/midi/CMakeLists.txt b/components/midi/CMakeLists.txt deleted file mode 100644 index 2249454..0000000 --- a/components/midi/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -idf_component_register(SRCS "midi_transport.cpp" - INCLUDE_DIRS "." - REQUIRES driver) diff --git a/components/midi/midi_transport.cpp b/components/midi/midi_transport.cpp deleted file mode 100644 index 5099e56..0000000 --- a/components/midi/midi_transport.cpp +++ /dev/null @@ -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(pvParameters); - - ESP_LOGI(TAG, "USB MIDI task started"); - - while (true) { - transport->task(); - vTaskDelay(pdMS_TO_TICKS(1)); - } -} diff --git a/include/app_task.h b/include/app_task.h new file mode 100644 index 0000000..fa3096c --- /dev/null +++ b/include/app_task.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#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); +}; diff --git a/components/hal/led_stub.h b/include/led_stub.h similarity index 63% rename from components/hal/led_stub.h rename to include/led_stub.h index 260bc2c..cd1a858 100644 --- a/components/hal/led_stub.h +++ b/include/led_stub.h @@ -1,4 +1,3 @@ -// components/hal/led_stub.h #pragma once #include @@ -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; diff --git a/components/midi/midi_transport.h b/include/midi_transport.h similarity index 59% rename from components/midi/midi_transport.h rename to include/midi_transport.h index 05ba4d1..07d0cdb 100644 --- a/components/midi/midi_transport.h +++ b/include/midi_transport.h @@ -1,9 +1,7 @@ -// components/midi/midi_transport.h #pragma once #include -#include -#include +#include 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 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 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); diff --git a/components/hal/switch_stub.h b/include/switch_stub.h similarity index 61% rename from components/hal/switch_stub.h rename to include/switch_stub.h index 513df2f..e7c2400 100644 --- a/components/hal/switch_stub.h +++ b/include/switch_stub.h @@ -1,4 +1,3 @@ -// components/hal/switch_stub.h #pragma once #include @@ -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; diff --git a/lib/controller/app_task.cpp b/lib/controller/app_task.cpp new file mode 100644 index 0000000..6d3c2fc --- /dev/null +++ b/lib/controller/app_task.cpp @@ -0,0 +1,88 @@ +#include "app_task.h" +#include + +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; + } + } +} diff --git a/components/hal/led_stub.cpp b/lib/hal/led_stub.cpp similarity index 63% rename from components/hal/led_stub.cpp rename to lib/hal/led_stub.cpp index f08151a..7e100e2 100644 --- a/components/hal/led_stub.cpp +++ b/lib/hal/led_stub.cpp @@ -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 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)", - note, led_index, channel, velocity, - velocity > 0 ? "ON" : "OFF"); + 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"); } diff --git a/components/hal/switch_stub.cpp b/lib/hal/switch_stub.cpp similarity index 74% rename from components/hal/switch_stub.cpp rename to lib/hal/switch_stub.cpp index 1cc1b97..b68ffb5 100644 --- a/components/hal/switch_stub.cpp +++ b/lib/hal/switch_stub.cpp @@ -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 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); } diff --git a/lib/midi/midi_transport.cpp b/lib/midi/midi_transport.cpp new file mode 100644 index 0000000..0a516d6 --- /dev/null +++ b/lib/midi/midi_transport.cpp @@ -0,0 +1,97 @@ +#include "midi_transport.h" +#include +#include +#include + +// 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 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 +} diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt deleted file mode 100644 index c4fbd1a..0000000 --- a/main/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -idf_component_register(SRCS "../main.cpp" - INCLUDE_DIRS "." - REQUIRES controller midi hal) diff --git a/main/idf_component.yml b/main/idf_component.yml deleted file mode 100644 index dd66ab6..0000000 --- a/main/idf_component.yml +++ /dev/null @@ -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" diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..f9fac45 --- /dev/null +++ b/platformio.ini @@ -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 diff --git a/sdkconfig.defaults b/sdkconfig.defaults deleted file mode 100644 index 865fdf2..0000000 --- a/sdkconfig.defaults +++ /dev/null @@ -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 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..6301452 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,69 @@ +// Loopy MIDI Controller - Phase 1 +// ESP32-S3 USB MIDI Foot Controller + +#include +#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 +}