Engel, Resnick, Roberts, Dieleman, Eck, Simonyan, Norouzi — 2017

Neural Audio Synthesis with WaveNet Autoencoders

Teaching a neural network to generate musical instrument sounds one audio sample at a time — and discovering a hidden "timbre space" where you can morph a flute into an organ.

Prerequisites: Convolutions + Autoencoders + Softmax
11
Chapters
4
Simulations

Chapter 0: The Problem

You want a computer to play a sound you have never heard before — not a recorded sample, not a copy of a violin, but a new instrument that sounds like the lovechild of a flute and an organ. How do you even represent "the sound of an instrument" so a machine can generate it?

For half a century, the answer was a synthesizer: a human engineer hand-designs oscillators, filters, and envelopes, then exposes knobs labeled "pitch," "cutoff," "resonance." It works, but every new timbre requires a human to invent the recipe. There is no "learn what a trumpet is and give me something nearby."

The core question: Can a neural network learn the manifold of instrument sounds directly from raw audio — so that we can generate realistic new notes, and smoothly interpolate between two instruments to create sounds that have never existed?

Why audio is brutally hard

Images had MNIST, CIFAR, ImageNet — big clean datasets that powered the GAN/VAE revolution. Audio had almost nothing comparable. And audio has a property that makes it nastier than images:

A concrete sense of the scale

Let's count. One NSynth note:

QuantityValueWhy it matters
Sample rate16,000 Hz16k numbers describe just one second
Note length4 seconds→ 64,000 raw samples per note
Fundamental pitch range~27 Hz to ~4186 HzThe "wiggle" repeats every ~4 to ~590 samples
Loudness envelope~1 event over 4 sA swell spanning all 64,000 samples

