AI/ML Explained: Audio & Speech Models

Day 53 · 2026-07-10
For: engineers with coding experience, outside the AI field

Speech Recognition & CTC AlignmentSpeech-to-Text / CTC

MechanismAlignmentWhisper / wav2vec
One-line analogy

You have two streams at mismatched sample rates: high-frequency audio frames (one every 20 ms, hundreds per sentence) and a low-frequency character sequence (a few dozen chars). The hard part: they're different lengths, and nothing tells you which frames map to which char—like joining two distributed streams whose clocks are unsynced, with no join key. CTC's trick resembles stream-processing's debounce + collapse-dedup: each frame emits one symbol (possibly "blank"), then you fold away consecutive repeats and blanks to get clean text.

The problem + how it works

The hardest part of speech recognition (STT / ASR, automatic speech recognition, turning speech into text) isn't "understanding" but alignment: training data only gives you "this audio = this sentence," without labeling which frames are "hel" and which are "lo." Hand-labeling alignments is prohibitively expensive. The genius of CTC (Connectionist Temporal Classification, Graves et al. 2006) is: it needs no alignment labels—it "integrates out" all possible alignments at once.

How: add a special blank (⊘) to the character set. The network emits a character distribution for every frame (possibly ⊘). A "frame→symbol" path becomes text via a collapse rule: first merge adjacent repeats, then delete all ⊘. So the text "hi" can be produced by countless frame-level paths, e.g. hh⊘i, ⊘hii, h⊘⊘i… CTC's loss is maximizing the summed probability of all paths that collapse to the correct text:

P(text | audio) = Σ_{all paths π that collapse to the text} P(π | audio)

Symbol by symbol: π is one frame-level path; collapse compresses it to text; Σ sums the probabilities of millions of valid paths. Why sum? Because we don't care which frame maps to which char, only that the final text is right—so we marginalize out alignment, the latent variable we don't care about. This sum looks exponential but is computed efficiently by dynamic programming (forward-backward), same lineage as HMMs. Blank also solves two real problems: distinguishing genuinely repeated characters ("hello"'s two l's need a ⊘ between them or they collapse into one) and what to output during silence/pauses.

CTC is one school (frame-independent, non-autoregressive, fast). Another is the encoder-decoder path of Whisper (Radford et al. 2022): the encoder compresses audio into features, the decoder autoregressively generates text char by char like translation, with alignment learned implicitly by attention. A third is wav2vec 2.0 (Baevski et al. 2020)—first self-supervised pretraining (a "fill-in-the-blank" on massive unlabeled audio, masking a stretch of waveform and predicting it contrastively), then fine-tuning on a little labeled data, letting the pretrained representation chew through half the alignment problem first.

Code example
# Transcribe once with official Whisper; then see CTC's collapse rule
import whisper                        # pip install openai-whisper
model = whisper.load_model("base")   # local model, no API key needed
result = model.transcribe("audio.mp3", language="en")
print(result["text"])              # encoder-decoder autoregressive output

# The CTC collapse rule itself is only a few lines — this is its core
def ctc_collapse(frames, blank="⊘"):
    out, prev = [], None
    for ch in frames:          # frames: per-frame argmax symbols
        if ch != prev and ch != blank:  # skip repeats, drop blanks
            out.append(ch)
        prev = ch
    return "".join(out)
print(ctc_collapse(list("hh⊘ii")))     # → hi
Common misconception + your scenario
Misconception: "CTC predicts each frame independently, so it 'understands' context."—No. CTC makes a hard assumption: given the audio, frame outputs are conditionally independent. It doesn't model language regularities between characters (e.g. "app" is likely followed by "le"). That's the price of being fast and streamable, and why in practice CTC often bolts on an external language model to correct errors. Whisper's autoregressive decoder has language modeling built in, so it's more fluent—but also more prone to "hallucinating" words that weren't spoken when it can't hear clearly, riding linguistic momentum.
📌 Super-individual scenario: run Whisper locally to turn meeting recordings and voice memos into text, then feed an LLM for summaries—a zero-cost "speech→knowledge" pipeline. Knowing CTC vs autoregressive tells you: for low-latency live captions pick CTC-family, for offline high-quality transcription pick Whisper-family.
Takeaway + question
💡 The core of speech recognition isn't "understanding" but "alignment"; CTC uses blank + collapse to sum-and-marginalize over countless alignment paths, needing no frame-level labels.
🤔 CTC integrates out "the latent we don't care about (alignment)" and optimizes only "the result we care about (text)." Seen the same idea elsewhere? (Hint: EM, marginalization in variational inference)

