diff --git a/app/engine.js b/app/engine.js index b23fa2b..262de8b 100644 --- a/app/engine.js +++ b/app/engine.js @@ -1794,6 +1794,7 @@ async function pump() { state.busy = true; const started = Date.now(); + let jobAudioMs = 0; try { /* job.at is when the audio ENDED. Live translation of stale audio is worthless, but the WORDS are not: however old the job, it still gets @@ -1805,6 +1806,9 @@ async function pump() { /* Near-silence telemetry (log-only this release): a chunk whose average level barely clears the noise floor is hallucination fuel. */ const audio = flatten(job.frames); + /* How much speech this chunk actually holds. The step-down below compares + against it, because "slow" only means anything relative to the audio. */ + jobAudioMs = (audio.length / SAMPLE_RATE) * 1000; let sum = 0; for (let i = 0; i < audio.length; i += 4) sum += audio[i] * audio[i]; const meanRms = Math.sqrt(sum / Math.ceil(audio.length / 4)); @@ -1908,11 +1912,43 @@ async function pump() { * talk, and the backlog only grows — step down before the service is lost * rather than after. Measured on the Intel laptop: 12-25 s, every sentence. */ - if (took > 8000) { + /* + * SLOWER THAN THE AUDIO IT COVERS — not slower than some constant. + * + * This used to fire only past 8 s, on the reasoning that 8 s meant slower + * than people talk. That was true when a chunk could be 8 s long. Chunks + * are capped at HARD_CUT_MAX, 5.5 s, so a 4.5 s chunk taking 4.3 s is + * already losing ground and never came near the old threshold. + * + * MEASURED on a campus M4 Pro: every sentence 4.2-4.7 s against roughly + * 4-5 s of audio, a queue 46 deep and a lag of 152 s still climbing — with + * nobody listening and no language being translated, so this was + * transcription alone. The one mechanism built to catch exactly that could + * not fire, because 4.4 s is not 8 s. + * + * The queue test is what separates "this machine cannot keep up" from one + * slow sentence: a backlog that is three deep and being fed faster than it + * drains does not recover on its own. + */ + /* + * 0.85 and a backlog of five, both measured rather than chosen. + * + * The Mini's sentences ran 4.4 s against 4.5 s of audio — nominally FASTER + * than real time, while the queue sat at 46 and the lag climbed past 150 s. + * Per-sentence time alone therefore misses it: the work outside this timer + * (segmenting, the preview model sharing the GPU, the scheduler) is enough + * to lose on its own. The margin catches "near enough to real time to be + * losing"; the queue depth is the proof that it actually is, and keeps one + * slow sentence on a healthy machine from touching anything. + */ + const losingGround = jobAudioMs > 0 && took > jobAudioMs * 0.85 && queue.length >= 5; + if (took > 8000 || losingGround) { state.slowRun = (state.slowRun || 0) + 1; if (state.slowRun >= 3 && !respawning && lighterAsr(state.asrModelInUse || state.cfg.asrModel)) { state.slowRun = 0; - log('transcription is slower than people are speaking — stepping down to a lighter model', 'err'); + log('transcription is slower than people are speaking (' + took + ' ms for ' + + Math.round(jobAudioMs) + ' ms of audio, ' + queue.length + ' waiting) — ' + + 'stepping down to a lighter model', 'err'); respawnWorker(state.asrWorker); } } else state.slowRun = 0; diff --git a/app/package.json b/app/package.json index 8e6ff91..562b348 100644 --- a/app/package.json +++ b/app/package.json @@ -2,7 +2,7 @@ "name": "church-translate-engine", "productName": "Church Translate", "version": "3.23.2", - "buildNumber": 116, + "buildNumber": 117, "private": true, "description": "Live sermon translation — self-contained: captures audio, transcribes, translates, and serves the congregation page itself", "main": "main.js", @@ -106,7 +106,7 @@ ] }, "asar": false, - "buildVersion": "116", + "buildVersion": "117", "win": { "target": [ {