fix click: 5ms fade-out from last sample to zero before silence padding

This commit is contained in:
2026-08-17 06:45:06 +00:00
parent b3e0fe3b30
commit 84474731e7
+19
View File
@@ -8,6 +8,7 @@ internal sealed class AudioOutput : IDisposable
private BufferedWaveProvider? _bufferProvider; private BufferedWaveProvider? _bufferProvider;
private int _sampleRate; private int _sampleRate;
private string _deviceName = string.Empty; private string _deviceName = string.Empty;
private float _lastSample;
private bool _disposed; private bool _disposed;
public Action<string>? Log { get; set; } public Action<string>? Log { get; set; }
@@ -18,6 +19,7 @@ internal sealed class AudioOutput : IDisposable
_sampleRate = sampleRate; _sampleRate = sampleRate;
_deviceName = deviceName ?? string.Empty; _deviceName = deviceName ?? string.Empty;
_lastSample = 0f;
Stop(); Stop();
@@ -42,6 +44,9 @@ internal sealed class AudioOutput : IDisposable
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
if (_bufferProvider is null) return; if (_bufferProvider is null) return;
if (samples.Length > 0)
_lastSample = samples[^1];
byte[] bytes = new byte[samples.Length * sizeof(float)]; byte[] bytes = new byte[samples.Length * sizeof(float)];
Buffer.BlockCopy(samples, 0, bytes, 0, bytes.Length); Buffer.BlockCopy(samples, 0, bytes, 0, bytes.Length);
_bufferProvider.AddSamples(bytes, 0, bytes.Length); _bufferProvider.AddSamples(bytes, 0, bytes.Length);
@@ -52,8 +57,21 @@ internal sealed class AudioOutput : IDisposable
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);
if (_bufferProvider is null || _sampleRate == 0) return; if (_bufferProvider is null || _sampleRate == 0) return;
// Fade out from last sample to zero over 5ms to avoid click
int fadeSamples = (int)(_sampleRate * 0.005);
float[] fade = new float[fadeSamples];
for (int i = 0; i < fadeSamples; i++)
{
float t = (float)i / fadeSamples;
fade[i] = _lastSample * (1f - t);
}
WriteSamples(fade);
// Then 500ms of silence to let NAudio drain
int padSamples = (int)(_sampleRate * 0.5); int padSamples = (int)(_sampleRate * 0.5);
WriteSamples(new float[padSamples]); WriteSamples(new float[padSamples]);
_lastSample = 0f;
} }
private void OnPlaybackStopped(object? sender, StoppedEventArgs e) private void OnPlaybackStopped(object? sender, StoppedEventArgs e)
@@ -71,6 +89,7 @@ internal sealed class AudioOutput : IDisposable
} }
_bufferProvider?.ClearBuffer(); _bufferProvider?.ClearBuffer();
_bufferProvider = null; _bufferProvider = null;
_lastSample = 0f;
} }
private static int FindDevice(string? deviceName) private static int FindDevice(string? deviceName)