Speech Synthesis ArchitectureText-to-Speech Architecture

Two-stageAcoustic model + Vocoder
One-line analogy

Modern TTS is like a two-stage compiler: the acoustic model is the front end, compiling text into an intermediate representation (IR)—a mel-spectrogram (a "musical score for sound," time on the x-axis, frequency energy on the y-axis); the vocoder is the back end, "code-generating" that IR into a playable waveform. The benefit is exactly a compiler's: separation of concerns—the front end handles content and prosody, the back end handles audio quality and timbre, each upgradeable independently.

The problem + how it works

Generating a 16 kHz waveform directly from text means the model must emit sixteen thousand highly-correlated samples per second—a search space too vast to learn. The breakthrough is dimensional relay: first generate the far lower-density mel-spectrogram (a few dozen frames per second, keeping only the frequency energy the ear is sensitive to, discarding phase detail), then have a vocoder fill the waveform back in.

① Acoustic model: text → mel-spectrogram. Tacotron 2 (Shen et al. 2018) is the classic: seq2seq + attention maps a character sequence to spectrogram frames. Here lies TTS's core difficulty—duration: text is short, speech is long, so how many frames does one character get? Where to stress or pause? Autoregressive models solve this implicitly via attention but are prone to skipping/repeating/breaking down; later FastSpeech-family models switched to an explicit duration predictor with parallel generation—more stable, faster.

② Vocoder: mel-spectrogram → waveform. The pioneer WaveNet (van den Oord et al. 2016)autoregressive, generating sample by sample, stunning quality but too slow to be practical (16k steps for 1 second). Successors used GANs (HiFi-GAN etc.) and diffusion vocoders to parallelize it, speeding up by orders of magnitude without losing quality.

③ New paradigm: codec language model. VALL-E (Wang et al. 2023) treats TTS as "language modeling"—first compress speech into discrete tokens with a neural codec (the star of the next section), then train an LLM to autoregressively predict those audio tokens, and finally decode back to waveform. It blurs the acoustic-model/vocoder split and brings a striking capability: 3 seconds of target audio suffices for zero-shot voice cloning.

Classic two-stage pipeline

text──acoustic model──▶mel-spectrogram (IR)──vocoder──▶waveform
      Tacotron2 / FastSpeech            WaveNet / HiFi-GAN / diffusion

New paradigm: codec language model (VALL-E)
text + 3s timbre prompt──▶autoregressively predict audio tokens──▶codec decode→waveform
Code example
# Run a two-stage TTS via HuggingFace's official pipeline (Bark/VITS similar)
from transformers import pipeline
import soundfile as sf

tts = pipeline("text-to-speech", model="microsoft/speecht5_tts")
# speaker vector: the "identity fingerprint" of timbre; swap for a cloned voice
import torch
spk = torch.zeros((1, 512))          # placeholder; use a real speaker embedding
out = tts("Hello, this is a speech synthesis demo.",
          forward_params={"speaker_embeddings": spk})
sf.write("out.wav", out["audio"], out["sampling_rate"])
# Inside: text→mel-spectrogram(acoustic)→waveform(vocoder), both stages wrapped
Common misconception + your scenario
Misconception: "TTS is already perfect, indistinguishable from a human."—Half true. Tacotron 2 does approach human recordings on single-sentence read-aloud naturalness (MOS), but that's a reading voice. What's genuinely hard is prosodic coherence, emotion, and emphasis over long text—machines still sound "flat." And autoregressive TTS has a chronic flaw: once attention alignment collapses it skips words, repeats infinitely, or cuts off—which is exactly why explicit duration modeling (FastSpeech) appeared.
📌 Personal project scenario: turn your notes or daily briefings into podcast audio with TTS, and "listen" instead of "read" during commutes. Understanding that acoustic model vs vocoder are separable tells you why voice cloning (swapping speaker vectors) and content/prosody are decoupled—changing timbre needs no retraining of the whole chain.
Takeaway + question
💡 TTS breaks through via a two-stage "text→mel-spectrogram→waveform" dimensionality reduction: acoustic model for content/prosody, vocoder for quality; codec language models (VALL-E) unify it into "autoregressively predicting audio tokens."
🤔 The acoustic model's core difficulty is "duration": how many frames per character, where to pause. Is this fundamentally the same as the scheduling problem of "estimating how many time slices a task needs" when you write code?

