feature cv
featurecv · modulationFEATURECV — an audio→CV feature extractor. Takes ONE audio input and turns the WHOLE signal's timbre + dynamics into control voltages plus an onset trigger, time-domain only (no FFT) so it is fully deterministic. Deliberately distinct from SYNESTHESIA (per-band energy/gates/onsets); featurecv analyses the broadband signal. Features — LOUD (cv): broadband RMS = overall loudness/energy. BRIGHT (cv): zero-crossing rate, a cheap spectral-brightness proxy (high = hissy/trebly, low = dark/bassy). PUNCH (cv): crest factor (peak ÷ RMS) = how spiky/transient vs sustained/compressed. ONSET (gate, edge=trigger): a short pulse that fires ONCE on each fresh attack/transient (time-domain spectral-flux peak-pick with an adaptive threshold + debounce). The three feature CVs are emitted BIPOLAR (−1..+1) by DEFAULT so a strong feature sweeps a knob-centred destination's FULL range; the POLARITY toggle switches to UNIPOLAR (0..1) for envelope-style modulation. Controls — GAIN: input trim into the analyser (×0.25..×4 log, unity at noon). ATK / REL: attack + release smoothing (ms, log) of the three feature CVs. POLARITY: BI [-1,+1] (default) / UNI [0,1]. SENS: onset sensitivity (linear; higher fires on smaller transients). DEBNCE: onset debounce (ms, log; minimum gap between triggers), which is a RATE CEILING of 1000÷ms — 12.5 Hz at the shipped 80 ms. Three measured facts the dials cannot print, and the faceplate does: the three feature CVs REST at −1.00 (bipolar) with nothing patched, not at 0; GAIN reaches only LOUD, because ZCR and crest are both scale-invariant; and LOUD PINS at full scale above −6.02 dBFS RMS at unity trim. DSP is own-code (clean-room) in packages/dsp/src/lib/featurecv-dsp.ts, reusing the synesthesia EnvFollower one-pole + applyBipolar + the time-domain flux-onset idea; the worklet wraps it (the SYNESTHESIA/SPECTROGRAPH analyser pattern: GAIN GainNode → worklet → per-feature output GainNodes + a muted keep-alive so process() runs while outputs are unpatched). The legacy card shows live LOUD/BRIGHT/PUNCH meters + an ONSET blink (display only — never writes the live Y.Doc); the FACEPLATE replaces them with the three drawn maps, because the snapshot behind those bars is the UNSMOOTHED, always-unipolar target and disagrees with the jacks it names. All patching is via the card's yellow drill-down PATCH PANEL — no side jacks; every knob is MIDI / control-surface assignable. Usage: patch LOUD into a VCA/filter to track dynamics, BRIGHT into a filter cutoff so the timbre opens as the source brightens, PUNCH into modulation that should react to transients, and ONSET into an envelope generator or drum voice to fire on each hit.
the faceplate
inputs
| id | cable | what it does |
|---|---|---|
in | audio | The audio signal to analyse — the measured signal, not a modulator. Its loudness, brightness, and transients drive every output. Patch a drum bus, vocal, synth voice, or full mix here. audio signal |
outputs
| id | cable | what it does |
|---|---|---|
loud | cv | LOUD CV — the broadband RMS (overall energy / loudness) of the input over a 21.3 ms window, smoothed by ATTACK/RELEASE. Patch into a VCA gain or filter cutoff to make a destination track how loud the source is. Polarity set by the POLARITY toggle, so a silent input rests at −1.00 (bipolar) or 0.00 (unipolar). ⚠ IT IS THE ONE FEATURE WITH A CEILING: the RMS is multiplied by 2 and clamped, so at unity GAIN any source above −6.02 dBFS RMS reads a flat full scale and stops modulating at all. GAIN divides that ceiling — ×4 brings it down to −18.1 dBFS — and the faceplate prints the live number as `loud clip`. control voltage (CV) |
bright | cv | BRIGHT CV — a brightness proxy from the zero-crossing rate: high when the sound is hissy / trebly (cymbals, noise, bright synths), low when it is dark / bassy. Patch into a filter cutoff so the timbre opens with the source's brightness. Polarity set by POLARITY. ⚠ SCALE-INVARIANT: it counts sign changes, so GAIN (and the source's own level) is bit-exactly a no-op here — measured, the rack's own white tap reads 0.97 unipolar at any trim. Full scale is reached at a zero-crossing rate of 0.5, which broadband noise sits essentially on top of. control voltage (CV) |
punch | cv | PUNCH CV — the crest factor (peak ÷ RMS): high for spiky, transient, percussive material and low for sustained, compressed tones. Patch into modulation you want to react to how punchy the source is. Polarity set by POLARITY. ⚠ ALSO SCALE-INVARIANT — a ratio of two quantities GAIN scales identically — and the map is calibrated on crest 1..6, which puts steady material near the BOTTOM: a sine (crest 1.41) reads 0.08 unipolar / −0.83 bipolar, and the rack's own white tap (uniform noise, crest 1.73) reads 0.15 / −0.71. Sustained sources live in the bottom fifth of this jack's range; it is transients that move it. control voltage (CV) |
onset | gate | ONSET trigger — fires a short 5 ms pulse ONCE on each detected attack (a fresh transient / hit) in the input, a clean rising edge that crosses the gate threshold, about 1.3 ms after the attack itself. Patch into an envelope generator, VCA, or drum voice to strike it from the live source. Unaffected by POLARITY. SENS sets how readily it fires; DEBNCE sets the minimum gap between pulses and therefore the fastest hit train that passes intact — 12.5 Hz at the shipped 80 ms, measured 36/36 pulses on a 3 s 12 Hz train, and every OTHER hit at 16 Hz (24 of 48). gate / trigger; trigger — fires once per rising edge |
params
| id | label | range | default | curve |
|---|---|---|---|---|
gain | Gain | 0.25..4 | 1 | log |
attack | Atk | 0.5..500ms | 10 | log |
release | Rel | 1..2000ms | 100 | log |
bipolar | Polarity | 0..1 | 1 | discrete |
onset_sens | Sens | 0..1 | 0.5 | linear |
onset_debounce | Debnce | 20..1000ms | 80 | log |
controls
| control | what it does |
|---|---|
| Atk | How fast the feature CVs RISE toward a new value (ms, log) — short attack snaps to transients, long attack glides smoothly past them. Applies to LOUD, BRIGHT, and PUNCH. ⚠ THE NUMBER IS A ONE-POLE TIME CONSTANT, NOT A RISE TIME: the follower reaches 63 % of a step in the printed time and 10→90 % in 2.197× it, so the shipped 10 ms delivers a 22 ms move. The faceplate prints the delivered figure as `atk rise`. |
| Polarity | POLARITY of the three feature CV outputs — BIPOLAR (−1..+1, the default, so a strong feature sweeps a knob-centred destination's full range) vs UNIPOLAR (0..1, classic envelope-style). Does not affect the ONSET trigger. ⚠ IT IS ALSO THE ONLY CONTROL ON THE MODULE THAT DOES ANYTHING WITH NOTHING PATCHED, and that is why it is ranked first: a silent or unpatched input makes all three features 0, which BIPOLAR maps to −1.00 — the bottom rail, not the centre — so an idle featurecv holds its destinations hard down until it hears something. UNIPOLAR rests the same silence at 0.00. |
| Gain | Input trim into the analyser (×0.25..×4, log, unity at noon) — boost a quiet source so LOUD reaches a usable CV range, or tame a hot one. It shapes the ANALYSIS level, not an audio output (there is none). ⚠ IT REACHES EXACTLY ONE OF THE THREE FEATURES. LOUD is a level and moves with it; BRIGHT counts zero crossings and PUNCH is a peak-to-RMS ratio, so both are scale-invariant and this trim is bit-exactly a no-op on them. What it does decide for LOUD is where that feature SATURATES: the RMS is doubled and clamped, so the input level at which LOUD pins at full scale is −6.02 dBFS at unity and 6.02 dB lower for every doubling of the trim. |
| Debnce | ONSET debounce (ms, log) — the minimum time between onset triggers, a lockout that blocks a re-trigger on a transient's ringing tail so one hit makes exactly one pulse. It is therefore a RATE CEILING: the fastest hit train that passes intact is 1000 ÷ this number, so the shipped 80 ms tops out at 12.5 Hz — measured 36/36 pulses on a 3 s 12 Hz train, and every OTHER hit at 16 Hz (24 of 48). That is the number that decides whether 16th-note hi-hats get through, and the faceplate prints it live as `max rate`. |
| Sens | ONSET sensitivity — higher lowers the detector's adaptive threshold so it fires on smaller transients; lower only triggers on strong, clear attacks. The threshold is `mean flux × N + 0.15` and this dial sets N, INVERTED: 4.00× at SENS 0, 2.60× at the shipped 0.50, 1.20× at SENS 1. The faceplate prints N live as `fires at`. ⚠ ITS AUTHORITY IS CONDITIONAL, and measured: on a clean, well-separated hit train the whole travel is a no-op (12 pulses out of 12 at every setting), because an unambiguous transient clears every threshold. Where it earns its place is AMBIGUOUS material — a tremolo tone goes 1 → 13 pulses across the same travel, and hits buried in a loud noise bed go 4 → 10. There is additionally an absolute floor no SENS setting defeats: a source whose slow envelope stays under 0.005 produces no onsets at all. |
| Rel | How fast the feature CVs FALL back when the input quietens or changes (ms, log) — short release tracks every dip, long release holds a smooth contour. Applies to LOUD, BRIGHT, and PUNCH. ⚠ Also a one-pole TIME CONSTANT: the shipped 100 ms delivers a 220 ms 90→10 % fall, printed on the faceplate as `rel fall`. |
source
featurecv.ts on GitHub.