Skip to content

Revive TapTools: 46 objects on modern Min + C++20, prune the Jamoma corpse#28

Merged
tap merged 40 commits into
mainfrom
claude/revival-md-tasks-vwwpa4
Jun 17, 2026
Merged

Revive TapTools: 46 objects on modern Min + C++20, prune the Jamoma corpse#28
tap merged 40 commits into
mainfrom
claude/revival-md-tasks-vwwpa4

Conversation

@tap

@tap tap commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Summary

Promotes the TapTools revival to the trunk. This branch carries the bulk of the modern revival: 46 objects ported to the Min SDK (Min as a thin wrapper, all DSP as portable C++ with no min-lib dependency, C++20, universal macOS + Windows via CMake/GitHub Actions). It is a clean fast-forward over main.

The large deletion count is the "prune the corpse" step — removal of the dead Jamoma Core/, max-sdk/, objectivemax/, build.rb, and stale .mxo binaries. The legacy tree remains preserved on the legacy (and windows) branches.

What's in here

  • Build foundation — root CMakeLists.txt, min-api submodule, CI on macOS (universal arm64+x86_64, verified) + Windows.
  • Tier 1 (utility/data) — change, prime, sieve, list.index, bits, gang, route, inquisitor, biquadcalc, radians~, random, etc.
  • Tier 2 (simple DSP) — dcblock~, noise~, pan~, crossfade~, split~, autothru~, count~/counter~, zerox~, width~, sift~, random~, pulsesub~.
  • Tier 3 (complex DSP) — adsr~, svf~, comb~, fourpole~, multitap~, limi~, verb~, procrastinate~, shift~, rotate, elixir~, fft.list~, fft.normalize~, fft.binmodulator~, buffer.peak~/snap~/record~.
  • Infrastructure — midimapper, folder (modernized on std::filesystem).
  • Jitter (3/5) — jit.sum, jit.proximity, jit.ali.
  • Each object ships its reference page (docs/) and help patcher (help/).

Follow-ups (not in this PR)

🤖 Generated with Claude Code

claude added 30 commits June 13, 2026 17:11
Catalog the full historical object set (current source, retired-but-documented,
retired-in-history, and Jamoma-migrated) plus the modernization assessment:
dead Jamoma dependency, dead Travis/Xcode build, non-arm64 binaries. Records
the locked decisions (macOS+Windows, cut Jamoma, CMake) and a staged path.
Stand up the proof-of-life build that replaces the retired Ruby/Xcode + Travis
toolchain:
- Root CMakeLists.txt driving a Min-DevKit package (min-api as submodule).
- Builds universal macOS (arm64+x86_64) binaries by default; the old i386/x86_64
  externals do not run on Apple Silicon.
- source/projects/tap.change: faithful standalone reimplementation of tap.change
  on the Min API in C++20 (no Jamoma). Also fixes the original's reversed
  list-storage memcpy. C++20 set per-target (Min defaults to C++17).
- GitHub Actions CI building + verifying externals on macOS and Windows.
- Remove the dead, never-initialized jamoma2 submodule.
…in (Windows)

- macOS build error: tap.change used 'max::t_atom' but the namespace is
  'c74::max'; 'using namespace c74::min' does not import it. Fully qualify.
- Windows configure error: max-package.cmake enters the WIN32 verinfo branch
  with an empty PACKAGE_VERSION because no root package-info.json[.in] existed,
  making string(REPLACE ...) fail. Add a proper root package-info.json.in
  (version, universal mac + win x64, package metadata).
…ackaging)

Demonstrates the complete per-object workflow, not just compilation:
- source/projects/tap.dcblock~: Min wrapper, C++20, DSP as portable std C++
  (one-pole/one-zero high-pass, R=0.9997 matching Jamoma's TTDCBlock). Preserves
  the documented interface: bypass, mute, clear. No min-lib dependency.
- docs/tap.dcblock~.maxref.xml: reference page (ported + corrected typo, added
  inlet/outlet docs, modern see-also).
- help/tap.dcblock~.maxhelp: help patcher ported into the package.
- Establishes repo-root-as-package layout (docs/, help/, externals/).
- REVIVAL.md: record the Min-as-thin-wrapper decision and progress log.
The Max SDK sanitizes a project folder's '~' to '_tilde' for the project name,
so add_library(${PROJECT_NAME}.cpp) looked for tap.dcblock_tilde.cpp. Rename
the folder and source to tap.dcblock_tilde (matching C74's min.xfade_tilde
convention); the output binary still maps back to tap.dcblock~.mxo, and the
docs/help keep the ~ name. Record the convention in REVIVAL.md.
….bits

Each ported to Min/C++20 as a standalone object (no Jamoma), with its reference
page (docs/) and help patcher (help/):
- tap.prime: faithful port of Jamoma's TTPrime (next prime strictly greater).
- tap.sieve: pass a value only when it matches; value as attribute + argument.
- tap.list.index: list<->indexed pairs; mode/offset/onebased attributes. Also
  correctly handles lists that begin with a symbol (improves on the original).
- tap.bits: int<->bit-list and ints->matrixctrl conversions.
- REVIVAL.md progress log updated.
…ap.list.index

mode == "literal" was ambiguous between operator==(attribute, symbol) and
operator==(symbol, char*) once the attribute->symbol conversion is considered.
Read the attribute into a 'symbol' local first, then compare against string
literals (unambiguous symbol == const char*).
- tap.random: control-rate float RNG, faithful port of the original LCG.
- tap.crossfade~: equal-power / linear crossfade of two signals; position 0..1
  via signal inlet or attribute. Formulas match Jamoma TTCrossfade 'calculate'.
- tap.pan~: stereo panner, equal-power / square-root / linear curves; position
  -1..1 via signal inlet or attribute. Formulas match Jamoma TTPanorama.
All self-contained (no min-lib, no shared lookup-table global). Each ships with
its reference page and help patcher. REVIVAL.md progress updated.
First generator object (sample_operator<0,1>). White/pink/brown/blue are faithful
ports of Jamoma's TTNoise (LCG white source + the same colouring filters, with the
pink filter's mb6 term correctly included in the sum); gaussian uses a std normal
distribution with mean/deviation. Self-contained, no min-lib. Ships with its
reference page and help patcher. REVIVAL.md progress updated.
Continues the TapTools revival (PR #27) by porting the rest of Tier 1 and
the bulk of the Tier-2 DSP objects off the dead Jamoma dependency onto the
modern Min/CMake build. Each object ships with source + reference page +
help patcher, and every one was verified to compile against the Min/Max SDK
toolchain.

Tier 1 (completed):
- tap.route       flexible router (whole message passed through to matched/
                  unmatched outlet; searchstring/searchpositions/partialmatch)
- tap.gang        4-in/4-out deferred fan-out (per-outlet queue + change
                  detection to break feedback loops)
- tap.radians~    hz/radians/degrees converter (signal + float outlet)
- tap.inquisitor  query another object's attributes via the Min patcher API
- tap.biquadcalc  RBJ Audio EQ Cookbook coefficient calculator for biquad~

Tier 2 DSP:
- tap.split~      route a signal to one of two outlets by value range
- tap.autothru~   automatic signal pass-through
- tap.width~      pulse-width meter (ms)
- tap.count~      gated sample counter
- tap.counter~    signal-transition counter
- tap.zerox~      zero-crossing counter (faithful port of TTZerocross)
- tap.random~     signal-triggered sample-and-hold RNG (per-vector edge test
                  from the original fixed to per-sample)

All DSP is portable C++ (no min-lib, no Jamoma). Reference pages and help
patchers were ported from the legacy package. tap.sift~ and tap.pulsesub~
are deferred with reasons recorded in REVIVAL.md (dual-mode outlet, and an
ADSR dependency, respectively).
Faithful port of Jamoma's TTAdsr (linear / exponential / hybrid curve
modes), triggered by the trigger attribute or by a signal crossing 0.5 on
the inlet. DSP is portable C++ (no Jamoma). Defaults to hybrid mode to match
the original's actual sound (the legacy Max wrapper's struct reported
"linear" but the underlying unit always ran hybrid). Reference page and help
patcher ported from the legacy package. Compile-verified against the
Min/Max SDK toolchain.