Neural Audio Codec & DiscretizationNeural Codec & Discretization

QuantizationRVQSoundStream / EnCodec
One-line analogy

Quantizing continuous audio into discrete codes is essentially a database's dictionary encoding: rather than storing a long string of high-precision floats, learn a codebook and map each small chunk of sound to an integer ID ("which codeword"). And RVQ (residual vector quantization) is more like staged delta encoding: the first level roughly approximates, the second encodes only "the residual (error) the first didn't capture," the third refines the residual again… approaching layer by layer, precision stacking up.

The problem + how it works

This section is the key hub for plugging audio into the LLM world. The whole LLM machinery (Transformers, next-token prediction) is built on discrete tokens, but audio is continuous. A neural audio codec is that "audio tokenizer": the encoder compresses the waveform into low-frequency features, the quantizer snaps each continuous feature vector to the nearest codeword in the codebook and emits an integer ID, and the decoder reconstructs the waveform from IDs. The whole chain trains end-to-end, aiming for "reconstructed quality as close to the original as possible."

The catch: a single codebook isn't enough. To cover rich timbres the codebook would need to be huge (hundreds of thousands of entries), unstable to train and wasteful. RVQ's answer is divide and conquer—a cascade of small codebooks:

RVQ: staged residual approximation (like delta encoding)

continuous vector x ──▶ codebook1 nearest codeword c₁, residual r₁ = x − c₁
r₁ ──▶ codebook2 approximate r₁ → c₂, residual r₂ = r₁ − c₂
r₂ ──▶ codebook3 approximate r₂ → c₃ … refine stage by stage
final x ≈ c₁+c₂+c₃+… , each audio frame = a set of codeword IDs (a few integers)

This way N small codebooks of K entries each yield Kᴺ expressive power while storing only N·K codewords—exponential expressiveness, linear storage. SoundStream (Zeghidour et al. 2021) pioneered this end-to-end RVQ codec; EnCodec (Défossez et al. 2022) pushed it to higher fidelity. Their significance goes beyond compression: audio thereby becomes a "discrete token sequence"—so VALL-E can "write" speech like writing sentences, and music and sound effects can be LLM-generated. This is precisely the bedrock that lets multimodal LLMs "hear and speak."

Code example
# Encode a waveform into discrete tokens with official EnCodec, then decode
from transformers import EncodecModel, AutoProcessor
import torch

model = EncodecModel.from_pretrained("facebook/encodec_24khz")
proc  = AutoProcessor.from_pretrained("facebook/encodec_24khz")

wav = torch.randn(1, 24000)          # 1s fake audio; load a real waveform instead
inp = proc(raw_audio=wav, sampling_rate=24000, return_tensors="pt")
enc = model.encode(inp["input_values"], inp["padding_mask"])
codes = enc.audio_codes           # discrete codes: integer IDs, shape [layers, frames]
print(codes.shape, codes.min().item(), codes.max().item())
# These integer IDs can serve as "audio tokens" fed into a language model
rec = model.decode(enc.audio_codes, enc.audio_scales,
                  inp["padding_mask"])[0]   # IDs → reconstructed waveform
Common misconception + your scenario
Misconception: "A codec is just MP3-style compression to save bandwidth."—Too narrow. Traditional codecs (MP3/Opus) are hand-crafted signal-processing algorithms whose output is a bitstream for the ear; a neural codec's output is semantic discrete tokens that go straight into a Transformer for generation, editing, understanding. For the same "discretization," MP3 is the endpoint, the neural codec is the starting point. Note also: quantization is inherently lossy—fewer codebook layers means harsher compression and more distortion; quality is traded for layers.
📌 Cross-disciplinary scenario: discretization is the universal motif of "cramming a continuous world into a symbol system"—it shows up in speech tokens, visual patches, even your own verbalized cognition of the world. RVQ's "staged residual approximation" gives you a concrete template for thinking about "how to approximate continuous reality with finite symbols."
Takeaway + question
💡 A neural codec is an "audio tokenizer": RVQ uses cascaded small codebooks to approximate residuals stage by stage—exponential expressiveness, linear storage—turning continuous waveforms into discrete tokens; this is the bedrock that lets LLMs "hear and speak."
🤔 Text tokens are natural discrete units humans built; audio tokens are learned by the model itself. Might the learned ones be more "efficient" than the human-made? What "basic unit of sound" do they correspond to?

