Full Code of vrubleg/soundkeeper for AI

master 44ab8c977d22 cached
29 files
124.6 KB
33.6k tokens
137 symbols
1 requests
Download .txt
Repository: vrubleg/soundkeeper
Branch: master
Commit: 44ab8c977d22
Files: 29
Total size: 124.6 KB

Directory structure:
gitextract_4oaczphi/

├── .editorconfig
├── .gitignore
├── BuildInfo.cmd
├── BuildInfo.csx
├── BuildInfo.hpp
├── BuildInfo.rc2
├── CSoundKeeper.cpp
├── CSoundKeeper.hpp
├── CSoundSession.cpp
├── CSoundSession.hpp
├── Common/
│   ├── BasicMacros.hpp
│   ├── Defer.hpp
│   ├── NtBase.hpp
│   ├── NtCriticalSection.hpp
│   ├── NtEvent.hpp
│   ├── NtHandle.hpp
│   ├── NtUtils.hpp
│   └── StrUtils.hpp
├── Common.hpp
├── License.md
├── ReadMe.txt
├── Resources.hpp
├── Resources.rc
├── RuntimeHacks.cpp
├── SoundKeeper.sln
├── SoundKeeper.vcxproj
├── SoundKeeper.vcxproj.filters
├── msvcrt32.lib
└── msvcrt64.lib

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8
end_of_line = crlf
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true
insert_final_newline = true
guidelines = 80, 120

[*.{vcxproj,vcxitems,filters,user,props,targets}]
insert_final_newline = unset
indent_style = space
indent_size = 2


================================================
FILE: .gitignore
================================================
**~**
/.vs/
/Bin/
/Build/
*.aps
*.vcxproj.user


================================================
FILE: BuildInfo.cmd
================================================
@echo off
setlocal

set NO_ERRORS=0

for %%a in (%*) do (
	if "%%a"=="--no-errors" set NO_ERRORS=1
)

where csi 1>nul 2>&1 && goto :ready

set VSWHERE_PATH=%ProgramFiles%\Microsoft Visual Studio\Installer\vswhere.exe
if not exist "%VSWHERE_PATH%" set VSWHERE_PATH=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe
if not exist "%VSWHERE_PATH%" goto :error
for /f "usebackq delims=#" %%a in (`"%VSWHERE_PATH%" -latest -property installationPath`) do set VSDEVCMD_PATH=%%a\Common7\Tools\VsDevCmd.bat
if not exist "%VSDEVCMD_PATH%" goto :error
set VSCMD_SKIP_SENDTELEMETRY=1
call "%VSDEVCMD_PATH%" -no_logo -startdir=none

where csi 1>nul 2>&1 && goto :ready

:error

if "%NO_ERRORS%"=="1" (
	echo Warning: Can't find csi to execute BuildInfo.csx. BuildInfo.hpp won't be updated.
	exit /b 0
) else (
	echo Error: Can't find csi to execute BuildInfo.csx. BuildInfo.hpp won't be updated.
	exit /b 1
)

:ready

csi BuildInfo.csx -- BuildInfo.hpp %*


================================================
FILE: BuildInfo.csx
================================================
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Linq;
using System.Diagnostics;
using System.Globalization;

var args = Environment.GetCommandLineArgs().ToList();
args = (args.IndexOf("--") > -1) ? args.Skip(args.IndexOf("--") + 1).ToList() : (new List<string>());

if (args.Count == 0 || args[0][0] == '-')
{
	Console.WriteLine("Error: Target file name is not specified.");
	Console.WriteLine("Usage: csi BuildInfo.csx -- <BuildInfo.hpp> [--no-errors] [--force]");
	Environment.Exit(1);
}

var BUILD_INFO_FILE = args[0];
var NO_ERRORS = args.IndexOf("--no-errors") != -1;
var FORCE_UPDATE = args.IndexOf("--force") != -1;

class Error : Exception
{
	public Error() { }
	public Error(string message) : base(message) { }
	public Error(string message, Exception e) : base(message, e) { }
}

class Success : Exception
{
	public Success() { }
	public Success(string message) : base(message) { }
	public Success(string message, Exception e) : base(message, e) { }
}

int RunCmd(string cmd, out string stdout, out string stderr)
{
	string name = "";
	string args = "";

	cmd = cmd.Trim();
	if (cmd.Length == 0) { throw new ArgumentException("Empty command"); }

	if (cmd[0] == '"')
	{
		var split_pos = cmd.IndexOf('"', 1);
		if (split_pos == -1) { throw new ArgumentException("Unpaired quote in command"); }
		name = cmd.Substring(1, split_pos).Trim();
		args = cmd.Substring(split_pos + 1).Trim();
	}
	else
	{
		var split_pos = cmd.IndexOfAny(new char[] {' ', '\t'});
		if (split_pos == -1)
		{
			name = cmd;
		}
		else
		{
			name = cmd.Substring(0, split_pos);
			args = cmd.Substring(split_pos + 1).Trim();
		}
	}

	using var process = new Process();
	var stdout_buffer = new StringBuilder();
	var stderr_buffer = new StringBuilder();

	process.StartInfo.FileName = name;
	process.StartInfo.Arguments = args;
	process.StartInfo.UseShellExecute = false;
	process.StartInfo.RedirectStandardOutput = true;
	process.StartInfo.RedirectStandardError = true;
	process.OutputDataReceived += new DataReceivedEventHandler((sender, e) => { stdout_buffer.AppendLine(e.Data); });
	process.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => { stderr_buffer.AppendLine(e.Data); });

	process.Start();
	process.BeginOutputReadLine();
	process.BeginErrorReadLine();
	process.WaitForExit();

	stdout = stdout_buffer.ToString();
	stderr = stderr_buffer.ToString();
	return process.ExitCode;
}

int RunCmd(string cmd, out string stdout)
{
	return RunCmd(cmd, out stdout, out var stderr);
}

int RunCmd(string cmd)
{
	return RunCmd(cmd, out var stdout, out var stderr);
}

class DefineItem
{
	public string Prefix  = "";
	public string Name    = "";
	public string Space   = "";
	public string Value   = "";
	public string Ignored = "";
	public bool   Deleted = false;

	public bool IsDefine { get { return Name != ""; } }

	public DefineItem(string line)
	{
		Parse(line);
	}

	public DefineItem(string name, string value)
	{
		Prefix  = "#define ";
		Name    = name;
		Space   = " ";
		Value   = value;
		Ignored = "";
		Deleted = false;
	}

	public void Parse(string line)
	{
		var match = Regex.Match(line, @"^(\s*#define\s+)([A-Za-z0-9_]+)(?:(\s+)(.*))?$");

		if (match.Success)
		{
			Prefix  = match.Groups[1].Value;
			Name    = match.Groups[2].Value;
			Space   = match.Groups[3].Value;
			Value   = match.Groups[4].Value;
			Ignored = "";
		}
		else
		{
			Prefix  = "";
			Name    = "";
			Space   = "";
			Value   = "";
			Ignored = line;
		}

		Deleted = false;
	}

	public string ToString()
	{
		return Prefix + Name + (Name != "" && Space == "" && Value != "" ? " " : Space) + Value + Ignored;
	}
}

class DefineEditor : List<DefineItem>
{
	public string FileName { get; set; }
	public Encoding Encoding { get; protected set; }

	private bool IsUTF8(byte[] data)
	{
		try
		{
			new UTF8Encoding(false, true).GetCharCount(data);
			return true;
		}
		catch
		{
			return false;
		}
	}

	public DefineEditor(string filename = null) : base()
	{
		Load(filename);
	}

	public bool Load()
	{
		return Load(FileName);
	}

	bool Load(string filename)
	{
		Clear();

		FileName = filename;
		Encoding = new UTF8Encoding(false);

		if (filename == null || !File.Exists(filename))
		{
			return false;
		}

		byte[] FileData = File.ReadAllBytes(filename);
		Encoding = (IsUTF8(FileData)) ? new UTF8Encoding(false) : Encoding.Default;
		var DataReader = new StreamReader(new MemoryStream(FileData), Encoding);
		string line;
		while ((line = DataReader.ReadLine()) != null)
		{
			this.Add(new DefineItem(line));
		}

		return true;
	}

	public void Save()
	{
		Save(FileName);
	}

	public void Save(string filename)
	{
		var DataStream = new FileStream(filename, FileMode.Create, FileAccess.Write);
		var DataWriter = new StreamWriter(DataStream, Encoding);
		foreach (DefineItem item in this)
		{
			if (item.Deleted) { break; }
			DataWriter.WriteLine(item.ToString());
		}
		DataWriter.Close();
	}

	public string GetRaw(string name)
	{
		return this.LastOrDefault(item => !item.Deleted && item.Name == name)?.Value;
	}

	public string GetRaw(string name, string defval)
	{
		return GetRaw(name) ?? defval;
	}

	public bool UpdRaw(string name, string value)
	{
		var item = this.LastOrDefault(item => item.Name == name);
		if (item == null)
		{
			return false;
		}
		else
		{
			item.Value = value;
			item.Deleted = false;
			return true;
		}
	}

	public void SetRaw(string name, string value)
	{
		if (!this.UpdRaw(name, value))
		{
			this.Add(new DefineItem(name, value));
		}
	}

	public void Delete(string name)
	{
		this.FindAll(item => item.Name == name).ForEach(item => item.Deleted = true);
	}

	public int GetInt(string name, int defval)
	{
		var value = (GetRaw(name) ?? "").Trim();
		if (value == "") { return defval; }
		if (value.StartsWith("0x"))
		{
			return Int32.Parse(value.Substring(2), NumberStyles.AllowHexSpecifier);
		}
		else
		{
			return Int32.Parse(value);
		}
	}

	public void SetInt(string name, int value, bool hex = false)
	{
		SetRaw(name, hex ? ("0x" + value.ToString("X8")) : value.ToString());
	}

	public void UpdInt(string name, int value, bool hex = false)
	{
		UpdRaw(name, hex ? ("0x" + value.ToString("X8")) : value.ToString());
	}
}

try
{
	string output;

	if (RunCmd("where git") != 0)
	{
		throw new Error("Git not found");
	}

	if (FORCE_UPDATE)
	{
		Console.WriteLine("Updating build information...");
	}
	else
	{
		Console.WriteLine("Checking if there are any changes...");

		// Check if there are any changes in the working tree since last commit.

		int exitcode = RunCmd("git diff HEAD --exit-code --quiet");

		if (exitcode == 0)
		{
			throw new Success("No changes found");
		}

		if (exitcode != 1)
		{
			throw new Error("An issue with Git repo");
		}

		// Check if there are any changes since last build info update.

		if (RunCmd("git ls-files -z --cached --modified --other --exclude-standard --deduplicate", out output) != 0)
		{
			throw new Error("Can't list files in the repo");
		}

		var files = output.Split(new char[] {'\0'}, StringSplitOptions.RemoveEmptyEntries);
		var latest_change = DateTime.MinValue;

		foreach (var file in files)
		{
			if (!File.Exists(file)) { continue; }
			var LastWriteTime = new FileInfo(file).LastWriteTime;
			if (LastWriteTime > latest_change) { latest_change = LastWriteTime; }
		}

		var latest_update = File.Exists(BUILD_INFO_FILE) ? new FileInfo(BUILD_INFO_FILE).LastWriteTime : DateTime.MinValue;
		if (latest_update >= latest_change)
		{
			throw new Success("No new changes found");
		}

		Console.WriteLine("There are some changes. Updating build information...");
	}

	var build_info = new DefineEditor(BUILD_INFO_FILE);

	// Get expected version from Git tags.

	int rev_major = 0;
	int rev_minor = 0;
	int rev_patch = 0;
	int rev_extra = 0;

	if (RunCmd("git describe --tags --match \"v[0-9]*.[0-9]*.[0-9]*\" --abbrev=8 HEAD", out output) == 0)
	{
		// Get version from the latest revision tag.
		var match = Regex.Match(output.Trim().ToLower(), @"^v([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9]+)-g([a-f0-9]+))?$");
		if (match.Success)
		{
			rev_major = Int32.Parse(match.Groups[1].Value);
			rev_minor = Int32.Parse(match.Groups[2].Value);
			rev_patch = Int32.Parse(match.Groups[3].Value);
			rev_extra = match.Groups[4].Value != "" ? Int32.Parse(match.Groups[4].Value) : 0;
		}
		else
		{
			throw new Error("Cannot parse Git version tag");
		}
	}
	else
	{
		// No version tag found. Get just revision count.
		if (RunCmd("git rev-list --count HEAD", out output) == 0)
		{
			output = output.Trim();
			rev_extra = output != "" ? Int32.Parse(output) : 0;
		}
		else
		{
			throw new Error("Cannot get revision count from Git");
		}
	}

	rev_extra++;

	// Update version.

	var want_rev = new Version(rev_major, rev_minor, rev_patch, rev_extra);
	var curr_rev = new Version(build_info.GetInt("REV_MAJOR", 0), build_info.GetInt("REV_MINOR", 0), build_info.GetInt("REV_PATCH", 0), build_info.GetInt("REV_EXTRA", 0));

	if (want_rev > curr_rev)
	{
		build_info.SetInt("REV_MAJOR", rev_major);
		build_info.SetInt("REV_MINOR", rev_minor);
		build_info.SetInt("REV_PATCH", rev_patch);
		build_info.SetInt("REV_EXTRA", rev_extra);
		build_info.UpdInt("REV_BUILD", 0);
	}

	// Update version build counter.

	var rev_build = build_info.GetInt("REV_BUILD", 0);
	rev_build++;
	build_info.UpdInt("REV_BUILD", rev_build);

	// Update date.

	var rev_time = DateTime.Now;

	if (build_info.GetInt("REV_YEAR", 0) != rev_time.Year
		|| build_info.GetInt("REV_MONTH", 0) != rev_time.Month
		|| build_info.GetInt("REV_DAY", 0) != rev_time.Day)
	{
		build_info.SetInt("REV_YEAR", rev_time.Year);
		build_info.SetInt("REV_MONTH", rev_time.Month);
		build_info.SetInt("REV_DAY", rev_time.Day);
		build_info.UpdInt("REV_DAY_BUILD", 0);
	}

	// Update date build counter.

	var rev_day_build = build_info.GetInt("REV_DAY_BUILD", 0);
	rev_day_build++;
	build_info.UpdInt("REV_DAY_BUILD", rev_day_build);

	// Update time.

	build_info.UpdInt("REV_HOUR",      rev_time.Hour);
	build_info.UpdInt("REV_MINUTE",    rev_time.Minute);
	build_info.UpdInt("REV_SECOND",    rev_time.Second);
	build_info.UpdInt("REV_TIMESTAMP", (int) new DateTimeOffset(rev_time).ToUnixTimeSeconds());

	build_info.Save();
}
catch (Success ex)
{
	Console.WriteLine(ex.Message + ".");
	Environment.Exit(0);
}
catch (Error ex)
{
	if (NO_ERRORS)
	{
		Console.WriteLine($"Warning: {ex.Message}.");
		Environment.Exit(0);
	}
	else
	{
		Console.WriteLine($"Error: {ex.Message}.");
		Environment.Exit(1);
	}
}
catch (Exception ex)
{
	if (NO_ERRORS)
	{
		Console.WriteLine($"Warning {ex.GetType().Name}: {ex.Message}.");
		Environment.Exit(0);
	}
	else
	{
		Console.WriteLine($"Error {ex.GetType().Name}: {ex.Message}.");
		Environment.Exit(1);
	}
}


================================================
FILE: BuildInfo.hpp
================================================
#ifndef BUILDINFO_HPP
#define BUILDINFO_HPP

#define APP_NAME        "Sound Keeper"
#define APP_FILE_NAME   "SoundKeeper"
#define APP_COPYRIGHT   "(C) 2014-2025 Evgeny Vrublevsky <me@veg.by>"
#define APP_COMPANY     "Vegalogic Software"

#define REV_MAJOR       1
#define REV_MINOR       3
#define REV_PATCH       5
#define REV_EXTRA       0
#define REV_BUILD       1

#define REV_YEAR        2025
#define REV_MONTH       7
#define REV_DAY         5
#define REV_DAY_BUILD   1

#endif // BUILDINFO_HPP


================================================
FILE: BuildInfo.rc2
================================================
#include "BuildInfo.hpp"
#include <winres.h>

#define RAWSTR(x) #x
#define STR(x) RAWSTR(x)

VS_VERSION_INFO VERSIONINFO
	FILEVERSION     REV_YEAR, REV_MONTH, REV_DAY, REV_DAY_BUILD
	PRODUCTVERSION  REV_MAJOR, REV_MINOR, REV_PATCH, REV_EXTRA
	FILEFLAGSMASK   VS_FFI_FILEFLAGSMASK
#if defined(_DEBUG)
	FILEFLAGS       VS_FF_DEBUG|VS_FF_PRERELEASE
#else
	FILEFLAGS       0x0L
#endif
	FILEOS          VOS_NT_WINDOWS32
	FILETYPE        VFT_APP
	FILESUBTYPE     VFT2_UNKNOWN
BEGIN
	BLOCK "StringFileInfo"
	BEGIN
		BLOCK "000904b0" // LANG_ENGLISH = 0x0009, CP_UNICODE = 0x04b0 = 1200
		BEGIN
			VALUE "CompanyName",        APP_COMPANY
#if defined(_DEBUG)
			VALUE "FileDescription",    APP_NAME " (Debug " APP_ARCH ")"
#else
			VALUE "FileDescription",    APP_NAME " (" APP_ARCH ")"
#endif
			VALUE "FileVersion",        STR(REV_YEAR) "." STR(REV_MONTH) "." STR(REV_DAY) "." STR(REV_DAY_BUILD)
			VALUE "InternalName",       APP_FILE_NAME
			VALUE "LegalCopyright",     APP_COPYRIGHT
			VALUE "OriginalFilename",   APP_FILE_NAME ".exe"
			VALUE "ProductName",        APP_NAME
			VALUE "ProductVersion",     STR(REV_MAJOR) "." STR(REV_MINOR) "." STR(REV_PATCH) "." STR(REV_EXTRA)
		END
	END
	BLOCK "VarFileInfo"
	BEGIN
		VALUE "Translation", 0x0009, 1200
	END
END


================================================
FILE: CSoundKeeper.cpp
================================================
// Tell mmdeviceapi.h to define its GUIDs in this translation unit.
#define INITGUID

#include "CSoundKeeper.hpp"

//
// Get time to sleeping (in seconds). It is not precise and updated just once in 15-30 seconds!
// On Windows 7-10, it outputs -1 (or values like -16, -31, ...) when auto sleep mode is disabled.
// On Windows 10, it may output negative values (-30, -60, ...) when sleeping was postponed not by user input.
// On Windows 11, the TimeRemaining field is always 0, so it doesn't work for some reason.
//

EXTERN_C NTSYSCALLAPI NTSTATUS NTAPI NtPowerInformation(
	_In_ POWER_INFORMATION_LEVEL InformationLevel,
	_In_reads_bytes_opt_(InputBufferLength) PVOID InputBuffer,
	_In_ ULONG InputBufferLength,
	_Out_writes_bytes_opt_(OutputBufferLength) PVOID OutputBuffer,
	_In_ ULONG OutputBufferLength
);

uint32_t GetSecondsToSleeping()
{
	struct SYSTEM_POWER_INFORMATION {
		ULONG MaxIdlenessAllowed;
		ULONG Idleness;
		LONG TimeRemaining;
		UCHAR CoolingMode;
	} spi = {0};

	if (!NT_SUCCESS(NtPowerInformation(SystemPowerInformation, NULL, 0, &spi, sizeof(spi))))
	{
		DebugLogError("Cannot get remaining time to sleeping.");
		return 0;
	}

#ifdef _CONSOLE

	static LONG last_result = 0x80000000;

	if (last_result != spi.TimeRemaining)
	{
		last_result = spi.TimeRemaining;
		DebugLog("Remaining time to sleeping: %d seconds.", spi.TimeRemaining);
	}

#endif

	if (spi.TimeRemaining >= 0)
	{
		return spi.TimeRemaining;
	}

	return (spi.TimeRemaining % 5 == 0 ? 0 : -1);
}

