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
82 changes: 82 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# TapTools

[![build](https://github.com/tap/TapTools-Max/actions/workflows/build.yml/badge.svg)](https://github.com/tap/TapTools-Max/actions/workflows/build.yml)
[![Tap House Style](https://github.com/tap/TapTools-Max/actions/workflows/style.yml/badge.svg)](https://github.com/tap/TapTools-Max/actions/workflows/style.yml)

A potpourri of objects for [Max](https://cycling74.com/products/max) — roots back to 1999,
rebuilt in 2026 on a modern toolchain: the [Min SDK](https://github.com/Cycling74/min-api),
CMake, GitHub Actions, macOS universal (arm64 + x86_64) and Windows x64 binaries. All DSP
lives in a portable, Max-free kernel library — [TapTools](https://github.com/tap/TapTools),
pinned here as a submodule — where it is unit-tested, benchmarked, and measured in executed
verification notebooks; the externals in this repository are thin wrappers over those
kernels.

## The objects

Around eighty objects across MSP, control, and Jitter. The headliners, by family:

- **Sources** — `tap.vco~` (polyBLEP virtual-analog oscillator with a seeded,
measured "analog section"), `tap.noise~` (five colors).
- **Filters** — `tap.svf~` (the morphing Simper SVF), `tap.ladder~` (nonlinear ZDF
transistor ladder), `tap.fourpole~`, `tap.diode~` (the 303's diode ladder),
`tap.autowah~` (Snow White envelope filter model), `tap.filter~`.
- **The rhythm machines** — `tap.303~` / `tap.303.seq~` (circuit-informed TB-303 voice and
sequencer), and the `tap.808.*` family: eight TR-808 voice channels recreated circuit
block by circuit block, calibrated against a real unit, plus `tap.808.seq~`.
- **Pitch and voice** — `tap.tune~` (real-time pitch correction), `tap.harmony~`
(formant-preserving multi-voice harmonizer), `tap.shift~`, `tap.pitchaccum~` (the GRM
shimmer loop), `tap.vocoder~` (24-band channel vocoder).
- **Rooms and strings** — `tap.convolve~` (exact true-stereo partitioned convolution),
`tap.verb~` (algorithmic Moorer reverb), `tap.5comb~` (the GRM tuned comb bank),
`tap.comb~`, `tap.delay~`, `tap.multitap~`.
- **The pedalboard and the spectral set** — `tap.overdrive~` (voiced feedback overdrive),
`tap.nr~` (per-bin spectral gate), `tap.spectra~` (bin remapper), `tap.fft.*`.
- **Dynamics, utility, Jitter** — `tap.limi~`, `tap.vca~`, `tap.adsr~`, `tap.crossfade~`,
`tap.pan~`, and a set of control and `tap.jit.*` objects.

Every DSP object ships its vertical slice: a reference page in `docs/`, a help patcher in
`help/`, and — for the kernel-backed objects — tests and measurements in the kernel repo.

## Documentation

- **[*Tools on Tap*](https://tap.github.io/TapTools/)** — the field guide: one chapter per
object family, machine appendices deriving each kernel, and a Recipes part that patches
the objects at famous records. Its standing promise: every performance claim is
measured, not remembered.
- **Reference pages and help patchers** — in Max, as usual.
- **[`REVIVAL.md`](REVIVAL.md)** — the authoritative roadmap and progress log of the 2026
revival, object by object.

## Installation

Clone (or download) into your Max Packages folder and build the externals (below), or grab
a built release when releases are cut. Requires Max 9+.

## Building from source

```sh
git clone https://github.com/tap/TapTools-Max.git
cd TapTools-Max
git submodule update --init --recursive
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
```

Built externals land in `externals/` (`.mxo` on macOS — universal by default — and
`.mxe64` on Windows; on Windows configure with `-A x64`). CI builds both platforms on
every push and fails if a macOS binary is not universal.

The DSP kernels build and test standalone in their own repo:
`cmake -S submodules/taptools -B build-kernel && ctest --test-dir build-kernel`.

## Testing

Three layers: the kernel repo's Catch2 batteries (DSP correctness, oracle-measured),
min-api wrapper tests here (attributes and message plumbing, run via `ctest`), and a
`runtime-tests/` harness that loads the real externals in a licensed Max for in-Max
validation. See `REVIVAL.md` for per-object status.

## License

[BSD 3-Clause](LICENSE). © 2002–2026 Timothy Place and the TapTools contributors. The
kernel library and its DspTap primitives are MIT-licensed in their own repositories.
25 changes: 25 additions & 0 deletions REVIVAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -1411,6 +1411,31 @@ maxtest** shipped (`runtime-tests/patchers/tap.tune~.maxtest.maxpat`, generated
this object: the in-Max validation pass itself (help patcher + maxtest under a licensed
Max), like the rest of the DSP set.

**13. Net-new object — `tap.harmony~` (2026-08-05).** ✅ **v1 shipped** — formant-preserving
multi-voice harmonizer (the keyboard-harmonizer effect: up to four pvoc voices at fractional-
semitone intervals over a kernel-aligned dry path, formants preserved by default, `chord`
message, `glide` portamento). Kernel: `taptools/harmonizer.h` (`tap::tools::harmony`),
composing DspTap's `pvoc` (Laroche–Dolson peak locking + LPC formant correction) — seven
Catch2 scenarios under the yin oracle, capi + ctypes bridge. Origin story: the *Tools on Tap*
Recipes drafting flagged the gap ("Hide and Seek" is a harmonizer, not a vocoder — see
`plans/recipes-improvements.md` §10). Build verification landed the same day: CI built the
wrapper green on both platforms (macOS universal, lipo-checked, and Windows x64) alongside
the full external set, with clang-format/clang-tidy clean. Remaining for this object: the
in-Max validation pass and a runtime maxtest (licensed-Max, on-Mac), and the v1.1
house-machinery items in the improvements plan (mute/bypass, 16-slot preset morph).

**14. `tap.adsr~` rebuilt as virtual-analog (2026-08-05).** ✅ **Shipped** — the envelope's DSP
moved to the kernel repo (`taptools/adsr.h`, `tap::tools::adsr::generator`) and gained a
circuit-model `analog` default: truncated RC attack toward a 1.4× overshoot target (CEM 3310
architecture), asymptotic decay/release (95 % closure at the knob time, pinned), retrigger from
the current level. The Jamoma TTAdsr curves survive verbatim as `hybrid`/`linear`/`exponential`
modes. The family trigger contract lands: `threshold` (default 0.005 — a seq row's plain 0.01
now registers; the hard-coded 0.5 gate is retired) and `velocity` sensitivity (gate amplitude
scales the hit; 0 = legacy). **The default mode change is deliberate and documented** — the
recipes-improvements plan §1 carries the reasoning. Kernel: eight Catch2 scenarios + executed
`adsr.ipynb`; wrapper shrunk to Min glue, maxref rewritten (retiring its phantom `int`/`float`
methods), wrapper tests updated. Remaining: in-Max validation.

Remaining (ongoing, now cross-repo — DSP lands in `tap/taptools`, then bump the submodule pin
here): lift the remaining simple inline-DSP objects' math into kernel headers opportunistically as
they're touched. Control/utility and Jitter objects never move — they are Max message-logic, not
Expand Down
126 changes: 64 additions & 62 deletions docs/tap.adsr~.maxref.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,103 +4,105 @@
<c74object name="tap.adsr~" module="msp" category="TapTools, MSP Generators">

<digest>
An ADSR envelope generator
Virtual-analog ADSR envelope generator
</digest>

<description>
The <o>tap.adsr~</o> object
generates a signal-rate envelope and may be triggered at either signal rate or control rate.
More information about ADSR envelopes is available on <a href="http://en.wikipedia.org/wiki/ADSR">this Wikipedia article</a>.
The <o>tap.adsr~</o> object generates an attack/decay/sustain/release envelope. The
default <at>mode</at>, <m>analog</m>, is a circuit model: the attack stage is an RC
charge toward an overshoot target above full scale, truncated at the peak — the classic
analog punch — and the decay and release stages are true RC discharges that taper into
their targets instead of stopping dead. Retrigger always rises from the current level,
like the capacitor it models. The legacy TapTools curves are preserved as the
<m>hybrid</m>, <m>linear</m>, and <m>exponential</m> modes.

A signal at the inlet gates the envelope: it opens above <at>threshold</at> (whose
default hears a <o>tap.808.seq~</o> row's plain hits) and closes below it, and the
gate's amplitude is velocity under the <at>velocity</at> sensitivity — a 303-style
accented 2.0 gate hits up to twice as hard. With no signal connected, the
<at>trigger</at> attribute drives the gate.
</description>


<!--METADATA-->
<metadatalist>
<metadata name="author">74 Objects</metadata>
<metadata name="tag">TapTools</metadata>
<metadata name="tag">Audio</metadata>
<metadata name="tag">Time</metadata>
<metadata name="tag">Generator</metadata>
<metadata name="tag">64-bit</metadata>
</metadatalist>


<!--INLETS-->
<inletlist>
<inlet id="0" name="INLET_NAME">
<digest>Trigger an envelope to begin.</digest>
</inlet>
</inletlist>

<!--OUTLETS-->
<outletlist>
<outlet id="0" name="OUTLET_NAME">
<digest>Audio signal: the generated envelope.</digest>
</outlet>
<outlet id="1" name="OUTLET_NAME">
<digest>Dumpout: for accessing object state.</digest>
</outlet>
</outletlist>


<!--MESSAGES
<!--MESSAGES-->
<methodlist>

<method name="int">
<arglist>
<arg name="input" type="int" optional="0" id="0" />
</arglist>
<description>The absolute(non-negative) value of the input is sent out the output.</description>
</method>

<method name="float">
<arglist>
<arg name="input" type="float" optional="0" id="0" />
</arglist>
<description>Converted to <m>int</m>, unless <o>abs</o> has a float argument. </description>
<method name="clear">
<description>
Reset the envelope to silence and idle; parameter values are kept.
</description>
</method>

</methodlist>
-->


<!--ATTRIBUTES-->
<attributelist>

<attribute name="attack" get="1" set="1" type="float" size="1">
<digest>Attack time in milliseconds</digest>
<description>
Attack time in milliseconds (1..60000). In analog mode this is the time to reach
full scale on the truncated RC charge. Default 50.
</description>
</attribute>

<attribute name="decay" get="1" set="1" type="float" size="1">
<digest>Decay time in milliseconds</digest>
<description>
Decay time in milliseconds (1..60000). In analog mode the stage closes 95% of
the way to the sustain level in this time and keeps tapering. Default 100.
</description>
</attribute>

<attribute name="sustain" get="1" set="1" type="float" size="1">
<digest>Sustain time in milliseconds</digest>
<description>Sustain level in decibels. Default -6.</description>
</attribute>

<attribute name="release" get="1" set="1" type="float" size="1">
<digest>Release time in milliseconds</digest>
<description>
Release time in milliseconds (1..60000). In analog mode the stage closes 95% of
the way to silence in this time, then ends at exactly zero. Default 500.
</description>
</attribute>

<attribute name="mode" get="1" set="1" type="symbol" size="1">
<digest>Determine the shape of the envelope</digest>
<description>Determines the shape of the envelope. The two options are <m>linear</m> and <m>exponential</m>.</description>
<description>
Envelope curve: <m>analog</m> (the circuit model, default), or the legacy
<m>hybrid</m> (linear attack, dB-linear decay/release), <m>linear</m>, and
<m>exponential</m> curves, preserved verbatim from the original object.
</description>
</attribute>

<attribute name="trigger" get="1" set="1" type="int" size="1">
<digest>Trigger a note on or off when no signal is connected to the inlet</digest>
<description>Triggers a note to on or off when no signal is connected to the inlet. If a signal is connected (also a 0 or 1) it overrides this attribute.</description>
<description>
Open the envelope (attack) while on; release it while off. Ignored while a
signal is connected to the inlet. Default off.
</description>
</attribute>
<attribute name="threshold" get="1" set="1" type="float" size="1">
<description>
Gate-open level for the signal inlet (0..1). The default (0.005) sits above the
trigger bus's noise floor and below a sequencer row's plain 0.01 level, so
un-accented hits register. Default 0.005.
</description>
</attribute>
<attribute name="velocity" get="1" set="1" type="float" size="1">
<description>
Velocity sensitivity (0..1). Peak and sustain scale by 1 + velocity × (gate
amplitude − 1): at 1, an accented 2.0 gate hits twice as hard and a half-level
gate lands soft; at 0 (default) the envelope ignores gate amplitude — the
legacy behavior.
</description>
</attribute>

</attributelist>


<!--SEEALSO-->
<seealsolist>
<seealso name="tap.pulsesub~"/>
<seealso name="adsr~"/>
<seealso name="function"/>
<seealso name="tap.vca~" />
<seealso name="tap.808.seq~" />
<seealso name="tap.303.seq~" />
<seealso name="tap.sustain~" />
</seealsolist>


</c74object>
Loading
Loading