Streaming & Real-Time MechanismsStreaming & Real-Time

CausalityStream processingLatency-quality trade-off
One-line analogy

Offline recognition is like batch processing: wait for the whole audio to arrive, see all context, compute the optimum slowly. Streaming recognition is like stream processing: results come out as data flows in, and you can't wait for the future. This forces a hard constraint—causality: the output at time t can only depend on inputs at t and before. The cost is exactly what you meet in streaming systems: you can't see future context, so results can only be suboptimal—unless you buffer a bit more (add latency) to buy quality.

The problem + how it works

Live captions, voice assistants, and simultaneous interpretation all require output while speaking, not after the sentence ends. But deep models love "seeing globally": standard attention lets every frame see the whole audio, and bidirectional convolutions read future frames too. To make them streaming, you must perform causality surgery—three mechanisms:

① Causal convolution / causal attention: force each frame to see only the past, not the future (mask out future frames), same lineage as an autoregressive LLM's causal mask. Fully streaming, zero latency, but losing future context drops accuracy.

② Chunk-based: cut audio into small chunks (e.g. 320 ms), allowing full bidirectional view within a chunk (local future) while keeping causality across chunks. This is the compromise knob between latency and quality: bigger chunks give more context and higher quality, but you wait one chunk longer—more latency.

③ Limited look-ahead: let the model peek at a small stretch of future (e.g. 200 ms) before emitting the current result—a little fixed latency to substantially recover the accuracy lost to causality.

Latency ←──────────────────────→ Quality

pure causal· small look-ahead· chunk (small)· chunk (large)· offline global
zero latency · low accuracy                         high latency · high accuracy

Structurally, non-autoregressive models like CTC are naturally suited to streaming (each frame independent, compute as frames arrive), whereas Whisper-style autoregressive decoders need extra design to stream—which is why low-latency settings favor CTC/Transducer families.

Code example
# The essence of streaming: a sliding buffer, compute per chunk, past-only
import numpy as np

class StreamingASR:
    def __init__(self, chunk=0.32, lookahead=0.2, sr=16000):
        self.buf = np.zeros(0, dtype=np.float32)   # accumulating buffer
        self.chunk = int(chunk * sr)               # samples per chunk
        self.la = int(lookahead * sr)             # look-ahead window (buys quality)

    def feed(self, audio):          # mic streams in continuously
        self.buf = np.concatenate([self.buf, audio])
        while len(self.buf) >= self.chunk + self.la:
            # take "current chunk + a bit of look-ahead", emit only the chunk's result
            window = self.buf[: self.chunk + self.la]
            yield self.decode_causal(window)   # causal decode: no revising finalized text
            self.buf = self.buf[self.chunk:]      # slide, drop what's processed
# chunk/lookahead are your two "latency↔quality" knobs
Common misconception + your scenario
Misconception: "A streaming model is just an offline model fed in chunks."—No. An offline model uses bidirectional context, so feeding it chunks directly degrades badly because the future it depends on no longer exists. A true streaming model must inject causal constraints at training time (causal masks, chunked training), learning to "judge from the past alone." Honest conclusion: streaming and offline are two models with different training objectives, not two uses of one model.
📌 Decision-support scenario: when adding voice features, first ask "how much latency can I tolerate." Real-time dialogue (<300ms) demands pure-causal/small-look-ahead and accepting an accuracy hit; post-hoc transcription doesn't care about latency, so use the offline global model for top quality. This "latency-for-quality" spectrum is the same decision muscle as trading off "consistency vs response time" in distributed systems.
Takeaway + question
💡 Streaming's core constraint is causality (no seeing the future); causal/chunk/look-ahead are three mechanisms for picking a point on the "latency↔quality" spectrum, and causality must be injected at training time—not a chunked use of an offline model.
🤔 Simultaneous interpreters also face "can't wait for the end," buying time by "predicting the next phrase." Could this "using prediction to offset causal latency" be applied to streaming speech models?

Further ReadingFurther Reading

