@@ -115,7 +115,13 @@ static volatile bool midi_in_error_recovery_needed = false;
115115#if 1
116116static usb_transfer_t * midi_out_xfer = nullptr ;
117117static volatile bool midi_out_in_flight = false ;
118- static uint8_t out_rb_storage[512 ] __attribute__((aligned(4 )));
118+ // OUT ring buffer. Holds USB-MIDI event packets (4 bytes each).
119+ // A full controller repaint queues ~150 packets (80 wipe + 64 pads +
120+ // tracks + scenes), so 512 bytes (128 packets) was too small and
121+ // dropped late packets — most noticeably the post-power-on scene 8
122+ // send, which would get dropped before the OUT thread could drain.
123+ // 2048 bytes (512 packets) gives comfortable headroom.
124+ static uint8_t out_rb_storage[2048 ] __attribute__((aligned(4 )));
119125static StaticRingbuffer_t out_rb_struct;
120126static RingbufHandle_t midi_out_rb = nullptr ;
121127#endif
@@ -329,31 +335,29 @@ static void midi_client_event_cb(const usb_host_client_event_msg_t* msg, void* /
329335 // rebuilds on first boot, re-applied presets from prior disconnect,
330336 // etc.).
331337 if (midiUsermodPtr) {
332- MIDI_LOG (" connect: repainting controller (immediate + 500ms)" );
333- stateUpdated (CALL_MODE_BUTTON );
334-
335- // 1500ms-delayed "settled" repaint. Always spawn — multiple paints
336- // are harmless and idempotent (each just re-sends the full state).
337- // The 1500ms delay gives WLED's setup loop time to apply the boot
338- // preset / load the boot playlist before we paint — otherwise the
339- // first paint sees currentPreset=0 and currentPlaylist=0 (defaults)
340- // and the playlist parent pad shows as plain blue until the 500ms
341- // paint kicks in. 1500ms comfortably covers WLED's setup work
342- // including playlist loading from LittleFS.
338+ // USB_Task now starts after the SD card in WLED's task list
339+ // (the boot-blocking mount task runs first), so by the time
340+ // we get this connect callback, the segment/preset structures
341+ // are already initialized and a direct repaint is safe.
342+ // Always do an immediate repaint to handle the common case
343+ // where the boot preset was already applied, plus a 1500ms-
344+ // delayed repaint for the case where the boot playlist is
345+ // still being set up (currentPlaylist may be 0 at t=0 even
346+ // though currentPreset is set).
347+ midiUsermodPtr->requestFullRepaint ();
348+ // Single delayed "settled" repaint using a static buffer (no
349+ // heap allocation, so no race with the SD-card task).
343350 struct RepaintCtx { MidiUsermod* ptr; };
344- RepaintCtx* ctx = new RepaintCtx{ midiUsermodPtr };
351+ static RepaintCtx s_repaint_ctx;
352+ s_repaint_ctx.ptr = midiUsermodPtr;
345353 xTaskCreate (
346354 [](void * arg) {
347- vTaskDelay (pdMS_TO_TICKS (1500 ));
348355 auto * c = (RepaintCtx*)arg;
349- if (c->ptr ) {
350- MIDI_LOG (" connect: settled repaint firing" );
351- c->ptr ->requestFullRepaint ();
352- }
353- delete c;
356+ vTaskDelay (pdMS_TO_TICKS (1500 ));
357+ if (c->ptr ) c->ptr ->requestFullRepaint ();
354358 vTaskDelete (NULL );
355359 },
356- " midi_repaint" , 2048 , ctx , 1 , nullptr );
360+ " midi_repaint" , 2048 , &s_repaint_ctx , 1 , nullptr );
357361 }
358362 } else {
359363 MIDI_LOG (" event: NEW_DEV addr=%d configure failed (likely MSC or unknown device)" , msg->new_dev .address );
0 commit comments