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
SynthesiserSampleProvider.cs
1using MeltySynth;
2using NAudio.Wave;
3
4namespace Gondwana.Audio.Midi;
5
28public class SynthesizerSampleProvider : ISampleProvider
29{
30 private readonly MidiFileSequencer _sequencer;
31 private readonly Synthesizer _synthesizer;
32 private readonly MeltySynth.MidiFile _midiFile;
33 private readonly bool _loop;
34 private const float Tolerance = 1e-6f; // Define a small tolerance for floating-point comparison
35 private readonly float[] _left = new float[8192];
36 private readonly float[] _right = new float[8192];
37
63 MidiFileSequencer sequencer,
64 Synthesizer synth,
65 MeltySynth.MidiFile midiFile,
66 bool loop)
67 {
68 _sequencer = sequencer;
69 _synthesizer = synth;
70 _midiFile = midiFile;
71 _loop = loop;
72 }
73
84 public WaveFormat WaveFormat { get; } = WaveFormat.CreateIeeeFloatWaveFormat(44100, 2);
85
123 public int Read(float[] buffer, int offset, int count)
124 {
125 int framesRequested = count / 2;
126 int framesRendered = 0;
127
128 while (framesRendered < framesRequested)
129 {
130 int remaining = framesRequested - framesRendered;
131 int renderCount = Math.Min(remaining, _left.Length);
132
133 var leftSpan = _left.AsSpan(0, renderCount);
134 var rightSpan = _right.AsSpan(0, renderCount);
135
136 // IMPORTANT: render via the sequencer, not directly via the synth.
137 _sequencer.Render(leftSpan, rightSpan);
138
139 bool silent = true;
140
141 for (int i = 0; i < renderCount; i++)
142 {
143 float left = leftSpan[i];
144 float right = rightSpan[i];
145
146 buffer[offset++] = left;
147 buffer[offset++] = right;
148
149 if (Math.Abs(left) > Tolerance || Math.Abs(right) > Tolerance)
150 silent = false;
151 }
152
153 framesRendered += renderCount;
154
155 if (silent)
156 {
157 if (_loop)
158 {
159 _sequencer.Play(_midiFile, loop: false); // manual restart
160 }
161 else
162 {
163 break;
164 }
165 }
166 }
167
168 return framesRendered * 2;
169 }
170
171 private readonly float[] _seekLeft = new float[512];
172 private readonly float[] _seekRight = new float[512];
173
205 public void Seek(TimeSpan time)
206 {
207 _synthesizer.Reset();
208 _sequencer.Stop();
209 _sequencer.Play(_midiFile, loop: false);
210
211 int framesToSkip = (int)(time.TotalSeconds * _synthesizer.SampleRate);
212
213 while (framesToSkip > 0)
214 {
215 int chunk = Math.Min(framesToSkip, _seekLeft.Length);
216
217 // IMPORTANT: seek-forward must also advance through the sequencer.
218 _sequencer.Render(_seekLeft.AsSpan(0, chunk), _seekRight.AsSpan(0, chunk));
219
220 framesToSkip -= chunk;
221 }
222 }
223}
WaveFormat WaveFormat
Gets the wave format of the synthesized audio output.
void Seek(TimeSpan time)
Seeks to a specific time position in the MIDI playback by resetting the synthesizer and fast-forwardi...
int Read(float[] buffer, int offset, int count)
Reads synthesized audio samples into the provided buffer.
SynthesizerSampleProvider(MidiFileSequencer sequencer, Synthesizer synth, MeltySynth.MidiFile midiFile, bool loop)
Initializes a new instance of the SynthesizerSampleProvider class with the specified MIDI sequencer,...