Revive TapTools: 46 objects on modern Min + C++20, prune the Jamoma corpse#28
Merged
Conversation
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.
…(rotate, svf~, comb~)
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.
…and remaining work
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.
…ning (procrastinate~, verb~)
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.
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.
…ining-frontier assessment
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-libdependency, C++20, universal macOS + Windows via CMake/GitHub Actions). It is a clean fast-forward overmain.The large deletion count is the "prune the corpse" step — removal of the dead Jamoma
Core/,max-sdk/,objectivemax/,build.rb, and stale.mxobinaries. The legacy tree remains preserved on thelegacy(andwindows) branches.What's in here
CMakeLists.txt,min-apisubmodule, CI on macOS (universal arm64+x86_64, verified) + Windows.std::filesystem).docs/) and help patcher (help/).Follow-ups (not in this PR)
xtc276) reduces to just the crossfade~/pan~modefix once this lands; merge it (or cherry-picke95a7db) afterward.taptools-minarchive branch: port the uniquetap.sustain~, graftbuffer.record~'s power-of-two fade andsift~'shigh_priorityoption, and adopt the Catch test harness.🤖 Generated with Claude Code