//
// CSoundKeeper implementation.
//

CSoundKeeper::CSoundKeeper() { }
CSoundKeeper::~CSoundKeeper() { }

// IUnknown methods

ULONG STDMETHODCALLTYPE CSoundKeeper::AddRef()
{
	return InterlockedIncrement(&m_ref_count);
}

ULONG STDMETHODCALLTYPE CSoundKeeper::Release()
{
	ULONG result = InterlockedDecrement(&m_ref_count);
	if (result == 0)
	{
		delete this;
	}
	return result;
}

HRESULT STDMETHODCALLTYPE CSoundKeeper::QueryInterface(REFIID riid, VOID **ppvInterface)
{
	if (IID_IUnknown == riid)
	{
		AddRef();
		*ppvInterface = (IUnknown*)this;
	}
	else if (__uuidof(IMMNotificationClient) == riid)
	{
		AddRef();
		*ppvInterface = (IMMNotificationClient*)this;
	}
	else
	{
		*ppvInterface = NULL;
		return E_NOINTERFACE;
	}
	return S_OK;
}

// Callback methods for device-event notifications.
// WARNING: Don't use m_mutex, it may cause a deadlock when CSoundKeeper::Restart -> Stop is in progress.

HRESULT STDMETHODCALLTYPE CSoundKeeper::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id)
{
	DebugLog("Device '%S' is default for flow %d and role %d.", device_id ? device_id : L"", flow, role);
	if (m_cfg_device_type == KeepDeviceType::Primary && flow == eRender && role == eConsole)
	{
		this->FireRestart();
	}
	return S_OK;
}

HRESULT STDMETHODCALLTYPE CSoundKeeper::OnDeviceAdded(LPCWSTR device_id)
{
	DebugLog("Device '%S' was added.", device_id);
	if (m_cfg_device_type != KeepDeviceType::Primary)
	{
		this->FireRestart();
	}
	return S_OK;
};

HRESULT STDMETHODCALLTYPE CSoundKeeper::OnDeviceRemoved(LPCWSTR device_id)
{
	DebugLog("Device '%S' was removed.", device_id);
	if (m_cfg_device_type != KeepDeviceType::Primary)
	{
		this->FireRestart();
	}
	return S_OK;
}

HRESULT STDMETHODCALLTYPE CSoundKeeper::OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state)
{
	DebugLog("Device '%S' new state: %d.", device_id, new_state);
	if (new_state == DEVICE_STATE_ACTIVE)
	{
		this->FireRestart();
	}
	return S_OK;
}

HRESULT STDMETHODCALLTYPE CSoundKeeper::OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key)
{
	return S_OK;
}

// Main thread methods.

uint32_t GetDeviceFormFactor(IMMDevice* device)
{
	uint32_t formfactor = -1;

	IPropertyStore* properties = nullptr;
	HRESULT hr = device->OpenPropertyStore(STGM_READ, &properties);
	if (FAILED(hr))
	{
		DebugLogWarning("Unable to retrieve property store of an audio device: 0x%08X.", hr);
		return formfactor;
	}

	PROPVARIANT prop_formfactor;
	PropVariantInit(&prop_formfactor);
	hr = properties->GetValue(PKEY_AudioEndpoint_FormFactor, &prop_formfactor);
	if (SUCCEEDED(hr) && prop_formfactor.vt == VT_UI4)
	{
		formfactor = prop_formfactor.uintVal;
#ifdef _CONSOLE
		LPWSTR device_id = nullptr;
		hr = device->GetId(&device_id);
		if (FAILED(hr))
		{
			DebugLogWarning("Unable to get device ID: 0x%08X.", hr);
		}
		else
		{
			DebugLog("Device ID: '%S'. Form Factor: %d.", device_id, formfactor);
			CoTaskMemFree(device_id);
		}
#endif
	}
	else
	{
		DebugLogWarning("Unable to retrieve formfactor of an audio device: 0x%08X.", hr);
	}

	PropVariantClear(&prop_formfactor);
	SafeRelease(properties);

	return formfactor;
}

HRESULT CSoundKeeper::Start()
{
	ScopedLock lock(m_mutex);

	HRESULT hr = S_OK;
	if (m_is_started) { return hr; }
	m_is_retry_required = false;

	if (m_cfg_device_type == KeepDeviceType::Primary)
	{
		DebugLog("Getting primary audio device...");

		IMMDevice* device = nullptr;
		hr = m_dev_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
		if (FAILED(hr))
		{
			if (hr == E_NOTFOUND)
			{
				DebugLogWarning("No primary device found. Working as a dummy...");
				m_is_started = true;
				return hr;
			}
			else
			{
				DebugLogError("Unable to retrieve default render device: 0x%08X.", hr);
				return hr;
			}
		}
		defer [&] { device->Release(); };

		m_is_started = true;

		if (uint32_t formfactor = GetDeviceFormFactor(device); formfactor == -1)
		{
			return hr;
		}
		else if (!m_cfg_allow_remote && formfactor == RemoteNetworkDevice)
		{
			DebugLog("Ignoring remote desktop audio device.");
			return hr;
		}

		m_sessions_count = 1;
		m_sessions = new CSoundSession*[m_sessions_count]();

		m_sessions[0] = new CSoundSession(this, device);
		m_sessions[0]->SetStreamType(m_cfg_stream_type);
		m_sessions[0]->SetFrequency(m_cfg_frequency);
		m_sessions[0]->SetAmplitude(m_cfg_amplitude);
		m_sessions[0]->SetPeriodicPlaying(m_cfg_play_seconds);
		m_sessions[0]->SetPeriodicWaiting(m_cfg_wait_seconds);
		m_sessions[0]->SetFading(m_cfg_fade_seconds);

		if (!m_sessions[0]->Start())
		{
			m_is_retry_required = true;
		}
	}
	else
	{
		DebugLog("Enumerating active audio devices...");

		IMMDeviceCollection* dev_collection = nullptr;
		hr = m_dev_enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &dev_collection);
		if (FAILED(hr))
		{
			DebugLogError("Unable to retrieve device collection: 0x%08X.", hr);
			return hr;
		}
		defer [&] { dev_collection->Release(); };

		hr = dev_collection->GetCount(&m_sessions_count);
		if (FAILED(hr))
		{
			DebugLogError("Unable to get device collection length: 0x%08X.", hr);
			return hr;
		}

		m_is_started = true;

		m_sessions = new CSoundSession*[m_sessions_count]();

		for (UINT i = 0; i < m_sessions_count; i++)
		{
			m_sessions[i] = nullptr;

			IMMDevice* device = nullptr;
			if (dev_collection->Item(i, &device) != S_OK) { continue; }
			defer [&] { device->Release(); };

			if (uint32_t formfactor = GetDeviceFormFactor(device); formfactor == -1)
			{
				continue;
			}
			else if (!m_cfg_allow_remote && formfactor == RemoteNetworkDevice)
			{
				DebugLog("Ignoring remote desktop audio device.");
				continue;
			}
			else if ((m_cfg_device_type == KeepDeviceType::Digital || m_cfg_device_type == KeepDeviceType::Analog)
				&& (m_cfg_device_type == KeepDeviceType::Digital) != (formfactor == SPDIF || formfactor == HDMI))
			{
				DebugLog("Skipping this device because of the Digital / Analog filter.");
				continue;
			}

			m_sessions[i] = new CSoundSession(this, device);
			m_sessions[i]->SetStreamType(m_cfg_stream_type);
			m_sessions[i]->SetFrequency(m_cfg_frequency);
			m_sessions[i]->SetAmplitude(m_cfg_amplitude);
			m_sessions[i]->SetPeriodicPlaying(m_cfg_play_seconds);
			m_sessions[i]->SetPeriodicWaiting(m_cfg_wait_seconds);
			m_sessions[i]->SetFading(m_cfg_fade_seconds);

			if (!m_sessions[i]->Start())
			{
				m_is_retry_required = true;
			}
		}

		if (m_sessions_count == 0)
		{
			DebugLogWarning("No suitable devices found. Working as a dummy...");
		}
	}

	return hr;
}

bool CSoundKeeper::Retry()
{
	ScopedLock lock(m_mutex);

	if (!m_is_retry_required) return true;
	m_is_retry_required = false;

	if (m_sessions != nullptr)
	{
		for (UINT i = 0; i < m_sessions_count; i++)
		{
			if (m_sessions[i] != nullptr)
			{
				if (!m_sessions[i]->Start())
				{
					m_is_retry_required = true;
				}
			}
		}
	}

	return !m_is_retry_required;
}

HRESULT CSoundKeeper::Stop()
{
	ScopedLock lock(m_mutex);

	if (!m_is_started) return S_OK;

	if (m_sessions != nullptr)
	{
		for (UINT i = 0; i < m_sessions_count; i++)
		{
			if (m_sessions[i] != nullptr)
			{
				m_sessions[i]->Stop();
				m_sessions[i]->Release();
			}
		}
		delete m_sessions;
	}

	m_sessions = nullptr;
	m_sessions_count = 0;
	m_is_started = false;
	m_is_retry_required = false;
	return S_OK;
}

HRESULT CSoundKeeper::Restart()
{
	ScopedLock lock(m_mutex);

	HRESULT hr = S_OK;

	hr = this->Stop();
	if (FAILED(hr))
	{
		return hr;
	}

	hr = this->Start();
	if (FAILED(hr))
	{
		return hr;
	}

	return S_OK;
}

CSoundSession* CSoundKeeper::FindSession(LPCWSTR device_id)
{
	ScopedLock lock(m_mutex);

	for (UINT i = 0; i < m_sessions_count; i++)
	{
		if (m_sessions[i] == nullptr)
		{
			continue;
		}

		if (auto curr = m_sessions[i]->GetDeviceId(); curr && StringEquals(curr, device_id))
		{
			// Call AddRef()? Use ComPtr?
			return m_sessions[i];
		}
	}

	return nullptr;
}

// Fire main thread control events.

void CSoundKeeper::FireRetry()
{
	TraceLog("Fire Retry!");
	m_do_retry = true;
}

void CSoundKeeper::FireRestart()
{
	TraceLog("Fire Restart!");
	m_do_restart = true;
}

void CSoundKeeper::FireShutdown()
{
	TraceLog("Fire Shutdown!");
	m_do_shutdown = true;
}

// Entry point methods.

void CSoundKeeper::SetStreamTypeDefaults(KeepStreamType stream_type)
{
	m_cfg_stream_type = stream_type;

	m_cfg_frequency = 0.0;
	m_cfg_amplitude = 0.0;
	m_cfg_play_seconds = 0.0;
	m_cfg_wait_seconds = 0.0;
	m_cfg_fade_seconds = 0.0;

	switch (stream_type)
	{
		case KeepStreamType::Fluctuate:
			m_cfg_frequency = 50.0;
			break;
		case KeepStreamType::Sine:
			m_cfg_frequency = 1.0;
			[[fallthrough]];
		case KeepStreamType::WhiteNoise:
		case KeepStreamType::BrownNoise:
		case KeepStreamType::PinkNoise:
			m_cfg_amplitude = 0.01;
			m_cfg_fade_seconds = 0.1;
			[[fallthrough]];
		default:
			break;
	}
}

void CSoundKeeper::ParseStreamArgs(KeepStreamType stream_type, const char* args)
{
	this->SetStreamTypeDefaults(stream_type);

	char* p = (char*) args;
	while (*p)
	{
		if (*p == ' ' || *p == '\t' || *p == '-') { p++; }
		else if (*p == 'f' || *p == 'a' || *p == 'l' || *p == 'w' || *p == 't')
		{
			char type = *p;
			p++;
			while (*p == ' ' || *p == '\t' || *p == '=') { p++; }
			if (*p < '0' || '9' < *p) { break; }

			double value = strtod(p, &p);
			if (type == 'f')
			{
				this->SetFrequency(std::min(value, 96000.0));
			}
			else if (type == 'a')
			{
				this->SetAmplitude(std::min(value / 100.0, 1.0));
			}
			else if (type == 'l')
			{
				this->SetPeriodicPlaying(value);
			}
			else if (type == 'w')
			{
				this->SetPeriodicWaiting(value);
			}
			else if (type == 't')
			{
				this->SetFading(value);
			}
		}
		else
		{
			break;
		}
	}
}

void CSoundKeeper::ParseModeString(const char* args)
{
	char buf[MAX_PATH];
	strcpy_s(buf, args);
	_strlwr(buf);

	if (strstr(buf, "all"))     { this->SetDeviceType(KeepDeviceType::All); }
	if (strstr(buf, "analog"))  { this->SetDeviceType(KeepDeviceType::Analog); }
	if (strstr(buf, "digital")) { this->SetDeviceType(KeepDeviceType::Digital); }
	if (strstr(buf, "kill"))    { this->SetDeviceType(KeepDeviceType::None); }
	if (strstr(buf, "remote"))  { this->SetAllowRemote(true); }
	if (strstr(buf, "nosleep")) { this->SetNoSleep(true); }

	if (strstr(buf, "openonly"))
	{
		this->SetStreamType(KeepStreamType::None);
	}
	else if (strstr(buf, "zero") || strstr(buf, "null"))
	{
		this->SetStreamType(KeepStreamType::Zero);
	}
	else if (char* p = strstr(buf, "fluctuate"))
	{
		this->ParseStreamArgs(KeepStreamType::Fluctuate, p+9);
	}
	else if (char* p = strstr(buf, "sine"))
	{
		this->ParseStreamArgs(KeepStreamType::Sine, p+4);
	}
	else if (char* p = strstr(buf, "white"))
	{
		this->ParseStreamArgs(KeepStreamType::WhiteNoise, p+5);
	}
	else if (char* p = strstr(buf, "brown"))
	{
		this->ParseStreamArgs(KeepStreamType::BrownNoise, p+5);
	}
	else if (char* p = strstr(buf, "pink"))
	{
		this->ParseStreamArgs(KeepStreamType::PinkNoise, p+4);
	}
}

HRESULT CSoundKeeper::Run()
{
	// Windows 8-10 audio service leaks handles and shared memory when exclusive mode is used, enable a workaround.
	uint32_t nt_build_number = GetNtBuildNumber();
	bool is_leaky_wasapi = 7601 < nt_build_number && nt_build_number < 22000;
	DebugLog("Windows Build Number: %u%s.", nt_build_number, is_leaky_wasapi ? " (leaky WASAPI)" : "");
	CSoundSession::EnableWaitExclusiveWorkaround(is_leaky_wasapi);

	// Set defaults.
	this->SetDeviceType(KeepDeviceType::Primary);
	this->SetStreamTypeDefaults(KeepStreamType::Fluctuate);

	// Parse file name for defaults.
	char fn_buffer[MAX_PATH];
	DWORD fn_size = GetModuleFileNameA(NULL, fn_buffer, MAX_PATH);
	if (fn_size != 0 && fn_size != MAX_PATH)
	{
		char* filename = strrchr(fn_buffer, '\\');
		if (filename)
		{
			filename++;
			DebugLog("Exe File Name: %s.", filename);
			this->ParseModeString(filename);
		}
	}

	// Parse command line for arguments.
	if (const char* cmdln = GetCommandLineA())
	{
		// Skip program file name.
		while (*cmdln == ' ') { cmdln++; }
		if (cmdln[0] == '"')
		{
			cmdln++;
			while (*cmdln != '"' && *cmdln != 0) { cmdln++; }
			if (*cmdln == '"') { cmdln++; }
		}
		else
		{
			while (*cmdln != ' ' && *cmdln != 0) { cmdln++; }
		}
		while (*cmdln == ' ') { cmdln++; }

		if (*cmdln != 0)
		{
			DebugLog("Command Line: %s.", cmdln);
			this->ParseModeString(cmdln);
		}
	}

	if (GetSecondsToSleeping() == 0)
	{
		DebugLogWarning("Sleep timer informarion is not available. Sleep detection is disabled.");
		m_cfg_no_sleep = true;
	}

#ifdef _CONSOLE

	switch (this->GetDeviceType())
	{
		case KeepDeviceType::None:      DebugLog("Device Type: None."); break;
		case KeepDeviceType::Primary:   DebugLog("Device Type: Primary."); break;
		case KeepDeviceType::All:       DebugLog("Device Type: All."); break;
		case KeepDeviceType::Analog:    DebugLog("Device Type: Analog."); break;
		case KeepDeviceType::Digital:   DebugLog("Device Type: Digital."); break;
		default:                        DebugLogError("Unknown Device Type."); break;
	}

	switch (this->GetStreamType())
	{
		case KeepStreamType::None:      DebugLog("Stream Type: None (Open Only)."); break;
		case KeepStreamType::Zero:      DebugLog("Stream Type: Zero."); break;
		case KeepStreamType::Fluctuate: DebugLog("Stream Type: Fluctuate (Frequency: %.3fHz).", this->GetFrequency()); break;
		case KeepStreamType::Sine:      DebugLog("Stream Type: Sine (Frequency: %.3fHz; Amplitude: %.3f%%; Fading: %.3fs).", this->GetFrequency(), this->GetAmplitude() * 100.0, this->GetFading()); break;
		case KeepStreamType::WhiteNoise:DebugLog("Stream Type: White Noise (Amplitude: %.3f%%; Fading: %.3fs).", this->GetAmplitude() * 100.0, this->GetFading()); break;
		case KeepStreamType::BrownNoise:DebugLog("Stream Type: Brown Noise (Amplitude: %.3f%%; Fading: %.3fs).", this->GetAmplitude() * 100.0, this->GetFading()); break;
		case KeepStreamType::PinkNoise: DebugLog("Stream Type: Pink Noise (Amplitude: %.3f%%; Fading: %.3fs).", this->GetAmplitude() * 100.0, this->GetFading()); break;
		default:                        DebugLogError("Unknown Stream Type."); break;
	}

	if (this->GetPeriodicPlaying() || this->GetPeriodicWaiting())
	{
		DebugLog("Periodicity: Enabled (Length: %.3fs; Waiting: %.3fs).", this->GetPeriodicPlaying(), this->GetPeriodicWaiting());
	}
	else
	{
		DebugLog("Periodicity: Disabled.");
	}

	if (m_cfg_no_sleep)
	{
		DebugLog("Sleep Detection: Disabled.");
	}

#endif

	// Stop another instance.

	Handle global_stop_event = CreateEventA(NULL, TRUE, FALSE, "SoundKeeperStopEvent");
	if (!global_stop_event)
	{
		DWORD le = GetLastError();
		DebugLogError("Unable to open global stop event. Error code: %d.", le);
		return HRESULT_FROM_WIN32(le);
	}

	Handle global_mutex = CreateMutexA(NULL, TRUE, "SoundKeeperMutex");
	if (!global_mutex)
	{
		DWORD le = GetLastError();
		DebugLogError("Unable to open global mutex. Error code: %d.", le);
		return HRESULT_FROM_WIN32(le);
	}
	if (GetLastError() == ERROR_ALREADY_EXISTS)
	{
		DebugLog("Stopping another instance...");
		SetEvent(global_stop_event);
		bool is_timeout = WaitForOne(global_mutex, 1000) == WAIT_TIMEOUT;
		ResetEvent(global_stop_event);
		if (is_timeout)
		{
			DebugLogError("Time out.");
			return HRESULT_FROM_WIN32(WAIT_TIMEOUT);
		}
	}
	defer [&] { ReleaseMutex(global_mutex); };

	HRESULT hr = S_OK;

	if (m_cfg_device_type == KeepDeviceType::None)
	{
		DebugLog("Self kill mode is enabled. Exit.");
		return hr;
	}

	// Initialization.

	hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_dev_enumerator));
	if (FAILED(hr))
	{
		DebugLogError("Unable to instantiate device enumerator: 0x%08X.", hr);
		return hr;
	}
	defer [&] { m_dev_enumerator->Release(); };

	hr = m_dev_enumerator->RegisterEndpointNotificationCallback(this);
	if (FAILED(hr))
	{
		DebugLogError("Unable to register for stream switch notifications: 0x%08X.", hr);
		return hr;
	}
	defer [&] { m_dev_enumerator->UnregisterEndpointNotificationCallback(this); };

	// Working loop.

	DebugLog("Enter main loop.");

	for (bool working = true; working; )
	{
		uint32_t seconds_to_sleeping = (m_cfg_no_sleep ? -1 : GetSecondsToSleeping());

		if (seconds_to_sleeping == 0)
		{
			if (m_is_started)
			{
				DebugLog("Going to sleep...");
				this->Stop();
			}
		}
		else
		{
			if (!m_is_started)
			{
				DebugLog("Starting...");
				this->Start();
			}
			else if (m_is_retry_required)
			{
				DebugLog("Retrying...");
				this->Retry();
			}
		}

		DWORD timeout = (m_is_retry_required || seconds_to_sleeping <= 30) ? 500 : (m_cfg_no_sleep ? INFINITE : 5000);

		switch (WaitForAny({ m_do_retry, m_do_restart, m_do_shutdown, global_stop_event }, timeout))
		{
		case WAIT_TIMEOUT:

			break;

		case WAIT_OBJECT_0 + 0:

			// Prevent multiple retries.
			while (WaitForOne(m_do_retry, 500) != WAIT_TIMEOUT);
			m_is_retry_required = true;
			break;

		case WAIT_OBJECT_0 + 1:

			// Prevent multiple restarts.
			while (WaitForOne(m_do_restart, 500) != WAIT_TIMEOUT);
			DebugLog("Restarting...");
			this->Restart();
			break;

		case WAIT_OBJECT_0 + 2:
		case WAIT_OBJECT_0 + 3:
		default:

			// We're done, exit the loop.
			DebugLog("Shutdown.");
			working = false;
			break;
		}
	}

	DebugLog("Leave main loop.");
	Stop();
	return hr;
}

