MIDI + SF2 support for HashLink, with Heaps integration.
This library is built around:
midifilefor MIDI parsingTinySoundFontfor SF2 synthesis
It gives you two main playback paths:
- accurate rendered playback: MIDI -> PCM via SF2
- Windows system MIDI playback: MIDI -> OS synth
The accurate path is the recommended one.
- decode
.mid/.midiwith.sf2into PCM16 or float PCM - set one default SoundFont and reuse it everywhere
- use MIDI files as
hxd.res.Soundin Heaps - probe MIDI and SoundFont files before decoding
- optional Windows system MIDI playback helpers
- MIDI:
mid,midi - SoundFont:
sf2
Helpers:
midisf2.format.MidiFormatmidisf2.format.SoundFontFormat
The main entry point is midisf2.Midi.
Useful methods:
setDefaultSoundFont(bytes)setDefaultSoundFontFromFile(path)trySetDefaultSoundFontFromFile(path)clearDefaultSoundFont()hasDefaultSoundFont()getDefaultSoundFontPath()probeMidi(bytes)probeSoundFont(bytes)preparePlayback(midiBytes, ?soundFontBytes, ?format, ?loop, ?fileNameHint)preparePlaybackFromFile(path, ?soundFontBytes, ?format, ?loop)preparePlaybackPCM16(midiBytes, ?soundFontBytes, ?loop, ?fileNameHint)preparePlaybackPCMFloat(midiBytes, ?soundFontBytes, ?loop, ?fileNameHint)preparePlaybackPCM16FromFile(path, ?soundFontBytes, ?loop)preparePlaybackPCMFloatFromFile(path, ?soundFontBytes, ?loop)decodeToPCM16(midiBytes, ?soundFontBytes)decodeToPCMFloat(midiBytes, ?soundFontBytes)describeLastError()
Windows-only system playback helpers:
isSystemPlaybackSupported()playWithSystemSynth(path, ?loop)playBytesWithSystemSynth(bytes, ?loop, ?fileNameHint)stopSystemSynth()isSystemSynthPlaying()
Decode with an explicit SoundFont:
final midiBytes = sys.io.File.getBytes("music.mid");
final sf2Bytes = sys.io.File.getBytes("soundfont.sf2");
final decoded = midisf2.Midi.decodeToPCMFloat(midiBytes, sf2Bytes);
if (decoded == null)
throw midisf2.Midi.describeLastError();Set a default SoundFont once:
midisf2.Midi.setDefaultSoundFontFromFile("soundfont.sf2");
final decoded = midisf2.Midi.decodeToPCM16(sys.io.File.getBytes("music.mid"));
if (decoded == null)
throw midisf2.Midi.describeLastError();decodeToPCM16() and decodeToPCMFloat() return:
{
bytes:haxe.io.Bytes,
channels:Int,
sampleRate:Int,
samples:Int,
floatFormat:Bool,
}If Heaps is present and midisf2.Boot.setup() is enabled, .mid and .midi resources are registered as hxd.res.Sound.
Minimal setup:
// optional: with SoundFont set, MIDI is rendered through SF2
midisf2.Midi.setDefaultSoundFontFromFile("soundfont.sf2");
final sound = hxd.Res.music; // midi
final channel = sound.play();What happens internally:
midisf2.Macrohookshxd.res.Sound- MIDI files are detected through
midisf2.format.MidiFormat - Heaps sound data is provided by
hxd.snd.MidiData
Important:
- if a default SoundFont is set, Heaps playback uses rendered MIDI + SF2
- if no default SoundFont is set and system MIDI playback is available,
sound.play()falls back to the OS synth - direct
hxd.snd.MidiDatausage still requires a SoundFont because it produces PCM data
For non-Heaps HashLink code, use the unified prepare helpers:
final prepared = midisf2.Midi.preparePlaybackFromFile("music.mid");
if (prepared == null)
throw midisf2.Midi.describeLastError();
switch (prepared) {
case System:
// playing through the OS synth already started
case Rendered(decoded):
// feed decoded.bytes / decoded.channels / decoded.sampleRate into your audio backend
}Use midisf2.PlaybackFormat.PCMFloat when your backend prefers float PCM.
Behavior:
- if a SoundFont is passed or configured by default, you get decoded PCM data
- if no SoundFont is available and system MIDI playback is supported, the OS synth starts automatically
- if neither path is available, the call returns
nullanddescribeLastError()explains why
There is also a Windows system MIDI path:
if (midisf2.Midi.isSystemPlaybackSupported())
midisf2.Midi.playWithSystemSynth("music.mid");This path uses the OS synthesizer and is not the reference playback path.
Use it when you specifically want system MIDI behavior. Use the SF2-rendered path when you want consistent output.
Dependencies are fetched automatically by CMake:
midifileTinySoundFont
Requirements:
- CMake 3.10+
- Ninja
- MSVC build tools
HASHLINKenvironment variable pointing to your HashLink installation
Build:
cmake --preset release
cmake --build --preset releaseOutputs:
midisf2.hdllmidisf2.libon Windows
Place midisf2.hdll next to your .hl output, or otherwise make sure HashLink can load it.
Test launchers:
tests\test-miniaudio.battests\test-openal.battests\test-heaps.battests\run-tests.bat
Test assets:
- MIDI fixture:
tests\testMain\midi\test.mid - SoundFont folder:
tests\testMain\sf2\
By default the sf2 folder is empty.
Put a .sf2 file there before running SoundFont-based playback tests.
Current test coverage includes:
- MIDI probing
- invalid input handling
- missing SoundFont handling
- Windows system MIDI playback sequencing
- rendered MIDI + SF2 playback through Miniaudio
- rendered MIDI + SF2 playback through OpenAL
- rendered MIDI + SF2 playback through Heaps sound
- rendered output is stereo, 48 kHz
- a tail is rendered after the last MIDI event to preserve note releases
- accurate playback is the MIDI + SF2 path
- system MIDI playback is a convenience path and may differ from SF2 rendering
- Linux support
- macOS support
- Linux system MIDI playback support
- macOS system MIDI playback support