#pragma once // tail_control — the REAPER-free logic behind the docked bank_panel's tail-mode // toggle. The panel shell (bank_panel.cpp) owns the SWELL window, LICE drawing, and // click hit-testing; what is NOT DAW-bound — the cycle order, the manual-length // clamp, and the toggle's label text — lives here so it is unit-tested outside the // DAW (CLAUDE.md §load-bearing split). Mirror of bank_grid / mode_switch. // // PURE MODULE: NO REAPER types, NO SWELL, NO vendor/ includes. Standard library // only (plus render_settings for the pure TailMode enum). Builds and unit-tests // without REAPER. #include #include #include "render_settings.h" // TailMode (pure enum) — the three-state tail contract namespace reasampler { // The Manual-mode starting length. 2 s is a musically useful default tail (a bar of // reverb throw at a moderate tempo) that is well under the 8 s cap. Also the value a // project with no stored tail setting (older / never-adjusted) falls back to on load. inline constexpr double kDefaultManualTailMs = 2000.0; // The fine-adjust step per scroll-wheel notch in Manual mode. 250 ms is coarse enough // that a few notches cover the useful range, fine enough to dial a length precisely. // Daniel-set. The panel maps one wheel notch to +/- this many ms via adjustManualMs. inline constexpr double kManualStepMs = 250.0; // The panel's current tail setting: the mode plus the length used ONLY when the // mode is Manual. Held as in-memory panel/session state (bank_panel.cpp), default // None so a capture with no explicit choice stays exact-bounds / byte-identical to // today. `manualMs` is a stored default a future fine-adjust UI can tune; it is // clamped to the 8 s cap (kMaxTailMs) before it ever reaches a CaptureRequest. struct TailSetting { TailMode mode = TailMode::None; double manualMs = kDefaultManualTailMs; }; // Cycles the tail mode: None -> Auto -> Manual -> None. Pure so the wrap order is // pinned by a test and the panel's click handler owns no enum arithmetic of its own. // An out-of-range value (unreachable for a valid enum) cycles back to None. TailMode cycleTailMode(TailMode current); // The effective manual length a Manual capture uses: `manualMs` clamped to // [0, kMaxTailMs] (the runaway guard the pure tailRenderSettingsFor also applies). // Exposed so the panel can show the clamped value and main.cpp hands a pre-clamped // tailMs into the CaptureRequest. Meaningful only for TailMode::Manual. double clampManualMs(double manualMs); // Applies `notches` scroll-wheel steps of `stepMs` each to `current`, clamped to // [0, kMaxTailMs]. Positive notches lengthen, negative shorten. Pure so the fine-adjust // arithmetic (and its clamp at both bounds) is unit-tested; the panel wheel handler // owns no arithmetic of its own. Meaningful only for TailMode::Manual. double adjustManualMs(double current, int notches, double stepMs); // The toggle's label for a setting, e.g. "Tail: Off", "Tail: Auto". In Manual mode the // clamped length is appended in seconds to one decimal, e.g. "Tail: Manual 2.0s" — // Off/Auto carry no length. Pure so the exact strings (and the Manual format) are // test-pinned, including the boundary lengths (0.0s, 8.0s). std::string tailToggleLabel(const TailSetting& setting); // JSON round-trip of a TailSetting (mode + manualMs), for persist to store the tail // setting per-project alongside the bank and view model. Kept pure/testable here — // the natural home, mirroring bank_model's serialize/deserialize. serialize emits a // compact object; deserialize returns std::nullopt on malformed input so the caller // (persist) falls back to a default setting, exactly as an absent key does. std::string serializeTailSetting(const TailSetting& setting); std::optional deserializeTailSetting(const std::string& json); } // namespace reasampler