Conception validee avec Ralph : noyau portable + une couche par systeme derriere trois contrats explicites. L'implementation Cocoa est deplacee sans etre modifiee. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
6.8 KiB
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 :
- MLX cible le GPU Apple Silicon. Aucun équivalent ailleurs.
- L'interface est entièrement Cocoa (424 lignes).
fcntl.flockn'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, puis mémorisation :
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.
| Configuration | Moteur | Modèle | Latence |
|---|---|---|---|
| Mac Apple Silicon | MLX | whisper-medium |
~1,3 s (mesuré) |
| Windows + NVIDIA | faster-whisper CUDA | medium float16 |
1–2 s (à mesurer) |
| Mac Intel · Windows CPU | faster-whisper CPU | small int8 |
3–8 s (à mesurer) |
medium est écarté sur CPU : 15 à 30 s tuerait l'usage.
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. Hors périmètre
Linux ; transcription en continu pendant la parole ; installeur double-clic ; synchronisation du vocabulaire entre machines.