Add voice processing settings and a speak-time bypass

This commit is contained in:
Fisto
2026-09-21 18:23:40 +02:00
parent 17d4339ea3
commit 63083c54ec
10 changed files with 230 additions and 9 deletions
+20
View File
@@ -25,6 +25,8 @@ export interface AudioServiceOptions {
getVoiceContext?: () => Promise<{ guildId?: string | null; channelId?: string | null } | undefined>;
getSpeakerName?: (userId: string) => Promise<string | undefined>;
onTranscript?: (event: Record<string, unknown>) => void;
/** Called true when a TTS burst starts and false when the queue drains. */
onSpeakActivity?: (active: boolean) => void;
}
export interface SpeakResult {
@@ -51,6 +53,8 @@ export class AudioService {
private readonly speakerIntervals: SpeakerInterval[] = [];
private readonly speakerNames = new Map<string, string>();
private playing = false;
/** True while a TTS burst holds the voice-processing bypass. */
private bypassed = false;
private duckEnabled = true;
private nextId = 1;
private ducked = false;
@@ -381,6 +385,14 @@ export class AudioService {
}
this.playing = true;
if (!this.bypassed) {
this.bypassed = true;
try {
this.options.onSpeakActivity?.(true);
} catch {
// Best effort: speech still plays.
}
}
try {
// Streaming word mode: the wrapper writes the WAV while it synthesizes, so
// playback starts on the first word instead of after the whole line.
@@ -408,6 +420,14 @@ export class AudioService {
} finally {
this.playing = false;
await this.applyDuck(false);
if (this.bypassed && this.queue.length === 0) {
this.bypassed = false;
try {
this.options.onSpeakActivity?.(false);
} catch {
// Best effort.
}
}
void this.pump();
}
}