Pular para conteúdo

💾 Histórico em PSRAM

O MIDIHandler pode manter um buffer circular de eventos que persiste além do limite da fila principal (maxEvents). Quando PSRAM está disponível, o buffer é alocado lá — permitindo históricos de centenas ou milhares de eventos sem consumir heap.


Quando Usar

  • Análise offline: processar uma sessão de improvisação depois que terminou
  • Visualização de histórico: scrollar por eventos passados no display
  • Debug: inspecionar o que aconteceu nos últimos N eventos
  • Machine learning: coletar dados de performance para análise

Configuração

Via MIDIHandlerConfig

MIDIHandlerConfig cfg;
cfg.historyCapacity = 500;  // guardar os últimos 500 eventos
midiHandler.begin(cfg);

Via enableHistory() — após begin()

midiHandler.begin();
midiHandler.enableHistory(500);  // pode ser chamado a qualquer momento

Como Funciona

flowchart TD
    EV["Novo Evento MIDI\n(MIDIEventData)"]
    QUEUE["Fila Principal\n(maxEvents = 20)\ngetQueue()"]
    HISTORY["Buffer Circular\n(historyCapacity = 500)\nPSRAM quando disponível"]

    EV --> QUEUE
    EV --> HISTORY

    subgraph PSRAM_CHECK["Alocação"]
        CHECK{{"ESP32_HOST_MIDI_HAS_PSRAM?"}}
        PSRAMOK["heap_caps_malloc\nMALLOC_CAP_SPIRAM"]
        HEAPOK["malloc()\n(heap normal)"]
    end

    HISTORY --> CHECK
    CHECK -->|"✅ Sim"| PSRAMOK
    CHECK -->|"❌ Não"| HEAPOK

    style PSRAM_CHECK fill:#37474F,color:#fff
    style PSRAMOK fill:#1B5E20,color:#fff
    style HEAPOK fill:#BF360C,color:#fff

PSRAM no ESP32-S3

Para ativar PSRAM no Arduino IDE:

Tools → PSRAM → "OPI PSRAM" (para ESP32-S3 com PSRAM OPI)
        ou
Tools → PSRAM → "Quad PSRAM" (para ESP32-S3 com PSRAM SIP)

Verificar se está ativo:

Serial.printf("PSRAM: %u bytes\n", ESP.getPsramSize());
Serial.printf("PSRAM livre: %u bytes\n", ESP.getFreePsram());

Verificar disponibilidade

A macro ESP32_HOST_MIDI_HAS_PSRAM é definida automaticamente em tempo de compilação se o sdkconfig tiver CONFIG_SPIRAM ou CONFIG_SPIRAM_SUPPORT.


Tamanho do Histórico

Cada MIDIEventData ocupa aproximadamente 80–120 bytes (depende do tamanho das strings STL).

Capacidade Memória aprox. Adequado para
100 ~10 KB Heap (sem PSRAM)
500 ~50 KB Heap ou PSRAM
1000 ~100 KB PSRAM recomendado
5000 ~500 KB PSRAM obrigatório

Acessar o Histórico

// Habilitar histórico de 500 eventos
midiHandler.enableHistory(500);

// A fila principal (getQueue()) continua limitada por maxEvents
// O histórico é acessado internamente — não há API direta de leitura

// Por ora, use a fila + seu próprio buffer para acesso ao histórico:
std::vector<MIDIEventData> meuHistorico;

void loop() {
    midiHandler.task();

    for (const auto& ev : midiHandler.getQueue()) {
        meuHistorico.push_back(ev);

        // Limitar ao tamanho desejado (buffer circular manual)
        if (meuHistorico.size() > 500) {
            meuHistorico.erase(meuHistorico.begin());
        }
    }
}

API de leitura do histórico

A API de leitura direta do buffer histórico interno está em desenvolvimento. Por enquanto, o padrão recomendado é manter seu próprio std::vector<MIDIEventData> como mostrado acima, usando PSRAM via ps_malloc() se necessário.


Alocar Vetor em PSRAM

Se você quiser armazenar seu próprio histórico em PSRAM:

#include <esp_heap_caps.h>

const int MAX_HISTORY = 1000;
MIDIEventData* historyBuffer = nullptr;
int historySize = 0;

void setup() {
    midiHandler.begin();

#if ESP32_HOST_MIDI_HAS_PSRAM
    historyBuffer = (MIDIEventData*)heap_caps_malloc(
        MAX_HISTORY * sizeof(MIDIEventData),
        MALLOC_CAP_SPIRAM
    );
    if (historyBuffer) {
        Serial.println("Histórico alocado em PSRAM");
    } else {
        // Fallback para heap
        historyBuffer = (MIDIEventData*)malloc(MAX_HISTORY * sizeof(MIDIEventData));
    }
#else
    historyBuffer = (MIDIEventData*)malloc(MAX_HISTORY * sizeof(MIDIEventData));
#endif
}

Exemplo — Analisador de Sessão

Coleta uma sessão de improvisação e mostra estatísticas ao final:

#include <ESP32_Host_MIDI.h>

std::vector<MIDIEventData> sessao;
bool gravando = true;

void setup() {
    Serial.begin(115200);

    MIDIHandlerConfig cfg;
    cfg.maxEvents = 20;
    cfg.historyCapacity = 500;
    midiHandler.begin(cfg);

    Serial.println("Gravando sessão... (pressione botão para parar)");
}

void loop() {
    midiHandler.task();

    if (gravando) {
        for (const auto& ev : midiHandler.getQueue()) {
            if (ev.status == "NoteOn") {
                sessao.push_back(ev);
            }
        }
    }

    // Simular fim da sessão após 30 segundos
    if (millis() > 30000 && gravando) {
        gravando = false;
        analisarSessao();
    }
}

void analisarSessao() {
    Serial.printf("=== ANÁLISE DA SESSÃO ===\n");
    Serial.printf("Total de notas: %d\n", (int)sessao.size());

    // Nota mais tocada
    int contador[128] = {0};
    for (const auto& ev : sessao) contador[ev.note]++;

    int notaMaisTocada = 0;
    for (int i = 1; i < 128; i++) {
        if (contador[i] > contador[notaMaisTocada]) notaMaisTocada = i;
    }

    Serial.printf("Nota mais tocada: %d (vezes: %d)\n",
        notaMaisTocada, contador[notaMaisTocada]);

    // Velocidade média
    int somaVel = 0;
    for (const auto& ev : sessao) somaVel += ev.velocity;
    Serial.printf("Velocidade média: %d\n",
        sessao.empty() ? 0 : somaVel / (int)sessao.size());
}

Próximos Passos