using NAudio.Wave; namespace Robovoice.App; internal sealed class AudioOutput : IDisposable { private WaveOutEvent? _waveOut; private BufferedWaveProvider? _bufferProvider; private int _sampleRate; private string _deviceName = string.Empty; private bool _playing; private bool _disposed; public Action? Log { get; set; } public void Start(int sampleRate, string? deviceName = null) { ObjectDisposedException.ThrowIf(_disposed, this); _sampleRate = sampleRate; _deviceName = deviceName ?? string.Empty; Stop(); int deviceNumber = FindDevice(_deviceName); _waveOut = new WaveOutEvent { DeviceNumber = deviceNumber, DesiredLatency = 200 }; _bufferProvider = new BufferedWaveProvider( WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, 1)) { BufferDuration = TimeSpan.FromSeconds(60), DiscardOnBufferOverflow = true, ReadFully = true, }; _waveOut.Init(_bufferProvider); _waveOut.PlaybackStopped += OnPlaybackStopped; _playing = true; _waveOut.Play(); } public void WriteSamples(float[] samples) { ObjectDisposedException.ThrowIf(_disposed, this); if (_bufferProvider is null) return; byte[] bytes = new byte[samples.Length * sizeof(float)]; Buffer.BlockCopy(samples, 0, bytes, 0, bytes.Length); _bufferProvider.AddSamples(bytes, 0, bytes.Length); } public void Flush() { ObjectDisposedException.ThrowIf(_disposed, this); if (_bufferProvider is null || _sampleRate == 0) return; int padSamples = (int)(_sampleRate * 0.5); WriteSamples(new float[padSamples]); } private void OnPlaybackStopped(object? sender, StoppedEventArgs e) { _playing = false; } public void Stop() { if (_waveOut is not null) { _waveOut.PlaybackStopped -= OnPlaybackStopped; _waveOut.Stop(); _waveOut.Dispose(); _waveOut = null; } _bufferProvider?.ClearBuffer(); _bufferProvider = null; _playing = false; } private static int FindDevice(string? deviceName) { if (string.IsNullOrEmpty(deviceName)) return -1; for (int i = 0; i < WaveOut.DeviceCount; i++) { var caps = WaveOut.GetCapabilities(i); if (caps.ProductName.Contains(deviceName, StringComparison.OrdinalIgnoreCase)) return i; } return -1; } public static IReadOnlyList<(int Index, string Name)> GetDevices() { var devices = new List<(int, string)>(); for (int i = 0; i < WaveOut.DeviceCount; i++) { var caps = WaveOut.GetCapabilities(i); devices.Add((i, caps.ProductName)); } return devices; } public void Dispose() { if (_disposed) return; Stop(); _disposed = true; } }