Audio: robust rate estimation + color-coded log levels

Harden the guessed-stream sample-rate measurement that produced wrong rates
(e.g. 44100 read as ~46205). New rate_estimator.hpp measures over longer
~0.5 s windows, rejects any window that doesn't snap to a standard rate
(standard rates are >8% apart, so a quantization/burst error big enough to
miss one lands in no-man's-land, never on a wrong neighbour), and requires
consensus across windows before committing. If consensus isn't reached it
commits a low-confidence estimate (new AudioFormat_LowConfidence, shown red)
rather than spinning or publishing garbage. Pure logic, unit-tested with
adversarial cadences (rate_estimator_test) incl. the real 46205 bug value.

Add log severity levels: hook logw/loge set LogRecord.level; the host Log
window colors warnings amber and errors red. The low-confidence rate logs a
warning. Protocol -> v15 (new format states); also reserves AudioFormat_Override.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 01:11:06 +02:00
parent f72da74f78
commit cb6749b511
11 changed files with 462 additions and 102 deletions

View File

@@ -27,11 +27,20 @@ inline constexpr wchar_t kLogRingPrefix[] = L"Local\\coop_log_";
inline constexpr std::uint32_t kLogMsgLen = 192; // chars per line (incl. NUL)
inline constexpr std::uint32_t kLogCapacity = 1024; // ring records
// Severity of a log line; drives the host Log window's colour. Stored in
// LogRecord::level. Info is 0 so existing/zero-filled records read as Info.
enum LogLevel : std::uint32_t
{
LogLevel_Info = 0,
LogLevel_Warn = 1,
LogLevel_Error = 2,
};
struct LogRecord
{
std::atomic<std::uint64_t> seq; // 0 = empty; else (global index + 1) once written
std::uint32_t pid;
std::uint32_t level; // reserved (0)
std::uint32_t level; // LogLevel
std::uint64_t millis; // producer timestamp (GetTickCount64)
char text[kLogMsgLen];
};
@@ -77,13 +86,14 @@ inline bool log_ring_valid(const LogRing& r)
r.msg_len == kLogMsgLen;
}
// Producer (hook): append a line. Multi-producer safe.
inline void log_ring_push(LogRing& r, std::uint32_t pid, std::uint64_t millis, const char* text)
// Producer (hook): append a line at severity `level` (LogLevel). Multi-producer safe.
inline void log_ring_push(LogRing& r, std::uint32_t pid, std::uint32_t level, std::uint64_t millis,
const char* text)
{
const std::uint64_t idx = r.write_index.fetch_add(1, std::memory_order_acq_rel);
LogRecord& rec = log_ring_records(&r)[idx % r.capacity];
rec.pid = pid;
rec.level = 0;
rec.level = level;
rec.millis = millis;
std::strncpy(rec.text, text, kLogMsgLen - 1);
rec.text[kLogMsgLen - 1] = '\0';

View File

@@ -12,7 +12,7 @@ namespace coop
// Bump whenever the layout of SharedBlock or CoopPadState changes. The hook
// refuses to attach to a host with a mismatched version.
inline constexpr std::uint32_t kProtocolVersion = 14;
inline constexpr std::uint32_t kProtocolVersion = 15;
// 'COOP' little-endian, used to sanity-check the mapping before trusting it.
inline constexpr std::uint32_t kProtocolMagic = 0x504F4F43u;
@@ -60,7 +60,9 @@ enum AudioFormatState : std::uint32_t
AudioFormat_Unknown = 0, // no format determined yet
AudioFormat_Exact = 1, // taken from the game's own IAudioClient::Initialize
AudioFormat_Measuring = 2, // guessed (device mix format); true sample rate being measured
AudioFormat_Measured = 3, // guessed rate measured; channels/bits assumed from the device
AudioFormat_Measured = 3, // guessed rate measured (consensus on a standard rate); ch/bits assumed
AudioFormat_LowConfidence = 4, // rate never reached consensus; best estimate published -- verify/override
AudioFormat_Override = 5, // operator set this format manually (see the per-stream op channel)
};
struct AudioStreamInfo