AquaSynth/tools/AquaSynthDingsHost/DingAudioPlayer.cs
2026-07-15 01:18:09 +01:00

77 lines
3 KiB
C#

using NAudio.CoreAudioApi;
using NAudio.Wave;
using NAudio.Wave.SampleProviders;
namespace AquaSynthDingsHost;
public sealed class DingAudioPlayer : IDisposable
{
private static readonly Guid SessionGroup = new("8414a5ca-a680-4b7c-9f17-cdb954177f9d");
private readonly object sync = new();
private readonly MixingSampleProvider mixer = new(WaveFormat.CreateIeeeFloatWaveFormat(44100, 2)) { ReadFully = true };
private readonly MMDevice device;
private readonly WasapiOut output;
private readonly List<AudioFileReader> readers = [];
private AudioSessionControl? session;
public DingAudioPlayer()
{
using var enumerator = new MMDeviceEnumerator();
device = enumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
output = new WasapiOut(device, AudioClientShareMode.Shared, true, 80);
output.Init(mixer);
output.Play();
session = FindSession();
if (session is not null)
{
session.DisplayName = "AquaSynth Dings";
session.SetGroupingParam(SessionGroup, Guid.Empty);
}
}
public float Volume { get => RequireSession().SimpleAudioVolume.Volume; set => RequireSession().SimpleAudioVolume.Volume = value; }
public bool Muted { get => RequireSession().SimpleAudioVolume.Mute; set => RequireSession().SimpleAudioVolume.Mute = value; }
public void Play(string wavPath, int delayMilliseconds)
{
var reader = new AudioFileReader(wavPath);
var input = new OffsetSampleProvider(reader.ToStereo()) { DelayBy = TimeSpan.FromMilliseconds(delayMilliseconds), Take = reader.TotalTime + TimeSpan.FromMilliseconds(delayMilliseconds) };
lock (sync) { readers.Add(reader); mixer.AddMixerInput(input); }
_ = Task.Delay(input.Take + TimeSpan.FromMilliseconds(150)).ContinueWith(_ =>
{
lock (sync) { mixer.RemoveMixerInput(input); readers.Remove(reader); reader.Dispose(); }
}, TaskScheduler.Default);
}
public void StopAll()
{
lock (sync) { mixer.RemoveAllMixerInputs(); foreach (var reader in readers) reader.Dispose(); readers.Clear(); }
}
private AudioSessionControl RequireSession() => session ??= FindSession() ?? throw new InvalidOperationException("AquaSynth Dings WASAPI session is unavailable.");
private AudioSessionControl? FindSession()
{
for (var attempt = 0; attempt < 20; attempt++)
{
device.AudioSessionManager.RefreshSessions();
var sessions = device.AudioSessionManager.Sessions;
for (var i = 0; i < sessions.Count; i++)
{
var candidate = sessions[i];
if (candidate.GetProcessID == (uint)Environment.ProcessId) return candidate;
}
Thread.Sleep(25);
}
return null;
}
public void Dispose()
{
StopAll();
session?.Dispose();
output.Stop();
output.Dispose();
device.Dispose();
}
}