docs: record Daniel's static-captures ruling on the wavetable direction
R1 (2026-08-03) closes F5 — no frame-indexed wavetables yet — deferring W1 with "yet" preserved as scope rather than rejection, and confirming W3 out. Near-term scope narrows to W0 alone. Cascades the consequences: F1's threshold amended (static does not mean one-cycle, so detection classifies nothing now), F2 simplified, F3 and F7 firmed, F8 narrowed to a binary. F6/band-limiting left open and explicitly not pre-ruled. PLAN.md item 4 updated to match.
This commit is contained in:
+19
-7
@@ -220,13 +220,25 @@ 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 carries EIGHT open [Daniel]-class forks and none is ruled**, so it gets a pointer
|
**It framed EIGHT [Daniel]-class forks. ONE is now ruled and SEVEN remain open**, so it
|
||||||
here rather than a phase section: `PLAN.md` is the active on-deck specification list, and
|
still gets a pointer here rather than a phase section: `PLAN.md` is the active on-deck
|
||||||
a phase with eight unruled forks is neither briefable nor consistent with the standing
|
specification list, and a phase whose specification rests on seven unruled forks is
|
||||||
claim above that **Λ is the only phase in this plan with unanswered [Daniel]-class
|
neither briefable nor consistent with the standing claim above that **Λ is the only phase
|
||||||
questions**. That claim is unaffected by this item — there is no phase here to except.
|
in this plan with unanswered [Daniel]-class questions**. That claim is unaffected by this
|
||||||
When the forks are ruled, the doc becomes a phase's backing product doc in the ordinary
|
item — there is no phase here to except. When the remaining forks are ruled, the doc
|
||||||
way (the Γ / Ε / Ρ / Λ pattern, not the doc-less Ψ / Ω one).
|
becomes a phase's backing product 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,
|
||||||
|
that is out of scope. just static captures right now."* It closes fork F5 (the
|
||||||
|
wavetable-position knob does not ship now), confirms the multi-frame wavetable kind out by
|
||||||
|
ruling rather than only by the doc's argument, and **narrows the near-term scope to the
|
||||||
|
doc's W0 — loop-grid recalibration for micro material, which is defect-class work with no
|
||||||
|
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
|
**Two things in it are decision-grade for work already in this plan, which is why it is
|
||||||
flagged rather than merely filed:**
|
flagged rather than merely filed:**
|
||||||
|
|||||||
@@ -15,11 +15,28 @@ snap-radius track already in flight and is NOT this doc's subject.** It is descr
|
|||||||
only where the reasoning needs it, because the defect and the direction share a diagnosis
|
only where the reasoning needs it, because the defect and the direction share a diagnosis
|
||||||
and the fix for one does not deliver the other.
|
and the fix for one does not deliver the other.
|
||||||
|
|
||||||
Status: **framed by product-designer, 2026-08-03. NOTHING here is ruled.** Eight
|
Status: **framed by product-designer 2026-08-03; ONE ruling has since landed against it —
|
||||||
[Daniel]-class forks are open (§7). **No `PLAN.md` phase exists for this work and none is
|
R1, recorded in full at §7.1.** Daniel, 2026-08-03:
|
||||||
proposed until the forks are ruled** — `PLAN.md`'s "Flagged for awareness" item 4 is the
|
|
||||||
only pointer to this doc, deliberately, so the plan's standing claim about unanswered
|
> *"I don't want actual frame-indexed wave tables yet, that is out of scope. just static
|
||||||
questions is not weakened by a direction that has not been decided.
|
> captures right now."*
|
||||||
|
|
||||||
|
R1 **closes F5** (W1, the wavetable-position knob, does not ship now) and **confirms W3 out
|
||||||
|
from Daniel** rather than only from this doc's argument. **Seven forks remain open** (§7).
|
||||||
|
|
||||||
|
**R1 does NOT rule F6 / band-limiting.** The question that decides F6 — *is the material ever
|
||||||
|
anything but sine?* — is still outstanding, Daniel knows it is outstanding, and it is
|
||||||
|
deliberately not pre-ruled anywhere below. Nothing in R1 touches aliasing: a *static*
|
||||||
|
single-cycle capture of anything but a sine, played across the keyboard, is exactly the case
|
||||||
|
§1.8 describes.
|
||||||
|
|
||||||
|
**The near-term scope is therefore W0, and only W0.** Read §2 as one body of work plus two
|
||||||
|
deferrals, not as four equal candidates.
|
||||||
|
|
||||||
|
**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
|
||||||
|
the plan's standing claim about unanswered questions is not weakened by a direction whose
|
||||||
|
shape is decided but whose dispatch is not.
|
||||||
|
|
||||||
Everything below is a product-designer call with its reasoning stated. Contradict it in
|
Everything below is a product-designer call with its reasoning stated. Contradict it in
|
||||||
review with an argument, not a preference.
|
review with an argument, not a preference.
|
||||||
@@ -42,15 +59,24 @@ anything but a pure sine, played at C5, resamples by ~17× and folds every parti
|
|||||||
~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 a mip pyramid of per-octave lowpassed tables. That is a
|
||||||
real build with a real migration cost, and it is separable from everything else here.
|
real build with a real migration cost, and it is separable from everything else here.
|
||||||
|
**After R1 it is also the only one of the four shapes whose scope is still undecided.**
|
||||||
|
|
||||||
**Recommended shape, four candidates, in order:**
|
**The four shapes, and where each now stands:**
|
||||||
|
|
||||||
| | What | Cost | Lean |
|
| | 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 | **Take** |
|
| **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** |
|
||||||
| **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 | **Take next** |
|
| **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 | **Its own decision** |
|
| **W2** | **Band-limited playback** — mip pyramid + a better interpolator | Real DSP build; changes the sound of existing captures unless made conditional | **OPEN — F6, undecided** |
|
||||||
| **W3** | A first-class multi-frame **wavetable kind** — N×2048 tables, morph parameter, `.wav` wavetable import | Breaks "one loaded capture, one parameter set" | **Out** |
|
| **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** |
|
||||||
|
|
||||||
|
**R1 is a scope ruling, not a rejection of the idea.** *"Yet"* is Daniel's word and it is
|
||||||
|
load-bearing: W1's §2 section is preserved in full, argument intact, so a future reader can
|
||||||
|
pick it back up rather than re-derive it. What would revive it is named there.
|
||||||
|
|
||||||
|
**W0 stands entirely on its own merits and always did.** It is defect-class work — Daniel
|
||||||
|
cannot currently set a correct loop on his own routine material — and none of its five parts
|
||||||
|
depends on a wavetable ambition. R1 does not shrink W0; it removes what sat on top of it.
|
||||||
|
|
||||||
**And the operational headline: this direction lands in the exact files Ω-W1-T5
|
**And the operational headline: this direction lands in the exact files Ω-W1-T5
|
||||||
`overlay-mapping` owns right now**, and Phase Ω's own acceptance criteria name the
|
`overlay-mapping` owns right now**, and Phase Ω's own acceptance criteria name the
|
||||||
@@ -179,9 +205,25 @@ was the snap forcing `L` to half the file.
|
|||||||
`defaultLoopBounds` puts the handles in the last quarter (`loop_span.h:89-96`) — the right
|
`defaultLoopBounds` puts the handles in the last quarter (`loop_span.h:89-96`) — the right
|
||||||
answer for a sustaining instrument sample and meaningless for a single cycle, where the last
|
answer for a sustaining instrument sample and meaningless for a single cycle, where the last
|
||||||
quarter is a quarter of a waveform. Combined with §1.1 this is the mechanical cause of
|
quarter is a quarter of a waveform. Combined with §1.1 this is the mechanical cause of
|
||||||
*"loop start snaps to the middle."* At micro scale the correct default is the whole file;
|
*"loop start snaps to the middle."* At micro scale the correct default is the whole file.
|
||||||
for a multi-cycle capture under W1 it is one period at the loop's own start. Fork **F1**
|
Fork **F1** decides whether the default is allowed to be material-aware at all.
|
||||||
decides whether the default is allowed to be material-aware at all.
|
|
||||||
|
**A distinction R1 forces, and it is easy to get wrong: *static* does not mean *one
|
||||||
|
cycle*.** R1 says the captures are static — the waveform does not evolve — and that is what
|
||||||
|
kills W1, because a position knob scrubbing across identical cycles is a knob that does
|
||||||
|
nothing. It does **not** say every capture is exactly one period long. A static grab that
|
||||||
|
happens to run three or four cycles is still in scope, and on it:
|
||||||
|
|
||||||
|
- the loop **length** must still be a whole number of periods, or §1.3(a)'s seam
|
||||||
|
discontinuity returns exactly as Daniel heard it;
|
||||||
|
- the loop **position** is immaterial, because every cycle is the same cycle.
|
||||||
|
|
||||||
|
So the period grid keeps its job on multi-cycle material even with W1 deferred, and
|
||||||
|
`period_detect` keeps an in-scope consumer there (it also, separately and unaffected by any
|
||||||
|
of this, keeps its Preserve/PSOLA consumer — see §4). This is what amends F1's threshold in
|
||||||
|
§3: "detection failed" and "this is table-like material" stopped being the same test the
|
||||||
|
moment W1 left, because the static multi-cycle capture is now the case that would fall
|
||||||
|
through it.
|
||||||
|
|
||||||
### 1.5 `period_detect` structurally cannot see a single cycle — the period must be DECLARED
|
### 1.5 `period_detect` structurally cannot see a single cycle — the period must be DECLARED
|
||||||
|
|
||||||
@@ -272,7 +314,9 @@ default. Fork **F6**.
|
|||||||
|
|
||||||
The brief's own framing is the right one: this is a question of what *changes*, not what
|
The brief's own framing is the right one: this is a question of what *changes*, not what
|
||||||
gets built new. Single-cycle captures already flow through capture → bank → instrument
|
gets built new. Single-cycle captures already flow through capture → bank → instrument
|
||||||
today. Four shapes, in ascending commitment.
|
today. Four shapes, in ascending commitment. **R1 settled two of them — W1 deferred, W3
|
||||||
|
confirmed out — so what follows is one body of work (W0), one open question (W2), and two
|
||||||
|
sections kept for the record.**
|
||||||
|
|
||||||
### W0 — Recalibrate the loop grid. No new concept.
|
### W0 — Recalibrate the loop grid. No new concept.
|
||||||
|
|
||||||
@@ -293,7 +337,24 @@ migration-clean** — a snap grid applies during a drag, so no saved instance's
|
|||||||
points move and no saved instance's sound changes. This is defect-class work that makes
|
points move and no saved instance's sound changes. This is defect-class work that makes
|
||||||
Daniel's material usable at all, and it is the recommended first move.
|
Daniel's material usable at all, and it is the recommended first move.
|
||||||
|
|
||||||
### W1 — The loop start becomes a wavetable POSITION
|
### W1 — The loop start becomes a wavetable POSITION — DEFERRED by R1 (2026-08-03)
|
||||||
|
|
||||||
|
**Daniel, 2026-08-03: *"I don't want actual frame-indexed wave tables yet, that is out of
|
||||||
|
scope. just static captures right now."* A loop start expressed as `k·P` IS a frame index
|
||||||
|
into a table, so this is W1 by name.** The deferral is sound on this doc's own §8 test
|
||||||
|
rather than only on the ruling: W1 is worth doing *if the captures are ever multi-cycle and
|
||||||
|
evolving*, and static material makes every frame of the table the same frame. A scrub across
|
||||||
|
identical cycles is inaudible. **The knob would have been dead weight, exactly as §8
|
||||||
|
predicted.**
|
||||||
|
|
||||||
|
**"Yet" is the operative word — this is a scope ruling, not a verdict on the idea.** The
|
||||||
|
argument below is kept whole, and unchanged, because nothing in it was refuted; only its
|
||||||
|
premise went away. **What would revive it: capture material that both runs to several cycles
|
||||||
|
AND evolves across them** — a filter sweep, a bowed attack, a slowly-detuning pair. That is a
|
||||||
|
change in Daniel's practice, not a change in the code, and it is the only trigger. If it
|
||||||
|
arrives, re-open F5 and read from here; do not re-derive.
|
||||||
|
|
||||||
|
The rest of this section is the preserved case.
|
||||||
|
|
||||||
The cheap, high-leverage feature, and the one that earns the word "wavetable."
|
The cheap, high-leverage feature, and the one that earns the word "wavetable."
|
||||||
|
|
||||||
@@ -319,7 +380,12 @@ 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
|
### W2 — Band-limited playback — UNAFFECTED by R1, still open
|
||||||
|
|
||||||
|
**R1 does not touch this and must not be read as touching it.** Band-limiting is a property
|
||||||
|
of playing *any* short table across the keyboard; whether the table's frames evolve is
|
||||||
|
irrelevant to whether its partials fold. A static single-cycle capture of a saw, played at
|
||||||
|
C5, aliases exactly as §1.8 describes. **F6 remains open and unruled.**
|
||||||
|
|
||||||
§1.8. Orthogonal to W0 and W1, and the only one of the four that is a genuine DSP build. It
|
§1.8. Orthogonal to W0 and W1, and the only one of the four that is a genuine DSP build. It
|
||||||
is also **independently valuable outside this direction**: it improves every ordinary sample
|
is also **independently valuable outside this direction**: it improves every ordinary sample
|
||||||
@@ -327,10 +393,13 @@ played more than an octave above its root, which is a much larger population tha
|
|||||||
single-cycle captures. That argues for judging it on its own merits rather than as
|
single-cycle captures. That argues for judging it on its own merits rather than as
|
||||||
wavetable-support. Fork **F6**.
|
wavetable-support. Fork **F6**.
|
||||||
|
|
||||||
### W3 — A first-class wavetable KIND — RECOMMENDED OUT
|
### W3 — A first-class wavetable KIND — OUT (recommended out; CONFIRMED by R1)
|
||||||
|
|
||||||
A bank entry that is N frames of a fixed size, a morph parameter across frames, and import
|
A bank entry that is N frames of a fixed size, a morph parameter across frames, and import
|
||||||
of third-party wavetable `.wav` files. Recommended out, with force:
|
of third-party wavetable `.wav` files. **This is "actual frame-indexed wave tables" in the
|
||||||
|
most literal sense, and R1 names it out of scope directly** — so it is now out by Daniel's
|
||||||
|
ruling and not merely by this doc's recommendation. The recommendation stood on its own and
|
||||||
|
is kept because the structural reason is the durable one:
|
||||||
|
|
||||||
- **It breaks a settled invariant.** *"The instrument holds ONE loaded capture and ONE set of
|
- **It breaks a settled invariant.** *"The instrument holds ONE loaded capture and ONE set of
|
||||||
playback parameters"* (`src/core/instrument/CLAUDE.md`) is the whole result of the zone
|
playback parameters"* (`src/core/instrument/CLAUDE.md`) is the whole result of the zone
|
||||||
@@ -383,6 +452,26 @@ outside it, which is the discrimination the band buys. A further tightening —
|
|||||||
first and last frames to nearly match — is available if false positives show up in practice
|
first and last frames to nearly match — is available if false positives show up in practice
|
||||||
and is deliberately not proposed pre-emptively.
|
and is deliberately not proposed pre-emptively.
|
||||||
|
|
||||||
|
**R1 amends condition 1, and this is a genuine correction rather than a restatement.**
|
||||||
|
Condition 1 used *detection failure* as the classifier because in the original framing
|
||||||
|
detection **succeeding** meant W1 territory, and W1 supplied its own default (one period at
|
||||||
|
the loop's start). With W1 deferred there is nothing downstream to catch that case, so a
|
||||||
|
static multi-cycle capture (§1.4) — detection succeeds, file is short, cycles are
|
||||||
|
interchangeable — falls straight through the threshold to the unimproved last-quarter
|
||||||
|
default. That is precisely the defect W0 exists to remove. The threshold should now read:
|
||||||
|
|
||||||
|
- **the frame count lies inside the band** `sr/kPeriodDetectMaxHz … sr/kPeriodDetectMinHz`,
|
||||||
|
i.e. 24 … 3200 frames at 48 kHz — this alone now carries the classification; **and**
|
||||||
|
- **a period is known**: **detected** where `detectPeriod` returns one, **declared as the
|
||||||
|
file length** where it does not (§1.5).
|
||||||
|
|
||||||
|
**Detection moves from being the classifier to being the period *source*, which is what it
|
||||||
|
was always better suited to be.** The false positive the band admits is unchanged (a very
|
||||||
|
short non-periodic one-shot), and so is the tightening available against it. **F1's lean is
|
||||||
|
unmoved — still (a), derived-only, governing defaults and the snap grid and never the
|
||||||
|
sound** — only the test inside (a) changes. F1(c), no classification at all, is untouched by
|
||||||
|
R1 and remains the serious conservative alternative.
|
||||||
|
|
||||||
**The named alternative, and it deserves a real hearing: no classification at all.** Make the
|
**The named alternative, and it deserves a real hearing: no classification at all.** Make the
|
||||||
period grid always available, let "the period is the whole file" be simply what the user gets
|
period grid always available, let "the period is the whole file" be simply what the user gets
|
||||||
when the loop spans the whole file, and accept a bad initial loop default on micro material.
|
when the loop spans the whole file, and accept a bad initial loop default on micro material.
|
||||||
@@ -409,18 +498,23 @@ conservative option on the table and it is fork **F1(c)**.
|
|||||||
inert at one period, the detector's answer is simply unused on that path — no code changes.
|
inert at one period, the detector's answer is simply unused on that path — no code changes.
|
||||||
|
|
||||||
**The one exception (§1.5):** the single-cycle case is permanently outside detection's reach,
|
**The one exception (§1.5):** the single-cycle case is permanently outside detection's reach,
|
||||||
so W0's period is declared from the file/loop length while W1's is detected. Two sources, one
|
so at the single-cycle limit the period is **declared** from the file/loop length, while on a
|
||||||
grid.
|
capture running to several cycles it is **detected**. Two sources, one grid. **R1 does not
|
||||||
|
collapse them into one** — deferring W1 removes the *scrub*, not the multi-cycle capture
|
||||||
|
(§1.4), so both arms of the split still have in-scope work to do.
|
||||||
|
|
||||||
**A genuine tension worth naming before someone "fixes" it into a bug.** `periodAnalysisSpan`
|
**A genuine tension worth naming before someone "fixes" it into a bug.** `periodAnalysisSpan`
|
||||||
uses `[loopStart, loopEnd)` only when that span is at least one full probe block
|
uses `[loopStart, loopEnd)` only when that span is at least one full probe block
|
||||||
(`2 · longestLagFrames`, 6400 frames at 48 kHz). Under W1 the loop is one period — always far
|
(`2 · longestLagFrames`, 6400 frames at 48 kHz). **Any one-period loop on micro material is
|
||||||
below that — so the detector will always fall back to analysing the whole source. **That is
|
far below that** — which under W0 is the *default* case, not an exotic one — so the detector
|
||||||
correct**: you want the period of the source material, not the period of a one-period loop,
|
will always fall back to analysing the whole source. **That is correct**: you want the period
|
||||||
which would be circular. But once W1 ships, the detector's answer and the loop's length are
|
of the source material, not the period of a one-period loop, which would be circular. But the
|
||||||
measuring deliberately different things, and the code will look like it has an inconsistency
|
detector's answer and the loop's length are then measuring deliberately different things, and
|
||||||
it does not have. It should be commented at the seam, per the comment conventions' "warnings
|
the code will look like it has an inconsistency it does not have. It should be commented at
|
||||||
against a plausible-but-wrong change."
|
the seam, per the comment conventions' "warnings against a plausible-but-wrong change."
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -428,12 +522,15 @@ against a plausible-but-wrong change."
|
|||||||
|
|
||||||
- **PPG Wave / Waldorf Microwave** — the wavetable-position knob as "which cycle of the stored
|
- **PPG Wave / Waldorf Microwave** — the wavetable-position knob as "which cycle of the stored
|
||||||
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
|
||||||
|
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** — mip-mapped, per-octave band-limited tables. This is the whole
|
||||||
of W2, and their universal adoption of it is the evidence that §1.8 is not a theoretical
|
of W2, and their universal adoption of it is the evidence that §1.8 is not a theoretical
|
||||||
concern.
|
concern. **Still live: F6 is open.**
|
||||||
- **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.
|
established precedent for F2's period grid; we would not be inventing it. **This is now the
|
||||||
|
load-bearing precedent, since W0 is the near-term scope.**
|
||||||
- **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.
|
||||||
@@ -473,22 +570,29 @@ Stated in §2 and the sole structural reason W3 is recommended out.
|
|||||||
move and no saved instance changes sound. A changed *default* only affects a capture that
|
move and no saved instance changes sound. A changed *default* only affects a capture that
|
||||||
has no stored loop.
|
has no stored loop.
|
||||||
- **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.
|
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.
|
exists to decide whether that cost is paid unconditionally or made conditional and declared.
|
||||||
|
|
||||||
|
**After R1 the whole near-term scope is migration-clean**, and the only migration risk left
|
||||||
|
in this doc sits behind an open fork rather than inside the work.
|
||||||
|
|
||||||
### 6.6 The hot path — HELD, and the guardrails are satisfiable by construction
|
### 6.6 The hot path — HELD, and the guardrails are satisfiable by construction
|
||||||
|
|
||||||
- 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**.
|
||||||
- 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.
|
(`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 mip level selection is **per note-on**. The interpolator change is a straight-line
|
||||||
4-tap with no dispatch.
|
4-tap with no dispatch.
|
||||||
- **W1's optional morph (F5c) is the one item that would touch `process()`** — a second read
|
- **W1's optional morph (F5c) was the one item that would have touched `process()`** — a
|
||||||
tap per frame. It must be measured against the guardrail before it is promised, and "drop
|
second read tap per frame, to be measured against the guardrail before it was promised.
|
||||||
it" is a legitimate outcome.
|
**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
|
||||||
|
UI-thread only. Nothing in W0 goes near `process()`.** The guardrail question that would have
|
||||||
|
needed a measurement before anything could be promised no longer gates this direction.
|
||||||
|
|
||||||
### 6.7 Phase Ω and the in-flight snap-radius track — the real sequencing constraint
|
### 6.7 Phase Ω and the in-flight snap-radius track — the real sequencing constraint
|
||||||
|
|
||||||
@@ -517,38 +621,89 @@ 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
|
||||||
|
new parameter left in this doc is W2's mode, which sits behind an open fork — so **nothing in
|
||||||
|
the near-term scope opens a one-way door.** A position knob's taper, the item that most
|
||||||
|
needed to be right first time, is a decision this direction no longer has to make under time
|
||||||
|
pressure.
|
||||||
|
|
||||||
### 6.9 The editor's space budget
|
### 6.9 The editor's space budget
|
||||||
|
|
||||||
W1's position control and W2's mode toggle both want deck real estate against
|
W1's position control and W2's mode toggle both want deck real estate against
|
||||||
`instrument-control-surface.md` §1.6's 82 px headroom, and `reasampler_editor.h` is already
|
`instrument-control-surface.md` §1.6's 82 px headroom, and `reasampler_editor.h` is already
|
||||||
tight against the ~600-line ceiling with Phase Ω spending its margin. This is a real
|
tight against the ~600-line ceiling with Phase Ω spending its margin. This was a real
|
||||||
constraint on how many of these shapes can 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
|
||||||
|
default, and two greyed affordances are all changes to existing surfaces — and F3's
|
||||||
|
"Tune to loop" is the one item that might want a hit target, which is a far smaller ask than
|
||||||
|
a knob. **The 82 px is uncontended by the near-term scope.** It comes back the moment F6 is
|
||||||
|
ruled toward a mode toggle, so the headroom should not be spent elsewhere on the assumption
|
||||||
|
that this direction has released it permanently.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Forks — EIGHT, all OPEN, none ruled
|
## 7. Forks — EIGHT total: ONE settled (F5), SEVEN 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.
|
rule.** None is pre-ruled here and none should be treated as settled by the lean. Rulings
|
||||||
|
recorded against this doc are logged in §7.1; the lean column below states, per fork, whether
|
||||||
|
R1 moved it.
|
||||||
|
|
||||||
| Fork | Question | Options | 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 | **(a)**, with (c) as the serious conservative alternative |
|
| **F1** | Is there a "micro" classification at all, and where does it come from? | (a) derived-only, governing **defaults and snap grid only, never the sound**; (b) derived + a user override toggle; (c) **no classification** — the period grid is always available and a bad default on micro material is accepted | **OPEN. Lean unmoved: (a)**, with (c) as the serious conservative alternative. **But R1 amends the test inside (a)** — the frame-count band now classifies and detection supplies the period, because "detection failed" stopped meaning "table-like material" when W1 left. See §3 |
|
||||||
| **F2** | What grid does a loop-mark drag snap to? | (a) zero-crossing always (today); (b) **period** when a period is known, zero-crossing otherwise; (c) both, modifier-selected — note `param_taper`'s modifier law explicitly excludes spline points, so a waveform-mark modifier is available but needs its own ruling | **(b)** — it makes the octave error unreachable rather than unlikely |
|
| **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) |
|
||||||
| **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 | **(b)** — honest, visible, and uses a parameter that already exists. (c) is a hidden pitch modifier |
|
| **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 | **(a)**, hard. ~1 ¢ at Daniel's own working range does not justify a bank-format change |
|
| **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 |
|
||||||
| **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 — **a second read tap on `process()`; measure before promising** | **(b)** if it ships |
|
| **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 | **(b)** if it ships. This is the fork that decides whether "wavetable synth" is rhetoric or a commitment |
|
| **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 |
|
||||||
| **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 | **(b)**. (c) changes the sound of a saved instance and should 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** first, dispatched after Ω-W1-T5 and the snap-radius track land; (b) one phase covering W0+W1; (c) not now | **(a)** — W0 is defect-class and unblocks Daniel's material; W1 is a feature that can be scoped once W0's grid exists |
|
| **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 |
|
||||||
|
|
||||||
**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.
|
- The pre-seam crossfade cannot deliver micro-scale smoothness (§1.2). Structural — and
|
||||||
- `period_detect` cannot see a single cycle (§1.5). Structural.
|
**untouched by R1**, which changes nothing about the loop's arithmetic.
|
||||||
- W3 breaks "one capture, one parameter set" (§2). If it returns, it returns as its own
|
- `period_detect` cannot see a single cycle (§1.5). Structural — and **untouched by R1**. The
|
||||||
product question.
|
declare-vs-detect split survives intact; deferring W1 removes the scrub, not the
|
||||||
- Sub-50 Hz is first-class. Nothing here narrows a range to avoid it.
|
multi-cycle capture that detection serves (§1.4).
|
||||||
|
- W3 breaks "one capture, one parameter set" (§2), **and is now out by Daniel's ruling as
|
||||||
|
well as by that argument.** If it returns, it returns as its own product question.
|
||||||
|
- Sub-50 Hz is first-class. Nothing here narrows a range to avoid it, and R1 does not change
|
||||||
|
that: capturing low remains the correct practice for the reason in §1.3, not a tolerance
|
||||||
|
the design absorbs.
|
||||||
|
- **W1's deferral is a scope ruling, not a structural result** — the one item in this list
|
||||||
|
that IS revisitable, on the single named trigger in §2 (material that is both multi-cycle
|
||||||
|
and evolving).
|
||||||
|
|
||||||
|
### 7.1 Rulings recorded
|
||||||
|
|
||||||
|
**R1 — Daniel, 2026-08-03.** Verbatim:
|
||||||
|
|
||||||
|
> *"I don't want actual frame-indexed wave tables yet, that is out of scope. just static
|
||||||
|
> captures right now."*
|
||||||
|
|
||||||
|
**What it settles:**
|
||||||
|
|
||||||
|
- **F5 → (a), not now.** A loop start expressed as `k·P` is a frame index into a table; on
|
||||||
|
static material every frame of that table is the same frame, so the knob scrubs between
|
||||||
|
identical cycles. This is §8's own kill test, met.
|
||||||
|
- **W3 confirmed out**, now by ruling and not only by this doc's argument.
|
||||||
|
- **W1 deferred, with "yet" recorded as Daniel's word.** Scope, not rejection. §2 keeps the
|
||||||
|
case whole and names the single trigger that revives it.
|
||||||
|
- **F8's option (b) eliminated** — there is no W0+W1 phase to size.
|
||||||
|
|
||||||
|
**What it explicitly does NOT settle:**
|
||||||
|
|
||||||
|
- **F6 / band-limiting.** The deciding question — *is the material ever anything but sine?* —
|
||||||
|
is outstanding, and Daniel knows it is. Aliasing is orthogonal to whether a table's frames
|
||||||
|
evolve; a static single-cycle saw played at C5 folds exactly as §1.8 says. **Do not read
|
||||||
|
R1 as answering it.**
|
||||||
|
- **F1, F2, F3, F4, F7, F8** — all still Daniel's. R1 shifts what some of them are choosing
|
||||||
|
between (the table above says which) without choosing.
|
||||||
|
- **The Phase Ω conflict in §6.7.** Still open, still with Daniel and whoever owns Ω's
|
||||||
|
acceptance criteria, and not this doc's to resolve.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -556,17 +711,21 @@ rule.** None is pre-ruled here and none should be treated as settled by the lean
|
|||||||
|
|
||||||
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.** Daniel cannot currently set a
|
- **W0 is worth doing regardless of the wavetable ambition — and R1 leaves that argument
|
||||||
correct loop on his own routine material, and the radius fix alone leaves a one-pixel drag
|
exactly as it was.** Daniel cannot currently set a correct loop on his own routine material,
|
||||||
able to produce a 0.997-period loop. That is defect-class.
|
and the radius fix alone leaves a one-pixel drag able to produce a 0.997-period loop. That
|
||||||
- **W1 is worth doing if Daniel's captures are ever multi-cycle and evolving.** If every
|
is defect-class. **It is now also the whole of the near-term scope.**
|
||||||
single-cycle capture is a static one-cycle grab, the position knob has nothing to scrub and
|
- **W1 is worth doing if Daniel's captures are ever multi-cycle and evolving. ANSWERED
|
||||||
W1 is dead weight. **This is the one question this doc cannot answer from the code, and it
|
2026-08-03: they are not — *"just static captures right now."*** The position knob has
|
||||||
is the thing most worth asking before F5 is ruled.**
|
nothing to scrub, and the test written here before the answer arrived is the reason F5
|
||||||
- **W2 is worth doing if the material is ever anything but sine.** A sine played up four
|
closes cleanly rather than by preference. The condition is unchanged and still live as a
|
||||||
octaves is fine; a captured saw is not. If Daniel's practice is harmonically rich
|
trigger: if the practice changes, this bullet is what re-opens F5.
|
||||||
single-cycle captures played across the keyboard, W2 stops being optional and becomes the
|
- **W2 is worth doing if the material is ever anything but sine. STILL UNANSWERED — this is
|
||||||
actual answer to "turn this into a wavetable synth."
|
the outstanding question.** A sine played up four octaves is fine; a captured saw is not. If
|
||||||
|
Daniel's practice is harmonically rich single-cycle captures played across the keyboard, W2
|
||||||
|
stops being optional and becomes the actual answer to "turn this into a wavetable synth."
|
||||||
|
**R1 does not bear on it either way**, because staticness and harmonic content are
|
||||||
|
independent properties of the same capture.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -577,9 +736,15 @@ 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**, because
|
blocking, but decision-grade." **No phase, no wave and no track is proposed there**, and R1
|
||||||
every fork above is open and `PLAN.md` is the *active on-deck specification list*. Adding a
|
does not change that — `PLAN.md` is the *active on-deck specification list*, and putting a
|
||||||
phase with eight unruled forks would both put an unbriefable phase in that list and falsify
|
phase in it while F8 is unruled would both put an unbriefable phase in that list and falsify
|
||||||
the plan's standing claim that Λ is the only phase carrying unanswered [Daniel]-class
|
the plan's standing claim that Λ is the only phase carrying unanswered [Daniel]-class
|
||||||
questions. When the forks are ruled, this doc becomes the backing product doc for a phase in
|
questions.
|
||||||
the ordinary way — the Γ / Ε / Ρ / Λ pattern, not the doc-less Ψ / Ω one.
|
|
||||||
|
**What R1 does change is that the proposable phase is now well-defined: W0-only, F8(a).**
|
||||||
|
Five forks still sit inside W0's own content (F1, F2, F3, F4, F7), F8 governs whether and
|
||||||
|
when it dispatches, and F6 belongs to W2 rather than to any of this — so the *shape* is
|
||||||
|
settled while the *specification* is not. When F8 is ruled, this doc becomes the backing
|
||||||
|
product doc for that phase in the ordinary way — the Γ / Ε / Ρ / Λ pattern, not the doc-less
|
||||||
|
Ψ / Ω one.
|
||||||
|
|||||||
Reference in New Issue
Block a user