Updates
Rolling release / release (push) Failing after 1m49s

This commit is contained in:
2026-09-12 19:36:06 -04:00
parent 9ad09b593c
commit d47e9e260f
30 changed files with 917 additions and 162 deletions
+27 -17
View File
@@ -198,6 +198,7 @@ export class VoiceLoop extends EventEmitter {
async speak(text) {
const generation = this._generation || 0;
if (this.tts && !this.status.tts && typeof this.tts.start === 'function') await this.ensureTts();
if (generation !== this._generation) return;
if (!isSpeakable(text) || !this.tts?.speak || !this.status.tts) {
this._releaseSpeaking();
return;
@@ -209,32 +210,41 @@ export class VoiceLoop extends EventEmitter {
return;
}
this._speechQueue = this._speechQueue.catch(() => {}).then(async () => {
let pending;
const prepare = (sentence) => Promise.resolve().then(async () => {
if (generation !== this._generation) return {};
const started = this.now();
const audio = await this.tts.speak(speakableForTts(sentence));
this.metrics.synthesis(this.now() - started, audio.samples.length * 1000 / audio.sampleRate);
return { audio };
}).catch((error) => ({ error }));
try {
if (generation !== this._generation) return;
for (const sentence of sentences) {
this.isSpeaking = true;
this.wake.pause();
pending = prepare(sentences[0]);
for (let i = 0; i < sentences.length; i++) {
const { audio, error } = await pending;
if (generation !== this._generation) return;
await this.speakSentence(sentence, generation);
if (error) throw error;
// Only one sentence ahead: overlap synthesis with playback, without
// parallel TTS requests or buffering an entire reply's audio.
pending = i + 1 < sentences.length ? prepare(sentences[i + 1]) : null;
await this.playback.play(audio.samples, audio.sampleRate);
try { this.daemon?.emit('SpeakingLevel', 0); } catch {}
}
} finally {
if (generation === this._generation) this._releaseSpeaking();
// A failed/interrupted playback may leave one synthesis in flight.
// Drain it before model teardown or the next queued utterance.
await pending;
if (generation === this._generation) {
this.cooldownUntil = this.now() + this.cooldownMs;
this._releaseSpeaking();
}
}
});
return this._speechQueue;
}
async speakSentence(text, generation) {
this.isSpeaking = true;
this.wake.pause();
try {
const audio = await this.tts.speak(speakableForTts(text));
if (generation !== this._generation) return;
await this.playback.play(audio.samples, audio.sampleRate);
try { this.daemon?.emit('SpeakingLevel', 0); } catch {}
} finally {
this.isSpeaking = false;
this.cooldownUntil = this.now() + this.cooldownMs;
this.wake.resume();
}
}
}
export { SentenceBuffer };