diff --git a/app.py b/app.py index 5d58370..1ae1971 100644 --- a/app.py +++ b/app.py @@ -123,7 +123,7 @@ class ZonzaApp(NSObject): def _warmup_async(self): def run(): try: - self.controller.transcriber.warmup() + self.controller.transcripteur.warmup() except Exception as e: print(f"[erreur] warmup modèle : {e}") threading.Thread(target=run, daemon=True).start() diff --git a/controller.py b/controller.py index 02587aa..06536e2 100644 --- a/controller.py +++ b/controller.py @@ -34,7 +34,7 @@ class ZonzaController: lambda fn: threading.Thread(target=fn, daemon=True).start() ) self.recorder = Recorder(config["sample_rate"], on_block=self._on_audio_block) - self.transcriber = transcripteur + self.transcripteur = transcripteur self.systeme = systeme self.recording = False self._lock = threading.Lock() @@ -43,6 +43,16 @@ class ZonzaController: self._blocs_recus = 0 # compteur pour détecter un micro muet self._veille_silence = None + def _jouer_son(self, nom): + """Joue un son, sauf si l'utilisateur les a coupés par le menu. + + La décision vit ICI et non dans la couche système : celle-ci doit rester + bête, sinon chaque nouvelle plateforme devra repenser à ce garde-fou. + """ + if not self.config["sounds"]: + return + self.systeme.jouer_son(nom) + def _on_audio_block(self, block): # thread audio : RMS brut → mise en forme (gain + courbe) → lissage attack/release # → push vers la fenêtre (thread-safe). Le lissage évite les saccades : montée @@ -69,7 +79,7 @@ class ZonzaController: self.recording = True self.pulse.show() self.pulse.set_status("Je vous écoute…") - self.systeme.jouer_son("start") + self._jouer_son("start") self.on_state("recording") self._arm_safety_timer() self._armer_veille_silence() @@ -89,7 +99,7 @@ class ZonzaController: if not entree_audio_muette(self._blocs_recus, self.recording): return print("[micro] aucun son reçu — le micro est probablement pris par une autre application") - self.systeme.jouer_son("error") + self._jouer_son("error") self.run_on_main(lambda: self.pulse.set_status("Aucun son — micro pris ailleurs ?")) def _ouvrir_flux(self): @@ -98,7 +108,7 @@ class ZonzaController: self.recorder.start() except Exception as e: print(f"[erreur] démarrage enregistrement : {e}") - self.systeme.jouer_son("error") + self._jouer_son("error") self.recording = False self._cancel_safety_timer() self.run_on_main(self.pulse.hide) @@ -144,11 +154,11 @@ class ZonzaController: principal (2026-08-21).""" try: audio = self.recorder.stop() - self.systeme.jouer_son("done") + self._jouer_son("done") if not is_long_enough(len(audio), self.config["sample_rate"], self.config["min_duration_s"]): return - text = self.transcriber.transcrire(audio) + text = self.transcripteur.transcrire(audio) if not text: return print(f"[texte] {text}") @@ -156,10 +166,10 @@ class ZonzaController: inject_text(text, self.systeme) except Exception as e: print(f"[erreur] collage échoué ({e}) — texte dans le presse-papier, fais Cmd+V") - self.systeme.jouer_son("error") + self._jouer_son("error") except Exception as e: print(f"[erreur] arrêt ou transcription : {e}") - self.systeme.jouer_son("error") + self._jouer_son("error") finally: self.run_on_main(self.pulse.hide) self.on_state("idle") diff --git a/tests/test_statut_bulle.py b/tests/test_statut_bulle.py index 3b6321c..3a1e769 100644 --- a/tests/test_statut_bulle.py +++ b/tests/test_statut_bulle.py @@ -65,9 +65,10 @@ class FauxSysteme: def acquerir_verrou_instance(self, chemin): return object() -def _ctrl(recorder, taches=None): +def _ctrl(recorder, taches=None, config=None): pulse = FauxPulse() - c = ZonzaController(CONFIG, pulse, FauxTranscripteur(), FauxSysteme(), lancer_tache=taches) + systeme = FauxSysteme() + c = ZonzaController(config or CONFIG, pulse, FauxTranscripteur(), systeme, lancer_tache=taches) c.recorder = recorder return c, pulse @@ -125,3 +126,24 @@ def test_un_enregistrement_trop_court_referme_aussi(): for t in differees: t() assert not pulse.visible + + +def test_le_menu_sons_joue_un_son_quand_actives(): + differees = [] + c, pulse = _ctrl(FauxRecorder(np.ones(16000, dtype="float32") * 0.1), taches=differees.append) + c.toggle(); c.toggle() + for t in differees: + t() + assert c.systeme.sons, "un cycle demarrage/arret doit produire au moins un son" + + +def test_le_menu_sons_coupe_le_son_quand_desactive(): + differees = [] + config = dict(CONFIG, sounds=False) + c, pulse = _ctrl( + FauxRecorder(np.ones(16000, dtype="float32") * 0.1), taches=differees.append, config=config + ) + c.toggle(); c.toggle() + for t in differees: + t() + assert c.systeme.sons == [], "sounds=False doit couper tous les sons"