FORCEINLINE HRESULT CSoundKeeper::Main()
{
	DebugThreadName("Main");

	DebugLog("Enter main thread.");

	if (HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); FAILED(hr))
	{
#ifndef _CONSOLE
		MessageBoxA(0, "Cannot initialize COM.", "Sound Keeper", MB_ICONERROR | MB_OK | MB_SYSTEMMODAL);
#else
		DebugLogError("Cannot initialize COM: 0x%08X.", hr);
#endif
		return hr;
	}

	CSoundKeeper* keeper = new CSoundKeeper();
	HRESULT hr = keeper->Run();
	SafeRelease(keeper);

	CoUninitialize();

#ifndef _CONSOLE
	if (FAILED(hr))
	{
		MessageBoxA(0, "Cannot initialize WASAPI.", "Sound Keeper", MB_ICONERROR | MB_OK | MB_SYSTEMMODAL);
	}
#else
	if (hr == S_OK)
	{
		DebugLog("Leave main thread. Exit code: 0.");
	}
	else
	{
		DebugLog("Leave main thread. Exit code: 0x%08X.", hr);
	}
#endif

	return hr;
}

#ifdef _CONSOLE

int main()
{
	if (const VS_FIXEDFILEINFO* ffi = GetFixedVersion())
	{
		DebugLog("Sound Keeper v%hu.%hu.%hu.%hu [%04hu/%02hu/%02hu] (" APP_ARCH ")",
			HIWORD(ffi->dwProductVersionMS),
			LOWORD(ffi->dwProductVersionMS),
			HIWORD(ffi->dwProductVersionLS),
			LOWORD(ffi->dwProductVersionLS),
			HIWORD(ffi->dwFileVersionMS),
			LOWORD(ffi->dwFileVersionMS),
			HIWORD(ffi->dwFileVersionLS),
			LOWORD(ffi->dwFileVersionLS)
		);
	}

	return CSoundKeeper::Main();
}

#else

int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ PWSTR pCmdLine, _In_ int nCmdShow)
{
	return CSoundKeeper::Main();
}

#endif


================================================
FILE: CSoundKeeper.hpp
================================================
#pragma once

#include "Common.hpp"

#include <mmdeviceapi.h>
#include <audiopolicy.h>

enum class KeepDeviceType { None, Primary, Digital, Analog, All };
enum class KeepStreamType { None, Zero, Fluctuate, Sine, WhiteNoise, BrownNoise, PinkNoise };

class CSoundKeeper;

#include "CSoundSession.hpp"

class CSoundKeeper : public IMMNotificationClient
{
protected:

	LONG                    m_ref_count = 1;
	CriticalSection         m_mutex;

	~CSoundKeeper();

public:

	CSoundKeeper();

	// IUnknown methods

	ULONG STDMETHODCALLTYPE AddRef();
	ULONG STDMETHODCALLTYPE Release();
	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface);

	// Callback methods for device-event notifications.

	HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id);
	HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id);
	HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id);
	HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state);
	HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key);

protected:

	IMMDeviceEnumerator*    m_dev_enumerator = nullptr;
	bool                    m_is_started = false;
	bool                    m_is_retry_required = false;
	CSoundSession**         m_sessions = nullptr;
	UINT                    m_sessions_count = 0;
	AutoResetEvent          m_do_shutdown = false;
	AutoResetEvent          m_do_restart = false;
	AutoResetEvent          m_do_retry = false;

	bool                    m_cfg_allow_remote = false;
	bool                    m_cfg_no_sleep = false;
	KeepDeviceType          m_cfg_device_type = KeepDeviceType::Primary;
	KeepStreamType          m_cfg_stream_type = KeepStreamType::Zero;
	double                  m_cfg_frequency = 0.0;
	double                  m_cfg_amplitude = 0.0;
	double                  m_cfg_play_seconds = 0.0;
	double                  m_cfg_wait_seconds = 0.0;
	double                  m_cfg_fade_seconds = 0.0;

	HRESULT Start();
	HRESULT Stop();
	HRESULT Restart();
	bool Retry();
	CSoundSession* FindSession(LPCWSTR device_id);

public:

	void SetDeviceType(KeepDeviceType device_type) { m_cfg_device_type = device_type; }
	void SetStreamType(KeepStreamType stream_type) { m_cfg_stream_type = stream_type; }
	void SetAllowRemote(bool allow) { m_cfg_allow_remote = allow; }
	void SetNoSleep(bool value) { m_cfg_no_sleep = value; }
	KeepDeviceType GetDeviceType() const { return m_cfg_device_type; }
	KeepStreamType GetStreamType() const { return m_cfg_stream_type; }
	bool GetAllowRemote() const { return m_cfg_allow_remote; }
	bool GetNoSleep() const { return m_cfg_no_sleep; }

	// Configuration methods.
	void SetFrequency(double frequency) { m_cfg_frequency = frequency; }
	void SetAmplitude(double amplitude) { m_cfg_amplitude = amplitude; }
	void SetPeriodicPlaying(double seconds) { m_cfg_play_seconds = seconds; }
	void SetPeriodicWaiting(double seconds) { m_cfg_wait_seconds = seconds; }
	void SetFading(double seconds) { m_cfg_fade_seconds = seconds; }
	double GetFrequency() const { return m_cfg_frequency; }
	double GetAmplitude() const { return m_cfg_amplitude; }
	double GetPeriodicPlaying() const { return m_cfg_play_seconds; }
	double GetPeriodicWaiting() const { return m_cfg_wait_seconds; }
	double GetFading() const { return m_cfg_fade_seconds; }

	// Set stream type and defaults.
	void SetStreamTypeDefaults(KeepStreamType stream_type);

	void FireRetry();
	void FireRestart();
	void FireShutdown();

	void ParseStreamArgs(KeepStreamType stream_type, const char* args);
	void ParseModeString(const char* args);
	HRESULT Run();
	static HRESULT Main();
};


================================================
FILE: CSoundSession.cpp
================================================
#include "CSoundSession.hpp"

// Enable Multimedia Class Scheduler Service.
#define ENABLE_MMCSS

#ifdef ENABLE_MMCSS
#include <avrt.h>
#endif

bool CSoundSession::g_is_leaky_wasapi = false;

CSoundSession::CSoundSession(CSoundKeeper* soundkeeper, IMMDevice* endpoint)
	: m_soundkeeper(soundkeeper), m_endpoint(endpoint)
{
	m_endpoint->AddRef();
	m_soundkeeper->AddRef();

	if (HRESULT hr = m_endpoint->GetId(&m_device_id); FAILED(hr))
	{
		DebugLogError("Unable to get device ID: 0x%08X.", hr);
		m_curr_mode = RenderingMode::Invalid;
	}
}

CSoundSession::~CSoundSession(void)
{
	this->Stop();
	if (m_device_id) { CoTaskMemFree(m_device_id); }
	SafeRelease(m_endpoint);
	SafeRelease(m_soundkeeper);
}

HRESULT STDMETHODCALLTYPE CSoundSession::QueryInterface(REFIID iid, void **object)
{
	if (object == NULL)
	{
		return E_POINTER;
	}
	*object = NULL;

	if (iid == IID_IUnknown)
	{
		*object = static_cast<IUnknown *>(static_cast<IAudioSessionEvents *>(this));
		AddRef();
	}
	else if (iid == __uuidof(IAudioSessionEvents))
	{
		*object = static_cast<IAudioSessionEvents *>(this);
		AddRef();
	}
	else
	{
		return E_NOINTERFACE;
	}
	return S_OK;
}

ULONG STDMETHODCALLTYPE CSoundSession::AddRef()
{
	return InterlockedIncrement(&m_ref_count);
}

ULONG STDMETHODCALLTYPE CSoundSession::Release()
{
	ULONG result = InterlockedDecrement(&m_ref_count);
	if (result == 0)
	{
		delete this;
	}
	return result;
}

//
// Initialize and start the renderer.
bool CSoundSession::Start()
{
	ScopedLock lock(m_mutex);

	if (!this->IsValid()) { return false; }
	if (this->IsStarted()) { return true; }

	this->Stop();

	//
	// Now create the thread which is going to drive the renderer.
	m_render_thread = CreateThread(NULL, 0, StartRenderingThread, this, 0, NULL);
	if (m_render_thread == NULL)
	{
		DebugLogError("Unable to create rendering thread: 0x%08X.", GetLastError());
		return false;
	}

	// Wait until rendering is started.
	if (WaitForAny({ m_is_started, m_render_thread }, INFINITE) != WAIT_OBJECT_0)
	{
		DebugLogError("Unable to start rendering.");
		this->Stop();
		return false;
	}

	return true;
}

//
// Stop the renderer and free all the resources.
void CSoundSession::Stop()
{
	ScopedLock lock(m_mutex);

	if (m_render_thread)
	{
		this->DeferNextMode(RenderingMode::Stop);
		WaitForOne(m_render_thread, INFINITE);
		CloseHandle(m_render_thread);
		m_render_thread = NULL;
		this->ResetCurrent();
		m_interrupt = false;
	}
}

//
// Rendering thread.
//

DWORD APIENTRY CSoundSession::StartRenderingThread(LPVOID context)
{
	DebugThreadName("Rendering");

	CSoundSession* renderer = static_cast<CSoundSession*>(context);

	DebugLog("Enter rendering thread. Device ID: '%S'.", renderer->GetDeviceId());

	if (HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); FAILED(hr))
	{
		DebugLogError("Unable to initialize COM in rendering thread: 0x%08X.", hr);
		return 1;
	}

#ifdef ENABLE_MMCSS
	HANDLE mmcss_handle = NULL;
	DWORD mmcss_task_index = 0;
	mmcss_handle = AvSetMmThreadCharacteristics(L"Audio", &mmcss_task_index);
	if (mmcss_handle == NULL)
	{
		DebugLogError("Unable to enable MMCSS on rendering thread: 0x%08X.", GetLastError());
	}
#endif

	DWORD result = renderer->RenderingThread();

#ifdef ENABLE_MMCSS
	if (mmcss_handle != NULL) { AvRevertMmThreadCharacteristics(mmcss_handle); }
#endif

	CoUninitialize();

	DebugLog("Leave rendering thread. Return code: %d.", result);
	return result;
}

DWORD CSoundSession::RenderingThread()
{
	m_is_started = true;
	m_curr_mode = RenderingMode::Rendering;
	m_play_attempts = 0;
	m_wait_attempts = 0;

	DWORD delay = 0;
	bool loop = true;

	while (loop)
	{
		DebugLog("Rendering thread mode: %d. Delay: %d.", m_curr_mode, delay);

		switch (WaitForOne(m_interrupt, delay))
		{
		case WAIT_OBJECT_0:

			DebugLog("Set new rendering thread mode: %d.", m_next_mode);
			m_curr_mode = m_next_mode;
			break;

		case WAIT_TIMEOUT:

			break;

		default:

			// Shouldn't happen.
			m_curr_mode = RenderingMode::Invalid;
			break;
		}
		delay = 0;

		switch (m_curr_mode)
		{
		case RenderingMode::Rendering:

			DebugLog("Render. Device State: %d.", this->GetDeviceState());
			m_curr_mode = this->Rendering();
			if (g_is_leaky_wasapi && m_curr_mode == RenderingMode::WaitExclusive)
			{
				delay = 30;
			}
			break;

		case RenderingMode::TryOpenDevice:

			DebugLog("Try to open the device. Device State: %d.", this->GetDeviceState());
			m_curr_mode = this->TryOpenDevice();
			if (g_is_leaky_wasapi && m_curr_mode == RenderingMode::WaitExclusive)
			{
				delay = 30;
			}
			break;

		case RenderingMode::WaitExclusive:

			if (g_is_leaky_wasapi)
			{
				DebugLog("Wait until exclusive session is finised.");
				m_curr_mode = this->WaitExclusive();
				if (m_curr_mode == RenderingMode::WaitExclusive)
				{
					delay = 500;
				}
				break;
			}
			[[fallthrough]];

		case RenderingMode::Retry:

			if (g_is_leaky_wasapi && m_play_attempts > 1000)
			{
				DebugLog("Attempts limit. Stop.");
				m_curr_mode = RenderingMode::Stop;
				break;
			}

			// m_play_attempts is 0 when it was interrupted while playing (when rendering was initialized without errors).
			delay = (m_play_attempts == 0 ? 100UL : 1000UL);

			DebugLog("Retry in %dms. Attempt: #%d.", delay, m_play_attempts);
			m_curr_mode = g_is_leaky_wasapi ? RenderingMode::TryOpenDevice : RenderingMode::Rendering;
			break;

		// case RenderingMode::Stop:
		// case RenderingMode::Invalid:
		default:

			loop = false;
			break;
		}
	}

	m_is_started = false;
	return m_curr_mode == RenderingMode::Invalid;
}

CSoundSession::RenderingMode CSoundSession::TryOpenDevice()
{
	HRESULT hr;

	DebugLog("Testing if the audio output is unused...");

	m_play_attempts++;

	// Now activate an IAudioClient object on our preferred endpoint.
	hr = m_endpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void**>(&m_audio_client));
	if (FAILED(hr))
	{
		DebugLogError("Unable to activate audio client: 0x%08X.", hr);
		return RenderingMode::Invalid;
	}
	defer[&]{ SafeRelease(m_audio_client); };

	// Use the smallest possible buffer format for testing.
	WAVEFORMATEX mix_format = { WAVE_FORMAT_PCM };
	mix_format.nChannels = 1;
	mix_format.nSamplesPerSec = 8000;
	mix_format.wBitsPerSample = 8;
	mix_format.nBlockAlign = mix_format.nChannels * ((mix_format.wBitsPerSample + 7) / 8);
	mix_format.nAvgBytesPerSec = mix_format.nSamplesPerSec * mix_format.nBlockAlign;

	// Initialize WASAPI in timer driven mode.
	hr = m_audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
		AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM,
		static_cast<UINT64>(0) * 10000, 0, &mix_format, NULL);

	if (FAILED(hr))
	{
		// According to the WASAPI docs, AUDCLNT_E_DEVICE_IN_USE (0x8889000A) should be returned when a device is busy.
		// It's true when WASAPI exclusive is used. But when ASIO is used, HRESULT of ERROR_BUSY (0x800700AA) is returned.
		if (hr == AUDCLNT_E_DEVICE_IN_USE || hr == HRESULT_FROM_WIN32(ERROR_BUSY))
		{
			DebugLogWarning("Unable to initialize audio client: 0x%08X (device is being used in exclusive mode).", hr);
			return RenderingMode::WaitExclusive;
		}
		else
		{
			DebugLogError("Unable to initialize audio client: 0x%08X.", hr);
			return RenderingMode::Invalid;
		}
	}

	return RenderingMode::Rendering;
}

