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:
+103
-35
@@ -220,40 +220,62 @@ in Γ-W4-T1 and changes no wave boundary.
|
|||||||
*"supporting smoothness at this micro scale would turn this into a wavetable synth, which
|
*"supporting smoothness at this micro scale would turn this into a wavetable synth, which
|
||||||
is desirable."* The product framing is `docs/product/single-cycle-and-wavetable.md`.
|
is desirable."* The product framing is `docs/product/single-cycle-and-wavetable.md`.
|
||||||
|
|
||||||
**It framed EIGHT [Daniel]-class forks. ONE is now ruled and SEVEN remain open**, so it
|
**It framed EIGHT [Daniel]-class forks; THREE rulings have since landed against it, and it
|
||||||
still gets a pointer here rather than a phase section: `PLAN.md` is the active on-deck
|
now carries TWELVE forks — ONE settled and ELEVEN open.** So it still gets a pointer here
|
||||||
specification list, and a phase whose specification rests on seven unruled forks is
|
rather than a phase section: `PLAN.md` is the active on-deck specification list, and a
|
||||||
neither briefable nor consistent with the standing claim above that **Λ is the only phase
|
phase whose specification rests on eleven unruled forks is neither briefable nor consistent
|
||||||
in this plan with unanswered [Daniel]-class questions**. That claim is unaffected by this
|
with the standing claim above that **Λ is the only phase in this plan with unanswered
|
||||||
item — there is no phase here to except. When the remaining forks are ruled, the doc
|
[Daniel]-class questions**. That claim is unaffected by this item — there is no phase here
|
||||||
becomes a phase's backing product doc in the ordinary way (the Γ / Ε / Ρ / Λ pattern, not
|
to except. When the remaining forks are ruled, the doc becomes a phase's backing product
|
||||||
the doc-less Ψ / Ω one).
|
doc in the ordinary way (the Γ / Ε / Ρ / Λ pattern, not the doc-less Ψ / Ω one).
|
||||||
|
|
||||||
**The ruling — Daniel, 2026-08-03:** *"I don't want actual frame-indexed wave tables yet,
|
**R1 — Daniel, 2026-08-03:** *"I don't want actual frame-indexed wave tables yet, that is
|
||||||
that is out of scope. just static captures right now."* It closes fork F5 (the
|
out of scope. just static captures right now."* It closes fork F5 (the wavetable-position
|
||||||
wavetable-position knob does not ship now), confirms the multi-frame wavetable kind out by
|
knob does not ship now) and confirms the multi-frame wavetable kind out by ruling rather
|
||||||
ruling rather than only by the doc's argument, and **narrows the near-term scope to the
|
than only by the doc's argument. *"Yet"* is recorded as a scope ruling, not a rejection;
|
||||||
doc's W0 — loop-grid recalibration for micro material, which is defect-class work with no
|
the doc keeps the deferred case whole and names the single trigger that would revive it.
|
||||||
new parameter, no format change and no `process()` involvement.** *"Yet"* is recorded as a
|
|
||||||
scope ruling, not a rejection; the doc keeps the deferred case whole and names the single
|
|
||||||
trigger that would revive it. **The ruling does NOT settle band-limiting** — the question
|
|
||||||
that decides it is still outstanding with Daniel, and nothing in the doc pre-rules it. Per
|
|
||||||
the doc's §7.1.
|
|
||||||
|
|
||||||
**Two things in it are decision-grade for work already in this plan, which is why it is
|
**R2 — Daniel, 2026-08-03**, answering the doc's own outstanding question (*is the material
|
||||||
flagged rather than merely filed:**
|
ever anything but sine?*): *"yes, complex waveforms must be supported purely."* **It closes
|
||||||
|
fork F6 IN — band-limited playback is in scope** — and reads *purely* as *faithfully,
|
||||||
|
without artifacts the capture does not contain*. Consequences that matter to this plan:
|
||||||
|
|
||||||
|
- **The near-term scope is now the doc's W0 AND W2, in that order** — W0 (loop-grid
|
||||||
|
recalibration; defect-class, no new parameter, no format change, no `process()`
|
||||||
|
involvement) and then W2, which consumes W0's declared period.
|
||||||
|
- **W2 is the first item in that doc that reaches `process()`.** The per-sample read binds
|
||||||
|
a level-indexed buffer rather than a fixed member: no branch, no dispatch, no virtual —
|
||||||
|
so this plan's `process()` guardrail is satisfied in kind — but `voice.h` is header-inline
|
||||||
|
with no LTO configured, so it is a **measurement obligation, not an argument**. A W2 track
|
||||||
|
with no before/after number has an opinion, not an acceptance criterion.
|
||||||
|
- **It re-opens a one-way door and re-contends the deck headroom.** W2 wants a declared
|
||||||
|
mode: a permanent VST3 parameter id on Γ-W4-T1's frozen table, and a control against
|
||||||
|
`instrument-control-surface.md` §1.6's 82 px. The doc's §6.8/§6.9 cost both, including
|
||||||
|
the caption-slack slot that may make the control free.
|
||||||
|
- **R2 does NOT rule the shape.** Four new forks (F9 table shape, F10 level selection, F11
|
||||||
|
interpolator, F12 Preserve coverage) are minted open with a recommended package.
|
||||||
|
|
||||||
|
**R3 — Daniel, 2026-08-03**, on the Phase Ω conflict this item used to record: *"call it
|
||||||
|
part of omega, that was a fence you built not me."* **The conflict is RESOLVED, and the
|
||||||
|
resolution is recorded in Phase Ω itself** — the phase's zero-crossing-snap criterion and
|
||||||
|
Ω-W1-T5's two criterion sites are amended in place. The snap-radius work is **Ω-W2-T5**
|
||||||
|
(branch `omega-w2-t5-snap-radius`, `b7b7e88`); the fence it appeared to break was a scope
|
||||||
|
constraint on Ω's own tracks, not a product ruling, and Ω-W1-T5's work is not invalidated
|
||||||
|
by it. R3 also creates a documentation gap — **Ctrl-defeats-snap on a marker drag** is a new
|
||||||
|
user-visible gesture that `docs/product/instrument-control-surface.md` §4.1 currently
|
||||||
|
contradicts — flagged for doc-keeper at Ω-W2-T5's note in this file.
|
||||||
|
|
||||||
|
**What remains decision-grade for work already in this plan:**
|
||||||
|
|
||||||
- **The immediate defect it came from is NOT its subject.** `nearestZeroCrossing`
|
|
||||||
(`src/core/instrument/ui/waveform_view.cpp:181-210`) fans out across the whole buffer
|
|
||||||
with no radius bound; a snap-radius track is in flight against it. **But 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.** A snap-radius change alters
|
|
||||||
exactly that behaviour. **Whoever owns Ω's criteria must reconcile the two** — this item
|
|
||||||
records the conflict, it does not resolve it.
|
|
||||||
- **Sequencing, if any of it is ever dispatched.** Every candidate touches
|
- **Sequencing, if any of it is ever dispatched.** Every candidate touches
|
||||||
`waveform_view.{h,cpp}`, `editor_input_waveform.cpp` and `editor_controls.cpp`'s
|
`waveform_view.{h,cpp}`, `editor_input_waveform.cpp` and `editor_controls.cpp`'s
|
||||||
`waveMarksFor` / `grabbableMarks` — Ω-W1-T5's exact surface. Nothing from that doc
|
`waveMarksFor` / `grabbableMarks` — Ω-W1-T5's exact surface. **Nothing from that doc
|
||||||
should dispatch until Ω-W1-T5 and the snap-radius track have both landed.
|
dispatches until Ω-W1-T5 has landed**, and it should build on Ω-W2-T5's radius rule
|
||||||
|
rather than replacing it.
|
||||||
|
- **F8 is re-opened by R2, not narrowed.** W0 and W2 now differ in kind — one is
|
||||||
|
migration-clean defect work, the other a measured DSP build with a frozen parameter and
|
||||||
|
five unruled forks — so "two phases rather than one" became a serious option again. **If
|
||||||
|
F9–F12 are unruled when W0 is ready, do not let W0 wait on them.**
|
||||||
|
|
||||||
## Phase-wide acceptance criteria
|
## Phase-wide acceptance criteria
|
||||||
|
|
||||||
@@ -353,6 +375,14 @@ statements become untrue the moment this phase lands:
|
|||||||
**Do not re-litigate these.** There is no product doc to index them in; this table is their one
|
**Do not re-litigate these.** There is no product doc to index them in; this table is their one
|
||||||
home, and a brief for any Ω track is written against it.
|
home, and a brief for any Ω track is written against it.
|
||||||
|
|
||||||
|
**One later ruling binds this phase from OUTSIDE that table, and is named here so the table is
|
||||||
|
not read as complete: R3 (Daniel, 2026-08-03)** — *"call it part of omega, that was a fence you
|
||||||
|
built not me"* — which makes the snap-radius work **Ω-W2-T5** and supersedes this phase's
|
||||||
|
zero-crossing-snap criterion. It is recorded in `docs/product/single-cycle-and-wavetable.md`
|
||||||
|
§7.1 rather than here, because it arose against that doc; it is deliberately **not** renamed
|
||||||
|
into the `Ω-F` series, since one ruling with two identifiers is worse than one ruling with a
|
||||||
|
cross-reference.
|
||||||
|
|
||||||
| Ruling | What it settled | Bound into |
|
| Ruling | What it settled | Bound into |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **Ω-F1** | **Widen the marker-alignment work to the correct scope.** The defect is the overlay↔waveform coordinate mapping as a whole, not the START mark — a class defect with four independent causes, inherited by every overlay | Ω-W1-T5 |
|
| **Ω-F1** | **Widen the marker-alignment work to the correct scope.** The defect is the overlay↔waveform coordinate mapping as a whole, not the START mark — a class defect with four independent causes, inherited by every overlay | Ω-W1-T5 |
|
||||||
@@ -371,9 +401,16 @@ hope.
|
|||||||
These bind every track in this phase, in addition to the plan-wide set above.
|
These bind every track in this phase, in addition to the plan-wide set above.
|
||||||
|
|
||||||
- **No behaviour changes outside the named defect.** Every track carries that criterion
|
- **No behaviour changes outside the named defect.** Every track carries that criterion
|
||||||
explicitly. The Gate deck face, the zero-crossing snap, the ballistics constants, the 500 ms
|
explicitly. The Gate deck face, the ballistics constants, the 500 ms bank/bake poll body and
|
||||||
bank/bake poll body and the audio thread are each named below as things that must come out
|
the audio thread are each named below as things that must come out the other side identical.
|
||||||
the other side identical.
|
**The zero-crossing snap was named here too, and that clause is SUPERSEDED — ruling R3,
|
||||||
|
Daniel, 2026-08-03:** *"call it part of omega, that was a fence you built not me."* It was a
|
||||||
|
**scope fence** — written to stop Ω's own tracks perturbing the snap while they fixed the
|
||||||
|
coordinate mapping — never a finding that `nearestZeroCrossing`'s unbounded fan-out was
|
||||||
|
correct. Daniel's DAW evidence (a one-cycle 60 Hz capture whose loop start teleports to the
|
||||||
|
half-cycle) supersedes it. **The superseding track is Ω-W2-T5 `snap-radius`**, which bounds
|
||||||
|
the snap to `kMarkerGrabWidth` and adds Ctrl-defeats-snap on a marker drag. The fence still
|
||||||
|
binds every OTHER Ω track: no track but Ω-W2-T5 may change the snap.
|
||||||
- **Nothing is added to any hot path.** No track here touches `process()`, the `peaks` envelope
|
- **Nothing is added to any hot path.** No track here touches `process()`, the `peaks` envelope
|
||||||
compute, audition, or the realtime-capture tick. Ω-W1-T3's work is entirely UI-thread paint
|
compute, audition, or the realtime-capture tick. Ω-W1-T3's work is entirely UI-thread paint
|
||||||
cost; Ω-W1-T1's is entirely main-thread REAPER API cost.
|
cost; Ω-W1-T1's is entirely main-thread REAPER API cost.
|
||||||
@@ -808,11 +845,20 @@ stays 1:1 with the sample's time axis" **is currently false by 2 px.**
|
|||||||
- **De-inset the waveform** — `[propose]`, **requires justification before it is taken**: it
|
- **De-inset the waveform** — `[propose]`, **requires justification before it is taken**: it
|
||||||
touches the shared `draw_kit`, which also serves the docked bank panel and the browse-card
|
touches the shared `draw_kit`, which also serves the docked bank panel and the browse-card
|
||||||
thumbnails, so its blast radius is three surfaces rather than one.
|
thumbnails, so its blast radius is three surfaces rather than one.
|
||||||
- **The zero-crossing snap must be preserved EXACTLY.** `nearestZeroCrossing`
|
- **The zero-crossing snap must be preserved EXACTLY *by this track*.** `nearestZeroCrossing`
|
||||||
(`waveform_view.cpp:170-199`) fans out symmetrically, probing `t−d` before `t+d` so an
|
(`waveform_view.cpp:181-210`) fans out symmetrically, probing `t−d` before `t+d` so an
|
||||||
equidistant tie resolves to the lower frame; it runs on the mono cache at drag time and is
|
equidistant tie resolves to the lower frame; it runs on the mono cache at drag time and is
|
||||||
exempt for the crossfade handle. **Behaviour-identical after the change** is an acceptance
|
exempt for the crossfade handle. **Behaviour-identical after the change** is an acceptance
|
||||||
criterion, not an assumption.
|
criterion, not an assumption.
|
||||||
|
|
||||||
|
> **Amended, ruling R3 (Daniel, 2026-08-03) — read this as scope, not as a verdict on the
|
||||||
|
> snap.** This clause bound T5 to change the coordinate mapping WITHOUT perturbing the snap,
|
||||||
|
> and **T5 met it: the fan-out it received is the fan-out it returned, and its tie-rule
|
||||||
|
> fixtures pass unmodified.** The snap's behaviour was subsequently changed on purpose by a
|
||||||
|
> DIFFERENT track — **Ω-W2-T5 `snap-radius`**, which bounds the fan-out to
|
||||||
|
> `kMarkerGrabWidth` — so a later reader finding a bounded snap in the tree is looking at
|
||||||
|
> Ω-W2-T5's work, not at a T5 violation. **Bounding which frames are REACHABLE does not
|
||||||
|
> change how a reachable tie RESOLVES**, which is why T5's fixtures survive it untouched.
|
||||||
- **One question the track must ANSWER, not dodge:** what "sample-accurate" means when
|
- **One question the track must ANSWER, not dodge:** what "sample-accurate" means when
|
||||||
`frames < columns` and one frame spans many pixel columns. A marker must land somewhere
|
`frames < columns` and one frame spans many pixel columns. A marker must land somewhere
|
||||||
inside its own frame's column span; which end, and whether the inverse `xToFrame`
|
inside its own frame's column span; which end, and whether the inverse `xToFrame`
|
||||||
@@ -828,6 +874,9 @@ stays 1:1 with the sample's time axis" **is currently false by 2 px.**
|
|||||||
at the last frame, and at an interior frame, asserted in the pure fixtures.
|
at the last frame, and at an interior frame, asserted in the pure fixtures.
|
||||||
- `frames < columns` and `frames > columns` are both covered.
|
- `frames < columns` and `frames > columns` are both covered.
|
||||||
- The zero-crossing snap returns the identical frame for the identical input, ties included.
|
- The zero-crossing snap returns the identical frame for the identical input, ties included.
|
||||||
|
**(Scope criterion on THIS track, discharged. Superseded phase-wide by R3 — the snap's
|
||||||
|
radius bound is Ω-W2-T5's deliberate change, and this track's tie-rule fixtures pass
|
||||||
|
unmodified under it.)**
|
||||||
- Trigger shows no loop marks and no loop caption; Gate-with-loop-off shows grey marks that
|
- Trigger shows no loop marks and no loop caption; Gate-with-loop-off shows grey marks that
|
||||||
still drag, and dragging one still turns the enable on.
|
still drag, and dragging one still turns the enable on.
|
||||||
- `docs/product/instrument-control-surface.md` §6.3(d) and §6.4 agree with the shipped
|
- `docs/product/instrument-control-surface.md` §6.3(d) and §6.4 agree with the shipped
|
||||||
@@ -848,8 +897,27 @@ fix** (the Λ-W3 precedent).
|
|||||||
| **T1** `view-topology-cache` | `src/core/view/` — tree / `parentOf` / handle-map memoization |
|
| **T1** `view-topology-cache` | `src/core/view/` — tree / `parentOf` / handle-map memoization |
|
||||||
| **T2** `content-detect-throttle` | `src/shell/panel/panel_input.cpp` — the 30 Hz detector's cadence |
|
| **T2** `content-detect-throttle` | `src/shell/panel/panel_input.cpp` — the 30 Hz detector's cadence |
|
||||||
| **T3** `omega-verification-sweep` | `docs/VERIFICATION.md` — the DAW record for all of Ω-W1 |
|
| **T3** `omega-verification-sweep` | `docs/VERIFICATION.md` — the DAW record for all of Ω-W1 |
|
||||||
|
| **T5** `snap-radius` | `src/core/instrument/ui/waveform_view.{h,cpp}` + the marker-drag input path — **spec'd nowhere; see below** |
|
||||||
|
|
||||||
**Shared files in the wave:** none. The three tracks touch three disjoint directories.
|
**Shared files in the wave:** none. T1–T3 touch three disjoint directories, and T5 touches a
|
||||||
|
fourth (Ω-W1-T5's surface, which lands first).
|
||||||
|
|
||||||
|
**Ω-W2-T5 has no spec section here, on purpose.** It arrived from ruling R3 — Daniel,
|
||||||
|
2026-08-03, recorded in `docs/product/single-cycle-and-wavetable.md` §7.1 and summarized at
|
||||||
|
"Flagged for awareness" item 4 above — after this phase was written, and is **committed on
|
||||||
|
branch `omega-w2-t5-snap-radius` (`b7b7e88`)**: it
|
||||||
|
bounds `nearestZeroCrossing`'s fan-out to `kMarkerGrabWidth` (5 px) and adds
|
||||||
|
**Ctrl-defeats-snap** on a marker drag. **Numbered T5 rather than T4 because it inherits
|
||||||
|
Ω-W1-T5's file surface; there is no Ω-W2-T4.** Two things it needs from other owners:
|
||||||
|
|
||||||
|
- **Flagged for doc-keeper:** its record belongs in `docs/COMPLETED.md`. This plan names it
|
||||||
|
only so the phase's own superseded criteria can point at what superseded them.
|
||||||
|
- **Flagged for doc-keeper — a documentation gap, not a defect.** *Ctrl-defeats-snap on a
|
||||||
|
marker drag* is a new **user-visible gesture** with no home in
|
||||||
|
`docs/product/instrument-control-surface.md`, and §4.1's exclusion block there — which
|
||||||
|
excludes waveform markers from the modifier law because *"overloading Shift there would
|
||||||
|
collide with a shipped gesture"* — now reads as a claim that markers carry no modifier at
|
||||||
|
all. That block needs amending and the gesture needs documenting.
|
||||||
|
|
||||||
#### Ω-W2-T1 — `view-topology-cache` *(measurement-gated on Ω-W1-T1's result)*
|
#### Ω-W2-T1 — `view-topology-cache` *(measurement-gated on Ω-W1-T1's result)*
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
> harmonics from the shape distortion. supporting smoothness at this micro scale would turn
|
||||||
> this into a wavetable synth, which is desirable."*
|
> this into a wavetable synth, which is desirable."*
|
||||||
|
|
||||||
**The immediate defect — `nearestZeroCrossing`'s unbounded fan-out — is owned by a
|
**The immediate defect — `nearestZeroCrossing`'s unbounded fan-out — is owned by
|
||||||
snap-radius track already in flight and is NOT this doc's subject.** It is described here
|
Ω-W2-T5 `snap-radius` and is NOT this doc's subject.** It is described here only where the
|
||||||
only where the reasoning needs it, because the defect and the direction share a diagnosis
|
reasoning needs it, because the defect and the direction share a diagnosis and the fix for one
|
||||||
and the fix for one does not deliver the other.
|
does not deliver the other.
|
||||||
|
|
||||||
Status: **framed by product-designer 2026-08-03; ONE ruling has since landed against it —
|
Status: **framed by product-designer 2026-08-03; THREE rulings have since landed against it —
|
||||||
R1, recorded in full at §7.1.** Daniel, 2026-08-03:
|
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
|
> *"I don't want actual frame-indexed wave tables yet, that is out of scope. just static
|
||||||
> captures right now."*
|
> captures right now."*
|
||||||
|
|
||||||
R1 **closes F5** (W1, the wavetable-position knob, does not ship now) and **confirms W3 out
|
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
|
**R2 — Daniel, 2026-08-03**, answering this doc's own second outstanding question (*is the
|
||||||
anything but sine?* — is still outstanding, Daniel knows it is outstanding, and it is
|
material ever anything but sine?*):
|
||||||
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.
|
|
||||||
|
|
||||||
**The near-term scope is therefore W0, and only W0.** Read §2 as one body of work plus two
|
> *"yes, complex waveforms must be supported purely."*
|
||||||
deferrals, not as four equal candidates.
|
|
||||||
|
**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 — **F9–F12**, 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** —
|
**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
|
`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
|
"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
|
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.
|
~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 wavetable synths solve it with per-octave lowpassed tables. That is a real build with a
|
||||||
real build with a real migration cost, and it is separable from everything else here.
|
real migration cost. **R2 rules it IN.** It is no longer this doc's hedge; it is half the
|
||||||
**After R1 it is also the only one of the four shapes whose scope is still undecided.**
|
work, and §1.8's second half is where its cost is actually derived.
|
||||||
|
|
||||||
**The four shapes, and where each now stands:**
|
**The four shapes, and where each now stands:**
|
||||||
|
|
||||||
| | What | Cost | Standing |
|
| | 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** |
|
| **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: F9–F12 |
|
||||||
| **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** |
|
| **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
|
**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
|
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.
|
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
|
**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
|
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
|
**And the operational headline, rewritten by R3: the Phase Ω collision is settled, and the
|
||||||
`overlay-mapping` owns right now**, and Phase Ω's own acceptance criteria name the
|
sequencing constraint that survives it is a file-ownership one, not a criteria one.**
|
||||||
zero-crossing snap as a thing that "must come out the other side identical" (`PLAN.md`,
|
Everything here lands in the files Ω-W1-T5 `overlay-mapping` owns, so nothing from this doc
|
||||||
Phase-Ω acceptance criteria; Ω-W1-T5's outline entry says *"Zero-crossing snap
|
dispatches until that track has landed. The *criteria* conflict — Ω naming the zero-crossing
|
||||||
behaviour-identical"*). Both the in-flight snap-radius track and anything from this doc
|
snap as behaviour-identical while a snap-radius change alters exactly that — is dissolved:
|
||||||
contend with that. §6.7 states what needs reconciling.
|
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
|
`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
|
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`
|
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
|
teleports to the one at the half-cycle. That is Daniel's octave-doubling exactly, and
|
||||||
radius track in flight closes it.
|
Ω-W2-T5's radius bound closes it.
|
||||||
|
|
||||||
**But the deeper point survives the radius fix.** The snap's premise is that a zero crossing
|
**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
|
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
|
period) or leaves the user to discover it. Auto-switching engines is rejected outright below
|
||||||
— it would change the sound of a saved instance.
|
— 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
|
`Voice::advanceFrame` reads with **linear interpolation** between the two bracketing source
|
||||||
frames at `readPos_`, advancing by `ratio_` (`voice.h:622-627, 705`). Two independent
|
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
|
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.
|
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
|
**The standard answer, and it is what makes a wavetable synth a wavetable synth: per-octave
|
||||||
pyramid.** Per-octave successively lowpassed copies of the table, precomputed at load, with
|
successively lowpassed copies of the table, precomputed at load, with the read level chosen
|
||||||
the read level chosen per voice from the ratio. Its properties fit this codebase unusually
|
per voice from the playback ratio.** Precompute is **cold and off the audio thread** —
|
||||||
well:
|
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:
|
||||||
- Precompute is **cold and off the audio thread** — exactly where `period_detect` already
|
nothing persists them and they take no rung of the payload ladder.
|
||||||
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.
|
|
||||||
|
|
||||||
Raising the read to cubic Hermite is a separate, straight-line 4-tap change with no
|
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
|
**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
|
reopens sounding identical"). Fork **F6**, which R2 narrows to *conditional or unconditional*
|
||||||
default. Fork **F6**.
|
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
|
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.
|
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
|
**Daniel, 2026-08-03: *"yes, complex waveforms must be supported purely."*** A static
|
||||||
of playing *any* short table across the keyboard; whether the table's frames evolve is
|
single-cycle capture of a saw, played at C5, aliases exactly as §1.8 describes; *purely*
|
||||||
irrelevant to whether its partials fold. A static single-cycle capture of a saw, played at
|
forbids that. **F6 is no longer "does this ship" — it is "conditional or unconditional".**
|
||||||
C5, aliases exactly as §1.8 describes. **F6 remains open and unruled.**
|
|
||||||
|
|
||||||
§1.8. Orthogonal to W0 and W1, and the only one of the four that is a genuine DSP build. It
|
It remains the only one of the four shapes that is a genuine DSP build, and it is
|
||||||
is also **independently valuable outside this direction**: it improves every ordinary sample
|
**independently valuable outside this direction**: it improves every ordinary sample played
|
||||||
played more than an octave above its root, which is a much larger population than
|
more than an octave above its root, a much larger population than single-cycle captures —
|
||||||
single-cycle captures. That argues for judging it on its own merits rather than as
|
**for Varispeed voices, and for Preserve voices only if F12 goes to (b)** (§1.8.1(3)).
|
||||||
wavetable-support. Fork **F6**.
|
|
||||||
|
**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)
|
### 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;
|
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.
|
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
|
**A threshold derived from the codebase rather than from taste.** A capture is a micro
|
||||||
candidate when *both*:
|
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
|
**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.
|
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
|
## 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.
|
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
|
**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.**
|
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
|
- **Serum / Massive / Vital** — per-octave band-limited tables, built by spectral truncation
|
||||||
of W2, and their universal adoption of it is the evidence that §1.8 is not a theoretical
|
of a whole-period table rather than by filter design. This is the whole of W2, and their
|
||||||
concern. **Still live: F6 is open.**
|
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
|
- **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
|
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
|
established precedent for F2's period grid; we would not be inventing it. **The load-bearing
|
||||||
load-bearing precedent, since W0 is the near-term scope.**
|
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
|
- **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
|
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.
|
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
|
- **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.)*
|
start already denotes. *(Deferred by R1; kept for the record.)*
|
||||||
- **W2 is not.** Both halves change the rendered audio of existing captures. Fork **F6**
|
- **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
|
**R2 retires the post-R1 relief here.** The near-term scope is no longer migration-clean as a
|
||||||
in this doc sits behind an open fork rather than inside the work.
|
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
|
- 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
|
- 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
|
wanted live, it belongs in the existing three-tier commit classification
|
||||||
(`deckParamCommit` / `liveCommitFor`) rather than in a new mechanism. *(Deferred by R1.)*
|
(`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
|
- **W2's table build is load-time**, on `period_detect`'s own link-graph precedent. **Its
|
||||||
4-tap with no dispatch.
|
level selection and its read are not.**
|
||||||
- **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.**
|
|
||||||
|
|
||||||
**Consequence of R1 worth stating plainly: the entire near-term scope is load-time and
|
**What W2 costs on the per-voice-per-sample path, as precisely as this doc can state it
|
||||||
UI-thread only. Nothing in W0 goes near `process()`.** The guardrail question that would have
|
without a measurement:**
|
||||||
needed a measurement before anything could be promised no longer gates this direction.
|
|
||||||
|
|
||||||
### 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}`,
|
### 6.7 Phase Ω — the criteria conflict is SETTLED by R3; the file-ownership constraint stands
|
||||||
`editor_input_waveform.cpp` and `editor_controls.cpp`'s `waveMarksFor`/`grabbableMarks`** —
|
|
||||||
which is the complete file set any period-grid work touches.
|
**This was the item most likely to cost someone a merge. R3 removes half of it.**
|
||||||
- **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
|
**Daniel, 2026-08-03:** *"call it part of omega, that was a fence you built not me."*
|
||||||
(*"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
|
- **The conflict this section recorded is dissolved, not sequenced around.** The snap-radius
|
||||||
something this doc can assert — it needs reconciling by whoever owns Ω's criteria, and the
|
work **is** a Phase Ω track — **Ω-W2-T5** (branch `omega-w2-t5-snap-radius`, commit
|
||||||
purpose of this bullet is to make sure someone does.
|
`b7b7e88`) — which bounds the snap to `kMarkerGrabWidth` (5 px, `waveform_view.h:70`) and
|
||||||
- **Recommended sequencing: nothing from this doc dispatches until Ω-W1-T5 and the
|
adds **Ctrl-defeats-snap** on a marker drag.
|
||||||
snap-radius track have both landed.** The period grid should be written against the
|
- **The fence was a scope constraint, not a product ruling.** Ω's acceptance criterion naming
|
||||||
post-Ω single coordinate mapping, not against today's four-discrepancy one, and it should
|
the zero-crossing snap among the things that "must come out the other side identical" was
|
||||||
build on whatever radius rule the in-flight track settles rather than replacing it.
|
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
|
### 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
|
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.
|
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
|
**R1 removed the near-term exposure here; R2 restores it, in a much cheaper form.** W2's mode
|
||||||
new parameter left in this doc is W2's mode, which sits behind an open fork — so **nothing in
|
is a new parameter, so the door is open again — but a two-state mode is the least dangerous
|
||||||
the near-term scope opens a one-way door.** A position knob's taper, the item that most
|
parameter this doc could have produced:
|
||||||
needed to be right first time, is a decision this direction no longer has to make under time
|
|
||||||
pressure.
|
- **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
|
### 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
|
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.
|
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
|
**R1 relieved it; R2 spends it back.** W0's five parts still add no control to the deck — a
|
||||||
default, and two greyed affordances are all changes to existing surfaces — and F3's
|
snap grid, a default, and two greyed affordances are all changes to existing surfaces — and
|
||||||
"Tune to loop" is the one item that might want a hit target, which is a far smaller ask than
|
F3's "Tune to loop" is the one item that might want a hit target, which is a far smaller ask
|
||||||
a knob. **The 82 px is uncontended by the near-term scope.** It comes back the moment F6 is
|
than a knob. **But W2's mode toggle is exactly the control §1.6 was warning about**, and the
|
||||||
ruled toward a mode toggle, so the headroom should not be spent elsewhere on the assumption
|
warning at the end of the post-R1 paragraph ("it comes back the moment F6 is ruled toward a
|
||||||
that this direction has released it permanently.
|
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
|
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
|
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
|
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.
|
||||||
|
|
||||||
|
**F9–F12 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 |
|
| 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 |
|
| **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 — 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) |
|
| **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 |
|
| **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 |
|
| **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 |
|
| **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 F9–F12 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 F9–F12 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:**
|
**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
|
- The pre-seam crossfade cannot deliver micro-scale smoothness (§1.2). Structural — and
|
||||||
**untouched by R1**, which changes nothing about the loop's arithmetic.
|
**untouched by R1 and by R2 alike.** The identity-map result is arithmetic on any
|
||||||
- `period_detect` cannot see a single cycle (§1.5). Structural — and **untouched by R1**. The
|
`P`-periodic source, not a property of sines: at `length == P` the tap `pos − length` equals
|
||||||
declare-vs-detect split survives intact; deferring W1 removes the scrub, not the
|
the head for a saw exactly as it does for a sine. A complex waveform does not create a job
|
||||||
multi-cycle capture that detection serves (§1.4).
|
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
|
- 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.
|
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
|
- 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.
|
case whole and names the single trigger that revives it.
|
||||||
- **F8's option (b) eliminated** — there is no W0+W1 phase to size.
|
- **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?* —
|
- **F6 / band-limiting.** Left outstanding by R1 and **answered by R2 below.**
|
||||||
is outstanding, and Daniel knows it is. Aliasing is orthogonal to whether a table's frames
|
- **The Phase Ω conflict in §6.7.** Left open by R1 and **dissolved by R3 below.**
|
||||||
evolve; a static single-cycle saw played at C5 folds exactly as §1.8 says. **Do not read
|
|
||||||
R1 as answering it.**
|
|
||||||
- **F1, F2, F3, F4, F7, F8** — all still Daniel's. R1 shifts what some of them are choosing
|
- **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.
|
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:
|
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
|
- **W0 is worth doing regardless of the wavetable ambition — and neither ruling touches that
|
||||||
exactly as it was.** Daniel cannot currently set a correct loop on his own routine material,
|
argument.** Daniel cannot currently set a correct loop on his own routine material, and
|
||||||
and the radius fix alone leaves a one-pixel drag able to produce a 0.997-period loop. That
|
Ω-W2-T5's radius bound alone leaves a one-pixel drag able to produce a 0.997-period loop.
|
||||||
is defect-class. **It is now also the whole of the near-term scope.**
|
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
|
- **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
|
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
|
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
|
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.
|
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
|
- **W2 is worth doing if the material is ever anything but sine. ANSWERED 2026-08-03: it is —
|
||||||
the outstanding question.** A sine played up four octaves is fine; a captured saw is not. If
|
*"yes, complex waveforms must be supported purely."*** A sine played up four octaves is
|
||||||
Daniel's practice is harmonically rich single-cycle captures played across the keyboard, W2
|
fine; a captured saw is not, and *purely* forbids the difference. W2 stops being optional
|
||||||
stops being optional and becomes the actual answer to "turn this into a wavetable synth."
|
and becomes the actual answer to "turn this into a wavetable synth." **Both of this
|
||||||
**R1 does not bear on it either way**, because staticness and harmonic content are
|
section's tests have now been met by evidence rather than by preference, which is the whole
|
||||||
independent properties of the same capture.
|
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.**
|
`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
|
`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
|
blocking, but decision-grade." **No phase, no wave and no track is proposed there**, and
|
||||||
does not change that — `PLAN.md` is the *active on-deck specification list*, and putting a
|
neither R1 nor R2 changes that — `PLAN.md` is the *active on-deck specification list*, and
|
||||||
phase in it while F8 is unruled would both put an unbriefable phase in that list and falsify
|
putting a phase in it while F8 is unruled would both put an unbriefable phase in that list and
|
||||||
the plan's standing claim that Λ is the only phase carrying unanswered [Daniel]-class
|
falsify the plan's standing claim that Λ is the only phase carrying unanswered [Daniel]-class
|
||||||
questions.
|
questions.
|
||||||
|
|
||||||
**What R1 does change is that the proposable phase is now well-defined: W0-only, F8(a).**
|
**What the two rulings change is what a proposable phase would contain.** After R1 it was
|
||||||
Five forks still sit inside W0's own content (F1, F2, F3, F4, F7), F8 governs whether and
|
W0-only. After R2 it is **two waves — W0 then W2** — with the order forced by W2 consuming
|
||||||
when it dispatches, and F6 belongs to W2 rather than to any of this — so the *shape* is
|
W0's declared period:
|
||||||
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
|
- **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 (F9–F12), 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.
|
||||||
|
|
||||||
|
**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.**
|
||||||
|
|||||||
Reference in New Issue
Block a user