tune.h — the pitch-correction kernel (backends, formant, auto-key), notebook, and book chapters#20
Merged
Merged
Conversation
Classic real-time pitch correction (the public-domain 1998 pipeline): the shared DspTap YIN detector feeds a scale/key mapper and a two-tap delay-line transposer — the tap.shift~ / pitchaccum engine (Hermite taps, complementary sin^2/cos^2 envelopes) with its grain window locked to an even multiple of the detected period. Period-locking keeps the taps an integer number of periods apart, which measurement showed is what makes the average retune ratio exact (a fixed-ms window clamp biased the pitch ~5 cents); the even-multiple rule preserves the lock when two periods would fall under the minimum window. Controls: key + 12-bit scale mask (presets provided) with per-note enables, MIDI-target mode (snaps to the nearest held note), retune speed as an exponential time constant (0 = hard snap), correction amount, detection range/threshold. Unpitched input relaxes to no correction. Allocation-free after prepare(); setters safe while audio runs. Bumps submodules/dsptap to pick up tap::dsp::yin. Tests drive the full chain with synthesized tones and measure output pitch using the DspTap detector as oracle: chromatic snap, scale mask steering, per-note enables, MIDI targeting, retune-speed glide, amount scaling, unpitched relaxation, amplitude sanity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BuUawafae7RR2ySWabuJcs
The corrector's last stage is now backend-selectable behind one interface (enum backend): the existing period-locked two-tap grain engine stays the validated low-latency default; tap::dsp::psola adds true TD-PSOLA (formant-preserving on voice-like material, latency ~2x the deepest detectable period); tap::dsp::pvoc adds the peak-locked phase vocoder (one-frame latency). Detector, scale/MIDI mapper, and retune glide are shared — only the resynthesis swaps. Both alternate backends are constructed at prepare(), so set_backend() is allocation-free and safe while audio runs (the incoming backend starts from cleared state). The detected period now rides its own slew for the PSOLA backend's per-sample period input. Bumps submodules/dsptap to pick up psola.h and pvoc.h. Tests: every backend snaps a 46-cent-sharp sawtooth onto the target within 6 cents at healthy level (saw, not sine — PSOLA resamples the spectral envelope and needs harmonics, per its header contract), and live backend switching stays finite while continuing to correct. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BuUawafae7RR2ySWabuJcs
set_formant() wires DspTap's new LPC formant preservation into the corrector's pvoc backend (the psola backend is inherently formant-preserving and the grain engine waveform-preserving, so the flag only changes the pvoc path). Survives being set before prepare(). Kernel test drives the pvoc backend with formant on and confirms the correction still lands. The C ABI gains tap.tune~'s full corrector (Tune: key/scale/notes, mode, backend, speed, amount, range, threshold, formant, MIDI notes, meters, buffer processing) plus a passthrough of the shared DspTap detector (Yin) so the notebooks track pitch with the same detector the corrector uses. notebooks/tune.ipynb (committed executed) measures the shipping kernel: the retune-speed family (hard snap to transparent) as pitch tracks, the three backends on the same vibrato voice (all land the correction; character and latency differ), and a 5.5-semitone MIDI-mode correction with and without formant preservation. The primitive-level findings live in DspTap's notebooks/pitchshift.ipynb. Bumps submodules/dsptap to the formant-capable pvoc. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BuUawafae7RR2ySWabuJcs
An opt-in key learner on the corrector: every voiced analysis folds its pitch class into a slowly-forgetting histogram (~60 s memory, leak applied per analysis), scored on demand against the published Krumhansl-Kessler major/minor profiles (Krumhansl 1990) by Pearson correlation over all 24 keys. Deliberately learning-only: the estimate never flips the corrector's targets on its own — autokey_apply() adopts it as key + scale when asked, and a mass guard (~0.5 s of voiced material) withholds estimates from silence. autokey_reset() forgets. Tests: a tonic-weighted D major scale reads as D major (confidence 0.95), an A harmonic-minor melody as A minor, the fresh corrector offers nothing, reset withdraws the estimate, apply sets key + scale. C ABI + taptools_py grow the matching autokey surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BuUawafae7RR2ySWabuJcs
Outlines for three chapters covering the tap.tune~ work: the user-facing 'The note you meant' (new Part VI — Staying in tune), and two machine appendices — 'Three ways to move a pitch: yin.h, psola.h, pvoc.h' (the two findings, told as they happened, plus the LPC formant section) and 'The nearest allowed note: tune.h' (the period-lock bug hunt as centerpiece, the backend seam, the auto-key learner). Each section lists the executed notebook cell or pinned test that already carries its measured claims, so drafting needs no new lab work. SUMMARY placement included; the plan file itself stays out of src/ so nothing half-written publishes into the built book. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BuUawafae7RR2ySWabuJcs
Three chapters from the drafted plan. 'The note you meant' (new Part VI — Staying in tune): the user-facing tap.tune~ chapter — retune speed as the instrument, targeting (key/scale, per-note toggles, MIDI mode), the three-backend trade table, formant, the advisory auto-key learner, the pitch outlet, recipes, and the honest when-not list, with the history paragraph handled plainly (expired patent, live trademark, polyphonic fence). 'Three ways to move a pitch: yin.h, psola.h, pvoc.h' (machine): the DspTap primitives as three claims about what a pitched sound is, with both development findings told as they happened — PSOLA's envelope-resampling double face and the naive phase vocoder's measured level collapse, including the frame-relative-modulator phase bug — plus the LPC formant trade. 'The nearest allowed note: tune.h' (machine): the composition appendix with the period-lock bug hunt as centerpiece, the target/glide policies, the backend seam, the key learner's advisory-only design, and the ledger. Every measured number is carried by an executed notebook cell or a pinned test, per the book's standing promise. SUMMARY gains Part VI and the two machine entries (machine part becomes Part VII); the plan file stays as the drafting record, marked drafted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BuUawafae7RR2ySWabuJcs
tap/DspTap#3 merged (rebase); the pin moves from the side-branch SHA to the identical tree on main (b3e9ee5) so it stays reachable after branch cleanup. Full kernel suite re-run against the merged pin: 145 cases pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BuUawafae7RR2ySWabuJcs
tap
added a commit
to tap/TapTools-Max
that referenced
this pull request
Jul 22, 2026
tap/TapTools#20 merged (rebase); the pin moves from the side-branch SHA to the identical tree on main (d5d3dd4) so it stays reachable after branch cleanup. No code change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BuUawafae7RR2ySWabuJcs
tap
added a commit
to tap/TapTools-Max
that referenced
this pull request
Jul 22, 2026
tap/TapTools#20 merged (rebase); the pin moves from the side-branch SHA to the identical tree on main (d5d3dd4) so it stays reachable after branch cleanup. No code change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BuUawafae7RR2ySWabuJcs
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.
The portable kernel behind
tap.tune~: classic real-time monophonic pitch correction (the public-domain expired-patent pipeline), composed from the new DspTap primitives (tap/DspTap#3).include/taptools/tune.h—tune::corrector:prepare(), so every setter is allocation-free and real-time safe.set_formantoption). Switching live is allocation-free and click-safe.autokey_estimate()reports,autokey_apply()adopts; nothing re-aims targets on its own. D-major scale reads as D major at 0.95 confidence.Also included:
Tune+Yinin the C ABI andtaptools_py;notebooks/tune.ipynb(committed executed): the retune-speed family, all three backends landing 220.00 Hz on the same vibrato voice, and the MIDI-mode formant demo.book/PLAN-pitch-chapters.md.submodules/dsptappinned to the Pitch primitives: yin, psola, pvoc (+ LPC formant preservation, C ABI, notebook) DspTap#3 head.Merge order: after tap/DspTap#3; before the TapTools-Max PR (which pins this branch).
🤖 Generated with Claude Code
https://claude.ai/code/session_01BuUawafae7RR2ySWabuJcs
Generated by Claude Code