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


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 →