Add tail manual fine-adjust (scroll) + per-project tail persistence

Relocate TailSetting into ReaSamplerSession; persist serializes it to
a new forever-stable "tail_setting" ext-state key (load-per-project,
save-on-save). Footer scroll-wheel adjusts Manual length in 250ms
steps; label now shows "Tail: Manual X.Xs". Pure step/label/JSON
round-trip covered by tests.
This commit is contained in:
2026-07-23 19:12:12 -04:00
parent 938c85a223
commit 37affc6a70
6 changed files with 365 additions and 38 deletions
+29
View File
@@ -184,6 +184,13 @@ void ReaSamplerSession::saveToActiveProject() {
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
kProjExtViewKey, viewJson.c_str());
// Additive: the docked panel's tail setting rides alongside in its own key, so the
// tail choice travels inside the .rpp. Independent write — does not disturb the
// bank_index or view_state above.
const std::string tailJson = serializeTailSetting(tail_);
SetProjExtState(static_cast<ReaProject*>(proj), kProjExtNamespace,
kProjExtTailKey, tailJson.c_str());
MarkProjectDirty(static_cast<ReaProject*>(proj));
}
@@ -208,6 +215,23 @@ ViewModeModel loadViewModel(ReaProject* proj) {
return std::move(*loaded);
}
// Load the tail setting from a project's tail_setting key, or return the default. An
// absent/empty key (older / never-adjusted project) yields the default setting (None /
// 2 s manual) — graceful, never a crash. Malformed JSON is warned and also falls back
// to default, mirroring the bank's and view's malformed handling.
TailSetting loadTailSetting(ReaProject* proj) {
if (!proj) return TailSetting{};
const std::string tailJson =
getProjExtStateString(proj, kProjExtNamespace, kProjExtTailKey);
if (tailJson.empty()) return TailSetting{}; // no stored setting -> default
std::optional<TailSetting> loaded = deserializeTailSetting(tailJson);
if (!loaded) {
ShowConsoleMsg("ReaSampler: stored tail setting is malformed — ignoring.\n");
return TailSetting{};
}
return *loaded;
}
} // namespace
void ReaSamplerSession::loadFromProject(void* proj, const std::string& projectDir) {
@@ -225,6 +249,11 @@ void ReaSamplerSession::loadFromProject(void* proj, const std::string& projectDi
// only — no visibility/processing is applied here (that is D4).
view_ = loadViewModel(static_cast<ReaProject*>(proj));
// The tail setting is restored on EVERY load path too (peer-symmetry): switching
// to a project with no stored setting must fall back to the default, not inherit
// the previous project's choice (this REPLACES the old session-carry behavior).
tail_ = loadTailSetting(static_cast<ReaProject*>(proj));
if (!proj) {
bank_ = BankIndex{};
return;