Unblocks the eventual port of tap.pulsesub~ (which is built on ADSR).
Faithful port with both original modes: mode 0 (default) emits the surviving
changed sample values as floats out a control outlet; mode 1 passes the
signal through with sifted samples replaced by the held value. The single
outlet is created to match the mode at instantiation (signal vs control),
using vector_operator with a dynamically-created outlet. The float dump uses
an SPSC ring buffer drained on the main thread via a queue, mirroring the
original's ring-buffer + clock approach. Compile-verified against the
Min/Max SDK toolchain.
Faithful port of the self-contained 3D rotation object (algorithm by Stephan
Moore) — rotates parallel x/y/z coordinate lists by Euler angles in degrees.
Pure data object, no Jamoma dependency. Reference page ported from the legacy
source folder (no help patcher exists upstream). Compile-verified.
Faithful port of the ttblue tt_svf + tt_lfo + tt_ramp trio as one
self-contained vector_operator: stereo Chamberlin SVF (lowpass/highpass/
bandpass/notch/peak, 2x oversampled), a vector-rate LFO (sine/triangle/
square/ramp) modulating the cutoff, and a portamento ramp on frequency
changes. The LFO is computed directly from a phase accumulator (equivalent
to the original wavetable) and denormals are flushed. All DSP is portable
C++ (no Jamoma). Compile-verified; audio behavior still needs runtime
validation in Max (as with the rest of the revival).
Faithful port of the ttblue tt_comb as a self-contained vector_operator:
circular delay buffer, feedback with a one-pole lowpass in the loop, optional
autoclipping, and ms/seconds delay+decay controls (delay drivable by signal).
Defaults feedback to the original's audible default (0.9) and guards the
feedback<->decay conversions against the original's undefined-when-unset
edge cases. Portable C++, no Jamoma. Compile-verified; audio behavior needs
runtime validation in Max.
Faithful port of Jamoma's TTPulseSub composite (phasor -> duty-cycle offset
-> ADSR -> multiply by input) as a single sample_operator. An internal pulse
train at 'frequency' with duty cycle 'length' gates a repeating ADSR
envelope (linear or exponential) applied to the input gain. Portable C++,
no Jamoma. Compile-verified.
Faithful ports rebuilt on Min's buffer_reference/buffer_lock (binding, the
'set' message, and notifications handled by the framework):

- tap.buffer.peak~   report the frame index of the hottest sample (control).
- tap.buffer.snap~   snap a ms/samples position to the nearest zero crossing
                     (vector_operator; raw interleaved access mirrors the
                     original incl. its +1/+2 round-off hacks; the original's
                     post-clamp loop-termination bug is fixed so the search
                     always terminates).
- tap.buffer.record~ smooth fade-windowed recording into a buffer~ with a
                     normalized sync output (vector_operator writing via
                     buffer_lock + dirty()).

Portable C++, no Jamoma. All compile-verified against the Min/Max SDK.
…r 3)

