Ψ-W2-T1 remediation: scrim the card name over the waveform, hedge two SDK claims, fix a UTF-8-truncation empty-label bug, file the legibility DAW deferral

This commit is contained in:
2026-08-01 22:08:35 -04:00
parent 3278b4eced
commit 0feb32d59b
12 changed files with 115 additions and 11 deletions
+4
View File
@@ -56,6 +56,10 @@ CaptureName composeCaptureName(const CaptureNameInputs& in) {
if (base.empty()) base = trimmed(in.fallback);
if (base.empty()) base = "capture";
base = truncateUtf8(base, kMaxSourceNameBytes);
// truncateUtf8 backs off over continuation bytes, so a name whose first kMaxSourceNameBytes
// bytes are ALL continuation bytes (0x80-0xBF) backs off to nothing — re-apply the "never an
// empty label" fallback after truncation, not just before it.
if (base.empty()) base = "capture";
CaptureName out;
out.label = base;
+3 -1
View File
@@ -12,7 +12,9 @@ Rect cardNameStrip(const Rect& cell) {
// is a text block, not a thumbnail.
const int minHeight = 3 * kCardStripHeight;
if (cell.width <= 2 * kCardStripPad || cell.height < minHeight) return Rect{};
// Inset by 1px from the top edge so the name never sits on the selection border.
// Inset by 1px from the top edge so the name never sits on the focused-cell inner ring
// (drawn at rect.y+1, panel_render.cpp) — the selection border itself is at rect.y and is
// clear regardless.
return Rect{cell.x + kCardStripPad, cell.y + 1,
cell.width - 2 * kCardStripPad, kCardStripHeight};
}
+7 -3
View File
@@ -15,9 +15,13 @@ inline constexpr int kCardStripHeight = 12;
inline constexpr int kCardStripPad = 3;
// The strip the card's name line occupies: across the top of the cell, drawn OVER the
// waveform exactly as the length read-out is drawn over it at the bottom. Empty when the
// cell has no room for both strips plus a waveform worth looking at — the caller draws
// nothing rather than burying the card under text.
// waveform exactly as the length read-out is drawn over it at the bottom, rather than a
// reserved non-drawing band — kCardStripHeight (12px) is a small slice of the shipping 84px
// cell, and the overlay matches the bottom read-out's existing convention rather than
// introducing a second layout rule. The draw site scrims behind the text so it stays legible
// over the waveform's accent fill (`kCardNameScrimAlpha`, `theme.h`). Empty when the cell has
// no room for both strips plus a waveform worth looking at — the caller draws nothing rather
// than burying the card under text.
Rect cardNameStrip(const Rect& cell);
// Musical length inputs, taken straight off a Sample's capture-time stamp. tempoBpm 0 = unknown;
+7
View File
@@ -97,6 +97,13 @@ KitColor compositeOver(const KitColor& over, const KitColor& under, double alpha
// test composes the same value the shell draws with (see compositeOver).
inline constexpr double kLoopSpanFillAlpha = 0.20;
// The card name strip's scrim: bg/base composited at this alpha UNDER the name text, so
// text/primary stays readable when a loud capture's waveform peak (accent/primary) reaches
// into the strip. Named HERE, same reason as kLoopSpanFillAlpha above — test_theme.cpp composes
// this exact value against the strip's worst-case background (accent/primary) to pin the 4.5:1
// body floor Font::Micro answers to.
inline constexpr double kCardNameScrimAlpha = 0.75;
// Relative luminance per WCAG 2.1 (sRGB linearization + 0.2126/0.7152/0.0722 weighting). Alpha
// is ignored — a translucent overlay's effective color is the caller's to compose first
// (compositeOver).
+4
View File
@@ -401,6 +401,10 @@ void RunRecaptureFromSource(ReaSamplerSession& session)
req.sampleRate = recipe->sampleRate;
req.channelCount = recipe->channelCount;
req.bitDepth = WavBitDepth::Float32;
// orig->displayName is the ORIGINAL capture's label (recapture preserves identity, it
// does not re-mint it — see this function's header comment), so the regenerated file's
// stem carries the original capture's stamp, not this render's — do not read it as a
// render timestamp.
req.baseName = orig->displayName.empty() ? "recapture" : orig->displayName;
req.trackGuids = recipe->trackGuids;
+4 -1
View File
@@ -7,6 +7,7 @@
#include "shell/capture/scope_resolve.h"
#include <cstring> // strnlen — bounded read of GetTrackName's buffer
#include <filesystem> // project-dir derivation for provenance parent resolution
#include <utility>
@@ -110,7 +111,9 @@ std::string trackName(MediaTrack* tr)
// runs once per capture, not per frame.
std::vector<char> buf(1024, '\0');
if (!GetTrackName(tr, buf.data(), static_cast<int>(buf.size()))) return {};
return std::string(buf.data());
// Bounded construction: the SDK doesn't document NUL-termination within bufOut_sz, so
// strnlen over the whole buffer (never past it) rather than trusting one.
return std::string(buf.data(), strnlen(buf.data(), buf.size()));
}
// Reads every track's P_RAZOREDITS (SDK header ~2899: space-separated triples of
+7 -4
View File
@@ -58,10 +58,13 @@ bool resolveRange(double& start, double& end, std::string& why);
// Collects the selected tracks (Track scope) into out.sourceTracks + GUIDs + names.
bool collectSelectedTracks(ResolvedSource& out);
// The track's display name, read-only. GetTrackName (SDK header ~3626) is used rather
// than P_NAME because it already answers REAPER's own convention for an unnamed track
// ("Track N"), which is exactly the deterministic fallback a capture label wants; P_NAME
// would hand back an empty string instead. Empty only if the read itself fails.
// The track's display name. GetTrackName (SDK header ~3626) is used rather than P_NAME
// because it already answers REAPER's own convention for an unnamed track ("Track N"),
// which is exactly the deterministic fallback a capture label wants; P_NAME would hand
// back an empty string instead. `[verify]` the SDK header states neither that the read is
// non-mutating nor what a `false` return means; the call site treats it as read-only and
// treats `false` (or a `true` with an untouched buffer) the same way — an empty name, which
// falls through to the scope literal fallback either way, so both readings are safe.
// Callers that build a ResolvedSource by hand (batch capture) use this directly.
std::string trackName(MediaTrack* tr);
+8 -2
View File
@@ -42,12 +42,18 @@ void drawCardMeta(LICE_IBitmap* bmp, const CellRect& rect, const Sample& s) {
}
// The capture's name across the top of the card. The kit clips with an end-ellipsis, so a
// long name shortens ON SCREEN only — the stored label is never truncated. An entry with
// no label (nothing writes one today, but old banks are not migrated) draws nothing.
// long name shortens ON SCREEN only — the stored label is never truncated. An entry with an
// empty label (old banks predate this track and are not migrated) draws nothing.
void drawCardName(LICE_IBitmap* bmp, const CellRect& rect, const Sample& s) {
if (s.displayName.empty()) return;
const CellRect strip = cardNameStrip(rect);
if (strip.empty()) return;
// Scrim behind the name: text/primary alone reads ~1:1 against the accent-lime waveform
// fill a loud capture's peak reaches into this strip. kCardNameScrimAlpha (core/ui/theme.h)
// is picked so bg/base composited at that alpha over accent/primary clears the 4.5:1 body
// floor Font::Micro answers to — pinned in test_theme.cpp.
LICE_FillRect(bmp, strip.x, strip.y, strip.width, strip.height,
toLice(roleColor(Role::BgBase)), static_cast<float>(kCardNameScrimAlpha), 0);
text(bmp, strip, s.displayName.c_str(), Font::Micro, Role::TextPrimary, Align::Left);
}
+1
View File
@@ -133,6 +133,7 @@ using ui::hitTestMenuButton;
using ui::hitTestPruneButton;
using ui::hitTestSlot;
using ui::hitTestTabStrip;
using ui::kCardNameScrimAlpha;
using ui::kCardStripHeight;
using ui::kCardStripPad;
using ui::menuButtonReserve;