feat: Kokoro voice blending — blend builder UI + weighted tensor synthesis

Add voice blend support to TTS pipeline and settings UI. Users can mix
2–5 Kokoro voices with per-voice weight sliders; the blended style tensor
replaces the single voice when enabled. Settings persist as JSON and auto-
load on synthesis when no explicit voice is supplied in the request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-30 19:10:22 -04:00
parent 98b3cdb593
commit 1460863e82
4 changed files with 237 additions and 7 deletions
+14 -2
View File
@@ -639,6 +639,11 @@ export interface VoiceEntry {
label: string
}
export interface VoiceBlendEntry {
voice: string
weight: number
}
export const getVoiceStatus = () => apiGet<VoiceStatusResult>('/api/voice/status')
export const getVoiceList = () =>
@@ -658,12 +663,19 @@ export async function transcribeAudio(blob: Blob): Promise<{ transcript: string;
export async function synthesiseSpeech(
text: string,
voice?: string,
speed?: number
speed?: number,
voiceBlend?: VoiceBlendEntry[]
): Promise<Blob> {
const body: Record<string, unknown> = { text, speed: speed ?? 1.0 }
if (voiceBlend && voiceBlend.length >= 2) {
body.voice_blend = voiceBlend
} else {
body.voice = voice ?? 'af_heart'
}
const res = await fetch('/api/voice/synthesise', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, voice: voice ?? 'af_heart', speed: speed ?? 1.0 }),
body: JSON.stringify(body),
})
if (!res.ok) {
const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }))