feat(t2-live): archi audio Voie A + Bugs 4/5/6 + indicateur de prise de parole (Sprint 6e)

- Voie A WAV : AudioContext unique au rate natif, tap AudioWorklet sur mixGain, uplink rate-aware 16k, alignement par horloge unique (fin offset/resample/concat). Anti-echo candidat. Cycle start=ws.onopen / stop=Terminer / cancel=aucun WAV.
- Bug 4 : 'Voir le rapport' route vers le rapport (navigatingAwayRef).
- Bug 5 : 'Annuler' (cancelDialogue) - arret sans evaluation, sans WAV, sans production.
- Bug 6 : 'Nouvelle simulation' route selon le type via champ tache propage (Report).
- Indicateur de prise de parole : state machine USER_SPEAKING/USER_SILENT (RMS + hysteresis).
- Cleanup : retrait instrumentation [BISECT] ; ref VAD renomme lastAiChunkTsRef.
- Removed : code mort mixTracksToInt16, resample16kTo24k + tests.
This commit is contained in:
Hermann_Kitio 2026-06-29 14:31:38 +03:00
parent 9bf95f5c05
commit 72795e924e
16 changed files with 848 additions and 257 deletions

View file

@ -0,0 +1,61 @@
/**
* pcm-record-processor.js AudioWorklet processor d'ENREGISTREMENT T2 Live
* (Sprint 6e, Voie A tap temps réel).
*
* Branché en dérivation sur le `mixGain` de capture (point de convergence
* micro + voix IA dans le contexte PARTAGÉ). Il LIT le mix au rate NATIF du
* contexte (typiquement 48 kHz), convertit Float32 Int16 little-endian, et
* envoie des chunks (~4096 samples) au thread principal via `port.postMessage`.
*
* Aucun rééchantillonnage : on enregistre au rate natif (le WAV est écrit à ce
* même rate côté useAudioRecording). L'alignement temporel micro/IA est natif
* les deux voix partagent l'horloge unique du contexte (plus de réassemblage
* offline à base d'offsets).
*
* Le node est tiré par le graphe via mixGain recordNode gain(0)
* destination (sink muet) ; ce processor n'écrit rien sur ses sorties (silence),
* il ne fait que prélever l'entrée. Le gain(0) garantit zéro résidu audible.
*
* Vanille JS (pas TS) : les AudioWorklet processors s'exécutent dans un scope
* global isolé qui ne peut pas importer depuis le bundle TS.
*/
const RECORD_CHUNK_SIZE = 4096
class PcmRecordProcessor extends AudioWorkletProcessor {
constructor() {
super()
this.buffer = new Float32Array(0)
}
process(inputs) {
const input = inputs[0]
if (!input || !input[0]) return true
const channelData = input[0] // mono (mix micro + IA)
const merged = new Float32Array(this.buffer.length + channelData.length)
merged.set(this.buffer)
merged.set(channelData, this.buffer.length)
this.buffer = merged
while (this.buffer.length >= RECORD_CHUNK_SIZE) {
const chunk = this.buffer.slice(0, RECORD_CHUNK_SIZE)
this.buffer = this.buffer.slice(RECORD_CHUNK_SIZE)
// Float32 [-1, 1] → Int16 PCM little-endian
const pcm = new ArrayBuffer(chunk.length * 2)
const view = new DataView(pcm)
for (let i = 0; i < chunk.length; i++) {
const s = Math.max(-1, Math.min(1, chunk[i]))
view.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7fff, true)
}
this.port.postMessage(pcm, [pcm])
}
return true
}
}
registerProcessor('pcm-record-processor', PcmRecordProcessor)