using System.Diagnostics; using System.Runtime.InteropServices; namespace RemSound.App; /// /// "Full CPU speed" mode for the current profile. When enabled, pulls every documented /// Windows lever to keep our process running at full clock — no EcoQoS downclocking, no /// migration to E-cores, no deep-C-state idling, 1 ms scheduler quantum, High priority /// class. All five mechanisms are per-process or per-thread: nothing affects other apps, /// and nothing here changes the system power plan (which is global) or anyone else's /// scheduling. Untoggling reverses every change cleanly. /// /// Mechanisms layered on: /// /// Process power throttling — opt out of EXECUTION_SPEED. This is the /// main one. Tells Windows' scheduler "this is not a background process; don't /// downclock it, don't migrate its threads onto efficiency cores on hybrid CPUs". /// Without this, Windows aggressively saves power on processes it thinks are /// idle, which is the root cause of the "cold start sounds awful, warms up after /// a few seconds" behaviour the user reported. Same API Discord, Spotify, OBS /// and similar real-time apps use. /// PowerSetRequest(EXECUTION_REQUIRED). Tells the kernel the system as a /// whole can't enter deep low-power C-states while this process is active. Belt /// and braces with #1 — covers the case where #1 isn't honoured (very old build /// of Windows 10, some custom OEM image, etc.). /// Process priority class = High. Standard for low-latency audio apps; /// bumps our dispatch priority above almost everything except OS internals. We /// deliberately do not use Realtime — that class can starve OS services /// (including the audio service itself) and is widely documented as causing /// worse audio behaviour, not better. /// timeBeginPeriod(1). Asks Windows for a 1 ms scheduler quantum. Helps /// any code path that does timer-based waiting. Since Windows 10 build 1803, /// this is scoped per-process — so we don't damage other apps' scheduling. We /// match every timeBeginPeriod with a timeEndPeriod when the /// feature is disabled, otherwise the OS keeps the elevated rate forever (the /// old global-period gotcha is long gone but we're tidy anyway). /// SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED). Older /// API but additive to #2; prevents the system from idle-sleeping. Negligible /// cost. ES_DISPLAY_REQUIRED is deliberately omitted — we don't need the /// screen on while we're running, just the CPU. /// /// /// Trade-offs the user should know about: /// /// Laptop on battery: faster drain while RemSound is open with this on. Worth /// the energy for a "live session" profile; not worth it for a "background /// listening" one. That's why this lives on the Profile, not on AppConfig. /// Desktop on mains: usually a couple of watts more, no practical downside. /// /// /// Idempotent — calling with the same value twice is a no-op. The /// internal state tracks the active power request handle so /// with enable: false releases every resource it acquired. Safe to call from the /// UI thread; none of the underlying API calls block in any meaningful sense. /// internal static class PerformanceMode { private static readonly object gate = new(); private static bool currentlyEnabled; private static IntPtr powerRequestHandle = IntPtr.Zero; private static ProcessPriorityClass? priorityBeforeBoost; /// Apply or reverse Full-CPU-speed mode. receives a /// short status line so the activation is visible in the diagnostic log file. /// Failures on individual mechanisms (e.g. an OEM image rejecting one of the Win32 /// calls) are logged but don't abort the rest of the application of the mode. public static void Apply(bool enable, Action? log = null) { lock (gate) { if (enable == currentlyEnabled) { log?.Invoke($"performance mode: already {(enable ? "on" : "off")}, no change"); return; } if (enable) { TrySetPowerThrottling(disable: true, log); TryStartPowerRequest(log); TryRaisePriority(log); TryBeginTimePeriod(log); TrySetThreadExecutionState(keepAwake: true, log); TrySetMemoryPriorityNormal(log); TryLockWorkingSetMin(log); currentlyEnabled = true; log?.Invoke("priority mode: ON (EcoQoS off, timer resolution honored, power-request set, priority High, 1 ms quantum, system-required, memory priority normal, working-set min locked)"); } else { TrySetPowerThrottling(disable: false, log); TryStopPowerRequest(log); TryRestorePriority(log); TryEndTimePeriod(log); TrySetThreadExecutionState(keepAwake: false, log); TryRelaxWorkingSet(log); currentlyEnabled = false; log?.Invoke("priority mode: OFF (all overrides cleared, defaults restored)"); } } } // === 1. Process power throttling (EcoQoS opt-out + timer-resolution honoring) === private static void TrySetPowerThrottling(bool disable, Action? log) { try { // We control two flags at once: // * EXECUTION_SPEED — StateMask=0 means "don't throttle this process" // (no downclock, no E-core migration). The main lever. // * IGNORE_TIMER_RESOLUTION — StateMask=0 means "honour my // timeBeginPeriod request even if other apps don't want fine grain". // Pairs naturally with the timeBeginPeriod(1) call further down. // When we revert (disable=false) we set ControlMask=0, which hands both // flags back to Windows' system-default decision-making rather than // forcing them on. var state = new PROCESS_POWER_THROTTLING_STATE { Version = ProcessPowerThrottlingCurrentVersion, ControlMask = disable ? (ProcessPowerThrottlingExecutionSpeed | ProcessPowerThrottlingIgnoreTimerResolution) : 0u, StateMask = 0u, }; var ok = SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, ref state, Marshal.SizeOf()); if (!ok) log?.Invoke($"priority mode: SetProcessInformation(power throttling) failed (win32={Marshal.GetLastWin32Error()})"); } catch (Exception ex) { log?.Invoke($"priority mode: power throttling threw: {ex.GetType().Name}: {ex.Message}"); } } // === 2. Kernel power request (no deep C-states) === private static void TryStartPowerRequest(Action? log) { try { var ctx = new REASON_CONTEXT { Version = PowerRequestContextVersion, Flags = PowerRequestContextSimpleString, Reason = "RemSound is running with Full CPU speed enabled.", }; var handle = PowerCreateRequest(ref ctx); if (handle == new IntPtr(-1)) { log?.Invoke($"performance mode: PowerCreateRequest failed (win32={Marshal.GetLastWin32Error()})"); return; } if (!PowerSetRequest(handle, PowerRequestExecutionRequired)) { log?.Invoke($"performance mode: PowerSetRequest failed (win32={Marshal.GetLastWin32Error()})"); CloseHandle(handle); return; } powerRequestHandle = handle; } catch (Exception ex) { log?.Invoke($"performance mode: power request threw: {ex.GetType().Name}: {ex.Message}"); } } private static void TryStopPowerRequest(Action? log) { if (powerRequestHandle == IntPtr.Zero) return; try { PowerClearRequest(powerRequestHandle, PowerRequestExecutionRequired); CloseHandle(powerRequestHandle); } catch (Exception ex) { log?.Invoke($"performance mode: power-request release threw: {ex.GetType().Name}: {ex.Message}"); } finally { powerRequestHandle = IntPtr.Zero; } } // === 3. Process priority class === private static void TryRaisePriority(Action? log) { try { using var proc = Process.GetCurrentProcess(); priorityBeforeBoost = proc.PriorityClass; proc.PriorityClass = ProcessPriorityClass.High; } catch (Exception ex) { log?.Invoke($"performance mode: priority raise threw: {ex.GetType().Name}: {ex.Message}"); } } private static void TryRestorePriority(Action? log) { if (priorityBeforeBoost is null) return; try { using var proc = Process.GetCurrentProcess(); proc.PriorityClass = priorityBeforeBoost.Value; } catch (Exception ex) { log?.Invoke($"performance mode: priority restore threw: {ex.GetType().Name}: {ex.Message}"); } finally { priorityBeforeBoost = null; } } // === 4. timeBeginPeriod / timeEndPeriod === private static bool timePeriodActive; private static void TryBeginTimePeriod(Action? log) { if (timePeriodActive) return; try { if (timeBeginPeriod(1) == 0) { timePeriodActive = true; } else { log?.Invoke("performance mode: timeBeginPeriod(1) failed"); } } catch (Exception ex) { log?.Invoke($"performance mode: timeBeginPeriod threw: {ex.GetType().Name}: {ex.Message}"); } } private static void TryEndTimePeriod(Action? log) { if (!timePeriodActive) return; try { timeEndPeriod(1); } catch (Exception ex) { log?.Invoke($"performance mode: timeEndPeriod threw: {ex.GetType().Name}: {ex.Message}"); } finally { timePeriodActive = false; } } // === 5. SetThreadExecutionState === private static void TrySetThreadExecutionState(bool keepAwake, Action? log) { try { var flags = keepAwake ? ES_CONTINUOUS | ES_SYSTEM_REQUIRED : ES_CONTINUOUS; var prev = SetThreadExecutionState(flags); if (prev == 0) log?.Invoke("priority mode: SetThreadExecutionState returned 0 (call rejected)"); } catch (Exception ex) { log?.Invoke($"priority mode: SetThreadExecutionState threw: {ex.GetType().Name}: {ex.Message}"); } } // === 6. Process memory priority === // Setting our process's memory priority explicitly to NORMAL. The OS default is // already NORMAL (5), but Windows can demote "background-looking" processes // (especially after long idle periods or under memory pressure), and a demoted // process gets its working-set pages evicted first when the trimmer runs. This // call is a defensive assertion: "I am a foreground-class process, treat my // pages accordingly". private static void TrySetMemoryPriorityNormal(Action? log) { try { var info = new MEMORY_PRIORITY_INFORMATION { MemoryPriority = MemoryPriorityNormal }; var ok = SetProcessInformationMemory(GetCurrentProcess(), ProcessMemoryPriority, ref info, Marshal.SizeOf()); if (!ok) log?.Invoke($"priority mode: SetProcessInformation(memory) failed (win32={Marshal.GetLastWin32Error()})"); } catch (Exception ex) { log?.Invoke($"priority mode: memory priority threw: {ex.GetType().Name}: {ex.Message}"); } } // === 7. Lock the minimum working-set size === // Windows trims a process's working set proactively when the machine looks idle. // For RemSound that's exactly the moment we'd rather it didn't — the first packet // after an idle stretch hits a cold cache and pages have to be brought back in, // which audibly stalls the first frames. Locking a minimum working set (32 MB — // enough for the JIT'd code + audio scratch buffers plus headroom for NAudio's // and the GC's reserves) tells Windows "you may not trim below this floor". // Released cleanly when priority mode is turned off. private const long MinWorkingSetBytes = 32L * 1024 * 1024; private static void TryLockWorkingSetMin(Action? log) { try { // dwMaximumWorkingSetSize = -1 means "leave unchanged". var min = (IntPtr)MinWorkingSetBytes; var maxUnchanged = (IntPtr)(-1); if (!SetProcessWorkingSetSizeEx(GetCurrentProcess(), min, maxUnchanged, QUOTA_LIMITS_HARDWS_MIN_ENABLE)) { log?.Invoke($"priority mode: SetProcessWorkingSetSizeEx(lock) failed (win32={Marshal.GetLastWin32Error()})"); } } catch (Exception ex) { log?.Invoke($"priority mode: working-set lock threw: {ex.GetType().Name}: {ex.Message}"); } } private static void TryRelaxWorkingSet(Action? log) { try { var unchanged = (IntPtr)(-1); if (!SetProcessWorkingSetSizeEx(GetCurrentProcess(), unchanged, unchanged, QUOTA_LIMITS_HARDWS_MIN_DISABLE)) { log?.Invoke($"priority mode: SetProcessWorkingSetSizeEx(unlock) failed (win32={Marshal.GetLastWin32Error()})"); } } catch (Exception ex) { log?.Invoke($"priority mode: working-set unlock threw: {ex.GetType().Name}: {ex.Message}"); } } // === Native interop === private const int ProcessMemoryPriority = 0; // PROCESS_INFORMATION_CLASS.ProcessMemoryPriority private const int ProcessPowerThrottling = 4; // PROCESS_INFORMATION_CLASS.ProcessPowerThrottling private const uint ProcessPowerThrottlingCurrentVersion = 1; private const uint ProcessPowerThrottlingExecutionSpeed = 0x1; private const uint ProcessPowerThrottlingIgnoreTimerResolution = 0x4; private const int PowerRequestExecutionRequired = 3; private const uint PowerRequestContextVersion = 0; private const uint PowerRequestContextSimpleString = 0x1; private const uint ES_CONTINUOUS = 0x80000000; private const uint ES_SYSTEM_REQUIRED = 0x00000001; private const uint MemoryPriorityNormal = 5; private const int QUOTA_LIMITS_HARDWS_MIN_ENABLE = 0x1; private const int QUOTA_LIMITS_HARDWS_MIN_DISABLE = 0x2; [StructLayout(LayoutKind.Sequential)] private struct PROCESS_POWER_THROTTLING_STATE { public uint Version; public uint ControlMask; public uint StateMask; } [StructLayout(LayoutKind.Sequential)] private struct MEMORY_PRIORITY_INFORMATION { public uint MemoryPriority; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct REASON_CONTEXT { public uint Version; public uint Flags; [MarshalAs(UnmanagedType.LPWStr)] public string Reason; } [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GetCurrentProcess(); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool SetProcessInformation(IntPtr hProcess, int ProcessInformationClass, ref PROCESS_POWER_THROTTLING_STATE ProcessInformation, int ProcessInformationSize); // Same kernel32 entry point as SetProcessInformation, but with a different struct type // in the ref parameter — declare a separate P/Invoke so the marshaller picks the right // signature without us needing to manually pin and StructureToPtr the buffer. [DllImport("kernel32.dll", SetLastError = true, EntryPoint = "SetProcessInformation")] private static extern bool SetProcessInformationMemory(IntPtr hProcess, int ProcessInformationClass, ref MEMORY_PRIORITY_INFORMATION ProcessInformation, int ProcessInformationSize); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool SetProcessWorkingSetSizeEx(IntPtr hProcess, IntPtr dwMinimumWorkingSetSize, IntPtr dwMaximumWorkingSetSize, int Flags); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr PowerCreateRequest(ref REASON_CONTEXT Context); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool PowerSetRequest(IntPtr PowerRequest, int RequestType); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool PowerClearRequest(IntPtr PowerRequest, int RequestType); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr hObject); [DllImport("winmm.dll")] private static extern uint timeBeginPeriod(uint uMilliseconds); [DllImport("winmm.dll")] private static extern uint timeEndPeriod(uint uMilliseconds); [DllImport("kernel32.dll", SetLastError = true)] private static extern uint SetThreadExecutionState(uint esFlags); }