Files
CoopAllTheThings/host/src/audio_panel.hpp
BlackMark 30eccf749d Apply clang-format across the whole tree
Run clang-format (the repo's .clang-format: LLVM base, 120 cols, tabs,
Allman functions) over every source file so the tree is formatter-clean.
Whitespace only -- no behavior change; full x64 + x86 suites pass.

Also set SortIncludes: false in .clang-format. Windows include order is
load-bearing (windows.h must precede tlhelp32.h / mmreg.h / xinput.h /
dinput.h; winsock2.h must precede windows.h), and the default
alphabetical sort reorders tlhelp32.h ahead of windows.h -- a build
break. Leaving order alone keeps the manual, correct grouping.
2026-07-12 11:52:53 +02:00

95 lines
4.2 KiB
C++

// Owns the audio-mirror pipeline (render-hook ring or WASAPI process loopback +
// re-render) and its UI. The target process is derived from the injected game's
// window; the render-stream debug view reads the hook's diagnostics back-channel.
#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 {
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) { 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);
// UI-fit check (headless test) only: render the richest content path -- as if a
// loopback mirror were running with long status/reason strings -- without a live
// AudioMirror, so the fit test can measure the panel's worst-case size. Never set in
// the shipping host (the render path is identical, just fed synthetic values).
void dev_set_demo(bool on) { demo_ = on; }
#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:
// 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;
bool demo_ = false; // UI-fit test only: render the richest content with synthetic values
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
// live/idle indicator over a short window, plus a ~2 Hz frames/s estimate.
std::uint64_t prev_frames_[kMaxAudioStreams] = {};
double last_active_[kMaxAudioStreams] = {}; // ImGui time a stream last advanced
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