Merge pull request #3 from rekuenkdr/fix/audio-crossfade-fixes

feat(audio): make overlap_samples fully configurable
This commit is contained in:
rekuenkdr
2026-02-03 08:56:13 +01:00
committed by GitHub
2 changed files with 10 additions and 10 deletions
+4 -5
View File
@@ -51,10 +51,9 @@ Streaming TTS can produce clicks, pops, and artifacts at chunk boundaries. This
Chunks are blended using a Hann window crossfade to eliminate boundary discontinuities:
```python
MIN_BLEND_SAMPLES = 512 # ~21ms at 24kHz
# blend_samples is at least MIN_BLEND_SAMPLES, even if overlap_samples is smaller
blend_samples = max(overlap_samples, MIN_BLEND_SAMPLES)
# ~21ms at 24kHz, matches RMS check window
# Lower values may cause clicks, set to 0 to disable
DEFAULT_BLEND_SAMPLES = 512
# Hann crossfade
fade_out = 0.5 * (1 + np.cos(np.pi * t))
@@ -161,7 +160,7 @@ for chunk, sr in model.stream_generate_voice_clone(
|-----------|---------|-------------|
| `emit_every_frames` | 8 | Emit audio every N frames |
| `decode_window_frames` | 80 | Decoder context window |
| `overlap_samples` | 512 | Crossfade overlap between chunks |
| `overlap_samples` | 512 | Crossfade overlap between chunks (0 to disable) |
| `max_frames` | 10000 | Maximum codec frames to generate |
| `first_chunk_emit_every` | 0 | Phase 1 emit interval (0 = disabled) |
| `first_chunk_decode_window` | 48 | Phase 1 decode window |
+6 -5
View File
@@ -103,9 +103,10 @@ def _crossfade(prev_tail: np.ndarray, new_head: np.ndarray) -> np.ndarray:
return prev_tail[:n] * fade_out + new_head[:n] * fade_in
# Minimum samples for boundary blending (prevents clicks even with overlap_samples=0)
# Default blend samples for boundary blending
# ~21ms at 24kHz, matches RMS check window for better coverage
MIN_BLEND_SAMPLES = 512
# Lower values may cause clicks, set to 0 to disable
DEFAULT_BLEND_SAMPLES = 512
def _add_ref_code_context(
@@ -2814,7 +2815,7 @@ class Qwen3TTSForConditionalGeneration(Qwen3TTSPreTrainedModel, GenerationMixin)
# Crossfade with previous chunk tail for smooth transition
# Always blend boundaries to prevent clicks from sliding window re-decode artifacts
blend_samples = max(overlap_samples, MIN_BLEND_SAMPLES)
blend_samples = overlap_samples
if decoded_tail is not None:
ov = min(blend_samples, len(decoded_tail), len(chunk))
if ov > 0:
@@ -2823,7 +2824,7 @@ class Qwen3TTSForConditionalGeneration(Qwen3TTSPreTrainedModel, GenerationMixin)
# Apply Hann fade-in to very first chunk to avoid pop at audio start
# Always apply fade-in on first chunk to prevent pop
blend_samples = max(overlap_samples, MIN_BLEND_SAMPLES)
blend_samples = overlap_samples
if decoded_tail is None:
fade_len = min(blend_samples, len(chunk))
if fade_len > 0:
@@ -2867,7 +2868,7 @@ class Qwen3TTSForConditionalGeneration(Qwen3TTSPreTrainedModel, GenerationMixin)
# Crossfade with previous tail
# Always blend flush boundary
blend_samples = max(overlap_samples, MIN_BLEND_SAMPLES)
blend_samples = overlap_samples
if decoded_tail is not None and len(wav) > 0:
ov = min(blend_samples, len(decoded_tail), len(wav))
if ov > 0: