diff --git a/docs/PLAN.md b/docs/PLAN.md index 7be35e9..2f7b35e 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -1390,6 +1390,58 @@ changes duration, so a resampled read is an explicit duration control — but th --- +#### Γ-W1-T7 — `psola-preserve` + +**Goal.** Preserve's splices become pitch-synchronous: the source's fundamental period is +detected offline at sample load, cached, and the splice jump becomes a whole number of that +period, so an aligned landing point exists by construction rather than being searched for. + +**Origin — Daniel's ruling, 2026-08-01, after Γ-W1-T5 landed.** T5's measurement pass showed +the shipped fixed 50 ms OLA window with a ±window/4 correlation search could only reach +landings spanning `[¾w, 1¼w]` — a 1.5:1 span that cannot contain a whole number of periods for +low material. A 30 Hz tone (1470 frames at 44.1 kHz) had no phase-aligned landing at all. A +second, distinct failure mode: splices recur every `window/|rate − shift|` frames and fail +when that interval is shorter than the output period. Three cheaper options were offered and +declined: widening `maxLag` to `window/2` (fixes the geometry, not the cadence); sizing the +window from the note's known fundamental at note-on (fixes both, but trusts root-note +tagging); enlarging the window to ~200 ms (fixes both, smears transients — the OLA crossfade +is `window/4`). + +**Why this is a new track and not an amendment to Γ-W1-T5.** It touches the sample-load / +analysis path, which is outside T5's stated surface boundary (`pitch_shift`, the stretcher +module, `voice`'s Preserve read path). + +**Why PSOLA is affordable here.** ReaSampler is a sampler, so the source is fixed and fully +known at load. Detection runs once during the reload that already happens, entirely off the +audio thread — the usual real-time objection to PSOLA does not apply. + +**Surface boundary — owns:** a new pure module `core/instrument/engine/period_detect` +(two-pass YIN, with its own `period_detect_tests` target), `pitch_shift`'s jump geometry, the +load-time hook in `map/sample_map`'s `buildSampleData`, and `voice`'s note-on. Adds no +`ComponentState` field and takes **no rung of the payload ladder** — a detected period is +derived from the audio, so it is cache, not state. + +**Behavior and constraints.** +- **Enforcement is by link graph, not by convention.** The detector runs off the audio thread + because `sampler_core` does not link `period_detect` — no translation unit on the render path + can name `detectPeriod`. An unknown period restores the fixed-window geometry byte for byte. +- **Dependency it discharges:** it gates the Rate control (Γ-W2-T1) on the plan's own stated + principle that Rate must not ship before its Preserve engine. + +**Status — landed, with open findings.** +- The **geometry** failure mode is closed and asserted. +- The **cadence** failure mode is **not** closed. It was re-characterized rather than fixed: + the previously-headlined 7–21 % artifact-energy readings turned out to be ~95 % the + measurement's own spectral leakage, leaving a real excess of 0.23–0.51 %. The track asserted + no-regression there rather than claiming an improvement. + +**Open questions.** +- **Unresolved review findings, not a design fork.** A later review of the follow-up fold + returned three unresolved Major findings; remediation has not yet been dispatched. The + findings themselves live in the review, not here. + +--- + ### Γ-W2 — New controls, and the overlay's marks **Depends on Γ-W1 for — four dependencies, two of them new:**