Lancet — user manual

Cut where it counts — a surgical dynamic EQ with an analog soul.

What's new in v0.4.0

The Attack knob is now true down to 0.1 ms. Up to v0.3.0 there was a fixed 50 ms smoother sitting behind the gain computer, so no Attack setting faster than about 50 ms could actually be heard - 0.1 ms, 1 ms and 20 ms all did the same thing. That smoother is gone; the detector's own ballistics are now the only thing shaping how fast a band moves, evaluated once per sample.

Please read this if you have existing sessions. A session using a non-zero Range with a fast Attack will react faster than it did before. This is a bug fix, not a re-voicing - the plugin now does what its own knob always said it did - but it is a real, audible change and you should expect to hear it. If a band now feels too grabby, dial its Attack up: for the first time, that control has the effect its label describes. Sessions with Range = 0 on every band (pure static EQ, no dynamics) are unchanged, and the static EQ curves themselves are identical to v0.3.0's in every band type, gain and Q.

Also new:

Both new per-band controls sit in the editor as combo boxes under each band's Type slot. Sessions saved by older versions load exactly as before, with both new controls at the settings that reproduce the old behaviour.

What's new in v0.3.0

A musical-voicing pass (see docs/voicing-notes.md) - measured where the plugin's own DSP made a measurement possible, honestly labeled "by ear, not yet tuned against real material" where it doesn't:

What's new in v0.2.0

A research-derived deep-dive rework (see docs/design-brief.md/ docs/research-notes.md), the suite's M2 preset system, and a German frame localisation:

What it is

Lancet is a six-band dynamic EQ in the spirit of the Waves F6 class - cited here as a documented reference point for the category, without implying endorsement, sponsorship, or affiliation by Waves Audio Ltd. Each band is a normal parametric EQ band (bell, or shelf on Band 1/Band 6) whose gain can additionally move with the program material. Feed it loud and it reacts — cutting a resonance only when it flares up, or opening a boost only when a part gets buried — then settles back to its static setting once the signal drops back down. Because each band's dynamic move is driven by its own pre-EQ, band-filtered detector, one band's cut never confuses another band's detector, and a band's own gain move never feeds back into its own detection.

Where a static EQ band asks "how much?", Lancet's dynamic bands also ask "when?" — the difference between permanently notching out a 3 kHz resonance (which also thins the tone whenever that resonance isn't present) and only pulling it back exactly when it rings.

Where it sits in a mix chain

Lancet is a corrective, surgical tool, most useful early-to-mid signal chain, before broad tonal shaping and bus compression:

Source track -> [gain staging / gate] -> Lancet (resonance/harshness control) -> broad EQ / saturation -> compression -> bus