CSoundSession::RenderingMode CSoundSession::Rendering()
{
	RenderingMode exit_mode;
	HRESULT hr;

	m_play_attempts++;

	// -------------------------------------------------------------------------
	// Rendering Init
	// -------------------------------------------------------------------------

	// Any errors below should invalidate this session.
	exit_mode = RenderingMode::Invalid;

	//
	// Now activate an IAudioClient object on our preferred endpoint.
	hr = m_endpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&m_audio_client));
	if (FAILED(hr))
	{
		DebugLogError("Unable to activate audio client: 0x%08X.", hr);
		return exit_mode;
	}
	defer [&] { SafeRelease(m_audio_client); };

	{
		// Get output format. Don't rely on it much since WASAPI reporting is not always accurate:
		// 24-bit compressed formats are reported as 16-bit; PCM int24 is reported as PCM int32.
		DebugLog("Getting output format...");
		IPropertyStore* properties = nullptr;
		hr = m_endpoint->OpenPropertyStore(STGM_READ, &properties);
		if (SUCCEEDED(hr))
		{
			PROPVARIANT out_format_prop;
			PropVariantInit(&out_format_prop);
			hr = properties->GetValue(PKEY_AudioEngine_DeviceFormat, &out_format_prop);
			if (SUCCEEDED(hr) && out_format_prop.vt == VT_BLOB)
			{
				auto out_format = reinterpret_cast<WAVEFORMATEX*>(out_format_prop.blob.pBlobData);
				m_out_sample_type = ParseSampleType(out_format);
			}
			else
			{
				DebugLogWarning("Unable to get output format of the device: 0x%08X.", hr);
			}
			PropVariantClear(&out_format_prop);
			SafeRelease(properties);
		}
		else
		{
			DebugLogWarning("Unable to get property store of the device: 0x%08X.", hr);
		}
	}

	{
		// Get mixer format. This is always float32.
		DebugLog("Getting mixing format...");
		WAVEFORMATEX* mix_format = nullptr;
		hr = m_audio_client->GetMixFormat(&mix_format);
		if (FAILED(hr))
		{
			DebugLogError("Unable to get mixing format on audio client: 0x%08X.", hr);
			return exit_mode;
		}
		defer [&] { CoTaskMemFree(mix_format); };

		m_mix_sample_type = ParseSampleType(mix_format);
		if (m_mix_sample_type != SampleType::Float32)
		{
			DebugLogError("Mixing format is not 32-bit float that is not supported.");
			return exit_mode;
		}

		m_channels_count = mix_format->nChannels;
		m_frame_size = mix_format->nBlockAlign;

		// Noise generation works best with the 48000Hz sample rate.
		if (m_stream_type == KeepStreamType::WhiteNoise || m_stream_type == KeepStreamType::BrownNoise || m_stream_type == KeepStreamType::PinkNoise)
		{
			DebugLog("Using 48000Hz sample rate for noise generation.");
			mix_format->nSamplesPerSec = 48000;
			mix_format->nAvgBytesPerSec= mix_format->nSamplesPerSec * mix_format->nBlockAlign;
		}

		m_sample_rate = mix_format->nSamplesPerSec;

		// Use smaller buffer if leaky WASAPI.
		// Rendering loop relies on not precise enough system timer so minimum viable buffer is 40ms.
		m_buffer_size_in_ms = g_is_leaky_wasapi ? 100 : 1000;

		// Initialize WASAPI in timer driven mode.
		hr = m_audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
			AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM /*| AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY*/,
			static_cast<UINT64>(m_buffer_size_in_ms) * 10000, 0, mix_format, NULL);
	}

	if (FAILED(hr))
	{
		// According to the WASAPI docs, AUDCLNT_E_DEVICE_IN_USE (0x8889000A) should be returned when a device is busy.
		// It's true when WASAPI exclusive is used. But when ASIO is used, HRESULT of ERROR_BUSY (0x800700AA) is returned.
		if (hr == AUDCLNT_E_DEVICE_IN_USE || hr == HRESULT_FROM_WIN32(ERROR_BUSY))
		{
			DebugLogWarning("Unable to initialize audio client: 0x%08X (device is being used in exclusive mode).", hr);
			exit_mode = RenderingMode::WaitExclusive;
		}
		else
		{
			DebugLogError("Unable to initialize audio client: 0x%08X.", hr);
			// exit_mode = RenderingMode::Invalid;
		}

		return exit_mode;
	}

	// Retry on any errors below.
	exit_mode = RenderingMode::Retry;

	//
	// Retrieve the buffer size for the audio client.
	hr = m_audio_client->GetBufferSize(&m_buffer_size_in_frames);
	if (FAILED(hr))
	{
		DebugLogError("Unable to get audio client buffer: 0x%08X.", hr);
		return exit_mode;
	}

	m_buffer_size_in_ms = (m_buffer_size_in_frames * 1000) / m_sample_rate;
	DebugLog("Output buffer: %ums (%u frames).", m_buffer_size_in_ms, m_buffer_size_in_frames);

	hr = m_audio_client->GetService(IID_PPV_ARGS(&m_render_client));
	if (FAILED(hr))
	{
		DebugLogError("Unable to get new render client: 0x%08X.", hr);
		return exit_mode;
	}
	defer [&] { m_render_client->Release(); };

	//
	// Register for session and endpoint change notifications.  
	hr = m_audio_client->GetService(IID_PPV_ARGS(&m_audio_session_control));
	if (FAILED(hr))
	{
		DebugLogError("Unable to retrieve session control: 0x%08X.", hr);
		return exit_mode;
	}
	defer [&] { m_audio_session_control->Release(); };
	hr = m_audio_session_control->RegisterAudioSessionNotification(this);
	if (FAILED(hr))
	{
		DebugLogError("Unable to register for stream switch notifications: 0x%08X.", hr);
		return exit_mode;
	}
	defer [&] { m_audio_session_control->UnregisterAudioSessionNotification(this); };

	// -------------------------------------------------------------------------
	// Rendering Loop
	// -------------------------------------------------------------------------

	DebugLog("Starting rendering...");

	// We need to pre-roll one buffer of data into the pipeline before starting.
	hr = this->Render();
	if (FAILED(hr))
	{
		DebugLogError("Can't render initial buffer: 0x%08X.", hr);
		return exit_mode;
	}

	hr = m_audio_client->Start();
	if (FAILED(hr))
	{
		DebugLogError("Unable to start render client: 0x%08X.", hr);
		return exit_mode;
	}

	DebugLog("Enter rendering loop.");

	m_play_attempts = 0;

	DWORD timeout = m_stream_type == KeepStreamType::None ? INFINITE : (m_buffer_size_in_ms / 2 + m_buffer_size_in_ms / 4);
	for (bool working = true; working; ) switch (WaitForOne(m_interrupt, timeout))
	{
	case WAIT_TIMEOUT: // Timeout.

		// Provide the next buffer of samples.
		hr = this->Render();
		if (FAILED(hr))
		{
			working = false;
		}
		break;

	case WAIT_OBJECT_0 + 0: // m_interrupt.

		// We're done, exit the loop.
		exit_mode = m_next_mode;
		working = false;
		break;

	default:

		// Should never happen.
		exit_mode = RenderingMode::Invalid;
		working = false;
		break;
	}

	DebugLog("Leave rendering loop. Stopping audio client...");

	m_audio_client->Stop();
	return exit_mode;
}

CSoundSession::SampleType CSoundSession::ParseSampleType(WAVEFORMATEX* format)
{
	SampleType result = SampleType::Unknown;

	if (format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT
		|| (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && reinterpret_cast<WAVEFORMATEXTENSIBLE*>(format)->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
	{
		DebugLog("Format: PCM %dch %dHz %d-bit float.", format->nChannels, format->nSamplesPerSec, format->wBitsPerSample);
		if (format->wBitsPerSample == 32)
		{
			result = SampleType::Float32;
		}
		else
		{
			DebugLogWarning("Unsupported PCM float sample type.");
		}
	}
	else if (format->wFormatTag == WAVE_FORMAT_PCM
		|| format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && reinterpret_cast<WAVEFORMATEXTENSIBLE*>(format)->SubFormat == KSDATAFORMAT_SUBTYPE_PCM)
	{
		DebugLog("Format: PCM %dch %dHz %d-bit integer.", format->nChannels, format->nSamplesPerSec, format->wBitsPerSample);
		if (format->wBitsPerSample == 16)
		{
			result = SampleType::Int16;
		}
		else if (format->wBitsPerSample == 24)
		{
			result = SampleType::Int24;
		}
		else if (format->wBitsPerSample == 32)
		{
			result = SampleType::Int32;
		}
		else
		{
			DebugLogWarning("Unsupported PCM integer sample type.");
		}
	}
	else
	{
#ifdef _CONSOLE
		if (format->wFormatTag != WAVE_FORMAT_EXTENSIBLE)
		{
			DebugLogWarning("Unrecognized format: 0x%04hX %dch %dHz %d-bit.", format->wFormatTag,
				format->nChannels, format->nSamplesPerSec, format->wBitsPerSample);
		}
		else
		{
			GUID& guid = reinterpret_cast<WAVEFORMATEXTENSIBLE*>(format)->SubFormat;
			DebugLogWarning("Unrecognized format: {%08lx-%04hx-%04hx-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx} %dch %dHz %d-bit.",
				guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1],
				guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7],
				format->nChannels, format->nSamplesPerSec, format->wBitsPerSample);
		}
#endif
	}

	return result;
}

HRESULT CSoundSession::Render()
{
	HRESULT hr = S_OK;

	TraceLog("Render.");

	if (m_stream_type == KeepStreamType::None)
	{
		return hr;
	}

	// Find out how much of the buffer *isn't* available (is padding).
	UINT32 padding = 0;
	hr = m_audio_client->GetCurrentPadding(&padding);
	if (FAILED(hr))
	{
		DebugLogError("Failed to get padding: 0x%08X.", hr);
		return hr;
	}

	// Calculate the number of frames available. It can be 0 right after waking PC up after sleeping.
	UINT32 need_frames = m_buffer_size_in_frames - padding;
	if (need_frames == 0)
	{
		DebugLogWarning("None samples were consumed. Was PC sleeping?");
		return S_OK;
	}

	BYTE* p_data;
	hr = m_render_client->GetBuffer(need_frames, &p_data);
	if (FAILED(hr))
	{
		DebugLogError("Failed to get buffer: 0x%08X.", hr);
		return hr;
	}

	// Generate sound.

	DWORD render_flags = NULL;

	uint64_t play_frames = static_cast<uint64_t>(m_play_seconds * m_sample_rate);
	uint64_t wait_frames = static_cast<uint64_t>(m_wait_seconds * m_sample_rate);
	uint64_t fade_frames = static_cast<uint64_t>(m_fade_seconds * m_sample_rate);

	if (!wait_frames && !fade_frames)
	{
		play_frames = 0;
	}
	else if (!play_frames)
	{
		wait_frames = 0;
	}

	if (play_frames)
	{
		fade_frames = std::min(fade_frames, play_frames / 2);
	}

	uint64_t period_frames = play_frames + wait_frames;

	if (period_frames && play_frames <= m_curr_frame && (m_curr_frame + need_frames) <= period_frames)
	{
		// Just silence whole time.
		render_flags = AUDCLNT_BUFFERFLAGS_SILENT;
		m_curr_frame = (m_curr_frame + need_frames) % period_frames;
	}
	else if (m_stream_type == KeepStreamType::Fluctuate && m_frequency)
	{
		uint64_t once_in_frames = std::max(uint64_t(double(m_sample_rate) / m_frequency), 2ULL);

		for (size_t i = 0; i < need_frames; i++)
		{
			uint32_t sample = 0;

			if ((!period_frames || m_curr_frame < play_frames) && m_curr_frame % once_in_frames == 0)
			{
				// 0x38000100 = 3.051851E-5 = 1.0/32767. Minimal 16-bit deviation from 0.
				// 0x34000001 = 1.192093E-7 = 1.0/8388607. Minimal 24-bit deviation from 0.
				sample = (m_out_sample_type == SampleType::Int16) ? 0x38000100 : 0x34000001;

				// Negate each odd time.
				if ((m_curr_frame / once_in_frames) & 1)
				{
					sample |= 0x80000000;
				}
			}

			for (size_t j = 0; j < m_channels_count; j++)
			{
				*reinterpret_cast<uint32_t*>(p_data + j * sizeof(float)) = sample;
			}

			p_data += m_frame_size;
			m_curr_frame++;
			if (period_frames) { m_curr_frame %= period_frames; }
		}
	}
	else if (m_stream_type == KeepStreamType::Sine && m_frequency && m_amplitude)
	{
		double theta_increment = (std::min(m_frequency, m_sample_rate / 2.0) * (M_PI*2)) / double(m_sample_rate);

		for (size_t i = 0; i < need_frames; i++)
		{
			double amplitude = m_amplitude;

			if (period_frames || m_curr_frame < fade_frames)
			{
				if (m_curr_frame < fade_frames)
				{
					// Fade in.
					double fade_volume = (1.0 / fade_frames) * m_curr_frame;
					amplitude *= pow(fade_volume, 2);
				}
				else if (!play_frames || m_curr_frame < (play_frames - fade_frames))
				{
					// Max volume.
				}
				else if (m_curr_frame < play_frames)
				{
					// Fade out.
					double fade_volume = (1.0 / fade_frames) * (play_frames - m_curr_frame);
					amplitude *= pow(fade_volume, 2);
				}
				else
				{
					// Silence.
					amplitude = 0;
				}
			}

			float sample = 0;

			if (amplitude)
			{
				sample = float(sin(m_curr_theta) * amplitude);
				m_curr_theta += theta_increment;
			}

			for (size_t j = 0; j < m_channels_count; j++)
			{
				*reinterpret_cast<float*>(p_data + j * sizeof(float)) = sample;
			}

			p_data += m_frame_size;
			m_curr_frame++;
			if (period_frames) { m_curr_frame %= period_frames; }
		}
	}
	else if ((m_stream_type == KeepStreamType::WhiteNoise || m_stream_type == KeepStreamType::BrownNoise || m_stream_type == KeepStreamType::PinkNoise) && m_amplitude)
	{
		uint64_t lcg_state = GetTickCount64();

		for (size_t i = 0; i < need_frames; i++)
		{
			double amplitude = m_amplitude;

			if (period_frames || m_curr_frame < fade_frames)
			{
				if (m_curr_frame < fade_frames)
				{
					// Fade in.
					double fade_volume = (1.0 / fade_frames) * m_curr_frame;
					amplitude *= pow(fade_volume, 2);
				}
				else if (!play_frames || m_curr_frame < (play_frames - fade_frames))
				{
					// Max volume.
				}
				else if (m_curr_frame < play_frames)
				{
					// Fade out.
					double fade_volume = (1.0 / fade_frames) * (play_frames - m_curr_frame);
					amplitude *= pow(fade_volume, 2);
				}
				else
				{
					// Silence.
					amplitude = 0;
				}
			}

			float sample = 0;

			if (amplitude)
			{
				lcg_state = lcg_state * 6364136223846793005ULL + 1; // LCG from Musl.
				double value = (double((lcg_state >> 32) & 0x7FFFFFFF) / double(0x7FFFFFFFU)) * 2.0 - 1.0; // -1 .. 1

				if (m_stream_type == KeepStreamType::BrownNoise)
				{
					// Brown Noise from SoX + a leaky integrator to reduce low frequency humming.
					m_curr_value += value * (1.0 / 16);
					m_curr_value /= 1.02; // The leaky integrator.
					m_curr_value = fmod(m_curr_value, 4);
					value = m_curr_value;

					// Normalize values out of the -1..1 range using "mirroring".
					// Example: 0.8, 0.9, 1.0, 0.9, 0.8, ..., -0.8, -0.9, -1.0, -0.9, -0.8, ...
					// Precondition: value must be between -4.0 and 4.0.
					if (value < -1.0 || 1.0 < value)
					{
						double sign = (value < 0.0) ? -1.0 : 1.0;
						value = fabs(value);
						value = ((value <= 3.0) ? (2.0 - value) : (value - 4.0)) * sign;
					}
				}
				else if (m_stream_type == KeepStreamType::PinkNoise)
				{
					// Paul Kellet's method.
					double white = value;
					m_curr_state[0] = 0.99886 * m_curr_state[0] + white * 0.0555179;
					m_curr_state[1] = 0.99332 * m_curr_state[1] + white * 0.0750759;
					m_curr_state[2] = 0.96900 * m_curr_state[2] + white * 0.1538520;
					m_curr_state[3] = 0.86650 * m_curr_state[3] + white * 0.3104856;
					m_curr_state[4] = 0.55000 * m_curr_state[4] + white * 0.5329522;
					m_curr_state[5] = -0.7616 * m_curr_state[5] - white * 0.0168980;
					value = m_curr_state[0] + m_curr_state[1] + m_curr_state[2] + m_curr_state[3] + m_curr_state[4] + m_curr_state[5] + m_curr_state[6] + white * 0.5362;
					value *= 0.11; // (roughly) compensate for gain.
					m_curr_state[6] = white * 0.115926;
				}

				sample = float(value * amplitude);
			}

			for (size_t j = 0; j < m_channels_count; j++)
			{
				*reinterpret_cast<float*>(p_data + j * sizeof(float)) = sample;
			}

			p_data += m_frame_size;
			m_curr_frame++;
			if (period_frames) { m_curr_frame %= period_frames; }
		}
	}
	else
	{
		// ZeroMemory(p_data, static_cast<SIZE_T>(m_frame_size) * need_frames);
		render_flags = AUDCLNT_BUFFERFLAGS_SILENT;
	}

	hr = m_render_client->ReleaseBuffer(need_frames, render_flags);
	if (FAILED(hr))
	{
		DebugLogError("Failed to release buffer: 0x%08X.", hr);
		return hr;
	}

	return S_OK;
}

CSoundSession::RenderingMode CSoundSession::WaitExclusive()
{
	RenderingMode exit_mode;
	HRESULT hr;

	// Any errors below should invalidate this session.
	exit_mode = RenderingMode::Invalid;

	IAudioSessionManager2* as_manager = nullptr;
	hr = m_endpoint->Activate(__uuidof(IAudioSessionManager2), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void**>(&as_manager));
	if (FAILED(hr))
	{
		DebugLogError("Unable to activate audio session manager: 0x%08X.", hr);
		return exit_mode;
	}
	defer [&] { as_manager->Release(); };

	IAudioSessionEnumerator* session_list = nullptr;
	hr = as_manager->GetSessionEnumerator(&session_list);
	if (FAILED(hr))
	{
		DebugLogError("Unable to get session enumerator: 0x%08X.", hr);
		return exit_mode;
	}
	defer [&] { session_list->Release(); };

	int session_count = 0;
	hr = session_list->GetCount(&session_count);
	if (FAILED(hr))
	{
		DebugLogError("Unable to get session count: 0x%08X.", hr);
		return exit_mode;
	}

	// Retry on any errors below.
	exit_mode = RenderingMode::Retry;

	IAudioSessionControl* session_control = nullptr;
	defer [&] { SafeRelease(session_control); };

	// Find active session on this device.
	for (int index = 0 ; index < session_count ; index++)
	{
		hr = session_list->GetSession(index, &session_control);
		if (FAILED(hr))
		{
			DebugLogError("Unable to get session #%d: 0x%08X.", index, hr);
			return exit_mode;
		}

		AudioSessionState state = AudioSessionStateInactive;
		hr = session_control->GetState(&state);
		if (FAILED(hr))
		{
			DebugLogError("Unable to get session #%d state: 0x%08X.", index, hr);
			return exit_mode;
		}

		if (state != AudioSessionStateActive)
		{
			SafeRelease(session_control);
		}
		else
		{
			break;
		}
	}

	if (!session_control)
	{
		if (++m_wait_attempts < 100)
		{
			DebugLog("No active sessions found, try again.");
			exit_mode = RenderingMode::WaitExclusive;
		}
		else
		{
			DebugLog("No active sessions found, tried too many times. Try to play.");
			m_wait_attempts = 0;
			exit_mode = RenderingMode::Retry;
		}
	}
	else
	{
		m_wait_attempts = 0;
		hr = session_control->RegisterAudioSessionNotification(this);
		if (FAILED(hr))
		{
			DebugLogError("Unable to register for stream switch notifications: 0x%08X.", hr);
			return exit_mode;
		}

		// Wait until we receive a notification that the streem is inactive.
		switch (WaitForOne(m_interrupt))
		{
		case WAIT_OBJECT_0: // m_interrupt.

			// We're done, exit the loop.
			exit_mode = m_next_mode;
			break;

		default:

			// Should never happen.
			exit_mode = RenderingMode::Invalid;
			break;
		}

		session_control->UnregisterAudioSessionNotification(this);
	}

	return exit_mode;
}

//
// Called when state of an audio session is changed.
HRESULT CSoundSession::OnStateChanged(AudioSessionState NewState)
{
	if (m_curr_mode != RenderingMode::WaitExclusive)
	{
		return S_OK;
	}

	// On stop, it becomes AudioSessionStateInactive (0), and then AudioSessionStateExpired (2).
	DebugLog("State of the exclusive mode session is changed: %d.", NewState);
	this->DeferNextMode(RenderingMode::Retry);
	return S_OK;
};

//
// Called when an audio session is disconnected.
HRESULT CSoundSession::OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason)
{
	if (m_curr_mode != RenderingMode::Rendering)
	{
		return S_OK;
	}

	switch (DisconnectReason)
	{
	case DisconnectReasonFormatChanged:

		DebugLog("Session is disconnected with reason %d. Retry.", DisconnectReason);
		this->DeferNextMode(RenderingMode::Retry);
		break;

	case DisconnectReasonExclusiveModeOverride:

		DebugLog("Session is disconnected with reason %d. WaitExclusive.", DisconnectReason);
		this->DeferNextMode(RenderingMode::WaitExclusive);
		break;

	default:

		DebugLog("Session is disconnected with reason %d. Restart.", DisconnectReason);
		this->DeferNextMode(RenderingMode::Invalid);
		m_soundkeeper->FireRestart();
		break;
	}

	return S_OK;
}

HRESULT CSoundSession::OnSimpleVolumeChanged(float NewSimpleVolume, BOOL NewMute, LPCGUID EventContext)
{
	if (NewMute)
	{
		// Shutdown Sound Keeper when muted.
		// m_soundkeeper->FireShutdown();
	}
	return S_OK;
}


================================================
FILE: CSoundSession.hpp
================================================
#pragma once

#include "Common.hpp"

#include <mmdeviceapi.h>
#include <audioclient.h>

class CSoundSession;

#include "CSoundKeeper.hpp"

class CSoundSession : IAudioSessionEvents
{
protected:

	static bool g_is_leaky_wasapi;

public:

	static void EnableWaitExclusiveWorkaround(bool enable) { g_is_leaky_wasapi = enable; }

protected:

	LONG                    m_ref_count = 1;
	CriticalSection         m_mutex;

	CSoundKeeper*           m_soundkeeper = nullptr;
	IMMDevice*              m_endpoint = nullptr;
	LPWSTR                  m_device_id = nullptr;
	KeepStreamType          m_stream_type = KeepStreamType::Zero;

	HANDLE                  m_render_thread = NULL;
	ManualResetEvent        m_is_started = false;

