This commit is contained in:
2026-09-26 12:11:20 +07:00
parent 7baeb0dfed
commit 9c4f8d748f
23 changed files with 965 additions and 74 deletions
+35 -65
View File
@@ -1,79 +1,49 @@
using NAudio.CoreAudioApi;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.Windows.AppLifecycle;
using System;
using System.Threading;
namespace HearingProtector;
internal static class Program
public static class Program
{
// ─── Настройки — только то, что интересно пользователю ───────────────
private const double MinVolumePercent = 1.0; // до какого % убавляем
private const double FullDecayMinutes = 30.0; // за сколько минут 100% → MinVolume
private const int PollMs = 1000; // шаг цикла
private const float UserChangeEps = 0.005f;// порог «ручное изменение»
private const int LogEveryTicks = 30; // лог раз в 30 сек
// ─── Производные величины — считаются автоматически ──────────────────
private static readonly double MinVolume = MinVolumePercent / 100.0;
private static readonly double TotalTicks = FullDecayMinutes * 60_000.0 / PollMs;
private static readonly double DecayPerTick = Math.Pow(MinVolume, 1.0 / TotalTicks);
private static readonly double DbPerTick = 20.0 * Math.Log10(DecayPerTick);
private static async Task Main()
[STAThread]
static void Main(string[] ___)
{
using var enumerator = new MMDeviceEnumerator();
var device = enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
var volume = device.AudioEndpointVolume;
var mainInstance = AppInstance.FindOrRegisterForKey("HearingProtector_Main");
// ── Показываем пользователю, что мы из его настроек вывели ──
Console.WriteLine($"Диапазон: 100% → {MinVolumePercent:F1}%");
Console.WriteLine($"Заданное время: {FullDecayMinutes:F1} мин " +
$"({TotalTicks:F0} тиков по {PollMs} мс)");
Console.WriteLine($"Множитель/тик: ×{DecayPerTick:F6} " +
$"= {DbPerTick:F4} дБ");
Console.WriteLine();
float current = volume.MasterVolumeLevelScalar;
float lastSet = current;
Console.WriteLine($"Стартуем с {current * 100:F1}%. Ctrl+C — выход.");
Console.WriteLine();
int tick = 0;
while (true)
if (!mainInstance.IsCurrent)
{
float actual = volume.MasterVolumeLevelScalar;
// Мы не первый экземпляр — просим основной обработать активацию и выходим.
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
mainInstance.RedirectActivationToAsync(activationArgs).AsTask().Wait();
return;
}
// 1) Реакция на ручное изменение громкости.
if (Math.Abs(actual - lastSet) > UserChangeEps)
{
var dir = actual > lastSet ? "повышение" : "понижение";
Console.WriteLine(
$"[{DateTime.Now:HH:mm:ss}] Ручное изменение ({dir}): " +
$"{lastSet * 100:F1}% → {actual * 100:F1}%");
current = actual;
}
// Подписываемся на активации от будущих попыток запуска.
mainInstance.Activated += OnInstanceActivated;
// 2) Один экспоненциальный шаг вниз.
current = (float)Math.Max(MinVolume, current * DecayPerTick);
// Стандартный запуск WinUI с правильным SynchronizationContext.
Application.Start(__ =>
{
var context = new DispatcherQueueSynchronizationContext(
DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
_ = new App();
});
}
// 3) Применяем, если значение реально отличается.
if (Math.Abs(volume.MasterVolumeLevelScalar - current) > 0.0005f)
volume.MasterVolumeLevelScalar = current;
// Срабатывает, когда кто-то пытается запустить второй экземпляр.
private static void OnInstanceActivated(object? sender, AppActivationArguments e)
{
// ВАЖНО: событие приходит из НЕ-UI потока, поэтому нужно маршалить.
var app = App.CurrentApp;
var window = app?.MainWindow;
lastSet = current;
// 4) Редкий лог с ETA.
if (++tick % LogEveryTicks == 0)
{
double etaSec = Math.Log(MinVolume / current) / Math.Log(DecayPerTick)
* PollMs / 1000.0;
var eta = TimeSpan.FromSeconds(Math.Max(0, etaSec));
Console.WriteLine(
$"[{DateTime.Now:HH:mm:ss}] " +
$"Громкость: {current * 100:F1}% " +
$"(до минимума ~{eta:hh\\:mm\\:ss})");
}
await Task.Delay(PollMs);
if (window is MainWindow mw)
{
window.DispatcherQueue.TryEnqueue(() => mw.RestoreFromTray());
}
}
}