2a9ab65944
Corrected the Render-dialog source name the refusal's evidence depends on, added a by-ear content check and a file-size channel proxy, and stopped two comments from overclaiming.
155 lines
8.6 KiB
Markdown
155 lines
8.6 KiB
Markdown
# DAW verification — track-scope capture over a multi-track selection
|
||
|
||
What a DAW pass must establish for the multi-track track capture, and the exact numbers
|
||
or strings to read off. Nothing below can be closed by a unit test: every item depends on
|
||
what REAPER actually does with a render request.
|
||
|
||
**Build to use.** Release, installed into `UserPlugins/`, REAPER restarted — extensions
|
||
load at startup only. Set the docked panel's tail toggle to **None** before every cell;
|
||
Auto adds an 8 s window and Manual a fixed one, and both would invalidate the frame-count
|
||
readings.
|
||
|
||
**Project to use.** One saved project, project sample rate pinned to 48000. Two audio
|
||
tracks, `A` and `B`, each holding one item at least 30 s long, with *audibly different*
|
||
content (a tone on `A`, a drum loop on `B`). One folder track `F` with `A` and `B` as its
|
||
children, used only in §5.
|
||
|
||
---
|
||
|
||
## 1. The regression floor — single-track track capture is unchanged
|
||
|
||
Select **track `A` only**. Make a time selection from **10.000 s to 12.000 s**. Run
|
||
*ReaSampler: capture selected track(s)*.
|
||
|
||
Read off:
|
||
|
||
- A file appears in the project's bank folder, and one new card appears on the panel.
|
||
- The card's length reads **2.000 s**; its frame count is **96000** (`round(12.0 × 48000)
|
||
− round(10.0 × 48000)`). The backend refuses the capture with `BoundsMismatch` if the
|
||
render is more than one frame off that, so a landed capture already proves the number
|
||
to ±1 — what you are confirming here is that it landed at all.
|
||
- The REAPER console shows **no** `ReaSampler capture failed:` line.
|
||
- Track `A` is still the only selected track afterwards.
|
||
- **Content, not just length.** Listen to the landed file. `A` and `B` carry *audibly
|
||
different* content by the project setup above (tone vs. drum loop), so this is a by-ear
|
||
check, not a null test: the capture must be the tone alone, with **no** drum-loop bleed.
|
||
Expected: pure tone, matching `A` soloed. Failing: any trace of `B`'s drum loop audible
|
||
in the file. This is not a tautology check — the SDK header's own `RENDER_SETTINGS` line
|
||
admits a second reading, `(&(1|2)==0)=master mix`, under which a single-track track
|
||
capture could render the **whole master mix** (both `A` and `B`) rather than `A` alone;
|
||
drum-loop bleed here is exactly what that misreading would produce, and this is the
|
||
cheapest place in the whole doc to catch it.
|
||
|
||
**This is the byte-identical floor.** If either cell now refuses, the change is wrong —
|
||
the refusal must fire only above one track.
|
||
|
||
## 2. The defect cell — two selected tracks now refuse
|
||
|
||
Select **`A` and `B` together**. Time selection 10.000–12.000 s. Run *capture selected
|
||
track(s)*.
|
||
|
||
Read off:
|
||
|
||
- The console prints exactly:
|
||
`ReaSampler capture failed: A track capture renders the selected tracks through the
|
||
master, and more than one track cannot land as a single file. Capture one track at a
|
||
time, or route them into a folder/bus track and capture that (a folder's own output is
|
||
its children summed).`
|
||
- **No** new card on the panel, and **no** new `.wav` in the bank folder (check the folder
|
||
directly — a stray file with nothing indexing it would mean the refusal fired too late).
|
||
- `A` and `B` are both still selected, both still unmuted, and neither track's fader, pan,
|
||
or FX-bypass state changed. The refusal returns before any guard is constructed, so
|
||
there should be nothing to restore — this reading is what confirms that.
|
||
|
||
Repeat with a **razor area spanning both tracks** and no time selection: identical
|
||
readings. Note that the track *selection* is what the refusal counts — a razor over two
|
||
tracks with only `A` selected is a one-track capture and must still succeed (§1).
|
||
|
||
## 3. The decisive observation — what `&128` actually writes
|
||
|
||
**This is the one that retires an inference, and it is the reason `docs/TODO.md` still
|
||
carries an entry.** The refusal in §2 rests on reading the SDK header's single-file bit
|
||
`&(4<<16)` as applying to item/razor sources only, never to `&128` — so N selected tracks
|
||
are believed to produce N files. That has never been observed.
|
||
|
||
Drive REAPER's own Render dialog by hand, with the extension out of the loop:
|
||
|
||
1. Select `A` and `B`.
|
||
2. File → Render. **Source:** *Selected tracks via master* — the dialog wording for `&128`
|
||
(SDK header ~3041). Do **not** pick *Stems (selected tracks)* — that is `&2`, a
|
||
different source bit that unambiguously writes one file per track and would confirm
|
||
nothing about `&128`.
|
||
**Bounds:** *Custom time range*, 10.000 to 12.000 s.
|
||
3. **File name:** a literal stem with **no wildcards at all** — e.g. `stemprobe`. Clear
|
||
`$track` / `$item` / anything else from the pattern; the extension writes exactly one
|
||
literal stem, so the probe must too.
|
||
4. Render to an empty scratch folder.
|
||
|
||
Read off — **the file count in that folder**:
|
||
|
||
- **Two files** (however REAPER disambiguated them, or one file that visibly got
|
||
overwritten): the inference holds, the §2 refusal is correct, and the `docs/TODO.md`
|
||
entry can be closed by writing this observation into `src/shell/capture/CLAUDE.md` as
|
||
fact.
|
||
- **One file containing `A` and `B` summed** (confirm by ear, or by nulling it against a
|
||
master render of the same range with only `A` and `B` unmuted): the inference is wrong,
|
||
the §2 refusal costs a working capture, and the track-scope half should be narrowed back
|
||
per the `docs/TODO.md` entry. The item-scope half stays either way.
|
||
|
||
Also record **what REAPER named the files** — that decides whether a future correct
|
||
multi-track capture could ever be built on this source at all.
|
||
|
||
## 4. Recapture replays the same answer
|
||
|
||
Take a **single-track** track capture that carries provenance (capture a range on `A`
|
||
whose source item is itself a bank sample, so `detectParent` fires), select its card, and
|
||
run *re-capture from source*. It must regenerate — same audio, same 96000 frames.
|
||
|
||
Then construct the multi-track case: a recorded recipe whose `trackGuids` names two
|
||
tracks. The reachable way to get one is to have captured it before this change; if no such
|
||
entry exists in any project, record that this cell was **not exercised** rather than
|
||
inventing one. When it is exercised, read off:
|
||
|
||
- `ReaSampler re-capture failed:` followed by the **same** message text as §2.
|
||
- The bank entry is untouched — same file, same hash, same card.
|
||
|
||
## 5. The way out actually works
|
||
|
||
Route `A` and `B` into folder `F`. Select **`F` only**, time selection 10.000–12.000 s,
|
||
capture track scope.
|
||
|
||
Read off: one card, 2.000 s, and the audio contains **both** `A` and `B`. This is what the
|
||
refusal message tells the user to do, so it has to be true.
|
||
|
||
## 6. Realtime still accepts a multi-track selection
|
||
|
||
Select `A` and `B`. Run *ReaSampler: capture selected track(s) in realtime* over the same
|
||
range. Read off: **one** card, and its audio contains both tracks. Realtime taps each
|
||
source track with a send into one temp track, so it sums where the offline render cannot —
|
||
the divergence from §2 is deliberate and this cell is what confirms it is real.
|
||
|
||
## 7. Mono collapse — what is and is not reachable
|
||
|
||
Capture a range on a track whose content is dead-center (a mono source panned center, or
|
||
a duplicated-channel file), using time selection **10.000 s to 12.000 s** (2.000 s, 96000
|
||
frames at 48000 Hz — the §1 convention, so the resulting file size is exact). The panel has
|
||
no channel-count readout anywhere (`Sample::channelCount` is not drawn by
|
||
`src/shell/panel/panel_render.cpp`), so read the proxy instead:
|
||
|
||
- Check the landed `.wav`'s size on disk (Explorer → Properties, or a directory listing). A
|
||
successful collapse is the extension's own rebuild — canonical 44-byte header + 96000 ×
|
||
4 bytes = **384,044 bytes**. A file near double that (~768,044 bytes, plus whatever
|
||
REAPER's own render adds for `bext`/metadata chunks) means the collapse did not fire —
|
||
recheck the source is genuinely dead-center before treating this as a defect.
|
||
- The console shows **no** `the lossless mono collapse ... already reached the bank; only
|
||
the size win from the collapse was lost.` line.
|
||
|
||
**Not DAW-reachable:** the collapse's *failure* branch. It fires only if the captured file
|
||
cannot be read, or its temporary rewrite cannot be written or renamed, inside the same
|
||
call that just rendered the file — there is no manual way to inject that fault between the
|
||
render and the rename. The branch is covered only at its reporting seam
|
||
(`tests/test_wav_codec.cpp`, `testCollapseOutcomeSuffixesAreDistinctStrings`), and its console line
|
||
has never been seen in a running REAPER. If you ever do see it, the render already reached
|
||
the bank — the report only tells you the collapse's size win was lost, not that the bytes
|
||
were verified (see `docs/TODO.md`'s 0-byte-render entry).
|