Reach for it when a static EQ cut would either under-treat the problem (leaving room for it to still poke through on the loudest hits) or over-treat it (thinning the tone on quieter passages where the problem isn't present). It also works as a mix-bus or master-bus tool for controlling a specific recurring resonance or harshness band without permanently coloring everything under it.

Signal flow

in --[Input Trim]--+--[pre-chain tap]--> each band's Detector (bandpass @ band freq/Q -> envelope)
                    |
                    +--> Band1 -> Band2 -> Band3 -> Band4 -> Band5 -> Band6 --> [Mix] --> [Output Trim] --> out

Every band's detector taps the signal right after Input Trim, before Band 1 - not that band's own serially-processed input - so a downstream band's gain move never perturbs an upstream band's detection, and no band's own move feeds back into triggering itself. See docs/architecture.md for the full engineering breakdown (gain-computer formula, detector selectivity, sub-block coefficient smoothing, Listen).

Under the hood

The per-sample gain path and the TPT SVF core

Up to v0.3.0 a band's realised gain came from a 50 ms juce::SmoothedValue sitting after the gain computer - the detector could react in half a millisecond and the filter would still take 50 ms to catch up, so most of the shipped 0.1-500 ms Attack range did nothing audible. v0.4.0 deletes that smoother and evaluates the whole chain - detector envelope, dB conversion, soft-knee gain computer, Range clamp, filter gain - once per sample. The detector envelope is the smoother now, and there is no stepped-gain fallback for any band type.

Deleting the smoother from a coefficient-rebuilding biquad would have been a zipper-noise generator on its own, so the filter core changed too. A direct-form biquad whose coefficients jump every sample is not a well-defined time-varying filter - its internal state means something different after each jump. Every band (bell, low shelf, high shelf) instead runs on lnct::TptSvf, a topology-preserving-transform (trapezoidal- integration) state-variable filter built from Andrew Simper's published Cytomic equations, with no third-party code vendored. Its two state variables are physical integrator outputs that keep their meaning no matter how the coefficients move, so gain can be modulated every sample without artefact. The bell's frequency warp is completely gain-independent - dynamic gain enters only as a scalar bandpass mix, never by re-tuning the centre frequency - and integrator state is kept in double even though the audio path is float, because the trapezoidal update's own cancellation loses digits in single precision at low frequency and high Q.

The realised static response is unchanged by any of this: verified against an independent double-precision reference to better than -100 dBFS peak residual over ten seconds of broadband noise per setting, and against the analytic RBJ magnitude response to within ±0.05 dB across a band-type/gain/Q grid. At exactly 0 dB every gain-dependent mix scalar is exactly zero, so an idle band is bit-transparent by construction, with no branch and no special case.

Detector design

In Split mode, the detector cascades two bandpass biquads at the band's own frequency/Q rather than one, because a single stage only reaches about -12 dB two octaves out at Q 1 - not enough headroom against a loud out-of-band tone falsely triggering the band. The cascade measures better than -24 dB, comfortably past the plugin's own >20 dB two-octave isolation bar. Stereo (or wider) input is linked: each cascade stage runs independently per channel with its own filter state, but the envelope follower is a single band-wide value fed by the loudest (max-abs) sample across channels at every instant - which avoids the stereo-image shift that fully independent per-channel gain reduction would introduce. There is no per-band unlink option. The envelope itself is a one-pole peak follower run per sample for correct ballistics timing; only the bandpass's own coefficients are throttled to sub-block granularity, never the envelope.

Auto-release: a dedicated fast reference envelope

Auto Release (off by default) shortens a band's effective release whenever the signal's own envelope is already falling on its own, clamped so the result is always at least as fast as - never slower than - the manual Release setting. The detail worth knowing: the fall-rate measurement comes from a second, dedicated, always-fast envelope inside the detector (same Attack coefficient, but a fixed release tied to the plugin's own 5 ms floor), not from the main envelope. Deriving it from the main envelope does not work - a slow envelope is itself a low-passed view of the input, rate-limited to roughly its own time constant, so "how fast is the slow envelope falling" mostly measures the slow envelope's own coefficient back at itself. An earlier internal implementation attempt made exactly that mistake, and its output was measurably identical whether Auto Release was on or off.

Anti-aliased saturation, still at zero latency

The optional per-band Saturation stage replaces a plain tanh waveshaper with a first-order antiderivative-antialiased (ADAA1) kernel of the same shape, computed as the difference quotient of ln(cosh(x)) between consecutive samples - same harmonic character, measurably less fold-back, no oversampling stage and no added latency. The specification is deliberately relative rather than an absolute alias-floor claim; see "Latency and aliasing" below for the measured numbers and the reasoning. Saturation only ever engages while a band is actively boosting (static + dynamic gain net positive) - a cutting or idle band is bit-identical with it switched on.

Engineering hygiene

Parameter reference

Per band (Band 1 - Band 6, identical controls unless noted)

Parameter Range Default Unit What it does musically
On Off / On Off (Band 3: On) Enables the band. An off band is a true bypass - it doesn't touch the signal at all, though its detector keeps running underneath so there's no jump when you switch it back on.
Type Bell / Shelf Bell Band 1 and Band 6 only. Band 1's Shelf is a Low Shelf (boosts/cuts everything below Freq); Band 6's Shelf is a High Shelf (boosts/cuts everything above Freq). Bands 2-5 are always Bell.
Freq 20 - 20000 100 / 250 / 630 / 1600 / 4000 / 10000 Hz The band's centre frequency (Bell) or corner frequency (Shelf) - both the filter's own shape and what its detector listens to.
Q 0.3 - 12 0.9 / 1.1 / 1.0 / 1.2 / 1.4 / 1.0 (v0.3.0, per band - see table below) How narrow (high Q) or broad (low Q) the band is. Ignored in Shelf mode, which always uses a fixed, standard shelf slope (Q = 0.707) regardless of this setting.
Gain -12 - +12 0 dB The band's static gain - always applied, dynamic or not. Set this to your "at rest" EQ move; Range then adds or subtracts on top of it when the detector triggers.
Range -12 - +12 0 dB How far the band's gain can move dynamically, on top of Gain. 0 = a pure static EQ band (no detector influence at all). Negative Range cuts as the signal gets louder past Threshold (the classic resonance-taming/de-essing move); positive Range boosts as it gets louder (an upward "duck-in" expansion move, useful for e.g. bringing out a pick attack only on hard-hit notes).
Thresh -60 - 0 -24 / -25 / -24 / -24 / -24 / -24 (calibrated, per band - see table below) dB The detector level above which the dynamic move starts engaging. A soft knee centred on this value makes the transition in gradual rather than a hard switch - the knee's own width scales with Range (v0.2.0): clamp(|Range| * 0.5, 2, 10) dB, so shallow Range settings read gentler and full-depth (±12 dB) Range settings sound identical to v0.1.0's fixed 6 dB knee.
Attack 0.1 - 500 25 / 15 / 8 / 4 / 2 / 3 (v0.3.0, per band - see table below) ms How quickly the dynamic gain moves once the detector crosses Threshold. Fast attack catches transients hard; slower attack lets a brief peak through before reacting, which can sound more natural on percussive material. The 500 ms ceiling is meant for slow, musical tonal-balancing moves, not transient catching. Since v0.4.0 this control is true across its whole range - see "What's new in v0.4.0" above; before that, everything below ~50 ms behaved identically.
Release 5 - 1500 280 / 180 / 130 / 100 / 70 / 90 (v0.3.0, per band - see table below) ms How quickly the dynamic gain returns toward Gain once the detector drops back below Threshold. Fast release can pump audibly on sustained material; slow release smooths the return out but can hold a cut/boost into content that no longer needs it.
Listen Off / On Off Solos that band's own detector signal - the bandpass-filtered, pre-EQ audio that's actually driving its dynamic move - in place of the normal program output, for auditioning exactly what triggers it. Exclusive: engaging Listen on one band disengages any other band's Listen. The full signal chain (including every band's own processing) keeps running underneath, so disengaging Listen never pops.
Auto Release (v0.2.0) Off / On Off Program-dependent auto-release: when on, the effective release time for a given transition shortens automatically (never below this plugin's own 5 ms Release floor, never past the manual Release setting itself) whenever the signal's own envelope is already falling on its own - useful for letting a band relax faster on naturally-decaying material without giving up a slower, musical manual Release for sustained material. Automation/preset-only in v0.2.0 - no dedicated editor knob yet (roadmap M3).
Gain/Q (v0.2.0) Off / On Off Gain/Q coupling: when on, the band's own filter Q widens (softens) proportionally to how far its dynamic gain currently sits toward Range - a gentler, more analog-style character at deeper dynamic moves. Static Gain never affects Q, only the dynamic component does. Automation/preset-only in v0.2.0 - no dedicated editor knob yet (roadmap M3).
SC Source (v0.4.0) Internal / External Internal Where this band's detector listens. Internal (the default, and every previous version's only behaviour) is the signal passing through the plugin, tapped before Band 1. External is the plugin's sidechain input - route something else to it in your host and this band moves in response to that instead, while still filtering the main signal. If your host provides no sidechain, or the sidechain input is left disabled, a band set to External falls back to Internal rather than going silent. No delay compensation is applied to the sidechain, so it must already be time-aligned by the host.
SC Mode (v0.4.0) Split / Wide Split How much of the detector's source this band listens to. Split (the default) filters the detector input down to this band's own frequency region, so only content near Freq can trigger it - the surgical behaviour. Wide skips that filter, so the band responds to overall level across the whole spectrum while still only moving its own band. Wide is what you want when a band should breathe with the mix rather than police one resonance. Listen follows this setting, so you always audition the real trigger signal.
Saturation (v0.3.0) Off / On Off Gentle waveshaping: when on, a soft drive is applied to the band's own output, but only while it's actively boosting (Gain + the dynamic contribution net positive) - a cutting or idle band is unaffected even with this on. Drive scales with how hard the band is boosting (barely-there near 0 dB, clearly audible but still soft-knee-shaped near +12 dB). Since v0.4.0 the waveshaper is anti-aliased, so it adds far less of the harsh fold-back grit that a plain waveshaper produces on high-frequency content, at no latency cost. Automation/preset-only - no dedicated editor knob yet (roadmap M3).

Per-band voicing defaults (docs/voicing-notes.md) - Q/Attack/Release tuned to each band's typical role along the existing frequency ladder (v0.3.0), Threshold calibrated by measurement so that every band begins engaging at the same programme loudness (issue #4 calibration pass - see below):

Band Freq Role Q Threshold Attack Release
1 100 Hz (Low Shelf) Boom/sub control 0.9 -24 dB 25 ms 280 ms
2 250 Hz Mud/box resonance (vocal & guitar body) 1.1 -25 dB 15 ms 180 ms
3 630 Hz General midrange presence (default-on demo band) 1.0 -24 dB 8 ms 130 ms
4 1600 Hz Vocal presence / guitar edge 1.2 -24 dB 4 ms 100 ms
5 4000 Hz Sibilance / pick attack / harshness 1.4 -24 dB 2 ms 70 ms
6 10000 Hz (High Shelf) Air / fizz recovery 1.0 -24 dB 3 ms 90 ms

Each band's default Threshold equals that band's own measured detector level under a -18 dBFS RMS pink-noise programme anchor (the common digital alignment-level convention), so at typical mix level every band sits right at the edge of engaging: dial in a Range and the band immediately starts working on anything at or above normal programme loudness, on every band alike. Run material hotter or colder than that convention (or into an Input Trim adjustment) and the engagement point shifts with it - Threshold remains an ordinary, per-band control; only its default is calibrated.

Global

Parameter Range Default Unit What it does
Input Trim -12 - +12 0 dB Gain applied before Band 1 - and before every band's detector taps the signal, so it also shifts what level reaches each band's Threshold.
Output Trim -12 - +12 0 dB Gain applied after Band 6 and after the Mix blend - the final gain stage, for matching Lancet's output level to whatever follows it in the chain.
Mix 0 - 100 100 % Parallel dry/wet blend of the whole six-band chain. 100% is fully processed; lower values blend in progressively more of the untouched (but still Input-Trimmed) signal - useful for "New York"-style parallel dynamic EQ, where you want the correction to add rather than fully replace.

Presets

A preset bar sits at the top of the editor: [<] [Preset Name] [>] to step through the factory and user library alphabetically, Save/Save As... to write your own, Delete for user presets, Import.../Export... for single .basilicapreset files or .zip banks, and a menu (click the preset name) with a "Set current as default" entry for your own out-of-the-box starting point. Eleven factory presets ship - see docs/presets.md for what each one does and why. User presets are stored per-user at ~/Library/Audio/Presets/Yves Vogl/Lancet/ on macOS (%APPDATA%/Yves Vogl/Lancet/Presets/ on Windows).

The editor's frame strings (preset bar labels, menus, dialogs) are localised to German automatically when the system language is German; parameter names, units, and technical terms (Attack, Release, Hz, dB, ms, …) always stay in English, matching every other Basilica Audio plugin.

Tips

Latency and aliasing

Lancet adds zero latency - always. It reports 0 samples before and after your host prepares it, at every sample rate and block size, with every band engaged and with the sidechain bus enabled. That is verified by impulse response (the peak really comes back on sample 0), not merely reported, and there is no dry-path delay compensation anywhere in the plugin. Every filter in the signal path - the six bands and their detectors - is minimum-phase with no lookahead.

Two v0.4.0 decisions keep it that way, deliberately:

Sample rates and formats. AU, VST3 and Standalone. Verified finite and zero-latency at 44.1, 48, 88.2, 96, 176.4 and 192 kHz, and across a sample-rate change mid-session. Mono and stereo main layouts are both supported; the sidechain input can be disabled, mono or stereo independently of the main layout, and anything wider than stereo on it is rejected rather than silently misinterpreted.

Sessions, presets and compatibility

Sessions saved by any earlier version load cleanly, with every stored parameter value preserved exactly and every parameter added since then sitting at the default that reproduces the older behaviour. Concretely: a v0.1.0 session's values survive, the v0.2.0/v0.3.0 per-band toggles (Auto Release, Gain/Q, Saturation) come back off, and v0.4.0's twelve new per-band choices come back at Internal and Split - the only routing any previous version ever had.

Saved state carries a schema-version stamp from v0.4.0 onward. A state without one is read as the older schema. Nothing needs converting today (every newer parameter's default is the older behaviour), but the stamp means a future release that genuinely does need to convert something has a reliable way to tell what it is reading.

One caveat, and it is the important one: while your stored values are untouched, a session that used a non-zero Range together with a fast Attack will sound different under v0.4.0, because the Attack path was broken before and is now fixed. See "What's new in v0.4.0" at the top of this document. The same applies to the ten factory presets that predate v0.4.0 - none of their stored values changed, but the ones whose names promise speed (De-Ess Stack, Transient Snare Crack, Fast-Recovery Demo) now behave the way their names always claimed.

Known limitations

Stated plainly, because knowing them is more useful than not: