[
  {
    "path": ".editorconfig",
    "content": "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 = true\r\ninsert_final_newline = true\r\nguidelines = 80, 120\r\n\r\n[*.{vcxproj,vcxitems,filters,user,props,targets}]\r\ninsert_final_newline = unset\r\nindent_style = space\r\nindent_size = 2\r\n"
  },
  {
    "path": ".gitignore",
    "content": "**~**\r\n/.vs/\r\n/Bin/\r\n/Build/\r\n*.aps\r\n*.vcxproj.user\r\n"
  },
  {
    "path": "BuildInfo.cmd",
    "content": "@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 1>nul 2>&1 && goto :ready\r\n\r\nset VSWHERE_PATH=%ProgramFiles%\\Microsoft Visual Studio\\Installer\\vswhere.exe\r\nif not exist \"%VSWHERE_PATH%\" set VSWHERE_PATH=%ProgramFiles(x86)%\\Microsoft Visual Studio\\Installer\\vswhere.exe\r\nif not exist \"%VSWHERE_PATH%\" goto :error\r\nfor /f \"usebackq delims=#\" %%a in (`\"%VSWHERE_PATH%\" -latest -property installationPath`) do set VSDEVCMD_PATH=%%a\\Common7\\Tools\\VsDevCmd.bat\r\nif not exist \"%VSDEVCMD_PATH%\" goto :error\r\nset VSCMD_SKIP_SENDTELEMETRY=1\r\ncall \"%VSDEVCMD_PATH%\" -no_logo -startdir=none\r\n\r\nwhere csi 1>nul 2>&1 && goto :ready\r\n\r\n:error\r\n\r\nif \"%NO_ERRORS%\"==\"1\" (\r\n\techo Warning: Can't find csi to execute BuildInfo.csx. BuildInfo.hpp won't be updated.\r\n\texit /b 0\r\n) else (\r\n\techo Error: Can't find csi to execute BuildInfo.csx. BuildInfo.hpp won't be updated.\r\n\texit /b 1\r\n)\r\n\r\n:ready\r\n\r\ncsi BuildInfo.csx -- BuildInfo.hpp %*\r\n"
  },
  {
    "path": "BuildInfo.csx",
    "content": "using System;\r\nusing System.Collections.Generic;\r\nusing System.Text;\r\nusing System.Text.RegularExpressions;\r\nusing System.Linq;\r\nusing System.Diagnostics;\r\nusing System.Globalization;\r\n\r\nvar args = Environment.GetCommandLineArgs().ToList();\r\nargs = (args.IndexOf(\"--\") > -1) ? args.Skip(args.IndexOf(\"--\") + 1).ToList() : (new List<string>());\r\n\r\nif (args.Count == 0 || args[0][0] == '-')\r\n{\r\n\tConsole.WriteLine(\"Error: Target file name is not specified.\");\r\n\tConsole.WriteLine(\"Usage: csi BuildInfo.csx -- <BuildInfo.hpp> [--no-errors] [--force]\");\r\n\tEnvironment.Exit(1);\r\n}\r\n\r\nvar BUILD_INFO_FILE = args[0];\r\nvar NO_ERRORS = args.IndexOf(\"--no-errors\") != -1;\r\nvar FORCE_UPDATE = args.IndexOf(\"--force\") != -1;\r\n\r\nclass Error : Exception\r\n{\r\n\tpublic Error() { }\r\n\tpublic Error(string message) : base(message) { }\r\n\tpublic Error(string message, Exception e) : base(message, e) { }\r\n}\r\n\r\nclass Success : Exception\r\n{\r\n\tpublic Success() { }\r\n\tpublic Success(string message) : base(message) { }\r\n\tpublic Success(string message, Exception e) : base(message, e) { }\r\n}\r\n\r\nint RunCmd(string cmd, out string stdout, out string stderr)\r\n{\r\n\tstring name = \"\";\r\n\tstring args = \"\";\r\n\r\n\tcmd = cmd.Trim();\r\n\tif (cmd.Length == 0) { throw new ArgumentException(\"Empty command\"); }\r\n\r\n\tif (cmd[0] == '\"')\r\n\t{\r\n\t\tvar split_pos = cmd.IndexOf('\"', 1);\r\n\t\tif (split_pos == -1) { throw new ArgumentException(\"Unpaired quote in command\"); }\r\n\t\tname = cmd.Substring(1, split_pos).Trim();\r\n\t\targs = cmd.Substring(split_pos + 1).Trim();\r\n\t}\r\n\telse\r\n\t{\r\n\t\tvar split_pos = cmd.IndexOfAny(new char[] {' ', '\\t'});\r\n\t\tif (split_pos == -1)\r\n\t\t{\r\n\t\t\tname = cmd;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tname = cmd.Substring(0, split_pos);\r\n\t\t\targs = cmd.Substring(split_pos + 1).Trim();\r\n\t\t}\r\n\t}\r\n\r\n\tusing var process = new Process();\r\n\tvar stdout_buffer = new StringBuilder();\r\n\tvar stderr_buffer = new StringBuilder();\r\n\r\n\tprocess.StartInfo.FileName = name;\r\n\tprocess.StartInfo.Arguments = args;\r\n\tprocess.StartInfo.UseShellExecute = false;\r\n\tprocess.StartInfo.RedirectStandardOutput = true;\r\n\tprocess.StartInfo.RedirectStandardError = true;\r\n\tprocess.OutputDataReceived += new DataReceivedEventHandler((sender, e) => { stdout_buffer.AppendLine(e.Data); });\r\n\tprocess.ErrorDataReceived += new DataReceivedEventHandler((sender, e) => { stderr_buffer.AppendLine(e.Data); });\r\n\r\n\tprocess.Start();\r\n\tprocess.BeginOutputReadLine();\r\n\tprocess.BeginErrorReadLine();\r\n\tprocess.WaitForExit();\r\n\r\n\tstdout = stdout_buffer.ToString();\r\n\tstderr = stderr_buffer.ToString();\r\n\treturn process.ExitCode;\r\n}\r\n\r\nint RunCmd(string cmd, out string stdout)\r\n{\r\n\treturn RunCmd(cmd, out stdout, out var stderr);\r\n}\r\n\r\nint RunCmd(string cmd)\r\n{\r\n\treturn RunCmd(cmd, out var stdout, out var stderr);\r\n}\r\n\r\nclass DefineItem\r\n{\r\n\tpublic string Prefix  = \"\";\r\n\tpublic string Name    = \"\";\r\n\tpublic string Space   = \"\";\r\n\tpublic string Value   = \"\";\r\n\tpublic string Ignored = \"\";\r\n\tpublic bool   Deleted = false;\r\n\r\n\tpublic bool IsDefine { get { return Name != \"\"; } }\r\n\r\n\tpublic DefineItem(string line)\r\n\t{\r\n\t\tParse(line);\r\n\t}\r\n\r\n\tpublic DefineItem(string name, string value)\r\n\t{\r\n\t\tPrefix  = \"#define \";\r\n\t\tName    = name;\r\n\t\tSpace   = \" \";\r\n\t\tValue   = value;\r\n\t\tIgnored = \"\";\r\n\t\tDeleted = false;\r\n\t}\r\n\r\n\tpublic void Parse(string line)\r\n\t{\r\n\t\tvar match = Regex.Match(line, @\"^(\\s*#define\\s+)([A-Za-z0-9_]+)(?:(\\s+)(.*))?$\");\r\n\r\n\t\tif (match.Success)\r\n\t\t{\r\n\t\t\tPrefix  = match.Groups[1].Value;\r\n\t\t\tName    = match.Groups[2].Value;\r\n\t\t\tSpace   = match.Groups[3].Value;\r\n\t\t\tValue   = match.Groups[4].Value;\r\n\t\t\tIgnored = \"\";\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tPrefix  = \"\";\r\n\t\t\tName    = \"\";\r\n\t\t\tSpace   = \"\";\r\n\t\t\tValue   = \"\";\r\n\t\t\tIgnored = line;\r\n\t\t}\r\n\r\n\t\tDeleted = false;\r\n\t}\r\n\r\n\tpublic string ToString()\r\n\t{\r\n\t\treturn Prefix + Name + (Name != \"\" && Space == \"\" && Value != \"\" ? \" \" : Space) + Value + Ignored;\r\n\t}\r\n}\r\n\r\nclass DefineEditor : List<DefineItem>\r\n{\r\n\tpublic string FileName { get; set; }\r\n\tpublic Encoding Encoding { get; protected set; }\r\n\r\n\tprivate bool IsUTF8(byte[] data)\r\n\t{\r\n\t\ttry\r\n\t\t{\r\n\t\t\tnew UTF8Encoding(false, true).GetCharCount(data);\r\n\t\t\treturn true;\r\n\t\t}\r\n\t\tcatch\r\n\t\t{\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic DefineEditor(string filename = null) : base()\r\n\t{\r\n\t\tLoad(filename);\r\n\t}\r\n\r\n\tpublic bool Load()\r\n\t{\r\n\t\treturn Load(FileName);\r\n\t}\r\n\r\n\tbool Load(string filename)\r\n\t{\r\n\t\tClear();\r\n\r\n\t\tFileName = filename;\r\n\t\tEncoding = new UTF8Encoding(false);\r\n\r\n\t\tif (filename == null || !File.Exists(filename))\r\n\t\t{\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tbyte[] FileData = File.ReadAllBytes(filename);\r\n\t\tEncoding = (IsUTF8(FileData)) ? new UTF8Encoding(false) : Encoding.Default;\r\n\t\tvar DataReader = new StreamReader(new MemoryStream(FileData), Encoding);\r\n\t\tstring line;\r\n\t\twhile ((line = DataReader.ReadLine()) != null)\r\n\t\t{\r\n\t\t\tthis.Add(new DefineItem(line));\r\n\t\t}\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n\tpublic void Save()\r\n\t{\r\n\t\tSave(FileName);\r\n\t}\r\n\r\n\tpublic void Save(string filename)\r\n\t{\r\n\t\tvar DataStream = new FileStream(filename, FileMode.Create, FileAccess.Write);\r\n\t\tvar DataWriter = new StreamWriter(DataStream, Encoding);\r\n\t\tforeach (DefineItem item in this)\r\n\t\t{\r\n\t\t\tif (item.Deleted) { break; }\r\n\t\t\tDataWriter.WriteLine(item.ToString());\r\n\t\t}\r\n\t\tDataWriter.Close();\r\n\t}\r\n\r\n\tpublic string GetRaw(string name)\r\n\t{\r\n\t\treturn this.LastOrDefault(item => !item.Deleted && item.Name == name)?.Value;\r\n\t}\r\n\r\n\tpublic string GetRaw(string name, string defval)\r\n\t{\r\n\t\treturn GetRaw(name) ?? defval;\r\n\t}\r\n\r\n\tpublic bool UpdRaw(string name, string value)\r\n\t{\r\n\t\tvar item = this.LastOrDefault(item => item.Name == name);\r\n\t\tif (item == null)\r\n\t\t{\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\titem.Value = value;\r\n\t\t\titem.Deleted = false;\r\n\t\t\treturn true;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void SetRaw(string name, string value)\r\n\t{\r\n\t\tif (!this.UpdRaw(name, value))\r\n\t\t{\r\n\t\t\tthis.Add(new DefineItem(name, value));\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void Delete(string name)\r\n\t{\r\n\t\tthis.FindAll(item => item.Name == name).ForEach(item => item.Deleted = true);\r\n\t}\r\n\r\n\tpublic int GetInt(string name, int defval)\r\n\t{\r\n\t\tvar value = (GetRaw(name) ?? \"\").Trim();\r\n\t\tif (value == \"\") { return defval; }\r\n\t\tif (value.StartsWith(\"0x\"))\r\n\t\t{\r\n\t\t\treturn Int32.Parse(value.Substring(2), NumberStyles.AllowHexSpecifier);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn Int32.Parse(value);\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void SetInt(string name, int value, bool hex = false)\r\n\t{\r\n\t\tSetRaw(name, hex ? (\"0x\" + value.ToString(\"X8\")) : value.ToString());\r\n\t}\r\n\r\n\tpublic void UpdInt(string name, int value, bool hex = false)\r\n\t{\r\n\t\tUpdRaw(name, hex ? (\"0x\" + value.ToString(\"X8\")) : value.ToString());\r\n\t}\r\n}\r\n\r\ntry\r\n{\r\n\tstring output;\r\n\r\n\tif (RunCmd(\"where git\") != 0)\r\n\t{\r\n\t\tthrow new Error(\"Git not found\");\r\n\t}\r\n\r\n\tif (FORCE_UPDATE)\r\n\t{\r\n\t\tConsole.WriteLine(\"Updating build information...\");\r\n\t}\r\n\telse\r\n\t{\r\n\t\tConsole.WriteLine(\"Checking if there are any changes...\");\r\n\r\n\t\t// Check if there are any changes in the working tree since last commit.\r\n\r\n\t\tint exitcode = RunCmd(\"git diff HEAD --exit-code --quiet\");\r\n\r\n\t\tif (exitcode == 0)\r\n\t\t{\r\n\t\t\tthrow new Success(\"No changes found\");\r\n\t\t}\r\n\r\n\t\tif (exitcode != 1)\r\n\t\t{\r\n\t\t\tthrow new Error(\"An issue with Git repo\");\r\n\t\t}\r\n\r\n\t\t// Check if there are any changes since last build info update.\r\n\r\n\t\tif (RunCmd(\"git ls-files -z --cached --modified --other --exclude-standard --deduplicate\", out output) != 0)\r\n\t\t{\r\n\t\t\tthrow new Error(\"Can't list files in the repo\");\r\n\t\t}\r\n\r\n\t\tvar files = output.Split(new char[] {'\\0'}, StringSplitOptions.RemoveEmptyEntries);\r\n\t\tvar latest_change = DateTime.MinValue;\r\n\r\n\t\tforeach (var file in files)\r\n\t\t{\r\n\t\t\tif (!File.Exists(file)) { continue; }\r\n\t\t\tvar LastWriteTime = new FileInfo(file).LastWriteTime;\r\n\t\t\tif (LastWriteTime > latest_change) { latest_change = LastWriteTime; }\r\n\t\t}\r\n\r\n\t\tvar latest_update = File.Exists(BUILD_INFO_FILE) ? new FileInfo(BUILD_INFO_FILE).LastWriteTime : DateTime.MinValue;\r\n\t\tif (latest_update >= latest_change)\r\n\t\t{\r\n\t\t\tthrow new Success(\"No new changes found\");\r\n\t\t}\r\n\r\n\t\tConsole.WriteLine(\"There are some changes. Updating build information...\");\r\n\t}\r\n\r\n\tvar build_info = new DefineEditor(BUILD_INFO_FILE);\r\n\r\n\t// Get expected version from Git tags.\r\n\r\n\tint rev_major = 0;\r\n\tint rev_minor = 0;\r\n\tint rev_patch = 0;\r\n\tint rev_extra = 0;\r\n\r\n\tif (RunCmd(\"git describe --tags --match \\\"v[0-9]*.[0-9]*.[0-9]*\\\" --abbrev=8 HEAD\", out output) == 0)\r\n\t{\r\n\t\t// Get version from the latest revision tag.\r\n\t\tvar match = Regex.Match(output.Trim().ToLower(), @\"^v([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9]+)-g([a-f0-9]+))?$\");\r\n\t\tif (match.Success)\r\n\t\t{\r\n\t\t\trev_major = Int32.Parse(match.Groups[1].Value);\r\n\t\t\trev_minor = Int32.Parse(match.Groups[2].Value);\r\n\t\t\trev_patch = Int32.Parse(match.Groups[3].Value);\r\n\t\t\trev_extra = match.Groups[4].Value != \"\" ? Int32.Parse(match.Groups[4].Value) : 0;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthrow new Error(\"Cannot parse Git version tag\");\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\t// No version tag found. Get just revision count.\r\n\t\tif (RunCmd(\"git rev-list --count HEAD\", out output) == 0)\r\n\t\t{\r\n\t\t\toutput = output.Trim();\r\n\t\t\trev_extra = output != \"\" ? Int32.Parse(output) : 0;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tthrow new Error(\"Cannot get revision count from Git\");\r\n\t\t}\r\n\t}\r\n\r\n\trev_extra++;\r\n\r\n\t// Update version.\r\n\r\n\tvar want_rev = new Version(rev_major, rev_minor, rev_patch, rev_extra);\r\n\tvar 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));\r\n\r\n\tif (want_rev > curr_rev)\r\n\t{\r\n\t\tbuild_info.SetInt(\"REV_MAJOR\", rev_major);\r\n\t\tbuild_info.SetInt(\"REV_MINOR\", rev_minor);\r\n\t\tbuild_info.SetInt(\"REV_PATCH\", rev_patch);\r\n\t\tbuild_info.SetInt(\"REV_EXTRA\", rev_extra);\r\n\t\tbuild_info.UpdInt(\"REV_BUILD\", 0);\r\n\t}\r\n\r\n\t// Update version build counter.\r\n\r\n\tvar rev_build = build_info.GetInt(\"REV_BUILD\", 0);\r\n\trev_build++;\r\n\tbuild_info.UpdInt(\"REV_BUILD\", rev_build);\r\n\r\n\t// Update date.\r\n\r\n\tvar rev_time = DateTime.Now;\r\n\r\n\tif (build_info.GetInt(\"REV_YEAR\", 0) != rev_time.Year\r\n\t\t|| build_info.GetInt(\"REV_MONTH\", 0) != rev_time.Month\r\n\t\t|| build_info.GetInt(\"REV_DAY\", 0) != rev_time.Day)\r\n\t{\r\n\t\tbuild_info.SetInt(\"REV_YEAR\", rev_time.Year);\r\n\t\tbuild_info.SetInt(\"REV_MONTH\", rev_time.Month);\r\n\t\tbuild_info.SetInt(\"REV_DAY\", rev_time.Day);\r\n\t\tbuild_info.UpdInt(\"REV_DAY_BUILD\", 0);\r\n\t}\r\n\r\n\t// Update date build counter.\r\n\r\n\tvar rev_day_build = build_info.GetInt(\"REV_DAY_BUILD\", 0);\r\n\trev_day_build++;\r\n\tbuild_info.UpdInt(\"REV_DAY_BUILD\", rev_day_build);\r\n\r\n\t// Update time.\r\n\r\n\tbuild_info.UpdInt(\"REV_HOUR\",      rev_time.Hour);\r\n\tbuild_info.UpdInt(\"REV_MINUTE\",    rev_time.Minute);\r\n\tbuild_info.UpdInt(\"REV_SECOND\",    rev_time.Second);\r\n\tbuild_info.UpdInt(\"REV_TIMESTAMP\", (int) new DateTimeOffset(rev_time).ToUnixTimeSeconds());\r\n\r\n\tbuild_info.Save();\r\n}\r\ncatch (Success ex)\r\n{\r\n\tConsole.WriteLine(ex.Message + \".\");\r\n\tEnvironment.Exit(0);\r\n}\r\ncatch (Error ex)\r\n{\r\n\tif (NO_ERRORS)\r\n\t{\r\n\t\tConsole.WriteLine($\"Warning: {ex.Message}.\");\r\n\t\tEnvironment.Exit(0);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tConsole.WriteLine($\"Error: {ex.Message}.\");\r\n\t\tEnvironment.Exit(1);\r\n\t}\r\n}\r\ncatch (Exception ex)\r\n{\r\n\tif (NO_ERRORS)\r\n\t{\r\n\t\tConsole.WriteLine($\"Warning {ex.GetType().Name}: {ex.Message}.\");\r\n\t\tEnvironment.Exit(0);\r\n\t}\r\n\telse\r\n\t{\r\n\t\tConsole.WriteLine($\"Error {ex.GetType().Name}: {ex.Message}.\");\r\n\t\tEnvironment.Exit(1);\r\n\t}\r\n}\r\n"
  },
  {
    "path": "BuildInfo.hpp",
    "content": "#ifndef BUILDINFO_HPP\r\n#define BUILDINFO_HPP\r\n\r\n#define APP_NAME        \"Sound Keeper\"\r\n#define APP_FILE_NAME   \"SoundKeeper\"\r\n#define APP_COPYRIGHT   \"(C) 2014-2025 Evgeny Vrublevsky <me@veg.by>\"\r\n#define APP_COMPANY     \"Vegalogic Software\"\r\n\r\n#define REV_MAJOR       1\r\n#define REV_MINOR       3\r\n#define REV_PATCH       5\r\n#define REV_EXTRA       0\r\n#define REV_BUILD       1\r\n\r\n#define REV_YEAR        2025\r\n#define REV_MONTH       7\r\n#define REV_DAY         5\r\n#define REV_DAY_BUILD   1\r\n\r\n#endif // BUILDINFO_HPP\r\n"
  },
  {
    "path": "BuildInfo.rc2",
    "content": "#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 VERSIONINFO\r\n\tFILEVERSION     REV_YEAR, REV_MONTH, REV_DAY, REV_DAY_BUILD\r\n\tPRODUCTVERSION  REV_MAJOR, REV_MINOR, REV_PATCH, REV_EXTRA\r\n\tFILEFLAGSMASK   VS_FFI_FILEFLAGSMASK\r\n#if defined(_DEBUG)\r\n\tFILEFLAGS       VS_FF_DEBUG|VS_FF_PRERELEASE\r\n#else\r\n\tFILEFLAGS       0x0L\r\n#endif\r\n\tFILEOS          VOS_NT_WINDOWS32\r\n\tFILETYPE        VFT_APP\r\n\tFILESUBTYPE     VFT2_UNKNOWN\r\nBEGIN\r\n\tBLOCK \"StringFileInfo\"\r\n\tBEGIN\r\n\t\tBLOCK \"000904b0\" // LANG_ENGLISH = 0x0009, CP_UNICODE = 0x04b0 = 1200\r\n\t\tBEGIN\r\n\t\t\tVALUE \"CompanyName\",        APP_COMPANY\r\n#if defined(_DEBUG)\r\n\t\t\tVALUE \"FileDescription\",    APP_NAME \" (Debug \" APP_ARCH \")\"\r\n#else\r\n\t\t\tVALUE \"FileDescription\",    APP_NAME \" (\" APP_ARCH \")\"\r\n#endif\r\n\t\t\tVALUE \"FileVersion\",        STR(REV_YEAR) \".\" STR(REV_MONTH) \".\" STR(REV_DAY) \".\" STR(REV_DAY_BUILD)\r\n\t\t\tVALUE \"InternalName\",       APP_FILE_NAME\r\n\t\t\tVALUE \"LegalCopyright\",     APP_COPYRIGHT\r\n\t\t\tVALUE \"OriginalFilename\",   APP_FILE_NAME \".exe\"\r\n\t\t\tVALUE \"ProductName\",        APP_NAME\r\n\t\t\tVALUE \"ProductVersion\",     STR(REV_MAJOR) \".\" STR(REV_MINOR) \".\" STR(REV_PATCH) \".\" STR(REV_EXTRA)\r\n\t\tEND\r\n\tEND\r\n\tBLOCK \"VarFileInfo\"\r\n\tBEGIN\r\n\t\tVALUE \"Translation\", 0x0009, 1200\r\n\tEND\r\nEND\r\n"
  },
  {
    "path": "CSoundKeeper.cpp",
    "content": "// 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//\r\n// Get time to sleeping (in seconds). It is not precise and updated just once in 15-30 seconds!\r\n// On Windows 7-10, it outputs -1 (or values like -16, -31, ...) when auto sleep mode is disabled.\r\n// On Windows 10, it may output negative values (-30, -60, ...) when sleeping was postponed not by user input.\r\n// On Windows 11, the TimeRemaining field is always 0, so it doesn't work for some reason.\r\n//\r\n\r\nEXTERN_C NTSYSCALLAPI NTSTATUS NTAPI NtPowerInformation(\r\n\t_In_ POWER_INFORMATION_LEVEL InformationLevel,\r\n\t_In_reads_bytes_opt_(InputBufferLength) PVOID InputBuffer,\r\n\t_In_ ULONG InputBufferLength,\r\n\t_Out_writes_bytes_opt_(OutputBufferLength) PVOID OutputBuffer,\r\n\t_In_ ULONG OutputBufferLength\r\n);\r\n\r\nuint32_t GetSecondsToSleeping()\r\n{\r\n\tstruct SYSTEM_POWER_INFORMATION {\r\n\t\tULONG MaxIdlenessAllowed;\r\n\t\tULONG Idleness;\r\n\t\tLONG TimeRemaining;\r\n\t\tUCHAR CoolingMode;\r\n\t} spi = {0};\r\n\r\n\tif (!NT_SUCCESS(NtPowerInformation(SystemPowerInformation, NULL, 0, &spi, sizeof(spi))))\r\n\t{\r\n\t\tDebugLogError(\"Cannot get remaining time to sleeping.\");\r\n\t\treturn 0;\r\n\t}\r\n\r\n#ifdef _CONSOLE\r\n\r\n\tstatic LONG last_result = 0x80000000;\r\n\r\n\tif (last_result != spi.TimeRemaining)\r\n\t{\r\n\t\tlast_result = spi.TimeRemaining;\r\n\t\tDebugLog(\"Remaining time to sleeping: %d seconds.\", spi.TimeRemaining);\r\n\t}\r\n\r\n#endif\r\n\r\n\tif (spi.TimeRemaining >= 0)\r\n\t{\r\n\t\treturn spi.TimeRemaining;\r\n\t}\r\n\r\n\treturn (spi.TimeRemaining % 5 == 0 ? 0 : -1);\r\n}\r\n\r\n//\r\n// CSoundKeeper implementation.\r\n//\r\n\r\nCSoundKeeper::CSoundKeeper() { }\r\nCSoundKeeper::~CSoundKeeper() { }\r\n\r\n// IUnknown methods\r\n\r\nULONG STDMETHODCALLTYPE CSoundKeeper::AddRef()\r\n{\r\n\treturn InterlockedIncrement(&m_ref_count);\r\n}\r\n\r\nULONG STDMETHODCALLTYPE CSoundKeeper::Release()\r\n{\r\n\tULONG result = InterlockedDecrement(&m_ref_count);\r\n\tif (result == 0)\r\n\t{\r\n\t\tdelete this;\r\n\t}\r\n\treturn result;\r\n}\r\n\r\nHRESULT STDMETHODCALLTYPE CSoundKeeper::QueryInterface(REFIID riid, VOID **ppvInterface)\r\n{\r\n\tif (IID_IUnknown == riid)\r\n\t{\r\n\t\tAddRef();\r\n\t\t*ppvInterface = (IUnknown*)this;\r\n\t}\r\n\telse if (__uuidof(IMMNotificationClient) == riid)\r\n\t{\r\n\t\tAddRef();\r\n\t\t*ppvInterface = (IMMNotificationClient*)this;\r\n\t}\r\n\telse\r\n\t{\r\n\t\t*ppvInterface = NULL;\r\n\t\treturn E_NOINTERFACE;\r\n\t}\r\n\treturn S_OK;\r\n}\r\n\r\n// Callback methods for device-event notifications.\r\n// WARNING: Don't use m_mutex, it may cause a deadlock when CSoundKeeper::Restart -> Stop is in progress.\r\n\r\nHRESULT STDMETHODCALLTYPE CSoundKeeper::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id)\r\n{\r\n\tDebugLog(\"Device '%S' is default for flow %d and role %d.\", device_id ? device_id : L\"\", flow, role);\r\n\tif (m_cfg_device_type == KeepDeviceType::Primary && flow == eRender && role == eConsole)\r\n\t{\r\n\t\tthis->FireRestart();\r\n\t}\r\n\treturn S_OK;\r\n}\r\n\r\nHRESULT STDMETHODCALLTYPE CSoundKeeper::OnDeviceAdded(LPCWSTR device_id)\r\n{\r\n\tDebugLog(\"Device '%S' was added.\", device_id);\r\n\tif (m_cfg_device_type != KeepDeviceType::Primary)\r\n\t{\r\n\t\tthis->FireRestart();\r\n\t}\r\n\treturn S_OK;\r\n};\r\n\r\nHRESULT STDMETHODCALLTYPE CSoundKeeper::OnDeviceRemoved(LPCWSTR device_id)\r\n{\r\n\tDebugLog(\"Device '%S' was removed.\", device_id);\r\n\tif (m_cfg_device_type != KeepDeviceType::Primary)\r\n\t{\r\n\t\tthis->FireRestart();\r\n\t}\r\n\treturn S_OK;\r\n}\r\n\r\nHRESULT STDMETHODCALLTYPE CSoundKeeper::OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state)\r\n{\r\n\tDebugLog(\"Device '%S' new state: %d.\", device_id, new_state);\r\n\tif (new_state == DEVICE_STATE_ACTIVE)\r\n\t{\r\n\t\tthis->FireRestart();\r\n\t}\r\n\treturn S_OK;\r\n}\r\n\r\nHRESULT STDMETHODCALLTYPE CSoundKeeper::OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key)\r\n{\r\n\treturn S_OK;\r\n}\r\n\r\n// Main thread methods.\r\n\r\nuint32_t GetDeviceFormFactor(IMMDevice* device)\r\n{\r\n\tuint32_t formfactor = -1;\r\n\r\n\tIPropertyStore* properties = nullptr;\r\n\tHRESULT hr = device->OpenPropertyStore(STGM_READ, &properties);\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogWarning(\"Unable to retrieve property store of an audio device: 0x%08X.\", hr);\r\n\t\treturn formfactor;\r\n\t}\r\n\r\n\tPROPVARIANT prop_formfactor;\r\n\tPropVariantInit(&prop_formfactor);\r\n\thr = properties->GetValue(PKEY_AudioEndpoint_FormFactor, &prop_formfactor);\r\n\tif (SUCCEEDED(hr) && prop_formfactor.vt == VT_UI4)\r\n\t{\r\n\t\tformfactor = prop_formfactor.uintVal;\r\n#ifdef _CONSOLE\r\n\t\tLPWSTR device_id = nullptr;\r\n\t\thr = device->GetId(&device_id);\r\n\t\tif (FAILED(hr))\r\n\t\t{\r\n\t\t\tDebugLogWarning(\"Unable to get device ID: 0x%08X.\", hr);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tDebugLog(\"Device ID: '%S'. Form Factor: %d.\", device_id, formfactor);\r\n\t\t\tCoTaskMemFree(device_id);\r\n\t\t}\r\n#endif\r\n\t}\r\n\telse\r\n\t{\r\n\t\tDebugLogWarning(\"Unable to retrieve formfactor of an audio device: 0x%08X.\", hr);\r\n\t}\r\n\r\n\tPropVariantClear(&prop_formfactor);\r\n\tSafeRelease(properties);\r\n\r\n\treturn formfactor;\r\n}\r\n\r\nHRESULT CSoundKeeper::Start()\r\n{\r\n\tScopedLock lock(m_mutex);\r\n\r\n\tHRESULT hr = S_OK;\r\n\tif (m_is_started) { return hr; }\r\n\tm_is_retry_required = false;\r\n\r\n\tif (m_cfg_device_type == KeepDeviceType::Primary)\r\n\t{\r\n\t\tDebugLog(\"Getting primary audio device...\");\r\n\r\n\t\tIMMDevice* device = nullptr;\r\n\t\thr = m_dev_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);\r\n\t\tif (FAILED(hr))\r\n\t\t{\r\n\t\t\tif (hr == E_NOTFOUND)\r\n\t\t\t{\r\n\t\t\t\tDebugLogWarning(\"No primary device found. Working as a dummy...\");\r\n\t\t\t\tm_is_started = true;\r\n\t\t\t\treturn hr;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tDebugLogError(\"Unable to retrieve default render device: 0x%08X.\", hr);\r\n\t\t\t\treturn hr;\r\n\t\t\t}\r\n\t\t}\r\n\t\tdefer [&] { device->Release(); };\r\n\r\n\t\tm_is_started = true;\r\n\r\n\t\tif (uint32_t formfactor = GetDeviceFormFactor(device); formfactor == -1)\r\n\t\t{\r\n\t\t\treturn hr;\r\n\t\t}\r\n\t\telse if (!m_cfg_allow_remote && formfactor == RemoteNetworkDevice)\r\n\t\t{\r\n\t\t\tDebugLog(\"Ignoring remote desktop audio device.\");\r\n\t\t\treturn hr;\r\n\t\t}\r\n\r\n\t\tm_sessions_count = 1;\r\n\t\tm_sessions = new CSoundSession*[m_sessions_count]();\r\n\r\n\t\tm_sessions[0] = new CSoundSession(this, device);\r\n\t\tm_sessions[0]->SetStreamType(m_cfg_stream_type);\r\n\t\tm_sessions[0]->SetFrequency(m_cfg_frequency);\r\n\t\tm_sessions[0]->SetAmplitude(m_cfg_amplitude);\r\n\t\tm_sessions[0]->SetPeriodicPlaying(m_cfg_play_seconds);\r\n\t\tm_sessions[0]->SetPeriodicWaiting(m_cfg_wait_seconds);\r\n\t\tm_sessions[0]->SetFading(m_cfg_fade_seconds);\r\n\r\n\t\tif (!m_sessions[0]->Start())\r\n\t\t{\r\n\t\t\tm_is_retry_required = true;\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tDebugLog(\"Enumerating active audio devices...\");\r\n\r\n\t\tIMMDeviceCollection* dev_collection = nullptr;\r\n\t\thr = m_dev_enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &dev_collection);\r\n\t\tif (FAILED(hr))\r\n\t\t{\r\n\t\t\tDebugLogError(\"Unable to retrieve device collection: 0x%08X.\", hr);\r\n\t\t\treturn hr;\r\n\t\t}\r\n\t\tdefer [&] { dev_collection->Release(); };\r\n\r\n\t\thr = dev_collection->GetCount(&m_sessions_count);\r\n\t\tif (FAILED(hr))\r\n\t\t{\r\n\t\t\tDebugLogError(\"Unable to get device collection length: 0x%08X.\", hr);\r\n\t\t\treturn hr;\r\n\t\t}\r\n\r\n\t\tm_is_started = true;\r\n\r\n\t\tm_sessions = new CSoundSession*[m_sessions_count]();\r\n\r\n\t\tfor (UINT i = 0; i < m_sessions_count; i++)\r\n\t\t{\r\n\t\t\tm_sessions[i] = nullptr;\r\n\r\n\t\t\tIMMDevice* device = nullptr;\r\n\t\t\tif (dev_collection->Item(i, &device) != S_OK) { continue; }\r\n\t\t\tdefer [&] { device->Release(); };\r\n\r\n\t\t\tif (uint32_t formfactor = GetDeviceFormFactor(device); formfactor == -1)\r\n\t\t\t{\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\telse if (!m_cfg_allow_remote && formfactor == RemoteNetworkDevice)\r\n\t\t\t{\r\n\t\t\t\tDebugLog(\"Ignoring remote desktop audio device.\");\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\telse if ((m_cfg_device_type == KeepDeviceType::Digital || m_cfg_device_type == KeepDeviceType::Analog)\r\n\t\t\t\t&& (m_cfg_device_type == KeepDeviceType::Digital) != (formfactor == SPDIF || formfactor == HDMI))\r\n\t\t\t{\r\n\t\t\t\tDebugLog(\"Skipping this device because of the Digital / Analog filter.\");\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tm_sessions[i] = new CSoundSession(this, device);\r\n\t\t\tm_sessions[i]->SetStreamType(m_cfg_stream_type);\r\n\t\t\tm_sessions[i]->SetFrequency(m_cfg_frequency);\r\n\t\t\tm_sessions[i]->SetAmplitude(m_cfg_amplitude);\r\n\t\t\tm_sessions[i]->SetPeriodicPlaying(m_cfg_play_seconds);\r\n\t\t\tm_sessions[i]->SetPeriodicWaiting(m_cfg_wait_seconds);\r\n\t\t\tm_sessions[i]->SetFading(m_cfg_fade_seconds);\r\n\r\n\t\t\tif (!m_sessions[i]->Start())\r\n\t\t\t{\r\n\t\t\t\tm_is_retry_required = true;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (m_sessions_count == 0)\r\n\t\t{\r\n\t\t\tDebugLogWarning(\"No suitable devices found. Working as a dummy...\");\r\n\t\t}\r\n\t}\r\n\r\n\treturn hr;\r\n}\r\n\r\nbool CSoundKeeper::Retry()\r\n{\r\n\tScopedLock lock(m_mutex);\r\n\r\n\tif (!m_is_retry_required) return true;\r\n\tm_is_retry_required = false;\r\n\r\n\tif (m_sessions != nullptr)\r\n\t{\r\n\t\tfor (UINT i = 0; i < m_sessions_count; i++)\r\n\t\t{\r\n\t\t\tif (m_sessions[i] != nullptr)\r\n\t\t\t{\r\n\t\t\t\tif (!m_sessions[i]->Start())\r\n\t\t\t\t{\r\n\t\t\t\t\tm_is_retry_required = true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn !m_is_retry_required;\r\n}\r\n\r\nHRESULT CSoundKeeper::Stop()\r\n{\r\n\tScopedLock lock(m_mutex);\r\n\r\n\tif (!m_is_started) return S_OK;\r\n\r\n\tif (m_sessions != nullptr)\r\n\t{\r\n\t\tfor (UINT i = 0; i < m_sessions_count; i++)\r\n\t\t{\r\n\t\t\tif (m_sessions[i] != nullptr)\r\n\t\t\t{\r\n\t\t\t\tm_sessions[i]->Stop();\r\n\t\t\t\tm_sessions[i]->Release();\r\n\t\t\t}\r\n\t\t}\r\n\t\tdelete m_sessions;\r\n\t}\r\n\r\n\tm_sessions = nullptr;\r\n\tm_sessions_count = 0;\r\n\tm_is_started = false;\r\n\tm_is_retry_required = false;\r\n\treturn S_OK;\r\n}\r\n\r\nHRESULT CSoundKeeper::Restart()\r\n{\r\n\tScopedLock lock(m_mutex);\r\n\r\n\tHRESULT hr = S_OK;\r\n\r\n\thr = this->Stop();\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\treturn hr;\r\n\t}\r\n\r\n\thr = this->Start();\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\treturn hr;\r\n\t}\r\n\r\n\treturn S_OK;\r\n}\r\n\r\nCSoundSession* CSoundKeeper::FindSession(LPCWSTR device_id)\r\n{\r\n\tScopedLock lock(m_mutex);\r\n\r\n\tfor (UINT i = 0; i < m_sessions_count; i++)\r\n\t{\r\n\t\tif (m_sessions[i] == nullptr)\r\n\t\t{\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\tif (auto curr = m_sessions[i]->GetDeviceId(); curr && StringEquals(curr, device_id))\r\n\t\t{\r\n\t\t\t// Call AddRef()? Use ComPtr?\r\n\t\t\treturn m_sessions[i];\r\n\t\t}\r\n\t}\r\n\r\n\treturn nullptr;\r\n}\r\n\r\n// Fire main thread control events.\r\n\r\nvoid CSoundKeeper::FireRetry()\r\n{\r\n\tTraceLog(\"Fire Retry!\");\r\n\tm_do_retry = true;\r\n}\r\n\r\nvoid CSoundKeeper::FireRestart()\r\n{\r\n\tTraceLog(\"Fire Restart!\");\r\n\tm_do_restart = true;\r\n}\r\n\r\nvoid CSoundKeeper::FireShutdown()\r\n{\r\n\tTraceLog(\"Fire Shutdown!\");\r\n\tm_do_shutdown = true;\r\n}\r\n\r\n// Entry point methods.\r\n\r\nvoid CSoundKeeper::SetStreamTypeDefaults(KeepStreamType stream_type)\r\n{\r\n\tm_cfg_stream_type = stream_type;\r\n\r\n\tm_cfg_frequency = 0.0;\r\n\tm_cfg_amplitude = 0.0;\r\n\tm_cfg_play_seconds = 0.0;\r\n\tm_cfg_wait_seconds = 0.0;\r\n\tm_cfg_fade_seconds = 0.0;\r\n\r\n\tswitch (stream_type)\r\n\t{\r\n\t\tcase KeepStreamType::Fluctuate:\r\n\t\t\tm_cfg_frequency = 50.0;\r\n\t\t\tbreak;\r\n\t\tcase KeepStreamType::Sine:\r\n\t\t\tm_cfg_frequency = 1.0;\r\n\t\t\t[[fallthrough]];\r\n\t\tcase KeepStreamType::WhiteNoise:\r\n\t\tcase KeepStreamType::BrownNoise:\r\n\t\tcase KeepStreamType::PinkNoise:\r\n\t\t\tm_cfg_amplitude = 0.01;\r\n\t\t\tm_cfg_fade_seconds = 0.1;\r\n\t\t\t[[fallthrough]];\r\n\t\tdefault:\r\n\t\t\tbreak;\r\n\t}\r\n}\r\n\r\nvoid CSoundKeeper::ParseStreamArgs(KeepStreamType stream_type, const char* args)\r\n{\r\n\tthis->SetStreamTypeDefaults(stream_type);\r\n\r\n\tchar* p = (char*) args;\r\n\twhile (*p)\r\n\t{\r\n\t\tif (*p == ' ' || *p == '\\t' || *p == '-') { p++; }\r\n\t\telse if (*p == 'f' || *p == 'a' || *p == 'l' || *p == 'w' || *p == 't')\r\n\t\t{\r\n\t\t\tchar type = *p;\r\n\t\t\tp++;\r\n\t\t\twhile (*p == ' ' || *p == '\\t' || *p == '=') { p++; }\r\n\t\t\tif (*p < '0' || '9' < *p) { break; }\r\n\r\n\t\t\tdouble value = strtod(p, &p);\r\n\t\t\tif (type == 'f')\r\n\t\t\t{\r\n\t\t\t\tthis->SetFrequency(std::min(value, 96000.0));\r\n\t\t\t}\r\n\t\t\telse if (type == 'a')\r\n\t\t\t{\r\n\t\t\t\tthis->SetAmplitude(std::min(value / 100.0, 1.0));\r\n\t\t\t}\r\n\t\t\telse if (type == 'l')\r\n\t\t\t{\r\n\t\t\t\tthis->SetPeriodicPlaying(value);\r\n\t\t\t}\r\n\t\t\telse if (type == 'w')\r\n\t\t\t{\r\n\t\t\t\tthis->SetPeriodicWaiting(value);\r\n\t\t\t}\r\n\t\t\telse if (type == 't')\r\n\t\t\t{\r\n\t\t\t\tthis->SetFading(value);\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n}\r\n\r\nvoid CSoundKeeper::ParseModeString(const char* args)\r\n{\r\n\tchar buf[MAX_PATH];\r\n\tstrcpy_s(buf, args);\r\n\t_strlwr(buf);\r\n\r\n\tif (strstr(buf, \"all\"))     { this->SetDeviceType(KeepDeviceType::All); }\r\n\tif (strstr(buf, \"analog\"))  { this->SetDeviceType(KeepDeviceType::Analog); }\r\n\tif (strstr(buf, \"digital\")) { this->SetDeviceType(KeepDeviceType::Digital); }\r\n\tif (strstr(buf, \"kill\"))    { this->SetDeviceType(KeepDeviceType::None); }\r\n\tif (strstr(buf, \"remote\"))  { this->SetAllowRemote(true); }\r\n\tif (strstr(buf, \"nosleep\")) { this->SetNoSleep(true); }\r\n\r\n\tif (strstr(buf, \"openonly\"))\r\n\t{\r\n\t\tthis->SetStreamType(KeepStreamType::None);\r\n\t}\r\n\telse if (strstr(buf, \"zero\") || strstr(buf, \"null\"))\r\n\t{\r\n\t\tthis->SetStreamType(KeepStreamType::Zero);\r\n\t}\r\n\telse if (char* p = strstr(buf, \"fluctuate\"))\r\n\t{\r\n\t\tthis->ParseStreamArgs(KeepStreamType::Fluctuate, p+9);\r\n\t}\r\n\telse if (char* p = strstr(buf, \"sine\"))\r\n\t{\r\n\t\tthis->ParseStreamArgs(KeepStreamType::Sine, p+4);\r\n\t}\r\n\telse if (char* p = strstr(buf, \"white\"))\r\n\t{\r\n\t\tthis->ParseStreamArgs(KeepStreamType::WhiteNoise, p+5);\r\n\t}\r\n\telse if (char* p = strstr(buf, \"brown\"))\r\n\t{\r\n\t\tthis->ParseStreamArgs(KeepStreamType::BrownNoise, p+5);\r\n\t}\r\n\telse if (char* p = strstr(buf, \"pink\"))\r\n\t{\r\n\t\tthis->ParseStreamArgs(KeepStreamType::PinkNoise, p+4);\r\n\t}\r\n}\r\n\r\nHRESULT CSoundKeeper::Run()\r\n{\r\n\t// Windows 8-10 audio service leaks handles and shared memory when exclusive mode is used, enable a workaround.\r\n\tuint32_t nt_build_number = GetNtBuildNumber();\r\n\tbool is_leaky_wasapi = 7601 < nt_build_number && nt_build_number < 22000;\r\n\tDebugLog(\"Windows Build Number: %u%s.\", nt_build_number, is_leaky_wasapi ? \" (leaky WASAPI)\" : \"\");\r\n\tCSoundSession::EnableWaitExclusiveWorkaround(is_leaky_wasapi);\r\n\r\n\t// Set defaults.\r\n\tthis->SetDeviceType(KeepDeviceType::Primary);\r\n\tthis->SetStreamTypeDefaults(KeepStreamType::Fluctuate);\r\n\r\n\t// Parse file name for defaults.\r\n\tchar fn_buffer[MAX_PATH];\r\n\tDWORD fn_size = GetModuleFileNameA(NULL, fn_buffer, MAX_PATH);\r\n\tif (fn_size != 0 && fn_size != MAX_PATH)\r\n\t{\r\n\t\tchar* filename = strrchr(fn_buffer, '\\\\');\r\n\t\tif (filename)\r\n\t\t{\r\n\t\t\tfilename++;\r\n\t\t\tDebugLog(\"Exe File Name: %s.\", filename);\r\n\t\t\tthis->ParseModeString(filename);\r\n\t\t}\r\n\t}\r\n\r\n\t// Parse command line for arguments.\r\n\tif (const char* cmdln = GetCommandLineA())\r\n\t{\r\n\t\t// Skip program file name.\r\n\t\twhile (*cmdln == ' ') { cmdln++; }\r\n\t\tif (cmdln[0] == '\"')\r\n\t\t{\r\n\t\t\tcmdln++;\r\n\t\t\twhile (*cmdln != '\"' && *cmdln != 0) { cmdln++; }\r\n\t\t\tif (*cmdln == '\"') { cmdln++; }\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\twhile (*cmdln != ' ' && *cmdln != 0) { cmdln++; }\r\n\t\t}\r\n\t\twhile (*cmdln == ' ') { cmdln++; }\r\n\r\n\t\tif (*cmdln != 0)\r\n\t\t{\r\n\t\t\tDebugLog(\"Command Line: %s.\", cmdln);\r\n\t\t\tthis->ParseModeString(cmdln);\r\n\t\t}\r\n\t}\r\n\r\n\tif (GetSecondsToSleeping() == 0)\r\n\t{\r\n\t\tDebugLogWarning(\"Sleep timer informarion is not available. Sleep detection is disabled.\");\r\n\t\tm_cfg_no_sleep = true;\r\n\t}\r\n\r\n#ifdef _CONSOLE\r\n\r\n\tswitch (this->GetDeviceType())\r\n\t{\r\n\t\tcase KeepDeviceType::None:      DebugLog(\"Device Type: None.\"); break;\r\n\t\tcase KeepDeviceType::Primary:   DebugLog(\"Device Type: Primary.\"); break;\r\n\t\tcase KeepDeviceType::All:       DebugLog(\"Device Type: All.\"); break;\r\n\t\tcase KeepDeviceType::Analog:    DebugLog(\"Device Type: Analog.\"); break;\r\n\t\tcase KeepDeviceType::Digital:   DebugLog(\"Device Type: Digital.\"); break;\r\n\t\tdefault:                        DebugLogError(\"Unknown Device Type.\"); break;\r\n\t}\r\n\r\n\tswitch (this->GetStreamType())\r\n\t{\r\n\t\tcase KeepStreamType::None:      DebugLog(\"Stream Type: None (Open Only).\"); break;\r\n\t\tcase KeepStreamType::Zero:      DebugLog(\"Stream Type: Zero.\"); break;\r\n\t\tcase KeepStreamType::Fluctuate: DebugLog(\"Stream Type: Fluctuate (Frequency: %.3fHz).\", this->GetFrequency()); break;\r\n\t\tcase KeepStreamType::Sine:      DebugLog(\"Stream Type: Sine (Frequency: %.3fHz; Amplitude: %.3f%%; Fading: %.3fs).\", this->GetFrequency(), this->GetAmplitude() * 100.0, this->GetFading()); break;\r\n\t\tcase KeepStreamType::WhiteNoise:DebugLog(\"Stream Type: White Noise (Amplitude: %.3f%%; Fading: %.3fs).\", this->GetAmplitude() * 100.0, this->GetFading()); break;\r\n\t\tcase KeepStreamType::BrownNoise:DebugLog(\"Stream Type: Brown Noise (Amplitude: %.3f%%; Fading: %.3fs).\", this->GetAmplitude() * 100.0, this->GetFading()); break;\r\n\t\tcase KeepStreamType::PinkNoise: DebugLog(\"Stream Type: Pink Noise (Amplitude: %.3f%%; Fading: %.3fs).\", this->GetAmplitude() * 100.0, this->GetFading()); break;\r\n\t\tdefault:                        DebugLogError(\"Unknown Stream Type.\"); break;\r\n\t}\r\n\r\n\tif (this->GetPeriodicPlaying() || this->GetPeriodicWaiting())\r\n\t{\r\n\t\tDebugLog(\"Periodicity: Enabled (Length: %.3fs; Waiting: %.3fs).\", this->GetPeriodicPlaying(), this->GetPeriodicWaiting());\r\n\t}\r\n\telse\r\n\t{\r\n\t\tDebugLog(\"Periodicity: Disabled.\");\r\n\t}\r\n\r\n\tif (m_cfg_no_sleep)\r\n\t{\r\n\t\tDebugLog(\"Sleep Detection: Disabled.\");\r\n\t}\r\n\r\n#endif\r\n\r\n\t// Stop another instance.\r\n\r\n\tHandle global_stop_event = CreateEventA(NULL, TRUE, FALSE, \"SoundKeeperStopEvent\");\r\n\tif (!global_stop_event)\r\n\t{\r\n\t\tDWORD le = GetLastError();\r\n\t\tDebugLogError(\"Unable to open global stop event. Error code: %d.\", le);\r\n\t\treturn HRESULT_FROM_WIN32(le);\r\n\t}\r\n\r\n\tHandle global_mutex = CreateMutexA(NULL, TRUE, \"SoundKeeperMutex\");\r\n\tif (!global_mutex)\r\n\t{\r\n\t\tDWORD le = GetLastError();\r\n\t\tDebugLogError(\"Unable to open global mutex. Error code: %d.\", le);\r\n\t\treturn HRESULT_FROM_WIN32(le);\r\n\t}\r\n\tif (GetLastError() == ERROR_ALREADY_EXISTS)\r\n\t{\r\n\t\tDebugLog(\"Stopping another instance...\");\r\n\t\tSetEvent(global_stop_event);\r\n\t\tbool is_timeout = WaitForOne(global_mutex, 1000) == WAIT_TIMEOUT;\r\n\t\tResetEvent(global_stop_event);\r\n\t\tif (is_timeout)\r\n\t\t{\r\n\t\t\tDebugLogError(\"Time out.\");\r\n\t\t\treturn HRESULT_FROM_WIN32(WAIT_TIMEOUT);\r\n\t\t}\r\n\t}\r\n\tdefer [&] { ReleaseMutex(global_mutex); };\r\n\r\n\tHRESULT hr = S_OK;\r\n\r\n\tif (m_cfg_device_type == KeepDeviceType::None)\r\n\t{\r\n\t\tDebugLog(\"Self kill mode is enabled. Exit.\");\r\n\t\treturn hr;\r\n\t}\r\n\r\n\t// Initialization.\r\n\r\n\thr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_dev_enumerator));\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Unable to instantiate device enumerator: 0x%08X.\", hr);\r\n\t\treturn hr;\r\n\t}\r\n\tdefer [&] { m_dev_enumerator->Release(); };\r\n\r\n\thr = m_dev_enumerator->RegisterEndpointNotificationCallback(this);\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Unable to register for stream switch notifications: 0x%08X.\", hr);\r\n\t\treturn hr;\r\n\t}\r\n\tdefer [&] { m_dev_enumerator->UnregisterEndpointNotificationCallback(this); };\r\n\r\n\t// Working loop.\r\n\r\n\tDebugLog(\"Enter main loop.\");\r\n\r\n\tfor (bool working = true; working; )\r\n\t{\r\n\t\tuint32_t seconds_to_sleeping = (m_cfg_no_sleep ? -1 : GetSecondsToSleeping());\r\n\r\n\t\tif (seconds_to_sleeping == 0)\r\n\t\t{\r\n\t\t\tif (m_is_started)\r\n\t\t\t{\r\n\t\t\t\tDebugLog(\"Going to sleep...\");\r\n\t\t\t\tthis->Stop();\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tif (!m_is_started)\r\n\t\t\t{\r\n\t\t\t\tDebugLog(\"Starting...\");\r\n\t\t\t\tthis->Start();\r\n\t\t\t}\r\n\t\t\telse if (m_is_retry_required)\r\n\t\t\t{\r\n\t\t\t\tDebugLog(\"Retrying...\");\r\n\t\t\t\tthis->Retry();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tDWORD timeout = (m_is_retry_required || seconds_to_sleeping <= 30) ? 500 : (m_cfg_no_sleep ? INFINITE : 5000);\r\n\r\n\t\tswitch (WaitForAny({ m_do_retry, m_do_restart, m_do_shutdown, global_stop_event }, timeout))\r\n\t\t{\r\n\t\tcase WAIT_TIMEOUT:\r\n\r\n\t\t\tbreak;\r\n\r\n\t\tcase WAIT_OBJECT_0 + 0:\r\n\r\n\t\t\t// Prevent multiple retries.\r\n\t\t\twhile (WaitForOne(m_do_retry, 500) != WAIT_TIMEOUT);\r\n\t\t\tm_is_retry_required = true;\r\n\t\t\tbreak;\r\n\r\n\t\tcase WAIT_OBJECT_0 + 1:\r\n\r\n\t\t\t// Prevent multiple restarts.\r\n\t\t\twhile (WaitForOne(m_do_restart, 500) != WAIT_TIMEOUT);\r\n\t\t\tDebugLog(\"Restarting...\");\r\n\t\t\tthis->Restart();\r\n\t\t\tbreak;\r\n\r\n\t\tcase WAIT_OBJECT_0 + 2:\r\n\t\tcase WAIT_OBJECT_0 + 3:\r\n\t\tdefault:\r\n\r\n\t\t\t// We're done, exit the loop.\r\n\t\t\tDebugLog(\"Shutdown.\");\r\n\t\t\tworking = false;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\tDebugLog(\"Leave main loop.\");\r\n\tStop();\r\n\treturn hr;\r\n}\r\n\r\nFORCEINLINE HRESULT CSoundKeeper::Main()\r\n{\r\n\tDebugThreadName(\"Main\");\r\n\r\n\tDebugLog(\"Enter main thread.\");\r\n\r\n\tif (HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); FAILED(hr))\r\n\t{\r\n#ifndef _CONSOLE\r\n\t\tMessageBoxA(0, \"Cannot initialize COM.\", \"Sound Keeper\", MB_ICONERROR | MB_OK | MB_SYSTEMMODAL);\r\n#else\r\n\t\tDebugLogError(\"Cannot initialize COM: 0x%08X.\", hr);\r\n#endif\r\n\t\treturn hr;\r\n\t}\r\n\r\n\tCSoundKeeper* keeper = new CSoundKeeper();\r\n\tHRESULT hr = keeper->Run();\r\n\tSafeRelease(keeper);\r\n\r\n\tCoUninitialize();\r\n\r\n#ifndef _CONSOLE\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tMessageBoxA(0, \"Cannot initialize WASAPI.\", \"Sound Keeper\", MB_ICONERROR | MB_OK | MB_SYSTEMMODAL);\r\n\t}\r\n#else\r\n\tif (hr == S_OK)\r\n\t{\r\n\t\tDebugLog(\"Leave main thread. Exit code: 0.\");\r\n\t}\r\n\telse\r\n\t{\r\n\t\tDebugLog(\"Leave main thread. Exit code: 0x%08X.\", hr);\r\n\t}\r\n#endif\r\n\r\n\treturn hr;\r\n}\r\n\r\n#ifdef _CONSOLE\r\n\r\nint main()\r\n{\r\n\tif (const VS_FIXEDFILEINFO* ffi = GetFixedVersion())\r\n\t{\r\n\t\tDebugLog(\"Sound Keeper v%hu.%hu.%hu.%hu [%04hu/%02hu/%02hu] (\" APP_ARCH \")\",\r\n\t\t\tHIWORD(ffi->dwProductVersionMS),\r\n\t\t\tLOWORD(ffi->dwProductVersionMS),\r\n\t\t\tHIWORD(ffi->dwProductVersionLS),\r\n\t\t\tLOWORD(ffi->dwProductVersionLS),\r\n\t\t\tHIWORD(ffi->dwFileVersionMS),\r\n\t\t\tLOWORD(ffi->dwFileVersionMS),\r\n\t\t\tHIWORD(ffi->dwFileVersionLS),\r\n\t\t\tLOWORD(ffi->dwFileVersionLS)\r\n\t\t);\r\n\t}\r\n\r\n\treturn CSoundKeeper::Main();\r\n}\r\n\r\n#else\r\n\r\nint APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ PWSTR pCmdLine, _In_ int nCmdShow)\r\n{\r\n\treturn CSoundKeeper::Main();\r\n}\r\n\r\n#endif\r\n"
  },
  {
    "path": "CSoundKeeper.hpp",
    "content": "#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 { None, Primary, Digital, Analog, All };\r\nenum class KeepStreamType { None, Zero, Fluctuate, Sine, WhiteNoise, BrownNoise, PinkNoise };\r\n\r\nclass CSoundKeeper;\r\n\r\n#include \"CSoundSession.hpp\"\r\n\r\nclass CSoundKeeper : public IMMNotificationClient\r\n{\r\nprotected:\r\n\r\n\tLONG                    m_ref_count = 1;\r\n\tCriticalSection         m_mutex;\r\n\r\n\t~CSoundKeeper();\r\n\r\npublic:\r\n\r\n\tCSoundKeeper();\r\n\r\n\t// IUnknown methods\r\n\r\n\tULONG STDMETHODCALLTYPE AddRef();\r\n\tULONG STDMETHODCALLTYPE Release();\r\n\tHRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface);\r\n\r\n\t// Callback methods for device-event notifications.\r\n\r\n\tHRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id);\r\n\tHRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id);\r\n\tHRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id);\r\n\tHRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state);\r\n\tHRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key);\r\n\r\nprotected:\r\n\r\n\tIMMDeviceEnumerator*    m_dev_enumerator = nullptr;\r\n\tbool                    m_is_started = false;\r\n\tbool                    m_is_retry_required = false;\r\n\tCSoundSession**         m_sessions = nullptr;\r\n\tUINT                    m_sessions_count = 0;\r\n\tAutoResetEvent          m_do_shutdown = false;\r\n\tAutoResetEvent          m_do_restart = false;\r\n\tAutoResetEvent          m_do_retry = false;\r\n\r\n\tbool                    m_cfg_allow_remote = false;\r\n\tbool                    m_cfg_no_sleep = false;\r\n\tKeepDeviceType          m_cfg_device_type = KeepDeviceType::Primary;\r\n\tKeepStreamType          m_cfg_stream_type = KeepStreamType::Zero;\r\n\tdouble                  m_cfg_frequency = 0.0;\r\n\tdouble                  m_cfg_amplitude = 0.0;\r\n\tdouble                  m_cfg_play_seconds = 0.0;\r\n\tdouble                  m_cfg_wait_seconds = 0.0;\r\n\tdouble                  m_cfg_fade_seconds = 0.0;\r\n\r\n\tHRESULT Start();\r\n\tHRESULT Stop();\r\n\tHRESULT Restart();\r\n\tbool Retry();\r\n\tCSoundSession* FindSession(LPCWSTR device_id);\r\n\r\npublic:\r\n\r\n\tvoid SetDeviceType(KeepDeviceType device_type) { m_cfg_device_type = device_type; }\r\n\tvoid SetStreamType(KeepStreamType stream_type) { m_cfg_stream_type = stream_type; }\r\n\tvoid SetAllowRemote(bool allow) { m_cfg_allow_remote = allow; }\r\n\tvoid SetNoSleep(bool value) { m_cfg_no_sleep = value; }\r\n\tKeepDeviceType GetDeviceType() const { return m_cfg_device_type; }\r\n\tKeepStreamType GetStreamType() const { return m_cfg_stream_type; }\r\n\tbool GetAllowRemote() const { return m_cfg_allow_remote; }\r\n\tbool GetNoSleep() const { return m_cfg_no_sleep; }\r\n\r\n\t// Configuration methods.\r\n\tvoid SetFrequency(double frequency) { m_cfg_frequency = frequency; }\r\n\tvoid SetAmplitude(double amplitude) { m_cfg_amplitude = amplitude; }\r\n\tvoid SetPeriodicPlaying(double seconds) { m_cfg_play_seconds = seconds; }\r\n\tvoid SetPeriodicWaiting(double seconds) { m_cfg_wait_seconds = seconds; }\r\n\tvoid SetFading(double seconds) { m_cfg_fade_seconds = seconds; }\r\n\tdouble GetFrequency() const { return m_cfg_frequency; }\r\n\tdouble GetAmplitude() const { return m_cfg_amplitude; }\r\n\tdouble GetPeriodicPlaying() const { return m_cfg_play_seconds; }\r\n\tdouble GetPeriodicWaiting() const { return m_cfg_wait_seconds; }\r\n\tdouble GetFading() const { return m_cfg_fade_seconds; }\r\n\r\n\t// Set stream type and defaults.\r\n\tvoid SetStreamTypeDefaults(KeepStreamType stream_type);\r\n\r\n\tvoid FireRetry();\r\n\tvoid FireRestart();\r\n\tvoid FireShutdown();\r\n\r\n\tvoid ParseStreamArgs(KeepStreamType stream_type, const char* args);\r\n\tvoid ParseModeString(const char* args);\r\n\tHRESULT Run();\r\n\tstatic HRESULT Main();\r\n};\r\n"
  },
  {
    "path": "CSoundSession.cpp",
    "content": "#include \"CSoundSession.hpp\"\r\n\r\n// Enable Multimedia Class Scheduler Service.\r\n#define ENABLE_MMCSS\r\n\r\n#ifdef ENABLE_MMCSS\r\n#include <avrt.h>\r\n#endif\r\n\r\nbool CSoundSession::g_is_leaky_wasapi = false;\r\n\r\nCSoundSession::CSoundSession(CSoundKeeper* soundkeeper, IMMDevice* endpoint)\r\n\t: m_soundkeeper(soundkeeper), m_endpoint(endpoint)\r\n{\r\n\tm_endpoint->AddRef();\r\n\tm_soundkeeper->AddRef();\r\n\r\n\tif (HRESULT hr = m_endpoint->GetId(&m_device_id); FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Unable to get device ID: 0x%08X.\", hr);\r\n\t\tm_curr_mode = RenderingMode::Invalid;\r\n\t}\r\n}\r\n\r\nCSoundSession::~CSoundSession(void)\r\n{\r\n\tthis->Stop();\r\n\tif (m_device_id) { CoTaskMemFree(m_device_id); }\r\n\tSafeRelease(m_endpoint);\r\n\tSafeRelease(m_soundkeeper);\r\n}\r\n\r\nHRESULT STDMETHODCALLTYPE CSoundSession::QueryInterface(REFIID iid, void **object)\r\n{\r\n\tif (object == NULL)\r\n\t{\r\n\t\treturn E_POINTER;\r\n\t}\r\n\t*object = NULL;\r\n\r\n\tif (iid == IID_IUnknown)\r\n\t{\r\n\t\t*object = static_cast<IUnknown *>(static_cast<IAudioSessionEvents *>(this));\r\n\t\tAddRef();\r\n\t}\r\n\telse if (iid == __uuidof(IAudioSessionEvents))\r\n\t{\r\n\t\t*object = static_cast<IAudioSessionEvents *>(this);\r\n\t\tAddRef();\r\n\t}\r\n\telse\r\n\t{\r\n\t\treturn E_NOINTERFACE;\r\n\t}\r\n\treturn S_OK;\r\n}\r\n\r\nULONG STDMETHODCALLTYPE CSoundSession::AddRef()\r\n{\r\n\treturn InterlockedIncrement(&m_ref_count);\r\n}\r\n\r\nULONG STDMETHODCALLTYPE CSoundSession::Release()\r\n{\r\n\tULONG result = InterlockedDecrement(&m_ref_count);\r\n\tif (result == 0)\r\n\t{\r\n\t\tdelete this;\r\n\t}\r\n\treturn result;\r\n}\r\n\r\n//\r\n// Initialize and start the renderer.\r\nbool CSoundSession::Start()\r\n{\r\n\tScopedLock lock(m_mutex);\r\n\r\n\tif (!this->IsValid()) { return false; }\r\n\tif (this->IsStarted()) { return true; }\r\n\r\n\tthis->Stop();\r\n\r\n\t//\r\n\t// Now create the thread which is going to drive the renderer.\r\n\tm_render_thread = CreateThread(NULL, 0, StartRenderingThread, this, 0, NULL);\r\n\tif (m_render_thread == NULL)\r\n\t{\r\n\t\tDebugLogError(\"Unable to create rendering thread: 0x%08X.\", GetLastError());\r\n\t\treturn false;\r\n\t}\r\n\r\n\t// Wait until rendering is started.\r\n\tif (WaitForAny({ m_is_started, m_render_thread }, INFINITE) != WAIT_OBJECT_0)\r\n\t{\r\n\t\tDebugLogError(\"Unable to start rendering.\");\r\n\t\tthis->Stop();\r\n\t\treturn false;\r\n\t}\r\n\r\n\treturn true;\r\n}\r\n\r\n//\r\n// Stop the renderer and free all the resources.\r\nvoid CSoundSession::Stop()\r\n{\r\n\tScopedLock lock(m_mutex);\r\n\r\n\tif (m_render_thread)\r\n\t{\r\n\t\tthis->DeferNextMode(RenderingMode::Stop);\r\n\t\tWaitForOne(m_render_thread, INFINITE);\r\n\t\tCloseHandle(m_render_thread);\r\n\t\tm_render_thread = NULL;\r\n\t\tthis->ResetCurrent();\r\n\t\tm_interrupt = false;\r\n\t}\r\n}\r\n\r\n//\r\n// Rendering thread.\r\n//\r\n\r\nDWORD APIENTRY CSoundSession::StartRenderingThread(LPVOID context)\r\n{\r\n\tDebugThreadName(\"Rendering\");\r\n\r\n\tCSoundSession* renderer = static_cast<CSoundSession*>(context);\r\n\r\n\tDebugLog(\"Enter rendering thread. Device ID: '%S'.\", renderer->GetDeviceId());\r\n\r\n\tif (HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Unable to initialize COM in rendering thread: 0x%08X.\", hr);\r\n\t\treturn 1;\r\n\t}\r\n\r\n#ifdef ENABLE_MMCSS\r\n\tHANDLE mmcss_handle = NULL;\r\n\tDWORD mmcss_task_index = 0;\r\n\tmmcss_handle = AvSetMmThreadCharacteristics(L\"Audio\", &mmcss_task_index);\r\n\tif (mmcss_handle == NULL)\r\n\t{\r\n\t\tDebugLogError(\"Unable to enable MMCSS on rendering thread: 0x%08X.\", GetLastError());\r\n\t}\r\n#endif\r\n\r\n\tDWORD result = renderer->RenderingThread();\r\n\r\n#ifdef ENABLE_MMCSS\r\n\tif (mmcss_handle != NULL) { AvRevertMmThreadCharacteristics(mmcss_handle); }\r\n#endif\r\n\r\n\tCoUninitialize();\r\n\r\n\tDebugLog(\"Leave rendering thread. Return code: %d.\", result);\r\n\treturn result;\r\n}\r\n\r\nDWORD CSoundSession::RenderingThread()\r\n{\r\n\tm_is_started = true;\r\n\tm_curr_mode = RenderingMode::Rendering;\r\n\tm_play_attempts = 0;\r\n\tm_wait_attempts = 0;\r\n\r\n\tDWORD delay = 0;\r\n\tbool loop = true;\r\n\r\n\twhile (loop)\r\n\t{\r\n\t\tDebugLog(\"Rendering thread mode: %d. Delay: %d.\", m_curr_mode, delay);\r\n\r\n\t\tswitch (WaitForOne(m_interrupt, delay))\r\n\t\t{\r\n\t\tcase WAIT_OBJECT_0:\r\n\r\n\t\t\tDebugLog(\"Set new rendering thread mode: %d.\", m_next_mode);\r\n\t\t\tm_curr_mode = m_next_mode;\r\n\t\t\tbreak;\r\n\r\n\t\tcase WAIT_TIMEOUT:\r\n\r\n\t\t\tbreak;\r\n\r\n\t\tdefault:\r\n\r\n\t\t\t// Shouldn't happen.\r\n\t\t\tm_curr_mode = RenderingMode::Invalid;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t\tdelay = 0;\r\n\r\n\t\tswitch (m_curr_mode)\r\n\t\t{\r\n\t\tcase RenderingMode::Rendering:\r\n\r\n\t\t\tDebugLog(\"Render. Device State: %d.\", this->GetDeviceState());\r\n\t\t\tm_curr_mode = this->Rendering();\r\n\t\t\tif (g_is_leaky_wasapi && m_curr_mode == RenderingMode::WaitExclusive)\r\n\t\t\t{\r\n\t\t\t\tdelay = 30;\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\r\n\t\tcase RenderingMode::TryOpenDevice:\r\n\r\n\t\t\tDebugLog(\"Try to open the device. Device State: %d.\", this->GetDeviceState());\r\n\t\t\tm_curr_mode = this->TryOpenDevice();\r\n\t\t\tif (g_is_leaky_wasapi && m_curr_mode == RenderingMode::WaitExclusive)\r\n\t\t\t{\r\n\t\t\t\tdelay = 30;\r\n\t\t\t}\r\n\t\t\tbreak;\r\n\r\n\t\tcase RenderingMode::WaitExclusive:\r\n\r\n\t\t\tif (g_is_leaky_wasapi)\r\n\t\t\t{\r\n\t\t\t\tDebugLog(\"Wait until exclusive session is finised.\");\r\n\t\t\t\tm_curr_mode = this->WaitExclusive();\r\n\t\t\t\tif (m_curr_mode == RenderingMode::WaitExclusive)\r\n\t\t\t\t{\r\n\t\t\t\t\tdelay = 500;\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\t[[fallthrough]];\r\n\r\n\t\tcase RenderingMode::Retry:\r\n\r\n\t\t\tif (g_is_leaky_wasapi && m_play_attempts > 1000)\r\n\t\t\t{\r\n\t\t\t\tDebugLog(\"Attempts limit. Stop.\");\r\n\t\t\t\tm_curr_mode = RenderingMode::Stop;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\r\n\t\t\t// m_play_attempts is 0 when it was interrupted while playing (when rendering was initialized without errors).\r\n\t\t\tdelay = (m_play_attempts == 0 ? 100UL : 1000UL);\r\n\r\n\t\t\tDebugLog(\"Retry in %dms. Attempt: #%d.\", delay, m_play_attempts);\r\n\t\t\tm_curr_mode = g_is_leaky_wasapi ? RenderingMode::TryOpenDevice : RenderingMode::Rendering;\r\n\t\t\tbreak;\r\n\r\n\t\t// case RenderingMode::Stop:\r\n\t\t// case RenderingMode::Invalid:\r\n\t\tdefault:\r\n\r\n\t\t\tloop = false;\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\tm_is_started = false;\r\n\treturn m_curr_mode == RenderingMode::Invalid;\r\n}\r\n\r\nCSoundSession::RenderingMode CSoundSession::TryOpenDevice()\r\n{\r\n\tHRESULT hr;\r\n\r\n\tDebugLog(\"Testing if the audio output is unused...\");\r\n\r\n\tm_play_attempts++;\r\n\r\n\t// Now activate an IAudioClient object on our preferred endpoint.\r\n\thr = m_endpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void**>(&m_audio_client));\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Unable to activate audio client: 0x%08X.\", hr);\r\n\t\treturn RenderingMode::Invalid;\r\n\t}\r\n\tdefer[&]{ SafeRelease(m_audio_client); };\r\n\r\n\t// Use the smallest possible buffer format for testing.\r\n\tWAVEFORMATEX mix_format = { WAVE_FORMAT_PCM };\r\n\tmix_format.nChannels = 1;\r\n\tmix_format.nSamplesPerSec = 8000;\r\n\tmix_format.wBitsPerSample = 8;\r\n\tmix_format.nBlockAlign = mix_format.nChannels * ((mix_format.wBitsPerSample + 7) / 8);\r\n\tmix_format.nAvgBytesPerSec = mix_format.nSamplesPerSec * mix_format.nBlockAlign;\r\n\r\n\t// Initialize WASAPI in timer driven mode.\r\n\thr = m_audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,\r\n\t\tAUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM,\r\n\t\tstatic_cast<UINT64>(0) * 10000, 0, &mix_format, NULL);\r\n\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\t// According to the WASAPI docs, AUDCLNT_E_DEVICE_IN_USE (0x8889000A) should be returned when a device is busy.\r\n\t\t// It's true when WASAPI exclusive is used. But when ASIO is used, HRESULT of ERROR_BUSY (0x800700AA) is returned.\r\n\t\tif (hr == AUDCLNT_E_DEVICE_IN_USE || hr == HRESULT_FROM_WIN32(ERROR_BUSY))\r\n\t\t{\r\n\t\t\tDebugLogWarning(\"Unable to initialize audio client: 0x%08X (device is being used in exclusive mode).\", hr);\r\n\t\t\treturn RenderingMode::WaitExclusive;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tDebugLogError(\"Unable to initialize audio client: 0x%08X.\", hr);\r\n\t\t\treturn RenderingMode::Invalid;\r\n\t\t}\r\n\t}\r\n\r\n\treturn RenderingMode::Rendering;\r\n}\r\n\r\nCSoundSession::RenderingMode CSoundSession::Rendering()\r\n{\r\n\tRenderingMode exit_mode;\r\n\tHRESULT hr;\r\n\r\n\tm_play_attempts++;\r\n\r\n\t// -------------------------------------------------------------------------\r\n\t// Rendering Init\r\n\t// -------------------------------------------------------------------------\r\n\r\n\t// Any errors below should invalidate this session.\r\n\texit_mode = RenderingMode::Invalid;\r\n\r\n\t//\r\n\t// Now activate an IAudioClient object on our preferred endpoint.\r\n\thr = m_endpoint->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void **>(&m_audio_client));\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Unable to activate audio client: 0x%08X.\", hr);\r\n\t\treturn exit_mode;\r\n\t}\r\n\tdefer [&] { SafeRelease(m_audio_client); };\r\n\r\n\t{\r\n\t\t// Get output format. Don't rely on it much since WASAPI reporting is not always accurate:\r\n\t\t// 24-bit compressed formats are reported as 16-bit; PCM int24 is reported as PCM int32.\r\n\t\tDebugLog(\"Getting output format...\");\r\n\t\tIPropertyStore* properties = nullptr;\r\n\t\thr = m_endpoint->OpenPropertyStore(STGM_READ, &properties);\r\n\t\tif (SUCCEEDED(hr))\r\n\t\t{\r\n\t\t\tPROPVARIANT out_format_prop;\r\n\t\t\tPropVariantInit(&out_format_prop);\r\n\t\t\thr = properties->GetValue(PKEY_AudioEngine_DeviceFormat, &out_format_prop);\r\n\t\t\tif (SUCCEEDED(hr) && out_format_prop.vt == VT_BLOB)\r\n\t\t\t{\r\n\t\t\t\tauto out_format = reinterpret_cast<WAVEFORMATEX*>(out_format_prop.blob.pBlobData);\r\n\t\t\t\tm_out_sample_type = ParseSampleType(out_format);\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tDebugLogWarning(\"Unable to get output format of the device: 0x%08X.\", hr);\r\n\t\t\t}\r\n\t\t\tPropVariantClear(&out_format_prop);\r\n\t\t\tSafeRelease(properties);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tDebugLogWarning(\"Unable to get property store of the device: 0x%08X.\", hr);\r\n\t\t}\r\n\t}\r\n\r\n\t{\r\n\t\t// Get mixer format. This is always float32.\r\n\t\tDebugLog(\"Getting mixing format...\");\r\n\t\tWAVEFORMATEX* mix_format = nullptr;\r\n\t\thr = m_audio_client->GetMixFormat(&mix_format);\r\n\t\tif (FAILED(hr))\r\n\t\t{\r\n\t\t\tDebugLogError(\"Unable to get mixing format on audio client: 0x%08X.\", hr);\r\n\t\t\treturn exit_mode;\r\n\t\t}\r\n\t\tdefer [&] { CoTaskMemFree(mix_format); };\r\n\r\n\t\tm_mix_sample_type = ParseSampleType(mix_format);\r\n\t\tif (m_mix_sample_type != SampleType::Float32)\r\n\t\t{\r\n\t\t\tDebugLogError(\"Mixing format is not 32-bit float that is not supported.\");\r\n\t\t\treturn exit_mode;\r\n\t\t}\r\n\r\n\t\tm_channels_count = mix_format->nChannels;\r\n\t\tm_frame_size = mix_format->nBlockAlign;\r\n\r\n\t\t// Noise generation works best with the 48000Hz sample rate.\r\n\t\tif (m_stream_type == KeepStreamType::WhiteNoise || m_stream_type == KeepStreamType::BrownNoise || m_stream_type == KeepStreamType::PinkNoise)\r\n\t\t{\r\n\t\t\tDebugLog(\"Using 48000Hz sample rate for noise generation.\");\r\n\t\t\tmix_format->nSamplesPerSec = 48000;\r\n\t\t\tmix_format->nAvgBytesPerSec= mix_format->nSamplesPerSec * mix_format->nBlockAlign;\r\n\t\t}\r\n\r\n\t\tm_sample_rate = mix_format->nSamplesPerSec;\r\n\r\n\t\t// Use smaller buffer if leaky WASAPI.\r\n\t\t// Rendering loop relies on not precise enough system timer so minimum viable buffer is 40ms.\r\n\t\tm_buffer_size_in_ms = g_is_leaky_wasapi ? 100 : 1000;\r\n\r\n\t\t// Initialize WASAPI in timer driven mode.\r\n\t\thr = m_audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,\r\n\t\t\tAUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM /*| AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY*/,\r\n\t\t\tstatic_cast<UINT64>(m_buffer_size_in_ms) * 10000, 0, mix_format, NULL);\r\n\t}\r\n\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\t// According to the WASAPI docs, AUDCLNT_E_DEVICE_IN_USE (0x8889000A) should be returned when a device is busy.\r\n\t\t// It's true when WASAPI exclusive is used. But when ASIO is used, HRESULT of ERROR_BUSY (0x800700AA) is returned.\r\n\t\tif (hr == AUDCLNT_E_DEVICE_IN_USE || hr == HRESULT_FROM_WIN32(ERROR_BUSY))\r\n\t\t{\r\n\t\t\tDebugLogWarning(\"Unable to initialize audio client: 0x%08X (device is being used in exclusive mode).\", hr);\r\n\t\t\texit_mode = RenderingMode::WaitExclusive;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tDebugLogError(\"Unable to initialize audio client: 0x%08X.\", hr);\r\n\t\t\t// exit_mode = RenderingMode::Invalid;\r\n\t\t}\r\n\r\n\t\treturn exit_mode;\r\n\t}\r\n\r\n\t// Retry on any errors below.\r\n\texit_mode = RenderingMode::Retry;\r\n\r\n\t//\r\n\t// Retrieve the buffer size for the audio client.\r\n\thr = m_audio_client->GetBufferSize(&m_buffer_size_in_frames);\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Unable to get audio client buffer: 0x%08X.\", hr);\r\n\t\treturn exit_mode;\r\n\t}\r\n\r\n\tm_buffer_size_in_ms = (m_buffer_size_in_frames * 1000) / m_sample_rate;\r\n\tDebugLog(\"Output buffer: %ums (%u frames).\", m_buffer_size_in_ms, m_buffer_size_in_frames);\r\n\r\n\thr = m_audio_client->GetService(IID_PPV_ARGS(&m_render_client));\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Unable to get new render client: 0x%08X.\", hr);\r\n\t\treturn exit_mode;\r\n\t}\r\n\tdefer [&] { m_render_client->Release(); };\r\n\r\n\t//\r\n\t// Register for session and endpoint change notifications.  \r\n\thr = m_audio_client->GetService(IID_PPV_ARGS(&m_audio_session_control));\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Unable to retrieve session control: 0x%08X.\", hr);\r\n\t\treturn exit_mode;\r\n\t}\r\n\tdefer [&] { m_audio_session_control->Release(); };\r\n\thr = m_audio_session_control->RegisterAudioSessionNotification(this);\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Unable to register for stream switch notifications: 0x%08X.\", hr);\r\n\t\treturn exit_mode;\r\n\t}\r\n\tdefer [&] { m_audio_session_control->UnregisterAudioSessionNotification(this); };\r\n\r\n\t// -------------------------------------------------------------------------\r\n\t// Rendering Loop\r\n\t// -------------------------------------------------------------------------\r\n\r\n\tDebugLog(\"Starting rendering...\");\r\n\r\n\t// We need to pre-roll one buffer of data into the pipeline before starting.\r\n\thr = this->Render();\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Can't render initial buffer: 0x%08X.\", hr);\r\n\t\treturn exit_mode;\r\n\t}\r\n\r\n\thr = m_audio_client->Start();\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Unable to start render client: 0x%08X.\", hr);\r\n\t\treturn exit_mode;\r\n\t}\r\n\r\n\tDebugLog(\"Enter rendering loop.\");\r\n\r\n\tm_play_attempts = 0;\r\n\r\n\tDWORD timeout = m_stream_type == KeepStreamType::None ? INFINITE : (m_buffer_size_in_ms / 2 + m_buffer_size_in_ms / 4);\r\n\tfor (bool working = true; working; ) switch (WaitForOne(m_interrupt, timeout))\r\n\t{\r\n\tcase WAIT_TIMEOUT: // Timeout.\r\n\r\n\t\t// Provide the next buffer of samples.\r\n\t\thr = this->Render();\r\n\t\tif (FAILED(hr))\r\n\t\t{\r\n\t\t\tworking = false;\r\n\t\t}\r\n\t\tbreak;\r\n\r\n\tcase WAIT_OBJECT_0 + 0: // m_interrupt.\r\n\r\n\t\t// We're done, exit the loop.\r\n\t\texit_mode = m_next_mode;\r\n\t\tworking = false;\r\n\t\tbreak;\r\n\r\n\tdefault:\r\n\r\n\t\t// Should never happen.\r\n\t\texit_mode = RenderingMode::Invalid;\r\n\t\tworking = false;\r\n\t\tbreak;\r\n\t}\r\n\r\n\tDebugLog(\"Leave rendering loop. Stopping audio client...\");\r\n\r\n\tm_audio_client->Stop();\r\n\treturn exit_mode;\r\n}\r\n\r\nCSoundSession::SampleType CSoundSession::ParseSampleType(WAVEFORMATEX* format)\r\n{\r\n\tSampleType result = SampleType::Unknown;\r\n\r\n\tif (format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT\r\n\t\t|| (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && reinterpret_cast<WAVEFORMATEXTENSIBLE*>(format)->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))\r\n\t{\r\n\t\tDebugLog(\"Format: PCM %dch %dHz %d-bit float.\", format->nChannels, format->nSamplesPerSec, format->wBitsPerSample);\r\n\t\tif (format->wBitsPerSample == 32)\r\n\t\t{\r\n\t\t\tresult = SampleType::Float32;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tDebugLogWarning(\"Unsupported PCM float sample type.\");\r\n\t\t}\r\n\t}\r\n\telse if (format->wFormatTag == WAVE_FORMAT_PCM\r\n\t\t|| format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && reinterpret_cast<WAVEFORMATEXTENSIBLE*>(format)->SubFormat == KSDATAFORMAT_SUBTYPE_PCM)\r\n\t{\r\n\t\tDebugLog(\"Format: PCM %dch %dHz %d-bit integer.\", format->nChannels, format->nSamplesPerSec, format->wBitsPerSample);\r\n\t\tif (format->wBitsPerSample == 16)\r\n\t\t{\r\n\t\t\tresult = SampleType::Int16;\r\n\t\t}\r\n\t\telse if (format->wBitsPerSample == 24)\r\n\t\t{\r\n\t\t\tresult = SampleType::Int24;\r\n\t\t}\r\n\t\telse if (format->wBitsPerSample == 32)\r\n\t\t{\r\n\t\t\tresult = SampleType::Int32;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tDebugLogWarning(\"Unsupported PCM integer sample type.\");\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n#ifdef _CONSOLE\r\n\t\tif (format->wFormatTag != WAVE_FORMAT_EXTENSIBLE)\r\n\t\t{\r\n\t\t\tDebugLogWarning(\"Unrecognized format: 0x%04hX %dch %dHz %d-bit.\", format->wFormatTag,\r\n\t\t\t\tformat->nChannels, format->nSamplesPerSec, format->wBitsPerSample);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tGUID& guid = reinterpret_cast<WAVEFORMATEXTENSIBLE*>(format)->SubFormat;\r\n\t\t\tDebugLogWarning(\"Unrecognized format: {%08lx-%04hx-%04hx-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx} %dch %dHz %d-bit.\",\r\n\t\t\t\tguid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1],\r\n\t\t\t\tguid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7],\r\n\t\t\t\tformat->nChannels, format->nSamplesPerSec, format->wBitsPerSample);\r\n\t\t}\r\n#endif\r\n\t}\r\n\r\n\treturn result;\r\n}\r\n\r\nHRESULT CSoundSession::Render()\r\n{\r\n\tHRESULT hr = S_OK;\r\n\r\n\tTraceLog(\"Render.\");\r\n\r\n\tif (m_stream_type == KeepStreamType::None)\r\n\t{\r\n\t\treturn hr;\r\n\t}\r\n\r\n\t// Find out how much of the buffer *isn't* available (is padding).\r\n\tUINT32 padding = 0;\r\n\thr = m_audio_client->GetCurrentPadding(&padding);\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Failed to get padding: 0x%08X.\", hr);\r\n\t\treturn hr;\r\n\t}\r\n\r\n\t// Calculate the number of frames available. It can be 0 right after waking PC up after sleeping.\r\n\tUINT32 need_frames = m_buffer_size_in_frames - padding;\r\n\tif (need_frames == 0)\r\n\t{\r\n\t\tDebugLogWarning(\"None samples were consumed. Was PC sleeping?\");\r\n\t\treturn S_OK;\r\n\t}\r\n\r\n\tBYTE* p_data;\r\n\thr = m_render_client->GetBuffer(need_frames, &p_data);\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Failed to get buffer: 0x%08X.\", hr);\r\n\t\treturn hr;\r\n\t}\r\n\r\n\t// Generate sound.\r\n\r\n\tDWORD render_flags = NULL;\r\n\r\n\tuint64_t play_frames = static_cast<uint64_t>(m_play_seconds * m_sample_rate);\r\n\tuint64_t wait_frames = static_cast<uint64_t>(m_wait_seconds * m_sample_rate);\r\n\tuint64_t fade_frames = static_cast<uint64_t>(m_fade_seconds * m_sample_rate);\r\n\r\n\tif (!wait_frames && !fade_frames)\r\n\t{\r\n\t\tplay_frames = 0;\r\n\t}\r\n\telse if (!play_frames)\r\n\t{\r\n\t\twait_frames = 0;\r\n\t}\r\n\r\n\tif (play_frames)\r\n\t{\r\n\t\tfade_frames = std::min(fade_frames, play_frames / 2);\r\n\t}\r\n\r\n\tuint64_t period_frames = play_frames + wait_frames;\r\n\r\n\tif (period_frames && play_frames <= m_curr_frame && (m_curr_frame + need_frames) <= period_frames)\r\n\t{\r\n\t\t// Just silence whole time.\r\n\t\trender_flags = AUDCLNT_BUFFERFLAGS_SILENT;\r\n\t\tm_curr_frame = (m_curr_frame + need_frames) % period_frames;\r\n\t}\r\n\telse if (m_stream_type == KeepStreamType::Fluctuate && m_frequency)\r\n\t{\r\n\t\tuint64_t once_in_frames = std::max(uint64_t(double(m_sample_rate) / m_frequency), 2ULL);\r\n\r\n\t\tfor (size_t i = 0; i < need_frames; i++)\r\n\t\t{\r\n\t\t\tuint32_t sample = 0;\r\n\r\n\t\t\tif ((!period_frames || m_curr_frame < play_frames) && m_curr_frame % once_in_frames == 0)\r\n\t\t\t{\r\n\t\t\t\t// 0x38000100 = 3.051851E-5 = 1.0/32767. Minimal 16-bit deviation from 0.\r\n\t\t\t\t// 0x34000001 = 1.192093E-7 = 1.0/8388607. Minimal 24-bit deviation from 0.\r\n\t\t\t\tsample = (m_out_sample_type == SampleType::Int16) ? 0x38000100 : 0x34000001;\r\n\r\n\t\t\t\t// Negate each odd time.\r\n\t\t\t\tif ((m_curr_frame / once_in_frames) & 1)\r\n\t\t\t\t{\r\n\t\t\t\t\tsample |= 0x80000000;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tfor (size_t j = 0; j < m_channels_count; j++)\r\n\t\t\t{\r\n\t\t\t\t*reinterpret_cast<uint32_t*>(p_data + j * sizeof(float)) = sample;\r\n\t\t\t}\r\n\r\n\t\t\tp_data += m_frame_size;\r\n\t\t\tm_curr_frame++;\r\n\t\t\tif (period_frames) { m_curr_frame %= period_frames; }\r\n\t\t}\r\n\t}\r\n\telse if (m_stream_type == KeepStreamType::Sine && m_frequency && m_amplitude)\r\n\t{\r\n\t\tdouble theta_increment = (std::min(m_frequency, m_sample_rate / 2.0) * (M_PI*2)) / double(m_sample_rate);\r\n\r\n\t\tfor (size_t i = 0; i < need_frames; i++)\r\n\t\t{\r\n\t\t\tdouble amplitude = m_amplitude;\r\n\r\n\t\t\tif (period_frames || m_curr_frame < fade_frames)\r\n\t\t\t{\r\n\t\t\t\tif (m_curr_frame < fade_frames)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Fade in.\r\n\t\t\t\t\tdouble fade_volume = (1.0 / fade_frames) * m_curr_frame;\r\n\t\t\t\t\tamplitude *= pow(fade_volume, 2);\r\n\t\t\t\t}\r\n\t\t\t\telse if (!play_frames || m_curr_frame < (play_frames - fade_frames))\r\n\t\t\t\t{\r\n\t\t\t\t\t// Max volume.\r\n\t\t\t\t}\r\n\t\t\t\telse if (m_curr_frame < play_frames)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Fade out.\r\n\t\t\t\t\tdouble fade_volume = (1.0 / fade_frames) * (play_frames - m_curr_frame);\r\n\t\t\t\t\tamplitude *= pow(fade_volume, 2);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\t// Silence.\r\n\t\t\t\t\tamplitude = 0;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tfloat sample = 0;\r\n\r\n\t\t\tif (amplitude)\r\n\t\t\t{\r\n\t\t\t\tsample = float(sin(m_curr_theta) * amplitude);\r\n\t\t\t\tm_curr_theta += theta_increment;\r\n\t\t\t}\r\n\r\n\t\t\tfor (size_t j = 0; j < m_channels_count; j++)\r\n\t\t\t{\r\n\t\t\t\t*reinterpret_cast<float*>(p_data + j * sizeof(float)) = sample;\r\n\t\t\t}\r\n\r\n\t\t\tp_data += m_frame_size;\r\n\t\t\tm_curr_frame++;\r\n\t\t\tif (period_frames) { m_curr_frame %= period_frames; }\r\n\t\t}\r\n\t}\r\n\telse if ((m_stream_type == KeepStreamType::WhiteNoise || m_stream_type == KeepStreamType::BrownNoise || m_stream_type == KeepStreamType::PinkNoise) && m_amplitude)\r\n\t{\r\n\t\tuint64_t lcg_state = GetTickCount64();\r\n\r\n\t\tfor (size_t i = 0; i < need_frames; i++)\r\n\t\t{\r\n\t\t\tdouble amplitude = m_amplitude;\r\n\r\n\t\t\tif (period_frames || m_curr_frame < fade_frames)\r\n\t\t\t{\r\n\t\t\t\tif (m_curr_frame < fade_frames)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Fade in.\r\n\t\t\t\t\tdouble fade_volume = (1.0 / fade_frames) * m_curr_frame;\r\n\t\t\t\t\tamplitude *= pow(fade_volume, 2);\r\n\t\t\t\t}\r\n\t\t\t\telse if (!play_frames || m_curr_frame < (play_frames - fade_frames))\r\n\t\t\t\t{\r\n\t\t\t\t\t// Max volume.\r\n\t\t\t\t}\r\n\t\t\t\telse if (m_curr_frame < play_frames)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Fade out.\r\n\t\t\t\t\tdouble fade_volume = (1.0 / fade_frames) * (play_frames - m_curr_frame);\r\n\t\t\t\t\tamplitude *= pow(fade_volume, 2);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\t// Silence.\r\n\t\t\t\t\tamplitude = 0;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tfloat sample = 0;\r\n\r\n\t\t\tif (amplitude)\r\n\t\t\t{\r\n\t\t\t\tlcg_state = lcg_state * 6364136223846793005ULL + 1; // LCG from Musl.\r\n\t\t\t\tdouble value = (double((lcg_state >> 32) & 0x7FFFFFFF) / double(0x7FFFFFFFU)) * 2.0 - 1.0; // -1 .. 1\r\n\r\n\t\t\t\tif (m_stream_type == KeepStreamType::BrownNoise)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Brown Noise from SoX + a leaky integrator to reduce low frequency humming.\r\n\t\t\t\t\tm_curr_value += value * (1.0 / 16);\r\n\t\t\t\t\tm_curr_value /= 1.02; // The leaky integrator.\r\n\t\t\t\t\tm_curr_value = fmod(m_curr_value, 4);\r\n\t\t\t\t\tvalue = m_curr_value;\r\n\r\n\t\t\t\t\t// Normalize values out of the -1..1 range using \"mirroring\".\r\n\t\t\t\t\t// Example: 0.8, 0.9, 1.0, 0.9, 0.8, ..., -0.8, -0.9, -1.0, -0.9, -0.8, ...\r\n\t\t\t\t\t// Precondition: value must be between -4.0 and 4.0.\r\n\t\t\t\t\tif (value < -1.0 || 1.0 < value)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tdouble sign = (value < 0.0) ? -1.0 : 1.0;\r\n\t\t\t\t\t\tvalue = fabs(value);\r\n\t\t\t\t\t\tvalue = ((value <= 3.0) ? (2.0 - value) : (value - 4.0)) * sign;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (m_stream_type == KeepStreamType::PinkNoise)\r\n\t\t\t\t{\r\n\t\t\t\t\t// Paul Kellet's method.\r\n\t\t\t\t\tdouble white = value;\r\n\t\t\t\t\tm_curr_state[0] = 0.99886 * m_curr_state[0] + white * 0.0555179;\r\n\t\t\t\t\tm_curr_state[1] = 0.99332 * m_curr_state[1] + white * 0.0750759;\r\n\t\t\t\t\tm_curr_state[2] = 0.96900 * m_curr_state[2] + white * 0.1538520;\r\n\t\t\t\t\tm_curr_state[3] = 0.86650 * m_curr_state[3] + white * 0.3104856;\r\n\t\t\t\t\tm_curr_state[4] = 0.55000 * m_curr_state[4] + white * 0.5329522;\r\n\t\t\t\t\tm_curr_state[5] = -0.7616 * m_curr_state[5] - white * 0.0168980;\r\n\t\t\t\t\tvalue = 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;\r\n\t\t\t\t\tvalue *= 0.11; // (roughly) compensate for gain.\r\n\t\t\t\t\tm_curr_state[6] = white * 0.115926;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tsample = float(value * amplitude);\r\n\t\t\t}\r\n\r\n\t\t\tfor (size_t j = 0; j < m_channels_count; j++)\r\n\t\t\t{\r\n\t\t\t\t*reinterpret_cast<float*>(p_data + j * sizeof(float)) = sample;\r\n\t\t\t}\r\n\r\n\t\t\tp_data += m_frame_size;\r\n\t\t\tm_curr_frame++;\r\n\t\t\tif (period_frames) { m_curr_frame %= period_frames; }\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\t// ZeroMemory(p_data, static_cast<SIZE_T>(m_frame_size) * need_frames);\r\n\t\trender_flags = AUDCLNT_BUFFERFLAGS_SILENT;\r\n\t}\r\n\r\n\thr = m_render_client->ReleaseBuffer(need_frames, render_flags);\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Failed to release buffer: 0x%08X.\", hr);\r\n\t\treturn hr;\r\n\t}\r\n\r\n\treturn S_OK;\r\n}\r\n\r\nCSoundSession::RenderingMode CSoundSession::WaitExclusive()\r\n{\r\n\tRenderingMode exit_mode;\r\n\tHRESULT hr;\r\n\r\n\t// Any errors below should invalidate this session.\r\n\texit_mode = RenderingMode::Invalid;\r\n\r\n\tIAudioSessionManager2* as_manager = nullptr;\r\n\thr = m_endpoint->Activate(__uuidof(IAudioSessionManager2), CLSCTX_INPROC_SERVER, NULL, reinterpret_cast<void**>(&as_manager));\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Unable to activate audio session manager: 0x%08X.\", hr);\r\n\t\treturn exit_mode;\r\n\t}\r\n\tdefer [&] { as_manager->Release(); };\r\n\r\n\tIAudioSessionEnumerator* session_list = nullptr;\r\n\thr = as_manager->GetSessionEnumerator(&session_list);\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Unable to get session enumerator: 0x%08X.\", hr);\r\n\t\treturn exit_mode;\r\n\t}\r\n\tdefer [&] { session_list->Release(); };\r\n\r\n\tint session_count = 0;\r\n\thr = session_list->GetCount(&session_count);\r\n\tif (FAILED(hr))\r\n\t{\r\n\t\tDebugLogError(\"Unable to get session count: 0x%08X.\", hr);\r\n\t\treturn exit_mode;\r\n\t}\r\n\r\n\t// Retry on any errors below.\r\n\texit_mode = RenderingMode::Retry;\r\n\r\n\tIAudioSessionControl* session_control = nullptr;\r\n\tdefer [&] { SafeRelease(session_control); };\r\n\r\n\t// Find active session on this device.\r\n\tfor (int index = 0 ; index < session_count ; index++)\r\n\t{\r\n\t\thr = session_list->GetSession(index, &session_control);\r\n\t\tif (FAILED(hr))\r\n\t\t{\r\n\t\t\tDebugLogError(\"Unable to get session #%d: 0x%08X.\", index, hr);\r\n\t\t\treturn exit_mode;\r\n\t\t}\r\n\r\n\t\tAudioSessionState state = AudioSessionStateInactive;\r\n\t\thr = session_control->GetState(&state);\r\n\t\tif (FAILED(hr))\r\n\t\t{\r\n\t\t\tDebugLogError(\"Unable to get session #%d state: 0x%08X.\", index, hr);\r\n\t\t\treturn exit_mode;\r\n\t\t}\r\n\r\n\t\tif (state != AudioSessionStateActive)\r\n\t\t{\r\n\t\t\tSafeRelease(session_control);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\tif (!session_control)\r\n\t{\r\n\t\tif (++m_wait_attempts < 100)\r\n\t\t{\r\n\t\t\tDebugLog(\"No active sessions found, try again.\");\r\n\t\t\texit_mode = RenderingMode::WaitExclusive;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tDebugLog(\"No active sessions found, tried too many times. Try to play.\");\r\n\t\t\tm_wait_attempts = 0;\r\n\t\t\texit_mode = RenderingMode::Retry;\r\n\t\t}\r\n\t}\r\n\telse\r\n\t{\r\n\t\tm_wait_attempts = 0;\r\n\t\thr = session_control->RegisterAudioSessionNotification(this);\r\n\t\tif (FAILED(hr))\r\n\t\t{\r\n\t\t\tDebugLogError(\"Unable to register for stream switch notifications: 0x%08X.\", hr);\r\n\t\t\treturn exit_mode;\r\n\t\t}\r\n\r\n\t\t// Wait until we receive a notification that the streem is inactive.\r\n\t\tswitch (WaitForOne(m_interrupt))\r\n\t\t{\r\n\t\tcase WAIT_OBJECT_0: // m_interrupt.\r\n\r\n\t\t\t// We're done, exit the loop.\r\n\t\t\texit_mode = m_next_mode;\r\n\t\t\tbreak;\r\n\r\n\t\tdefault:\r\n\r\n\t\t\t// Should never happen.\r\n\t\t\texit_mode = RenderingMode::Invalid;\r\n\t\t\tbreak;\r\n\t\t}\r\n\r\n\t\tsession_control->UnregisterAudioSessionNotification(this);\r\n\t}\r\n\r\n\treturn exit_mode;\r\n}\r\n\r\n//\r\n// Called when state of an audio session is changed.\r\nHRESULT CSoundSession::OnStateChanged(AudioSessionState NewState)\r\n{\r\n\tif (m_curr_mode != RenderingMode::WaitExclusive)\r\n\t{\r\n\t\treturn S_OK;\r\n\t}\r\n\r\n\t// On stop, it becomes AudioSessionStateInactive (0), and then AudioSessionStateExpired (2).\r\n\tDebugLog(\"State of the exclusive mode session is changed: %d.\", NewState);\r\n\tthis->DeferNextMode(RenderingMode::Retry);\r\n\treturn S_OK;\r\n};\r\n\r\n//\r\n// Called when an audio session is disconnected.\r\nHRESULT CSoundSession::OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason)\r\n{\r\n\tif (m_curr_mode != RenderingMode::Rendering)\r\n\t{\r\n\t\treturn S_OK;\r\n\t}\r\n\r\n\tswitch (DisconnectReason)\r\n\t{\r\n\tcase DisconnectReasonFormatChanged:\r\n\r\n\t\tDebugLog(\"Session is disconnected with reason %d. Retry.\", DisconnectReason);\r\n\t\tthis->DeferNextMode(RenderingMode::Retry);\r\n\t\tbreak;\r\n\r\n\tcase DisconnectReasonExclusiveModeOverride:\r\n\r\n\t\tDebugLog(\"Session is disconnected with reason %d. WaitExclusive.\", DisconnectReason);\r\n\t\tthis->DeferNextMode(RenderingMode::WaitExclusive);\r\n\t\tbreak;\r\n\r\n\tdefault:\r\n\r\n\t\tDebugLog(\"Session is disconnected with reason %d. Restart.\", DisconnectReason);\r\n\t\tthis->DeferNextMode(RenderingMode::Invalid);\r\n\t\tm_soundkeeper->FireRestart();\r\n\t\tbreak;\r\n\t}\r\n\r\n\treturn S_OK;\r\n}\r\n\r\nHRESULT CSoundSession::OnSimpleVolumeChanged(float NewSimpleVolume, BOOL NewMute, LPCGUID EventContext)\r\n{\r\n\tif (NewMute)\r\n\t{\r\n\t\t// Shutdown Sound Keeper when muted.\r\n\t\t// m_soundkeeper->FireShutdown();\r\n\t}\r\n\treturn S_OK;\r\n}\r\n"
  },
  {
    "path": "CSoundSession.hpp",
    "content": "#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#include \"CSoundKeeper.hpp\"\r\n\r\nclass CSoundSession : IAudioSessionEvents\r\n{\r\nprotected:\r\n\r\n\tstatic bool g_is_leaky_wasapi;\r\n\r\npublic:\r\n\r\n\tstatic void EnableWaitExclusiveWorkaround(bool enable) { g_is_leaky_wasapi = enable; }\r\n\r\nprotected:\r\n\r\n\tLONG                    m_ref_count = 1;\r\n\tCriticalSection         m_mutex;\r\n\r\n\tCSoundKeeper*           m_soundkeeper = nullptr;\r\n\tIMMDevice*              m_endpoint = nullptr;\r\n\tLPWSTR                  m_device_id = nullptr;\r\n\tKeepStreamType          m_stream_type = KeepStreamType::Zero;\r\n\r\n\tHANDLE                  m_render_thread = NULL;\r\n\tManualResetEvent        m_is_started = false;\r\n\r\n\tenum class RenderingMode { Stop, Rendering, Retry, WaitExclusive, TryOpenDevice, Invalid };\r\n\tRenderingMode           m_curr_mode = RenderingMode::Stop;\r\n\tRenderingMode           m_next_mode = RenderingMode::Stop;\r\n\tAutoResetEvent          m_interrupt = false;\r\n\tDWORD                   m_play_attempts = 0;\r\n\tDWORD                   m_wait_attempts = 0;\r\n\r\n\tvoid DeferNextMode(RenderingMode next_mode)\r\n\t{\r\n\t\tm_next_mode = next_mode;\r\n\t\tm_interrupt = true;\r\n\t}\r\n\r\n\tIAudioClient*           m_audio_client = nullptr;\r\n\tIAudioRenderClient*     m_render_client = nullptr;\r\n\tIAudioSessionControl*   m_audio_session_control = nullptr;\r\n\r\n\tenum class SampleType { Unknown, Int16, Int24, Int32, Float32 };\r\n\tstatic SampleType ParseSampleType(WAVEFORMATEX* format);\r\n\tSampleType              m_mix_sample_type = SampleType::Unknown;\r\n\tSampleType              m_out_sample_type = SampleType::Unknown;\r\n\r\n\tUINT32                  m_sample_rate = 0;\r\n\tUINT32                  m_channels_count = 0;\r\n\tUINT32                  m_frame_size = 0;\r\n\r\n\tUINT32                  m_buffer_size_in_ms = 1000;\r\n\tUINT32                  m_buffer_size_in_frames = 0;\r\n\r\n\t// Sound generation settings.\r\n\tdouble                  m_frequency = 0.0;\r\n\tdouble                  m_amplitude = 0.0;\r\n\r\n\t// Periodicity settings.\r\n\tdouble                  m_play_seconds = 0.0;\r\n\tdouble                  m_wait_seconds = 0.0;\r\n\tdouble                  m_fade_seconds = 0.0;\r\n\r\n\t// Current state.\r\n\tuint64_t                m_curr_frame = 0;\r\n\tunion\r\n\t{\r\n\t\tdouble              m_curr_state[8]{0}; // Pink Noise.\r\n\t\tdouble              m_curr_value;       // Brown Noise.\r\n\t\tdouble              m_curr_theta;       // Sine.\r\n\t};\r\n\r\npublic:\r\n\r\n\tCSoundSession(CSoundKeeper* soundkeeper, IMMDevice* endpoint);\r\n\tbool Start();\r\n\tvoid Stop();\r\n\tbool IsStarted() const { return m_is_started; }\r\n\tbool IsValid() const { return m_curr_mode != RenderingMode::Invalid; };\r\n\tLPCWSTR GetDeviceId() const { return m_device_id; }\r\n\tDWORD GetDeviceState() { DWORD state = 0; m_endpoint->GetState(&state); return state; }\r\n\r\n\tvoid ResetCurrent()\r\n\t{\r\n\t\tif (m_curr_frame)\r\n\t\t{\r\n\t\t\tm_curr_frame = 0;\r\n\t\t\tmemset(m_curr_state, 0, sizeof(m_curr_state));\r\n\t\t}\r\n\t}\r\n\r\n\tvoid SetStreamType(KeepStreamType stream_type)\r\n\t{\r\n\t\tm_stream_type = stream_type;\r\n\t\tthis->ResetCurrent();\r\n\t}\r\n\r\n\tKeepStreamType GetStreamType() const\r\n\t{\r\n\t\treturn m_stream_type;\r\n\t}\r\n\r\n\t// Sine generation settings.\r\n\r\n\tvoid SetFrequency(double frequency)\r\n\t{\r\n\t\tm_frequency = frequency > 0 ? frequency : 0;\r\n\t\tthis->ResetCurrent();\r\n\t}\r\n\r\n\tdouble GetFrequency() const\r\n\t{\r\n\t\treturn m_frequency;\r\n\t}\r\n\r\n\tvoid SetAmplitude(double amplitude)\r\n\t{\r\n\t\tm_amplitude = amplitude > 0 ? amplitude : 0;\r\n\t\tthis->ResetCurrent();\r\n\t}\r\n\r\n\tdouble GetAmplitude() const\r\n\t{\r\n\t\treturn m_amplitude;\r\n\t}\r\n\r\n\t// Periodicity settings.\r\n\r\n\tvoid SetPeriodicPlaying(double seconds)\r\n\t{\r\n\t\tm_play_seconds = seconds > 0 ? seconds : 0;\r\n\t\tthis->ResetCurrent();\r\n\t}\r\n\r\n\tdouble GetPeriodicPlaying() const\r\n\t{\r\n\t\treturn m_play_seconds;\r\n\t}\r\n\r\n\tvoid SetPeriodicWaiting(double seconds)\r\n\t{\r\n\t\tm_wait_seconds = seconds > 0 ? seconds : 0;\r\n\t\tthis->ResetCurrent();\r\n\t}\r\n\r\n\tdouble GetPeriodicWaiting() const\r\n\t{\r\n\t\treturn m_wait_seconds;\r\n\t}\r\n\r\n\tvoid SetFading(double seconds)\r\n\t{\r\n\t\tm_fade_seconds = seconds > 0 ? seconds : 0;\r\n\t\tthis->ResetCurrent();\r\n\t}\r\n\r\n\tdouble GetFading() const\r\n\t{\r\n\t\treturn m_fade_seconds;\r\n\t}\r\n\r\nprotected:\r\n\r\n\t~CSoundSession(void);\r\n\r\n\t//\r\n\t// Rendering thread.\r\n\t//\r\n\r\n\tstatic DWORD APIENTRY StartRenderingThread(LPVOID context);\r\n\tDWORD RenderingThread();\r\n\tRenderingMode TryOpenDevice();\r\n\tRenderingMode Rendering();\r\n\tHRESULT Render();\r\n\tRenderingMode WaitExclusive();\r\n\r\npublic:\r\n\r\n\t//\r\n\t// IUnknown\r\n\t//\r\n\r\n\tHRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **object);\r\n\tULONG STDMETHODCALLTYPE AddRef();\r\n\tULONG STDMETHODCALLTYPE Release();\r\n\r\nprotected:\r\n\r\n\t//\r\n\t// IAudioSessionEvents.\r\n\t//\r\n\r\n\tSTDMETHOD(OnDisplayNameChanged) (LPCWSTR /*NewDisplayName*/, LPCGUID /*EventContext*/) { return S_OK; };\r\n\tSTDMETHOD(OnIconPathChanged) (LPCWSTR /*NewIconPath*/, LPCGUID /*EventContext*/) { return S_OK; };\r\n\tSTDMETHOD(OnSimpleVolumeChanged) (float /*NewSimpleVolume*/, BOOL /*NewMute*/, LPCGUID /*EventContext*/);\r\n\tSTDMETHOD(OnChannelVolumeChanged) (DWORD /*ChannelCount*/, float /*NewChannelVolumes*/[], DWORD /*ChangedChannel*/, LPCGUID /*EventContext*/) { return S_OK; };\r\n\tSTDMETHOD(OnGroupingParamChanged) (LPCGUID /*NewGroupingParam*/, LPCGUID /*EventContext*/) { return S_OK; };\r\n\tSTDMETHOD(OnStateChanged) (AudioSessionState NewState);\r\n\tSTDMETHOD(OnSessionDisconnected) (AudioSessionDisconnectReason DisconnectReason);\r\n};\r\n"
  },
  {
    "path": "Common/BasicMacros.hpp",
    "content": "#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 the argument without expanding macro definitions.\r\n#define STRINGIFY_NX(x) #x\r\n\r\n// Stringify the argument.\r\n#define STRINGIFY(x) STRINGIFY_NX(x)\r\n\r\n// Concatenate two tokens without expanding macro definitions.\r\n#define TOKEN_CONCAT_NX(x, y) x ## y\r\n\r\n// Concatenate two tokens.\r\n#define TOKEN_CONCAT(x, y) TOKEN_CONCAT_NX(x, y)\r\n"
  },
  {
    "path": "Common/Defer.hpp",
    "content": "#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) { };\r\n\tDeferrer(const Deferrer&) = delete;\r\n\t~Deferrer() { f(); }\r\n};\r\n\r\n// Defer execution of the following lambda to the end of current scope.\r\n#define defer Deferrer TOKEN_CONCAT(__deferred, __COUNTER__) =\r\n"
  },
  {
    "path": "Common/NtBase.hpp",
    "content": "#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 WIN32_LEAN_AND_MEAN\r\n#define NOMINMAX\r\n#define NOCOMM\r\n#define NOKANJI\r\n#define NOSOUND\r\n#include <windows.h>\r\n#include <objbase.h>\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\n// The best way to get HMODULE of current module, supported since VS2002.\r\n\r\nEXTERN_C IMAGE_DOS_HEADER __ImageBase;\r\n#define THIS_MODULE ((HMODULE)&__ImageBase)\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\nEXTERN_C_START\r\n\r\ntypedef _Success_(return >= 0) LONG NTSTATUS;\r\ntypedef NTSTATUS* PNTSTATUS;\r\n\r\n#ifndef NT_SUCCESS\r\n#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)\r\n#endif\r\n\r\ntypedef struct _UNICODE_STRING\r\n{\r\n\tUSHORT Length;\r\n\tUSHORT MaximumLength;\r\n\t_Field_size_bytes_part_(MaximumLength, Length) PWCH Buffer;\r\n} UNICODE_STRING, * PUNICODE_STRING;\r\n\r\ntypedef const UNICODE_STRING* PCUNICODE_STRING;\r\n\r\ntypedef struct _OBJECT_ATTRIBUTES\r\n{\r\n\tULONG Length;\r\n\tHANDLE RootDirectory;\r\n\tPUNICODE_STRING ObjectName;\r\n\tULONG Attributes;\r\n\tPVOID SecurityDescriptor; // PSECURITY_DESCRIPTOR;\r\n\tPVOID SecurityQualityOfService; // PSECURITY_QUALITY_OF_SERVICE\r\n} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;\r\n\r\ntypedef const OBJECT_ATTRIBUTES* PCOBJECT_ATTRIBUTES;\r\n\r\nEXTERN_C_END\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n"
  },
  {
    "path": "Common/NtCriticalSection.hpp",
    "content": "#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 CriticalSection\r\n{\r\n\tCriticalSection(const CriticalSection&) = delete;\r\n\tCriticalSection& operator=(const CriticalSection&) = delete;\r\n\r\nprotected:\r\n\r\n\tCRITICAL_SECTION m_cs;\r\n\r\npublic:\r\n\r\n\tCriticalSection()\r\n\t{\r\n\t\t(void) InitializeCriticalSection(&m_cs);\r\n\t}\r\n\r\n\tCriticalSection(DWORD spin_count)\r\n\t{\r\n\t\t(void) InitializeCriticalSectionAndSpinCount(&m_cs, spin_count);\r\n\t}\r\n\r\n\t~CriticalSection()\r\n\t{\r\n\t\tDeleteCriticalSection(&m_cs);\r\n\t}\r\n\r\n\tvoid Lock()\r\n\t{\r\n\t\tEnterCriticalSection(&m_cs);\r\n\t}\r\n\r\n\tbool TryLock()\r\n\t{\r\n\t\treturn TryEnterCriticalSection(&m_cs) ? true : false;\r\n\t}\r\n\r\n\tbool TryLock(DWORD timeout)\r\n\t{\r\n\t\tULONGLONG start = GetTickCount64();\r\n\t\twhile (true)\r\n\t\t{\r\n\t\t\tif (TryLock())\r\n\t\t\t{\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t\tif ((GetTickCount64() - start) >= timeout)\r\n\t\t\t{\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tYieldProcessor();\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n\tvoid Unlock()\r\n\t{\r\n\t\tLeaveCriticalSection(&m_cs);\r\n\t}\r\n};\r\n\r\ntemplate<typename T>\r\nclass ScopedLock\r\n{\r\n\tScopedLock() = delete;\r\n\tScopedLock(const ScopedLock&) = delete;\r\n\tScopedLock& operator=(const ScopedLock&) = delete;\r\n\r\nprotected:\r\n\r\n\tT& m_lock;\r\n\r\npublic:\r\n\r\n\tScopedLock(T& lock) : m_lock(lock)\r\n\t{\r\n\t\tm_lock.Lock();\r\n\t}\r\n\r\n\t~ScopedLock()\r\n\t{\r\n\t\tm_lock.Unlock();\r\n\t}\r\n};\r\n"
  },
  {
    "path": "Common/NtEvent.hpp",
    "content": "#pragma once\r\n\r\n#include \"NtBase.hpp\"\r\n#include \"NtHandle.hpp\"\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\nEXTERN_C_START\r\n\r\ntypedef enum _EVENT_TYPE\r\n{\r\n\tNotificationEvent,\r\n\tSynchronizationEvent\r\n} EVENT_TYPE;\r\n\r\n#ifndef EVENT_QUERY_STATE\r\n#define EVENT_QUERY_STATE 0x0001\r\n#endif\r\n\r\nNTSYSCALLAPI\r\nNTSTATUS\r\nNTAPI\r\nNtCreateEvent(\r\n\t_Out_ PHANDLE EventHandle,\r\n\t_In_ ACCESS_MASK DesiredAccess,\r\n\t_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,\r\n\t_In_ EVENT_TYPE EventType,\r\n\t_In_ BOOLEAN InitialState\r\n);\r\n\r\nNTSYSCALLAPI\r\nNTSTATUS\r\nNTAPI\r\nNtSetEvent(\r\n\t_In_ HANDLE EventHandle,\r\n\t_Out_opt_ PLONG PreviousState\r\n);\r\n\r\nNTSYSCALLAPI\r\nNTSTATUS\r\nNTAPI\r\nNtResetEvent(\r\n\t_In_ HANDLE EventHandle,\r\n\t_Out_opt_ PLONG PreviousState\r\n);\r\n\r\nNTSYSCALLAPI\r\nNTSTATUS\r\nNTAPI\r\nNtPulseEvent(\r\n\t_In_ HANDLE EventHandle,\r\n\t_Out_opt_ PLONG PreviousState\r\n);\r\n\r\ntypedef enum _EVENT_INFORMATION_CLASS\r\n{\r\n\tEventBasicInformation\r\n} EVENT_INFORMATION_CLASS;\r\n\r\ntypedef struct _EVENT_BASIC_INFORMATION\r\n{\r\n\tEVENT_TYPE EventType;\r\n\tLONG EventState;\r\n} EVENT_BASIC_INFORMATION, *PEVENT_BASIC_INFORMATION;\r\n\r\nNTSYSCALLAPI\r\nNTSTATUS\r\nNTAPI\r\nNtQueryEvent(\r\n\t_In_ HANDLE EventHandle,\r\n\t_In_ EVENT_INFORMATION_CLASS EventInformationClass,\r\n\t_Out_writes_bytes_(EventInformationLength) PVOID EventInformation,\r\n\t_In_ ULONG EventInformationLength,\r\n\t_Out_opt_ PULONG ReturnLength\r\n);\r\n\r\nEXTERN_C_END\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\nclass SyncEvent : public Handle\r\n{\r\npublic:\r\n\r\n\tenum class ResetMode { Manual, Auto };\r\n\r\n\texplicit SyncEvent(const ResetMode mode, const bool state) : Handle(NULL)\r\n\t{\r\n\t\tNtCreateEvent(\r\n\t\t\t&m_handle,\r\n\t\t\tEVENT_ALL_ACCESS,\r\n\t\t\tnullptr,\r\n\t\t\t(mode == ResetMode::Manual ? NotificationEvent : SynchronizationEvent),\r\n\t\t\tstate\r\n\t\t);\r\n\t}\r\n\r\n\tbool Get() const\r\n\t{\r\n\t\tEVENT_BASIC_INFORMATION info {};\r\n\t\tNtQueryEvent(m_handle, EventBasicInformation, &info, sizeof(info), nullptr);\r\n\t\treturn info.EventState;\r\n\t}\r\n\r\n\tvoid Set(const bool state)\r\n\t{\r\n\t\tif (state)\r\n\t\t{\r\n\t\t\tNtSetEvent(m_handle, nullptr);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tNtResetEvent(m_handle, nullptr);\r\n\t\t}\r\n\t}\r\n\r\n\tbool GetSet(const bool state)\r\n\t{\r\n\t\tLONG prev = 0;\r\n\t\tif (state)\r\n\t\t{\r\n\t\t\tNtSetEvent(m_handle, &prev);\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tNtResetEvent(m_handle, &prev);\r\n\t\t}\r\n\t\treturn prev;\r\n\t}\r\n\r\n\tvoid Pulse()\r\n\t{\r\n\t\tNtPulseEvent(m_handle, nullptr);\r\n\t}\r\n\r\n\tbool GetPulse()\r\n\t{\r\n\t\tLONG prev = 0;\r\n\t\tNtPulseEvent(m_handle, &prev);\r\n\t\treturn prev;\r\n\t}\r\n};\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\nclass AutoResetEvent : public SyncEvent\r\n{\r\npublic:\r\n\r\n\tAutoResetEvent(const bool state) : SyncEvent(ResetMode::Auto, state) {}\r\n\r\n\tAutoResetEvent& operator=(const bool state)\r\n\t{\r\n\t\tthis->Set(state);\r\n\t\treturn *this;\r\n\t}\r\n\r\n\toperator bool() const\r\n\t{\r\n\t\treturn this->Get();\r\n\t}\r\n};\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\nclass ManualResetEvent : public SyncEvent\r\n{\r\npublic:\r\n\r\n\tManualResetEvent(const bool state) : SyncEvent(ResetMode::Manual, state) {}\r\n\r\n\tManualResetEvent& operator=(const bool state)\r\n\t{\r\n\t\tthis->Set(state);\r\n\t\treturn *this;\r\n\t}\r\n\r\n\toperator bool() const\r\n\t{\r\n\t\treturn this->Get();\r\n\t}\r\n};\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n"
  },
  {
    "path": "Common/NtHandle.hpp",
    "content": "#pragma once\r\n\r\n#include \"NtBase.hpp\"\r\n#include <initializer_list>\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\nclass Handle\r\n{\r\n\tHandle(const Handle&) = delete;\r\n\tHandle& operator= (const Handle&) = delete;\r\n\r\nprotected:\r\n\r\n\tHANDLE m_handle;\r\n\r\npublic:\r\n\r\n\tHandle(HANDLE handle) : m_handle(handle) {}\r\n\tHANDLE GetHandle() const { return m_handle; }\r\n\toperator HANDLE() const { return m_handle; }\r\n\t~Handle() { if (m_handle) { CloseHandle(m_handle); } }\r\n};\r\n\r\ninline DWORD AlertableSleep(DWORD timeout)\r\n{\r\n\treturn SleepEx(timeout, true);\r\n}\r\n\r\ninline DWORD WaitForOne(HANDLE handle, DWORD timeout = INFINITE)\r\n{\r\n\treturn WaitForSingleObject(handle, timeout);\r\n}\r\n\r\ninline DWORD AlertableWaitForOne(HANDLE handle, DWORD timeout = INFINITE)\r\n{\r\n\treturn WaitForSingleObjectEx(handle, timeout, true);\r\n}\r\n\r\ninline DWORD WaitForAny(std::initializer_list<HANDLE> handles, DWORD timeout = INFINITE)\r\n{\r\n\treturn WaitForMultipleObjects((DWORD)handles.size(), handles.begin(), FALSE, timeout);\r\n}\r\n\r\ninline DWORD AlertableWaitForAny(std::initializer_list<HANDLE> handles, DWORD timeout = INFINITE)\r\n{\r\n\treturn WaitForMultipleObjectsEx((DWORD)handles.size(), handles.begin(), FALSE, timeout, true);\r\n}\r\n\r\ninline DWORD WaitForAll(std::initializer_list<HANDLE> handles, DWORD timeout = INFINITE)\r\n{\r\n\treturn WaitForMultipleObjects((DWORD)handles.size(), handles.begin(), TRUE, timeout);\r\n}\r\n\r\ninline DWORD AlertableWaitForAll(std::initializer_list<HANDLE> handles, DWORD timeout = INFINITE)\r\n{\r\n\treturn WaitForMultipleObjectsEx((DWORD)handles.size(), handles.begin(), TRUE, timeout, true);\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n"
  },
  {
    "path": "Common/NtUtils.hpp",
    "content": "#pragma once\r\n\r\n#include \"NtBase.hpp\"\r\n#include \"StrUtils.hpp\"\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\ntemplate <class T> void SafeRelease(T*& com_obj_ptr)\r\n{\r\n\tif (com_obj_ptr)\r\n\t{\r\n\t\tcom_obj_ptr->Release();\r\n\t\tcom_obj_ptr = nullptr;\r\n\t}\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\n// Gets NT version and build number. \r\n// The upper 4 bits of build number are reserved for the type of the OS build.\r\n// 0xC for a \"checked\" (or debug) build, and 0xF for a \"free\" (or retail) build.\r\n\r\nextern \"C\" NTSYSAPI VOID WINAPI RtlGetNtVersionNumbers(\r\n\t__out_opt DWORD* pNtMajorVersion,\r\n\t__out_opt DWORD* pNtMinorVersion,\r\n\t__out_opt DWORD* pNtBuildNumber\r\n);\r\n\r\ninline uint32_t GetNtBuildNumber()\r\n{\r\n\tstatic uint32_t build_number = 0;\r\n\r\n\tif (build_number == 0)\r\n\t{\r\n\t\tRtlGetNtVersionNumbers(NULL, NULL, (DWORD*)&build_number);\r\n\t\tbuild_number &= ~0xF0000000; // Clear type of OS build bits.\r\n\t}\r\n\r\n\treturn build_number;\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\ninline HMODULE GetKernelBaseDll()\r\n{\r\n\tstatic HMODULE dll = 0;\r\n\tif (!dll) { dll = GetModuleHandleA(\"kernelbase.dll\"); }\r\n\treturn dll;\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\ninline HRESULT WINAPI SetThreadDescriptionW(_In_ HANDLE hThread, _In_ PCWSTR lpThreadDescription)\r\n{\r\n\tstatic void* pfn = nullptr;\r\n\r\n\tif (!pfn)\r\n\t{\r\n\t\tif (HMODULE dll = GetKernelBaseDll())\r\n\t\t{\r\n\t\t\tpfn = GetProcAddress(dll, \"SetThreadDescription\");\r\n\t\t}\r\n\t}\r\n\r\n\tif (!pfn) { return E_NOTIMPL; }\r\n\r\n\treturn static_cast<decltype(SetThreadDescription)*>(pfn)(hThread, lpThreadDescription);\r\n}\r\n\r\ninline HRESULT SetCurrentThreadDescriptionW(PCWSTR desc)\r\n{\r\n\treturn SetThreadDescriptionW(GetCurrentThread(), desc);\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\ninline HRESULT WINAPI GetThreadDescriptionW(_In_ HANDLE hThread, _Outptr_result_z_ PWSTR* ppszThreadDescription)\r\n{\r\n\tstatic void* pfn = nullptr;\r\n\r\n\tif (!pfn)\r\n\t{\r\n\t\tif (HMODULE dll = GetKernelBaseDll())\r\n\t\t{\r\n\t\t\tpfn = GetProcAddress(dll, \"GetThreadDescription\");\r\n\t\t}\r\n\t}\r\n\r\n\tif (!pfn) { return E_NOTIMPL; }\r\n\r\n\treturn static_cast<decltype(GetThreadDescription)*>(pfn)(hThread, ppszThreadDescription);\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\nstruct RsrcSpan\r\n{\r\n\tconst uint8_t* data;\r\n\tsize_t size;\r\n\toperator bool() const { return data != nullptr; }\r\n};\r\n\r\ninline RsrcSpan GetResource(HMODULE hModule, LPCTSTR lpName, LPCTSTR lpType)\r\n{\r\n\tHRSRC hrsrc = FindResource(hModule, lpName, lpType);\r\n\tif (!hrsrc) { return { nullptr, 0 }; }\r\n\tHGLOBAL hglobal = LoadResource(hModule, hrsrc);\r\n\tif (!hglobal) { return { nullptr, 0 }; }\r\n\treturn { (uint8_t*)LockResource(hglobal), SizeofResource(hModule, hrsrc) };\r\n}\r\n\r\ninline const VS_FIXEDFILEINFO* GetFixedVersion(HMODULE hModule = NULL)\r\n{\r\n\tauto rsrc = GetResource(hModule, MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);\r\n\tif (!rsrc) { return nullptr; }\r\n\r\n\t// Parse the resource as the VS_VERSION_INFO pseudo structure to get the VS_FIXEDFILEINFO.\r\n\r\n\tif (rsrc.size < (40 + sizeof(VS_FIXEDFILEINFO))) { return nullptr; }\r\n\tif (*(uint16_t*)(rsrc.data + 0) < (40 + sizeof(VS_FIXEDFILEINFO))) { return nullptr; } // VS_VERSIONINFO::wLength\r\n\tif (*(uint16_t*)(rsrc.data + 2) != sizeof(VS_FIXEDFILEINFO)) { return nullptr; }       // VS_VERSIONINFO::wValueLength\r\n\tif (*(uint16_t*)(rsrc.data + 4) != 0) { return nullptr; }                              // VS_VERSIONINFO::wType (0 is binary, 1 is text)\r\n\tif (!StringEquals((wchar_t*)(rsrc.data + 6), L\"VS_VERSION_INFO\")) { return nullptr; }  // VS_VERSIONINFO::szKey\r\n\tconst VS_FIXEDFILEINFO* ffi = (const VS_FIXEDFILEINFO*)(rsrc.data + 40);               // VS_VERSIONINFO::Value\r\n\tif (ffi->dwSignature != VS_FFI_SIGNATURE || ffi->dwStrucVersion != VS_FFI_STRUCVERSION) { return nullptr; }\r\n\r\n\treturn ffi;\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n"
  },
  {
    "path": "Common/StrUtils.hpp",
    "content": "#pragma once\r\n\r\n#include <stdint.h>\r\n#include <stddef.h>\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\nusing TransformByteCharFuncType = char (*)(char);\r\nusing TransformWideCharFuncType = wchar_t (*)(wchar_t);\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\nconstexpr char NoTransform(char c)\r\n{\r\n\treturn c;\r\n}\r\n\r\nconstexpr char AsciiToLower(char c)\r\n{\r\n\treturn ('A' <= c && c <= 'Z') ? (c | 0b00100000) : c;\r\n}\r\n\r\nconstexpr char AsciiToUpper(char c)\r\n{\r\n\treturn ('a' <= c && c <= 'z') ? (c & 0b11011111) : c;\r\n}\r\n\r\nconstexpr wchar_t NoTransform(wchar_t c)\r\n{\r\n\treturn c;\r\n}\r\n\r\nconstexpr wchar_t AsciiToLower(wchar_t c)\r\n{\r\n\treturn (L'A' <= c && c <= L'Z') ? (c | 0b0000000000100000) : c;\r\n}\r\n\r\nconstexpr wchar_t AsciiToUpper(wchar_t c)\r\n{\r\n\treturn (L'a' <= c && c <= L'z') ? (c & 0b1111111111011111) : c;\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\ntemplate <TransformByteCharFuncType TransformChar = NoTransform>\r\nconstexpr int StringCompare(const char* l, const char* r)\r\n{\r\n\tfor (; *l && TransformChar(*l) == TransformChar(*r); l++, r++);\r\n\treturn (int)(uint8_t)TransformChar(*l) - (int)(uint8_t)TransformChar(*r);\r\n}\r\n\r\ntemplate <TransformWideCharFuncType TransformChar = NoTransform>\r\nconstexpr int StringCompare(const wchar_t* l, const wchar_t* r)\r\n{\r\n\tfor (; *l && TransformChar(*l) == TransformChar(*r); l++, r++);\r\n\treturn (int)TransformChar(*l) - (int)TransformChar(*r);\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\ntemplate <TransformByteCharFuncType TransformChar = NoTransform>\r\nconstexpr bool StringEquals(const char* l, const char* r)\r\n{\r\n\treturn StringCompare<TransformChar>(l, r) == 0;\r\n}\r\n\r\ntemplate <TransformWideCharFuncType TransformChar = NoTransform>\r\nconstexpr bool StringEquals(const wchar_t* l, const wchar_t* r)\r\n{\r\n\treturn StringCompare<TransformChar>(l, r) == 0;\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\ntemplate <TransformByteCharFuncType TransformChar = NoTransform>\r\nconstexpr const char* StringFindPtr(const char* str, const char* sub)\r\n{\r\n\twhile (*str)\r\n\t{\r\n\t\tconst char *l = str; const char *r = sub;\r\n\t\twhile (*l && TransformChar(*l) == TransformChar(*r)) { l++; r++; }\r\n\t\tif (!*r) { return str; }\r\n\t\tstr++;\r\n\t}\r\n\r\n\treturn nullptr;\r\n}\r\n\r\ntemplate <TransformWideCharFuncType TransformChar = NoTransform>\r\nconstexpr const wchar_t* StringFindPtr(const wchar_t* str, const wchar_t* sub)\r\n{\r\n\twhile (*str)\r\n\t{\r\n\t\tconst wchar_t* l = str; const wchar_t* r = sub;\r\n\t\twhile (*l && TransformChar(*l) == TransformChar(*r)) { l++; r++; }\r\n\t\tif (!*r) { return str; }\r\n\t\tstr++;\r\n\t}\r\n\r\n\treturn nullptr;\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\ntemplate <TransformByteCharFuncType TransformChar = NoTransform>\r\nconstexpr size_t StringIndexOf(const char* str, const char* sub)\r\n{\r\n\tauto ptr = StringFindPtr<TransformChar>(str, sub);\r\n\treturn ptr ? (ptr - str) : -1;\r\n}\r\n\r\ntemplate <TransformWideCharFuncType TransformChar = NoTransform>\r\nconstexpr size_t StringIndexOf(const wchar_t* str, const wchar_t* sub)\r\n{\r\n\tauto ptr = StringFindPtr<TransformChar>(str, sub);\r\n\treturn ptr ? (ptr - str) : -1;\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n\r\ntemplate <TransformByteCharFuncType TransformChar = NoTransform>\r\nconstexpr bool StringContains(const char* str, const char* sub)\r\n{\r\n\treturn StringFindPtr<TransformChar>(str, sub) != nullptr;\r\n}\r\n\r\ntemplate <TransformWideCharFuncType TransformChar = NoTransform>\r\nconstexpr bool StringContains(const wchar_t* str, const wchar_t* sub)\r\n{\r\n\treturn StringFindPtr<TransformChar>(str, sub) != nullptr;\r\n}\r\n\r\n// ---------------------------------------------------------------------------------------------------------------------\r\n"
  },
  {
    "path": "Common.hpp",
    "content": "#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#include \"Common/Defer.hpp\"\r\n#include \"Common/NtBase.hpp\"\r\n#include \"Common/NtHandle.hpp\"\r\n#include \"Common/NtEvent.hpp\"\r\n#include \"Common/NtCriticalSection.hpp\"\r\n#include \"Common/NtUtils.hpp\"\r\n#include \"Common/StrUtils.hpp\"\r\n#include <algorithm> // std::min and std::max.\r\n#include <math.h>\r\n\r\n#ifdef _CONSOLE\r\n\r\ninline void DebugLogImpl(const char * funcname, const char * type, const char * format, ...)\r\n{\r\n\tstatic CriticalSection mutex;\r\n\tScopedLock lock(mutex);\r\n\r\n\tstatic bool is_inited = false;\r\n\tstatic bool show_trace = false;\r\n\r\n\tif (!is_inited)\r\n\t{\r\n\t\t// It also looks in path to exe, but it's fine for a debug build.\r\n\t\tchar buf[MAX_PATH];\r\n\t\tstrcpy_s(buf, GetCommandLineA());\r\n\t\t_strlwr(buf);\r\n\t\tif (StringContains<AsciiToLower>(buf, \"trace\")) { show_trace = true; }\r\n\t\tis_inited = true;\r\n\t}\r\n\r\n\tif (!show_trace && type && StringEquals<AsciiToLower>(type, \"TRACE\")) { return; }\r\n\r\n\tstatic uint64_t prev_date = 0;\r\n\tSYSTEMTIME now = {0};\r\n\tGetSystemTime(&now);\r\n\r\n\t// Output current date once. First 8 bytes are current date, so we can compare it as a 64-bit integer.\r\n\tif (prev_date != *((uint64_t*)&now))\r\n\t{\r\n\t\tprev_date = *((uint64_t*)&now);\r\n\t\tprintf(\"%04d/%02d/%02d \", now.wYear, now.wMonth, now.wDay);\r\n\t}\r\n\r\n\tprintf(\"%02d:%02d:%02d.%03d \", now.wHour, now.wMinute, now.wSecond, now.wMilliseconds);\r\n\tprintf(\"[%5d] \", GetThreadId(GetCurrentThread()));\r\n\r\n\tif (show_trace && funcname)\r\n\t{\r\n\t\tprintf(\"[%s] \", funcname);\r\n\t}\r\n\r\n\tif (type && !StringEquals<AsciiToLower>(type, \"TRACE\") && !StringEquals<AsciiToLower>(type, \"INFO\"))\r\n\t{\r\n\t\tprintf(\"[%s] \", type);\r\n\t}\r\n\r\n\tva_list argptr;\r\n\tva_start(argptr, format);\r\n\tvprintf(format, argptr);\r\n\r\n\tprintf(\"\\n\");\r\n\tfflush(stdout);\r\n}\r\n\r\n#define DebugLog(...) DebugLogImpl(__FUNCTION__, \"INFO\", __VA_ARGS__)\r\n#define DebugLogWarning(...) DebugLogImpl(__FUNCTION__, \"WARNING\", __VA_ARGS__)\r\n#define DebugLogError(...) DebugLogImpl(__FUNCTION__, \"ERROR\", __VA_ARGS__)\r\n#define TraceLog(...) DebugLogImpl(__FUNCTION__, \"TRACE\", __VA_ARGS__)\r\n#define DebugThreadName(...) SetCurrentThreadDescriptionW(L ## __VA_ARGS__)\r\n\r\n#else\r\n\r\n#define DebugLog(...)\r\n#define DebugLogWarning(...)\r\n#define DebugLogError(...)\r\n#define TraceLog(...)\r\n#define DebugThreadName(...)\r\n\r\n#endif\r\n"
  },
  {
    "path": "License.md",
    "content": "# MIT License\r\n\r\nCopyright (c) 2014-2024 Evgeny Vrublevsky\r\n\r\nPermission 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:\r\n\r\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\r\n\r\nTHE 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.\r\n"
  },
  {
    "path": "ReadMe.txt",
    "content": "Sound Keeper v1.3.5 [2025/07/05]\r\nhttps://veg.by/projects/soundkeeper/\r\n\r\nPrevents SPDIF/HDMI digital audio playback devices from sleeping. Uses WASAPI, requires Windows 7+.\r\n\r\nYou need just one exe file (others can be removed):\r\n- SoundKeeper32.exe is for x86-32 Windows.\r\n- SoundKeeper64.exe is for x86-64 Windows.\r\n- SoundKeeperARM64.exe is for ARM64 Windows.\r\n\r\nThe program doesn't have a GUI. It starts to do its job right after the process is started.\r\nTo close the program, just kill the SoundKeeper.exe process.\r\nTo autorun, copy SoundKeeper.exe into the startup directory (to open it, press Win+R, enter \"shell:startup\").\r\n\r\nDefault behavior can be changed by adding settings to the Sound Keeper executable file name or by passing them\r\nas command line arguments. Setting names are case insensitive.\r\n\r\nSupported device type settings:\r\n- \"Primary\" keeps on primary audio output only. Used by default.\r\n- \"All\" keeps on all enabled audio outputs.\r\n- \"Digital\" keeps on all enabled SPDIF and HDMI audio outputs (like it was in Sound Keeper v1.0).\r\n- \"Analog\" keeps on all enabled audio outputs except SPDIF and HDMI.\r\n\r\nSupported stream type settings:\r\n- \"OpenOnly\" opens audio output, but doesn't play anything. Sometimes it helps.\r\n- \"Zero\" plays stream of zeroes. It may be not enough for some hardware.\r\n- \"Fluctuate\" plays stream of zeroes with the smallest non-zero samples once in a second. Used by default.\r\n- \"Sine\" plays 1Hz sine wave at 1% volume. The frequency and amplitude can be changed. Useful for analog outputs.\r\n- \"White\", \"Brown\", or \"Pink\" play named noise, with the same parameters as the sine (except frequency).\r\n\r\nSine and noise stream parameters:\r\n- F is frequency. Default: 1Hz for Sine and 50Hz for Fluctuate. Applicable for: Fluctuate, Sine.\r\n- A is amplitude. Default: 1%. If you want to use inaudible noise, set it to 0.1%. Applicable for: Sine, Noise.\r\n- L is length of sound (in seconds). Default: infinite.\r\n- W is waiting time between sounds if L is set. Use to enable periodic sound.\r\n- T is transition or fading time. Default: 0.1 second. Applicable for: Sine, Noise.\r\n\r\nExamples:\r\n- SoundKeeperZeroAll.exe generates zero amplitude stream on all enabled audio outputs.\r\n- SoundKeeperAll.exe generates default inaudible stream on all enabled audio outputs.\r\n- SoundKeeperSineF10A5.exe generates 10Hz sine wave with 5% amplitude on primary audio output. It is inaudible.\r\n- SoundKeeperSineF1000A15.exe generates 1000Hz sine wave with 15% amplitude. It is audible! Use it for testing.\r\n- \"SoundKeeper.exe sine -f 1000 -a 15\" is a command line version of the previous example.\r\n- \"SoundKeeper.exe brown -a 0.1\" (settings are command line arguments) generates brown noise with 0.1% amplitude.\r\n\r\nWhat's new\r\n\r\nv1.3.5 [2025/07/05]:\r\n- Handle ASIO exclusive mode by waiting until it ends, similar to WASAPI exclusive mode.\r\n\r\nv1.3.4 [2024/09/15]:\r\n- Tune the Windows 8-10 WASAPI memory leak workaround to make it effective for longer time.\r\n- Native ARM64 version (with statically linked runtime hence the bigger binary).\r\n\r\nv1.3.3 [2023/08/19]:\r\n- Fixed arguments parsing bug: \"All\" or \"Analog\" after specifying stream type led to amplitude set to 0.\r\n\r\nv1.3.2 [2023/08/18]:\r\n- \"Fluctuate\" treats 32-bit output format as 24-bit since WASAPI reports 24-bit as 32-bit for some reason.\r\n- \"Fluctuate\" generates 50 fluctuations per second by default. It helps in many more cases.\r\n- Sound Keeper doesn't exit when it is muted.\r\n\r\nv1.3.1 [2023/01/30]:\r\n- A potential deadlock when audio devices are added or removed has been fixed.\r\n- \"Fluctuate\" treats non-PCM output formats (like Dolby Atmos) as 24-bit instead of 16-bit.\r\n- Frequency parameter is limited by half of current sample rate to avoid generation of unexpected noise.\r\n- More detailed logs in debug builds. Debug output is flushed immediately, so it can be redirected to a file.\r\n\r\nv1.3.0 [2022/07/28]:\r\n- \"Fluctuate\" is 1 fluctuation per second by default. Frequency can be changed using the F parameter.\r\n- Periodic playing of a sine sound with optional fading.\r\n- \"White\", \"Brown\", and \"Pink\" noise signal types.\r\n- Self kill command is added. Run \"soundkeeper kill\" to stop running Sound Keeper instance.\r\n- \"Analog\" switch was added. It works as the opposite of \"Digital\".\r\n- Ignores remote desktop audio device (this feature can be disabled using the \"Remote\" switch).\r\n- New \"OpenOnly\" mode that just opens audio output, but doesn't stream anything.\r\n- New \"NoSleep\" switch which disables PC sleep detection (Windows 7-10).\r\n- The program is not confused anymore when PC auto sleep is disabled on Windows 10.\r\n\r\nv1.2.2 [2022/05/15]:\r\n- Work as a dummy when no suitable devices found.\r\n- Sound Keeper shouldn't prevent PC from automatic going into sleep mode on Windows 10.\r\n\r\nv1.2.1 [2021/11/05]:\r\n- Sound Keeper works on Windows 11.\r\n- The workaround that allowed PC to sleep had to be disabled on Windows 11.\r\n\r\nv1.2.0 [2021/10/30]:\r\n- Sound Keeper doesn't prevent PC from automatic going into sleep mode on Windows 7.\r\n- New \"Sine\" stream type which can be useful for analog outputs or too smart digital outputs.\r\n- When a user starts a new Sound Keeper instance, the previous one is stopped automatically.\r\n- \"Fluctuate\" stream type considers sample format of the output (16/24/32-bit integer, and 32-bit float).\r\n- Command line arguments are supported. Example: \"soundkeeper sine -f 1000 -a 10\".\r\n- The workaround for the Audio Service memory leak is enabled on affected Windows versions only (8, 8.1, and 10).\r\n\r\nv1.1.0 [2020/07/18]:\r\n- Default behavior can be changed by adding options to the Sound Keeper executable file name.\r\n- Primary audio output is used by default.\r\n- Inaudible stream is used by default.\r\n- Workaround for a Windows 10 bug which causes a memory leak in the Audio Service when audio output is busy.\r\n\r\nv1.0.4 [2020/03/14]: Fixed a potential memory leak when another program uses audio output in exclusive mode.\r\nv1.0.3 [2019/07/14]: Exclusive mode doesn't prevent Sound Keeper from working.\r\nv1.0.2 [2017/12/23]: 64-bit version is added.\r\nv1.0.1 [2017/12/21]: Waking PC up after sleeping doesn't prevent Sound Keeper from working.\r\nv1.0.0 [2014/12/24]: Initial release.\r\n\r\n(c) 2014-2025 Evgeny Vrublevsky <me@veg.by>\r\n"
  },
  {
    "path": "Resources.hpp",
    "content": "//{{NO_DEPENDENCIES}}\r\n// Microsoft Visual C++ generated include file.\r\n// Used by SoundKeeper.rc\r\n//\r\n#define IDI_MAIN                        100\r\n\r\n// Next default values for new objects\r\n// \r\n#ifdef APSTUDIO_INVOKED\r\n#ifndef APSTUDIO_READONLY_SYMBOLS\r\n#define _APS_NO_MFC                     1\r\n#define _APS_NEXT_RESOURCE_VALUE        101\r\n#define _APS_NEXT_COMMAND_VALUE         40001\r\n#define _APS_NEXT_CONTROL_VALUE         1001\r\n#define _APS_NEXT_SYMED_VALUE           101\r\n#endif\r\n#endif\r\n"
  },
  {
    "path": "Resources.rc",
    "content": "// 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 APSTUDIO_READONLY_SYMBOLS\r\n/////////////////////////////////////////////////////////////////////////////\r\n//\r\n// Generated from the TEXTINCLUDE 2 resource.\r\n//\r\n#include <winres.h>\r\n\r\n/////////////////////////////////////////////////////////////////////////////\r\n#undef APSTUDIO_READONLY_SYMBOLS\r\n\r\n/////////////////////////////////////////////////////////////////////////////\r\n// English resources\r\n\r\n#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\nLANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL\r\n\r\n#ifdef APSTUDIO_INVOKED\r\n/////////////////////////////////////////////////////////////////////////////\r\n//\r\n// TEXTINCLUDE\r\n//\r\n\r\n1 TEXTINCLUDE \r\nBEGIN\r\n    \"Resources.hpp\\0\"\r\nEND\r\n\r\n2 TEXTINCLUDE \r\nBEGIN\r\n    \"#include <winres.h>\\r\\n\"\r\n    \"\\0\"\r\nEND\r\n\r\n3 TEXTINCLUDE \r\nBEGIN\r\n    \"#include \"\"BuildInfo.rc2\"\"\\r\\n\"\r\n    \"\\0\"\r\nEND\r\n\r\n#endif    // APSTUDIO_INVOKED\r\n\r\n\r\n/////////////////////////////////////////////////////////////////////////////\r\n//\r\n// Icon\r\n//\r\n\r\n// Icon with lowest ID value placed first to ensure application icon\r\n// remains consistent on all systems.\r\nIDI_MAIN                ICON                    \"Main.ico\"\r\n\r\n#endif    // English resources\r\n/////////////////////////////////////////////////////////////////////////////\r\n\r\n\r\n\r\n#ifndef APSTUDIO_INVOKED\r\n/////////////////////////////////////////////////////////////////////////////\r\n//\r\n// Generated from the TEXTINCLUDE 3 resource.\r\n//\r\n#include \"BuildInfo.rc2\"\r\n\r\n/////////////////////////////////////////////////////////////////////////////\r\n#endif    // not APSTUDIO_INVOKED\r\n\r\n"
  },
  {
    "path": "RuntimeHacks.cpp",
    "content": "// We use msvcrt.dll as a C runtime to save space in release builds.\r\n// It has limited set of functions, so we reimplement missing ones here.\r\n\r\n#if defined(_VC_NODEFAULTLIB)\r\n\r\n// ...\r\n\r\n#endif\r\n"
  },
  {
    "path": "SoundKeeper.sln",
    "content": "﻿\r\nMicrosoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio Version 17\r\nVisualStudioVersion = 17.9.34714.143\r\nMinimumVisualStudioVersion = 10.0.40219.1\r\nProject(\"{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}\") = \"SoundKeeper\", \"SoundKeeper.vcxproj\", \"{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}\"\r\nEndProject\r\nGlobal\r\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n\t\tDebug|ARM64 = Debug|ARM64\r\n\t\tDebug|Win32 = Debug|Win32\r\n\t\tDebug|Win64 = Debug|Win64\r\n\t\tDevelop|ARM64 = Develop|ARM64\r\n\t\tDevelop|Win32 = Develop|Win32\r\n\t\tDevelop|Win64 = Develop|Win64\r\n\t\tRelease|ARM64 = Release|ARM64\r\n\t\tRelease|Win32 = Release|Win32\r\n\t\tRelease|Win64 = Release|Win64\r\n\tEndGlobalSection\r\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Debug|ARM64.ActiveCfg = Debug|ARM64\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Debug|ARM64.Build.0 = Debug|ARM64\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Debug|Win32.ActiveCfg = Debug|Win32\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Debug|Win32.Build.0 = Debug|Win32\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Debug|Win64.ActiveCfg = Debug|x64\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Debug|Win64.Build.0 = Debug|x64\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Develop|ARM64.ActiveCfg = Develop|ARM64\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Develop|ARM64.Build.0 = Develop|ARM64\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Develop|Win32.ActiveCfg = Develop|Win32\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Develop|Win32.Build.0 = Develop|Win32\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Develop|Win64.ActiveCfg = Develop|x64\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Develop|Win64.Build.0 = Develop|x64\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Release|ARM64.ActiveCfg = Release|ARM64\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Release|ARM64.Build.0 = Release|ARM64\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Release|Win32.ActiveCfg = Release|Win32\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Release|Win32.Build.0 = Release|Win32\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Release|Win64.ActiveCfg = Release|x64\r\n\t\t{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}.Release|Win64.Build.0 = Release|x64\r\n\tEndGlobalSection\r\n\tGlobalSection(SolutionProperties) = preSolution\r\n\t\tHideSolutionNode = FALSE\r\n\tEndGlobalSection\r\n\tGlobalSection(ExtensibilityGlobals) = postSolution\r\n\t\tSolutionGuid = {2C6CC7E0-E590-49CB-B735-38BB01870C12}\r\n\tEndGlobalSection\r\nEndGlobal\r\n"
  },
  {
    "path": "SoundKeeper.vcxproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\r\n  <ItemGroup Label=\"ProjectConfigurations\">\r\n    <ProjectConfiguration Include=\"Debug|ARM64\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>ARM64</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Debug|Win32\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Debug|x64\">\r\n      <Configuration>Debug</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Develop|ARM64\">\r\n      <Configuration>Develop</Configuration>\r\n      <Platform>ARM64</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Develop|Win32\">\r\n      <Configuration>Develop</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Develop|x64\">\r\n      <Configuration>Develop</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|ARM64\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>ARM64</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|Win32\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>Win32</Platform>\r\n    </ProjectConfiguration>\r\n    <ProjectConfiguration Include=\"Release|x64\">\r\n      <Configuration>Release</Configuration>\r\n      <Platform>x64</Platform>\r\n    </ProjectConfiguration>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClCompile Include=\"CSoundKeeper.cpp\" />\r\n    <ClCompile Include=\"CSoundSession.cpp\" />\r\n    <ClCompile Include=\"RuntimeHacks.cpp\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClInclude Include=\"BuildInfo.hpp\" />\r\n    <ClInclude Include=\"Resources.hpp\" />\r\n    <ClInclude Include=\"Common.hpp\" />\r\n    <ClInclude Include=\"Common\\BasicMacros.hpp\" />\r\n    <ClInclude Include=\"Common\\Defer.hpp\" />\r\n    <ClInclude Include=\"Common\\NtBase.hpp\" />\r\n    <ClInclude Include=\"Common\\NtCriticalSection.hpp\" />\r\n    <ClInclude Include=\"Common\\NtEvent.hpp\" />\r\n    <ClInclude Include=\"Common\\NtHandle.hpp\" />\r\n    <ClInclude Include=\"Common\\NtUtils.hpp\" />\r\n    <ClInclude Include=\"Common\\StrUtils.hpp\" />\r\n    <ClInclude Include=\"CSoundKeeper.hpp\" />\r\n    <ClInclude Include=\"CSoundSession.hpp\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ResourceCompile Include=\"Resources.rc\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <Image Include=\"Main.ico\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <UpToDateCheckInput Include=\"BuildInfo.cmd\" />\r\n    <UpToDateCheckInput Include=\"BuildInfo.csx\" />\r\n    <UpToDateCheckInput Include=\"BuildInfo.rc2\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <CopyFileToFolders Include=\"ReadMe.txt\" />\r\n  </ItemGroup>\r\n  <PropertyGroup Label=\"Globals\">\r\n    <ProjectGuid>{15DF7FD9-CEF4-43DF-8521-A67E843C6F8F}</ProjectGuid>\r\n    <Keyword>Win32Proj</Keyword>\r\n    <ConfigurationType>Application</ConfigurationType>\r\n    <ProjectName>SoundKeeper</ProjectName>\r\n    <CharacterSet>Unicode</CharacterSet>\r\n    <PlatformToolset Condition=\"'$(Platform)'!='ARM64'\">v142</PlatformToolset>\r\n    <PlatformToolset Condition=\"'$(Platform)'=='ARM64'\">v143</PlatformToolset>\r\n    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\r\n  <PropertyGroup Condition=\"'$(Configuration)'=='Debug'\" Label=\"Configuration\">\r\n    <UseDebugLibraries>true</UseDebugLibraries>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)'=='Develop'\" Label=\"Configuration\">\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)'=='Release'\" Label=\"Configuration\">\r\n    <UseDebugLibraries>false</UseDebugLibraries>\r\n    <WholeProgramOptimization>true</WholeProgramOptimization>\r\n  </PropertyGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\r\n  <ImportGroup Label=\"ExtensionSettings\">\r\n  </ImportGroup>\r\n  <ImportGroup Label=\"PropertySheets\">\r\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\r\n  </ImportGroup>\r\n  <PropertyGroup Label=\"UserMacros\">\r\n    <IntDir>$(SolutionDir)Build\\$(ProjectName)_$(PlatformShortName)_$(Configuration)\\</IntDir>\r\n    <OutDir>$(SolutionDir)Bin\\</OutDir>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Platform)'=='Win32'\">\r\n    <AppArchName>x86-32</AppArchName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Platform)'=='x64'\">\r\n    <AppArchName>x86-64</AppArchName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Platform)'=='ARM64'\">\r\n    <AppArchName>ARM64</AppArchName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <LinkIncremental>true</LinkIncremental>\r\n    <TargetName>$(ProjectName)32d</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <LinkIncremental>true</LinkIncremental>\r\n    <TargetName>$(ProjectName)64d</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\r\n    <LinkIncremental>true</LinkIncremental>\r\n    <TargetName>$(ProjectName)ARM64d</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Develop|Win32'\">\r\n    <LinkIncremental>false</LinkIncremental>\r\n    <TargetName>$(ProjectName)32d</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Develop|x64'\">\r\n    <LinkIncremental>false</LinkIncremental>\r\n    <TargetName>$(ProjectName)64d</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Develop|ARM64'\">\r\n    <LinkIncremental>false</LinkIncremental>\r\n    <TargetName>$(ProjectName)ARM64d</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <LinkIncremental>false</LinkIncremental>\r\n    <TargetName>$(ProjectName)32</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <LinkIncremental>false</LinkIncremental>\r\n    <TargetName>$(ProjectName)64</TargetName>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\r\n    <LinkIncremental>false</LinkIncremental>\r\n    <TargetName>$(ProjectName)ARM64</TargetName>\r\n  </PropertyGroup>\r\n  <ItemDefinitionGroup>\r\n    <ClCompile>\r\n      <LanguageStandard>stdcpp20</LanguageStandard>\r\n      <PrecompiledHeader>NotUsing</PrecompiledHeader>\r\n      <WarningLevel>Level3</WarningLevel>\r\n      <DisableSpecificWarnings>26812</DisableSpecificWarnings>\r\n      <MultiProcessorCompilation>true</MultiProcessorCompilation>\r\n      <PreprocessorDefinitions>APP_ARCH=\"$(AppArchName)\";%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n    </ClCompile>\r\n    <ResourceCompile>\r\n      <PreprocessorDefinitions>APP_ARCH=\\\"$(AppArchName)\\\";%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n    </ResourceCompile>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\r\n    <ClCompile>\r\n      <Optimization>Disabled</Optimization>\r\n      <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r\n      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Console</SubSystem>\r\n      <MinimumRequiredVersion>6.0</MinimumRequiredVersion>\r\n      <GenerateDebugInformation>true</GenerateDebugInformation>\r\n      <AdditionalDependencies>avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n    <ResourceCompile>\r\n      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n    </ResourceCompile>\r\n    <PreBuildEvent>\r\n      <Command>BuildInfo.cmd --no-errors</Command>\r\n    </PreBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|x64'\">\r\n    <ClCompile>\r\n      <Optimization>Disabled</Optimization>\r\n      <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r\n      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Console</SubSystem>\r\n      <MinimumRequiredVersion>6.0</MinimumRequiredVersion>\r\n      <GenerateDebugInformation>true</GenerateDebugInformation>\r\n      <AdditionalDependencies>avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n    <ResourceCompile>\r\n      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n    </ResourceCompile>\r\n    <PreBuildEvent>\r\n      <Command>BuildInfo.cmd --no-errors</Command>\r\n    </PreBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|ARM64'\">\r\n    <ClCompile>\r\n      <Optimization>Disabled</Optimization>\r\n      <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>\r\n      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Console</SubSystem>\r\n      <MinimumRequiredVersion>10.0</MinimumRequiredVersion>\r\n      <GenerateDebugInformation>true</GenerateDebugInformation>\r\n      <AdditionalDependencies>avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n    </Link>\r\n    <ResourceCompile>\r\n      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n    </ResourceCompile>\r\n    <PreBuildEvent>\r\n      <Command>BuildInfo.cmd --no-errors</Command>\r\n    </PreBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Develop|Win32'\">\r\n    <ClCompile>\r\n      <Optimization>MinSpace</Optimization>\r\n      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r\n      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Console</SubSystem>\r\n      <MinimumRequiredVersion>6.0</MinimumRequiredVersion>\r\n      <GenerateDebugInformation>true</GenerateDebugInformation>\r\n      <AdditionalDependencies>avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <AdditionalOptions>/EMITTOOLVERSIONINFO:NO /NOVCFEATURE /NOCOFFGRPINFO /STUB:EmptyStub.bin /MERGE:.pdata=.rdata /MERGE:_RDATA=.rdata %(AdditionalOptions)</AdditionalOptions>\r\n      <RandomizedBaseAddress>false</RandomizedBaseAddress>\r\n    </Link>\r\n    <ResourceCompile>\r\n      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n    </ResourceCompile>\r\n    <PreBuildEvent>\r\n      <Command>BuildInfo.cmd --no-errors</Command>\r\n    </PreBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Develop|x64'\">\r\n    <ClCompile>\r\n      <Optimization>MinSpace</Optimization>\r\n      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r\n      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Console</SubSystem>\r\n      <MinimumRequiredVersion>6.0</MinimumRequiredVersion>\r\n      <GenerateDebugInformation>true</GenerateDebugInformation>\r\n      <AdditionalDependencies>avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <AdditionalOptions>/EMITTOOLVERSIONINFO:NO /NOVCFEATURE /NOCOFFGRPINFO /STUB:EmptyStub.bin /MERGE:.pdata=.rdata /MERGE:_RDATA=.rdata %(AdditionalOptions)</AdditionalOptions>\r\n      <RandomizedBaseAddress>false</RandomizedBaseAddress>\r\n    </Link>\r\n    <ResourceCompile>\r\n      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n    </ResourceCompile>\r\n    <PreBuildEvent>\r\n      <Command>BuildInfo.cmd --no-errors</Command>\r\n    </PreBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Develop|ARM64'\">\r\n    <ClCompile>\r\n      <Optimization>MinSpace</Optimization>\r\n      <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r\n      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Console</SubSystem>\r\n      <MinimumRequiredVersion>10.0</MinimumRequiredVersion>\r\n      <GenerateDebugInformation>true</GenerateDebugInformation>\r\n      <AdditionalDependencies>avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <AdditionalOptions>/EMITTOOLVERSIONINFO:NO /NOVCFEATURE /NOCOFFGRPINFO /STUB:EmptyStub.bin /MERGE:.pdata=.rdata /MERGE:_RDATA=.rdata %(AdditionalOptions)</AdditionalOptions>\r\n    </Link>\r\n    <ResourceCompile>\r\n      <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n    </ResourceCompile>\r\n    <PreBuildEvent>\r\n      <Command>BuildInfo.cmd --no-errors</Command>\r\n    </PreBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\r\n    <ClCompile>\r\n      <Optimization>MinSpace</Optimization>\r\n      <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r\n      <DebugInformationFormat>None</DebugInformationFormat>\r\n      <BufferSecurityCheck>false</BufferSecurityCheck>\r\n      <ExceptionHandling>false</ExceptionHandling>\r\n      <RuntimeTypeInfo>false</RuntimeTypeInfo>\r\n      <EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>\r\n      <OmitDefaultLibName>true</OmitDefaultLibName>\r\n      <AdditionalOptions>/Zc:alignedNew- /Zc:sizedDealloc- %(AdditionalOptions)</AdditionalOptions>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Windows</SubSystem>\r\n      <MinimumRequiredVersion>6.0</MinimumRequiredVersion>\r\n      <GenerateDebugInformation>false</GenerateDebugInformation>\r\n      <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>\r\n      <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>\r\n      <AdditionalDependencies>msvcrt32.lib;avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <AdditionalOptions>/EMITTOOLVERSIONINFO:NO /NOVCFEATURE /NOCOFFGRPINFO /STUB:EmptyStub.bin /MERGE:.pdata=.rdata %(AdditionalOptions)</AdditionalOptions>\r\n      <RandomizedBaseAddress>false</RandomizedBaseAddress>\r\n    </Link>\r\n    <ResourceCompile>\r\n      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n    </ResourceCompile>\r\n    <PreBuildEvent>\r\n      <Command>BuildInfo.cmd --no-errors</Command>\r\n    </PreBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|x64'\">\r\n    <ClCompile>\r\n      <Optimization>MinSpace</Optimization>\r\n      <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r\n      <DebugInformationFormat>None</DebugInformationFormat>\r\n      <BufferSecurityCheck>false</BufferSecurityCheck>\r\n      <ExceptionHandling>false</ExceptionHandling>\r\n      <RuntimeTypeInfo>false</RuntimeTypeInfo>\r\n      <OmitDefaultLibName>true</OmitDefaultLibName>\r\n      <AdditionalOptions>/Zc:alignedNew- /Zc:sizedDealloc- %(AdditionalOptions)</AdditionalOptions>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Windows</SubSystem>\r\n      <MinimumRequiredVersion>6.0</MinimumRequiredVersion>\r\n      <GenerateDebugInformation>false</GenerateDebugInformation>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>\r\n      <IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>\r\n      <AdditionalDependencies>msvcrt64.lib;avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <AdditionalOptions>/EMITTOOLVERSIONINFO:NO /NOVCFEATURE /NOCOFFGRPINFO /STUB:EmptyStub.bin /MERGE:.pdata=.rdata %(AdditionalOptions)</AdditionalOptions>\r\n      <RandomizedBaseAddress>false</RandomizedBaseAddress>\r\n    </Link>\r\n    <ResourceCompile>\r\n      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n    </ResourceCompile>\r\n    <PreBuildEvent>\r\n      <Command>BuildInfo.cmd --no-errors</Command>\r\n    </PreBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <ItemDefinitionGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|ARM64'\">\r\n    <ClCompile>\r\n      <Optimization>MinSpace</Optimization>\r\n      <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>\r\n      <FunctionLevelLinking>true</FunctionLevelLinking>\r\n      <IntrinsicFunctions>true</IntrinsicFunctions>\r\n      <PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n      <RuntimeLibrary>MultiThreaded</RuntimeLibrary>\r\n      <DebugInformationFormat>None</DebugInformationFormat>\r\n      <BufferSecurityCheck>false</BufferSecurityCheck>\r\n      <ExceptionHandling>false</ExceptionHandling>\r\n      <RuntimeTypeInfo>false</RuntimeTypeInfo>\r\n      <AdditionalOptions>/Zc:alignedNew- /Zc:sizedDealloc- %(AdditionalOptions)</AdditionalOptions>\r\n    </ClCompile>\r\n    <Link>\r\n      <SubSystem>Windows</SubSystem>\r\n      <MinimumRequiredVersion>10.0</MinimumRequiredVersion>\r\n      <GenerateDebugInformation>false</GenerateDebugInformation>\r\n      <EnableCOMDATFolding>true</EnableCOMDATFolding>\r\n      <OptimizeReferences>true</OptimizeReferences>\r\n      <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>\r\n      <AdditionalDependencies>avrt.lib;ntdll.lib;powrprof.lib;%(AdditionalDependencies)</AdditionalDependencies>\r\n      <AdditionalOptions>/EMITTOOLVERSIONINFO:NO /NOVCFEATURE /NOCOFFGRPINFO /STUB:EmptyStub.bin /MERGE:.pdata=.rdata %(AdditionalOptions)</AdditionalOptions>\r\n    </Link>\r\n    <ResourceCompile>\r\n      <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>\r\n    </ResourceCompile>\r\n    <PreBuildEvent>\r\n      <Command>BuildInfo.cmd --no-errors</Command>\r\n    </PreBuildEvent>\r\n  </ItemDefinitionGroup>\r\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\r\n  <ImportGroup Label=\"ExtensionTargets\">\r\n  </ImportGroup>\r\n</Project>"
  },
  {
    "path": "SoundKeeper.vcxproj.filters",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\r\n  <ItemGroup>\r\n    <Filter Include=\"Source Files\">\r\n      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\r\n      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>\r\n    </Filter>\r\n    <Filter Include=\"Resource Files\">\r\n      <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>\r\n      <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>\r\n    </Filter>\r\n    <Filter Include=\"Source Files\\Common\">\r\n      <UniqueIdentifier>{882ed8e4-f8b5-427d-974f-3f7a45977aca}</UniqueIdentifier>\r\n    </Filter>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClInclude Include=\"BuildInfo.hpp\">\r\n      <Filter>Resource Files</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"Resources.hpp\">\r\n      <Filter>Resource Files</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"Common.hpp\">\r\n      <Filter>Source Files</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"Common\\BasicMacros.hpp\">\r\n      <Filter>Source Files\\Common</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"Common\\Defer.hpp\">\r\n      <Filter>Source Files\\Common</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"Common\\NtBase.hpp\">\r\n      <Filter>Source Files\\Common</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"Common\\NtCriticalSection.hpp\">\r\n      <Filter>Source Files\\Common</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"Common\\NtEvent.hpp\">\r\n      <Filter>Source Files\\Common</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"Common\\NtHandle.hpp\">\r\n      <Filter>Source Files\\Common</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"Common\\NtUtils.hpp\">\r\n      <Filter>Source Files\\Common</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"Common\\StrUtils.hpp\">\r\n      <Filter>Source Files\\Common</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"CSoundKeeper.hpp\">\r\n      <Filter>Source Files</Filter>\r\n    </ClInclude>\r\n    <ClInclude Include=\"CSoundSession.hpp\">\r\n      <Filter>Source Files</Filter>\r\n    </ClInclude>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ClCompile Include=\"CSoundKeeper.cpp\">\r\n      <Filter>Source Files</Filter>\r\n    </ClCompile>\r\n    <ClCompile Include=\"CSoundSession.cpp\">\r\n      <Filter>Source Files</Filter>\r\n    </ClCompile>\r\n    <ClCompile Include=\"RuntimeHacks.cpp\">\r\n      <Filter>Source Files</Filter>\r\n    </ClCompile>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <ResourceCompile Include=\"Resources.rc\">\r\n      <Filter>Resource Files</Filter>\r\n    </ResourceCompile>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <Image Include=\"Main.ico\">\r\n      <Filter>Resource Files</Filter>\r\n    </Image>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <UpToDateCheckInput Include=\"BuildInfo.cmd\">\r\n      <Filter>Resource Files</Filter>\r\n    </UpToDateCheckInput>\r\n    <UpToDateCheckInput Include=\"BuildInfo.csx\">\r\n      <Filter>Resource Files</Filter>\r\n    </UpToDateCheckInput>\r\n    <UpToDateCheckInput Include=\"BuildInfo.rc2\">\r\n      <Filter>Resource Files</Filter>\r\n    </UpToDateCheckInput>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <CopyFileToFolders Include=\"ReadMe.txt\" />\r\n  </ItemGroup>\r\n</Project>"
  }
]