diff --git a/include/app_task.h b/include/app_task.h index 19fdebe..652d7a0 100644 --- a/include/app_task.h +++ b/include/app_task.h @@ -28,6 +28,7 @@ private: static const uint8_t NUM_PADS = 10; PadMapping pad_mapping[NUM_PADS]; bool last_switch_state[NUM_PADS]; + uint8_t cc_map[NUM_PADS]; // SysEx reassembly buffer static const uint8_t SYSEX_MAX_LEN = 64; diff --git a/src/app_task.cpp b/src/app_task.cpp index 8a521ae..d2c509c 100644 --- a/src/app_task.cpp +++ b/src/app_task.cpp @@ -6,12 +6,14 @@ AppTask::AppTask(LedStub* led, SwitchStub* sw, UsbMidiTransport* midi) // Launchpad X standard: bottom row = notes 36-45 (C2 to A2) on channel 1 const uint8_t launchpad_notes[10] = {36, 37, 38, 39, 40, 41, 42, 43, 44, 45}; + const uint8_t cc_assignments[10] = {112, 22, 113, 25, 114, 24, 115, 26, 116, 117}; 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 = launchpad_notes[i]; pad_mapping[i].led_index = i; + cc_map[i] = cc_assignments[i]; last_switch_state[i] = false; } } @@ -23,7 +25,10 @@ void AppTask::begin() { process_midi_event(event); }); - Serial.println("[APP] Controller ready - Launchpad X mode (notes 36-45, ch1)"); + Serial.println("[APP] Controller ready - CC mode"); + for (uint8_t i = 0; i < NUM_PADS; i++) { + Serial.printf("[APP] Pad %d -> CC%d -> LED%d\n", i + 1, cc_map[i], i); + } } void AppTask::update() { @@ -92,18 +97,16 @@ void AppTask::process_midi_event(const MidiEvent& event) { Serial.printf("[APP] NOTE Ch%d ignored (not Launchpad channel 1-3)\n", midi_channel); } } - // CONTROL_CHANGE fallback for generic MIDI / Loopy Pro generic mode + // CONTROL_CHANGE: look up which pad this CC belongs to else if (event.type == MidiEvent::CONTROL_CHANGE) { uint8_t cc_num = event.data1; uint8_t cc_val = event.data2; - // Map CC to pad: CC2-11 (Loopy Pro), CC0-9, CC36-45 - if (cc_num >= 2 && cc_num < 2 + NUM_PADS) { - led_index = cc_num - 2; - } else if (cc_num < NUM_PADS) { - led_index = cc_num; - } else if (cc_num >= 36 && cc_num < 36 + NUM_PADS) { - led_index = cc_num - 36; + for (uint8_t i = 0; i < NUM_PADS; i++) { + if (cc_map[i] == cc_num) { + led_index = i; + break; + } } if (led_index < NUM_PADS) { @@ -113,8 +116,8 @@ void AppTask::process_midi_event(const MidiEvent& event) { cc_val, led_index ); - Serial.printf("[APP] CC -> LED: Ch%d CC%d Val%d -> LED%d\n", - midi_channel, cc_num, cc_val, led_index); + Serial.printf("[APP] CC%d Val%d -> LED%d\n", + cc_num, cc_val, led_index); } else { Serial.printf("[APP] CC Ch%d CC%d Val%d - no mapping\n", midi_channel, cc_num, cc_val); @@ -126,15 +129,12 @@ void AppTask::process_switch_event(uint8_t switch_id, bool pressed) { 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; - // Loopy Pro Generic mode mirrors CC back on the same CC it received - // CC2-11 maps to pads 0-9 - uint8_t cc_num = 2 + i; + uint8_t cc_num = cc_map[i]; uint8_t value = pressed ? 127 : 0; if (pressed) { midi_transport->send_cc(channel, cc_num, value); } - // Don't send CC 0 on release — Loopy Pro uses velocity 0 to turn off Serial.printf("[APP] Switch %d -> Ch%d CC%d Val%d (%s)\n", switch_id, channel, cc_num, value,