E2A2 — clean speech#

E2A2 (E²A², Encoding Efficient Asymmetric Autoencoders, github.com/danjacobellis/E2A2) is the successor of FRAPPE v3: the same split trainer (frozen per-group encoders — one learned strided projection per latent channel — SC6 softsign companding onto the coder alphabet [−31, 31], one merged decoder per operating point) with the entropy stage replaced by GGDLPC (a learned FIR predictor per channel, two affine laws mapping a causal statistic to a folded-GGD Huffman table, plus a derived zero-run mode; the C engine builds itself on import). Every reported rate is real byte-padded stream bits, round-trip verified in this notebook by decoding the latents back from the truncated bytes.

E2A2 clean speech is the 16 kHz mono speech codec trained clean→clean (an ordinary speech codec; LibriSpeech-only training pool). Ladder ps 256/128/64/32/16 × 4/6/6/12/8 channels, all_detail streams at macroregion 256 samples (16 ms — streaming-capable).

Stream layout all_detail: no global stream — every channel is coded per macroregion, and every macroregion is decodable from its own bytes alone (one byte-pad each). The stream for operating point n is the literal bit-prefix covering channels ≤ n.

The codec is loaded from the Hugging Face hub (danjacobellis/E2A2, clean_speech/ subdirectory) through the compressors.e2a2 public API — there is no local checkpoint or training code in this notebook.

import json, glob
import torch, numpy as np, matplotlib.pyplot as plt
import torchaudio
from IPython.display import Audio, display
from datasets import load_dataset
from torchvision.transforms import ToPILImage
from compressors import e2a2

Load the codec and its entropy coder#

One MergedAutoencoder per operating point, all from the hub. Each operating point has its own merged decoder (multidec); the encoders froze at merge, so every operating point’s encoder is a channel prefix of the full one. entropy.json is the GGDLPC blob (per-channel predictor taps + law scalars); the Huffman tables are generated from it when the coder loads.

NAME = 'clean_speech'
device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
config, models, coder = e2a2.load_all_codecs(NAME, device=device)
ops = list(config.cumulative_channels)
model = models[ops[-1]]
rom = e2a2.rom_facts(coder.blob)

print(f'codec         = {config.codec}/{NAME}   (hub format {config.format}, source checkpoint {config.source_checkpoint})')
print(f'modality      = {config.modality}   spatial rank = {config.dim}   input channels = {config.input_channels}')
print(f'ps ladder     = {list(config.ps)} x {list(config.group_sizes)} channels')
print(f'boundaries    = {ops}  (cumulative channels = operating points)')
for n in ops:
    d = config.decoder_configs[ops.index(n)]
    print(f'  op {n:>2}: decoder ps={d["decoder_ps"]} dim={d["decoder_dim"]} depth={d["decoder_depth"]}')
for s, (ps_s, start, end) in enumerate(model.scale_groups):
    print(f'  scale {s+1}: ps={ps_s:>4}  channels {start+1}-{end}')
print(f'\nentropy coder: GGDLPC, stream_mode={coder.stream_mode}, macroregion={coder.macroregion} samples'
      + (f', global stream = channels 1-{coder.n_coarse}' if coder.n_coarse else ', no global stream'))
print(f'  alphabet [-31, 31]; persistent model = {rom["law_scalars"]} law scalars + '
      f'{rom["predictor_ints"]} predictor ints for {rom["n_channels"]} channels')
print(f'  {rom["note"]}')
codec         = E2A2/clean_speech   (hub format e2a2_hub1, source checkpoint rG1c_g4gs12_g5.pth)
modality      = audio   spatial rank = 1   input channels = 1
ps ladder     = [256, 128, 64, 32, 16] x [4, 6, 6, 12, 8] channels
boundaries    = [4, 10, 16, 28, 36]  (cumulative channels = operating points)
  op  4: decoder ps=128 dim=1024 depth=24
  op 10: decoder ps=128 dim=1024 depth=24
  op 16: decoder ps=128 dim=1024 depth=24
  op 28: decoder ps=128 dim=1024 depth=24
  op 36: decoder ps=128 dim=1024 depth=24
  scale 1: ps= 256  channels 1-4
  scale 2: ps= 128  channels 5-10
  scale 3: ps=  64  channels 11-16
  scale 4: ps=  32  channels 17-28
  scale 5: ps=  16  channels 29-36