	enum class RenderingMode { Stop, Rendering, Retry, WaitExclusive, TryOpenDevice, Invalid };
	RenderingMode           m_curr_mode = RenderingMode::Stop;
	RenderingMode           m_next_mode = RenderingMode::Stop;
	AutoResetEvent          m_interrupt = false;
	DWORD                   m_play_attempts = 0;
	DWORD                   m_wait_attempts = 0;

	void DeferNextMode(RenderingMode next_mode)
	{
		m_next_mode = next_mode;
		m_interrupt = true;
	}

	IAudioClient*           m_audio_client = nullptr;
	IAudioRenderClient*     m_render_client = nullptr;
	IAudioSessionControl*   m_audio_session_control = nullptr;

	enum class SampleType { Unknown, Int16, Int24, Int32, Float32 };
	static SampleType ParseSampleType(WAVEFORMATEX* format);
	SampleType              m_mix_sample_type = SampleType::Unknown;
	SampleType              m_out_sample_type = SampleType::Unknown;

	UINT32                  m_sample_rate = 0;
	UINT32                  m_channels_count = 0;
	UINT32                  m_frame_size = 0;

	UINT32                  m_buffer_size_in_ms = 1000;
	UINT32                  m_buffer_size_in_frames = 0;

	// Sound generation settings.
	double                  m_frequency = 0.0;
	double                  m_amplitude = 0.0;

	// Periodicity settings.
	double                  m_play_seconds = 0.0;
	double                  m_wait_seconds = 0.0;
	double                  m_fade_seconds = 0.0;

	// Current state.
	uint64_t                m_curr_frame = 0;
	union
	{
		double              m_curr_state[8]{0}; // Pink Noise.
		double              m_curr_value;       // Brown Noise.
		double              m_curr_theta;       // Sine.
	};

public:

	CSoundSession(CSoundKeeper* soundkeeper, IMMDevice* endpoint);
	bool Start();
	void Stop();
	bool IsStarted() const { return m_is_started; }
	bool IsValid() const { return m_curr_mode != RenderingMode::Invalid; };
	LPCWSTR GetDeviceId() const { return m_device_id; }
	DWORD GetDeviceState() { DWORD state = 0; m_endpoint->GetState(&state); return state; }

	void ResetCurrent()
	{
		if (m_curr_frame)
		{
			m_curr_frame = 0;
			memset(m_curr_state, 0, sizeof(m_curr_state));
		}
	}

	void SetStreamType(KeepStreamType stream_type)
	{
		m_stream_type = stream_type;
		this->ResetCurrent();
	}

	KeepStreamType GetStreamType() const
	{
		return m_stream_type;
	}

	// Sine generation settings.

	void SetFrequency(double frequency)
	{
		m_frequency = frequency > 0 ? frequency : 0;
		this->ResetCurrent();
	}

	double GetFrequency() const
	{
		return m_frequency;
	}

	void SetAmplitude(double amplitude)
	{
		m_amplitude = amplitude > 0 ? amplitude : 0;
		this->ResetCurrent();
	}

	double GetAmplitude() const
	{
		return m_amplitude;
	}

	// Periodicity settings.

	void SetPeriodicPlaying(double seconds)
	{
		m_play_seconds = seconds > 0 ? seconds : 0;
		this->ResetCurrent();
	}

	double GetPeriodicPlaying() const
	{
		return m_play_seconds;
	}

	void SetPeriodicWaiting(double seconds)
	{
		m_wait_seconds = seconds > 0 ? seconds : 0;
		this->ResetCurrent();
	}

	double GetPeriodicWaiting() const
	{
		return m_wait_seconds;
	}

	void SetFading(double seconds)
	{
		m_fade_seconds = seconds > 0 ? seconds : 0;
		this->ResetCurrent();
	}

	double GetFading() const
	{
		return m_fade_seconds;
	}

protected:

	~CSoundSession(void);

	//
	// Rendering thread.
	//

	static DWORD APIENTRY StartRenderingThread(LPVOID context);
	DWORD RenderingThread();
	RenderingMode TryOpenDevice();
	RenderingMode Rendering();
	HRESULT Render();
	RenderingMode WaitExclusive();

public:

	//
	// IUnknown
	//

	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **object);
	ULONG STDMETHODCALLTYPE AddRef();
	ULONG STDMETHODCALLTYPE Release();

protected:

	//
	// IAudioSessionEvents.
	//

	STDMETHOD(OnDisplayNameChanged) (LPCWSTR /*NewDisplayName*/, LPCGUID /*EventContext*/) { return S_OK; };
	STDMETHOD(OnIconPathChanged) (LPCWSTR /*NewIconPath*/, LPCGUID /*EventContext*/) { return S_OK; };
	STDMETHOD(OnSimpleVolumeChanged) (float /*NewSimpleVolume*/, BOOL /*NewMute*/, LPCGUID /*EventContext*/);
	STDMETHOD(OnChannelVolumeChanged) (DWORD /*ChannelCount*/, float /*NewChannelVolumes*/[], DWORD /*ChangedChannel*/, LPCGUID /*EventContext*/) { return S_OK; };
	STDMETHOD(OnGroupingParamChanged) (LPCGUID /*NewGroupingParam*/, LPCGUID /*EventContext*/) { return S_OK; };
	STDMETHOD(OnStateChanged) (AudioSessionState NewState);
	STDMETHOD(OnSessionDisconnected) (AudioSessionDisconnectReason DisconnectReason);
};


================================================
FILE: Common/BasicMacros.hpp
================================================
#pragma once

// Mark a variable as unused explicitly to avoid warnings.
#define UNUSED(x) (void)(x)

// Stringify the argument without expanding macro definitions.
#define STRINGIFY_NX(x) #x

// Stringify the argument.
#define STRINGIFY(x) STRINGIFY_NX(x)

// Concatenate two tokens without expanding macro definitions.
#define TOKEN_CONCAT_NX(x, y) x ## y

// Concatenate two tokens.
#define TOKEN_CONCAT(x, y) TOKEN_CONCAT_NX(x, y)


================================================
FILE: Common/Defer.hpp
================================================
#pragma once

#include "BasicMacros.hpp"

template <typename T>
struct Deferrer
{
	T f;
	Deferrer(T f) : f(f) { };
	Deferrer(const Deferrer&) = delete;
	~Deferrer() { f(); }
};

// Defer execution of the following lambda to the end of current scope.
#define defer Deferrer TOKEN_CONCAT(__deferred, __COUNTER__) =


================================================
FILE: Common/NtBase.hpp
================================================
#pragma once

#include <stdint.h>
#include <stddef.h>

#define _WIN32_WINNT 0x0601
#include <sdkddkver.h>
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#define NOCOMM
#define NOKANJI
#define NOSOUND
#include <windows.h>
#include <objbase.h>

// ---------------------------------------------------------------------------------------------------------------------

// The best way to get HMODULE of current module, supported since VS2002.

EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define THIS_MODULE ((HMODULE)&__ImageBase)

// ---------------------------------------------------------------------------------------------------------------------

EXTERN_C_START

typedef _Success_(return >= 0) LONG NTSTATUS;
typedef NTSTATUS* PNTSTATUS;

#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#endif

typedef struct _UNICODE_STRING
{
	USHORT Length;
	USHORT MaximumLength;
	_Field_size_bytes_part_(MaximumLength, Length) PWCH Buffer;
} UNICODE_STRING, * PUNICODE_STRING;

typedef const UNICODE_STRING* PCUNICODE_STRING;