Deep QuestionsDeep Questions

1. The four concepts (recognition/synthesis/discretization/streaming) look scattered, but share one unifying thread. What is it?
The thread is the repeated crossing of the "continuous signal ↔ discrete symbol" gap. Sound is a continuous, un-segmented, variable-length time stream; the whole intelligence machinery (Transformers, token prediction, language regularities) is built on discrete symbols. Recognition folds continuous waveforms into discrete text (CTC eliminates alignment freedom along the time axis); synthesis reverses it, unfolding discrete text into continuous waveforms (the difficulty is the one-to-many "text→duration" expansion); discretization (codec) is the purest crossing—directly minting a discrete token set for audio; streaming does the same along the time dimension, chopping a continuous input into incrementally-processable units. See this line and you'll notice multimodal LLMs move identically: for any modality you want an LLM to process, step one is to mint a discrete token set for it (visual patches, audio codec codes). Discretization is the universal interface for plugging anything into symbolic intelligence.
2. Once VALL-E turns speech into tokens, "speaking" and "writing" are no different in the model's eyes. Where does this unification lead speech models? And what are the risks?
Direction: once audio becomes tokens, speech inherits all of the LLM's dividends—in-context learning (3-second example clones timbre, the very image of few-shot), scaling laws, and multimodal unification (text/audio/visual tokens mixed in one sequence, one model "hearing-thinking-speaking" end-to-end, no longer a recognize→LLM→synthesize assembly). The endpoint may be a native speech dialogue model: no text detour, understanding and replying directly in audio-token space, preserving tone, emotion, laughter. Risks also stem from this unification: (1) voice-cloning abuse—3 seconds can forge anyone's voice, collapsing the fraud barrier; (2) hallucination migrating to speech, and speech hallucinations are harder to notice than text; (3) copyright and identity—does a person's voice count as biometric data requiring consent? (4) the more real synthesis gets, the harder detection becomes. Technical unification brings a capability leap, but also drags every alignment/safety problem of the text era into the harder-to-govern audio domain.
3. In speech recognition, "CTC (frame-independent, fast, streamable)" vs "autoregressive decoder (with a language model, high quality, hard to stream)" is a classic trade-off. Seen an isomorphic trade-off in other technical fields?
This is the classic "independent parallelism vs sequential dependency" opposition. CTC assumes frame outputs are conditionally independent—parallelizable, low-latency, streamable, but abandons modeling dependencies between outputs (doesn't know "app" is followed by "le"); an autoregressive decoder makes each output depend on all prior ones—models dependency, high quality, but must generate sequentially and is hard to stream. Isomorphic trade-offs: lock-free parallelism vs serial transactions in distributed systems, eventual vs strong consistency in databases, parallel vs autoregressive decoding in LLM inference—all the same table. The deep structure: how strong is the dependency between output elements? Weak → parallelize (save time, forgo some global optimum); strong → go sequential (preserve quality, pay latency). The classic middle ground—parallel draft, then sequential refine (CTC candidates + LM rescoring, speculative decoding's draft+verify)—is the same move across speech, translation, and LLM inference. Recognize "is this a dependency-strength-decides-parallelism problem" and you can port intuition from one field to another.
4. When humans learn their mother tongue, no one gives "frame-level alignment labels," yet they segment words and learn to speak from a continuous sound stream. How does this differ from the machine paths here, and what does it suggest for "building more human-like learning systems"?
Similar: wav2vec 2.0's self-supervision is close in spirit—babies also learn speech representations from unlabeled sound streams via "predict/fill-in," long before word meaning; CTC's "no alignment labels needed" echoes "no one tells a child which millisecond is which phoneme." Different: (1) babies learn multimodally and embodied—sound accompanies vision, intent, feedback (pointing at an apple saying "apple"), so alignment signal comes from cross-modal association, not audio alone; (2) babies listen, speak, and get corrected, a closed interactive loop, not a fixed dataset; (3) babies use far less data than Whisper's 680k hours yet generalize better, implying strong inductive biases. Takeaways: a more human-like system might (a) go multimodal-joint rather than piling on single-modality data; (b) learn via interactive closed loops rather than offline big data; (c) accept that raw data scale has limits and structural priors are the key to sample efficiency. This connects speech learning to Day 41's world models and embodied intelligence—true language understanding may be inseparable from a body that "acts in the world."