Files
CoopAllTheThings/host/src/audio/audio_overrides.hpp
BlackMark 32028cb286 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>
2026-06-22 01:59:11 +02:00

69 lines
2.1 KiB
C++

// Per-game audio-format overrides, persisted next to the exe so a game whose hooked
// format was wrong/unrecoverable is auto-corrected on its next launch. Keyed by the
// game's image name (case-insensitive). Two writers: the operator's manual Override, and
// auto-learning a format the hook caught exactly at IAudioClient::Initialize (ground
// truth). A human-readable one-line-per-game text file:
//
// # CoopAllTheThings per-game audio overrides
// brotato.exe = 44100 2 32 float
// snb.exe = 48000 2 16 pcm
#pragma once
#include <cstdint>
#include <map>
#include <string>
namespace coop
{
struct AudioFormatOverride
{
std::uint32_t rate = 0;
std::uint32_t channels = 0;
std::uint32_t bits = 0;
std::uint32_t format_tag = 0; // WAVE_FORMAT_PCM (1) / WAVE_FORMAT_IEEE_FLOAT (3)
[[nodiscard]] bool valid() const
{
return rate != 0 && channels != 0 && bits != 0;
}
bool operator==(const AudioFormatOverride& o) const
{
return rate == o.rate && channels == o.channels && bits == o.bits && format_tag == o.format_tag;
}
bool operator!=(const AudioFormatOverride& o) const
{
return !(*this == o);
}
};
class AudioOverrideStore
{
public:
// `path` empty -> default (exe_dir/coop_audio_overrides.ini). Does not load yet.
explicit AudioOverrideStore(std::wstring path = {});
void load(); // (re)read the file; missing file is fine (empty store)
// Look up an override by image name (case-insensitive, basename). False if none.
[[nodiscard]] bool find(const std::wstring& image_name, AudioFormatOverride& out) const;
// Store image_name -> fmt and persist immediately. Sets *differed = true when an
// existing entry for that game differed from `fmt` (caller warns the operator).
void set(const std::wstring& image_name, const AudioFormatOverride& fmt, bool* differed = nullptr);
[[nodiscard]] const std::wstring& path() const
{
return path_;
}
private:
static std::wstring key_of(const std::wstring& image_name); // lowercased basename
void save() const;
std::wstring path_;
std::map<std::wstring, AudioFormatOverride> map_;
};
} // namespace coop