docs: cascade rulings R2 (band-limiting in scope) and R3 (the Omega snap fence yields)

R2 closes fork F6 IN: complex waveforms must be supported purely, so band-limited
playback enters the near-term scope alongside the loop-grid recalibration. Re-derives
the three post-R1 reliefs it spends — it reaches process(), reopens a parameter door,
and recontends the deck headroom — and mints F9-F12 for the shape it deliberately
does not rule. R3 makes the snap-radius work Phase Omega's own W2-T5 and supersedes
the zero-crossing-snap criterion at its three sites, recording that W1-T5 met it.
This commit is contained in:
2026-08-03 15:37:16 -04:00
parent d5280687f3
commit 5042e2709b
2 changed files with 568 additions and 168 deletions
+465 -133
View File
@@ -10,28 +10,52 @@ ReaSampler 9000 in REAPER on a literal single-cycle 60 Hz sine capture:
> harmonics from the shape distortion. supporting smoothness at this micro scale would turn
> this into a wavetable synth, which is desirable."*
**The immediate defect — `nearestZeroCrossing`'s unbounded fan-out — is owned by a
snap-radius track already in flight and is NOT this doc's subject.** It is described here
only where the reasoning needs it, because the defect and the direction share a diagnosis
and the fix for one does not deliver the other.
**The immediate defect — `nearestZeroCrossing`'s unbounded fan-out — is owned by
Ω-W2-T5 `snap-radius` and is NOT this doc's subject.** It is described here only where the
reasoning needs it, because the defect and the direction share a diagnosis and the fix for one
does not deliver the other.
Status: **framed by product-designer 2026-08-03; ONE ruling has since landed against it —
R1, recorded in full at §7.1.** Daniel, 2026-08-03:
Status: **framed by product-designer 2026-08-03; THREE rulings have since landed against it —
R1, R2 and R3, recorded in full at §7.1.**
**R1 — Daniel, 2026-08-03:**
> *"I don't want actual frame-indexed wave tables yet, that is out of scope. just static
> captures right now."*
R1 **closes F5** (W1, the wavetable-position knob, does not ship now) and **confirms W3 out
from Daniel** rather than only from this doc's argument. **Seven forks remain open** (§7).
from Daniel** rather than only from this doc's argument.
**R1 does NOT rule F6 / band-limiting.** The question that decides F6 — *is the material ever
anything but sine?* — is still outstanding, Daniel knows it is outstanding, and it is
deliberately not pre-ruled anywhere below. Nothing in R1 touches aliasing: a *static*
single-cycle capture of anything but a sine, played across the keyboard, is exactly the case
§1.8 describes.
**R2 — Daniel, 2026-08-03**, answering this doc's own second outstanding question (*is the
material ever anything but sine?*):
**The near-term scope is therefore W0, and only W0.** Read §2 as one body of work plus two
deferrals, not as four equal candidates.
> *"yes, complex waveforms must be supported purely."*
**R2 closes F6 IN, toward band-limiting — W2 is in scope.** *"Purely"* is read here as
**faithfully: without artifacts the capture does not contain**, and §1.8's aliasing is
precisely the artifact that reading names. What that commits to, what shape it can take and
what it costs is worked out at §1.8 and §2's W2 section. **It is the one item in this doc
that reaches `process()`** — the post-R1 claim that the near-term scope was entirely
load-time and UI-thread is **RETIRED**, and §6.6 states what replaces it.
**R3 — Daniel, 2026-08-03**, on the Phase Ω conflict this doc recorded at §6.7:
> *"call it part of omega, that was a fence you built not me"*
**R3 dissolves that conflict rather than sequencing around it.** The snap-radius work IS a
Phase Ω track (Ω-W2-T5); Ω's *"the zero-crossing snap comes out the other side identical"*
criterion was a **scope fence** built to stop Ω's own tracks perturbing the snap while they
fixed the coordinate mapping — never a product ruling that the snap's behaviour was correct —
and Daniel's DAW evidence supersedes it. §6.7 is now a record, not an open item.
**The near-term scope is therefore W0 AND W2** — two bodies of work, one deferral (W1), one
confirmed out (W3). They are not peers: under the recommended shape W2 **consumes W0's
declared period** (§1.8), so the wave order is forced rather than chosen.
**TWELVE forks: ONE settled (F5), ELEVEN open.** R2 narrows F6 to a binary and mints four new
forks — **F9F12**, carrying W2's shape — because the shape is genuinely unruled and is
Daniel's to rule, not this doc's to assume. §7 states a recommended package he can take or
override fork by fork.
**No `PLAN.md` phase exists for this work and none is proposed until F8 is ruled**
`PLAN.md`'s "Flagged for awareness" item 4 is the only pointer to this doc, deliberately, so
@@ -57,33 +81,39 @@ is a **recalibration plus a period grid**, not a synthesis engine.
"wavetable synth," and it is not the loop — it is band-limiting.** A 30 Hz single cycle of
anything but a pure sine, played at C5, resamples by ~17× and folds every partial above
~1.4 kHz. Daniel's test case is a sine, which is exactly the one waveform that hides this.
Real wavetable synths solve it with a mip pyramid of per-octave lowpassed tables. That is a
real build with a real migration cost, and it is separable from everything else here.
**After R1 it is also the only one of the four shapes whose scope is still undecided.**
Real wavetable synths solve it with per-octave lowpassed tables. That is a real build with a
real migration cost. **R2 rules it IN.** It is no longer this doc's hedge; it is half the
work, and §1.8's second half is where its cost is actually derived.
**The four shapes, and where each now stands:**
| | What | Cost | Standing |
|---|---|---|---|
| **W0** | Recalibrate the loop grid for micro material — period snap, whole-file default, tune-from-loop, Preserve inert | No format change, no bank field, no hot-path change, migration-clean | **THE NEAR-TERM SCOPE** |
| **W0** | Recalibrate the loop grid for micro material — period snap, whole-file default, tune-from-loop, Preserve inert | No format change, no bank field, no hot-path change, migration-clean | **NEAR-TERM SCOPE, first** |
| **W1** | Loop start becomes a **wavetable position** — scrub the loop through a multi-cycle capture in whole periods | One parameter that re-expresses one that exists; reuses `period_detect` unchanged | **DEFERRED — R1, 2026-08-03** |
| **W2** | **Band-limited playback**mip pyramid + a better interpolator | Real DSP build; changes the sound of existing captures unless made conditional | **OPEN — F6, undecided** |
| **W2** | **Band-limited playback**per-octave lowpassed tables + a better interpolator | Real DSP build; **the one item that reaches `process()`**; needs a declared mode to hold the migration bar | **NEAR-TERM SCOPE, second — IN by R2.** Shape unruled: F9F12 |
| **W3** | A first-class multi-frame **wavetable kind** — N×2048 tables, morph parameter, `.wav` wavetable import | Breaks "one loaded capture, one parameter set" | **OUT — confirmed by R1** |
**W0 first is not a preference, it is a dependency.** The recommended W2 build (F9(a)) builds
each level by zeroing the partials above its limit in the table's own DFT — which is exact
only when the table is a whole number of periods, i.e. only when W0's declared period exists.
Take a different F9 and the dependency weakens; take F9(c) and it inverts. §1.8.
**R1 is a scope ruling, not a rejection of the idea.** *"Yet"* is Daniel's word and it is
load-bearing: W1's §2 section is preserved in full, argument intact, so a future reader can
pick it back up rather than re-derive it. What would revive it is named there.
**W0 stands entirely on its own merits and always did.** It is defect-class work — Daniel
cannot currently set a correct loop on his own routine material — and none of its five parts
depends on a wavetable ambition. R1 does not shrink W0; it removes what sat on top of it.
depends on a wavetable ambition. R1 does not shrink W0; R2 does not enlarge it.
**And the operational headline: this direction lands in the exact files Ω-W1-T5
`overlay-mapping` owns right now**, and Phase Ω's own acceptance criteria name the
zero-crossing snap as a thing that "must come out the other side identical" (`PLAN.md`,
Phase-Ω acceptance criteria; Ω-W1-T5's outline entry says *"Zero-crossing snap
behaviour-identical"*). Both the in-flight snap-radius track and anything from this doc
contend with that. §6.7 states what needs reconciling.
**And the operational headline, rewritten by R3: the Phase Ω collision is settled, and the
sequencing constraint that survives it is a file-ownership one, not a criteria one.**
Everything here lands in the files Ω-W1-T5 `overlay-mapping` owns, so nothing from this doc
dispatches until that track has landed. The *criteria* conflict — Ω naming the zero-crossing
snap as behaviour-identical while a snap-radius change alters exactly that — is dissolved:
the snap-radius work is Ω's own Ω-W2-T5, and the fence it appeared to break was a scope
fence, now superseded. §6.7.
---
@@ -112,8 +142,8 @@ shrinks toward one cycle. Each is read out of the code, not assumed.
`nearestZeroCrossing` (`src/core/instrument/ui/waveform_view.cpp:181-210`) fans out from the
target frame across the entire buffer with no radius bound. On an 800-frame single-cycle
sine there are two crossings, so a mark parked in the last quarter by `defaultLoopBounds`
teleports to the one at the half-cycle. That is Daniel's octave-doubling exactly, and the
radius track in flight closes it.
teleports to the one at the half-cycle. That is Daniel's octave-doubling exactly, and
Ω-W2-T5's radius bound closes it.
**But the deeper point survives the radius fix.** The snap's premise is that a zero crossing
is where a splice is *safe* — a heuristic borrowed from destructive editing of complex
@@ -275,7 +305,7 @@ Fork **F7** decides whether the surface says so (grey/refuse Preserve when the l
period) or leaves the user to discover it. Auto-switching engines is rejected outright below
— it would change the sound of a saved instance.
### 1.8 Aliasing — the real boundary, and the one Daniel's sine hides
### 1.8 Aliasing — the real boundary, and the one Daniel's sine hid until R2
`Voice::advanceFrame` reads with **linear interpolation** between the two bracketing source
frames at `readPos_`, advancing by `ratio_` (`voice.h:622-627, 705`). Two independent
@@ -290,23 +320,95 @@ deficiencies, both invisible on a sine:
top octave plus imaging. Acceptable for a sample played near its root; conspicuous for a
table played several octaves up, which is the wavetable use case by definition.
**The standard answer, and it is what makes a wavetable synth a wavetable synth: a mip
pyramid.** Per-octave successively lowpassed copies of the table, precomputed at load, with
the read level chosen per voice from the ratio. Its properties fit this codebase unusually
well:
- Precompute is **cold and off the audio thread** — exactly where `period_detect` already
sits, by link graph (`period_detect.h:5-7`).
- Level selection is **per note-on**, not per sample: no dispatch on the hot path, satisfying
structural heuristic 3 and the `process()` guardrail.
- Memory is bounded at ~2× the table.
**The standard answer, and it is what makes a wavetable synth a wavetable synth: per-octave
successively lowpassed copies of the table, precomputed at load, with the read level chosen
per voice from the playback ratio.** Precompute is **cold and off the audio thread**
exactly where `period_detect` already sits, by link graph (`period_detect.h:5-7`) — and the
levels are DERIVED from the audio, so like `sourcePeriodFrames` they are cache and not state:
nothing persists them and they take no rung of the payload ladder.
Raising the read to cubic Hermite is a separate, straight-line 4-tap change with no
indirection — also guardrail-safe, but it changes the sound of *every* capture.
indirection. Note one property that matters for its migration story: at `frac == 0` a cubic
Hermite read returns `pcm[i0]` exactly as the linear read does, so **a capture played at its
root with no offsets is bit-identical under either interpolator**; only a repitched read
changes.
**Both collide head-on with the plan-wide migration bar** ("a project saved before a change
reopens sounding identical"). They must therefore be **conditional and declared**, not
default. Fork **F6**.
reopens sounding identical"). Fork **F6**, which R2 narrows to *conditional or unconditional*
rather than *whether at all*.
#### 1.8.1 What R2 actually commits to, worked out rather than assumed
R2's word is *purely*, and the honest reading is **faithfully — the instrument must not add
artifacts the capture does not contain.** Four consequences follow, and three of them
retire claims this doc made after R1.
**(1) It reaches `process()`, and that is unavoidable.** Precompute is load-time, but
*selecting* a level is per-voice and *reading* it is per-sample: `advanceFrame` binds
`pcm` from `sample_->frames` (`voice.h:448-449`) and reads it every frame. Band-limiting
means that bind resolves to a level-indexed buffer instead of a fixed member. That is **not a
branch, not a virtual, and not a dispatch** — root `CLAUDE.md`'s guardrail language forbids
indirection on the per-voice-per-sample path, and an indexed vector bind is none of those —
but it IS a change to the hottest loop in the program, in a header that inlines with no LTO
configured. **It must be measured, not argued.** This is the first item in this doc that
cannot be discharged by reasoning about the code.
**(2) The ratio is not constant within a note, so "select per note-on" is an approximation,
not a design.** `ratio_ = baseRatio_ * envFactor` (`voice.h:652`) — the pitch envelope
multiplies the read increment per frame, and the Pitch knob is a LIVE control re-applied to a
sounding voice (`pitchOffsetRatio_`, `voice.h:722`). A note latched at its base ratio spends
a +12 semitone pitch-envelope peak an octave under-filtered, which is exactly the artifact R2
forbids. Fork **F10** carries the four available answers; the cheap ones are wrong at the
peak and the correct ones cost either dullness or a second read tap.
**(3) Preserve is a different resampler and a mip level does not reach it.** Under Preserve
the voice's read advances at `stretchRate_` (`voice.h:616`) and the transposition happens
inside the SOLA shifter's **fractional ring read, linear interp** (`pitch_shift.h:184`).
A level chosen from the voice's read ratio therefore does nothing there. Covering Preserve
means feeding the ring from a level chosen by the *shift* ratio — a second build, inside a
module whose artifact budget is already dominated by its splice. Fork **F12**. Whichever way
it goes, **§2's claim that W2 "improves every ordinary sample played more than an octave
above its root" is true only for Varispeed unless F12 goes to (b)**, and that qualification
belongs in the claim rather than in a footnote.
**(4) It needs a declared mode, which is a frozen parameter id and a deck slot.** §6.5, §6.8
and §6.9 are each re-derived below; all three of the post-R1 reliefs are spent.
#### 1.8.2 The 30 Hz case makes band-limiting HARDER in depth and EASIER in kind — and the easier half decides it
Daniel's sub-50 Hz practice is first-class (§1.2 of this doc's framing, §1.3's argument), so
this interaction is worked out rather than deferred.
**Harder — depth and fold count.** The level count needed is the octaves of upward playback.
A 30 Hz table played to C8 (4186 Hz) is 7.1 octaves → **eight levels**; a 440 Hz table to C8
is 3.25 octaves → four. Capturing low doubles the pyramid's depth. It also maximizes the fold
count: at C5 a 30 Hz source keeps only `24000/523.25 ≈ 45` partials un-aliased out of the up
to `24000/30 = 800` it can carry. For a saw-like 1/n spectrum the folded energy is roughly
1.3 % of total power (≈ 19 dB) — audible hash, not a theoretical concern.
**Easier — and this is the decisive half.** At exactly one period the table's DFT bins **are**
its harmonics. Each level can therefore be built by transforming, zeroing every partial above
that level's limit, and transforming back: **exact, with no filter design, no transition band
and no phase error.** That is how the Serum/Vital class of synth builds its tables (§5), and
it requires knowing the period exactly. §1.5 already establishes that at the single-cycle
limit the period is **DECLARED** — it is the file length. **The very property that makes
detection structurally impossible is what makes exact band-limiting possible.** A 220 Hz
capture (218.2 frames per cycle, not an integer) has no such alignment and needs a real
filter design instead.
**And the depth 30 Hz forces is free exactly where it is forced.** Under the same-rate table
shape (F9(a)) eight levels of a 1600-frame mono table is 12,800 floats ≈ 51 KB. Depth is
cheap precisely because a low capture's table is short in absolute terms. It is the *long*
capture that is expensive — and the instrument loads **any** bank capture, a full-mix stem
bounce included, where `SampleData::frames` is a `std::vector<float>` per channel of the
whole file. Eight same-rate levels of a three-minute stereo bounce is hundreds of megabytes.
**So the pyramid must be gated on length or on the micro classification, and that gating is
what collides with §3's never-change-the-sound constraint** — see §3.
**One floor worth stating because it makes the migration argument cheap:** at or below the
root there is nothing to fold (`ratio ≤ 1`), so level 0 is the unmodified capture and a note
played at root is bit-identical with band-limiting on. The pyramid is an upward-only
mechanism.
---
@@ -380,18 +482,39 @@ The optional second half — **interpolating between adjacent period-frames** fo
rather than a step — costs a second read tap on the per-sample path and must be checked
against the `process()` guardrail before it is promised. Fork **F5** splits scrub from morph.
### W2 — Band-limited playback — UNAFFECTED by R1, still open
### W2 — Band-limited playback — IN SCOPE by R2 (2026-08-03)
**R1 does not touch this and must not be read as touching it.** Band-limiting is a property
of playing *any* short table across the keyboard; whether the table's frames evolve is
irrelevant to whether its partials fold. A static single-cycle capture of a saw, played at
C5, aliases exactly as §1.8 describes. **F6 remains open and unruled.**
**Daniel, 2026-08-03: *"yes, complex waveforms must be supported purely."*** A static
single-cycle capture of a saw, played at C5, aliases exactly as §1.8 describes; *purely*
forbids that. **F6 is no longer "does this ship" — it is "conditional or unconditional".**
§1.8. Orthogonal to W0 and W1, and the only one of the four that is a genuine DSP build. It
is also **independently valuable outside this direction**: it improves every ordinary sample
played more than an octave above its root, which is a much larger population than
single-cycle captures. That argues for judging it on its own merits rather than as
wavetable-support. Fork **F6**.
It remains the only one of the four shapes that is a genuine DSP build, and it is
**independently valuable outside this direction**: it improves every ordinary sample played
more than an octave above its root, a much larger population than single-cycle captures —
**for Varispeed voices, and for Preserve voices only if F12 goes to (b)** (§1.8.1(3)).
**It is no longer orthogonal to W0.** Under the recommended shape the levels are built by
zeroing partials above each limit in the table's own DFT, which is exact only over a whole
number of periods — so W2 consumes the declared period W0 establishes (§1.8.2). W0 first is
therefore a dependency, not a preference. Under F9(b) the dependency softens to a preference;
under F9(c) it inverts, because a resynthesized table supplies its own period.
**The four shape decisions, each a fork, with the recommended package named.** They are
genuinely independent axes, so Daniel can take the package in one word or override any single
one:
| Fork | Axis | Recommended | Why, and what it costs |
|---|---|---|---|
| **F6** | Conditional or unconditional | **(b) gated behind a declared mode** | Unconditional breaks the migration bar for every saved instance that plays above its root. The mode costs a frozen parameter id (§6.8) and a deck slot (§6.9) |
| **F9** | The table's shape | **(a) same-rate filtered copies** | Zero index-domain change — loop points, `startFrame`, the crossfade tap and `sourcePeriodFrames` all keep their meaning. Costs `L`×`N` memory, so it must be gated on capture length |
| **F10** | When the level is chosen | **(c) per-block reselection** | Matches the standing "observation is at block boundaries, never per frame" discipline; no per-sample branch. Costs a small spectral step at a level change |
| **F11** | Does the interpolator upgrade ride the same lever | **(c) cubic Hermite under the same mode** | One lever, one migration event; the two are the same complaint about reconstruction quality on a repitched read |
| **F12** | Does Preserve get covered | **(a) Varispeed only, for now** | Cheap, and defensible at micro scale where Preserve is definitionally wrong anyway (§1.7). Costs the unqualified version of the "improves every ordinary sample" claim |
**What W2 does NOT do, stated so it is not assumed:** it writes nothing to the bank, adds no
`Sample` field, and takes no payload rung for the tables themselves — they are derived cache
on the `period_detect` precedent. Only the *mode* persists, as an additive `ComponentState`
tail on the established discipline.
### W3 — A first-class wavetable KIND — OUT (recommended out; CONFIRMED by R1)
@@ -439,6 +562,25 @@ derived classification may change DEFAULTS and the SNAP GRID, and may never chan
SOUND.** A heuristic that picks a better starting loop is safe because the user can drag it;
a heuristic that changes playback is a surprise waiting to be filed as a bug.
**R2 puts that constraint under direct pressure, and the resolution matters more than the
threshold below it.** §1.8.2 shows the band-limited tables must be gated on capture length or
on the micro classification, because eight same-rate levels of a stem bounce is hundreds of
megabytes. But if a derived classification decides *whether a capture is band-limited*, then
a heuristic decides its sound — the exact thing this section forbids. **Split the two gates
and the constraint survives intact:**
- **The MODE decides the sound, and the mode is the user's** (F6(b)). Band-limiting is on or
off because the user said so, on any capture, of any length.
- **The classification decides only whether the tables are built EAGERLY at load.** A
non-micro capture with the mode on still gets band-limited; it just pays its build (or a
bounded-depth build) on demand rather than eagerly. That is a memory-and-latency policy, not
a sound policy.
**This is a recommendation, not a ruling — it changes what F1 is choosing between**, because
it means F1(a) no longer has to carry a sound decision it was explicitly built not to carry.
If Daniel prefers the simpler "derived classification also gates band-limiting", §3's hard
constraint has to be relaxed explicitly rather than eroded quietly.
**A threshold derived from the codebase rather than from taste.** A capture is a micro
candidate when *both*:
@@ -516,6 +658,17 @@ the seam, per the comment conventions' "warnings against a plausible-but-wrong c
**R1 does not defer this.** The paragraph originally attached the circularity to W1; it in
fact attaches to any one-period loop, so it arrives with W0 rather than after it.
**R2 gives the DECLARED half of the split a second consumer, and it is the more demanding
one.** W0 wants the period to place a snap grid — an error of a fraction of a frame there
costs a slightly wrong default. W2's recommended build wants it to align a DFT with the
table's harmonics, where the alignment is either exact or the whole method degrades into an
approximate filter design (§1.8.2). **That raises the bar on the declaration but not on the
detection**: at the single-cycle limit the declaration is the file length, which is exact by
the capture's own exact-bounds guarantee (§6.2), and above that limit the exactness has to be
earned from a whole-period loop rather than from a detector's fractional estimate. **The two
sources of truth stay two; R2 makes the declared one load-bearing for sound, where before it
was load-bearing only for a default.**
---
## 5. Prior art borrowed, and what each contributes
@@ -524,13 +677,17 @@ fact attaches to any one-period loop, so it arrives with W0 rather than after it
table." W1 is this control, transplanted onto captured material rather than authored tables.
**Recorded for whoever picks W1 back up; deferred by R1, and note that the PPG precedent
presumes a table whose frames differ, which static capture material does not supply.**
- **Serum / Massive / Vital** — mip-mapped, per-octave band-limited tables. This is the whole
of W2, and their universal adoption of it is the evidence that §1.8 is not a theoretical
concern. **Still live: F6 is open.**
- **Serum / Massive / Vital** — per-octave band-limited tables, built by spectral truncation
of a whole-period table rather than by filter design. This is the whole of W2, and their
universal adoption of it is the evidence that §1.8 is not a theoretical concern. **Now
load-bearing: R2 rules W2 in, and the spectral-truncation build is what F9(a) borrows.**
The transplant is not free — those synths own their table format and can guarantee a
whole-period power-of-two frame, where we inherit whatever the capture is; §1.8.2 is where
that difference is paid.
- **Kontakt / EXS-class samplers' loop-finding tools** — snap a loop to a *detected period* and
offer a "find loop" pass, rather than snapping only to zero crossings. This is the
established precedent for F2's period grid; we would not be inventing it. **This is now the
load-bearing precedent, since W0 is the near-term scope.**
established precedent for F2's period grid; we would not be inventing it. **The load-bearing
precedent for W0, which is the first of the two near-term bodies of work.**
- **Serum's import-a-sample-as-a-wavetable flow** — it asks the user to declare or confirm the
cycle length rather than detecting it from a single cycle. That is independent confirmation
of §1.5's declare-vs-detect split, arrived at from the same constraint.
@@ -572,45 +729,103 @@ Stated in §2 and the sole structural reason W3 is recommended out.
- **W1 is migration-clean** if the position parameter defaults to the position the stored loop
start already denotes. *(Deferred by R1; kept for the record.)*
- **W2 is not.** Both halves change the rendered audio of existing captures. Fork **F6**
exists to decide whether that cost is paid unconditionally or made conditional and declared.
now decides only whether that cost is paid unconditionally or made conditional and declared.
**After R1 the whole near-term scope is migration-clean**, and the only migration risk left
in this doc sits behind an open fork rather than inside the work.
**R2 retires the post-R1 relief here.** The near-term scope is no longer migration-clean as a
whole; W0 still is, and W2 is not. Two properties make the bar cheap to hold rather than
expensive, and both are worth stating because they are easy to miss:
### 6.6 The hot path — HELD, and the guardrails are satisfiable by construction
- **At or below the root nothing folds**, so level 0 is the unmodified capture and a note at
root is bit-identical with the mode on (§1.8.2).
- **A cubic Hermite read at `frac == 0` returns `pcm[i0]`**, exactly as the linear read does
— so the interpolator half is also bit-identical at unity ratio (§1.8).
**The bar therefore breaks only for repitched playback, and only with the mode on.** Under
F6(b) a saved instance reopens with the mode off and sounds identical, which is the whole
argument for (b) over (c).
### 6.6 The hot path — HELD for W0; W2 reaches it, and the post-R1 claim here is RETIRED
**The retired claim, stated plainly so nobody carries it forward: after R1 this section said
"the entire near-term scope is load-time and UI-thread only. Nothing in W0 goes near
`process()`." That was true of W0 and is still true of W0. It is FALSE of the near-term scope
as R2 leaves it.**
- The period grid, the classification and the tune-from-loop computation are **load-time and
UI-thread only**.
UI-thread only**. W0 is unchanged by R2 in every respect.
- W1's scrub is a **reload-tier or note-on-latched** parameter, not a per-sample one; if it is
wanted live, it belongs in the existing three-tier commit classification
(`deckParamCommit` / `liveCommitFor`) rather than in a new mechanism. *(Deferred by R1.)*
- W2's mip level selection is **per note-on**. The interpolator change is a straight-line
4-tap with no dispatch.
- **W1's optional morph (F5c) was the one item that would have touched `process()`** — a
second read tap per frame, to be measured against the guardrail before it was promised.
**R1 closes F5 at (a), so that item is off the table for now.**
- **W2's table build is load-time**, on `period_detect`'s own link-graph precedent. **Its
level selection and its read are not.**
**Consequence of R1 worth stating plainly: the entire near-term scope is load-time and
UI-thread only. Nothing in W0 goes near `process()`.** The guardrail question that would have
needed a measurement before anything could be promised no longer gates this direction.
**What W2 costs on the per-voice-per-sample path, as precisely as this doc can state it
without a measurement:**
### 6.7 Phase Ω and the in-flight snap-radius track — the real sequencing constraint
- **The read.** `advanceFrame` binds `pcm` from `sample_->frames` (`voice.h:448-449`) every
frame. Band-limiting makes that an indexed bind into a level array. **No branch, no virtual,
no dispatch** — so root `CLAUDE.md`'s guardrail wording and structural heuristic 3 are both
satisfied in kind. But `advanceFrame` is header-inline **by RT constraint with no LTO
configured**, and whether the compiler hoists the indexed load out of the block loop is a
fact about the generated code, not about the design. **Measure it.**
- **The level selection.** Under F10(c) it is once per voice per block — the same cadence as
the live block's seqlock read, which is an established discipline rather than a new
mechanism. Under F10(d) it is a **second read tap per frame**, which is the identical cost
§6.6 flagged for W1's morph and which must clear the guardrail before it is promised.
- **The interpolator.** Two extra loads and a handful of multiply-adds per channel per frame
under F11(b)/(c), straight-line, no dispatch. The cheapest of the three to reason about and
the most expensive to un-ship, because it changes every repitched note.
- **`voice.h` is already a documented ~600-line ceiling exception**, so W2's edits there need
a responsibility seam rather than added bulk — and F9(b)'s level-relative frame domain would
add far more of it than F9(a)'s bind change. That is a structural argument for F9(a)
independent of the memory one.
This is the item most likely to cost someone a merge.
**Net: W0 is guardrail-free; W2 is the first item in this direction that a specialist must
measure before it can be accepted.** A W2 track whose acceptance criteria contain no
before/after number has an opinion, not a criterion — the same rule Phase Ω applies to its own
two performance items.
- **Ω-W1-T5 `overlay-mapping` currently owns `src/core/instrument/ui/waveform_view.{h,cpp}`,
`editor_input_waveform.cpp` and `editor_controls.cpp`'s `waveMarksFor`/`grabbableMarks`** —
which is the complete file set any period-grid work touches.
- **Phase Ω's own acceptance criteria name the zero-crossing snap as a thing that must come
out the other side identical**, and Ω-W1-T5's outline entry repeats it
(*"Zero-crossing snap behaviour-identical"*). **A snap-radius track changes exactly that
behaviour.** Whether that criterion has already been amended for the in-flight track is not
something this doc can assert — it needs reconciling by whoever owns Ω's criteria, and the
purpose of this bullet is to make sure someone does.
- **Recommended sequencing: nothing from this doc dispatches until Ω-W1-T5 and the
snap-radius track have both landed.** The period grid should be written against the
post-Ω single coordinate mapping, not against today's four-discrepancy one, and it should
build on whatever radius rule the in-flight track settles rather than replacing it.
### 6.7 Phase Ω — the criteria conflict is SETTLED by R3; the file-ownership constraint stands
**This was the item most likely to cost someone a merge. R3 removes half of it.**
**Daniel, 2026-08-03:** *"call it part of omega, that was a fence you built not me."*
- **The conflict this section recorded is dissolved, not sequenced around.** The snap-radius
work **is** a Phase Ω track — **Ω-W2-T5** (branch `omega-w2-t5-snap-radius`, commit
`b7b7e88`) — which bounds the snap to `kMarkerGrabWidth` (5 px, `waveform_view.h:70`) and
adds **Ctrl-defeats-snap** on a marker drag.
- **The fence was a scope constraint, not a product ruling.** Ω's acceptance criterion naming
the zero-crossing snap among the things that "must come out the other side identical" was
written to stop Ω's *own* tracks perturbing the snap while they fixed the coordinate
mapping. It was never a finding that the snap's unbounded fan-out was correct, and Daniel's
DAW evidence supersedes it. `PLAN.md` now records the supersession at all three criterion
sites.
- **Ω-W1-T5 is not invalidated by this.** It was required to preserve the snap exactly, and it
did; its tie-rule fixtures pass unmodified under the bounded entry, because bounding the
fan-out changes which frames are *reachable*, not how a reachable tie resolves. The change
came later and from a different track.
- **What survives is a pure file-ownership constraint, and it is unchanged in form.** Every
candidate in this doc touches `waveform_view.{h,cpp}`, `editor_input_waveform.cpp` and
`editor_controls.cpp`'s `waveMarksFor`/`grabbableMarks` — Ω-W1-T5's exact surface — so
**nothing here dispatches until Ω-W1-T5 has landed**, and the period grid should be written
against the post-Ω single coordinate mapping and on top of Ω-W2-T5's radius rule rather than
replacing either. *(At the time of writing, `dev`'s `nearestZeroCrossing`
(`waveform_view.cpp:181-210`) is still the unbounded fan-out — `b7b7e88` is on its own
branch. Confirm what has actually merged before dispatching, rather than reading landed
status out of this doc.)*
- **One documentation gap R3 creates, flagged rather than filled here.**
**Ctrl-defeats-snap on a marker drag is a new user-visible gesture with no home in
`docs/product/instrument-control-surface.md`** — and worse than absent, it is *contradicted*
there: §4.1's exclusion block states that waveform markers are excluded from the modifier
law because *"overloading Shift there would collide with a shipped gesture"*, which now
reads as a claim that markers carry no modifier at all. **Flagged for doc-keeper: that
block needs amending and the gesture needs documenting.** This doc does not write that
content.
- **And one cascade into this doc's own forks: Ctrl on a waveform-mark drag is now TAKEN.**
F2's option (c) — a modifier-selected choice between the period grid and the zero-crossing
grid — can no longer use Ctrl, and Shift is already excluded on marks for the reason quoted
above. F2(c) now has to name a modifier it can actually have.
### 6.8 The frozen VST3 parameter table and the one-way doors
@@ -621,11 +836,25 @@ one annotated exception from Ω-W1-T4, so a second annotation is cheap precedent
freeze the moment it ships**, so a position knob's scale must be right first time. W0 adds no
parameter at all, which is one more reason to take it first.
**R1 removes the near-term exposure here entirely.** With W1 deferred, the only prospective
new parameter left in this doc is W2's mode, which sits behind an open fork — so **nothing in
the near-term scope opens a one-way door.** A position knob's taper, the item that most
needed to be right first time, is a decision this direction no longer has to make under time
pressure.
**R1 removed the near-term exposure here; R2 restores it, in a much cheaper form.** W2's mode
is a new parameter, so the door is open again — but a two-state mode is the least dangerous
parameter this doc could have produced:
- **Its taper is trivial** and its range is `{off, on}`, so `parameter-automation.md` §8's
"the taper and range freeze the moment it ships" costs nothing to get right.
- **Its id is permanent regardless.** It appends to the FOREVER-FROZEN table from Γ-W4-T1 and
is never renumbered, and `param_id.h`'s signal-flow-order comment takes a second annotated
exception — cheap precedent, already set by Ω-W1-T4.
- **The real door is the mode's SEMANTICS, not its taper.** A boolean "band-limit on/off"
can never later become a three-way quality selector without either re-tapering a frozen
parameter or minting a second id and deprecating the first. **If more than two states are
ever plausible, that has to be decided before the parameter ships, not after** — this is the
one thing about W2 that must be right first time.
- **A capture-length or depth limit is NOT a parameter.** It is an internal policy (§3), and
keeping it out of the parameter table is what stops a memory decision from freezing into the
automation surface.
**W0 still adds no parameter at all**, which remains an argument for taking it first.
### 6.9 The editor's space budget
@@ -634,40 +863,70 @@ W1's position control and W2's mode toggle both want deck real estate against
tight against the ~600-line ceiling with Phase Ω spending its margin. This was a real
constraint on how many of these shapes could ship together, not a footnote.
**R1 relieves it for now.** W0's five parts add no control to the deck — a snap grid, a
default, and two greyed affordances are all changes to existing surfaces — and F3's
"Tune to loop" is the one item that might want a hit target, which is a far smaller ask than
a knob. **The 82 px is uncontended by the near-term scope.** It comes back the moment F6 is
ruled toward a mode toggle, so the headroom should not be spent elsewhere on the assumption
that this direction has released it permanently.
**R1 relieved it; R2 spends it back.** W0's five parts still add no control to the deck — a
snap grid, a default, and two greyed affordances are all changes to existing surfaces — and
F3's "Tune to loop" is the one item that might want a hit target, which is a far smaller ask
than a knob. **But W2's mode toggle is exactly the control §1.6 was warning about**, and the
warning at the end of the post-R1 paragraph ("it comes back the moment F6 is ruled toward a
mode toggle") has come true.
**The conditional-cost option, and it should be measured before any px is budgeted: a
caption-row toggle in a group with caption slack costs ZERO width.** The precedent is
FILTER's `Band|Notch`, which rides its caption slack rather than the knob row and is the
92 px that makes the SOUND row fit; a group carries **two** caption-toggle slots for exactly
this reason, and a `rowToggle` is the expensive alternative that widens the group. Whether a
second caption slot with slack exists in a group where a band-limit mode belongs — the read
path is a pitch-side concern — is a **measurement against `deck_groups`/`knob_deck`, not an
assumption this doc may make.** If a free slot exists, W2 costs 0 of the 82 px. If not, it
costs a `rowToggle`'s width and the headroom is genuinely spent.
**Either way the headroom should not be spent elsewhere on the assumption that this direction
released it** — R1's relief was temporary and R2 ended it.
---
## 7. Forks — EIGHT total: ONE settled (F5), SEVEN open
## 7. Forks — TWELVE total: ONE settled (F5), ELEVEN open
Each carries a product-designer lean and the cost of taking it. **Every one is Daniel's to
rule.** None is pre-ruled here and none should be treated as settled by the lean. Rulings
recorded against this doc are logged in §7.1; the lean column below states, per fork, whether
R1 moved it.
R1 or R2 moved it.
**F9F12 are new, minted by R2 to carry W2's shape.** R2 ruled that band-limiting happens; it
deliberately did not rule how. Four independent axes, so the recommended package can be taken
in one word or overridden fork by fork:
> **Recommended package: F6(b) + F9(a) + F10(c) + F11(c) + F12(a)** — a declared mode, gating
> same-rate spectrally-truncated levels, reselected per block, with the cubic read on the same
> lever, Varispeed only for now. Costs: `L`×`N` memory on a gated set of captures, a permanent
> two-state parameter id, a deck toggle that may be free, a small spectral step at a level
> change, and a measurement obligation on the per-sample read.
| Fork | Question | Options | Standing / lean |
|---|---|---|---|
| **F1** | Is there a "micro" classification at all, and where does it come from? | (a) derived-only, governing **defaults and snap grid only, never the sound**; (b) derived + a user override toggle; (c) **no classification** — the period grid is always available and a bad default on micro material is accepted | **OPEN. Lean unmoved: (a)**, with (c) as the serious conservative alternative. **But R1 amends the test inside (a)** — the frame-count band now classifies and detection supplies the period, because "detection failed" stopped meaning "table-like material" when W1 left. See §3 |
| **F2** | What grid does a loop-mark drag snap to? | (a) zero-crossing always (today); (b) **period** when a period is known, zero-crossing otherwise; (c) both, modifier-selected — note `param_taper`'s modifier law explicitly excludes spline points, so a waveform-mark modifier is available but needs its own ruling | **OPEN. Lean unmoved: (b)** — it makes the octave error unreachable rather than unlikely. **Simplified by R1:** on a true one-cycle file the period grid admits exactly ONE loop (start 0, whole file), so "grid" there means "a single admissible loop"; the grid only has multiple positions on a multi-cycle capture, which R1 leaves in scope (§1.4) |
| **F1** | Is there a "micro" classification at all, and where does it come from? | (a) derived-only, governing **defaults and snap grid only, never the sound**; (b) derived + a user override toggle; (c) **no classification** — the period grid is always available and a bad default on micro material is accepted | **OPEN. Lean unmoved: (a)**, with (c) as the serious conservative alternative. **R1 amends the test inside (a)** — the frame-count band now classifies and detection supplies the period. **R2 adds a job (a) must NOT take**: gating band-limiting on a derived class would let a heuristic decide the sound. §3 recommends splitting the gates (mode decides sound, class decides eager build) so (a)'s constraint survives |
| **F2** | What grid does a loop-mark drag snap to? | (a) zero-crossing always (today); (b) **period** when a period is known, zero-crossing otherwise; (c) both, modifier-selected | **OPEN. Lean unmoved: (b)** — it makes the octave error unreachable rather than unlikely. **Simplified by R1:** on a true one-cycle file the period grid admits exactly ONE loop (start 0, whole file). **Narrowed by R3: (c)'s modifier vocabulary is now nearly empty** — Ω-W2-T5 takes **Ctrl** for defeat-snap on a marker drag, and `instrument-control-surface.md` §4.1 already excludes **Shift** on marks. (c) must now name a modifier it can actually have. **R2 raises (b)'s stakes** — a non-whole-period seam step scales with the source's high-frequency content, which is what a complex waveform has more of |
| **F3** | Does the instrument correct tuning from the loop length? | (a) never — the user dials Pitch (today); (b) a **one-shot "Tune to loop"** that writes `pitchOffsetSemitones`; (c) automatic and continuous, derived and unstored | **OPEN. Lean (b) FIRMS.** (c)'s only real motivation was keeping tuning correct while a scrub moved the loop; R1 removes the scrub, so with a static loop length a one-shot write is unambiguous and (c) is now pure hidden-pitch-modifier with nothing bought |
| **F4** | Fractional loop bounds? | (a) **no** — integer forever; buy resolution by capturing low, and document that; (b) yes — fractional loop end, which changes a **bank intrinsic** on `Sample` and takes a payload rung | **OPEN. Lean unmoved: (a)**, hard. ~1 ¢ at Daniel's own working range does not justify a bank-format change. R1 does not touch this: the quantizer is one frame regardless of what the material does |
| **F4** | Fractional loop bounds? | (a) **no** — integer forever; buy resolution by capturing low, and document that; (b) yes — fractional loop end, which changes a **bank intrinsic** on `Sample` and takes a payload rung | **OPEN. Lean (a) HOLDS, but R2 puts it under real pressure and that should be visible.** The tuning arithmetic is unchanged (~1 ¢ at 30 Hz), but a one-frame phase error at the seam produces a step whose size scales with the waveform's slope — small on a sine, larger on a harmonically rich capture. The existing mitigation still carries it: a one-cycle file is one period by construction (§1.3), so the error is a bad-capture case, not a routine one. **Note that F9(c) would dissolve this fork entirely** by taking pitch off `L` — one more reason it is the option that deserves an explicit refusal rather than silence |
| **F5** | Does W1 ship, and does it morph? | (a) not now; (b) scrub only — whole periods, no interpolation; (c) scrub + interpolate between adjacent period-frames | **SETTLED (a), Daniel 2026-08-03 (R1).** This inverts the lean, which was (b) — the lean assumed multi-cycle *evolving* material, and §8's own test said so. "Yet": revisit if the material changes, per §2's W1 section |
| **F6** | Band-limiting (W2) — in scope, and conditional? | (a) out for now; (b) **mip pyramid + better interpolator, gated behind a mode** so the migration bar holds; (c) unconditional, accept the break with a version note | **OPEN — explicitly NOT ruled by R1, and now the largest open question here.** Lean unchanged: **(b)** if it ships. This is the fork that decides whether "wavetable synth" is rhetoric or a commitment |
| **F6** | Band-limiting (W2) — conditional or unconditional? | ~~(a) out for now~~**eliminated by R2**; (b) **gated behind a declared mode** so the migration bar holds; (c) unconditional, accept the break with a version note | **OPEN, narrowed to a binary by R2 (2026-08-03).** R2 answered the *whether*: *"complex waveforms must be supported purely."* Lean unchanged: **(b)** — and it is cheap, because at root and at unity ratio the two paths are bit-identical (§6.5), so (b)'s off state is a genuine no-op rather than a maintained second engine. (c) buys one less parameter at the cost of every saved instance that plays above its root |
| **F7** | Preserve, and the XFADE mark, at one-period loops | (a) leave both live and let the user discover they do nothing; (b) **refuse/grey both** when the loop is one period; (c) auto-switch the engine to Varispeed | **OPEN. Lean (b) FIRMS and simplifies.** R1 makes the one-period loop the *typical* shape rather than one of several, so the affordance is wrong more of the time; and with a static loop the condition is evaluated once rather than tracked across a moving one. (c) still changes the sound of a saved instance and should still be rejected |
| **F8** | Does this become a Phase, and when? | (a) a phase sized **W0-only**, dispatched after Ω-W1-T5 and the snap-radius track land; ~~(b) one phase covering W0+W1~~**eliminated by R1**; (c) not now | **OPEN, but narrowed to a binary (a) vs (c).** Lean unmoved: **(a)** — W0 is defect-class and unblocks Daniel's material. R1 makes (a) cleaner than it was: a W0-only phase is now the whole of the direction's near-term scope rather than the first slice of it |
| **F8** | Does this become a Phase, and when? | (a) **one phase, two waves — W0 then W2** dispatched after Ω-W1-T5 lands; ~~(b) one phase covering W0+W1~~**eliminated by R1**; (c) not now; (d) **two phases** — W0 now as defect-class work, W2 separately once F9F12 are ruled | **OPEN. Lean: (a)**, with **(d) the serious alternative R2 creates.** R1 had reduced this to a binary; R2 re-opens it, because W0 and W2 now differ in kind — W0 is migration-clean, parameter-free, guardrail-free defect work, and W2 is a measured DSP build with a frozen parameter and five unruled shape forks. (a) keeps the dependency (W2 consumes W0's declared period) inside one plan; (d) stops W0 waiting on W2's forks. **If F9F12 are not ruled when W0 is ready, (d) is the honest answer** |
| **F9** | *(new, R2)* What SHAPE do the band-limited tables take? | (a) **same-rate filtered copies**`L` full-length levels, **zero index-domain change**; (b) **decimated mip pyramid** — memory bounded at ~2× regardless of depth, but every frame-domain quantity (loop wrap, crossfade tap `pos length`, Trigger span, spline phase, `sourcePeriodFrames`) becomes level-relative; (c) **resynthesize to a fixed power-of-two table** from the declared period | **OPEN. Lean: (a)**, gated on capture length. It keeps the change inside a buffer bind rather than spreading a second frame domain through `voice.h` — already a documented ceiling exception. (b) is the right answer if long captures must be covered without gating. **(c) is recommended AGAINST and the reason is identity, not cost:** it dissolves F4's quantizer and makes every level exact, but the instrument would stop playing the captured bytes — this tool's trust anchor is that what you captured is what you hear, and (c) is also the option that most resembles what R1 put out of scope |
| **F10** | *(new, R2)* WHEN is a voice's level chosen? | (a) note-on latch from `baseRatio_`; (b) note-on latch from the **worst-case** ratio the note can reach (pitch-env peak + velocity throw); (c) **per-block reselection**; (d) per-block with a crossfade between adjacent levels | **OPEN. Lean: (c)** — it matches the standing "observation is at block boundaries, never per frame" discipline and adds no per-sample branch. (a) is wrong under a pitch envelope: `ratio_ = baseRatio_ * envFactor` (`voice.h:652`) and the Pitch knob is LIVE, so a latched level is under-filtered at the peak — the artifact R2 forbids. (b) is the fallback if (c)'s step is audible: no step by construction, at the cost of a dull note start. (d) removes the step and costs a **second read tap per frame** — the same cost this doc refused to promise for W1's morph without a measurement |
| **F11** | *(new, R2)* Does the interpolator upgrade ride the same lever? | (a) linear stays; (b) **cubic Hermite unconditionally**; (c) **cubic under the same declared mode** as band-limiting | **OPEN. Lean: (c)** — one lever, one migration event, and the two are the same complaint about reconstruction quality on a repitched read. A mip fixes folding but not linear interpolation's sinc² sag and imaging, so (a) leaves half of *purely* undelivered. (b) is defensible on the grounds that it is bit-identical at `frac == 0` — root playback is unchanged — but it still moves every repitched note in every saved instance with no way to decline |
| **F12** | *(new, R2)* Does **Preserve** get covered? | (a) **Varispeed only, for now**; (b) also feed the SOLA ring from a level chosen by the SHIFT ratio | **OPEN. Lean: (a)**. Under Preserve the voice reads at `stretchRate_` (`voice.h:616`) and the transposition is the shifter's own fractional ring read (`pitch_shift.h:184`), so a voice-side level does nothing there — (b) is a second build inside a module whose artifact budget is already dominated by its splice. (a) is defensible at micro scale, where Preserve is definitionally the wrong engine anyway (§1.7). **The cost of (a) is a claim, and it must be qualified rather than dropped:** "improves every ordinary sample played above its root" is Varispeed-only under (a) |
**What is NOT a fork, and should not be re-opened as one:**
- The pre-seam crossfade cannot deliver micro-scale smoothness (§1.2). Structural — and
**untouched by R1**, which changes nothing about the loop's arithmetic.
- `period_detect` cannot see a single cycle (§1.5). Structural — and **untouched by R1**. The
declare-vs-detect split survives intact; deferring W1 removes the scrub, not the
multi-cycle capture that detection serves (§1.4).
**untouched by R1 and by R2 alike.** The identity-map result is arithmetic on any
`P`-periodic source, not a property of sines: at `length == P` the tap `pos length` equals
the head for a saw exactly as it does for a sine. A complex waveform does not create a job
for the crossfade that it did not have before.
- `period_detect` cannot see a single cycle (§1.5). Structural — and **untouched by R1 and
R2**. The declare-vs-detect split survives intact; R2 gives the DECLARED arm a second and
more demanding consumer (§4) without changing what either arm can do.
- W3 breaks "one capture, one parameter set" (§2), **and is now out by Daniel's ruling as
well as by that argument.** If it returns, it returns as its own product question.
- Sub-50 Hz is first-class. Nothing here narrows a range to avoid it, and R1 does not change
@@ -694,16 +953,69 @@ R1 moved it.
case whole and names the single trigger that revives it.
- **F8's option (b) eliminated** — there is no W0+W1 phase to size.
**What it explicitly does NOT settle:**
**What it explicitly did NOT settle** *(both items have since been ruled — see R2 and R3)*:
- **F6 / band-limiting.** The deciding question — *is the material ever anything but sine?*
is outstanding, and Daniel knows it is. Aliasing is orthogonal to whether a table's frames
evolve; a static single-cycle saw played at C5 folds exactly as §1.8 says. **Do not read
R1 as answering it.**
- **F6 / band-limiting.** Left outstanding by R1 and **answered by R2 below.**
- **The Phase Ω conflict in §6.7.** Left open by R1 and **dissolved by R3 below.**
- **F1, F2, F3, F4, F7, F8** — all still Daniel's. R1 shifts what some of them are choosing
between (the table above says which) without choosing.
- **The Phase Ω conflict in §6.7.** Still open, still with Daniel and whoever owns Ω's
acceptance criteria, and not this doc's to resolve.
**R2 — Daniel, 2026-08-03.** Verbatim, answering this doc's own second outstanding question
(*is the material ever anything but sine?*):
> *"yes, complex waveforms must be supported purely."*
**What it settles:**
- **F6's option (a) — "out for now" — is eliminated. W2 is in scope**, and the fork narrows
to conditional (b) vs unconditional (c).
- **The near-term scope becomes W0 AND W2**, in that order, because the recommended W2 build
consumes W0's declared period (§1.8.2).
- **§8's kill test for W2 is met by evidence rather than preference**, the same way R1 met
W1's. The condition was written before the answer existed.
**What it costs, stated because a scope ruling that hides its price is not decision-grade —
all three of R1's reliefs are spent:**
- **It reaches `process()`.** The read binds a level-indexed buffer (`voice.h:448-449`). No
branch, no dispatch, no virtual — but a measurement obligation, not an argument (§6.6).
- **It opens a one-way door**: a permanent VST3 parameter id whose *semantics* (two-state vs
a quality selector) must be right first time (§6.8).
- **It contends the 82 px deck headroom** again — possibly at zero cost via a caption-row
slot, which is a measurement and not an assumption (§6.9).
- **It breaks the migration bar for repitched playback**, which is why the lean is F6(b);
root playback and unity-ratio playback stay bit-identical either way (§6.5).
**What it explicitly does NOT settle:** *the shape.* R2 rules that band-limiting happens, not
how. **F9 (table shape), F10 (level selection), F11 (interpolator), F12 (Preserve coverage)
are minted open**, each with a lean and a cost, and the recommended package at the head of §7
is a recommendation and not a ruling. **F1, F2, F3, F4, F7, F8 remain Daniel's**; R2 moves
what F1, F2, F4 and F8 are choosing between (the table says how) without choosing.
**R3 — Daniel, 2026-08-03.** Verbatim, on the Phase Ω conflict recorded at §6.7:
> *"call it part of omega, that was a fence you built not me"*
**What it settles:**
- **The snap-radius work is a Phase Ω track — Ω-W2-T5** (branch `omega-w2-t5-snap-radius`,
commit `b7b7e88`): the snap is bounded to `kMarkerGrabWidth` (5 px) and a marker drag gains
**Ctrl-defeats-snap**.
- **Ω's "the zero-crossing snap comes out identical" criterion was a scope fence, not a
product ruling**, and is superseded by Daniel's DAW evidence. `PLAN.md` records the
supersession at the phase criterion and at Ω-W1-T5's two criterion sites.
- **Ω-W1-T5's work is not invalidated.** It was required to preserve the snap exactly and it
did; its tie-rule fixtures pass unmodified under the bounded entry. The change came later
and from a different track.
- **§6.7 becomes a record rather than an open item.** What survives from it is a file-ownership
sequencing constraint, unchanged in form.
**Two consequences R3 creates, both recorded rather than resolved here:**
- **Ctrl-defeats-snap is a new user-visible gesture that `instrument-control-surface.md` §4.1
contradicts.** Flagged for doc-keeper; this doc does not write that content.
- **F2's option (c) loses Ctrl**, and Shift was already excluded on marks. (c) must name a
modifier it can have.
---
@@ -711,21 +1023,29 @@ R1 moved it.
Stated plainly so the direction can be killed cheaply if it is not:
- **W0 is worth doing regardless of the wavetable ambition — and R1 leaves that argument
exactly as it was.** Daniel cannot currently set a correct loop on his own routine material,
and the radius fix alone leaves a one-pixel drag able to produce a 0.997-period loop. That
is defect-class. **It is now also the whole of the near-term scope.**
- **W0 is worth doing regardless of the wavetable ambition — and neither ruling touches that
argument.** Daniel cannot currently set a correct loop on his own routine material, and
Ω-W2-T5's radius bound alone leaves a one-pixel drag able to produce a 0.997-period loop.
That is defect-class. **R2 adds a second reason rather than replacing this one:** a
non-whole-period seam step scales with the source's high-frequency content, so complex
material makes the same defect louder.
- **W1 is worth doing if Daniel's captures are ever multi-cycle and evolving. ANSWERED
2026-08-03: they are not — *"just static captures right now."*** The position knob has
nothing to scrub, and the test written here before the answer arrived is the reason F5
closes cleanly rather than by preference. The condition is unchanged and still live as a
trigger: if the practice changes, this bullet is what re-opens F5.
- **W2 is worth doing if the material is ever anything but sine. STILL UNANSWERED — this is
the outstanding question.** A sine played up four octaves is fine; a captured saw is not. If
Daniel's practice is harmonically rich single-cycle captures played across the keyboard, W2
stops being optional and becomes the actual answer to "turn this into a wavetable synth."
**R1 does not bear on it either way**, because staticness and harmonic content are
independent properties of the same capture.
- **W2 is worth doing if the material is ever anything but sine. ANSWERED 2026-08-03: it is
*"yes, complex waveforms must be supported purely."*** A sine played up four octaves is
fine; a captured saw is not, and *purely* forbids the difference. W2 stops being optional
and becomes the actual answer to "turn this into a wavetable synth." **Both of this
section's tests have now been met by evidence rather than by preference, which is the whole
reason for writing them before the answers existed** — R1 killed W1 on the first, R2
committed W2 on the second, and neither turned on anyone's taste.
- **The test that remains, and it now sits under W2 rather than beside it: is a DECLARED
quality mode acceptable, or must the instrument simply sound right with no switch?** F6(b)
buys the migration bar with a control; F6(c) buys one less control with a behaviour change
in every saved instance that plays above its root. If Daniel's answer is "it should just be
right", F6 goes to (c) and the parameter and deck-slot costs in §6.8/§6.9 vanish with it.
---
@@ -736,15 +1056,27 @@ product-designer may not edit `CLAUDE.md`. **Flagged for staff-engineer or doc-k
`single-cycle-and-wavetable.md` to that list.**
`docs/PLAN.md` carries one pointer to this doc, as item 4 under "Flagged for awareness — not
blocking, but decision-grade." **No phase, no wave and no track is proposed there**, and R1
does not change that — `PLAN.md` is the *active on-deck specification list*, and putting a
phase in it while F8 is unruled would both put an unbriefable phase in that list and falsify
the plan's standing claim that Λ is the only phase carrying unanswered [Daniel]-class
blocking, but decision-grade." **No phase, no wave and no track is proposed there**, and
neither R1 nor R2 changes that — `PLAN.md` is the *active on-deck specification list*, and
putting a phase in it while F8 is unruled would both put an unbriefable phase in that list and
falsify the plan's standing claim that Λ is the only phase carrying unanswered [Daniel]-class
questions.
**What R1 does change is that the proposable phase is now well-defined: W0-only, F8(a).**
Five forks still sit inside W0's own content (F1, F2, F3, F4, F7), F8 governs whether and
when it dispatches, and F6 belongs to W2 rather than to any of this — so the *shape* is
settled while the *specification* is not. When F8 is ruled, this doc becomes the backing
product doc for that phase in the ordinary way — the Γ / Ε / Ρ / Λ pattern, not the doc-less
**What the two rulings change is what a proposable phase would contain.** After R1 it was
W0-only. After R2 it is **two waves — W0 then W2** — with the order forced by W2 consuming
W0's declared period:
- **Wave 1 (W0)** is briefable the moment F1, F2, F3, F4 and F7 are ruled. It is
defect-class, migration-clean, parameter-free and guardrail-free.
- **Wave 2 (W2)** needs F6 plus the four new shape forks (F9F12), and carries a measurement
obligation and a permanent parameter id.
**That asymmetry is what re-opens F8** and makes (d) — two phases rather than one — a serious
option R1 had closed off: W0 should not wait on W2's forks. When F8 is ruled, this doc becomes
the backing product doc in the ordinary way — the Γ / Ε / Ρ / Λ pattern, not the doc-less
Ψ / Ω one.
**One documentation obligation this doc creates but does not own, restated here so it is not
lost:** Ω-W2-T5's **Ctrl-defeats-snap** gesture needs a home in
`docs/product/instrument-control-surface.md`, whose §4.1 currently reads as though waveform
markers carry no modifier at all. **Flagged for doc-keeper.**