Skip to the content
PCBKitNew board
2 layers175 × 120 mmDuck, the printed circuit board in 3D

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

1

README

README.md

Angry 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

text
 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 the muff param.
  • Octave = op-amp precision full-wave rectifier (fwr.cir), no transformer. octave_gen.cir produces 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.cir behavioral / vcf_real.cir real). 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. See memory / 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 .subckt per block: input_buffer, fuzz, fuzz_muff, fwr, octave_rect/octave_gen, hpf, gmint+vcf/vcf_real, envfollow, duck(mixed)/duck_split(hybrid/parallel), pedal
  • spice/tests/ — per-block test benches
  • sim/ — Python harness: run.py, render.py, octave_balance.py, fft_report.py, spicelib.py, extract_sample.py
  • audio/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.md
    • build_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_pcb in pcbnew → Alt+3)
    • AUDIT.md (netlist-translation audit + gap fixes) ; BOM.md ; COST.md (build-cost estimate)
    • pedal_build.cir in spice/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*.png drawn 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:

text
# 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_pcb

Board: 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:

text
uv run sim/run.py spice/tests/tb_input_buffer.cir

Render guitar through the pedal (→ audio/out/):

text
# 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 committed audio/out/build_grid/ + build_grid_orig/ are the --build grids 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):

text
uv run sim/octave_balance.py --wav audio/in/di_sample.wav --squeal 1.0 --goct 8

Parts

105
QtyValueFootprintReferences
1610kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR14, R15, R16, R18, R21, R22, R23, R25, R34, R35, R36, R37, R38, R39, R40, R48
131N4148D_DO-35_SOD27_P2.54mm_Vertical_AnodeUpD2, D3, D4, D5, D6, D7, D8, D9, D10, D11, D12, D13, D14
11100nC_Rect_L7.0mm_W2.5mm_P5.00mmC3, C4, C6, C8, C19, C20, C21, C22, C23, C24, C25
6100kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR1, R2, R6, R9, R27, R28
6TL074DIP-14_W7.62mmU1, U2, U3, U4, U5, U6
522kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR29, R49, R50, R51, R52
41MR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR3, R4, R7, R10
31.5nC_Rect_L7.0mm_W2.5mm_P5.00mmC5, C7, C9
3100kPotentiometer_Alps_RK097_Single_HorizontalRV1, RV2, RV3
21.5kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR30, R32
210nC_Rect_L7.0mm_W2.5mm_P5.00mmC16, C17
2110kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR31, R33
212kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR8, R11
215kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR19, R20
21kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR44, R46
22.2kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR5, R13
2220nCP_Radial_D5.0mm_P2.50mmC11, C12
24.7kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR17, R24
247kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR43, R45
268kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR12, R47
2AudioJack2Jack_6.35mm_Neutrik_NJ3FD-V_VerticalJ1, J2
1100kPotentiometer_Alps_RK097_Dual_HorizontalRV4
1100uCP_Radial_D5.0mm_P2.50mmC1
112nC_Rect_L7.0mm_W2.5mm_P5.00mmC14
11N5817D_DO-41_SOD81_P10.16mm_HorizontalD1
11uC_Rect_L7.0mm_W2.5mm_P5.00mmC15
122nC_Rect_L7.0mm_W2.5mm_P5.00mmC18
127nC_Rect_L7.0mm_W2.5mm_P5.00mmC10
13.3kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR26
139kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR42
147nC_Rect_L7.0mm_W2.5mm_P5.00mmC13
147uCP_Radial_D5.0mm_P2.50mmC2
16.8kR_Axial_DIN0207_L6.3mm_D2.5mm_P2.54mm_VerticalR41
1Barrel_JackBarrelJack_HorizontalJ3
1LM13700DIP-16_W7.62mmU7

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 mmVCO, the printed circuit board in 3D

    VCO

    nickajeglin/Eurorack-pcbs

    A collection of easy to solder DIY eurorack PCB's

    104 parts208
  • 2 layers65 × 100 mmJOVE, the printed circuit board in 3D

    JOVE

    minisystem/JOVE

    Eurorack multimode filter inspired by the Roland Jupiter-6

    156 parts109
  • 2 layers29.5 × 100 mmBYOM VCF main, the printed circuit board in 3D

    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 mmBare VCA II, the printed circuit board in 3D

    Bare VCA II

    Blinken-Lights/ASCII

    ASCII Modular hardware and software repo

    206 parts50MIT
  • 2 layers233.7 × 172.2 mmOvercycler3, the printed circuit board in 3D

    Overcycler3

    gligli/overcycler

    Affordable DIY polyphonic hybrid single cycle / analog synthesizer

    335 parts45GPL-3.0
  • 2 layers200.4 × 251.2 mmHypjolin, the printed circuit board in 3D

    Hypjolin

    triglav-modular/Hypjolin

    Cross-modulating ultra-chaos generator

    340 parts45Unlicense
  • 2 layers48 × 110 mmFoundation, the printed circuit board in 3D

    Foundation

    odeliy/noise-reap

    126 parts37