docs: close out Θ-W2-T1 filter-voice-path into COMPLETED
Move the landed filter-voice-path track from PLAN.md into COMPLETED.md with full narrative (quantizer removal, editor floor raise). Mark Θ-W2 fully landed, matching W1's phrasing; fix a stale forward reference.
This commit is contained in:
@@ -151,6 +151,44 @@ path.
|
|||||||
pre- vs post-envelope placement is sound-defining), the drive dial's calibration, and
|
pre- vs post-envelope placement is sound-defining), the drive dial's calibration, and
|
||||||
the fact that drive authority varies ~11 dB across the morph sweep.
|
the fact that drive authority varies ~11 dB across the morph sweep.
|
||||||
|
|
||||||
|
### Θ-W2-T1 — filter-voice-path
|
||||||
|
|
||||||
|
Wires the pure filter module into ReaSampler 9000's per-voice signal path as a new fixed
|
||||||
|
processing point between the pitch envelope and the amp stage, gives it its own
|
||||||
|
knob-deck group, and relays the deck row in signal-flow order (pitch → filter → amp).
|
||||||
|
Each `Voice` owns its own `VoiceFilter` and a second `AdsrEnvelope` instance — per-voice,
|
||||||
|
never shared — and neither adds allocation or virtual dispatch to the per-sample path.
|
||||||
|
Parameters — morph position, cutoff, Q, drive, mod amount (bipolar ±100%, targeting
|
||||||
|
cutoff), velocity and key-tracking modulation, then AHDSR — live in the one parameter
|
||||||
|
set; `FilterParams` stores the filter module's own `FilterSettings` by value rather than
|
||||||
|
a parallel copy of the normalized positions. The filter is off by default and bit-exact
|
||||||
|
off — a project saved before the change reopens sounding identical, pinned by a
|
||||||
|
bit-equality test. `ComponentState`'s params payload moves v8 → v9, appending the filter
|
||||||
|
tail; a v8 blob is a strict prefix and lifts to the off/neutral filter default, with
|
||||||
|
non-finite filter fields falling back to neutral, pinned by a golden byte-literal
|
||||||
|
fixture. Deck composition was extracted into a new pure module
|
||||||
|
`core/instrument/ui/deck_groups`, with the pitch → filter → amp row order pinned by
|
||||||
|
test; the filter's AHDSR ships as its own `FILTER ENV` group sibling to `FILTER`,
|
||||||
|
mirroring the existing `PITCH` / `PITCH ENV` split, and a morph-law toggle (`Band` |
|
||||||
|
`Notch`) ships as the FILTER group's row toggle.
|
||||||
|
|
||||||
|
**Deviations from spec:**
|
||||||
|
- **An initial mod-quantizer was added, then rejected and removed.** A
|
||||||
|
`kFilterModSteps = 2048` step gate on the coefficient re-solve stair-stepped the
|
||||||
|
corner (~5.8 cents per step); Daniel rejected it. Replaced by a cutoff-only re-solve
|
||||||
|
(`VoiceFilter::setCutoffNorm`) that re-derives only `g = tan(π·fc/sr)` — Q's parabola,
|
||||||
|
the morph's cos/sin, and the folded mix are all cutoff-independent and stay cached
|
||||||
|
from `prepare()`; `filter_params` hoists its constant logs. Measured at 48 kHz,
|
||||||
|
Release, net of the sweep generator: kernel alone 2.8 ns/frame, full `prepare()` 56.9
|
||||||
|
ns, cutoff-only re-solve 15.5 ns — about 1.2% of a core for 16 continuously-swept
|
||||||
|
voices. The corner now sweeps continuously rather than stair-stepping.
|
||||||
|
- **The editor floor was raised to 840×620**, which is also its new default size, up
|
||||||
|
from a 560×460 floor at which the grown deck wrapped to four rows and pushed
|
||||||
|
`FILTER ENV` / `AMP ENVELOPE` / `VOICE` / `MASTER` off-screen with no scroll.
|
||||||
|
`kEditorMinWidth`/`kEditorMinHeight` now live in `sample_bands`, read by both the
|
||||||
|
shell's `checkSizeConstraint` and the opening `ViewRect`. Consequence worth
|
||||||
|
recording: a host with a saved editor rect below 840×620 is clamped up on reopen.
|
||||||
|
|
||||||
### Θ-W2-T2 — stereo-waveform-lanes
|
### Θ-W2-T2 — stereo-waveform-lanes
|
||||||
|
|
||||||
Delivered as specified: two stacked lanes, L above R, in stereo mode; one lane in mono;
|
Delivered as specified: two stacked lanes, L above R, in stereo mode; one lane in mono;
|
||||||
|
|||||||
+10
-73
@@ -145,8 +145,8 @@ collision is real and the serialization is the correct answer.
|
|||||||
the full narrative of each. Between them: the zone subsystem is retired, the wave's two
|
the full narrative of each. Between them: the zone subsystem is retired, the wave's two
|
||||||
responsibility seams (the `sampler_core` split, the Sample-face band split) are in
|
responsibility seams (the `sampler_core` split, the Sample-face band split) are in
|
||||||
place, the two extension-side capture-handoff defects are fixed, and the filter DSP has
|
place, the two extension-side capture-handoff defects are fixed, and the filter DSP has
|
||||||
landed as a standalone pure module with no call site — Θ-W2-T1 integrates it into the
|
landed as a standalone pure module — Θ-W2-T1 has since wired it into the voice path (see
|
||||||
voice path.
|
below).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -158,77 +158,14 @@ band-stack allocator that T2 and T3 fill; and W1-T1's key-range answer, which de
|
|||||||
what T3's piano strip displays. Authoring any of this against the per-zone model means
|
what T3's piano strip displays. Authoring any of this against the per-zone model means
|
||||||
writing storage plumbing W1 deletes.
|
writing storage plumbing W1 deletes.
|
||||||
|
|
||||||
**Θ-W2-T2 (`stereo-waveform-lanes`) and Θ-W2-T3 (`toolbar-and-piano-strip`) have
|
**All three tracks have landed** — Θ-W2-T1 (`filter-voice-path`), Θ-W2-T2
|
||||||
landed** — see `docs/COMPLETED.md`. Between them: the waveform band now shows both
|
(`stereo-waveform-lanes`), and Θ-W2-T3 (`toolbar-and-piano-strip`) — see
|
||||||
channels in stereo mode behind a type-enforced full-height overlay contract, and the
|
`docs/COMPLETED.md` for the full narrative of each. Between them: the filter sits in the
|
||||||
chrome band's toolbar and piano strip are cleaned up per spec. **One track remains:**
|
per-voice signal path with its own deck, the waveform band shows both channels in
|
||||||
Θ-W2-T1 (`filter-voice-path`), which puts the filter into the per-voice signal path and
|
stereo mode behind a type-enforced full-height overlay contract, and the chrome band's
|
||||||
gives it its deck. The three tracks were disjoint by band — T1 owns the parameter model
|
toolbar and piano strip are cleaned up per spec. The three tracks were disjoint by
|
||||||
and the deck band, T2 owned the waveform band, T3 the chrome band — and none
|
band — T1 owned the parameter model and the deck band, T2 owned the waveform band, T3
|
||||||
re-allocates the band stack.
|
the chrome band — and none re-allocated the band stack.
|
||||||
|
|
||||||
#### Θ-W2-T1 — `filter-voice-path`
|
|
||||||
|
|
||||||
**Goal.** Put the filter into the per-voice signal path as a new fixed processing point,
|
|
||||||
give it its deck, and relay the deck row in signal-flow order.
|
|
||||||
|
|
||||||
**Consolidates item 2 (integration half).** The DSP module is Θ-W1-T3. The filter
|
|
||||||
envelope's *curve treatment and mode-driven shape* arrive in Θ-W3 — this track ships the
|
|
||||||
filter envelope in the existing staged AHDSR shape.
|
|
||||||
|
|
||||||
**Surface boundary — owns:** `core/instrument/engine/` voice-render filter call site +
|
|
||||||
`zone_params.h` (filter parameters), `core/instrument/map/component_state_io` (version
|
|
||||||
bump for the filter parameters), `core/instrument/ui/knob_deck` + the **deck band** of
|
|
||||||
the Sample face (geometry, paint, input). Reads the band allocator; does not change it.
|
|
||||||
|
|
||||||
**Behavior.**
|
|
||||||
- **Pipeline position.** A new processing point in the sampler audio pipeline: **after
|
|
||||||
the pitch envelope, before the amp stage.** The amp envelope still shapes the filtered
|
|
||||||
result.
|
|
||||||
- **Per-voice.** Each sounding voice runs its own filter with its own envelope state —
|
|
||||||
not a shared instance-wide filter.
|
|
||||||
- **Parameters:** mode, cutoff, Q, mod amt — then the AHDSR controls.
|
|
||||||
- **Mod amt: bipolar, −100% to +100%, targeting cutoff** — covering the full range
|
|
||||||
from either end.
|
|
||||||
- Cutoff / Q / mode ranges are the module's, from Θ-W1-T3.
|
|
||||||
- **Velocity and key-tracking modulation ship with this item — not deferred.** The
|
|
||||||
filter gains velocity modulation following the amp's velocity-transfer-curve pattern
|
|
||||||
and key-tracking following the pitch key-tracking pattern. The follow-up established
|
|
||||||
the parallel, not new ranges or control layout — follow the cited precedents. (The
|
|
||||||
*bipolar domain and default* of the filter velocity curve, and its button's home, are
|
|
||||||
item 11 — Θ-W4-T2. This track wires the modulation path; that track shapes the curve.)
|
|
||||||
- **Off by default.** Pre-existing saved instances and freshly loaded captures sound
|
|
||||||
unchanged until the user engages it.
|
|
||||||
- **Parameters live in the instrument's one parameter set** (post-item-16) — no per-zone
|
|
||||||
storage, no Sample/Zone parity.
|
|
||||||
- **Label.** The user-facing deck-group label is **"Filter"**. "MM preamp" is working
|
|
||||||
shorthand for the DSP lineage, not UI text.
|
|
||||||
- **Deck reorder.** The knob deck row is relaid out in signal-flow order:
|
|
||||||
**pitch → filter → amp**.
|
|
||||||
|
|
||||||
**Acceptance criteria.**
|
|
||||||
- With the filter engaged, played notes are audibly filtered at the specified pipeline
|
|
||||||
point: the filter acts on pitched (post-pitch-envelope) signal and the amp envelope
|
|
||||||
still shapes the filtered result — audible ordering pitch → filter → amp.
|
|
||||||
- Mode, cutoff, Q, and mod-amt controls appear in a deck group labeled **"Filter"**.
|
|
||||||
- Cutoff sweeps the full audio spectrum on a log scale, fully open to fully closed; Q
|
|
||||||
spans 0.1 → 10 with √2 at the control's center; mod amt at −100% and at +100% each
|
|
||||||
drive cutoff across the full range, from opposite ends.
|
|
||||||
- High Q audibly emphasizes the cutoff region in both HP and LP modes.
|
|
||||||
- **Two simultaneously sounding voices at different envelope phases are filtered
|
|
||||||
independently** — per-voice processing is audible, not a shared filter.
|
|
||||||
- Filter parameters live in the one parameter set: they edit in one place and govern the
|
|
||||||
instrument as a whole.
|
|
||||||
- Velocity and key-tracking modulation of the filter are audible — velocity following
|
|
||||||
the amp-velocity-transfer-curve pattern, key-tracking following the
|
|
||||||
pitch-key-tracking pattern.
|
|
||||||
- The deck row reads pitch → filter → amp left-to-right.
|
|
||||||
- **The filter is off by default:** a project saved before this change reopens sounding
|
|
||||||
identical, and a freshly loaded capture sounds unchanged until the filter is engaged.
|
|
||||||
- The filter tick adds no allocation and no virtual dispatch to `process()`.
|
|
||||||
|
|
||||||
**Open questions.** None carried from item 2 — all three of its prior questions (other
|
|
||||||
mod sources, parameter storage side, neutral default) closed in the source doc.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user