zonza/docs/superpowers/specs/2026-08-23-zonza-multiplateforme-design.md
Ralph Mayola d7680227db Spec : remplacer mes estimations par les mesures reelles
L'estimation annoncait 15 a 30 s pour medium sur CPU et l'ecartait d'avance. La
mesure dit 4,4 s — et ce modele est PLUS JUSTE que le moteur GPU en service.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 18:45:42 +02:00

180 lines
9.1 KiB
Markdown

# Zonza multiplateforme — conception
**Date :** 2026-08-23
**But :** faire tourner Zonza sur Mac Apple Silicon, Mac Intel, Windows avec NVIDIA et Windows sans GPU, sans rien casser de la version macOS en service.
---
## 1. Point de départ
Audit du 2026-08-23 : **55 % du code est déjà portable**.
| Fichier | Lignes | État |
|---|---|---|
| `core.py` | 228 | portable |
| `controller.py` | 164 | portable — **aucun import Cocoa** |
| `audio_level.py` | 55 | portable |
| `engine.py` | 160 | 1 import Quartz, 4 appels `afplay`, et MLX |
| `app.py` | 156 | 100 % Cocoa |
| `pulse_window.py` | 268 | 100 % Cocoa |
Trois obstacles réels :
1. **MLX cible le GPU Apple Silicon.** Aucun équivalent ailleurs.
2. **L'interface est entièrement Cocoa** (424 lignes).
3. **`fcntl.flock` n'existe pas sur Windows** — le verrou d'instance unique, qui empêche le gel constaté le 2026-08-21, disparaîtrait en silence.
## 2. Approche retenue
**Noyau portable + une couche par système, derrière des contrats explicites.**
Écartées : réécrire toute l'interface en Qt (perd les contournements Tahoe durement acquis, risque de régression sur la machine en service) ; maintenir deux applications séparées (double surface de bugs, alors que deux jours viennent d'être passés à en corriger une seule).
L'approche retenue ne sacrifie rien : l'implémentation Cocoa est **déplacée sans être modifiée**, et le contrat qu'elle respecte existe déjà — c'est celui que `FauxPulse` implémente dans les tests.
## 3. Structure
```
zonza/
├── core.py inchangé — pur
├── audio_level.py inchangé — pur
├── controller.py inchangé — ne parle qu'aux contrats
├── moteur/ choisir_transcripteur() · mlx.py · faster.py
├── interface/ choisir_interface() · cocoa/ · qt/
├── systeme/ choisir_systeme() · macos.py · windows.py
└── lanceur.py point d'entrée commun
```
### Contrats
| Contrat | Méthodes |
|---|---|
| `Transcripteur` | `warmup()` · `transcrire(audio) -> str` |
| `Overlay` | `show()` · `hide()` · `set_level()` · `set_status()` · `rearmer_garde()` |
| `Systeme` | `jouer_son()` · `modificateurs_enfonces()` · `raccourci_defaut()` · `chemin_log()` · `acquerir_verrou_instance()` |
`acquerir_verrou_instance` rejoint la couche système : `fcntl.flock` sur macOS, `msvcrt.locking` sur Windows.
## 4. Moteur de transcription
Détection au premier lancement, puis **mémorisée dans `reglages.json`**, à côté du journal (`~/Library/Application Support/Zonza/` ou `%LOCALAPPDATA%\Zonza\`). Supprimer ce fichier relance la détection.
```
Apple Silicon ? → MLX
sinon, CUDA disponible ? → faster-whisper GPU
sinon → faster-whisper CPU
```
La **décision** est une fonction pure recevant `(plateforme, processeur, cuda_disponible)` ; l'**interrogation** de la machine est séparée. Les quatre configurations sont donc testables depuis un seul Mac.
**Mesuré le 2026-08-23** — trois échantillons, amorce de vocabulaire active, sur le CPU d'un Apple Silicon :
| Moteur et modèle | Latence | Termes techniques | Taille |
|---|---|---|---|
| faster-whisper `base` int8 CPU | 0,52 s | 10/13 | 142 Mo |
| faster-whisper `small` int8 CPU | 1,42 s | 11/13 | 464 Mo |
| faster-whisper `medium` int8 CPU | 4,40 s | **13/13** | 1460 Mo |
| MLX `medium` GPU *(moteur actuel)* | 0,75 s | 11/13 | 489 Mo |
🔴 **Ces chiffres démentent l'estimation initiale de cette spec**, qui annonçait 15 à 30 s pour `medium` sur CPU et l'écartait d'avance. C'est 4,4 s. Et `faster-whisper medium` sur CPU est **plus juste que le moteur GPU en service** (13/13 contre 11/13).
Choix retenus dans `core.MODELES_PAR_MOTEUR` :
| Configuration | Moteur | Modèle |
|---|---|---|
| Mac Apple Silicon | MLX | `mlx-community/whisper-medium` |
| Windows + NVIDIA | faster-whisper CUDA | `Systran/faster-whisper-medium` |
| Mac Intel · Windows CPU | faster-whisper CPU | `Systran/faster-whisper-small` |
⚠️ **Réserve sur le CPU** : les 4,4 s de `medium` ont été mesurées sur un CPU Apple Silicon, rapide. Un CPU Intel ou Windows peut demander trois à quatre fois plus. D'où `small` par défaut — même précision (11/13) que le MLX utilisé aujourd'hui, pour 1,4 s ici. **La commande de diagnostic mesurera la machine réelle et pourra promouvoir `medium`.**
`CONFIG["model"]`, désormais à `None`, force manuellement le modèle et prime sur la table.
**Première étape de l'implémentation : mesurer réellement les deux dernières lignes.** Le 2026-08-21, une mesure analogue a renversé l'intuition — `large-v3-turbo` s'est révélé PIRE que `medium`.
`faster-whisper` accepte `initial_prompt` avec la même sémantique : **l'amorce de 47 termes survit sur les quatre configurations**. Sur `small`, elle compense partiellement une moindre précision sur les noms propres — à mesurer, pas à espérer.
Réglages manuels : `CONFIG["moteur"]` et `CONFIG["modele"]` forcent le choix.
## 5. Interface
L'overlay Qt reprend le dessin à l'identique — `QPainter` pour `NSBezierPath`, `QTimer` pour `NSTimer`, fenêtre *frameless*, toujours au-dessus, translucide, `WindowTransparentForInput`. Icône de barre par `QSystemTrayIcon`.
**La géométrie reste partagée** : `core.bar_metrics()` ne connaît aucun système, les deux interfaces la lisent. Un changement de taille s'applique aux deux.
## 6. Couche système
| | macOS | Windows |
|---|---|---|
| Son | `afplay` | `winsound.PlaySound` |
| Modificateurs | Quartz `CGEventSourceFlagsState` | `GetAsyncKeyState` (ctypes) |
| Journal | `~/Library/Logs/Zonza.log` | `%LOCALAPPDATA%\Zonza\Zonza.log` |
| Raccourci | `Cmd+Ctrl+D` | `Ctrl+Alt+D` |
| Verrou | `fcntl.flock` | `msvcrt.locking` |
`pynput`, `pyperclip`, `sounddevice` et `numpy` fonctionnent tels quels sur Windows : raccourci global, presse-papier et capture micro ne demandent aucun travail.
## 7. Installation
`install.sh` et `install.ps1` créent l'environnement et n'installent **que ce qui sert à la machine** (`mlx-whisper` seulement sur Apple Silicon, `PySide6` seulement où Qt est utilisé), puis posent le lancement au démarrage. `build_app.sh` reste inchangé pour le bundle Mac.
Un installeur double-clic est **écarté** : produire un `.exe` exige une machine Windows, absente ; et py2app/PyInstaller ont déjà été retirés de ce projet en juin parce qu'empaqueter les shaders Metal de MLX échouait.
## 8. Vérification
**Contrainte assumée : aucune machine Windows n'est disponible pour vérifier.**
Deux réponses.
**Tests par contrat.** Chaque contrat a UNE suite, rejouée contre TOUTES ses implémentations. Si l'overlay Qt oublie `rearmer_garde`, la suite rougit sans Windows. La détection matérielle étant pure, les quatre configurations sont couvertes depuis un seul Mac.
**Commande de diagnostic**, exécutée par Ralph sur la machine cible :
```
zonza --diagnostic
```
Sortie attendue :
```
Plateforme : Windows 11 / x86_64 / CUDA absent
Moteur retenu : faster-whisper CPU, modèle small int8
Micro : ✓ 1064 blocs reçus en 1 s
Transcription : ✓ 2,8 s — « Pousse le commit sur Gitea »
Presse-papier : ✓
Raccourci : ✓ Ctrl+Alt+D capté
Overlay : ✓ affiché puis refermé
Verrou : ✓ seconde instance refusée
```
**Engagement :** il ne sera jamais affirmé que Zonza fonctionne sous Windows. Seulement que les tests passent, que les contrats sont respectés et que le diagnostic est prêt. La preuve vient de la machine cible.
## 9. Acquis à ne pas perdre
Les correctifs des 21 et 22 août doivent survivre au port, et être **testés dans chaque implémentation** :
- aucune opération audio sur le thread d'interface (le gel venait de là) ;
- libération du flux audio avant toute réouverture (Zonza s'affamait elle-même) ;
- attente du relâchement des modificateurs avant le collage ;
- durée de vie maximale de l'overlay (210 s, 20 s en transcription) ;
- alerte micro muet à 1,8 s ;
- verrou d'instance unique.
## 10. Découpage de l'implémentation
Le périmètre dépasse un seul plan. Quatre étapes, chacune livrable et vérifiable seule :
**Étape 1 — Extraire les contrats.** Créer `moteur/`, `interface/`, `systeme/` ; y déplacer le code Cocoa **sans le modifier** ; faire passer `controller.py` par les contrats. Aucun changement de comportement : les 80 tests actuels doivent rester verts, et Zonza fonctionner à l'identique sur le Mac. C'est l'étape qui dé-risque toutes les suivantes.
**Étape 2 — Moteur interchangeable.****Fait le 2026-08-23** (`2ccb614`, 115 tests) : `moteur/faster.py`, table `MODELES_PAR_MOTEUR`, forçage manuel, et les mesures ci-dessus.
**Étape 3 — Interface Qt.** Rejouer la suite de contrat de l'overlay contre l'implémentation Qt. Vérifiable sur le Mac : Qt y tourne aussi, ce qui permet de comparer les deux overlays côte à côte.
**Étape 4 — Couche Windows, installation, diagnostic.** La seule étape dont la vérification finale dépend d'une machine Windows.
Les étapes 1 à 3 sont entièrement vérifiables sans Windows.
## 11. Hors périmètre
Linux ; transcription en continu pendant la parole ; installeur double-clic ; synchronisation du vocabulaire entre machines.