Audio: per-game persisted format overrides + auto-learn

Persist audio overrides keyed by game image name (coop_audio_overrides.ini
next to the exe) so a known-bad game is auto-corrected on its next launch:
on attach to a guessed stream the host applies any saved override, and a
manual Override now saves too. A format the hook catches exactly at
IAudioClient::Initialize is auto-saved as that game's override (ground truth);
if it overwrites a differing stored value, a warning is logged. Host-originated
log lines now reach the Log window via IpcServer::host_log (color-coded).

Validated live (harness): a pre-seeded override for coop_tone is auto-applied
over the guess (state -> manual override, 48000/2/16). audio_overrides_test
covers persist/reload/case-insensitive lookup/differing-overwrite. Trim README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 01:59:11 +02:00
parent fe6f8462ab
commit 32028cb286
12 changed files with 483 additions and 12 deletions

View File

@@ -4,10 +4,13 @@
#pragma once
#include <cstdint>
#include <functional>
#include <string>
#include <windows.h>
#include "audio/audio_loopback.hpp"
#include "audio/audio_overrides.hpp"
#include "ipc/ipc_server.hpp"
namespace coop
@@ -16,6 +19,11 @@ namespace coop
class AudioPanel
{
public:
AudioPanel()
{
overrides_.load();
}
// The window whose process audio to mirror (0 if none); typically the
// injected game's HWND.
void set_target(HWND target)
@@ -23,6 +31,13 @@ public:
target_ = target;
}
// Wire a sink for host-side log lines (override-overwrite warnings etc.). main
// connects this to the injection panel's Log-window channel.
void set_logger(std::function<void(std::uint32_t, const char*)> logger)
{
logger_ = std::move(logger);
}
// `status` is the hook's back-channel, for the render-stream view. With
// `debug_details` on, the per-stream table is shown.
void draw_ui(const HookStatusView& status, bool debug_details);
@@ -66,11 +81,24 @@ public:
#endif
private:
// Auto-apply a stored override over a guessed stream, and auto-save a format the hook
// caught exactly at Initialize (warning if it overwrites a differing stored value).
void manage_overrides(const HookStatusView& status, DWORD pid);
static std::wstring image_name_from_pid(DWORD pid);
HWND target_ = nullptr;
DWORD dev_pid_ = 0; // test harness only: force a (windowless) target pid; 0 in production
bool enabled_ = false;
AudioMirror mirror_;
// Per-game persisted overrides + per-target session bookkeeping.
AudioOverrideStore overrides_;
std::function<void(std::uint32_t, const char*)> logger_;
DWORD applied_pid_ = 0; // pid the session flags below were reset for
std::wstring target_image_; // current target's image path (store keys on the basename)
bool override_applied_ = false; // applied (or confirmed none) the stored override this session
bool exact_saved_ = false; // saved the exact Initialize format this session
// Per-stream activity tracking for the "live" column. Audio buffers release in
// bursts, so most UI frames see no change; comparing to just the previous frame
// flickers. Instead we remember when each stream last advanced and debounce the