typedef struct _OBJECT_ATTRIBUTES
{
	ULONG Length;
	HANDLE RootDirectory;
	PUNICODE_STRING ObjectName;
	ULONG Attributes;
	PVOID SecurityDescriptor; // PSECURITY_DESCRIPTOR;
	PVOID SecurityQualityOfService; // PSECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;

typedef const OBJECT_ATTRIBUTES* PCOBJECT_ATTRIBUTES;

EXTERN_C_END

// ---------------------------------------------------------------------------------------------------------------------


================================================
FILE: Common/NtCriticalSection.hpp
================================================
#pragma once

#include "NtBase.hpp"

//
// A simple reentrant lock based on Windows Critical Sections.
//

class CriticalSection
{
	CriticalSection(const CriticalSection&) = delete;
	CriticalSection& operator=(const CriticalSection&) = delete;

protected:

	CRITICAL_SECTION m_cs;

public:

	CriticalSection()
	{
		(void) InitializeCriticalSection(&m_cs);
	}

	CriticalSection(DWORD spin_count)
	{
		(void) InitializeCriticalSectionAndSpinCount(&m_cs, spin_count);
	}

	~CriticalSection()
	{
		DeleteCriticalSection(&m_cs);
	}

	void Lock()
	{
		EnterCriticalSection(&m_cs);
	}

	bool TryLock()
	{
		return TryEnterCriticalSection(&m_cs) ? true : false;
	}

	bool TryLock(DWORD timeout)
	{
		ULONGLONG start = GetTickCount64();
		while (true)
		{
			if (TryLock())
			{
				return true;
			}
			if ((GetTickCount64() - start) >= timeout)
			{
				return false;
			}
			YieldProcessor();
		}
		return false;
	}

	void Unlock()
	{
		LeaveCriticalSection(&m_cs);
	}
};

template<typename T>
class ScopedLock
{
	ScopedLock() = delete;
	ScopedLock(const ScopedLock&) = delete;
	ScopedLock& operator=(const ScopedLock&) = delete;

protected:

	T& m_lock;

public:

	ScopedLock(T& lock) : m_lock(lock)
	{
		m_lock.Lock();
	}

	~ScopedLock()
	{
		m_lock.Unlock();
	}
};


================================================
FILE: Common/NtEvent.hpp
================================================
#pragma once

#include "NtBase.hpp"
#include "NtHandle.hpp"

// ---------------------------------------------------------------------------------------------------------------------

EXTERN_C_START

typedef enum _EVENT_TYPE
{
	NotificationEvent,
	SynchronizationEvent
} EVENT_TYPE;

#ifndef EVENT_QUERY_STATE
#define EVENT_QUERY_STATE 0x0001
#endif

NTSYSCALLAPI
NTSTATUS
NTAPI
NtCreateEvent(
	_Out_ PHANDLE EventHandle,
	_In_ ACCESS_MASK DesiredAccess,
	_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
	_In_ EVENT_TYPE EventType,
	_In_ BOOLEAN InitialState
);

NTSYSCALLAPI
NTSTATUS
NTAPI
NtSetEvent(
	_In_ HANDLE EventHandle,
	_Out_opt_ PLONG PreviousState
);

NTSYSCALLAPI
NTSTATUS
NTAPI
NtResetEvent(
	_In_ HANDLE EventHandle,
	_Out_opt_ PLONG PreviousState
);

NTSYSCALLAPI
NTSTATUS
NTAPI
NtPulseEvent(
	_In_ HANDLE EventHandle,
	_Out_opt_ PLONG PreviousState
);

typedef enum _EVENT_INFORMATION_CLASS
{
	EventBasicInformation
} EVENT_INFORMATION_CLASS;

typedef struct _EVENT_BASIC_INFORMATION
{
	EVENT_TYPE EventType;
	LONG EventState;
} EVENT_BASIC_INFORMATION, *PEVENT_BASIC_INFORMATION;

NTSYSCALLAPI
NTSTATUS
NTAPI
NtQueryEvent(
	_In_ HANDLE EventHandle,
	_In_ EVENT_INFORMATION_CLASS EventInformationClass,
	_Out_writes_bytes_(EventInformationLength) PVOID EventInformation,
	_In_ ULONG EventInformationLength,
	_Out_opt_ PULONG ReturnLength
);

EXTERN_C_END

// ---------------------------------------------------------------------------------------------------------------------

class SyncEvent : public Handle
{
public:

	enum class ResetMode { Manual, Auto };

	explicit SyncEvent(const ResetMode mode, const bool state) : Handle(NULL)
	{
		NtCreateEvent(
			&m_handle,
			EVENT_ALL_ACCESS,
			nullptr,
			(mode == ResetMode::Manual ? NotificationEvent : SynchronizationEvent),
			state
		);
	}

	bool Get() const
	{
		EVENT_BASIC_INFORMATION info {};
		NtQueryEvent(m_handle, EventBasicInformation, &info, sizeof(info), nullptr);
		return info.EventState;
	}

	void Set(const bool state)
	{
		if (state)
		{
			NtSetEvent(m_handle, nullptr);
		}
		else
		{
			NtResetEvent(m_handle, nullptr);
		}
	}

	bool GetSet(const bool state)
	{
		LONG prev = 0;
		if (state)
		{
			NtSetEvent(m_handle, &prev);
		}
		else
		{
			NtResetEvent(m_handle, &prev);
		}
		return prev;
	}

	void Pulse()
	{
		NtPulseEvent(m_handle, nullptr);
	}

	bool GetPulse()
	{
		LONG prev = 0;
		NtPulseEvent(m_handle, &prev);
		return prev;
	}
};

// ---------------------------------------------------------------------------------------------------------------------

class AutoResetEvent : public SyncEvent
{
public:

	AutoResetEvent(const bool state) : SyncEvent(ResetMode::Auto, state) {}

	AutoResetEvent& operator=(const bool state)
	{
		this->Set(state);
		return *this;
	}

	operator bool() const
	{
		return this->Get();
	}
};

// ---------------------------------------------------------------------------------------------------------------------

class ManualResetEvent : public SyncEvent
{
public:

	ManualResetEvent(const bool state) : SyncEvent(ResetMode::Manual, state) {}

	ManualResetEvent& operator=(const bool state)
	{
		this->Set(state);
		return *this;
	}

	operator bool() const
	{
		return this->Get();
	}
};

// ---------------------------------------------------------------------------------------------------------------------


================================================
FILE: Common/NtHandle.hpp
================================================
#pragma once

#include "NtBase.hpp"
#include <initializer_list>

// ---------------------------------------------------------------------------------------------------------------------

class Handle
{
	Handle(const Handle&) = delete;
	Handle& operator= (const Handle&) = delete;

protected:

	HANDLE m_handle;

public:

	Handle(HANDLE handle) : m_handle(handle) {}
	HANDLE GetHandle() const { return m_handle; }
	operator HANDLE() const { return m_handle; }
	~Handle() { if (m_handle) { CloseHandle(m_handle); } }
};

inline DWORD AlertableSleep(DWORD timeout)
{
	return SleepEx(timeout, true);
}

inline DWORD WaitForOne(HANDLE handle, DWORD timeout = INFINITE)
{
	return WaitForSingleObject(handle, timeout);
}

inline DWORD AlertableWaitForOne(HANDLE handle, DWORD timeout = INFINITE)
{
	return WaitForSingleObjectEx(handle, timeout, true);
}

inline DWORD WaitForAny(std::initializer_list<HANDLE> handles, DWORD timeout = INFINITE)
{
	return WaitForMultipleObjects((DWORD)handles.size(), handles.begin(), FALSE, timeout);
}

inline DWORD AlertableWaitForAny(std::initializer_list<HANDLE> handles, DWORD timeout = INFINITE)
{
	return WaitForMultipleObjectsEx((DWORD)handles.size(), handles.begin(), FALSE, timeout, true);
}

inline DWORD WaitForAll(std::initializer_list<HANDLE> handles, DWORD timeout = INFINITE)
{
	return WaitForMultipleObjects((DWORD)handles.size(), handles.begin(), TRUE, timeout);
}

inline DWORD AlertableWaitForAll(std::initializer_list<HANDLE> handles, DWORD timeout = INFINITE)
{
	return WaitForMultipleObjectsEx((DWORD)handles.size(), handles.begin(), TRUE, timeout, true);
}

// ---------------------------------------------------------------------------------------------------------------------


================================================
FILE: Common/NtUtils.hpp
================================================
#pragma once

#include "NtBase.hpp"
#include "StrUtils.hpp"

// ---------------------------------------------------------------------------------------------------------------------

template <class T> void SafeRelease(T*& com_obj_ptr)
{
	if (com_obj_ptr)
	{
		com_obj_ptr->Release();
		com_obj_ptr = nullptr;
	}
}

// ---------------------------------------------------------------------------------------------------------------------

// Gets NT version and build number. 
// The upper 4 bits of build number are reserved for the type of the OS build.
// 0xC for a "checked" (or debug) build, and 0xF for a "free" (or retail) build.

extern "C" NTSYSAPI VOID WINAPI RtlGetNtVersionNumbers(
	__out_opt DWORD* pNtMajorVersion,
	__out_opt DWORD* pNtMinorVersion,
	__out_opt DWORD* pNtBuildNumber
);

inline uint32_t GetNtBuildNumber()
{
	static uint32_t build_number = 0;

	if (build_number == 0)
	{
		RtlGetNtVersionNumbers(NULL, NULL, (DWORD*)&build_number);
		build_number &= ~0xF0000000; // Clear type of OS build bits.
	}

	return build_number;
}

// ---------------------------------------------------------------------------------------------------------------------

inline HMODULE GetKernelBaseDll()
{
	static HMODULE dll = 0;
	if (!dll) { dll = GetModuleHandleA("kernelbase.dll"); }
	return dll;
}

// ---------------------------------------------------------------------------------------------------------------------

inline HRESULT WINAPI SetThreadDescriptionW(_In_ HANDLE hThread, _In_ PCWSTR lpThreadDescription)
{
	static void* pfn = nullptr;

	if (!pfn)
	{
		if (HMODULE dll = GetKernelBaseDll())
		{
			pfn = GetProcAddress(dll, "SetThreadDescription");
		}
	}

	if (!pfn) { return E_NOTIMPL; }

	return static_cast<decltype(SetThreadDescription)*>(pfn)(hThread, lpThreadDescription);
}

inline HRESULT SetCurrentThreadDescriptionW(PCWSTR desc)
{
	return SetThreadDescriptionW(GetCurrentThread(), desc);
}

// ---------------------------------------------------------------------------------------------------------------------

inline HRESULT WINAPI GetThreadDescriptionW(_In_ HANDLE hThread, _Outptr_result_z_ PWSTR* ppszThreadDescription)
{
	static void* pfn = nullptr;

	if (!pfn)
	{
		if (HMODULE dll = GetKernelBaseDll())
		{
			pfn = GetProcAddress(dll, "GetThreadDescription");
		}
	}

	if (!pfn) { return E_NOTIMPL; }

	return static_cast<decltype(GetThreadDescription)*>(pfn)(hThread, ppszThreadDescription);
}

// ---------------------------------------------------------------------------------------------------------------------

struct RsrcSpan
{
	const uint8_t* data;
	size_t size;
	operator bool() const { return data != nullptr; }
};

inline RsrcSpan GetResource(HMODULE hModule, LPCTSTR lpName, LPCTSTR lpType)
{
	HRSRC hrsrc = FindResource(hModule, lpName, lpType);
	if (!hrsrc) { return { nullptr, 0 }; }
	HGLOBAL hglobal = LoadResource(hModule, hrsrc);
	if (!hglobal) { return { nullptr, 0 }; }
	return { (uint8_t*)LockResource(hglobal), SizeofResource(hModule, hrsrc) };
}

inline const VS_FIXEDFILEINFO* GetFixedVersion(HMODULE hModule = NULL)
{
	auto rsrc = GetResource(hModule, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
	if (!rsrc) { return nullptr; }

	// Parse the resource as the VS_VERSION_INFO pseudo structure to get the VS_FIXEDFILEINFO.

	if (rsrc.size < (40 + sizeof(VS_FIXEDFILEINFO))) { return nullptr; }
	if (*(uint16_t*)(rsrc.data + 0) < (40 + sizeof(VS_FIXEDFILEINFO))) { return nullptr; } // VS_VERSIONINFO::wLength
	if (*(uint16_t*)(rsrc.data + 2) != sizeof(VS_FIXEDFILEINFO)) { return nullptr; }       // VS_VERSIONINFO::wValueLength
	if (*(uint16_t*)(rsrc.data + 4) != 0) { return nullptr; }                              // VS_VERSIONINFO::wType (0 is binary, 1 is text)
	if (!StringEquals((wchar_t*)(rsrc.data + 6), L"VS_VERSION_INFO")) { return nullptr; }  // VS_VERSIONINFO::szKey
	const VS_FIXEDFILEINFO* ffi = (const VS_FIXEDFILEINFO*)(rsrc.data + 40);               // VS_VERSIONINFO::Value
	if (ffi->dwSignature != VS_FFI_SIGNATURE || ffi->dwStrucVersion != VS_FFI_STRUCVERSION) { return nullptr; }

	return ffi;
}

// ---------------------------------------------------------------------------------------------------------------------


================================================
FILE: Common/StrUtils.hpp
================================================
#pragma once

#include <stdint.h>
#include <stddef.h>

// ---------------------------------------------------------------------------------------------------------------------

using TransformByteCharFuncType = char (*)(char);
using TransformWideCharFuncType = wchar_t (*)(wchar_t);

// ---------------------------------------------------------------------------------------------------------------------

constexpr char NoTransform(char c)
{
	return c;
}

constexpr char AsciiToLower(char c)
{
	return ('A' <= c && c <= 'Z') ? (c | 0b00100000) : c;
}

constexpr char AsciiToUpper(char c)
{
	return ('a' <= c && c <= 'z') ? (c & 0b11011111) : c;
}

constexpr wchar_t NoTransform(wchar_t c)
{
	return c;
}

constexpr wchar_t AsciiToLower(wchar_t c)
{
	return (L'A' <= c && c <= L'Z') ? (c | 0b0000000000100000) : c;
}

constexpr wchar_t AsciiToUpper(wchar_t c)
{
	return (L'a' <= c && c <= L'z') ? (c & 0b1111111111011111) : c;
}

// ---------------------------------------------------------------------------------------------------------------------

template <TransformByteCharFuncType TransformChar = NoTransform>
constexpr int StringCompare(const char* l, const char* r)
{
	for (; *l && TransformChar(*l) == TransformChar(*r); l++, r++);
	return (int)(uint8_t)TransformChar(*l) - (int)(uint8_t)TransformChar(*r);
}

template <TransformWideCharFuncType TransformChar = NoTransform>
constexpr int StringCompare(const wchar_t* l, const wchar_t* r)
{
	for (; *l && TransformChar(*l) == TransformChar(*r); l++, r++);
	return (int)TransformChar(*l) - (int)TransformChar(*r);
}

// ---------------------------------------------------------------------------------------------------------------------

template <TransformByteCharFuncType TransformChar = NoTransform>
constexpr bool StringEquals(const char* l, const char* r)
{
	return StringCompare<TransformChar>(l, r) == 0;
}

template <TransformWideCharFuncType TransformChar = NoTransform>
constexpr bool StringEquals(const wchar_t* l, const wchar_t* r)
{
	return StringCompare<TransformChar>(l, r) == 0;
}

// ---------------------------------------------------------------------------------------------------------------------

template <TransformByteCharFuncType TransformChar = NoTransform>
constexpr const char* StringFindPtr(const char* str, const char* sub)
{
	while (*str)
	{
		const char *l = str; const char *r = sub;
		while (*l && TransformChar(*l) == TransformChar(*r)) { l++; r++; }
		if (!*r) { return str; }
		str++;
	}

	return nullptr;
}

template <TransformWideCharFuncType TransformChar = NoTransform>
constexpr const wchar_t* StringFindPtr(const wchar_t* str, const wchar_t* sub)
{
	while (*str)
	{
		const wchar_t* l = str; const wchar_t* r = sub;
		while (*l && TransformChar(*l) == TransformChar(*r)) { l++; r++; }
		if (!*r) { return str; }
		str++;
	}

	return nullptr;
}

// ---------------------------------------------------------------------------------------------------------------------

template <TransformByteCharFuncType TransformChar = NoTransform>
constexpr size_t StringIndexOf(const char* str, const char* sub)
{
	auto ptr = StringFindPtr<TransformChar>(str, sub);
	return ptr ? (ptr - str) : -1;
}

template <TransformWideCharFuncType TransformChar = NoTransform>
constexpr size_t StringIndexOf(const wchar_t* str, const wchar_t* sub)
{
	auto ptr = StringFindPtr<TransformChar>(str, sub);
	return ptr ? (ptr - str) : -1;
}

// ---------------------------------------------------------------------------------------------------------------------

template <TransformByteCharFuncType TransformChar = NoTransform>
constexpr bool StringContains(const char* str, const char* sub)
{
	return StringFindPtr<TransformChar>(str, sub) != nullptr;
}

template <TransformWideCharFuncType TransformChar = NoTransform>
constexpr bool StringContains(const wchar_t* str, const wchar_t* sub)
{
	return StringFindPtr<TransformChar>(str, sub) != nullptr;
}

// ---------------------------------------------------------------------------------------------------------------------


================================================
FILE: Common.hpp
================================================
#pragma once

#define _CRT_SECURE_NO_WARNINGS
#define _USE_MATH_DEFINES

#include "Common/BasicMacros.hpp"
#include "Common/Defer.hpp"
#include "Common/NtBase.hpp"
#include "Common/NtHandle.hpp"
#include "Common/NtEvent.hpp"
#include "Common/NtCriticalSection.hpp"
#include "Common/NtUtils.hpp"
#include "Common/StrUtils.hpp"
#include <algorithm> // std::min and std::max.
#include <math.h>

#ifdef _CONSOLE

inline void DebugLogImpl(const char * funcname, const char * type, const char * format, ...)
{
	static CriticalSection mutex;
	ScopedLock lock(mutex);

	static bool is_inited = false;
	static bool show_trace = false;

	if (!is_inited)
	{
		// It also looks in path to exe, but it's fine for a debug build.
		char buf[MAX_PATH];
		strcpy_s(buf, GetCommandLineA());
		_strlwr(buf);
		if (StringContains<AsciiToLower>(buf, "trace")) { show_trace = true; }
		is_inited = true;
	}

	if (!show_trace && type && StringEquals<AsciiToLower>(type, "TRACE")) { return; }

	static uint64_t prev_date = 0;
	SYSTEMTIME now = {0};
	GetSystemTime(&now);

	// Output current date once. First 8 bytes are current date, so we can compare it as a 64-bit integer.
	if (prev_date != *((uint64_t*)&now))
	{
		prev_date = *((uint64_t*)&now);
		printf("%04d/%02d/%02d ", now.wYear, now.wMonth, now.wDay);
	}

	printf("%02d:%02d:%02d.%03d ", now.wHour, now.wMinute, now.wSecond, now.wMilliseconds);
	printf("[%5d] ", GetThreadId(GetCurrentThread()));

	if (show_trace && funcname)
	{
		printf("[%s] ", funcname);
	}

	if (type && !StringEquals<AsciiToLower>(type, "TRACE") && !StringEquals<AsciiToLower>(type, "INFO"))
	{
		printf("[%s] ", type);
	}

	va_list argptr;
	va_start(argptr, format);
	vprintf(format, argptr);

	printf("\n");
	fflush(stdout);
}

#define DebugLog(...) DebugLogImpl(__FUNCTION__, "INFO", __VA_ARGS__)
#define DebugLogWarning(...) DebugLogImpl(__FUNCTION__, "WARNING", __VA_ARGS__)
#define DebugLogError(...) DebugLogImpl(__FUNCTION__, "ERROR", __VA_ARGS__)
#define TraceLog(...) DebugLogImpl(__FUNCTION__, "TRACE", __VA_ARGS__)
#define DebugThreadName(...) SetCurrentThreadDescriptionW(L ## __VA_ARGS__)

#else

#define DebugLog(...)
#define DebugLogWarning(...)
#define DebugLogError(...)
#define TraceLog(...)
#define DebugThreadName(...)

#endif


================================================
FILE: License.md
================================================
# MIT License

Copyright (c) 2014-2024 Evgeny Vrublevsky

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: ReadMe.txt
================================================
Sound Keeper v1.3.5 [2025/07/05]
https://veg.by/projects/soundkeeper/

Prevents SPDIF/HDMI digital audio playback devices from sleeping. Uses WASAPI, requires Windows 7+.

You need just one exe file (others can be removed):
- SoundKeeper32.exe is for x86-32 Windows.
- SoundKeeper64.exe is for x86-64 Windows.
- SoundKeeperARM64.exe is for ARM64 Windows.

The program doesn't have a GUI. It starts to do its job right after the process is started.
To close the program, just kill the SoundKeeper.exe process.
To autorun, copy SoundKeeper.exe into the startup directory (to open it, press Win+R, enter "shell:startup").

Default behavior can be changed by adding settings to the Sound Keeper executable file name or by passing them
as command line arguments. Setting names are case insensitive.

Supported device type settings:
- "Primary" keeps on primary audio output only. Used by default.
- "All" keeps on all enabled audio outputs.
- "Digital" keeps on all enabled SPDIF and HDMI audio outputs (like it was in Sound Keeper v1.0).
- "Analog" keeps on all enabled audio outputs except SPDIF and HDMI.

Supported stream type settings:
- "OpenOnly" opens audio output, but doesn't play anything. Sometimes it helps.
- "Zero" plays stream of zeroes. It may be not enough for some hardware.
- "Fluctuate" plays stream of zeroes with the smallest non-zero samples once in a second. Used by default.
- "Sine" plays 1Hz sine wave at 1% volume. The frequency and amplitude can be changed. Useful for analog outputs.
- "White", "Brown", or "Pink" play named noise, with the same parameters as the sine (except frequency).

Sine and noise stream parameters:
- F is frequency. Default: 1Hz for Sine and 50Hz for Fluctuate. Applicable for: Fluctuate, Sine.
- A is amplitude. Default: 1%. If you want to use inaudible noise, set it to 0.1%. Applicable for: Sine, Noise.
- L is length of sound (in seconds). Default: infinite.
- W is waiting time between sounds if L is set. Use to enable periodic sound.
- T is transition or fading time. Default: 0.1 second. Applicable for: Sine, Noise.

Examples:
- SoundKeeperZeroAll.exe generates zero amplitude stream on all enabled audio outputs.
- SoundKeeperAll.exe generates default inaudible stream on all enabled audio outputs.
- SoundKeeperSineF10A5.exe generates 10Hz sine wave with 5% amplitude on primary audio output. It is inaudible.
- SoundKeeperSineF1000A15.exe generates 1000Hz sine wave with 15% amplitude. It is audible! Use it for testing.
- "SoundKeeper.exe sine -f 1000 -a 15" is a command line version of the previous example.
- "SoundKeeper.exe brown -a 0.1" (settings are command line arguments) generates brown noise with 0.1% amplitude.

What's new

v1.3.5 [2025/07/05]:
- Handle ASIO exclusive mode by waiting until it ends, similar to WASAPI exclusive mode.

v1.3.4 [2024/09/15]:
- Tune the Windows 8-10 WASAPI memory leak workaround to make it effective for longer time.
- Native ARM64 version (with statically linked runtime hence the bigger binary).

v1.3.3 [2023/08/19]:
- Fixed arguments parsing bug: "All" or "Analog" after specifying stream type led to amplitude set to 0.

v1.3.2 [2023/08/18]:
- "Fluctuate" treats 32-bit output format as 24-bit since WASAPI reports 24-bit as 32-bit for some reason.
- "Fluctuate" generates 50 fluctuations per second by default. It helps in many more cases.
- Sound Keeper doesn't exit when it is muted.

v1.3.1 [2023/01/30]:
- A potential deadlock when audio devices are added or removed has been fixed.
- "Fluctuate" treats non-PCM output formats (like Dolby Atmos) as 24-bit instead of 16-bit.
- Frequency parameter is limited by half of current sample rate to avoid generation of unexpected noise.
- More detailed logs in debug builds. Debug output is flushed immediately, so it can be redirected to a file.

v1.3.0 [2022/07/28]:
- "Fluctuate" is 1 fluctuation per second by default. Frequency can be changed using the F parameter.
- Periodic playing of a sine sound with optional fading.
- "White", "Brown", and "Pink" noise signal types.
- Self kill command is added. Run "soundkeeper kill" to stop running Sound Keeper instance.
- "Analog" switch was added. It works as the opposite of "Digital".
- Ignores remote desktop audio device (this feature can be disabled using the "Remote" switch).
- New "OpenOnly" mode that just opens audio output, but doesn't stream anything.
- New "NoSleep" switch which disables PC sleep detection (Windows 7-10).
- The program is not confused anymore when PC auto sleep is disabled on Windows 10.

v1.2.2 [2022/05/15]:
- Work as a dummy when no suitable devices found.
- Sound Keeper shouldn't prevent PC from automatic going into sleep mode on Windows 10.

v1.2.1 [2021/11/05]:
- Sound Keeper works on Windows 11.
- The workaround that allowed PC to sleep had to be disabled on Windows 11.

v1.2.0 [2021/10/30]:
- Sound Keeper doesn't prevent PC from automatic going into sleep mode on Windows 7.
- New "Sine" stream type which can be useful for analog outputs or too smart digital outputs.
- When a user starts a new Sound Keeper instance, the previous one is stopped automatically.
- "Fluctuate" stream type considers sample format of the output (16/24/32-bit integer, and 32-bit float).
- Command line arguments are supported. Example: "soundkeeper sine -f 1000 -a 10".
- The workaround for the Audio Service memory leak is enabled on affected Windows versions only (8, 8.1, and 10).

v1.1.0 [2020/07/18]:
- Default behavior can be changed by adding options to the Sound Keeper executable file name.
- Primary audio output is used by default.
- Inaudible stream is used by default.
- Workaround for a Windows 10 bug which causes a memory leak in the Audio Service when audio output is busy.

v1.0.4 [2020/03/14]: Fixed a potential memory leak when another program uses audio output in exclusive mode.
v1.0.3 [2019/07/14]: Exclusive mode doesn't prevent Sound Keeper from working.
v1.0.2 [2017/12/23]: 64-bit version is added.
v1.0.1 [2017/12/21]: Waking PC up after sleeping doesn't prevent Sound Keeper from working.
v1.0.0 [2014/12/24]: Initial release.

(c) 2014-2025 Evgeny Vrublevsky <me@veg.by>


================================================
FILE: Resources.hpp
================================================
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by SoundKeeper.rc
//
#define IDI_MAIN                        100

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        101
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


================================================
FILE: Resources.rc
================================================
// Microsoft Visual C++ generated resource script.
//
#pragma code_page(65001)

#include "Resources.hpp"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include <winres.h>

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// English resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "Resources.hpp\0"
END

2 TEXTINCLUDE 
BEGIN
    "#include <winres.h>\r\n"
    "\0"
END

3 TEXTINCLUDE 
BEGIN
    "#include ""BuildInfo.rc2""\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Icon
//

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_MAIN                ICON                    "Main.ico"

#endif    // English resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
#include "BuildInfo.rc2"

/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED



================================================
FILE: RuntimeHacks.cpp
================================================
// We use msvcrt.dll as a C runtime to save space in release builds.
// It has limited set of functions, so we reimplement missing ones here.

#if defined(_VC_NODEFAULTLIB)

// ...

#endif


================================================
FILE: SoundKeeper.sln
================================================

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.9.34714.143
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SoundKeeper", "SoundKeeper.vcxproj", "{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}"
EndProject
Global
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
		Debug|ARM64 = Debug|ARM64
		Debug|Win32 = Debug|Win32
		Debug|Win64 = Debug|Win64
		Develop|ARM64 = Develop|ARM64
		Develop|Win32 = Develop|Win32
		Develop|Win64 = Develop|Win64
		Release|ARM64 = Release|ARM64
		Release|Win32 = Release|Win32
		Release|Win64 = Release|Win64
	EndGlobalSection
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Debug|ARM64.ActiveCfg = Debug|ARM64
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Debug|ARM64.Build.0 = Debug|ARM64
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Debug|Win32.ActiveCfg = Debug|Win32
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Debug|Win32.Build.0 = Debug|Win32
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Debug|Win64.ActiveCfg = Debug|x64
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Debug|Win64.Build.0 = Debug|x64
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Develop|ARM64.ActiveCfg = Develop|ARM64
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Develop|ARM64.Build.0 = Develop|ARM64
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Develop|Win32.ActiveCfg = Develop|Win32
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Develop|Win32.Build.0 = Develop|Win32
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Develop|Win64.ActiveCfg = Develop|x64
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Develop|Win64.Build.0 = Develop|x64
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Release|ARM64.ActiveCfg = Release|ARM64
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Release|ARM64.Build.0 = Release|ARM64
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Release|Win32.ActiveCfg = Release|Win32
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Release|Win32.Build.0 = Release|Win32
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Release|Win64.ActiveCfg = Release|x64
		{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Release|Win64.Build.0 = Release|x64
	EndGlobalSection
	GlobalSection(SolutionProperties) = preSolution
		HideSolutionNode = FALSE
	EndGlobalSection
	GlobalSection(ExtensibilityGlobals) = postSolution
		SolutionGuid = {2C6CC7E0-E590-49CB-B735-38BB01870C12}
	EndGlobalSection
EndGlobal


================================================
FILE: SoundKeeper.vcxproj
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup Label="ProjectConfigurations">
    <ProjectConfiguration Include="Debug|ARM64">
      <Configuration>Debug</Configuration>
      <Platform>ARM64</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Debug|Win32">
      <Configuration>Debug</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Debug|x64">
      <Configuration>Debug</Configuration>
      <Platform>x64</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Develop|ARM64">
      <Configuration>Develop</Configuration>
      <Platform>ARM64</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Develop|Win32">
      <Configuration>Develop</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Develop|x64">
      <Configuration>Develop</Configuration>
      <Platform>x64</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|ARM64">
      <Configuration>Release</Configuration>
      <Platform>ARM64</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|Win32">
      <Configuration>Release</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|x64">
      <Configuration>Release</Configuration>
      <Platform>x64</Platform>
    </ProjectConfiguration>
  </ItemGroup>
  <ItemGroup>
    <ClCompile Include="CSoundKeeper.cpp" />
    <ClCompile Include="CSoundSession.cpp" />
    <ClCompile Include="RuntimeHacks.cpp" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="BuildInfo.hpp" />
    <ClInclude Include="Resources.hpp" />
    <ClInclude Include="Common.hpp" />
    <ClInclude Include="Common\BasicMacros.hpp" />
    <ClInclude Include="Common\Defer.hpp" />
    <ClInclude Include="Common\NtBase.hpp" />
    <ClInclude Include="Common\NtCriticalSection.hpp" />
    <ClInclude Include="Common\NtEvent.hpp" />
    <ClInclude Include="Common\NtHandle.hpp" />
    <ClInclude Include="Common\NtUtils.hpp" />
    <ClInclude Include="Common\StrUtils.hpp" />
    <ClInclude Include="CSoundKeeper.hpp" />
    <ClInclude Include="CSoundSession.hpp" />
  </ItemGroup>
  <ItemGroup>
    <ResourceCompile Include="Resources.rc" />
  </ItemGroup>
  <ItemGroup>
    <Image Include="Main.ico" />
  </ItemGroup>
  <ItemGroup>
    <UpToDateCheckInput Include="BuildInfo.cmd" />
    <UpToDateCheckInput Include="BuildInfo.csx" />
    <UpToDateCheckInput Include="BuildInfo.rc2" />
  </ItemGroup>
  <ItemGroup>
    <CopyFileToFolders Include="ReadMe.txt" />
  </ItemGroup>
  <PropertyGroup Label="Globals">
    <ProjectGuid>{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}</ProjectGuid>
    <Keyword>Win32Proj</Keyword>
    <ConfigurationType>Application</ConfigurationType>
    <ProjectName>SoundKeeper</ProjectName>
    <CharacterSet>Unicode</CharacterSet>
    <PlatformToolset Condition="'$(Platform)'!='ARM64'">v142</PlatformToolset>
    <PlatformToolset Condition="'$(Platform)'=='ARM64'">v143</PlatformToolset>
    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
  <PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
    <UseDebugLibraries>true</UseDebugLibraries>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)'=='Develop'" Label="Configuration">
    <UseDebugLibraries>false</UseDebugLibraries>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
    <UseDebugLibraries>false</UseDebugLibraries>
    <WholeProgramOptimization>true</WholeProgramOptimization>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
  <ImportGroup Label="ExtensionSettings">
  </ImportGroup>
  <ImportGroup Label="PropertySheets">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <PropertyGroup Label="UserMacros">
    <IntDir>$(SolutionDir)Build\$(ProjectName)_$(PlatformShortName)_$(Configuration)\</IntDir>
    <OutDir>$(SolutionDir)Bin\</OutDir>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Platform)'=='Win32'">
    <AppArchName>x86-32</AppArchName>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Platform)'=='x64'">
    <AppArchName>x86-64</AppArchName>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Platform)'=='ARM64'">
    <AppArchName>ARM64</AppArchName>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <LinkIncremental>true</LinkIncremental>
    <TargetName>$(ProjectName)32d</TargetName>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <LinkIncremental>true</LinkIncremental>
    <TargetName>$(ProjectName)64d</TargetName>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
    <LinkIncremental>true</LinkIncremental>
    <TargetName>$(ProjectName)ARM64d</TargetName>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Develop|Win32'">
    <LinkIncremental>false</LinkIncremental>
    <TargetName>$(ProjectName)32d</TargetName>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Develop|x64'">
    <LinkIncremental>false</LinkIncremental>
    <TargetName>$(ProjectName)64d</TargetName>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Develop|ARM64'">
    <LinkIncremental>false</LinkIncremental>
    <TargetName>$(ProjectName)ARM64d</TargetName>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <LinkIncremental>false</LinkIncremental>
    <TargetName>$(ProjectName)32</TargetName>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <LinkIncremental>false</LinkIncremental>
    <TargetName>$(ProjectName)64</TargetName>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
    <LinkIncremental>false</LinkIncremental>
    <TargetName>$(ProjectName)ARM64</TargetName>
  </PropertyGroup>
  <ItemDefinitionGroup>
    <ClCompile>
      <LanguageStandard>stdcpp20</LanguageStandard>
      <PrecompiledHeader>NotUsing</PrecompiledHeader>
      <WarningLevel>Level3</WarningLevel>
      <DisableSpecificWarnings>26812</DisableSpecificWarnings>
      <MultiProcessorCompilation>true</MultiProcessorCompilation>
      <PreprocessorDefinitions>APP_ARCH="$(AppArchName)";%(PreprocessorDefinitions)</PreprocessorDefinitions>
    </ClCompile>
    <ResourceCompile>
      <PreprocessorDefinitions>APP_ARCH=\"$(AppArchName)\";%(PreprocessorDefinitions)</PreprocessorDefinitions>
    </ResourceCompile>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <ClCompile>
      <Optimization>Disabled</Optimization>
      <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
    </ClCompile>
    <Link>
      <SubSystem>Console</SubSystem>
      <MinimumRequiredVersion>6.0</MinimumRequiredVersion>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <AdditionalDependencies>avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>
    </Link>
    <ResourceCompile>
      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
    </ResourceCompile>
    <PreBuildEvent>
      <Command>BuildInfo.cmd --no-errors</Command>
    </PreBuildEvent>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <ClCompile>
      <Optimization>Disabled</Optimization>
      <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
    </ClCompile>
    <Link>
      <SubSystem>Console</SubSystem>
      <MinimumRequiredVersion>6.0</MinimumRequiredVersion>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <AdditionalDependencies>avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>
    </Link>
    <ResourceCompile>
      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
    </ResourceCompile>
    <PreBuildEvent>
      <Command>BuildInfo.cmd --no-errors</Command>
    </PreBuildEvent>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
    <ClCompile>
      <Optimization>Disabled</Optimization>
      <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
    </ClCompile>
    <Link>
      <SubSystem>Console</SubSystem>
      <MinimumRequiredVersion>10.0</MinimumRequiredVersion>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <AdditionalDependencies>avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>
    </Link>
    <ResourceCompile>
      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
    </ResourceCompile>
    <PreBuildEvent>
      <Command>BuildInfo.cmd --no-errors</Command>
    </PreBuildEvent>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Develop|Win32'">
    <ClCompile>
      <Optimization>MinSpace</Optimization>
      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
    </ClCompile>
    <Link>
      <SubSystem>Console</SubSystem>
      <MinimumRequiredVersion>6.0</MinimumRequiredVersion>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <AdditionalDependencies>avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
      <AdditionalOptions>/EMITTOOLVERSIONINFO:NO /NOVCFEATURE /NOCOFFGRPINFO /STUB:EmptyStub.bin /MERGE:.pdata=.rdata /MERGE:_RDATA=.rdata %(AdditionalOptions)</AdditionalOptions>
      <RandomizedBaseAddress>false</RandomizedBaseAddress>
    </Link>
    <ResourceCompile>
      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
    </ResourceCompile>
    <PreBuildEvent>
      <Command>BuildInfo.cmd --no-errors</Command>
    </PreBuildEvent>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Develop|x64'">
    <ClCompile>
      <Optimization>MinSpace</Optimization>
      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
    </ClCompile>
    <Link>
      <SubSystem>Console</SubSystem>
      <MinimumRequiredVersion>6.0</MinimumRequiredVersion>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <AdditionalDependencies>avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
      <AdditionalOptions>/EMITTOOLVERSIONINFO:NO /NOVCFEATURE /NOCOFFGRPINFO /STUB:EmptyStub.bin /MERGE:.pdata=.rdata /MERGE:_RDATA=.rdata %(AdditionalOptions)</AdditionalOptions>
      <RandomizedBaseAddress>false</RandomizedBaseAddress>
    </Link>
    <ResourceCompile>
      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
    </ResourceCompile>
    <PreBuildEvent>
      <Command>BuildInfo.cmd --no-errors</Command>
    </PreBuildEvent>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Develop|ARM64'">
    <ClCompile>
      <Optimization>MinSpace</Optimization>
      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
    </ClCompile>
    <Link>
      <SubSystem>Console</SubSystem>
      <MinimumRequiredVersion>10.0</MinimumRequiredVersion>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <AdditionalDependencies>avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
      <AdditionalOptions>/EMITTOOLVERSIONINFO:NO /NOVCFEATURE /NOCOFFGRPINFO /STUB:EmptyStub.bin /MERGE:.pdata=.rdata /MERGE:_RDATA=.rdata %(AdditionalOptions)</AdditionalOptions>
    </Link>
    <ResourceCompile>
      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
    </ResourceCompile>
    <PreBuildEvent>
      <Command>BuildInfo.cmd --no-errors</Command>
    </PreBuildEvent>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <ClCompile>
      <Optimization>MinSpace</Optimization>
      <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
      <FunctionLevelLinking>true</FunctionLevelLinking>
      <IntrinsicFunctions>true</IntrinsicFunctions>
      <PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
      <DebugInformationFormat>None</DebugInformationFormat>
      <BufferSecurityCheck>false</BufferSecurityCheck>
      <ExceptionHandling>false</ExceptionHandling>
      <RuntimeTypeInfo>false</RuntimeTypeInfo>
      <EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
      <OmitDefaultLibName>true</OmitDefaultLibName>
      <AdditionalOptions>/Zc:alignedNew- /Zc:sizedDealloc- %(AdditionalOptions)</AdditionalOptions>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <MinimumRequiredVersion>6.0</MinimumRequiredVersion>
      <GenerateDebugInformation>false</GenerateDebugInformation>
      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
      <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
      <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
      <AdditionalDependencies>msvcrt32.lib;avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>
      <AdditionalOptions>/EMITTOOLVERSIONINFO:NO /NOVCFEATURE /NOCOFFGRPINFO /STUB:EmptyStub.bin /MERGE:.pdata=.rdata %(AdditionalOptions)</AdditionalOptions>
      <RandomizedBaseAddress>false</RandomizedBaseAddress>
    </Link>
    <ResourceCompile>
      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
    </ResourceCompile>
    <PreBuildEvent>
      <Command>BuildInfo.cmd --no-errors</Command>
    </PreBuildEvent>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <ClCompile>
      <Optimization>MinSpace</Optimization>
      <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
      <FunctionLevelLinking>true</FunctionLevelLinking>
      <IntrinsicFunctions>true</IntrinsicFunctions>
      <PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
      <DebugInformationFormat>None</DebugInformationFormat>
      <BufferSecurityCheck>false</BufferSecurityCheck>
      <ExceptionHandling>false</ExceptionHandling>
      <RuntimeTypeInfo>false</RuntimeTypeInfo>
      <OmitDefaultLibName>true</OmitDefaultLibName>
      <AdditionalOptions>/Zc:alignedNew- /Zc:sizedDealloc- %(AdditionalOptions)</AdditionalOptions>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <MinimumRequiredVersion>6.0</MinimumRequiredVersion>
      <GenerateDebugInformation>false</GenerateDebugInformation>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
      <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
      <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
      <AdditionalDependencies>msvcrt64.lib;avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>
      <AdditionalOptions>/EMITTOOLVERSIONINFO:NO /NOVCFEATURE /NOCOFFGRPINFO /STUB:EmptyStub.bin /MERGE:.pdata=.rdata %(AdditionalOptions)</AdditionalOptions>
      <RandomizedBaseAddress>false</RandomizedBaseAddress>
    </Link>
    <ResourceCompile>
      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
    </ResourceCompile>
    <PreBuildEvent>
      <Command>BuildInfo.cmd --no-errors</Command>
    </PreBuildEvent>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
    <ClCompile>
      <Optimization>MinSpace</Optimization>
      <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
      <FunctionLevelLinking>true</FunctionLevelLinking>
      <IntrinsicFunctions>true</IntrinsicFunctions>
      <PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
      <DebugInformationFormat>None</DebugInformationFormat>
      <BufferSecurityCheck>false</BufferSecurityCheck>
      <ExceptionHandling>false</ExceptionHandling>
      <RuntimeTypeInfo>false</RuntimeTypeInfo>
      <AdditionalOptions>/Zc:alignedNew- /Zc:sizedDealloc- %(AdditionalOptions)</AdditionalOptions>
    </ClCompile>
    <Link>
      <SubSystem>Windows</SubSystem>
      <MinimumRequiredVersion>10.0</MinimumRequiredVersion>
      <GenerateDebugInformation>false</GenerateDebugInformation>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
      <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
      <AdditionalDependencies>avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>
      <AdditionalOptions>/EMITTOOLVERSIONINFO:NO /NOVCFEATURE /NOCOFFGRPINFO /STUB:EmptyStub.bin /MERGE:.pdata=.rdata %(AdditionalOptions)</AdditionalOptions>
    </Link>
    <ResourceCompile>
      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
    </ResourceCompile>
    <PreBuildEvent>
      <Command>BuildInfo.cmd --no-errors</Command>
    </PreBuildEvent>
  </ItemDefinitionGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
  <ImportGroup Label="ExtensionTargets">
  </ImportGroup>
