This commit is contained in:
valenti
2026-10-16 12:33:56 +01:00
parent 236fd509ca
commit 430997bd2e
11 changed files with 291 additions and 24 deletions
+61 -5
View File
@@ -172,7 +172,7 @@ See [`.env.example`](.env.example) for a copy-paste template.
| `FILLER_DELAY_MS` | `2500` | How long (ms) to wait for the response audio to start before playing the filler. If the reply starts earlier, the filler is dropped instead of talking over the beginning of the answer. |
| `FILLER_GAIN_IDLE` | `1.0` | Filler gain when TTS is idle. |
| `FILLER_GAIN_DUCK` | `0.15` | Filler gain while TTS is active (ducking). |
| `TTS_PREBUFFER_MS` | `900` | Synthesized audio (ms) buffered on the TTS channel before the reply starts playing. Lower = the agent speaks sooner; higher = fewer drop-outs on uneven TTS chunk arrival. Only awaited at the start of a reply; mid-reply underruns resume as soon as any audio is queued again. |
| `TTS_PREBUFFER_MS` | `450` | Synthesized audio (ms) buffered on the TTS channel before the reply starts playing. Lower = the agent speaks sooner; higher = fewer drop-outs on uneven TTS chunk arrival. Only awaited at the start of a reply; mid-reply underruns resume as soon as any audio is queued again. |
| `TTS_PIPELINE_DEPTH` | `3` | How many TTS requests may be in flight at once. Chunks are synthesized ahead of playback and replayed in order, so each request's start-up latency hides behind the previous chunk's audio. `1` restores strictly serial synthesis. |
| `BACKCHANNEL_ENABLED` | `true` | Master switch for during-utterance acknowledgments. |
| `BACKCHANNEL_DIR` | `assets/backchannel` | Directory of `*.raw` backchannel PCM clips (16-bit LE mono, 44.1 kHz). |
@@ -266,6 +266,51 @@ An unknown value panics at startup with a clear message.
- Supports voice cloning via the `references` field.
- Honors barge-in cancellation.
##### Starting the Drama 3 backend (SGLang-Omni)
The `fish` engine talks to an **SGLang-Omni** server running Drama 3. Start it
with the vocoder streaming window narrowed, otherwise the vocoder emits ~3 s
bursts and the client is forced into a large pre-buffer:
```bash
sgl-omni serve \
--model-path fishaudio/drama-3 \
--host 0.0.0.0 --port 8000 \
--tts_engine.gpu_memory_fraction 0.45 \
--tts_engine.factory.max_running_requests 4 \
--vocoder.factory.stream_followup_stride 16 \
--vocoder.factory.stream_followup_stride_ramp "[10,12,16]"
```
In this deployment the launcher is `/root/drama3/start-drama3.sh` and the server
is kept alive by **pm2** as the process `start-drama3`:
```bash
pm2 restart start-drama3 --update-env # after editing the launcher
pm2 logs start-drama3 --lines 100 # CUDA-graph rebuild takes ~2 min at start
```
The two `--vocoder.factory.*` overrides are the key part:
| Flag | Meaning |
|---|---|
| `stream_followup_stride` | Steady-state window, in codec frames (`2048` samples = `46.4 ms` each). `16` ⇒ `0.743 s` bursts; the upstream default `64` ⇒ `2.97 s`. |
| `stream_followup_stride_ramp` | Window sizes for the first windows, as a JSON tuple. `[10,12,16]` emits a `0.464 s` then a `0.557 s` window first, so playback can start sooner. Upstream default `(10,12,32)`. |
Two gotchas when writing the command:
- The flags are **dotted keys into the pydantic config**, so they go *without* a
`stages.` prefix — `--vocoder.factory.stream_followup_stride`, not
`--stages.vocoder.factory.stream_followup_stride` (the latter is rejected).
- The ramp tuple must be passed as a **JSON string** — `"[10,12,16]"` — because
the config manager parses non-scalar paths with JSON.
Smaller stride = finer delivery granularity but more compute: each window
re-decodes `stream_overlap_tokens` (default `32`) of context, so `stride 16`
decodes 48 frames per 16 emitted. Keep the stride at or below the client's
`TTS_PREBUFFER_MS` budget (see *Mixer & jitter buffering*) — with the defaults
above, `TTS_PREBUFFER_MS=450` starts playback on the first window.
#### `kokoro` — Kokoro TTS *(stub)*
[`src/tts/kokoro.rs`](src/tts/kokoro.rs)
@@ -630,6 +675,12 @@ A ready-to-use speech-to-speech example client is served at **`/mivocero`**
(44.1 kHz, 16-bit LE mono, 20 ms frames).
- **Chat box** — shows the user transcript (from STT) and the assistant reply
**as it streams**, plus messages typed in the composer.
- **Karaoke highlighting** — the assistant bubble is rendered word by word: each
word lights up as it is *spoken*, driven by the audio playhead (the playback
worklet reports the samples it has actually emitted). Words already spoken are
bright, the word currently sounding is accented, and the rest stay dim. The
rate is an estimate (`KARAOKE_CPS`, ~15 chars/s) — it only affects the visual
highlight, never the audio.
- **Text composer** — typing and sending a message runs a full LLM → TTS turn
without using the microphone (`{"type":"user_text","text":…}`).
- **Interrupt button** — flushes audio and cancels the in-flight reply
@@ -690,13 +741,18 @@ Constants are defined in [`src/config.rs`](src/config.rs):
`0.15`); the backchannel gain is fixed (`BACKCHANNEL_GAIN`, default `0.35`).
Output is clamped to `i16`.
- **Pre-buffer:** TTS playback does not start until the queue holds
≥ `TTS_PREBUFFER_MS` (default **900 ms**) of synthesized audio. It absorbs
≥ `TTS_PREBUFFER_MS` (default **450 ms**) of synthesized audio. It absorbs
uneven TTS chunk arrival; lower it to start speaking sooner, raise it for fewer
drop-outs. With pipelined synthesis (above) the pre-buffer only has to cover
arrival jitter, not the TTS engine's per-request start-up, so it can stay
small. Note: Drama-3 delivers ~1.08× real time in bursts every ~2.7 s, so a
~900 ms buffer favours latency over perfect gaplessness on very long replies —
raising it towards ~3 s is what actually removes the remaining drop-outs.
small. Note: Drama-3 delivers ~1.04× real time, and its *granularity* is set by
the server's vocoder window
(`--vocoder.factory.stream_followup_stride`, in codec frames of 2048 samples =
46.4 ms). With stride `16` and ramp `"[10,12,16]"` the first window is 10
frames = 0.464 s, so `450 ms` starts playback on that very first window (the
steady state then delivers a 0.743 s burst every ~0.69 s). With the upstream
default (`64`, a 2.97 s burst every ~2.7 s) gapless playback would instead
require a ~3 s pre-buffer.
- **Resilient resume:** the full pre-buffer is only required at the *first*
playback of a reply. If the queue empties mid-reply (after the 200 ms drain
grace) playback pauses, but resumes as soon as *any* audio is queued again —