The 2015 original used jamoma2's LowpassFourPole, whose source is not in the
repo (the jamoma2 submodule was never populated), so this is a faithful-intent
reimplementation: a Stilson/Smith Moog-ladder 4-pole resonant lowpass with
frequency + q controls, processed on two channels. Portable C++. No maxref
exists upstream for this object. Compile-verified.
Faithful port of ttblue tt_multitap as a sample_operator: circular delay
buffer with N taps, each with its own delay (ms) and gain (dB) via vector
attributes. Portable C++, no Jamoma. Compile-verified.
Faithful port of the ttblue tt_limiter as a vector_operator: linked-stereo
look-ahead limiting with an integrated DC blocker, pre/post gain, and linear
or exponential recovery. Adds the standard bypass/mute attributes. Portable
C++, no Jamoma. Compile-verified.
Per-bin FFT normalization (scale by 1/(fftsize/2), negate imaginary, halve
DC + Nyquist) as a vector_operator for use inside pfft~. Implements the
intended DC/Nyquist halving that the original's 0.49-biased equality test
silently disabled. Portable C++. Compile-verified.
Gathers spectral values (indexed by the bin-index inlet) into a frame and
emits them as a list once per frame, with mult scaling, optional Nyquist
truncation, and autopoll. vector_operator collects on the audio thread; the
list is emitted on the main thread via a queue. Portable C++. Compile-verified.
Faithful port of the self-contained N-channel (2-10) mixer: each inlet is
toggle-gated, active inlets share equal gain (1/N), changes are slewed over a
per-inlet or global time, output scaled by 0.95. Variable signal inlets are
created dynamically (the pattern used for sift~'s dynamic outlet); the
original's nine per-count perform routines fold into one variable-width loop.
Portable C++, no Jamoma. Compile-verified.
Faithful port: each FFT bin gets its own LFO (frequency, depth, phase, shape)
modulating its amplitude, a per-bin tremolo. The ttblue wavetable LFOs are
computed directly from per-bin phase accumulators using the 0..1 modulator
waveforms. frequency/depth/phase/shape accept either [index value] pairs or
full lists. vector_operator for use inside pfft~. Portable C++. Compile-verified.
Faithful reconstruction of the ttblue tt_shift meta-object as a self-contained
sample_operator: a phasor sweeps two interpolated delay taps (180 deg apart)
across the recent input, each windowed by the original padded-Welch envelope
(the exact 256-point half-table, mirrored to 512) and summed (overlap-add).
ratio + window_size controls. Portable C++, no Jamoma. Compile-verified.
Faithful reconstruction of the ttblue tt_procrastinate meta-object: four
tap.shift~-style voices chained in cascade, each with a random pitch ratio,
delay, gain, and equal-power pan drawn from configurable ranges and
regenerated on bang. Embeds the padded-Welch window; sample_operator<1,2>
stereo out. Portable C++, no Jamoma. Compile-verified.
Faithful reconstruction of the ttblue tt_verb core: an 18-tap early-reflection
pattern feeds six parallel LFO-modulated comb filters (each with a damping
lowpass in its feedback loop), summed and run through a Schroeder allpass and
an output lowpass, then crossfaded (equal power) with the dry signal and
gain-scaled. Two prime-"deviated" cores (the original's deviate() reuses the
prime helper) give a decorrelated stereo image. Includes DC-block and clip
stages. The optional look-ahead limiter and internal oversampling stages of
the original wrapper are noted as a follow-up. Portable C++, no Jamoma.
Compile-verified.

With this, all of Tier 3 is complete.
claude and others added 10 commits June 14, 2026 00:42
Folds the tt_limiter algorithm (shared with tap.limi~) into verb~'s output
stage with limit/limiter_threshold/limiter_lookahead/limiter_release
attributes, matching the original wrapper's default (limit on). Only the
original's internal oversampling (off by default) remains unported.
Compile-verified.
The modern build is fully self-contained on min-api, so the dead weight is
gone now that every object is ported:
- Core/ (old Jamoma), source/ttblue/, and all legacy source/tap.* wrappers
- the legacy TapTools/ package (docs/help already live in docs/ + help/; its
  .mxo binaries were i386/x86_64 only and don't run on Apple Silicon)
- the old max-sdk/ and objectivemax/ SDK/framework trees
- build.rb and .travis.yml (replaced by CMake + GitHub Actions)

Also drops build_test/ CMake artifacts that were accidentally committed and
adds build_test/ to .gitignore. Everything removed is preserved in git
history. The working tree is now just the modern Min-DevKit package.
…ucture)

Faithful port of the original control-logic object: seven inlets (note,
poly-pressure, control, program, aftertouch, bend, channel) remap a matched
MIDI message to the 'mapto' template, optionally appending the channel and
incoming values per the 'includes' flags, with channel/match1/match2 filters.
Pure logic, no Max MIDI API. Reference page + help patcher restored from git
history (legacy TapTools/ package was pruned). Compile-verified.
…system (infrastructure)

Reimplements the original's make/delete/copy/move operations on portable C++
std::filesystem, replacing the AppleScript (macOS) and Win32-shell code of the
2003 original with identical, cross-platform behavior (bangs the outlet on
completion, errors to the Max console). 'unzip' is intentionally dropped (no
portable std archive support). Reference page + help patcher restored from git
history. Compile-verified.
…Jitter family)

Both rebuilt as plain Min objects that read a named jit.matrix through the
Jitter API (c74::max), since they are matrix->value reductions rather than
matrix->matrix operators:
- tap.jit.sum: sum all cell values (char/long/float32/float64, all planes)
- tap.jit.proximity: nearest 2D point in the matrix to an input coordinate
Docs/help restored from git history. Compile-verified (JitterAPI links on the
mac/win CI; only object compilation is checked locally on Linux).
Faithful port of Ali Momeni's interpolation object as a plain Min object: a
control coordinate looks up per-point weights from a 3D space matrix, which
are normalized and used to interpolate the rows of a data matrix into an
output vector (list-output mode). Reads both matrices through the Jitter API.
The original's optional jit.matrix-output mode is noted as not included.
Docs/help restored from history. Compile-verified.
macOS was failing because tap.folder uses std::filesystem, which requires a
10.15+ deployment target, but Min's min-pretarget.cmake force-pins
CMAKE_OSX_DEPLOYMENT_TARGET to 10.11. Set the floor to macOS 11 (tracking
Max 9's requirement) project-wide by appending -mmacosx-version-min=11.0 for
compile + link in the root CMakeLists — the only reliable override given the
force-pin, since CMake has no per-target deployment property.

Windows was failing because MSVC rejects static_cast<std::string>(symbol):
c74::min::symbol has both operator std::string() and operator const char*(),
which is ambiguous (clang accepts it). Convert via .c_str() in tap.folder and
tap.route (3 sites).

Also declare macOS min_version 11.0 in package-info.json.in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tap tap merged commit c6dbcb8 into main Jun 17, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants