Files
L'électron rare 410d0cf273
CI / sanity (push) Successful in 17s
CI / host-tests (push) Successful in 22s
CI / box3-build (push) Failing after 15s
CI / build (push) Successful in 2m25s
feat: delay page, visual click, clip, hardening
Improvement batch on top of the validated system. STATE grows
27 -> 28 bytes (click_pos): both firmwares must be reflashed
together, a version mix shows the disconnected banner.

- Box-3 'Effets' tab: delay level/feedback/length sliders wired
  to three new CMD opcodes (0x10-0x12) calling the ml_delay
  setters that were only reachable over MIDI before.
- visual metronome: STATE carries the click bar position; the
  click button flashes on each beat, brighter on beat 1.
- input clip indicator: near-full-scale input turns the input VU
  red for 2 s (peak hold).
- fix a regression from the VT100 removal: status messages could
  Serial.printf from the audio core (MIDI callbacks), busting the
  1.088 ms budget. They are now staged and printed by core 0.
- flush the link RX buffer after an SD operation so commands
  touched during the blocking save/load no longer replay in a
  burst afterwards.
- static_assert on sizeof(link_state_t) in the shared header and
  TRACK_CNT == LINK_TRACK_CNT in the looper.
- protocol.md updated: 28-byte layout, delay opcodes, real
  liveness rules (any valid frame, 8 s during SD operations).

Verified: pio run SUCCESS, idf.py build OK, C host test OK,
5/5 pytest.
2026-08-06 14:52:44 +02:00

6.2 KiB
Raw Permalink Blame History

Protocole de liaison UART « looper link »

Liaison série entre l'ESP32 Audio Kit (moteur looper) et l'ESP32-S3-BOX-3 (afficheur + surface tactile).

  • Source de vérité : shared/looper_link.h (header C partagé par les deux firmwares) et tests/link_sim.py (implémentation Python miroir).
  • Physique : UART 3 fils (TX/RX croisés + GND), 460 800 bauds, 8N1, niveaux 3,3 V. Brochage : voir wiring.md.

Trame

[0xA5] [type:1] [len:1] [payload:len] [crc8:1]
  • 0xA5 : octet de synchronisation (SOF).
  • type : type de message (voir ci-dessous).
  • len : taille du payload (≤ 64 octets).
  • crc8 : CRC8 Dallas/Maxim (polynôme réfléchi 0x8C) calculé sur type + len + payload.

Une trame corrompue est ignorée silencieusement ; le parseur se resynchronise sur le prochain 0xA5. La perte d'une trame est sans conséquence : l'état complet est réémis 20 fois par seconde.

Messages

STATE (0x01) — Audio Kit → Box-3, périodique à 20 Hz

Payload : link_state_t, 28 octets, little-endian, packé.

Champ Type Description
flags[4] u8×4 par piste : bit0 DATA, bit1 ACTIVE, bit2 REC, bit3 ERASE
gain_out[4] u8×4 gain de sortie par piste (0255)
pan[4] u8×4 panoramique par piste (0 = gauche, 128 = centre, 255 = droite)
pos u16 position de lecture relative (065535 sur MAX_LOOP)
len u16 longueur de boucle relative (065535 sur MAX_LOOP)
tempo_bpm u16 tempo du métronome en BPM
click_on u8 métronome audible (0/1)
click_pos u8 position dans la mesure de 4 temps (0255) — métronome visuel
input_src u8 source d'entrée : 0 = line, 1 = mic
gain_in u8 gain d'entrée (0255)
vu[6] u8×6 VU-mètres : pistes 14, entrée, sortie

La taille est verrouillée par un static_assert dans le header partagé et par les tests hôte (C et Python) — toute dérive de layout casse la compilation ou les tests.

EVT (0x02) — Audio Kit → Box-3, ponctuel

Payload : link_evt_t, 2 octets : code, arg.

Code Nom Argument
0x01 SD_PROGRESS avancement 0100 %
0x02 SD_DONE numéro de session
0x03 SD_ERROR 1 = mount, 2 = open, 3 = format, 4 = write
0x04 RESET_DONE

CMD (0x10) — Box-3 → Audio Kit

Payload : link_cmd_t, 4 octets : op (u8), arg0 (u8), arg1 (u16 LE). Chaque opcode appelle une fonction existante du looper — la Box-3 ne porte aucune logique métier.

Op Nom Arguments Fonction appelée
0x01 SELECT_TRACK arg0 = piste (03) Loop_SelectTrack
0x02 STOP_TRACK arg0 = piste Loop_StopChannel
0x03 ERASE_TRACK arg0 = piste Loop_EraseTrack
0x04 SET_LENGTH Loop_SetLength
0x05 START_ALL Loop_StartAll
0x06 STOP_ALL Loop_Stop
0x07 GAIN_OUT arg0 = piste, arg1 = 065535 Loop_SetChannelGainOut
0x08 PAN arg0 = piste, arg1 = 065535 Loop_SetChannelPan
0x09 GAIN_IN arg1 = 065535 Loop_SetChannelGainIn
0x0A CLICK_TOGGLE Click_ToggleOnOff
0x0B SET_TEMPO arg1 = BPM Click_SetTempoBPM
0x0C TOGGLE_SRC MTLooper_ToggleSource
0x0D RESET_ALL Loop_CompleteReset (+ EVT RESET_DONE)
0x0E SD_SAVE arg0 = session (07 côté UI) Sd_RequestSave
0x0F SD_LOAD arg0 = session Sd_RequestLoad
0x10 DELAY_FB arg1 = 065535 Delay_SetFeedback
0x11 DELAY_LEN arg1 = 065535 Delay_SetLength
0x12 DELAY_LEVEL arg1 = 065535 Delay_SetOutputLevel

Pas d'acquittement sur CMD : l'effet est visible dans le STATE suivant (≤ 50 ms). Les réglages de delay ne sont pas réémis dans STATE (fire-and-forget, comme les sliders MIDI).

Les commandes reçues pendant une opération SD sont ignorées : le looper purge son tampon de réception à la fin de l'opération.

Détection de déconnexion

Toute trame CRC-valide (STATE ou EVT) rafraîchit la vivacité du lien. La Box-3 affiche un bandeau « Looper déconnecté » après 500 ms sans trame valide (LINK_TIMEOUT_MS) — délai élargi à 8 s (LINK_SD_TIMEOUT_MS) entre l'envoi d'une commande SD et son événement terminal, car le looper n'émet plus de STATE pendant l'accès carte (seuls les SD_PROGRESS circulent, à intervalle irrégulier).

Vecteur de test figé

CMD SELECT_TRACK piste 2 :

A5 10 04 01 02 00 00 84

Ce vecteur est vérifié à l'identique par tests/test_link_protocol.c (C) et tests/test_link_sim.py (Python) — il garantit la parité des deux implémentations. Ne pas le modifier sans changer les deux tests.

Tester sans matériel complet

tests/link_sim.py (nécessite pyserial pour le CLI) :

# décoder ce que l'Audio Kit émet (via l'adaptateur USB-série de ton choix)
python3 tests/link_sim.py --port /dev/cu.usbserial-XXXX monitor

# envoyer une commande au looper
python3 tests/link_sim.py --port ... cmd SELECT_TRACK 2
python3 tests/link_sim.py --port ... cmd SET_TEMPO 0 90

# simuler un Audio Kit pour tester la Box-3 seule (STATE de démo à 20 Hz)
python3 tests/link_sim.py --port ... replay-state