Skip to content

playout: TTS patter airs truncated at the last pause β€” cue-out from silencedetect cuts the final sentence of every multi-sentence blurbΒ #424

Description

@genwave-radio

πŸ› Symptom (heard live on demo β€” v2.9.0 AND v3.3.0/T229)

Multi-sentence patter airs truncated at a pause point; the LLM copy in the booth log is complete. 2026-08-09 ~09:32 local, three consecutive segments in one break all cut:

  • back-announce cut after "…Train Room's Horizons," (lost the whole second clause)
  • weather segment cut after "…Edmonton morning sky grey" (lost ~ΒΎ of the copy)
  • lead-in cut after "…next up from D.B. Ricapito" (lost the track title!)

v2.9.0 sightings identical in shape ("…an exercise in brass," β†’ rest dropped; "…rising star lollita" β†’ rest dropped). Loki confirms: back-announce blurb got ~3s of air against ~10s of copy (engine q Prepared timeline 15:32:08β†’:11β†’:17β†’:24 UTC).

πŸ” Root cause β€” cue-out lands on the last intra-clip pause

Two independently-correct components compose into the bug:

  1. Tts:SentencePauseSeconds = 0.6 (TtsOptions.cs:33) β†’ KokoroPauseMarkup splices [pause:0.6s] after every non-final sentence ender (KokoroPauseMarkup.cs:83-101); kokoro-fastapi v0.6.0 renders it as true digital silence. Deliberately no trailing pause after the final sentence (:126).
  2. FfmpegCueAnalyzer runs on TTS clips unconditionally (TtsSegmentSource.cs:189-197) with silencedetect noise=-50dB:duration=0.5 (CueDetectionOptions.cs:13,16) and sets cueOut = starts[^1] β€” the LAST silence_start in the file (FfmpegCueAnalyzer.cs:107-109).

0.6s > 0.5s β‡’ every injected sentence pause registers as silence; with no trailing pause to be "last", the last inter-sentence pause becomes cue-out β‡’ the final sentence of every multi-sentence blurb is cut. LiquidsoapAnnotationBuilder.cs:43-46 stamps liq_cue_out with no TTS exemption; LS 2.4 honors it natively (CHANGELOG note). Pre-markup vintages (v2.9.0) cut the same way whenever a Kokoro-native pause (long comma beat, spoken dash) hit β‰₯0.5s @ β‰€βˆ’50dB β€” gh-#116's pause markup landed 07-30, one day before v2.9.0, so both mechanisms were live for both reported vintages.

Why nobody sees it off-air: DurationMs is derived from cue-out (TtsSegmentSource.cs:204-206), so now-playing agrees with the short audio and no gap airs; the TTS preview path does no cue measurement (TtsPreviewController.cs:13,25) and plays the copy complete.

Amplifier: the gh-#303 "no commas, short sentences" prompt rule maximizes sentence count β‡’ maximizes injected pauses.

πŸ”§ Fix direction

TTS clips are rendered digital audio β€” full extent IS the content; silence-based cue trimming is a music heuristic and should not apply. Simplest: skip cue analysis for TTS segments (Cue = null on the TTS MediaItem) and derive DurationMs from actual file duration instead of cue-out. Decide whether SafeSegmentAuthor.cs:161 (authored imaging, persisted cue) needs the same treatment.

🧹 Riders found while digging (fix or file separately)

  • TTS cache key omits SentencePauseSeconds (TtsSegmentSource.cs:313-319) β€” changing the pause setting reuses stale audio.
  • blank.eat(max_blank=1.5) (genwave.liq:62) starts eating sentence pauses if an operator raises Tts:SentencePauseSeconds above 1.5 (range allows 5.0).
  • liq_cross_duration for TTS is computed from the already-truncated DurationMs (LiquidsoapAnnotationBuilder.cs:60-62) β€” self-heals with the fix.

βœ… Verification

On the box, pick a truncated blurb under /tts/<station>/blurbs/ and run ffmpeg -i <wav> -af silencedetect=noise=-50dB:duration=0.5 -f null - β€” prediction: file is complete, last silence_start equals the on-air cut instant, and the pushed annotation in the api log carries that value as liq_cue_out. After fix: full copy airs; back-announce/weather/lead-in play to their final words.

Refs: #116 (pause markup), #303 (no-commas rule), #292 (comma pause), #288 (patter durations), #80 (crossfade vs short blurbs β€” the cross:1 warnings during these breaks are this bug's downstream symptom).

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High prioritybugSomething isn't workingdemoFound on GenWave demo

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions