Add SysEx handling for Launchpad X Programmer mode
- Parse all SysEx CIN types (start, continue, end) - Reassemble multi-packet SysEx messages - Handle layout select (Programmer mode = 0x7F) - Handle Programmer/Live mode switch
This commit is contained in:
@@ -29,6 +29,14 @@ private:
|
||||
PadMapping pad_mapping[NUM_PADS];
|
||||
bool last_switch_state[NUM_PADS];
|
||||
|
||||
// SysEx reassembly buffer
|
||||
static const uint8_t SYSEX_MAX_LEN = 64;
|
||||
uint8_t sysex_buffer[SYSEX_MAX_LEN];
|
||||
uint8_t sysex_len = 0;
|
||||
bool sysex_active = false;
|
||||
|
||||
void process_switch_event(uint8_t switch_id, bool pressed);
|
||||
void run_palette_test();
|
||||
void handle_sysex(const uint8_t* data, uint8_t len);
|
||||
void process_sysex_packet(const uint8_t* packet, uint8_t cin);
|
||||
};
|
||||
|
||||
@@ -50,6 +50,14 @@ void AppTask::process_midi_event(const MidiEvent& event) {
|
||||
// (visible without serial when connected to iPad)
|
||||
led_driver->flash_activity();
|
||||
|
||||
if (event.type == MidiEvent::SYSEX) {
|
||||
// Cin is encoded in channel for SYSEX packets
|
||||
uint8_t cin = event.channel;
|
||||
uint8_t packet[3] = {event.data1, event.data2, 0};
|
||||
process_sysex_packet(packet, cin);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t led_index = 0xFF;
|
||||
uint8_t midi_channel = event.channel;
|
||||
uint8_t midi_note = event.data1;
|
||||
@@ -137,3 +145,54 @@ void AppTask::process_switch_event(uint8_t switch_id, bool pressed) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AppTask::process_sysex_packet(const uint8_t* packet, uint8_t cin) {
|
||||
// Cin values: 0x4=start/short, 0x5=continue, 0x6=end (2 bytes), 0x7=end (1 byte/3 bytes)
|
||||
|
||||
if (cin == 0x4) { // SysEx start
|
||||
sysex_active = true;
|
||||
sysex_len = 0;
|
||||
}
|
||||
|
||||
if (!sysex_active || sysex_len >= SYSEX_MAX_LEN) return;
|
||||
|
||||
// Add data bytes (skip F0/F7 which are handled by Cin)
|
||||
sysex_buffer[sysex_len++] = packet[0];
|
||||
if (cin == 0x4 || cin == 0x5 || cin == 0x6) {
|
||||
sysex_buffer[sysex_len++] = packet[1];
|
||||
}
|
||||
|
||||
if (cin == 0x6 || cin == 0x7) { // SysEx end
|
||||
sysex_active = false;
|
||||
handle_sysex(sysex_buffer, sysex_len);
|
||||
sysex_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void AppTask::handle_sysex(const uint8_t* data, uint8_t len) {
|
||||
if (len < 7) return;
|
||||
|
||||
// Check Novation SysEx header: F0 00 20 29 02 0C/0D ...
|
||||
if (data[0] != 0x00 || data[1] != 0x20 || data[2] != 0x29 || data[3] != 0x02) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t sub_id = data[4];
|
||||
uint8_t command = data[5];
|
||||
|
||||
Serial.printf("[APP] SysEx: sub=%02X cmd=%02X len=%d\n", sub_id, command, len);
|
||||
|
||||
// Command 0x00 = Layout select, 0x0E = Programmer/Live mode
|
||||
if (command == 0x00 && len >= 7) { // Layout select
|
||||
uint8_t layout = data[6];
|
||||
Serial.printf("[APP] Layout select: %02X\n", layout);
|
||||
|
||||
// 0x7F = Programmer mode
|
||||
if (layout == 0x7F) {
|
||||
Serial.println("[APP] Entered Programmer mode");
|
||||
}
|
||||
} else if (command == 0x0E && len >= 7) { // Programmer/Live mode
|
||||
uint8_t mode = data[6];
|
||||
Serial.printf("[APP] Programmer mode: %02X\n", mode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,10 @@ void UsbMidiTransport::parse_midi_packet(const uint8_t* buffer, uint32_t size, M
|
||||
event.data2 = buffer[3];
|
||||
|
||||
switch (cin) {
|
||||
case 0x4: event.type = MidiEvent::SYSEX; break; // SysEx start/short
|
||||
case 0x5: event.type = MidiEvent::SYSEX; break; // SysEx continue
|
||||
case 0x6: event.type = MidiEvent::SYSEX; break; // SysEx end
|
||||
case 0x7: event.type = MidiEvent::SYSEX; break; // SysEx end (single byte)
|
||||
case 0x8: event.type = MidiEvent::NOTE_OFF; break;
|
||||
case 0x9:
|
||||
event.type = (event.data2 > 0) ? MidiEvent::NOTE_ON : MidiEvent::NOTE_OFF;
|
||||
|
||||
Reference in New Issue
Block a user