-
Notifications
You must be signed in to change notification settings - Fork 47
Add sf2 (soundfont) support to wildmidi #270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
b39488d
initial sf2 support
psi29a 76b4d8a
fix building on *bsd; add git
psi29a 418d477
coderabbit advice
psi29a 164b71b
critical rendering fix to prevent OOM (mix-buffer growth; realloc-int…
psi29a 1e8d0d0
add tsf in-tree
psi29a 49a0c49
CR event-pool propagation fixes
psi29a 4147d43
purge sf2 tests
psi29a 92d460a
dos2unix tsf.h
sezero 4c5d0a3
eliminate a c99'ism in tsf.h
sezero 395dd4e
bigenedian block for sf2 support; overflow fix for 32-bit; realloc g…
psi29a 9db1e54
do things the right way
psi29a 74d6b85
terminal-event slot fix
psi29a 3ab0b17
internal_midi.c: handle possible absence of SIZE_MAX macro
sezero 9998fc4
tsf.h: fix wrong preprocessor check for TSF_SQRTF.
sezero e073243
tsf.h: do not use addressof operator on arrays.
sezero 36a3a9d
sf2 support: handle possible absence of powf, expf and/or sqrtf.
sezero b1301bd
internal_midi.c: include limits.h
sezero d2c7d00
sf2: big endian support.
sezero a5a4349
tsf.h: fixed "initializer element is not constant" errors from old gcc
sezero 74fb978
bump to 0.5.0
psi29a File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| # SoundFont2 (SF2) Support | ||
|
|
||
| Implementation plan and design notes for [issue #8](https://github.com/Mindwerks/wildmidi/issues/8). | ||
|
|
||
| ## Approach | ||
|
|
||
| WildMIDI gains SF2 rendering via [TinySoundFont](https://github.com/schellingb/TinySoundFont) | ||
| (`tsf.h`, MIT licensed, single-header, pure C), fetched at configure time with CMake | ||
| `FetchContent` and pinned to a known-good commit. | ||
|
|
||
| The key insight: WildMIDI's format parsers (MID, XMI, MUS, HMP, HMI) already convert | ||
| everything into a single time-stamped event list (`struct _event`), and the renderer is | ||
| just the stage that consumes that list. SF2 support swaps the *rendering* stage while | ||
| keeping every parser, the timing engine, and the public API untouched. No other MIDI | ||
| library renders XMI/MUS/HMP/HMI through SoundFonts — this is WildMIDI's niche. | ||
|
|
||
| TinySoundFont's common criticism — "you must do your own MIDI parsing" — is exactly | ||
| what makes it the right fit here: its `tsf_channel_*` API maps 1:1 onto WildMIDI's | ||
| already-decoded event types. | ||
|
|
||
| ## How it works | ||
|
|
||
| 1. **Loading** — `WildMidi_Init()` / `WildMidi_InitVIO()` sniff the config file for the | ||
| RIFF/`sfbk` magic (not the file extension). An `.sf2` file given directly as the | ||
| "config file" is loaded through the existing VIO buffer callbacks into one global | ||
| `tsf*` instance. Additionally, the timidity.cfg parser understands a | ||
| `soundfont <file>` directive (relative paths resolved against the config dir, same | ||
| as `source`), so `/etc/timidity/fluidr3_gm.cfg` works as-is. | ||
| 2. **Per-song synth** — each opened midi (`struct _mdi`) gets its own `tsf_copy()` of | ||
| the global instance (sample data shared, voices private), created in `_WM_initMDI()` | ||
| and freed in `_WM_freeMDI()`. Concurrent handles stay safe. | ||
| 3. **Rendering** — `WM_GetOutput_SF2()` mirrors the scheduling loop of | ||
| `WM_GetOutput_Linear()`. Every event still runs its normal `do_event` (tempo, lyrics, | ||
| channel state; GUS note-ons no-op safely since no GUS patches are loaded) and is | ||
| additionally translated to the corresponding `tsf_channel_*` call. Audio is rendered | ||
| by `tsf_render_short()` into the 32-bit mix buffer, so `WM_MO_REVERB` and the rest of | ||
| the output pipeline behave exactly like the GUS path. | ||
| 4. **Release tails** — when the event list ends, rendering continues while | ||
| `tsf_active_voice_count() > 0`, so SF2 release envelopes aren't clipped at | ||
| end-of-song. | ||
|
|
||
| ## Event translation | ||
|
|
||
| | WildMIDI event | TSF call | | ||
| |---|---| | ||
| | note on/off | `tsf_channel_note_on` / `_off` | | ||
| | patch change | `tsf_channel_set_presetnumber(..., isdrum)` | | ||
| | pitch wheel | `tsf_channel_set_pitchwheel` | | ||
| | all controllers (bank, volume, pan, expression, hold, RPN/NRPN, sound/notes off, …) | `tsf_channel_midi_control` (TSF implements the full CC map) | | ||
| | Roland drum-track sysex | `tsf_channel_set_bank_preset(ch, 128, 0)` | | ||
|
|
||
| Channel 9 defaults to bank 128 (drums) at synth creation, per GM convention. | ||
|
|
||
| ## Build | ||
|
|
||
| * `WANT_SF2` CMake option, default `ON`; requires CMake >= 3.14 (auto-disables with a | ||
| warning on older CMake so legacy platforms keep building). | ||
| * Offline/distro builds: point `FETCHCONTENT_SOURCE_DIR_TINYSOUNDFONT` at a local | ||
| checkout — standard CMake, no extra plumbing. | ||
| * License: `tsf.h` is MIT, compatible with linking into the LGPLv3 library. | ||
|
|
||
| ## Non-goals (first pass) | ||
|
|
||
| * Mixing GUS patches and SF2 in one config — if a soundfont is loaded it takes over | ||
| rendering entirely. | ||
| * SF3 (ogg-compressed) soundfonts — one `#define TSF_SF3` + stb_vorbis away if wanted. | ||
| * timidity per-soundfont options (`amp=`, `order=`, `remove`) — parsed token is the | ||
| filename only. | ||
| * `WildMidi_MasterVolume()` does not affect the SF2 path yet (TSF has its own gain). |
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| /* | ||
| * sf2.h -- SoundFont2 rendering via TinySoundFont | ||
| * | ||
| * Copyright (C) WildMIDI Developers 2026 | ||
| * | ||
| * This file is part of WildMIDI. | ||
| * | ||
| * WildMIDI is free software: you can redistribute and/or modify the player | ||
| * under the terms of the GNU General Public License and you can redistribute | ||
| * and/or modify the library under the terms of the GNU Lesser General Public | ||
| * License as published by the Free Software Foundation, either version 3 of | ||
| * the licenses, or(at your option) any later version. | ||
| * | ||
| * WildMIDI is distributed in the hope that it will be useful, but WITHOUT | ||
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License and | ||
| * the GNU Lesser General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU General Public License and the | ||
| * GNU Lesser General Public License along with WildMIDI. If not, see | ||
| * <http://www.gnu.org/licenses/>. | ||
| */ | ||
|
|
||
| #ifndef __SF2_H | ||
| #define __SF2_H | ||
|
|
||
| struct _mdi; | ||
| struct _event; | ||
|
|
||
| /* returns 1 if the buffer looks like a RIFF sfbk (SoundFont2) file */ | ||
| extern int _WM_SF2_Magic(const uint8_t *data, uint32_t size); | ||
|
|
||
| /* load/free the global soundfont instance */ | ||
| extern int _WM_SF2_Load(const uint8_t *data, uint32_t size); | ||
| extern void _WM_SF2_Unload(void); | ||
| extern int _WM_SF2_Active(void); | ||
|
|
||
| /* per-mdi synth instances (voices private, sample data shared) */ | ||
| extern void *_WM_SF2_NewSynth(uint16_t rate); | ||
| extern void _WM_SF2_FreeSynth(void *synth); | ||
| extern void _WM_SF2_Reset(void *synth); | ||
|
|
||
| /* translate a wildmidi event to the synth */ | ||
| extern void _WM_SF2_Event(void *synth, struct _mdi *mdi, struct _event *event); | ||
|
|
||
| /* returns nonzero while notes are still sounding (release tails) */ | ||
| extern int _WM_SF2_ActiveVoices(void *synth); | ||
|
|
||
| /* render stereo frames into the 32bit mix buffer */ | ||
| extern void _WM_SF2_Render(void *synth, int32_t *out, uint32_t frames); | ||
|
|
||
| #endif /* __SF2_H */ |
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
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
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.