Ctrl K
ComunidadGuías…

position_state.h

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#ifndef POSITION_STATE_H
#define POSITION_STATE_H
#include <cstdint>
#include <algorithm>
#include <cmath>

// Caller holds the position mutex. Time is monotonic milliseconds; explicit
// arguments let tests cover connection/poll races without wall-clock sleeps.
class ConnectionPosition {
public:
    void allowZeroStart() { allowZero = true; }
    // Queue-only anchor: identity and PLAYING are already confirmed by SOAP.
    // No HTTP connection, PCM submission or 1.1-second stream lease is involved.
    void item(unsigned id, uint32_t ms, uint32_t rate, uint64_t now, uint64_t frozen = 0) {
        reset(id);
        anchored = hadAudio = heardPosition = zeroObserved = true;
        relative = ms; observed = started = now;
        reportTime = now; reportFrames = std::max(double(frozen), double(ms) * rate / 1000);
        lastAudible = uint64_t(reportFrames);
    }
    void itemPoll(uint32_t ms, uint64_t now) {
        if (!anchored || ms < relative) return;
        if (ms != relative) observed = now;
        relative = ms;
    }

    // An eligible current-stream probe is fresh evidence, unlike a leased
    // normal zero. Seed only the explicit rate-change reporting coordinate.
    void startZero(double time) {
        if (!allowZero || !anchored || heardPosition || pendingAudibleBase ||
            !std::isfinite(time) || time < started) return;
        conservativeStart = true;
        observed = uint64_t(time); heardPosition = zeroObserved = true;
    }
    bool needsStartEvidence() const { return allowZero && relative == 0; }
    bool audioStarted() const { return anchored && heardPosition && relative > 0 && !pendingAudibleBase; }
    void reset(unsigned id) {
        if (id != stream) allowZero = false;
        zeroObserved = lowerStart = conservativeStart = false;
        stream = id; request = 0; base = relative = lastFrames = 0;
        anchored = hadAudio = heardPosition = pendingAudibleBase = false;
        audibleBase = lastAudible = 0; reportTime = 0; reportFrames = 0; slewOffset = 0; reportingModel = false; ++generation;
    }
    void connection(unsigned id, uint64_t req, uint64_t now) {
        if (id != stream) reset(id);
        zeroObserved = lowerStart = conservativeStart = false;
        request = req; reportTime = 0; reportFrames = 0; slewOffset = 0; reportingModel = false;
        // Hold the last reported position until the first PCM offset is known.
        base = lastFrames;
        audibleBase = lastAudible;
        relative = 0; anchored = false; pendingAudibleBase = false; started = now; ++generation;
        if (!hadAudio) base = 0;
    }
    void pcm(unsigned id, uint64_t req, uint64_t firstFrame, uint64_t now, bool deferAudible = false) {
        if (id != stream || req != request || anchored) return;
        base = hadAudio ? firstFrame : 0;
        // S7#2 (27 Sep): the first GET closed after 1.25 s, before
        // any positive RelTime. Its queued PCM is not an audible offset.
        pendingAudibleBase = deferAudible && heardPosition;
        audibleBase = heardPosition ? (deferAudible ? lastAudible : base) : 0;
        relative = 0; anchored = hadAudio = true;
        started = now; ++generation;
    }
    uint64_t measurementBase() const { return base; }
    uint64_t token() const { return generation; }
    void poll(uint64_t token, uint32_t ms, uint64_t now) {
        if (token != generation || !anchored || (ms == 0 && (!allowZero || relative != 0 || !zeroObserved))) return;
        // Stop may report zero before the next GET; retain the last position.
        // A new connection/PCM anchor already clears relative to zero.
        // Position reads are cached for one second. Ignore that cache after
        // a handoff, and reject an old RelTime larger than this GET's lifetime.
        if (now - started < 1100 || ms > now - started + 1000) return;
        if (pendingAudibleBase) { audibleBase = base; pendingAudibleBase = false; }
        // Repeated leased values are the same observation, not a fresh bracket.
        if (!heardPosition || relative != ms) observed = now;
        if (allowZero && ms && !relative) lowerStart = conservativeStart = true;
        relative = ms;
        if (ms) allowZero = false; // Startup-only: reconnects retain the positive-read safeguard.
        if (!ms) zeroObserved = true;
        heardPosition = true;
    }
    uint64_t frames(uint32_t rate) {
        lastFrames = base + uint64_t(relative) * rate / 1000;
        return lastFrames;
    }
    unsigned streamId() const { return stream; }
    bool modelAudibleFrames(uint32_t rate, double now, double candidate, double rateScale, uint64_t &result, double relSecond = NAN, double observedMono = NAN) {
        if (!anchored || !heardPosition || pendingAudibleBase || now < observed ||
            !std::isfinite(now) || !std::isfinite(candidate) || !std::isfinite(rateScale) || rateScale <= 0) return false;
        // RelTime is floored at observation. Project its interval to now, rather
        // than force the model onto a leased, whole-second staircase.
        // Prefer the timestamp of the actual normal SOAP read, already recorded
        // by the probe seed path, when it corresponds to this leased value.
        const double sampleMs = std::isfinite(observedMono) && relSecond == double(relative) / 1000
            ? observedMono * 1000 : double(observed);
        if (sampleMs > now) return false;
        const double elapsed = (double(now) - sampleMs) * rate * rateScale / 1000;
        const double low = double(audibleBase) + double(relative) * rate / 1000 + elapsed;
        const double high = low + rate;
        const double bounded = std::max(low, std::min(high, candidate));
        if (bounded < 0 || bounded >= double(UINT64_MAX)) return false;
        frames(rate); // preserve the encoded handoff coordinate
        lastAudible = std::max(lastAudible, uint64_t(bounded));
        result = lastAudible;
        return true;
    }
    uint64_t audibleFrames(uint32_t rate) {
        frames(rate); // retain the encoded-stream coordinate for handoffs
        const uint64_t next = audibleBase + uint64_t(relative) * rate / 1000;
        if (next > lastAudible) lastAudible = next;
        return lastAudible;
    }
    // Smooth only the audible reporting coordinate; encoded handoffs stay exact.
    uint64_t smoothFrames(uint32_t rate, double now, bool model, double candidate,
                          double scale = 1, double relSecond = NAN, double observedMono = NAN) {
        if (!anchored || !heardPosition || (!relative && !zeroObserved) || pendingAudibleBase || !rate) return audibleFrames(rate);
        const double sample = std::isfinite(observedMono) && relSecond == double(relative) / 1000
            ? observedMono * 1000 : double(observed);
        double target = audibleBase + (double(relative) / 1000 + (relative ? .5 : 0) + std::max(0., now - sample) * scale / 1000) * rate;
        if (model) {
            const double low = target - .5 * rate;
            target = std::clamp(candidate, low, low + rate);
        }
        // A restarted stream begins at the conservative end of its observed
        // interval. Do not import a speculative zero from before TRANSITIONING.
        if (!reportTime && lowerStart) {
            reportTime = now;
            reportFrames = target - .5 * rate;
            lowerStart = false;
        }
        const double dt = reportTime ? std::max(0., now - reportTime) / 1000 : 0;
        const double projected = reportTime ? reportFrames + dt * rate * scale : target;
        const double difference = target - projected;
        // 15% corrects the observed 650 ms phase error in 4.34 seconds.
        // A qualified model correction above 750 ms is a discontinuity.
        // Unlocked observations constrain nominal projection below; they
        // cannot steer it towards a midpoint that follows the polling phase.
        const double correction = conservativeStart ? .05 : .15;
        double next = !reportTime || (model && std::abs(difference) > .75 * rate) ? target
            : projected + std::clamp(difference, -correction * dt * rate, correction * dt * rate);
        // Unlocked re-anchors use the latest quantisation endpoints and
        // cannot accumulate unbounded phase lag.
        // RelTime midpoint phase follows the polling phase. Chasing each new
        // midpoint runs at the polling rate (and slews a whole second when
        // that phase crosses a tick). Keep unity/prior projection whenever
        // it is still consistent with the latest quantisation interval.
        if (!model) next = std::clamp(projected, target - .5 * rate, target + .5 * rate);
        reportFrames = model ? std::max(double(lastAudible), std::max(0., next)) : std::max(0., next);
        lastAudible = uint64_t(reportFrames);
        reportTime = now; reportingModel = model;
        slewOffset = (double(lastAudible) - target) / rate;
        frames(rate);
        return lastAudible;
    }
    const char *phase(bool prior) const {
        return reportingModel ? (std::abs(slewOffset) > .00005 ? "slewing" : "locked")
                              : (prior ? "prior" : "acquiring");
    }
    double offset() const { return slewOffset; }
private:
    double reportTime = 0, reportFrames = 0, slewOffset = 0;
    bool lowerStart = false, conservativeStart = false;
    bool reportingModel = false, allowZero = false, zeroObserved = false;
    unsigned stream = 0;
    uint64_t request = 0, generation = 0, base = 0, started = 0, lastFrames = 0, observed = 0;
    uint32_t relative = 0;
    uint64_t audibleBase = 0, lastAudible = 0;
    bool anchored = false, hadAudio = false, heardPosition = false, pendingAudibleBase = false;
};
#endif