Skip to content

Instantly share code, notes, and snippets.

@martinpi
Created December 3, 2025 09:18
Show Gist options
  • Select an option

  • Save martinpi/e632dc4d073c3167aab370903c8faa5f to your computer and use it in GitHub Desktop.

Select an option

Save martinpi/e632dc4d073c3167aab370903c8faa5f to your computer and use it in GitHub Desktop.
Playing back MIDI in Godot .NET using MeltySynth
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