// tail_control — pure implementation. See tail_control.h. #include "core/capture/tail_control.h" #include #include #include "core/json/json.h" namespace reasampler::capture { TailMode cycleTailMode(TailMode current) { switch (current) { case TailMode::None: return TailMode::Auto; case TailMode::Auto: return TailMode::Manual; case TailMode::Manual: return TailMode::None; } return TailMode::None; // unreachable for a valid enum; fail to the safe default } double clampManualMs(double manualMs) { return std::clamp(manualMs, 0.0, kMaxTailMs); } double adjustManualMs(double current, int notches, double stepMs) { return clampManualMs(current + notches * stepMs); } std::string tailToggleLabel(const TailSetting& setting) { switch (setting.mode) { case TailMode::None: return "Tail: Off"; case TailMode::Auto: return "Tail: Auto"; case TailMode::Manual: { // Clamped so the readout can't show an over-cap value even if // manualMs was stored past the cap. const double seconds = clampManualMs(setting.manualMs) / 1000.0; char buf[32]; std::snprintf(buf, sizeof(buf), "Tail: Manual %.1fs", seconds); return std::string(buf); } } return "Tail: Off"; // unreachable for a valid enum; fail to the safe default } // --- JSON round-trip --------------------------------------------------------- // manualMs round-trips exactly (json::numToStr uses the shortest %.17g-class // form for doubles); deserialize returns nullopt on any parse failure. namespace { // The persisted integer for a mode. Stable forever (stored in the .rpp): never // renumber these values or an already-saved project reads back the wrong mode. int modeToInt(TailMode m) { switch (m) { case TailMode::None: return 0; case TailMode::Auto: return 1; case TailMode::Manual: return 2; } return 0; } std::optional modeFromInt(int v) { switch (v) { case 0: return TailMode::None; case 1: return TailMode::Auto; case 2: return TailMode::Manual; default: return std::nullopt; // unknown enumerant -> malformed -> default } } } // namespace std::string serializeTailSetting(const TailSetting& setting) { // Byte-identical to the former snprintf writer: {"mode":%d,"manualMs":%.17g}. std::string out; { json::Writer w(out); w.keyRaw("mode", json::numToStr(modeToInt(setting.mode))); w.keyRaw("manualMs", json::numToStr(setting.manualMs)); } // Writer closes the object here (see bank_model's NRVO note) return out; } std::optional deserializeTailSetting(const std::string& blob) { json::Reader r(blob); if (!r.consume('{')) return std::nullopt; int modeInt = 0; double ms = 0.0; bool haveMode = false, haveMs = false; r.skipWs(); if (!r.consume('}')) { do { std::string key; if (!r.parseKey(key)) return std::nullopt; if (key == "mode") { if (!r.parseInt(modeInt)) return std::nullopt; haveMode = true; } else if (key == "manualMs") { if (!r.parseDouble(ms)) return std::nullopt; haveMs = true; } else { if (!r.skipValue()) return std::nullopt; // forward-compat } } while (r.consume(',')); if (!r.consume('}')) return std::nullopt; } if (!haveMode || !haveMs) return std::nullopt; // absent key -> malformed -> default const std::optional mode = modeFromInt(modeInt); if (!mode) return std::nullopt; TailSetting out; out.mode = *mode; out.manualMs = ms; return out; } } // namespace reasampler::capture