10internal sealed class VariableSpeedSampleProvider : ISampleProvider
12 private readonly ISampleProvider _source;
13 private readonly
int _channels;
14 private readonly
float[] _currentFrame;
15 private readonly
float[] _nextFrame;
16 private readonly
object _sync =
new();
18 private bool _initialized;
19 private bool _hasCurrent;
20 private bool _hasNext;
21 private double _phase;
22 private float _playbackSpeed = 1.0f;
30 public VariableSpeedSampleProvider(ISampleProvider source)
32 _source = source ??
throw new ArgumentNullException(nameof(source));
33 _channels = source.WaveFormat.Channels;
35 throw new ArgumentException(
"Source must expose at least one audio channel.", nameof(source));
37 _currentFrame =
new float[_channels];
38 _nextFrame =
new float[_channels];
44 public WaveFormat WaveFormat => _source.WaveFormat;
53 public float PlaybackSpeed
55 get => Volatile.Read(ref _playbackSpeed);
56 set => Volatile.Write(
58 Math.Clamp(value, AudioResource.MinimumPlaybackSpeed, AudioResource.MaximumPlaybackSpeed));
69 public int Read(
float[] buffer,
int offset,
int count)
80 var frameCapacity = count / _channels;
81 var framesWritten = 0;
83 while (framesWritten < frameCapacity && _hasCurrent)
85 var destination = offset + (framesWritten * _channels);
86 var phase = (float)_phase;
88 for (var channel = 0; channel < _channels; channel++)
90 var next = _hasNext ? _nextFrame[channel] : _currentFrame[channel];
91 buffer[destination + channel] = _currentFrame[channel] + ((next - _currentFrame[channel]) * phase);
95 _phase += PlaybackSpeed;
97 while (_phase >= 1.0 && _hasCurrent)
104 return framesWritten * _channels;
112 public void Reset(Action? reposition =
null)
116 reposition?.Invoke();
117 _initialized =
false;
124 private void EnsureInitialized()
129 _hasCurrent = ReadFrame(_currentFrame);
130 _hasNext = _hasCurrent && ReadFrame(_nextFrame);
135 private void AdvanceFrame()
143 Array.Copy(_nextFrame, _currentFrame, _channels);
145 _hasNext = ReadFrame(_nextFrame);
148 private bool ReadFrame(
float[] frame)
151 while (read < _channels)
153 var count = _source.Read(frame, read, _channels - read);