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:
@@ -36,6 +36,15 @@ add_executable(rate_estimator_test rate_estimator_test.cpp)
|
||||
target_include_directories(rate_estimator_test PRIVATE ${CMAKE_SOURCE_DIR}/hook/src)
|
||||
add_test(NAME rate_estimator_test COMMAND rate_estimator_test)
|
||||
|
||||
# Unit test for the per-game audio override store (persist/reload, case-insensitive
|
||||
# lookup, differing-overwrite detection). Reuses the shipping source. No device.
|
||||
add_executable(audio_overrides_test
|
||||
audio_overrides_test.cpp
|
||||
${CMAKE_SOURCE_DIR}/host/src/audio/audio_overrides.cpp)
|
||||
target_include_directories(audio_overrides_test PRIVATE ${CMAKE_SOURCE_DIR}/host/src)
|
||||
target_link_libraries(audio_overrides_test PRIVATE coop_common)
|
||||
add_test(NAME audio_overrides_test COMMAND audio_overrides_test)
|
||||
|
||||
# Unit test for the host->game mouse coordinate mapping (letterbox inverse +
|
||||
# decorated-window client offset). Header-only, no device.
|
||||
add_executable(mkb_map_test mkb_map_test.cpp)
|
||||
@@ -163,6 +172,7 @@ coop_output_subdir(tests
|
||||
mkb_map_test
|
||||
audio_mix_test
|
||||
rate_estimator_test
|
||||
audio_overrides_test
|
||||
audio_loopback_test
|
||||
audio_hook_test
|
||||
srgb_format_test
|
||||
|
||||
88
tests/audio_overrides_test.cpp
Normal file
88
tests/audio_overrides_test.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
// Unit test for the per-game audio override store (save/load round-trip, case-insensitive
|
||||
// lookup by image name, and the differing-overwrite detection that drives the warning).
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <mmreg.h>
|
||||
|
||||
#include "audio/audio_overrides.hpp"
|
||||
|
||||
using namespace coop;
|
||||
|
||||
namespace
|
||||
{
|
||||
int g_failures = 0;
|
||||
void check(bool ok, const char* what)
|
||||
{
|
||||
if (!ok)
|
||||
{
|
||||
std::printf("FAIL: %s\n", what);
|
||||
++g_failures;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::printf(" ok: %s\n", what);
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring temp_path()
|
||||
{
|
||||
wchar_t dir[MAX_PATH] = {};
|
||||
GetTempPathW(MAX_PATH, dir);
|
||||
return std::wstring(dir) + L"coop_overrides_test_" + std::to_wstring(GetCurrentProcessId()) + L".ini";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
const std::wstring path = temp_path();
|
||||
DeleteFileW(path.c_str());
|
||||
|
||||
const AudioFormatOverride brotato{44100, 2, 32, WAVE_FORMAT_IEEE_FLOAT};
|
||||
const AudioFormatOverride snb{48000, 2, 16, WAVE_FORMAT_PCM};
|
||||
|
||||
{
|
||||
AudioOverrideStore store(path);
|
||||
store.load(); // missing file -> empty
|
||||
AudioFormatOverride got;
|
||||
check(!store.find(L"brotato.exe", got), "empty store: no entry");
|
||||
|
||||
bool differed = true;
|
||||
store.set(L"C:\\games\\Brotato.exe", brotato, &differed); // full path -> basename key
|
||||
check(!differed, "first set: not a differing overwrite");
|
||||
store.set(L"snb.exe", snb, &differed);
|
||||
check(!differed, "new game set: not a differing overwrite");
|
||||
|
||||
// Same value again -> not differing.
|
||||
store.set(L"brotato.exe", brotato, &differed);
|
||||
check(!differed, "identical re-set: not differing");
|
||||
|
||||
// Different value -> differing (drives the warning).
|
||||
const AudioFormatOverride brotato2{48000, 2, 32, WAVE_FORMAT_IEEE_FLOAT};
|
||||
store.set(L"brotato.exe", brotato2, &differed);
|
||||
check(differed, "changed value: flagged as differing overwrite");
|
||||
}
|
||||
|
||||
// Reload from disk in a fresh store: persistence + case-insensitive basename lookup.
|
||||
{
|
||||
AudioOverrideStore store(path);
|
||||
store.load();
|
||||
AudioFormatOverride got;
|
||||
check(store.find(L"BROTATO.EXE", got), "reload: found case-insensitively");
|
||||
check(got == AudioFormatOverride{48000, 2, 32, WAVE_FORMAT_IEEE_FLOAT}, "reload: latest value persisted");
|
||||
check(store.find(L"D:\\steam\\snb.exe", got) && got == snb, "reload: found by full path basename");
|
||||
check(!store.find(L"unknown.exe", got), "reload: unknown game absent");
|
||||
}
|
||||
|
||||
DeleteFileW(path.c_str());
|
||||
if (g_failures == 0)
|
||||
{
|
||||
std::printf("PASS audio_overrides_test\n");
|
||||
return 0;
|
||||
}
|
||||
std::printf("FAILED audio_overrides_test (%d)\n", g_failures);
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user