VALL-E developer on retainer: 15–25 ms initial silence gap in every voice-cloned output — reference audio codec tokens prepend silence onset — language model learns to match reference silence — 3 weeks — VALL-E developer on monthly retainer
Microsoft VALL-E (microsoft/vall-e) neural codec language model TTS • Python 3.11; PyTorch 2.1.x • podcast dubbing service • 3-second reference audio per speaker • base VALL-E (no reference): speech starts immediately • with reference audio: consistent 15–25 ms near-silence gap before first phoneme in every output • gap identical across random seeds and target texts • developer checks reference audio length, VALL-E inference parameters, EnCodec codec token generation for 3 weeks • root cause: VALL-E prepends reference audio’s EnCodec codec tokens to the conditioning sequence; reference recording first frames include 50–80 ms near-silence onset before speaker begins; language model trained to follow reference characteristics generates matching silence onset at start of every output • fix: energy-threshold trim of initial silence from each reference recording (RMS < −45 dBFS for >3 consecutive frames = silence to trim); re-encode trimmed reference through EnCodec • initial silence gap: 15–25 ms → 0 ms • MOS: 4.0 (unchanged) • speaker cosine similarity: 0.953 → 0.951 (trim removes only pre-speech silence, not voiced content) • 1.5h retainer engagement
Westfield VALL-E TTS Ltd. built a podcast dubbing service using Microsoft VALL-E for zero-shot voice cloning from 3-second reference audio clips of each podcast guest. The service synthesized dubbed translations of podcast episodes, preserving each speaker’s voice characteristics across the translated script. Voice similarity was high and MOS ratings were strong, but the client noticed an audible quality issue: every synthesized utterance began with a brief moment of near-silence before the first word. The gap was consistently 15–25 ms across all speakers and all target texts. It was imperceptible at normal listening speed but created an audible “cut” artifact when multiple dubbed utterances were concatenated together in the final podcast track, as each gap was perceived as an additional silence between utterances where none existed in the original.
The team compared with the base VALL-E model (no reference audio conditioning): the base model produced speech that started immediately, confirming the initial silence was reference-dependent rather than a fixed model artifact. They tried different reference audio clips (longer references of 5 and 10 seconds, different segments of the same speaker, references recorded in a studio vs on a consumer microphone), different VALL-E inference parameters (temperature, top-p sampling), and different target texts. The initial silence gap persisted in every configuration that used reference audio conditioning, but disappeared when no reference was used. After three weeks without resolution, they engaged a VALL-E retainer developer who identified that the codec encoding of the reference audio’s pre-speech onset was being prepended to the conditioning sequence, and the language model was faithfully reproducing that silence in its outputs.
The VALL-E inference pipeline at the time of the engagement:
# Westfield VALL-E TTS Ltd. — podcast dubbing pipeline
# Microsoft VALL-E (microsoft/vall-e), Python 3.11, PyTorch 2.1.x
# Use case: zero-shot voice cloning for multilingual podcast dubbing
import torch
import numpy as np
import torchaudio
from encodec import EncodecModel
from vall_e.inference import generate
# EnCodec model for reference audio encoding
codec = EncodecModel.encodec_model_24khz()
codec.set_target_bandwidth(6.0)
def encode_reference(reference_path):
"""
Encode reference audio into EnCodec codec tokens for VALL-E conditioning.
VALL-E architecture:
1. EnCodec encodes reference audio into discrete codec tokens
(e.g., 8 codebook levels at 75 Hz frame rate = 75 tokens/sec)
2. These reference codec tokens are prepended to the target sequence
3. The neural codec language model autoregressively generates
continuation tokens: target text phonemes conditioned on reference tokens
4. EnCodec decoder reconstructs waveform from generated codec tokens
Bug: reference audio recording begins with 50–80 ms near-silence onset
before the reference speaker's first phoneme. EnCodec faithfully encodes
this near-silence as codec tokens. VALL-E's language model, trained to
generate continuations that match reference acoustic characteristics,
reproduces the silence onset in every synthesized output.
"""
wav, sr = torchaudio.load(reference_path)
if sr != 24000:
wav = torchaudio.functional.resample(wav, sr, 24000)
# Encode reference — includes any near-silence onset
with torch.no_grad():
encoded = codec.encode(wav.unsqueeze(0))
# encoded.audio_codes shape: [batch, n_codebooks, n_frames]
# At 75 Hz frame rate, 50–80 ms onset = 3–6 near-silence frames
# These silence frames are prepended to VALL-E's conditioning context
reference_tokens = encoded.audio_codes # Bug: includes silence onset frames
return reference_tokens
def synthesize_buggy(text, reference_path):
"""
Synthesize speech using VALL-E with reference audio conditioning.
Bug: initial silence gap 15–25 ms before first phoneme.
"""
reference_tokens = encode_reference(reference_path)
# VALL-E conditions on reference_tokens (prepended to target sequence)
# Language model generates codec tokens for target text
# First generated tokens follow the reference onset pattern — silence
generated_tokens = generate(
text=text,
reference_tokens=reference_tokens, # prepended conditioning context
temperature=1.0,
top_p=0.9,
)
# Decode generated tokens → waveform
with torch.no_grad():
waveform = codec.decode(generated_tokens)
return waveform
# Diagnosis: initial silence gap analysis
# Measured over 100 utterances, 5 reference speakers, 20 target texts each:
#
# With reference audio (buggy):
# Initial silence gap (RMS < -45 dBFS):
# Speaker A reference onset: 62 ms → synthesized gap: 18.3 ms mean (σ=2.1)
# Speaker B reference onset: 71 ms → synthesized gap: 22.6 ms mean (σ=1.8)
# Speaker C reference onset: 48 ms → synthesized gap: 14.9 ms mean (σ=2.4)
# Speaker D reference onset: 83 ms → synthesized gap: 25.1 ms mean (σ=2.0)
# Speaker E reference onset: 55 ms → synthesized gap: 17.2 ms mean (σ=1.9)
#
# Without reference audio (base model):
# Initial silence gap: 1.2 ms mean (σ=0.8) — essentially zero
#
# Correlation: reference onset duration vs synthesized gap: r = 0.973
# Conclusion: VALL-E language model scales synthesized silence onset proportionally
# to the near-silence duration in the reference codec token prefix
#
# EnCodec token analysis:
# 75 Hz frame rate → 1 frame per 13.3 ms
# Speaker A: 62 ms onset → 4.7 frames → 4 near-silence tokens in prefix
# Language model generates ~1.4 near-silence output tokens per reference onset token
# (synthesized gap 18.3 ms / 13.3 ms per frame = 1.4 output frames)
print("Root cause: reference audio onset near-silence encoded as codec tokens")
print("VALL-E language model generates matching silence onset in synthesized output")
print("Correlation reference onset ↔ synthesized gap: r = 0.973")
Why VALL-E reproduces the reference audio’s silence onset in synthesized outputs
VALL-E’s voice cloning is fundamentally an in-context learning problem: the neural codec language model receives the reference audio’s codec tokens as a conditioning prefix and learns to generate continuation tokens that maintain the voice’s acoustic characteristics while following the target text’s phoneme sequence. The model is trained on speech data where the codec conditioning prefix and the generated continuation share the same speaker’s acoustic properties. One of those acoustic properties, in naturally recorded reference audio, is the brief near-silence onset before the speaker begins the first phoneme of the reference utterance. EnCodec faithfully encodes this near-silence — it is real acoustic content, just at very low amplitude — and the resulting near-silence codec tokens are prepended to the language model’s conditioning sequence.
The language model, encountering a conditioning prefix that begins with low-energy near-silence tokens, has learned that the natural continuation after such a prefix is to also begin with low energy before transitioning to voiced speech. This is not a mistake in the model’s reasoning — in its training data, continuations after silence-onset references did indeed begin with brief silence when the target utterance began with a fresh onset. The model is correctly capturing the voice’s onset characteristics; the problem is that those characteristics include an artifact of the microphone recording setup (the 50–80 ms pre-speech silence) rather than a property of the voice itself.
The fix is upstream of VALL-E: trim the near-silence onset from each reference audio recording before EnCodec encoding. An energy-threshold detector identifies frames at the start of the reference where the RMS power falls below −45 dBFS for three or more consecutive frames, and removes those frames before encoding. The threshold of −45 dBFS is chosen to be below the level of any intentionally breathy or soft-onset phonemes (which are typically above −35 dBFS) but above the measurement noise floor of a typical recording environment (typically below −60 dBFS). After trimming, the codec tokens prepended to VALL-E’s conditioning sequence begin immediately with the voiced content of the reference, and the language model generates outputs that also begin immediately.
# Fix: energy-threshold silence trim before EnCodec reference encoding
# RMS < -45 dBFS for >3 consecutive frames = silence to trim from onset
import torch
import numpy as np
import torchaudio
from encodec import EncodecModel
from vall_e.inference import generate
codec = EncodecModel.encodec_model_24khz()
codec.set_target_bandwidth(6.0)
SILENCE_THRESHOLD_DBFS = -45.0 # dBFS RMS threshold for silence detection
SILENCE_MIN_FRAMES = 3 # Minimum consecutive silent frames to trigger trim
FRAME_DURATION_MS = 13.3 # EnCodec frame duration at 24 kHz (320 samples)
SAMPLES_PER_FRAME = 320 # 24000 Hz / 75 Hz frame rate
def measure_onset_silence_ms(wav_numpy, sample_rate=24000):
"""
Measure the duration of near-silence at the start of the audio.
Returns the number of samples to trim.
wav_numpy: float32 array, shape [samples] or [channels, samples]
"""
if wav_numpy.ndim == 2:
wav_numpy = wav_numpy.mean(axis=0) # Mix to mono for analysis
# Analyze in frames matching EnCodec's frame rate
n_frames = len(wav_numpy) // SAMPLES_PER_FRAME
consecutive_silence = 0
trim_samples = 0
for frame_idx in range(n_frames):
start = frame_idx * SAMPLES_PER_FRAME
end = start + SAMPLES_PER_FRAME
frame = wav_numpy[start:end]
# RMS energy in dBFS
rms = np.sqrt(np.mean(frame ** 2))
rms_dbfs = 20 * np.log10(rms + 1e-10)
if rms_dbfs < SILENCE_THRESHOLD_DBFS:
consecutive_silence += 1
else:
# First non-silent frame encountered
if consecutive_silence >= SILENCE_MIN_FRAMES:
# Enough consecutive silence found — trim up to here
trim_samples = frame_idx * SAMPLES_PER_FRAME
break
return trim_samples
def encode_reference_fixed(reference_path):
"""
Encode reference audio into codec tokens with onset silence trimmed.
Prevents VALL-E from conditioning on silence onset codec tokens.
"""
wav, sr = torchaudio.load(reference_path)
if sr != 24000:
wav = torchaudio.functional.resample(wav, sr, 24000)
wav_numpy = wav.numpy()
# Detect and trim onset silence
trim_samples = measure_onset_silence_ms(wav_numpy)
if trim_samples > 0:
trim_ms = (trim_samples / 24000) * 1000
print(f" Trimming {trim_ms:.1f} ms onset silence from reference "
f"({trim_samples} samples at RMS < {SILENCE_THRESHOLD_DBFS} dBFS)")
wav = wav[:, trim_samples:] # Trim from onset
# Validate: speaker similarity should be preserved after trimming
# (voiced content is unchanged — only pre-speech silence removed)
with torch.no_grad():
encoded = codec.encode(wav.unsqueeze(0))
reference_tokens = encoded.audio_codes # Clean: no silence onset frames
return reference_tokens
def synthesize_fixed(text, reference_path):
"""
Synthesize speech with onset-trimmed reference encoding.
Language model no longer conditioned on silence onset tokens.
"""
reference_tokens = encode_reference_fixed(reference_path)
generated_tokens = generate(
text=text,
reference_tokens=reference_tokens,
temperature=1.0,
top_p=0.9,
)
with torch.no_grad():
waveform = codec.decode(generated_tokens)
return waveform
# ---- Results after onset silence trimming ----
#
# Initial silence gap measurement (100 utterances, 5 reference speakers):
#
# Speaker A: reference onset trimmed 62 ms (4 frames removed)
# Before fix: 18.3 ms mean synthesized gap (σ=2.1)
# After fix: 0.0 ms mean synthesized gap (σ=0.4) ← effectively zero
#
# Speaker B: reference onset trimmed 71 ms (5 frames removed)
# Before fix: 22.6 ms mean synthesized gap (σ=1.8)
# After fix: 0.0 ms mean synthesized gap (σ=0.3) ← effectively zero
#
# Speaker C: reference onset trimmed 48 ms (3 frames removed)
# Before fix: 14.9 ms mean synthesized gap (σ=2.4)
# After fix: 0.0 ms mean synthesized gap (σ=0.6) ← effectively zero
#
# MOS evaluation (20-listener crowdsource, 50 utterances per condition):
# Before fix: 4.0 (σ=0.3) — high quality, silence gap not counted against MOS
# After fix: 4.0 (σ=0.3) — identical MOS (trim removes silence, not vocal content)
#
# Speaker cosine similarity (using WeSpeaker speaker encoder):
# Before fix: 0.953 (reference vs synthesized speaker embedding)
# After fix: 0.951 (trim removes 3–5 near-silence frames; speaker identity preserved)
#
# Podcast concatenation quality:
# Before fix: 15–25 ms inter-utterance click artifact in final dubbed track
# After fix: 0 ms artifact — dubbed utterances concatenate cleanly
print("Fix: onset silence trim before EnCodec encoding")
print("Initial silence gap: 15–25 ms → 0 ms; MOS: 4.0 → 4.0 (unchanged)")
print("Speaker cosine similarity: 0.953 → 0.951 (trim does not affect voiced content)")
Related retainer posts
- Tortoise TTS developer on retainer — autoregressive TTS; rare proper nouns mispronounced 20% with fast preset N=4; CLVP reranking pool too small to include correct pronunciation; fix: NER + selective N=32 for proper noun sentences; 20% → 2% — 3 weeks
- Bark developer on retainer — autoregressive TTS; long-sequence semantic token repetition loop in outputs longer than ~15 seconds; fix: repetition penalty and temperature scheduling during autoregressive decoding — 3 weeks
- CosyVoice developer on retainer — flow-matching TTS; cross-lingual mode English speaker voice for Mandarin text; fourth tone starting pitch 10–15 Hz lower than expected; English pitch IQR clips Mandarin fourth-tone starting pitch; fix: PSOLA pitch range normalization to 70 Hz IQR on English reference — 3 weeks
Track retainer hours without a client portal
HourTab turns your time-tracker CSV into a public URL your client bookmarks. No login. No portal. CSV in, URL out.
See pricing →