Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions REVIVAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,36 @@ GitHub Actions CI.
network, eight voices* (the `tr808_*` headers — the bridged-T solved on its states,
the calibration lesson), and *Time as a function of phase* (`step_seq.h` — the O(1)
derivation, gate-hold look-ahead, armed-recall re-derivation).
- ✅ **Net-new object — `tap.vca~` (2026-07-18).** The 303's transistor VCA stage, lifted out
where it can amplify anything. Kernel: **`vca.h`** (`taptools::vca`) — the two-circuit `svf.h`
idiom applied to a gain stage: `clean` is the pure linear multiply (bit-identical to `*~`),
`warm` is the one-transistor class-A saturator `S(v) = (tanh(d·v+b) − tanh(b)) / (d·sech²(b))`
(stock `d=2.0`, `b=0.3` — the probe-calibrated 303 constants, now exposed as `drive`/`bias`),
applied to the post-gain signal so the even-harmonic warmth and compression **track the control
voltage** the way a discrete VCA does and a multiply cannot, plus an optional output-coupling DC
block (`dcblock`) for the shaper's signal-dependent offset. **`tb303_voice.h` was refactored to
compose `vca::shape()`** — one implementation shared between `tap.303~` and the standalone; the
voice keeps its own Open303 output high-pass and gain, so the extraction left it **bit-identical**
(pinned by `tests/vca_test.cpp`, which matches `shape()` to the old inline formula and re-runs the
303 warm-vs-clean scenarios). Wrapper `tap.vca_tilde` (audio in + signal/float gain CV in),
`circuit clean|warm` + `drive`/`bias`/`dcblock`/`bypass`/`mute`, the full vertical slice (maxref +
help patcher). Remaining: runtime validation in Max.
- ✅ **The 808 swing-VCA harmonics — `vca.h` `swing` mode + the noise voices (2026-07-18).** The
first piece of the flagged 808 circuit-sim work, and the lowest-risk one: the swing-type VCA's
"many high harmonics" (Service Notes, RS/CL VCA), which `swing_vca()` had modeled as a flat
`x·env`. Kernel: **`vca.h` gains `mode_swing`** — a symmetric (odd-harmonic) `tanh(d·v)/d`
saturator, unity-slope-at-0 so quiet tails stay clean and hot transients pick up grit and
compression, no DC (symmetric → no coupling block needed). One implementation: a static
`vca::swing_shape(v, drive)` that both `mode_swing` and **`swing_vca(x, env, drive)`** route
through. **`drive` defaults to 0 → the exact linear passthru**, so every calibrated 808 voice
stays **bit-identical** until it opts in (the full existing `tr808_*` suite passes unchanged; the
snare test adds an explicit off-is-bit-identical / on-saturates-and-compresses scenario). Wired
into the three noise-path voices — **snare** (snappy), **clap** (CP + maracas output), **tom**
(noise "reverberation") — each exposing a `drive` circuit-bend attribute (0..12). `tap.vca~` gains
the matching `circuit swing`. The heavier WDF `@circuit` pass (the kick bridged-T etc.) stays
where the field guide left it — gated on an A/B showing an audible delta the informed model misses,
which the DAFx-14 finding suggests may never open. Remaining: A/B calibration of the swing `drive`
against reference, and runtime validation in Max.

---

Expand Down
9 changes: 9 additions & 0 deletions docs/tap.808.clap~.maxref.xml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@
it, leaving only the three teeth; above 1 exaggerates the wash.
</description>
</attribute>
<attribute name="drive" get="1" set="1" type="float" size="1">
<digest>Circuit bend: swing-VCA drive on the output VCA (default 0 = linear)</digest>
<description>
Swing-VCA drive on the output VCA, 0..12, for both the clap and maracas models.
0 is the calibrated linear model (default); higher engages the swing VCA's
symmetric harmonic saturation — grit and compression that ride the envelope.
See tap.vca~ (the swing circuit).
</description>
</attribute>
<attribute name="seed" get="1" set="1" type="int" size="1">
<digest>Noise seed (deterministic)</digest>
<description>
Expand Down
9 changes: 9 additions & 0 deletions docs/tap.808.snare~.maxref.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@
(~238/476 Hz) — about 1.4 here.
</description>
</attribute>
<attribute name="drive" get="1" set="1" type="float" size="1">
<digest>Circuit bend: swing-VCA drive on the snappy path (default 0 = linear)</digest>
<description>
Swing-VCA drive on the snappy noise path, 0..12. 0 is the calibrated linear
model (default); higher engages the swing VCA's symmetric harmonic saturation —
grit and compression that ride the snappy envelope, hardest on the transient
crack. See tap.vca~ (the swing circuit).
</description>
</attribute>
<attribute name="seed" get="1" set="1" type="int" size="1">
<digest>Noise seed (deterministic)</digest>
<description>
Expand Down
9 changes: 9 additions & 0 deletions docs/tap.808.tom~.maxref.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@
<digest>Output level</digest>
<description>Output level, 0..1 (default 1).</description>
</attribute>
<attribute name="drive" get="1" set="1" type="float" size="1">
<digest>Circuit bend: swing-VCA drive on the noise layer (default 0 = linear)</digest>
<description>
Swing-VCA drive on the noise "reverberation" layer, 0..12 (toms only — congas
have no noise layer). 0 is the calibrated linear model (default); higher engages
the swing VCA's symmetric harmonic saturation riding the noise envelope. See
tap.vca~ (the swing circuit).
</description>
</attribute>
<attribute name="seed" get="1" set="1" type="int" size="1">
<digest>Noise seed (toms; deterministic)</digest>
<description>Seed (&gt;= 1) for the tom noise layer; give mc. instances different seeds.</description>
Expand Down
108 changes: 108 additions & 0 deletions docs/tap.vca~.maxref.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<?xml-stylesheet href="./_c74_ref.xsl" type="text/xsl"?>

