Created
December 3, 2025 09:18
-
-
Save martinpi/e632dc4d073c3167aab370903c8faa5f to your computer and use it in GitHub Desktop.
Playing back MIDI in Godot .NET using MeltySynth
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using Godot; | |
| using MeltySynth; | |
| public partial class MidiRenderer : Node { | |
| [Export] public AudioStreamPlayer Player { get; set; } = default!; | |
| private int _sampleRate = 44100; | |
| private int _duration = 3; | |
| private byte[] _buffer = default!; | |
| public static byte[] GetSamplesWaveData(float[] left, float[] right, int samplesCount) { | |
| var pcm = new byte[samplesCount * 4]; | |
| int sampleIndex = 0, | |
| pcmIndex = 0; | |
| while (sampleIndex < samplesCount) { | |
| var sampleLeft = (short)(left[sampleIndex] * short.MaxValue); | |
| var sampleRight = (short)(right[sampleIndex] * short.MaxValue); | |
| pcm[pcmIndex] = (byte)(sampleLeft & 0xff); | |
| pcm[pcmIndex + 1] = (byte)((sampleLeft >> 8) & 0xff); | |
| pcm[pcmIndex + 2] = (byte)(sampleRight & 0xff); | |
| pcm[pcmIndex + 3] = (byte)((sampleRight >> 8) & 0xff); | |
| sampleIndex++; | |
| pcmIndex += 2 * 2; | |
| } | |
| return pcm; | |
| } | |
| public void RenderSamples() { | |
| // Create the synthesizer. | |
| var soundFont = ProjectSettings.GlobalizePath("res://assets/midi/BeepComp.sf2"); | |
| var synthesizer = new Synthesizer(soundFont, _sampleRate); | |
| // Play some notes (middle C, E, G). | |
| synthesizer.NoteOn(0, 60, 100); | |
| synthesizer.NoteOn(0, 64, 100); | |
| synthesizer.NoteOn(0, 67, 100); | |
| // The output buffer (3 seconds). | |
| var left = new float[_duration * _sampleRate]; | |
| var right = new float[_duration * _sampleRate]; | |
| // Render the waveform. | |
| synthesizer.Render(left, right); | |
| _buffer = GetSamplesWaveData(left, right, _duration * _sampleRate); | |
| } | |
| public void LoadSamples(string file) { | |
| var soundFont = ProjectSettings.GlobalizePath("res://assets/midi/BeepComp.sf2"); | |
| var path = ProjectSettings.GlobalizePath(file); | |
| var synthesizer = new Synthesizer(soundFont, _sampleRate); | |
| var midiFile = new MidiFile(path); | |
| var sequencer = new MidiFileSequencer(synthesizer); | |
| sequencer.Play(midiFile, false); | |
| // The output buffer. | |
| var left = new float[(int)(_sampleRate * midiFile.Length.TotalSeconds)]; | |
| var right = new float[(int)(_sampleRate * midiFile.Length.TotalSeconds)]; | |
| sequencer.Render(left, right); | |
| _buffer = GetSamplesWaveData(left, right, (int)(midiFile.Length.TotalSeconds * _sampleRate)); | |
| } | |
| public override void _Ready() { | |
| if (Player.Stream is AudioStreamWav wavPlayer) // Type as a generator to access MixRate. | |
| { | |
| wavPlayer.MixRate = _sampleRate; | |
| wavPlayer.Format = AudioStreamWav.FormatEnum.Format16Bits; | |
| wavPlayer.Data = _buffer; | |
| Player.Play(); | |
| } | |
| } | |
| public override void _EnterTree() { | |
| // RenderSamples(); | |
| LoadSamples("res://assets/midi/Star-Wars-Theme.mid"); | |
| base._EnterTree(); | |
| } | |
| public override void _ExitTree() { | |
| Player.Stop(); | |
| base._ExitTree(); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment