Name captures after their source track: label and filename both, on every interactive mint site, and show the name on the panel card
This commit is contained in:
@@ -49,6 +49,7 @@ Detail specific to these pure modules:
|
||||
- `capture_realtime` (`core/capture`, **renamed from `realtime_record` in Q-W3** — the Q-9 naming rider: pure module takes the stem, the shell takes the suffix, matching `drag_out`/`drag_out_win`) — the M8 realtime-record pure logic: capture scope + FX-tap point → `I_RECMODE`/`I_RECMODE_FLAGS` values, wet/dry → tap point, the recorded-file → `Sample` mapping, and the async record-phase state machine. Depends on `bank_model` for the plain `Sample`/`SourceMode` types. The transport/temp-track/send recipe lives in the shell (`shell/capture/capture_realtime_shell.cpp` + `capture_realtime_finalize.cpp`).
|
||||
- `batch_capture` — pure batch-capture planner: maps source ranges to capture units and aggregates results.
|
||||
- `capture_paths` — the REAPER-free path arithmetic behind offline capture: bank-subfolder + unique-filename derivation (`deriveBankPaths`, forward-slash form, no filesystem touch), the absolute-render-dir vs. project-relative-index-path split (`BankPaths`), the persist-side inverse (`resolveBankFile`, `projectDirOfRpp`), the Save-As bank-relocation plan (`deriveRelocationPlan`), and the GUID-primary project-identity classifier (`classifyProjectTransition` → `NoOp`/`Load`/`SaveAsRelocate`) the persist-poll timer drives.
|
||||
- `capture_name` — the REAPER-free composition of one capture's label + file-stem base from its source-track name(s), a local-calendar discriminator (`MM-DD HHMM`, from the shell's clock read), and an optional batch ordinal. The label and the stem deliberately diverge: the stem still passes through `capture_paths::sanitizeStem` (so a name that sanitizes to nothing files as `capture`), while the label keeps the source name verbatim. Stem uniqueness stays entirely `makeUniqueTag`'s — this module never disambiguates.
|
||||
- `insert_plan` — the REAPER-free logic behind the `insert` shell (M6): computes the `InsertMedia` `mode` bitmask from an `InsertOptions` struct (placement target, tempo-conform ratio, preserve-pitch flag), guaranteeing the &4 stretch-to-time-selection bit is never set and that no tempo bits are set when `conform == None`.
|
||||
- `render_settings` — the REAPER-free logic behind the capture action family: `SourceMode` → `RENDER_SETTINGS` bit mapping, `P_RAZOREDITS` string parsing + range-union bounds, razor-else-time range inference, the FX-scope bypass plan (`fxBypassPlanFor`), the tail-mode → `RENDER_TAILFLAG`/`RENDER_NORMALIZE`/`RENDER_TRIMEND` mapping (`tailRenderSettingsFor`) and its realtime-window analog (`realtimeRecordWindowEnd`), and the capture-action taxonomy table (`captureActionTable`) `main.cpp` iterates to register the CAPTURE_ITEM/CAPTURE_TRACK family.
|
||||
- `render_window` — the REAPER-free frame arithmetic behind exact capture bounds: `frameCountFor` (the frame count a project-time window occupies at the project rate — the number the offline backend checks the rendered file against before landing it, so a widened render is refused rather than banked) and `itemExtentPrintsWindow`, the predicate `render_settings::sourceModeForScope` consults to decide whether REAPER's selected-items render source can express a requested window at all.
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
reasampler_pure_library(capture_paths SOURCES capture_paths.cpp)
|
||||
reasampler_test(capture_paths LINK capture_paths)
|
||||
|
||||
reasampler_pure_library(capture_name SOURCES capture_name.cpp)
|
||||
# capture_paths: the stem base's real contract is that sanitizeStem keeps it legal, so the
|
||||
# name tests assert the composed stem THROUGH the sanitizer rather than in isolation.
|
||||
reasampler_test(capture_name LINK capture_name capture_paths)
|
||||
|
||||
reasampler_pure_library(insert_plan SOURCES insert_plan.cpp)
|
||||
reasampler_test(insert_plan LINK insert_plan)
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// capture_name — pure implementation. See the header.
|
||||
|
||||
#include "core/capture/capture_name.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace reasampler::capture {
|
||||
|
||||
namespace {
|
||||
|
||||
// A track name padded with spaces would render ragged in the label and as underscores in
|
||||
// the stem, so both ends are trimmed before anything else looks at it.
|
||||
std::string trimmed(const std::string& s) {
|
||||
std::size_t b = 0;
|
||||
std::size_t e = s.size();
|
||||
auto isSpace = [](unsigned char c) {
|
||||
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
||||
};
|
||||
while (b < e && isSpace(static_cast<unsigned char>(s[b]))) ++b;
|
||||
while (e > b && isSpace(static_cast<unsigned char>(s[e - 1]))) --e;
|
||||
return s.substr(b, e - b);
|
||||
}
|
||||
|
||||
// Truncating mid-sequence would put invalid UTF-8 into the persisted label, so the cut
|
||||
// backs off over continuation bytes (10xxxxxx). The stem does not care — sanitizeStem
|
||||
// replaces every non-ASCII byte anyway — but one rule for both keeps them the same name.
|
||||
std::string truncateUtf8(const std::string& s, std::size_t maxBytes) {
|
||||
if (s.size() <= maxBytes) return s;
|
||||
std::size_t cut = maxBytes;
|
||||
while (cut > 0 && (static_cast<unsigned char>(s[cut]) & 0xC0) == 0x80) --cut;
|
||||
return s.substr(0, cut);
|
||||
}
|
||||
|
||||
int clampTo(int v, int lo, int hi) { return v < lo ? lo : (v > hi ? hi : v); }
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string formatCaptureStamp(const CaptureStamp& stamp) {
|
||||
if (stamp.month < 1 || stamp.day < 1) return {};
|
||||
char buf[24];
|
||||
std::snprintf(buf, sizeof(buf), "%02d-%02d %02d%02d",
|
||||
clampTo(stamp.month, 1, 12), clampTo(stamp.day, 1, 31),
|
||||
clampTo(stamp.hour, 0, 23), clampTo(stamp.minute, 0, 59));
|
||||
return buf;
|
||||
}
|
||||
|
||||
CaptureName composeCaptureName(const CaptureNameInputs& in) {
|
||||
std::string base;
|
||||
int named = 0;
|
||||
for (const std::string& raw : in.sourceNames) {
|
||||
const std::string n = trimmed(raw);
|
||||
if (n.empty()) continue;
|
||||
if (base.empty()) base = n;
|
||||
++named;
|
||||
}
|
||||
if (base.empty()) base = trimmed(in.fallback);
|
||||
if (base.empty()) base = "capture";
|
||||
base = truncateUtf8(base, kMaxSourceNameBytes);
|
||||
|
||||
CaptureName out;
|
||||
out.label = base;
|
||||
out.stemBase = base;
|
||||
|
||||
// Several sources collapse onto the first one's name plus a count of the rest — the
|
||||
// alternative (joining every name) produces a stem no one can read and a label that
|
||||
// no longer fits a card.
|
||||
if (named > 1) {
|
||||
const std::string extra = std::to_string(named - 1);
|
||||
out.label += " +" + extra;
|
||||
out.stemBase += "+" + extra;
|
||||
}
|
||||
|
||||
if (in.ordinal > 0) {
|
||||
const std::string ord = std::to_string(in.ordinal);
|
||||
out.label += " #" + ord;
|
||||
out.stemBase += "-" + ord;
|
||||
}
|
||||
|
||||
const std::string stamp = formatCaptureStamp(in.stamp);
|
||||
if (!stamp.empty()) out.label += " " + stamp;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace reasampler::capture
|
||||
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
// capture_name — the REAPER-free composition of one capture's label and file-stem base
|
||||
// from its source-track name(s), a local-calendar discriminator, and an optional batch
|
||||
// ordinal. The shell reads the names and the clock; the SHAPE of a capture's name is
|
||||
// decided here so it is testable without a DAW.
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler::capture {
|
||||
|
||||
// The capture's own moment, already broken down into LOCAL calendar fields by the shell.
|
||||
// Passing fields rather than an epoch is what keeps the format deterministic under test:
|
||||
// an epoch would render differently per machine timezone. month < 1 or day < 1 means
|
||||
// "no stamp" and suppresses the discriminator entirely.
|
||||
struct CaptureStamp {
|
||||
int month = 0; // 1-12
|
||||
int day = 0; // 1-31
|
||||
int hour = 0; // 0-23
|
||||
int minute = 0; // 0-59
|
||||
};
|
||||
|
||||
// Longest source-name prefix kept in either the label or the stem. Real track names sit
|
||||
// far under it; the bound exists so a pathological name cannot push the rendered file
|
||||
// path toward the platform's limit, and so a label and its file still read as the same
|
||||
// name.
|
||||
inline constexpr std::size_t kMaxSourceNameBytes = 64;
|
||||
|
||||
struct CaptureNameInputs {
|
||||
// Source-track names in source order — the first non-empty one names the capture,
|
||||
// the rest only contribute the "+N" multi-source marker.
|
||||
std::vector<std::string> sourceNames;
|
||||
|
||||
CaptureStamp stamp;
|
||||
|
||||
// Batch unit ordinal; <= 0 for a single capture.
|
||||
int ordinal = 0;
|
||||
|
||||
// The scope literal ("item"/"track"/"realtime"), used ONLY when no source name
|
||||
// resolved at all — otherwise the source name wins.
|
||||
std::string fallback = "capture";
|
||||
};
|
||||
|
||||
struct CaptureName {
|
||||
// Sample::displayName. Legible, carries the source name verbatim, and is explicitly
|
||||
// NOT unique (core/model/CLAUDE.md §resample_name) — the stamp serves the eye.
|
||||
std::string label;
|
||||
|
||||
// deriveBankPaths' baseName. Still passes through sanitizeStem, and stem uniqueness
|
||||
// is still entirely makeUniqueTag's job.
|
||||
std::string stemBase;
|
||||
};
|
||||
|
||||
// "MM-DD HHMM" (e.g. "08-01 1432"); empty when the stamp carries no calendar date.
|
||||
// Year is deliberately omitted: the card and the browse list are narrow, and Sample
|
||||
// carries the full createdTimestamp for anything needing the exact moment.
|
||||
std::string formatCaptureStamp(const CaptureStamp& stamp);
|
||||
|
||||
CaptureName composeCaptureName(const CaptureNameInputs& in);
|
||||
|
||||
} // namespace reasampler::capture
|
||||
@@ -199,7 +199,7 @@ RazorRange razorUnionBounds(const std::vector<RazorRange>& ranges);
|
||||
struct CaptureActionDef {
|
||||
const char* commandSuffix; // e.g. "CAPTURE_TRACK" — FOREVER-STABLE (composed w/ prefix)
|
||||
const char* descriptionPhrase; // e.g. "capture selected track(s)" — Actions-list phrase
|
||||
const char* baseName; // file-stem base for this capture
|
||||
const char* baseName; // file-stem FALLBACK; the source track normally names the capture
|
||||
CaptureScope scope; // FX scope (item / track)
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user