docs(product): reshape S16 to Varispeed/Preserve pitch-engine modes (duration-preserving correction)

Per-zone toggle: Varispeed (classic, cheap) vs Preserve (duration-holding,
WDL_SimplePitchShifter candidate — per-voice RT viability assessed: pre-warm
settles onset latency + queue growth; quality basic; hand-rolled pure module
held as upgrade). Pitch envelope rides either engine. Fork S16-F1: default
mode is Daniel's call.
This commit is contained in:
2026-07-26 20:24:44 -04:00
parent c64ab687bf
commit bf1bf5283c
3 changed files with 478 additions and 145 deletions
+110 -13
View File
@@ -7,12 +7,20 @@ the **product framing behind a scoped phase**. Its build roadmap lives in **PLAN
*why* (the plugin-format reasoning, the bare-VST3-vs-JUCE assessment, the settled
decision record).
Status: framed by product-designer (2026-07-26), **revised 2026-07-26 (r7)**. r7 records the
Status: framed by product-designer (2026-07-26), **revised 2026-07-26 (r8)**. r8 records
Daniel's **duration-preserving correction** (2026-07-26, verbatim: *"isn't that ratio stuff
going to change the playback rate? I want duration-preserving repitching"*): the ratio path is
**varispeed** (pitch/duration coupled), so S16 is reshaped from "pitch envelope only" into a
**pitch-engine mode — Varispeed vs Preserve — plus the pitch envelope** riding either engine.
The WDL verdict flips: `WDL_SimplePitchShifter` (duration-preserving OLA), previously dismissed
as the wrong tool, is **now the Preserve-engine candidate** and got a real per-voice RT
viability assessment. Two S16 forks flagged: **S16-F1** (engine default — lean Preserve) and
**S16-F2** (Preserve implementation — WDL shifter first, hand-rolled pure module held). See the
r8 Addendum in §4. r7 records the
**sampling-modes engine directive** (Daniel, 2026-07-26): Trigger vs Gate play modes (Gate =
AHDSR, Trigger = one-shot with %-length + fades), a modifiable start point in both, and an
off-by-default AD pitch envelope — specced as **new Phase S points S15/S16**, with the WDL
pitch surface swept and reported (sinc `WDL_Resampler` held as an optional quality upgrade;
no formant-preserving/elastique in WDL). See the "sampling modes" Addendum in §4. r6 records the
pitch surface swept and reported. See the "sampling modes" Addendum in §4. r6 records the
**workflow-first reframe of S10** (Daniel, 2026-07-26): the editor's default face becomes a
**capture browser + guided single-capture setup**, a fresh instance is **silent with a "pick
a capture" empty state** (reversing the S4 first-sample auto-play), and multi-zone editing is
@@ -777,6 +785,12 @@ partly on filename, fall back to keeping the filename and record that as shipped
### Addendum — sampling modes (Trigger/Gate) + pitch envelope (Daniel, 2026-07-26)
> **Superseded in part by the r8 Addendum below (2026-07-26).** Daniel's duration-preserving
> correction reshaped S16 from "pitch envelope only" into a Varispeed/Preserve pitch-engine
> mode, and **flipped this addendum's WDL verdict** — `WDL_SimplePitchShifter` (called the
> "wrong tool" in item 3 below) is now the Preserve-engine candidate. Read this as the r7
> point-in-time record; the r8 Addendum carries the current S16 shape.
Daniel directed a set of engine features for the sampler, specced as **new Phase S points
S15 (Trigger vs Gate) and S16 (pitch envelope)**. **The feature set is settled** — recorded
here per the doc's settled-decisions convention; PLAN.md §S15/S16 and CONTEXT.md §Sampling
@@ -829,6 +843,81 @@ off by default. Explore using WDL pitch capabilities."*
**lean per-zone only** (the single capture is already a one-zone map), flagged because it
touches S10's single-capture setup surface.
### Addendum — duration-preserving correction: S16 becomes pitch-engine modes (Daniel, 2026-07-26, r8)
**Correction, verbatim (Daniel, 2026-07-26):** *"isn't that ratio stuff going to change the
playback rate? I want duration-preserving repitching."* Daniel is right about the mechanics.
The r7 S16 spec modulated pitch by biasing the per-frame read ratio (`readPos_ += ratio_`) —
that is **varispeed**: pitch and duration are coupled (an octave up halves the note's
duration). Daniel wants **duration-preserving** repitch (a transposed note keeps its length).
This reshapes S16 and **flips the r7 WDL verdict** on `WDL_SimplePitchShifter`.
**What changed (the reframe, then the spec):**
1. **The reframe — this is a mode, not a replacement.** Both behaviors are musically
legitimate, so the answer is not "swap varispeed for preserve" but **a per-zone/per-capture
pitch-engine mode**:
- **Varispeed** (current path, cheap, zero-latency) — pitch/duration coupled. The
**classic sampler / RS5K** default; right for **drums / one-shots** (pitch-down-lengthens-
the-hit is a feature there).
- **Preserve** (duration-preserving) — a pitch shifter transposes the output while the read
holds the source duration. Right for **tempo-locked loops and phrases** (a transposed loop
still lines up to the bar) — which is what captured banks skew toward (project slices).
The pitch envelope (r7's S16 body) then rides **either** engine: under Varispeed it biases
the read ratio (as specced); under Preserve it biases the shifter's shift amount. So the
envelope is preserved, re-homed onto the engine seam.
2. **Fork S16-F1 (Daniel's call): the default engine. Lean Preserve.** Argued honestly:
Preserve because Daniel asked for it **unprompted** (reads as his expectation) and the
capture workflow is **loop/phrase-heavy**; but Varispeed is the **classic-sampler
expectation**, is **cheaper + zero-latency**, is **bit-identical to today's shipped feel**,
and is what percussive one-shots want. Recommendation: **default Preserve, prominent cheap
per-zone toggle to Varispeed.** Daniel's call.
3. **WDL verdict corrected — `WDL_SimplePitchShifter` is now the right category.** Under
"duration-preserving is the requirement," r7's dismissal ("wrong tool, duration-preserving
OLA") inverts: **duration-preserving is exactly what we need.** A real per-voice RT
viability assessment (from `vendor/WDL/WDL/simple_pitchshift.h`):
- **API:** push/pull block (`GetBuffer`/`BufferDone`/`GetSamples`); `set_shift(2^(semi/12))`
for pitch with an **independent** `set_tempo(1.0)` duration knob — pitch and duration
separately controllable, exactly Preserve.
- **Per-voice:** modest memory (OLA ring ≈ window·srate ≈ a few KB/voice at the 50 ms
quality-0 window). CPU cheap (O(length), a few mults + one OLA crossfade/frame, **no
FFT**) → **N polyphonic voices each running one is feasible** in RT discipline.
- **Costs owned:** (i) **onset latency** ~half-window (~25 ms @ 50 ms) — the load-bearing
cost; pre-warm at voice-allocation, and it lands on sustained/loop material (Varispeed
serves tight one-shots); (ii) **queue-growth** allocation in `BufferDone` — settled by a
silence pre-warm so no `process`-thread allocation in steady state; (iii) **basic
quality** (SimpleWindowed warble on big transpositions; `set_formant_shift` is an empty
stub → no formant preservation) — acceptable for loops, replaceable by route (b).
- **Fork S16-F2:** **route (a)** `WDL_SimplePitchShifter` (low-cost proof) vs **route (b)** a
hand-rolled pure `pitch_shift` OLA/granular module (house pattern, CTest-testable, full
control). **Lean (a) first, (b) held** as the quality/latency upgrade — same
`PitchEngine::Preserve` contract behind the seam.
4. **Not proposed / restated ceilings.** `WDL_Resampler` is a *resampler* (couples duration) —
a held **Varispeed-quality** upgrade, **not** a Preserve engine. **elastique is NOT
available** (licensed zplane, not vendored). JUCE / rubberband / signalsmith are each a
**new-dependency fork carrying full D-A weight** (bare-VST3-no-framework is the locked D-A
choice) — **not proposed**.
5. **S15 interaction (cleaner under Preserve).** Trigger's **%-length** becomes **pitch-
independent** under Preserve (wall-clock stable under transpose — cleaner than Varispeed,
where transposing a Trigger also scales its length); Gate's **sustain loop** contract under
Preserve is *loop the source read, shift the output* (loop points stay source-frame facts);
the **start point** is engine-independent (a source-frame offset). Channel-agnostic for S7
(the shifter is `set_nch`-aware; one instance per voice carries all channels).
6. **RT/CPU honesty.** Preserve is **meaningfully heavier** than varispeed — a per-voice DSP
object with its own budget, pre-warm, and a possible **Preserve-mode-specific voice cap**
(below the Varispeed cap) if per-voice cost demands it. Put in Verify: pre-warm → no
`process` allocation; measure per-voice CPU + onset latency against the polyphony cap. Treat
S16's Preserve-engine point as the phase's next real DSP spike, not a thin envelope add-on.
**Where the spec lives:** PLAN.md §S16 (reshaped to "pitch engine modes + pitch envelope",
with forks S16-F1/F2 and the corrected WDL finding) and the S15 × S16 interaction note;
CONTEXT.md §Pitch engine modes — Varispeed vs Preserve + the corrected WDL surface finding.
---
## Where this landed
@@ -878,21 +967,29 @@ Post-DAW-test directives (2026-07-26; see the "product name + UX overhaul" Adden
current drawing and adopt the L1 kit when it lands — not gated on Phase L.** Answers
Daniel's "the VST is dogshit / temple os / does Cockos have a toolkit" (2026-07-26,
post-S1S6 DAW test).
10. **Sampling modes + pitch envelope → engine features** (**S15** Trigger vs Gate,
**S16** pitch envelope; see the "sampling modes" Addendum in §4). Gate = AHDSR held note
(hold added to today's ADSR); Trigger = one-shot with %-length + fade-in/out, ignores
note-off; both carry a modifiable start point; Gate keeps loop points. Pitch envelope =
per-voice AD, off by default, hand-rolled over the existing per-frame ratio read (no WDL
needed). WDL swept: `WDL_Resampler` (sinc) is a held optional repitch-quality upgrade;
no formant-preserving/elastique in WDL. Forks S15-F1 (choke, held) / S15-F2 (param
granularity, lean per-zone) flagged. Feature set settled.
10. **Sampling modes + pitch engine → engine features** (**S15** Trigger vs Gate, **S16**
pitch-engine modes + pitch envelope; see the "sampling modes" r7 + "duration-preserving"
r8 Addenda in §4). Gate = AHDSR held note (hold added to today's ADSR); Trigger = one-shot
with %-length + fade-in/out, ignores note-off; both carry a modifiable start point; Gate
keeps loop points. **S16 reshaped (r8, Daniel's duration-preserving correction):** a per-
zone **pitch-engine mode — Varispeed** (current, cheap, pitch/duration coupled — classic
sampler, right for drums) **vs Preserve** (duration-preserving via a per-voice pitch
shifter — right for tempo-locked loops/phrases). Pitch envelope = per-voice AD, off by
default, riding either engine (biases `ratio_` under Varispeed, the shift amount under
Preserve). WDL verdict corrected: **`WDL_SimplePitchShifter` is the Preserve-engine
candidate** (duration-preserving OLA — RT-viable per-voice with pre-warm; the load-bearing
cost is onset latency), `WDL_Resampler` (sinc) held as a Varispeed-quality upgrade only; no
formant-preserving/elastique in WDL. Forks: **S16-F1** (engine default — lean Preserve,
Daniel's call), **S16-F2** (Preserve impl — WDL shifter first / hand-rolled held), plus
S15-F1 (choke, held) / S15-F2 (param granularity, lean per-zone). Feature set settled;
the engine default is Daniel's fork.
**Authoritative from here:** **PLAN.md §Phase S** is the roadmap (S1S6 the original
dependency chain: spike → `Sample` fields → pure sampler core → Tier 0 → Tier 1 → embedded
UI; then **S7** stereo, **S8** ingest, **S9** change-detection, **S10S13** the ReaSampler
9000 UX overhaul, **S15/S16** the Trigger-vs-Gate + pitch-envelope engine features);
9000 UX overhaul, **S15/S16** the Trigger-vs-Gate + pitch-engine-modes engine features);
**CONTEXT.md §Phase S** is the spec (seam-field semantics, scope contracts, the channel-mode
/ ingest / bank-generation / sampling-mode / pitch-envelope contracts, the UX-overhaul spec,
/ ingest / bank-generation / sampling-mode / pitch-engine contracts, the UX-overhaul spec,
the product-name convention, the pure/shell split, the WDL finding, the must-verify
SDK/bridge surfaces). This doc is the framing/decision record they point back to. The "no
PLAN.md footprint" era is over.