You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
All 12 demo spots: ANNOUNCER, VOICE1 and VOICE2 in one voice. Kokoro log for spot #11 (2026-09-05 20:28:37–44Z): four POST /v1/audio/speech, all af_alloy. station.ad_spot.voice_plan is NULL on every generated row.
Why
src/GenWave.Ads/AdRenderService.cs:123-127 — null/empty plan ⇒ every tag → stationIdentity.Current.Voice.
src/GenWave.Tts/AdScriptWriteRequest.cs:44-51 carries no voice fields; AdScriptWriter.cs:397-398 says so: "for a generated spot every tag maps to the station voice anyway". Only the editor's voicePlan field or the API can set one.
Per-line voice switching already works when a plan exists (CrosstalkAssembler.cs:208-216, 393-403), so this is a plan-building gap, not a render gap.
Ruling (Dean, 2026-09-06)
A separate ad cast pool, distinct from the DJ personas; the owner can set the ANNOUNCER voice (default Station:Voice).
Fix
Settings: Ads:AnnouncerVoice (empty = station voice), Ads:CastVoices (list of Kokoro voice ids; ships with a small mixed default set distinct from the persona defaults). Live settings, help keys.
Worker/refill: after the validator passes, build the plan — ANNOUNCER → announcer voice; VOICE1/VOICE2 → two distinct pool voices picked deterministically per spot (seeded by spot id + brand so a regeneration re-casts the same way), never equal to the announcer; persist as voice_plan (AdVoicePlanEntry(Tag, VoiceId, Pace) jsonb). Owner drafts with an explicit plan keep it.
Kokoro's log for a rendered spot shows ≥ 2 distinct voices when the script has ≥ 2 tags; voice_plan non-null on worker-written rows; SPEC F161.2 rider.
What airs
All 12 demo spots: ANNOUNCER, VOICE1 and VOICE2 in one voice. Kokoro log for spot #11 (2026-09-05 20:28:37–44Z): four
POST /v1/audio/speech, allaf_alloy.station.ad_spot.voice_planis NULL on every generated row.Why
src/GenWave.Ads/AdRenderService.cs:123-127— null/empty plan ⇒ every tag →stationIdentity.Current.Voice.src/GenWave.Tts/AdScriptWriteRequest.cs:44-51carries no voice fields;AdScriptWriter.cs:397-398says so: "for a generated spot every tag maps to the station voice anyway". Only the editor'svoicePlanfield or the API can set one.CrosstalkAssembler.cs:208-216, 393-403), so this is a plan-building gap, not a render gap.Ruling (Dean, 2026-09-06)
A separate ad cast pool, distinct from the DJ personas; the owner can set the ANNOUNCER voice (default
Station:Voice).Fix
Ads:AnnouncerVoice(empty = station voice),Ads:CastVoices(list of Kokoro voice ids; ships with a small mixed default set distinct from the persona defaults). Live settings, help keys.voice_plan(AdVoicePlanEntry(Tag, VoiceId, Pace)jsonb). Owner drafts with an explicit plan keep it.Acceptance
Kokoro's log for a rendered spot shows ≥ 2 distinct voices when the script has ≥ 2 tags;
voice_plannon-null on worker-written rows; SPEC F161.2 rider.Epic: #380