The Radiohead family: nine externals, and the kernel pin that carries them - #67
Conversation
The multi-head tape echo: one record head, a span of moving tape, and up to four playback heads along it, summed to stereo. span is the motor -- the delay of a head at the far end of the path -- so it moves the whole layout together and bends pitch on the way, the tap.discreet~ doppler contract. The default four evenly spaced heads are a nominal layout, not measured from any unit; ratios builds a Copicat-style three in one message. It carries the tape family's inversion further, which is why it exists. tap.delay~ caps feedback at 0.99 and tap.discreet~ reaches exactly 1.0; here regen goes past 1.0 into deliberate sound-on-sound self-oscillation, bounded by the drive saturator rather than a gain cap -- and capped back to 1.0 per sample whenever drive is 0, so dropping drive mid-howl lands the loop instead of letting it run away. The kernel's own suite carries the DSP, including the bitwise null test against delay.h's multitap. Here: six min-api scenarios for the wrapper surface (defaults, the past-unity regen ceiling that tap.discreet~ does not have, the head layout reaching the tape, bitwise-dry mix, clear), the reference page, the help patcher, and the submodule pin bumped to the kernel commit that adds tapecho.h. First object of the Radiohead family planned in the kernel repo's book/PLAN-radiohead-family.md; REVIVAL.md carries the entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
The second Radiohead-family object and the one with the most direct lineage to this package: a Max stutter patch arriving as a Max object. The input is captured continuously and, on a step grid, the machine rolls dice and re-fires a slice of what just went past -- density how often it grabs, divisions how finely it chops, repeats how long it holds on, reverse the per-repeat chance of running backwards, jump how far back it may reach. An original design in the brassage tradition, not a port of anyone's patch. The kernel's own suite carries the DSP, including the pinned-dice identity (one outcome possible, so the machine must reduce to exactly a one-step delay, bitwise). Here: five min-api scenarios covering the documented defaults, the dice clamps, and the two contracts a patcher can observe without a scope -- at density 0 the object is a bitwise bypass whatever the seed and whatever the mix, and the same seed replays the same performance through the wrapper, with clear rewinding the stream. Also the reference page, the help patcher, the REVIVAL.md entry, and the kernel pin bumped to the commit that adds stammer.h. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
Two cascaded clipping stages and a bass/contrast/treble voicing section -- the harder, more scooped school beside tap.overdrive~'s feedback soft-clipper. A recreation of a circuit class on the Yeh/Abel/Smith DAFx-07 simplified cascade, not a component model of any one pedal. Five min-api scenarios cover the documented defaults, the control clamps, oversample snapping, the silence-in-silence-out contract that holds even wide open and asymmetric, and that the gain knob actually changes the output. The oversample default of 2 is pinned deliberately: it is a measured choice rather than a typo, since every factor beats 1 by orders of magnitude but the sequence is not monotone and 2 comes out best. Also the reference page, the help patcher, a REVIVAL.md entry that records the two measurement findings (cascade gain staging, and the house oversampling filter being too shallow here), and the kernel pin bumped to the commit carrying fuzz.h. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
The first piece of tap.ondes~ to land, shipping alone because a measured expressive gain law is useful on anything -- as a swell pedal on a guitar, or as the last VCA in any chain. Its contract is unusual for the package: rather than making design choices and measuring them, this object is obliged to reproduce someone else's measurement. Quartier et al. (Acta Acustica 2015) published the six nuance boundaries across the key's travel on ondes No. 320, and the kernel interpolates those seven points to within 6e-5 dB. The wrapper takes position from a signal on the right inlet (the performance surface) or from an attribute, in normalized travel or in the published millimetres. Four min-api scenarios cover the defaults, the clamps, and the two behaviours a patcher would otherwise file as bugs: a key at rest is exactly silent, and so is the bottom 45% of the throw -- which is the key bending before it reaches the powder bag, not a dead spot in the object. The help patcher and the reference page both say so plainly, because the first reaction to that dead travel is reasonably going to be "this is broken". Also the REVIVAL.md entry and the kernel pin bumped to the commit carrying touche.h. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
Three vertical slices over the kernel commit that added diffuseur.h and scrub.h: wrappers, min-api tests, reference pages, help patchers, the pin bumped to match, and REVIVAL entry 22. tap.metallique~ and tap.palme~ are the Ondes Martenot's resonating loudspeakers, as driven resonators rather than struck ones — there is no trigger, the input excites the body continuously, and the moving-iron transducer sits UPSTREAM of the body because that is the order the instrument wires them. Both ship as standalone effects because that is where their value is: run a guitar through the palme. Both reference pages say plainly what is published and what is not — the mode data is Fletcher & Rossing's general physics, not a measurement of Martenot's instruments, and the string tuning and the two nonlinear coefficients are design choices. tap.scrub~ is a granular scrub pad over live capture, with position and pitch as two independent signal inlets — the two axes of a pad. Its tape is tap.stammer~'s capture class itself. The delay null the object rests on is re-pinned at the wrapper so a plumbing mistake cannot hide it, expressed in samples rather than milliseconds because the mock harness does not necessarily report 48 kHz. 14 min-api scenarios across the three. The scrub's reference page states the transposition's warble with its measured numbers rather than leaving a patcher to discover it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
Two vertical slices over the kernel commits that added ondes.h: wrappers, min-api tests, reference pages, help patchers, the pin bumped, and REVIVAL entry 23. tap.triode~ is one common-cathode valve stage solved on its load line, and it is almost entirely a citation: the enhanced Norman Koren model with parameter sets fitted to the valves in ondes Martenot No. 169, plus each published stage's supply, cathode resistor and plate load. Its reference page says which side of that line every number falls on. tap.ondes~ is the voice — a heterodyne envelope detector into two valve stages into the intensity key. A source, not an effect: its help patcher wires a tap.palme~ after it, because that is how the instrument works. The two signal inlets are the two hands, the ribbon is semitones above A1 because the published law is linear in semitones, and nothing quantizes. The wrapper tests earned their keep: one asked for silence at a rest key position and got 20 ms of sound, which turned out to be the voice's anti-zipper setting never reaching the intensity key's own slew. Fixed in the kernel and pinned there. Both objects document what is missing as carefully as what is present — the waveform registers are in none of the sources obtained, and the header, the reference page and the help patcher all say so rather than inventing them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
Picks up the six book chapters for tap.scrub~, the diffuseurs and the Ondes (docs only) and the umbrella header's include sort. No DSP change, so no wrapper change — this keeps the pin on a commit that stays reachable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
Bumps the kernel pin to pick up cascaded 2x resampling in fuzz.h. The single zero-stuff-by-N chain was what made 4x and 8x measure worse than 2x; cascading removes the reversal, and the shipped default of 2x turned out to have been generalized from a single test tone, collapsing above about 6 kHz. The attribute default now tracks kernel::k_default_os rather than a literal, so the two cannot drift again. Wrapper test, object description and maxref follow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
|
The macOS job went red, and it is not this PR. Recording it here rather than leaving it to be inferred from a re-run. What failed: Why it is not mine:
It is a real bug, though, so it is filed rather than shrugged off: #68. There is a second data point — I have re-run the failed job. If it goes green, this PR is unaffected and #68 carries the flake. If Generated by Claude Code |
tap/TapTools#34 merged by rebase, so the branch commits got new SHAs and the old pin (2785d24) will become unreachable when that branch is deleted. The pin now points at 015e1e9 on main, whose tree hash is 3758c1d9 -- identical to the branch tip the PR was reviewed at, so this is a repoint rather than a content change. It also picks up the one kernel commit the pin was behind by, which is docs-only: the three Radiohead-family recipes and the register source hunt. All nine wrapper tests re-run against the new pin and pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
|
tap/TapTools#34 has merged, so the merge-order step this PR's description called for is done: the pin now points at It merged by rebase, so the branch commits got new SHAs. All nine wrapper tests re-run against the new pin and pass. This PR is now ready on its own terms. Generated by Claude Code |
What this changes
Adds nine Max externals — the Radiohead family — each as a full vertical slice: wrapper,
CMakeLists.txt, unit tests, reference page and help patcher. Bumpssubmodules/taptoolsto the branch carrying their kernels (tap/TapTools#34, which must merge first).tap.tapecho~tap.stammer~tap.fuzz~tap.scrub~tap.touche~tap.metallique~/tap.palme~tap.triode~tap.ondes~Why
REVIVAL.mdhad the port consolidated onmainwith the frontiers named; this is the first wave of new objects rather than revivals, and it follows the split the repo is built around — every line of DSP lives in the kernel repo, and each.cpphere is Min glue and nothing else. The largest wrapper is under 230 lines and most of that is attribute documentation.Two of the nine are deliberately standalone rather than folded into
tap.ondes~: the diffuseurs are resonating loudspeakers, and a resonating loudspeaker does not care what drives it — a guitar intotap.palme~is the best argument for shipping them separately.Verification
Built and run here (Linux):
pre-commitclang-format clean on every commit.Wrapper tests earn their keep here — they are not duplicates of the kernel's.
tap.ondes~'s test asserts the thing a patcher would file as a bug, that a key at rest is exactly silent, and it failed, exposing a real kernel defect:voice::set_smooth_msnever forwarded totouche::key, so a key at zero kept sounding for 20 ms. Fixed in the kernel with its own scenario. The two-layer split doing exactly what it is for.Not done here, stated plainly:
runtime-tests/patchers/*.maxtest.maxpatstarters exist for them yet. The help patchers are generated and structurally validated (every patchline endpoint checked against declared inlet/outlet counts) but have not been opened. This is the real gap in this PR and it needs a Mac..mxo, no.mxe64— so the macOS universal check and the Windows build are genuinely first exercised by CI on this PR.Notes for the reviewer
Submodule pin moved.
submodules/taptools→ the tip of the matching branch in tap/taptools. Merge The Radiohead family: nine kernels, twelve chapters, three recipes TapTools#34 first, then this pin needs repointing at the identical tree onmainso it stays reachable after that branch is deleted.Contract change:
tap.fuzz~'s@oversampledefault moves 2 → 4. The kernel's resampler was rebuilt as a cascade of 2× stages (the single zero-stuff by N was making more oversampling measure worse), and separately the old default turned out to have been generalized from a single test tone at 3733 Hz — swept properly, 2× collapses above about 6 kHz. The attribute default now readskernel::k_default_osrather than a literal, so the two cannot drift apart again.tap.fuzz~has only ever existed on this branch, so no released patch is affected.Max/Pd package. A reference page and a help patcher ship for all nine. The macOS universal check is unchanged and still enforced by CI.
Some of these link
tap::dsp, some do not.tap.tune~and the spectral trio already did; nothing new here needs it — the family's kernels are stdlib-only, including the ondes tube solve.Naming.
tap.metallique~andtap.palme~take the instrument's own French names for its diffuseurs, which are descriptive rather than anyone's trademark. The scrub deliberately avoids the obvious product association in its name, following the Bloom → garden precedent.REVIVAL.mdcarries entries 18–23 for this wave; its progress log is current as oftap.ondes~.Generated by Claude Code