π 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:
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).
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).
π 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:
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 Preparedtimeline 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:
Tts:SentencePauseSeconds = 0.6(TtsOptions.cs:33) βKokoroPauseMarkupsplices[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).FfmpegCueAnalyzerruns on TTS clips unconditionally (TtsSegmentSource.cs:189-197) withsilencedetect noise=-50dB:duration=0.5(CueDetectionOptions.cs:13,16) and setscueOut = 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-46stampsliq_cue_outwith 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:
DurationMsis 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 = nullon the TTSMediaItem) and deriveDurationMsfrom actual file duration instead of cue-out. Decide whetherSafeSegmentAuthor.cs:161(authored imaging, persisted cue) needs the same treatment.π§Ή Riders found while digging (fix or file separately)
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 raisesTts:SentencePauseSecondsabove 1.5 (range allows 5.0).liq_cross_durationfor TTS is computed from the already-truncatedDurationMs(LiquidsoapAnnotationBuilder.cs:60-62) β self-heals with the fix.β Verification
On the box, pick a truncated blurb under
/tts/<station>/blurbs/and runffmpeg -i <wav> -af silencedetect=noise=-50dB:duration=0.5 -f null -β prediction: file is complete, lastsilence_startequals the on-air cut instant, and the pushed annotation in the api log carries that value asliq_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:1warnings during these breaks are this bug's downstream symptom).