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:
2026-08-01 21:46:53 -04:00
parent 09d64c9f46
commit 3278b4eced
23 changed files with 626 additions and 25 deletions
+85
View File
@@ -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