</Project>

================================================
FILE: SoundKeeper.vcxproj.filters
================================================
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <Filter Include="Source Files">
      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
    </Filter>
    <Filter Include="Resource Files">
      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>
    </Filter>
    <Filter Include="Source Files\Common">
      <UniqueIdentifier>{882ed8e4-f8b5-427d-974f-3f7a45977aca}</UniqueIdentifier>
    </Filter>
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="BuildInfo.hpp">
      <Filter>Resource Files</Filter>
    </ClInclude>
    <ClInclude Include="Resources.hpp">
      <Filter>Resource Files</Filter>
    </ClInclude>
    <ClInclude Include="Common.hpp">
      <Filter>Source Files</Filter>
    </ClInclude>
    <ClInclude Include="Common\BasicMacros.hpp">
      <Filter>Source Files\Common</Filter>
    </ClInclude>
    <ClInclude Include="Common\Defer.hpp">
      <Filter>Source Files\Common</Filter>
    </ClInclude>
    <ClInclude Include="Common\NtBase.hpp">
      <Filter>Source Files\Common</Filter>
    </ClInclude>
    <ClInclude Include="Common\NtCriticalSection.hpp">
      <Filter>Source Files\Common</Filter>
    </ClInclude>
    <ClInclude Include="Common\NtEvent.hpp">
      <Filter>Source Files\Common</Filter>
    </ClInclude>
    <ClInclude Include="Common\NtHandle.hpp">
      <Filter>Source Files\Common</Filter>
    </ClInclude>
    <ClInclude Include="Common\NtUtils.hpp">
      <Filter>Source Files\Common</Filter>
    </ClInclude>
    <ClInclude Include="Common\StrUtils.hpp">
      <Filter>Source Files\Common</Filter>
    </ClInclude>
    <ClInclude Include="CSoundKeeper.hpp">
      <Filter>Source Files</Filter>
    </ClInclude>
    <ClInclude Include="CSoundSession.hpp">
      <Filter>Source Files</Filter>
    </ClInclude>
  </ItemGroup>
  <ItemGroup>
    <ClCompile Include="CSoundKeeper.cpp">
      <Filter>Source Files</Filter>
    </ClCompile>
    <ClCompile Include="CSoundSession.cpp">
      <Filter>Source Files</Filter>
    </ClCompile>
    <ClCompile Include="RuntimeHacks.cpp">
      <Filter>Source Files</Filter>
    </ClCompile>
  </ItemGroup>
  <ItemGroup>
    <ResourceCompile Include="Resources.rc">
      <Filter>Resource Files</Filter>
    </ResourceCompile>
  </ItemGroup>
  <ItemGroup>
    <Image Include="Main.ico">
      <Filter>Resource Files</Filter>
    </Image>
  </ItemGroup>
  <ItemGroup>
    <UpToDateCheckInput Include="BuildInfo.cmd">
      <Filter>Resource Files</Filter>
    </UpToDateCheckInput>
    <UpToDateCheckInput Include="BuildInfo.csx">
      <Filter>Resource Files</Filter>
    </UpToDateCheckInput>
    <UpToDateCheckInput Include="BuildInfo.rc2">
      <Filter>Resource Files</Filter>
    </UpToDateCheckInput>
  </ItemGroup>
  <ItemGroup>
    <CopyFileToFolders Include="ReadMe.txt" />
  </ItemGroup>
</Project>
Download .txt
gitextract_4oaczphi/

├── .editorconfig
├── .gitignore
├── BuildInfo.cmd
├── BuildInfo.csx
├── BuildInfo.hpp
├── BuildInfo.rc2
├── CSoundKeeper.cpp
├── CSoundKeeper.hpp
├── CSoundSession.cpp
├── CSoundSession.hpp
├── Common/
│   ├── BasicMacros.hpp
│   ├── Defer.hpp
│   ├── NtBase.hpp
│   ├── NtCriticalSection.hpp
│   ├── NtEvent.hpp
│   ├── NtHandle.hpp
│   ├── NtUtils.hpp
│   └── StrUtils.hpp
├── Common.hpp
├── License.md
├── ReadMe.txt
├── Resources.hpp
├── Resources.rc
├── RuntimeHacks.cpp
├── SoundKeeper.sln
├── SoundKeeper.vcxproj
├── SoundKeeper.vcxproj.filters
├── msvcrt32.lib
└── msvcrt64.lib
Download .txt
SYMBOL INDEX (137 symbols across 12 files)