entropy coder: GGDLPC, stream_mode=all_detail, macroregion=256 samples, no global stream
  alphabet [-31, 31]; persistent model = 216 law scalars + 108 predictor ints for 36 channels
  edges, folded GGD pmfs, quotient split, canonical Huffman tables and the run mode are GENERATED from the per-channel scalars at load (GGDLPC.tables)

Analysis filterbank#

One learned ps-tap projection per latent channel: impulse response (left) and magnitude response (right, Nyquist 8 kHz); ≤ 6 channels per panel.

def chunk_sizes(n, max_per=6):
    k = max(1, -(-n // max_per))
    return [n // k + (1 if i < n % k else 0) for i in range(k)]

fs_hz = config.sample_rate
N = 2048
freqs_khz = np.fft.rfftfreq(N) * fs_hz / 1e3
panels = []
for s, (ps_s, start, end) in enumerate(model.scale_groups):
    W = model.encoders[s][0].weight.data.cpu().numpy()[:, 0]
    off = 0
    for sz in chunk_sizes(end - start):
        panels.append((ps_s, start + off + 1, start + off + sz, W[off:off + sz]))
        off += sz

fig, axes = plt.subplots(len(panels), 2, figsize=(10, 1.6 * len(panels)), dpi=100, constrained_layout=True)
axes = np.atleast_2d(axes)
for (ax_imp, ax_mag), (ps_s, ch_lo, ch_hi, filt) in zip(axes, panels):
    t_ms = np.arange(filt.shape[1]) / fs_hz * 1e3
    for c, h in enumerate(filt, start=ch_lo):
        H = 20 * np.log10(np.abs(np.fft.rfft(h, N)) + 1e-9)
        ax_imp.plot(t_ms, h, lw=0.8, label=f'ch{c}')
        ax_mag.plot(freqs_khz, H, lw=0.8, label=f'ch{c}')
    ax_imp.set_title(f'ps={ps_s}   channels {ch_lo}-{ch_hi}  (impulse response)', fontsize=8)
    ax_mag.set_title(f'ps={ps_s}   channels {ch_lo}-{ch_hi}', fontsize=8)
    ax_mag.set_xlim(0, fs_hz / 2e3); ax_mag.set_ylim(-60, 10)
    ax_imp.set_ylabel('h(t)'); ax_mag.set_ylabel('|H(f)| (dB)')
    ax_mag.legend(fontsize=6, loc='upper right', framealpha=0.6, ncol=3)
axes[-1, 0].set_xlabel('time (ms)'); axes[-1, 1].set_xlabel('frequency (kHz)')
plt.show()
_images/57a6af3e2d387352ad360c5b89fb47cefa466b32027e0fb4e8bb432e347911a8.webp

Load an example clip#

One row of the frozen validation set danjacobellis/e2a2_speech_val (Audio columns clean / noisy / target, 16 kHz, one row per validation clip — the trainer’s frozen sets). The codec input is the clean column; normalization follows the trainer’s audio contract: zero-mean, peak-normalize the INPUT to [−1, 1]; cropped (not looped) to a whole number of macroregions.

ROW_INDEX = 9
ds = load_dataset('danjacobellis/e2a2_speech_val', split='validation')
row = ds[ROW_INDEX]
fs = config.sample_rate
x = torch.from_numpy(row['clean']['array'].astype(np.float32)).view(1, -1)
mean = x.mean(); scale = 1.0 / ((x - mean).abs().max() + 1e-8)
x = (x - mean) * scale
unit = max(coder.macroregion, max(config.ps))
L = unit * (x.shape[-1] // unit)
x = x[..., :L]
n_samples = x.shape[-1]
x_in = x.unsqueeze(0).to(device)
x_ref = torch.from_numpy(row['clean']['array'].astype(np.float32)).view(1, -1)
x_ref = ((x_ref - mean) * scale)[..., :L]
print(f'fs   = {fs} Hz')
print(f'clip = {tuple(x.shape)}  ({n_samples / fs:.2f} s = {n_samples // coder.macroregion} macroregions of {coder.macroregion} samples),  range [{x.min():.2f}, {x.max():.2f}]')
fs   = 16000 Hz
clip = (1, 160000)  (10.00 s = 625 macroregions of 256 samples),  range [-1.00, 0.50]
mel = torchaudio.transforms.MelSpectrogram(sample_rate=fs, n_fft=1024, hop_length=256, n_mels=80)

def make_spectrogram(x_1ch):
    S = (mel(x_1ch) + 1e-8).log()
    S = ((S - S.mean()) / (3 * S.std()) + 0.5).clamp(0, 1)
    return ToPILImage()(S.flip(0)).resize((600, 160))

print('input (clean)')
display(make_spectrogram(x[0]))
Audio(x.numpy(), rate=fs)
input (clean)
_images/8153a66b960dcbecc9c7179bcc2d6e8d4e897889a58fbe6cdd2068f49649c42e.webp

Analysis transform and rounding#

Encode to the per-scale latents (companding included) and round to the coder’s [-31, 31] grid.

with torch.no_grad():
    latents = model.encode(x_in)
n_latent_values = sum(z.numel() for z in latents)
print(f'{x_in.numel()} input samples -> {n_latent_values} latent values ({n_latent_values / x_in.numel():.3f}x)')
for s, z in enumerate(latents):
    print(f'  scale {s+1} (ps={model.scale_groups[s][0]:>4}): latent {tuple(z.shape)}  range [{z.min():.1f}, {z.max():.1f}]')
n_clip = sum(int((z.round().abs() > 31).sum()) for z in latents)
latents_q = e2a2.encode_to_latents(model, x_in)
latents_np = [z[0].numpy() for z in latents_q]
print(f'clipped: {n_clip}/{n_latent_values}')
160000 input samples -> 165000 latent values (1.031x)
  scale 1 (ps= 256): latent (1, 4, 625)  range [-6.6, 7.0]
  scale 2 (ps= 128): latent (1, 6, 1250)  range [-5.8, 8.6]
  scale 3 (ps=  64): latent (1, 6, 2500)  range [-9.2, 8.2]
  scale 4 (ps=  32): latent (1, 12, 5000)  range [-10.4, 10.1]
  scale 5 (ps=  16): latent (1, 8, 10000)  range [-9.8, 9.2]
clipped: 0/165000

GGDLPC all-detail entropy coding#

encode_latents produces one byte-padded stream per 16 ms macroregion and nothing else — every region decodable from its own bytes alone; the stream for operating point n is the literal bit-prefix covering channels ≤ n. CR is against 16-bit PCM.

enc = e2a2.encode_latents(coder, latents_q)
assert enc.gblob == b''
region_bytes = (enc.prefix[:, -1] + 7) // 8
print(f'{len(enc.streams)} macroregion streams; at max detail {int(region_bytes.sum()):,} bytes total '
      f'(per region min/median/max = {int(region_bytes.min())}/{int(np.median(region_bytes))}/{int(region_bytes.max())})')
print(f'\n{"op":>4} {"stream bits":>12} {"bits/sample":>12} {"kbps":>7} {"CR":>8} {"bits/latent":>12}')
for n in ops:
    bits = e2a2.op_bits(coder, enc, n)
    nl = sum(min(max(n - start, 0), end - start) * z.shape[1]
             for z, (ps_s, start, end) in zip(latents_np, model.scale_groups))
    print(f'{n:>4} {bits:>12,} {bits / n_samples:>12.6f} {bits / (n_samples / fs) / 1000:>7.2f} '
          f'{16 * n_samples / bits:>7.1f}x {bits / nl:>12.3f}')
625 macroregion streams; at max detail 21,552 bytes total (per region min/median/max = 7/28/146)

  op  stream bits  bits/sample    kbps       CR  bits/latent
   4        8,416     0.052600    0.84   304.2x        3.366
  10       20,992     0.131200    2.10   122.0x        2.099
  16       33,824     0.211400    3.38    75.7x        1.353
  28      125,512     0.784450   12.55    20.4x        1.477
  36      172,416     1.077600   17.24    14.8x        1.045

Round-trip verification#

verify_latents decodes the latents back from the truncated bytes alone at every operating point’s literal bit-prefix (the global stream from its own bytes; every macroregion from its own bytes, no cross-region input) and asserts them bit-exact against the encoder’s. The reconstructions below are decoded from those byte-decoded latents (decode_latents), not from the encoder’s copy.

e2a2.verify_latents(coder, enc, latents_q, ops)
print(f'round trip verified at ops {ops}: every op decoded from its truncated bytes alone, bit-exact')
round trip verified at ops [4, 10, 16, 28, 36]: every op decoded from its truncated bytes alone, bit-exact

Reconstructions at each operating point#

Each op decodes its byte-prefix with its own merged decoder; PSNR vs the input (= the clean reference for this codec).

rd_clip = []
for n in ops:
    lts = e2a2.decode_latents(coder, enc, n)
    with torch.no_grad():
        xh = models[n].decode([z.to(device) for z in lts]).clamp(-1, 1)[0].float().cpu()
    bits = e2a2.op_bits(coder, enc, n)
    psnr_in = -10 * torch.nn.functional.mse_loss(x / 2 + 0.5, xh / 2 + 0.5).log10().item()
    psnr_ref = -10 * torch.nn.functional.mse_loss(x_ref / 2 + 0.5, xh / 2 + 0.5).log10().item()
    rd_clip.append((bits / n_samples, psnr_ref, n))
    print(f'{n:>2} ch:  {bits / n_samples:.6f} bits/sample  {bits / (n_samples / fs) / 1000:6.2f} kbps   '
          f'PSNR vs clean = {psnr_ref:.2f} dB   PSNR vs input = {psnr_in:.2f} dB')
    display(make_spectrogram(xh[0]))
    display(Audio(xh.numpy(), rate=fs))
 4 ch:  0.052600 bits/sample    0.84 kbps   PSNR vs clean = 34.43 dB   PSNR vs input = 34.43 dB
_images/e02f02427b87aba80272ad5c0cc2578af41bd7252424ea85d65bc2345b6c31fd.webp
10 ch:  0.131200 bits/sample    2.10 kbps   PSNR vs clean = 39.39 dB   PSNR vs input = 39.39 dB
_images/b55a27f127cb3259a971ae554b40d7d52f53a1ca98c07f90deabd60895af3d71.webp
16 ch:  0.211400 bits/sample    3.38 kbps   PSNR vs clean = 42.12 dB   PSNR vs input = 42.12 dB
_images/387ed38afee4806142e011e19f23faad0a4f7917bc6fbab2a9a042d5dc72d7f1.webp
28 ch:  0.784450 bits/sample   12.55 kbps   PSNR vs clean = 47.31 dB   PSNR vs input = 47.31 dB
_images/44ae5194dc431bdff03aad12d142fae1e4c8b168a81c8c0c253a504ed610d20a.webp
36 ch:  1.077600 bits/sample   17.24 kbps   PSNR vs clean = 48.61 dB   PSNR vs input = 48.61 dB
_images/c1fc5717c038f26f35b58ad8a4ff7b595be8a872d8e29eb23bee0c9f6fa28b56.webp

Per-macroregion rate profile#

Each 16 ms macroregion carries its own byte-padded stream — the rate allocation over time is directly visible, and any region can be transmitted, dropped, or upgraded independently.

t_edges = np.arange(len(enc.streams) + 1) * coder.macroregion / fs
fig, axes = plt.subplots(2, 1, figsize=(8, 4), dpi=120, sharex=True, gridspec_kw={'height_ratios': [1, 1.6]})
axes[0].plot(np.arange(n_samples) / fs, x[0].numpy(), lw=0.3, color='0.4')
axes[0].set_ylabel('amplitude'); axes[0].set_title('input', fontsize=9)
for n in ops:
    rb = 8 * ((enc.prefix[:, n - 1] + 7) // 8)
    axes[1].stairs(rb / (coder.macroregion / fs) / 1000, t_edges, label=f'{n} ch')
axes[1].set_xlabel('time (s)'); axes[1].set_ylabel('kbps')
axes[1].set_title(f'per-macroregion rate ({coder.macroregion} samples = {coder.macroregion/fs:.3f} s each)', fontsize=9)
axes[1].legend(fontsize=8); axes[1].grid(True, alpha=0.3)
plt.tight_layout(); plt.show()
_images/9e3a990ed093aaa568d1e994ad6b328a640f4d99c7433ff9b070c2672eb7a404.webp

Rate–distortion#

This clip’s operating points over the whole-validation-set curve from results/e2a2_clean_speech/ (PSNR vs the clean reference; falls back to the hub rd.json). There is no in-repo baseline for this modality; the reference lines are the trainer ledger’s silence baseline (24.70 dB vs clean).

def latest(pattern):
    paths = sorted(glob.glob(pattern))
    return paths[-1] if paths else None

rd_path = latest(f'results/e2a2_{NAME}/rate_distortion_*.json')
if rd_path is not None:
    rd = json.load(open(rd_path))
    key = 'channel_counts' if 'channel_counts' in rd else 'quality_values'
    rd_ops = list(rd[key])
    rd_label = f"E2A2 {NAME} — whole validation split ({rd.get('n_images', rd.get('n_samples'))} samples, compressors harness, verified streams)"
else:
    rd = None
    hub_rd = e2a2.hub_rd_curve(NAME)
    rd_ops = [p['n_ch'] for p in hub_rd['rd_curve']]
    rd_label = f"E2A2 {NAME} — whole validation split (hub rd.json, trainer's verified streams)"

if rd is not None:
    val_bps = [rd['results'][str(n)]['mean']['kbps'] * 1000 / rd['sample_rate'] for n in rd_ops]
    val_psnr = [rd['results'][str(n)]['mean']['PSNR_dB'] for n in rd_ops]
else:
    val_bps = [p['bpp'] for p in hub_rd['rd_curve']]; val_psnr = [p['psnr'] for p in hub_rd['rd_curve']]

plt.figure(figsize=(6.5, 4), dpi=120)
plt.semilogx(val_bps, val_psnr, 'o-', color='tab:blue', label=rd_label)
plt.semilogx([p[0] for p in rd_clip], [p[1] for p in rd_clip], 's--', color='tab:blue', alpha=0.5, label=f'E2A2 {NAME} — this clip (row {ROW_INDEX})')
plt.axhline(24.70, color='0.5', ls=':', lw=1, label='silence vs clean (24.70 dB, trainer ledger)')

for n, bv, psv in zip(rd_ops, val_bps, val_psnr):
    plt.annotate(f'{n}ch', (bv, psv), fontsize=7, color='tab:blue', xytext=(3, -9), textcoords='offset points')
plt.xlabel('bits per sample (16 kHz mono)'); plt.ylabel('PSNR vs clean (dB)')
plt.title('E2A2 clean speech — validation set')
plt.grid(True, which='both', alpha=0.3); plt.legend(fontsize=7); plt.tight_layout(); plt.show()

print('E2A2 clean_speech, validation average (verified streams):')
for n, bv, psv in zip(rd_ops, val_bps, val_psnr):
    print(f'  {n:>2} ch:  {bv:.6f} bits/sample  ({bv * fs / 1000:6.2f} kbps)   PSNR={psv:.2f} dB')
_images/b91f16cefd84b4f7b9c3f2f7e70d2baaeda03669cc7fd08100f51e46ccf8dc83.webp
E2A2 clean_speech, validation average (verified streams):
   4 ch:  0.052983 bits/sample  (  0.85 kbps)   PSNR=28.79 dB
  10 ch:  0.159882 bits/sample  (  2.56 kbps)   PSNR=33.10 dB
  16 ch:  0.292507 bits/sample  (  4.68 kbps)   PSNR=35.43 dB
  28 ch:  1.090351 bits/sample  ( 17.45 kbps)   PSNR=42.07 dB
  36 ch:  1.676055 bits/sample  ( 26.82 kbps)   PSNR=46.22 dB