

A guitar pedal: a thick octave-fuzz feeding a dynamics-driven envelope band-pass ("Q-Tron") filter. Designed and voiced entirely in ngspice, validated against real-part models, ready for KiCad.
- Size
- 175 × 120 mm
- Copper layers
- 2
- Thickness
- 1.6 mm
- Parts
- 105
- Nets
- 70
- Pads
- 310
- Tracks, vias
- 941, 29
- Routed
- Yes
- KiCad
- KiCad 10.0 (file 20260206)
- Created
- 25 Jun 2026
- Updated
- 26 Jun 2026
Overview
Duck is an open-source printed circuit board by dfd, from the dfd/angry_squealing_duck repository on GitHub. It is a 2-layer board of 175 × 120 mm with 105 parts on 70 nets, routed with 941 tracks and 29 vias.
It is built around the LM13700M. Its parts include 52 resistors, 25 capacitors, 14 diodes, 7 ICs and 3 connectors.
Main components
1README
README.mdAngry Squealing Duck
A guitar pedal: a thick octave-fuzz feeding a dynamics-driven envelope band-pass ("Q-Tron") filter. Designed and voiced entirely in ngspice, validated against real-part models, ready for KiCad.
Controls (4 knobs + 2 footswitches):
- Volume — output level
- Anger — fuzz amount / sustain (Big-Muff-style)
- Squeal — up-octave level: CCW = none, ~noon = "ring on top", CW = octave-dominant blast
- Quack — envelope band-pass intensity (ganged sweep depth + resonance Q)
- Master true-bypass footswitch, and a dedicated Squeal (octave) footswitch (kick the octave in/out, Hoof-Reaper style)
All knobs CCW = that effect off.
Signal architecture
IN ─► Input buffer ─► SPLIT ─┬─ AUDIO: Fuzz(Anger) ─┬─► BP envelope filter(Quack) ──┐
│ └─► Octave-up(Squeal) ─► fixed HP┤─►[Volume]─► OUT
└─ CONTROL: Envelope follower (raw) ─► Iabc (cutoff) ─────┘Key voicing decisions (each one earned the hard way — see git log / memory):
- Octave AFTER the fuzz (Octavia / Fender-Blender pattern): rectifying the distorted signal yields a prominent ring. Octave before the fuzz buried it ~30 dB under the fuzz's odd harmonics.
- Body and octave on separate filter paths ("hybrid" route): the fuzz body goes through the band-pass envelope filter (it quacks); the octave goes through a fixed high-pass (stays clear/bright instead of being swept under). A single shared filter ("mixed") muffled the octave.
- Envelope tapped from the RAW split (pre-fuzz) so the filter tracks picking dynamics — fuzz compresses dynamics away.
Design choices
- Single 9 V supply, op-amp-buffered 4.5 V virtual ground (
vref) — a passive divider sags under the high-gain fuzz. Every stage input is AC-coupled. - Fuzz = 3-stage cascaded Big-Muff-style soft-clipper with asymmetric stages for
warmth (
fuzz_muff.cir, default). A single-stage hard clipper (fuzz.cir, DOD-250 family) is kept selectable via themuffparam. - Octave = op-amp precision full-wave rectifier (
fwr.cir), no transformer.octave_gen.cirproduces octave-only; Squeal level is quadratic so noon = ring, full = blast (goct=8, ~+9 dB octave-over-body, calibrated on real DI clips). - Envelope follower = precision rectifier + 2-pole averaging (not peak-detect, which hunted in a ~12 Hz limit cycle that re-triggered the filter on held notes).
- Filter = LM13700 OTA state-variable filter (
vcf.cirbehavioral /vcf_real.cirreal). Has LP/BP/HP/mixed taps (Q-Tron-style mode); the hybrid route uses BP for the body. Quack maps to a linear Q (0.7→3.5) and an envelope-swept cutoff (gm = Iabc). gm control is ground-referenced to avoid a virtual-ground latch. - Behavioral-ideal first to lock topology/voicing, then real part models for build
accuracy (
opamp_real.sub= TL072,lm13700.sub= OTA) — validated: voicing essentially unchanged on real silicon. Seememory/real-parts-and-build-notes.
Status
Design, voicing, real-part validation, and a routed KiCad PCB are complete.
| done | |
|---|---|
| ✅ | All blocks designed + integrated (input buffer → fuzz → octave → filter → volume) |
| ✅ | Voiced by ear on real guitar (MOTU M2), grid of Squeal×Anger×Quack rendered |
| ✅ | Real part models (TL072 + LM13700) — chain converges, voicing holds |
| ✅ | All-real buildable netlist (pedal_build.cir, no B-sources) — re-validated |
| ✅ | Quack sweep restored: V-to-I gain ×73 → body cutoff ~280 Hz→2.1 kHz, auditioned-good |
| ✅ | KiCad PCB: placed, auto-routed, gerbers exported |
| ✅ | Tier-1 de-risk audit (kicad/AUDIT.md): caught + fixed 10 reversed diodes; added per-IC decoupling + Schottky protection + center-neg polarity; DRC 0 unconnected / 0 violations |
Quack note: the real envelope is tiny (~0.04 V peak), so the V-to-I needs high gain
(Rdi/Rdj = 1.5k, Rdf/Rdk = 110k → ×73) for the filter to sweep. Quack is most
audible at lower Squeal — at full Squeal the octave sits on top by design.
Audit caught a board-killer: duck.py (hand-translated) had 10/14 diodes reversed
(fuzz clippers + both rectifiers) — ERC + sim pass anyway since the SPICE is a separate,
correct source. Fixed to match SPICE; see kicad/AUDIT.md. Not yet hardware-validated:
real noise floor + the LM13700 filter's behavior (the /50 attenuation hack, Iabc ceiling) —
confirm by breadboarding the filter + fuzz before fab (kicad/breadboard/).
Layout
spice/lib/— models:opamp.sub(ideal),opamp_real.sub(TL072),lm13700.sub(OTA),diode.mod(1N4148),supply.cir(buffered vref)spice/blocks/— one.subcktper block:input_buffer,fuzz,fuzz_muff,fwr,octave_rect/octave_gen,hpf,gmint+vcf/vcf_real,envfollow,duck(mixed)/duck_split(hybrid/parallel),pedalspice/tests/— per-block test benchessim/— Python harness:run.py,render.py,octave_balance.py,fft_report.py,spicelib.py,extract_sample.pyaudio/in/— committed DI samples;audio/out/— rendered WAVs (git-ignored)raw_wavs/— large source recordings (git-ignored)kicad/— the board, generated programmatically:duck.py(SKiDL netlist generator) →duck.net(ERC-clean) +BOM.mdbuild_pcb.py(pcbnew: parse netlist → place → board) ;finish_pcb.py(values + GND pour)duck.kicad_pcb(routed, DRC-clean) ;gerbers/+duck.drl+duck-pos.csv(fab-ready)duck_3d_top.png/duck_3d_persp.png(3D renders; or open the.kicad_pcbin pcbnew →Alt+3)AUDIT.md(netlist-translation audit + gap fixes) ;BOM.md;COST.md(build-cost estimate)pedal_build.cirinspice/blocks/is the all-real buildable schematic-of-record
kicad/breadboard/— prototype-first docs (build it on a breadboard before the PCB):BUILD_GUIDE.md(staged bring-up) +CONNECTIONS.md(per-stage wiring tables, net = row)gen_blocks.py/make_tables.py(generators) ;0*.pngdrawn schematics (simple stages)
audio/out/build_grid/+build_grid_orig/— full real-parts grids (Squeal×Anger×Quack ∈ {0,0.5,1})
KiCad / PCB workflow
The board is generated entirely by script (no GUI), bridging two Pythons because
pcbnew is built for system Python 3.14 while the sim harness uses uv's 3.13:
# 1. netlist (uv / 3.13)
KICAD8_SYMBOL_DIR=/usr/share/kicad/symbols uv run kicad/duck.py # -> duck.net (+ ERC)
# 2. place + board (system python 3.14, needs pcbnew)
/usr/bin/python3 kicad/build_pcb.py # -> duck.kicad_pcb
# 3. route: export DSN, run freerouting (needs a display, e.g. :0), import SES
/usr/bin/python3 -c "import pcbnew,sys; b=pcbnew.LoadBoard('kicad/duck.kicad_pcb'); pcbnew.ExportSpecctraDSN(b,'/tmp/duck.dsn')"
DISPLAY=:0 java -jar freerouting.jar -de /tmp/duck.dsn -do /tmp/duck.ses -mp 14
/usr/bin/python3 -c "import pcbnew; b=pcbnew.LoadBoard('kicad/duck.kicad_pcb'); pcbnew.ImportSpecctraSES(b,'/tmp/duck.ses'); pcbnew.SaveBoard('kicad/duck.kicad_pcb',b)"
# 4. finishing pass (snap values + GND pour) + checks + fab outputs
/usr/bin/python3 kicad/finish_pcb.py
kicad-cli pcb drc kicad/duck.kicad_pcb
kicad-cli pcb export gerbers --output kicad/gerbers/ kicad/duck.kicad_pcbBoard: 2-layer, 175×120 mm (1590DD-class), 6× TL074 + 1× LM13700 + 14× 1N4148,
4 knobs (Volume / Anger / Squeal / Quack-dual-gang). No graphical schematic
(eeschema has no scripting API) — pedal_build.cir + duck.py are the design-of-record.
Prototype it first (breadboard)
kicad/breadboard/ holds prototype-first docs. Build + test the circuit on solderless
breadboards one stage at a time before committing to the PCB — see BUILD_GUIDE.md for the
staged bring-up order (supply → input → fuzz → octave → envelope → V-to-I → filter → output)
and CONNECTIONS.md for per-stage wiring tables (each net = one breadboard row; IC pins
annotated). Each stage uses its own op-amp IC (not the PCB's shared packing) so it tests
standalone. Regenerate with gen_blocks.py + make_tables.py. (SKiDL's schematic auto-router
only handles the simple stages, so the dense ones are table-only — better for breadboarding anyway.)
Workflow
Run a block test bench:
uv run sim/run.py spice/tests/tb_input_buffer.cirRender guitar through the pedal (→ audio/out/):
# the LOCKED, buildable design (= the PCB): real parts, no B-sources, V-to-I gain x73
uv run sim/render.py [wav] --build --squeal 1 --anger 0.8 --quack 0.6 [--subdir name]
# the behavioral routes (faster iteration / experiments):
uv run sim/render.py [wav] --route hybrid --squeal 1 --anger 0.8 --quack 0.6 [--real]--build=pedal_build.cir, the all-real buildable pedal that matches the PCB (use this).--real= TL072 + LM13700 models on the behavioral chain ·--route= mixed|hybrid|parallel- Scratch files are unique per render, so render a grid in parallel on many cores:
... | xargs -P 24 -I A sh -c 'uv run sim/render.py A'(the committedaudio/out/build_grid/+build_grid_orig/are the--buildgrids of Squeal×Anger×Quack ∈ {0, 0.5, 1.0} on both DI samples).
Measure octave-vs-body level on real clips (don't trust single-note benches — they over-read the octave ~6 dB):
uv run sim/octave_balance.py --wav audio/in/di_sample.wav --squeal 1.0 --goct 8Parts
105| Qty | Value | Footprint | References |
|---|---|---|---|
| 16 | 10k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R14, R15, R16, R18, R21, R22, R23, R25, R34, R35, R36, R37, R38, R39, R40, R48 |
| 13 | 1N4148 | D_DO-35_SOD27_P2.54mm_Vertical_AnodeUp | D2, D3, D4, D5, D6, D7, D8, D9, D10, D11, D12, D13, D14 |
| 11 | 100n | C_Rect_L7.0mm_W2.5mm_P5.00mm | C3, C4, C6, C8, C19, C20, C21, C22, C23, C24, C25 |
| 6 | 100k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R1, R2, R6, R9, R27, R28 |
| 6 | TL074 | DIP-14_W7.62mm | U1, U2, U3, U4, U5, U6 |
| 5 | 22k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R29, R49, R50, R51, R52 |
| 4 | 1M | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R3, R4, R7, R10 |
| 3 | 1.5n | C_Rect_L7.0mm_W2.5mm_P5.00mm | C5, C7, C9 |
| 3 | 100k | Potentiometer_Alps_RK097_Single_Horizontal | RV1, RV2, RV3 |
| 2 | 1.5k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R30, R32 |
| 2 | 10n | C_Rect_L7.0mm_W2.5mm_P5.00mm | C16, C17 |
| 2 | 110k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R31, R33 |
| 2 | 12k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R8, R11 |
| 2 | 15k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R19, R20 |
| 2 | 1k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R44, R46 |
| 2 | 2.2k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R5, R13 |
| 2 | 220n | CP_Radial_D5.0mm_P2.50mm | C11, C12 |
| 2 | 4.7k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R17, R24 |
| 2 | 47k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R43, R45 |
| 2 | 68k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R12, R47 |
| 2 | AudioJack2 | Jack_6.35mm_Neutrik_NJ3FD-V_Vertical | J1, J2 |
| 1 | 100k | Potentiometer_Alps_RK097_Dual_Horizontal | RV4 |
| 1 | 100u | CP_Radial_D5.0mm_P2.50mm | C1 |
| 1 | 12n | C_Rect_L7.0mm_W2.5mm_P5.00mm | C14 |
| 1 | 1N5817 | D_DO-41_SOD81_P10.16mm_Horizontal | D1 |
| 1 | 1u | C_Rect_L7.0mm_W2.5mm_P5.00mm | C15 |
| 1 | 22n | C_Rect_L7.0mm_W2.5mm_P5.00mm | C18 |
| 1 | 27n | C_Rect_L7.0mm_W2.5mm_P5.00mm | C10 |
| 1 | 3.3k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R26 |
| 1 | 39k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R42 |
| 1 | 47n | C_Rect_L7.0mm_W2.5mm_P5.00mm | C13 |
| 1 | 47u | CP_Radial_D5.0mm_P2.50mm | C2 |
| 1 | 6.8k | R_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_Vertical | R41 |
| 1 | Barrel_Jack | BarrelJack_Horizontal | J3 |
| 1 | LM13700 | DIP-16_W7.62mm | U7 |
Questions
How big is the Duck PCB?
It measures 175 × 120 mm and has 2 copper layers, 1.6 mm thick.
What parts does the Duck use?
105 parts, built around the LM13700M. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.
Where can I download the Duck KiCad files?
From the dfd/angry_squealing_duck repository on GitHub, at the commit this page was made from: the board is kicad/duck.kicad_pcb and its schematic OUT: Pin U1/6/-,-,p7514962063015773255,p6/INPUT, Pin U1/7/,p3768724659903456224,p7/OUTPUT/08_output.kicad_sch. PCBKit opens the board in the browser, with nothing to install.
Can I use the Duck design in my own project?
Its repository states no licence, so its author keeps all rights: ask dfd before reusing the design.
Similar boards
- 2 layers37 × 113.9 mm

- 2 layers27.9 × 101.6 mm

MFOS Eight Stage Phase Shifter - Main
mzuelch/CATs-Eurosynth
Analog Synth Eurorack Modules
105 parts166MIT - 2 layers65 × 100 mm

- 2 layers29.5 × 100 mm

BYOM VCF main
clacktronics/EuroClack_BYOM_Modules
In this repository is all the modules in the book for Build Your Own Modular the Clacktronics ring binder product to build your own modular synthesizer from scratch. clacktronics.co.uk/byom/.
38 parts58 - 2 layers70.5 × 128.3 mm

- 2 layers233.7 × 172.2 mm

Overcycler3
gligli/overcycler
Affordable DIY polyphonic hybrid single cycle / analog synthesizer
335 parts45GPL-3.0 - 2 layers200.4 × 251.2 mm

- 2 layers48 × 110 mm
