├── src ├── ESP32_Host_MIDI.h ├── ESP32_Pin_Config.h ├── BLE_Conexion.h ├── BLE_Conexion.cpp ├── USB_Conexion.h ├── MIDI_handler.h ├── USB_Conexion.cpp └── MIDI_handler.cpp ├── .gitignore ├── library.properties ├── examples └── T-Display-S3 │ ├── T-Display-S3.ino │ ├── ST7789_Handler.h │ └── ST7789_Handler.cpp ├── keywords.txt ├── LICENSE.txt ├── README.md └── RELEASE.md /src/ESP32_Host_MIDI.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_HOST_MIDI_H 2 | #define ESP32_HOST_MIDI_H 3 | 4 | #include "ESP32_Pin_Config.h" 5 | #include "USB_Conexion.h" 6 | #include "BLE_Conexion.h" 7 | #include "MIDI_Handler.h" 8 | 9 | #endif // ESP32_HOST_MIDI_H 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignorar ambientes virtuais 2 | /displayhandler.h 3 | /displayhandler.cpp 4 | /T-DisplayS3_Host_MIDI.ino 5 | /ESP32_Host_MIDI.ino 6 | /MIDI_PCM5102A_Handler.h 7 | /MIDI_PCM5102A_Handler.cpp 8 | /ESP32_PCM5102A_MIDI.h 9 | /ESP32_PCM5102A_MIDI.cpp 10 | /src/src.ino 11 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ESP32_Host_MIDI 2 | version= 2.0.0 3 | author=sauloverissimo 4 | maintainer=Saulo Verissimo 5 | sentence=Arduino library to host MIDI messages on ESP32 6 | paragraph=Receive MIDI messages via USB-OTG or BLE on ESP32. Translate message types and display them. Tested on ESP32-S3, but compatible with other microcontrollers. 7 | category=Communication 8 | url=https://github.com/sauloverissimo/ESP32_Host_MIDI 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/ESP32_Pin_Config.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_PIN_CONFIG_H 2 | #define ESP32_PIN_CONFIG_H 3 | 4 | // Configurações para comunicação USB 5 | #define USB_DP_PIN 19 6 | #define USB_DN_PIN 18 7 | 8 | // Configurações para o display T-Display S3 9 | #define TFT_CS_PIN 6 10 | #define TFT_DC_PIN 16 11 | #define TFT_RST_PIN 5 12 | #define TFT_BL_PIN 38 13 | 14 | // Configurações para o áudio via I2S (utilizado pelo PCM5102A no exemplo) 15 | #define I2S_BCK_PIN 11 // Conectado ao BCK do PCM5102A 16 | #define I2S_WS_PIN 13 // Conectado ao LRCK do PCM5102A 17 | #define I2S_DATA_OUT_PIN 12 // Conectado ao DIN do PCM5102A 18 | 19 | #define PIN_POWER_ON 15 20 | 21 | #define PIN_BUTTON_1 0 22 | #define PIN_BUTTON_2 14 23 | #define PIN_BAT_VOLT 4 24 | 25 | #define PIN_IIC_SCL 17 26 | #define PIN_IIC_SDA 18 27 | 28 | #endif // ESP32_PIN_CONFIG_H 29 | -------------------------------------------------------------------------------- /src/BLE_Conexion.h: -------------------------------------------------------------------------------- 1 | #ifndef BLE_CONEXION_H 2 | #define BLE_CONEXION_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // UUIDs padrão para BLE MIDI – mantenha os mesmos definidos no seu projeto. 9 | #define BLE_MIDI_SERVICE_UUID "03B80E5A-EDE8-4B33-A751-6CE34EC4C700" 10 | #define BLE_MIDI_CHARACTERISTIC_UUID "7772E5DB-3868-4112-A1A9-F2669D106BF3" 11 | 12 | // Estrutura para armazenar um pacote BLE bruto (opcional, se for necessário para depuração). 13 | struct RawBleMessage { 14 | uint8_t data[64]; // Buffer completo recebido (tamanho máximo 64 bytes) 15 | size_t length; // Número real de bytes recebidos (será usado somente os 4 primeiros) 16 | }; 17 | 18 | class BLE_Conexion { 19 | public: 20 | // Tipo para callback de mensagem MIDI 21 | typedef void (*MIDIMessageCallback)(const uint8_t* data, size_t length); 22 | 23 | BLE_Conexion(); 24 | 25 | // Inicializa o servidor BLE MIDI e inicia a publicidade. 26 | void begin(); 27 | 28 | // Processa eventos BLE (geralmente não é necessário um task periódico). 29 | void task(); 30 | 31 | // Retorna se há algum dispositivo BLE conectado. 32 | bool isConnected() const; 33 | 34 | // Define um callback para processar as mensagens MIDI recebidas via BLE. 35 | void setMidiMessageCallback(MIDIMessageCallback cb); 36 | 37 | // Callback virtual que é chamado sempre que uma mensagem BLE MIDI (4 bytes) é recebida. 38 | // A camada superior (ou subclasse) pode sobrescrever este método. 39 | virtual void onMidiDataReceived(const uint8_t* data, size_t length); 40 | 41 | protected: 42 | BLEServer* pServer; 43 | BLECharacteristic* pCharacteristic; 44 | MIDIMessageCallback midiCallback; 45 | }; 46 | 47 | #endif // BLE_CONEXION_H 48 | -------------------------------------------------------------------------------- /examples/T-Display-S3/T-Display-S3.ino: -------------------------------------------------------------------------------- 1 | 2 | // MIDI Controller Answer 3 | 4 | #include 5 | #include 6 | #include "ST7789_Handler.h" 7 | 8 | void setup() { 9 | Serial.begin(115200); 10 | 11 | display.init(); 12 | display.print("Display OK..."); 13 | delay(500); 14 | 15 | midiHandler.begin(); 16 | display.print("Interpretador MIDI inicializado..."); 17 | 18 | // Desativa o histórico para este teste (ou ajuste conforme sua necessidade) 19 | midiHandler.enableHistory(0); 20 | display.print("Host USB & BLE MIDI Inicializado..."); 21 | delay(500); 22 | } 23 | 24 | void loop() { 25 | 26 | midiHandler.task(); 27 | std::vector resposta = midiHandler.getAnswer("som"); 28 | display.print(resposta); 29 | delayMicroseconds(10); 30 | } 31 | 32 | 33 | 34 | // MIDI Data Queue 35 | 36 | // #include 37 | // #include 38 | // #include "ST7789_Handler.h" 39 | 40 | // void setup() { 41 | // Serial.begin(115200); 42 | 43 | // display.init(); 44 | // display.print("Display OK..."); 45 | // delay(500); 46 | 47 | // midiHandler.begin(); 48 | // display.print("Interpretador MIDI inicializado..."); 49 | 50 | // midiHandler.enableHistory(0); 51 | // display.print("Host USB & BLE MIDI Inicializado..."); 52 | // delay(500); 53 | 54 | // } 55 | 56 | // void loop() { 57 | // midiHandler.task(); 58 | 59 | // static String ultimaMsg; 60 | // const auto& queue = midiHandler.getQueue(); 61 | // String log; 62 | // if(queue.empty()){ 63 | // log = "[Press any key to start...]\n[Aperte uma tecla para iniciar...]"; 64 | // } 65 | // else { 66 | // std::string active = midiHandler.getActiveNotes(); 67 | // size_t NotesCount = midiHandler.getActiveNotesCount(); 68 | // log += "[" + String(NotesCount)+ "] " + String(active.c_str()) + "\n"; 69 | // int count = 0; 70 | // for(auto it = queue.rbegin(); it != queue.rend() && count < 12; ++it, ++count){ 71 | // char line[200]; 72 | // sprintf(line, "%d;%d;%lu;%lu;%d;%s;%d;%s;%s;%d;%d;%d", 73 | // it->index, it->msgIndex, it->tempo, it->delay, it->canal, 74 | // it->mensagem.c_str(), it->nota, it->som.c_str(), it->oitava.c_str(), 75 | // it->velocidade, it->flushOff, it->blockIndex); 76 | // log += String(line) + "\n"; 77 | // } 78 | // } 79 | // display.print(log.c_str()); 80 | // delayMicroseconds(1); 81 | // } 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/BLE_Conexion.cpp: -------------------------------------------------------------------------------- 1 | #include "BLE_Conexion.h" 2 | 3 | BLE_Conexion::BLE_Conexion() 4 | : pServer(nullptr), pCharacteristic(nullptr), midiCallback(nullptr) 5 | { 6 | } 7 | 8 | void BLE_Conexion::begin() { 9 | BLEDevice::init("ESP32 MIDI BLE"); 10 | pServer = BLEDevice::createServer(); 11 | // (Opcional: configure callbacks de conexão se necessário.) 12 | 13 | BLEService* pService = pServer->createService(BLE_MIDI_SERVICE_UUID); 14 | pCharacteristic = pService->createCharacteristic( 15 | BLE_MIDI_CHARACTERISTIC_UUID, 16 | BLECharacteristic::PROPERTY_WRITE_NR 17 | ); 18 | 19 | // Cria um callback para onWrite que extrai os 4 primeiros bytes da mensagem e os encaminha. 20 | class BLECallback : public BLECharacteristicCallbacks { 21 | public: 22 | BLE_Conexion* bleCon; 23 | BLECallback(BLE_Conexion* con) : bleCon(con) {} 24 | void onWrite(BLECharacteristic* characteristic) override { 25 | std::string rxValue = characteristic->getValue().c_str(); 26 | // Se houver ao menos 4 bytes, extrai os 4 primeiros. 27 | if(rxValue.size() >= 4) { 28 | const uint8_t* data = reinterpret_cast(rxValue.data()); 29 | // Chama o callback definido pelo usuário (se houver). 30 | if(bleCon->midiCallback) { 31 | bleCon->midiCallback(data, 4); 32 | } 33 | // Chama também o callback virtual para que uma subclasse possa sobrescrever. 34 | bleCon->onMidiDataReceived(data, 4); 35 | } 36 | } 37 | }; 38 | 39 | pCharacteristic->setCallbacks(new BLECallback(this)); 40 | pService->start(); 41 | 42 | BLEAdvertising* pAdvertising = BLEDevice::getAdvertising(); 43 | pAdvertising->addServiceUUID(BLE_MIDI_SERVICE_UUID); 44 | pAdvertising->setScanResponse(false); 45 | BLEDevice::startAdvertising(); 46 | } 47 | 48 | void BLE_Conexion::task() { 49 | // No BLE geralmente não é necessário processamento periódico. 50 | } 51 | 52 | bool BLE_Conexion::isConnected() const { 53 | if(pServer) 54 | return (pServer->getConnectedCount() > 0); 55 | return false; 56 | } 57 | 58 | void BLE_Conexion::setMidiMessageCallback(MIDIMessageCallback cb) { 59 | midiCallback = cb; 60 | } 61 | 62 | void BLE_Conexion::onMidiDataReceived(const uint8_t* data, size_t length) { 63 | // Implementação padrão: não faz nada. 64 | (void)data; 65 | (void)length; 66 | } 67 | -------------------------------------------------------------------------------- /src/USB_Conexion.h: -------------------------------------------------------------------------------- 1 | #ifndef USB_CONEXION_H 2 | #define USB_CONEXION_H 3 | 4 | #include 5 | #include 6 | 7 | /* 8 | Estrutura para armazenar um pacote USB bruto. 9 | Embora a transferência seja de 64 bytes, somente os 4 primeiros são relevantes. 10 | */ 11 | struct RawUsbMessage { 12 | uint8_t data[64]; // Buffer completo recebido (tamanho máximo 64 bytes) 13 | size_t length; // Número real de bytes recebidos (será usado somente os 4 primeiros) 14 | }; 15 | 16 | class USB_Conexion { 17 | public: 18 | USB_Conexion(); 19 | 20 | // Inicializa o USB Host e registra o cliente. 21 | void begin(); 22 | 23 | // Deve ser chamado periodicamente para tratar os eventos USB. 24 | // Nesta versão, processQueue() é chamado para encaminhar os pacotes. 25 | void task(); 26 | 27 | // Retorna se a conexão USB está pronta. 28 | bool isConnected() const { return isReady; } 29 | 30 | // Callback virtual para encaminhar os dados MIDI brutos (os 4 primeiros bytes). 31 | // A camada superior deve sobrescrever esse método para processar os dados. 32 | virtual void onMidiDataReceived(const uint8_t* data, size_t length); 33 | 34 | // Callbacks de conexão (vazios por padrão). 35 | virtual void onDeviceConnected(); 36 | virtual void onDeviceDisconnected(); 37 | 38 | // Métodos de acesso à fila (para depuração ou análise externa) 39 | int getQueueSize() const; 40 | const RawUsbMessage& getQueueMessage(int index) const; 41 | 42 | protected: 43 | bool isReady; 44 | uint8_t interval; // Intervalo de polling (em ms) 45 | unsigned long lastCheck; 46 | 47 | usb_host_client_handle_t clientHandle; 48 | usb_device_handle_t deviceHandle; 49 | uint32_t eventFlags; 50 | usb_transfer_t* midiTransfer; // Ponteiro para a transferência USB 51 | 52 | // Fila para armazenar os pacotes USB brutos. 53 | static const int QUEUE_SIZE = 64; 54 | RawUsbMessage usbQueue[QUEUE_SIZE]; 55 | volatile int queueHead; 56 | volatile int queueTail; 57 | 58 | // Dados de controle de conexão 59 | bool firstMidiReceived; 60 | bool isMidiDeviceConfirmed; 61 | String deviceName; 62 | 63 | // Funções auxiliares para gerenciar a fila. 64 | bool enqueueMidiMessage(const uint8_t* data, size_t length); 65 | bool dequeueMidiMessage(RawUsbMessage &msg); 66 | void processQueue(); 67 | 68 | // Callbacks internos do USB Host. 69 | static void _clientEventCallback(const usb_host_client_event_msg_t *eventMsg, void *arg); 70 | static void _onReceive(usb_transfer_t *transfer); 71 | void _processConfig(const usb_config_desc_t *config_desc); 72 | }; 73 | 74 | #endif // USB_CONEXION_H 75 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ###################################### 2 | # ESP32_Host_MIDI Library Keywords # 3 | ###################################### 4 | 5 | ###################################### 6 | # Classes 7 | ###################################### 8 | ESP32_Host_MIDI KEYWORD1 9 | ESP32_PCM5102A_MIDI KEYWORD1 10 | ESP32_BLE KEYWORD1 11 | MIDIHandler KEYWORD1 12 | DisplayHandler KEYWORD1 13 | 14 | ###################################### 15 | # Functions 16 | ###################################### 17 | begin KEYWORD2 18 | task KEYWORD2 19 | onMidiMessage KEYWORD2 20 | playNote KEYWORD2 21 | stopNote KEYWORD2 22 | update KEYWORD2 23 | isConnected KEYWORD2 24 | setMidiMessageCallback KEYWORD2 25 | sendMidiMessage KEYWORD2 26 | getRawFormat KEYWORD2 27 | getShortFormat KEYWORD2 28 | getNoteNumberFormat KEYWORD2 29 | getMessageFormat KEYWORD2 30 | getMessageStatusFormat KEYWORD2 31 | getNoteSound KEYWORD2 32 | getNoteSoundOctave KEYWORD2 33 | getProgramFormat KEYWORD2 34 | getMessageVector KEYWORD2 35 | getUsbMidiFormat KEYWORD2 36 | printMidiMessage KEYWORD2 37 | clear KEYWORD2 38 | 39 | ###################################### 40 | # Constants 41 | ###################################### 42 | USB_DP_PIN LITERAL1 43 | USB_DN_PIN LITERAL1 44 | TFT_CS_PIN LITERAL1 45 | TFT_DC_PIN LITERAL1 46 | TFT_RST_PIN LITERAL1 47 | TFT_BL_PIN LITERAL1 48 | I2S_BCK_PIN LITERAL1 49 | I2S_WS_PIN LITERAL1 50 | I2S_DATA_OUT_PIN LITERAL1 51 | PIN_POWER_ON LITERAL1 52 | PIN_BUTTON_1 LITERAL1 53 | PIN_BUTTON_2 LITERAL1 54 | PIN_BAT_VOLT LITERAL1 55 | PIN_IIC_SCL LITERAL1 56 | PIN_IIC_SDA LITERAL1 57 | BLE_MIDI_SERVICE_UUID LITERAL1 58 | BLE_MIDI_CHARACTERISTIC_UUID LITERAL1 59 | I2S_NUM LITERAL1 60 | I2S_SAMPLE_RATE LITERAL1 61 | TFT_GRAY LITERAL1 62 | 63 | ###################################### 64 | # Types 65 | ###################################### 66 | TypeElement KEYWORD2 67 | TypeVector KEYWORD2 68 | TypeTable KEYWORD2 69 | TypeCube KEYWORD2 70 | Vector KEYWORD2 71 | Table KEYWORD2 72 | Element KEYWORD2 73 | 74 | ###################################### 75 | # Miscellaneous 76 | ###################################### 77 | Arduino KEYWORD2 78 | #include KEYWORD2 79 | #define KEYWORD2 80 | -------------------------------------------------------------------------------- /examples/T-Display-S3/ST7789_Handler.h: -------------------------------------------------------------------------------- 1 | #ifndef ST7789_HANDLER_H 2 | #define ST7789_HANDLER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // Classe ST7789_Handler: 9 | // Gerencia o display ST7789 do T‑Display-S3 (baseado no código previamente implementado em DisplayHandler). 10 | class ST7789_Handler { 11 | public: 12 | ST7789_Handler(); 13 | 14 | // Inicializa o display (configuração de rotação, fonte, cor, etc.) 15 | void init(); 16 | 17 | // Exibe uma string formatada na tela, evitando flickering. 18 | void print(const std::string& message); 19 | 20 | // Exibe um vetor de strings concatenado na tela, evitando flickering. 21 | void print(const std::vector& messages); 22 | 23 | // Exibe uma string formatada na tela que pula linha, evitando flickering. 24 | void println(const std::string& message); 25 | 26 | // Exibe um vetor de strings concatenado na tela que pula linha, evitando flickering. 27 | void println(const std::vector& messages); 28 | 29 | 30 | // Limpa o display 31 | void clear(); 32 | 33 | private: 34 | // Classe customizada baseada em LovyanGFX para configurar o painel ST7789 via bus paralelo de 8 bits 35 | class LGFX : public lgfx::LGFX_Device { 36 | public: 37 | LGFX(void) { 38 | { // Configuração do bus paralelo de 8 bits 39 | auto cfg = _bus_instance.config(); 40 | cfg.pin_wr = 8; 41 | cfg.pin_rd = 9; 42 | cfg.pin_rs = 7; // D/C 43 | cfg.pin_d0 = 39; 44 | cfg.pin_d1 = 40; 45 | cfg.pin_d2 = 41; 46 | cfg.pin_d3 = 42; 47 | cfg.pin_d4 = 45; 48 | cfg.pin_d5 = 46; 49 | cfg.pin_d6 = 47; 50 | cfg.pin_d7 = 48; 51 | _bus_instance.config(cfg); 52 | _panel_instance.setBus(&_bus_instance); 53 | } 54 | { // Configuração do painel ST7789 55 | auto cfg = _panel_instance.config(); 56 | cfg.pin_cs = 6; 57 | cfg.pin_rst = 5; 58 | cfg.pin_busy = -1; 59 | cfg.offset_rotation = 1; 60 | cfg.offset_x = 35; 61 | cfg.readable = false; 62 | cfg.invert = true; 63 | cfg.rgb_order = false; 64 | cfg.dlen_16bit = false; 65 | cfg.bus_shared = false; 66 | // Para orientação horizontal: display de 170 x 320 67 | cfg.panel_width = 170; 68 | cfg.panel_height = 320; 69 | _panel_instance.config(cfg); 70 | } 71 | setPanel(&_panel_instance); 72 | { // Configuração da luz de fundo 73 | auto cfg = _light_instance.config(); 74 | cfg.pin_bl = 38; 75 | cfg.invert = false; 76 | cfg.freq = 22000; 77 | cfg.pwm_channel = 7; 78 | _light_instance.config(cfg); 79 | _panel_instance.setLight(&_light_instance); 80 | } 81 | } 82 | private: 83 | lgfx::Bus_Parallel8 _bus_instance; 84 | lgfx::Panel_ST7789 _panel_instance; 85 | lgfx::Light_PWM _light_instance; 86 | }; 87 | 88 | LGFX tft; 89 | }; 90 | 91 | extern ST7789_Handler display; 92 | 93 | #endif // ST7789_HANDLER_H 94 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Saulo Veríssimo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | --- 24 | 25 | Licença MIT 26 | 27 | Copyright (c) 2025 Saulo Veríssimo 28 | 29 | É concedida permissão, gratuitamente, a qualquer pessoa que obtenha uma cópia 30 | deste software e dos arquivos de documentação associados (o "Software"), para 31 | lidar com o Software sem restrições, incluindo, sem limitação, os direitos de 32 | usar, copiar, modificar, mesclar, publicar, distribuir, sublicenciar e/ou vender 33 | cópias do Software, e permitir que pessoas a quem o Software seja fornecido 34 | façam o mesmo, sujeitas às seguintes condições: 35 | 36 | O aviso de copyright acima e este aviso de permissão devem ser incluídos em todas 37 | as cópias ou partes substanciais do Software. 38 | 39 | O SOFTWARE É FORNECIDO "TAL COMO ESTÁ", SEM GARANTIA DE QUALQUER TIPO, EXPRESSA OU 40 | IMPLÍCITA, INCLUINDO, MAS NÃO SE LIMITANDO ÀS GARANTIAS DE COMERCIALIZAÇÃO, ADEQUAÇÃO 41 | A UM PROPÓSITO ESPECÍFICO E NÃO VIOLAÇÃO. EM NENHUMA HIPÓTESE OS AUTORES OU DETENTORES 42 | DOS DIREITOS AUTORAIS SERÃO RESPONSÁVEIS POR QUALQUER REIVINDICAÇÃO, DANO OU OUTRA 43 | RESPONSABILIDADE, SEJA EM UMA AÇÃO DE CONTRATO, ATO ILÍCITO OU OUTRO, DECORRENTE DE, 44 | FORA DE OU EM CONEXÃO COM O SOFTWARE OU O USO OU OUTRAS NEGOCIAÇÕES NO SOFTWARE. 45 | 46 | --- 47 | 48 | Licencia MIT 49 | 50 | Derechos de autor (c) 2025 Saulo Veríssimo 51 | 52 | Se concede permiso, de forma gratuita, a cualquier persona que obtenga una copia 53 | de este software y los archivos de documentación asociados (el "Software"), para 54 | tratar con el Software sin restricciones, incluyendo, sin limitación, los derechos 55 | de usar, copiar, modificar, fusionar, publicar, distribuir, sublicenciar y/o vender 56 | copias del Software, y permitir a las personas a quienes se les proporcione el Software 57 | hacer lo mismo, sujeto a las siguientes condiciones: 58 | 59 | El aviso de copyright anterior y este aviso de permiso deben incluirse en todas 60 | las copias o partes sustanciales del Software. 61 | 62 | EL SOFTWARE SE PROPORCIONA "TAL CUAL", SIN GARANTÍA DE NINGÚN TIPO, EXPRESA O 63 | IMPLÍCITA, INCLUYENDO, PERO NO LIMITADO A, LAS GARANTÍAS DE COMERCIABILIDAD, 64 | IDONEIDAD PARA UN PROPÓSITO PARTICULAR Y NO INFRACCIÓN. EN NINGÚN CASO LOS AUTORES O 65 | TITULARES DEL COPYRIGHT SERÁN RESPONSABLES DE NINGUNA RECLAMACIÓN, DAÑO U OTRA 66 | RESPONSABILIDAD, YA SEA EN UNA ACCIÓN CONTRACTUAL, AGRAVIO O DE OTRO MODO, QUE SURJA 67 | DE, FUERA O EN CONEXIÓN CON EL SOFTWARE O EL USO U OTRAS NEGOCIACIONES EN EL SOFTWARE. 68 | 69 | --- 70 | 71 | Licenza MIT 72 | 73 | Copyright (c) 2025 Saulo Veríssimo 74 | 75 | Si concede il permesso, gratuitamente, a chiunque ottenga una copia di questo software 76 | e dei file di documentazione associati (il "Software"), di trattare il Software senza 77 | restrizioni, inclusi, senza limitazione, i diritti di usare, copiare, modificare, 78 | unire, pubblicare, distribuire, concedere in sublicenza e/o vendere copie del Software, 79 | e permettere alle persone a cui il Software è fornito di fare lo stesso, soggetto 80 | alle seguenti condizioni: 81 | 82 | L'avviso di copyright sopra e questo avviso di autorizzazione devono essere inclusi 83 | in tutte le copie o parti sostanziali del Software. 84 | 85 | IL SOFTWARE VIENE FORNITO "COSÌ COM'È", SENZA GARANZIA DI ALCUN TIPO, ESPRESSA O 86 | IMPLICITA, INCLUSE MA NON LIMITATE ALLE GARANZIE DI COMMERCIABILITÀ, IDONEITÀ 87 | PER UNO SCOPO PARTICOLARE E NON VIOLAZIONE. IN NESSUN CASO GLI AUTORI O I TITOLARI 88 | DEL COPYRIGHT SARANNO RESPONSABILI PER QUALSIASI RECLAMO, DANNO O ALTRA RESPONSABILITÀ, 89 | SIA IN UN'AZIONE CONTRATTUALE, ILLECITO O ALTRO, DERIVANTE DA, FUORI O IN CONNESSIONE 90 | CON IL SOFTWARE O L'USO O ALTRE TRATTATIVE NEL SOFTWARE. 91 | -------------------------------------------------------------------------------- /src/MIDI_handler.h: -------------------------------------------------------------------------------- 1 | #ifndef MIDI_HANDLER_H 2 | #define MIDI_HANDLER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "USB_Conexion.h" 9 | #include "BLE_Conexion.h" 10 | 11 | // Estrutura que representa um evento MIDI com os campos desejados 12 | struct MIDIEventData { 13 | int index; // Contador global de eventos 14 | int msgIndex; // Índice para vincular NoteOn e NoteOff 15 | unsigned long tempo; // Tempo (em milissegundos – usamos millis()) 16 | unsigned long delay; // Diferença em ms em relação ao evento anterior 17 | int canal; // Canal MIDI 18 | std::string mensagem; // "NoteOn" ou "NoteOff" 19 | int nota; // Número da nota MIDI 20 | std::string som; // Nota musical (ex.: "C") 21 | std::string oitava; // Nota com oitava (ex.: "C4") 22 | int velocidade; // Velocidade 23 | int blockIndex; // Índice do bloco (agrupamento de notas simultâneas) 24 | int flushOff; // Indica se a nota sofreu flush (0 = normal, 1 = flush) 25 | }; 26 | 27 | class MIDIHandler { 28 | public: 29 | MIDIHandler(); 30 | 31 | void begin(); 32 | void task(); 33 | void enableHistory(int capacity); 34 | void addEvent(const MIDIEventData& event); 35 | void processQueue(); 36 | void setQueueLimit(int maxEvents); 37 | const std::deque& getQueue() const; 38 | 39 | void handleMidiMessage(const uint8_t* data, size_t length); 40 | 41 | std::string getActiveNotesString() const; 42 | std::string getActiveNotes() const; 43 | std::vector getActiveNotesVector() const; 44 | size_t getActiveNotesCount() const; 45 | void clearActiveNotesNow(); 46 | 47 | // Funções auxiliares para manipulação de blocos de eventos: 48 | // Retorna o maior blockIndex dentre os eventos da fila. 49 | int lastBlock(const std::deque& queue) const; 50 | // Filtra os eventos do bloco informado e extrai os campos solicitados. 51 | // Se 'fields' não for informado ou contiver "all", retorna todos os campos formatados. 52 | // Retorna os eventos de um bloco específico, podendo escolher se deseja os rótulos nos campos ou apenas os valores 53 | std::vector getBlock(int block, const std::deque& queue, const std::vector& fields = { "all" }, bool includeLabels = false) const; 54 | // Retorna os dados do último bloco MIDI registrado, podendo filtrar um campo específico ou retornar todos os dados do bloco. 55 | // Retorna os dados do último bloco MIDI registrado, podendo filtrar um campo específico e escolher se deseja rótulos nos campos. 56 | // Retorna os dados do último bloco MIDI, podendo aceitar uma string única ou um vetor de strings. 57 | std::vector getAnswer(const std::string& field = "all", bool includeLabels = false) const; 58 | std::vector getAnswer(const std::vector& fields, bool includeLabels = false) const; 59 | 60 | 61 | 62 | private: 63 | std::deque eventQueue; 64 | int maxEvents; 65 | int globalIndex; 66 | int nextMsgIndex; 67 | unsigned long lastTempo; 68 | 69 | std::unordered_map activeNotes; 70 | std::unordered_map activeMsgIndex; 71 | 72 | int nextBlockIndex; 73 | int currentBlockIndex; 74 | unsigned long lastNoteOffTime; 75 | static const unsigned long NOTE_TIMEOUT = 1200; 76 | 77 | // Histórico armazenado na PSRAM (buffer dinâmico circular) 78 | MIDIEventData* historyQueue; 79 | int historyQueueCapacity; 80 | int historyQueueSize; 81 | int historyQueueHead; 82 | 83 | // Expande dinamicamente o buffer circular na PSRAM para evitar perda de eventos 84 | void expandHistoryQueue(); 85 | 86 | void flushActiveNotes(unsigned long currentTime); 87 | std::string getNoteName(int note) const; 88 | std::string getNoteWithOctave(int note) const; 89 | 90 | class MyUSB_Conexion : public USB_Conexion { 91 | public: 92 | MyUSB_Conexion(MIDIHandler* handler) 93 | : handler(handler) {} 94 | virtual void onMidiDataReceived(const uint8_t* data, size_t length) override { 95 | handler->handleMidiMessage(data, length); 96 | } 97 | private: 98 | MIDIHandler* handler; 99 | }; 100 | 101 | class MyBLE_Conexion : public BLE_Conexion { 102 | public: 103 | MyBLE_Conexion(MIDIHandler* handler) 104 | : handler(handler) {} 105 | virtual void onMidiDataReceived(const uint8_t* data, size_t length) override { 106 | handler->handleMidiMessage(data, length); 107 | } 108 | private: 109 | MIDIHandler* handler; 110 | }; 111 | 112 | MyUSB_Conexion usbCon; 113 | MyBLE_Conexion bleCon; 114 | }; 115 | 116 | extern MIDIHandler midiHandler; 117 | 118 | #endif // MIDI_HANDLER_H 119 | -------------------------------------------------------------------------------- /examples/T-Display-S3/ST7789_Handler.cpp: -------------------------------------------------------------------------------- 1 | #include "ST7789_Handler.h" 2 | #include 3 | 4 | ST7789_Handler display; 5 | 6 | // Define TFT_GRAY caso não esteja definido 7 | #ifndef TFT_GRAY 8 | #define TFT_GRAY 0x8410 9 | #endif 10 | 11 | ST7789_Handler::ST7789_Handler() { 12 | // Construtor simples (pode ser expandido se necessário) 13 | } 14 | 15 | void ST7789_Handler::init() { 16 | tft.init(); 17 | tft.setRotation(2); // Rotaciona 180° para correção da orientação 18 | tft.setTextSize(1); // Fonte pequena para as linhas regulares 19 | tft.setTextColor(TFT_WHITE); 20 | tft.fillScreen(TFT_BLACK); 21 | 22 | // Acende a luz de fundo manualmente (se necessário) 23 | pinMode(38, OUTPUT); 24 | digitalWrite(38, HIGH); // Garante que a luz de fundo esteja ligada 25 | } 26 | 27 | // Sobrecarga para exibir uma string simples 28 | void ST7789_Handler::print(const std::string& message) { 29 | static std::string lastMessage = ""; // Armazena a última mensagem exibida 30 | 31 | // Se a mensagem não mudou, não redesenha para evitar flickering 32 | if (lastMessage == message) { 33 | return; 34 | } 35 | lastMessage = message; // Atualiza a mensagem armazenada 36 | 37 | // Limpa a tela apenas quando necessário 38 | tft.fillScreen(TFT_BLACK); 39 | 40 | const int margin = 2; 41 | const int availableHeight = 240 - margin; 42 | const int totalLines = 12; 43 | const int lineHeight = availableHeight / totalLines; 44 | 45 | int x = 2; 46 | int y = margin / 2; 47 | 48 | // Divide a string em múltiplas linhas 49 | String text(message.c_str()); 50 | String lines[totalLines]; 51 | int lineCount = 0; 52 | int start = 0; 53 | int idx = text.indexOf('\n'); 54 | while (idx != -1 && lineCount < totalLines) { 55 | lines[lineCount++] = text.substring(start, idx); 56 | start = idx + 1; 57 | idx = text.indexOf('\n', start); 58 | } 59 | if (lineCount < totalLines && start < text.length()) { 60 | lines[lineCount++] = text.substring(start); 61 | } 62 | 63 | // Exibe cada linha com espaçamento adequado 64 | for (int i = 0; i < lineCount; i++) { 65 | tft.setTextSize(1); 66 | tft.setTextColor(TFT_WHITE); 67 | tft.drawString(lines[i], x, y); 68 | y += lineHeight - 2; 69 | } 70 | } 71 | 72 | // Sobrecarga para exibir um vetor de strings 73 | void ST7789_Handler::print(const std::vector& messages) { 74 | static std::string lastMessage = ""; // Armazena a última mensagem exibida 75 | 76 | // Concatena os elementos do vetor em uma única string separada por ", " 77 | std::string concatenatedMessage; 78 | for (size_t i = 0; i < messages.size(); ++i) { 79 | concatenatedMessage += messages[i]; 80 | if (i < messages.size() - 1) { 81 | concatenatedMessage += ", "; 82 | } 83 | } 84 | 85 | // Se a mensagem não mudou, não redesenha para evitar flickering 86 | if (lastMessage == concatenatedMessage) { 87 | return; 88 | } 89 | lastMessage = concatenatedMessage; // Atualiza a mensagem armazenada 90 | 91 | // Chamamos a versão da função `print(const std::string&)` 92 | print(concatenatedMessage); 93 | } 94 | 95 | // Sobrecarga para exibir uma string quebrando por vírgula 96 | void ST7789_Handler::println(const std::string& message) { 97 | static std::string lastMessage = ""; // Armazena a última mensagem exibida 98 | 99 | // Se a mensagem não mudou, não redesenha para evitar flickering 100 | if (lastMessage == message) { 101 | return; 102 | } 103 | lastMessage = message; // Atualiza a mensagem armazenada 104 | 105 | // Limpa a tela apenas quando necessário 106 | tft.fillScreen(TFT_BLACK); 107 | 108 | const int margin = 2; 109 | const int availableHeight = 240 - margin; 110 | const int totalLines = 12; 111 | const int lineHeight = availableHeight / totalLines; 112 | 113 | int x = 2; 114 | int y = margin / 2; 115 | 116 | // Divide a string em linhas quebrando por "," 117 | String text(message.c_str()); 118 | String lines[totalLines]; 119 | int lineCount = 0; 120 | int start = 0; 121 | int idx = text.indexOf(','); 122 | while (idx != -1 && lineCount < totalLines) { 123 | lines[lineCount++] = text.substring(start, idx); 124 | start = idx + 1; 125 | idx = text.indexOf(',', start); 126 | } 127 | if (lineCount < totalLines && start < text.length()) { 128 | lines[lineCount++] = text.substring(start); 129 | } 130 | 131 | // Exibe cada linha com espaçamento adequado 132 | for (int i = 0; i < lineCount; i++) { 133 | tft.setTextSize(1); 134 | tft.setTextColor(TFT_WHITE); 135 | tft.drawString(lines[i], x, y); // Removemos .trim(), pois não retorna um valor 136 | y += lineHeight - 2; 137 | } 138 | } 139 | 140 | // Sobrecarga para exibir um vetor de strings, quebrando cada elemento em uma nova linha 141 | void ST7789_Handler::println(const std::vector& messages) { 142 | static std::string lastMessage = ""; // Armazena a última mensagem exibida 143 | 144 | // Concatena os elementos do vetor para verificar mudança 145 | std::string concatenatedMessage; 146 | for (const auto& msg : messages) { 147 | concatenatedMessage += msg + ","; 148 | } 149 | 150 | // Se a mensagem não mudou, não redesenha para evitar flickering 151 | if (lastMessage == concatenatedMessage) { 152 | return; 153 | } 154 | lastMessage = concatenatedMessage; // Atualiza a mensagem armazenada 155 | 156 | // Chamamos a versão da função `println(const std::string&)` 157 | println(concatenatedMessage); 158 | } 159 | 160 | // Apenas uma definição para evitar erro de duplicação 161 | void ST7789_Handler::clear() { 162 | tft.fillScreen(TFT_BLACK); 163 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32_Host_MIDI 🎹📡 2 | 3 | ![image](https://github.com/user-attachments/assets/bba1c679-6c76-45b7-aa29-a3201a69b36a) 4 | 5 | Project developed for the Arduino IDE. 6 | 7 | This project provides a complete solution for receiving, interpreting, and displaying MIDI messages via USB and BLE on the ESP32 (especially ESP32-S3) using the T‑Display S3. The library is modular and can be easily adapted to other hardware by modifying the configuration file(s). 8 | 9 | --- 10 | 11 | ## Overview 12 | 13 | The **ESP32_Host_MIDI** library allows the ESP32 to: 14 | - Act as a USB host for MIDI devices (via the **USB_Conexion** module), 15 | - Function as a BLE MIDI server (via the **BLE_Conexion** module), 16 | - Process and interpret MIDI messages (using the **MIDI_Handler** module), and 17 | - Display formatted MIDI data on a display (via the **ST7789_Handler** module). 18 | 19 | The core header **ESP32_Host_MIDI.h** integrates the pin configuration, USB/BLE connectivity, and MIDI handling functionalities. 20 | 21 | --- 22 | 23 | ## File Structure 24 | 25 | ### Core Library Files (in the `src/` folder) 26 | - **ESP32_Pin_Config.h** 27 | Defines the pin configuration for USB communication, display control, and audio output. 28 | - *Examples:* `USB_DP_PIN`, `USB_DN_PIN`, `TFT_CS_PIN`, `TFT_DC_PIN`, `TFT_RST_PIN`, `TFT_BL_PIN`. 29 | 30 | - **USB_Conexion.h / USB_Conexion.cpp** 31 | Implements the USB host functionality to receive MIDI data from connected MIDI devices. 32 | - **Key Functions:** 33 | - `begin()`: Initializes the USB host and registers the client. 34 | - `task()`: Handles USB events and processes incoming data. 35 | - `onMidiDataReceived()`: Virtual function (to be overridden) for processing received MIDI messages (first 4 bytes). 36 | 37 | - **BLE_Conexion.h / BLE_Conexion.cpp** 38 | Implements the BLE MIDI server, enabling the ESP32 to receive MIDI messages via Bluetooth Low Energy. 39 | - **Key Functions:** 40 | - `begin()`: Initializes the BLE server and starts advertising the MIDI service. 41 | - `task()`: Processes BLE events (if needed). 42 | - `setMidiMessageCallback()`: Registers a callback to handle incoming BLE MIDI messages. 43 | - `onMidiDataReceived()`: Virtual function (to be overridden) for processing BLE MIDI messages. 44 | 45 | - **MIDI_Handler.h / MIDI_Handler.cpp** 46 | Processes and interprets raw MIDI data (removing USB headers when necessary) and manages MIDI events: 47 | - **Features:** 48 | - Handles MIDI events (NoteOn and NoteOff). 49 | - Converts MIDI note numbers into musical notes (e.g., "C4"). 50 | - Maintains the state of active notes and an optional history buffer (using PSRAM). 51 | - Provides utility functions to retrieve formatted MIDI event data. 52 | - **Key Functions:** 53 | - `begin()`: Initializes the MIDI handler and associated USB/BLE connections. 54 | - `task()`: Processes incoming USB and BLE MIDI events. 55 | - `handleMidiMessage(const uint8_t* data, size_t length)`: Interprets MIDI messages and categorizes them into NoteOn, NoteOff, etc. 56 | - `addEvent(const MIDIEventData& event)`: Stores MIDI events in an event queue. 57 | - `processQueue()`: Manages the event queue, ensuring it stays within the configured limits. 58 | - `enableHistory(int capacity)`: Enables a history buffer in PSRAM for MIDI event storage. 59 | - `setQueueLimit(int maxEvents)`: Defines the maximum number of events stored in the queue. 60 | - `getActiveNotesString()`: Returns a formatted string listing active MIDI notes. 61 | - `getBlock(int block, const std::deque& queue, const std::vector& fields)`: Retrieves specific blocks of MIDI events. 62 | - `flushActiveNotes(unsigned long currentTime)`: Clears active notes when necessary. 63 | - `clearActiveNotesNow()`: Immediately clears active notes. 64 | - `getAnswer(const std::vector& fields, bool includeLabels)`: Returns MIDI event details based on requested fields. 65 | 66 | - **ST7789_Handler.h / ST7789_Handler.cpp** 67 | Manages the ST7789 display on the T‑Display S3 using the LovyanGFX library: 68 | - **Key Functions:** 69 | - `init()`: Initializes the display (rotation, font, colors, etc.). 70 | - `print()` / `println()`: Displays text on the screen while minimizing flickering. 71 | - `clear()`: Clears the display. 72 | 73 | - **ESP32_Host_MIDI.h** 74 | The core header that includes the pin configuration, USB/BLE connectivity, and MIDI handling modules. 75 | 76 | ### Example Sketches (in the `examples/T-Display-S3/` folder) 77 | - Contains a sketch demonstrating: 78 | - USB and BLE MIDI reception, 79 | - Processing of MIDI messages using **MIDI_Handler**, and 80 | - Display of formatted MIDI data on the T‑Display S3 using **ST7789_Handler**. 81 | - The `examples/T-Display-S3/images/` folder includes images showing the project in action. 82 | 83 | --- 84 | 85 | ## Operation 86 | 87 | 1. **MIDI USB-OTG Reception:** 88 | When a MIDI device is connected via USB, the **USB_Conexion** module captures the MIDI data and passes it to **MIDI_Handler** for processing. 89 | 90 | 2. **MIDI BLE Reception:** 91 | The **BLE_Conexion** module enables the ESP32 to operate as a BLE MIDI server, receiving MIDI messages from paired Bluetooth devices. 92 | 93 | 3. **MIDI Message Processing:** 94 | **MIDI_Handler** interprets incoming MIDI messages (handling NoteOn and NoteOff events), converts MIDI note numbers into musical notes, and optionally stores events in a history buffer. 95 | 96 | 4. **Display Output:** 97 | The **ST7789_Handler** module handles the display of formatted MIDI information on the T‑Display S3, ensuring smooth text rendering without flickering. 98 | 99 | --- 100 | 101 | ## Customization 102 | 103 | The library is designed to be modular: 104 | - You can modify **ESP32_Pin_Config.h** to change the pin assignments according to your hardware. 105 | - The modules **USB_Conexion**, **BLE_Conexion**, **MIDI_Handler**, and **ST7789_Handler** can be extended or replaced to suit specific application requirements. 106 | 107 | --- 108 | 109 | ## Getting Started 110 | 111 | 1. **Install Dependencies:** 112 | - Arduino IDE. 113 | - LovyanGFX library for display management. 114 | - Required BLE libraries (included in the ESP32 core). 115 | 116 | 2. **Load the Example:** 117 | Open the example sketch from `examples/T-Display-S3/` in the Arduino IDE, adjust the pin configuration if necessary, and upload it to your ESP32-S3 board. 118 | 119 | 3. **Connect a MIDI Device:** 120 | Use a USB MIDI device or pair a BLE MIDI device to test MIDI message reception and display. 121 | 122 | --- 123 | 124 | ## Contributing 125 | 126 | Contributions, bug reports, and suggestions are welcome! 127 | Feel free to open an issue or submit a pull request on GitHub. 128 | 129 | --- 130 | 131 | ## License 132 | 133 | This project is released under the MIT License. See the [LICENSE](LICENSE) file for details. 134 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | ```markdown 2 | # 🚀 ESP32_Host_MIDI - Release 2.0 🎹📡 3 | 4 | Welcome to version **2.0** of the **ESP32_Host_MIDI** library! This update brings significant improvements over the previous version, including performance optimizations, new features, and enhanced stability for MIDI communication via **USB and BLE** on the **ESP32-S3** with **T-Display S3**. 5 | 6 | --- 7 | 8 | ## 🔥 Key Updates and Improvements 9 | 10 | ### 🏗️ Enhanced Modular Architecture 11 | - Improved code structure for easier maintenance and expansion. 12 | - Clear separation between **USB MIDI**, **BLE MIDI**, **Display Output**, and **MIDI Processing** modules. 13 | 14 | ### 🎛️ New MIDI Processing Queue 15 | - The **MIDI_Handler** now uses a **dynamic queue** (`std::deque`) to store MIDI events, providing more flexibility and better control over data flow. 16 | - Added an **adjustable event limit** to prevent overloading. 17 | 18 | ### 📡 Improved USB MIDI Host 19 | - **Reworked USB MIDI packet processing** ensures that only the **first 4 relevant bytes** are used, reducing latency. 20 | - Optimized **USB message queue**, increasing efficiency in handling received MIDI packets. 21 | - **New callback system** for USB MIDI device connection and disconnection events. 22 | 23 | ### 📲 Enhanced BLE MIDI Support 24 | - The **ESP32 now functions as a stable and reliable BLE MIDI server**. 25 | - **Direct callback for processing received BLE MIDI messages**. 26 | - Optimized BLE advertising for better compatibility with mobile MIDI apps. 27 | 28 | ### 🎨 Improved MIDI Display Output 29 | - Uses **LovyanGFX** for optimized rendering on the **ST7789**. 30 | - New `printMessage()` method in **ST7789_Handler**: 31 | - Displays **multi-line messages**. 32 | - **Automatically highlights** the last line for better separation of MIDI events. 33 | - Reduces flickering by avoiding unnecessary screen refreshes. 34 | 35 | ### 🏎️ Performance Boost 36 | - **Better use of USB interrupts**, reducing the load on the main loop. 37 | - **Removal of unnecessary delays**, improving real-time MIDI event processing. 38 | - **Optimized chord and sequence detection**. 39 | 40 | --- 41 | 42 | ## 📂 Code Structure in Version 2.0 43 | 44 | The new file structure is designed for modularity and clarity: 45 | 46 | 📁 **/src/** 47 | - `USB_Conexion.h / USB_Conexion.cpp` → Manages USB MIDI communication. 48 | - `BLE_Conexion.h / BLE_Conexion.cpp` → Manages BLE MIDI communication. 49 | - `MIDI_Handler.h / MIDI_Handler.cpp` → Interprets and organizes MIDI events. 50 | - `ESP32_Pin_Config.h` → Defines ESP32 pin configurations. 51 | 52 | 📁 **/examples/** 53 | - Complete example for **T-Display S3**, showcasing **MIDI USB + BLE + Display** integration. 54 | 55 | --- 56 | 57 | ## ⚡ How to Upgrade to Version 2.0? 58 | 59 | 1. **Download the updated library files** and replace the old ones. 60 | 2. **Check your code**: 61 | - If you were using `onMidiMessage()` directly, now you should override `onMidiDataReceived()` in **USB_Conexion** or **BLE_Conexion**. 62 | - To display MIDI messages on **ST7789**, use `printMessage()` in **ST7789_Handler**. 63 | 64 | --- 65 | 66 | ## 🛠️ How to Use the New Version? 67 | 68 | ### 1️⃣ Initialize USB MIDI Communication: 69 | ```cpp 70 | USB_Conexion usbMIDI; 71 | usbMIDI.begin(); 72 | ``` 73 | 74 | ### 2️⃣ Initialize BLE MIDI Communication: 75 | ```cpp 76 | BLE_Conexion bleMIDI; 77 | bleMIDI.begin(); 78 | ``` 79 | 80 | ### 3️⃣ Process MIDI in the Main Loop: 81 | ```cpp 82 | void loop() { 83 | usbMIDI.task(); 84 | bleMIDI.task(); 85 | } 86 | ``` 87 | 88 | ### 4️⃣ Display MIDI Messages on the Screen: 89 | ```cpp 90 | ST7789_Handler display; 91 | display.init(); 92 | display.printMessage("MIDI Active!", 0, 0); 93 | ``` 94 | 95 | --- 96 | 97 | ## 🏆 Conclusion 98 | 99 | This update **significantly improves the stability, efficiency, and functionality of ESP32_Host_MIDI**. If you were using the previous version, we highly recommend upgrading to take full advantage of all the new features and optimizations! 100 | 101 | Thanks to the community for the feedback and suggestions! 🎶💙 102 | 103 | --- 104 | 📌 **ESP32_Host_MIDI 2.0 - Faster, More Stable, More Complete!** 🚀🎵 105 | 106 | 107 | ```markdown 108 | # 🚀 ESP32_Host_MIDI - Versão 2.0 🎹📡 109 | 110 | Bem-vindo à versão **2.0** da biblioteca **ESP32_Host_MIDI**! Esta atualização traz melhorias significativas em relação à versão anterior, incluindo otimizações de desempenho, novos recursos e maior estabilidade para comunicação MIDI via **USB e BLE** no **ESP32-S3** com **T-Display S3**. 111 | 112 | --- 113 | 114 | ## 🔥 Principais Atualizações e Melhorias 115 | 116 | ### 🏗️ Arquitetura Modular Aprimorada 117 | - Estrutura de código melhorada para facilitar a manutenção e expansão. 118 | - Separação clara entre os módulos de **USB MIDI**, **BLE MIDI**, **Saída no Display** e **Processamento MIDI**. 119 | 120 | ### 🎛️ Nova Fila de Processamento MIDI 121 | - O **MIDI_Handler** agora usa uma **fila dinâmica** (`std::deque`) para armazenar eventos MIDI, proporcionando mais flexibilidade e melhor controle sobre o fluxo de dados. 122 | - Adicionado um **limite ajustável de eventos** para evitar sobrecarga. 123 | 124 | ### 📡 Melhorias no Host USB MIDI 125 | - **Reformulação do processamento de pacotes USB MIDI**, garantindo que apenas os **4 primeiros bytes relevantes** sejam utilizados, reduzindo a latência. 126 | - Otimização da **fila de mensagens USB**, aumentando a eficiência no tratamento dos pacotes MIDI recebidos. 127 | - **Novo sistema de callbacks** para eventos de conexão e desconexão de dispositivos MIDI USB. 128 | 129 | ### 📲 Suporte Aprimorado para BLE MIDI 130 | - O **ESP32 agora funciona como um servidor MIDI BLE mais estável e confiável**. 131 | - **Callback direto para processar mensagens MIDI recebidas via BLE**. 132 | - Publicidade BLE otimizada para maior compatibilidade com aplicativos MIDI móveis. 133 | 134 | ### 🎨 Melhorias na Exibição MIDI no Display 135 | - Utiliza **LovyanGFX** para otimização da renderização no **ST7789**. 136 | - Novo método `printMessage()` no **ST7789_Handler**: 137 | - Exibe **mensagens em várias linhas**. 138 | - **Destaque automático** para a última linha, melhorando a separação dos eventos MIDI. 139 | - Redução de flickering ao evitar atualizações desnecessárias da tela. 140 | 141 | ### 🏎️ Aumento de Desempenho 142 | - **Melhor uso de interrupções USB**, reduzindo a carga no loop principal. 143 | - **Remoção de delays desnecessários**, melhorando o processamento em tempo real dos eventos MIDI. 144 | - **Otimização na detecção de acordes e sequências de notas**. 145 | 146 | --- 147 | 148 | ## 📂 Estrutura do Código na Versão 2.0 149 | 150 | A nova estrutura de arquivos foi projetada para modularidade e clareza: 151 | 152 | 📁 **/src/** 153 | - `USB_Conexion.h / USB_Conexion.cpp` → Gerencia a comunicação USB MIDI. 154 | - `BLE_Conexion.h / BLE_Conexion.cpp` → Gerencia a comunicação BLE MIDI. 155 | - `MIDI_Handler.h / MIDI_Handler.cpp` → Interpreta e organiza eventos MIDI. 156 | - `ESP32_Pin_Config.h` → Define as configurações de pinos do ESP32. 157 | 158 | 📁 **/examples/** 159 | - Exemplo completo para **T-Display S3**, demonstrando a integração **MIDI USB + BLE + Display**. 160 | 161 | --- 162 | 163 | ## ⚡ Como Atualizar para a Versão 2.0? 164 | 165 | 1. **Baixe os arquivos atualizados da biblioteca** e substitua os arquivos antigos. 166 | 2. **Verifique seu código**: 167 | - Se você estava usando `onMidiMessage()` diretamente, agora deve sobrescrever `onMidiDataReceived()` em **USB_Conexion** ou **BLE_Conexion**. 168 | - Para exibir mensagens MIDI no **ST7789**, use `printMessage()` no **ST7789_Handler**. 169 | 170 | --- 171 | 172 | ## 🛠️ Como Usar a Nova Versão? 173 | 174 | ### 1️⃣ Inicializar Comunicação USB MIDI: 175 | ```cpp 176 | USB_Conexion usbMIDI; 177 | usbMIDI.begin(); 178 | ``` 179 | 180 | ### 2️⃣ Inicializar Comunicação BLE MIDI: 181 | ```cpp 182 | BLE_Conexion bleMIDI; 183 | bleMIDI.begin(); 184 | ``` 185 | 186 | ### 3️⃣ Processar MIDI no Loop Principal: 187 | ```cpp 188 | void loop() { 189 | usbMIDI.task(); 190 | bleMIDI.task(); 191 | } 192 | ``` 193 | 194 | ### 4️⃣ Exibir Mensagens MIDI na Tela: 195 | ```cpp 196 | ST7789_Handler display; 197 | display.init(); 198 | display.printMessage("MIDI Ativo!", 0, 0); 199 | ``` 200 | 201 | --- 202 | 203 | ## 🏆 Conclusão 204 | 205 | Esta atualização **melhora significativamente a estabilidade, eficiência e funcionalidade do ESP32_Host_MIDI**. Se você estava usando a versão anterior, recomendamos fortemente a atualização para aproveitar todos os novos recursos e otimizações! 206 | 207 | Agradecemos à comunidade pelo feedback e sugestões! 🎶💙 208 | 209 | --- 210 | 📌 **ESP32_Host_MIDI 2.0 - Mais Rápido, Mais Estável, Mais Completo!** 🚀🎵 211 | ``` 212 | 213 | -------------------------------------------------------------------------------- /src/USB_Conexion.cpp: -------------------------------------------------------------------------------- 1 | #include "USB_Conexion.h" 2 | #include 3 | 4 | static bool isValidMidiMessage(const uint8_t* midiData, size_t length) { 5 | // Validação mínima: verifica se há pelo menos 2 bytes e o primeiro tem bit 7 set. 6 | if (length < 2) return false; 7 | if ((midiData[0] & 0x80) == 0) return false; 8 | uint8_t status = midiData[0] & 0xF0; 9 | if (status == 0xC0 || status == 0xD0) 10 | return (length >= 2); 11 | else 12 | return (length >= 3); 13 | } 14 | 15 | USB_Conexion::USB_Conexion() 16 | : isReady(false), 17 | interval(0), 18 | lastCheck(0), 19 | clientHandle(nullptr), 20 | deviceHandle(nullptr), 21 | eventFlags(0), 22 | midiTransfer(nullptr), 23 | queueHead(0), 24 | queueTail(0), 25 | firstMidiReceived(false), 26 | isMidiDeviceConfirmed(false), 27 | deviceName("") 28 | { 29 | } 30 | 31 | void USB_Conexion::begin() { 32 | usb_host_config_t config = { 33 | .skip_phy_setup = false, 34 | .intr_flags = ESP_INTR_FLAG_LEVEL1, 35 | }; 36 | usb_host_install(&config); 37 | 38 | usb_host_client_config_t client_config = { 39 | .is_synchronous = true, 40 | .max_num_event_msg = 10, 41 | .async = { 42 | .client_event_callback = _clientEventCallback, 43 | .callback_arg = this, 44 | } 45 | }; 46 | usb_host_client_register(&client_config, &clientHandle); 47 | } 48 | 49 | void USB_Conexion::task() { 50 | usb_host_lib_handle_events(1, &eventFlags); 51 | usb_host_client_handle_events(clientHandle, 1); 52 | 53 | if (isReady && midiTransfer) { 54 | unsigned long now = millis(); 55 | if ((now - lastCheck) > interval) { 56 | lastCheck = now; 57 | usb_host_transfer_submit(midiTransfer); 58 | } 59 | } 60 | // Chama processQueue() para encaminhar os pacotes para onMidiDataReceived(). 61 | processQueue(); 62 | } 63 | 64 | void USB_Conexion::onDeviceConnected() { 65 | // Implementação padrão (vazia). A camada superior pode sobrescrever. 66 | } 67 | 68 | void USB_Conexion::onDeviceDisconnected() { 69 | // Implementação padrão (vazia). 70 | } 71 | 72 | void USB_Conexion::onMidiDataReceived(const uint8_t* data, size_t length) { 73 | // Implementação padrão (vazia). A camada superior deve sobrescrever. 74 | (void)data; 75 | (void)length; 76 | } 77 | 78 | bool USB_Conexion::enqueueMidiMessage(const uint8_t* data, size_t /*length*/) { 79 | int next = (queueHead + 1) % QUEUE_SIZE; 80 | if (next == queueTail) { 81 | // Fila cheia; descarta a mensagem. 82 | return false; 83 | } 84 | // Copia sempre os 4 primeiros bytes. 85 | size_t copyLength = 4; 86 | memcpy(usbQueue[queueHead].data, data, copyLength); 87 | usbQueue[queueHead].length = copyLength; 88 | queueHead = next; 89 | return true; 90 | } 91 | 92 | bool USB_Conexion::dequeueMidiMessage(RawUsbMessage &msg) { 93 | if (queueTail == queueHead) 94 | return false; 95 | msg = usbQueue[queueTail]; 96 | queueTail = (queueTail + 1) % QUEUE_SIZE; 97 | return true; 98 | } 99 | 100 | void USB_Conexion::processQueue() { 101 | RawUsbMessage msg; 102 | while (dequeueMidiMessage(msg)) { 103 | onMidiDataReceived(msg.data, msg.length); 104 | } 105 | } 106 | 107 | int USB_Conexion::getQueueSize() const { 108 | int size = queueHead - queueTail; 109 | if (size < 0) size += QUEUE_SIZE; 110 | return size; 111 | } 112 | 113 | const RawUsbMessage& USB_Conexion::getQueueMessage(int index) const { 114 | int realIndex = (queueTail + index) % QUEUE_SIZE; 115 | return usbQueue[realIndex]; 116 | } 117 | 118 | // ---------- Callbacks Internos ---------- 119 | 120 | void USB_Conexion::_clientEventCallback(const usb_host_client_event_msg_t *eventMsg, void *arg) { 121 | USB_Conexion *usbCon = static_cast(arg); 122 | esp_err_t err; 123 | switch (eventMsg->event) { 124 | case USB_HOST_CLIENT_EVENT_NEW_DEV: 125 | err = usb_host_device_open(usbCon->clientHandle, eventMsg->new_dev.address, &usbCon->deviceHandle); 126 | if (err != ESP_OK) { 127 | return; 128 | } 129 | { 130 | const usb_config_desc_t *config_desc; 131 | err = usb_host_get_active_config_descriptor(usbCon->deviceHandle, &config_desc); 132 | if (err != ESP_OK) { 133 | return; 134 | } 135 | usbCon->_processConfig(config_desc); 136 | } 137 | usbCon->onDeviceConnected(); 138 | break; 139 | case USB_HOST_CLIENT_EVENT_DEV_GONE: 140 | if (usbCon->midiTransfer) { 141 | usb_host_transfer_free(usbCon->midiTransfer); 142 | usbCon->midiTransfer = nullptr; 143 | } 144 | usb_host_device_close(usbCon->clientHandle, usbCon->deviceHandle); 145 | usbCon->isReady = false; 146 | usbCon->onDeviceDisconnected(); 147 | break; 148 | default: 149 | break; 150 | } 151 | } 152 | 153 | void USB_Conexion::_onReceive(usb_transfer_t *transfer) { 154 | USB_Conexion *usbCon = static_cast(transfer->context); 155 | if (transfer->status == 0 && transfer->actual_num_bytes > 0) { 156 | // Enfileira somente os 4 primeiros bytes. 157 | usbCon->enqueueMidiMessage(transfer->data_buffer, 4); 158 | } 159 | if (usbCon->isReady) { 160 | esp_err_t err = usb_host_transfer_submit(transfer); 161 | (void)err; 162 | } 163 | } 164 | 165 | void USB_Conexion::_processConfig(const usb_config_desc_t *config_desc) { 166 | const uint8_t* p = config_desc->val; 167 | uint16_t totalLength = config_desc->wTotalLength; 168 | uint16_t index = 0; 169 | bool claimedOk = false; 170 | while (index < totalLength) { 171 | uint8_t len = p[index]; 172 | if (len == 0) break; 173 | uint8_t descriptorType = p[index+1]; 174 | if (descriptorType == 0x04) { // Interface Descriptor 175 | uint8_t bInterfaceNumber = p[index+2]; 176 | uint8_t bAlternateSetting = p[index+3]; 177 | uint8_t bNumEndpoints = p[index+4]; 178 | uint8_t bInterfaceClass = p[index+5]; 179 | uint8_t bInterfaceSubClass = p[index+6]; 180 | if (bInterfaceClass == 0x01 && bInterfaceSubClass == 0x03) { 181 | esp_err_t err = usb_host_interface_claim(clientHandle, deviceHandle, bInterfaceNumber, bAlternateSetting); 182 | if (err == ESP_OK) { 183 | uint16_t idx2 = index + len; 184 | while (idx2 < totalLength) { 185 | uint8_t len2 = p[idx2]; 186 | if (len2 == 0) break; 187 | uint8_t type2 = p[idx2+1]; 188 | if (type2 == 0x04) break; 189 | if (type2 == 0x05 && bNumEndpoints > 0) { 190 | uint8_t bEndpointAddress = p[idx2+2]; 191 | uint8_t bmAttributes = p[idx2+3]; 192 | uint16_t wMaxPacketSize = (p[idx2+4] | (p[idx2+5] << 8)); 193 | uint8_t bInterval = p[idx2+6]; 194 | if (bEndpointAddress & 0x80) { 195 | uint8_t transferType = bmAttributes & 0x03; 196 | uint32_t timeout = (transferType == 0x02) ? 3000 : 0; 197 | esp_err_t e2 = usb_host_transfer_alloc(wMaxPacketSize, timeout, &midiTransfer); 198 | if (e2 == ESP_OK && midiTransfer != nullptr) { 199 | midiTransfer->device_handle = deviceHandle; 200 | midiTransfer->bEndpointAddress = bEndpointAddress; 201 | midiTransfer->callback = _onReceive; 202 | midiTransfer->context = this; 203 | midiTransfer->num_bytes = wMaxPacketSize; 204 | interval = (bInterval == 0) ? 1 : bInterval; 205 | isReady = true; 206 | claimedOk = true; 207 | return; 208 | } 209 | } 210 | } 211 | idx2 += len2; 212 | } 213 | usb_host_interface_release(clientHandle, deviceHandle, bInterfaceNumber); 214 | } 215 | } 216 | } 217 | index += len; 218 | } 219 | if (!claimedOk) { 220 | esp_err_t err = usb_host_transfer_alloc(64, 3000, &midiTransfer); 221 | if (err == ESP_OK && midiTransfer != nullptr) { 222 | midiTransfer->device_handle = deviceHandle; 223 | midiTransfer->bEndpointAddress = 0x81; 224 | midiTransfer->callback = _onReceive; 225 | midiTransfer->context = this; 226 | midiTransfer->num_bytes = 64; 227 | interval = 1; 228 | isReady = true; 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/MIDI_handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "MIDI_Handler.h" 3 | MIDIHandler midiHandler; 4 | 5 | #include 6 | #include 7 | #include "esp_heap_caps.h" // Para alocação em PSRAM 8 | 9 | // Definição da constante NOTE_TIMEOUT (1200 ms, conforme definido no header) 10 | const unsigned long MIDIHandler::NOTE_TIMEOUT; 11 | 12 | MIDIHandler::MIDIHandler() 13 | : maxEvents(20), 14 | globalIndex(0), 15 | nextMsgIndex(1), 16 | lastTempo(0), 17 | nextBlockIndex(1), 18 | currentBlockIndex(0), 19 | lastNoteOffTime(0), 20 | historyQueue(nullptr), 21 | historyQueueCapacity(0), 22 | historyQueueSize(0), 23 | historyQueueHead(0), 24 | usbCon(this), 25 | bleCon(this) { 26 | // Por padrão, o histórico está desativado. 27 | } 28 | 29 | void MIDIHandler::begin() { 30 | // Inicializa as conexões USB e BLE internas 31 | usbCon.begin(); 32 | bleCon.begin(); 33 | } 34 | 35 | void MIDIHandler::task() { 36 | // Processa os eventos das conexões USB e BLE 37 | usbCon.task(); 38 | bleCon.task(); 39 | } 40 | 41 | void MIDIHandler::enableHistory(int capacity) { 42 | if (capacity <= 0) { 43 | // Desativa o histórico, liberando a memória alocada 44 | if (historyQueue) { 45 | free(historyQueue); 46 | historyQueue = nullptr; 47 | } 48 | historyQueueCapacity = 0; 49 | historyQueueSize = 0; 50 | historyQueueHead = 0; 51 | Serial.println("Histórico MIDI desativado!"); 52 | return; 53 | } 54 | 55 | // Se já existe um histórico, libera a memória antes de realocar 56 | if (historyQueue) { 57 | free(historyQueue); 58 | historyQueue = nullptr; 59 | } 60 | 61 | // Aloca memória para o histórico na PSRAM 62 | historyQueue = static_cast(heap_caps_malloc(capacity * sizeof(MIDIEventData), MALLOC_CAP_SPIRAM)); 63 | 64 | if (!historyQueue) { 65 | Serial.println("Erro ao alocar memória para historyQueue na PSRAM!"); 66 | historyQueueCapacity = 0; 67 | return; 68 | } 69 | 70 | historyQueueCapacity = capacity; 71 | historyQueueSize = 0; 72 | historyQueueHead = 0; 73 | 74 | // Inicializa cada objeto no buffer usando placement new para evitar problemas com std::string 75 | for (int i = 0; i < historyQueueCapacity; i++) { 76 | new (&historyQueue[i]) MIDIEventData(); 77 | } 78 | 79 | Serial.println("Histórico MIDI ativado!"); 80 | } 81 | 82 | 83 | void MIDIHandler::setQueueLimit(int maxEvents) { 84 | this->maxEvents = maxEvents; 85 | } 86 | 87 | const std::deque& MIDIHandler::getQueue() const { 88 | return eventQueue; 89 | } 90 | 91 | // Função privada que expande o buffer dinâmico (ring buffer) na PSRAM. 92 | void MIDIHandler::expandHistoryQueue() { 93 | // Define nova capacidade (se o atual for 0, define um tamanho inicial, por exemplo, 10) 94 | int newCapacity = (historyQueueCapacity > 0) ? (historyQueueCapacity * 2) : 10; 95 | MIDIEventData* newQueue = static_cast(heap_caps_malloc(newCapacity * sizeof(MIDIEventData), MALLOC_CAP_SPIRAM)); 96 | if (!newQueue) { 97 | Serial.println("Erro ao expandir o histórico MIDI!"); 98 | return; 99 | } 100 | // Calcula o índice da cauda no buffer circular atual. 101 | int tail = (historyQueueHead + historyQueueCapacity - historyQueueSize) % historyQueueCapacity; 102 | // Copia os eventos do buffer antigo para o novo, preservando a ordem. 103 | for (int i = 0; i < historyQueueSize; i++) { 104 | int index = (tail + i) % historyQueueCapacity; 105 | newQueue[i] = historyQueue[index]; 106 | } 107 | // Inicializa os demais elementos do novo buffer (evita problemas com std::string) 108 | for (int i = historyQueueSize; i < newCapacity; i++) { 109 | new (&newQueue[i]) MIDIEventData(); 110 | } 111 | free(historyQueue); 112 | historyQueue = newQueue; 113 | historyQueueCapacity = newCapacity; 114 | // Após copiar os elementos, o novo head é imediatamente após o último elemento. 115 | historyQueueHead = historyQueueSize; 116 | Serial.printf("Histórico MIDI expandido para %d eventos!\n", historyQueueCapacity); 117 | } 118 | 119 | void MIDIHandler::addEvent(const MIDIEventData& event) { 120 | // Adiciona o evento na fila principal (armazenada na SRAM) 121 | eventQueue.push_back(event); 122 | processQueue(); 123 | 124 | // Se o histórico estiver ativo, adiciona o evento no buffer dinâmico na PSRAM 125 | if (historyQueue != nullptr && historyQueueCapacity > 0) { 126 | // Se o buffer estiver cheio, expande-o para não descartar nenhum evento 127 | if (historyQueueSize == historyQueueCapacity) { 128 | expandHistoryQueue(); 129 | } 130 | historyQueue[historyQueueHead] = event; 131 | historyQueueHead = (historyQueueHead + 1) % historyQueueCapacity; 132 | historyQueueSize++; 133 | } 134 | } 135 | 136 | 137 | // void MIDIHandler::addEvent(const MIDIEventData& event) { 138 | // // Adiciona o evento na fila principal (SRAM) 139 | // eventQueue.push_back(event); 140 | // processQueue(); 141 | 142 | // // Se o histórico estiver ativo, adiciona o evento no buffer circular da PSRAM 143 | // if (historyQueue != nullptr && historyQueueCapacity > 0) { 144 | // historyQueue[historyQueueHead] = event; 145 | // historyQueueHead = (historyQueueHead + 1) % historyQueueCapacity; 146 | // if (historyQueueSize < historyQueueCapacity) { 147 | // historyQueueSize++; 148 | // } 149 | // } 150 | // } 151 | 152 | void MIDIHandler::processQueue() { 153 | while (eventQueue.size() > static_cast(maxEvents)) { 154 | eventQueue.pop_front(); 155 | } 156 | } 157 | 158 | std::string MIDIHandler::getNoteName(int note) const { 159 | static const char* names[12] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; 160 | return names[note % 12]; 161 | } 162 | 163 | std::string MIDIHandler::getNoteWithOctave(int note) const { 164 | int octave = (note / 12) - 1; 165 | return getNoteName(note) + std::to_string(octave); 166 | } 167 | 168 | 169 | std::string MIDIHandler::getActiveNotes() const { 170 | std::ostringstream oss; 171 | oss << "{"; 172 | 173 | // Passo 1: Criar um vetor com as chaves do mapa (números das notas) 174 | std::vector sortedNotes; 175 | for (const auto& kv : activeNotes) { 176 | sortedNotes.push_back(kv.first); 177 | } 178 | 179 | // Passo 2: Ordenar o vetor 180 | std::sort(sortedNotes.begin(), sortedNotes.end()); 181 | 182 | // Passo 3: Construir a string ordenada 183 | bool first = true; 184 | for (int note : sortedNotes) { 185 | if (!first) oss << ", "; 186 | oss << getNoteName(note); // Pega o bloco correspondente 187 | first = false; 188 | } 189 | 190 | oss << "}"; 191 | return oss.str(); 192 | } 193 | 194 | std::vector MIDIHandler::getActiveNotesVector() const { 195 | std::vector activeNotesVector; 196 | 197 | // Usamos std::map para garantir a ordenação automática das notas MIDI 198 | std::map sortedActiveNotes(activeNotes.begin(), activeNotes.end()); 199 | 200 | // Percorre o mapa ordenado e adiciona as notas ao vetor 201 | for (const auto& kv : sortedActiveNotes) { 202 | activeNotesVector.push_back(getNoteName(kv.first)); 203 | } 204 | 205 | return activeNotesVector; 206 | } 207 | 208 | std::string MIDIHandler::getActiveNotesString() const { 209 | std::ostringstream oss; 210 | oss << "{"; 211 | 212 | // Usamos um std::map para manter as notas ordenadas automaticamente 213 | std::map sortedActiveNotes(activeNotes.begin(), activeNotes.end()); 214 | 215 | bool first = true; 216 | for (const auto& kv : sortedActiveNotes) { 217 | if (!first) oss << ", "; 218 | oss << getNoteName(kv.first) << ", {" << kv.second << "}"; 219 | first = false; 220 | } 221 | 222 | oss << "}"; 223 | return oss.str(); 224 | } 225 | 226 | 227 | size_t MIDIHandler::getActiveNotesCount() const { 228 | return activeNotes.size(); 229 | } 230 | 231 | void MIDIHandler::flushActiveNotes(unsigned long currentTime) { 232 | for (auto const& entry : activeNotes) { 233 | int note = entry.first; 234 | int block = entry.second; 235 | MIDIEventData event; 236 | event.index = ++globalIndex; 237 | event.msgIndex = activeMsgIndex[note]; 238 | event.tempo = currentTime; 239 | event.delay = (globalIndex == 1) ? 0 : (currentTime - lastTempo); 240 | lastTempo = currentTime; 241 | event.canal = 1; // Canal fixo para flush; ajuste se necessário. 242 | event.mensagem = "NoteOff"; 243 | event.nota = note; 244 | event.som = getNoteName(note); 245 | event.oitava = getNoteWithOctave(note); 246 | event.velocidade = 0; 247 | event.blockIndex = block; 248 | event.flushOff = 1; // Indica que essa nota foi desligada por um flush 249 | addEvent(event); 250 | } 251 | activeNotes.clear(); 252 | activeMsgIndex.clear(); 253 | currentBlockIndex = 0; 254 | lastNoteOffTime = 0; 255 | } 256 | 257 | // Limpa as notas ativas 258 | void MIDIHandler::clearActiveNotesNow() { 259 | activeNotes.clear(); 260 | activeMsgIndex.clear(); 261 | currentBlockIndex = 0; 262 | lastNoteOffTime = 0; 263 | } 264 | 265 | int MIDIHandler::lastBlock(const std::deque& queue) const { 266 | int maxBlock = 0; 267 | for (const auto& event : queue) { 268 | if (event.blockIndex > maxBlock) { 269 | maxBlock = event.blockIndex; 270 | } 271 | } 272 | return maxBlock; 273 | } 274 | 275 | std::vector MIDIHandler::getBlock(int block, const std::deque& queue, const std::vector& fields, bool includeLabels) const { 276 | std::vector blockEvents; 277 | 278 | // Filtra apenas os eventos NoteOn do bloco especificado 279 | for (const auto& event : queue) { 280 | if (event.blockIndex == block && event.mensagem == "NoteOn") { 281 | blockEvents.push_back(event); 282 | } 283 | } 284 | 285 | // Ordena os eventos por número de nota (campo "nota") 286 | std::sort(blockEvents.begin(), blockEvents.end(), [](const MIDIEventData& a, const MIDIEventData& b) { 287 | return a.nota < b.nota; 288 | }); 289 | 290 | std::vector result; 291 | 292 | // Se "all" foi solicitado, retorna todos os campos 293 | if (fields.size() == 1 && fields[0] == "all") { 294 | for (const auto& event : blockEvents) { 295 | std::ostringstream oss; 296 | if (includeLabels) { // Com rótulos 297 | oss << "{index:" << event.index 298 | << ", msgIndex:" << event.msgIndex 299 | << ", tempo:" << event.tempo 300 | << ", delay:" << event.delay 301 | << ", canal:" << event.canal 302 | << ", mensagem:" << event.mensagem 303 | << ", nota:" << event.nota 304 | << ", som:" << event.som 305 | << ", oitava:" << event.oitava 306 | << ", velocidade:" << event.velocidade 307 | << ", blockIndex:" << event.blockIndex 308 | << ", flushOff:" << event.flushOff << "}"; 309 | } else { // Sem rótulos 310 | oss << "{ " << event.index 311 | << ", " << event.msgIndex 312 | << ", " << event.tempo 313 | << ", " << event.delay 314 | << ", " << event.canal 315 | << ", " << event.mensagem 316 | << ", " << event.nota 317 | << ", " << event.som 318 | << ", " << event.oitava 319 | << ", " << event.velocidade 320 | << ", " << event.blockIndex 321 | << ", " << event.flushOff << " }"; 322 | } 323 | result.push_back(oss.str()); 324 | } 325 | } 326 | // Se apenas um campo foi solicitado 327 | else if (fields.size() == 1) { 328 | std::string field = fields[0]; 329 | for (const auto& event : blockEvents) { 330 | if (field == "som") { 331 | result.push_back(event.som); 332 | } else if (field == "oitava" || field == "octave") { 333 | result.push_back(event.oitava); 334 | } else if (field == "mensagem") { 335 | result.push_back(event.mensagem); 336 | } else if (field == "nota") { 337 | result.push_back(std::to_string(event.nota)); 338 | } else if (field == "tempo") { 339 | result.push_back(std::to_string(event.tempo)); 340 | } else if (field == "velocidade") { 341 | result.push_back(std::to_string(event.velocidade)); 342 | } 343 | } 344 | } 345 | // Se mais de um campo foi solicitado 346 | else { 347 | for (const auto& event : blockEvents) { 348 | std::ostringstream oss; 349 | bool first = true; 350 | for (const auto& field : fields) { 351 | if (!first) oss << ", "; 352 | if (field == "som") { 353 | oss << event.som; 354 | } else if (field == "oitava" || field == "octave") { 355 | oss << event.oitava; 356 | } else if (field == "mensagem") { 357 | oss << event.mensagem; 358 | } else if (field == "nota") { 359 | oss << event.nota; 360 | } else if (field == "tempo") { 361 | oss << event.tempo; 362 | } else if (field == "velocidade") { 363 | oss << event.velocidade; 364 | } 365 | first = false; 366 | } 367 | result.push_back(oss.str()); 368 | } 369 | } 370 | 371 | return result; 372 | } 373 | 374 | std::vector MIDIHandler::getAnswer(const std::string& field, bool includeLabels) const { 375 | return getAnswer(std::vector{ field }, includeLabels); 376 | } 377 | 378 | std::vector MIDIHandler::getAnswer(const std::vector& fields, bool includeLabels) const { 379 | std::vector result; 380 | const std::deque& queue = getQueue(); 381 | 382 | if (!queue.empty()) { 383 | int lastBlockIdx = lastBlock(queue); 384 | 385 | // Passa o vetor de campos diretamente para getBlock() 386 | result = getBlock(lastBlockIdx, queue, fields, includeLabels); 387 | } 388 | 389 | return result; 390 | } 391 | 392 | 393 | void MIDIHandler::handleMidiMessage(const uint8_t* data, size_t length) { 394 | const uint8_t* midiData = (length == 3) ? data : (length >= 4 ? data + 1 : nullptr); 395 | if (midiData == nullptr) return; 396 | 397 | unsigned long tempo = millis(); 398 | 399 | if (!activeNotes.empty() && (lastNoteOffTime != 0) && (tempo - lastNoteOffTime >= NOTE_TIMEOUT)) { 400 | flushActiveNotes(tempo); 401 | } 402 | 403 | uint8_t midiStatus = midiData[0] & 0xF0; 404 | int note = midiData[1]; 405 | int velocity = midiData[2]; 406 | int canal = (midiData[0] & 0x0F) + 1; 407 | unsigned long diff = (globalIndex == 0) ? 0 : (tempo - lastTempo); 408 | lastTempo = tempo; 409 | std::string mensagem; 410 | int msgIndex = 0; 411 | int blockIdx = currentBlockIndex; // Ajuste: inicializa com o bloco atual 412 | 413 | if (midiStatus == 0x90) { // NoteOn 414 | if (velocity > 0) { 415 | mensagem = "NoteOn"; 416 | msgIndex = nextMsgIndex++; 417 | if (activeNotes.empty()) { 418 | currentBlockIndex = nextBlockIndex++; // Garante que o índice seja atualizado corretamente 419 | } 420 | blockIdx = currentBlockIndex; 421 | activeNotes[note] = currentBlockIndex; 422 | activeMsgIndex[note] = msgIndex; 423 | } else { // NoteOn com velocity 0 equivale a NoteOff 424 | mensagem = "NoteOff"; 425 | auto it = activeNotes.find(note); 426 | if (it != activeNotes.end()) { 427 | blockIdx = it->second; // Recupera o bloco correto antes de apagar 428 | msgIndex = activeMsgIndex[note]; 429 | activeNotes.erase(it); 430 | activeMsgIndex.erase(note); 431 | } else { 432 | blockIdx = currentBlockIndex; // Mantém o último bloco conhecido 433 | } 434 | lastNoteOffTime = tempo; 435 | } 436 | } else if (midiStatus == 0x80) { // NoteOff 437 | mensagem = "NoteOff"; 438 | auto it = activeNotes.find(note); 439 | if (it != activeNotes.end()) { 440 | blockIdx = it->second; 441 | msgIndex = activeMsgIndex[note]; 442 | activeNotes.erase(it); 443 | activeMsgIndex.erase(note); 444 | } else { 445 | blockIdx = currentBlockIndex; // Mantém o último bloco conhecido 446 | } 447 | lastNoteOffTime = tempo; 448 | } else { 449 | return; 450 | } 451 | 452 | if (activeNotes.empty()) { 453 | currentBlockIndex = 0; 454 | lastNoteOffTime = 0; 455 | } 456 | 457 | MIDIEventData event; 458 | event.index = ++globalIndex; 459 | event.msgIndex = msgIndex; 460 | event.tempo = tempo; 461 | event.delay = diff; 462 | event.canal = canal; 463 | event.mensagem = mensagem; 464 | event.nota = note; 465 | event.som = getNoteName(note); 466 | event.oitava = getNoteWithOctave(note); 467 | event.velocidade = velocity; 468 | event.blockIndex = blockIdx; 469 | event.flushOff = 0; 470 | 471 | addEvent(event); 472 | } 473 | --------------------------------------------------------------------------------