l-synth-diagram-wisdom
Authoring decision system for synth tutorial diagrams (plot register vs. patch register). Use PROACTIVELY when writing or editing any guide article (synth-kw, mm-deep, oxi-coral, etc.) that needs a di...
File Structure
l-synth-diagram-wisdom/
├── SKILL.md
└── references/
├── adsr-comparison.md
├── basic-signal-chain.md
├── fm-carrier-waveform-comparison.md
├── fm-sidebands.md
└── voltage-pitch.md
- references/adsr-comparison.md
- references/basic-signal-chain.md
- references/fm-carrier-waveform-comparison.md
- references/fm-sidebands.md
- references/voltage-pitch.md
Synth Diagram Authoring Wisdom
Every diagram in the modular-synth tutorial series lives in one of two registers. Picking the wrong register is the most common authoring mistake — it costs hours of rework. Decide register first, then pick a generator/composer, then wire it into the article.
This skill is the authoring spec future Claude sessions follow. The implementations it points at (the synth-svg sub-package for plot register, the patch composer engine for patch register) are the moving parts.
When to invoke
Load this skill PROACTIVELY whenever any of the following is true:
- Writing or editing a guide article (
synth-kw-*,mm-deep-*,oxi-coral-*, or any new series) that needs a diagram. - Designing a brand-new diagram type from scratch — even before you know which register fits.
- Reviewing an article PR that adds or changes diagrams. The pedagogy rubric at the bottom of this file is the review checklist.
- Adding a new generator to
sub-packages/synth-svg/— confirm it stays inside the shared axis vocabulary.
The two-register decision tree
What is the diagram trying to show?
│
├─ A curve / shape / signal over time-or-frequency
│ (waveform, envelope, filter response, voltage staircase,
│ spectrum, modulation depth, timing diagram)
│ → PLOT register
│ Use sub-packages/synth-svg generators.
│ Common x-y axes, oscilloscope-style dark theme.
│
└─ How modules connect — signal flow, cables, patches
(CV cable from LFO to VCA, audio chain VCO→VCF→VCA,
"wire this knob to this jack" instructions)
→ PATCH register
Use the patch composer (typed-TS — see #1782 decision; #1778 implementation).
Symbols come from the Patch & Tweak symbol set.
attribution: true is MANDATORY.
Quick rules — when in doubt, use this table:
| You want to show… | Register |
|---|---|
| A waveform shape (sine, saw, square) | plot |
| An ADSR envelope | plot |
| Filter cutoff / resonance response curve | plot |
| 1V/oct voltage-to-pitch relationship | plot |
| FM spectrum / sidebands | plot |
| Modulation depth visualization | plot |
| Sequencer timing / clock division | plot |
| Mixer channel levels over time | plot |
| ”Patch the LFO’s output into VCA’s CV input” | patch |
| Signal-flow chain across multiple modules | patch |
| ”Here is the full patch for this lesson” | patch |
If the diagram contains both a routing-graph and a waveform, split it into two diagrams. Never mix registers in one SVG.
Plot register reference
The plot register is fully implemented in sub-packages/synth-svg/. Every plot diagram is a TypeScript-generated SVG string, written to static/images/synth-svg/<name>.svg, referenced from MDX as /images/synth-svg/<name>.svg.
Existing generators
| Generator function | File | What it draws |
|---|---|---|
generateSingleWaveform | src/generators/waveform.ts | One waveform with cycles + label (sine, saw, square, triangle, pulse) |
generateWaveformComparison | src/generators/waveform.ts | Several waveforms side-by-side for visual comparison |
generateFrequencyResponse | src/generators/frequency-response.ts | One filter curve (LPF, HPF, BPF, Notch) with cutoff + resonance |
generateFilterComparison | src/generators/frequency-response.ts | All four filter types overlaid |
generateSlopeComparison | src/generators/frequency-response.ts | Filter slopes (12 dB/oct vs 24 dB/oct, etc.) |
generateAdsr | src/generators/adsr.ts | Single ADSR envelope with gate signal |
generateAdsrComparison | src/generators/adsr.ts | Multiple ADSR presets stacked for comparison |
generateResponseCurve | src/generators/response-curve.ts | Linear vs exponential curve |
generateResponseCurveComparison | src/generators/response-curve.ts | VCA linear/exponential side-by-side |
generateSignalTransform | src/generators/signal-transform.ts | Attenuverter, S&H, quantizer transforms |
generateVoltagePitch | src/generators/voltage-pitch.ts | 1V/oct line + piano keys overlay |
generateVoltagePitchStaircase | src/generators/voltage-pitch.ts | Stepped voltage-to-pitch staircase |
generateNoiseSpectrumComparison | src/generators/spectrum.ts | White, pink, red noise frequency spectra |
generateFmSynthesisSidebands | src/generators/spectrum.ts | FM carrier + sidebands spectrum |
generateDelayFeedback | src/generators/spectrum.ts | Delay feedback impulse train |
generateModulationDepth | src/generators/modulation-mix.ts | LFO modulation depth visualization |
generateMixerChannels | src/generators/modulation-mix.ts | Mixer channel level meters / sums |
generatePolyphonyVoices | src/generators/modulation-mix.ts | Multi-voice polyphony stack |
generateClockDividerMultiplier | src/generators/timing.ts | Clock divider / multiplier timing |
generateSequencerSteps | src/generators/timing.ts | Sequencer step gates / CV values |
generateSignalFlowChain | src/generators/timing.ts | Timing-aligned signal chain |
Multi-panel composite sub-pattern
Some lessons need to show two or three time-locked signals at once — a carrier next to its modulator next to the FM result, or a master VCO next to a free-running and a synced slave. These are multi-panel composites: 2–4 vertically stacked panels inside a single SVG, sharing the same X-axis.
When to reach for it:
- The reader needs to mentally drop a vertical line through the diagram and read corresponding states across signals. Examples: carrier / modulator / FM result; master / slave-free / slave-synced; PWM source / pulse output; unmodulated signal / control signal / modulated signal.
- The lesson hinges on a contrast that lands faster as “see how this row differs from that row” than as prose.
Recipe:
- Locked time axis — every panel uses the same
plotXandplotW. Compute them once and reuse. The reader’s vertical-line check only works if the X coordinates are identical. - Equal panel heights for direct comparison. Reuse
dimensions.tall(800×350) for 3-panel composites, or step up to a custom800×380if you need slightly more room. Avoid mixing tall and short panels — visual weight implies pedagogical weight. - Side-tag per panel — short label rendered at the left of each panel:
Carrier,Modulator,Result,VCO A,VCO B (free),VCO B (hard sync),Env In,PWM In. The tag belongs inside the SVG (it identifies the row); descriptive captions do NOT (those go in MDX body text — see “No captions inside SVG” in the review rubric). - Per-panel grid + Y-axis ticks — each panel calls
drawGridand emits its own+/0/−Y-axis tick labels via a small inlinedrawPanelhelper. The helper is local to your generator file; do not invent a new shared primitive for it until the same composite is used in 3+ generators (see the “Prototype before registering” pre-step of the Authoring loop). - Bipolar vs unipolar mixing is OK — a single composite can hold bipolar panels (audio, bipolar LFO) and a unipolar panel (envelope, gate-level CV) side by side. Use the unipolar variant of the panel layout for the latter (
0Vat bottom rail) — see “Semantic axis conventions” below.
Worked examples on main (all in static/images/synth-svg/, all referenced from src/mdx/guides/wpa-module-guide-ep1.mdx):
| Filename | Panels | What it shows |
|---|---|---|
wpa-2v2-fm-saw-linear.svg, wpa-2v2-fm-saw-tzfm.svg | 3 (Carrier / Modulator / Result) | Same carrier+modulator+depth under each FM mode; only the result panel differs. The two SVGs are an axis-locked pair. |
wpa-2v2-soft-sync.svg, wpa-2v2-hard-sync.svg | 3 (VCO A / VCO B free / VCO B synced) | Master sine + free-running slave + synced slave, with vertical trigger guides spanning all panels. |
wpa-2v2-env-in.svg | 3 (Modulator / Env In / Result) | Bipolar modulator + unipolar envelope CV + result with envelope-shaped FM depth — mixed-polarity composite. |
wpa-2v2-pwm.svg | 3 (Pulse at 50% / PWM In / Pulse PWM) | Reference pulse + control signal + modulated pulse. |
wpa-2v2-waveform-sub-octave-square.svg | 2 | Parent square at f locked to sub-square at f/2 on a shared time axis. |
MDX embedding pattern
Plot-register SVGs are embedded into MDX articles with a small consistent pattern (established in WPA EP.1):
- Heading level: use
###for the article subsection that owns the diagram. (####is fine for subsections that don’t carry a diagram. The visual-rhythm rule is: if it has a diagram, it deserves###.) - Embed via JSX
<img>, not Markdown— JSX lets you carryclassNamefor centering. - Always use
className="mx-auto block max-w-full"— centers on wide screens, shrinks to fit on mobile. Do not invent per-image widths or styles. alttext describes what the diagram shows. Since the SVG itself carries no caption text, thealtis the only programmatic description for screen readers and translation passes.
### スルーゼロリニア FM(TZFM)
<img
src="/images/synth-svg/wpa-2v2-fm-saw-tzfm.svg"
alt="TZFM:赤い区間でキャリアのノコギリ波が逆方向にスロープを反転させながら走り続ける"
className="mx-auto block max-w-full"
/>
TZFM だと、止まる代わりにキャリアが進行方向を反転させて走り続けます。…
Recipe: rendering FM-modulated carrier waveforms
When a lesson needs to show what an FM-modulated carrier actually looks like (not just the f vs V_mod transfer curve), synthesize the result panel by numerically integrating the instantaneous frequency:
- Math:
f_inst(t) = f_carrier + fm_depth × V_mod(t). For non-TZ linear FM, clampf_instto0from below — the oscillator dwells flat when frequency would go negative. For TZFM, leavef_instsigned — negative values cause phase to decrease, which is the “waveform plays backward” phenomenon. - Phase integration: accumulate
phase += f_inst × dtper sample. Sample the carrier shape from the runningphase:- Sawtooth in
[-1, +1]:2 × (phase − Math.floor(phase)) − 1 - Sine:
Math.sin(2 × Math.PI × phase)
- Sawtooth in
const samples = 1800;
const dt = 1 / samples;
const fCarrier = 4; // baseline carrier cycles across plot width
const fmDepth = 6; // peak frequency deviation (cycles)
const modCycles = 1; // modulator cycles across plot width
let phase = 0;
const pts: Point[] = [];
for (let i = 0; i <= samples; i++) {
const t = i / samples;
const modVal = Math.sin(2 * Math.PI * modCycles * t);
let fInst = fCarrier + fmDepth * modVal;
if (mode === 'linear' && fInst < 0) fInst = 0; // non-TZ halts at 0 Hz
const frac = phase - Math.floor(phase);
const sawVal = 2 * frac - 1;
pts.push({ x: plotX + t * plotW, y: centerY - sawVal * amp });
if (i < samples) phase += fInst * dt;
}
Carrier shape choice — sawtooth, not sine, when the lesson is about phase reversal. TZFM, hard sync, and soft sync all hinge on “the waveform direction inverts here.” Sawtooth makes that visible via slope: a forward sawtooth ramps up; a reversed sawtooth ramps down. Sine carries the same physical reversal but to the eye it just looks like a sine that retraces itself — much less obvious. Use sine only when the lesson is about FM index / spectrum / Bessel sidebands and direction is incidental.
This recipe is currently realized in wpa-2v2-fm-saw-linear.svg and wpa-2v2-fm-saw-tzfm.svg, but the canonical generator function (e.g. generateFmCarrierWaveform in src/generators/fm.ts) and its registration in src/scripts/generate-files.ts (or the matching topic-specific generate script) still need to land per step 3 of the Authoring loop. See references/fm-carrier-waveform-comparison.md for the full recipe.
Read sub-packages/synth-svg/README.md for the runtime / build commands. Two scripts matter, and confusing them is the most common authoring mistake:
pnpm generate— rebuildspreview.html(the visual-verification target). Does not write SVG files intostatic/images/synth-svg/.pnpm generate:files— writes the canonical SVGs registered insrc/scripts/generate-files.tstostatic/images/synth-svg/. Topic-specific scripts (src/generate-spectrum-svgs.ts,src/generate-modulation-svgs.ts,src/generate-timing-svgs.ts,src/generate-svg-files.ts) emit the remaining SVGs and are invoked directly viatsx.
If your new diagram needs to land as a file on disk (it does, if MDX references it), you must register it in one of those generator scripts and run that script. pnpm generate alone refreshes only the preview and is not enough.
Theme tokens cheat sheet
All plot diagrams must import tokens from sub-packages/synth-svg/src/core/theme.ts. Never hard-code a color, font, or size. Never invent new ones; if you need a new token, add it there and reuse.
Colors (from theme.ts → colors):
| Token | Value | Use |
|---|---|---|
colors.background | #0d1117 | Diagram background (oscilloscope dark) |
colors.panelBg | #161b22 | Inner panel for multi-panel layouts |
colors.primary | #00d4ff | Primary trace (signal under discussion) |
colors.secondary | #ff6b6b | Second trace (comparison) |
colors.tertiary | #4ecdc4 | Third trace |
colors.accent | #ffd93d | Fourth trace / gate |
colors.purple | #a78bfa | Fifth trace |
colors.grid | rgba(255,255,255,0.08) | Minor grid lines |
colors.gridMajor | rgba(255,255,255,0.15) | Major grid lines |
colors.axis | rgba(255,255,255,0.3) | Axis lines |
colors.axisLabel | rgba(255,255,255,0.5) | Axis tick labels |
colors.textPrimary | rgba(255,255,255,0.9) | Diagram title |
colors.textSecondary | rgba(255,255,255,0.6) | Body labels |
colors.textMuted | rgba(255,255,255,0.4) | Small captions, units |
colors.gate | #ffd93d | Gate / trigger signal |
colors.centerLine | rgba(255,255,255,0.2) | Bipolar zero line |
Convenience array: waveColors = [primary, secondary, tertiary, accent, purple] — index into this when generating N-trace plots.
Fonts (from theme.ts → fonts):
| Token | Value |
|---|---|
fonts.mono | 'SF Mono', 'Fira Code', 'Cascadia Code', 'JetBrains Mono', monospace |
fonts.label | 'SF Mono', 'Fira Code', 'Cascadia Code', monospace |
Everything is monospace — this is intentional, it reinforces the oscilloscope register.
Font sizes (theme.ts → fontSizes): tiny: 9, small: 10, normal: 11, medium: 12, large: 14, title: 16.
Dimensions (theme.ts → dimensions): wide: 800×250, standard: 800×300, tall: 800×350, panel: 240×200, threePanel: 800×280. Pick the closest preset — don’t invent custom widths.
Padding (theme.ts → padding): standard, compact, labeled — pick one based on whether axes carry labels.
Common axis vocabulary
This is the structural answer to the user’s original “common axis xy” ask. Every plot-register diagram reads from the same coordinate-system primitives. A new diagram MUST reuse these — do not redefine axis labeling, grid styling, or font tokens.
| Primitive | Source | What it gives you |
|---|---|---|
grid(svg, plotX, plotY, plotW, plotH, { xDivisions, yDivisions, showCenterLine }) | src/core/primitives/index.ts (canonical) | Dashed minor grid; optional bipolar zero line |
axis(svg, plotX, plotY, plotW, plotH, { xLabel, yLabel }) | src/core/primitives/index.ts (canonical) | X and Y axis lines + axis labels |
drawGrid(...) / drawAxes(...) | src/core/svg-builder.ts (lower-level) | Identical behavior; grid/axis are thin wrappers around these. Prefer the primitives barrel for new generators. |
drawTitle(svg, x, y, title) | src/core/svg-builder.ts | Centered diagram title in colors.textPrimary |
padding.standard / compact / labeled | src/core/theme.ts | Plot inset; pick labeled when axes carry x/y labels |
dimensions.wide / standard / tall / panel / threePanel | src/core/theme.ts | Canvas size |
colors.grid + colors.gridMajor + colors.axis + colors.axisLabel | src/core/theme.ts | The visual language of the axis itself |
Semantic axis conventions
| Axis use | x-scale | y-scale | Examples |
|---|---|---|---|
| Time-domain signal | linear time | linear amplitude (often bipolar) | waveform, ADSR, modulation-depth, sequencer-steps |
| Frequency-domain signal | log frequency (20 Hz → 20 kHz) | linear amplitude or dB | filter response, noise spectrum, FM sidebands |
| Control voltage | linear time | linear voltage (V) | voltage-pitch staircase, modulation, attenuverter |
| Bipolar signals | linear time | showCenterLine: true | attenuverter outputs, bipolar LFO |
| Unipolar control voltage (envelopes, Env In, gate-level CV) | linear time | linear voltage, 0V at bottom rail | env-adsr, Env In panel of wpa-2v2-env-in.svg |
- Unipolar Y-axis variant: when a signal is unipolar (EG output, gate, 0–5V CV), put
0Von the BOTTOM rail of the panel and label the top as+5Vor similar. Do not put0Von the centerline — it makes the bottom half read as “available negative CV space,” which is misleading for EG-style signals (modular EGs never swing negative). PassshowCenterLine: falseand draw an emphasized baseline line atplotY + panelH. The worked example is the middle (Env In) panel ofwpa-2v2-env-in.svg, which sits between two bipolar panels in the same composite.
Reuse, do not invent. If a new diagram needs a new axis convention, that’s a sign it belongs in a new shared helper inside src/core/, not inline in one generator.
Cross-panel decoration primitives
For multi-panel composites, two reusable visual primitives mark structure across the time axis. Treat them as part of the shared vocabulary — not new generators, just consistent conventions.
Trigger guide lines — vertical dashed lines spanning all panels at event times (sync triggers, clock edges, gate events). They make the timing relationship between a source signal and downstream effects readable end-to-end:
- Color:
colors.gate(yellow#ffd93d) at ~0.45opacity. - Stroke:
strokeWidth: 1,strokeDasharray: '4,4'. - Drawn BEFORE the panel signals so signal traces overlay on top — the guides recede visually but the eye can still pick them up.
- Worked examples:
wpa-2v2-soft-sync.svg,wpa-2v2-hard-sync.svg(positive-zero-crossings of VCO A).
Time-window highlight rect — a subtle filled rect spanning vertically across all panels, marking a special time interval (a danger zone, a swing range, a region the reader’s attention should land on):
- Both colors are existing theme tokens at very low opacity — the rgba literals are documentation shorthand. In generator code, derive the fill from the matching theme color rather than re-typing the hex; the rule “all colors come from
theme.ts → colors” in the review rubric still applies. - Faint red —
colors.secondary(#ff6b6b) at ~0.07opacity (rgba(255, 107, 107, 0.07)). Use for danger / breakdown / negative-f semantics — “in this window, something exceptional happens to the signal” (e.g., the segment where instantaneous frequency would dip below zero in TZFM vs linear FM diagrams). - Faint purple —
colors.purple(#a78bfa) at ~0.08opacity (rgba(167, 139, 250, 0.08)). Use for modulator swing range / region of interest semantics — “consider what happens within this range” (e.g., the ±1V modulator swing band on a transfer curve). - Drawn UNDER the grid and signal traces so it sits as a background tint, not a foreground element.
- Worked examples:
wpa-2v2-fm-saw-linear.svg,wpa-2v2-fm-saw-tzfm.svg(red, below-zero window);wpa-2v2-fm-tzfm-transfer.svg(purple, ±1V swing band).
Patch register reference
Status: engine shipped in Waves 1–3 of the Synth Diagram System epic (#1774). The typed-TS composer was chosen over D2 (see #1782 spike — D2 rejected because
image-shape nodes cannot have child sub-anchors, making port-specific cable routing impossible). The spike SVGs remain undersub-packages/synth-svg/src/spike/d2/as historical evidence.
Real call shape (as shipped; re-exported from src/index.ts):
import { patch, sym, cable, label, at, near, from, to } from '@takazudo-modular/synth-svg';
const svg = patch({
size: [720, 340],
symbols: [
sym('vco-sine', at(40, 90)),
sym('vcf-lowpass', at(200, 86)),
sym('vca', at(400, 86)),
sym('env-adsr', at(400, 220)),
],
cables: [
cable(from('vco-sine.out'), to('vcf-lowpass.in'), { kind: 'audio', route: 'straight' }),
cable(from('vcf-lowpass.out'), to('vca.in'), { kind: 'audio', route: 'straight' }),
cable(from('env-adsr.out'), to('vca.cv'), { kind: 'cv', route: 'orthogonal' }),
],
labels: [
label('Audio Out', near('vca', 'right')),
],
attribution: true,
});
Key helpers: sym(id, position), cable(from, to, opts), label(text, position), at(x, y), near(symbolId, side), from(anchorRef), to(anchorRef). Anchor refs use symbolId.portName syntax (e.g., vca.cv, vcf-lowpass.in). Multi-instance: second occurrence of the same symbol id uses id#N (e.g., vco-sine#1.out). Also available: row(), col(), stack() layout helpers.
Patch & Tweak symbol vocabulary
The Patch & Tweak symbol set is the visual library. 104 SVG symbols live under sub-packages/synth-svg/assets/pt-symbols/, organised into four directory categories that map directly to PtSymbolCategory in the type system:
| Category directory | Symbol examples | Role in signal flow |
|---|---|---|
audio-sources | vco-sine, vco-saw, vco-square, vco-triangle, noise-white | Always upstream; generate audio |
audio-modifiers | vcf-lowpass, vcf-highpass, vcf-bandpass, vca, mixer, wavefolder | Shape, filter, or amplify audio |
cv-sources | env-adsr, env-ar, lfo-sine, lfo-square, lfo-triangle | Generate control voltages (trigger/gate-driven or free-running) |
cv-modifiers | attenuverter, slew, quantizer, sample-hold, switch | Process or route CV signals |
The full set of ids is the filenames (without .svg) in each directory. Use ptSymbols registry (src/pt-symbols/index.ts) to look up any id at runtime. Common ids used in reference recipes: vco-sine, vcf-lowpass, vca, env-adsr, lfo-sine, mixer.
Cable conventions are part of the symbol contract: audio cables and CV cables must be visually distinct — different stroke, color, or both. The exact distinction is fixed by the symbol pack; do not override it on a per-diagram basis.
Attribution is MANDATORY
Every patch-register diagram must set attribution: true (or the engine-equivalent flag) so the CC BY-ND 4.0 footer renders inline with the SVG. Skipping attribution violates the symbol-pack license and is a release-blocker.
- The attribution string is fixed by the license — do not paraphrase.
- The CC BY-ND license forbids recoloring or restyling the symbols. Use them as shipped.
- Do not strip the footer with CSS or crop it out.
LICENSE.txtfor the symbol pack lives atsub-packages/synth-svg/assets/pt-symbols/LICENSE.txt(withATTRIBUTION.mdandSOURCE_MANIFEST.jsonalongside).
License & attribution (full rules)
- Plot register — output is our own code, MIT-licensed alongside the rest of
sub-packages/synth-svg/. No special attribution required. - Patch register — uses the Patch & Tweak (P&T) symbol set, licensed CC BY-ND 4.0.
BY— attribution is mandatory on every diagram that uses these symbols.ND— no derivative works. We cannot recolor, restyle, restroke, or otherwise modify the symbols. Use them as-shipped.- Attribution text is exact and fixed by the license — do not paraphrase or shorten.
LICENSE.txtlives atsub-packages/synth-svg/assets/pt-symbols/LICENSE.txt.
If a future authoring decision pressures you to “just remove the attribution to make it look cleaner” — refuse, and link the article reviewer back to this section.
Authoring loop
For every new diagram, follow these steps in order. Step 0 is a pre-step that runs alongside Step 1 — design exploration before committing to a canonical generator.
-
Prototype before registering. Before adding a function to
sub-packages/synth-svg/src/generators/, write a standalone TS script under__inbox/<topic>/generate.ts(gitignored) that imports the relevant primitives fromsub-packages/synth-svg/src/...and writes SVGs to$DROPBOX_SCREENSHOTS_DIR(or any preview-friendly path). Iterate on shape, axes, depth, panel layout with the human reviewer in the loop via/sspreview. Once the design is approved, proceed to Step 2 (canonicalize the generator) and Step 3 (register the output filename). This split — prototype in__inbox/, ship insub-packages/synth-svg/— keeps the canonical generators clean and avoids speculative-design commits that the article later rejects. The WPA 2V2 diagrams (wpa-2v2-fm-*.svg,wpa-2v2-soft-sync.svg,wpa-2v2-hard-sync.svg,wpa-2v2-env-in.svg,wpa-2v2-pwm.svg) were authored this way — see PR #1787 for the merged result andreferences/fm-carrier-waveform-comparison.mdfor the surviving recipe. -
Decide register. Walk the decision tree at the top of this file. If you can’t decide, the diagram is probably trying to show two things — split it.
-
Pick or add a generator/composer call. For plot register, scan the generator table above. If nothing fits, add a new function to the relevant
src/generators/<topic>.ts(reusing axis/grid primitives — never inline new ones). For patch register, use the typed-TS composer (shipped in Waves 1–3 of #1774 — see the call shape in “Patch register reference” above). -
Register the SVG output filename in the matching generator script under
sub-packages/synth-svg/src/:src/scripts/generate-files.ts— the central script (waveform / filter / ADSR / response-curve / signal-transform / voltage-pitch).src/generate-spectrum-svgs.ts— noise spectrum, FM sidebands, delay feedback.src/generate-modulation-svgs.ts— modulation-depth, mixer, polyphony.src/generate-timing-svgs.ts— clock dividers, sequencer steps, signal-flow chain.src/generate-svg-files.ts— extended canonical set (alt ADSR comparison presets, etc.).
Pick the script whose topic matches; do not invent a new one unless adding a new topic area. Filenames are kebab-case and descriptive (match the existing convention —
fm-synthesis-sidebands.svg,adsr-comparison-piano-pad.svg, etc.). -
Reference from MDX as
/images/synth-svg/<name>.svg. Patch diagrams reference their own engine output path (per #1782). -
Regenerate AND visually verify:
- Run the right generator script —
pnpm generate:filesfor the central set, ortsx src/generate-<topic>-svgs.tsfor the topic-specific ones.pnpm generatealone only rebuildspreview.html— it does NOT write the SVG files MDX references. - Run
pnpm generate(orpnpm devfor watch mode) to refreshpreview.html, then open it to visually verify: axis labels readable, colors match theme tokens, no clipping, attribution renders for patch diagrams.
- Run the right generator script —
If any step fails, the diagram is not done — do not merge.
Diagram pedagogy review rubric
Run this checklist before declaring a new diagram (or a PR that adds/edits diagrams) done. Inline here so review is one-stop.
Register
- Is the register choice correct per the decision tree? (curve → plot, routing → patch)
- Are plot and patch registers kept in separate SVGs (not mixed)?
Pedagogy — does the diagram match the lesson?
- Does the diagram illustrate exactly the concept the surrounding paragraphs explain — no more, no less?
- Is every visual element on screen carrying weight? (Remove decoration that doesn’t teach.)
- For patch diagrams: does cable routing match what the lesson actually says to patch?
- For patch diagrams: are CV cables vs audio cables visually distinct?
- For comparison plots (e.g., ADSR presets, filter types): is the contrast the lesson is making actually visible at a glance?
Axis vocabulary (plot register)
- Uses
grid/axisfromsrc/core/primitives/index.ts(or the lower-leveldrawGrid/drawAxesfromsrc/core/svg-builder.ts) — no inline grid/axis code. Prefer the primitives barrel for new generators. - x-scale is correct for the domain (linear time for time-domain, log frequency for spectra).
- y-scale uses
showCenterLine: truefor bipolar signals. - y-scale uses
showCenterLine: falsewith an emphasized bottom baseline for unipolar signals (envelopes, gate-level CV) —0Vsits at the bottom rail, NOT on the centerline. - All colors come from
theme.ts → colors— no hard-coded hex values. - All fonts come from
theme.ts → fonts— no inline font-family declarations. - No descriptive caption text inside the SVG. The only text inside a plot SVG is: axis labels, axis tick values (
+5V,0V, etc.), a single short panel side-tag (Carrier,Env In), and optionally a Y-axis reference marker (e.g.f₀). Any longer explanation belongs in MDX body text immediately above or below the image — keeps SVGs reusable across articles and translations and lets prose carry the lesson-specific narrative.
Generator wiring (plot register)
- Generator is exported from its
src/generators/<topic>.tsfile. - An entry exists in the correct generator script (
src/scripts/generate-files.ts,src/generate-spectrum-svgs.ts,src/generate-modulation-svgs.ts,src/generate-timing-svgs.ts, orsrc/generate-svg-files.ts) producing the SVG atstatic/images/synth-svg/<name>.svg. - The script has been run (not just edited) — the SVG file exists on disk.
- Filename is kebab-case and matches the existing convention for that topic area.
- MDX references the SVG as
/images/synth-svg/<name>.svg.
Patch register
-
attribution: true(or engine equivalent) is set — CC BY-ND footer is rendering. - No symbol has been recolored, restroked, or otherwise modified.
- Cable categories (audio vs CV) follow the symbol pack’s conventions, not ad-hoc choices.
Output sanity
- The matching
pnpm generate:files/ topic-specifictsxscript insub-packages/synth-svg/completes without errors and writes the new.svgfile. -
pnpm generateinsub-packages/synth-svg/completes without errors and refreshespreview.html. - The diagram renders correctly in
preview.html. - No clipping at the SVG viewBox edges; labels are not overflowing.
If any box is unchecked, the diagram is not done.
Seed reference recipes
Worked examples live under references/. Each follows the same shape: Pedagogical intent / Visual recipe / Generator call / Source articles. Treat them as the model for any new reference recipe.
references/fm-sidebands.md— FM synthesis spectrum (sidebands around a carrier)references/voltage-pitch.md— 1V/oct staircase (CV → pitch mapping)references/adsr-comparison.md— ADSR preset comparison (piano vs pad)references/basic-signal-chain.md— basic patch-register signal chainreferences/fm-carrier-waveform-comparison.md— TZFM vs linear FM 3-panel composite (time-domain phase-reversal comparison)
Key source files
sub-packages/synth-svg/README.md— generator runtime + commandssub-packages/synth-svg/src/core/theme.ts— single source of truth for colors, fonts, sizes, dimensions, paddingsub-packages/synth-svg/src/core/svg-builder.ts—SvgBuilderclass +drawGrid/drawAxes/drawTitleaxis primitivessub-packages/synth-svg/src/generators/— one file per topic; each exports one or moregenerate*functionssub-packages/synth-svg/src/scripts/generate-files.ts— central script: register most SVG filenames here so they ship tostatic/images/synth-svg/sub-packages/synth-svg/src/generate-spectrum-svgs.ts— spectrum-topic SVG filenames (FM sidebands, noise spectra, delay feedback)sub-packages/synth-svg/src/generate-modulation-svgs.ts— modulation-topic SVG filenames (modulation-depth, mixer, polyphony)sub-packages/synth-svg/src/generate-timing-svgs.ts— timing-topic SVG filenames (clock-divider, sequencer-steps, signal-flow-chain)sub-packages/synth-svg/src/generate-svg-files.ts— extended canonical set (alt ADSR-comparison presets, etc.)sub-packages/synth-svg/preview.html— visual verification target