From 150615b2c35be606638ad03543bb687c317e3c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas=20Stephan?= Date: Mon, 1 Sep 2025 23:13:31 -0300 Subject: [PATCH 1/3] feat(midi-cc-ab-preset-selection): add MIDI CC 120/121 for independent A/B slot targeting - Add CC 120: Load Preset to Slot A (values 0-19) - Add CC 121: Load Preset to Slot B (values 0-19) - Both commands bypass global slot configuration - Both commands load without switching active slot (selectSlot=0) - Maintain full backward compatibility with CC 127 - Add proper validation and error logging - Update MIDI commands documentation Resolves independent A/B preset loading for HX Stomp workflows --- .gitignore | 3 ++- MidiCommands.md | 2 ++ source/main/midi_helper.c | 34 ++++++++++++++++++++++++++++++++- source/main/usb_tonex_one.c | 38 +++++++++++++++++++++++++++++++++++++ source/main/usb_tonex_one.h | 4 ++++ 5 files changed, 79 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 34d8ad56..3eb9f0b4 100644 --- a/.gitignore +++ b/.gitignore @@ -23,5 +23,6 @@ ui_design_480x320land/backup ui_design_480x320land/cache ui_design_480x320land/autosave - +# Cursor +.cursor/ diff --git a/MidiCommands.md b/MidiCommands.md index d5e118a8..1055d45c 100644 --- a/MidiCommands.md +++ b/MidiCommands.md @@ -116,4 +116,6 @@ This project uses a low-cost embedded controller (Espressif ESP32-S3) to form a | Cab Sim Bypass | 117 | On: 127, Off: 0 | | Global Tempo Source | 118 | Global: 127, Local: 0 | | Global Tuning Reference | 119 | 0-127 | +| Load Preset to Slot A | 120 | 0-19 | +| Load Preset to Slot B | 121 | 0-19 | | Select Preset | 127 | 0-19 | diff --git a/source/main/midi_helper.c b/source/main/midi_helper.c index 670d1e9c..30a5f674 100644 --- a/source/main/midi_helper.c +++ b/source/main/midi_helper.c @@ -1227,12 +1227,44 @@ esp_err_t midi_helper_adjust_param_via_midi(uint8_t change_num, uint8_t midi_val value = tonex_params_clamp_value(param, value); } break; + case 120: + { + // Load preset to Slot A without switching to it + if (midi_value >= (usb_get_max_presets_for_connected_modeller())) + { + ESP_LOGW(TAG, "Unsupported Midi CC 120 value %d (max %d)", midi_value, usb_get_max_presets_for_connected_modeller() - 1); + } + else + { + usb_tonex_one_load_preset_to_slot_a(midi_value); + } + + // no param change needed + return ESP_OK; + } break; + + case 121: + { + // Load preset to Slot B without switching to it + if (midi_value >= (usb_get_max_presets_for_connected_modeller())) + { + ESP_LOGW(TAG, "Unsupported Midi CC 121 value %d (max %d)", midi_value, usb_get_max_presets_for_connected_modeller() - 1); + } + else + { + usb_tonex_one_load_preset_to_slot_b(midi_value); + } + + // no param change needed + return ESP_OK; + } break; + case 127: { // Custom case: use CC to change params. if (midi_value >= (usb_get_max_presets_for_connected_modeller())) { - ESP_LOGW(TAG, "Unsupported Midi CC 127 value %d", change_num); + ESP_LOGW(TAG, "Unsupported Midi CC 127 value %d", midi_value); } else { diff --git a/source/main/usb_tonex_one.c b/source/main/usb_tonex_one.c index 9be9cfd7..a692f7fb 100644 --- a/source/main/usb_tonex_one.c +++ b/source/main/usb_tonex_one.c @@ -431,6 +431,44 @@ static esp_err_t __attribute__((unused)) usb_tonex_one_set_active_slot(Slot newS return tonex_common_transmit(cdc_dev, FramedBuffer, framed_length, TONEX_USB_TX_BUFFER_SIZE); } +/**************************************************************************** +* NAME: usb_tonex_one_load_preset_to_slot_a +* DESCRIPTION: Load a preset to Slot A without switching to it (MIDI CC 120) +* PARAMETERS: preset - Preset index (0-19 for presets 1-20) +* RETURN: ESP_OK on success, ESP_FAIL on error +* NOTES: Bypasses CONFIG_ITEM_SAVE_PRESET_TO_SLOT configuration +*****************************************************************************/ +esp_err_t usb_tonex_one_load_preset_to_slot_a(uint16_t preset) +{ + if (preset >= MAX_PRESETS_TONEX_ONE) + { + ESP_LOGW(TAG, "Invalid preset index %d for Slot A (max %d)", preset, MAX_PRESETS_TONEX_ONE - 1); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Loading preset %d to Slot A via MIDI CC 120", preset); + return usb_tonex_one_set_preset_in_slot(preset, A, 0); +} + +/**************************************************************************** +* NAME: usb_tonex_one_load_preset_to_slot_b +* DESCRIPTION: Load a preset to Slot B without switching to it (MIDI CC 121) +* PARAMETERS: preset - Preset index (0-19 for presets 1-20) +* RETURN: ESP_OK on success, ESP_FAIL on error +* NOTES: Bypasses CONFIG_ITEM_SAVE_PRESET_TO_SLOT configuration +*****************************************************************************/ +esp_err_t usb_tonex_one_load_preset_to_slot_b(uint16_t preset) +{ + if (preset >= MAX_PRESETS_TONEX_ONE) + { + ESP_LOGW(TAG, "Invalid preset index %d for Slot B (max %d)", preset, MAX_PRESETS_TONEX_ONE - 1); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Loading preset %d to Slot B via MIDI CC 121", preset); + return usb_tonex_one_set_preset_in_slot(preset, B, 0); +} + /**************************************************************************** * NAME: * DESCRIPTION: diff --git a/source/main/usb_tonex_one.h b/source/main/usb_tonex_one.h index bea26464..3eb678f9 100644 --- a/source/main/usb_tonex_one.h +++ b/source/main/usb_tonex_one.h @@ -30,6 +30,10 @@ void usb_tonex_one_init(class_driver_t* driver_obj, QueueHandle_t comms_queue); void usb_tonex_one_deinit(void); void usb_tonex_one_preallocate_memory(void); +// MIDI CC slot targeting functions +esp_err_t usb_tonex_one_load_preset_to_slot_a(uint16_t preset); +esp_err_t usb_tonex_one_load_preset_to_slot_b(uint16_t preset); + #ifdef __cplusplus } /*extern "C"*/ #endif From 951b266fc5325be02712822ea3763926098a941f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas=20Stephan?= Date: Fri, 5 Sep 2025 19:48:13 -0300 Subject: [PATCH 2/3] chore: undo changes to .gitignore --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3eb9f0b4..813d1a78 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,4 @@ ui_design_480x320land/backup ui_design_480x320land/cache ui_design_480x320land/autosave -# Cursor -.cursor/ From 7357e392324c18746ab0b355b64727320c6d4b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas=20Stephan?= Date: Mon, 8 Sep 2025 10:35:46 -0300 Subject: [PATCH 3/3] feat: add thread-safe MIDI CC commands for independent A/B preset slot targeting - Add CC 120 (Load Preset to Slot A) and CC 121 (Load Preset to Slot B) - Implement thread-safe architecture using message queues instead of direct USB calls - Add USB_COMMAND_LOAD_PRESET_TO_SLOT_A/B commands to enum - Add usb_load_preset_to_slot_a/b() queue functions in usb_comms.c - Add command handlers in usb_tonex_one_handle() switch statement - Update MIDI handlers to use new thread-safe functions - Allows HX Stomp to independently load presets into both slots without forcing mode changes - Preserves TONEX ONE's native A/B toggle functionality and tuner access Addresses PR feedback regarding thread safety - replaced direct USB function calls from MIDI thread with proper message queue pattern as suggested by reviewer. --- source/main/midi_helper.c | 4 +-- source/main/usb_comms.c | 56 ++++++++++++++++++++++++++++++ source/main/usb_comms.h | 6 +++- source/main/usb_tonex_one.c | 69 +++++++++++++++++-------------------- 4 files changed, 95 insertions(+), 40 deletions(-) diff --git a/source/main/midi_helper.c b/source/main/midi_helper.c index b296e9b3..911b6f0c 100644 --- a/source/main/midi_helper.c +++ b/source/main/midi_helper.c @@ -1236,7 +1236,7 @@ esp_err_t midi_helper_adjust_param_via_midi(uint8_t change_num, uint8_t midi_val } else { - usb_tonex_one_load_preset_to_slot_a(midi_value); + usb_load_preset_to_slot_a(midi_value); } // no param change needed @@ -1252,7 +1252,7 @@ esp_err_t midi_helper_adjust_param_via_midi(uint8_t change_num, uint8_t midi_val } else { - usb_tonex_one_load_preset_to_slot_b(midi_value); + usb_load_preset_to_slot_b(midi_value); } // no param change needed diff --git a/source/main/usb_comms.c b/source/main/usb_comms.c index e3a15a5c..3efeefd5 100644 --- a/source/main/usb_comms.c +++ b/source/main/usb_comms.c @@ -441,6 +441,62 @@ void usb_modify_parameter(uint16_t index, float value) } } +/**************************************************************************** +* NAME: +* DESCRIPTION: +* PARAMETERS: +* RETURN: +* NOTES: +*****************************************************************************/ +void usb_load_preset_to_slot_a(uint32_t preset) +{ + tUSBMessage message; + + if (usb_input_queue == NULL) + { + ESP_LOGE(TAG, "usb_load_preset_to_slot_a queue null"); + } + else + { + message.Command = USB_COMMAND_LOAD_PRESET_TO_SLOT_A; + message.Payload = preset; + + // send to queue + if (xQueueSend(usb_input_queue, (void*)&message, 0) != pdPASS) + { + ESP_LOGE(TAG, "usb_load_preset_to_slot_a queue send failed!"); + } + } +} + +/**************************************************************************** +* NAME: +* DESCRIPTION: +* PARAMETERS: +* RETURN: +* NOTES: +*****************************************************************************/ +void usb_load_preset_to_slot_b(uint32_t preset) +{ + tUSBMessage message; + + if (usb_input_queue == NULL) + { + ESP_LOGE(TAG, "usb_load_preset_to_slot_b queue null"); + } + else + { + message.Command = USB_COMMAND_LOAD_PRESET_TO_SLOT_B; + message.Payload = preset; + + // send to queue + if (xQueueSend(usb_input_queue, (void*)&message, 0) != pdPASS) + { + ESP_LOGE(TAG, "usb_load_preset_to_slot_b queue send failed!"); + } + } +} + /**************************************************************************** * NAME: * DESCRIPTION: diff --git a/source/main/usb_comms.h b/source/main/usb_comms.h index 2aa311f6..9fdaf195 100644 --- a/source/main/usb_comms.h +++ b/source/main/usb_comms.h @@ -38,7 +38,9 @@ enum AmpModellers enum USB_Commands { USB_COMMAND_SET_PRESET, - USB_COMMAND_MODIFY_PARAMETER + USB_COMMAND_MODIFY_PARAMETER, + USB_COMMAND_LOAD_PRESET_TO_SLOT_A, + USB_COMMAND_LOAD_PRESET_TO_SLOT_B }; typedef struct @@ -61,6 +63,8 @@ void init_usb_comms(void); // thread safe public API void usb_set_preset(uint32_t preset); void usb_modify_parameter(uint16_t index, float value); +void usb_load_preset_to_slot_a(uint32_t preset); +void usb_load_preset_to_slot_b(uint32_t preset); uint8_t usb_get_max_presets_for_connected_modeller(void); uint8_t usb_get_first_preset_index_for_connected_modeller(void); diff --git a/source/main/usb_tonex_one.c b/source/main/usb_tonex_one.c index a692f7fb..78098457 100644 --- a/source/main/usb_tonex_one.c +++ b/source/main/usb_tonex_one.c @@ -431,43 +431,6 @@ static esp_err_t __attribute__((unused)) usb_tonex_one_set_active_slot(Slot newS return tonex_common_transmit(cdc_dev, FramedBuffer, framed_length, TONEX_USB_TX_BUFFER_SIZE); } -/**************************************************************************** -* NAME: usb_tonex_one_load_preset_to_slot_a -* DESCRIPTION: Load a preset to Slot A without switching to it (MIDI CC 120) -* PARAMETERS: preset - Preset index (0-19 for presets 1-20) -* RETURN: ESP_OK on success, ESP_FAIL on error -* NOTES: Bypasses CONFIG_ITEM_SAVE_PRESET_TO_SLOT configuration -*****************************************************************************/ -esp_err_t usb_tonex_one_load_preset_to_slot_a(uint16_t preset) -{ - if (preset >= MAX_PRESETS_TONEX_ONE) - { - ESP_LOGW(TAG, "Invalid preset index %d for Slot A (max %d)", preset, MAX_PRESETS_TONEX_ONE - 1); - return ESP_FAIL; - } - - ESP_LOGI(TAG, "Loading preset %d to Slot A via MIDI CC 120", preset); - return usb_tonex_one_set_preset_in_slot(preset, A, 0); -} - -/**************************************************************************** -* NAME: usb_tonex_one_load_preset_to_slot_b -* DESCRIPTION: Load a preset to Slot B without switching to it (MIDI CC 121) -* PARAMETERS: preset - Preset index (0-19 for presets 1-20) -* RETURN: ESP_OK on success, ESP_FAIL on error -* NOTES: Bypasses CONFIG_ITEM_SAVE_PRESET_TO_SLOT configuration -*****************************************************************************/ -esp_err_t usb_tonex_one_load_preset_to_slot_b(uint16_t preset) -{ - if (preset >= MAX_PRESETS_TONEX_ONE) - { - ESP_LOGW(TAG, "Invalid preset index %d for Slot B (max %d)", preset, MAX_PRESETS_TONEX_ONE - 1); - return ESP_FAIL; - } - - ESP_LOGI(TAG, "Loading preset %d to Slot B via MIDI CC 121", preset); - return usb_tonex_one_set_preset_in_slot(preset, B, 0); -} /**************************************************************************** * NAME: @@ -1354,6 +1317,38 @@ void usb_tonex_one_handle(class_driver_t* driver_obj) // failed return to queue? } } + } break; + + case USB_COMMAND_LOAD_PRESET_TO_SLOT_A: + { + if (message.Payload < MAX_PRESETS_TONEX_ONE) + { + ESP_LOGI(TAG, "Loading preset %d to Slot A via MIDI CC 120", (int)message.Payload); + if (usb_tonex_one_set_preset_in_slot(message.Payload, A, 0) != ESP_OK) + { + ESP_LOGE(TAG, "Failed to load preset %d to Slot A", (int)message.Payload); + } + } + else + { + ESP_LOGW(TAG, "Invalid preset index %d for Slot A (max %d)", (int)message.Payload, MAX_PRESETS_TONEX_ONE - 1); + } + } break; + + case USB_COMMAND_LOAD_PRESET_TO_SLOT_B: + { + if (message.Payload < MAX_PRESETS_TONEX_ONE) + { + ESP_LOGI(TAG, "Loading preset %d to Slot B via MIDI CC 121", (int)message.Payload); + if (usb_tonex_one_set_preset_in_slot(message.Payload, B, 0) != ESP_OK) + { + ESP_LOGE(TAG, "Failed to load preset %d to Slot B", (int)message.Payload); + } + } + else + { + ESP_LOGW(TAG, "Invalid preset index %d for Slot B (max %d)", (int)message.Payload, MAX_PRESETS_TONEX_ONE - 1); + } } break; case USB_COMMAND_MODIFY_PARAMETER: