SDL_mixer: initial soundfont support

From 359465af9ece4a38eddc2cda7c6e377e3be3184a Mon Sep 17 00:00:00 2001
From: Ozkan Sezer <[EMAIL REDACTED]>
Date: Thu, 31 Jul 2025 05:56:10 +0300
Subject: [PATCH] initial soundfont support

based on tim0.2i+tr+sfp1.diff for timidity-0.2i from 1997, authored by Takashi Iwai.
---
 CMakeLists.txt          |    2 +
 src/timidity/README.sf  |   70 +++
 src/timidity/instrum.c  |   18 +
 src/timidity/playmidi.c |  140 +++---
 src/timidity/readsbk.c  |  440 +++++++++++++++++
 src/timidity/sbk.h      |   96 ++++
 src/timidity/sflayer.h  |   78 +++
 src/timidity/sndfont.c  | 1018 +++++++++++++++++++++++++++++++++++++++
 src/timidity/sndfont.h  |   19 +
 src/timidity/timidity.c |   75 ++-
 src/timidity/timidity.h |    8 +
 11 files changed, 1883 insertions(+), 81 deletions(-)
 create mode 100644 src/timidity/README.sf
 create mode 100644 src/timidity/readsbk.c
 create mode 100644 src/timidity/sbk.h
 create mode 100644 src/timidity/sflayer.h
 create mode 100644 src/timidity/sndfont.c
 create mode 100644 src/timidity/sndfont.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1f1b7d57..82e1ce9b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -989,6 +989,8 @@ if(SDLMIXER_MIDI_TIMIDITY)
         src/timidity/playmidi.c
         src/timidity/readmidi.c
         src/timidity/resample.c
+        src/timidity/readsbk.c
+        src/timidity/sndfont.c
         src/timidity/tables.c
         src/timidity/timidity.c
     )
diff --git a/src/timidity/README.sf b/src/timidity/README.sf
new file mode 100644
index 00000000..0c222f10
--- /dev/null
+++ b/src/timidity/README.sf
@@ -0,0 +1,70 @@
+================================================================
+	** Timidity SoundFont Extension **
+
+	    written by Takashi Iwai
+		<iwai@dragon.mm.t.u-tokyo.ac.jp>
+		<http://bahamut.mm.t.u-tokyo.ac.jp/~iwai/>
+
+	patch level 1: April 2, 1997
+================================================================
+
+* WHAT'S THIS?
+
+This is an extension to use samples in SoundFont files with
+timidity-0.2i.  You can employ both SoundFont file together with
+ordinary GUS patch files.  Both SBK and SF2 formats are supported.
+
+
+* USAGE
+
+Two commands are newly added in configuration.
+
+To specify the SoundFont file to be used, just add a line in config
+file like:
+
+	 soundfont sffile [order=number]
+
+The first parameter is the file name to be loaded.  The file itself
+is stored once after reading all configurations, then converted to
+the internal records except wave sample data.
+
+The optional argument specifies the order of searching.
+'order=0' means to search the SoundFont file at first, then search
+the GUS patches if the appropriate sample is not found.
+'order=1' means to search the SoundFont file after GUS patches.
+
+Another command 'font' is supplied to control the behavior of sample
+selection.  If you don't want to use some samples in the SoundFont
+file, specify the sample via 'exclude' sub-command.
+
+	font exclude bank [preset [keynote]]
+
+The first parameter is MIDI bank number of the sample to be removed.
+The optional second parameter is MIDI program number of the sample.
+For drum samples, specify 128 as bank, and drumset number as preset,
+and keynote number for the drum sample.
+
+You can change the order of individual sample (or bank) via "order"
+sub-command.
+
+	font order number bank [preset [keynote]]
+
+The first parameter is the order number (zero or one) to be changed,
+and the sequent parameters are as well as in exclude command above.
+
+
+* BUGS & TODO'S
+
+- noises on some bass drum samples
+- support of modulation envelope
+- support of cut off / resonance
+- support of chorus / reverb
+
+
+* CHANGES
+
+- pl.1
+	+ fix volume envelope calcuation
+	+ add font command
+	+ fix font-exclude control
+
diff --git a/src/timidity/instrum.c b/src/timidity/instrum.c
index cce6dcd2..77b80880 100644
--- a/src/timidity/instrum.c
+++ b/src/timidity/instrum.c
@@ -18,6 +18,7 @@
 #include "options.h"
 #include "common.h"
 #include "instrum.h"
+#include "sndfont.h"
 #include "resample.h"
 #include "tables.h"
 
@@ -209,6 +210,8 @@ static void load_instrument(MidiSong *song, const char *name,
   ip = *out;
   if (!ip) goto nomem;
 
+  ip->type = INST_GUS;
+
   ip->samples = tmp[198];
   ip->sample = SDL_malloc(sizeof(Sample) * ip->samples);
   if (!ip->sample) goto nomem;
@@ -543,6 +546,14 @@ static int fill_bank(MidiSong *song, int dr, int b)
 	    }
 	  else
 	    {
+	      /* preload soundfont */
+	      bank->instrument[i] = load_soundfont(song, 0,
+							(dr)? 128 : b,
+							(dr)? b : i,
+							(dr)? i : -1);
+	      if (bank->instrument[i])
+		continue;
+	      /* try gus patch */
 	      load_instrument(song,
 				     bank->tone[i].name,
 				     &bank->instrument[i],
@@ -559,6 +570,13 @@ static int fill_bank(MidiSong *song, int dr, int b)
 				     bank->tone[i].strip_envelope :
 				     ((dr) ? 1 : -1),
 				     bank->tone[i].strip_tail);
+	      if (bank->instrument[i])
+		continue;
+	      /* no patch; search soundfont again. */
+	      bank->instrument[i] = load_soundfont(song, 1,
+							(dr)? 128 : b,
+							(dr)? b : i,
+							(dr)? i : -1);
 	      if (!bank->instrument[i]) {
 		SNDDBG(("Couldn't load instrument %s (%s %d, program %d)\n",
 		   bank->tone[i].name,
diff --git a/src/timidity/playmidi.c b/src/timidity/playmidi.c
index 7407ce70..f024f947 100644
--- a/src/timidity/playmidi.c
+++ b/src/timidity/playmidi.c
@@ -56,52 +56,6 @@ static void reset_midi(MidiSong *song)
   reset_voices(song);
 }
 
-static void select_sample(MidiSong *song, int v, Instrument *ip)
-{
-  Sint32 f, cdiff, diff;
-  int s,i;
-  Sample *sp, *closest;
-
-  s=ip->samples;
-  sp=ip->sample;
-
-  if (s==1)
-    {
-      song->voice[v].sample=sp;
-      return;
-    }
-
-  f=song->voice[v].orig_frequency;
-  for (i=0; i<s; i++, sp++)
-    {
-      if (sp->low_freq <= f && sp->high_freq >= f)
-	{
-	  song->voice[v].sample=sp;
-	  return;
-	}
-    }
-
-  /*
-     No suitable sample found! We'll select the sample whose root
-     frequency is closest to the one we want. (Actually we should
-     probably convert the low, high, and root frequencies to MIDI
-     note values and compare those.)
-   */
-  cdiff=0x7FFFFFFF;
-  closest=sp=ip->sample;
-  for(i=0; i<s; i++, sp++)
-    {
-      diff=sp->root_freq - f;
-      if (diff<0) diff=-diff;
-      if (diff<cdiff)
-	{
-	  cdiff=diff;
-	  closest=sp;
-	}
-    }
-  song->voice[v].sample=closest;
-}
-
 static void recompute_freq(MidiSong *song, int v)
 {
   int 
@@ -215,31 +169,28 @@ static void recompute_amp(MidiSong *song, int v)
     }
 }
 
-static void start_note(MidiSong *song, MidiEvent *e, int i)
+static int find_voice(MidiSong *song, MidiEvent *e);
+
+static int find_samples(MidiSong *song, MidiEvent *e, int *vlist)
 {
   Instrument *ip;
-  int j;
+  Sample *sp, *closest;
+  Sint32 f, cdiff, diff;
+  int i, nv, note;
 
   if (ISDRUMCHANNEL(song, e->channel))
     {
       if (!(ip=song->drumset[song->channel[e->channel].bank]->instrument[e->a]))
 	{
 	  if (!(ip=song->drumset[0]->instrument[e->a]))
-	    return; /* No instrument? Then we can't play. */
+	    return 0; /* No instrument? Then we can't play. */
 	}
-      if (ip->samples != 1)
+      if (ip->type == INST_GUS && ip->samples != 1)
 	{
 	  SNDDBG(("Strange: percussion instrument with %d samples!",
 		  ip->samples));
+	  return 0;
 	}
-
-      if (ip->sample->note_to_use) /* Do we have a fixed pitch? */
-	song->voice[i].orig_frequency = freq_table[(int)(ip->sample->note_to_use)];
-      else
-	song->voice[i].orig_frequency = freq_table[e->a & 0x7F];
-
-      /* drums are supposed to have only one sample */
-      song->voice[i].sample = ip->sample;
     }
   else
     {
@@ -249,16 +200,55 @@ static void start_note(MidiSong *song, MidiEvent *e, int i)
 		 instrument[song->channel[e->channel].program]))
 	{
 	  if (!(ip=song->tonebank[0]->instrument[song->channel[e->channel].program]))
-	    return; /* No instrument? Then we can't play. */
+	    return 0; /* No instrument? Then we can't play. */
+	}
+    }
+
+  if (ip->sample->note_to_use)
+    note = ip->sample->note_to_use;
+  else
+    note = e->a & 0x7f;
+  f = freq_table[note];
+
+  nv = 0;
+  for (i = 0, sp = ip->sample; i < ip->samples; i++, sp++)
+    {
+      if (sp->low_freq <= f && sp->high_freq >= f)
+	{
+	  vlist[nv] = find_voice(song, e);
+	  song->voice[vlist[nv]].orig_frequency = f;
+	  song->voice[vlist[nv]].sample = sp;
+	  nv++;
 	}
+    }
 
-      if (ip->sample->note_to_use) /* Fixed-pitch instrument? */
-	song->voice[i].orig_frequency = freq_table[(int)(ip->sample->note_to_use)];
-      else
-	song->voice[i].orig_frequency = freq_table[e->a & 0x7F];
-      select_sample(song, i, ip);
+  if (nv == 0)
+    {
+      cdiff = 0x7FFFFFFF;
+      closest = sp = ip->sample;
+      for (i = 0; i < ip->samples; i++, sp++)
+	{
+	  diff = sp->root_freq - f;
+	  if (diff < 0) diff = -diff;
+	  if (diff < cdiff)
+	    {
+	      cdiff = diff;
+	      closest = sp;
+	    }
+	}
+      vlist[nv] = find_voice(song, e);
+      song->voice[vlist[nv]].orig_frequency = f;
+      song->voice[vlist[nv]].sample = closest;
+      nv++;
     }
 
+  return nv;
+}
+
+static void start_note(MidiSong *song, MidiEvent *e, int i)
+{
+  int j;
+
   song->voice[i].status = VOICE_ON;
   song->voice[i].channel = e->channel;
   song->voice[i].note = e->a;
@@ -307,11 +297,10 @@ static void kill_note(MidiSong *song, int i)
 }
 
 /* Only one instance of a note can be playing on a single channel. */
-static void note_on(MidiSong *song)
+static int find_voice(MidiSong *song, MidiEvent *e)
 {
   int i = song->voices, lowest=-1;
   Sint32 lv=0x7FFFFFFF, v;
-  MidiEvent *e = song->current_event;
 
   while (i--)
     {
@@ -325,8 +314,7 @@ static void note_on(MidiSong *song)
   if (lowest != -1)
     {
       /* Found a free voice. */
-      start_note(song,e,lowest);
-      return;
+      return lowest;
     }
 
   /* Look for the decaying note with the lowest volume */
@@ -357,12 +345,25 @@ static void note_on(MidiSong *song)
 
       song->cut_notes++;
       song->voice[lowest].status=VOICE_FREE;
-      start_note(song,e,lowest);
+      return lowest;
     }
   else
     song->lost_notes++;
+  return 0;
 }
 
+static void note_on(MidiSong *song)
+{
+  MidiEvent *e = song->current_event;
+  int i, nv;
+  int vlist[32];
+
+  nv = find_samples(song, e, vlist);
+  for (i = 0; i < nv; i++)
+    start_note(song, e, vlist[i]);
+}
+
+
 static void finish_note(MidiSong *song, int i)
 {
   if (song->voice[i].sample->modes & MODES_ENVELOPE)
@@ -398,7 +399,6 @@ static void note_off(MidiSong *song)
 	  }
 	else
 	  finish_note(song, i);
-	return;
       }
 }
 
diff --git a/src/timidity/readsbk.c b/src/timidity/readsbk.c
new file mode 100644
index 00000000..3c66723b
--- /dev/null
+++ b/src/timidity/readsbk.c
@@ -0,0 +1,440 @@
+/*
+
+    TiMidity -- Experimental MIDI to WAVE converter
+    Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
+
+    readsbk.c: read soundfont file
+    Copyright (C) 1996,1997 Takashi Iwai
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the Perl Artistic License, available in COPYING.
+*/
+
+#include <SDL3/SDL.h>
+
+#include "timidity.h"
+#include "options.h"
+#include "common.h"
+#include "sbk.h"
+
+/*----------------------------------------------------------------
+ * function prototypes
+ *----------------------------------------------------------------*/
+
+#define NEW(type,nums)	(type*)SDL_calloc((nums), sizeof(type))
+
+static int READCHUNK(tchunk *vp, SDL_IOStream *io)
+{
+	if (SDL_ReadIO(io, vp, 8) != 8) return -1;
+	vp->size = SDL_Swap32LE(vp->size);
+	return 1;
+}
+
+static int READDW(Sint32 *vp, SDL_IOStream *io)
+{
+	if (SDL_ReadIO(io, vp, 4) != 4) return -1;
+	*vp = SDL_Swap32LE(*vp);
+	return 1;
+}
+
+static int READW(Uint16 *vp, SDL_IOStream *io)
+{
+	if (SDL_ReadIO(io, vp, 2) != 2) return -1;
+	*vp = SDL_Swap16LE(*vp);
+	return 1;
+}
+
+#define READSTR(var,io)	SDL_ReadIO(io, var, 20)
+#define READID(var,io)	SDL_ReadIO(io, var, 4)
+#define READB(var,io)	SDL_ReadIO(io, var, 1)
+#define SKIPB(io)	SDL_SeekIO(io, 1, SDL_IO_SEEK_CUR);
+#define SKIPW(io)	SDL_SeekIO(io, 2, SDL_IO_SEEK_CUR);
+#define SKIPDW(io)	SDL_SeekIO(io, 4, SDL_IO_SEEK_CUR);
+
+static int getchunk(char *id);
+static void process_chunk(int id, int s, SFInfo *sf, SDL_IOStream *io);
+static void load_sample_names(int size, SFInfo *sf, SDL_IOStream *io);
+static void load_preset_header(int size, SFInfo *sf, SDL_IOStream *io);
+static void load_inst_header(int size, SFInfo *sf, SDL_IOStream *io);
+static void load_bag(int size, SFInfo *sf, SDL_IOStream *io, int *totalp, Uint16 **bufp);
+static void load_gen(int size, SFInfo *sf, SDL_IOStream *io, int *totalp, tgenrec **bufp);
+static void load_sample_info(int size, SFInfo *sf, SDL_IOStream *io);
+
+
+enum {
+	/* level 0 */
+	UNKN_ID, RIFF_ID, LIST_ID,
+	/* level 1 */
+	INFO_ID, SDTA_ID, PDTA_ID,
+	/* info stuff */
+	IFIL_ID, ISNG_ID, IROM_ID, INAM_ID, IVER_ID, IPRD_ID, ICOP_ID,
+	/* sample data stuff */
+	SNAM_ID, SMPL_ID,
+	/* preset stuff */
+	PHDR_ID, PBAG_ID, PMOD_ID, PGEN_ID,
+	/* inst stuff */
+	INST_ID, IBAG_ID, IMOD_ID, IGEN_ID,
+	/* sample header */
+	SHDR_ID,
+};
+
+
+/*----------------------------------------------------------------
+ * debug routine
+ *----------------------------------------------------------------*/
+
+#if 0
+static void debugid(char *tag, char *p)
+{
+	char buf[5]; SDL_strlcpy(buf, p, 5);
+	SDL_Log("[%s:%s]", tag, buf);
+}
+
+static void debugname(char *tag, char *p)
+{
+	char buf[21]; SDL_strlcpy(buf, p, 21);
+	SDL_Log("[%s:%s]", tag, buf);
+}
+
+static void debugval(char *tag, int v)
+{
+	SDL_Log("[%s:%d]", tag, v);
+}
+#else
+#define debugid(t,s) /**/
+#define debugname(t,s) /**/
+#define debugval(t,v) /**/
+#endif
+
+
+/*----------------------------------------------------------------
+ * load sbk file
+ *----------------------------------------------------------------*/
+
+void load_sbk(SDL_IOStream *io, SFInfo *sf)
+{
+	tchunk chunk, subchunk;
+
+	READID(sf->sbkh.riff, io);
+	READDW(&sf->sbkh.size, io);
+	READID(sf->sbkh.sfbk, io);
+
+	sf->in_rom = 1;
+	while (SDL_GetIOStatus(io) != SDL_IO_STATUS_EOF) {
+		READID(chunk.id, io);
+		switch (getchunk(chunk.id)) {
+		case LIST_ID:
+			READDW(&chunk.size, io);
+			READID(subchunk.id, io);
+			process_chunk(getchunk(subchunk.id), chunk.size - 4, sf, io);
+			break;
+		}
+	}
+}
+
+
+/*----------------------------------------------------------------
+ * free buffer
+ *----------------------------------------------------------------*/
+
+void free_sbk(SFInfo *sf)
+{
+	SDL_free(sf->samplenames);
+	SDL_free(sf->presethdr);
+	SDL_free(sf->sampleinfo);
+	SDL_free(sf->insthdr);
+	SDL_free(sf->presetbag);
+	SDL_free(sf->instbag);
+	SDL_free(sf->presetgen);
+	SDL_free(sf->instgen);
+	/*SDL_free(sf->sf_name);*/
+	SDL_memset(sf, 0, sizeof(*sf));
+}
+
+
+
+/*----------------------------------------------------------------
+ * get id value
+ *----------------------------------------------------------------*/
+
+static int getchunk(char *id)
+{
+	static struct idstring {
+		char *str;
+		int id;
+	} idlist[] = {
+		{"LIST", LIST_ID},
+		{"INFO", INFO_ID},
+		{"sdta", SDTA_ID},
+		{"snam", SNAM_ID},
+		{"smpl", SMPL_ID},
+		{"pdta", PDTA_ID},
+		{"phdr", PHDR_ID},
+		{"pbag", PBAG_ID},
+		{"pmod", PMOD_ID},
+		{"pgen", PGEN_ID},
+		{"inst", INST_ID},
+		{"ibag", IBAG_ID},
+		{"imod", IMOD_ID},
+		{"igen", IGEN_ID},
+		{"shdr", SHDR_ID},
+		{"ifil", IFIL_ID},
+		{"isng", ISNG_ID},
+		{"irom", IROM_ID},
+		{"iver", IVER_ID},
+		{"INAM", INAM_ID},
+		{"IPRD", IPRD_ID},
+		{"ICOP", ICOP_ID},
+	};
+
+	int i;
+
+	for (i = 0; i < sizeof(idlist)/sizeof(idlist[0]); i++) {
+		if (SDL_strncmp(id, idlist[i].str, 4) == 0) {
+			debugid("ok", id);
+			return idlist[i].id;
+		}
+	}
+
+	debugid("xx", id);
+	return UNKN_ID;
+}
+
+
+static void load_sample_names(int size, SFInfo *sf, SDL_IOStream *io)
+{
+	int i;
+	sf->nrsamples = size / 20;
+	sf->samplenames = NEW(tsamplenames, sf->nrsamples);
+	for (i = 0; i < sf->nrsamples; i++) {
+		READSTR(sf->samplenames[i].name, io);
+	}
+}
+
+static void load_preset_header(int size, SFInfo *sf, SDL_IOStream *io)
+{
+	int i;
+	sf->nrpresets = size / 38;
+	sf->presethdr = NEW(tpresethdr, sf->nrpresets);
+	for (i = 0; i < sf->nrpresets; i++) {
+		READSTR(sf->presethdr[i].name, io);
+		READW(&sf->presethdr[i].preset, io);
+		READW(&sf->presethdr[i].bank, io);
+		READW(&sf->presethdr[i].bagNdx, io);
+		SKIPDW(io); /* lib */
+		SKIPDW(io); /* genre */
+		SKIPDW(io); /* morph */
+	}
+}
+
+static void load_inst_header(int size, SFInfo *sf, SDL_IOStream *io)
+{
+	int i;
+
+	sf->nrinsts = size / 22;
+	sf->insthdr = NEW(tinsthdr, sf->nrinsts);
+	for (i = 0; i < sf->nrinsts; i++) {
+		READSTR(sf->insthdr[i].name, io);
+		READW(&sf->insthdr[i].bagNdx, io);
+	}
+}
+
+static void load_bag(int size, SFInfo *sf, SDL_IOStream *io, int *totalp, Uint16 **bufp)
+{
+	Uint16 *buf;
+	int i;
+
+	(void) sf;
+	debugval("bagsize", size);
+	size /= 4;
+	buf = NEW(Uint16, size);
+	for (i = 0; i < size; i++) {
+		READW(&buf[i], io);
+		SKIPW(io); /* mod */
+	}
+	*totalp = size;
+	*bufp = buf;
+}
+
+static void load_gen(int size, SFInfo *sf, SDL_IOStream *io, int *totalp, tgenrec **bufp)
+{
+	tgenrec *buf;
+	int i;
+
+	(void) sf;
+	debugval("gensize", size);
+	size /= 4;
+	buf = NEW(tgenrec, size);
+	for (i = 0; i < size; i++) {
+		READW(&buf[i].oper, io);
+		READW(&buf[i].amount, io);
+	}
+	*totalp = size;
+	*bufp = buf;
+}
+
+static void load_sample_info(int size, SFInfo *sf, SDL_IOStream *io)
+{
+	int i;
+
+	debugval("infosize", size);
+	if (sf->version > 1) {
+		sf->nrinfos = size / 46;
+		sf->nrsamples = sf->nrinfos;
+		sf->sampleinfo = NEW(tsampleinfo, sf->nrinfos);
+		sf->samplenames = NEW(tsamplenames, sf->nrsamples);
+	}
+	else  {
+		sf->nrinfos = size / 16;
+		sf->sampleinfo = NEW(tsampleinfo, sf->nrinfos);
+	}
+
+	for (i = 0; i < sf->nrinfos; i++) {
+		if (sf->version > 1)
+			READSTR(sf->samplenames[i].name, io);
+		READDW(&sf->sampleinfo[i].startsample, io);
+		READDW(&sf->sampleinfo[i].endsample, io);
+		READDW(&sf->sampleinfo[i].startloop, io);
+		READDW(&sf->sampleinfo[i].endloop, io);
+		if (sf->version > 1) {
+			READDW(&sf->sampleinfo[i].samplerate, io);
+			READB(&sf->sampleinfo[i].originalPitch, io);
+			READB(&sf->sampleinfo[i].pitchCorrection, io);
+			READW(&sf->sampleinfo[i].samplelink, io);
+			READW(&sf->sampleinfo[i].sampletype, io);
+		} else {
+			if (sf->sampleinfo[i].startsample == 0)
+				sf->in_rom = 0;
+			sf->sampleinfo[i].startloop++;
+			sf->sampleinfo[i].endloop += 2;
+			sf->sampleinfo[i].samplerate = 44100;
+			sf->sampleinfo[i].originalPitch = 60;
+			sf->sampleinfo[i].pitchCorrection = 0;
+			sf->sampleinfo[i].samplelink = 0;
+			if (sf->in_rom)
+				sf->sampleinfo[i].sampletype = 0x8001;
+			else
+				sf->sampleinfo[i].sampletype = 1;
+		}
+	}
+}
+
+static void process_chunk(int id, int s, SFInfo *sf, SDL_IOStream *io)
+{
+	int cid;
+	tchunk subchunk;
+
+	(void) s;
+
+	switch (id) {
+	case INFO_ID:
+		READCHUNK(&subchunk, io);
+		while ((cid = getchunk(subchunk.id)) != LIST_ID) {
+			switch (cid) {
+			case IFIL_ID:
+				READW(&sf->version, io);
+				READW(&sf->minorversion, io);
+				break;
+			/*
+			case INAM_ID:
+				sf->sf_name = (char*)SDL_malloc(subchunk.size);
+				if (sf->sf_name == NULL) {
+					SNDDBG(("can't malloc\n"));
+				}
+				SDL_ReadIO(io, sf->sf_name, subchunk.size);
+				break;
+			*/
+			default:
+				SDL_SeekIO(io, subchunk.size, SDL_IO_SEEK_CUR);
+				break;
+			}
+			READCHUNK(&subchunk, io);
+			if (SDL_GetIOStatus(io) == SDL_IO_STATUS_EOF)
+				return;
+		}
+		SDL_SeekIO(io, -8, SDL_IO_SEEK_CUR); /* seek back */
+		break;
+
+	case SDTA_ID:
+		READCHUNK(&subchunk, io);
+		while ((cid = getchunk(subchunk.id)) != LIST_ID) {
+			switch (cid) {
+			case SNAM_ID:
+				if (sf->version > 1) {
+					SNDDBG(("**** version 2 has obsolete format??\n"));
+					SDL_SeekIO(io, subchunk.size, SDL_IO_SEEK_CUR);
+				} else
+					load_sample_names(subchunk.size, sf, io);
+				break;
+			case SMPL_ID:
+				sf->samplepos = SDL_TellIO(io);
+				sf->samplesize = subchunk.size;
+				SDL_SeekIO(io, subchunk.size, SDL_IO_SEEK_CUR);
+			}
+			READCHUNK(&subchunk, io);
+			if (SDL_GetIOStatus(io) == SDL_IO_STATUS_EOF)
+				return;
+		}
+		SDL_SeekIO(io, -8, SDL_IO_SEEK_CUR); /* seek back */
+		break;
+
+	case PDTA_ID:
+		READCHUNK(&subchunk, io);
+		while ((cid = getchunk(subchunk.id)) != LIST_ID) {
+			switch (cid) {
+			case PHDR_ID:
+				load_preset_header(subchunk.size, sf, io);
+				break;
+
+			case PBAG_ID:
+				load_bag(subchunk.size, sf, io,
+					 &sf->nrpbags, &sf->presetbag);
+				break;
+
+			case PMOD_ID: /* ignored */
+				SDL_SeekIO(io, subchunk.size, SDL_IO_SEEK_CUR);
+				break;
+
+			case PGEN_ID:
+				load_gen(subchunk.size, sf, io,
+					 &sf->nrpgens, &sf->presetgen);
+				break;
+
+			case INST_ID:
+				load_inst_header(subchunk.size, sf, io);
+				break;
+
+			case IBAG_ID:
+				load_bag(subchunk.size, sf, io,
+					 &sf->nribags, &sf->instbag);
+				break;
+
+			case IMOD_ID: /* ingored */
+				SDL_SeekIO(io, subchunk.size, SDL_IO_SEEK_CUR);
+				break;
+
+			case IGEN_ID:
+				load_gen(subchunk.size, sf, io,
+					 &sf->nrigens, &sf->instgen);
+				break;
+
+			case SHDR_ID:
+				load_sample_info(subchunk.size, sf, io);
+				break;
+
+			default:
+				SNDDBG(("unknown id\n"));
+				SDL_SeekIO(io, subchunk.size, SDL_IO_SEEK_CUR);
+				break;
+			}
+			READCHUNK(&subchunk, io);
+			if (SDL_GetIOStatus(io) == SDL_IO_STATUS_EOF) {
+				debugid("file", "EOF");
+				return;
+			}
+		}
+		SDL_SeekIO(io, -8, SDL_IO_SEEK_CUR); /* rewind */
+		break;
+	}
+}
+
diff --git a/src/timidity/sbk.h b/src/timidity/sbk.h
new file mode 100644
index 00000000..a4ec7f48
--- /dev/null
+++ b/src/timidity/sbk.h
@@ -0,0 +1,96 @@
+/*
+
+    TiMidity -- Experimental MIDI to WAVE converter
+    Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
+
+    sbk.h: SoundFont(tm) file format
+    Copyright (C) 1996,1997 Takashi Iwai
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the Perl Artistic License, available in COPYING.
+*/
+
+#ifndef SBK_H_DEF
+#define SBK_H_DEF
+
+typedef struct _tchunk {
+	char id[4];
+	Sint32 size;
+} tchunk;
+
+typedef struct _tsbkheader {
+	char riff[4];	/* RIFF */
+	Sint32 size;	/* size of sbk after there bytes */
+	char sfbk[4];	/* sfbk id */
+} tsbkheader;
+
+typedef struct _tsamplenames {
+	char name[20];
+} tsamplenames;
+
+typedef struct _tpresethdr {
+	char name[20];
+	Uint16 preset, bank, bagNdx;
+	/*int lib, genre, morphology;*/ /* reserved */
+} tpresethdr;
+
+typedef struct _tsampleinfo {
+	Sint32 startsample, endsample;
+	Sint32 startloop, endloop;
+	/* ver.2 additional info */
+	Sint32 samplerate;
+	Uint8 originalPitch;
+	Uint8 pitchCorrection;
+	Uint16 samplelink;
+	Uint16 sampletype;  /*1=mono, 2=right, 4=left, 8=linked, $8000=ROM*/
+} tsampleinfo;
+
+typedef struct _tinsthdr {
+	char name[20];
+	Uint16 bagNdx;
+} tinsthdr;
+
+typedef struct _tgenrec {
+	Sint16 oper;
+	Sint16 amount;
+} tgenrec;
+
+
+typedef struct _SFInfo {
+	Uint16 version, minorversion;
+	Sint32 samplepos, samplesize;
+
+	int nrsamples;
+	tsamplenames *samplenames;
+
+	int nrpresets;
+	tpresethdr *presethdr;
+
+	int nrinfos;
+	tsampleinfo *sampleinfo;
+
+	int nrinsts;
+	tinsthdr *insthdr;
+
+	int nrpbags, nribags;
+	Uint16 *presetbag, *instbag;
+
+	int nrpgens, nrigens;
+	tgenrec *presetgen, *instgen;
+
+	tsbkheader sbkh;
+
+	/*char *sf_name;*/
+
+	int in_rom;
+} SFInfo;
+
+
+/*----------------------------------------------------------------
+ * functions
+ *----------------------------------------------------------------*/
+
+void load_sbk(SDL_IOStream *io, SFInfo *sf);
+void free_sbk(SFInfo *sf);
+
+#endif
diff --git a/src/timidity/sflayer.h b/src/timidity/sflayer.h
new file mode 100644
index 00000000..aa6dc854
--- /dev/null
+++ b/src/timidity/sflayer.h
@@ -0,0 +1,78 @@
+/*
+
+    TiMidity -- Experimental MIDI to WAVE converter
+    Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the Perl Artistic License, available in COPYING.
+*/
+
+#ifndef SFLAYER_H_DEF /* sflayer.h: SoundFont layer structure */
+#define SFLAYER_H_DEF
+
+enum {
+	SF_startAddrs,         /* sample start address -4 (0 to * 0xffffff) */
+        SF_endAddrs,
+        SF_startloopAddrs,     /* loop start address -4 (0 to * 0xffffff) */
+        SF_endloopAddrs,       /* loop end address -3 (0 to * 0xffffff) */
+        SF_startAddrsHi,       /* high word of startAddrs */
+        SF_lfo1ToPitch,        /* main fm: lfo1-> pitch */
+        SF_lfo2ToPitch,        /* aux fm:  lfo2-> pitch */
+        SF_env1ToPitch,        /* pitch env: env1(aux)-> pitch */
+        SF_initialFilterFc,    /* initial filter cutoff */
+        SF_initialFilterQ,     /* filter Q */
+        SF_lfo1ToFilterFc,     /* filter modulation: lfo1 -> filter * cutoff */
+        SF_env1ToFilterFc,     /* filter env: env1(aux)-> filter * cutoff */
+        SF_endAddrsHi,         /* high word of endAddrs */
+        SF_lfo1ToVolume,       /* tremolo: lfo1-> volume */
+        SF_env2ToVolume,       /* Env2Depth: env2-> volume */
+        SF_chorusEffectsSend,  /* chorus */
+        SF_reverbEffectsSend,  /* reverb */
+        SF_panEffectsSend,     /* pan */
+        SF_auxEffectsSend,     /* pan auxdata (internal) */
+        SF_sampleVolume,       /* used internally */
+        SF_unused3,
+        SF_delayLfo1,          /* delay 0x8000-n*(725us) */
+        SF_freqLfo1,           /* frequency */
+        SF_delayLfo2,          /* delay 0x8000-n*(725us) */
+        SF_freqLfo2,           /* frequency */
+        SF_delayEnv1,          /* delay 0x8000 - n(725us) */
+        SF_attackEnv1,         /* attack */
+        SF_holdEnv1,             /* hold */
+        SF_decayEnv1,            /* decay */
+        SF_sustainEnv1,          /* sustain */
+        SF_releaseEnv1,          /* release */
+        SF_autoHoldEnv1,
+        SF_autoDecayEnv1,
+        SF_delayEnv2,            /* delay 0x8000 - n(725us) */
+        SF_attackEnv2,           /* attack */
+        SF_holdEnv2,             /* hold */
+        SF_decayEnv2,            /* decay */
+        SF_sustainEnv2,          /* sustain */
+        SF_releaseEnv2,          /* release */
+        SF_autoHoldEnv2,
+        SF_autoDecayEnv2,
+        SF_instrument,           /* */
+        SF_nop,
+        SF_keyRange,             /* */
+        SF_velRange,             /* */
+        SF_startloopAddrsHi,     /* high word of startloopAddrs */
+        SF_keynum,               /* */
+        SF_velocity,             /* */
+        SF_instVol,              /* */
+        SF_keyTuning,
+        SF_endloopAddrsHi,       /* high word of endloopAddrs */
+        SF_coarseTune,
+        SF_fineTune,
+        SF_sampleId,
+        SF_sampleFlags,
+        SF_samplePitch,          /* SF1 only */
+        SF_scaleTuning,
+        SF_keyExclusiveClass,
+        SF_rootKey,
+	SF_EOF,
+};
+
+#define SFPARM_SIZE	SF_EOF
+
+#endif
diff --git a/src/timidity/sndfont.c b/src/timidity/sndfont.c
new file mode 100644
index 00000000..3a6cf03b
--- /dev/null
+++ b/src/timidity/sndfont.c
@@ -0,0 +1,1018 @@
+/*
+
+    TiMidity -- Experimental MIDI to WAVE converter
+    Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
+
+    sndfont.c: SoundFont file extension
+    written by Takashi Iwai <iwai@dragon.mm.t.u-tokyo.ac.jp>
+    Copyright (C) 1996,1997 Takashi Iwai
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the Perl Artistic License, available in COPYING.
+*/
+
+#include <SDL3/SDL.h>
+
+#include "timidity.h"
+#include "options.h"
+#include "common.h"
+#include "tables.h"
+#include "instrum.h"
+#include "sbk.h"
+#include "sflayer.h"
+#include "sndfont.h"
+#include "resample.h"
+
+/*----------------------------------------------------------------
+ * compile flags
+ *----------------------------------------------------------------*/
+
+/*#define SF_CLOSE_EACH_FILE*/
+
+/*#define SF_SUPPRESS_ENVELOPE*/
+/*#define SF_SUPPRESS_TREMOLO*/
+/*#define SF_SUPPRESS_VIBRATO*/
+#define SF_SUPPRESS_CUTOFF
+
+/*----------------------------------------------------------------
+ * local parameters
+ *----------------------------------------------------------------*/
+
+typedef struct _Layer {
+	Sint16 val[SFPARM_SIZE];
+	Sint8 set[SFPARM_SIZE];
+} Layer;
+
+typedef struct _SampleList {
+	Sample v;
+	struct _SampleList *next;
+	Sint32 startsample, endsample;
+	Sint32 cutoff_freq;
+	float resonance;
+} SampleList;
+
+typedef struct _InstList {
+	int bank, preset, keynote;
+	int samples;
+	int order;
+	SampleList *slist;
+	struct _InstList *next;
+} InstList;
+
+typedef struct SFInsts {
+	char *fname;
+	SDL_IOStream *io;
+	Uint16 version, minorversion;
+	Sint32 samplepos, samplesize;
+	InstList *instlist;
+} SFInsts;
+
+typedef struct _SFExclude {
+	int bank, preset, keynote;
+	struct _SFExclude *next;
+} SFExclude;
+
+typedef struct _SFOrder {
+	int bank, preset, keynote;
+	int order;
+	struct _SFOrder *next;
+} SFOrder;
+
+
+/*----------------------------------------------------------------*/
+
+static void free_sample(InstList *ip);
+static Instrument *load_from_file(MidiSong *song, SFInsts *rec, InstList *ip);
+static int is_excluded(int bank, int preset, int keynote);
+static void free_exclude(void);
+static int is_ordered(int bank, int preset, int keynote);
+static void free_order(void);
+static void parse_preset(MidiSong *song, SFInsts *rec, SFInfo *sf, int preset, int order);
+static void parse_gen(Layer *lay, tgenrec *gen);
+static void parse_preset_layer(Layer *lay, SFInfo *sf, int idx);
+#if 0 /* not used */
+static void merge_layer(Layer *dst, Layer *src);
+#endif
+static int search_inst(Layer *lay);
+static void parse_inst(MidiSong *song, SFInsts *rec, Layer *pr_lay, SFInfo *sf, int preset, int inst, int order);
+static void parse_inst_layer(Layer *lay, SFInfo *sf, int idx);
+static int search_sample(Layer *lay);
+static void append_layer(Layer *dst, Layer *src, SFInfo *sf);
+static void make_inst(MidiSong *song, SFInsts *rec, Layer *lay, SFInfo *sf, int pr_idx, int in_idx, int order);
+static Sint32 calc_root_pitch(Layer *lay, SFInfo *sf, SampleList *sp);
+#ifndef SF_SUPPRESS_ENVELOPE
+static void convert_volume_envelope(MidiSong *song, Layer *lay, SFInfo *sf, SampleList *sp);
+#endif
+static Sint32 to_offset(int offset);
+static Sint32 calc_rate(MidiSong *song, int diff, int time);
+static Sint32 to_msec(Layer *lay, SFInfo *sf, int index);
+static float calc_volume(Layer *lay, SFInfo *sf);
+static Sint32 calc_sustain(Layer *lay, SFInfo *sf);
+#ifndef SF_SUPPRESS_TREMOLO
+static void convert_tremolo(MidiSong *song, Layer *lay, SFInfo *sf, SampleList *sp);
+#endif
+#ifndef SF_SUPPRESS_VIBRATO
+static void convert_vibrato(MidiSong *song, Layer *lay, SFInfo *sf, SampleList *sp);
+#endif
+#ifndef SF_SUPPRESS_CUTOFF
+static void do_lowpass(Sample *sp, Sint32 freq, float resonance);
+#endif
+static void calc_cutoff(Layer *lay, SFInfo *sf, SampleList *sp);
+static void calc_filterQ(Layer *lay, SFInfo *sf, SampleList *sp);
+
+/*----------------------------------------------------------------*/
+
+
+static SFInsts sfrec;
+static SFExclude *sfexclude;
+static SFOrder *sforder;
+
+#ifndef SF_SUPPRESS_CUTOFF
+static const int cutoff_allowed = 0;
+#endif
+
+
+void init_soundfont(MidiSong *song, const char *fname, int order)
+{
+	static SFInfo sfinfo;
+	int i;
+
+	SNDDBG(("init soundfonts `%s'\n", fname));
+
+	if ((sfrec.io = timi_ope

(Patch may be truncated, please check the link at the top of this post.)