So a model must simultaneously reason about structure at the scale of 4 samples (a high note's period) and 64,000 samples (the whole envelope). That is a dynamic range of 16,000× in timescale. This is the heart of the difficulty.

The paper makes two contributions to crack this: a new WaveNet autoencoder architecture, and NSynth, a dataset of ~306,000 musical notes — an order of magnitude bigger than anything public at the time.

What makes raw-waveform audio modeling harder than image modeling?

Chapter 1: The Key Insight

WaveNet (van den Oord et al., 2016) had already shown you could generate raw audio one sample at a time with an autoregressive model — predict sample i from all the samples before it. It sounded astonishing for speech. But left to itself, an unconditioned WaveNet just "babbles": it produces locally plausible audio with no long-term structure, like a person sleep-talking convincing-sounding nonsense.

To get coherent speech, the original WaveNet had to be told what to say — conditioned on externally-provided linguistic features (phonemes, durations) computed by a separate TTS front-end. That external conditioning is the crutch.

The insight: Don't hand the model external conditioning. Let the model learn its own conditioning signal directly from the audio. Bolt a second WaveNet on the front as an encoder that reads the whole note and squeezes out a compact, time-varying code Z. Then condition the decoder on Z. The model invents its own "linguistic features" — and that code turns out to be a meaningful timbre space.

From two probability statements

The original WaveNet factorizes the joint probability of a waveform x = (x1, ..., xN) as a chain of next-sample predictions:

p(x) = ∏i=1N p(xi | x1, ..., xi-1)

Read this as: "the probability of the whole sound is the product of, at each step, predicting the next sample given everything heard so far." The autoencoder adds one extra thing to condition on — a learned code f(x):

p(x) = ∏i=1N p(xi | x1, ..., xi-1, f(x))

Here f(x) = Z is the encoder's output: a sequence of hidden codes distributed in time. Every symbol: xi = the i-th audio sample; x1...i-1 = all past samples (the autoregressive context); f(x) = a compressed summary of the entire note that gives the decoder global guidance the local context alone cannot.

Why not make Z a latent variable (a VAE)? The authors tried treating Z as a probabilistic latent p(Z|x) to marginalize over. It worked worse. The reason (from Chen et al., 2016): the WaveNet decoder is so powerful it can model the audio on its own and simply ignore a noisy latent. A deterministic encoding sidesteps this "posterior collapse." But there is a subtlety: the decoder could ignore the deterministic Z too — it doesn't, only because Z is such a strong, clean signal for the target that learning to use it is the path of least resistance.
What problem does the learned encoding f(x) solve compared to vanilla WaveNet?

Chapter 2: How WaveNet Generates Sound

Before the autoencoder, you must understand the decoder it wraps. WaveNet is an autoregressive model: to produce a 64,000-sample note it runs 64,000 forward passes, each predicting one sample from the past.

The data flow of one prediction step

At step i, the network sees a window of prior samples and must output a distribution over the next sample's value. Concretely:

Why categorical, not regression? If you ask the network to output one number and penalize squared error, you are implicitly assuming the next sample is Gaussian-distributed around a mean. Real audio is multimodal — given a context, the next sample might plausibly be high or low. A softmax over 256 bins can represent any shape of distribution, including bimodal ones. MSE cannot. (The paper later confirms: training the baseline on raw waveform with MSE "highlighted the inadequacy of the independent Gaussian assumption.")

"Causal" is the load-bearing word

To predict sample i, the model may only use samples 1...i-1. If a convolution kernel reached even one sample into the future, the model would be cheating — it would see the answer. A causal convolution shifts its kernel so the output at time t depends only on inputs at times ≤ t. This is what makes the chain-rule factorization in Ch.1 honest.

The cost of doing it sample-by-sample

This is gorgeous and also painfully slow at generation time. Training is parallel (you have the whole ground-truth waveform, so all 64,000 predictions can be made at once with teacher forcing). But generation is inherently serial: sample i must exist before you can compute sample i+1. 64,000 sequential network evaluations per note. This serial bottleneck is the price of the principled likelihood.

Why does WaveNet output a softmax over 256 values instead of regressing a single number?

Chapter 3: Dilated Convolutions — Reach Without Cost

Here is the tension. To capture a low note whose period is hundreds of samples, the model needs a receptive field of hundreds of samples. A normal convolution with a small kernel sees only a handful of neighbors. Stacking many normal layers grows the field linearly — you'd need hundreds of layers. Too expensive.

Dilated convolutions solve this. A dilation of d means the kernel skips d-1 samples between each tap. Stack layers with dilations 1, 2, 4, 8, 16, ... and the receptive field grows exponentially: with kernel size 2, after L layers the field is 2L samples.

Dilated Causal Convolution — Watch the Receptive Field Explode
1 layer · dilation 1 · receptive field = 2 samples

Each new layer doubles the dilation. The highlighted output neuron's receptive field (the input samples it can "see") grows exponentially. Causality: it never reaches to the right of itself.

A worked receptive-field calculation

With kernel size k = 2 and dilations doubling each layer, the receptive field R after L layers is:

R = (k - 1) · (2L - 1) + 1 = (2L+1 - 1)

Plug in numbers. With 10 layers (dilations 1,2,...,512): R = 211 - 1 = 2047 samples. That's ~128 ms at 16 kHz — from just 10 layers. WaveNet stacks several such blocks. The paper's encoder alone is a 30-layer residual stack of dilated convs. The exponential trick is the only reason raw-audio modeling is tractable.

Sanity check the formula: L=1, single layer, kernel 2: R = 22-1 = 3? No — for one layer the field is just k = 2. The closed form assumes the standard 1,2,4,... stack; for a single dilation-1 layer it's simply k. The takeaway is the shape of the growth: doubling layers roughly squares the reach.
Why use dilated convolutions instead of stacking normal convolutions?

Chapter 4: The WaveNet Autoencoder (Showcase)

Now we assemble the full architecture from Figure 1b. Trace the data flow end to end — this is the heart of the paper.

WaveNet Autoencoder Data Flow — Step Through It
Stage 0: raw waveform input — 64000 samples
Stride slider (pooling). Smaller stride = more timesteps in Z, finer detail. Total embedding size held ~constant.
stride 512 → Z = 125 × 16

The encoder: from waveform to temporal code

  1. Input: raw waveform, x ∈ R64000 (one note).
  2. Temporal encoder: 30 layers of dilated conv (128 channels each) + ReLU, then 1×1 convolutions. Output: a dense feature sequence, still at the full audio rate.
  3. Average pooling with stride s: this is the only downsampling in the encoder. It collapses the time axis by a factor of s.
  4. Output Z: a temporal embedding — a sequence with separate dimensions for time and channel. At the sweet-spot stride 512 (= 32 ms per step), Z has shape [125, 16]: 125 timesteps, 16 channels each.
Why "temporal" embedding matters. Unlike a classic autoencoder that crushes a whole input into one fixed vector, Z keeps a time axis. This is the architectural unlock: the embedding's length scales with the input's length. That is why the model — trained only on 4-second notes — can later reconstruct an arbitrarily long melody (Ch.10). The code is a local summary, not a global one.

The decoder: Z as a per-layer bias

  1. Upsample Z: nearest-neighbor interpolation blows Z back up from 125 steps to the full 64,000-sample rate (each Z step is held for s samples).
  2. Bias every layer: at each decoder layer, a different learned linear projection of the upsampled Z is added as a bias. So Z conditions the decoder everywhere, not just at the input.
  3. Causally-shifted waveform input: the decoder also takes the shifted ground-truth waveform (teacher forcing during training; its own past samples during generation).
  4. Output: per step, a softmax over 256 mu-law values → the reconstructed sample.

The compression budget, concretely

Input: 64,000 samples. Embedding Z at stride 512: 125 × 16 = 2000 numbers. That's a 32× compression. The paper explores the trade-off by varying the stride while holding total embedding size roughly constant: smaller stride → more timesteps but fewer channels each (finer temporal resolution, less per-step expressivity); larger stride → fewer, fatter timesteps. They found stride 512 (32 ms, 16 dims/step) the sweet spot.

The driving-function analogy (Figure 6). When you plot Z over time, it tracks the loudness contour of the note — rising on attack, decaying to near-zero in silence. The authors describe Z as a learned "driving function" for a nonlinear oscillator: the decoder is the oscillator, and Z tells it how to evolve. This is why Z generalizes — it encodes how the sound moves, not which exact note.
What is special about the temporal embedding Z compared to a standard autoencoder bottleneck?

Chapter 5: mu-law Encoding & the Categorical Loss

The decoder predicts a softmax over 256 values. Why 256, and how do you turn a continuous waveform (a 16-bit float between -1 and 1) into 256 categories without it sounding terrible? The answer is mu-law companding, a trick borrowed from telephony.

The problem with naive quantization

If you split [-1, 1] into 256 equal bins (linear quantization), the step size is fixed everywhere. But human hearing is logarithmic — we perceive ratios of loudness, not absolute differences. Quiet sounds, which carry tons of perceptual detail, get crushed into a few coarse bins. Loud peaks get more resolution than they need.

The mu-law fix

Mu-law applies a logarithmic squashing before quantizing, so quiet signals get fine resolution and loud signals get coarse resolution — matching the ear:

f(x) = sign(x) · ln(1 + μ|x|) / ln(1 + μ),   μ = 255

Every symbol: x = the raw sample in [-1, 1]; μ = 255 (gives 256 levels after quantizing); sign(x) preserves the waveform's polarity; the ln compresses the dynamic range. After applying f, you round to one of 256 levels. To play the audio back, you apply the inverse expansion.

Worked example. Take a quiet sample x = 0.01 and a loud one x = 0.8.
Linear 8-bit: both land in bins spaced 1/128 ≈ 0.0078 apart. The quiet 0.01 is barely 1.3 bins from zero — almost no resolution.
mu-law: f(0.01) = ln(1+255·0.01)/ln(256) = ln(3.55)/5.545 = 1.267/5.545 ≈ 0.228. f(0.8) = ln(1+204)/ln(256) = ln(205)/5.545 = 5.323/5.545 ≈ 0.960.
The quiet sample is now pushed out to 0.228 (~29 bins from zero) while the loud one only reaches 0.96 (~123 bins). Quiet detail is preserved; loud peaks share bins they don't miss.

The training loss

With audio quantized to 256 categories, the loss is plain cross-entropy (categorical), summed over every sample:

L = -∑i=1N log pθ(xi | x<i, Z)

This is the negative log-likelihood of the chain rule from Ch.1. No phase to estimate, no MSE Gaussian assumption — just "how surprised was the model by the true next bin?" The downside the paper flags honestly: the mu-law quantization itself injects a slight built-in distortion, most audible at low frequencies. Removing this distortion (e.g. with finer or learned quantization) is named as ongoing work.

Why apply mu-law companding before quantizing to 256 levels?

Chapter 6: The NSynth Dataset

You cannot study a learned audio manifold without data that spans it cleanly. The authors built NSynth: ~306,043 musical notes, an order of magnitude larger than any comparable public dataset at the time. Its design is deliberate — every axis of variation is controlled so the embedding space has something structured to learn.

How each note was generated

Average of 65.4 pitches per instrument × ~5 velocities × 1006 instruments ≈ 300k notes. The controlled grid is the point: by fixing instrument, sweeping pitch and velocity, you can later ask "does the embedding separate which instrument from which pitch?"

The three annotation axes

AnnotationValuesWhat it captures
Sourceacoustic / electronic / syntheticHow the sound was produced
Familybrass, keyboard, string, ... (exactly one)High-level instrument type
Qualitiesbright, percussive, ... (zero or more)Sonic character tags
Why a controlled grid beats "audio in the wild." Earlier audio efforts drowned in the multi-scale chaos of real recordings. NSynth's notes are single, clean, isolated, and labeled along orthogonal axes (instrument ⊥ pitch ⊥ velocity). That orthogonality is exactly what lets the paper later measure whether the model disentangles pitch from timbre (Ch.9 territory) — you can hold timbre fixed and vary pitch, because the dataset was built that way.

NSynth shipped as TFRecord files (TensorFlow Example protos), split into train and holdout sets — making it a reusable benchmark, the "ImageNet of musical notes" the field was missing.

Why did the authors build NSynth as a controlled grid (instrument × pitch × velocity) rather than scraping audio from the wild?

Chapter 7: The Spectral Baseline (and why phase ruins everything)

A new method means nothing without a strong baseline to beat. The authors built a careful convolutional autoencoder operating on spectrograms — and the story of why it struggles teaches you something deep about audio.

The architecture

A 10-layer conv encoder and 10-layer conv decoder, 2×2 strides, 4×4 kernels, leaky-ReLU + batch norm, channels growing 128 → 1024, then a fully-connected bottleneck to a single 19,841-dim hidden vector (matched in size to the WaveNet autoencoder's embedding). Classic image-autoencoder design, pointed at audio.

The representation rabbit hole

What does the conv net actually see? The authors tried, in order of increasing cleverness:

  1. Raw waveform + MSE: failed. (The Gaussian assumption again — Ch.2.)
  2. Real + imaginary FFT: low MSE, but low perceptual quality. The loss said "great"; the ear said "no."
  3. Log-magnitude power spectrum, peak-normalized to [0,1]: this correlated best with perceived distortion.
The phase catastrophe. A spectrogram has two parts: magnitude (how much energy at each frequency) and phase (the timing alignment of each frequency component). Magnitude is easy and smooth; phase is a nightmare — it wraps around 2π, it's discontinuous, and tiny phase errors are audible as buzzing and roughness. Every attempt to predict phase directly produced poor samples.

The Griffin-Lim escape hatch

So they didn't predict phase at all. They predicted only the magnitude spectrum, then used the Griffin-Lim algorithm (1984) to reconstruct a plausible phase iteratively. The recipe: large FFT (1024) relative to hop size (256), run Griffin-Lim for 1000 iterations. As a final heuristic, the MSE loss was frequency-weighted — weight 10 at 0 Hz decreasing linearly to 1 at 4000 Hz and above — to enforce phase coherence on the fundamentals, where frequency errors hurt most.

The lesson the baseline teaches. The spectral approach has to throw phase away and guess it back. WaveNet, working in the raw time domain, never separates magnitude from phase — it models the waveform directly, so phase is built in. This is precisely why the baseline produces "duller," buzzier sounds (Ch.8): Griffin-Lim's guessed phase is never as crisp as phase the model never had to lose.
Why is the spectral baseline at a disadvantage compared to the raw-waveform WaveNet?

Chapter 8: Reconstruction — Reading Rainbowgrams

How do you show that one model sounds better than another in a paper, on paper? The authors invented a visualization called the Rainbowgram — and then proved the point with a quantitative classifier.

What a Rainbowgram is

It's a constant-Q transform (CQT) spectrogram where:

Why color-code the phase derivative? Because a harmonic that holds a steady frequency produces a constant phase shift each timestep, which shows up as a solid continuous colored line. Phase noise — exactly what Griffin-Lim leaves behind — shows up as speckled, faded rainbow texture. The eye can finally see what the ear hears.

The phase-shift math. If a harmonic's true frequency fharm and an FFT bin's center fbin differ slightly, each hop introduces a constant phase shift Δφ = (fbin - fharm) · (hop/samplerate). Constant shift → constant color → a clean stripe. This is why a coherent harmonic "draws a rainbow" — and why noisy phase smears it.

The quantitative proof

Qualitative plots invite skepticism. So the authors trained a multi-task classifier to predict pitch and quality labels, then ran it on reconstructions from each model. The logic: if a reconstruction preserves the note's identity, a classifier should read it correctly — an audio analog of the Inception Score.

Audio sourcePitch accuracyQuality accuracy
Original audio91.6%90.1%
WaveNet reconstruction79.6%88.9%
Spectral baseline reconstruction46.9%85.2%

Read it: the classifier extracts pitch from WaveNet reconstructions ~70% more successfully than from the baseline (79.6 vs 46.9), and quality accuracy is nearly equal to the original audio. The qualitative "WaveNet sounds crisper, baseline buzzes" claim now has a number behind it.

What the numbers reveal. Quality accuracy barely differs across models (88.9 vs 85.2) — both preserve the broad sonic character. But pitch accuracy collapses for the baseline (46.9%). Pitch lives in precise harmonic frequencies, and that's exactly what Griffin-Lim's guessed phase smears. The metric pinpoints where the baseline fails: not in timbre, but in frequency precision.

In a Rainbowgram, what does the color encode, and why does phase noise look "speckled"?

Chapter 9: Timbre Morphing in Embedding Space

This is the payoff that made the paper famous. Because Z is a learned manifold, points between two real notes are themselves valid sounds. Linearly interpolate the embeddings of a bass and a flute, decode the result — and you get an instrument that has never existed but sounds real.

Interpolate in Z-Space — Drag to Morph Instruments
Bass Flute
α = 0.50 · Z-space: harmonics fuse into a new timbre

Top: spectrogram of the morph. Toggle the two modes and compare. Z-space interpolation fuses harmonics into a single coherent new sound; raw-audio interpolation just superimposes two notes playing at once.

The crucial difference: fusion vs. superposition

The interpolation is dead simple math:

Zα = (1 - α) · Zbass + α · Zflute,   α ∈ [0, 1]

But where you interpolate changes everything:

Why fusion happens. Z is a "driving function" (Ch.4), not a sound. The decoder is a nonlinear system. Feeding it an averaged driving function does not average the outputs — the nonlinearity produces emergent structure (new harmonics, time-varying overtone mixing). This is the difference between averaging two recipes and averaging two cakes.

The honest catch: pitch–timbre entanglement

The authors hoped to hold timbre fixed and freely change pitch by conditioning on a one-hot pitch input. Their initial attempts failed — the model learned to ignore the pitch conditioning. Investigating with a linear pitch classifier trained on the embeddings, they found that when you do add pitch conditioning during training, classification accuracy from Z drops by 13–30% (and up to a 75% relative drop at small embedding sizes). That drop is the evidence of disentangling: the more the conditioning works, the less pitch information leaks into Z. With a small (128-dim) baseline embedding they got it to balance — playing two octaves of a C-major chord from a single timbre embedding.

Why does interpolating in Z-space "fuse" instruments while interpolating raw audio just layers them?

Chapter 10: Connections & Cheat Sheet

This paper sits at a pivotal moment in generative audio — bridging the autoregressive waveform era and the latent-space generative era that followed.

2016
WaveNet — raw-audio autoregression, needs external conditioning
2017
NSynth WaveNet AE — learns its own temporal code; timbre manifold; NSynth dataset
2018
Parallel WaveNet / WaveGlow — fix the slow serial generation
2020+
Jukebox, DDSP, diffusion audio, EnCodec — latent audio codes everywhere

What generalized beyond the dataset

Because Z is a local driving function, the model — trained only on isolated 4-second notes — can reconstruct things it never saw: a whole sequence of notes, or a note held longer than 3 seconds. It even glissandos smoothly between pitches (it never saw note transitions, so it slides between them). The honest limitation the authors flag: due to memory constraints, the model can't capture truly global context — Z's receptive field is bounded by the training chunk size.

Cheat sheet — every symbol and idea

Symbol / termMeaning
p(x) = ∏ p(xi | x<i, f(x))Autoregressive likelihood, conditioned on learned code Z = f(x)
Z = f(x)Temporal embedding; shape [125, 16] at stride 512 (32× compression)
Dilated causal convKernel skips d-1 taps; receptive field grows exponentially; never sees the future
R = 2L+1 - 1Receptive field after L layers (kernel 2, doubling dilation)
mu-law, μ=255Log companding → 256 categories; matches logarithmic hearing
Softmax over 256Categorical (multimodal) output; replaces MSE/Gaussian
Zα = (1-α)ZA + αZBLinear interpolation → timbre fusion (not superposition)
Griffin-LimBaseline's phase reconstruction (1000 iters); the source of its buzz
RainbowgramCQT spectrogram: brightness = magnitude, color = instantaneous frequency

The core Python: the temporal autoencoder loss

python
import torch, torch.nn.functional as F

def mu_law_encode(x, mu=255):
    # x in [-1, 1] -> integer category in [0, 255]
    comp = torch.sign(x) * torch.log1p(mu * x.abs()) / torch.log1p(torch.tensor(float(mu)))
    return ((comp + 1) / 2 * mu + 0.5).long()      # [N] ints in [0,255]

def wavenet_ae_loss(encoder, decoder, x):
    # x: [B, N] raw waveform in [-1, 1], N = 64000
    Z      = encoder(x)                  # [B, 125, 16]  temporal code (32x compression)
    Z_up   = F.interpolate(Z.transpose(1,2), size=x.shape[-1],
                           mode='nearest').transpose(1,2)  # [B, N, 16] upsampled
    x_in   = F.pad(x, (1, 0))[:, :-1]      # causal shift: predict x[i] from x[<i]
    logits = decoder(x_in, Z_up)         # [B, N, 256] softmax over mu-law bins, Z biases each layer
    target = mu_law_encode(x)            # [B, N] categories
    return F.cross_entropy(logits.reshape(-1, 256), target.reshape(-1))

# Timbre morphing at inference: average two notes' codes, decode the result
def morph(decoder, z_a, z_b, alpha=0.5):
    z = (1 - alpha) * z_a + alpha * z_b   # fusion, not superposition
    return decoder.generate(z)            # autoregressive, 64000 serial steps
"...meaningfully interpolating in timbre to create new types of sounds that are realistic and expressive."
— from the NSynth abstract