Ctrl K
ComunidadGuías…

volume_control.h

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

Archivo original ↗Preguntar a KiBot
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
#pragma once
#include <algorithm>
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
namespace bridge {
struct VolumeSettings {
    bool speaker = false;
    int maximum = 100;
    std::string source = "default";
};
VolumeSettings volumeSettings(const std::string &room, const char *option, const char *path);
// One serialized controller owns both directions. Values are LMS integer mixer
// percentages, never SlimProto gains and never applied to PCM.
class VolumeSync {
    struct Field { std::optional<int> lms, speaker, pending, attempted, expected; uint64_t changed = 0, confirmUntil = 0; };
    Field volume, mute;
    uint64_t lastWrite = 0;
    int maximum;
    bool capped = false;
public:
    explicit VolumeSync(int maximum) : maximum(maximum) {}
    void reset() { volume = {}; mute = {}; }
    void observeLms(int v, int m, uint64_t now) {
        auto observe = [&](Field &f, int value) {
            if (f.lms == value) return;
            f.lms = value; f.pending = value; f.attempted.reset(); f.changed = now;
        };
        observe(volume, v); observe(mute, m);
    }
    void observeSpeaker(int v, int m, uint64_t now, const std::function<void(const std::string &)> &send) {
        auto observe = [&](Field &f, int value, const char *command) {
            bool confirmationExpired = false;
            if (f.expected) {
                const bool confirmed = value == *f.expected;
                if (confirmed || now < f.confirmUntil) {
                    // Group changes can arrive in intermediate steps. Confirm
                    // our target before classifying later changes as app input.
                    f.speaker = value;
                    if (confirmed) {
                        if (f.pending && (&f == &volume ? std::min(*f.pending, maximum) : *f.pending) == value)
                            f.pending.reset();
                        f.expected.reset(); f.attempted.reset();
                    }
                    return;
                }
                f.expected.reset(); confirmationExpired = true;
            }
            if (confirmationExpired || (f.speaker && f.speaker != value)) {
                // A change before our write is an app action. An uncertain
                // write reaching its target is confirmation, not an app echo.
                const bool confirmation = f.attempted == value;
                f.pending.reset(); f.attempted.reset();
                if (!confirmation && f.lms != value) {
                    f.lms = value;
                    send(std::string(command) + std::to_string(value));
                }
            }
            f.speaker = value;
        };
        observe(volume, v, "mixer volume "); observe(mute, m, "mixer muting ");
    }
    void write(uint64_t now, const std::function<bool(bool, int)> &send,
               const std::function<void()> &capLog, const std::function<bool()> &prepare = {}) {
        auto apply = [&](Field &f, bool muting) {
            if (!f.pending || !f.speaker || now < f.changed + 200) return;
            const int desired = muting ? *f.pending : std::min(*f.pending, maximum);
            if (desired != *f.pending && !capped) { capped = true; capLog(); }
            if (f.speaker == desired) {
                if (f.expected && f.expected != desired) return;
                f.pending.reset(); f.attempted.reset(); return;
            }
            if (lastWrite && now < lastWrite + 200) return;
            lastWrite = now;
            if (!muting && prepare && prepare()) return;
            // An uncertain write is verified by the next authoritative read.
            f.attempted = desired; f.expected = desired; f.confirmUntil = now + 2000;
            if (send(muting, desired)) { f.speaker = desired; f.pending.reset(); f.attempted.reset(); }
        };
        apply(volume, false); apply(mute, true);
    }
};
// Immutable startup settings, also used by the existing room-status publisher.
inline VolumeSettings &roomVolumeSettings() { static VolumeSettings value; return value; }
} // namespace bridge