feat(capture): bindable capture family — master/tracks/items/razor (M7)

Four wet capture actions route to OfflineRenderBackend (snapshot/restore
RENDER_*, 32-bit float), produce a Sample, add to the bank, persist. Pure
render_settings maps source mode to RENDER_SETTINGS and parses P_RAZOREDITS
(tested). No arrange insertion. True dry deferred to M10.
This commit is contained in:
2026-07-23 10:01:34 -04:00
parent 4f1231ea81
commit 7b530193b2
7 changed files with 679 additions and 60 deletions
+108
View File
@@ -0,0 +1,108 @@
#pragma once
// render_settings — the REAPER-free logic behind the M7 capture action family.
//
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO
// vendor/ includes. Standard library only. The capture shell (capture.cpp) reads
// the actual DAW state (time selection, selected tracks/items, razor strings) and
// hands the raw values here so the three genuinely-pure, easy-to-get-wrong pieces
// are unit-tested outside the DAW:
//
// 1. sourceMode -> the RENDER_SETTINGS integer bit value (wet only; M7 scope).
// 2. a P_RAZOREDITS string -> the list of (start,end) ranges + their union bound.
// 3. the capture-action table (id string, description, source mode, wet/dry) —
// the taxonomy, in one place so main.cpp iterates it instead of hand-listing.
//
// The RENDER_SETTINGS bit MEANINGS are transcribed verbatim from
// reaper_plugin_functions.h line ~3041 (see kRender* constants); the CHOICE of
// which bits each source mode sets is this module's logic and is tested.
#include <string>
#include <vector>
#include "bank_model.h" // SourceMode (pure enum)
namespace reasampler {
// --- RENDER_SETTINGS source/processing bits (verbatim from SDK header ~3041) --
//
// Only the bits M7 actually uses are named. Values are the documented bit
// weights; the DOC of each is the SDK header's, not a guess.
inline constexpr int kRenderMasterMix = 0; // (&(1|2))==0, no source bits
inline constexpr int kRenderSelItems = 32; // &32 selected media items
inline constexpr int kRenderSelItemsViaMaster = 64; // &64 selected media items via master
inline constexpr int kRenderSelTracksViaMaster = 128; // &128 selected tracks via master
inline constexpr int kRenderRazorEdits = 4096; // &4096 render razor edits
// NOTE: kRenderPreFaderStems (&8192) is NOT used in M7. REAPER offline render has
// no true pre-FX "dry" bit. Pre-fader stems are post-FX/pre-fader-volume — an
// approximation, not a dry capture. True pre-FX dry requires FX-bypass-around-
// render or the M8 realtime pre-FX path; it will be designed with the M10 null
// test. All M7 capture actions are wet-only.
inline constexpr int kRenderSingleFile = (4 << 16); // items/razor -> one file
// The RENDER_SETTINGS value for a given source mode. `supported` is false only
// for SourceMode::Realtime (that is the M8 backend, not offline render).
struct RenderSettingsChoice {
int settings = kRenderMasterMix;
bool supported = true; // false => not an offline-render source in M7
};
// Maps a source mode to the wet RENDER_SETTINGS value for M7.
// All M7 actions are wet-only (post-FX). `wetDry` is accepted but ignored for
// the mapping — retained in CaptureRequest as the seam for future M10 dry work.
//
// CONFIRMED (SDK header ~3041):
// MasterMix / TimeSelection -> master mix (0).
// SelectedTracks -> &128 selected tracks via master.
// SelectedItems -> &32 | single-file (one wav, not one-per-item).
// RazorArea -> &4096| single-file.
RenderSettingsChoice renderSettingsFor(SourceMode mode, double wetDry);
// A single razor-edit area: a time range on one track (envelope GUID ignored —
// M7 captures track-audio razor areas, not envelope lanes).
struct RazorRange {
double startSeconds = 0.0;
double endSeconds = 0.0;
};
// Parses ONE track's P_RAZOREDITS string (SDK header ~2899): space-separated
// TRIPLES of <start> <end> <envGuidString>. The envelope GUID is "" (an empty
// quoted string, i.e. the literal two chars `""`) for a track-audio area and a
// GUID like {…} for an envelope-lane area.
//
// Returns only the track-audio ranges (envelope-lane triples are skipped — M7
// renders track audio). Malformed/short trailing tokens are ignored, not fatal.
// A range with end <= start is dropped (no negative/empty areas leak through).
std::vector<RazorRange> parseRazorEdits(const std::string& razorString);
// The union bound (min start, max end) of a set of razor ranges — the exact
// window the offline render must cover so every area is inside the rendered file.
// Returns {0,0} for an empty input (caller treats that as "no razor area").
RazorRange razorUnionBounds(const std::vector<RazorRange>& ranges);
// --- Capture-action taxonomy (the bindable set main.cpp registers) -----------
//
// One row per bindable action. All M7 rows are wet-only (post-FX). Tail is OFF
// for every row (exact bounds); a tail-on variant is a later opt-in, YAGNI now.
// Yields a bounded, discoverable set with NO dialogs (the tool's no-clutter ethos).
//
// commandString is FOREVER-STABLE (user keybindings key off it) — never change a
// shipped value. baseName feeds the file stem (sanitized by capture_paths).
// wetDry is retained as the M10 seam; all M7 rows set it to 1.0.
struct CaptureActionDef {
const char* commandString; // CEREBELLUM_REASAMPLER_… FOREVER-STABLE id string
const char* description; // Actions-list label
const char* baseName; // file-stem base for this capture
SourceMode sourceMode;
double wetDry; // 1.0 (wet) for all M7 rows; seam for M10 dry
};
// The full M7 capture-action table. Iterated by main.cpp to register the family
// and route each fired command back to its definition. Kept here (pure) so the
// taxonomy is one testable list, not scattered registration code.
//
// Four wet-only rows: CAPTURE_MASTER, CAPTURE_TRACKS_WET, CAPTURE_ITEMS_WET,
// CAPTURE_RAZOR_WET. Dry variants are deferred to M10 (null-test work) — see the
// kRenderPreFaderStems note above for why offline dry is non-trivial.
const std::vector<CaptureActionDef>& captureActionTable();
} // namespace reasampler