A Plugin Doctor side-by-side of a real UADx Manley Massive Passive VST against PassiveFour.jsfx (band 1/Lo, ~150 Hz, ~5.7 dB boost) showed 0.7.0's Bandwidth knob wasn't remotely close: the real curve is a broad, low- shelf-like plateau spanning ~100-300 Hz, decaying gracefully out past 5 Hz and 10+ kHz; ours was a narrow spike. Root cause: Rq (0.7.0's parallel Q-spoiling resistor) caps a branch's impedance at Rq everywhere, not just near resonance. A real L-C branch's impedance keeps rising toward infinity far from resonance; a parallel Rq never lets it, so pushing Rq low enough to matter didn't broaden the peak so much as flatten the entire spectrum toward a constant gain floor (measured directly: +4.8 dB everywhere, five octaves from center, at a middling "wide" setting). wdf_boost_cut_band() drops Rq entirely. Bandwidth is now the caller's responsibility (PassiveFour.jsfx's process_band()): scale L up and C down by the same z0_ratio, keeping L*C (and the resonant frequency) fixed while changing the characteristic impedance sqrt(L/C). A branch built this way still has unbounded impedance at the frequency extremes regardless of ratio, so it can't degenerate into a flat floor - and this is how real analog bandwidth pots actually work. Verified end to end against the same real captured Lo-band measurement (docs/passive-four/reference-measurements-2026-09-13.txt): PassiveFour.jsfx's own curve now matches within ~0.2-1.6 dB from 5 Hz to 20 kHz, down from being off by multiple dB at most frequencies. Caught a second bug while implementing the fix: an early draft applied an extra sqrt() to z0_ratio before scaling L/C, silently halving the knob's effect in log-scale terms versus what BW_RATIO_MIN implied. Found by re-deriving the L/C-ratio math from scratch (Z0_new/Z0_old = ratio needs L *= ratio, C /= ratio directly, no sqrt) and re-validating tools/analyze_boostcut.py's analytical reference against the corrected formula (0.000 dB match) instead of trusting the first plausible-looking result. tools/check.sh also gained a 48 kHz/96 kHz sample-rate check (WDF port resistances depend on 1/srate; previously only exercised at 44100). circuit-notes.md documents this fix and separately answers an external architecture review's other points (per-sample recomputation is intentional/bug-fix-verified not an oversight, wdf_peak_band() is a validation-only harness not used by the plugin, frequency prewarping is handled via WDF port resistances and actively verified by the analytical comparisons, and the 4-band cascade is a deliberate buffered-stages design) - most were based on parts of the codebase that don't apply to the shipped plugin or were already validated. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
jsfx
A personal collection of open-source REAPER JSFX effects, distributed as a ReaPack repository. Each project is a single self-contained .jsfx file under Effects/<Project>/, with its own hand-drawn @gfx interface — no companion scripts or extra extensions to install.
Installing via ReaPack
- In REAPER:
Extensions→ReaPack→Import a repository... - Paste:
https://git.cybergate.ch/cybergate/jsfx/raw/branch/main/index.xml Extensions→ReaPack→Synchronize packages, then browse/install individual projects from the "jsfx" repository.
Projects
Passive Four
An open-source 4-band passive-style tube EQ.
Passive Four is inspired by classic passive tube equalizers of the "bridged-T" school (most famously the Manley Massive Passive). It is an independent implementation: the LC network and tube stage are built to reproduce the documented behavior of that class of EQ (bandwidth that widens with gain, boost/cut interaction on the same band, tube coloration), using component values estimated and tuned by ear/measurement — not a copy of any manufacturer's proprietary schematic. Passive Four is not affiliated with, sponsored by, or endorsed by Manley Laboratories, Inc. "Massive Passive" is a trademark of Manley Laboratories, Inc.
See docs/passive-four/circuit-notes.md for the DSP design rationale.
Status: early but functional prototype. All 4 bands are live, each with a real stepped frequency selector (values read off a Manley Massive Passive panel photo) and continuous Boost/Cut knobs that interact through a shared bridged-T network rather than simply adding/canceling in dB. Passive Wave Digital Filter DSP throughout, with a per-band make-up gain so each band is transparent at rest and boost/cut swing symmetrically around 0 dB. No independent Bandwidth/Q control yet, and component values are still estimates pending calibration against real hardware measurements (see docs/passive-four/circuit-notes.md).
File: Effects/Passive Four/PassiveFour.jsfx — everything (DSP + UI) in one file. Install it and add "Passive Four" as an FX on a track; that's it.
Dev tools
Not part of the plugin, but published the same way for convenience: impulse_gen.jsfx, dump_fx_params.lua, and sweep_fx_params.lua (categories Effects/Passive Four Dev Tools / Scripts/Passive Four Dev Tools) automate capturing reference impulse-response measurements from a third-party plugin inside REAPER — see tools/README.md.
Repository architecture
Each project is one .jsfx file: the audio processing (EEL2, in @init/@slider/@sample) and the interface (hand-drawn with JSFX's native gfx_* primitives in @gfx) live together. This was chosen over a ReaImGui-based UI after hitting it in practice: ReaImGui can't be embedded in a JSFX's own window (it only runs from separate REAPER scripts), which would have meant a companion script per project, cross-language communication (gmem, TrackFX_GetParam/SetParam), and two packages for the user to install and keep in sync — real friction for a "just works" open-source plugin.
ReaPack derives each package's category from its folder path (Effects/<Project>) and its type from its file extension (.jsfx → effect) — no extra config needed per project. Every installable file carries a metadata header (@author, @version, @about, @changelog).
Adding a new project
- Create
Effects/<Project name>/for the JSFX (and itslib/includes, if any). - Add a metadata header to the file (see
Effects/Passive Four/PassiveFour.jsfxfor the format). - Regenerate
index.xml(see below) and commit it alongside your changes.
Maintaining index.xml
index.xml is currently hand-maintained at the repository root: reapack-index (the official Ruby-based generator) depends on the rugged gem, whose native extension fails to build against this host's libgit2 (a known rugged/Debian packaging issue), so it isn't used yet. Each <source> URL points at raw/branch/main/... on this Gitea host, so it always serves the current content of main for whatever version is declared.
When adding a project or bumping a version:
- Add/update the file's own header (
@author/@version/@about/@changelog) for documentation purposes. - Add or update the matching
<category>/<reapack>/<version>entry inindex.xmlby hand (samename/type/desc/changelogvalues), with a<source main="main">URL of the formhttps://git.cybergate.ch/cybergate/jsfx/raw/branch/main/<path>(percent-encode spaces as%20). If the packageimports other files (e.g.lib/*.jsfx-inc), add one extra<source file="lib/whatever.jsfx-inc">per file inside the same<version>so ReaPack installs them alongside the main file -fileis a path relative to the category directory. - Validate before pushing:
python3 -c "import xml.dom.minidom as m; m.parse('index.xml')".
If reapack-index becomes usable later (e.g. via a container with a working libgit2 toolchain), it can take over with:
gem install reapack-index
reapack-index --url-template 'https://git.cybergate.ch/cybergate/jsfx/raw/commit/$commit/$path' .
License
MIT — see LICENSE.