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:
2026-06-22 01:46:59 +02:00
parent c24bfdd64f
commit 90f40ae479
13 changed files with 607 additions and 111 deletions

View File

@@ -27,8 +27,47 @@ public:
// `debug_details` on, the per-stream table is shown.
void draw_ui(const HookStatusView& status, bool debug_details);
#ifdef COOP_TEST_HARNESS
// Test-harness hooks (debug builds only): drive the real audio code paths and read
// state back, incl. targeting a windowless process by pid (coop_tone has no window).
void dev_set_enabled(bool on)
{
enabled_ = on;
}
void dev_set_pid(DWORD pid)
{
dev_pid_ = pid;
}
void dev_request_op(unsigned slot, std::uint32_t kind, std::uint32_t rate, std::uint32_t ch,
std::uint32_t bits, std::uint32_t tag)
{
mirror_.request_op(slot, kind, rate, ch, bits, tag);
}
[[nodiscard]] bool dev_running() const
{
return mirror_.running();
}
[[nodiscard]] unsigned dev_rate() const
{
return mirror_.sample_rate();
}
[[nodiscard]] unsigned dev_channels() const
{
return mirror_.channels();
}
[[nodiscard]] std::string dev_source() const
{
return mirror_.source_name();
}
[[nodiscard]] std::string dev_reason() const
{
return mirror_.fallback_reason();
}
#endif
private:
HWND target_ = nullptr;
DWORD dev_pid_ = 0; // test harness only: force a (windowless) target pid; 0 in production
bool enabled_ = false;
AudioMirror mirror_;
@@ -41,6 +80,12 @@ private:
std::uint64_t rate_base_frames_[kMaxAudioStreams] = {};
double frames_per_s_[kMaxAudioStreams] = {};
double rate_base_time_ = 0.0;
// Operator format-override editor (debug details). Applies to the primary stream.
int ov_rate_ = 48000;
int ov_channels_ = 2;
int ov_bits_idx_ = 1; // 0 = 16-bit, 1 = 32-bit
int ov_fmt_idx_ = 1; // 0 = PCM, 1 = float
};
} // namespace coop