julienandClaude Sonnet 5 daead8a1d9 Replace 0.7.0's Rq bandwidth mechanism with an L/C-ratio approach (0.8.0)
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>
2026-09-14 05:32:59 +02:00

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

  1. In REAPER: Extensions → ReaPack → Import a repository...
  2. Paste: https://git.cybergate.ch/cybergate/jsfx/raw/branch/main/index.xml
  3. 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

  1. Create Effects/<Project name>/ for the JSFX (and its lib/ includes, if any).
  2. Add a metadata header to the file (see Effects/Passive Four/PassiveFour.jsfx for the format).
  3. 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:

  1. Add/update the file's own header (@author/@version/@about/@changelog) for documentation purposes.
  2. Add or update the matching <category>/<reapack>/<version> entry in index.xml by hand (same name/type/desc/changelog values), with a <source main="main"> URL of the form https://git.cybergate.ch/cybergate/jsfx/raw/branch/main/<path> (percent-encode spaces as %20). If the package imports 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 - file is a path relative to the category directory.
  3. 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.

S
Description
No description provided
Readme MIT
2 MiB
Languages
Python 53.3%
Lua 24.9%
C++ 12%
Shell 9.8%