feat: voix JARVIS, Parakeet v3 pour le français, worklets audio sous CSP stricte (v2.4.1) (#19)

* feat: voix JARVIS, Parakeet v3 pour le français, worklets audio sous CSP stricte (v2.4.1)

Voix
- Timbre « JARVIS » (Web Audio) : hauteur légèrement abaissée, chaleur dans
  les basses, présence, compression douce, courte réverbération d'intercom.
  Activé par défaut, réglable dans Paramètres → Voix.
- Préréglage « Voix JARVIS » en un clic : moteur local, voix masculine
  française (Piper Tom téléchargé automatiquement, Kokoro n'ayant pas de voix
  française masculine), débit calme. Affichage de la voix active.
- Boutons Masculine / Féminine désormais lisibles (style du sélecteur ajouté).

Reconnaissance
- Parakeet TDT 0.6B v3 (NVIDIA NeMo, int8, 25 langues européennes dont le
  français) ajouté au catalogue et recommandé : plus précis et plus rapide que
  Whisper sur processeur, ponctuation incluse.
- 0,4 s de silence ajoutées avant et après chaque énoncé avant la
  reconnaissance (syllabes coupées, hallucinations de Whisper sur les clips
  courts).

Écoute permanente
- Les modules AudioWorklet sont livrés en fichiers statiques (public/worklets)
  chargés depuis l'application : en version installée, la CSP stricte
  (script-src 'self') refusait les URL blob et l'écoute permanente échouait
  avec « Unable to load a worklet's module ». Repli blob conservé pour le dev.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Wn5VX9HNbJ7N54hR24u9Y

* docs: mesures Parakeet vs Whisper et worklets dans la feuille de route

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Wn5VX9HNbJ7N54hR24u9Y

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
LogiFlowandClaude Fable 5.1 authored and GitHub committed 2026-09-04 13:02:32 +02:00
1 parent d1b12c16c5
commit 5ef440e8cf
13 files changed
+221 -11

No files matched your search

+16 -1
View File
@@ -66,6 +66,21 @@ function getWorkletUrl(): string {
return workletUrl;
}
/**
* Load an AudioWorklet module: first the static file shipped with the app (allowed by the strict
* CSP, script-src 'self'), then a blob URL for dev servers that do not serve /worklets.
*/
export async function loadWorklet(ctx: AudioContext, name: string, blobUrl: () => string): Promise<void> {
const staticUrl = new URL(`worklets/${name}.js`, document.baseURI).href;
try {
await ctx.audioWorklet.addModule(staticUrl);
return;
} catch (err) {
Log.debug('audio', `static worklet ${name} unavailable (${(err as Error).message}), trying blob`);
}
await ctx.audioWorklet.addModule(blobUrl());
}
export async function listMicrophones(): Promise<MicDevice[]> {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
@@ -147,7 +162,7 @@ export class MicCapture {
audioBus.setInputAnalyser(analyser);
try {
await this.ctx.audioWorklet.addModule(getWorkletUrl());
await loadWorklet(this.ctx, 'eveflow-capture', getWorkletUrl);
this.worklet = new AudioWorkletNode(this.ctx, 'eveflow-capture', { numberOfInputs: 1, numberOfOutputs: 0, channelCount: 1 });
this.worklet.port.onmessage = (event: MessageEvent<Float32Array>) => this.onSamples(event.data);
source.connect(this.worklet);