The Radiohead family: nine kernels, twelve chapters, three recipes - #34
Merged
Conversation
Drafting record for a family of performed-electronics kernels: the multi-head tape echo, the live stutter rig, the Ondes Martenot and its diffuseurs, the ShredMaster-school fuzz, and the Kaoss-school scrub. Surveyed 2026-08-15 and amended the same day against the Eno components wave before any code: the echo becomes composition over tape_loop.h, the diffuseurs inherit the garden's modal idiom, and the family adopts the components-first delivery template, the seeded-randomness convention, and the Bloom -> garden trademark posture from the start. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
The first kernel of the Radiohead family, and the plan's prediction tested: tapecho.h is composition over tape_loop.h, not a new machine. One reel in delay-line topology, one transport, one wear stage, and up to four playback heads at settable positions along the path -- span_ms is the motor (the delay of a ratio-1.0 head) so a speed change moves every head together, as varispeed does. Where discreet.h lets regeneration reach exactly 1.0 because the wear path is the stabilizer, this one goes past unity into deliberate sound-on-sound howl, bounded by the saturator's 1/drive ceiling rather than a feedback cap -- and capped back to 1.0 per sample whenever drive is 0, since that is the only thing holding the past-unity regime. The head layout is a nominal even spacing, freely settable; no spacings are claimed as measured from any unit. The null test is the load-bearing one: with the tape path neutral, a one-head echo is bitwise delay.h's multitap, pinned in the kernel tests and again across the C ABI in the notebook. Also measured: per-pass generation loss within 0.2% of the analytic wear transfer, wow at 10.91 cents against 10.88 predicted, and every past-unity drive setting plateauing under its ceiling. Ships the layer the family template asks for: 11 Catch2 scenarios, the C ABI plus ctypes surface (TapEcho), the executed tapecho.ipynb, and radiohead_render with four performed scenarios. The Max wrapper and the book chapter are still to come. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
The wrapper, its min-api scenarios, the reference page and the help patcher landed in TapTools-Max alongside the pin bump, so the plan's per-object status now reads shipped end-to-end with only the book chapter and the on-Mac validation pass outstanding. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
The second Radiohead-family kernel, and an original design rather than a port: capture the input continuously, then on a rhythmic grid roll dice and re-fire a slice of what just went past. Brassage tradition (Roads, Microsound); the lineage to the band's own Max rigs informs what the object is for and nothing about what the code does. The planned component split holds: a capture (a tape_loop.h reel with one advancing head) and a slicer that owns every draw and the slice in flight, under a thin machine. Like tapecho.h's head, a slicer needs a capture, so it is documented as a component for composition and testing rather than standalone-external material. The suite and the notebook both lean on a pinned-dice identity: with density 1, whole-step slices, one forward pass and no flank, the machine must reduce to exactly a one-step delay, bitwise. That single identity pins the grid countdown, the slice origin and the playback head together -- much stronger than chasing three off-by-ones separately. Around it: the seeded-performance contract (same seed bit-identical, a different seed changes 89% of samples), the garden.h disabled-generator contract (at density 0 the dice are never rolled, so the seed provably cannot matter and the object is a bitwise bypass at any mix), the repeat invariant, the reverse identity, and the flanks' exact edges. The material contract is measured at its premise rather than asserted: slices of a sustained sine are 1.000 alike by magnitude spectrum, slices of a plucked phrase 0.286 -- re-ordering interchangeable things cannot do much, which is why this object wants transients. Ships the family template: 9 Catch2 scenarios, the C ABI plus ctypes surface (Stammer), the executed stammer.ipynb, and three radiohead_render scenarios including the performed disintegration. The Max wrapper and the chapter are still to come. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
The wrapper, its min-api scenarios, the reference page and the help patcher landed in TapTools-Max alongside the pin bump, so two of the family's five objects now read shipped end-to-end. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
Four chapters in a new Part V, "The machines you ride" -- the name the family thesis earned: where the Eno part is systems you set up and walk away from, these are objects whose point is the performance surface. User-facing: "Four heads and a motor" (tap.tapecho~) opens on that posture difference and treats the bitwise null test against delay.h as the design claim rather than a curiosity; "The part that comes apart" (tap.stammer~) leads with the thing to internalize before patching -- density grabs, repeats holds -- and presents the material contract as a measurement. Machine appendices: tapecho.h is deliberately the book's shortest, because its content is that tape_loop.h needed no changes at all to serve a second topology; stammer.h spends its space on how to pin three interacting integer clocks with one identity, plus the draw order as contract and why not every class boundary is a seam. Figures are measured through the C ABI per the eno.py rule. The occupancy figure's first draft emitted a 117 MB SVG from a per-sample fill_between at 48 kHz; busy stretches are now drawn as spans computed from run boundaries, same picture at 44 KB. Also fixes introduction.md, whose part list had been stale since the Eno wave landed -- it still called Part IV "The spectral set" and stopped at Part IX. It now matches the eleven parts that exist. The drafting record notes that the list is an unchecked second copy of SUMMARY's structure. mdbook builds clean with create-missing = false. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
The sources exist and are identified, with an honest column for how far each was actually read: the Acta Acustica intensity-key study, the Forum Acusticum diffuseur-loudspeaker paper (read in full), the IEEE circuit simulation and its ribbon-oscillator companion, plus the Leipp and Laurendeau primary references they cite. Two design assumptions were corrected by reading rather than by assuming. The touche d'intensite maps displacement, not force, across a 50 dB range per note -- so the control input is a position and only the taper is still open. And the diffuseurs are driven, not struck: the metallique is a motor-excited gong and the palme an electromagnet driving twelve strings, so the garden's strike-excited modal idiom applies for the resonator maths but not for the excitation. The early transducer is also inherently nonlinear, which a resonator-only model would miss entirely. Also records that hobbyist build pages give the palme 24 strings where the peer-reviewed source says 12, and that those pages are unciteable here. The gate is clearer but not open: three full texts are still needed and automated retrieval is blocked, which is a manual step rather than something to guess around. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
The OK Computer-era dirt, and a sibling of overdrive.h rather than a replacement: that object is a feedback soft-clipper chasing the TS lineage, this is the harder, more scooped school -- two cascaded clipping stages and a bass/contrast/treble voicing section. The method is Yeh, Abel & Smith's DAFx-07 simplified cascade (conditioning filter -> memoryless nonlinearity -> equalization filter, twice), which supplies the architecture, the justification for approximating the diode limiter's moving-pole ODE with a static curve between fixed filters, the curve family, and the reason asymmetry exists. It is a recreation of a circuit class: no component value or corner is claimed as measured from any unit, and the voicing constants are the sound of the object. Two defects the measurements caught. The first cut had the second stage fully clipped at gain 0 -- the knob did nothing over most of its travel -- because the tanh family's small-signal slope compounds across a cascade; retuned, the harmonic ratio now sweeps 0.010 to 0.358. And the house 4th-order oversampling filter made aliasing *worse* at 4x and 8x than at 2x, so this kernel uses 8th order, which restores the monotone improvement the setting promises. Two test-design errors are recorded in the suite because both are easy to repeat: an alias test whose tone divided the sample rate, so every fold landed on a harmonic and was invisible; and probe frequencies near enough the fundamental to measure window leakage instead. The test now asserts only what is true -- two orders of magnitude against no oversampling, and deliberately no ordering among 2x/4x/8x, where the residual is -60 dB and noise dominates. Nine Catch2 scenarios plus the C ABI and ctypes surface. The notebook, a render scenario, the Max slice and the chapter are still to come. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
Adds the executed fuzz.ipynb and three radiohead_render scenarios (the gain sweep, the voicing section, and the knee sharpening into the even harmonics). Building the notebook caught an overclaim in the kernel header, the plan and the previous commit message: 8th-order anti-aliasing was described as restoring the monotone improvement that oversampling promises. It does not. Measured against a 3733 Hz tone, fold energy runs 1.2e-1 / 2.7e-5 / 7.4e-4 / 1.8e-3 at 1x / 2x / 4x / 8x -- every factor is worth having over none, and 2x is the best of them, so 2x is now the default rather than 4x. Eighth order is still the right filter (it improves 4x about sixfold over the house 4th-order pattern); it just does not do what was claimed. The cause of the non-monotonicity is recorded as open, with one hypothesis tested and ruled out -- biquads going ill-conditioned at the low normalized cutoffs a high factor needs, disproved by an impulse-response check showing clean decay to denormal at every factor -- and the untested one (imaging from zero-stuff upsampling intermodulating in the clipper) named along with the fix it would imply, cascaded 2x resampling. The kernel test already asserted only what was true here, so it needed no change: all factors beat 1x by orders of magnitude, and no ordering among them is pinned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
The full texts arrived, so the source hunt closes. The touche d'intensite is now fully specified and its table is reproduced in the plan, because the table is the specification: 4.5 mm of travel carries the whole 50 dB, the map is memoryless (the paper shows it does not depend on gesture velocity), the taper is distinctly not linear-in-dB against displacement, it is linear in dB against log force over the playable region, and it separates cleanly from pitch. The bigger news is an amendment. The plan assumed a waveform-register VCO built on vco.h. The circuit paper models five coupled stages and finds the oscillators are essentially pure -- about 0.03% second-harmonic distortion -- which is the simplification the authors themselves use to reach real time. The timbre comes from the two triode stages after the demodulator and then from the diffuseur. So the kernel is a clean heterodyne source into a triode-flavoured nonlinearity into the touche curve into the diffuseurs, which is nearer overdrive.h and fuzz.h than vco.h, and a different object than the one sketched. Their full solve runs at 768 kHz and their plugin eats 85% of a laptop core, so the documented simplifications are the route, not a faithful circuit solve. The gate is open in the sense that matters: what remains is design, not sourcing. tap.ondes~ needs a design pass against these findings first. Also records that the papers are open access but sit behind an anti-automation wall with no mirror, and that the wall was not circumvented. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
"The dirt with two stages" places tap.fuzz~ against tap.overdrive~ -- two dirt objects, not competitors -- and spends its length on the three things a patcher can act on: the knee as a character control, why the gain floor sits below unity, and why oversample 2 beats 8. The machine appendix is deliberately about two mistakes, because the DSP is a published recipe followed closely and the failures are the reusable part. Small-signal gain compounds across a cascade, which left the second stage saturated at gain 0 and was inaudible precisely because the object sounded like a distortion at every setting. And the house oversampling filter measured wrong here -- but so did my first explanation for it, so the appendix records the ruled-out hypothesis (biquad conditioning, disproved by an impulse-response check) next to the surviving one rather than leaving a vague note. The two aliasing test-design errors are written up too, since both passed review the first time. Two measured figures via book/figures/radiohead.py, SUMMARY and the introduction's part list updated, and the chapters' drafting record extended. mdbook builds clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
The papers describe a different object than the family plan sketched, so this is the design pass they forced, written before any code. The instrument is heterodyne and its oscillators are measured as essentially pure, so the kernel is a clean source into a triode nonlinearity into the intensity-key gain law into a driven resonator -- nearer fuzz.h and garden.h than vco.h. Synthesizing the difference tone directly is a documented simplification of a published model rather than a shortcut, and the header should carry the number that justifies it: the full port-Hamiltonian solve runs at 768 kHz and its plugin eats 85% of a laptop core. The component split differs from the family's recent objects in a way worth planning for: three of the four parts are independently useful. The touche is a measured, published expressive gain curve that would improve almost any object, and the diffuseurs run anything you feed them, so both should be standalone externals from the start rather than seams discovered later. Work is ordered to front-load those: touche first (fully specified, small, shippable alone), then the diffuseurs, then the triode -- which needs a listening comparison to settle whether to use a published grid-conduction curve or the house tanh family -- and the source last. Records what is still unsourced: diffuseur-specific modal data, and the waveform-register filter shapes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
The first piece of the Ondes Martenot, and the one that is useful on its own: the touche d'intensite as a gain law. The curve is not modelled or fitted -- it is Quartier et al.'s published measurement (Acta Acustica 101(2), 2015, Table II), interpolated with monotone cubic segments through all seven points. 50 dB over 4.5 mm, referenced to 0 dB at full press, and the seven points come back to within 6e-5 dB. Monotone cubic rather than a spline because an overshoot here is a non-monotone gain -- audible as a dip while pressing harder. And interpolation rather than a fit because the shape is the whole point: equal dB steps correspond to displacement steps of 1.0, 0.6, 0.5, 0.4, 0.5, 1.5 mm, so a straight line in dB-against-mm departs by 8.3 dB and throws away the property that makes the key expressive. Two things the tests caught. The normalized domain has to be the physical travel, not the measured band: mapping 0..1 onto 4.3-8.8 mm put position 0 exactly on the first published point while returning silence there. The paper puts gestures at roughly 3-9.5 mm with the measured band inside, so position spans 9.5 mm and the bottom 45% is genuinely silent -- the key bending before it reaches the powder bag. And the dead zone belongs in the lookup rather than the table, because zeroing entries below the floor put a cliff next to it and a query landing on the floor read 4.2 dB low. Eleven Catch2 scenarios, the C ABI plus ctypes surface, the executed notebook, and a render putting the curve against a linear fade and a fade linear in dB -- it is audibly neither. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
Two objects in one pass, both from book/PLAN-radiohead-family.md. diffuseur.h — tap.metallique~ and tap.palme~, the Ondes Martenot's resonating loudspeakers as DRIVEN resonators. garden.h's modal maths carries over; its strike envelopes do not. Five components under two thin cabinets: `mode` (the constant-peak-gain two-pole resonator, unit peak gain at any Q and exact nulls at DC and Nyquist, so the bank needs neither limiter nor blocker), `plate` (eight free-circular-plate modes as beating doublets), `sympathetic` and `harp` (twelve damped waveguide loops), and `transducer` (the moving-iron driver). The signal order is a claim about the instrument and is pinned by a null test: the cabinet is exactly transducer -> body, bitwise, and the reversed wiring differs by 28% of peak. The transducer's bound is 2/saturation rather than the 1/saturation the saturator alone would give, because removing the DC from a hard-driven squared law doubles the worst-case swing. The bodies are recreations of the general physics (Fletcher & Rossing), stated as such: no ondes-specific modal measurement exists in any of the sources. The palme gets twelve strings per the peer-reviewed source, not the twenty-four of the hobbyist build pages. scrub.h — tap.scrub~, a granular scrub pad whose position and pitch are two independent performable signals. The tape is stammer::capture itself, shared rather than copied, as that header's limits promised; the only addition the stutter needed was the fractional read its slices never used. Hann at hop size/2 overlap-adds to exactly 1, so held still at unity pitch the scrub is the input delayed, to 4.4e-16. One real defect found by measurement and fixed: anchoring grains at the position advances their origins at the write head's speed, so transposition applied only inside a grain, the average read rate came back to 1, and a steady tone emerged at its ORIGINAL pitch with a comb around it. The read head is now phase-continuous, wrapped back only after wandering +-1.5 grains (a bound chosen by sweep). A measurement warning is recorded with it: a single-bin probe read the fixed kernel as broken, 0.02 where the band figure was 0.43. Also here: 31 Catch2 scenarios across the two suites, the C ABI and ctypes surfaces (including the bare `Plate` and `Transducer` components), two executed notebooks, four radiohead_render scenarios, and the README's kernel tables brought up to date with the six objects that had fallen out of them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
…urce ondes.h — the last two pieces of the instrument, and the two the family plan had wrong. Three components under a thin composition: `triode` (one common-cathode stage solved on its load line), `detector` (the heterodyne pair and its envelope detector, in closed form), and `voice` (detector into two triode stages into the intensity key). Almost none of this is voiced by ear. Najnudel, Hélie, Roze & Boutin (IEEE/ACM TASLP 28, 2020) name the tube model — the enhanced Norman Koren law — write out its equations, and publish parameter sets fitted to the actual valves in ondes No. 169, along with every stage's supply voltage and cathode resistor. So the triode is a citation rather than a design: the static load-line solution at a published operating point, which is a memoryless nonlinearity in the DAFx-07 sense, so tabulating it is not an approximation of the model but the model itself. The fitted 6C5 lands at 8.85 mA against its datasheet's 8 mA typical. The plan's central instruction for the source was wrong, and catching it is the most valuable thing here. "Synthesize the difference tone directly as a sinusoid" would have thrown away the instrument's largest source of harmonics: the paper's licence to use a sinewave generator applies to the OSCILLATORS, and the demodulator is an envelope detector, not a mixer. The envelope of two summed oscillators is 2|cos|, whose series puts H2 at -14.0 dB and H3 at -21.3 dB before any valve touches the signal. What replaces the carrier is an identity rather than a simplification: the envelope of cos(P) + d*cos(P-p) is exactly sqrt(1 + d^2 + 2d cos p), so the 80 kHz carrier drops out of the arithmetic. Running the published 200 us RC detector on that closed form reproduces the full heterodyne-plus-diode-plus-RC simulation to within 0.10 dB on every harmonic at every pitch, with one uniform 3% level offset. It also makes oscillator balance a real physical timbre control, and the detector's pitch dependence falls out free. Two errors found by measurement and fixed: a stage that quietly un-inverted itself applied the tube's asymmetry to the wrong side of the waveform, so the drive knob REDUCED harmonics as it was turned up; and an oversampling probe that measured its own leakage rather than aliasing. Two things the sources do not settle — where the intensity key sits in the chain, and the winding sense of the transformer between the stages — are switches rather than silent guesses, because both measure as audible. Also: evidence for fuzz.h's open oversampler question. This object runs the same 8th-order chain around a comparably hard nonlinearity but as a SOURCE, with nothing zero-stuffed and therefore no images, and its sequence never reverses where fuzz.h's did. Recorded in both headers as evidence, not proof. 17 Catch2 scenarios, the C ABI and ctypes surfaces (Triode, Detector, Ondes, plus the bare tube law), the executed notebooks/ondes.ipynb, and four render scenarios including the whole instrument through each of its diffuseurs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
`voice::process()` drove the ribbon and the key through their setters, which means the key's 20 ms anti-zipper ramp gets re-targeted on every sample when a signal is driving it — so the key lags the hand by the full slew time instead of tracking it. That is exactly wrong for the one control the instrument is played with. `process(semitones, key_position)` takes both straight from the caller with the ramps bypassed, the way scrub.h and touche.h already do it; the ramps still tick so switching back to the attribute path is continuous rather than a jump. The C ABI's process_mod now routes through it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
touche::key keeps its own anti-zipper slew, and voice::set_smooth_ms was not reaching it — so the one control the instrument is actually played with slewed at 20 ms whatever the caller asked for, and a key set to rest kept sounding for that long. Found by a wrapper test in TapTools-Max asking for silence at a rest position and getting 20 ms of sound; pinned here now so the kernel carries it. prepare() re-applies it too, since touche::key::prepare resets its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
Nine objects shipped over the last three days without chapters. They become six: chapters follow instruments, not externals. User-facing (Part V — The machines you ride): - src/scrub.md — "Two hands on the same tape". Filed beside stammer.md because the two share a tape literally. Carries the delay identity (4.4e-16), the position/pitch decoupling, and the warble measured rather than apologized for (98.8% of a clean shifter's band energy, 92.0% of its focus), including the open question against tap.pitchaccum~. - src/diffuseurs.md — "Loudspeakers you can play". Both diffuseurs in one chapter: driven not struck, the transducer upstream of the body (bitwise null; the reverse wiring differs by 28% of peak), twelve strings not twenty-four, and the line between the peer-reviewed instruments and the recreated bodies. - src/ondes.md — "The instrument that is not a synthesizer". tap.ondes~, tap.triode~ and tap.touche~ together: heterodyne, the demodulator as the largest harmonic source (-14.0/-21.3/-26.4 dB), the closed-form envelope, the ribbon linear in semitones, 50 dB in 4.5 mm, the valve as a citation, and the two controls that are labelled choices. Machine appendices (Part X), each earning its length from what went wrong: - machine/scrub.md — grains anchored at the position cancel the transposition, and a single-bin probe reads the fix as the bug. - machine/diffuseur.md — unit peak gain removing the limiter, the DC blocker and the decay/level coupling in one choice; the 2/saturation correction; the selectivity test that measured its own on/off step. - machine/ondes.md — the stage that needed no design, the detector that is an identity, the sign error that ran the drive knob backwards, three measurements that lied, and the oversampling evidence for fuzz.h's open question. Eight new measured figures in book/figures/radiohead.py, same contract as the rest: the shipping kernels through the C ABI, never illustrations. SUMMARY, the introduction's part list and the README's chapter count follow; PLAN-radiohead-chapters.md carries the drafting record. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
touche.h sorts before the tr808_* block under the house SortIncludes: CaseSensitive, and it was appended after them. This has failed the clang-format job on every commit since tap.touche~ landed; the local pre-commit hook was not installed in this clone, which is why six commits went out without it being caught. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
The recorded "near-total cancellation" in tap.pitchaccum~ (band energy 0.004 at 311 Hz +19 semitones) was an artifact of the metric, not the object. The sweep integrated a fixed +-15 Hz band: about 115 cents wide at 220 Hz but only 26 cents at 932 Hz, so at high transposed pitches the probe was narrower than the shifter's own spread and missed the energy. Widened to a constant 3 %, the two "cancellations" read 0.63 and 0.85 and none exists anywhere on the sweep. This is the mistake machine/scrub.md had just finished warning about, committed one section later, so the appendix now carries the second half of its own rule: a band wide enough in the units the process works in. For a pitch shifter that unit is cents, never hertz. What survives is filed as issue #33: mean 0.907 / worst 0.633 band energy against the scrub's 0.988 / 0.917; the strongest spectral line sitting 5-20 Hz beside the intended pitch; and yin reading up to +35 cents sharp at 110 Hz for the +7 and +19 intervals, which is ambiguous between a real error and a detector artifact and is written up as ambiguous. Corrects book/src/scrub.md, book/src/machine/scrub.md and book/PLAN-radiohead-family.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
The imaging hypothesis was right. fuzz.h zero-stuffed by N in one step and filtered once at 0.45/N normalized (0.056 at 8x), which left N-1 images for a single filter to suppress at a corner that tightened with every doubling; residuals entering the clipper intermodulated into exactly the non-harmonic products the alias probe measures. ondes.h had supplied the evidence without being built for it -- same filters, comparably hard nonlinearity, but a source with nothing zero-stuffed, and no reversal. The chain is now one 2x stage per doubling, each filtering at 0.225 of its own operating rate, a corner that never tightens however deep the cascade goes. The reversal is gone: worst step-up past 2x is a ratio of 1.017, where the old chain ran up to 3x worse per doubling. Where 4x and 8x were merely adequate they are now two to four orders of magnitude better (5171 Hz at 8x: 2.3e-3 to 1.9e-7). The 2x column is unchanged, as it must be -- one doubling is one stage either way, which is the best available check that nothing else moved. Cost: 3.16 % of a core at 8x against 3.02 %. A second, larger error surfaced doing it. Every number in the original write-up came from a single test tone at 3733 Hz, where 2x happens to look best. Swept across nine tones, 2x collapses above about 6 kHz and at 10499 Hz measures worse than no oversampling at all, because the clipper's low harmonics already exceed the base Nyquist there. The shipped default of 2x was safe only for material below 6 kHz. The default is now 4x, and 8x earns its keep above about 7.5 kHz where harmonics start folding inside the 4x band. The aliasing scenario now asserts the property the cascade delivers -- the sequence never reverses -- and that 4x beats 2x at a bright tone, which the old chain would have failed. fuzz.ipynb SS5 is re-executed with the nine-tone sweep and the probe's own floor plotted. The chapter, the appendix (whose "hypothesis that died" section becomes "a hypothesis that was right", plus a new "one tone is not a sweep"), ondes.h's cross-reference and both plans follow. Still open, unchanged: whether overdrive.h is owed the same change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
The recipes are what make the family a rig rather than nine objects: - recipes/tape-and-stutter.md -- fuzz into stutter into tape echo, and why that order (a stutter of a distorted signal is a stutter; a distortion of a stuttered signal amplifies every slice edge). Carries the occupancy numbers, the seed contract, the bitwise bypass at density 0, and the material contract. - recipes/scrub-pad.md -- the two-axis controller the object needs before it means anything, freeze as a six-step performance, and the one real constraint (keep the position at least size*(rate-1) back, or the grain tail runs off the front of the tape). - recipes/ondes-rig.md -- the assembled instrument. Most of its length is the two hands rather than the settings, because the ribbon being linear in semitones and the key's bottom 45 % being silent are the two facts that decide whether it sounds like an ondes or like a synthesizer. Writing them caught a documentation error the chapters had missed: a first draft set the stutter's jump to 0.15, reading it as a probability like reverse. It is milliseconds. Recipes are the only part of the book that must name every control with a legal value, so they check the reference pages in a way prose does not -- noted in the drafting record. Also records the second waveform-register source hunt in PLAN-ondes.md. It did not close the gate, but it leaves the next attempt further along: Leipp's Bulletin du GAM n.60 (1972) is confirmed to exist and to be held in the Catgut archive but is digitized nowhere reachable; the ribbon-oscillator companion paper is out of scope by its own title; and the best new lead is the patents (the 1928 "Perfectionnements aux instruments de musique electriques" and FR 841.128 of 1939), which are published, expired and schematic-bearing but which Google Patents and Espacenet both refused to serve here. The abundant hobbyist descriptions of the register waveforms are recorded as a trap rather than a source: none is published literature and none gives a filter shape, so implementing from them would break both the IP policy and the promise the header makes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HKD7onDgpfjRi2PA8mhn5
tap
pushed a commit
to tap/TapTools-Max
that referenced
this pull request
Aug 18, 2026
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
pushed a commit
to tap/TapTools-Max
that referenced
this pull request
Aug 18, 2026
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
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.
What this changes
Adds the Radiohead family — nine objects across seven new kernel headers — with their tests, C ABI, executed notebooks, render scenes, book chapters and recipes. Also fixes
fuzz.h's long-open oversampler question, and retracts a measurement this repo had recorded againsttap.pitchaccum~(now #33).tapecho.htap.tapecho~stammer.htap.stammer~fuzz.htap.fuzz~scrub.htap.scrub~touche.htap.touche~diffuseur.htap.metallique~,tap.palme~ondes.htap.triode~,tap.ondes~Why
The family's thesis is the control is the instrument — objects you ride rather than set up and walk away from — which is what earns them a part of their own in the book rather than being filed by topology next to
tap.discreet~.Two of them exist because reading the sources changed the plan. The family plan assumed the ondes Martenot was an oscillator with waveform registers; it is heterodyne, its oscillators measure at about 0.03 % second harmonic, and its largest single source of harmonics is the demodulator's
2|cos|envelope — 14.0 dB down at H2 — before any valve. The plan's instruction to "synthesize the difference tone directly as a sinusoid" would have thrown that away. And the diffuseurs are driven, not struck, sogarden.h's modal maths carries over while its strike envelopes do not.The
fuzz.hfix is the payoff from a cross-file observation:ondes.hruns the same 8th-order filters around a comparably hard nonlinearity but as a source with nothing zero-stuffed, and its oversampling sequence never reversed. That pointed at imaging as the culprit in the fuzz, and acting on it confirmed it.Verification
Built and run here:
ctest— 322 test cases, 1 758 389 assertions, all passing. New:tapecho_test.cpp,stammer_test.cpp,fuzz_test.cpp,touche_test.cpp(11 scenarios),scrub_test.cpp(13),diffuseur_test.cpp(18),ondes_test.cpp(18).clang-format-18 --dry-run --Werrorover the same file set CI checks: clean.taptools_capi, zero errors and zero warnings.mdbook build: clean, and every internal link and image path insrc/resolves (checked with a script).radiohead_renderbuilt and all 19 scenes rendered and listened to.book/figures/radiohead.pyre-run end to end; the six pre-existing SVGs came back byte-equivalent modulo timestamps, so only the eight new ones are in the diff.Load-bearing measurements, each pinned twice (executed notebook cell + Catch2 scenario):
tapecho.h— head returns land onspan × ratio; bitwise equality withdelay.h's multitap when the tape path is neutral; regeneration past unity self-oscillates but stays under|in|max + regen/drive; wow 10.91 cents measured against 10.88 predicted.stammer.h— pinned-dice one-step-delay identity, bitwise; occupancy 41 / 76 / 90 / 96 % across four density/repeat pairs; different seeds change 89.4 % of samples; slice similarity 1.000 (sine) against 0.286 (played phrase).scrub.h— held still at unity pitch the object is the input delayed, to 4.4e-16; transposing retains 98.8 % of a clean shifter's band energy (worst 91.7 %) at 92.0 % of its concentration (worst 75.0 %).diffuseur.h— a cabinet is bitwisetransducer → body, and the reverse wiring differs by 28 % of peak; plate weights sum to exactly 1 with unit peak gain per mode; all twelve palme strings ≥ 4.4× selective.ondes.h— the closed-form envelope matches a full 80 kHz heterodyne-plus-diode-plus-RC simulation to within 0.10 dB on every harmonic (uniform 3.0–3.2 % level offset); H2 runs −14.0 dB at A2 to −19.3 dB at A6;drivesweeps THD 0.221 → 0.344 monotonically.touche.h— the published Quartier et al. table interpolated, not fitted: 50 dB from 4.3 mm to 8.8 mm, exact silence below.fuzz.h— the new cascade's worst step-up past 2× is a ratio of 1.017 (was up to 3× worse per doubling); 4× and 8× improved by two to four orders of magnitude; 3.16 % of a core at 8× against 3.02 % before.Not done here, stated plainly:
*.maxtest.maxpatstarters exist for them. That gate lives in TapTools-Max and needs a Mac; it is the one place where "tested" is genuinely incomplete for this work.Notes for the reviewer
Contract change:
fuzz.h'soversampledefault moves 2 → 4. Two separate errors made the old default wrong. The chain zero-stuffed by N in one step and filtered once at 0.45/N (0.056 normalized at 8×), which left N−1 images for one filter and made more oversampling measure worse; it now cascades one 2× stage per doubling at a corner that never tightens. And every number behind the old default came from a single test tone at 3733 Hz, where 2× happens to look best — swept across nine tones, 2× collapses above about 6 kHz and at 10499 Hz measures worse than no oversampling at all. No released consumer is affected:tap.fuzz~has only ever existed on this branch.Submodule pin. No pin moves in this repo. The consumer PR in TapTools-Max moves
submodules/taptoolsto this branch's tip — after this merges by rebase/squash, that pin needs repointing at the identical tree onmainso it stays reachable once the branch is deleted.Notebooks re-executed, because behaviour changed. They are committed executed.
A retraction rides along, and it is the most reviewable thing here. This repo previously recorded a near-total cancellation in
tap.pitchaccum~(band energy 0.004 at 311 Hz +19 semitones). It does not survive rechecking: the sweep integrated a fixed ±15 Hz band, about 115 cents wide at 220 Hz but only 26 cents at 932 Hz, so at high transposed pitches the probe was narrower than the shifter's own spread. Widened to a constant 3 %, those points read 0.63 and 0.85. What survives is filed as tap.pitchaccum~: the shifted line sits beside the intended pitch, not on it #33 and is smaller and honestly ambiguous.src/machine/scrub.mdnow carries the second half of its own rule — a band wide enough in the units the process works in — because the mistake was committed one section after writing the warning against it.A defect found by a wrapper test, not a kernel test.
voice::set_smooth_msnever forwarded totouche::key, so an ondes key at rest kept sounding for 20 ms. The kernel suite tests DSP promises; the wrapper suite tests what a patcher observes, and those are not the same set. Fixed here with its own scenario.Provenance, since most of this is recreation rather than port. Fletcher & Rossing for the plate and string physics; Roads for the granular tradition; Yeh/Abel/Smith DAFx-07 for both distortion cascades; Koren 1996 with Cohen & Hélie 2010 for the tube, with parameters transcribed from Najnudel et al. 2020 Table II; Quartier et al. 2015 for the intensity key; Wijnand et al. 2023 for the diffuseurs. Where a source stops, the header says so rather than filling the gap: the diffuseur bodies are labelled recreations of the general physics because no ondes-specific modal measurement exists in any source obtained, and the ondes waveform registers are deliberately absent — a second source hunt is recorded in
book/PLAN-ondes.md, including the two leads worth trying next (Leipp's Bulletin du GAM n°60, and Martenot's patents).Book. Six user-facing chapters and six appendices across Parts V and X, plus three recipes in Part XI.
introduction.md's part list is a second copy of SUMMARY's structure with nothing checking them against each other, so it was updated by hand — worth knowing for the next part insertion.Generated by Claude Code