120 lines
3.8 KiB
C++
120 lines
3.8 KiB
C++
// tail_control — pure implementation. See tail_control.h.
|
|
|
|
#include "core/capture/tail_control.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
|
|
#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<TailMode> 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<TailSetting> 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<TailMode> mode = modeFromInt(modeInt);
|
|
if (!mode) return std::nullopt;
|
|
|
|
TailSetting out;
|
|
out.mode = *mode;
|
|
out.manualMs = ms;
|
|
return out;
|
|
}
|
|
|
|
} // namespace reasampler::capture
|