2026-09-25 15:52:58 +07:00
|
|
|
|
using NAudio.CoreAudioApi;
|
|
|
|
|
|
|
|
|
|
|
|
namespace HearingProtector;
|
|
|
|
|
|
|
|
|
|
|
|
internal static class Program
|
2026-09-25 15:26:15 +07:00
|
|
|
|
{
|
2026-09-25 15:52:58 +07:00
|
|
|
|
// ─── Настройки — только то, что интересно пользователю ───────────────
|
|
|
|
|
|
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()
|
2026-09-25 15:26:15 +07:00
|
|
|
|
{
|
2026-09-25 15:52:58 +07:00
|
|
|
|
using var enumerator = new MMDeviceEnumerator();
|
|
|
|
|
|
var device = enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
|
|
|
|
|
|
var volume = device.AudioEndpointVolume;
|
|
|
|
|
|
|
|
|
|
|
|
// ── Показываем пользователю, что мы из его настроек вывели ──
|
|
|
|
|
|
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)
|
2026-09-25 15:26:15 +07:00
|
|
|
|
{
|
2026-09-25 15:52:58 +07:00
|
|
|
|
float actual = volume.MasterVolumeLevelScalar;
|
|
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2) Один экспоненциальный шаг вниз.
|
|
|
|
|
|
current = (float)Math.Max(MinVolume, current * DecayPerTick);
|
|
|
|
|
|
|
|
|
|
|
|
// 3) Применяем, если значение реально отличается.
|
|
|
|
|
|
if (Math.Abs(volume.MasterVolumeLevelScalar - current) > 0.0005f)
|
|
|
|
|
|
volume.MasterVolumeLevelScalar = current;
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
2026-09-25 15:26:15 +07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-09-25 15:52:58 +07:00
|
|
|
|
}
|