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.
This commit is contained in:
@@ -42,23 +42,21 @@
|
||||
#include "coop/wav.hpp"
|
||||
#include "tone_source.hpp" // in-process sine renderer (shared with coop_tone), for --selfcheck
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace {
|
||||
|
||||
struct Options
|
||||
{
|
||||
unsigned long pid = 0; // attach to this pid instead of spawning coop_tone
|
||||
unsigned long listen = 0; // --listen: passively loopback-capture this pid's output (e.g. coop_host)
|
||||
double freq = 1000.0; // the tone frequency (for pitch analysis)
|
||||
unsigned rate = 44100; // tone render rate (the Brotato/Godot non-device case by default)
|
||||
struct Options {
|
||||
unsigned long pid = 0; // attach to this pid instead of spawning coop_tone
|
||||
unsigned long listen = 0; // --listen: passively loopback-capture this pid's output (e.g. coop_host)
|
||||
double freq = 1000.0; // the tone frequency (for pitch analysis)
|
||||
unsigned rate = 44100; // tone render rate (the Brotato/Godot non-device case by default)
|
||||
unsigned channels = 2;
|
||||
unsigned bits = 32; // 32 = float, 16 = pcm
|
||||
int seconds = 6; // capture duration
|
||||
bool render = false; // --render: measure the host RENDER path (run_hooked), not just capture
|
||||
bool baseline = false; // --baseline: loopback-capture the tone directly (no hook/mirror) as a floor
|
||||
bool selfcheck = false; // --selfcheck: render a clean tone in-process + self-capture (control for self-capture)
|
||||
std::wstring wav_in; // analyze this .wav instead of capturing
|
||||
std::wstring wav_out; // where to dump the captured audio (default next to the exe)
|
||||
unsigned bits = 32; // 32 = float, 16 = pcm
|
||||
int seconds = 6; // capture duration
|
||||
bool render = false; // --render: measure the host RENDER path (run_hooked), not just capture
|
||||
bool baseline = false; // --baseline: loopback-capture the tone directly (no hook/mirror) as a floor
|
||||
bool selfcheck = false; // --selfcheck: render a clean tone in-process + self-capture (control for self-capture)
|
||||
std::wstring wav_in; // analyze this .wav instead of capturing
|
||||
std::wstring wav_out; // where to dump the captured audio (default next to the exe)
|
||||
};
|
||||
|
||||
std::wstring sibling(const std::wstring& path, const wchar_t* name)
|
||||
@@ -71,20 +69,17 @@ std::wstring sibling(const std::wstring& path, const wchar_t* name)
|
||||
std::wstring find_coop_tone()
|
||||
{
|
||||
const std::wstring here = coop::exe_directory() + L"coop_tone.exe";
|
||||
if (GetFileAttributesW(here.c_str()) != INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
if (GetFileAttributesW(here.c_str()) != INVALID_FILE_ATTRIBUTES) {
|
||||
return here;
|
||||
}
|
||||
std::wstring dir = coop::exe_directory();
|
||||
if (!dir.empty())
|
||||
{
|
||||
if (!dir.empty()) {
|
||||
dir.pop_back();
|
||||
}
|
||||
const std::size_t slash = dir.find_last_of(L"\\/");
|
||||
const std::wstring root = (slash == std::wstring::npos) ? std::wstring() : dir.substr(0, slash + 1);
|
||||
const std::wstring in_tests = root + L"tests\\coop_tone.exe";
|
||||
if (GetFileAttributesW(in_tests.c_str()) != INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
if (GetFileAttributesW(in_tests.c_str()) != INVALID_FILE_ATTRIBUTES) {
|
||||
return in_tests;
|
||||
}
|
||||
return here;
|
||||
@@ -96,9 +91,8 @@ bool inject_via_helper(unsigned long pid, const std::wstring& dll_path)
|
||||
{
|
||||
const std::wstring helper = sibling(dll_path, L"coop_inject_x86.exe");
|
||||
const std::wstring x86_dll = sibling(dll_path, L"coop_hook_x86.dll");
|
||||
if (GetFileAttributesW(helper.c_str()) == INVALID_FILE_ATTRIBUTES ||
|
||||
GetFileAttributesW(x86_dll.c_str()) == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
if (GetFileAttributesW(helper.c_str()) == INVALID_FILE_ATTRIBUTES
|
||||
|| GetFileAttributesW(x86_dll.c_str()) == INVALID_FILE_ATTRIBUTES) {
|
||||
std::printf("ERROR: x86 helper/dll missing next to the tool.\n");
|
||||
return false;
|
||||
}
|
||||
@@ -106,8 +100,7 @@ bool inject_via_helper(unsigned long pid, const std::wstring& dll_path)
|
||||
STARTUPINFOW si{};
|
||||
si.cb = sizeof(si);
|
||||
PROCESS_INFORMATION pi{};
|
||||
if (!CreateProcessW(helper.c_str(), cmd.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi))
|
||||
{
|
||||
if (!CreateProcessW(helper.c_str(), cmd.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) {
|
||||
std::printf("ERROR: CreateProcess(coop_inject_x86) failed (%lu).\n", GetLastError());
|
||||
return false;
|
||||
}
|
||||
@@ -121,35 +114,30 @@ bool inject_via_helper(unsigned long pid, const std::wstring& dll_path)
|
||||
|
||||
bool inject(unsigned long pid, const std::wstring& dll_path)
|
||||
{
|
||||
if (GetFileAttributesW(dll_path.c_str()) == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
if (GetFileAttributesW(dll_path.c_str()) == INVALID_FILE_ATTRIBUTES) {
|
||||
std::printf("ERROR: coop_hook.dll not found next to the tool.\n");
|
||||
return false;
|
||||
}
|
||||
const DWORD access = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION |
|
||||
PROCESS_VM_WRITE | PROCESS_VM_READ;
|
||||
const DWORD access =
|
||||
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ;
|
||||
HANDLE process = OpenProcess(access, FALSE, pid);
|
||||
if (process == nullptr)
|
||||
{
|
||||
if (process == nullptr) {
|
||||
std::printf("ERROR: OpenProcess(%lu) failed (%lu). Run as administrator?\n", pid, GetLastError());
|
||||
return false;
|
||||
}
|
||||
USHORT proc_machine = IMAGE_FILE_MACHINE_UNKNOWN, native_machine = IMAGE_FILE_MACHINE_UNKNOWN;
|
||||
if (IsWow64Process2(process, &proc_machine, &native_machine) && proc_machine != IMAGE_FILE_MACHINE_UNKNOWN)
|
||||
{
|
||||
if (IsWow64Process2(process, &proc_machine, &native_machine) && proc_machine != IMAGE_FILE_MACHINE_UNKNOWN) {
|
||||
CloseHandle(process);
|
||||
return inject_via_helper(pid, dll_path);
|
||||
}
|
||||
const SIZE_T bytes = (dll_path.size() + 1) * sizeof(wchar_t);
|
||||
void* remote = VirtualAllocEx(process, nullptr, bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
bool ok = false;
|
||||
if (remote != nullptr && WriteProcessMemory(process, remote, dll_path.c_str(), bytes, nullptr))
|
||||
{
|
||||
auto load_library = reinterpret_cast<LPTHREAD_START_ROUTINE>(
|
||||
GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW"));
|
||||
if (remote != nullptr && WriteProcessMemory(process, remote, dll_path.c_str(), bytes, nullptr)) {
|
||||
auto load_library =
|
||||
reinterpret_cast<LPTHREAD_START_ROUTINE>(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW"));
|
||||
HANDLE thread = CreateRemoteThread(process, nullptr, 0, load_library, remote, 0, nullptr);
|
||||
if (thread != nullptr)
|
||||
{
|
||||
if (thread != nullptr) {
|
||||
WaitForSingleObject(thread, INFINITE);
|
||||
DWORD exit_code = 0;
|
||||
GetExitCodeThread(thread, &exit_code);
|
||||
@@ -157,8 +145,7 @@ bool inject(unsigned long pid, const std::wstring& dll_path)
|
||||
ok = (exit_code != 0);
|
||||
}
|
||||
}
|
||||
if (remote != nullptr)
|
||||
{
|
||||
if (remote != nullptr) {
|
||||
VirtualFreeEx(process, remote, 0, MEM_RELEASE);
|
||||
}
|
||||
CloseHandle(process);
|
||||
@@ -168,13 +155,11 @@ bool inject(unsigned long pid, const std::wstring& dll_path)
|
||||
void enable_hook_trace()
|
||||
{
|
||||
wchar_t dir[MAX_PATH] = {};
|
||||
if (GetTempPathW(MAX_PATH, dir) != 0)
|
||||
{
|
||||
if (GetTempPathW(MAX_PATH, dir) != 0) {
|
||||
const std::wstring sentinel = std::wstring(dir) + L"coop_hook.log.on";
|
||||
HANDLE h = CreateFileW(sentinel.c_str(), GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (h != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (h != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(h);
|
||||
}
|
||||
}
|
||||
@@ -186,24 +171,22 @@ void enable_hook_trace()
|
||||
HANDLE spawn_tone(const Options& o, unsigned long& tone_pid)
|
||||
{
|
||||
const std::wstring exe = find_coop_tone();
|
||||
if (GetFileAttributesW(exe.c_str()) == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
if (GetFileAttributesW(exe.c_str()) == INVALID_FILE_ATTRIBUTES) {
|
||||
std::printf("ERROR: coop_tone.exe not found (looked next to the tool and in ../tests/).\n");
|
||||
return nullptr;
|
||||
}
|
||||
HANDLE rd = nullptr, wr = nullptr;
|
||||
SECURITY_ATTRIBUTES sa{sizeof(sa), nullptr, TRUE};
|
||||
if (!CreatePipe(&rd, &wr, &sa, 0))
|
||||
{
|
||||
if (!CreatePipe(&rd, &wr, &sa, 0)) {
|
||||
return nullptr;
|
||||
}
|
||||
SetHandleInformation(rd, HANDLE_FLAG_INHERIT, 0);
|
||||
|
||||
// coop_tone [seconds] [freq] [rate] [channels] [bits] [float|pcm]
|
||||
const wchar_t* kind = (o.bits == 32) ? L"float" : L"pcm";
|
||||
std::wstring cmd = L"\"" + exe + L"\" " + std::to_wstring(o.seconds + 4) + L" " +
|
||||
std::to_wstring(static_cast<long>(o.freq)) + L" " + std::to_wstring(o.rate) + L" " +
|
||||
std::to_wstring(o.channels) + L" " + std::to_wstring(o.bits) + L" " + kind;
|
||||
std::wstring cmd = L"\"" + exe + L"\" " + std::to_wstring(o.seconds + 4) + L" "
|
||||
+ std::to_wstring(static_cast<long>(o.freq)) + L" " + std::to_wstring(o.rate) + L" "
|
||||
+ std::to_wstring(o.channels) + L" " + std::to_wstring(o.bits) + L" " + kind;
|
||||
STARTUPINFOW si{};
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESTDHANDLES;
|
||||
@@ -213,8 +196,7 @@ HANDLE spawn_tone(const Options& o, unsigned long& tone_pid)
|
||||
const BOOL launched =
|
||||
CreateProcessW(exe.c_str(), cmd.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi);
|
||||
CloseHandle(wr);
|
||||
if (!launched)
|
||||
{
|
||||
if (!launched) {
|
||||
std::printf("ERROR: CreateProcess(coop_tone) failed (%lu).\n", GetLastError());
|
||||
CloseHandle(rd);
|
||||
return nullptr;
|
||||
@@ -227,27 +209,20 @@ HANDLE spawn_tone(const Options& o, unsigned long& tone_pid)
|
||||
char ch = 0;
|
||||
DWORD got = 0;
|
||||
const DWORD start = GetTickCount();
|
||||
while (GetTickCount() - start < 5000)
|
||||
{
|
||||
if (ReadFile(rd, &ch, 1, &got, nullptr) && got == 1)
|
||||
{
|
||||
if (ch == '\n')
|
||||
{
|
||||
while (GetTickCount() - start < 5000) {
|
||||
if (ReadFile(rd, &ch, 1, &got, nullptr) && got == 1) {
|
||||
if (ch == '\n') {
|
||||
break;
|
||||
}
|
||||
if (ch != '\r')
|
||||
{
|
||||
if (ch != '\r') {
|
||||
line.push_back(ch);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
CloseHandle(rd);
|
||||
if (line.rfind("TONE_RENDERING", 0) == 0)
|
||||
{
|
||||
if (line.rfind("TONE_RENDERING", 0) == 0) {
|
||||
std::printf("coop_tone: %s\n", line.c_str());
|
||||
return pi.hProcess;
|
||||
}
|
||||
@@ -261,16 +236,13 @@ HANDLE spawn_tone(const Options& o, unsigned long& tone_pid)
|
||||
// Capture the hook's audio ring for `seconds`, draining frequently so the tool itself
|
||||
// never causes an overrun -- the captured buffer is then exactly what the hook produced.
|
||||
// Fills `pcm` (interleaved) and reports the declared format. Returns false if no format.
|
||||
bool capture_ring(coop::AudioRingHeader* ring, int seconds, std::vector<std::uint8_t>& pcm,
|
||||
std::uint32_t& rate, std::uint32_t& channels, std::uint32_t& bits,
|
||||
std::uint32_t& format_tag, std::uint64_t& overruns)
|
||||
bool capture_ring(coop::AudioRingHeader* ring, int seconds, std::vector<std::uint8_t>& pcm, std::uint32_t& rate,
|
||||
std::uint32_t& channels, std::uint32_t& bits, std::uint32_t& format_tag, std::uint64_t& overruns)
|
||||
{
|
||||
// Wait up to 8 s for the hook to publish a format (late attach measures the rate first).
|
||||
const DWORD wait_end = GetTickCount() + 8000;
|
||||
while (!coop::audio_ring_format_ready(*ring))
|
||||
{
|
||||
if (GetTickCount() >= wait_end)
|
||||
{
|
||||
while (!coop::audio_ring_format_ready(*ring)) {
|
||||
if (GetTickCount() >= wait_end) {
|
||||
std::printf("ERROR: hook never published an audio format (no stream captured).\n");
|
||||
return false;
|
||||
}
|
||||
@@ -280,48 +252,40 @@ bool capture_ring(coop::AudioRingHeader* ring, int seconds, std::vector<std::uin
|
||||
channels = ring->channels;
|
||||
bits = ring->bits;
|
||||
format_tag = ring->format_tag;
|
||||
std::printf("Hook published format: %u Hz / %u ch / %u-bit / tag %u. Capturing %d s...\n", rate, channels,
|
||||
bits, format_tag, seconds);
|
||||
std::printf("Hook published format: %u Hz / %u ch / %u-bit / tag %u. Capturing %d s...\n", rate, channels, bits,
|
||||
format_tag, seconds);
|
||||
|
||||
std::vector<std::uint8_t> scratch(coop::kAudioRingCapacity);
|
||||
const DWORD cap_end = GetTickCount() + static_cast<DWORD>(seconds) * 1000;
|
||||
while (GetTickCount() < cap_end)
|
||||
{
|
||||
while (GetTickCount() < cap_end) {
|
||||
std::uint32_t got = coop::audio_ring_pop(*ring, scratch.data(), static_cast<std::uint32_t>(scratch.size()));
|
||||
if (got > 0)
|
||||
{
|
||||
if (got > 0) {
|
||||
pcm.insert(pcm.end(), scratch.begin(), scratch.begin() + got);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
Sleep(2); // ring momentarily empty; poll again shortly
|
||||
}
|
||||
}
|
||||
// Drain any tail.
|
||||
std::uint32_t got = 0;
|
||||
while ((got = coop::audio_ring_pop(*ring, scratch.data(), static_cast<std::uint32_t>(scratch.size()))) > 0)
|
||||
{
|
||||
while ((got = coop::audio_ring_pop(*ring, scratch.data(), static_cast<std::uint32_t>(scratch.size()))) > 0) {
|
||||
pcm.insert(pcm.end(), scratch.begin(), scratch.begin() + got);
|
||||
}
|
||||
overruns = ring->overruns.load(std::memory_order_relaxed);
|
||||
return !pcm.empty();
|
||||
}
|
||||
|
||||
void print_report(const coop::ToneReport& r, double expected_hz, std::uint32_t declared_rate,
|
||||
std::uint64_t overruns)
|
||||
void print_report(const coop::ToneReport& r, double expected_hz, std::uint32_t declared_rate, std::uint64_t overruns)
|
||||
{
|
||||
std::printf("\n================ FIDELITY REPORT ================\n");
|
||||
std::printf(" samples analyzed : %zu frames (%.2f s @ %u Hz)\n", r.frames, r.duration_sec, r.sample_rate);
|
||||
std::printf(" level : RMS %.4f peak %.4f clipped %.3f%%\n", r.rms, r.peak,
|
||||
r.clipped_fraction * 100.0);
|
||||
if (expected_hz > 0.0)
|
||||
{
|
||||
std::printf(" PITCH : %.2f Hz captured vs %.2f Hz played -> %+.1f cents (x%.4f)\n",
|
||||
r.dominant_hz, expected_hz, r.pitch_error_cents, r.pitch_error_ratio);
|
||||
if (expected_hz > 0.0) {
|
||||
std::printf(" PITCH : %.2f Hz captured vs %.2f Hz played -> %+.1f cents (x%.4f)\n", r.dominant_hz,
|
||||
expected_hz, r.pitch_error_cents, r.pitch_error_ratio);
|
||||
// If the pitch is off, the most likely cause is a wrong declared rate. Show the rate
|
||||
// the captured pitch implies, so a misdetection is obvious at a glance.
|
||||
if (r.pitch_error_ratio > 0.0)
|
||||
{
|
||||
if (r.pitch_error_ratio > 0.0) {
|
||||
const double implied_true_rate = declared_rate / r.pitch_error_ratio;
|
||||
std::printf(" implied true rate: ~%.0f Hz (declared %u Hz)%s\n", implied_true_rate, declared_rate,
|
||||
std::fabs(r.pitch_error_cents) > 15.0 ? " <-- MISMATCH" : "");
|
||||
@@ -330,69 +294,58 @@ void print_report(const coop::ToneReport& r, double expected_hz, std::uint32_t d
|
||||
}
|
||||
std::printf(" discontinuities : %u clicks (%.2f/s)\n", r.glitch_count, r.glitch_rate_per_sec);
|
||||
std::printf(" dropouts : %u gaps, %.1f ms total\n", r.dropout_count, r.dropout_ms);
|
||||
if (overruns != UINT64_MAX)
|
||||
{
|
||||
if (overruns != UINT64_MAX) {
|
||||
std::printf(" ring overruns : %llu (host fell behind -> dropped packets)\n",
|
||||
static_cast<unsigned long long>(overruns));
|
||||
}
|
||||
|
||||
std::printf("------------------- VERDICT --------------------\n");
|
||||
int problems = 0;
|
||||
if (expected_hz > 0.0 && std::fabs(r.pitch_error_cents) > 15.0)
|
||||
{
|
||||
if (expected_hz > 0.0 && std::fabs(r.pitch_error_cents) > 15.0) {
|
||||
std::printf(" [X] PITCH SHIFT: captured rate is wrong (audible). Likely a mis-measured\n"
|
||||
" late-attach rate -- see implied true rate above.\n");
|
||||
++problems;
|
||||
}
|
||||
if (r.dropout_count > 0)
|
||||
{
|
||||
std::printf(" [X] DROPOUTS: %u silence gap(s) -- choppy / 'metallic' under-run artifacts.\n",
|
||||
r.dropout_count);
|
||||
if (r.dropout_count > 0) {
|
||||
std::printf(" [X] DROPOUTS: %u silence gap(s) -- choppy / 'metallic' under-run artifacts.\n", r.dropout_count);
|
||||
++problems;
|
||||
}
|
||||
if (r.glitch_rate_per_sec > 1.0)
|
||||
{
|
||||
if (r.glitch_rate_per_sec > 1.0) {
|
||||
std::printf(" [X] CLICKS: %.1f discontinuities/s -- torn/dropped packets.\n", r.glitch_rate_per_sec);
|
||||
++problems;
|
||||
}
|
||||
if (expected_hz > 0.0 && r.snr_db < 40.0)
|
||||
{
|
||||
if (expected_hz > 0.0 && r.snr_db < 40.0) {
|
||||
std::printf(" [X] DISTORTION: SNR %.1f dB is low for a pure tone.\n", r.snr_db);
|
||||
++problems;
|
||||
}
|
||||
if (problems == 0)
|
||||
{
|
||||
if (problems == 0) {
|
||||
std::printf(" [OK] Captured audio is faithful (pitch, purity, continuity all good).\n");
|
||||
}
|
||||
std::printf("=================================================\n");
|
||||
}
|
||||
|
||||
// Resolve a WAVEFORMATEX (possibly EXTENSIBLE) to the scalar fields the analyzer wants.
|
||||
void resolve_waveformat(const WAVEFORMATEX* w, std::uint32_t& rate, std::uint32_t& channels,
|
||||
std::uint32_t& bits, std::uint32_t& tag)
|
||||
void resolve_waveformat(const WAVEFORMATEX* w, std::uint32_t& rate, std::uint32_t& channels, std::uint32_t& bits,
|
||||
std::uint32_t& tag)
|
||||
{
|
||||
rate = w->nSamplesPerSec;
|
||||
channels = w->nChannels;
|
||||
bits = w->wBitsPerSample;
|
||||
tag = w->wFormatTag;
|
||||
if (w->wFormatTag == WAVE_FORMAT_EXTENSIBLE && w->cbSize >= 22)
|
||||
{
|
||||
if (w->wFormatTag == WAVE_FORMAT_EXTENSIBLE && w->cbSize >= 22) {
|
||||
const auto* ext = reinterpret_cast<const WAVEFORMATEXTENSIBLE*>(w);
|
||||
tag = (ext->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) ? coop::kToneFormatFloat
|
||||
: coop::kToneFormatPcm;
|
||||
tag = (ext->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) ? coop::kToneFormatFloat : coop::kToneFormatPcm;
|
||||
}
|
||||
}
|
||||
|
||||
// Loopback-capture `pid`'s render output (device-clock faithful, gaps included) for
|
||||
// `seconds`, into `pcm`, and report the device format. Shared by --render (self) and
|
||||
// --baseline (the tone directly). Assumes COM is already initialized on this thread.
|
||||
bool loopback_capture_pid(unsigned long pid, int seconds, std::vector<std::uint8_t>& pcm,
|
||||
std::uint32_t& rate, std::uint32_t& channels, std::uint32_t& bits,
|
||||
std::uint32_t& tag, std::uint32_t& block_align)
|
||||
bool loopback_capture_pid(unsigned long pid, int seconds, std::vector<std::uint8_t>& pcm, std::uint32_t& rate,
|
||||
std::uint32_t& channels, std::uint32_t& bits, std::uint32_t& tag, std::uint32_t& block_align)
|
||||
{
|
||||
WAVEFORMATEX* mix = coop::default_render_format();
|
||||
if (mix == nullptr)
|
||||
{
|
||||
if (mix == nullptr) {
|
||||
std::printf("ERROR: could not get the default render format.\n");
|
||||
return false;
|
||||
}
|
||||
@@ -403,17 +356,13 @@ bool loopback_capture_pid(unsigned long pid, int seconds, std::vector<std::uint8
|
||||
const bool ok = cap.start(pid, mix, [&](const BYTE* data, std::uint32_t frames, bool silent) {
|
||||
const std::size_t bytes = static_cast<std::size_t>(frames) * mix->nBlockAlign;
|
||||
std::lock_guard<std::mutex> lk(m);
|
||||
if (silent || data == nullptr)
|
||||
{
|
||||
if (silent || data == nullptr) {
|
||||
pcm.insert(pcm.end(), bytes, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
pcm.insert(pcm.end(), data, data + bytes);
|
||||
}
|
||||
});
|
||||
if (ok)
|
||||
{
|
||||
if (ok) {
|
||||
Sleep(static_cast<DWORD>(seconds) * 1000);
|
||||
}
|
||||
cap.stop();
|
||||
@@ -429,36 +378,32 @@ bool loopback_capture_pid(unsigned long pid, int seconds, std::vector<std::uint8
|
||||
int run_listen_mode(const Options& o)
|
||||
{
|
||||
const bool com_ok = SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
|
||||
std::printf("Listening to pid %lu's render output for %d s (e.g. the live coop_host mirror)...\n",
|
||||
o.listen, o.seconds);
|
||||
std::printf("Listening to pid %lu's render output for %d s (e.g. the live coop_host mirror)...\n", o.listen,
|
||||
o.seconds);
|
||||
std::vector<std::uint8_t> pcm;
|
||||
std::uint32_t rate = 0, channels = 0, bits = 0, tag = 0, block = 0;
|
||||
const bool ok = loopback_capture_pid(o.listen, o.seconds, pcm, rate, channels, bits, tag, block);
|
||||
int rc = 1;
|
||||
if (ok && !pcm.empty())
|
||||
{
|
||||
if (ok && !pcm.empty()) {
|
||||
std::wstring out = o.wav_out.empty() ? (coop::exe_directory() + L"coop_listen.wav") : o.wav_out;
|
||||
if (coop::wav_write(out, pcm.data(), pcm.size(), rate, channels, bits, tag))
|
||||
{
|
||||
if (coop::wav_write(out, pcm.data(), pcm.size(), rate, channels, bits, tag)) {
|
||||
std::wprintf(L"Wrote captured output: %ls\n", out.c_str());
|
||||
}
|
||||
// Trim the first ~0.7 s for analysis (loopback capture ramp-up) -- the .wav keeps it all.
|
||||
const std::size_t skip =
|
||||
std::min<std::size_t>(pcm.size(), static_cast<std::size_t>(rate) * block * 7 / 10);
|
||||
const std::size_t skip = std::min<std::size_t>(pcm.size(), static_cast<std::size_t>(rate) * block * 7 / 10);
|
||||
auto mono = coop::decode_channel(pcm.data() + skip, pcm.size() - skip, tag, bits, channels, 0);
|
||||
if (!mono.empty())
|
||||
{
|
||||
if (!mono.empty()) {
|
||||
std::printf("\n[LISTEN] pid %lu render output (what the guest hears):\n", o.listen);
|
||||
const coop::ToneReport r = coop::analyze_tone(mono.data(), mono.size(), rate, o.freq);
|
||||
print_report(r, o.freq, rate, UINT64_MAX);
|
||||
rc = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
std::printf("ERROR: no audio captured from pid %lu (is it rendering?).\n", o.listen);
|
||||
}
|
||||
if (com_ok) { CoUninitialize(); }
|
||||
if (com_ok) {
|
||||
CoUninitialize();
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -471,16 +416,13 @@ int run_selfcheck_mode(const Options& o)
|
||||
const bool com_ok = SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
|
||||
std::atomic<bool> stop{false};
|
||||
std::thread renderer([&]() {
|
||||
if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)))
|
||||
{
|
||||
if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED))) {
|
||||
return;
|
||||
}
|
||||
coop::tone::ToneSource tone;
|
||||
coop::tone::ToneFormat tf; // {} = device mix format (no resample), cleanest reference
|
||||
if (tone.open(tf, o.freq))
|
||||
{
|
||||
while (!stop.load(std::memory_order_relaxed))
|
||||
{
|
||||
if (tone.open(tf, o.freq)) {
|
||||
while (!stop.load(std::memory_order_relaxed)) {
|
||||
tone.render_step(100);
|
||||
}
|
||||
tone.close();
|
||||
@@ -497,23 +439,21 @@ int run_selfcheck_mode(const Options& o)
|
||||
renderer.join();
|
||||
|
||||
int rc = 1;
|
||||
if (ok && !pcm.empty())
|
||||
{
|
||||
if (ok && !pcm.empty()) {
|
||||
const std::size_t skip = std::min<std::size_t>(pcm.size(), static_cast<std::size_t>(rate) * block * 7 / 10);
|
||||
auto mono = coop::decode_channel(pcm.data() + skip, pcm.size() - skip, tag, bits, channels, 0);
|
||||
if (!mono.empty())
|
||||
{
|
||||
if (!mono.empty()) {
|
||||
std::printf("\n[SELFCHECK] in-process tone via the self-capture path (control):\n");
|
||||
const coop::ToneReport r = coop::analyze_tone(mono.data(), mono.size(), rate, o.freq);
|
||||
print_report(r, o.freq, rate, UINT64_MAX);
|
||||
rc = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
std::printf("ERROR: selfcheck produced no audio.\n");
|
||||
}
|
||||
if (com_ok) { CoUninitialize(); }
|
||||
if (com_ok) {
|
||||
CoUninitialize();
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -529,28 +469,25 @@ int run_baseline_mode(const Options& o, HANDLE tone_proc, unsigned long target_p
|
||||
std::uint32_t rate = 0, channels = 0, bits = 0, tag = 0, block = 0;
|
||||
const bool ok = loopback_capture_pid(target_pid, o.seconds, pcm, rate, channels, bits, tag, block);
|
||||
int rc = 1;
|
||||
if (ok && !pcm.empty())
|
||||
{
|
||||
if (ok && !pcm.empty()) {
|
||||
const std::size_t skip = std::min<std::size_t>(pcm.size(), static_cast<std::size_t>(rate) * block * 7 / 10);
|
||||
auto mono = coop::decode_channel(pcm.data() + skip, pcm.size() - skip, tag, bits, channels, 0);
|
||||
if (!mono.empty())
|
||||
{
|
||||
if (!mono.empty()) {
|
||||
std::printf("\n[BASELINE] tone direct (measurement floor):\n");
|
||||
const coop::ToneReport r = coop::analyze_tone(mono.data(), mono.size(), rate, o.freq);
|
||||
print_report(r, o.freq, rate, UINT64_MAX);
|
||||
rc = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
std::printf("ERROR: baseline loopback capture produced no audio.\n");
|
||||
}
|
||||
if (tone_proc != nullptr)
|
||||
{
|
||||
if (tone_proc != nullptr) {
|
||||
TerminateProcess(tone_proc, 0);
|
||||
CloseHandle(tone_proc);
|
||||
}
|
||||
if (com_ok) { CoUninitialize(); }
|
||||
if (com_ok) {
|
||||
CoUninitialize();
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -565,10 +502,11 @@ int run_render_mode(const Options& o, HANDLE tone_proc, unsigned long target_pid
|
||||
const bool com_ok = SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
|
||||
|
||||
WAVEFORMATEX* mix = coop::default_render_format();
|
||||
if (mix == nullptr)
|
||||
{
|
||||
if (mix == nullptr) {
|
||||
std::printf("ERROR: could not get the default render format.\n");
|
||||
if (com_ok) { CoUninitialize(); }
|
||||
if (com_ok) {
|
||||
CoUninitialize();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
std::uint32_t rate = 0, channels = 0, bits = 0, tag = 0;
|
||||
@@ -580,38 +518,34 @@ int run_render_mode(const Options& o, HANDLE tone_proc, unsigned long target_pid
|
||||
std::vector<std::uint8_t> rendered;
|
||||
std::mutex rendered_mutex;
|
||||
coop::ProcessLoopbackCapture selfcap;
|
||||
const bool cap_ok = selfcap.start(GetCurrentProcessId(), mix,
|
||||
[&](const BYTE* data, std::uint32_t frames, bool silent) {
|
||||
const std::size_t bytes = static_cast<std::size_t>(frames) * mix->nBlockAlign;
|
||||
std::lock_guard<std::mutex> lk(rendered_mutex);
|
||||
if (silent || data == nullptr)
|
||||
{
|
||||
rendered.insert(rendered.end(), bytes, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
rendered.insert(rendered.end(), data, data + bytes);
|
||||
}
|
||||
});
|
||||
if (!cap_ok)
|
||||
{
|
||||
const bool cap_ok =
|
||||
selfcap.start(GetCurrentProcessId(), mix, [&](const BYTE* data, std::uint32_t frames, bool silent) {
|
||||
const std::size_t bytes = static_cast<std::size_t>(frames) * mix->nBlockAlign;
|
||||
std::lock_guard<std::mutex> lk(rendered_mutex);
|
||||
if (silent || data == nullptr) {
|
||||
rendered.insert(rendered.end(), bytes, 0);
|
||||
} else {
|
||||
rendered.insert(rendered.end(), data, data + bytes);
|
||||
}
|
||||
});
|
||||
if (!cap_ok) {
|
||||
std::printf("ERROR: self-loopback capture failed to start.\n");
|
||||
CoTaskMemFree(mix);
|
||||
if (com_ok) { CoUninitialize(); }
|
||||
if (com_ok) {
|
||||
CoUninitialize();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Drive the real mirror: it discovers the hook's ring, re-renders it (silencing the game).
|
||||
coop::AudioMirror mirror;
|
||||
if (!mirror.start(target_pid))
|
||||
{
|
||||
if (!mirror.start(target_pid)) {
|
||||
std::printf("ERROR: AudioMirror failed to start.\n");
|
||||
}
|
||||
std::printf("Rendering through the real AudioMirror for %d s (source warms up, then measure)...\n",
|
||||
o.seconds);
|
||||
std::printf("Rendering through the real AudioMirror for %d s (source warms up, then measure)...\n", o.seconds);
|
||||
Sleep(static_cast<DWORD>(o.seconds) * 1000);
|
||||
std::printf(" mirror: source=%s status=\"%s\" buffered=%u ms\n", mirror.source_name(),
|
||||
mirror.status().c_str(), mirror.buffered_ms());
|
||||
std::printf(" mirror: source=%s status=\"%s\" buffered=%u ms\n", mirror.source_name(), mirror.status().c_str(),
|
||||
mirror.buffered_ms());
|
||||
mirror.stop();
|
||||
selfcap.stop();
|
||||
|
||||
@@ -623,100 +557,70 @@ int run_render_mode(const Options& o, HANDLE tone_proc, unsigned long target_pid
|
||||
|
||||
// Trim the first ~0.7 s: it contains start-up priming / the loopback warming up, which
|
||||
// would otherwise read as a spurious leading dropout.
|
||||
const std::size_t skip = std::min<std::size_t>(pcm.size(), static_cast<std::size_t>(rate) *
|
||||
mix->nBlockAlign * 7 / 10);
|
||||
const std::size_t skip =
|
||||
std::min<std::size_t>(pcm.size(), static_cast<std::size_t>(rate) * mix->nBlockAlign * 7 / 10);
|
||||
const std::uint8_t* body = pcm.data() + skip;
|
||||
const std::size_t body_bytes = pcm.size() - skip;
|
||||
|
||||
std::wstring out = o.wav_out.empty() ? (coop::exe_directory() + L"coop_render.wav") : o.wav_out;
|
||||
if (coop::wav_write(out, body, body_bytes, rate, channels, bits, tag))
|
||||
{
|
||||
if (coop::wav_write(out, body, body_bytes, rate, channels, bits, tag)) {
|
||||
std::wprintf(L"Wrote rendered output: %ls\n", out.c_str());
|
||||
}
|
||||
|
||||
auto mono = coop::decode_channel(body, body_bytes, tag, bits, channels, 0);
|
||||
if (mono.empty())
|
||||
{
|
||||
if (mono.empty()) {
|
||||
std::printf("NOTE: render format isn't float32/int16; WAV written, analysis skipped.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
std::printf("\n[RENDER PATH] what the guest actually hears (real AudioMirror output):\n");
|
||||
const coop::ToneReport r = coop::analyze_tone(mono.data(), mono.size(), rate, o.freq);
|
||||
print_report(r, o.freq, rate, UINT64_MAX);
|
||||
}
|
||||
|
||||
CoTaskMemFree(mix);
|
||||
if (tone_proc != nullptr)
|
||||
{
|
||||
if (tone_proc != nullptr) {
|
||||
TerminateProcess(tone_proc, 0);
|
||||
CloseHandle(tone_proc);
|
||||
}
|
||||
if (com_ok) { CoUninitialize(); }
|
||||
if (com_ok) {
|
||||
CoUninitialize();
|
||||
}
|
||||
return mono.empty() ? 1 : 0;
|
||||
}
|
||||
|
||||
bool parse_args(int argc, wchar_t** argv, Options& o)
|
||||
{
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::wstring a = argv[i];
|
||||
auto next = [&](unsigned& dst) {
|
||||
if (i + 1 < argc)
|
||||
{
|
||||
if (i + 1 < argc) {
|
||||
dst = static_cast<unsigned>(_wtoi(argv[++i]));
|
||||
}
|
||||
};
|
||||
if (a == L"--pid" && i + 1 < argc)
|
||||
{
|
||||
if (a == L"--pid" && i + 1 < argc) {
|
||||
o.pid = std::wcstoul(argv[++i], nullptr, 10);
|
||||
}
|
||||
else if (a == L"--listen" && i + 1 < argc)
|
||||
{
|
||||
} else if (a == L"--listen" && i + 1 < argc) {
|
||||
o.listen = std::wcstoul(argv[++i], nullptr, 10);
|
||||
}
|
||||
else if (a == L"--freq" && i + 1 < argc)
|
||||
{
|
||||
} else if (a == L"--freq" && i + 1 < argc) {
|
||||
o.freq = _wtof(argv[++i]);
|
||||
}
|
||||
else if (a == L"--rate")
|
||||
{
|
||||
} else if (a == L"--rate") {
|
||||
next(o.rate);
|
||||
}
|
||||
else if (a == L"--channels")
|
||||
{
|
||||
} else if (a == L"--channels") {
|
||||
next(o.channels);
|
||||
}
|
||||
else if (a == L"--bits")
|
||||
{
|
||||
} else if (a == L"--bits") {
|
||||
next(o.bits);
|
||||
}
|
||||
else if (a == L"--seconds" && i + 1 < argc)
|
||||
{
|
||||
} else if (a == L"--seconds" && i + 1 < argc) {
|
||||
o.seconds = std::max(1, _wtoi(argv[++i]));
|
||||
}
|
||||
else if (a == L"--render")
|
||||
{
|
||||
} else if (a == L"--render") {
|
||||
o.render = true;
|
||||
}
|
||||
else if (a == L"--baseline")
|
||||
{
|
||||
} else if (a == L"--baseline") {
|
||||
o.baseline = true;
|
||||
}
|
||||
else if (a == L"--selfcheck")
|
||||
{
|
||||
} else if (a == L"--selfcheck") {
|
||||
o.selfcheck = true;
|
||||
}
|
||||
else if (a == L"--wav" && i + 1 < argc)
|
||||
{
|
||||
} else if (a == L"--wav" && i + 1 < argc) {
|
||||
o.wav_in = argv[++i];
|
||||
}
|
||||
else if (a == L"--out" && i + 1 < argc)
|
||||
{
|
||||
} else if (a == L"--out" && i + 1 < argc) {
|
||||
o.wav_out = argv[++i];
|
||||
}
|
||||
else if (a == L"--help" || a == L"-h")
|
||||
{
|
||||
} else if (a == L"--help" || a == L"-h") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -728,8 +632,7 @@ bool parse_args(int argc, wchar_t** argv, Options& o)
|
||||
int wmain(int argc, wchar_t** argv)
|
||||
{
|
||||
Options o;
|
||||
if (!parse_args(argc, argv, o))
|
||||
{
|
||||
if (!parse_args(argc, argv, o)) {
|
||||
std::printf("usage: coop_audio_validate [--pid N] [--listen N] [--freq Hz] [--rate Hz]\n"
|
||||
" [--channels N] [--bits 16|32] [--seconds N] [--render | --baseline | --selfcheck]\n"
|
||||
" [--wav file] [--out file]\n"
|
||||
@@ -745,19 +648,16 @@ int wmain(int argc, wchar_t** argv)
|
||||
}
|
||||
|
||||
// --- Mode C: analyze a recorded .wav ---------------------------------------------
|
||||
if (!o.wav_in.empty())
|
||||
{
|
||||
if (!o.wav_in.empty()) {
|
||||
coop::WavData wd;
|
||||
if (!coop::wav_read(o.wav_in, wd))
|
||||
{
|
||||
if (!coop::wav_read(o.wav_in, wd)) {
|
||||
std::wprintf(L"ERROR: could not read WAV '%ls'.\n", o.wav_in.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::printf("Loaded WAV: %u Hz / %u ch / %u-bit / tag %u, %zu bytes\n", wd.sample_rate, wd.channels,
|
||||
wd.bits, wd.format_tag, wd.pcm.size());
|
||||
std::printf("Loaded WAV: %u Hz / %u ch / %u-bit / tag %u, %zu bytes\n", wd.sample_rate, wd.channels, wd.bits,
|
||||
wd.format_tag, wd.pcm.size());
|
||||
auto mono = coop::decode_channel(wd.pcm.data(), wd.pcm.size(), wd.format_tag, wd.bits, wd.channels, 0);
|
||||
if (mono.empty())
|
||||
{
|
||||
if (mono.empty()) {
|
||||
std::printf("ERROR: unsupported WAV sample format (need 16-bit PCM or 32-bit float).\n");
|
||||
return 1;
|
||||
}
|
||||
@@ -767,44 +667,36 @@ int wmain(int argc, wchar_t** argv)
|
||||
}
|
||||
|
||||
// --- Control: render a clean tone in-process + self-capture (no target needed) ----
|
||||
if (o.selfcheck)
|
||||
{
|
||||
if (o.selfcheck) {
|
||||
return run_selfcheck_mode(o);
|
||||
}
|
||||
|
||||
// --- Live: passively record an already-running process's output (e.g. coop_host) --
|
||||
if (o.listen != 0)
|
||||
{
|
||||
if (o.listen != 0) {
|
||||
return run_listen_mode(o);
|
||||
}
|
||||
|
||||
// --- Acquire a target: spawn coop_tone, or attach to a given pid ------------------
|
||||
HANDLE tone_proc = nullptr;
|
||||
unsigned long target_pid = o.pid;
|
||||
if (target_pid == 0)
|
||||
{
|
||||
if (target_pid == 0) {
|
||||
tone_proc = spawn_tone(o, target_pid);
|
||||
if (tone_proc == nullptr)
|
||||
{
|
||||
if (tone_proc == nullptr) {
|
||||
return 1;
|
||||
}
|
||||
Sleep(700); // let the tone reach steady state before we inject
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
std::printf("Attaching to existing pid %lu (tone freq assumed %.0f Hz).\n", target_pid, o.freq);
|
||||
}
|
||||
|
||||
// --- Mode: measurement floor (no hook, no mirror) ---------------------------------
|
||||
if (o.baseline)
|
||||
{
|
||||
if (o.baseline) {
|
||||
return run_baseline_mode(o, tone_proc, target_pid);
|
||||
}
|
||||
|
||||
// --- Set up the IPC the hook expects, then inject (late attach: ring AFTER inject) -
|
||||
coop::SharedMemory ipc;
|
||||
if (!ipc.create(coop::shared_memory_name(target_pid), sizeof(coop::SharedBlock)))
|
||||
{
|
||||
if (!ipc.create(coop::shared_memory_name(target_pid), sizeof(coop::SharedBlock))) {
|
||||
std::printf("ERROR: create input mapping failed (%lu).\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
@@ -817,15 +709,13 @@ int wmain(int argc, wchar_t** argv)
|
||||
enable_hook_trace();
|
||||
|
||||
std::printf("Injecting coop_hook.dll into pid %lu ...\n", target_pid);
|
||||
if (!inject(target_pid, coop::deployed_artifact_path(L"coop_hook.dll")))
|
||||
{
|
||||
if (!inject(target_pid, coop::deployed_artifact_path(L"coop_hook.dll"))) {
|
||||
std::printf("ERROR: injection failed.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// --- Mode B: measure the host RENDER path (real AudioMirror) ----------------------
|
||||
if (o.render)
|
||||
{
|
||||
if (o.render) {
|
||||
Sleep(1200); // let the hook register the stream before the mirror reads it
|
||||
const int rc = run_render_mode(o, tone_proc, target_pid);
|
||||
block->magic = 0;
|
||||
@@ -837,9 +727,7 @@ int wmain(int argc, wchar_t** argv)
|
||||
// hook's late-attach guess+measure path (the exact Brotato scenario).
|
||||
Sleep(1500);
|
||||
coop::SharedMemory ring_shm;
|
||||
if (!ring_shm.create(coop::audio_ring_name(target_pid),
|
||||
coop::audio_ring_total_size(coop::kAudioRingCapacity)))
|
||||
{
|
||||
if (!ring_shm.create(coop::audio_ring_name(target_pid), coop::audio_ring_total_size(coop::kAudioRingCapacity))) {
|
||||
std::printf("ERROR: create audio ring mapping failed (%lu).\n", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
@@ -853,30 +741,24 @@ int wmain(int argc, wchar_t** argv)
|
||||
std::uint64_t overruns = 0;
|
||||
const bool captured = capture_ring(ring, o.seconds, pcm, rate, channels, bits, format_tag, overruns);
|
||||
|
||||
if (captured)
|
||||
{
|
||||
if (captured) {
|
||||
// Dump the captured audio so it can be listened to.
|
||||
std::wstring out = o.wav_out.empty() ? (coop::exe_directory() + L"coop_capture.wav") : o.wav_out;
|
||||
if (coop::wav_write(out, pcm.data(), pcm.size(), rate, channels, bits, format_tag))
|
||||
{
|
||||
if (coop::wav_write(out, pcm.data(), pcm.size(), rate, channels, bits, format_tag)) {
|
||||
std::wprintf(L"Wrote captured audio: %ls\n", out.c_str());
|
||||
}
|
||||
|
||||
auto mono = coop::decode_channel(pcm.data(), pcm.size(), format_tag, bits, channels, 0);
|
||||
if (mono.empty())
|
||||
{
|
||||
if (mono.empty()) {
|
||||
std::printf("NOTE: captured format isn't float32/int16, can't decode for analysis (WAV still written).\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
const coop::ToneReport r = coop::analyze_tone(mono.data(), mono.size(), rate, o.freq);
|
||||
print_report(r, o.freq, rate, overruns);
|
||||
}
|
||||
}
|
||||
|
||||
block->magic = 0; // invalidate so a late hook read won't trust stale data
|
||||
if (tone_proc != nullptr)
|
||||
{
|
||||
if (tone_proc != nullptr) {
|
||||
TerminateProcess(tone_proc, 0);
|
||||
CloseHandle(tone_proc);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user