<c74object name="tap.vca~" module="msp" category="TapTools, MSP Modifiers">

<digest>
Voltage-controlled amplifier — clean multiply or warm transistor stage
</digest>

<description>
The <o>tap.vca~</o> object is a voltage-controlled amplifier. The left inlet is the
audio; the right inlet is the gain / control voltage — a signal for true per-sample
modulation, or a float to set the <at>gain</at> attribute. The <at>circuit</at>
attribute chooses the character: <b>clean</b> is a pure linear multiply, bit-identical
to <o>*~</o> — transparent, with no added harmonics or DC. <b>warm</b> is the Roland
TB-303's one-transistor class-A VCA stage — the same kernel <o>tap.303~</o> runs — a
biased, slope-normalized tanh applied after the gain: quiet signals stay essentially
clean, while hot signals pick up even-harmonic warmth and gentle compression that track
the control voltage, the way a discrete VCA does and a plain multiply cannot. An output
DC block (the <at>dcblock</at> attribute) sheds the shaped signal's DC offset, as the
hardware's output coupling capacitor does. The <at>drive</at> and <at>bias</at>
attributes shape the warm circuit (stock at the 303's values). One channel per object;
wrap in mc. for multichannel.
</description>


<!--METADATA-->
<metadatalist>
<metadata name="author">74 Objects</metadata>
<metadata name="tag">TapTools</metadata>
<metadata name="tag">Audio</metadata>
<metadata name="tag">MSP Modifiers</metadata>
<metadata name="tag">Multichannel</metadata>
<metadata name="tag">64-bit</metadata>
</metadatalist>


<!--MESSAGES-->
<methodlist>
<method name="signal">
<arglist/>
<description>In the left inlet, the audio to amplify. In the right inlet, the gain /
control voltage (linear), applied per sample.</description>
</method>
<method name="float">
<arglist>
<arg name="gain" type="float" optional="0" id="1" />
</arglist>
<description>In the right inlet, sets the <at>gain</at> attribute. Ignored while a
signal is connected to that inlet.</description>
</method>
<method name="clear">
<arglist/>
<description>Reset the stage's DC-block state.</description>
</method>
</methodlist>


<!--ATTRIBUTES-->
<attributelist>
<attribute name="circuit" get="1" set="1" type="symbol" size="1">
<description>Amplifier circuit: clean (default, the pure linear multiply,
bit-identical to *~), warm (the TB-303 one-transistor class-A stage — asymmetric
biased-tanh saturation with even harmonics, plus the output DC block), or swing (the
TR-808 swing-type VCA — symmetric saturation with odd harmonics and no DC, the same
stage the 808 noise voices use). In warm and swing the warmth and compression ride the
control voltage; bias is ignored by swing.</description>
</attribute>
<attribute name="gain" get="1" set="1" type="float" size="1">
<description>Linear gain applied when no signal is connected to the right inlet. A
signal in the right inlet overrides this per sample.</description>
</attribute>
<attribute name="drive" get="1" set="1" type="float" size="1">
<description>Drive into the transistor stage's tanh for the warm and swing circuits
(0.1-12; default 2, the stock 303 value). Higher drive means more harmonics and more
compression. Ignored by the clean circuit.</description>
</attribute>
<attribute name="bias" get="1" set="1" type="float" size="1">
<description>Warm-circuit operating-point bias (-2..2; default 0.3, the stock 303
value). The asymmetry that produces even harmonics; 0 is a symmetric (odd-only)
shaper. Ignored by the clean circuit.</description>
</attribute>
<attribute name="dcblock" get="1" set="1" type="int" size="1">
<description>Warm-circuit output DC block (default on). The biased shaper leaves a
signal-dependent DC offset on AC material; this one-pole coupling high-pass removes
it, as the hardware's output capacitor does. Turn off to hear the raw
asymmetry.</description>
</attribute>
<attribute name="bypass" get="1" set="1" type="int" size="1">
<description>Pass the input through unprocessed.</description>
</attribute>
<attribute name="mute" get="1" set="1" type="int" size="1">
<description>Silence the output.</description>
</attribute>
</attributelist>


<!--SEEALSO-->
<seealsolist>
<seealso name="tap.303~"/>
<seealso name="tap.crossfade~"/>
<seealso name="tap.pan~"/>
<seealso name="*~"/>
<seealso name="gain~"/>
</seealsolist>


</c74object>
Loading
Loading