From c6971d3733fe9a5087ee6c231f6f9e48a629c4ae Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Jul 2026 14:50:24 +0000 Subject: [PATCH 1/4] book: measured figures + signal-flow diagram for the overdrive chapters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The overdrive chapters (and the book as a whole) were text-only. Seven figures now carry the chapters' claims visually, under the book's own rule — measured, not remembered: - book/figures/overdrive.py drives the shipping kernel through the C ABI (the notebooks' ctypes bridge) and renders six SVGs into book/src/images/overdrive/: the gain-tilt-vs-drive headline, the never-flat peak transfer, odd-only vs even-series spectra across asymmetry, the body voicing curves, the 1x-vs-4x alias floor, and the shape() curve against its rejected alternatives (tanh, hard clip). Regenerate with python3 book/figures/overdrive.py after any kernel behavior change. - block-diagram.svg (hand-authored) puts the whole kernel on one line for the machine chapter: main path, the negative-LF-feedback loop, the unity clean path, and the oversampled region. - Colors are the house notebook hues with the amber snapped darker (#efb118 -> #b8890f) so the triple passes lightness-band and CVD separation checks on a light page; every multi-series figure carries direct labels so identity never rides on color alone. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01S3twSGfwUK8jnUhp5Qx6wx --- book/figures/overdrive.py | 193 ++ book/src/images/overdrive/alias.svg | 3216 +++++++++++++++++++ book/src/images/overdrive/block-diagram.svg | 76 + book/src/images/overdrive/body.svg | 536 ++++ book/src/images/overdrive/harmonics.svg | 572 ++++ book/src/images/overdrive/shape.svg | 450 +++ book/src/images/overdrive/tilt.svg | 537 ++++ book/src/images/overdrive/transfer.svg | 433 +++ book/src/machine/overdrive.md | 32 +- book/src/overdrive.md | 33 +- 10 files changed, 6074 insertions(+), 4 deletions(-) create mode 100644 book/figures/overdrive.py create mode 100644 book/src/images/overdrive/alias.svg create mode 100644 book/src/images/overdrive/block-diagram.svg create mode 100644 book/src/images/overdrive/body.svg create mode 100644 book/src/images/overdrive/harmonics.svg create mode 100644 book/src/images/overdrive/shape.svg create mode 100644 book/src/images/overdrive/tilt.svg create mode 100644 book/src/images/overdrive/transfer.svg diff --git a/book/figures/overdrive.py b/book/figures/overdrive.py new file mode 100644 index 0000000..a458b6d --- /dev/null +++ b/book/figures/overdrive.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +"""Generate the measured figures for the overdrive book chapters. + +Drives the *shipping* kernel (overdrive.h) through the C ABI via the notebooks' +ctypes bridge — the same rule as the verification notebooks: figures are +measurements of the real DSP, never illustrations of what it should do. The +companion notebook (notebooks/overdrive.ipynb) carries the same measurements +with commentary; this script renders the book-styled SVGs. + +Regenerate after a kernel behavior change: + + python3 book/figures/overdrive.py # writes book/src/images/overdrive/*.svg + +Colors: the house categorical hues (notebooks/taptools_py.py PALETTE), with the +amber snapped darker (#efb118 -> #b8890f) so the triple passes the print/CVD +lightness-band and separation checks on a light page. Every multi-series figure +carries direct labels, so identity never rides on color alone. +""" + +import pathlib +import sys + +import numpy as np +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt + +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2] / "notebooks")) +import taptools_py as tap + +OUT = pathlib.Path(__file__).resolve().parents[1] / "src" / "images" / "overdrive" +OUT.mkdir(parents=True, exist_ok=True) + +BLUE, AMBER, RED = "#4269d0", "#b8890f", "#ff725c" +INK, MUTED = "#1a1a1a", "#666666" + +plt.rcParams.update({ + "figure.dpi": 96, "figure.figsize": (7.2, 3.1), + "svg.fonttype": "none", "font.family": "sans-serif", "font.size": 9.5, + "axes.grid": True, "grid.alpha": 0.22, "grid.linewidth": 0.5, + "axes.spines.top": False, "axes.spines.right": False, + "axes.edgecolor": MUTED, "axes.labelcolor": INK, + "xtick.color": MUTED, "ytick.color": MUTED, + "axes.titlesize": 10, "axes.titlecolor": INK, + "lines.linewidth": 2.0, "legend.frameon": False, "legend.fontsize": 8.5, +}) + +fs = 48000 + + +def tone(f, amp, seconds): + t = np.arange(int(seconds * fs)) / fs + return amp * np.sin(2 * np.pi * f * t) + + +def level(y, f): + n = len(y) + t = np.arange(n) / fs + win = np.hanning(n) + return 2.0 * np.abs(np.dot(y * win, np.exp(-2j * np.pi * f * t))) / win.sum() + + +def gain_db(f, amp=5e-4, settle=0.25, meas=0.5, **params): + o = tap.Overdrive(fs, **params) + x = tone(f, amp, settle + meas) + y = o.process(x) + n0 = int(settle * fs) + return 20 * np.log10(level(y[n0:], f) / level(x[n0:], f)) + + +def spectrum_db(y, ref): + win = np.hanning(len(y)) + mag = np.abs(np.fft.rfft(y * win)) / (win.sum() / 2) + freqs = np.fft.rfftfreq(len(y), 1 / fs) + return freqs, 20 * np.log10(np.maximum(mag / ref, 1e-9)) + + +def save(fig, name): + path = OUT / name + fig.savefig(path, bbox_inches="tight") + plt.close(fig) + print(f"wrote {path}") + + +# -- 1. the headline: gain tilt grows with drive --------------------------------------- + +freqs = np.geomspace(30, 12000, 22) +fig, ax = plt.subplots() +for drive, color in ((0.0, BLUE), (0.5, AMBER), (0.9, RED)): + g = np.array([gain_db(f, drive=drive) for f in freqs]) + ax.semilogx(freqs, g, color=color, label=f"drive {drive}") + ax.annotate(f"drive {drive}", (freqs[-1] * 1.06, g[-1]), color=color, + fontsize=8.5, va="center") +ax.set(xlabel="frequency (Hz)", ylabel="small-signal gain (dB)", xlim=(28, 26000), + title="The loop's tilt grows with drive: bass pinned, mids take the gain") +ax.legend(loc="upper left") +save(fig, "tilt.svg") + +# -- 2. the transfer never flattens ---------------------------------------------------- + +amps = np.geomspace(0.05, 1.0, 12) +fig, ax = plt.subplots(figsize=(5.4, 3.1)) +for drive, color in ((0.0, BLUE), (0.5, AMBER), (1.0, RED)): + peaks = [] + for amp in amps: + o = tap.Overdrive(fs, drive=drive) + y = o.process(tone(500, amp, 0.75)) + peaks.append(np.abs(y[int(0.25 * fs):]).max()) + ax.plot(amps, peaks, "o-", ms=3.5, color=color, label=f"drive {drive}") + ax.annotate(f"drive {drive}", (amps[-1] * 1.03, peaks[-1]), color=color, + fontsize=8.5, va="center") +ax.set(xlabel="input peak", ylabel="output peak", xlim=(0, 1.18), + title="Peak transfer at 500 Hz: rising at every drive, never a plateau") +ax.legend(loc="upper left") +save(fig, "transfer.svg") + +# -- 3. even harmonics vs asymmetry ---------------------------------------------------- + +f0 = 220.5 +fig, axes = plt.subplots(1, 2, figsize=(7.2, 3.1), sharey=True) +for ax, asym in ((axes[0], 0.0), (axes[1], 0.6)): + o = tap.Overdrive(fs, drive=0.6, asymmetry=asym) + y = o.process(tone(f0, 0.5, 1.5))[int(0.5 * fs):] + fr, db = spectrum_db(y, ref=level(y, f0)) + ax.plot(fr, db, color=BLUE, lw=0.9) + for h in (2, 4): # flag the even harmonics + ax.annotate(f"H{h}", (h * f0, 20 * np.log10(max(level(y, h * f0) / level(y, f0), 1e-8)) + 6), + color=RED, fontsize=8.5, ha="center") + ax.set(xlim=(0, 2000), ylim=(-100, 8), xlabel="frequency (Hz)", + title=f"asymmetry {asym}") +axes[0].set_ylabel("level re fundamental (dB)") +fig.suptitle("Asymmetry turns on the even series (220.5 Hz, drive 0.6)", fontsize=10, color=INK, y=1.04) +save(fig, "harmonics.svg") + +# -- 4. body voicing ------------------------------------------------------------------- + +freqs_b = np.geomspace(25, 16000, 24) +fig, ax = plt.subplots() +# The curves converge at the top of the band, so the direct labels sit at the +# low end where the three are far apart (~15 dB of separation at 40 Hz). +for body, color, label in ((-1.0, BLUE, "body −1 (full lows)"), + (0.0, AMBER, "body 0"), + (1.0, RED, "body +1 (tight, mid push)")): + g = np.array([gain_db(f, drive=0.3, body=body) for f in freqs_b]) + ax.semilogx(freqs_b, g, color=color, label=label) + ax.annotate(label, (freqs_b[0] * 1.15, g[0]), color=color, fontsize=8.5, va="bottom") +ax.set(xlabel="frequency (Hz)", ylabel="small-signal gain (dB)", xlim=(23, 17000), + title="body: the voicing control (drive 0.3)") +ax.legend(loc="lower right") +save(fig, "body.svg") + +# -- 5. alias floor: 1x vs the default 4x (machine chapter) ---------------------------- + +f0a = 5001.0 +alias_f = fs - 7 * f0a +fig, ax = plt.subplots() +# 1x behind, 4x on top with slight transparency: every red peak visible above +# the blue mass is alias energy the default 4x removes. +for os_, color, alpha in ((1, RED, 1.0), (4, BLUE, 0.88)): + o = tap.Overdrive(fs, drive=0.9, asymmetry=0.2, oversample=os_) + y = o.process(tone(f0a, 0.6, 1.5))[int(0.5 * fs):] + fr, db = spectrum_db(y, ref=level(y, f0a)) + ax.plot(fr, db, lw=0.8, color=color, alpha=alpha, label=f"oversample {os_}×") +ax.axvline(alias_f, color=MUTED, lw=0.8, ls="--") +ax.annotate("folded H7\n(12993 Hz)", (alias_f, 2), color=MUTED, fontsize=8.5, + ha="center", va="bottom") +ax.set(xlim=(0, fs / 2), ylim=(-110, 14), xlabel="frequency (Hz)", + ylabel="level re fundamental (dB)", + title="5001 Hz at drive 0.9: the alias floor at 1× vs the default 4×") +ax.legend(loc="upper right") +save(fig, "alias.svg") + +# -- 6. the shaper vs its rejected alternatives (machine chapter) ---------------------- + +u = np.linspace(-4, 4, 400) +fig, ax = plt.subplots(figsize=(5.4, 3.3)) +curves = ( + (u / np.sqrt(1 + u * u), BLUE, "u/√(1+u²) (chosen)", 2.4), + (np.tanh(u), AMBER, "tanh (libm cost)", -1.5), + (np.clip(u, -1, 1), RED, "hard clip (aliases)", 1.2), +) +for y, color, label, lx in curves: + ax.plot(u, y, color=color, lw=2.0, label=label) +ax.annotate("u/√(1+u²)", (2.5, 2.5 / np.sqrt(1 + 2.5**2) - 0.16), color=BLUE, fontsize=8.5) +ax.annotate("tanh", (-2.4, np.tanh(-2.4) - 0.18), color=AMBER, fontsize=8.5, ha="right") +ax.annotate("hard clip", (1.35, 1.06), color=RED, fontsize=8.5) +ax.set(xlabel="input u", ylabel="shape(u)", ylim=(-1.35, 1.35), + title="The static curve: smooth, monotonic, asymptotic — no plateau, no seam") +ax.legend(loc="upper left") +save(fig, "shape.svg") + +print("done") diff --git a/book/src/images/overdrive/alias.svg b/book/src/images/overdrive/alias.svg new file mode 100644 index 0000000..f5d77a7 --- /dev/null +++ b/book/src/images/overdrive/alias.svg @@ -0,0 +1,3216 @@ + + + + + + + + 2026-07-24T14:47:51.335654 + image/svg+xml + + + Matplotlib v3.11.1, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 5000 + + + + + + + + + + + + + 10000 + + + + + + + + + + + + + 15000 + + + + + + + + + + + + + 20000 + + + + frequency (Hz) + + + + + + + + + + + + + + + + + −100 + + + + + + + + + + + + + −80 + + + + + + + + + + + + + −60 + + + + + + + + + + + + + −40 + + + + + + + + + + + + + −20 + + + + + + + + + + + + + 0 + + + + level re fundamental (dB) + + + + + + + + + + + + + + + + + + + folded H7 + (12993 Hz) + + + 5001 Hz at drive 0.9: the alias floor at 1× vs the default 4× + + + + + + + oversample 1× + + + + + + oversample 4× + + + + + + + + + + diff --git a/book/src/images/overdrive/block-diagram.svg b/book/src/images/overdrive/block-diagram.svg new file mode 100644 index 0000000..d73b324 --- /dev/null +++ b/book/src/images/overdrive/block-diagram.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + runs at the oversampled rate (default 4×) + + + x + + + preamp + + + body pre-EQ + (highpass) + + + ↑ 4× + + + + + × G + + + Σ + + + + + + shape(· + b) + − shape(b) + + + w + + + Σ + + + ↓ 4× + + + DC block + + + body post-EQ + · makeup + + y + + + + unity clean path — the transfer never flattens + + + + + LP 660 Hz + (zero-delay) + + × g_fb + negative LF feedback: + bass gain pinned at G/(1+g_fb) + diff --git a/book/src/images/overdrive/body.svg b/book/src/images/overdrive/body.svg new file mode 100644 index 0000000..430270d --- /dev/null +++ b/book/src/images/overdrive/body.svg @@ -0,0 +1,536 @@ + + + + + + + + 2026-07-24T14:47:51.188215 + image/svg+xml + + + Matplotlib v3.11.1, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + 2 + + + + + + + + + + + + + + + + + + 1 + 0 + 3 + + + + + + + + + + + + + + + + + + 1 + 0 + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + frequency (Hz) + + + + + + + + + + + + + + + + + −20 + + + + + + + + + + + + + −15 + + + + + + + + + + + + + −10 + + + + + + + + + + + + + −5 + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 5 + + + + + + + + + + + + + 10 + + + + small-signal gain (dB) + + + + + + + + + + + + + + + + + + + body −1 (full lows) + + + body 0 + + + body +1 (tight, mid push) + + + body: the voicing control (drive 0.3) + + + + + + + body −1 (full lows) + + + + + + body 0 + + + + + + body +1 (tight, mid push) + + + + + + + + + + diff --git a/book/src/images/overdrive/harmonics.svg b/book/src/images/overdrive/harmonics.svg new file mode 100644 index 0000000..c2eb873 --- /dev/null +++ b/book/src/images/overdrive/harmonics.svg @@ -0,0 +1,572 @@ + + + + + + + + 2026-07-24T14:47:50.121533 + image/svg+xml + + + Matplotlib v3.11.1, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 500 + + + + + + + + + + + + + 1000 + + + + + + + + + + + + + 1500 + + + + + + + + + + + + + 2000 + + + + frequency (Hz) + + + + + + + + + + + + + + + + + −100 + + + + + + + + + + + + + −80 + + + + + + + + + + + + + −60 + + + + + + + + + + + + + −40 + + + + + + + + + + + + + −20 + + + + + + + + + + + + + 0 + + + + level re fundamental (dB) + + + + + + + + + + + + + asymmetry 0.0 + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 500 + + + + + + + + + + + + + 1000 + + + + + + + + + + + + + 1500 + + + + + + + + + + + + + 2000 + + + + frequency (Hz) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + H2 + + + H4 + + + asymmetry 0.6 + + + + Asymmetry turns on the even series (220.5 Hz, drive 0.6) + + + + + + + + + + + diff --git a/book/src/images/overdrive/shape.svg b/book/src/images/overdrive/shape.svg new file mode 100644 index 0000000..cfcce2c --- /dev/null +++ b/book/src/images/overdrive/shape.svg @@ -0,0 +1,450 @@ + + + + + + + + 2026-07-24T14:47:51.445108 + image/svg+xml + + + Matplotlib v3.11.1, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + −4 + + + + + + + + + + + + + −3 + + + + + + + + + + + + + −2 + + + + + + + + + + + + + −1 + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 1 + + + + + + + + + + + + + 2 + + + + + + + + + + + + + 3 + + + + + + + + + + + + + 4 + + + + input u + + + + + + + + + + + + + + + + + −1.0 + + + + + + + + + + + + + −0.5 + + + + + + + + + + + + + 0.0 + + + + + + + + + + + + + 0.5 + + + + + + + + + + + + + 1.0 + + + + shape(u) + + + + + + + + + + + + + + + + + + + u/√(1+u²) + + + tanh + + + hard clip + + + The static curve: smooth, monotonic, asymptotic — no plateau, no seam + + + + + + + u/√(1+u²) (chosen) + + + + + + tanh (libm cost) + + + + + + hard clip (aliases) + + + + + + + + + + diff --git a/book/src/images/overdrive/tilt.svg b/book/src/images/overdrive/tilt.svg new file mode 100644 index 0000000..59b1589 --- /dev/null +++ b/book/src/images/overdrive/tilt.svg @@ -0,0 +1,537 @@ + + + + + + + + 2026-07-24T14:47:49.458386 + image/svg+xml + + + Matplotlib v3.11.1, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + 2 + + + + + + + + + + + + + + + + + + 1 + 0 + 3 + + + + + + + + + + + + + + + + + + 1 + 0 + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + frequency (Hz) + + + + + + + + + + + + + + + + + −15 + + + + + + + + + + + + + −10 + + + + + + + + + + + + + −5 + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 5 + + + + + + + + + + + + + 10 + + + + + + + + + + + + + 15 + + + + small-signal gain (dB) + + + + + + + + + + + + + + + + + + + drive 0.0 + + + drive 0.5 + + + drive 0.9 + + + The loop's tilt grows with drive: bass pinned, mids take the gain + + + + + + + drive 0.0 + + + + + + drive 0.5 + + + + + + drive 0.9 + + + + + + + + + + diff --git a/book/src/images/overdrive/transfer.svg b/book/src/images/overdrive/transfer.svg new file mode 100644 index 0000000..58d9c3d --- /dev/null +++ b/book/src/images/overdrive/transfer.svg @@ -0,0 +1,433 @@ + + + + + + + + 2026-07-24T14:47:49.944677 + image/svg+xml + + + Matplotlib v3.11.1, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.0 + + + + + + + + + + + + + 0.2 + + + + + + + + + + + + + 0.4 + + + + + + + + + + + + + 0.6 + + + + + + + + + + + + + 0.8 + + + + + + + + + + + + + 1.0 + + + + input peak + + + + + + + + + + + + + + + + + 0.0 + + + + + + + + + + + + + 0.2 + + + + + + + + + + + + + 0.4 + + + + + + + + + + + + + 0.6 + + + + + + + + + + + + + 0.8 + + + + + + + + + + + + + 1.0 + + + + output peak + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + drive 0.0 + + + drive 0.5 + + + drive 1.0 + + + Peak transfer at 500 Hz: rising at every drive, never a plateau + + + + + + + + + + drive 0.0 + + + + + + + + + drive 0.5 + + + + + + + + + drive 1.0 + + + + + + + + + + diff --git a/book/src/machine/overdrive.md b/book/src/machine/overdrive.md index 4e97340..b78f294 100644 --- a/book/src/machine/overdrive.md +++ b/book/src/machine/overdrive.md @@ -32,6 +32,15 @@ w = shape( G·x − g_fb·LP(w) ) the clipper inside a lowpass feedback loo y = x + w the unity clean path (non-inverting topology) ``` +![Signal-flow diagram: preamp and body pre-EQ into the oversampled region, +where the gained signal meets a summing node, the shaper, and a lowpass +feedback path; a unity clean path bypasses the shaper; DC block and body +post-EQ follow at base rate](../images/overdrive/block-diagram.svg) + +*The whole kernel on one line. The red loop is the frequency-dependent gain; +the amber path is why the transfer never flattens; everything inside the +dashed region runs at the oversampled rate.* + `G` is the drive gain (a dB sweep, +6 to +46). The lowpass `LP` (one-pole, corner 660 Hz) makes the fed-back signal predominantly low-frequency, so the negative feedback suppresses gain exactly where the pedal does. In the linear @@ -106,6 +115,14 @@ Three candidates from the brief, in the order they were rejected: every SIMD ISA this kernel targets, and reduces to a small LUT for a future fixed-point port. +![The three candidate curves overlaid: the hard clip's corners, tanh's tighter +knee, and the chosen rational curve's gentler, seam-free +approach](../images/overdrive/shape.svg) + +*The chosen curve reaches its asymptote more slowly than tanh — softer knee, +lower-order harmonics — and unlike the hard clip it has no corner for the +spectrum to pay for.* + Asymmetry — the even-harmonic control the odd-only Jamoma curves structurally lacked — is a bias *inside* the shaper, output-corrected so silence stays silence: `w = shape(u + b) − shape(b)` with `b = 0.5·asymmetry`. At @@ -137,6 +154,15 @@ in-band harmonics stay within measurement error. ADAA remains the flagged experiment for after the voicing locks, so the comparison is apples to apples. +![Output spectra of a 5001 Hz tone at drive 0.9, 1x overlaid on 4x: the 1x +trace shows alias peaks standing tens of dB above the 4x +floor](../images/overdrive/alias.svg) + +*Every red peak standing above the blue mass is inharmonic fold-back the +default 4× removes; the true harmonics (multiples of 5001 Hz) coincide in +both traces. The dashed line marks the folded seventh harmonic the kernel +suite pins.* + ## The voicing layer, honestly labeled Everything above is structure; the *sound* of the body control is a handful @@ -166,5 +192,7 @@ the same two-tier scheme as `svf.h`. Everything in this chapter is executable: the loop math and stability claims are pinned by `tests/overdrive_test.cpp` (silence decay, tilt-grows-with- drive, even-harmonic emergence, DC blocking, alias improvement, determinism), -and every number is a cell in -[the verification notebook](https://github.com/tap/TapTools/blob/main/notebooks/overdrive.ipynb). +every number is a cell in +[the verification notebook](https://github.com/tap/TapTools/blob/main/notebooks/overdrive.ipynb), +and the measured figures are regenerated from the shipping kernel by +`book/figures/overdrive.py`. diff --git a/book/src/overdrive.md b/book/src/overdrive.md index e3a1fa4..2148933 100644 --- a/book/src/overdrive.md +++ b/book/src/overdrive.md @@ -15,7 +15,9 @@ the shaper sits inside a lowpass feedback loop: distortion with a memory. Companion material: the reference page and help patcher in the TapTools-Max package, and the [verification notebook](https://github.com/tap/TapTools/blob/main/notebooks/overdrive.ipynb), where every number below is an executed, plotted measurement of the shipping -kernel. +kernel. The figures in this chapter are measurements too — regenerated from the +same kernel through the C ABI by `book/figures/overdrive.py`, never drawn by +hand. ## What the loop buys @@ -25,7 +27,14 @@ drive**. Measured between 80 Hz and 4 kHz, the tilt is +5 dB at `drive 0` (just the voicing EQ), +16.3 dB at `drive 0.5`, +17.2 dB at `drive 0.9`. Low frequencies are pinned near-clean by the feedback while mids and highs take the full drive gain — so a low E stays articulate under the same setting that -saturates the pick attack. That is the Tube Screamer "tightness" in one plot. +saturates the pick attack. That is the Tube Screamer "tightness" in one plot: + +![Small-signal gain versus frequency at drive 0, 0.5, and 0.9: the curves tilt +progressively steeper as drive rises, with bass pinned and mids +lifting](images/overdrive/tilt.svg) + +*The measured headline. A memoryless shaper's version of this figure is three +horizontal lines.* The second structural trait: the transfer **never flattens**. A unity clean path is summed around the clipper — the non-inverting op-amp topology — so @@ -34,6 +43,12 @@ however hard the shaped part saturates, output keeps rising with input mode's hard ±1 plateau, a large part of what read as "digital," is gone by construction. +![Output peak versus input peak at three drive settings: every curve keeps +rising with reduced slope, none goes flat](images/overdrive/transfer.svg) + +*Compression without a ceiling: the slope falls as drive rises, but never to +zero.* + ## The knobs, one by one ### `drive` — 0 to 1, edge-of-breakup to saturated @@ -58,6 +73,13 @@ treble lift is +2.5 dB at 8 kHz. The exact centers and gains are by-ear placeholders pending the in-Max voicing pass against LGW demos — the shape of the control is final, the seasoning isn't. +![Small-signal response at body −1, 0, and +1: fuller lows and a top lift +counterclockwise, thinner lows and an upper-mid push +clockwise](images/overdrive/body.svg) + +*The knob's whole range. Note the crossover around 500 Hz: `body` trades lows +against upper mids around a stable center, like the pedal.* + ### `asymmetry` — the even harmonics the old object couldn't make Both Jamoma modes were odd functions: odd harmonics only, the entire "warmth" @@ -70,6 +92,13 @@ measured output mean under full drive, full asymmetry: 10⁻¹⁰. (The original TTOverdrive contained a DC blocker whose output was computed and then discarded; this one is load-bearing.) +![Harmonic spectra of a 220.5 Hz tone at asymmetry 0 and 0.6: the left panel +shows odd harmonics only, the right adds the full even +series](images/overdrive/harmonics.svg) + +*The same tone, the same drive — the only change is `asymmetry`, and the even +series (H2, H4, …) appears between the odd lines.* + ### `oversample` — 1, 2, 4, or 8; default 4 Clipping makes harmonics; harmonics past Nyquist fold back as inharmonic From b8635a17bf29bc3956a4054ef7639ccba8268ae3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Jul 2026 14:59:58 +0000 Subject: [PATCH 2/4] =?UTF-8?q?book:=20PLAN-figures=20=E2=80=94=20the=20fu?= =?UTF-8?q?ll-book=20figure=20survey=20and=20sequencing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every chapter surveyed (14 user-facing + 13 machine) for figure opportunities, graded HIGH/MED/LOW against the chapters' actual claims, and sequenced into four waves: hand-authored signal-flow diagrams (no dependencies, 14 candidates ranked), measured figures with the C-ABI bridge already wired (9 chapters ready today), pure-math derivation plots for the machine chapters, and the work blocked on new capi bindings (comb, pitchaccum, vocoder, nr/spectra, the non-kick 808 voices — each of which would also unlock a future verification notebook). Follows the overdrive figure set as precedent and the PLAN-*.md drafting-record convention. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01S3twSGfwUK8jnUhp5Qx6wx --- book/PLAN-figures.md | 156 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 book/PLAN-figures.md diff --git a/book/PLAN-figures.md b/book/PLAN-figures.md new file mode 100644 index 0000000..619af37 --- /dev/null +++ b/book/PLAN-figures.md @@ -0,0 +1,156 @@ +# PLAN: figures for the rest of the book + +Drafting record, 2026-07-23. A full survey of every chapter (14 user-facing, +13 machine deep-dives) for image/diagram/visualization opportunities, graded +and sequenced. The overdrive chapters are the shipped precedent: measured +SVGs generated by a committed script driving the shipping kernel through the +C ABI (`book/figures/overdrive.py`), plus one hand-authored signal-flow +diagram (`src/images/overdrive/block-diagram.svg`). Everything below follows +the same two rules: + +- **Measured, not remembered.** A plot is a measurement of the shipping DSP + (via `notebooks/taptools_py.py`) or a faithful rendering of the math the + chapter derives — never an illustration of what the code *should* do. +- **Diagrams are of the actual topology.** Boxes and loops mirror the kernel + header's structure, in the house diagram style (grey main path, colored + emphasis paths, dashed rate regions). + +Conventions carried forward from the overdrive set: one generation script +per chapter under `book/figures/`, images under `src/images//`, +house palette with the amber snapped to `#b8890f` (passes the lightness-band +and CVD checks on a light page), direct labels on every multi-series figure. + +## The constraint that shapes the sequencing + +`taptools_py` reaches: `Convolver, Svf, Ladder, Diode, TB303, Vco, Wah, +Overdrive, TriggerRow, NoteRow, Kick, Tune, Yin`. **Not reachable**: the +comb bank, pitchaccum, vocoder, nr/spectra (the STFT pair), and every TR-808 +voice except the kick. Measured figures for those need `tools/capi` + +bridge extensions first (per CLAUDE.md, bindings are extended alongside the +kernel). Hand-authored diagrams and pure-math plots carry no such +dependency — which is why they lead the plan. + +## Wave 1 — signal-flow diagrams (no dependencies, highest leverage) + +Hand-authored SVGs in the overdrive diagram style. Ranked; the top four are +topologies the prose visibly strains to convey. + +1. **tb303 voice** (`acid.md` + `machine/tb303.md`) — the chapter's literal + thesis ("the couplings are the instrument"): osc → coupling HP → diode + ladder → VCA main path, with accent fanning to MEG clock/VCA/C13 sweep + and slide as an RC on the pitch CV. One diagram serves both chapters. +2. **pitchaccum loop** (`pitchaccum.md` + `machine/pitchaccum.md`) — feedback + re-entering *upstream of the transposer taps* is the whole object; drawn + against the ordinary shifter-in-feedback patch that never climbs. +3. **comb voice ring** (`fivecomb.md` + `machine/comb.md`) — Hermite tap → + loop lowpass → normalized DC blocker → warp allpass → ×fb ring, with the + d/2 pickup branch. (Both GRM chapters are otherwise blocked on bindings, + so the diagrams are their fastest wins.) +4. **diode diffusion line** (`machine/diode.md`) — four coupled capacitor + nodes with bidirectional charge/steal arrows, halved top cap, tanh on + every edge, `−k·hp(v4)` feedback — the picture that separates a + diffusion line from a buffered cascade. +5. **vocoder banks** (`vocoder.md` + `machine/vocoder.md`) — modulator bank + + followers over carrier bank into 24 multipliers; the canonical diagram. +6. **STFT scaffold** (`machine/spectral.md`, referenced by `nr.md` / + `spectra.md`) — ring → analysis window → FFT → pluggable op → IFFT → + overlap-add, latency = N annotated. +7. **conv FDL** (`convolve.md` + `machine/conv.md`) — the frequency-domain + delay line ring MAC'd against partition spectra; plus the small + overlap-save keep/discard framing diagram. +8. **ladder loop** (`ladder.md` + `machine/ladder.md`) — four tanh one-pole + stages, `−4·resonance` feedback tap annotated with the ¼-gain/−180° + oscillation condition. +9. **svf TPT core** (`svf.md` + `machine/svf.md`) — two trapezoidal + integrators in a ZDF loop, damping tap, m0/m1/m2 output mixer ("morph is + three multiplies downstream"). +10. **vco fan-out** (`vco.md` + `machine/vco.md`) — one master phase read + four ways, polyBLEP correction summed in, FM/sync/analog injections. + (The chapter already has this as ASCII art begging to be an SVG.) +11. **autowah chain** (`autowah.md` + `machine/autowah.md`) — rectifier → + RC follower → sweep law → *boxed borrowed `svf_filter`*, showing the + composition and the two patch points. +12. **tune pipeline** (`tune.md` + `machine/tune.md`) — per-hop YIN brain + over the per-sample glide/ratio path into three swappable backends + behind one seam; hop/sample clock boundary marked. +13. **seq phase math** (`machine/seq.md`) — one phase ramp deriving step + index for rows of different lengths (12 vs 16); polymeter as arithmetic. +14. **808 bridged-T** (`drums.md` + `machine/tr808.md`) — the shared + resonator core fanning to the eight voices; the kick's leg-modulation + loop drawn in. + +## Wave 2 — measured figures, zero new plumbing + +One `book/figures/.py` per chapter, the overdrive script pattern. +HIGH items only (the surveys hold the MED/LOW backlog): + +- **vco**: naive-vs-polyBLEP folded-harmonic spectrum (the 47 dB headline); + the `track 5` cents-error line (±15 cents across ±3 octaves). +- **svf**: order 2/4/8 response family (−3.01 dB at fc at every order); + morph-corner vs discrete-mode overlay (difference exactly 0); the + five-octave 90 Hz-LFO modulation-survival timeline. +- **ladder**: THD-vs-drive walk (0.5→33%); measured self-oscillation + frequency vs cutoff (tuning to 0.11% at 8 kHz). +- **autowah**: envelope + cutoff trace on a plucked note (the `Wah` class + returns both per sample — purpose-built for this); measured sweep law vs + the design curve (0.000 cents); the STFT-extracted-peak vs kernel-cutoff + overlay from `autowah_validation.ipynb` (0.979 correlation). +- **convolve**: error-vs-`np.convolve` across block sizes (3×10⁻¹² floor); + the 2×2 true-stereo path-gain matrix. +- **acid**: diode response vs Stinchcombe's published transfer function + (0.028 dB); the C13 accent "wow" trace (cutoff peak ×1.94 build and + decay) — the marquee behavior; MEG plain-vs-accent envelopes. +- **drums (kick only)**: modeled-vs-measured spectrum + decay envelope (the + calibration honesty claim); the three-behavior trace (attack punch, + retrigger, sigh) on one waveform/pitch plot. +- **tune**: the `speed` pitch-track figure (three glides onto the same + 46-cent-sharp note — the notebook already draws it); hard-snap vibrato + terracing; the period-lock cents-vs-window curve (the +5.41¢ war story, + measured with the `Yin` oracle). +- **seq**: gate/pitch trace of the 16-step 3-slide pattern (exactly 13 + note-ons); swing boundary timeline. + +## Wave 3 — pure-math plots (no kernel needed, machine chapters) + +Derivation conclusions made visible; cheap and high-value: + +- ladder: Hⁿ magnitude/phase, n = 1..4, the Barkhausen ¼/−180° point. +- vco: the polyBLEP residual r(τ) with its ±½ midpoints. +- spectral: the COLA proof (four shifted Hann² summing to 3/2); the nr + expander knee family (slope 0/2/∞). +- pitchaccum: the cos²/sin² partition-of-unity envelope pair. +- vocoder: RBJ constant-peak bandpass family pinned at 0 dB vs the + constant-skirt variant. +- diode: s-plane pole map, spread-real vs Moog's coincident four; required + k vs cutoff (why a stock 303 never sings). +- comb: the loop-gain-crosses-unity war story (|H_lp·H_dc| un-normalized vs + normalized). +- pitch: raw d(τ) vs cumulative-mean-normalized d′(τ) (YIN's octave trap + disarmed); the naive-remap-scatters vs rigid-region-translate bin diagram. +- tb303: the envmod offset law (2/3 above, 1/3 below the knob). + +## Wave 4 — blocked on C-ABI bindings + +Measured figures needing `tools/capi` + `taptools_py` extensions first +(each also unlocks a future verification notebook, so the plumbing pays +twice): + +| Binding needed | Unlocks (HIGH items) | +|---|---| +| comb bank | fractional-vs-integer tuning beats; RT60 flat vs pitch; warp/phase spectra | +| pitchaccum | the +7→+14→+21 accumulation spectrogram; bounded-at-0.99 trace | +| vocoder | bank response at narrow/wide q; linearity pin | +| nr / spectra (STFT pair) | before/after spectrogram; per-bin gain curve; remap staircase + octave-fold spectra | +| 808 voices beyond kick | per-voice calibration panels; metal-bank seed spread | + +## Suggested execution order + +Wave 1 items 1–4 first (the strain-relieving diagrams), then Wave 2 one +chapter per PR in reading order, folding in that chapter's Wave 1 diagram +and Wave 3 math plots so each PR ships a chapter *complete*. Wave 4 as a +separate plumbing PR per binding, kernel-repo CLAUDE.md rules applying +(capi + bridge + notebook together). + +Full per-chapter detail (every MED/LOW item, quoted claims, exact data +sources) lives in the survey transcripts behind this plan; this file keeps +the decisions. From 52b3b1d906b1c7ff6ebeab2f11e18a092ccc45d6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Jul 2026 15:29:18 +0000 Subject: [PATCH 3/4] book: the four highest-leverage signal-flow diagrams (PLAN-figures wave 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hand-authored SVGs in the overdrive diagram style, embedded in both the user-facing and machine chapters they serve: - tb303 voice (acid.md + machine/tb303.md): the couplings drawn — the accent bus fanning to MEG clock, C13 charge, and VCA; the C13 sweep capacitor on the cutoff sum; slide as an RC on the pitch CV. 'The couplings are the instrument,' as a picture. - pitchaccum loop (pitchaccum.md + machine/pitchaccum.md): a two-panel contrast — feedback re-entering upstream of the transposer taps (+7 -> +14 -> +21) against the ordinary shifter-in-feedback patch whose staircase never climbs. - comb voice ring (fivecomb.md + machine/comb.md): the plucked-string loop in file order — Hermite read, loop lowpass, normalized DC blocker, warp allpass, ring-time-derived fb — with the d/2 pickup tap branched to the output subtractor. - diode diffusion line (machine/diode.md): four coupled capacitor nodes with charge-in/stolen-back arrows on every junction, the halved top cap, and the 150 Hz feedback high-pass that keeps a stock 303 from singing. Every diagram rasterized and visually inspected; box contents and constants match the kernel headers and chapter text. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01S3twSGfwUK8jnUhp5Qx6wx --- book/src/acid.md | 9 ++ book/src/fivecomb.md | 9 ++ book/src/images/comb/block-diagram.svg | 70 ++++++++++++++++ book/src/images/diode/block-diagram.svg | 85 +++++++++++++++++++ book/src/images/pitchaccum/block-diagram.svg | 74 ++++++++++++++++ book/src/images/tb303/block-diagram.svg | 88 ++++++++++++++++++++ book/src/machine/comb.md | 5 ++ book/src/machine/diode.md | 11 ++- book/src/machine/pitchaccum.md | 4 + book/src/machine/tb303.md | 7 ++ book/src/pitchaccum.md | 8 ++ 11 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 book/src/images/comb/block-diagram.svg create mode 100644 book/src/images/diode/block-diagram.svg create mode 100644 book/src/images/pitchaccum/block-diagram.svg create mode 100644 book/src/images/tb303/block-diagram.svg diff --git a/book/src/acid.md b/book/src/acid.md index 2c8f04c..de80395 100644 --- a/book/src/acid.md +++ b/book/src/acid.md @@ -68,6 +68,15 @@ round out the `tap.ladder~` surface. The attributes mirror the seven-knob panel; the calibrations are Open303's measured laws. +![Signal-flow diagram of the 303 voice: pitch through slide into oscillator, +shaper, coupling highpass, diode ladder, and VCA, with the accent bus +fanning to the envelope, the C13 sweep capacitor, and the VCA, and the +envelope-driven cutoff CV feeding the ladder](images/tb303/block-diagram.svg) + +*The blocks are ordinary; the red and amber wires are the 303. Accent +touches three destinations at once, and C13 remembers across notes — the +couplings are the instrument.* + - **`waveform`** — `saw` or `square`. The square is the hardware's shaped saw: `−tanh(10^(36.9/20)·saw + 4.37)`, Open303's measured constants verbatim — rounded and notched, audibly not a 50 % pulse. diff --git a/book/src/fivecomb.md b/book/src/fivecomb.md index a984f5a..1d6d926 100644 --- a/book/src/fivecomb.md +++ b/book/src/fivecomb.md @@ -30,6 +30,15 @@ recreated rather than ported: precise feedback cap, not a hard limiter — high resonance rings clean instead of distorting. +![Signal-flow diagram of one comb voice: input sums with feedback into a +fractional delay line, read by Hermite interpolation, with the feedback ring +running through the loop lowpass, normalized DC blocker, warp allpass, and +ring-time-derived feedback gain; a pickup tap at half the loop feeds the +output subtractor](images/comb/block-diagram.svg) + +*One voice of five. The red ring is the string; the amber tap is the pluck +position.* + ## The knobs, one by one ### `freq1..5` and `freq` — the tuning diff --git a/book/src/images/comb/block-diagram.svg b/book/src/images/comb/block-diagram.svg new file mode 100644 index 0000000..1380baf --- /dev/null +++ b/book/src/images/comb/block-diagram.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + one comb voice (of five) — the plucked-string ring + + + in + + + Σ + + + delay line + length = 1/f (fractional) + + + Hermite read + 4-point, 3rd-order — in tune + + + + + + + Σ + + + y + + + + + pickup tap at d/2 + phase 100: evens cancel, odds double + + + + + loop lowpass + brightness decay + + + DC blocker + peak gain normalized to 1 + + + warp allpass + stiff-string dispersion + + + × fb + + + fb derived from ring time at the CURRENT delay: + a voice keeps its decay as its pitch sweeps — + and with the loop's peak gain pinned at 1, + res 100 rings clean: no clipper needed. + + ×5 voices under the freq/res masters and the 16-slot morph engine; main tap compensated at the fundamental. + diff --git a/book/src/images/diode/block-diagram.svg b/book/src/images/diode/block-diagram.svg new file mode 100644 index 0000000..88dfe3e --- /dev/null +++ b/book/src/images/diode/block-diagram.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + the diode ladder — a diffusion line, not a buffered cascade + + + x + + + × drive + + + Σ + + + + u + + S(u−v₁) + + + + + v₁ + cap node, ω + + + + charge in + stolen back + + + v₂ + cap node, ω + + + + S(v₂−v₃) + + + v₃ + cap node, ω + + + + S(v₃−v₄) + + + v₄ + halved cap: 2ω + + + + y + + + + + + HP ~150 Hz + the feedback high-pass + + + × k + + u = drive·x − k·hp(v₄) + + + every edge is a tanh S(·): + each node's charge leaks both ways, + so the poles are real and spread + ~25:1 — not four coincident poles. + + Oscillation needs k ≈ 17 (Routh–Hurwitz), rising toward low cutoffs — and the 150 Hz high-pass starves the loop of the + low-frequency gain it would need there: the reason a stock 303 never quite sings. + diff --git a/book/src/images/pitchaccum/block-diagram.svg b/book/src/images/pitchaccum/block-diagram.svg new file mode 100644 index 0000000..324a22c --- /dev/null +++ b/book/src/images/pitchaccum/block-diagram.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + tap.pitchaccum~ — feedback re-enters upstream of the transposer + + in + + + Σ + + + delay buffer + ≤ 3 s + + + two moving enveloped taps + cos²/sin² sum-to-1 · Hermite · ±24 st + + + + y + + + + + DC blocker + + + × fb ≤ .99 + + back into the buffer — transposed AGAIN on every trip + + each pass re-enters the taps: + +7 st → +14 → +21 → … + the staircase climbs; the 0.99 cap + and DC blocker keep it bounded. + + + + the ordinary "shifter in a feedback loop" patch — for contrast + + in + + + Σ + + + delay + + + + + pitch shifter + + y + + + feedback taps the delay output: re-delayed, but shifted only once + + every echo is +7 st, forever: + +7 → +7 → +7 → … + the staircase never climbs. + diff --git a/book/src/images/tb303/block-diagram.svg b/book/src/images/tb303/block-diagram.svg new file mode 100644 index 0000000..ea5ebcd --- /dev/null +++ b/book/src/images/tb303/block-diagram.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + accent + + + hotter + faster decay (~200 ms) + + charge via resonance pot + + hotter VCA + + + + MEG + RC env, 3 ms atk + + + × envmod + + + + Σ + cutoff knob + + + + C13 sweep cap + across-note memory + + + + cutoff CV + + + + VCA env + fixed 1.23 s decay + + + + pitch + + + slide RC + τ = 60 ms + + + saw osc + shaper + square = −tanh(·) + + + coupling HP + bass tightener + + + diode ladder + see diode_ladder.h + + + + one-transistor VCA (vca.h shape) + warmth tracks the control voltage + + + out coupling + HP + gain + + y + + + grey: the blocks — every 303 clone has them. + red: what accent touches (three destinations). + amber: the cutoff CV path C13 leans on. + + The couplings are the instrument. + diff --git a/book/src/machine/comb.md b/book/src/machine/comb.md index a5e5798..88c8e39 100644 --- a/book/src/machine/comb.md +++ b/book/src/machine/comb.md @@ -101,6 +101,11 @@ delayed = read_hermite(d_read) → one-pole lowpass → DC blocker → warp allpass → × fb → + in → write ``` +![The comb voice ring as a diagram: delay line, Hermite read, then the +feedback chain through lowpass, DC blocker, warp allpass, and the ring-time +feedback gain back to the input sum, with the d/2 pickup tap branched to the +output](../images/comb/block-diagram.svg) + **The lowpass** is the string's brightness decay: every round trip gets a little darker, highs first, like a real string. Its coefficient is exact — `m_lp_a[v] = 1 − e^(−2π·fc/m_sr)` — placing the −3 dB corner at fc by diff --git a/book/src/machine/diode.md b/book/src/machine/diode.md index 4f30a5c..9b5be7d 100644 --- a/book/src/machine/diode.md +++ b/book/src/machine/diode.md @@ -30,7 +30,16 @@ v4' = 2ω·S(v3 − v4) [the top capacitor is halved on the schematic] Each middle equation has *two* terms — charge in from the left, charge stolen by the right. That is the loading, and it is the whole story: this is -a discrete diffusion line, not four independent one-poles. Its normalized +a discrete diffusion line, not four independent one-poles. + +![The diode ladder as four coupled capacitor nodes with charge flowing in +from the left and stolen back by the right at every junction, a halved top +capacitor, and the feedback path from the top node through the 150 Hz +high-pass and the resonance gain back to the input +sum](../images/diode/block-diagram.svg) + +*Every edge is a tanh; every middle node leaks both ways. The bidirectional +arrows are what a buffered cascade doesn't have — and why the poles spread.* Its normalized transfer function works out to exactly Stinchcombe's measured TB-303 response, diff --git a/book/src/machine/pitchaccum.md b/book/src/machine/pitchaccum.md index f212b48..52ab702 100644 --- a/book/src/machine/pitchaccum.md +++ b/book/src/machine/pitchaccum.md @@ -121,6 +121,10 @@ Goertzel energy at 659.26 Hz (one pass) in the 0.32–0.55 s window and at 987.77 Hz (two passes — the accumulation itself) in the 0.65–1.0 s window, each dominating the off-frequency reference bin. +![The accumulation loop drawn against the ordinary shifter-in-feedback +patch: upstream re-entry transposes every pass again; output-tap feedback +shifts only once](../images/pitchaccum/block-diagram.svg) + ## Boundedness: the loop gain really is fb The constant-sum envelope has a second payoff. Since e_a, e_b ≥ 0 and diff --git a/book/src/machine/tb303.md b/book/src/machine/tb303.md index d0b5b26..01bed5b 100644 --- a/book/src/machine/tb303.md +++ b/book/src/machine/tb303.md @@ -22,6 +22,13 @@ envelopes → the C13 update → the cutoff sum → oscillator and shaper → coupling high-pass → diode ladder → VCA → output coupling. Each stanza below is one of those steps. +![Signal-flow diagram of the 303 voice with the couplings highlighted: the +accent bus fanning to the envelope clock, the C13 charge path, and the VCA; +the C13 capacitor feeding the cutoff sum](../images/tb303/block-diagram.svg) + +*The file, as a schematic. Grey is what every clone has; red is what accent +touches; amber is the cutoff CV that C13 leans on.* + ## Slide: one coefficient, no special case ```text diff --git a/book/src/pitchaccum.md b/book/src/pitchaccum.md index 71d1840..bf5841a 100644 --- a/book/src/pitchaccum.md +++ b/book/src/pitchaccum.md @@ -34,6 +34,14 @@ musical instead of muddy: The signature is measurable: set +7 semitones and the kernel test finds energy at +7 **and +14** — the second pass, the accumulation itself. +![Two signal-flow diagrams contrasted: tap.pitchaccum~ feeds its output back +into the delay buffer upstream of the moving transposer taps, so every pass +is transposed again; the ordinary shifter-in-a-feedback-loop patch taps the +delay output and only ever shifts once](images/pitchaccum/block-diagram.svg) + +*The topology is the effect. Feedback re-enters upstream of the taps, so the +staircase climbs; in the ordinary patch every echo is the same interval.* + ## The knobs, one by one (per shadow, ×2) ### `pitch1` / `pitch2` — the step of the staircase From 1c8b962c5bfac26a14380371b18882d066510d78 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Jul 2026 16:16:09 +0000 Subject: [PATCH 4/4] book: signal-flow diagrams for the remaining wave-1 chapters (PLAN-figures) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ten hand-authored SVGs in the house diagram style, completing wave 1 of book/PLAN-figures.md. Each is embedded in its user-facing chapter and its machine deep-dive (twelve chapters total): - ladder: four tanh one-poles in the k = 4·resonance loop, the comp bargain, and the Xpander pole-mix taps - svf: the two-integrator TPT/ZDF core with the downstream output mix ('morph is three multiplies downstream') - vco: one master phase fanning to four readings, polyBLEP corrections, and the per-seed analog injections - autowah: the detector chain and sweep law around the boxed, borrowed svf_filter member - conv: overlap-save framing and the frequency-domain delay line with the double-buffered partition spectra - vocoder: two identical 24-band banks meeting at the multiplier array - spectral: the shared STFT scaffold with the pluggable op (embedded in nr.md and machine/spectral.md) - tune: the per-hop brain over the per-sample corrector, with the backend seam - seq: time as a function of phase, plus the polymeter tick panel - tr808: the bridged-T resonator drawn as a circuit, with the kick's per-sample leg modulation and the eight-voice fan-out Every diagram rasterized and visually inspected (three needed layout rebuilds); constants and box contents match the kernel headers and chapter text. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01S3twSGfwUK8jnUhp5Qx6wx --- book/src/autowah.md | 4 ++ book/src/convolve.md | 4 ++ book/src/drums.md | 4 ++ book/src/images/autowah/block-diagram.svg | 65 ++++++++++++++++++ book/src/images/conv/block-diagram.svg | 72 ++++++++++++++++++++ book/src/images/ladder/block-diagram.svg | 72 ++++++++++++++++++++ book/src/images/seq/phase-math.svg | 56 ++++++++++++++++ book/src/images/spectral/stft-scaffold.svg | 48 ++++++++++++++ book/src/images/svf/block-diagram.svg | 66 +++++++++++++++++++ book/src/images/tr808/bridged-t.svg | 74 +++++++++++++++++++++ book/src/images/tune/block-diagram.svg | 66 +++++++++++++++++++ book/src/images/vco/block-diagram.svg | 76 ++++++++++++++++++++++ book/src/images/vocoder/block-diagram.svg | 65 ++++++++++++++++++ book/src/ladder.md | 4 ++ book/src/machine/autowah.md | 4 ++ book/src/machine/conv.md | 5 ++ book/src/machine/ladder.md | 4 ++ book/src/machine/seq.md | 4 ++ book/src/machine/spectral.md | 5 ++ book/src/machine/svf.md | 4 ++ book/src/machine/tr808.md | 5 ++ book/src/machine/tune.md | 4 ++ book/src/machine/vco.md | 4 ++ book/src/machine/vocoder.md | 4 ++ book/src/nr.md | 4 ++ book/src/svf.md | 4 ++ book/src/tune.md | 4 ++ book/src/vco.md | 4 ++ book/src/vocoder.md | 4 ++ 29 files changed, 739 insertions(+) create mode 100644 book/src/images/autowah/block-diagram.svg create mode 100644 book/src/images/conv/block-diagram.svg create mode 100644 book/src/images/ladder/block-diagram.svg create mode 100644 book/src/images/seq/phase-math.svg create mode 100644 book/src/images/spectral/stft-scaffold.svg create mode 100644 book/src/images/svf/block-diagram.svg create mode 100644 book/src/images/tr808/bridged-t.svg create mode 100644 book/src/images/tune/block-diagram.svg create mode 100644 book/src/images/vco/block-diagram.svg create mode 100644 book/src/images/vocoder/block-diagram.svg diff --git a/book/src/autowah.md b/book/src/autowah.md index 7fc13f4..7f3d04e 100644 --- a/book/src/autowah.md +++ b/book/src/autowah.md @@ -40,6 +40,10 @@ law. The measured control behavior: measures 256 ms, and the release fits a pure exponential with residual σ = 0.004 — an RC discharge, like the hardware. +![Signal-flow diagram of the autowah: the audio path through the borrowed SVF core and dry/wet mix, with the detector chain of sensitivity gain, rectifier, RC follower, tanh knee, and exponential sweep law driving the cutoff](images/autowah/block-diagram.svg) + +*A detector, a law, and a borrowed filter — the amber chain is everything this object adds to the SVF it composes.* + ## The knobs, one by one ### `sensitivity` — the trigger level, and the secret mode diff --git a/book/src/convolve.md b/book/src/convolve.md index 6503e6c..575a325 100644 --- a/book/src/convolve.md +++ b/book/src/convolve.md @@ -43,6 +43,10 @@ restart). The rest of the surface mirrors `tap.verb~` so the two reverbs read as siblings: `mix`, `gain`, `predelay`, `normalize` (energy-based, so quiet and hot IRs land at comparable levels), `bypass`, `mute`. +![Diagram of uniformly partitioned overlap-save convolution: framed input blocks through an FFT into the frequency-domain delay line, multiplied per bin against the double-buffered IR partition spectra, then IFFT with the aliased half discarded](images/conv/block-diagram.svg) + +*The wiring: one FFT in, one IFFT out, and a multiply-accumulate that is the only cost growing with IR length.* + ## True stereo, by channel count A stereo room isn't two mono rooms: sound from the left source arrives at the diff --git a/book/src/drums.md b/book/src/drums.md index cb068e7..4eaa1d6 100644 --- a/book/src/drums.md +++ b/book/src/drums.md @@ -93,6 +93,10 @@ noise layer. The rim channel is `@model rimshot|claves`: the rimshot's ~1667 + 455 Hz crack with the swing-VCA's harmonics, versus the claves' pure ~2500 Hz tick. Tunings sit within ~4 % of the measured unit. +![The bridged-T resonator circuit: an op-amp with capacitive arms, a resistive bridge, and a leg to ground, triggered through an injection resistor, with the kick's per-sample leg modulation drawn in red — and the eight voices grouped by how they use it](images/tr808/bridged-t.svg) + +*Roland's universal voice circuit. Eight voices, one network — the kick earns its punch by modulating the leg per sample.* + ## The calibration pass, honestly The §7.2 calibration ran against a real TR-808 (s/n 103852) recorded from diff --git a/book/src/images/autowah/block-diagram.svg b/book/src/images/autowah/block-diagram.svg new file mode 100644 index 0000000..c43e558 --- /dev/null +++ b/book/src/images/autowah/block-diagram.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + tap.autowah~ — a detector, a law, and a borrowed filter + + + in + + + + + svf_filter + the borrowed core (svf.h) + + + dry/wet mix + equal-power crossfade + + y + + dry + + + + (or the + sidechain + inlet) + + × sensitivity + + + rectifier + full / half wave + + + RC follower + atk/rel, exact e^(−1/τfs) + + + + + tanh knee + soft top-of-travel + + + cutoff = bias · 2^(sweep·range) + exp in Hz — 0.000 cents error + + cutoff CV, per sample + + + + envelope outlet (0..1) + + sensitivity at the floor = the detector off: + the object degenerates to a bare SVF parked at bias + (the cocked-wah mode the runtime test pins). + diff --git a/book/src/images/conv/block-diagram.svg b/book/src/images/conv/block-diagram.svg new file mode 100644 index 0000000..f84791c --- /dev/null +++ b/book/src/images/conv/block-diagram.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + uniformly-partitioned overlap-save — the frequency-domain delay line + + + in + + + frame [prev | new] + 2B samples, hop B + + + FFT + + + + + frequency-domain delay line + + X_m + + X_m−1 + + X_m−2 + + + X_m−P+1 + + + + IR partition spectra H₀ … H_P−1 + double-buffered — an IR swap is one atomic pointer flip + + + + + + + + + + Y = Σ H_j · X_m−j + per-bin MAC — the only cost + that grows with IR length; + FFT cost is constant + + + + + IFFT + + + + + aliased half + discard + + valid B + keep → y + + Overlap-save: convolve 2B samples circularly, keep only the clean second half — exactly B linear-convolution samples per block, + latency exactly B. Measured against direct convolution: max difference 3×10⁻¹², identical at every block size. + There is no "character" in this engine — the room you loaded is the room you get. + diff --git a/book/src/images/ladder/block-diagram.svg b/book/src/images/ladder/block-diagram.svg new file mode 100644 index 0000000..4910215 --- /dev/null +++ b/book/src/images/ladder/block-diagram.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + the Moog ladder — four saturating one-poles inside one feedback loop + + + x + + + × drive + + + Σ + + + + + + + tanh → G₁ + + + tanh → G₂ + + + tanh → G₃ + + + tanh → G₄ + + + y₄ + + + + + pole mix + Xpander modes, taps y₀…y₄ + + y + + + + per-stage taps: lp24, lp12, bp, hp — the Xpander trick + + + + + × k = 4·res + k = 4: oscillation + + at fc each stage is −45° and gain 1/√2: four give −180° and ¼, so k = 4·resonance + puts the oscillation threshold exactly at resonance 1. Each stage is a one-pole with + g = tan(πfc/fs), and the loop is solved zero-delay (predict linear, saturate, commit). + + + + + + comp · k · x + recovers the eaten passband + + → into the Σ: resonance stops costing bass + diff --git a/book/src/images/seq/phase-math.svg b/book/src/images/seq/phase-math.svg new file mode 100644 index 0000000..d8de8c3 --- /dev/null +++ b/book/src/images/seq/phase-math.svg @@ -0,0 +1,56 @@ + + + + + + + + time as a function of phase — no clock, no state, no drift + + + + + + + one phase ramp, [0,1) per cycle + + + + k = ⌊φ · length⌋ + step derived, not counted; + swing warps odd boundaries + + + k ≠ k_prev ? + step entry — the only memory + + + + + trigger-row emitter + pulse_ms · accent level 0.01/0.5/1.0 + + note-row emitter + pitch · gate (closes at 0.5) · slide hold + + + polymeter as arithmetic — two lengths, one ramp, zero drift + + len 16 + + + + + + + + + len 12 + + + + + + + both rows read the same φ — boundaries at k/len, re-derived every sample. They cannot drift; they meet again at the wrap, exactly. + diff --git a/book/src/images/spectral/stft-scaffold.svg b/book/src/images/spectral/stft-scaffold.svg new file mode 100644 index 0000000..8b8374f --- /dev/null +++ b/book/src/images/spectral/stft-scaffold.svg @@ -0,0 +1,48 @@ + + + + + + + + the STFT scaffold (stft.h) — one pump, pluggable spectral ops + + + in + + + input ring + hop = N/4 + + + × Hann + analysis window + + + FFT + + + spectral op(re, im, N) + nr: per-bin downward expander + spectra: bin remap + Hermitian mirror + + + IFFT + + + × Hann + synthesis window + + + overlap-add + ÷ 3/2 (COLA) + + out + + + The window is applied twice (analysis and synthesis), so smoothing covers both the op's + discontinuities and the frame seams; four shifted Hann² windows at hop N/4 sum to exactly 3/2 at + every sample — divide once and, with the op at identity, the output reconstructs the input below 10⁻⁶. + Latency is exactly N samples, independent of the op — verified by the impulse test both effects share. + tap.nr~ and tap.spectra~ are this one scaffold with different middles: the pump is shared, certified machinery. + diff --git a/book/src/images/svf/block-diagram.svg b/book/src/images/svf/block-diagram.svg new file mode 100644 index 0000000..083adcb --- /dev/null +++ b/book/src/images/svf/block-diagram.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + the TPT state-variable core — two integrators, one zero-delay loop, every response downstream + + + v₀ + + + Σ + + + + + hp + + + trapezoidal, g = tan(πfc/fs) + + + v₁ (band) + + + + ic1/ic2 equivalent currents + + + v₂ (low) + + + + + + × k + + damping (k = 1/Q) + + the algebraic loop — solved per sample, no unit delay anywhere in it + + + + output mix + m₀·v₀′ + m₁·v₁ + m₂·v₂ + LP/HP/BP/notch/peak/allpass, + bell + shelves, and the morph circle + + y + + every response is three multiplies + downstream of the same two states — + which is why morph corners are + bit-identical to the discrete modes, + and why the driven circuit only has + to tanh one node (the band). + + A-stable by construction: modulate fc every sample and the loop re-solves — no delay to blow the fuse the Chamberlin SVF carries. + diff --git a/book/src/images/tr808/bridged-t.svg b/book/src/images/tr808/bridged-t.svg new file mode 100644 index 0000000..30a8f0b --- /dev/null +++ b/book/src/images/tr808/bridged-t.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + the bridged-T resonator — Roland's universal voice circuit + + + trigger + 4–14 V pulse + + + injection R + (parallels the leg) + + + + + op-amp + + + + out — the ring + + + + + + + + + + + C_b + C_a + + + R bridge + + + Vcomm (exposed) + + + + R leg + + + + + fc = 1 / ( 2π · √(R_leg_eff · R_bridge · C_a · C_b) ) + solved trapezoidally on its states — so per-sample + leg modulation (the kick's trick, red) costs nothing. + + + + kick only: + env → Q43 shorts a leg R (attack shift); + Vcomm → sigh nonlinearity shrinks the leg (pitch sigh) + + + one class, eight voices + as the resonator (rings when pinged): + kick · toms · congas · rimshot · claves + as a tuned band-pass (shapes noise/metal): + snare shells · clap filter · cowbell · hat/cymbal + (the metal bank's six square oscillators feed it) + Schematics set the frequencies (within ~4%); + recordings of s/n 103852 set the envelopes. + diff --git a/book/src/images/tune/block-diagram.svg b/book/src/images/tune/block-diagram.svg new file mode 100644 index 0000000..f524c58 --- /dev/null +++ b/book/src/images/tune/block-diagram.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + tap.tune~ — a per-hop brain over a per-sample corrector + + + + runs per hop — the brain + + + YIN + tap::dsp::yin + + f₀ + + target mapper + scale mask · 12 toggles · held MIDI + + + glide + speed (τ) · amount (fader) + + + ratio + 2^(applied/12) + + + + pitch/key outlet + (+ advisory autokey) + + + in + + + + + + input ring + shared by brain + backends + + + resynthesis backend (one seam) + grain (2-tap, ~few ms) · psola (~36 ms) · pvoc (~21 ms) + pvoc branch: LPC formant preservation + + y + + + ratio, slewed per sample + + the seam is the design: + three engines trade character + against latency and land the + same intonation (±6 cents, + all three settle on 220.00 Hz). + + The grain window slews period-locked (an integer number of detected periods) — off-lock the two-tap engine carries a +5-cent bias. + diff --git a/book/src/images/vco/block-diagram.svg b/book/src/images/vco/block-diagram.svg new file mode 100644 index 0000000..7795525 --- /dev/null +++ b/book/src/images/vco/block-diagram.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + one master phase, many readings + + + pitch + + FM (thru-zero) + + drift · jitter · track · detune + the analog section — per-seed, 0 by default + + + Σ + + f + + + + phase accumulator + φ += f/fs, wraps in [0,1) + sync resets φ (one-sided BLEP) + + + + + + + + + + + + sin: sin(2πφ) + + saw: 2φ−1 + polyBLEP + + pulse: φ>pw + 2× polyBLEP + + triangle: leaky ∫ of pulse + imperfect: even harmonics rise + + + + imperfect (per-seed shaper skew) + + + + + + + + shape crossfade + sin → saw → pulse → tri + + y + + every waveform is a *reading* of the same φ — + morphing never re-synchronizes anything, and the + polyBLEP residual (±½ at the wrap) buys 47 dB of + alias suppression over the naive readings. + + The analog section injects into two places only: the frequency sum (drift, jitter, track, detune) and the shapers (imperfect) — + all deterministic per seed. A seed is a serial number; seed 0 is the ideal instrument. + diff --git a/book/src/images/vocoder/block-diagram.svg b/book/src/images/vocoder/block-diagram.svg new file mode 100644 index 0000000..147faee --- /dev/null +++ b/book/src/images/vocoder/block-diagram.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + two identical banks and a multiplier — everything you hear is carrier + + + modulator + + + 24-band BP bank + 50 Hz → 12 kHz, log-spaced + + + 24 envelope followers + rectify → one-pole (response_interval) + + + carrier + + + the same 24-band bank + constant 0 dB peak at any q + + + + + + + + + × + × + × + × 24, band-matched + + + band k's envelope gates band k's carrier — never audio itself + + + + + + Σ + + + × gain + + y + + + the graph shape proves the pins: + a silent carrier is silence (nothing to gate), + gain is exactly linear (one multiply), + a silent modulator decays to silence + (the followers relax to zero). + + Both banks share coefficients (RBJ constant-peak biquads), so band k means the same frequencies on both sides — the multiply is honest. + diff --git a/book/src/ladder.md b/book/src/ladder.md index 3540a90..5b28424 100644 --- a/book/src/ladder.md +++ b/book/src/ladder.md @@ -28,6 +28,10 @@ Two things separate a serious ladder model from a filter with a "Moog" label: the feedback fights the saturation the way the hardware does. That is where the compression, the "sag," and the bounded self-oscillation come from. +![Signal-flow diagram of the ladder: drive into a summing node, four tanh one-pole stages in series, the resonance feedback tap, the comp compensation path, and the Xpander pole-mix taps](images/ladder/block-diagram.svg) + +*The whole filter: four stages, one loop. The red tap sets resonance, the amber paths are the comp bargain and the Xpander mode taps.* + ## The knobs, one by one ### `frequency` and the right inlet diff --git a/book/src/machine/autowah.md b/book/src/machine/autowah.md index 8418ab4..4b9440d 100644 --- a/book/src/machine/autowah.md +++ b/book/src/machine/autowah.md @@ -36,6 +36,10 @@ else. Resonance meaning is shared the same way — the wah's 0..1 knob goes through the SVF's own `q_from_resonance` mapping, so "resonance 0.7" means the same Q in both objects. +![The autowah composition as a diagram: detector chain and sweep law around the boxed borrowed svf_filter member](../images/autowah/block-diagram.svg) + +*The composition decision, drawn: everything amber is this file; the blue box is svf.h, borrowed intact.* + ## The detector: gain, rectifier, follower The detector chain in `process()` is three lines: diff --git a/book/src/machine/conv.md b/book/src/machine/conv.md index 9d07103..dcef8a3 100644 --- a/book/src/machine/conv.md +++ b/book/src/machine/conv.md @@ -81,6 +81,11 @@ below confirm that is all there is. ## The frequency-domain delay line +![The FDL as a diagram: the ring of past input spectra multiplied per bin against the partition spectra, accumulated, and inverse-transformed with the aliased half discarded](../images/conv/block-diagram.svg) + +*Why FFT cost is constant in IR length: the transforms bracket the structure, and only the MAC sees the partitions.* + + The delays `j·B` remain. Delaying partition j's contribution by j blocks is the same as convolving it with the input from j blocks *ago* — and the frame for block m − j has already been transformed. So the engine keeps a ring of diff --git a/book/src/machine/ladder.md b/book/src/machine/ladder.md index 18a643b..275035f 100644 --- a/book/src/machine/ladder.md +++ b/book/src/machine/ladder.md @@ -64,6 +64,10 @@ five seconds at k = 4.4 finite, under 2.0 peak, RMS steady within a 0.7–1.4× band. An all-zero state also solves the equations — hence the header's advice to ping it. +![The ladder as a diagram: four tanh one-pole stages wrapped by the k = 4·resonance feedback, with comp and the pole-mix taps](../images/ladder/block-diagram.svg) + +*The file as a schematic: the Barkhausen condition lives at the red tap.* + ## The algebraic loop, solved linearly first Zero-delay feedback through four stages means y4 depends on the stage-1 diff --git a/book/src/machine/seq.md b/book/src/machine/seq.md index a68a820..0008d60 100644 --- a/book/src/machine/seq.md +++ b/book/src/machine/seq.md @@ -44,6 +44,10 @@ and the landing step fires once; feed it a constant and nothing happens after the first sample. `reset()` just forgets `k_previous`, so a transport start fires its downbeat. +![Time as a function of phase, drawn: a phase ramp through the floor derivation and step-entry inequality into the two emitters, plus two row lengths reading one ramp without drift](../images/seq/phase-math.svg) + +*The one decision the file turns on — and polymeter falling out of it as arithmetic.* + ## Why phase, not a pulse clock The alternative — count incoming clock pulses — is how most step sequencers diff --git a/book/src/machine/spectral.md b/book/src/machine/spectral.md index c95db9e..ade3cff 100644 --- a/book/src/machine/spectral.md +++ b/book/src/machine/spectral.md @@ -42,6 +42,11 @@ e^(−i…)) and the conjugate-bin layout the effects below depend on. ## `stft.h`: the scaffold, and the COLA proof +![The STFT scaffold as a diagram: ring buffers and windows bracketing the FFT, the pluggable op, and the COLA-normalized overlap-add](../images/spectral/stft-scaffold.svg) + +*One pump, two effects: nr and spectra are this pipeline with different middles.* + + `stft` is the overlap-add machinery shared verbatim by both effects: Hann window, fixed 4× overlap (`m_hop = m_fftsize / m_overlap`), a circular input buffer, a circular output *accumulator*, and a per-sample pump. diff --git a/book/src/machine/svf.md b/book/src/machine/svf.md index 2cc567b..1e05cb4 100644 --- a/book/src/machine/svf.md +++ b/book/src/machine/svf.md @@ -82,6 +82,10 @@ regime of fc or modulation rate where the update gains exceed unity. The notebook's 90 Hz-LFO-through-five-octaves torture test isn't surviving by margin; it's surviving by theorem. +![The TPT SVF core as a diagram: two trapezoidal integrators, the damping and low feedback into the input sum, and the downstream output mixer](../images/svf/block-diagram.svg) + +*The loop the algebra just solved, and the mixer the next section explains.* + ## The output mix, and why morph corners cost nothing Every response is a weighted sum over the same solved values: diff --git a/book/src/machine/tr808.md b/book/src/machine/tr808.md index 7cee02c..f179721 100644 --- a/book/src/machine/tr808.md +++ b/book/src/machine/tr808.md @@ -16,6 +16,11 @@ designator or a paper section; the calibration numbers live in ## `bridged_t.h`: the universal voice circuit +![The bridged-T resonator drawn as a circuit: op-amp, capacitive arms, bridge and leg resistors, the exposed Vcomm node, and the kick's per-sample leg modulation](../images/tr808/bridged-t.svg) + +*The network every voice reuses, with the kick's circuit-bending drawn in red.* + + An op-amp with a bridged-T network in its feedback path — capacitive arms `C_a`, `C_b`, a resistive bridge, a resistive leg to ground — rings when kicked, as a decaying pseudo-sinusoid at diff --git a/book/src/machine/tune.md b/book/src/machine/tune.md index e465701..9211b91 100644 --- a/book/src/machine/tune.md +++ b/book/src/machine/tune.md @@ -35,6 +35,10 @@ grain window. Unpitched frames set the correction goal to zero — the corrector *relaxes* toward honesty — while the window holds its last value rather than lurching toward a default. +![tap.tune~'s pipeline as a diagram: the per-hop YIN/target/glide/ratio brain over the per-sample ring and resynthesis backend seam](../images/tune/block-diagram.svg) + +*The pipeline and its two clocks — the dashed region runs per hop, everything else per sample.* + ## The period lock, told as a bug hunt The first version of the resynthesis stage was the two-tap `tap.shift~` diff --git a/book/src/machine/vco.md b/book/src/machine/vco.md index 27edc61..d976e9b 100644 --- a/book/src/machine/vco.md +++ b/book/src/machine/vco.md @@ -27,6 +27,10 @@ crossfades adjacent readings of one phase, so it can never produce a discontinuity the phase itself doesn't have. The problem is entirely the discontinuities. +![The VCO as a diagram: frequency sum into the master phase accumulator, fanning to the four waveform readings and the shape crossfade](../images/vco/block-diagram.svg) + +*The fan-out the chapter title promises: every waveform is a reading of the same φ.* + ## The residual: deriving `poly_blep` A naive saw jumps by −2 at the wrap; a step's spectrum falls at only diff --git a/book/src/machine/vocoder.md b/book/src/machine/vocoder.md index 8f3ae08..09ddd7e 100644 --- a/book/src/machine/vocoder.md +++ b/book/src/machine/vocoder.md @@ -60,6 +60,10 @@ The fourth pinned property, determinism (two identical runs compare equal with `==`), is the repo-wide claim that the kernel is pure state-machine arithmetic: no randomness, no time, no allocation in the audio path. +![The vocoder graph as a diagram: two identical filter banks, per-band envelope followers, 24 multipliers, and the summed gain](../images/vocoder/block-diagram.svg) + +*The bilinear form in 24 subbands — the graph shape the proofs read off.* + ## Where the bands sit Twenty-four bands span 50 Hz to 12 kHz, log-spaced. `band_frequency(i)` diff --git a/book/src/nr.md b/book/src/nr.md index 1fa2e65..80da800 100644 --- a/book/src/nr.md +++ b/book/src/nr.md @@ -22,6 +22,10 @@ purpose with `threshold` and `slope`; the machinery itself is transparent. Also pinned: a tone below threshold is strongly attenuated; a tone above passes untouched. +![Diagram of the STFT scaffold both spectral objects share: input ring, analysis window, FFT, the pluggable spectral operation, IFFT, synthesis window, and COLA-normalized overlap-add](images/spectral/stft-scaffold.svg) + +*The pump this object runs on — tap.nr~ is this scaffold with a per-bin downward expander in the middle.* + ## The knobs, one by one ### `threshold` — where quiet begins diff --git a/book/src/svf.md b/book/src/svf.md index 75a5227..50779a4 100644 --- a/book/src/svf.md +++ b/book/src/svf.md @@ -24,6 +24,10 @@ kernel become the sweep engine inside `tap.autowah~`. The notebook slams the cutoff across five octaves with a 90 Hz LFO under full-band noise; the output stays bounded, no oversampling tricks required. +![Signal-flow diagram of the TPT state-variable core: a summing node into two trapezoidal integrators with damping and low feedback, and the output mix that forms every response](images/svf/block-diagram.svg) + +*Two integrators in a zero-delay loop; every response — and the morph — is three multiplies downstream of the same two states.* + ## The knobs, one by one ### `type` — the discrete responses, the morph, and the EQ family diff --git a/book/src/tune.md b/book/src/tune.md index 44dea64..072ba98 100644 --- a/book/src/tune.md +++ b/book/src/tune.md @@ -23,6 +23,10 @@ package, the runtime maxtest, and two executed notebooks — `tune.ipynb` here and `pitchshift.ipynb` in the DspTap repo — that measured every claim below. +![Signal-flow diagram of tap.tune~: a per-hop brain of YIN, target mapper, glide, and ratio over a per-sample path through the input ring into the selectable resynthesis backend](images/tune/block-diagram.svg) + +*A per-hop brain over a per-sample corrector, with one seam where three resynthesis engines interchange.* + ## The knob that is the instrument: `speed` `speed` is the time constant, in milliseconds, of the glide onto the target diff --git a/book/src/vco.md b/book/src/vco.md index 5f3ddc3..45f308e 100644 --- a/book/src/vco.md +++ b/book/src/vco.md @@ -75,6 +75,10 @@ Two things about this are worth knowing so they don't surprise you: Single-channel, like every TapTools DSP object: wrap it in `mc.` for stacks, and keep reading, because the analog section was designed around exactly that. +![Signal-flow diagram of the VCO: pitch, FM, and the per-seed analog section sum into a master phase accumulator, which fans out to four waveform readings crossfaded by shape](images/vco/block-diagram.svg) + +*One phase, many readings — with polyBLEP correcting the edges and the analog section injecting in exactly two places.* + ## The knobs, one by one ### `frequency`, and gliding diff --git a/book/src/vocoder.md b/book/src/vocoder.md index 54d0e6f..8fcf510 100644 --- a/book/src/vocoder.md +++ b/book/src/vocoder.md @@ -36,6 +36,10 @@ backwards is the classic first-patch bug, and it sounds like it: a synth "speaking" your voice is right; your voice weakly filtered by a synth is backwards. +![Signal-flow diagram of the vocoder: the modulator through a 24-band filter bank into envelope followers, the carrier through an identical bank, per-band multipliers, and a summed gain stage](images/vocoder/block-diagram.svg) + +*Two identical banks meeting at 24 multipliers. Envelopes gate the carrier; modulator audio never reaches the output.* + ## The knobs, one by one ### `q` — intelligibility vs. smoothness