Audio: operator re-measure + format override (host<->hook op channel)
Add a per-stream op channel in AudioRingHeader (op_seq + op_* fields, version 2): the host posts re-measure / override commands, the hook applies them and re-publishes (bumping format_generation), and the host rebuilds its render client live on the change. The Audio panel (under Debug details) gains a "Re-measure rate" button and a rate/channels/bit-depth/format override -- for when detection is wrong or the channels/bit-depth were unrecoverable. Also add a debug-only IPC test harness (-DCOOP_TEST_HARNESS, off by default, absent from the shipped host): a file-based command channel that drives the host's real UI code paths (inject / audio / re-measure / override / screenshot / status) for scripted validation, instead of unreliable synthetic mouse input. Used to validate live: late-attach to coop_tone@44100 -> measured 44100, promoted to hooked; override -> 2ch state, re-measure -> reconverge. Trim the README roadmap to what's left; document the harness + rate_estimator_test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,8 +11,14 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <mmreg.h>
|
||||
#include <timeapi.h>
|
||||
|
||||
#ifdef COOP_TEST_HARNESS
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#endif
|
||||
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
|
||||
#include "imgui.h"
|
||||
@@ -27,6 +33,7 @@
|
||||
#include "injection_panel.hpp"
|
||||
#include "input/input_worker.hpp"
|
||||
#include "log_panel.hpp"
|
||||
#include "test_harness.hpp"
|
||||
#include "ui/app_chrome.hpp"
|
||||
|
||||
namespace
|
||||
@@ -82,6 +89,102 @@ void draw_screenshot_toast(double seconds_since, const std::string& name)
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
#ifdef COOP_TEST_HARNESS
|
||||
std::wstring widen(const std::string& s)
|
||||
{
|
||||
if (s.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
const int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(s.size()), nullptr, 0);
|
||||
std::wstring w(static_cast<std::size_t>(n), L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(s.size()), w.data(), n);
|
||||
return w;
|
||||
}
|
||||
|
||||
// Run a test-harness command on the main thread, hitting the same code the UI buttons do.
|
||||
// Returns a one-line response the driver reads back.
|
||||
std::string apply_test_command(const std::string& cmd, coop::UiState& ui, coop::InjectionPanel& injection,
|
||||
coop::AudioPanel& audio, coop::D3D11Window& window)
|
||||
{
|
||||
std::vector<std::string> tok;
|
||||
{
|
||||
std::istringstream is(cmd);
|
||||
std::string t;
|
||||
while (is >> t)
|
||||
{
|
||||
tok.push_back(t);
|
||||
}
|
||||
}
|
||||
if (tok.empty())
|
||||
{
|
||||
return "empty";
|
||||
}
|
||||
const std::string& v = tok[0];
|
||||
auto arg = [&](std::size_t i) -> std::string { return i < tok.size() ? tok[i] : std::string(); };
|
||||
auto num = [&](std::size_t i) -> unsigned { return static_cast<unsigned>(std::strtoul(arg(i).c_str(), nullptr, 10)); };
|
||||
|
||||
if (v == "inject")
|
||||
{
|
||||
const unsigned long pid = injection.dev_inject_by_name(widen(arg(1)));
|
||||
return pid != 0 ? ("ok pid " + std::to_string(pid)) : "fail no-process-or-inject-failed";
|
||||
}
|
||||
if (v == "audio")
|
||||
{
|
||||
const bool on = arg(1) == "on";
|
||||
if (on)
|
||||
{
|
||||
audio.dev_set_pid(injection.target_pid());
|
||||
}
|
||||
audio.dev_set_enabled(on);
|
||||
return "ok";
|
||||
}
|
||||
if (v == "debug")
|
||||
{
|
||||
ui.debug_details = (arg(1) == "on");
|
||||
return "ok";
|
||||
}
|
||||
if (v == "remeasure")
|
||||
{
|
||||
audio.dev_request_op(num(1), coop::AudioRingOp_Remeasure, 0, 0, 0, 0);
|
||||
return "ok";
|
||||
}
|
||||
if (v == "override")
|
||||
{
|
||||
const std::uint32_t tag = arg(5) == "float" ? static_cast<std::uint32_t>(WAVE_FORMAT_IEEE_FLOAT)
|
||||
: static_cast<std::uint32_t>(WAVE_FORMAT_PCM);
|
||||
audio.dev_request_op(num(1), coop::AudioRingOp_Override, num(2), num(3), num(4), tag);
|
||||
return "ok";
|
||||
}
|
||||
if (v == "screenshot")
|
||||
{
|
||||
const std::wstring p = screenshot_path();
|
||||
window.request_screenshot(p);
|
||||
return "ok";
|
||||
}
|
||||
if (v == "quit")
|
||||
{
|
||||
ui.request_quit = true;
|
||||
return "ok";
|
||||
}
|
||||
if (v == "status")
|
||||
{
|
||||
const coop::HookStatusView st = injection.hook_status();
|
||||
const std::string reason = audio.dev_reason();
|
||||
char buf[512];
|
||||
std::snprintf(buf, sizeof(buf),
|
||||
"audio_running=%d source=%s rate=%u ch=%u state=%u streams=%u inj_pid=%lu inj_state=%d "
|
||||
"reason=%s",
|
||||
audio.dev_running() ? 1 : 0, audio.dev_source().c_str(), audio.dev_rate(),
|
||||
audio.dev_channels(), st.audio_streams[0].format_state, st.audio_streams_seen,
|
||||
injection.target_pid(), static_cast<int>(injection.target_state()),
|
||||
reason.empty() ? "-" : reason.c_str());
|
||||
return buf;
|
||||
}
|
||||
return "unknown-command";
|
||||
}
|
||||
#endif // COOP_TEST_HARNESS
|
||||
|
||||
#ifdef COOP_WITH_STEAM
|
||||
// Absolute path to the bundled Steam Input action manifest (next to the exe).
|
||||
std::string steam_manifest_path()
|
||||
@@ -208,6 +311,9 @@ int run()
|
||||
coop::register_ui_settings(ui);
|
||||
coop::FrameStats stats;
|
||||
|
||||
coop::TestHarness harness; // debug builds only; a no-op shim otherwise
|
||||
harness.init();
|
||||
|
||||
// Frame-sync: the hook generation we last presented (so we wait for the next one).
|
||||
std::uint32_t last_synced_gen = 0;
|
||||
|
||||
@@ -249,6 +355,13 @@ int run()
|
||||
stats.tick(ImGui::GetIO().DeltaTime * 1000.0f);
|
||||
log.pull(injection); // drain hook log lines even while the Log window is hidden
|
||||
|
||||
#ifdef COOP_TEST_HARNESS
|
||||
if (std::string tcmd = harness.poll_command(); !tcmd.empty())
|
||||
{
|
||||
harness.write_response(apply_test_command(tcmd, ui, injection, audio, window));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_F1, false))
|
||||
{
|
||||
show_overlay = !show_overlay;
|
||||
|
||||
Reference in New Issue
Block a user