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

132 lines
6.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](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) :
```bash
# 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
```