FILE: CSoundKeeper.cpp
  function GetSecondsToSleeping (line 21) | uint32_t GetSecondsToSleeping()
  function ULONG (line 65) | ULONG STDMETHODCALLTYPE CSoundKeeper::AddRef()
  function ULONG (line 70) | ULONG STDMETHODCALLTYPE CSoundKeeper::Release()
  function HRESULT (line 80) | HRESULT STDMETHODCALLTYPE CSoundKeeper::QueryInterface(REFIID riid, VOID...
  function HRESULT (line 103) | HRESULT STDMETHODCALLTYPE CSoundKeeper::OnDefaultDeviceChanged(EDataFlow...
  function HRESULT (line 113) | HRESULT STDMETHODCALLTYPE CSoundKeeper::OnDeviceAdded(LPCWSTR device_id)
  function HRESULT (line 123) | HRESULT STDMETHODCALLTYPE CSoundKeeper::OnDeviceRemoved(LPCWSTR device_id)
  function HRESULT (line 133) | HRESULT STDMETHODCALLTYPE CSoundKeeper::OnDeviceStateChanged(LPCWSTR dev...
  function HRESULT (line 143) | HRESULT STDMETHODCALLTYPE CSoundKeeper::OnPropertyValueChanged(LPCWSTR d...
  function GetDeviceFormFactor (line 150) | uint32_t GetDeviceFormFactor(IMMDevice* device)
  function HRESULT (line 193) | HRESULT CSoundKeeper::Start()
  function HRESULT (line 346) | HRESULT CSoundKeeper::Stop()
  function HRESULT (line 372) | HRESULT CSoundKeeper::Restart()
  function CSoundSession (line 393) | CSoundSession* CSoundKeeper::FindSession(LPCWSTR device_id)
  function HRESULT (line 552) | HRESULT CSoundKeeper::Run()
  function FORCEINLINE (line 774) | FORCEINLINE HRESULT CSoundKeeper::Main()
  function main (line 817) | int main()
  function wWinMain (line 838) | int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrev...

FILE: CSoundKeeper.hpp
  type KeepDeviceType (line 8) | enum class KeepDeviceType { None, Primary, Digital, Analog, All }
  type KeepStreamType (line 9) | enum class KeepStreamType { None, Zero, Fluctuate, Sine, WhiteNoise, Bro...
  class CSoundKeeper (line 11) | class CSoundKeeper
    method SetDeviceType (line 71) | void SetDeviceType(KeepDeviceType device_type) { m_cfg_device_type = d...
    method SetStreamType (line 72) | void SetStreamType(KeepStreamType stream_type) { m_cfg_stream_type = s...
    method SetAllowRemote (line 73) | void SetAllowRemote(bool allow) { m_cfg_allow_remote = allow; }
    method SetNoSleep (line 74) | void SetNoSleep(bool value) { m_cfg_no_sleep = value; }
    method KeepDeviceType (line 75) | KeepDeviceType GetDeviceType() const { return m_cfg_device_type; }
    method KeepStreamType (line 76) | KeepStreamType GetStreamType() const { return m_cfg_stream_type; }
    method GetAllowRemote (line 77) | bool GetAllowRemote() const { return m_cfg_allow_remote; }
    method GetNoSleep (line 78) | bool GetNoSleep() const { return m_cfg_no_sleep; }
    method SetFrequency (line 81) | void SetFrequency(double frequency) { m_cfg_frequency = frequency; }
    method SetAmplitude (line 82) | void SetAmplitude(double amplitude) { m_cfg_amplitude = amplitude; }
    method SetPeriodicPlaying (line 83) | void SetPeriodicPlaying(double seconds) { m_cfg_play_seconds = seconds; }
    method SetPeriodicWaiting (line 84) | void SetPeriodicWaiting(double seconds) { m_cfg_wait_seconds = seconds; }
    method SetFading (line 85) | void SetFading(double seconds) { m_cfg_fade_seconds = seconds; }
    method GetFrequency (line 86) | double GetFrequency() const { return m_cfg_frequency; }
    method GetAmplitude (line 87) | double GetAmplitude() const { return m_cfg_amplitude; }
    method GetPeriodicPlaying (line 88) | double GetPeriodicPlaying() const { return m_cfg_play_seconds; }
    method GetPeriodicWaiting (line 89) | double GetPeriodicWaiting() const { return m_cfg_wait_seconds; }
    method GetFading (line 90) | double GetFading() const { return m_cfg_fade_seconds; }
  class CSoundKeeper (line 15) | class CSoundKeeper : public IMMNotificationClient
    method SetDeviceType (line 71) | void SetDeviceType(KeepDeviceType device_type) { m_cfg_device_type = d...
    method SetStreamType (line 72) | void SetStreamType(KeepStreamType stream_type) { m_cfg_stream_type = s...
    method SetAllowRemote (line 73) | void SetAllowRemote(bool allow) { m_cfg_allow_remote = allow; }
    method SetNoSleep (line 74) | void SetNoSleep(bool value) { m_cfg_no_sleep = value; }
    method KeepDeviceType (line 75) | KeepDeviceType GetDeviceType() const { return m_cfg_device_type; }
    method KeepStreamType (line 76) | KeepStreamType GetStreamType() const { return m_cfg_stream_type; }
    method GetAllowRemote (line 77) | bool GetAllowRemote() const { return m_cfg_allow_remote; }
    method GetNoSleep (line 78) | bool GetNoSleep() const { return m_cfg_no_sleep; }
    method SetFrequency (line 81) | void SetFrequency(double frequency) { m_cfg_frequency = frequency; }
    method SetAmplitude (line 82) | void SetAmplitude(double amplitude) { m_cfg_amplitude = amplitude; }
    method SetPeriodicPlaying (line 83) | void SetPeriodicPlaying(double seconds) { m_cfg_play_seconds = seconds; }
    method SetPeriodicWaiting (line 84) | void SetPeriodicWaiting(double seconds) { m_cfg_wait_seconds = seconds; }
    method SetFading (line 85) | void SetFading(double seconds) { m_cfg_fade_seconds = seconds; }
    method GetFrequency (line 86) | double GetFrequency() const { return m_cfg_frequency; }
    method GetAmplitude (line 87) | double GetAmplitude() const { return m_cfg_amplitude; }
    method GetPeriodicPlaying (line 88) | double GetPeriodicPlaying() const { return m_cfg_play_seconds; }
    method GetPeriodicWaiting (line 89) | double GetPeriodicWaiting() const { return m_cfg_wait_seconds; }
    method GetFading (line 90) | double GetFading() const { return m_cfg_fade_seconds; }

FILE: CSoundSession.cpp
  function HRESULT (line 33) | HRESULT STDMETHODCALLTYPE CSoundSession::QueryInterface(REFIID iid, void...
  function ULONG (line 58) | ULONG STDMETHODCALLTYPE CSoundSession::AddRef()
  function ULONG (line 63) | ULONG STDMETHODCALLTYPE CSoundSession::Release()
  function DWORD (line 125) | DWORD APIENTRY CSoundSession::StartRenderingThread(LPVOID context)
  function DWORD (line 161) | DWORD CSoundSession::RenderingThread()
  function HRESULT (line 581) | HRESULT CSoundSession::Render()
  function HRESULT (line 947) | HRESULT CSoundSession::OnStateChanged(AudioSessionState NewState)
  function HRESULT (line 962) | HRESULT CSoundSession::OnSessionDisconnected(AudioSessionDisconnectReaso...
  function HRESULT (line 994) | HRESULT CSoundSession::OnSimpleVolumeChanged(float NewSimpleVolume, BOOL...

FILE: CSoundSession.hpp
  class CSoundSession (line 8) | class CSoundSession
    method EnableWaitExclusiveWorkaround (line 20) | static void EnableWaitExclusiveWorkaround(bool enable) { g_is_leaky_wa...
    type RenderingMode (line 35) | enum class RenderingMode { Stop, Rendering, Retry, WaitExclusive, TryO...
    method DeferNextMode (line 42) | void DeferNextMode(RenderingMode next_mode)
    type SampleType (line 52) | enum class SampleType { Unknown, Int16, Int24, Int32, Float32 }
    method IsStarted (line 87) | bool IsStarted() const { return m_is_started; }
    method IsValid (line 88) | bool IsValid() const { return m_curr_mode != RenderingMode::Invalid; }
    method LPCWSTR (line 89) | LPCWSTR GetDeviceId() const { return m_device_id; }
    method DWORD (line 90) | DWORD GetDeviceState() { DWORD state = 0; m_endpoint->GetState(&state)...
    method ResetCurrent (line 92) | void ResetCurrent()
    method SetStreamType (line 101) | void SetStreamType(KeepStreamType stream_type)
    method KeepStreamType (line 107) | KeepStreamType GetStreamType() const
    method SetFrequency (line 114) | void SetFrequency(double frequency)
    method GetFrequency (line 120) | double GetFrequency() const
    method SetAmplitude (line 125) | void SetAmplitude(double amplitude)
    method GetAmplitude (line 131) | double GetAmplitude() const
    method SetPeriodicPlaying (line 138) | void SetPeriodicPlaying(double seconds)
    method GetPeriodicPlaying (line 144) | double GetPeriodicPlaying() const
    method SetPeriodicWaiting (line 149) | void SetPeriodicWaiting(double seconds)
    method GetPeriodicWaiting (line 155) | double GetPeriodicWaiting() const
    method SetFading (line 160) | void SetFading(double seconds)
    method GetFading (line 166) | double GetFading() const
  class CSoundSession (line 12) | class CSoundSession : IAudioSessionEvents
    method EnableWaitExclusiveWorkaround (line 20) | static void EnableWaitExclusiveWorkaround(bool enable) { g_is_leaky_wa...
    type RenderingMode (line 35) | enum class RenderingMode { Stop, Rendering, Retry, WaitExclusive, TryO...
    method DeferNextMode (line 42) | void DeferNextMode(RenderingMode next_mode)
    type SampleType (line 52) | enum class SampleType { Unknown, Int16, Int24, Int32, Float32 }
    method IsStarted (line 87) | bool IsStarted() const { return m_is_started; }
    method IsValid (line 88) | bool IsValid() const { return m_curr_mode != RenderingMode::Invalid; }
    method LPCWSTR (line 89) | LPCWSTR GetDeviceId() const { return m_device_id; }
    method DWORD (line 90) | DWORD GetDeviceState() { DWORD state = 0; m_endpoint->GetState(&state)...
    method ResetCurrent (line 92) | void ResetCurrent()
    method SetStreamType (line 101) | void SetStreamType(KeepStreamType stream_type)
    method KeepStreamType (line 107) | KeepStreamType GetStreamType() const
    method SetFrequency (line 114) | void SetFrequency(double frequency)
    method GetFrequency (line 120) | double GetFrequency() const
    method SetAmplitude (line 125) | void SetAmplitude(double amplitude)
    method GetAmplitude (line 131) | double GetAmplitude() const
    method SetPeriodicPlaying (line 138) | void SetPeriodicPlaying(double seconds)
    method GetPeriodicPlaying (line 144) | double GetPeriodicPlaying() const
    method SetPeriodicWaiting (line 149) | void SetPeriodicWaiting(double seconds)
    method GetPeriodicWaiting (line 155) | double GetPeriodicWaiting() const
    method SetFading (line 160) | void SetFading(double seconds)
    method GetFading (line 166) | double GetFading() const

FILE: Common.hpp
  function DebugLogImpl (line 19) | inline void DebugLogImpl(const char * funcname, const char * type, const...

FILE: Common/Defer.hpp
  type Deferrer (line 6) | struct Deferrer
    method Deferrer (line 9) | Deferrer(T f) : f(f) { }
    method Deferrer (line 10) | Deferrer(const Deferrer&) = delete;

FILE: Common/NtBase.hpp
  type _UNICODE_STRING (line 34) | struct _UNICODE_STRING
  type _OBJECT_ATTRIBUTES (line 43) | struct _OBJECT_ATTRIBUTES

FILE: Common/NtCriticalSection.hpp
  class CriticalSection (line 9) | class CriticalSection
    method CriticalSection (line 11) | CriticalSection(const CriticalSection&) = delete;
    method CriticalSection (line 12) | CriticalSection& operator=(const CriticalSection&) = delete;
    method CriticalSection (line 20) | CriticalSection()
    method CriticalSection (line 25) | CriticalSection(DWORD spin_count)
    method Lock (line 35) | void Lock()
    method TryLock (line 40) | bool TryLock()
    method TryLock (line 45) | bool TryLock(DWORD timeout)
    method Unlock (line 63) | void Unlock()
  class ScopedLock (line 70) | class ScopedLock
    method ScopedLock (line 72) | ScopedLock() = delete;
    method ScopedLock (line 73) | ScopedLock(const ScopedLock&) = delete;
    method ScopedLock (line 74) | ScopedLock& operator=(const ScopedLock&) = delete;
    method ScopedLock (line 82) | ScopedLock(T& lock) : m_lock(lock)

FILE: Common/NtEvent.hpp
  type _EVENT_INFORMATION_CLASS (line 55) | enum _EVENT_INFORMATION_CLASS
  type _EVENT_BASIC_INFORMATION (line 60) | struct _EVENT_BASIC_INFORMATION
  function EXTERN_C_END (line 77) | EXTERN_C_END
  class AutoResetEvent (line 146) | class AutoResetEvent : public SyncEvent
    method AutoResetEvent (line 150) | AutoResetEvent(const bool state) : SyncEvent(ResetMode::Auto, state) {}
    method AutoResetEvent (line 152) | AutoResetEvent& operator=(const bool state)
  class ManualResetEvent (line 166) | class ManualResetEvent : public SyncEvent
    method ManualResetEvent (line 170) | ManualResetEvent(const bool state) : SyncEvent(ResetMode::Manual, stat...
    method ManualResetEvent (line 172) | ManualResetEvent& operator=(const bool state)

FILE: Common/NtHandle.hpp
  class Handle (line 8) | class Handle
    method Handle (line 10) | Handle(const Handle&) = delete;
    method Handle (line 11) | Handle& operator= (const Handle&) = delete;
    method Handle (line 19) | Handle(HANDLE handle) : m_handle(handle) {}
    method HANDLE (line 20) | HANDLE GetHandle() const { return m_handle; }
  function DWORD (line 25) | inline DWORD AlertableSleep(DWORD timeout)
  function DWORD (line 30) | inline DWORD WaitForOne(HANDLE handle, DWORD timeout = INFINITE)
  function DWORD (line 35) | inline DWORD AlertableWaitForOne(HANDLE handle, DWORD timeout = INFINITE)
  function DWORD (line 40) | inline DWORD WaitForAny(std::initializer_list<HANDLE> handles, DWORD tim...
  function DWORD (line 45) | inline DWORD AlertableWaitForAny(std::initializer_list<HANDLE> handles, ...
  function DWORD (line 50) | inline DWORD WaitForAll(std::initializer_list<HANDLE> handles, DWORD tim...
  function DWORD (line 55) | inline DWORD AlertableWaitForAll(std::initializer_list<HANDLE> handles, ...

FILE: Common/NtUtils.hpp
  function SafeRelease (line 8) | void SafeRelease(T*& com_obj_ptr)
  function GetNtBuildNumber (line 29) | inline uint32_t GetNtBuildNumber()
  function HMODULE (line 44) | inline HMODULE GetKernelBaseDll()
  function HRESULT (line 53) | inline HRESULT WINAPI SetThreadDescriptionW(_In_ HANDLE hThread, _In_ PC...
  function HRESULT (line 70) | inline HRESULT SetCurrentThreadDescriptionW(PCWSTR desc)
  function HRESULT (line 77) | inline HRESULT WINAPI GetThreadDescriptionW(_In_ HANDLE hThread, _Outptr...
  type RsrcSpan (line 96) | struct RsrcSpan
  function RsrcSpan (line 103) | inline RsrcSpan GetResource(HMODULE hModule, LPCTSTR lpName, LPCTSTR lpT...
  function VS_FIXEDFILEINFO (line 112) | inline const VS_FIXEDFILEINFO* GetFixedVersion(HMODULE hModule = NULL)

FILE: Common/StrUtils.hpp
  function NoTransform (line 13) | constexpr char NoTransform(char c)
  function AsciiToLower (line 18) | constexpr char AsciiToLower(char c)
  function AsciiToUpper (line 23) | constexpr char AsciiToUpper(char c)
  function wchar_t (line 28) | constexpr wchar_t NoTransform(wchar_t c)
  function wchar_t (line 33) | constexpr wchar_t AsciiToLower(wchar_t c)
  function wchar_t (line 38) | constexpr wchar_t AsciiToUpper(wchar_t c)
  function StringCompare (line 46) | constexpr int StringCompare(const char* l, const char* r)
  function StringCompare (line 53) | constexpr int StringCompare(const wchar_t* l, const wchar_t* r)
  function StringEquals (line 62) | constexpr bool StringEquals(const char* l, const char* r)
  function StringEquals (line 68) | constexpr bool StringEquals(const wchar_t* l, const wchar_t* r)
  function wchar_t (line 90) | constexpr const wchar_t* StringFindPtr(const wchar_t* str, const wchar_t...
  function StringIndexOf (line 106) | constexpr size_t StringIndexOf(const char* str, const char* sub)
  function StringIndexOf (line 113) | constexpr size_t StringIndexOf(const wchar_t* str, const wchar_t* sub)
  function StringContains (line 122) | constexpr bool StringContains(const char* str, const char* sub)
  function StringContains (line 128) | constexpr bool StringContains(const wchar_t* str, const wchar_t* sub)
Condensed preview — 29 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (142K chars).
[
  {
    "path": ".editorconfig",
    "chars": 300,
    "preview": "root = true\r\n\r\n[*]\r\ncharset = utf-8\r\nend_of_line = crlf\r\nindent_style = tab\r\nindent_size = 4\r\ntrim_trailing_whitespace ="
  },
  {
    "path": ".gitignore",
    "chars": 53,
    "preview": "**~**\r\n/.vs/\r\n/Bin/\r\n/Build/\r\n*.aps\r\n*.vcxproj.user\r\n"
  },
  {
    "path": "BuildInfo.cmd",
    "chars": 993,
    "preview": "@echo off\r\nsetlocal\r\n\r\nset NO_ERRORS=0\r\n\r\nfor %%a in (%*) do (\r\n\tif \"%%a\"==\"--no-errors\" set NO_ERRORS=1\r\n)\r\n\r\nwhere csi"
  },
  {
    "path": "BuildInfo.csx",
    "chars": 11245,
    "preview": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Text;\r\nusing System.Text.RegularExpressions;\r\nusing Syste"
  },
  {
    "path": "BuildInfo.hpp",
    "chars": 521,
    "preview": "#ifndef BUILDINFO_HPP\r\n#define BUILDINFO_HPP\r\n\r\n#define APP_NAME        \"Sound Keeper\"\r\n#define APP_FILE_NAME   \"SoundKe"
  },
  {
    "path": "BuildInfo.rc2",
    "chars": 1300,
    "preview": "#include \"BuildInfo.hpp\"\r\n#include <winres.h>\r\n\r\n#define RAWSTR(x) #x\r\n#define STR(x) RAWSTR(x)\r\n\r\nVS_VERSION_INFO VERSI"
  },
  {
    "path": "CSoundKeeper.cpp",
    "chars": 21111,
    "preview": "// Tell mmdeviceapi.h to define its GUIDs in this translation unit.\r\n#define INITGUID\r\n\r\n#include \"CSoundKeeper.hpp\"\r\n\r\n"
  },
  {
    "path": "CSoundKeeper.hpp",
    "chars": 3771,
    "preview": "#pragma once\r\n\r\n#include \"Common.hpp\"\r\n\r\n#include <mmdeviceapi.h>\r\n#include <audiopolicy.h>\r\n\r\nenum class KeepDeviceType"
  },
  {
    "path": "CSoundSession.cpp",
    "chars": 27887,
    "preview": "#include \"CSoundSession.hpp\"\r\n\r\n// Enable Multimedia Class Scheduler Service.\r\n#define ENABLE_MMCSS\r\n\r\n#ifdef ENABLE_MMC"
  },
  {
    "path": "CSoundSession.hpp",
    "chars": 5404,
    "preview": "#pragma once\r\n\r\n#include \"Common.hpp\"\r\n\r\n#include <mmdeviceapi.h>\r\n#include <audioclient.h>\r\n\r\nclass CSoundSession;\r\n\r\n#"
  },
  {
    "path": "Common/BasicMacros.hpp",
    "chars": 451,
    "preview": "#pragma once\r\n\r\n// Mark a variable as unused explicitly to avoid warnings.\r\n#define UNUSED(x) (void)(x)\r\n\r\n// Stringify "
  },
  {
    "path": "Common/Defer.hpp",
    "chars": 328,
    "preview": "#pragma once\r\n\r\n#include \"BasicMacros.hpp\"\r\n\r\ntemplate <typename T>\r\nstruct Deferrer\r\n{\r\n\tT f;\r\n\tDeferrer(T f) : f(f) { "
  },
  {
    "path": "Common/NtBase.hpp",
    "chars": 1566,
    "preview": "#pragma once\r\n\r\n#include <stdint.h>\r\n#include <stddef.h>\r\n\r\n#define _WIN32_WINNT 0x0601\r\n#include <sdkddkver.h>\r\n#define"
  },
  {
    "path": "Common/NtCriticalSection.hpp",
    "chars": 1353,
    "preview": "#pragma once\r\n\r\n#include \"NtBase.hpp\"\r\n\r\n//\r\n// A simple reentrant lock based on Windows Critical Sections.\r\n//\r\n\r\nclass"
  },
  {
    "path": "Common/NtEvent.hpp",
    "chars": 3530,
    "preview": "#pragma once\r\n\r\n#include \"NtBase.hpp\"\r\n#include \"NtHandle.hpp\"\r\n\r\n// ---------------------------------------------------"
  },
  {
    "path": "Common/NtHandle.hpp",
    "chars": 1789,
    "preview": "#pragma once\r\n\r\n#include \"NtBase.hpp\"\r\n#include <initializer_list>\r\n\r\n// -----------------------------------------------"
  },
  {
    "path": "Common/NtUtils.hpp",
    "chars": 4342,
    "preview": "#pragma once\r\n\r\n#include \"NtBase.hpp\"\r\n#include \"StrUtils.hpp\"\r\n\r\n// ---------------------------------------------------"
  },
  {
    "path": "Common/StrUtils.hpp",
    "chars": 4183,
    "preview": "#pragma once\r\n\r\n#include <stdint.h>\r\n#include <stddef.h>\r\n\r\n// ---------------------------------------------------------"
  },
  {
    "path": "Common.hpp",
    "chars": 2343,
    "preview": "#pragma once\r\n\r\n#define _CRT_SECURE_NO_WARNINGS\r\n#define _USE_MATH_DEFINES\r\n\r\n#include \"Common/BasicMacros.hpp\"\r\n#includ"
  },
  {
    "path": "License.md",
    "chars": 1090,
    "preview": "# MIT License\r\n\r\nCopyright (c) 2014-2024 Evgeny Vrublevsky\r\n\r\nPermission is hereby granted, free of charge, to any perso"
  },
  {
    "path": "ReadMe.txt",
    "chars": 6251,
    "preview": "Sound Keeper v1.3.5 [2025/07/05]\r\nhttps://veg.by/projects/soundkeeper/\r\n\r\nPrevents SPDIF/HDMI digital audio playback dev"
  },
  {
    "path": "Resources.hpp",
    "chars": 497,
    "preview": "//{{NO_DEPENDENCIES}}\r\n// Microsoft Visual C++ generated include file.\r\n// Used by SoundKeeper.rc\r\n//\r\n#define IDI_MAIN "
  },
  {
    "path": "Resources.rc",
    "chars": 1678,
    "preview": "// Microsoft Visual C++ generated resource script.\r\n//\r\n#pragma code_page(65001)\r\n\r\n#include \"Resources.hpp\"\r\n\r\n#define "
  },
  {
    "path": "RuntimeHacks.cpp",
    "chars": 197,
    "preview": "// We use msvcrt.dll as a C runtime to save space in release builds.\r\n// It has limited set of functions, so we reimplem"
  },
  {
    "path": "SoundKeeper.sln",
    "chars": 2431,
    "preview": "\r\nMicrosoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio Version 17\r\nVisualStudioVersion = 17.9.3"
  },
  {
    "path": "SoundKeeper.vcxproj",
    "chars": 19751,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/m"
  },
  {
    "path": "SoundKeeper.vcxproj.filters",
    "chars": 3205,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbui"
  }
]

// ... and 2 more files (download for full content)

About this extraction

This page contains the full source code of the vrubleg/soundkeeper GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 29 files (124.6 KB), approximately 33.6k tokens, and a symbol index with 137 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!