Gondwana Game Engine Development (master) - 2.6.1
Gondwana is a cross-platform 2.5D game and rendering engine written in C#/.NET 8. It provides fine-grained control over rendering, timing, and scene composition, with built-in support for parallax, z-ordering, pixel overhang, collision detection, and particle effects. Gondwana targets desktop and web platforms using SkiaSharp for graphics and pluggable audio backends for desktop and browser targets.
Loading...
Searching...
No Matches
VariableSpeedSampleProvider.cs
1using NAudio.Wave;
2
4
10internal sealed class VariableSpeedSampleProvider : ISampleProvider
11{
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();
17
18 private bool _initialized;
19 private bool _hasCurrent;
20 private bool _hasNext;
21 private double _phase;
22 private float _playbackSpeed = 1.0f;
23
30 public VariableSpeedSampleProvider(ISampleProvider source)
31 {
32 _source = source ?? throw new ArgumentNullException(nameof(source));
33 _channels = source.WaveFormat.Channels;
34 if (_channels <= 0)
35 throw new ArgumentException("Source must expose at least one audio channel.", nameof(source));
36
37 _currentFrame = new float[_channels];
38 _nextFrame = new float[_channels];
39 }
40
44 public WaveFormat WaveFormat => _source.WaveFormat;
45
53 public float PlaybackSpeed
54 {
55 get => Volatile.Read(ref _playbackSpeed);
56 set => Volatile.Write(
57 ref _playbackSpeed,
58 Math.Clamp(value, AudioResource.MinimumPlaybackSpeed, AudioResource.MaximumPlaybackSpeed));
59 }
60
69 public int Read(float[] buffer, int offset, int count)
70 {
71 if (count <= 0)
72 return 0;
73
74 lock (_sync)
75 {
76 EnsureInitialized();
77 if (!_hasCurrent)
78 return 0;
79
80 var frameCapacity = count / _channels;
81 var framesWritten = 0;
82
83 while (framesWritten < frameCapacity && _hasCurrent)
84 {
85 var destination = offset + (framesWritten * _channels);
86 var phase = (float)_phase;
87
88 for (var channel = 0; channel < _channels; channel++)
89 {
90 var next = _hasNext ? _nextFrame[channel] : _currentFrame[channel];
91 buffer[destination + channel] = _currentFrame[channel] + ((next - _currentFrame[channel]) * phase);
92 }
93
94 framesWritten++;
95 _phase += PlaybackSpeed;
96
97 while (_phase >= 1.0 && _hasCurrent)
98 {
99 _phase -= 1.0;
100 AdvanceFrame();
101 }
102 }
103
104 return framesWritten * _channels;
105 }
106 }
107
112 public void Reset(Action? reposition = null)
113 {
114 lock (_sync)
115 {
116 reposition?.Invoke();
117 _initialized = false;
118 _hasCurrent = false;
119 _hasNext = false;
120 _phase = 0;
121 }
122 }
123
124 private void EnsureInitialized()
125 {
126 if (_initialized)
127 return;
128
129 _hasCurrent = ReadFrame(_currentFrame);
130 _hasNext = _hasCurrent && ReadFrame(_nextFrame);
131 _phase = 0;
132 _initialized = true;
133 }
134
135 private void AdvanceFrame()
136 {
137 if (!_hasNext)
138 {
139 _hasCurrent = false;
140 return;
141 }
142
143 Array.Copy(_nextFrame, _currentFrame, _channels);
144 _hasCurrent = true;
145 _hasNext = ReadFrame(_nextFrame);
146 }
147
148 private bool ReadFrame(float[] frame)
149 {
150 var read = 0;
151 while (read < _channels)
152 {
153 var count = _source.Read(frame, read, _channels - read);
154 if (count == 0)
155 return false;
156
157 read += count;
158 }
159
160 return true;
161 }
162}