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.)