Files
reasampler/docs/verify-track-scope-multitrack.md
T
daniel 2a9ab65944 Ψ-W3 remediation: fix the verify doc's wrong render source, add missing content checks, soften unverified claims
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.
2026-08-01 23:01:50 -04:00

8.6 KiB
Raw Permalink Blame History

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.00012.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.00012.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).