From 10c0d67b78395a9226957d8d785c4ac9bcb1d0ac Mon Sep 17 00:00:00 2001
From: Randy Heit <rheit@zdoom.fake>
Date: Fri, 11 Apr 2008 04:59:23 +0000
Subject: [PATCH] - Changed the MIDIStreamer to send the all notes off
 controller to each   channel when restarting the song, rather than emitting a
 single note off   event which only has a 1 in 127 chance of being for a note
 that's playing   on that channel. Then I decided it would probably be a good
 idea to reset   all the controllers as well. - Increasing the size of the
 internal Timidity stream buffer from 1/14 sec   (copied from the OPL player)
 improved its sound dramatically, so apparently   Timidity has issues with
 short stream buffers. It's now at 1/2 sec in   length. However, there seems
 to be something weird going on with   corazonazul_ff6boss.mid near the
 beginning where it stops and immediately   restarts a guitar on the exact
 same note. - Added a new sound debugging cvar: snd_drawoutput, which can show
 various   oscilloscopes and spectrums. - Internal TiMidity now plays music. -
 Changed the progdir global variable into an FString.

SVN r900 (trunk)
---
 Makefile.linux                          |    2 +-
 Makefile.mingw                          |    2 +-
 docs/rh-log.txt                         |   19 +
 src/cmdlib.cpp                          |    2 +-
 src/cmdlib.h                            |    2 +-
 src/d_main.cpp                          |    6 +
 src/oplsynth/music_opl_mididevice.cpp   |    2 +-
 src/s_sound.cpp                         |    3 +-
 src/sdl/i_main.cpp                      |   17 +-
 src/sound/fmodsound.cpp                 |  314 +++++
 src/sound/fmodsound.h                   |   14 +
 src/sound/i_music.cpp                   |   24 +-
 src/sound/i_musicinterns.h              |   65 +-
 src/sound/i_sound.cpp                   |    5 +
 src/sound/i_sound.h                     |    3 +
 src/sound/music_midi_base.cpp           |    2 +-
 src/sound/music_midi_midiout.cpp        |    4 +-
 src/sound/music_midistream.cpp          |   32 +-
 src/sound/music_mus_midiout.cpp         |    6 +-
 src/sound/music_timidity_mididevice.cpp |  519 ++++++++
 src/timidity/CHANGES                    |   58 +
 src/timidity/COPYING                    |  513 ++++++++
 src/timidity/FAQ                        |  100 ++
 src/timidity/README                     |   61 +
 src/timidity/common.cpp                 |  152 +++
 src/timidity/dls1.h                     |  266 ++++
 src/timidity/dls2.h                     |  130 ++
 src/timidity/instrum.cpp                |  952 ++++++++++++++
 src/timidity/instrum.obj                |  Bin 0 -> 20356 bytes
 src/timidity/instrum_dls.cpp            | 1260 ++++++++++++++++++
 src/timidity/mix.cpp                    |  584 +++++++++
 src/timidity/playmidi.cpp               | 1575 +++++++++++++++++++++++
 src/timidity/resample.cpp               |  608 +++++++++
 src/timidity/tables.cpp                 |  224 ++++
 src/timidity/timidity.cpp               |  513 ++++++++
 src/timidity/timidity.h                 |  604 +++++++++
 src/v_draw.cpp                          |   92 +-
 src/v_video.cpp                         |   49 -
 src/win32/i_main.cpp                    |   15 +-
 zdoom.vcproj                            |  120 ++
 40 files changed, 8807 insertions(+), 112 deletions(-)
 create mode 100644 src/sound/music_timidity_mididevice.cpp
 create mode 100644 src/timidity/CHANGES
 create mode 100644 src/timidity/COPYING
 create mode 100644 src/timidity/FAQ
 create mode 100644 src/timidity/README
 create mode 100644 src/timidity/common.cpp
 create mode 100644 src/timidity/dls1.h
 create mode 100644 src/timidity/dls2.h
 create mode 100644 src/timidity/instrum.cpp
 create mode 100644 src/timidity/instrum.obj
 create mode 100644 src/timidity/instrum_dls.cpp
 create mode 100644 src/timidity/mix.cpp
 create mode 100644 src/timidity/playmidi.cpp
 create mode 100644 src/timidity/resample.cpp
 create mode 100644 src/timidity/tables.cpp
 create mode 100644 src/timidity/timidity.cpp
 create mode 100644 src/timidity/timidity.h

diff --git a/Makefile.linux b/Makefile.linux
index 077c56d0e..9c62fad8a 100644
--- a/Makefile.linux
+++ b/Makefile.linux
@@ -20,7 +20,7 @@ CFLAGS += -Dstricmp=strcasecmp -Dstrnicmp=strncasecmp -DNEED_STRUPR
 LDFLAGS += -lz -ljpeg -`sdl-config --libs` `pkg-config gtk+-2.0 --libs` $(FMOD_PREFIX)/lib/libfmodex.so
 NASMFLAGS += -f elf -DM_TARGET_LINUX
 
-SRCDIRS = src/ $(addprefix src/,g_doom/ g_heretic/ g_hexen/ g_raven/ g_shared/ g_strife/ oplsynth/ sound/ sdl/ textures/ thingdef/ xlat/)
+SRCDIRS = src/ $(addprefix src/,g_doom/ g_heretic/ g_hexen/ g_raven/ g_shared/ g_strife/ oplsynth/ sound/ sdl/ textures/ thingdef/ xlat/ timidity/)
 VPATH = $(SRCDIRS)
 INCLUDES = $(addprefix -I,$(SRCDIRS))
 INCLUDES += -Isnes_spc/snes_spc/ -I$(FMOD_PREFIX)/include/fmodex/
diff --git a/Makefile.mingw b/Makefile.mingw
index 72210c8ba..bc414439b 100644
--- a/Makefile.mingw
+++ b/Makefile.mingw
@@ -56,7 +56,7 @@ ifeq ($(CONFIG),Release)
   TARGET = $(RELEASETARGET)
 endif
 
-SRCDIRS = src/ $(addprefix src/,g_doom/ g_heretic/ g_hexen/ g_raven/ g_shared/ g_strife/ oplsynth/ sound/ win32/ textures/ thingdef/)
+SRCDIRS = src/ $(addprefix src/,g_doom/ g_heretic/ g_hexen/ g_raven/ g_shared/ g_strife/ oplsynth/ sound/ win32/ textures/ thingdef/ timidity/)
 VPATH = $(SRCDIRS)
 
 CPPSRCS = $(wildcard $(addsuffix *.cpp,$(SRCDIRS))) src/xlat/parse_xlat.cpp
diff --git a/docs/rh-log.txt b/docs/rh-log.txt
index 37e70f28d..9b0b8a43d 100644
--- a/docs/rh-log.txt
+++ b/docs/rh-log.txt
@@ -1,7 +1,26 @@
+April 10, 2008
+- Changed the MIDIStreamer to send the all notes off controller to each
+  channel when restarting the song, rather than emitting a single note off
+  event which only has 1 in 127 chance of being for a note that's playing
+  on that channel. Then I decided it would probably be a good idea to reset
+  all the controllers as well.
+- Increasing the size of the internal Timidity stream buffer from 1/14 sec
+  (copied from the OPL player) improved its sound dramatically, so apparently
+  Timidity has issues with short stream buffers. It's now at 1/2 sec in
+  length. However, there seems to be something weird going on with
+  corazonazul_ff6boss.mid near the beginning where it stops and immediately
+  restarts a guitar on the exact same note.
+- Added a new sound debugging cvar: snd_drawoutput, which can show various
+  oscilloscopes and spectrums.
+
 April 10, 2008 (Changes by Graf Zahl)
 - Eliminated some more global variables (onmobj, DoRipping, LastRipped,
   MissileActor, bulletpitch and linetarget.)
 
+April 9, 2008
+- Internal TiMidity now plays music. Unfortunately, it doesn't sound right. :(
+- Changed the progdir global variable into an FString.
+
 April 9, 2008 (Changes by Graf Zahl)
 - Replaced P_PathTraverse with an FPathTraverse class, rewrote all code using
   P_PathTraverse and got rid of a lot of global variables in the process.
diff --git a/src/cmdlib.cpp b/src/cmdlib.cpp
index 686c5870a..e4df119a0 100644
--- a/src/cmdlib.cpp
+++ b/src/cmdlib.cpp
@@ -22,7 +22,7 @@ gamedir will hold progdir + the game directory (id1, id2, etc)
 
   */
 
-char progdir[1024];
+FString progdir;
 
 static inline bool IsSeperator (int c)
 {
diff --git a/src/cmdlib.h b/src/cmdlib.h
index af31bb264..12808a492 100644
--- a/src/cmdlib.h
+++ b/src/cmdlib.h
@@ -28,7 +28,7 @@
 int		Q_filelength (FILE *f);
 bool FileExists (const char *filename);
 
-extern	char	progdir[1024];
+extern	FString progdir;
 
 void	FixPathSeperator (char *path);
 static void	inline FixPathSeperator (FString &path) { path.ReplaceChars('\\', '/'); }
diff --git a/src/d_main.cpp b/src/d_main.cpp
index 660f274a2..d78b6dd58 100644
--- a/src/d_main.cpp
+++ b/src/d_main.cpp
@@ -179,6 +179,7 @@ CVAR (Float, timelimit, 0.f, CVAR_SERVERINFO);
 CVAR (Bool, queryiwad, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
 CVAR (String, defaultiwad, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
 CVAR (Int, wipetype, 1, CVAR_ARCHIVE);
+CVAR (Int, snd_drawoutput, 0, 0);
 
 bool DrawFSHUD;				// [RH] Draw fullscreen HUD?
 wadlist_t *wadfiles;		// [RH] remove limit on # of loaded wads
@@ -666,6 +667,11 @@ void D_Display ()
 		NoWipe = 10;
 	}
 
+	if (snd_drawoutput && GSnd != NULL)
+	{
+		GSnd->DrawWaveDebug(snd_drawoutput);
+	}
+
 	if (!wipe || NoWipe < 0)
 	{
 		NetUpdate ();			// send out any new accumulation
diff --git a/src/oplsynth/music_opl_mididevice.cpp b/src/oplsynth/music_opl_mididevice.cpp
index bfab365c8..a0e401151 100644
--- a/src/oplsynth/music_opl_mididevice.cpp
+++ b/src/oplsynth/music_opl_mididevice.cpp
@@ -512,5 +512,5 @@ void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2)
 bool OPLMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata)
 {
 	OPLMIDIDevice *device = (OPLMIDIDevice *)userdata;
-	return device->ServiceStream (buff, len);
+	return device->ServiceStream(buff, len);
 }
diff --git a/src/s_sound.cpp b/src/s_sound.cpp
index 055f23716..65710560a 100644
--- a/src/s_sound.cpp
+++ b/src/s_sound.cpp
@@ -50,6 +50,7 @@
 #include "gi.h"
 #include "templates.h"
 #include "zstring.h"
+#include "timidity/timidity.h"
 
 // MACROS ------------------------------------------------------------------
 
@@ -275,7 +276,7 @@ void S_Init ()
 	{
 		S_ReturnChannel(Channels);
 	}
-	
+
 	// no sounds are playing, and they are not paused
 	MusicPaused = false;
 	SoundPaused = false;
diff --git a/src/sdl/i_main.cpp b/src/sdl/i_main.cpp
index 336b303ce..8ecdf1143 100644
--- a/src/sdl/i_main.cpp
+++ b/src/sdl/i_main.cpp
@@ -244,13 +244,20 @@ int main (int argc, char **argv)
 		atexit (call_terms);
 		atterm (I_Quit);
 
-		if (realpath (argv[0], progdir) == NULL)
-			strcpy (progdir, argv[0]);
-		char *slash = strrchr (progdir, '/');
-		if (slash)
+		// Should we even be doing anything with progdir on Unix systems?
+		char program[PATH_MAX];
+		if (realpath (argv[0], program) == NULL)
+			strcpy (program, argv[0]);
+		char *slash = strrchr (program, '/');
+		if (slash != NULL)
+		{
 			*(slash + 1) = '\0';
+			progdir = program;
+		}
 		else
-			progdir[0] = '.', progdir[1] = '/', progdir[2] = '\0';
+		{
+			progdir = "./";
+		}
 
 		C_InitConsole (80*8, 25*8, false);
 		D_DoomMain ();
diff --git a/src/sound/fmodsound.cpp b/src/sound/fmodsound.cpp
index fd97d5145..75c4df13f 100644
--- a/src/sound/fmodsound.cpp
+++ b/src/sound/fmodsound.cpp
@@ -72,6 +72,8 @@ extern HWND Window;
 
 #define ERRCHECK(x)
 
+#define SPECTRUM_SIZE				256
+
 
 // TYPES -------------------------------------------------------------------
 
@@ -739,6 +741,23 @@ void FMODSoundRenderer::Shutdown()
 	}
 }
 
+//==========================================================================
+//
+// FMODSoundRenderer :: GetOutputRate
+//
+//==========================================================================
+
+float FMODSoundRenderer::GetOutputRate()
+{
+	int rate;
+
+	if (FMOD_OK == Sys->getSoftwareFormat(&rate, NULL, NULL, NULL, NULL, NULL))
+	{
+		return float(rate);
+	}
+	return 48000.f;		// Guess, but this should never happen.
+}
+
 //==========================================================================
 //
 // FMODSoundRenderer :: PrintStatus
@@ -1715,3 +1734,298 @@ float F_CALLBACK FMODSoundRenderer::RolloffCallback(FMOD_CHANNEL *channel, float
 		return (powf(10.f, volume) - 1.f) / 9.f;
 	}
 }
+
+//==========================================================================
+//
+// FMODSoundRenderer :: DrawWaveDebug
+//
+// Bit 0: ( 1) Show oscilloscope for sfx.
+// Bit 1: ( 2) Show spectrum for sfx.
+// Bit 2: ( 4) Show oscilloscope for music.
+// Bit 3: ( 8) Show spectrum for music.
+// Bit 4: (16) Show oscilloscope for all sounds.
+// Bit 5: (32) Show spectrum for all sounds.
+//
+//==========================================================================
+
+void FMODSoundRenderer::DrawWaveDebug(int mode)
+{
+	const int window_height = 100;
+	float wavearray[MAXWIDTH];
+	int window_size;
+	int numoutchans;
+	int y;
+
+	if (FMOD_OK != Sys->getSoftwareFormat(NULL, NULL, &numoutchans, NULL, NULL, NULL))
+	{
+		return;
+	}
+
+	// Scale all the channel windows so one group fits completely on one row, with
+	// 16 pixels of padding between each window.
+	window_size = (screen->GetWidth() - 16) / numoutchans - 16;
+
+	y = 16;
+	y = DrawChannelGroupOutput(SfxGroup, wavearray, window_size, window_height, y, mode);
+	y = DrawChannelGroupOutput(MusicGroup, wavearray, window_size, window_height, y, mode >> 2);
+	y = DrawSystemOutput(wavearray, window_size, window_height, y, mode >> 4);
+}
+
+//==========================================================================
+//
+// FMODSoundRenderer :: DrawChannelGroupOutput
+//
+// Draws an oscilloscope and/or a spectrum for a channel group.
+//
+//==========================================================================
+
+int FMODSoundRenderer::DrawChannelGroupOutput(FMOD::ChannelGroup *group, float *wavearray, int width, int height, int y, int mode)
+{
+	int y1, y2;
+
+	switch (mode & 0x03)
+	{
+	case 0x01:		// Oscilloscope only
+		return DrawChannelGroupWaveData(group, wavearray, width, height, y, false);
+
+	case 0x02:		// Spectrum only
+		return DrawChannelGroupSpectrum(group, wavearray, width, height, y, false);
+
+	case 0x03:		// Oscilloscope + Spectrum
+		width = (width + 16) / 2 - 16;
+		y1 = DrawChannelGroupSpectrum(group, wavearray, width, height, y, true);
+		y2 = DrawChannelGroupWaveData(group, wavearray, width, height, y, true);
+		return MAX(y1, y2);
+	}
+	return y;
+}
+
+//==========================================================================
+//
+// FMODSoundRenderer :: DrawSystemOutput
+//
+// Like DrawChannelGroupOutput(), but uses the system object.
+//
+//==========================================================================
+
+int FMODSoundRenderer::DrawSystemOutput(float *wavearray, int width, int height, int y, int mode)
+{
+	int y1, y2;
+
+	switch (mode & 0x03)
+	{
+	case 0x01:		// Oscilloscope only
+		return DrawSystemWaveData(wavearray, width, height, y, false);
+
+	case 0x02:		// Spectrum only
+		return DrawSystemSpectrum(wavearray, width, height, y, false);
+
+	case 0x03:		// Oscilloscope + Spectrum
+		width = (width + 16) / 2 - 16;
+		y1 = DrawSystemSpectrum(wavearray, width, height, y, true);
+		y2 = DrawSystemWaveData(wavearray, width, height, y, true);
+		return MAX(y1, y2);
+	}
+	return y;
+}
+
+//==========================================================================
+//
+// FMODSoundRenderer :: DrawChannelGroupWaveData
+//
+// Draws all the output channels for a specified channel group.
+// Setting skip to true causes it to skip every other window.
+//
+//==========================================================================
+
+int FMODSoundRenderer::DrawChannelGroupWaveData(FMOD::ChannelGroup *group, float *wavearray, int width, int height, int y, bool skip)
+{
+	int drawn = 0;
+	int x = 16;
+
+	while (FMOD_OK == group->getWaveData(wavearray, width, drawn))
+	{
+		drawn++;
+		DrawWave(wavearray, x, y, width, height);
+		x += (width + 16) << int(skip);
+	}
+	if (drawn)
+	{
+		y += height + 16;
+	}
+	return y;
+}
+
+//==========================================================================
+//
+// FMODSoundRenderer::DrawSystemWaveData
+//
+// Like DrawChannelGroupWaveData, but it uses the system object to get the
+// complete output.
+//
+//==========================================================================
+
+int FMODSoundRenderer::DrawSystemWaveData(float *wavearray, int width, int height, int y, bool skip)
+{
+	int drawn = 0;
+	int x = 16;
+
+	while (FMOD_OK == Sys->getWaveData(wavearray, width, drawn))
+	{
+		drawn++;
+		DrawWave(wavearray, x, y, width, height);
+		x += (width + 16) << int(skip);
+	}
+	if (drawn)
+	{
+		y += height + 16;
+	}
+	return y;
+}
+
+//==========================================================================
+//
+// FMODSoundRenderer :: DrawWave
+//
+// Draws an oscilloscope at the specified coordinates on the screen. Each
+// entry in the wavearray buffer has its own column. IOW, there are <width>
+// entries in wavearray.
+//
+//==========================================================================
+
+void FMODSoundRenderer::DrawWave(float *wavearray, int x, int y, int width, int height)
+{
+	float scale = height / 2.f;
+	float mid = y + scale;
+	int i;
+
+	// Draw a box around the oscilloscope.
+	screen->DrawLine(x - 1, y - 1, x + width, y - 1, -1, MAKEARGB(160, 0, 40, 200));
+	screen->DrawLine(x + width + 1, y - 1, x + width, y + height, -1, MAKEARGB(160, 0, 40, 200));
+	screen->DrawLine(x + width, y + height, x - 1, y + height, -1, MAKEARGB(160, 0, 40, 200));
+	screen->DrawLine(x - 1, y + height, x - 1, y - 1, -1, MAKEARGB(160, 0, 40, 200));
+
+	// Draw the actual oscilloscope.
+	if (screen->Accel2D)
+	{ // Drawing this with lines is super-slow without hardware acceleration, at least with
+	  // the debug build.
+		float lasty = wavearray[0] * scale + mid;
+		float newy;
+		for (i = 1; i < width; ++i)
+		{
+			newy = wavearray[i] * scale + mid;
+			screen->DrawLine(x + i - 1, int(lasty), x + i, int(newy), -1, MAKEARGB(255,255,248,248));
+			lasty = newy;
+		}
+	}
+	else
+	{
+		for (i = 0; i < width; ++i)
+		{
+			float y = wavearray[i] * scale + mid;
+			screen->DrawPixel(x + i, int(y), -1, MAKEARGB(255,255,255,255));
+		}
+	}
+}
+
+//==========================================================================
+//
+// FMODSoundRenderer :: DrawChannelGroupSpectrum
+//
+// Draws all the spectrum for a specified channel group.
+// Setting skip to true causes it to skip every other window, starting at
+// the second one.
+//
+//==========================================================================
+
+int FMODSoundRenderer::DrawChannelGroupSpectrum(FMOD::ChannelGroup *group, float *spectrumarray, int width, int height, int y, bool skip)
+{
+	int drawn = 0;
+	int x = 16;
+
+	if (skip)
+	{
+		x += width + 16;
+	}
+	while (FMOD_OK == group->getSpectrum(spectrumarray, SPECTRUM_SIZE, drawn, FMOD_DSP_FFT_WINDOW_TRIANGLE))
+	{
+		drawn++;
+		DrawSpectrum(spectrumarray, x, y, width, height);
+		x += (width + 16) << int(skip);
+	}
+	if (drawn)
+	{
+		y += height + 16;
+	}
+	return y;
+}
+
+//==========================================================================
+//
+// FMODSoundRenderer::DrawSystemSpectrum
+//
+// Like DrawChannelGroupSpectrum, but it uses the system object to get the
+// complete output.
+//
+//==========================================================================
+
+int FMODSoundRenderer::DrawSystemSpectrum(float *spectrumarray, int width, int height, int y, bool skip)
+{
+	int drawn = 0;
+	int x = 16;
+
+	if (skip)
+	{
+		x += width + 16;
+	}
+	while (FMOD_OK == Sys->getSpectrum(spectrumarray, SPECTRUM_SIZE, drawn, FMOD_DSP_FFT_WINDOW_TRIANGLE))
+	{
+		drawn++;
+		DrawSpectrum(spectrumarray, x, y, width, height);
+		x += (width + 16) << int(skip);
+	}
+	if (drawn)
+	{
+		y += height + 16;
+	}
+	return y;
+}
+
+//==========================================================================
+//
+// FMODSoundRenderer :: DrawSpectrum
+//
+// Draws a spectrum at the specified coordinates on the screen.
+//
+//==========================================================================
+
+void FMODSoundRenderer::DrawSpectrum(float *spectrumarray, int x, int y, int width, int height)
+{
+	float scale = height / 2.f;
+	float mid = y + scale;
+	float db;
+	int top;
+
+	// Draw a border and dark background for the spectrum.
+	screen->DrawLine(x - 1, y - 1, x + width, y - 1, -1, MAKEARGB(160, 0, 40, 200));
+	screen->DrawLine(x + width + 1, y - 1, x + width, y + height, -1, MAKEARGB(160, 0, 40, 200));
+	screen->DrawLine(x + width, y + height, x - 1, y + height, -1, MAKEARGB(160, 0, 40, 200));
+	screen->DrawLine(x - 1, y + height, x - 1, y - 1, -1, MAKEARGB(160, 0, 40, 200));
+	screen->Dim(MAKERGB(0,0,0), 0.3f, x, y, width, height);
+
+	// Draw the actual spectrum.
+	for (int i = 0; i < width; ++i)
+	{
+		db = spectrumarray[i * (SPECTRUM_SIZE - 2) / width + 1];
+		db = MAX(-150.f, 10 * log10f(db) * 2);		// Convert to decibels and clamp
+		db = 1.f - (db / -150.f);
+		db *= height;
+		top = (int)db;
+		if (top >= height)
+		{
+			top = height - 1;
+		}
+//		screen->Clear(x + i, int(y + height - db), x + i + 1, y + height, -1, MAKEARGB(255, 255, 255, 40));
+		screen->Dim(MAKERGB(255,255,40), 0.65f, x + i, y + height - top, 1, top);
+	}
+}
diff --git a/src/sound/fmodsound.h b/src/sound/fmodsound.h
index 19cc72650..00ab4411d 100644
--- a/src/sound/fmodsound.h
+++ b/src/sound/fmodsound.h
@@ -16,6 +16,7 @@ public:
 	void LoadSound (sfxinfo_t *sfx);
 	void UnloadSound (sfxinfo_t *sfx);
 	unsigned int GetMSLength(sfxinfo_t *sfx);
+	float GetOutputRate();
 
 	// Streaming sounds.
 	SoundStream *CreateStream (SoundStreamCallback callback, int buffsamples, int flags, int samplerate, void *userdata);
@@ -48,6 +49,8 @@ public:
 	FString GatherStats ();
 	void ResetEnvironment ();
 
+	void DrawWaveDebug(int mode);
+
 private:
 	bool SFXPaused;
 	bool InitSuccess;
@@ -65,6 +68,17 @@ private:
 	void Shutdown ();
 	void DumpDriverCaps(FMOD_CAPS caps, int minfrequency, int maxfrequency);
 
+	int DrawChannelGroupOutput(FMOD::ChannelGroup *group, float *wavearray, int width, int height, int y, int mode);
+	int DrawSystemOutput(float *wavearray, int width, int height, int y, int mode);
+
+	int DrawChannelGroupWaveData(FMOD::ChannelGroup *group, float *wavearray, int width, int height, int y, bool skip);
+	int DrawSystemWaveData(float *wavearray, int width, int height, int y, bool skip);
+	void DrawWave(float *wavearray, int x, int y, int width, int height);
+
+	int DrawChannelGroupSpectrum(FMOD::ChannelGroup *group, float *wavearray, int width, int height, int y, bool skip);
+	int DrawSystemSpectrum(float *wavearray, int width, int height, int y, bool skip);
+	void DrawSpectrum(float *spectrumarray, int x, int y, int width, int height);
+
 	FMOD::System *Sys;
 	FMOD::ChannelGroup *SfxGroup, *PausableSfx;
 	FMOD::ChannelGroup *MusicGroup;
diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp
index 6a148f33c..ea0a4e1a1 100644
--- a/src/sound/i_music.cpp
+++ b/src/sound/i_music.cpp
@@ -70,8 +70,7 @@ extern void ChildSigHandler (int signum);
 #include "tempfiles.h"
 #include "templates.h"
 #include "stats.h"
-
-#include <fmod.h>
+#include "timidity/timidity.h"
 
 EXTERN_CVAR (Int, snd_samplerate)
 EXTERN_CVAR (Int, snd_mididevice)
@@ -156,6 +155,8 @@ void I_InitMusic (void)
 {
 	static bool setatterm = false;
 
+	Timidity::LoadConfig();
+
 	snd_musicvolume.Callback ();
 
 	nomusic = !!Args->CheckParm("-nomusic") || !!Args->CheckParm("-nosound");
@@ -187,8 +188,9 @@ void I_ShutdownMusic(void)
 		S_StopMusic (true);
 		assert (currSong == NULL);
 	}
+	Timidity::FreeAll();
 #ifdef _WIN32
-	I_ShutdownMusicWin32 ();
+	I_ShutdownMusicWin32();
 #endif // _WIN32
 }
 
@@ -327,12 +329,16 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le
 			*/
 			if ((snd_mididevice == -3 && device == MDEV_DEFAULT) || device == MDEV_OPL)
 			{
-				info = new MUSSong2 (file, musiccache, len, true);
+				info = new MUSSong2 (file, musiccache, len, MIDI_OPL);
 			}
 			else if (device == MDEV_TIMIDITY || (device == MDEV_DEFAULT && snd_mididevice == -2))
 			{
 				info = new TimiditySong (file, musiccache, len);
 			}
+			else if ((snd_mididevice == -4 && device == MDEV_DEFAULT) && GSnd != NULL)
+			{
+				info = new MUSSong2(file, musiccache, len, MIDI_Timidity);
+			}
 			if (info != NULL && !info->IsValid())
 			{
 				delete info;
@@ -373,7 +379,7 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le
 #ifdef _WIN32
 		if (info == NULL)
 		{
-			info = new MUSSong2 (file, musiccache, len, false);
+			info = new MUSSong2 (file, musiccache, len, MIDI_Win);
 		}
 #endif // _WIN32
 	}
@@ -406,12 +412,16 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le
 			*/
 			if ((device == MDEV_OPL || (snd_mididevice == -3 && device == MDEV_DEFAULT)) && GSnd != NULL)
 			{
-				info = new MIDISong2 (file, musiccache, len, true);
+				info = new MIDISong2 (file, musiccache, len, MIDI_OPL);
 			}
 			else if ((device == MDEV_TIMIDITY || (snd_mididevice == -2 && device == MDEV_DEFAULT)) && GSnd != NULL)
 			{
 				info = new TimiditySong (file, musiccache, len);
 			}
+			else if ((snd_mididevice == -4 && device == MDEV_DEFAULT) && GSnd != NULL)
+			{
+				info = new MIDISong2(file, musiccache, len, MIDI_Timidity);
+			}
 			if (info != NULL && !info->IsValid())
 			{
 				delete info;
@@ -421,7 +431,7 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le
 #ifdef _WIN32
 			if (info == NULL && device != MDEV_FMOD && (snd_mididevice >= 0 || device == MDEV_MMAPI))
 			{
-				info = new MIDISong2 (file, musiccache, len, false);
+				info = new MIDISong2 (file, musiccache, len, MIDI_Win);
 			}
 #endif // _WIN32
 		}
diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h
index e807839e1..202f6ddad 100644
--- a/src/sound/i_musicinterns.h
+++ b/src/sound/i_musicinterns.h
@@ -205,12 +205,69 @@ public:
 	void Stop();
 };
 
+// Internal TiMidity MIDI device --------------------------------------------
+
+namespace Timidity { struct Renderer; }
+
+class TimidityMIDIDevice : public MIDIDevice
+{
+public:
+	TimidityMIDIDevice();
+	~TimidityMIDIDevice();
+
+	int Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata);
+	void Close();
+	bool IsOpen() const;
+	int GetTechnology() const;
+	int SetTempo(int tempo);
+	int SetTimeDiv(int timediv);
+	int StreamOut(MIDIHDR *data);
+	int StreamOutSync(MIDIHDR *data);
+	int Resume();
+	void Stop();
+	int PrepareHeader(MIDIHDR *data);
+	int UnprepareHeader(MIDIHDR *data);
+	bool FakeVolume();
+	bool Pause(bool paused);
+	bool NeedThreadedCallback();
+	void PrecacheInstruments(const BYTE *instruments, int count);
+
+protected:
+	static bool FillStream(SoundStream *stream, void *buff, int len, void *userdata);
+	bool ServiceStream (void *buff, int numbytes);
+
+	void (*Callback)(unsigned int, void *, DWORD, DWORD);
+	void *CallbackData;
+
+	void CalcTickRate();
+	int PlayTick();
+
+	FCriticalSection CritSec;
+	SoundStream *Stream;
+	Timidity::Renderer *Renderer;
+	double Tempo;
+	double Division;
+	double SamplesPerTick;
+	double NextTickIn;
+	MIDIHDR *Events;
+	bool Started;
+	DWORD Position;
+};
+
 // Base class for streaming MUS and MIDI files ------------------------------
 
+// MIDI device selection.
+enum EMIDIDevice
+{
+	MIDI_Win,
+	MIDI_OPL,
+	MIDI_Timidity
+};
+
 class MIDIStreamer : public MusInfo
 {
 public:
-	MIDIStreamer(bool opl);
+	MIDIStreamer(EMIDIDevice type);
 	~MIDIStreamer();
 
 	void MusicVolumeChanged();
@@ -276,7 +333,7 @@ protected:
 	int InitialTempo;
 	BYTE ChannelVolumes[16];
 	DWORD Volume;
-	bool UseOPLDevice;
+	EMIDIDevice DeviceType;
 	bool CallbackIsThreaded;
 	FString DumpFilename;
 };
@@ -286,7 +343,7 @@ protected:
 class MUSSong2 : public MIDIStreamer
 {
 public:
-	MUSSong2(FILE *file, char *musiccache, int length, bool opl);
+	MUSSong2(FILE *file, char *musiccache, int length, EMIDIDevice type);
 	~MUSSong2();
 
 	MusInfo *GetOPLDumper(const char *filename);
@@ -311,7 +368,7 @@ protected:
 class MIDISong2 : public MIDIStreamer
 {
 public:
-	MIDISong2(FILE *file, char *musiccache, int length, bool opl);
+	MIDISong2(FILE *file, char *musiccache, int length, EMIDIDevice type);
 	~MIDISong2();
 
 	MusInfo *GetOPLDumper(const char *filename);
diff --git a/src/sound/i_sound.cpp b/src/sound/i_sound.cpp
index 84a8ab5c0..0a9ff4793 100644
--- a/src/sound/i_sound.cpp
+++ b/src/sound/i_sound.cpp
@@ -204,6 +204,10 @@ FString SoundRenderer::GatherStats ()
 	return "No stats for this sound renderer.";
 }
 
+void SoundRenderer::DrawWaveDebug(int mode)
+{
+}
+
 void SoundRenderer::ResetEnvironment ()
 {
 }
@@ -221,3 +225,4 @@ FString SoundStream::GetStats()
 {
 	return "No stream stats available.";
 }
+
diff --git a/src/sound/i_sound.h b/src/sound/i_sound.h
index 2e4a7e081..3bb9bae5c 100644
--- a/src/sound/i_sound.h
+++ b/src/sound/i_sound.h
@@ -75,6 +75,7 @@ public:
 	virtual void LoadSound (sfxinfo_t *sfx) = 0;	// load a sound from disk
 	virtual void UnloadSound (sfxinfo_t *sfx) = 0;	// unloads a sound from memory
 	virtual unsigned int GetMSLength(sfxinfo_t *sfx) = 0;	// Gets the length of a sound at its default frequency
+	virtual float GetOutputRate() = 0;
 
 	// Streaming sounds.
 	virtual SoundStream *CreateStream (SoundStreamCallback callback, int buffbytes, int flags, int samplerate, void *userdata) = 0;
@@ -105,6 +106,8 @@ public:
 	virtual void PrintDriversList () = 0;
 	virtual FString GatherStats ();
 	virtual void ResetEnvironment ();
+
+	virtual void DrawWaveDebug(int mode);
 };
 
 extern SoundRenderer *GSnd;
diff --git a/src/sound/music_midi_base.cpp b/src/sound/music_midi_base.cpp
index d7bc8f1fe..5313aaa82 100644
--- a/src/sound/music_midi_base.cpp
+++ b/src/sound/music_midi_base.cpp
@@ -19,7 +19,7 @@ CUSTOM_CVAR (Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
 	if (!nummididevicesset)
 		return;
 
-	if ((self >= (signed)nummididevices) || (self < -3))
+	if ((self >= (signed)nummididevices) || (self < -4))
 	{
 		Printf ("ID out of range. Using default device.\n");
 		self = 0;
diff --git a/src/sound/music_midi_midiout.cpp b/src/sound/music_midi_midiout.cpp
index 11c41f27f..612da30ba 100644
--- a/src/sound/music_midi_midiout.cpp
+++ b/src/sound/music_midi_midiout.cpp
@@ -100,8 +100,8 @@ static BYTE CommonLengths[15] = { 0, 1, 2, 1, 0, 0, 0, 0,  0, 0, 0, 0, 0, 0, 0 }
 //
 //==========================================================================
 
-MIDISong2::MIDISong2 (FILE *file, char *musiccache, int len, bool opl)
-: MIDIStreamer(opl), MusHeader(0), Tracks(0)
+MIDISong2::MIDISong2 (FILE *file, char *musiccache, int len, EMIDIDevice type)
+: MIDIStreamer(type), MusHeader(0), Tracks(0)
 {
 	int p;
 	int i;
diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp
index c57ddfad6..2f923fd34 100644
--- a/src/sound/music_midistream.cpp
+++ b/src/sound/music_midistream.cpp
@@ -69,12 +69,12 @@ extern UINT mididevice;
 //
 //==========================================================================
 
-MIDIStreamer::MIDIStreamer(bool opl)
+MIDIStreamer::MIDIStreamer(EMIDIDevice type)
 :
 #ifdef _WIN32
   PlayerThread(0), ExitEvent(0), BufferDoneEvent(0),
 #endif
-  MIDI(0), Division(0), InitialTempo(500000), UseOPLDevice(opl)
+  MIDI(0), Division(0), InitialTempo(500000), DeviceType(type)
 {
 #ifdef _WIN32
 	BufferDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
@@ -102,7 +102,7 @@ MIDIStreamer::MIDIStreamer(const char *dumpname)
 #ifdef _WIN32
   PlayerThread(0), ExitEvent(0), BufferDoneEvent(0),
 #endif
-  MIDI(0), Division(0), InitialTempo(500000), UseOPLDevice(true), DumpFilename(dumpname)
+  MIDI(0), Division(0), InitialTempo(500000), DeviceType(MIDI_OPL), DumpFilename(dumpname)
 {
 #ifdef _WIN32
 	BufferDoneEvent = NULL;
@@ -198,16 +198,23 @@ void MIDIStreamer::Play(bool looping)
 	{
 		MIDI = new OPLDumperMIDIDevice(DumpFilename);
 	}
-	else
+	else switch(DeviceType)
+	{
+	case MIDI_Win:
 #ifdef _WIN32
-	if (!UseOPLDevice)
-	{
 		MIDI = new WinMIDIDevice(mididevice);
-	}
-	else
+		break;
 #endif
-	{
+		assert(0);
+		// Intentional fall-through for non-Windows systems.
+
+	case MIDI_Timidity:
+		MIDI = new TimidityMIDIDevice;
+		break;
+
+	case MIDI_OPL:
 		MIDI = new OPLMIDIDevice;
+		break;
 	}
 	
 #ifndef _WIN32
@@ -682,8 +689,11 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, DWORD max_time)
 			{
 				events[0] = 0;				// dwDeltaTime
 				events[1] = 0;				// dwStreamID
-				events[2] = MIDI_NOTEOFF | i | (60 << 8) | (64<<16);
-				events += 3;
+				events[2] = MIDI_CTRLCHANGE | i | (123 << 8);	// All notes off
+				events[3] = 0;
+				events[4] = 0;
+				events[5] = MIDI_CTRLCHANGE | i | (121 << 8);	// Reset controllers
+				events += 6;
 			}
 			DoRestart();
 		}
diff --git a/src/sound/music_mus_midiout.cpp b/src/sound/music_mus_midiout.cpp
index 651f154b4..03b2fa4ce 100644
--- a/src/sound/music_mus_midiout.cpp
+++ b/src/sound/music_mus_midiout.cpp
@@ -84,8 +84,8 @@ static const BYTE CtrlTranslate[15] =
 //
 //==========================================================================
 
-MUSSong2::MUSSong2 (FILE *file, char *musiccache, int len, bool opl)
-: MIDIStreamer(opl), MusHeader(0), MusBuffer(0)
+MUSSong2::MUSSong2 (FILE *file, char *musiccache, int len, EMIDIDevice type)
+: MIDIStreamer(type), MusHeader(0), MusBuffer(0)
 {
 #ifdef _WIN32
 	if (ExitEvent == NULL)
@@ -201,7 +201,7 @@ void MUSSong2::Precache()
 		}
 		else if (used[i] >= 135 && used[i] <= 181)
 		{ // Percussions are 100-based, not 128-based, eh?
-			work[j++] = (used[i] - 100) | 0x80;
+			work[j++] = used[i] - 100 + 128;
 		}
 	}
 	MIDI->PrecacheInstruments(&work[0], j);
diff --git a/src/sound/music_timidity_mididevice.cpp b/src/sound/music_timidity_mididevice.cpp
new file mode 100644
index 000000000..f0afe7b13
--- /dev/null
+++ b/src/sound/music_timidity_mididevice.cpp
@@ -0,0 +1,519 @@
+/*
+** music_timidity_mididevice.cpp
+** Provides access to TiMidity as a generic MIDI device.
+**
+**---------------------------------------------------------------------------
+** Copyright 2008 Randy Heit
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+
+// HEADER FILES ------------------------------------------------------------
+
+#include "i_musicinterns.h"
+#include "templates.h"
+#include "doomdef.h"
+#include "m_swap.h"
+#include "w_wad.h"
+#include "timidity/timidity.h"
+
+// MACROS ------------------------------------------------------------------
+
+// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
+
+// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
+
+// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
+
+// EXTERNAL DATA DECLARATIONS ----------------------------------------------
+
+// PRIVATE DATA DEFINITIONS ------------------------------------------------
+
+// PUBLIC DATA DEFINITIONS -------------------------------------------------
+
+CVAR(Bool, timidity_watch, false, 0)
+
+// CODE --------------------------------------------------------------------
+
+//==========================================================================
+//
+// TimidityMIDIDevice Constructor
+//
+//==========================================================================
+
+TimidityMIDIDevice::TimidityMIDIDevice()
+{
+	Stream = NULL;
+	Tempo = 0;
+	Division = 0;
+	Events = NULL;
+	Started = false;
+	Renderer = NULL;
+	Renderer = new Timidity::Renderer(GSnd->GetOutputRate());
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice Destructor
+//
+//==========================================================================
+
+TimidityMIDIDevice::~TimidityMIDIDevice()
+{
+	Close();
+	if (Renderer != NULL)
+	{
+		delete Renderer;
+	}
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: Open
+//
+// Returns 0 on success.
+//
+//==========================================================================
+
+int TimidityMIDIDevice::Open(void (*callback)(unsigned int, void *, DWORD, DWORD), void *userdata)
+{
+	Stream = GSnd->CreateStream(FillStream, int(Renderer->rate / 2) * 4,
+		SoundStream::Float, int(Renderer->rate), this);
+	if (Stream == NULL)
+	{
+		return 2;
+	}
+
+	Callback = callback;
+	CallbackData = userdata;
+	Tempo = 500000;
+	Division = 100;
+	CalcTickRate();
+	Renderer->Reset();
+	return 0;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: Close
+//
+//==========================================================================
+
+void TimidityMIDIDevice::Close()
+{
+	if (Stream != NULL)
+	{
+		delete Stream;
+		Stream = NULL;
+	}
+	Started = false;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: IsOpen
+//
+//==========================================================================
+
+bool TimidityMIDIDevice::IsOpen() const
+{
+	return Stream != NULL;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: GetTechnology
+//
+//==========================================================================
+
+int TimidityMIDIDevice::GetTechnology() const
+{
+	return MOD_SWSYNTH;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: SetTempo
+//
+//==========================================================================
+
+int TimidityMIDIDevice::SetTempo(int tempo)
+{
+	Tempo = tempo;
+	CalcTickRate();
+	return 0;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: SetTimeDiv
+//
+//==========================================================================
+
+int TimidityMIDIDevice::SetTimeDiv(int timediv)
+{
+	Division = timediv;
+	CalcTickRate();
+	return 0;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: CalcTickRate
+//
+// Tempo is the number of microseconds per quarter note.
+// Division is the number of ticks per quarter note.
+//
+//==========================================================================
+
+void TimidityMIDIDevice::CalcTickRate()
+{
+	SamplesPerTick = Renderer->rate / (1000000.0 / Tempo) / Division;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: Resume
+//
+//==========================================================================
+
+int TimidityMIDIDevice::Resume()
+{
+	if (!Started)
+	{
+		if (Stream->Play(true, 1, false))
+		{
+			Started = true;
+			return 0;
+		}
+		return 1;
+	}
+	return 0;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: Stop
+//
+//==========================================================================
+
+void TimidityMIDIDevice::Stop()
+{
+	if (Started)
+	{
+		Stream->Stop();
+		Started = false;
+	}
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: StreamOutSync
+//
+// This version is called from the main game thread and needs to
+// synchronize with the player thread.
+//
+//==========================================================================
+
+int TimidityMIDIDevice::StreamOutSync(MIDIHDR *header)
+{
+	CritSec.Enter();
+	StreamOut(header);
+	CritSec.Leave();
+	return 0;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: StreamOut
+//
+// This version is called from the player thread so does not need to
+// arbitrate for access to the Events pointer.
+//
+//==========================================================================
+
+int TimidityMIDIDevice::StreamOut(MIDIHDR *header)
+{
+	header->lpNext = NULL;
+	if (Events == NULL)
+	{
+		Events = header;
+		NextTickIn = SamplesPerTick * *(DWORD *)header->lpData;
+		Position = 0;
+	}
+	else
+	{
+		MIDIHDR **p;
+
+		for (p = &Events; *p != NULL; p = &(*p)->lpNext)
+		{ }
+		*p = header;
+	}
+	return 0;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: PrepareHeader
+//
+//==========================================================================
+
+int TimidityMIDIDevice::PrepareHeader(MIDIHDR *header)
+{
+	return 0;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: UnprepareHeader
+//
+//==========================================================================
+
+int TimidityMIDIDevice::UnprepareHeader(MIDIHDR *header)
+{
+	return 0;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: FakeVolume
+//
+// Since the TiMidity output is rendered as a normal stream, its volume is
+// controlled through the GSnd interface, not here.
+//
+//==========================================================================
+
+bool TimidityMIDIDevice::FakeVolume()
+{
+	return false;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: NeedThreadedCallabck
+//
+// OPL can service the callback directly rather than using a separate
+// thread.
+//
+//==========================================================================
+
+bool TimidityMIDIDevice::NeedThreadedCallback()
+{
+	return false;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: Pause
+//
+//==========================================================================
+
+bool TimidityMIDIDevice::Pause(bool paused)
+{
+	if (Stream != NULL)
+	{
+		return Stream->SetPaused(paused);
+	}
+	return true;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: PrecacheInstruments
+//
+// For each entry, bit 7 set indicates that the instrument is percussion and
+// the lower 7 bits contain the note number to use on MIDI channel 10,
+// otherwise it is melodic and the lower 7 bits are the program number.
+//
+//==========================================================================
+
+void TimidityMIDIDevice::PrecacheInstruments(const BYTE *instruments, int count)
+{
+	for (int i = 0; i < count; ++i)
+	{
+		Renderer->MarkInstrument(0, instruments[i] >> 7, instruments[i] & 127);
+	}
+	Renderer->load_missing_instruments();
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: PlayTick
+//
+// event[0] = delta time
+// event[1] = unused
+// event[2] = event
+//
+//==========================================================================
+
+int TimidityMIDIDevice::PlayTick()
+{
+	DWORD delay = 0;
+
+	while (delay == 0 && Events != NULL)
+	{
+		DWORD *event = (DWORD *)(Events->lpData + Position);
+		if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO)
+		{
+			SetTempo(MEVT_EVENTPARM(event[2]));
+		}
+		else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG)
+		{
+			Renderer->HandleLongMessage((BYTE *)&event[3], MEVT_EVENTPARM(event[2]));
+		}
+		else if (MEVT_EVENTTYPE(event[2]) == 0)
+		{ // Short MIDI event
+			int status = event[2] & 0xff;
+			int parm1 = (event[2] >> 8) & 0x7f;
+			int parm2 = (event[2] >> 16) & 0x7f;
+			Renderer->HandleEvent(status, parm1, parm2);
+
+			if (timidity_watch)
+			{
+				static const char *const commands[8] =
+				{
+					"Note off",
+					"Note on",
+					"Poly press",
+					"Ctrl change",
+					"Prgm change",
+					"Chan press",
+					"Pitch bend",
+					"SysEx"
+				};
+#ifdef _WIN32
+				char buffer[128];
+				sprintf(buffer, "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2);
+				OutputDebugString(buffer);
+#else
+				fprintf(stderr, "C%02d: %11s %3d %3d\n", (status & 15) + 1, commands[(status >> 4) & 7], parm1, parm2);
+#endif
+			}
+		}
+
+		// Advance to next event.
+		if (event[2] < 0x80000000)
+		{ // Short message
+			Position += 12;
+		}
+		else
+		{ // Long message
+			Position += 12 + ((MEVT_EVENTPARM(event[2]) + 3) & ~3);
+		}
+
+		// Did we use up this buffer?
+		if (Position >= Events->dwBytesRecorded)
+		{
+			Events = Events->lpNext;
+			Position = 0;
+
+			if (Callback != NULL)
+			{
+				Callback(MOM_DONE, CallbackData, 0, 0);
+			}
+		}
+
+		if (Events == NULL)
+		{ // No more events. Just return something to keep the song playing
+		  // while we wait for more to be submitted.
+			return int(Division);
+		}
+
+		delay = *(DWORD *)(Events->lpData + Position);
+	}
+	return delay;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: ServiceStream
+//
+//==========================================================================
+
+bool TimidityMIDIDevice::ServiceStream (void *buff, int numbytes)
+{
+	float *samples = (float *)buff;
+	float *samples1;
+	int numsamples = numbytes / sizeof(float) / 2;
+	bool prev_ended = false;
+	bool res = true;
+
+	samples1 = samples;
+	memset(buff, 0, numbytes);
+
+	CritSec.Enter();
+	while (numsamples > 0)
+	{
+		double ticky = NextTickIn;
+		int tick_in = int(NextTickIn);
+		int samplesleft = MIN(numsamples, tick_in);
+
+		if (samplesleft > 0)
+		{
+			Renderer->ComputeOutput(samples1, samplesleft);
+			assert(NextTickIn == ticky);
+			NextTickIn -= samplesleft;
+			assert(NextTickIn >= 0);
+			numsamples -= samplesleft;
+			samples1 += samplesleft * 2;
+		}
+		
+		if (NextTickIn < 1)
+		{
+			int next = PlayTick();
+			assert(next >= 0);
+			if (next == 0)
+			{ // end of song
+				if (numsamples > 0)
+				{
+					Renderer->ComputeOutput(samples1, samplesleft);
+				}
+				res = false;
+				break;
+			}
+			else
+			{
+				NextTickIn += SamplesPerTick * next;
+				assert(NextTickIn >= 0);
+			}
+		}
+	}
+	CritSec.Leave();
+	return res;
+}
+
+//==========================================================================
+//
+// TimidityMIDIDevice :: FillStream									static
+//
+//==========================================================================
+
+bool TimidityMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata)
+{
+	TimidityMIDIDevice *device = (TimidityMIDIDevice *)userdata;
+	return device->ServiceStream(buff, len);
+}
diff --git a/src/timidity/CHANGES b/src/timidity/CHANGES
new file mode 100644
index 000000000..045e53739
--- /dev/null
+++ b/src/timidity/CHANGES
@@ -0,0 +1,58 @@
+This version of TiMidity should contain all the fixes from the
+September 25 2003 SDL_mixer CVS snapshot, plus extended GUS patch
+support from later SDL_mixer. In addition, it contains these changes
+from SDL_sound:
+
+* Removal of much unused or unnecessary code, such as
+
+  + The "hooks" for putting a user interface onto TiMidity.
+  + The antialias filter. It wasn't active, and even at 4 kHz I
+    couldn't hear any difference when activating it.
+  + Removed all traces of LOOKUP_HACK and LOOKUP_INTERPOLATION.
+    According to the code comments they weren't very good anyway.
+    ("degrades sound quality noticeably"). I also removed the
+    disclaimer about the "8-bit uLaw to 16-bit PCM and the 13-bit-PCM
+    to 8-bit uLaw tables" disclaimer, since I believe those were the
+    tables I removed.
+  + Removed LOOKUP_SINE since it was already commented out. I think we
+    can count on our target audience having math co-processors
+    nowadays.
+  + Removed USE_LDEXP since it wasn't being used and "it doesn't make
+    much of a difference either way".
+  + Removed decompress hack from open_file() since it didn't look very
+    portable.
+  + Removed heaps of unnecessary constants.
+  + Removed unused functions.
+  + Assume that LINEAR_INTERPOLATION is always used, so remove all
+    code dealing with it not being so. It's not that I think the
+    difference in audio quality is that great, but since it wouldn't
+    compile without code changes I assume no one's used it for quite
+    some time...
+  + Assume PRECALC_LOOPS is always defined. Judging by the comments it
+    may not make much of a difference either way, so why maintain two
+    versions of the same code?
+
+* Made TiMidity look for its configuration file in both /etc and
+  /usr/local/lib/timidity. (Windows version remains unchanged.)
+
+* The following files have been removed: controls.c, controls.h,
+  filter.c, filter.h, sdl_a.c, sdl_c.c
+
+* Added support for loading DLS format instruments:
+	Timidity_LoadDLS(), Timidity_FreeDLS(), Timidity_LoadDLSSong()
+
+This version of TiMidity also contains my own changes for ZDoom:
+
+* Removed readmidi.c: TiMidity is now fed MIDI events directly to
+	produce output. The TimidityMIDIDevice class is responsible for
+	feeding TiMidity data and collecting output from it. Since
+	ZDoom's MIDI parser ignores SysEx messages, so does this TiMidity,
+	though this can be changed if necessary.
+
+* Removed all the precalculated math from tables.c in favor of using
+	real math functions.
+	
+* All sample values are now floats, and only a stereo 32-bit float
+	output buffer is supported.
+
+* Moved everything into the Timidity namespace.
diff --git a/src/timidity/COPYING b/src/timidity/COPYING
new file mode 100644
index 000000000..2fa5da3ac
--- /dev/null
+++ b/src/timidity/COPYING
@@ -0,0 +1,513 @@
+Please note that the included source from Timidity, the MIDI decoder, is also
+ licensed under the following terms (GNU LGPL), but can also be used
+ separately under the GNU GPL, or the Perl Artistic License. Those licensing
+ terms are not reprinted here, but can be found on the web easily.
+
+
+-------------------
+
+
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+		  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+			    NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/src/timidity/FAQ b/src/timidity/FAQ
new file mode 100644
index 000000000..1ee0b77bf
--- /dev/null
+++ b/src/timidity/FAQ
@@ -0,0 +1,100 @@
+---------------------------*-indented-text-*------------------------------
+
+    TiMidity -- Experimental MIDI to WAVE converter
+    Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
+
+--------------------------------------------------------------------------
+
+   Frequently Asked Questions with answers:
+
+--------------------------------------------------------------------------
+Q: What is it?
+
+A: Where?  Well Chris, TiMidity is a software-only synthesizer, MIDI
+   renderer, MIDI to WAVE converter, realtime MIDI player for UNIX machines,
+   even (I've heard) a Netscape helper application.  It takes a MIDI file
+   and writes a WAVE or raw PCM data or plays it on your digital audio
+   device.  It sounds much more realistic than FM synthesis, but you need a
+   ~100Mhz processor to listen to 32kHz stereo music in the background while
+   you work.  11kHz mono can be played on a low-end 486, and, to some, it
+   still sounds better than FM.
+
+--------------------------------------------------------------------------
+Q: I don't have a GUS, can I use TiMidity?
+
+A: Yes.  That's the point.  You don't need a Gravis Ultrasound to use
+   TiMidity, you just need GUS-compatible patches, which are freely
+   available on the Internet.  See below for pointers.
+
+--------------------------------------------------------------------------
+Q: I have a GUS, can I use TiMidity?
+
+A: The DOS port doesn't have GUS support, and TiMidity won't be taking
+   advantage of the board's internal synthesizer under other operating
+   systems either.  So it kind of defeats the purpose.  But you can use it.
+
+--------------------------------------------------------------------------
+Q: I tried playing a MIDI file I got off the Net but all I got was a
+   dozen warnings saying "No instrument mapped to tone bank 0, program
+   xx - this instrument will not be heard". What's wrong?
+
+A: The General MIDI standard specifies 128 melodic instruments and
+   some sixty percussion sounds. If you wish to play arbitrary General
+   MIDI files, you'll need to get more patch files.
+
+   There's a program called Midia for SGI's, which also plays MIDI
+   files and has a lot more bells and whistles than TiMidity. It uses
+   GUS-compatible patches, too -- so you can get the 8 MB set at
+   ftp://archive.cs.umbc.edu/pub/midia for pretty good GM compatibility.
+
+   There are also many excellent patches on the Ultrasound FTP sites.
+   I can recommend Dustin McCartney's collections gsdrum*.zip and
+   wow*.zip in the "[.../]sound/patches/files" directory. The huge
+   ProPats series (pp3-*.zip) contains good patches as well. General
+   MIDI files can also be found on these sites.
+
+   This site list is from the GUS FAQ:
+
+>                        FTP Sites                     Archive Directories
+>                        ---------                     -------------------
+> Main N.American Site:  archive.orst.edu              pub/packages/gravis
+>                        wuarchive.wustl.edu           systems/ibmpc/ultrasound
+> Main Asian Site:       nctuccca.edu.tw               PC/ultrasound
+> Main European Site:    src.doc.ic.ac.uk              packages/ultrasound
+> Main Australian Site:  ftp.mpx.com.au                /ultrasound/general
+>                                                      /ultrasound/submit
+> South African Site:    ftp.sun.ac.za                 /pub/packages/ultrasound
+> Submissions:           archive.epas.utoronto.ca      pub/pc/ultrasound/submit
+> Newly Validated Files: archive.epas.utoronto.ca      pub/pc/ultrasound
+> 
+> Mirrors:               garbo.uwasa.fi                mirror/ultrasound
+>                        ftp.st.nepean.uws.edu.au      pc/ultrasound
+>                        ftp.luth.se                   pub/msdos/ultrasound
+
+--------------------------------------------------------------------------
+Q: Some files have awful clicks and pops.
+
+A: Find out which patch is responsible for the clicking (try "timidity
+   -P<patch> <midi/test-decay|midi/test-panning>".  Add "strip=tail" in
+   the config file after its name.  If this doesn't fix it, mail me the
+   patch.
+
+--------------------------------------------------------------------------
+Q: I'm playing Fantasie Impromptu in the background. When I run Netscape,
+   the sound gets choppy and it takes ten minutes to load. What can I do?
+
+A: Here are some things to try:
+
+   - Use a lower sampling rate.
+
+   - Use mono output. This can improve performance by 10-30%.
+     (Using 8-bit instead of 16-bit output makes no difference.)
+
+   - Use a smaller number of simultaneous voices.
+
+   - Make sure you compiled with FAST_DECAY enabled in options.h
+
+   - Recompile with an Intel-optimized gcc for a 5-15%
+     performance increase.
+
+--------------------------------------------------------------------------
diff --git a/src/timidity/README b/src/timidity/README
new file mode 100644
index 000000000..9c9c55aad
--- /dev/null
+++ b/src/timidity/README
@@ -0,0 +1,61 @@
+[This version of timidity has been stripped for simplicity in porting to SDL,
+and then even further for SDL_sound]
+---------------------------------*-text-*---------------------------------
+
+        From http://www.cgs.fi/~tt/discontinued.html :
+
+        If you'd like to continue hacking on TiMidity, feel free. I'm
+        hereby extending the TiMidity license agreement: you can now 
+        select the most convenient license for your needs from (1) the
+        GNU GPL, (2) the GNU LGPL, or (3) the Perl Artistic License.  
+
+--------------------------------------------------------------------------
+
+        This is the README file for TiMidity v0.2i
+
+	TiMidity is a MIDI to WAVE converter that uses Gravis
+Ultrasound(*)-compatible patch files to generate digital audio data
+from General MIDI files. The audio data can be played through any
+sound device or stored on disk. On a fast machine, music can be
+played in real time. TiMidity runs under Linux, FreeBSD, HP-UX, SunOS, and
+Win32, and porting to other systems with gcc should be easy.
+
+	TiMidity Features:
+
+	* 32 or more dynamically allocated fully independent voices
+	* Compatibility with GUS patch files
+	* Output to 16- or 8-bit PCM or uLaw audio device, file, or
+          stdout at any sampling rate
+	* Optional interactive mode with real-time status display
+	  under ncurses and SLang terminal control libraries. Also
+          a user friendly motif interface since version 0.2h
+	* Support for transparent loading of compressed MIDI files and
+	  patch files
+
+	* Support for the following MIDI events:
+	  - Program change
+	  - Key pressure
+	  - Channel main volume
+	  - Tempo
+	  - Panning
+	  - Damper pedal (Sustain)
+	  - Pitch wheel
+	  - Pitch wheel sensitivity
+	  - Change drum set
+
+* The GNU General Public License can, as always, be found in the file
+  "../COPYING".
+
+* TiMidity requires sampled instruments (patches) to play MIDI files. You
+  should get the file "timidity-lib-0.1.tar.gz" and unpack it in the same
+  directory where you unpacked the source code archive. You'll want more
+  patches later -- read the file "FAQ" for pointers.
+
+* Timidity is no longer supported, but can be found by searching the web.
+
+
+                        Tuukka Toivonen <toivonen@clinet.fi>
+
+[(*) Any Registered Trademarks used anywhere in the documentation or
+source code for TiMidity are acknowledged as belonging to their
+respective owners.]
diff --git a/src/timidity/common.cpp b/src/timidity/common.cpp
new file mode 100644
index 000000000..baf74b74a
--- /dev/null
+++ b/src/timidity/common.cpp
@@ -0,0 +1,152 @@
+/*
+
+	TiMidity -- Experimental MIDI to WAVE converter
+	Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
+
+	This library is free software; you can redistribute it and/or
+	modify it under the terms of the GNU Lesser General Public
+	License as published by the Free Software Foundation; either
+	version 2.1 of the License, or (at your option) any later version.
+
+	This library 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
+	Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public
+	License along with this library; if not, write to the Free Software
+	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+	common.c
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "timidity.h"
+#include "zstring.h"
+#include "tarray.h"
+#include "i_system.h"
+
+namespace Timidity
+{
+
+/* I guess "rb" should be right for any libc */
+#define OPEN_MODE "rb"
+
+FString current_filename;
+
+static TArray<FString> PathList;
+
+/* Try to open a file for reading. */
+static FILE *try_to_open(const char *name, int decompress, int noise_mode)
+{
+	FILE *fp;
+
+	fp = fopen(name, OPEN_MODE);
+
+	if (!fp)
+		return 0;
+
+	return fp;
+}
+
+/* This is meant to find and open files for reading. */
+FILE *open_file(const char *name, int decompress, int noise_mode)
+{
+	FILE *fp;
+
+	if (!name || !(*name))
+	{
+		return 0;
+	}
+
+	/* First try the given name */
+	current_filename = name;
+
+	if ((fp = try_to_open(current_filename, decompress, noise_mode)))
+		return fp;
+
+#ifdef ENOENT
+	if (noise_mode && (errno != ENOENT))
+	{
+		return 0;
+	}
+#endif
+
+	if (name[0] != '/'
+#ifdef _WIN32
+		&& name[0] != '\\'
+#endif
+	)
+	{
+		for (unsigned int plp = PathList.Size(); plp-- != 0; )
+		{ /* Try along the path then */
+			current_filename = "";
+			if (PathList[plp].IsNotEmpty())
+			{
+				current_filename = PathList[plp];
+				if (current_filename[current_filename.Len() - 1] != '/'
+#ifdef _WIN32
+					&& current_filename[current_filename.Len() - 1] != '\\'
+#endif
+					)
+				{
+					current_filename += '/';
+				}
+			}
+			current_filename += name;
+			if ((fp = try_to_open(current_filename, decompress, noise_mode)))
+				return fp;
+			if (noise_mode && (errno != ENOENT))
+			{
+				return 0;
+			}
+		}
+	}
+
+	/* Nothing could be opened. */
+	current_filename = "";
+	return 0;
+}
+
+/* This closes files opened with open_file */
+void close_file(FILE *fp)
+{
+	fclose(fp);
+}
+
+/* This is meant for skipping a few bytes in a file or fifo. */
+void skip(FILE *fp, size_t len)
+{
+	fseek(fp, (long)len, SEEK_CUR);
+}
+
+/* This'll allocate memory or die. */
+void *safe_malloc(size_t count)
+{
+	void *p;
+	if (count > (1 << 21))
+	{
+		I_Error("Timidity: Tried allocating %d bytes. This must be a bug.", count);
+	}
+	else if ((p = malloc(count)))
+	{
+		return p;
+	}
+	else
+	{
+		I_Error("Timidity: Couldn't malloc %d bytes.", count);
+	}
+	return 0;	// Unreachable.
+}
+
+/* This adds a directory to the path list */
+void add_to_pathlist(const char *s)
+{
+	PathList.Push(s);
+}
+
+}
diff --git a/src/timidity/dls1.h b/src/timidity/dls1.h
new file mode 100644
index 000000000..abc2075a5
--- /dev/null
+++ b/src/timidity/dls1.h
@@ -0,0 +1,266 @@
+/*==========================================================================;
+//
+//  dls1.h
+//
+//
+//  Description:
+//
+//  Interface defines and structures for the Instrument Collection Form
+//  RIFF DLS.
+//
+//
+//  Written by Sonic Foundry 1996.  Released for public use.
+//
+//=========================================================================*/
+
+#ifndef _INC_DLS1
+#define _INC_DLS1
+
+/*//////////////////////////////////////////////////////////////////////////
+//
+//
+// Layout of an instrument collection:
+//
+//
+// RIFF [] 'DLS ' [dlid,colh,INSTLIST,WAVEPOOL,INFOLIST]
+//
+// INSTLIST
+// LIST [] 'lins'
+//               LIST [] 'ins ' [dlid,insh,RGNLIST,ARTLIST,INFOLIST]
+//               LIST [] 'ins ' [dlid,insh,RGNLIST,ARTLIST,INFOLIST]
+//               LIST [] 'ins ' [dlid,insh,RGNLIST,ARTLIST,INFOLIST]
+//
+// RGNLIST
+// LIST [] 'lrgn' 
+//               LIST [] 'rgn '  [rgnh,wsmp,wlnk,ARTLIST]
+//               LIST [] 'rgn '  [rgnh,wsmp,wlnk,ARTLIST]
+//               LIST [] 'rgn '  [rgnh,wsmp,wlnk,ARTLIST]
+//
+// ARTLIST
+// LIST [] 'lart'
+//         'art1' level 1 Articulation connection graph
+//         'art2' level 2 Articulation connection graph
+//         '3rd1' Possible 3rd party articulation structure 1
+//         '3rd2' Possible 3rd party articulation structure 2 .... and so on
+//
+// WAVEPOOL 
+// ptbl [] [pool table]
+// LIST [] 'wvpl'
+//               [path],
+//               [path],
+//               LIST [] 'wave' [dlid,RIFFWAVE]
+//               LIST [] 'wave' [dlid,RIFFWAVE]
+//               LIST [] 'wave' [dlid,RIFFWAVE]
+//               LIST [] 'wave' [dlid,RIFFWAVE]
+//               LIST [] 'wave' [dlid,RIFFWAVE]
+//
+// INFOLIST
+// LIST [] 'INFO' 
+//               'icmt' 'One of those crazy comments.'
+//               'icop' 'Copyright (C) 1996 Sonic Foundry'
+//
+/////////////////////////////////////////////////////////////////////////*/
+
+
+/*/////////////////////////////////////////////////////////////////////////
+// FOURCC's used in the DLS file
+/////////////////////////////////////////////////////////////////////////*/
+
+#define FOURCC_DLS   mmioFOURCC('D','L','S',' ')
+#define FOURCC_DLID  mmioFOURCC('d','l','i','d')
+#define FOURCC_COLH  mmioFOURCC('c','o','l','h')
+#define FOURCC_WVPL  mmioFOURCC('w','v','p','l')
+#define FOURCC_PTBL  mmioFOURCC('p','t','b','l')
+#define FOURCC_PATH  mmioFOURCC('p','a','t','h')
+#define FOURCC_wave  mmioFOURCC('w','a','v','e')
+#define FOURCC_LINS  mmioFOURCC('l','i','n','s')
+#define FOURCC_INS   mmioFOURCC('i','n','s',' ')
+#define FOURCC_INSH  mmioFOURCC('i','n','s','h')
+#define FOURCC_LRGN  mmioFOURCC('l','r','g','n')
+#define FOURCC_RGN   mmioFOURCC('r','g','n',' ')
+#define FOURCC_RGNH  mmioFOURCC('r','g','n','h')
+#define FOURCC_LART  mmioFOURCC('l','a','r','t')
+#define FOURCC_ART1  mmioFOURCC('a','r','t','1')
+#define FOURCC_WLNK  mmioFOURCC('w','l','n','k')
+#define FOURCC_WSMP  mmioFOURCC('w','s','m','p')
+#define FOURCC_VERS  mmioFOURCC('v','e','r','s')
+
+/*/////////////////////////////////////////////////////////////////////////
+// Articulation connection graph definitions 
+/////////////////////////////////////////////////////////////////////////*/
+
+/* Generic Sources */
+#define CONN_SRC_NONE              0x0000
+#define CONN_SRC_LFO               0x0001
+#define CONN_SRC_KEYONVELOCITY     0x0002
+#define CONN_SRC_KEYNUMBER         0x0003
+#define CONN_SRC_EG1               0x0004
+#define CONN_SRC_EG2               0x0005
+#define CONN_SRC_PITCHWHEEL        0x0006
+
+/* Midi Controllers 0-127 */
+#define CONN_SRC_CC1               0x0081
+#define CONN_SRC_CC7               0x0087
+#define CONN_SRC_CC10              0x008a
+#define CONN_SRC_CC11              0x008b
+
+/* Generic Destinations */
+#define CONN_DST_NONE              0x0000
+#define CONN_DST_ATTENUATION       0x0001
+#define CONN_DST_PITCH             0x0003
+#define CONN_DST_PAN               0x0004
+
+/* LFO Destinations */
+#define CONN_DST_LFO_FREQUENCY     0x0104
+#define CONN_DST_LFO_STARTDELAY    0x0105
+
+/* EG1 Destinations */
+#define CONN_DST_EG1_ATTACKTIME    0x0206
+#define CONN_DST_EG1_DECAYTIME     0x0207
+#define CONN_DST_EG1_RELEASETIME   0x0209
+#define CONN_DST_EG1_SUSTAINLEVEL  0x020a
+
+/* EG2 Destinations */
+#define CONN_DST_EG2_ATTACKTIME    0x030a
+#define CONN_DST_EG2_DECAYTIME     0x030b
+#define CONN_DST_EG2_RELEASETIME   0x030d
+#define CONN_DST_EG2_SUSTAINLEVEL  0x030e
+
+#define CONN_TRN_NONE              0x0000
+#define CONN_TRN_CONCAVE           0x0001
+
+typedef struct _DLSID {
+  ULONG    ulData1;
+  USHORT   usData2;
+  USHORT   usData3;
+  BYTE     abData4[8];
+} DLSID, FAR *LPDLSID;
+
+typedef struct _DLSVERSION {
+  DWORD    dwVersionMS;
+  DWORD    dwVersionLS;
+} DLSVERSION, FAR *LPDLSVERSION;
+                   
+
+typedef struct _CONNECTION {
+  USHORT   usSource;
+  USHORT   usControl;
+  USHORT   usDestination;
+  USHORT   usTransform;
+  LONG     lScale;
+} CONNECTION, FAR *LPCONNECTION;
+
+
+/* Level 1 Articulation Data */
+
+typedef struct _CONNECTIONLIST {
+  ULONG    cbSize;            /* size of the connection list structure */
+  ULONG    cConnections;      /* count of connections in the list */
+} CONNECTIONLIST, FAR *LPCONNECTIONLIST;
+
+
+
+/*/////////////////////////////////////////////////////////////////////////
+// Generic type defines for regions and instruments
+/////////////////////////////////////////////////////////////////////////*/
+
+typedef struct _RGNRANGE {
+  USHORT usLow;
+  USHORT usHigh;
+} RGNRANGE, FAR * LPRGNRANGE;
+
+#define F_INSTRUMENT_DRUMS      0x80000000
+
+typedef struct _MIDILOCALE {
+  ULONG ulBank;
+  ULONG ulInstrument;
+} MIDILOCALE, FAR *LPMIDILOCALE;
+
+/*/////////////////////////////////////////////////////////////////////////
+// Header structures found in an DLS file for collection, instruments, and
+// regions.
+/////////////////////////////////////////////////////////////////////////*/
+
+#define F_RGN_OPTION_SELFNONEXCLUSIVE  0x0001
+
+typedef struct _RGNHEADER {
+  RGNRANGE RangeKey;            /* Key range  */
+  RGNRANGE RangeVelocity;       /* Velocity Range  */
+  USHORT   fusOptions;          /* Synthesis options for this range */
+  USHORT   usKeyGroup;          /* Key grouping for non simultaneous play */
+                                /* 0 = no group, 1 up is group */
+                                /* for Level 1 only groups 1-15 are allowed */
+} RGNHEADER, FAR *LPRGNHEADER;
+
+typedef struct _INSTHEADER {
+  ULONG      cRegions;          /* Count of regions in this instrument */
+  MIDILOCALE Locale;            /* Intended MIDI locale of this instrument */
+} INSTHEADER, FAR *LPINSTHEADER;
+
+typedef struct _DLSHEADER {
+  ULONG      cInstruments;      /* Count of instruments in the collection */
+} DLSHEADER, FAR *LPDLSHEADER;
+
+/*////////////////////////////////////////////////////////////////////////////
+// definitions for the Wave link structure
+////////////////////////////////////////////////////////////////////////////*/
+
+/* ****  For level 1 only WAVELINK_CHANNEL_MONO is valid  **** */
+/* ulChannel allows for up to 32 channels of audio with each bit position */
+/* specifiying a channel of playback */
+
+#define WAVELINK_CHANNEL_LEFT    0x0001l
+#define WAVELINK_CHANNEL_RIGHT   0x0002l
+
+#define F_WAVELINK_PHASE_MASTER  0x0001
+
+typedef struct _WAVELINK { /* any paths or links are stored right after struct */
+  USHORT   fusOptions;     /* options flags for this wave */
+  USHORT   usPhaseGroup;   /* Phase grouping for locking channels */
+  ULONG    ulChannel;      /* channel placement */
+  ULONG    ulTableIndex;   /* index into the wave pool table, 0 based */
+} WAVELINK, FAR *LPWAVELINK;
+
+#define POOL_CUE_NULL  0xffffffffl
+
+typedef struct _POOLCUE { 
+  ULONG    ulOffset;       /* Offset to the entry in the list */
+} POOLCUE, FAR *LPPOOLCUE;
+
+typedef struct _POOLTABLE {
+  ULONG    cbSize;            /* size of the pool table structure */
+  ULONG    cCues;             /* count of cues in the list */
+} POOLTABLE, FAR *LPPOOLTABLE;
+
+/*////////////////////////////////////////////////////////////////////////////
+// Structures for the "wsmp" chunk
+////////////////////////////////////////////////////////////////////////////*/
+
+#define F_WSMP_NO_TRUNCATION     0x0001l
+#define F_WSMP_NO_COMPRESSION    0x0002l
+
+
+typedef struct _rwsmp {
+  ULONG   cbSize;
+  USHORT  usUnityNote;         /* MIDI Unity Playback Note */
+  SHORT   sFineTune;           /* Fine Tune in log tuning */
+  LONG    lAttenuation;        /* Overall Attenuation to be applied to data */
+  ULONG   fulOptions;          /* Flag options  */
+  ULONG   cSampleLoops;        /* Count of Sample loops, 0 loops is one shot */
+} WSMPL, FAR *LPWSMPL;
+
+
+/* This loop type is a normal forward playing loop which is continually */
+/* played until the envelope reaches an off threshold in the release */
+/* portion of the volume envelope */
+
+#define WLOOP_TYPE_FORWARD   0
+
+typedef struct _rloop {
+  ULONG cbSize;
+  ULONG ulType;              /* Loop Type */
+  ULONG ulStart;             /* Start of loop in samples */
+  ULONG ulLength;            /* Length of loop in samples */
+} WLOOP, FAR *LPWLOOP;
+
+#endif /*_INC_DLS1 */
diff --git a/src/timidity/dls2.h b/src/timidity/dls2.h
new file mode 100644
index 000000000..30cec23a2
--- /dev/null
+++ b/src/timidity/dls2.h
@@ -0,0 +1,130 @@
+/*
+ 
+ 	dls2.h
+ 	
+ 	Description:
+ 
+ 	Interface defines and structures for the DLS2 extensions of DLS.
+ 
+ 
+     Written by Microsoft 1998.  Released for public use.
+ 
+*/
+ 
+#ifndef _INC_DLS2
+#define _INC_DLS2
+ 
+/*
+     FOURCC's used in the DLS2 file, in addition to DLS1 chunks
+*/
+ 
+#define FOURCC_RGN2  mmioFOURCC('r','g','n','2')
+#define FOURCC_LAR2  mmioFOURCC('l','a','r','2')
+#define FOURCC_ART2  mmioFOURCC('a','r','t','2')
+#define FOURCC_CDL   mmioFOURCC('c','d','l',' ')
+#define FOURCC_DLID	 mmioFOURCC('d','l','i','d')
+ 
+/*
+     Articulation connection graph definitions. These are in addition to
+     the definitions in the DLS1 header.
+*/
+ 
+/* Generic Sources (in addition to DLS1 sources. */
+#define CONN_SRC_POLYPRESSURE		0x0007	/* Polyphonic Pressure */
+#define CONN_SRC_CHANNELPRESSURE		0x0008	/* Channel Pressure */
+#define CONN_SRC_VIBRATO			0x0009	/* Vibrato LFO */
+#define CONN_SRC_MONOPRESSURE       	0x000a  /* MIDI Mono pressure */
+ 
+ 
+/* Midi Controllers */
+#define CONN_SRC_CC91			0x00db	/* Reverb Send */
+#define CONN_SRC_CC93			0x00dd	/* Chorus Send */
+ 
+ 
+/* Generic Destinations */
+#define CONN_DST_GAIN			0x0001	/* Same as CONN_DST_ ATTENUATION, but more appropriate terminology. */
+#define CONN_DST_KEYNUMBER 0x0005  /* Key Number Generator */
+ 
+/* Audio Channel Output Destinations */
+#define CONN_DST_LEFT			0x0010	/* Left Channel Send */
+#define CONN_DST_RIGHT			0x0011	/* Right Channel Send */
+#define CONN_DST_CENTER			0x0012	/* Center Channel Send */
+#define CONN_DST_LEFTREAR			0x0013	/* Left Rear Channel Send */
+#define CONN_DST_RIGHTREAR			0x0014	/* Right Rear Channel Send */
+#define CONN_DST_LFE_CHANNEL		0x0015	/* LFE Channel Send */
+#define CONN_DST_CHORUS			0x0080	/* Chorus Send */
+#define CONN_DST_REVERB			0x0081	/* Reverb Send */
+ 
+/* Vibrato LFO Destinations */
+#define CONN_DST_VIB_FREQUENCY		0x0114	/* Vibrato Frequency */
+#define CONN_DST_VIB_STARTDELAY		0x0115	/* Vibrato Start Delay */
+ 
+/* EG1 Destinations */
+#define CONN_DST_EG1_DELAYTIME		0x020B	/* EG1 Delay Time */
+#define CONN_DST_EG1_HOLDTIME		0x020C	/* EG1 Hold Time */
+#define CONN_DST_EG1_SHUTDOWNTIME		0x020D	/* EG1 Shutdown Time */
+ 
+ 
+/*	EG2 Destinations */
+#define CONN_DST_EG2_DELAYTIME		0x030F	/* EG2 Delay Time */
+#define CONN_DST_EG2_HOLDTIME		0x0310	/* EG2 Hold Time */
+ 
+ 
+/* Filter Destinations */
+#define CONN_DST_FILTER_CUTOFF		0x0500	/* Filter Cutoff Frequency */
+#define CONN_DST_FILTER_Q			0x0501	/* Filter Resonance */
+ 
+ 
+/* Transforms */
+#define CONN_TRN_CONVEX			0x0002	/* Convex Transform */
+#define CONN_TRN_SWITCH			0x0003	/* Switch Transform */
+ 
+ 
+/*	Conditional chunk operators */
+ #define DLS_CDL_AND			0x0001	/* X = X & Y */
+ #define DLS_CDL_OR			0x0002	/* X = X | Y */
+ #define DLS_CDL_XOR			0x0003	/* X = X ^ Y */
+ #define DLS_CDL_ADD			0x0004	/* X = X + Y */
+ #define DLS_CDL_SUBTRACT		0x0005	/* X = X - Y */
+ #define DLS_CDL_MULTIPLY		0x0006	/* X = X * Y */
+ #define DLS_CDL_DIVIDE		0x0007	/* X = X / Y */
+ #define DLS_CDL_LOGICAL_AND	0x0008	/* X = X && Y */
+ #define DLS_CDL_LOGICAL_OR		0x0009	/* X = X || Y */
+ #define DLS_CDL_LT			0x000A	/* X = (X < Y) */
+ #define DLS_CDL_LE			0x000B	/* X = (X <= Y) */
+ #define DLS_CDL_GT			0x000C	/* X = (X > Y) */
+ #define DLS_CDL_GE			0x000D	/* X = (X >= Y) */
+ #define DLS_CDL_EQ			0x000E	/* X = (X == Y) */
+ #define DLS_CDL_NOT			0x000F	/* X = !X */
+ #define DLS_CDL_CONST		0x0010	/* 32-bit constant */
+ #define DLS_CDL_QUERY		0x0011	/* 32-bit value returned from query */
+ #define DLS_CDL_QUERYSUPPORTED	0x0012	/* Test to see if query is supported by synth */
+ 
+/*
+  Loop and release
+*/
+
+#define WLOOP_TYPE_RELEASE 1
+
+/*
+  WaveLink chunk <wlnk-ck>
+*/
+
+#define F_WAVELINK_MULTICHANNEL 0x0002
+
+
+/*
+  DLSID queries for <cdl-ck>
+*/
+
+DEFINE_GUID(DLSID_GMInHardware, 0x178f2f24, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12);
+DEFINE_GUID(DLSID_GSInHardware, 0x178f2f25, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12);
+DEFINE_GUID(DLSID_XGInHardware, 0x178f2f26, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12);
+DEFINE_GUID(DLSID_SupportsDLS1, 0x178f2f27, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12);
+DEFINE_GUID(DLSID_SupportsDLS2, 0xf14599e5, 0x4689, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6);
+DEFINE_GUID(DLSID_SampleMemorySize, 0x178f2f28, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12);
+DEFINE_GUID(DLSID_ManufacturersID, 0xb03e1181, 0x8095, 0x11d2, 0xa1, 0xef, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8);
+DEFINE_GUID(DLSID_ProductID, 0xb03e1182, 0x8095, 0x11d2, 0xa1, 0xef, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8);
+DEFINE_GUID(DLSID_SamplePlaybackRate, 0x2a91f713, 0xa4bf, 0x11d2, 0xbb, 0xdf, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8);
+
+#endif	/* _INC_DLS2 */
diff --git a/src/timidity/instrum.cpp b/src/timidity/instrum.cpp
new file mode 100644
index 000000000..0d1fc9ee5
--- /dev/null
+++ b/src/timidity/instrum.cpp
@@ -0,0 +1,952 @@
+/*
+
+	TiMidity -- Experimental MIDI to WAVE converter
+	Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
+
+	This library is free software; you can redistribute it and/or
+	modify it under the terms of the GNU Lesser General Public
+	License as published by the Free Software Foundation; either
+	version 2.1 of the License, or (at your option) any later version.
+
+	This library 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
+	Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public
+	License along with this library; if not, write to the Free Software
+	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+	instrum.c 
+
+	Code to load and unload GUS-compatible instrument patches.
+
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "timidity.h"
+#include "m_swap.h"
+
+namespace Timidity
+{
+
+extern InstrumentLayer *load_instrument_dls(Renderer *song, int drum, int bank, int instrument);
+
+/* Some functions get aggravated if not even the standard banks are 
+available. */
+ToneBank standard_tonebank, standard_drumset;
+
+/* This is only used for tracks that don't specify a program */
+int default_program = DEFAULT_PROGRAM;
+
+
+static void free_instrument(Instrument *ip)
+{
+	Sample *sp;
+	int i;
+	if (ip == NULL)
+	{
+		return;
+	}
+	for (i = 0, sp = &(ip->sample[0]); i < ip->samples; i++, sp++)
+	{
+		if (sp->data != NULL)
+		{
+			free(sp->data);
+		}
+	}
+	for (i = 0, sp = &(ip->right_sample[0]); i < ip->right_samples; i++)
+	{
+		if (sp->data != NULL)
+		{
+			free(sp->data);
+		}
+	}
+	free(ip->sample);
+	if (ip->right_sample != NULL)
+	{
+		free(ip->right_sample);
+	}
+	free(ip);
+}
+
+
+static void free_layer(InstrumentLayer *lp)
+{
+	InstrumentLayer *next;
+
+	for (; lp; lp = next)
+	{
+		next = lp->next;
+		free_instrument(lp->instrument);
+		free(lp);
+	}
+}
+
+static void free_bank(int dr, int b)
+{
+	int i;
+	ToneBank *bank = ((dr) ? drumset[b] : tonebank[b]);
+	for (i = 0; i < MAXPROG; i++)
+	{
+		if (bank->tone[i].layer != NULL)
+		{
+			/* Not that this could ever happen, of course */
+			if (bank->tone[i].layer != MAGIC_LOAD_INSTRUMENT)
+			{
+				free_layer(bank->tone[i].layer);
+				bank->tone[i].layer = NULL;
+			}
+		}
+	}
+}
+
+
+int convert_envelope_rate_attack(Renderer *song, BYTE rate, BYTE fastness)
+{
+	int r;
+
+	r  = 3 - ((rate>>6) & 0x3);
+	r *= 3;
+	r  = (int)(rate & 0x3f) << r; /* 6.9 fixed point */
+
+	/* 15.15 fixed point. */
+	return int(((r * 44100) / song->rate) * song->control_ratio) << 10;
+}
+
+int convert_envelope_rate(Renderer *song, BYTE rate)
+{
+	int r;
+
+	r  = 3 - ((rate>>6) & 0x3);
+	r *= 3;
+	r  = (int)(rate & 0x3f) << r; /* 6.9 fixed point */
+
+	/* 15.15 fixed point. */
+	return int(((r * 44100) / song->rate) * song->control_ratio) 
+		<< ((song->fast_decay) ? 10 : 9);
+}
+
+int convert_envelope_offset(BYTE offset)
+{
+	/* This is not too good... Can anyone tell me what these values mean?
+	Are they GUS-style "exponential" volumes? And what does that mean? */
+
+	/* 15.15 fixed point */
+	return offset << (7 + 15);
+}
+
+int convert_tremolo_sweep(Renderer *song, BYTE sweep)
+{
+	if (!sweep)
+		return 0;
+
+	return
+		int(((song->control_ratio * SWEEP_TUNING) << SWEEP_SHIFT) / (song->rate * sweep));
+}
+
+int convert_vibrato_sweep(Renderer *song, BYTE sweep, int vib_control_ratio)
+{
+	if (!sweep)
+		return 0;
+
+	return
+		(int) (FSCALE((double) (vib_control_ratio) * SWEEP_TUNING, SWEEP_SHIFT) / (song->rate * sweep));
+
+	/* this was overflowing with seashore.pat
+
+	((vib_control_ratio * SWEEP_TUNING) << SWEEP_SHIFT) / (song->rate * sweep);
+	*/
+}
+
+int convert_tremolo_rate(Renderer *song, BYTE rate)
+{
+	return
+		int(((song->control_ratio * rate) << RATE_SHIFT) / (TREMOLO_RATE_TUNING * song->rate));
+}
+
+int convert_vibrato_rate(Renderer *song, BYTE rate)
+{
+	/* Return a suitable vibrato_control_ratio value */
+	return
+		int((VIBRATO_RATE_TUNING * song->rate) / (rate * 2 * VIBRATO_SAMPLE_INCREMENTS));
+}
+
+static void reverse_data(sample_t *sp, int ls, int le)
+{
+	sample_t s, *ep = sp + le;
+	sp += ls;
+	le -= ls;
+	le /= 2;
+	while (le--)
+	{
+		    s = *sp;
+		*sp++ = *ep;
+		*ep-- = s;
+	}
+}
+
+/* 
+If panning or note_to_use != -1, it will be used for all samples,
+instead of the sample-specific values in the instrument file. 
+
+For note_to_use, any value <0 or >127 will be forced to 0.
+
+For other parameters, 1 means yes, 0 means no, other values are
+undefined.
+
+TODO: do reverse loops right */
+static InstrumentLayer *load_instrument(Renderer *song, const char *name, int font_type, int percussion,
+										int panning, int amp, int cfg_tuning, int note_to_use,
+										int strip_loop, int strip_envelope,
+										int strip_tail, int bank, int gm_num, int sf_ix)
+{
+	InstrumentLayer *lp, *lastlp, *headlp;
+	Instrument *ip;
+	FILE *fp;
+	BYTE tmp[239];
+	int i,j;
+	bool noluck = false;
+	bool sf2flag = false;
+	int right_samples = 0;
+	int stereo_channels = 1, stereo_layer;
+	int vlayer_list[19][4], vlayer, vlayer_count;
+
+	if (!name) return 0;
+
+	/* Open patch file */
+	if ((fp = open_file(name, 1, OF_NORMAL)) == NULL)
+	{
+		/* Try with various extensions */
+		FString tmp = name;
+		tmp += ".pat";
+		if ((fp = open_file(tmp, 1, OF_NORMAL)) == NULL)
+		{
+#ifdef unix			// Windows isn't case-sensitive.
+			tmp.ToUpper();
+			if ((fp = open_file(tmp, 1, OF_NORMAL)) == NULL)
+#endif
+			{
+				noluck = true;
+			}
+		}
+	}
+
+	if (noluck)
+	{
+		song->ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Instrument `%s' can't be found.", name);
+		return 0;
+	}
+
+	/*song->ctl->cmsg(CMSG_INFO, VERB_NOISY, "Loading instrument %s", current_filename);*/
+
+	/* Read some headers and do cursory sanity checks. There are loads
+	of magic offsets. This could be rewritten... */
+
+	if ((239 != fread(tmp, 1, 239, fp)) ||
+		(memcmp(tmp, "GF1PATCH110\0ID#000002", 22) &&
+		memcmp(tmp, "GF1PATCH100\0ID#000002", 22))) /* don't know what the
+													differences are */
+	{
+		song->ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "%s: not an instrument", name);
+		return 0;
+	}
+
+	/* patch layout:
+	* bytes:  info:		starts at offset:
+	* 12	header (see above)	0
+	* 10    Gravis ID			12
+	* 60	description			22
+	*  1	instruments			82
+	*  1	voices				83
+	*  1	channels			84
+	*  2	number of waveforms	85
+	*  2	master volume		87
+	*  4	datasize			89
+	* 36   reserved, but now:	93
+	*		 7 "SF2EXT\0" id	93
+	*		 1 right samples	100
+	*		28 reserved			101
+	*  2	instrument number	129
+	* 16	instrument name		131
+	*  4	instrument size		147
+	*  1	number of layers	151
+	* 40	reserved			152
+	*  1	layer duplicate		192
+	*  1	layer number		193
+	*  4	layer size			194
+	*  1	number of samples	198
+	* 40	reserved			199
+	* 							239
+	* THEN, for each sample, see below
+	*/
+
+	if (!memcmp(tmp + 93, "SF2EXT", 6))
+	{
+		sf2flag = true;
+		vlayer_count = tmp[152];
+	}
+
+	if (tmp[82] != 1 && tmp[82] != 0) /* instruments. To some patch makers, 0 means 1 */
+	{
+		song->ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Can't handle patches with %d instruments", tmp[82]);
+		return 0;
+	}
+
+	if (tmp[151] != 1 && tmp[151] != 0) /* layers. What's a layer? */
+	{
+		song->ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Can't handle instruments with %d layers", tmp[151]);
+		return 0;
+	}
+
+
+	if (sf2flag && vlayer_count > 0)
+	{
+		for (i = 0; i < 9; i++)
+			for (j = 0; j < 4; j++)
+				vlayer_list[i][j] = tmp[153+i*4+j];
+		for (i = 9; i < 19; i++)
+			for (j = 0; j < 4; j++)
+				vlayer_list[i][j] = tmp[199+(i-9)*4+j];
+	}
+	else
+	{
+		for (i = 0; i < 19; i++)
+			for (j = 0; j < 4; j++)
+				vlayer_list[i][j] = 0;
+		vlayer_list[0][0] = 0;
+		vlayer_list[0][1] = 127;
+		vlayer_list[0][2] = tmp[198];
+		vlayer_list[0][3] = 0;
+		vlayer_count = 1;
+	}
+
+	lastlp = 0;
+
+	for (vlayer = 0; vlayer < vlayer_count; vlayer++)
+	{
+		lp = (InstrumentLayer *)safe_malloc(sizeof(InstrumentLayer));
+		lp->lo = vlayer_list[vlayer][0];
+		lp->hi = vlayer_list[vlayer][1];
+		ip = (Instrument *)safe_malloc(sizeof(Instrument));
+		lp->instrument = ip;
+		lp->next = 0;
+
+		if (lastlp != NULL)
+		{
+			lastlp->next = lp;
+		}
+		else
+		{
+			headlp = lp;
+		}
+
+		lastlp = lp;
+
+		ip->type = sf2flag ? INST_SF2 : INST_GUS;
+		ip->samples = vlayer_list[vlayer][2];
+		ip->sample = (Sample *)safe_malloc(sizeof(Sample) * ip->samples);
+		ip->left_samples = ip->samples;
+		ip->left_sample = ip->sample;
+		right_samples = vlayer_list[vlayer][3];
+		ip->right_samples = right_samples;
+		if (right_samples)
+		{
+			ip->right_sample = (Sample *)safe_malloc(sizeof(Sample) * right_samples);
+			stereo_channels = 2;
+		}
+		else
+		{
+			ip->right_sample = NULL;
+		}
+
+		song->ctl->cmsg(CMSG_INFO, VERB_NOISY, "%s%s[%d,%d] %s(%d-%d layer %d of %d)",
+			(percussion)? "   ":"", name,
+			(percussion)? note_to_use : gm_num, bank,
+			(right_samples)? "(2) " : "",
+			lp->lo, lp->hi, vlayer+1, vlayer_count);
+
+		for (stereo_layer = 0; stereo_layer < stereo_channels; stereo_layer++)
+		{
+			int sample_count;
+
+			if (stereo_layer == 0)
+			{
+				sample_count = ip->left_samples;
+			}
+			else if (stereo_layer == 1)
+			{
+				sample_count = ip->right_samples;
+			}
+
+			for (i = 0; i < sample_count; i++)
+			{
+				BYTE fractions;
+				int tmplong;
+				WORD tmpshort;
+				WORD sample_volume;
+				BYTE tmpchar;
+				Sample *sp;
+				BYTE sf2delay;
+
+#define READ_CHAR(thing) \
+	if (1 != fread(&tmpchar, 1, 1, fp)) goto fail; \
+	thing = tmpchar;
+#define READ_SHORT(thing) \
+	if (1 != fread(&tmpshort, 2, 1, fp)) goto fail; \
+	thing = LittleShort(tmpshort);
+#define READ_LONG(thing) \
+	if (1 != fread(&tmplong, 4, 1, fp)) goto fail; \
+	thing = LittleLong(tmplong);
+
+				/*
+				*  7	 sample name
+				*  1	 fractions
+				*  4	 length
+				*  4	 loop start
+				*  4	 loop end
+				*  2	 sample rate
+				*  4	 low frequency
+				*  4	 high frequency
+				*  4	 root frequency
+				*  2	 finetune
+				*  1	 panning
+				*  6	 envelope rates				|
+				*  6	 envelope offsets			|  18 bytes
+				*  3	 tremolo sweep, rate, depth	|
+				*  3	 vibrato sweep, rate, depth	|
+				*  1	 sample mode
+				*  2	 scale frequency
+				*  2	 scale factor				| from 0 to 2048 or 0 to 2
+				*  2	 sample volume (??)
+				* 34	 reserved
+				* Now: 1 delay
+				* 33	 reserved
+				*/
+				skip(fp, 7); /* Skip the wave name */
+
+				if (1 != fread(&fractions, 1, 1, fp))
+				{
+fail:
+					song->ctl->cmsg(CMSG_ERROR, VERB_NORMAL, "Error reading sample %d", i);
+					if (stereo_layer == 1)
+					{
+						for (j = 0; j < i; j++)
+						{
+							free(ip->right_sample[j].data);
+						}
+						free(ip->right_sample);
+						i = ip->left_samples;
+					}
+					for (j = 0; j < i; j++)
+					{
+						free(ip->left_sample[j].data);
+					}
+					free(ip->left_sample);
+					free(ip);
+					free(lp);
+					return 0;
+				}
+
+				if (stereo_layer == 0)
+				{
+					sp = &(ip->left_sample[i]);
+				}
+				else if (stereo_layer == 1)
+				{
+					sp = &(ip->right_sample[i]);
+				}
+
+				READ_LONG(sp->data_length);
+				READ_LONG(sp->loop_start);
+				READ_LONG(sp->loop_end);
+				READ_SHORT(sp->sample_rate);
+				READ_LONG(sp->low_freq);
+				READ_LONG(sp->high_freq);
+				READ_LONG(sp->root_freq);
+				skip(fp, 2); /* Unused by GUS: Why have a "root frequency" and then "tuning"?? */
+				sp->low_vel = 0;
+				sp->high_vel = 127;
+
+				READ_CHAR(tmp[0]);
+
+				if (panning == -1)
+					sp->panning = (tmp[0] * 8 + 4) & 0x7f;
+				else
+					sp->panning = (BYTE)(panning & 0x7F);
+				sp->panning |= sp->panning << 7;
+
+				sp->resonance = 0;
+				sp->cutoff_freq = 0;
+				sp->reverberation = 0;
+				sp->chorusdepth = 0;
+				sp->exclusiveClass = 0;
+				sp->keyToModEnvHold = 0;
+				sp->keyToModEnvDecay = 0;
+				sp->keyToVolEnvHold = 0;
+				sp->keyToVolEnvDecay = 0;
+
+				if (cfg_tuning)
+				{
+					double tune_factor = (double)(cfg_tuning) / 1200.0;
+					tune_factor = pow(2.0, tune_factor);
+					sp->root_freq = (uint32)( tune_factor * (double)sp->root_freq );
+				}
+
+				/* envelope, tremolo, and vibrato */
+				if (18 != fread(tmp, 1, 18, fp)) goto fail; 
+
+				if (!tmp[13] || !tmp[14])
+				{
+					sp->tremolo_sweep_increment = 0;
+					sp->tremolo_phase_increment = 0;
+					sp->tremolo_depth = 0;
+					song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " * no tremolo");
+				}
+				else
+				{
+					sp->tremolo_sweep_increment = convert_tremolo_sweep(song, tmp[12]);
+					sp->tremolo_phase_increment = convert_tremolo_rate(song, tmp[13]);
+					sp->tremolo_depth = tmp[14];
+					song->ctl->cmsg(CMSG_INFO, VERB_DEBUG,
+						" * tremolo: sweep %d, phase %d, depth %d",
+						sp->tremolo_sweep_increment, sp->tremolo_phase_increment,
+						sp->tremolo_depth);
+				}
+
+				if (!tmp[16] || !tmp[17])
+				{
+					sp->vibrato_sweep_increment = 0;
+					sp->vibrato_control_ratio = 0;
+					sp->vibrato_depth = 0;
+					song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " * no vibrato");
+				}
+				else
+				{
+					sp->vibrato_control_ratio = convert_vibrato_rate(song, tmp[16]);
+					sp->vibrato_sweep_increment= convert_vibrato_sweep(song, tmp[15], sp->vibrato_control_ratio);
+					sp->vibrato_depth = tmp[17];
+					song->ctl->cmsg(CMSG_INFO, VERB_DEBUG,
+						" * vibrato: sweep %d, ctl %d, depth %d",
+						sp->vibrato_sweep_increment, sp->vibrato_control_ratio,
+						sp->vibrato_depth);
+
+				}
+
+				READ_CHAR(sp->modes);
+				READ_SHORT(sp->freq_center);
+				READ_SHORT(sp->freq_scale);
+
+				if (sf2flag)
+				{
+					READ_SHORT(sample_volume);
+					READ_CHAR(sf2delay);
+					READ_CHAR(sp->exclusiveClass);
+					skip(fp, 32);
+				}
+				else
+				{
+					skip(fp, 36);
+				}
+
+				/* Mark this as a fixed-pitch instrument if such a deed is desired. */
+				if (note_to_use != -1)
+					sp->note_to_use = (BYTE)(note_to_use);
+				else
+					sp->note_to_use = 0;
+
+				/* seashore.pat in the Midia patch set has no Sustain. I don't
+				understand why, and fixing it by adding the Sustain flag to
+				all looped patches probably breaks something else. We do it
+				anyway. */
+
+				if (sp->modes & MODES_LOOPING) 
+					sp->modes |= MODES_SUSTAIN;
+
+				/* Strip any loops and envelopes we're permitted to */
+				if ((strip_loop == 1) && 
+					(sp->modes & (MODES_SUSTAIN | MODES_LOOPING | MODES_PINGPONG | MODES_REVERSE)))
+				{
+					song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " - Removing loop and/or sustain");
+					sp->modes &=~(MODES_SUSTAIN | MODES_LOOPING | MODES_PINGPONG | MODES_REVERSE);
+				}
+
+				if (strip_envelope == 1)
+				{
+					if (sp->modes & MODES_ENVELOPE)
+						song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " - Removing envelope");
+					sp->modes &= ~MODES_ENVELOPE;
+				}
+				else if (strip_envelope != 0)
+				{
+					/* Have to make a guess. */
+					if (!(sp->modes & (MODES_LOOPING | MODES_PINGPONG | MODES_REVERSE)))
+					{
+						/* No loop? Then what's there to sustain? No envelope needed either... */
+						sp->modes &= ~(MODES_SUSTAIN|MODES_ENVELOPE);
+						song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, 
+							" - No loop, removing sustain and envelope");
+					}
+					else if (!memcmp(tmp, "??????", 6) || tmp[11] >= 100) 
+					{
+						/* Envelope rates all maxed out? Envelope end at a high "offset"?
+						That's a weird envelope. Take it out. */
+						sp->modes &= ~MODES_ENVELOPE;
+						song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " - Weirdness, removing envelope");
+					}
+					else if (!(sp->modes & MODES_SUSTAIN))
+					{
+						/* No sustain? Then no envelope.  I don't know if this is
+						justified, but patches without sustain usually don't need the
+						envelope either... at least the Gravis ones. They're mostly
+						drums.  I think. */
+						sp->modes &= ~MODES_ENVELOPE;
+						song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " - No sustain, removing envelope");
+					}
+				}
+
+				sp->attenuation = 0;
+
+				for (j = ATTACK; j < DELAY; j++)
+				{
+					sp->envelope_rate[j] =
+						(j < 3) ? convert_envelope_rate_attack(song, tmp[j], 11) : convert_envelope_rate(song, tmp[j]);
+					sp->envelope_offset[j] = convert_envelope_offset(tmp[6+j]);
+				}
+				if (sf2flag)
+				{
+					if (sf2delay > 5)
+					{
+						sf2delay = 5;
+					}
+					sp->envelope_rate[DELAY] = (int)( (sf2delay * song->rate) / 1000 );
+				}
+				else
+				{
+					sp->envelope_rate[DELAY] = 0;
+				}
+				sp->envelope_offset[DELAY] = 0;
+
+				for (j = ATTACK; j < DELAY; j++)
+				{
+					sp->modulation_rate[j] = float(sp->envelope_rate[j]);
+					sp->modulation_offset[j] = float(sp->envelope_offset[j]);
+				}
+				sp->modulation_rate[DELAY] = sp->modulation_offset[DELAY] = 0;
+				sp->modEnvToFilterFc = 0;
+				sp->modEnvToPitch = 0;
+				sp->lfo_sweep_increment = 0;
+				sp->lfo_phase_increment = 0;
+				sp->modLfoToFilterFc = 0;
+
+				/* Then read the sample data */
+				if (((sp->modes & MODES_16BIT) && sp->data_length/2 > MAX_SAMPLE_SIZE) ||
+					(!(sp->modes & MODES_16BIT) && sp->data_length > MAX_SAMPLE_SIZE))
+				{
+					goto fail;
+				}
+				sp->data = (sample_t *)safe_malloc(sp->data_length + 1);
+
+				if (1 != fread(sp->data, sp->data_length, 1, fp))
+					goto fail;
+
+				convert_sample_data(sp, sp->data);
+
+				/* Reverse reverse loops and pass them off as normal loops */
+				if (sp->modes & MODES_REVERSE)
+				{
+					int t;
+					/* The GUS apparently plays reverse loops by reversing the
+					whole sample. We do the same because the GUS does not SUCK. */
+
+					song->ctl->cmsg(CMSG_WARNING, VERB_NORMAL, "Reverse loop in %s", name);
+					reverse_data((sample_t *)sp->data, 0, sp->data_length);
+					sp->data[sp->data_length] = sp->data[sp->data_length - 1];
+
+					t = sp->loop_start;
+					sp->loop_start = sp->data_length - sp->loop_end;
+					sp->loop_end = sp->data_length - t;
+
+					sp->modes &= ~MODES_REVERSE;
+					sp->modes |= MODES_LOOPING; /* just in case */
+				}
+
+				if (amp != -1)
+				{
+					sp->volume = (amp) / 100.f;
+				}
+				else if (sf2flag)
+				{
+					sp->volume = (sample_volume) / 255.f;
+				}
+				else
+				{
+#if defined(ADJUST_SAMPLE_VOLUMES)
+					/* Try to determine a volume scaling factor for the sample.
+					This is a very crude adjustment, but things sound more
+					balanced with it. Still, this should be a runtime option. */
+					int i, numsamps = sp->data_length;
+					sample_t maxamp = 0, a;
+					sample_t *tmp;
+					for (i = numsamps, tmp = sp->data; i; --i)
+					{
+						a = abs(*tmp++);
+						if (a > maxamp)
+							maxamp = a;
+					}
+					sp->volume = 1 / maxamp;
+					song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " * volume comp: %f", sp->volume);
+#else
+					sp->volume = 1;
+#endif
+				}
+
+				/* Then fractional samples */
+				sp->data_length <<= FRACTION_BITS;
+				sp->loop_start <<= FRACTION_BITS;
+				sp->loop_end <<= FRACTION_BITS;
+
+				/* Adjust for fractional loop points. */
+				sp->loop_start |= (fractions & 0x0F) << (FRACTION_BITS-4);
+				sp->loop_end |= ((fractions>>4) & 0x0F) << (FRACTION_BITS-4);
+
+				/* If this instrument will always be played on the same note,
+				and it's not looped, we can resample it now. */
+				if (sp->note_to_use && !(sp->modes & MODES_LOOPING))
+					pre_resample(song, sp);
+
+				if (strip_tail == 1)
+				{
+					/* Let's not really, just say we did. */
+					song->ctl->cmsg(CMSG_INFO, VERB_DEBUG, " - Stripping tail");
+					sp->data_length = sp->loop_end;
+				}
+			} /* end of sample loop */
+		} /* end of stereo layer loop */
+	} /* end of vlayer loop */
+
+
+	close_file(fp);
+	return headlp;
+}
+
+void convert_sample_data(Sample *sp, const void *data)
+{
+	/* convert everything to 32-bit floating point data */
+	sample_t *newdata;
+
+	switch (sp->modes & (MODES_16BIT | MODES_UNSIGNED))
+	{
+	case 0:
+	  {					/* 8-bit, signed */
+		SBYTE *cp = (SBYTE *)data;
+		newdata = (sample_t *)safe_malloc((sp->data_length + 1) * sizeof(sample_t));
+		for (int i = 0; i < sp->data_length; ++i)
+		{
+			if (cp[i] < 0)
+			{
+				newdata[i] = float(cp[i]) / 128.f;
+			}
+			else
+			{
+				newdata[i] = float(cp[i]) / 127.f;
+			}
+		}
+		break;
+	  }
+
+	case MODES_UNSIGNED:
+	  {					/* 8-bit, unsigned */
+		BYTE *cp = (BYTE *)data;
+		newdata = (sample_t *)safe_malloc((sp->data_length + 1) * sizeof(sample_t));
+		for (int i = 0; i < sp->data_length; ++i)
+		{
+			int c = cp[i] - 128;
+			if (c < 0)
+			{
+				newdata[i] = float(c) / 128.f;
+			}
+			else
+			{
+				newdata[i] = float(c) / 127.f;
+			}
+		}
+		break;
+	  }
+
+	case MODES_16BIT:
+	  {					/* 16-bit, signed */
+		SWORD *cp = (SWORD *)data;
+		/* Convert these to samples */
+		sp->data_length >>= 1;
+		sp->loop_start >>= 1;
+		sp->loop_end >>= 1;
+		newdata = (sample_t *)safe_malloc((sp->data_length + 1) * sizeof(sample_t));
+		for (int i = 0; i < sp->data_length; ++i)
+		{
+			int c = LittleShort(cp[i]);
+			if (c < 0)
+			{
+				newdata[i] = float(c) / 32768.f;
+			}
+			else
+			{
+				newdata[i] = float(c) / 32767.f;
+			}
+		}
+		break;
+	  }
+
+	case MODES_16BIT | MODES_UNSIGNED:
+	  {					/* 16-bit, unsigned */
+		WORD *cp = (WORD *)data;
+		/* Convert these to samples */
+		sp->data_length >>= 1;
+		sp->loop_start >>= 1;
+		sp->loop_end >>= 1;
+		newdata = (sample_t *)safe_malloc((sp->data_length + 1) * sizeof(sample_t));
+		for (int i = 0; i < sp->data_length; ++i)
+		{
+			int c = LittleShort(cp[i]) - 32768;
+			if (c < 0)
+			{
+				newdata[i] = float(c) / 32768.f;
+			}
+			else
+			{
+				newdata[i] = float(c) / 32767.f;
+			}
+		}
+		break;
+	  }
+	}
+	/* Duplicate the final sample for linear interpolation. */
+	newdata[sp->data_length] = newdata[sp->data_length - 1];
+	if (sp->data != NULL)
+	{
+		free(sp->data);
+	}
+	sp->data = newdata;
+}
+
+static int fill_bank(Renderer *song, int dr, int b)
+{
+	int i, errors = 0;
+	ToneBank *bank = ((dr) ? drumset[b] : tonebank[b]);
+	if (bank == NULL)
+	{
+		song->ctl->cmsg(CMSG_ERROR, VERB_NORMAL, 
+			"Huh. Tried to load instruments in non-existent %s %d",
+			(dr) ? "drumset" : "tone bank", b);
+		return 0;
+	}
+	for (i = 0; i < MAXPROG; i++)
+	{
+		if (bank->tone[i].layer == MAGIC_LOAD_INSTRUMENT)
+		{
+			bank->tone[i].layer = load_instrument_dls(song, dr, b, i);
+			if (bank->tone[i].layer != NULL)
+			{
+				continue;
+			}
+			if (bank->tone[i].name.IsEmpty())
+			{
+				song->ctl->cmsg(CMSG_WARNING, (b!=0) ? VERB_VERBOSE : VERB_NORMAL,
+					"No instrument mapped to %s %d, program %d%s",
+					(dr)? "drum set" : "tone bank", b, i, 
+					(b!=0) ? "" : " - this instrument will not be heard");
+				if (b!=0)
+				{
+					/* Mark the corresponding instrument in the default
+					bank / drumset for loading (if it isn't already) */
+					if (!dr)
+					{
+						if (!(standard_tonebank.tone[i].layer))
+							standard_tonebank.tone[i].layer=
+							MAGIC_LOAD_INSTRUMENT;
+					}
+					else
+					{
+						if (!(standard_drumset.tone[i].layer))
+							standard_drumset.tone[i].layer=
+							MAGIC_LOAD_INSTRUMENT;
+					}
+				}
+				bank->tone[i].layer=0;
+				errors++;
+			}
+			else if (!(bank->tone[i].layer=
+				load_instrument(song, bank->tone[i].name, 
+				bank->tone[i].font_type,
+				(dr) ? 1 : 0,
+				bank->tone[i].pan,
+				bank->tone[i].amp,
+				bank->tone[i].tuning,
+				(bank->tone[i].note!=-1) ? 
+				bank->tone[i].note :
+			((dr) ? i : -1),
+				(bank->tone[i].strip_loop!=-1) ?
+				bank->tone[i].strip_loop :
+			((dr) ? 1 : -1),
+				(bank->tone[i].strip_envelope != -1) ? 
+				bank->tone[i].strip_envelope :
+			((dr) ? 1 : -1),
+				bank->tone[i].strip_tail,
+				b,
+				((dr) ? i + 128 : i),
+				bank->tone[i].sf_ix
+				)))
+			{
+				song->ctl->cmsg(CMSG_ERROR, VERB_NORMAL, 
+					"Couldn't load instrument %s (%s %d, program %d)",
+					bank->tone[i].name,
+					(dr)? "drum set" : "tone bank", b, i);
+				errors++;
+			}
+		}
+	}
+	return errors;
+}
+
+int Renderer::load_missing_instruments()
+{
+	int i = MAXBANK, errors = 0;
+	while (i--)
+	{
+		if (tonebank[i] != NULL)
+			errors += fill_bank(this, 0,i);
+		if (drumset[i] != NULL)
+			errors += fill_bank(this, 1,i);
+	}
+	return errors;
+}
+
+void free_instruments()
+{
+	int i = 128;
+	while (i--)
+	{
+		if (tonebank[i] != NULL)
+			free_bank(0,i);
+		if (drumset[i] != NULL)
+			free_bank(1,i);
+	}
+}
+
+int Renderer::set_default_instrument(const char *name)
+{
+	InstrumentLayer *lp;
+	if (!(lp = load_instrument(this, name, FONT_NORMAL, 0, -1, -1, 0, -1, -1, -1, -1, 0, -1, -1)))
+		return -1;
+	if (default_instrument)
+		free_layer(default_instrument);
+	default_instrument = lp;
+	default_program = SPECIAL_PROGRAM;
+	return 0;
+}
+
+}
diff --git a/src/timidity/instrum.obj b/src/timidity/instrum.obj
new file mode 100644
index 0000000000000000000000000000000000000000..5ea265d40a19a739823c4b43c31c267d835cfe83
GIT binary patch
literal 20356
zcmb_^4|J5(nfIM!2qOk(z-UpU4j2Rk(}V;FLYGO%XmA-#ka430hme_=K$6L3CMq6M
z3B!C}hL@4pk9f4z`lohvw{>mneyH726I7zRY}ZnD*A%qT+8*L?T#n|f(WYg8&vWnl
zZxYb`&i8Vd`Q7I}|L$|2`{#Y{yjky9DBkmq-}K1}A&v@p6^A<`jq!UTswn&b8}sy%
z`kvtt#o@@-uDgqF(`(uR9?jzO2~SmVXSgBWpmPb)n=8ch>1LUbPn7tI?QCKi==)~~
zaRC46a^OGs28y@FVoXpU1xu_YiFXO#Kyf^BU))kve2oy_gRZ03OX3gl<rTGCvi);`
zet)?|5YXrG`KIFA{T2E4enq}-e?`9Ud_}&er{c?siwEudyDUDh2XV>byBvH!1K)AQ
zOZ_nw-zA{G2cA#AgzsYT{Ymiv^2PeH@`d`tvrI9luc`XOiMlzH_>fmq=qsGWH+BCO
zeMP<{Uy-l;EAUY}R!`!ax*fhrd{eih<}32mPQ{m%2L<r29(=8e_gr~!HTdoUPyd(j
zVOWYM6b~R@Q{`0&Y7U=^uV_c35*392pMdY8<&r}2oJwCY=*z(qR(zP2rsTU8^xfcj
z?o0SC1>X_yoK$?axMcbF2k_)z&ZW!dU(t@Rqzd5%xDGrk6<-d@Q?+9;=mGHDr})m*
z4jOL{gXeD*UzUG1yDx(0u;Mf9uHCS)sD5n_YWSjY`S0L)&%#IZxYz6bdbR(?%HaBq
z>(^D?Ft4>`Yhy=Se4d?(s<nZQS;Sk{SKm?@@ZUDiOGb;u1#D=++8Z|AAC5-bHpM%;
z+P80tbvAB_x3sl{TjD!5wY0}TZ7Ys$-Buwk_BiHYaS<0mZov`xIXBSK*cpvQo8sPu
z8y0zQ?I6+nTiWmTu8p>Jw6sP#h1l8<_BOP(MjIRAEm5pv#T^ZCGQCbJkF>|VTjs|W
zcpDqq7sS0=Bi^QHS9`cv+;n4UZRN(bHKnB`VqNt$C3G!?oIx+K)91%-@U}<e-iCIs
z@*Aqbf7^{q{q-A#Sj+mG8`{II5ifjbY>vdd_qN2Fz4OBs310qeyrwpjzqMgUq%$TK
zE?wk>34v}A^JDX4-<%&_GC#c8J3qE?e)xLmWj-<xZNldw;`MiSMmxQok%lnEDb~=|
z0e|L)h4(tR?~Qjx+M=yd(s`X;xWOB{HxlUp(-Ln-b3=^Smw3aG4)&dNO4)l_wstne
zm8>dQWgFwIS(4uCy&GWf9`d9$8tp*v!e57Xv94IWp`~3|>m%*=L|UUAymbNnmMBXs
zK}(bZB}6*B7Ung4!N=>pe-&xz47W#OF^iPJDRn9(Q!${O8zT20>=Ef}OFQDsZNDel
z+SL~EHX`S4@Xl{yTernKTRJ+ZVQ5J!WW&hhSR^ju(e{XUYeV~X&}zDxi@h5=TOwg^
zoZ5p_we~hFv`5>okKET1i&LJ?k5NbAg0J!BmY9VZnbg|K`G*W{jx=;KAKCPhQCwEL
z2%c%j+t$$0Aw6IN=;Y4m-JK0>Fop)6OUK%1S8JGtOqSc^<-$pdIQ0ZgOM>Q>JQ37R
z*SQb+^9RTC5+k{<(8RX*%~W7Q3r=i5mYWJrP-S9djTV^r^+?J;q4_6T2C9FAZRjhz
z{!xg}KG%XS_Y1q@KX8b_kr~?WH@|k&cKv_U?>;z_{QhkCinl%+P7Ge~>ygRkj=~;1
zb`6ePDm{_q{NzBFvuq$!s0CbF4>`Mi;M&0vkNaRCzvV4z$fKWq&H~C`mlC-4gg^fz
zo0Xpsa7h8g=!Adbz0XgPPo^Wv9_+YZU_*l=(;j_A{M!`>6&c|Afa_Q9557O`Wg_c_
zRn$<QIKBFI&F^v_?EMg`6YuAy0!I_wM|0i(y9raa7Cd^ksGp7c^HctDYEhjl?d*4g
zR?FTbUU#CWhWum1mAOC*j3ovgslXVzO7oAcs&aJ)w7__7V9foCS;=GFUd=zA>qo(r
z3cPv3|K>^F0BFH6^!0FIU*;-<ITd{Ug#UF_?;k@>3>Wrfo*4Zl5=sl^r-EnTw^rjy
z7o^FX4K{C1D{uU#69bNv|1>!sJiV&U)xBEtpUL&3-&{STyTFwSoZ3-v!hcG4<g{{R
zwn+<|MuH9(?#=vow2q8ZC_a>-_|+G1B30|6(kxl<aE(*dI3?edAsbOn21Yn!BOJ03
z3fU?xfYOKVSDiPf{3jFLCq*}WJgI!7I`#)X>NNj|3eZnRpNCow&^t0fzoKE2x*6T!
z(p<7IgKM0s#wq!x4CHa;qyIPu@;Egoc$@;cPV=Mmj{Bv8%ASwJE<`f;wn;))!{fL0
z?gJ-xA3WA+!Q(2@-<KhhPJpI$O$=8*4EA1BBwiUH58|^aIJ*fOX~(Wiq#h#TT{I~A
z0BjfMcRrGF$Um_gHADV!swW$PiNq+<bRszrzZKC$swQ^!<%{?n)YNT5{KoWz%Fp$W
zQ|gn@SmNhB1+<j`UvOf>HV5Q%e-vNlDP*`BqHMohjg3S%)X&!ZoF4~R4RSa$Q&Bed
z$do<<TZ!&*(Y2ZdE*SF5T%N9g7Vy*|l{Yj~OSXw^Ioz#vD*NY@4UArdc@+(&dW^^5
zMB=amQ)^H6#4b?W>C-f>{S(^<mSCtQb~;>V-N|3`SfAJcUuv~LJ`A|@=EL2I?Qbp7
zg5#;`8OR}vpor&Wo`8Yv$CjYY>Gd8bd*MF#X7Vlfo+Gf9==O*>n!xl7njqbzNPzkw
zI0R7{Xu(skKLPvHzz<~u%LYh=yMLe-v8FMmyX9roH&Tqc=1Q*;FS|%#m-2Y1hl*!$
zWv-S^CVr+fa4Uv(=?q2_htNdQLFqvA#@Og981UQ?7lEl~a2G05fssreG^T<h)^IF5
zOG&DNp{@5UOa)G6JY~m7+TDLRy>a&0RPdx#q9{wEC>3~Tk^}~^8S*(<5_3|4<C!bi
zKMYHF^fra_ZECdUe>+73RCS(KJ%hS-1flg%m}U2C8y!nfI*FQhP;=Z?Q(!9<+DhIH
z)HBqu0mzy*^376u{S288wa8F?kW?7Vle4L-mK%b^I7)p$En$F$!8Un+6h4{(cGfi$
zACCb{4mr9FudxP={&|7|F{ZDW7>_-tPa)TzBEhmZurS<yB>Cc>&Yl%VlF!oDP;%_g
z_`-!zi>~Ls@0yNLRq{2Ma?ps+*A3B%$U}7ljhS@%5okC?vh0_oOmdduB>Cym2`i8L
zU_belJQklv7R{Dg2SEY+dIYzr7=5+P{|UonW*iOBrvuM7h}u=Y?NaGdKUL~O2>X=y
zYP}{&HHKrTnMZ9h%~}IYX6M4}wmj9)Ao5K&NxPp}GMWze!C+si{|q#{4<>I0Q)1i^
zFLfVG6HdMrzZR_|c#g@8?7VUb=B$Ik{fRFe@x|C|?T2gEU}LoZJiaAaCQThm6Vb;A
zLE;d3A>0qXkHX~1G0{D7LHF%L$tNiXaAn*YW10jcI7C&>?h@p$)=xOSXYZrnbRXO|
zjY5aS9Q?qU-gC*zsFNJ2{JzY~neW0H-S%QC_D}GnG68EBXIV>=O4<cTU|7S-#P-ws
z`o7QB4$Ne0{)xd6XL{c>w#FQ(f*!Iqx({q-1F_7}ZcLi2vNe!fL2H8q%48wDw5aB~
zA?HI-Dl$hiFAOEgB9(@cKL>gM2)*))RB`~Nq2%jEEybG#&`|O*o#sD7g0x3G!(N@y
zf@fCA^hm#ory1MwPZ0y{D3i}Y2s<^nqNNG%J-}`4os~r-Cwl7~+X_w5ACah3kS3g}
zaccdfW2nl-`VX;3@mU(XCiSOkT$y~ymg+xE9m*O~%z})iWRf=nT0bkr7D{@@<_=7w
z_NVHcT9V5(&X%`muc40$c)(IsF2%r}X?4z2ZN8m?-K0mU-ZzcHnM%`?B;_@po>El>
zi9<Y3#TRH?dk$lBFp1G_1)B_ZYsfc820#ku4<&stK%~qJ6DPV0MEpW6P?SoqMm6mr
zDpLIfxdu`d7AYNTN$PP-($u$gGLr6tRgPL2U-!XT8#FctmU7gSK1}OaFw&$#4d)!%
zdRgZ3@}cBE=*HYynlO(N0$M+Pt_qI1(>rI;5NFOVbgS%w{8ah?7&3EYjibx0f>>3M
zN>a__VS-V-s-LRd=_07yHl1cxm08&1kJs@`-mif}pG7^G)7*P5Myg}voNJ!eJ=ks=
zf%Yjz<DtJQFwwn0&L1tuD1-FY%+U_a8bDdM>&(1rRAptER)v*m!e+`ozexS7f@F%F
zXc=HrDk(kMo*Xizx13DrRfc(K++dNtY&e3M`>fmSlgklUYjbjIJ*oaa>YTZHXlUat
z(^&RW?#**jX=0%+K-it>^gfhy3l#SFw9#H38qAiY>J$c;>?UWrp9s3Yioc#mHuJN(
z6t+#{*vCUX%j@k??2Cw9dcoRgfJ=9B(&?R(-T53O_e=d$qb11-4e#^$^QOV6Nq=f^
zV{smb9wC04LzuA76Q5?{=SXWRK3?20z#}86xpJtA4vmm&<nxfPfTndb-FJhS6-N(9
zdy`br%R7fe_nfm`S7V-})hS)@FHR^(eDHu&*xN_eGPAY7oW%PNNOyYo5>3_*fBfzv
zPg02+Hq>(xm@dhAWcLUc*ta9QXaQH8M{wUs1#Uvq&~Cm_QaYQ4cG0@8vo;O&@Zw`K
zf5NqiZm)=UpYVI-jq1CPcc4j$)2?@u1l}zyB1{LD0$9b&Be|sSCw$(MY2qC|v>c!A
zmy+}~Q{>}5Az4E7=jrpehZZuXN-a%TdY!u2%l$Wd(#|b0J{$S{+#hN0)IW{(OUL3D
zVjGo5aG-0Q9<0~79eNaocE72YRlGi+ax;Bw%f;ks-9CCo_k+AQ>4Zwm%C4-WNbM%w
zdy`~*%4#wtqp&wkZuRz(G1)Np!QE8A?A}L>eTaCm0C1s~^r{J;e6mjXg~%ZnHSsNK
zZT|%XkD;|PGk5Xh2r{Bl<sD$U^i=sB70lMs6A!&rp3wuS+uKjt^=!uGf;>v~kRGW+
z+fBH3Z+bfTbXk|`t=(i_Z!-DT+e;RWCR4#^GU+jzOoHcX@_9$v-+YenaqIV{dA#w1
zIyFh;q?0t)WX)PJ65)n5X*?G-r5^eNxp}^+>n|W=pSt?SAfvWEw2yv*RLIF`YP)|N
zKYO-G<9V?umEJ@BF4-Mvo+`53JPLNzrgY-Vn4bW%Y}4L;4#Pt<_P6oqIR%7WqbqaK
zwj%4?mgvSE_AJ>sd;LYdyGOx?y%2}yNDr0FuKSR5j5y<kvf6#{A*TG3R|dbgkBi+4
z9bJAq#|Nw9W#`B^BQK=#*Q8)?k5sk9(RFhchcEs$Q<ZI~N-vDZw699R-o2!1lsYQW
zy+pY8P*Ad13%e?^tUO`$!?b4|hMv(Up~<+xqgh=a3v}C<kD-~1^iO;d-nlHxp3OB=
zyKN|D{8=t}yUMbnirozjO&NDn%@jd7vg`No6*n4m4@C%fZ}$C8f^Tl}-025VD+{^Z
zMcXcv`=GshE}`~Wr?~u7vIxpD-g9!eB!{e*u4-wLNv-cIGNk@YdQXv0xT%p&$%dz@
z_xeU}M+7mW^L^?-ybME)hC$-<Y2U&F0fN&|OW!R6d}{E@sa!q;;q@+6wVJl|6r&;E
zYBC`MI8<3r1zR+xy4B0;u9AB{DR!c|N4YI^4sYt)WOeui9JcZgZ|b=(^Ea|F!ygZS
zhn~GE$&ZKY<5DRW_D7$i3icSK@BG+c8`^agm@ega&)c$S;eMHFxrs*NJK~o}ZCaJ<
z(Zz><HU16!m_$4<H=C`iK(h7R?;?h8O&i@jrcb0eG@yGFqhDk^cMp&?o+713IO#B^
zZ>rM;)ZTC=>0?p#Tv|%$Tx!X{Iw%Lf$GwNr)cRD#VO#kMOsiNExz$>))h=#NdHKW^
zmuE1IpL{F4&ZYfdu02R1!_~X-{QN;BkdsQbB25&hR8r|$T&1sm4ny%`3uCGhH?5@7
zXu^L=4RZdn*S)E9>2q7*+4$ur{DtcAKdX87JCDG#(R|#Hnn{11`z2l|QfV(Nok-H>
z6hi+&w^uSGHLg^;7F<KgdVE5oK>9r%2Bz<g_}2QVKGna4uqBSE^d?X+IdDzlBRu&a
zmc$|Y6!Cg3N%g~G`0+w~*9|A>YuMQKIfozT-85@S3_7K{;Up1<lVte4;opw@*Xz^n
z&sRkDCe!!-=+92mpE=SW`ucKz$P;yn$Y*QP8R=VP*WBSG#p)gDHpfltr%3XXAOfSU
zG_fBdDB=YtILz-H;s7&yCV!YW#46~l?j%N`-b3#`Lj5dLg?7(9RK)JNnOJxaYF%jN
zsI07W4Ss+pA(U@)oF)@|mPzyId`^>AtCCKeHTXq-s=petdXhK?KgaF=dZNgDuW}8J
zV^*&LC2@#89p5TfD&;WZDjGK@uOvsQC#&zDrKMQ{M@0-WuW9|HbX7l*zXgZsJC$TZ
zX>wo{8{JuGA08url(zJtL;Uu3ke<Q!)ey&F9fk-!Wk`&;2S-#mE>z<*m7^*L$DOu%
zeZZzLvN@9OU-Z%@2Yc!HS^hY?E|wnQiMnyr<4SawIJy>@9c5dyRUp+Ubi&8c*FQ$O
zG||VR(}*-Sd&Zoaq-SV%X@S#a|H3qW1kdMTI79N_W+PqDlM3Qg4fn?q6MZoqt>W>Q
zho^Re0S^u=XsiZ3Xht!P5->nF<0y`9+=p|9F9RhpfFo$S?;pk)@@1eWK5{~L=CJ<U
zU;mMSw4Vx$<H^r11O`uR@YKOL2waP&6j%V{K_Ko{<w2m)sC;H0H;w@5w1<xXjYmZJ
zndfaL*<L=MYRs0c*7SqFG5f(Ej=#rnnlSl^lsdM<X{&znk=W^RiTDb*Cy(V4J7>7g
z;z6M0OvmU^JgrbCJww$F7g{VbKd^WR|LSm*<)1+K65Y^xEycCYh2ugxRjhI9i<#4s
zCC<z*brmw6B-u4>^q-(z`Om^Q;2+Nvz-@W{h`>%82MZmgQs+&qs&n$M0TM3{fJ{fD
zfpNK{8CjGWv?|15*SO_ClZ*r=R@D?h3%dMgx)$iSEt}5FQCerKj58=Yl27cIBjRqR
z&oMV><}I+3n`q?AwzK#r_|rr<d4dnvHX2V;D)8715BCv`#G7X`E7`3=r)(U#Mc5Kj
zI#W*VCL(kU^>_g=^`0#ofbqjl@%IqKGc^2eW<Hj`Dn^Te)-b99ss<_$F9F@jw6}rk
znRW?Q*}r1iGN6r&?gEmv-!p1|YSiXpZz*f5fNo`p+kw_Ide*2NGiqJ%SJvJHR0F;O
zu?6T3mIwpYGHM2@W7G<?j?oK1n}7<$e;8;5mKFN#W1jE<EnyS@x}MP{pu1S&?}0Wm
z`T!`*=nJ4?=F80!;_Hk&KqZVW11e=y3`9TZ%M*Sesc#F=5YwUt>H#{!v_1oUg5S6|
zfL0(Tfas@O1!55fRT=Xw1uAD$2Sh7xp4b8;ZR`a)z_ho3{tr-r_z>vZj7EVxTssEz
z5Yuum(5+nnBqbIA^>OVgAUvRkjhlg_jqO1DxppVeY_9DEdXQ-k1L2GX5>Fenp8^#y
zZ4gLmIR=FDGw{7<@LfJb2%PtUwg^b_-2(JArfoK8^YZl^+H9aP12xRlYaa%h!?I5U
zUB>7r5d9)OPrL(k1*0o368!7mJh22w+I<NqjoJe7OCZ_a-vQC8ohLpq_-=ITIeDjn
zeqx|E40PN;zcY{@ow1*7bQ$Ow106TeG<1RFTV|k$f%XAOPY)Ti$I&e*_Vi_-|HWtk
zD8bg|ql2Wr`+=_F+7ys%`BOlvm~Q~c&*&m_-_^`_C6MH62J$lPUW29q-N+Iz0@3Ya
zfpDOk=Q5uMNXjk%k{;Y?p#26qW*`UBLGl$Fs2)hRx7k3y0h-4)MuDV_`AC~<m{txX
zY5hP&Onb(ly$Cd)X|Do32vi_G1-h1LIY=NWdjrq{rmZt*-v_E-i8p{&GWrbY21fgl
zX!VQ+fMh%V0CW@6Tu8o!KzZV8K+;A9(BoYDDbNF4yRcA*os3=rdW36lLK;f69cTyF
z{x#4&jNSr@F**U%!RXIGix|zkO1Jhb&_1R;iq35VDiF^CUB|S8K+>z<0kv}NXF&ao
zUiRYG_l$mHpzY}9Cz<vl&{K?F1*&1R2puF*)Ihro^oW6;G0<;-<ouV1j+C0A3fCDQ
ze~GpHJ#hB7syrgU2STd6#YcM&`spXOVEl_ly&RZ0C2)QA&J_bVEvIGpLKN@D{{sH~
zD9I=uDM;1ZY}KSPaVno=LC1A^jqpA87ZQ7X^4lf9N202Hhx+DPi3h%I(8#-C>^;r9
zLFw0b!7lwmE#2)H3@+?au^%NTFeiz}bVlq06Q}UgBF}xZun##KEWl@O06$W5Wj@?|
z*h6i^rr7<0-;;}97SbNa!y8oM54sl94<A7zL4*Gl=oTEcW&`1Q)8^N(w5pX2Q@MBn
z{|or1^lt#=@oMlRre(#RwKVTS)i(UF3qM+L$}JN0mFkb5vK%HtaS;FWcs&1rX}T^P
zK+++I&}O>uNr$!!ln$zS5&!deostLxOVM;4dOv5|Mg4i(e1tXI+x&B;Vd4}<n@t@u
zRHoRgU^CL~oZ-;jLrkcRhuH$P;yD(lIj)aUc6(G1jmm6Ch9H&4FH1;AP&BR|0_E{L
zCM1Qu68!?bxFx*cNIQ0qLgWfxQ=}nYTvFmdy`)&*03>Rz!;eq<WqtcE2UWR_b~^(~
z);F(mnWvY&a6RDoI#w~dif+4UdFj%njF|`T6`HRLQby)Frs0$Pc(`Qxz0AlTR7V%{
z2<uyc#GmV!>b*d@k02@HA-{!Kf&Zj~{^yWCD^}o@8_HGb4^b-cA_tZO8>Ng_3@cAL
z?_jNzWqi0~rK}LUvh=JJy>?1zsrUywrF?}rnMJAKy}m_r`AYGjoo87I@60Sb%Sw6N
zTPQ0^g$JQEDJx4##lkEKTAD>!F2Y%q3K6$c(5H`OQI?6HXHiy)<913}8F!1tpR#h{
zLPuFCE5t%OW!W;Z-cDIjA-32l6)W-kQ;VL8l4Z+9Zx&^R=*yy1h{x;{yfTKZiRn*8
z$?_HAcot8En8>266fQKuXhlWIiV{&^r&N>(UlyfQtk0q>74-(i`i5p6`Q)H?TxrAy
zT!*UhrLGO?J5P93&hG<d3;vswU!e51jdBX55gX-l^c7``l(LTQ>EO$iPYi&re{#!K
zeRScaCP>glJUO;Gfo4Hd>h{gO&zn-;R8qu)P~wUy3Smj<HYi?2Nr6&}|7N>>hEj)(
z@;*v^1|`(g8HtcezDQ5e`des9z_UlvL;tNUrB%Z8`G-}1`?Bs0?t`RTJQLenid!Cw
z)Xtwiz0j1pUP%!TTQ9{J5Jy0j*5Oa-N>J7r6ste9oiY9S%ZF=!V`}y*&BSB(ht^Y5
zYQ=SPvCt`Iu~`A)k+4gz5?<-y(q*XoM~)$R2@lIkK-q#Rl=Bc6`%6f5FomS(dIMi)
z6a~S>ML#Z=m(H_PR)9zpYlhDRE!RQwGF{>7#h(hL7tb@4)XYgzw3*6vV6)CE$PKTT
z{K=GxDyfSmNx4}Hw=XPptEl-eQ!1sT+>@ku&2S);eC6%f<1nSZr=&bAMGUWjLO&^|
zT$Pox5F2@URm&=D6l=fZ7SYm!?>_W=wP}l<o6to(_I{^DH`k#n?GCzIO{qPKOgwfe
zYLS^OgSTC^+LSt=q=?5Zg&RkU)Fb~q4O5KX-9J)N#ABDDn47lh|M1Okm{PARDdMq9
z@r-QDKj~+GaHT1AQb`eyT?#jcmS~`WOZRY0NfA$u2z5m7<!b)$X6fObfnmL`^5i|H
z9w%)ya1oE)`emS)Ev;GOe8!Y2R8quam!eEIrBJ}7`&_Q1h)2@aD~cRVp|(g{V_OHy
zC<<o3hOi<Snb~fWqS~HGl<!WW{A3d4ghA2$`PiU%sa=*g%V{5sJsUnTXhg~1-THf7
zMu-b&Qo(fvduWv^1U1)jH9kpd(UBwHlTx9khOMzCkZ={U6jLMtpJbh`OG&fWItEvQ
zvP)4UPXj1tY&^Z7^v$(Oy#mVHHp)k!{CJ+^v9}bd37@aQCtW$^E1xu5dIc?#xUPg#
zcB!jC&2`W$OV^SOUau)tuB5IqrPTZ86q#HHMTye+y2DE=O{u?BQddJNSA-hS87*;e
z(Uoo83mSG8^ojo;fA=oS`qioQcqd8CWvOeG)Zx2Nb(+@qE2+6G1s3tNq7b&X9btrZ
z8ov(wpZ?q^rf1nV_-h_YYMyCbwVfh-uC||1QrAE#SLUI5^S+3sX#OV)vp>yy$h37S
zZANevnNpI#&z*7|i&3KZ<b3qpX;W&olA3RNsH8YDyk0m!`TkQst~aIbP*T^LQp&>x
zWW_PHhd5f{YXN&m@EK4%2ptVP8MA){h1eve*z#8YLQv)0qDL15b^Vb<a4`>~E3g%!
ztdf))Pzr_cB7KNoqz?t~3U$NX5wRv5X=><djfd0=!@iB`&3&J*tfo?|X=;eYL*Ync
z!wzdr=oVBpM)5j#XFR08ejUP_;}PU(yrFTsh53%kn%c_XZPE6-eWgCX-*@LZB+n@_
zr9`x;2`@$4Wo)|DTks)%@7>O4amT7#z3x7hgue!ip%5nHR$qBZ6aOIr{llb}m+3V6
z2EC~@-W7|43B{wWOGB|30<MQmcaWm?Kk{#~fUV*3&jEz!F5RZKo|?^nDggaZTUBpu
zQMjq8snKxWY*ECZsTQdSEVFQPOBH>&oqtWNp(zq-<BYTUQd?QSjzUlGorl_5VlljA
zA2MHOkNG%V%v7wa^w-pbzX@;1BZ=G3nO`;3QBtj`E);H!Wtk7IGyZhMdipyI6cQsz
zYowOOw%FaiwbAx?XS6jC4O=^@uCfwLwN=%3!rGdu8|g0vAisTeReli3({(X_TSt6{
z&sr6_g=*GE+HKTzM69OXGI-aKnxHCjm5-P>T4uBUCryG^aM-vgNR6%0SR{mqS{rsp
zC8bHICAzh%$>$@sj?M`3UM7{zBIlDF)z-{{j2b9qO%XGvkQfF%va+$<QZ6qkwWp0S
z&`R)J$Gmj;@{;9E*2FaF%T}7)rahfz^63=$ZPHgRwK}ZR?cC72Cbqq$V^S>ipmAca
zDK$D1xy!w`<_;^Vvhwy4jKXRP@tP8AjZVei$!MS4J(k|#KIL$SVv)wKPW-u*P-8T@
zy(J=iAq%NF(zsn!_{zl^?7V!wJ1PSKGTkZv8pf2fML-S%x&i-^u~|>q$$XQ>rX@=(
z>iBP(Op4&-F-b0F**}jI1~Ca>UBHSZS2fFMnPN)O;~%Q=-Ek8<tzTDDyWEfO65pMq
zX-#OIwZbHa{P^Q0?P2_hju8D#j>)a2fUGImh`K8KY-MDFz0ZmNmaf)THIW$kP#V0^
zB+HoqE3c{9-x_JdpB1qL#V`=EIH~?{iG>XVoqY_2ZZw*5Zm!bvwZy1AuQR5Zb4Tnv
qo|FX*7fO8yfx$Y3wk+PzB;KNy_Lg`{Lu<?Zk<KDv)~f*6tN$A)cM;V9

literal 0
HcmV?d00001

diff --git a/src/timidity/instrum_dls.cpp b/src/timidity/instrum_dls.cpp
new file mode 100644
index 000000000..9987d05d1
--- /dev/null
+++ b/src/timidity/instrum_dls.cpp
@@ -0,0 +1,1260 @@
+/*
+
+	TiMidity -- Experimental MIDI to WAVE converter
+	Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
+
+	This library is free software; you can redistribute it and/or
+	modify it under the terms of the GNU Lesser General Public
+	License as published by the Free Software Foundation; either
+	version 2.1 of the License, or (at your option) any later version.
+
+	This library 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
+	Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public
+	License along with this library; if not, write to the Free Software
+	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+	instrum_dls.c
+
+*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "timidity.h"
+#include "doomdef.h"
+#include "m_swap.h"
+
+#define __Sound_SetError(x)
+
+namespace Timidity
+{
+
+/*-------------------------------------------------------------------------*/
+/* * * * * * * * * * * * * * * * * load_riff.h * * * * * * * * * * * * * * */
+/*-------------------------------------------------------------------------*/
+struct RIFF_Chunk
+{
+	RIFF_Chunk()
+	{
+		memset(this, 0, sizeof(*this));
+	}
+	~RIFF_Chunk()
+	{
+		// data is not freed here because it may be owned by a parent chunk
+		if (child != NULL)
+		{
+			delete child;
+		}
+		if (next != NULL)
+		{
+			delete next;
+		}
+	}
+
+	DWORD	magic;
+	DWORD	length;
+	DWORD	subtype;
+	BYTE	*data;
+	RIFF_Chunk *child;
+	RIFF_Chunk *next;
+};
+
+RIFF_Chunk *LoadRIFF(FILE *src);
+void FreeRIFF(RIFF_Chunk *chunk);
+void PrintRIFF(RIFF_Chunk *chunk, int level);
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/*-------------------------------------------------------------------------*/
+/* * * * * * * * * * * * * * * * * load_riff.c * * * * * * * * * * * * * * */
+/*-------------------------------------------------------------------------*/
+#define RIFF        MAKE_ID('R','I','F','F')
+#define LIST        MAKE_ID('L','I','S','T')
+
+static bool ChunkHasSubType(DWORD magic)
+{
+	return (magic == RIFF || magic == LIST);
+}
+
+static int ChunkHasSubChunks(DWORD magic)
+{
+	return (magic == RIFF || magic == LIST);
+}
+
+static void LoadSubChunks(RIFF_Chunk *chunk, BYTE *data, DWORD left)
+{
+	BYTE *subchunkData;
+	DWORD subchunkDataLen;
+
+	while ( left > 8 ) {
+		RIFF_Chunk *child = new RIFF_Chunk;
+		RIFF_Chunk *next, *prev = NULL;
+		for ( next = chunk->child; next; next = next->next ) {
+			prev = next;
+		}
+		if ( prev ) {
+			prev->next = child;
+		} else {
+			chunk->child = child;
+		}
+
+		child->magic = *(DWORD *)data;
+		data += 4;
+		left -= 4;
+		child->length = LittleLong(*(DWORD *)data);
+		data += 4;
+		left -= 4;
+		child->data = data;
+
+		if ( child->length > left ) {
+			child->length = left;
+		}
+
+		subchunkData = child->data;
+		subchunkDataLen = child->length;
+		if ( ChunkHasSubType(child->magic) && subchunkDataLen >= 4 ) {
+			child->subtype = *(DWORD *)subchunkData;
+			subchunkData += 4;
+			subchunkDataLen -= 4;
+		}
+		if ( ChunkHasSubChunks(child->magic) ) {
+			LoadSubChunks(child, subchunkData, subchunkDataLen);
+		}
+
+		data += child->length + (child->length & 1);
+		left -= child->length + (child->length & 1);
+	}
+}
+
+RIFF_Chunk *LoadRIFF(FILE *src)
+{
+	RIFF_Chunk *chunk;
+	BYTE *subchunkData;
+	DWORD subchunkDataLen;
+
+	/* Allocate the chunk structure */
+	chunk = new RIFF_Chunk;
+
+	/* Make sure the file is in RIFF format */
+	fread(&chunk->magic, 4, 1, src);
+	fread(&chunk->length, 4, 1, src);
+	chunk->length = LittleLong(chunk->length);
+	if ( chunk->magic != RIFF ) {
+		__Sound_SetError("Not a RIFF file");
+		delete chunk;
+		return NULL;
+	}
+	chunk->data = (BYTE *)malloc(chunk->length);
+	if ( chunk->data == NULL ) {
+		__Sound_SetError(ERR_OUT_OF_MEMORY);
+		delete chunk;
+		return NULL;
+	}
+	if ( fread(chunk->data, chunk->length, 1, src) != 1 ) {
+		__Sound_SetError(ERR_IO_ERROR);
+		FreeRIFF(chunk);
+		return NULL;
+	}
+	subchunkData = chunk->data;
+	subchunkDataLen = chunk->length;
+	if ( ChunkHasSubType(chunk->magic) && subchunkDataLen >= 4 ) {
+		chunk->subtype = *(DWORD *)subchunkData;
+		subchunkData += 4;
+		subchunkDataLen -= 4;
+	}
+	if ( ChunkHasSubChunks(chunk->magic) ) {
+		LoadSubChunks(chunk, subchunkData, subchunkDataLen);
+	}
+	return chunk;
+}
+
+void FreeRIFF(RIFF_Chunk *chunk)
+{
+	free(chunk->data);
+	delete chunk;
+}
+
+void PrintRIFF(RIFF_Chunk *chunk, int level)
+{
+	static char prefix[128];
+
+	if ( level == sizeof(prefix)-1 ) {
+		return;
+	}
+	if ( level > 0 ) {
+		prefix[(level-1)*2] = ' ';
+		prefix[(level-1)*2+1] = ' ';
+	}
+	prefix[level*2] = '\0';
+	printf("%sChunk: %c%c%c%c (%d bytes)", prefix,
+		((chunk->magic >>  0) & 0xFF),
+		((chunk->magic >>  8) & 0xFF),
+		((chunk->magic >> 16) & 0xFF),
+		((chunk->magic >> 24) & 0xFF), chunk->length);
+	if ( chunk->subtype ) {
+		printf(" subtype: %c%c%c%c",
+			((chunk->subtype >>  0) & 0xFF),
+			((chunk->subtype >>  8) & 0xFF),
+			((chunk->subtype >> 16) & 0xFF),
+			((chunk->subtype >> 24) & 0xFF));
+	}
+	printf("\n");
+	if ( chunk->child ) {
+		printf("%s{\n", prefix);
+		PrintRIFF(chunk->child, level + 1);
+		printf("%s}\n", prefix);
+	}
+	if ( chunk->next ) {
+		PrintRIFF(chunk->next, level);
+	}
+	if ( level > 0 ) {
+		prefix[(level-1)*2] = '\0';
+	}
+}
+
+#ifdef TEST_MAIN_RIFF
+
+main(int argc, char *argv[])
+{
+	int i;
+	for ( i = 1; i < argc; ++i ) {
+		RIFF_Chunk *chunk;
+		SDL_RWops *src = SDL_RWFromFile(argv[i], "rb");
+		if ( !src ) {
+			fprintf(stderr, "Couldn't open %s: %s", argv[i], SDL_GetError());
+			continue;
+		}
+		chunk = LoadRIFF(src);
+		if ( chunk ) {
+			PrintRIFF(chunk, 0);
+			FreeRIFF(chunk);
+		} else {
+			fprintf(stderr, "Couldn't load %s: %s\n", argv[i], SDL_GetError());
+		}
+		SDL_RWclose(src);
+	}
+}
+
+#endif // TEST_MAIN
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/*-------------------------------------------------------------------------*/
+/* * * * * * * * * * * * * * * * * load_dls.h  * * * * * * * * * * * * * * */
+/*-------------------------------------------------------------------------*/
+/* This code is based on the DLS spec version 1.1, available at:
+http://www.midi.org/about-midi/dls/dlsspec.shtml
+*/
+
+/* Some typedefs so the public dls headers don't need to be modified */
+#define FAR
+typedef SWORD	SHORT;
+typedef WORD	USHORT;
+typedef SDWORD	LONG;
+typedef DWORD	ULONG;
+#define mmioFOURCC	MAKE_ID
+#define DEFINE_GUID(A, B, C, E, F, G, H, I, J, K, L, M)
+
+#include "dls1.h"
+#include "dls2.h"
+
+struct WaveFMT
+{
+	WORD wFormatTag;
+	WORD wChannels;
+	DWORD dwSamplesPerSec;
+	DWORD dwAvgBytesPerSec;
+	WORD wBlockAlign;
+	WORD wBitsPerSample;
+};
+
+struct DLS_Wave
+{
+	WaveFMT *format;
+	BYTE *data;
+	DWORD length;
+	WSMPL *wsmp;
+	WLOOP *wsmp_loop;
+};
+
+struct DLS_Region
+{
+	RGNHEADER *header;
+	WAVELINK *wlnk;
+	WSMPL *wsmp;
+	WLOOP *wsmp_loop;
+	CONNECTIONLIST *art;
+	CONNECTION *artList;
+};
+
+struct DLS_Instrument
+{
+	const char *name;
+	INSTHEADER *header;
+	DLS_Region *regions;
+	CONNECTIONLIST *art;
+	CONNECTION *artList;
+};
+
+struct DLS_Data
+{
+	RIFF_Chunk *chunk;
+
+	DWORD cInstruments;
+	DLS_Instrument *instruments;
+
+	POOLTABLE *ptbl;
+	POOLCUE *ptblList;
+	DLS_Wave *waveList;
+
+	const char *name;
+	const char *artist;
+	const char *copyright;
+	const char *comments;
+};
+
+DLS_Data *LoadDLS(FILE *src);
+void FreeDLS(DLS_Data *chunk);
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/*-------------------------------------------------------------------------*/
+/* * * * * * * * * * * * * * * * * load_dls.c  * * * * * * * * * * * * * * */
+/*-------------------------------------------------------------------------*/
+
+#define FOURCC_LIST    mmioFOURCC('L','I','S','T')
+#define FOURCC_FMT     mmioFOURCC('f','m','t',' ')
+#define FOURCC_DATA    mmioFOURCC('d','a','t','a')
+#define FOURCC_INFO    mmioFOURCC('I','N','F','O')
+#define FOURCC_IARL    mmioFOURCC('I','A','R','L')
+#define FOURCC_IART    mmioFOURCC('I','A','R','T')
+#define FOURCC_ICMS    mmioFOURCC('I','C','M','S')
+#define FOURCC_ICMT    mmioFOURCC('I','C','M','T')
+#define FOURCC_ICOP    mmioFOURCC('I','C','O','P')
+#define FOURCC_ICRD    mmioFOURCC('I','C','R','D')
+#define FOURCC_IENG    mmioFOURCC('I','E','N','G')
+#define FOURCC_IGNR    mmioFOURCC('I','G','N','R')
+#define FOURCC_IKEY    mmioFOURCC('I','K','E','Y')
+#define FOURCC_IMED    mmioFOURCC('I','M','E','D')
+#define FOURCC_INAM    mmioFOURCC('I','N','A','M')
+#define FOURCC_IPRD    mmioFOURCC('I','P','R','D')
+#define FOURCC_ISBJ    mmioFOURCC('I','S','B','J')
+#define FOURCC_ISFT    mmioFOURCC('I','S','F','T')
+#define FOURCC_ISRC    mmioFOURCC('I','S','R','C')
+#define FOURCC_ISRF    mmioFOURCC('I','S','R','F')
+#define FOURCC_ITCH    mmioFOURCC('I','T','C','H')
+
+
+static void FreeRegions(DLS_Instrument *instrument)
+{
+	if ( instrument->regions ) {
+		free(instrument->regions);
+	}
+}
+
+static void AllocRegions(DLS_Instrument *instrument)
+{
+	int datalen = (instrument->header->cRegions * sizeof(DLS_Region));
+	FreeRegions(instrument);
+	instrument->regions = (DLS_Region *)malloc(datalen);
+	if ( instrument->regions ) {
+		memset(instrument->regions, 0, datalen);
+	}
+}
+
+static void FreeInstruments(DLS_Data *data)
+{
+	if ( data->instruments ) {
+		DWORD i;
+		for ( i = 0; i < data->cInstruments; ++i ) {
+			FreeRegions(&data->instruments[i]);
+		}
+		free(data->instruments);
+	}
+}
+
+static void AllocInstruments(DLS_Data *data)
+{
+	int datalen = (data->cInstruments * sizeof(DLS_Instrument));
+	FreeInstruments(data);
+	data->instruments = (DLS_Instrument *)malloc(datalen);
+	if ( data->instruments ) {
+		memset(data->instruments, 0, datalen);
+	}
+}
+
+static void FreeWaveList(DLS_Data *data)
+{
+	if ( data->waveList ) {
+		free(data->waveList);
+	}
+}
+
+static void AllocWaveList(DLS_Data *data)
+{
+	int datalen = (data->ptbl->cCues * sizeof(DLS_Wave));
+	FreeWaveList(data);
+	data->waveList = (DLS_Wave *)malloc(datalen);
+	if ( data->waveList ) {
+		memset(data->waveList, 0, datalen);
+	}
+}
+
+static void Parse_colh(DLS_Data *data, RIFF_Chunk *chunk)
+{
+	data->cInstruments = LittleLong(*(DWORD *)chunk->data);
+	AllocInstruments(data);
+}
+
+static void Parse_insh(DLS_Data *data, RIFF_Chunk *chunk, DLS_Instrument *instrument)
+{
+	INSTHEADER *header = (INSTHEADER *)chunk->data;
+	header->cRegions = LittleLong(header->cRegions);
+	header->Locale.ulBank = LittleLong(header->Locale.ulBank);
+	header->Locale.ulInstrument = LittleLong(header->Locale.ulInstrument);
+	instrument->header = header;
+	AllocRegions(instrument);
+}
+
+static void Parse_rgnh(DLS_Data *data, RIFF_Chunk *chunk, DLS_Region *region)
+{
+	RGNHEADER *header = (RGNHEADER *)chunk->data;
+	header->RangeKey.usLow = LittleShort(header->RangeKey.usLow);
+	header->RangeKey.usHigh = LittleShort(header->RangeKey.usHigh);
+	header->RangeVelocity.usLow = LittleShort(header->RangeVelocity.usLow);
+	header->RangeVelocity.usHigh = LittleShort(header->RangeVelocity.usHigh);
+	header->fusOptions = LittleShort(header->fusOptions);
+	header->usKeyGroup = LittleShort(header->usKeyGroup);
+	region->header = header;
+}
+
+static void Parse_wlnk(DLS_Data *data, RIFF_Chunk *chunk, DLS_Region *region)
+{
+	WAVELINK *wlnk = (WAVELINK *)chunk->data;
+	wlnk->fusOptions = LittleShort(wlnk->fusOptions);
+	wlnk->usPhaseGroup = LittleShort(wlnk->usPhaseGroup);
+	wlnk->ulChannel = LittleShort(wlnk->ulChannel);
+	wlnk->ulTableIndex = LittleShort(wlnk->ulTableIndex);
+	region->wlnk = wlnk;
+}
+
+static void Parse_wsmp(DLS_Data *data, RIFF_Chunk *chunk, WSMPL **wsmp_ptr, WLOOP **wsmp_loop_ptr)
+{
+	DWORD i;
+	WSMPL *wsmp = (WSMPL *)chunk->data;
+	WLOOP *loop;
+	wsmp->cbSize = LittleLong(wsmp->cbSize);
+	wsmp->usUnityNote = LittleShort(wsmp->usUnityNote);
+	wsmp->sFineTune = LittleShort(wsmp->sFineTune);
+	wsmp->lAttenuation = LittleLong(wsmp->lAttenuation);
+	wsmp->fulOptions = LittleLong(wsmp->fulOptions);
+	wsmp->cSampleLoops = LittleLong(wsmp->cSampleLoops);
+	loop = (WLOOP *)((BYTE *)chunk->data + wsmp->cbSize);
+	*wsmp_ptr = wsmp;
+	*wsmp_loop_ptr = loop;
+	for ( i = 0; i < wsmp->cSampleLoops; ++i ) {
+		loop->cbSize = LittleLong(loop->cbSize);
+		loop->ulType = LittleLong(loop->ulType);
+		loop->ulStart = LittleLong(loop->ulStart);
+		loop->ulLength = LittleLong(loop->ulLength);
+		++loop;
+	}
+}
+
+static void Parse_art(DLS_Data *data, RIFF_Chunk *chunk, CONNECTIONLIST **art_ptr, CONNECTION **artList_ptr)
+{
+	DWORD i;
+	CONNECTIONLIST *art = (CONNECTIONLIST *)chunk->data;
+	CONNECTION *artList;
+	art->cbSize = LittleLong(art->cbSize);
+	art->cConnections = LittleLong(art->cConnections);
+	artList = (CONNECTION *)((BYTE *)chunk->data + art->cbSize);
+	*art_ptr = art;
+	*artList_ptr = artList;
+	for ( i = 0; i < art->cConnections; ++i ) {
+		artList->usSource = LittleShort(artList->usSource);
+		artList->usControl = LittleShort(artList->usControl);
+		artList->usDestination = LittleShort(artList->usDestination);
+		artList->usTransform = LittleShort(artList->usTransform);
+		artList->lScale = LittleLong(artList->lScale);
+		++artList;
+	}
+}
+
+static void Parse_lart(DLS_Data *data, RIFF_Chunk *chunk, CONNECTIONLIST **conn_ptr, CONNECTION **connList_ptr)
+{
+	/* FIXME: This only supports one set of connections */
+	for ( chunk = chunk->child; chunk; chunk = chunk->next ) {
+		DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic;
+		switch(magic) {
+			case FOURCC_ART1:
+			case FOURCC_ART2:
+				Parse_art(data, chunk, conn_ptr, connList_ptr);
+				return;
+		}
+	}
+}
+
+static void Parse_rgn(DLS_Data *data, RIFF_Chunk *chunk, DLS_Region *region)
+{
+	for ( chunk = chunk->child; chunk; chunk = chunk->next ) {
+		DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic;
+		switch(magic) {
+			case FOURCC_RGNH:
+				Parse_rgnh(data, chunk, region);
+				break;
+			case FOURCC_WLNK:
+				Parse_wlnk(data, chunk, region);
+				break;
+			case FOURCC_WSMP:
+				Parse_wsmp(data, chunk, &region->wsmp, &region->wsmp_loop);
+				break;
+			case FOURCC_LART:
+			case FOURCC_LAR2:
+				Parse_lart(data, chunk, &region->art, &region->artList);
+				break;
+		}
+	}
+}
+
+static void Parse_lrgn(DLS_Data *data, RIFF_Chunk *chunk, DLS_Instrument *instrument)
+{
+	DWORD region = 0;
+	for ( chunk = chunk->child; chunk; chunk = chunk->next ) {
+		DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic;
+		switch(magic) {
+			case FOURCC_RGN:
+			case FOURCC_RGN2:
+				if ( region < instrument->header->cRegions ) {
+					Parse_rgn(data, chunk, &instrument->regions[region++]);
+				}
+				break;
+		}
+	}
+}
+
+static void Parse_INFO_INS(DLS_Data *data, RIFF_Chunk *chunk, DLS_Instrument *instrument)
+{
+	for ( chunk = chunk->child; chunk; chunk = chunk->next ) {
+		DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic;
+		switch(magic) {
+			case FOURCC_INAM: /* Name */
+				instrument->name = (const char *)chunk->data;
+				break;
+		}
+	}
+}
+
+static void Parse_ins(DLS_Data *data, RIFF_Chunk *chunk, DLS_Instrument *instrument)
+{
+	for ( chunk = chunk->child; chunk; chunk = chunk->next ) {
+		DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic;
+		switch(magic) {
+			case FOURCC_INSH:
+				Parse_insh(data, chunk, instrument);
+				break;
+			case FOURCC_LRGN:
+				Parse_lrgn(data, chunk, instrument);
+				break;
+			case FOURCC_LART:
+			case FOURCC_LAR2:
+				Parse_lart(data, chunk, &instrument->art, &instrument->artList);
+				break;
+			case FOURCC_INFO:
+				Parse_INFO_INS(data, chunk, instrument);
+				break;
+		}
+	}
+}
+
+static void Parse_lins(DLS_Data *data, RIFF_Chunk *chunk)
+{
+	DWORD instrument = 0;
+	for ( chunk = chunk->child; chunk; chunk = chunk->next ) {
+		DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic;
+		switch(magic) {
+			case FOURCC_INS:
+				if ( instrument < data->cInstruments ) {
+					Parse_ins(data, chunk, &data->instruments[instrument++]);
+				}
+				break;
+		}
+	}
+}
+
+static void Parse_ptbl(DLS_Data *data, RIFF_Chunk *chunk)
+{
+	DWORD i;
+	POOLTABLE *ptbl = (POOLTABLE *)chunk->data;
+	ptbl->cbSize = LittleLong(ptbl->cbSize);
+	ptbl->cCues = LittleLong(ptbl->cCues);
+	data->ptbl = ptbl;
+	data->ptblList = (POOLCUE *)((BYTE *)chunk->data + ptbl->cbSize);
+	for ( i = 0; i < ptbl->cCues; ++i ) {
+		data->ptblList[i].ulOffset = LittleLong(data->ptblList[i].ulOffset);
+	}
+	AllocWaveList(data);
+}
+
+static void Parse_fmt(DLS_Data *data, RIFF_Chunk *chunk, DLS_Wave *wave)
+{
+	WaveFMT *fmt = (WaveFMT *)chunk->data;
+	fmt->wFormatTag = LittleShort(fmt->wFormatTag);
+	fmt->wChannels = LittleShort(fmt->wChannels);
+	fmt->dwSamplesPerSec = LittleLong(fmt->dwSamplesPerSec);
+	fmt->dwAvgBytesPerSec = LittleLong(fmt->dwAvgBytesPerSec);
+	fmt->wBlockAlign = LittleShort(fmt->wBlockAlign);
+	fmt->wBitsPerSample = LittleShort(fmt->wBitsPerSample);
+	wave->format = fmt;
+}
+
+static void Parse_data(DLS_Data *data, RIFF_Chunk *chunk, DLS_Wave *wave)
+{
+	wave->data = chunk->data;
+	wave->length = chunk->length;
+}
+
+static void Parse_wave(DLS_Data *data, RIFF_Chunk *chunk, DLS_Wave *wave)
+{
+	for ( chunk = chunk->child; chunk; chunk = chunk->next ) {
+		DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic;
+		switch(magic) {
+			case FOURCC_FMT:
+				Parse_fmt(data, chunk, wave);
+				break;
+			case FOURCC_DATA:
+				Parse_data(data, chunk, wave);
+				break;
+			case FOURCC_WSMP:
+				Parse_wsmp(data, chunk, &wave->wsmp, &wave->wsmp_loop);
+				break;
+		}
+	}
+}
+
+static void Parse_wvpl(DLS_Data *data, RIFF_Chunk *chunk)
+{
+	DWORD wave = 0;
+	for ( chunk = chunk->child; chunk; chunk = chunk->next ) {
+		DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic;
+		switch(magic) {
+			case FOURCC_wave:
+				if ( wave < data->ptbl->cCues ) {
+					Parse_wave(data, chunk, &data->waveList[wave++]);
+				}
+				break;
+		}
+	}
+}
+
+static void Parse_INFO_DLS(DLS_Data *data, RIFF_Chunk *chunk)
+{
+	for ( chunk = chunk->child; chunk; chunk = chunk->next ) {
+		DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic;
+		switch(magic) {
+			case FOURCC_IARL: /* Archival Location */
+				break;
+			case FOURCC_IART: /* Artist */
+				data->artist = (const char *)chunk->data;
+				break;
+			case FOURCC_ICMS: /* Commisioned */
+				break;
+			case FOURCC_ICMT: /* Comments */
+				data->comments = (const char *)chunk->data;
+				break;
+			case FOURCC_ICOP: /* Copyright */
+				data->copyright = (const char *)chunk->data;
+				break;
+			case FOURCC_ICRD: /* Creation Date */
+				break;
+			case FOURCC_IENG: /* Engineer */
+				break;
+			case FOURCC_IGNR: /* Genre */
+				break;
+			case FOURCC_IKEY: /* Keywords */
+				break;
+			case FOURCC_IMED: /* Medium */
+				break;
+			case FOURCC_INAM: /* Name */
+				data->name = (const char *)chunk->data;
+				break;
+			case FOURCC_IPRD: /* Product */
+				break;
+			case FOURCC_ISBJ: /* Subject */
+				break;
+			case FOURCC_ISFT: /* Software */
+				break;
+			case FOURCC_ISRC: /* Source */
+				break;
+			case FOURCC_ISRF: /* Source Form */
+				break;
+			case FOURCC_ITCH: /* Technician */
+				break;
+		}
+	}
+}
+
+DLS_Data *LoadDLS(FILE *src)
+{
+	RIFF_Chunk *chunk;
+	DLS_Data *data = (DLS_Data *)malloc(sizeof(*data));
+	if ( !data ) {
+		__Sound_SetError(ERR_OUT_OF_MEMORY);
+		return NULL;
+	}
+	memset(data, 0, sizeof(*data));
+
+	data->chunk = LoadRIFF(src);
+	if ( !data->chunk ) {
+		FreeDLS(data);
+		return NULL;
+	}
+
+	for ( chunk = data->chunk->child; chunk; chunk = chunk->next ) {
+		DWORD magic = (chunk->magic == FOURCC_LIST) ? chunk->subtype : chunk->magic;
+		switch(magic) {
+			case FOURCC_COLH:
+				Parse_colh(data, chunk);
+				break;
+			case FOURCC_LINS:
+				Parse_lins(data, chunk);
+				break;
+			case FOURCC_PTBL:
+				Parse_ptbl(data, chunk);
+				break;
+			case FOURCC_WVPL:
+				Parse_wvpl(data, chunk);
+				break;
+			case FOURCC_INFO:
+				Parse_INFO_DLS(data, chunk);
+				break;
+		}
+	}
+	return data;
+}
+
+void FreeDLS(DLS_Data *data)
+{
+	if ( data->chunk ) {
+		FreeRIFF(data->chunk);
+	}
+	FreeInstruments(data);
+	FreeWaveList(data);
+	free(data);
+}
+
+static const char *SourceToString(USHORT usSource)
+{
+	static char unknown[32];
+	switch(usSource) {
+		case CONN_SRC_NONE:
+			return "NONE";
+		case CONN_SRC_LFO:
+			return "LFO";
+		case CONN_SRC_KEYONVELOCITY:
+			return "KEYONVELOCITY";
+		case CONN_SRC_KEYNUMBER:
+			return "KEYNUMBER";
+		case CONN_SRC_EG1:
+			return "EG1";
+		case CONN_SRC_EG2:
+			return "EG2";
+		case CONN_SRC_PITCHWHEEL:
+			return "PITCHWHEEL";
+		case CONN_SRC_CC1:
+			return "CC1";
+		case CONN_SRC_CC7:
+			return "CC7";
+		case CONN_SRC_CC10:
+			return "CC10";
+		case CONN_SRC_CC11:
+			return "CC11";
+		case CONN_SRC_POLYPRESSURE:
+			return "POLYPRESSURE";
+		case CONN_SRC_CHANNELPRESSURE:
+			return "CHANNELPRESSURE";
+		case CONN_SRC_VIBRATO:
+			return "VIBRATO";
+		case CONN_SRC_MONOPRESSURE:
+			return "MONOPRESSURE";
+		case CONN_SRC_CC91:
+			return "CC91";
+		case CONN_SRC_CC93:
+			return "CC93";
+		default:
+			sprintf(unknown, "UNKNOWN (0x%04x)", usSource);
+			return unknown;
+	}
+}
+
+static const char *TransformToString(USHORT usTransform)
+{
+	static char unknown[32];
+	switch (usTransform) {
+		case CONN_TRN_NONE:
+			return "NONE";
+		case CONN_TRN_CONCAVE:
+			return "CONCAVE";
+		case CONN_TRN_CONVEX:
+			return "CONVEX";
+		case CONN_TRN_SWITCH:
+			return "SWITCH";
+		default:
+			sprintf(unknown, "UNKNOWN (0x%04x)", usTransform);
+			return unknown;
+	}
+}
+
+static const char *DestinationToString(USHORT usDestination)
+{
+	static char unknown[32];
+	switch (usDestination) {
+		case CONN_DST_NONE:
+			return "NONE";
+		case CONN_DST_ATTENUATION:
+			return "ATTENUATION";
+		case CONN_DST_PITCH:
+			return "PITCH";
+		case CONN_DST_PAN:
+			return "PAN";
+		case CONN_DST_LFO_FREQUENCY:
+			return "LFO_FREQUENCY";
+		case CONN_DST_LFO_STARTDELAY:
+			return "LFO_STARTDELAY";
+		case CONN_DST_EG1_ATTACKTIME:
+			return "EG1_ATTACKTIME";
+		case CONN_DST_EG1_DECAYTIME:
+			return "EG1_DECAYTIME";
+		case CONN_DST_EG1_RELEASETIME:
+			return "EG1_RELEASETIME";
+		case CONN_DST_EG1_SUSTAINLEVEL:
+			return "EG1_SUSTAINLEVEL";
+		case CONN_DST_EG2_ATTACKTIME:
+			return "EG2_ATTACKTIME";
+		case CONN_DST_EG2_DECAYTIME:
+			return "EG2_DECAYTIME";
+		case CONN_DST_EG2_RELEASETIME:
+			return "EG2_RELEASETIME";
+		case CONN_DST_EG2_SUSTAINLEVEL:
+			return "EG2_SUSTAINLEVEL";
+		case CONN_DST_KEYNUMBER:
+			return "KEYNUMBER";
+		case CONN_DST_LEFT:
+			return "LEFT";
+		case CONN_DST_RIGHT:
+			return "RIGHT";
+		case CONN_DST_CENTER:
+			return "CENTER";
+		case CONN_DST_LEFTREAR:
+			return "LEFTREAR";
+		case CONN_DST_RIGHTREAR:
+			return "RIGHTREAR";
+		case CONN_DST_LFE_CHANNEL:
+			return "LFE_CHANNEL";
+		case CONN_DST_CHORUS:
+			return "CHORUS";
+		case CONN_DST_REVERB:
+			return "REVERB";
+		case CONN_DST_VIB_FREQUENCY:
+			return "VIB_FREQUENCY";
+		case CONN_DST_VIB_STARTDELAY:
+			return "VIB_STARTDELAY";
+		case CONN_DST_EG1_DELAYTIME:
+			return "EG1_DELAYTIME";
+		case CONN_DST_EG1_HOLDTIME:
+			return "EG1_HOLDTIME";
+		case CONN_DST_EG1_SHUTDOWNTIME:
+			return "EG1_SHUTDOWNTIME";
+		case CONN_DST_EG2_DELAYTIME:
+			return "EG2_DELAYTIME";
+		case CONN_DST_EG2_HOLDTIME:
+			return "EG2_HOLDTIME";
+		case CONN_DST_FILTER_CUTOFF:
+			return "FILTER_CUTOFF";
+		case CONN_DST_FILTER_Q:
+			return "FILTER_Q";
+		default:
+			sprintf(unknown, "UNKNOWN (0x%04x)", usDestination);
+			return unknown;
+	}
+}
+
+static void PrintArt(const char *type, CONNECTIONLIST *art, CONNECTION *artList)
+{
+	DWORD i;
+	printf("%s Connections:\n", type);
+	for ( i = 0; i < art->cConnections; ++i ) {
+		printf("  Source: %s, Control: %s, Destination: %s, Transform: %s, Scale: %d\n",
+			SourceToString(artList[i].usSource),
+			SourceToString(artList[i].usControl),
+			DestinationToString(artList[i].usDestination),
+			TransformToString(artList[i].usTransform),
+			artList[i].lScale);
+	}
+}
+
+static void PrintWave(DLS_Wave *wave, DWORD index)
+{
+	WaveFMT *format = wave->format;
+	if ( format ) {
+		printf("  Wave %u: Format: %hu, %hu channels, %u Hz, %hu bits (length = %u)\n", index, format->wFormatTag, format->wChannels, format->dwSamplesPerSec, format->wBitsPerSample, wave->length);
+	}
+	if ( wave->wsmp ) {
+		DWORD i;
+		printf("    wsmp->usUnityNote = %hu\n", wave->wsmp->usUnityNote);
+		printf("    wsmp->sFineTune = %hd\n", wave->wsmp->sFineTune);
+		printf("    wsmp->lAttenuation = %d\n", wave->wsmp->lAttenuation);
+		printf("    wsmp->fulOptions = 0x%8.8x\n", wave->wsmp->fulOptions);
+		printf("    wsmp->cSampleLoops = %u\n", wave->wsmp->cSampleLoops);
+		for ( i = 0; i < wave->wsmp->cSampleLoops; ++i ) {
+			WLOOP *loop = &wave->wsmp_loop[i];
+			printf("    Loop %u:\n", i);
+			printf("      ulStart = %u\n", loop->ulStart);
+			printf("      ulLength = %u\n", loop->ulLength);
+		}
+	}
+}
+
+static void PrintRegion(DLS_Region *region, DWORD index)
+{
+	printf("  Region %u:\n", index);
+	if ( region->header ) {
+		printf("    RangeKey = { %hu - %hu }\n", region->header->RangeKey.usLow, region->header->RangeKey.usHigh);
+		printf("    RangeVelocity = { %hu - %hu }\n", region->header->RangeVelocity.usLow, region->header->RangeVelocity.usHigh);
+		printf("    fusOptions = 0x%4.4hx\n", region->header->fusOptions);
+		printf("    usKeyGroup = %hu\n", region->header->usKeyGroup);
+	}
+	if ( region->wlnk ) {
+		printf("    wlnk->fusOptions = 0x%4.4hx\n", region->wlnk->fusOptions);
+		printf("    wlnk->usPhaseGroup = %hu\n", region->wlnk->usPhaseGroup);
+		printf("    wlnk->ulChannel = %u\n", region->wlnk->ulChannel);
+		printf("    wlnk->ulTableIndex = %u\n", region->wlnk->ulTableIndex);
+	}
+	if ( region->wsmp ) {
+		DWORD i;
+		printf("    wsmp->usUnityNote = %hu\n", region->wsmp->usUnityNote);
+		printf("    wsmp->sFineTune = %hd\n", region->wsmp->sFineTune);
+		printf("    wsmp->lAttenuation = %d\n", region->wsmp->lAttenuation);
+		printf("    wsmp->fulOptions = 0x%8.8x\n", region->wsmp->fulOptions);
+		printf("    wsmp->cSampleLoops = %u\n", region->wsmp->cSampleLoops);
+		for ( i = 0; i < region->wsmp->cSampleLoops; ++i ) {
+			WLOOP *loop = &region->wsmp_loop[i];
+			printf("    Loop %u:\n", i);
+			printf("      ulStart = %u\n", loop->ulStart);
+			printf("      ulLength = %u\n", loop->ulLength);
+		}
+	}
+	if ( region->art && region->art->cConnections > 0 ) {
+		PrintArt("Region", region->art, region->artList);
+	}
+}
+
+static void PrintInstrument(DLS_Instrument *instrument, DWORD index)
+{
+	printf("Instrument %u:\n", index);
+	if ( instrument->name ) {
+		printf("  Name: %s\n", instrument->name);
+	}
+	if ( instrument->header ) {
+		DWORD i;
+		printf("  ulBank = 0x%8.8x\n", instrument->header->Locale.ulBank);
+		printf("  ulInstrument = %u\n", instrument->header->Locale.ulInstrument);
+		printf("  Regions: %u\n", instrument->header->cRegions);
+		for ( i = 0; i < instrument->header->cRegions; ++i ) {
+			PrintRegion(&instrument->regions[i], i);
+		}
+	}
+	if ( instrument->art && instrument->art->cConnections > 0 ) {
+		PrintArt("Instrument", instrument->art, instrument->artList);
+	}
+};
+
+void PrintDLS(DLS_Data *data)
+{
+	printf("DLS Data:\n");
+	printf("cInstruments = %u\n", data->cInstruments); 
+	if ( data->instruments ) {
+		DWORD i;
+		for ( i = 0; i < data->cInstruments; ++i ) {
+			PrintInstrument(&data->instruments[i], i);
+		}
+	}
+	if ( data->ptbl && data->ptbl->cCues > 0 ) {
+		DWORD i;
+		printf("Cues: ");
+		for ( i = 0; i < data->ptbl->cCues; ++i ) {
+			if ( i > 0 ) {
+				printf(", ");
+			}
+			printf("%u", data->ptblList[i].ulOffset);
+		}
+		printf("\n");
+	}
+	if ( data->waveList ) {
+		DWORD i;
+		printf("Waves:\n");
+		for ( i = 0; i < data->ptbl->cCues; ++i ) {
+			PrintWave(&data->waveList[i], i);
+		}
+	}
+	if ( data->name ) {
+		printf("Name: %s\n", data->name);
+	}
+	if ( data->artist ) {
+		printf("Artist: %s\n", data->artist);
+	}
+	if ( data->copyright ) {
+		printf("Copyright: %s\n", data->copyright);
+	}
+	if ( data->comments ) {
+		printf("Comments: %s\n", data->comments);
+	}
+}
+
+#ifdef TEST_MAIN_DLS
+}
+
+int main(int argc, char *argv[])
+{
+	int i;
+	for ( i = 1; i < argc; ++i ) {
+		Timidity::DLS_Data *data;
+		FILE *src = fopen(argv[i], "rb");
+		if ( !src ) {
+			fprintf(stderr, "Couldn't open %s: %s", argv[i], strerror(errno));
+			continue;
+		}
+		data = Timidity::LoadDLS(src);
+		if ( data ) {
+			Timidity::PrintRIFF(data->chunk, 0);
+			Timidity::PrintDLS(data);
+			Timidity::FreeDLS(data);
+		} else {
+			fprintf(stderr, "Couldn't load %s: %s\n", argv[i], strerror(errno));
+		}
+		fclose(src);
+	}
+	return 0;
+}
+
+namespace Timidity
+{
+#endif // TEST_MAIN
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/*-------------------------------------------------------------------------*/
+/* * * * * * * * * * * * * * * * * instrum_dls.c * * * * * * * * * * * * * */
+/*-------------------------------------------------------------------------*/
+
+#ifndef TEST_MAIN_DLS
+
+DLS_Data *Timidity_LoadDLS(FILE *src)
+{
+	return LoadDLS(src);
+}
+
+void Timidity_FreeDLS(DLS_Data *patches)
+{
+	FreeDLS(patches);
+}
+
+/* convert timecents to sec */
+static double to_msec(int timecent)
+{
+	if (timecent == 0x80000000 || timecent == 0)
+		return 0.0;
+	return 1000.0 * pow(2.0, (double)(timecent / 65536) / 1200.0);
+}
+
+/* convert decipercent to {0..1} */
+static double to_normalized_percent(int decipercent)
+{
+	return ((double)(decipercent / 65536)) / 1000.0;
+}
+
+/* convert from 8bit value to fractional offset (15.15) */
+static SDWORD to_offset(int offset)
+{
+	return (SDWORD)offset << (7+15);
+}
+
+/* calculate ramp rate in fractional unit;
+* diff = 8bit, time = msec
+*/
+static SDWORD calc_rate(Renderer *song, int diff, int sample_rate, double msec)
+{
+	double rate;
+
+	if(msec < 6)
+		msec = 6;
+	if(diff == 0)
+		diff = 255;
+	diff <<= (7+15);
+	rate = ((double)diff / song->rate) * song->control_ratio * 1000.0 / msec;
+	return (SDWORD)rate;
+}
+
+static int load_connection(ULONG cConnections, CONNECTION *artList, USHORT destination)
+{
+	ULONG i;
+	int value = 0;
+	for (i = 0; i < cConnections; ++i) {
+		CONNECTION *conn = &artList[i];
+		if(conn->usDestination == destination) {
+			// The formula for the destination is:
+			// usDestination = usDestination + usTransform(usSource * (usControl * lScale))
+			// Since we are only handling source/control of NONE and identity
+			// transform, this simplifies to: usDestination = usDestination + lScale
+			if (conn->usSource == CONN_SRC_NONE &&
+				conn->usControl == CONN_SRC_NONE &&
+				conn->usTransform == CONN_TRN_NONE)
+				value += conn->lScale;
+		}
+	}
+	return value;
+}
+
+static void load_region_dls(Renderer *song, Sample *sample, DLS_Instrument *ins, DWORD index)
+{
+	DLS_Region *rgn = &ins->regions[index];
+	DLS_Wave *wave = &song->patches->waveList[rgn->wlnk->ulTableIndex];
+
+	if (!(rgn->header->fusOptions & F_RGN_OPTION_SELFNONEXCLUSIVE))
+	{
+		sample->exclusiveClass = (SBYTE)rgn->header->usKeyGroup;
+	}
+	sample->low_freq = SDWORD(note_to_freq(rgn->header->RangeKey.usLow));
+	sample->high_freq = SDWORD(note_to_freq(rgn->header->RangeKey.usHigh));
+	sample->root_freq = SDWORD(note_to_freq(rgn->wsmp->usUnityNote));
+	sample->low_vel = rgn->header->RangeVelocity.usLow;
+	sample->high_vel = rgn->header->RangeVelocity.usHigh;
+
+	sample->modes = wave->format->wBitsPerSample == 8 ? MODES_UNSIGNED : MODES_16BIT;
+	sample->sample_rate = wave->format->dwSamplesPerSec;
+	sample->data = NULL;
+	sample->data_length = wave->length;
+	convert_sample_data(sample, wave->data);
+	if (rgn->wsmp->cSampleLoops) {
+		sample->modes |= (MODES_LOOPING|MODES_SUSTAIN);
+		sample->loop_start = rgn->wsmp_loop->ulStart / 2;
+		sample->loop_end = sample->loop_start + (rgn->wsmp_loop->ulLength / 2);
+	}
+	sample->volume = 1.0f;
+
+	if (sample->modes & MODES_SUSTAIN) {
+		int value;
+		double attack, hold, decay, release; int sustain;
+		CONNECTIONLIST *art = NULL;
+		CONNECTION *artList = NULL;
+
+		if (ins->art && ins->art->cConnections > 0 && ins->artList) {
+			art = ins->art;
+			artList = ins->artList;
+		} else {
+			art = rgn->art;
+			artList = rgn->artList;
+		}
+
+		value = load_connection(art->cConnections, artList, CONN_DST_EG1_ATTACKTIME);
+		attack = to_msec(value);
+		value = load_connection(art->cConnections, artList, CONN_DST_EG1_HOLDTIME);
+		hold = to_msec(value);
+		value = load_connection(art->cConnections, artList, CONN_DST_EG1_DECAYTIME);
+		decay = to_msec(value);
+		value = load_connection(art->cConnections, artList, CONN_DST_EG1_RELEASETIME);
+		release = to_msec(value);
+		value = load_connection(art->cConnections, artList, CONN_DST_EG1_SUSTAINLEVEL);
+		sustain = (int)((1.0 - to_normalized_percent(value)) * 250.0);
+		value = load_connection(art->cConnections, artList, CONN_DST_PAN);
+		sample->panning = (int)((0.5 + to_normalized_percent(value)) * 16383.f);
+
+		/*
+		printf("%d, Rate=%d LV=%d HV=%d Low=%d Hi=%d Root=%d Pan=%d Attack=%f Hold=%f Sustain=%d Decay=%f Release=%f\n", index, sample->sample_rate, rgn->header->RangeVelocity.usLow, rgn->header->RangeVelocity.usHigh, sample->low_freq, sample->high_freq, sample->root_freq, sample->panning, attack, hold, sustain, decay, release);
+		*/
+
+		sample->envelope_offset[ATTACK] = to_offset(255);
+		sample->envelope_rate[ATTACK] = calc_rate(song, 255, sample->sample_rate, attack);
+
+		sample->envelope_offset[HOLD] = to_offset(250);
+		sample->envelope_rate[HOLD] = calc_rate(song, 5, sample->sample_rate, hold);
+
+		sample->envelope_offset[DECAY] = to_offset(sustain);
+		sample->envelope_rate[DECAY] = calc_rate(song, 255 - sustain, sample->sample_rate, decay);
+
+		sample->envelope_offset[RELEASE] = to_offset(0);
+		sample->envelope_rate[RELEASE] = calc_rate(song, 5 + sustain, sample->sample_rate, release);
+
+		sample->envelope_offset[RELEASEB] = to_offset(0);
+		sample->envelope_rate[RELEASEB] = to_offset(1);
+
+		sample->envelope_offset[RELEASEC] = to_offset(0);
+		sample->envelope_rate[RELEASEC] = to_offset(1);
+
+		sample->modes |= MODES_ENVELOPE;
+	}
+	for (int j = ATTACK; j < DELAY; j++)
+	{
+		sample->modulation_rate[j] = float(sample->envelope_rate[j]);
+		sample->modulation_offset[j] = float(sample->envelope_offset[j]);
+	}
+
+	sample->data_length <<= FRACTION_BITS;
+	sample->loop_start <<= FRACTION_BITS;
+	sample->loop_end <<= FRACTION_BITS;
+}
+
+InstrumentLayer *load_instrument_dls(Renderer *song, int drum, int bank, int instrument)
+{
+	InstrumentLayer *layer;
+	DWORD i;
+	DLS_Instrument *dls_ins;
+
+	if (song->patches == NULL)
+		return NULL;
+
+	drum = drum ? 0x80000000 : 0;
+	for (i = 0; i < song->patches->cInstruments; ++i) {
+		dls_ins = &song->patches->instruments[i];
+		if ((dls_ins->header->Locale.ulBank & 0x80000000) == drum &&
+			((dls_ins->header->Locale.ulBank >> 8) & 0xFF) == bank &&
+			dls_ins->header->Locale.ulInstrument == instrument)
+			break;
+	}
+	if (i == song->patches->cInstruments && !bank) {
+		for (i = 0; i < song->patches->cInstruments; ++i) {
+			dls_ins = &song->patches->instruments[i];
+			if ((dls_ins->header->Locale.ulBank & 0x80000000) == drum &&
+				dls_ins->header->Locale.ulInstrument == instrument)
+				break;
+		}
+	}
+	if (i == song->patches->cInstruments) {
+//		SNDDBG(("Couldn't find %s instrument %d in bank %d\n", drum ? "drum" : "melodic", instrument, bank));
+		return NULL;
+	}
+
+	layer = (InstrumentLayer *)safe_malloc(sizeof(InstrumentLayer));
+	layer->lo = 0;
+	layer->hi = 127;
+	layer->instrument = (Instrument *)safe_malloc(sizeof(Instrument));
+	layer->instrument->type = INST_DLS;
+	layer->instrument->samples = dls_ins->header->cRegions;
+	layer->instrument->sample = (Sample *)safe_malloc(layer->instrument->samples * sizeof(Sample));
+	layer->instrument->left_samples = layer->instrument->samples;
+	layer->instrument->left_sample = layer->instrument->sample;
+	layer->instrument->right_samples = 0;
+	layer->instrument->right_sample = NULL;
+	memset(layer->instrument->sample, 0, layer->instrument->samples * sizeof(Sample));
+	/*
+	printf("Found %s instrument %d in bank %d named %s with %d regions\n", drum ? "drum" : "melodic", instrument, bank, dls_ins->name, inst->samples);
+	*/
+	for (i = 0; i < dls_ins->header->cRegions; ++i) {
+		load_region_dls(song, &layer->instrument->sample[i], dls_ins, i);
+	}
+	return layer;
+}
+#endif	/* !TEST_MAIN_DLS */
+
+}
diff --git a/src/timidity/mix.cpp b/src/timidity/mix.cpp
new file mode 100644
index 000000000..7e155e049
--- /dev/null
+++ b/src/timidity/mix.cpp
@@ -0,0 +1,584 @@
+/*
+
+	TiMidity -- Experimental MIDI to WAVE converter
+	Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
+
+	This library is free software; you can redistribute it and/or
+	modify it under the terms of the GNU Lesser General Public
+	License as published by the Free Software Foundation; either
+	version 2.1 of the License, or (at your option) any later version.
+
+	This library 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
+	Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public
+	License along with this library; if not, write to the Free Software
+	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+	mix.c
+
+*/
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "timidity.h"
+
+namespace Timidity
+{
+
+/* Returns 1 if envelope runs out */
+int recompute_envelope(Voice *v)
+{
+	int stage;
+
+	stage = v->envelope_stage;
+
+	if (stage >= DELAY)
+	{
+		/* Envelope ran out. */
+		int tmp = (v->status == VOICE_DIE); /* Already displayed as dead */
+		v->status = VOICE_FREE;
+		return 1;
+	}
+
+	if (v->sample->modes & MODES_ENVELOPE)
+	{
+		if (v->status == VOICE_ON || v->status == VOICE_SUSTAINED)
+		{
+			if (stage > DECAY)
+			{
+				/* Freeze envelope until note turns off. Trumpets want this. */
+				v->envelope_increment = 0;
+				return 0;
+			}
+		}
+	}
+	v->envelope_stage = stage + 1;
+
+	if (v->envelope_volume == v->sample->envelope_offset[stage])
+		return recompute_envelope(v);
+	v->envelope_target = v->sample->envelope_offset[stage];
+	v->envelope_increment = v->sample->envelope_rate[stage];
+	if (v->envelope_target < v->envelope_volume)
+		v->envelope_increment = -v->envelope_increment;
+	return 0;
+}
+
+void apply_envelope_to_amp(Voice *v)
+{
+	double lamp = v->left_amp, ramp;
+	if (v->panned == PANNED_MYSTERY)
+	{
+		ramp = v->right_amp;
+
+		if (v->tremolo_phase_increment)
+		{
+			lamp *= v->tremolo_volume;
+			ramp *= v->tremolo_volume;
+		}
+		if (v->sample->modes & MODES_ENVELOPE)
+		{
+			double vol = calc_vol(v->envelope_volume / float(1 << 30));
+			lamp *= vol;
+			ramp *= vol;
+		}
+
+		v->left_mix = float(lamp);
+		v->right_mix = float(ramp);
+	}
+	else
+	{
+		if (v->tremolo_phase_increment)
+			lamp *= v->tremolo_volume;
+		if (v->sample->modes & MODES_ENVELOPE)
+			lamp *= calc_vol(v->envelope_volume / float(1 << 30));
+
+		v->left_mix = float(lamp);
+	}
+}
+
+static int update_envelope(Voice *v)
+{
+	v->envelope_volume += v->envelope_increment;
+	/* Why is there no ^^ operator?? */
+	if (((v->envelope_increment < 0) &&
+		(v->envelope_volume <= v->envelope_target)) ||
+		((v->envelope_increment > 0) &&
+		(v->envelope_volume >= v->envelope_target)))
+	{
+		v->envelope_volume = v->envelope_target;
+		if (recompute_envelope(v))
+			return 1;
+	}
+	return 0;
+}
+
+static void update_tremolo(Voice *v)
+{
+	int depth = v->sample->tremolo_depth << 7;
+
+	if (v->tremolo_sweep)
+	{
+		/* Update sweep position */
+
+		v->tremolo_sweep_position += v->tremolo_sweep;
+		if (v->tremolo_sweep_position >= (1 << SWEEP_SHIFT))
+		{
+			/* Swept to max amplitude */
+			v->tremolo_sweep = 0;
+		}
+		else
+		{
+			/* Need to adjust depth */
+			depth *= v->tremolo_sweep_position;
+			depth >>= SWEEP_SHIFT;
+		}
+	}
+
+	v->tremolo_phase += v->tremolo_phase_increment;
+
+	v->tremolo_volume = (float)
+		(1.0 - FSCALENEG((sine(v->tremolo_phase >> RATE_SHIFT) + 1.0)
+		* depth * TREMOLO_AMPLITUDE_TUNING,
+		17));
+
+	/* I'm not sure about the +1.0 there -- it makes tremoloed voices'
+	volumes on average the lower the higher the tremolo amplitude. */
+}
+
+/* Returns 1 if the note died */
+static int update_signal(Voice *v)
+{
+	if (v->envelope_increment && update_envelope(v))
+		return 1;
+
+	if (v->tremolo_phase_increment)
+		update_tremolo(v);
+
+	apply_envelope_to_amp(v);
+	return 0;
+}
+
+static void mix_mystery_signal(SDWORD control_ratio, const sample_t *sp, float *lp, Voice *v, int count)
+{
+	final_volume_t 
+		left = v->left_mix, 
+		right = v->right_mix;
+	int cc;
+	sample_t s;
+
+	if (!(cc = v->control_counter))
+	{
+		cc = control_ratio;
+		if (update_signal(v))
+			return;	/* Envelope ran out */
+
+		left = v->left_mix;
+		right = v->right_mix;
+	}
+
+	while (count)
+	{
+		if (cc < count)
+		{
+			count -= cc;
+			while (cc--)
+			{
+				s = *sp++;
+				lp[0] += left * s;
+				lp[1] += right * s;
+				lp += 2;
+			}
+			cc = control_ratio;
+			if (update_signal(v))
+				return;	/* Envelope ran out */
+			left = v->left_mix;
+			right = v->right_mix;
+		}
+		else
+		{
+			v->control_counter = cc - count;
+			while (count--)
+			{
+				s = *sp++;
+				lp[0] += left * s;
+				lp[1] += right * s;
+				lp += 2;
+			}
+			return;
+		}
+	}
+}
+
+static void mix_center_signal(SDWORD control_ratio, const sample_t *sp, float *lp, Voice *v, int count)
+{
+	final_volume_t 
+		left = v->left_mix;
+	int cc;
+	sample_t s;
+
+	if (!(cc = v->control_counter))
+	{
+		cc = control_ratio;
+		if (update_signal(v))
+			return;	/* Envelope ran out */
+		left = v->left_mix;
+	}
+
+	while (count)
+	{
+		if (cc < count)
+		{
+			count -= cc;
+			while (cc--)
+			{
+				s = *sp++ * left;
+				lp[0] += s;
+				lp[1] += s;
+				lp += 2;
+			}
+			cc = control_ratio;
+			if (update_signal(v))
+				return;	/* Envelope ran out */
+			left = v->left_mix;
+		}
+		else
+		{
+			v->control_counter = cc - count;
+			while (count--)
+			{
+				s = *sp++ * left;
+				lp[0] += s;
+				lp[1] += s;
+				lp += 2;
+			}
+			return;
+		}
+	}
+}
+
+static void mix_single_left_signal(SDWORD control_ratio, const sample_t *sp, float *lp, Voice *v, int count)
+{
+	final_volume_t 
+		left = v->left_mix;
+	int cc;
+
+	if (!(cc = v->control_counter))
+	{
+		cc = control_ratio;
+		if (update_signal(v))
+			return;	/* Envelope ran out */
+		left = v->left_mix;
+	}
+
+	while (count)
+	{
+		if (cc < count)
+		{
+			count -= cc;
+			while (cc--)
+			{
+				lp[0] += *sp++ * left;
+				lp += 2;
+			}
+			cc = control_ratio;
+			if (update_signal(v))
+				return;	/* Envelope ran out */
+			left = v->left_mix;
+		}
+		else
+		{
+			v->control_counter = cc - count;
+			while (count--)
+			{
+				lp[0] += *sp++ * left;
+				lp += 2;
+			}
+			return;
+		}
+	}
+}
+
+static void mix_single_right_signal(SDWORD control_ratio, const sample_t *sp, float *lp, Voice *v, int count)
+{
+	final_volume_t 
+		left = v->left_mix;
+	int cc;
+
+	if (!(cc = v->control_counter))
+	{
+		cc = control_ratio;
+		if (update_signal(v))
+			return;	/* Envelope ran out */
+		left = v->left_mix;
+	}
+
+	while (count)
+	{
+		if (cc < count)
+		{
+			count -= cc;
+			while (cc--)
+			{
+				lp[1] += *sp++ * left;
+				lp += 2;
+			}
+			cc = control_ratio;
+			if (update_signal(v))
+				return;	/* Envelope ran out */
+			left = v->left_mix;
+		}
+		else
+		{
+			v->control_counter = cc - count;
+			while (count--)
+			{
+				lp[1] += *sp++ * left;
+				lp += 2;
+			}
+			return;
+		}
+	}
+}
+
+static void mix_mono_signal(SDWORD control_ratio, const sample_t *sp, float *lp, Voice *v, int count)
+{
+	final_volume_t 
+		left = v->left_mix;
+	int cc;
+
+	if (!(cc = v->control_counter))
+	{
+		cc = control_ratio;
+		if (update_signal(v))
+			return;	/* Envelope ran out */
+		left = v->left_mix;
+	}
+
+	while (count)
+	{
+		if (cc < count)
+		{
+			count -= cc;
+			while (cc--)
+			{
+				*lp++ += *sp++ * left;
+			}
+			cc = control_ratio;
+			if (update_signal(v))
+				return;	/* Envelope ran out */
+			left = v->left_mix;
+		}
+		else
+		{
+			v->control_counter = cc - count;
+			while (count--)
+			{
+				*lp++ += *sp++ * left;
+			}
+			return;
+		}
+	}
+}
+
+static void mix_mystery(SDWORD control_ratio, const sample_t *sp, float *lp, Voice *v, int count)
+{
+	final_volume_t 
+		left = v->left_mix, 
+		right = v->right_mix;
+	sample_t s;
+
+	while (count--)
+	{
+		s = *sp++;
+		lp[0] += s * left;
+		lp[1] += s * right;
+		lp += 2;
+	}
+}
+
+static void mix_center(const sample_t *sp, float *lp, Voice *v, int count)
+{
+	final_volume_t 
+		left = v->left_mix;
+	sample_t s;
+
+	while (count--)
+	{
+		s = *sp++ * left;
+		lp[0] += s;
+		lp[1] += s;
+		lp += 2;
+	}
+}
+
+static void mix_single_left(const sample_t *sp, float *lp, Voice *v, int count)
+{
+	final_volume_t 
+		left = v->left_mix;
+
+	while (count--)
+	{
+		lp[0] += *sp++ * left;
+		lp += 2;
+	}
+}
+static void mix_single_right(const sample_t *sp, float *lp, Voice *v, int count)
+{
+	final_volume_t 
+		left = v->left_mix;
+
+	while (count--)
+	{
+		lp[1] += *sp++ * left;
+		lp += 2;
+	}
+}
+
+static void mix_mono(const sample_t *sp, float *lp, Voice *v, int count)
+{
+	final_volume_t 
+		left = v->left_mix;
+
+	while (count--)
+	{
+		*lp++ += *sp++ * left;
+	}
+}
+
+/* Ramp a note out in c samples */
+static void ramp_out(const sample_t *sp, float *lp, Voice *v, int c)
+{
+	final_volume_t left, right, li, ri;
+
+	sample_t s = 0; /* silly warning about uninitialized s */
+
+	/* Fix by James Caldwell */
+	if ( c == 0 ) c = 1;
+
+	left = v->left_mix;
+	li = -(left/c);
+	if (li == 0) li = -1;
+
+	/* printf("Ramping out: left=%d, c=%d, li=%d\n", left, c, li); */
+
+	if (v->panned == PANNED_MYSTERY)
+	{
+		right = v->right_mix;
+		ri = -(right/c);
+		while (c--)
+		{
+			left += li; if (left < 0) left = 0;
+			right += ri; if (right < 0) right = 0;
+			s = *sp++;
+			lp[0] += s * left;
+			lp[1] += s * right;
+			lp += 2;
+		}
+	}
+	else if (v->panned == PANNED_CENTER)
+	{
+		while (c--)
+		{
+			left += li;
+			if (left < 0)
+				return;
+			s = *sp++ * left;
+			lp[0] += s;
+			lp[1] += s;
+			lp += 2;
+		}
+	}
+	else if (v->panned == PANNED_LEFT)
+	{
+		while (c--)
+		{
+			left += li;
+			if (left < 0)
+				return;
+			lp[0] += *sp++ * left;
+			lp += 2;
+		}
+	}
+	else if (v->panned == PANNED_RIGHT)
+	{
+		while (c--)
+		{
+			left += li;
+			if (left < 0)
+				return;
+			s = *sp++;
+			lp[1] += *sp++ * left;
+			lp += 2;
+		}
+	}
+}
+
+
+/**************** interface function ******************/
+
+void mix_voice(Renderer *song, float *buf, Voice *v, int c)
+{
+	int count = c;
+	sample_t *sp;
+	if (c < 0)
+	{
+		return;
+	}
+	if (v->status == VOICE_DIE)
+	{
+		if (count >= MAX_DIE_TIME)
+			count = MAX_DIE_TIME;
+		sp = resample_voice(song, v, &count);
+		ramp_out(sp, buf, v, count);
+		v->status = VOICE_FREE;
+	}
+	else
+	{
+		sp = resample_voice(song, v, &count);
+		if (count < 0)
+		{
+			return;
+		}
+		if (v->panned == PANNED_MYSTERY)
+		{
+			if (v->envelope_increment || v->tremolo_phase_increment)
+				mix_mystery_signal(song->control_ratio, sp, buf, v, count);
+			else
+				mix_mystery(song->control_ratio, sp, buf, v, count);
+		}
+		else if (v->panned == PANNED_CENTER)
+		{
+			if (v->envelope_increment || v->tremolo_phase_increment)
+				mix_center_signal(song->control_ratio, sp, buf, v, count);
+			else
+				mix_center(sp, buf, v, count);
+		}
+		else
+		{ 
+			/* It's either full left or full right. In either case,
+			every other sample is 0. Just get the offset right: */
+
+			if (v->envelope_increment || v->tremolo_phase_increment)
+			{
+				if (v->panned == PANNED_RIGHT)
+					mix_single_right_signal(song->control_ratio, sp, buf, v, count);
+				else
+					mix_single_left_signal(song->control_ratio, sp, buf, v, count);
+			}
+			else 
+			{
+				if (v->panned == PANNED_RIGHT)
+					mix_single_right(sp, buf, v, count);
+				else
+					mix_single_left(sp, buf, v, count);
+			}
+		}
+	}
+}
+
+}
diff --git a/src/timidity/playmidi.cpp b/src/timidity/playmidi.cpp
new file mode 100644
index 000000000..cbe6afb2d
--- /dev/null
+++ b/src/timidity/playmidi.cpp
@@ -0,0 +1,1575 @@
+/*
+
+	TiMidity -- Experimental MIDI to WAVE converter
+	Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
+
+	This library is free software; you can redistribute it and/or
+	modify it under the terms of the GNU Lesser General Public
+	License as published by the Free Software Foundation; either
+	version 2.1 of the License, or (at your option) any later version.
+
+	This library 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
+	Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public
+	License along with this library; if not, write to the Free Software
+	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+	playmidi.c -- random stuff in need of rearrangement
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "timidity.h"
+
+#pragma intrinsic(pow)
+
+namespace Timidity
+{
+
+static bool opt_stereo_surround = false;
+bool adjust_panning_immediately = false;
+
+void Renderer::reset_voices()
+{
+	for (int i = 0; i < MAX_VOICES; i++)
+		voice[i].status = VOICE_FREE;
+}
+
+/* Process the Reset All Controllers event */
+void Renderer::reset_controllers(int c)
+{
+	channel[c].volume = (100 << 7) | 100;
+	channel[c].expression = 0x3fff;
+	channel[c].sustain = 0;
+	channel[c].pitchbend = 0x2000;
+	channel[c].pitchfactor = 0; /* to be computed */
+
+	channel[c].reverberation = 0;
+	channel[c].chorusdepth = 0;
+}
+
+void Renderer::reset_midi()
+{
+	for (int i = 0; i < MAXCHAN; i++)
+	{
+		reset_controllers(i);
+		/* The rest of these are unaffected by the Reset All Controllers event */
+		channel[i].program = default_program;
+		channel[i].panning = NO_PANNING;
+		channel[i].pitchsens = 200;
+		channel[i].bank = 0; /* tone bank or drum set */
+		channel[i].harmoniccontent = 64;
+		channel[i].releasetime = 64;
+		channel[i].attacktime = 64;
+		channel[i].brightness = 64;
+		channel[i].sfx = 0;
+	}
+	reset_voices();
+}
+
+void Renderer::select_sample(int v, Instrument *ip)
+{
+	float f, cdiff, diff, midfreq;
+	int s, i;
+	Sample *sp, *closest;
+
+	s = ip->samples;
+	sp = ip->sample;
+
+	if (s == 1)
+	{
+		voice[v].sample = sp;
+		return;
+	}
+
+	f = voice[v].orig_frequency;
+	/* 
+	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 = 1e10;
+	closest = sp = ip->sample;
+	midfreq = float(sp->low_freq + sp->high_freq) / 2;
+	for (i = 0; i < s; i++)
+	{
+		diff = sp->root_freq - f;
+		/*  But the root freq. can perfectly well lie outside the keyrange
+		*  frequencies, so let's try:
+		*/
+		/* diff = midfreq - f; */
+		if (diff < 0) diff = -diff;
+		if (diff < cdiff)
+		{
+			cdiff = diff;
+			closest = sp;
+		}
+		sp++;
+	}
+	voice[v].sample = closest;
+	return;
+}
+
+
+
+void Renderer::select_stereo_samples(int v, InstrumentLayer *lp)
+{
+	Instrument *ip;
+	InstrumentLayer *nlp, *bestvel;
+	int diffvel, midvel, mindiff;
+
+	/* select closest velocity */
+	bestvel = lp;
+	mindiff = 500;
+	for (nlp = lp; nlp; nlp = nlp->next)
+	{
+		midvel = (nlp->hi + nlp->lo)/2;
+		if (!midvel)
+		{
+			diffvel = 127;
+		}
+		else if (voice[v].velocity < nlp->lo || voice[v].velocity > nlp->hi)
+		{
+			diffvel = 200;
+		}
+		else
+		{
+			diffvel = voice[v].velocity - midvel;
+		}
+		if (diffvel < 0)
+		{
+			diffvel = -diffvel;
+		}
+		if (diffvel < mindiff)
+		{
+			mindiff = diffvel;
+			bestvel = nlp;
+		}
+	}
+	ip = bestvel->instrument;
+
+	if (ip->right_sample)
+	{
+		ip->sample = ip->right_sample;
+		ip->samples = ip->right_samples;
+		select_sample(v, ip);
+		voice[v].right_sample = voice[v].sample;
+	}
+	else
+	{
+		voice[v].right_sample = NULL;
+	}
+	ip->sample = ip->left_sample;
+	ip->samples = ip->left_samples;
+	select_sample(v, ip);
+}
+
+
+void Renderer::recompute_freq(int v)
+{
+	Channel *ch = &channel[voice[v].channel];
+	int 
+		sign = (voice[v].sample_increment < 0), /* for bidirectional loops */
+		pb = ch->pitchbend;
+	double a;
+
+	if (!voice[v].sample->sample_rate)
+		return;
+
+	if (voice[v].vibrato_control_ratio)
+	{
+		/* This instrument has vibrato. Invalidate any precomputed
+		sample_increments. */
+
+		int i = VIBRATO_SAMPLE_INCREMENTS;
+		while (i--)
+			voice[v].vibrato_sample_increment[i]=0;
+	}
+
+	if (pb == 0x2000 || pb < 0 || pb > 0x3FFF)
+	{
+		voice[v].frequency = voice[v].orig_frequency;
+	}
+	else
+	{
+		pb -= 0x2000;
+		if (ch->pitchfactor == 0)
+		{
+			/* Damn. Somebody bent the pitch. */
+			ch->pitchfactor = pow(2.f, ((abs(pb) * ch->pitchsens) / (8191.f * 1200.f)));
+		}
+		if (pb < 0)
+		{
+			voice[v].frequency = voice[v].orig_frequency / ch->pitchfactor;
+		}
+		else
+		{
+			voice[v].frequency = voice[v].orig_frequency * ch->pitchfactor;
+		}
+	}
+
+	a = FSCALE(((double)(voice[v].sample->sample_rate) * voice[v].frequency) /
+		((double)(voice[v].sample->root_freq) * rate),
+		FRACTION_BITS);
+
+	if (sign) 
+		a = -a; /* need to preserve the loop direction */
+
+	voice[v].sample_increment = (int)(a);
+}
+
+void Renderer::recompute_amp(Voice *v)
+{
+	double tempamp;
+	int chan = v->channel;
+	int panning = v->panning;
+	double vol = channel[chan].volume / 16383.f;
+	double expr = calc_vol(channel[chan].expression / 16383.f);
+	double vel = calc_vol(v->velocity / 127.f);
+
+	if (channel[chan].kit)
+	{
+		int note = v->sample->note_to_use;
+		if (note > 0 && drumvolume[chan][note] >= 0) { vol = drumvolume[chan][note] / 127.f; }
+		if (note > 0 && drumpanpot[chan][note] >= 0) { panning = drumvolume[chan][note]; panning |= panning << 7; }
+	}
+
+	vol = calc_vol(vol);
+	tempamp = vel * vol * expr * v->sample->volume;
+
+	if (panning > (60 << 7) && panning < (68 << 7))
+	{
+		v->panned = PANNED_CENTER;
+		v->left_amp = (float)(tempamp * 0.70710678118654752440084436210485);	// * sqrt(0.5)
+	}
+	else if (panning < (5 << 7))
+	{
+		v->panned = PANNED_LEFT;
+		v->left_amp = (float)tempamp;
+	}
+	else if (panning > (123 << 7))
+	{
+		v->panned = PANNED_RIGHT;
+		v->left_amp = (float)tempamp; /* left_amp will be used */
+	}
+	else
+	{
+		double pan = panning / 16384.0;
+		v->panned = PANNED_MYSTERY;
+		v->left_amp = (float)(tempamp * sqrt(1.0 - pan));
+		v->right_amp = (float)(tempamp * sqrt(pan));
+	}
+}
+
+
+#define NOT_CLONE 0
+#define STEREO_CLONE 1
+#define REVERB_CLONE 2
+#define CHORUS_CLONE 3
+
+
+/* just a variant of note_on() */
+int Renderer::vc_alloc(int j)
+{
+	int i = voices; 
+
+	while (i--)
+	{
+		if (i != j && (voice[i].status & VOICE_FREE))
+		{
+			return i;
+		}
+	}
+	return -1;
+}
+
+void Renderer::kill_others(int i)
+{
+	int j = voices; 
+
+	if (!voice[i].sample->exclusiveClass) return;
+
+	while (j--)
+	{
+		if (voice[j].status & (VOICE_FREE|VOICE_OFF|VOICE_DIE)) continue;
+		if (i == j) continue;
+		if (voice[i].channel != voice[j].channel) continue;
+		if (voice[j].sample->note_to_use)
+		{
+			if (voice[j].sample->exclusiveClass != voice[i].sample->exclusiveClass) continue;
+			kill_note(j);
+		}
+	}
+}
+
+
+void Renderer::clone_voice(Instrument *ip, int v, int note, int vel, int clone_type, int variationbank)
+{
+	int w, played_note, chorus = 0, reverb = 0, milli;
+	int chan = voice[v].channel;
+
+	if (clone_type == STEREO_CLONE)
+	{
+		if (!voice[v].right_sample && variationbank != 3) return;
+		if (variationbank == 6) return;
+	}
+
+	if (channel[chan].kit)
+	{
+		reverb = drumreverberation[chan][voice[v].note];
+		chorus = drumchorusdepth[chan][voice[v].note];
+	}
+	else
+	{
+		reverb = channel[chan].reverberation;
+		chorus = channel[chan].chorusdepth;
+	}
+
+	if (clone_type == REVERB_CLONE) chorus = 0;
+	else if (clone_type == CHORUS_CLONE) reverb = 0;
+	else if (clone_type == STEREO_CLONE) reverb = chorus = 0;
+
+	if (reverb > 127) reverb = 127;
+	if (chorus > 127) chorus = 127;
+
+	if (clone_type == CHORUS_CLONE)
+	{
+		if (variationbank == 32) chorus = 30;
+		else if (variationbank == 33) chorus = 60;
+		else if (variationbank == 34) chorus = 90;
+	}
+
+	chorus /= 2;  /* This is an ad hoc adjustment. */
+
+	if (!reverb && !chorus && clone_type != STEREO_CLONE) return;
+
+	if ( (w = vc_alloc(v)) < 0 ) return;
+
+	voice[w] = voice[v];
+	if (clone_type == STEREO_CLONE) voice[v].clone_voice = w;
+	voice[w].clone_voice = v;
+	voice[w].clone_type = clone_type;
+
+	voice[w].sample = voice[v].right_sample;
+	voice[w].velocity = vel;
+
+	milli = int(rate / 1000);
+
+	if (clone_type == STEREO_CLONE)
+	{
+		int left, right, leftpan, rightpan;
+		int panrequest = voice[v].panning;
+		if (variationbank == 3)
+		{
+			voice[v].panning = 0;
+			voice[w].panning = 127;
+		}
+		else
+		{
+			if (voice[v].sample->panning > voice[w].sample->panning)
+			{
+				left = w;
+				right = v;
+			}
+			else
+			{
+				left = v;
+				right = w;
+			}
+#define INSTRUMENT_SEPARATION 12
+			leftpan = panrequest - INSTRUMENT_SEPARATION / 2;
+			rightpan = leftpan + INSTRUMENT_SEPARATION;
+			if (leftpan < 0)
+			{
+				leftpan = 0;
+				rightpan = leftpan + INSTRUMENT_SEPARATION;
+			}
+			if (rightpan > 127)
+			{
+				rightpan = 127;
+				leftpan = rightpan - INSTRUMENT_SEPARATION;
+			}
+			voice[left].panning = leftpan;
+			voice[right].panning = rightpan;
+			voice[right].echo_delay = 20 * milli;
+		}
+	}
+
+	voice[w].volume = voice[w].sample->volume;
+
+	if (reverb)
+	{
+		if (opt_stereo_surround)
+		{
+			if (voice[w].panning > 64)
+				voice[w].panning = 127;
+			else
+				voice[w].panning = 0;
+		}
+		else
+		{
+			if (voice[v].panning < 64)
+				voice[w].panning = 64 + reverb/2;
+			else
+				voice[w].panning = 64 - reverb/2;
+		}
+
+		/* try 98->99 for melodic instruments ? (bit much for percussion) */
+		/* voice[w].volume *= calc_vol(((127 - reverb) / 8 + 98) / 127.f); */
+		voice[w].volume = float(voice[w].volume * calc_vol((911 - reverb) / (8 * 127.f)));
+
+		voice[w].echo_delay += reverb * milli;
+		voice[w].envelope_rate[DECAY] *= 2;
+		voice[w].envelope_rate[RELEASE] /= 2;
+
+		if (XG_System_reverb_type >= 0)
+		{
+			int subtype = XG_System_reverb_type & 0x07;
+			int rtype = XG_System_reverb_type >> 3;
+			switch (rtype)
+			{
+			case 0: /* no effect */
+				break;
+			case 1: /* hall */
+				if (subtype) voice[w].echo_delay += 100 * milli;
+				break;
+			case 2: /* room */
+				voice[w].echo_delay /= 2;
+				break;
+			case 3: /* stage */
+				voice[w].velocity = voice[v].velocity;
+				break;
+			case 4: /* plate */
+				voice[w].panning = voice[v].panning;
+				break;
+			case 16: /* white room */
+				voice[w].echo_delay = 0;
+				break;
+			case 17: /* tunnel */
+				voice[w].echo_delay *= 2;
+				voice[w].velocity /= 2;
+				break;
+			case 18: /* canyon */
+				voice[w].echo_delay *= 2;
+				break;
+			case 19: /* basement */
+				voice[w].velocity /= 2;
+				break;
+			default:
+				break;
+			}
+		}
+	}
+	played_note = voice[w].sample->note_to_use;
+	if (!played_note)
+	{
+		played_note = note & 0x7f;
+		if (variationbank == 35) played_note += 12;
+		else if (variationbank == 36) played_note -= 12;
+		else if (variationbank == 37) played_note += 7;
+		else if (variationbank == 36) played_note -= 7;
+	}
+#if 0
+	played_note = ( (played_note - voice[w].sample->freq_center) * voice[w].sample->freq_scale ) / 1024 +
+		voice[w].sample->freq_center;
+#endif
+	voice[w].note = played_note;
+	voice[w].orig_frequency = note_to_freq(played_note);
+
+	if (chorus)
+	{
+		if (opt_stereo_surround)
+		{
+			if (voice[v].panning < 64)
+				voice[w].panning = voice[v].panning + 32;
+			else
+				voice[w].panning = voice[v].panning - 32;
+		}
+
+		if (!voice[w].vibrato_control_ratio)
+		{
+			voice[w].vibrato_control_ratio = 100;
+			voice[w].vibrato_depth = 6;
+			voice[w].vibrato_sweep = 74;
+		}
+		voice[w].volume *= 0.40f;
+		voice[v].volume = voice[w].volume;
+		recompute_amp(&voice[v]);
+		apply_envelope_to_amp(&voice[v]);
+
+		voice[w].vibrato_sweep = chorus/2;
+		voice[w].vibrato_depth /= 2;
+		if (!voice[w].vibrato_depth) voice[w].vibrato_depth = 2;
+		voice[w].vibrato_control_ratio /= 2;
+		voice[w].echo_delay += 30 * milli;
+
+		if (XG_System_chorus_type >= 0)
+		{
+			int subtype = XG_System_chorus_type & 0x07;
+			int chtype = 0x0f & (XG_System_chorus_type >> 3);
+			float chorus_factor;
+
+			switch (chtype)
+			{
+			case 0: /* no effect */
+				break;
+			case 1: /* chorus */
+				chorus /= 3;
+				chorus_factor = pow(2.f, chorus / (256.f * 12.f));
+				if(channel[ voice[w].channel ].pitchbend + chorus < 0x2000)
+				{
+					voice[w].orig_frequency = voice[w].orig_frequency * chorus_factor;
+				}
+				else
+				{
+					voice[w].orig_frequency = voice[w].orig_frequency / chorus_factor;
+				}
+				if (subtype)
+				{
+					voice[w].vibrato_depth *= 2;
+				}
+				break;
+			case 2: /* celeste */
+				voice[w].orig_frequency += (voice[w].orig_frequency/128) * chorus;
+				break;
+			case 3: /* flanger */
+				voice[w].vibrato_control_ratio = 10;
+				voice[w].vibrato_depth = 100;
+				voice[w].vibrato_sweep = 8;
+				voice[w].echo_delay += 200 * milli;
+				break;
+			case 4: /* symphonic : cf Children of the Night /128 bad, /1024 ok */
+				voice[w].orig_frequency += (voice[w].orig_frequency/512) * chorus;
+				voice[v].orig_frequency -= (voice[v].orig_frequency/512) * chorus;
+				recompute_freq(v);
+				break;
+			case 8: /* phaser */
+				break;
+			default:
+				break;
+			}
+		}
+		else
+		{
+			float chorus_factor;
+			chorus /= 3;
+			chorus_factor = pow(2.f, chorus / (256.f * 12.f));
+			if (channel[ voice[w].channel ].pitchbend + chorus < 0x2000)
+			{
+				voice[w].orig_frequency = voice[w].orig_frequency * chorus_factor;
+			}
+			else
+			{
+				voice[w].orig_frequency = voice[w].orig_frequency / chorus_factor;
+			}
+		}
+	}
+#if 0
+	voice[w].loop_start = voice[w].sample->loop_start;
+	voice[w].loop_end = voice[w].sample->loop_end;
+#endif
+	voice[w].echo_delay_count = voice[w].echo_delay;
+	if (reverb) voice[w].echo_delay *= 2;
+
+	recompute_freq(w);
+	recompute_amp(&voice[w]);
+	if (voice[w].sample->modes & MODES_ENVELOPE)
+	{
+		/* Ramp up from 0 */
+		voice[w].envelope_stage = ATTACK;
+		voice[w].modulation_stage = ATTACK;
+		voice[w].envelope_volume = 0;
+		voice[w].modulation_volume = 0;
+		voice[w].control_counter = 0;
+		voice[w].modulation_counter = 0;
+		recompute_envelope(&voice[w]);
+		/*recompute_modulation(w);*/
+	}
+	else
+	{
+		voice[w].envelope_increment = 0;
+		voice[w].modulation_increment = 0;
+	}
+	apply_envelope_to_amp(&voice[w]);
+}
+
+
+void Renderer::xremap(int *banknumpt, int *this_notept, int this_kit)
+{
+	int i, newmap;
+	int banknum = *banknumpt;
+	int this_note = *this_notept;
+	int newbank, newnote;
+
+	if (!this_kit)
+	{
+		if (banknum == SFXBANK && tonebank[SFXBANK])
+		{
+			return;
+		}
+		if (banknum == SFXBANK && tonebank[120])
+		{
+			*banknumpt = 120;
+		}
+		return;
+	}
+	if (this_kit != 127 && this_kit != 126)
+	{
+		return;
+	}
+	for (i = 0; i < XMAPMAX; i++)
+	{
+		newmap = xmap[i][0];
+		if (!newmap) return;
+		if (this_kit == 127 && newmap != XGDRUM) continue;
+		if (this_kit == 126 && newmap != SFXDRUM1) continue;
+		if (xmap[i][1] != banknum) continue;
+		if (xmap[i][3] != this_note) continue;
+		newbank = xmap[i][2];
+		newnote = xmap[i][4];
+		if (newbank == banknum && newnote == this_note) return;
+		if (!drumset[newbank]) return;
+		if (!drumset[newbank]->tone[newnote].layer) return;
+		if (drumset[newbank]->tone[newnote].layer == MAGIC_LOAD_INSTRUMENT) return;
+		*banknumpt = newbank;
+		*this_notept = newnote;
+		return;
+	}
+}
+
+
+void Renderer::start_note(int ch, int this_note, int this_velocity, int i)
+{
+	InstrumentLayer *lp;
+	Instrument *ip;
+	int j, banknum;
+	int played_note, drumpan = NO_PANNING;
+	int rt;
+	int attacktime, releasetime, decaytime, variationbank;
+	int brightness = channel[ch].brightness;
+	int harmoniccontent = channel[ch].harmoniccontent;
+	int drumsflag = channel[ch].kit;
+	int this_prog = channel[ch].program;
+
+	if (channel[ch].sfx)
+	{
+		banknum = channel[ch].sfx;
+	}
+	else
+	{
+		banknum = channel[ch].bank;
+	}
+
+	voice[i].velocity = this_velocity;
+
+	if (XG_System_On) xremap(&banknum, &this_note, drumsflag);
+	/*   if (current_config_pc42b) pcmap(&banknum, &this_note, &this_prog, &drumsflag); */
+
+	if (drumsflag)
+	{
+		if (NULL == drumset[banknum] || NULL == (lp = drumset[banknum]->tone[this_note].layer))
+		{
+			if (!(lp = drumset[0]->tone[this_note].layer))
+				return; /* No instrument? Then we can't play. */
+		}
+		ip = lp->instrument;
+		if (ip->type == INST_GUS && ip->samples != 1)
+		{
+			ctl->cmsg(CMSG_WARNING, VERB_VERBOSE, 
+				"Strange: percussion instrument with %d samples!", ip->samples);
+		}
+
+		if (ip->sample->note_to_use) /* Do we have a fixed pitch? */
+		{
+			voice[i].orig_frequency = note_to_freq(ip->sample->note_to_use);
+			drumpan = drumpanpot[ch][(int)ip->sample->note_to_use];
+			drumpan |= drumpan << 7;
+		}
+		else
+			voice[i].orig_frequency = note_to_freq(this_note & 0x7F);
+	}
+	else
+	{
+		if (channel[ch].program == SPECIAL_PROGRAM)
+		{
+			lp = default_instrument;
+		}
+		else if (NULL == tonebank[channel[ch].bank] || NULL == (lp = tonebank[channel[ch].bank]->tone[channel[ch].program].layer))
+		{
+			if (NULL == (lp = tonebank[0]->tone[this_prog].layer))
+				return; /* No instrument? Then we can't play. */
+		}
+		ip = lp->instrument;
+		if (ip->sample->note_to_use) /* Fixed-pitch instrument? */
+			voice[i].orig_frequency = note_to_freq(ip->sample->note_to_use);
+		else
+			voice[i].orig_frequency = note_to_freq(this_note & 0x7F);
+	}
+
+	select_stereo_samples(i, lp);
+
+	played_note = voice[i].sample->note_to_use;
+
+	if (!played_note || !drumsflag) played_note = this_note & 0x7f;
+#if 0
+	played_note = ( (played_note - voice[i].sample->freq_center) * voice[i].sample->freq_scale ) / 1024 +
+		voice[i].sample->freq_center;
+#endif
+	voice[i].status = VOICE_ON;
+	voice[i].channel = ch;
+	voice[i].note = played_note;
+	voice[i].velocity = this_velocity;
+	voice[i].sample_offset = 0;
+	voice[i].sample_increment = 0; /* make sure it isn't negative */
+
+	voice[i].tremolo_phase = 0;
+	voice[i].tremolo_phase_increment = voice[i].sample->tremolo_phase_increment;
+	voice[i].tremolo_sweep = voice[i].sample->tremolo_sweep_increment;
+	voice[i].tremolo_sweep_position = 0;
+
+	voice[i].vibrato_sweep = voice[i].sample->vibrato_sweep_increment;
+	voice[i].vibrato_sweep_position = 0;
+	voice[i].vibrato_depth = voice[i].sample->vibrato_depth;
+	voice[i].vibrato_control_ratio = voice[i].sample->vibrato_control_ratio;
+	voice[i].vibrato_control_counter = voice[i].vibrato_phase=0;
+//	voice[i].vibrato_delay = voice[i].sample->vibrato_delay;
+
+	kill_others(i);
+
+	for (j = 0; j < VIBRATO_SAMPLE_INCREMENTS; j++)
+		voice[i].vibrato_sample_increment[j] = 0;
+
+	attacktime = channel[ch].attacktime;
+	releasetime = channel[ch].releasetime;
+	decaytime = 64;
+	variationbank = channel[ch].variationbank;
+
+	switch (variationbank)
+	{
+	case  8:
+		attacktime = 64+32;
+		break;
+	case 12:
+		decaytime = 64-32;
+		break;
+	case 16:
+		brightness = 64+16;
+		break;
+	case 17:
+		brightness = 64+32;
+		break;
+	case 18:
+		brightness = 64-16;
+		break;
+	case 19:
+		brightness = 64-32;
+		break;
+	case 20:
+		harmoniccontent = 64+16;
+		break;
+#if 0
+	case 24:
+		voice[i].modEnvToFilterFc=2.0;
+		voice[i].sample->cutoff_freq = 800;
+		break;
+	case 25:
+		voice[i].modEnvToFilterFc=-2.0;
+		voice[i].sample->cutoff_freq = 800;
+		break;
+	case 27:
+		voice[i].modLfoToFilterFc=2.0;
+		voice[i].lfo_phase_increment=109;
+		voice[i].lfo_sweep=122;
+		voice[i].sample->cutoff_freq = 800;
+		break;
+	case 28:
+		voice[i].modLfoToFilterFc=-2.0;
+		voice[i].lfo_phase_increment=109;
+		voice[i].lfo_sweep=122;
+		voice[i].sample->cutoff_freq = 800;
+		break;
+#endif
+	default:
+		break;
+	}
+
+	for (j = ATTACK; j < MAXPOINT; j++)
+	{
+		voice[i].envelope_rate[j] = voice[i].sample->envelope_rate[j];
+		voice[i].envelope_offset[j] = voice[i].sample->envelope_offset[j];
+	}
+
+	voice[i].echo_delay = voice[i].envelope_rate[DELAY];
+	voice[i].echo_delay_count = voice[i].echo_delay;
+
+	if (attacktime != 64)
+	{
+		rt = voice[i].envelope_rate[ATTACK];
+		rt = rt + ( (64-attacktime)*rt ) / 100;
+		if (rt > 1000)
+		{
+			voice[i].envelope_rate[ATTACK] = rt;
+		}
+	}
+	if (releasetime != 64)
+	{
+		rt = voice[i].envelope_rate[RELEASE];
+		rt = rt + ( (64-releasetime)*rt ) / 100;
+		if (rt > 1000)
+		{
+			voice[i].envelope_rate[RELEASE] = rt;
+		}
+	}
+	if (decaytime != 64)
+	{
+		rt = voice[i].envelope_rate[DECAY];
+		rt = rt + ( (64-decaytime)*rt ) / 100;
+		if (rt > 1000)
+		{
+			voice[i].envelope_rate[DECAY] = rt;
+		}
+	}
+
+	if (channel[ch].panning != NO_PANNING)
+	{
+		voice[i].panning = channel[ch].panning;
+	}
+	else
+	{
+		voice[i].panning = voice[i].sample->panning;
+	}
+	if (drumpan != NO_PANNING)
+	{
+		voice[i].panning = drumpan;
+	}
+
+	if (variationbank == 1)
+	{
+		int pan = voice[i].panning;
+		int disturb = 0;
+		/* If they're close up (no reverb) and you are behind the pianist,
+		* high notes come from the right, so we'll spread piano etc. notes
+		* out horizontally according to their pitches.
+		*/
+		if (this_prog < 21)
+		{
+			int n = voice[i].velocity - 32;
+			if (n < 0) n = 0;
+			if (n > 64) n = 64;
+			pan = pan/2 + n;
+		}
+		/* For other types of instruments, the music sounds more alive if
+		* notes come from slightly different directions.  However, instruments
+		* do drift around in a sometimes disconcerting way, so the following
+		* might not be such a good idea.
+		*/
+		else
+		{
+			disturb = (voice[i].velocity/32 % 8) + (voice[i].note % 8); /* /16? */
+		}
+
+		if (pan < 64)
+		{
+			pan += disturb;
+		}
+		else
+		{
+			pan -= disturb;
+		}
+		pan = pan < 0 ? 0 : pan > 127 ? 127 : pan;
+		voice[i].panning = pan | (pan << 7);
+	}
+
+	recompute_freq(i);
+	recompute_amp(&voice[i]);
+	if (voice[i].sample->modes & MODES_ENVELOPE)
+	{
+		/* Ramp up from 0 */
+		voice[i].envelope_stage = ATTACK;
+		voice[i].envelope_volume = 0;
+		voice[i].control_counter = 0;
+		recompute_envelope(&voice[i]);
+	}
+	else
+	{
+		voice[i].envelope_increment = 0;
+	}
+	apply_envelope_to_amp(&voice[i]);
+
+	voice[i].clone_voice = -1;
+	voice[i].clone_type = NOT_CLONE;
+
+	clone_voice(ip, i, this_note, this_velocity, STEREO_CLONE, variationbank);
+	clone_voice(ip, i, this_note, this_velocity, CHORUS_CLONE, variationbank);
+	clone_voice(ip, i, this_note, this_velocity, REVERB_CLONE, variationbank);
+}
+
+void Renderer::kill_note(int i)
+{
+	voice[i].status = VOICE_DIE;
+	if (voice[i].clone_voice >= 0)
+	{
+		voice[ voice[i].clone_voice ].status = VOICE_DIE;
+	}
+}
+
+
+/* Only one instance of a note can be playing on a single channel. */
+void Renderer::note_on(int chan, int note, int vel)
+{
+	int i = voices, lowest = -1; 
+	float lv = 1e10, v;
+
+	while (i--)
+	{
+		if (voice[i].status == VOICE_FREE)
+		{
+			lowest = i; /* Can't get a lower volume than silence */
+		}
+		else if (voice[i].channel == chan && (voice[i].note == note || channel[chan].mono))
+		{
+			kill_note(i);
+		}
+	}
+
+	if (lowest != -1)
+	{
+		/* Found a free voice. */
+		start_note(chan, note, vel, lowest);
+		return;
+	}
+
+	/* Look for the decaying note with the lowest volume */
+	if (lowest == -1)
+	{
+		i = voices;
+		while (i--)
+		{
+			if ( (voice[i].status & ~(VOICE_ON | VOICE_DIE | VOICE_FREE)) &&
+				(!voice[i].clone_type))
+			{
+				v = voice[i].left_mix;
+				if ((voice[i].panned == PANNED_MYSTERY) && (voice[i].right_mix > v))
+				{
+					v = voice[i].right_mix;
+				}
+				if (v < lv)
+				{
+					lv = v;
+					lowest = i;
+				}
+			}
+		}
+	}
+
+	if (lowest != -1)
+	{
+		int cl = voice[lowest].clone_voice;
+
+		/* This can still cause a click, but if we had a free voice to
+		spare for ramping down this note, we wouldn't need to kill it
+		in the first place... Still, this needs to be fixed. Perhaps
+		we could use a reserve of voices to play dying notes only. */
+
+		if (cl >= 0)
+		{
+			if (voice[cl].clone_type == STEREO_CLONE ||
+				(!voice[cl].clone_type && voice[lowest].clone_type == STEREO_CLONE))
+			{
+				voice[cl].status = VOICE_FREE;
+			}
+			else if (voice[cl].clone_voice == lowest)
+			{
+				voice[cl].clone_voice = -1;
+			}
+		}
+
+		cut_notes++;
+		voice[lowest].status = VOICE_FREE;
+		start_note(chan, note, vel, lowest);
+	}
+	else
+	{
+		lost_notes++;
+	}
+}
+
+void Renderer::finish_note(int i)
+{
+	if (voice[i].sample->modes & MODES_ENVELOPE)
+	{
+		/* We need to get the envelope out of Sustain stage */
+		voice[i].envelope_stage = RELEASE;
+		voice[i].status = VOICE_OFF;
+		recompute_envelope(&voice[i]);
+		apply_envelope_to_amp(&voice[i]);
+	}
+	else
+	{
+		/* Set status to OFF so resample_voice() will let this voice out
+		of its loop, if any. In any case, this voice dies when it
+		hits the end of its data (ofs>=data_length). */
+		voice[i].status = VOICE_OFF;
+	}
+
+	int v;
+	if ( (v = voice[i].clone_voice) >= 0)
+	{
+		voice[i].clone_voice = -1;
+		finish_note(v);
+	}
+}
+
+void Renderer::note_off(int chan, int note, int vel)
+{
+	int i = voices, v;
+	while (i--)
+	{
+		if (voice[i].status == VOICE_ON &&
+			voice[i].channel == chan &&
+			voice[i].note == note)
+		{
+			if (channel[chan].sustain)
+			{
+				voice[i].status = VOICE_SUSTAINED;
+
+				if ( (v = voice[i].clone_voice) >= 0)
+				{
+					if (voice[v].status == VOICE_ON)
+						voice[v].status = VOICE_SUSTAINED;
+				}
+			}
+			else
+			{
+				finish_note(i);
+			}
+			return;
+		}
+	}
+}
+
+/* Process the All Notes Off event */
+void Renderer::all_notes_off(int c)
+{
+	int i = voices;
+	ctl->cmsg(CMSG_INFO, VERB_DEBUG, "All notes off on channel %d", c);
+	while (i--)
+	{
+		if (voice[i].status == VOICE_ON && voice[i].channel == c)
+		{
+			if (channel[c].sustain) 
+			{
+				voice[i].status = VOICE_SUSTAINED;
+			}
+			else
+			{
+				finish_note(i);
+			}
+		}
+	}
+}
+
+/* Process the All Sounds Off event */
+void Renderer::all_sounds_off(int c)
+{
+	int i = voices;
+	while (i--)
+	{
+		if (voice[i].channel == c && 
+			voice[i].status != VOICE_FREE &&
+			voice[i].status != VOICE_DIE)
+		{
+			kill_note(i);
+		}
+	}
+}
+
+void Renderer::adjust_pressure(int chan, int note, int amount)
+{
+	int i = voices;
+	while (i--)
+	{
+		if (voice[i].status == VOICE_ON &&
+			voice[i].channel == chan &&
+			voice[i].note == note)
+		{
+			voice[i].velocity = amount;
+			recompute_amp(&voice[i]);
+			apply_envelope_to_amp(&voice[i]);
+			return;
+		}
+	}
+}
+
+void Renderer::adjust_panning(int c)
+{
+	int i = voices;
+	while (i--)
+	{
+		if ((voice[i].channel == c) &&
+			(voice[i].status == VOICE_ON || voice[i].status == VOICE_SUSTAINED))
+		{
+			if (voice[i].clone_type != NOT_CLONE) continue;
+			voice[i].panning = channel[c].panning;
+			recompute_amp(&voice[i]);
+			apply_envelope_to_amp(&voice[i]);
+		}
+	}
+}
+
+void Renderer::drop_sustain(int c)
+{
+	int i = voices;
+	while (i--)
+	{
+		if (voice[i].status == VOICE_SUSTAINED && voice[i].channel == c)
+			finish_note(i);
+	}
+}
+
+void Renderer::adjust_pitchbend(int c)
+{
+	int i = voices;
+	while (i--)
+	{
+		if (voice[i].status != VOICE_FREE && voice[i].channel == c)
+		{
+			recompute_freq(i);
+		}
+	}
+}
+
+void Renderer::adjust_volume(int c)
+{
+	int i = voices;
+	while (i--)
+	{
+		if (voice[i].channel == c &&
+			(voice[i].status == VOICE_ON || voice[i].status == VOICE_SUSTAINED))
+		{
+			recompute_amp(&voice[i]);
+			apply_envelope_to_amp(&voice[i]);
+		}
+	}
+}
+
+void Renderer::HandleEvent(int status, int parm1, int parm2)
+{
+	int command = status & 0xF0;
+	int chan	= status & 0x0F;
+
+	switch (command)
+	{
+	case ME_NOTEON:
+		parm1 += channel[chan].transpose;
+		if (parm2 == 0)
+		{
+			note_off(chan, parm1, 0);
+		}
+		else
+		{
+			note_on(chan, parm1, parm2);
+		}
+		break;
+
+	case ME_NOTEOFF:
+		parm1 += channel[chan].transpose;
+		note_off(chan, parm1, parm2);
+		break;
+
+	case ME_KEYPRESSURE:
+		adjust_pressure(chan, parm1, parm2);
+		break;
+
+	case ME_CONTROLCHANGE:
+		HandleController(chan, parm1, parm2);
+		break;
+
+	case ME_PROGRAM:
+		/* if (ISDRUMCHANNEL(chan)) { */
+		if (channel[chan].kit) {
+			/* Change drum set */
+			channel[chan].bank = parm1;
+		}
+		else
+		{
+			channel[chan].program = parm1;
+		}
+		break;
+
+	case ME_CHANNELPRESSURE:
+		/* Unimplemented */
+		break;
+
+	case ME_PITCHWHEEL:
+		channel[chan].pitchbend = parm1 | (parm2 << 7);
+		channel[chan].pitchfactor = 0;
+		/* Adjust for notes already playing */
+		adjust_pitchbend(chan);
+		break;
+	}
+}
+
+void Renderer::HandleController(int chan, int ctrl, int val)
+{
+	switch (ctrl)
+	{
+    /* These should be the SCC-1 tone bank switch
+       commands. I don't know why there are two, or
+       why the latter only allows switching to bank 0.
+       Also, some MIDI files use 0 as some sort of
+       continuous controller. This will cause lots of
+       warnings about undefined tone banks. */
+	case CTRL_BANK_SELECT:
+		if (XG_System_On)
+		{
+			if (val == SFX_BANKTYPE)
+			{
+				channel[chan].sfx = SFXBANK;
+				channel[chan].kit = 0;
+			}
+			else
+			{
+				channel[chan].sfx = 0;
+				channel[chan].kit = val;
+			}
+		}
+		else
+		{
+			channel[chan].bank = val;
+		}
+		break;
+
+	case CTRL_BANK_SELECT+32:
+		if (XG_System_On)
+		{
+			channel[chan].bank = val;
+		}
+		break;
+
+	case CTRL_VOLUME:
+		channel[chan].volume = (channel[chan].volume & 0x007F) | (val << 7);
+		adjust_volume(chan);
+		break;
+
+	case CTRL_VOLUME+32:
+		channel[chan].volume = (channel[chan].volume & 0x3F80) | (val);
+		adjust_volume(chan);
+		break;
+
+	case CTRL_EXPRESSION:
+		channel[chan].expression = (channel[chan].expression & 0x007F) | (val << 7);
+		adjust_volume(chan);
+		break;
+
+	case CTRL_EXPRESSION+32:
+		channel[chan].expression = (channel[chan].expression & 0x3F80) | (val);
+		adjust_volume(chan);
+		break;
+
+	case CTRL_PAN:
+		channel[chan].panning = (channel[chan].panning & 0x007F) | (val << 7);
+		if (adjust_panning_immediately)
+		{
+			adjust_panning(chan);
+		}
+		break;
+
+	case CTRL_PAN+32:
+		channel[chan].panning = (channel[chan].panning & 0x3F80) | (val);
+		if (adjust_panning_immediately)
+		{
+			adjust_panning(chan);
+		}
+		break;
+
+	case CTRL_SUSTAIN:
+		channel[chan].sustain = val;
+		if (val == 0)
+		{
+			drop_sustain(chan);
+		}
+		break;
+
+	case CTRL_HARMONICCONTENT:
+		channel[chan].harmoniccontent = val;
+		break;
+
+	case CTRL_RELEASETIME:
+		channel[chan].releasetime = val;
+		break;
+
+	case CTRL_ATTACKTIME:
+		channel[chan].attacktime = val;
+		break;
+
+	case CTRL_BRIGHTNESS:
+		channel[chan].brightness = val;
+		break;
+
+	case CTRL_REVERBERATION:
+		channel[chan].reverberation = val;
+		break;
+
+	case CTRL_CHORUSDEPTH:
+		channel[chan].chorusdepth = val;
+		break;
+
+	case CTRL_NRPN_LSB:
+		channel[chan].nrpn = (channel[chan].nrpn & 0x3F80) | (val);
+		channel[chan].nrpn_mode = true;
+		break;
+
+	case CTRL_NRPN_MSB:
+		channel[chan].nrpn = (channel[chan].nrpn & 0x007F) | (val << 7);
+		channel[chan].nrpn_mode = true;
+		break;
+
+	case CTRL_RPN_LSB:
+		channel[chan].rpn = (channel[chan].rpn & 0x3F80) | (val);
+		channel[chan].nrpn_mode = false;
+		break;
+
+	case CTRL_RPN_MSB:
+		channel[chan].rpn = (channel[chan].rpn & 0x007F) | (val << 7);
+		channel[chan].nrpn_mode = false;
+		break;
+
+	case CTRL_DATA_ENTRY:
+		if (channel[chan].nrpn_mode)
+		{
+			DataEntryCoarseNRPN(chan, channel[chan].nrpn, val);
+		}
+		else
+		{
+			DataEntryCoarseRPN(chan, channel[chan].rpn, val);
+		}
+		break;
+
+	case CTRL_DATA_ENTRY+32:
+		if (channel[chan].nrpn_mode)
+		{
+			DataEntryFineNRPN(chan, channel[chan].nrpn, val);
+		}
+		else
+		{
+			DataEntryFineRPN(chan, channel[chan].rpn, val);
+		}
+		break;
+
+	case CTRL_ALL_SOUNDS_OFF:
+		all_sounds_off(chan);
+		break;
+
+	case CTRL_RESET_CONTROLLERS:
+		reset_controllers(chan);
+		break;
+
+	case CTRL_ALL_NOTES_OFF:
+		all_notes_off(chan);
+		break;
+	}
+}
+
+void Renderer::DataEntryCoarseRPN(int chan, int rpn, int val)
+{
+	switch (rpn)
+	{
+	case RPN_PITCH_SENS:
+		channel[chan].pitchsens = (channel[chan].pitchsens % 100) + (val * 100);
+		channel[chan].pitchfactor = 0;
+		break;
+
+	// TiMidity resets the pitch sensitivity when a song attempts to write to
+	// RPN_RESET. My docs tell me this is just a dummy value that is guaranteed
+	// to not cause future data entry to go anywhere until a new RPN is set.
+	}
+}
+
+void Renderer::DataEntryFineRPN(int chan, int rpn, int val)
+{
+	switch (rpn)
+	{
+	case RPN_PITCH_SENS:
+		channel[chan].pitchsens = (channel[chan].pitchsens / 100) * 100 + val;
+		channel[chan].pitchfactor = 0;
+		break;
+	}
+}
+
+// [RH] Q. What device are we pretending to be by responding to these NRPNs?
+void Renderer::DataEntryCoarseNRPN(int chan, int nrpn, int val)
+{
+	switch (nrpn & 0x3F80)
+	{
+	case 0x0080:
+		if (nrpn == NRPN_BRIGHTNESS)
+		{
+			channel[chan].brightness = val;
+		}
+		else if (nrpn == NRPN_HARMONICCONTENT)
+		{
+			channel[chan].harmoniccontent = val;
+		}
+		break;
+
+	case NRPN_DRUMVOLUME:
+		drumvolume[chan][nrpn & 0x007F] = val;
+		break;
+
+	case NRPN_DRUMPANPOT:
+		if (val == 0)
+		{
+			val = 127 * rand() / RAND_MAX;
+		}
+		drumpanpot[chan][nrpn & 0x007F] = val;
+		break;
+
+	case NRPN_DRUMREVERBERATION:
+		drumreverberation[chan][nrpn & 0x007F] = val;
+		break;
+
+	case NRPN_DRUMCHORUSDEPTH:
+		drumchorusdepth[chan][nrpn & 0x007F] = val;
+		break;
+	}
+}
+
+void Renderer::DataEntryFineNRPN(int chan, int nrpn, int val)
+{
+	// We don't care about fine data entry for any NRPN at this time.
+}
+
+void Renderer::HandleLongMessage(const BYTE *data, int len)
+{
+	// SysEx handling goes here.
+}
+
+#if 0
+MidiSong *Timidity_LoadSong(char *midifile)
+{
+	MidiSong *song;
+	int32 events;
+	SDL_RWops *rw;
+
+	/* Allocate memory for the song */
+	song = (MidiSong *)safe_malloc(sizeof(*song));
+	memset(song, 0, sizeof(*song));
+
+	/* Open the file */
+	strcpy(midi_name, midifile);
+
+	rw = SDL_RWFromFile(midifile, "rb");
+	if ( rw != NULL ) {
+		song->events=read_midi_file(rw, &events, &song->samples);
+		SDL_RWclose(rw);
+	}
+
+	/* Make sure everything is okay */
+	if (!song->events) {
+		free(song);
+		song = NULL;
+	}
+	return(song);
+}
+
+MidiSong *Timidity_LoadSong_RW(SDL_RWops *rw)
+{
+	MidiSong *song;
+	int32 events;
+
+	/* Allocate memory for the song */
+	song = (MidiSong *)safe_malloc(sizeof(*song));
+	memset(song, 0, sizeof(*song));
+
+	strcpy(midi_name, "SDLrwops source");
+
+	song->events=read_midi_file(rw, &events, &song->samples);
+
+	/* Make sure everything is okay */
+	if (!song->events) {
+		free(song);
+		song = NULL;
+	}
+	return(song);
+}
+
+void Timidity_Start(MidiSong *song)
+{
+	load_missing_instruments();
+	master_volume = 1;
+	sample_count = song->samples;
+	event_list = song->events;
+	lost_notes=cut_notes=0;
+
+	skip_to(0);
+	midi_playing = 1;
+}
+
+int Timidity_Active(void)
+{
+	return(midi_playing);
+}
+
+void Timidity_Stop(void)
+{
+	midi_playing = 0;
+}
+
+void Timidity_FreeSong(MidiSong *song)
+{
+	if (free_instruments_afterwards)
+		free_instruments();
+
+	free(song->events);
+	free(song);
+}
+
+void Timidity_Close(void)
+{
+	if (resample_buffer) {
+		free(resample_buffer);
+		resample_buffer=NULL;
+	}
+	if (common_buffer) {
+		free(common_buffer);
+		common_buffer=NULL;
+	}
+	free_instruments();
+	free_pathlist();
+}
+#endif
+
+void Renderer::Reset()
+{
+	int i;
+
+	lost_notes = cut_notes = 0;
+	GM_System_On = GS_System_On = XG_System_On = 0;
+	XG_System_reverb_type = XG_System_chorus_type = XG_System_variation_type = 0;
+	memset(&drumvolume, -1, sizeof(drumvolume));
+	memset(&drumchorusdepth, -1, sizeof(drumchorusdepth));
+	memset(&drumreverberation, -1, sizeof(drumreverberation));
+	memset(&drumpanpot, NO_PANNING, sizeof(drumpanpot));
+	for (i = 0; i < MAXCHAN; ++i)
+	{
+		channel[i].kit = ISDRUMCHANNEL(i) ? 127 : 0;
+		channel[i].brightness = 64;
+		channel[i].harmoniccontent = 64;
+		channel[i].variationbank = 0;
+		channel[i].chorusdepth = 0;
+		channel[i].reverberation = 0;
+		channel[i].transpose = 0;
+	}
+	reset_midi();
+}
+
+}
diff --git a/src/timidity/resample.cpp b/src/timidity/resample.cpp
new file mode 100644
index 000000000..9c9b72d6a
--- /dev/null
+++ b/src/timidity/resample.cpp
@@ -0,0 +1,608 @@
+/*
+
+	TiMidity -- Experimental MIDI to WAVE converter
+	Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
+
+	This library is free software; you can redistribute it and/or
+	modify it under the terms of the GNU Lesser General Public
+	License as published by the Free Software Foundation; either
+	version 2.1 of the License, or (at your option) any later version.
+
+	This library 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
+	Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public
+	License along with this library; if not, write to the Free Software
+	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+	resample.c
+
+*/
+
+#include <math.h>
+#include <stdio.h>
+#include <malloc.h>
+
+#include "timidity.h"
+
+namespace Timidity
+{
+
+#define RESAMPLATION {\
+	int o = ofs >> FRACTION_BITS, m = ofs & FRACTION_MASK; \
+	*dest++ = src[o] + (src[o + 1] - src[o]) * m / (1 << FRACTION_BITS);\
+  }
+
+#define FINALINTERP if (ofs == le) *dest++ = src[ofs >> FRACTION_BITS];
+/* So it isn't interpolation. At least it's final. */
+
+/*************** resampling with fixed increment *****************/
+
+static sample_t *rs_plain(sample_t *resample_buffer, Voice *v, int *countptr)
+{
+	/* Play sample until end, then free the voice. */
+
+	const sample_t 
+		*src = v->sample->data;
+	sample_t
+		*dest = resample_buffer;
+	int
+		ofs = v->sample_offset,
+		incr = v->sample_increment,
+		le = v->sample->data_length,
+		count = *countptr;
+
+	int i;
+
+	if (incr < 0) incr = -incr; /* In case we're coming out of a bidir loop */
+
+	/* Precalc how many times we should go through the loop.
+	NOTE: Assumes that incr > 0 and that ofs <= le */
+	i = (le - ofs) / incr + 1;
+
+	if (i > count)
+	{
+		i = count;
+		count = 0;
+	} 
+	else
+	{
+		count -= i;
+	}
+
+	while (i--) 
+	{
+		RESAMPLATION;
+		ofs += incr;
+	}
+
+	if (ofs >= le) 
+	{
+		FINALINTERP;
+		v->status = VOICE_FREE;
+		*countptr -= count + 1;
+	}
+
+	v->sample_offset = ofs; /* Update offset */
+	return resample_buffer;
+}
+
+static sample_t *rs_loop(sample_t *resample_buffer, Voice *vp, int count)
+{
+	/* Play sample until end-of-loop, skip back and continue. */
+
+	int
+		ofs = vp->sample_offset, 
+		incr = vp->sample_increment,
+		le = vp->sample->loop_end, 
+		ll = le - vp->sample->loop_start;
+	sample_t
+		*dest = resample_buffer;
+	const sample_t
+		*src = vp->sample->data;
+
+	int i;
+
+	while (count) 
+	{
+		if (ofs >= le)
+			/* NOTE: Assumes that ll > incr and that incr > 0. */
+			ofs -= ll;
+		/* Precalc how many times we should go through the loop */
+		i = (le - ofs) / incr + 1;
+		if (i > count) 
+		{
+			i = count;
+			count = 0;
+		} 
+		else
+		{
+			count -= i;
+		}
+		while (i--) 
+		{
+			RESAMPLATION;
+			ofs += incr;
+		}
+	}
+
+	vp->sample_offset=ofs; /* Update offset */
+	return resample_buffer;
+}
+
+static sample_t *rs_bidir(sample_t *resample_buffer, Voice *vp, int count)
+{
+	int
+		ofs = vp->sample_offset,
+		incr = vp->sample_increment,
+		le = vp->sample->loop_end,
+		ls = vp->sample->loop_start;
+	sample_t
+		*dest = resample_buffer;
+	const sample_t 
+		*src = vp->sample->data;
+
+	int
+		le2 = le << 1, 
+		ls2 = ls << 1,
+		i;
+	/* Play normally until inside the loop region */
+
+	if (ofs <= ls) 
+	{
+		/* NOTE: Assumes that incr > 0, which is NOT always the case
+		when doing bidirectional looping.  I have yet to see a case
+		where both ofs <= ls AND incr < 0, however. */
+		i = (ls - ofs) / incr + 1;
+		if (i > count) 
+		{
+			i = count;
+			count = 0;
+		} 
+		else
+		{
+			count -= i;
+		}
+		while (i--) 
+		{
+			RESAMPLATION;
+			ofs += incr;
+		}
+	}
+
+	/* Then do the bidirectional looping */
+
+	while(count) 
+	{
+		/* Precalc how many times we should go through the loop */
+		i = ((incr > 0 ? le : ls) - ofs) / incr + 1;
+		if (i > count) 
+		{
+			i = count;
+			count = 0;
+		} 
+		else
+		{
+			count -= i;
+		}
+		while (i--) 
+		{
+			RESAMPLATION;
+			ofs += incr;
+		}
+		if (ofs >= le) 
+		{
+			/* fold the overshoot back in */
+			ofs = le2 - ofs;
+			incr *= -1;
+		} 
+		else if (ofs <= ls) 
+		{
+			ofs = ls2 - ofs;
+			incr *= -1;
+		}
+	}
+
+	vp->sample_increment = incr;
+	vp->sample_offset = ofs; /* Update offset */
+	return resample_buffer;
+}
+
+/*********************** vibrato versions ***************************/
+
+/* We only need to compute one half of the vibrato sine cycle */
+static int vib_phase_to_inc_ptr(int phase)
+{
+	if (phase < VIBRATO_SAMPLE_INCREMENTS/2)
+		return VIBRATO_SAMPLE_INCREMENTS/2-1-phase;
+	else if (phase >= 3*VIBRATO_SAMPLE_INCREMENTS/2)
+		return 5*VIBRATO_SAMPLE_INCREMENTS/2-1-phase;
+	else
+		return phase-VIBRATO_SAMPLE_INCREMENTS/2;
+}
+
+static int update_vibrato(float output_rate, Voice *vp, int sign)
+{
+	int depth;
+	int phase;
+	double a, pb;
+
+	if (vp->vibrato_phase++ >= 2*VIBRATO_SAMPLE_INCREMENTS-1)
+		vp->vibrato_phase=0;
+	phase = vib_phase_to_inc_ptr(vp->vibrato_phase);
+
+	if (vp->vibrato_sample_increment[phase])
+	{
+		if (sign)
+			return -vp->vibrato_sample_increment[phase];
+		else
+			return vp->vibrato_sample_increment[phase];
+	}
+
+	/* Need to compute this sample increment. */
+	depth = vp->sample->vibrato_depth << 7;
+
+	if (vp->vibrato_sweep)
+	{
+		/* Need to update sweep */
+		vp->vibrato_sweep_position += vp->vibrato_sweep;
+		if (vp->vibrato_sweep_position >= (1<<SWEEP_SHIFT))
+			vp->vibrato_sweep=0;
+		else
+		{
+			/* Adjust depth */
+			depth *= vp->vibrato_sweep_position;
+			depth >>= SWEEP_SHIFT;
+		}
+	}
+
+	a = FSCALE(((double)(vp->sample->sample_rate) * vp->frequency) /
+		((double)(vp->sample->root_freq) * output_rate),
+		FRACTION_BITS);
+
+	pb = (sine(vp->vibrato_phase * (1.0/(2*VIBRATO_SAMPLE_INCREMENTS)))
+		* (double)(depth) * VIBRATO_AMPLITUDE_TUNING);
+
+	a *= pow(2.0, pb / (8191 * 12.f));
+
+	/* If the sweep's over, we can store the newly computed sample_increment */
+	if (!vp->vibrato_sweep)
+		vp->vibrato_sample_increment[phase] = (int) a;
+
+	if (sign)
+		a = -a; /* need to preserve the loop direction */
+
+	return (int) a;
+}
+
+static sample_t *rs_vib_plain(sample_t *resample_buffer, float rate, Voice *vp, int *countptr)
+{
+	/* Play sample until end, then free the voice. */
+
+	sample_t
+		*dest = resample_buffer;
+	const sample_t 
+		*src = vp->sample->data;
+	int
+		le = vp->sample->data_length,
+		ofs = vp->sample_offset, 
+		incr = vp->sample_increment, 
+		count = *countptr;
+	int 
+		cc = vp->vibrato_control_counter;
+
+	/* This has never been tested */
+
+	if (incr < 0) incr = -incr; /* In case we're coming out of a bidir loop */
+
+	while (count--)
+	{
+		if (!cc--)
+		{
+			cc = vp->vibrato_control_ratio;
+			incr = update_vibrato(rate, vp, 0);
+		}
+		RESAMPLATION;
+		ofs += incr;
+		if (ofs >= le)
+		{
+			FINALINTERP;
+			vp->status = VOICE_FREE;
+			*countptr -= count+1;
+			break;
+		}
+	}
+
+	vp->vibrato_control_counter = cc;
+	vp->sample_increment = incr;
+	vp->sample_offset = ofs; /* Update offset */
+	return resample_buffer;
+}
+
+static sample_t *rs_vib_loop(sample_t *resample_buffer, float rate, Voice *vp, int count)
+{
+	/* Play sample until end-of-loop, skip back and continue. */
+
+	int
+		ofs = vp->sample_offset, 
+		incr = vp->sample_increment, 
+		le = vp->sample->loop_end,
+		ll = le - vp->sample->loop_start;
+	sample_t
+		*dest = resample_buffer;
+	const sample_t 
+		*src = vp->sample->data;
+	int 
+		cc = vp->vibrato_control_counter;
+
+	int i;
+	int
+		vibflag=0;
+
+	while (count) 
+	{
+		/* Hopefully the loop is longer than an increment */
+		if (ofs >= le)
+			ofs -= ll;
+		/* Precalc how many times to go through the loop, taking
+		the vibrato control ratio into account this time. */
+		i = (le - ofs) / incr + 1;
+		if (i > count) i = count;
+		if (i > cc)
+		{
+			i = cc;
+			vibflag = 1;
+		} 
+		else
+		{
+			cc -= i;
+		}
+		count -= i;
+		while (i--) 
+		{
+			RESAMPLATION;
+			ofs += incr;
+		}
+		if (vibflag) 
+		{
+			cc = vp->vibrato_control_ratio;
+			incr = update_vibrato(rate, vp, 0);
+			vibflag = 0;
+		}
+	}
+
+	vp->vibrato_control_counter = cc;
+	vp->sample_increment = incr;
+	vp->sample_offset = ofs; /* Update offset */
+	return resample_buffer;
+}
+
+static sample_t *rs_vib_bidir(sample_t *resample_buffer, float rate, Voice *vp, int count)
+{
+	int
+		ofs = vp->sample_offset, 
+		incr = vp->sample_increment,
+		le = vp->sample->loop_end, 
+		ls = vp->sample->loop_start;
+	sample_t
+		*dest = resample_buffer;
+	const sample_t 
+		*src = vp->sample->data;
+	int 
+		cc = vp->vibrato_control_counter;
+
+	int
+		le2 = le << 1,
+		ls2 = ls << 1,
+		i;
+	int
+		vibflag = 0;
+
+	/* Play normally until inside the loop region */
+	while (count && (ofs <= ls)) 
+	{
+		i = (ls - ofs) / incr + 1;
+		if (i > count)
+		{
+			i = count;
+		}
+		if (i > cc) 
+		{
+			i = cc;
+			vibflag = 1;
+		} 
+		else
+		{
+			cc -= i;
+		}
+		count -= i;
+		while (i--) 
+		{
+			RESAMPLATION;
+			ofs += incr;
+		}
+		if (vibflag) 
+		{
+			cc = vp->vibrato_control_ratio;
+			incr = update_vibrato(rate, vp, 0);
+			vibflag = 0;
+		}
+	}
+
+	/* Then do the bidirectional looping */
+
+	while (count) 
+	{
+		/* Precalc how many times we should go through the loop */
+		i = ((incr > 0 ? le : ls) - ofs) / incr + 1;
+		if(i > count)
+		{
+			i = count;
+		}
+		if(i > cc) 
+		{
+			i = cc;
+			vibflag = 1;
+		} 
+		else
+		{
+			cc -= i;
+		}
+		count -= i;
+		while (i--) 
+		{
+			RESAMPLATION;
+			ofs += incr;
+		}
+		if (vibflag) 
+		{
+			cc = vp->vibrato_control_ratio;
+			incr = update_vibrato(rate, vp, (incr < 0));
+			vibflag = 0;
+		}
+		if (ofs >= le) 
+		{
+			/* fold the overshoot back in */
+			ofs = le2 - ofs;
+			incr *= -1;
+		} 
+		else if (ofs <= ls) 
+		{
+			ofs = ls2 - ofs;
+			incr *= -1;
+		}
+	}
+
+	vp->vibrato_control_counter = cc;
+	vp->sample_increment = incr;
+	vp->sample_offset = ofs; /* Update offset */
+	return resample_buffer;
+}
+
+sample_t *resample_voice(Renderer *song, Voice *vp, int *countptr)
+{
+	int ofs;
+	BYTE modes;
+
+	if (vp->sample->sample_rate == 0)
+	{
+		/* Pre-resampled data -- just update the offset and check if
+		we're out of data. */
+		ofs = vp->sample_offset >> FRACTION_BITS; /* Kind of silly to use FRACTION_BITS here... */
+		if (*countptr >= (vp->sample->data_length >> FRACTION_BITS) - ofs)
+		{
+			/* Note finished. Free the voice. */
+			vp->status = VOICE_FREE;
+
+			/* Let the caller know how much data we had left */
+			*countptr = (vp->sample->data_length >> FRACTION_BITS) - ofs;
+		}
+		else
+		{
+			vp->sample_offset += *countptr << FRACTION_BITS;
+		}
+		return vp->sample->data + ofs;
+	}
+
+	/* Need to resample. Use the proper function. */
+	modes = vp->sample->modes;
+
+	if (vp->vibrato_control_ratio)
+	{
+		if ((modes & MODES_LOOPING) &&
+			((modes & MODES_ENVELOPE) ||
+			 (vp->status == VOICE_ON || vp->status == VOICE_SUSTAINED)))
+		{
+			if (modes & MODES_PINGPONG)
+				return rs_vib_bidir(song->resample_buffer, song->rate, vp, *countptr);
+			else
+				return rs_vib_loop(song->resample_buffer, song->rate, vp, *countptr);
+		}
+		else
+		{
+			return rs_vib_plain(song->resample_buffer, song->rate, vp, countptr);
+		}
+	}
+	else
+	{
+		if ((modes & MODES_LOOPING) &&
+			((modes & MODES_ENVELOPE) ||
+			(vp->status == VOICE_ON || vp->status == VOICE_SUSTAINED)))
+		{
+			if (modes & MODES_PINGPONG)
+				return rs_bidir(song->resample_buffer, vp, *countptr);
+			else
+				return rs_loop(song->resample_buffer, vp, *countptr);
+		}
+		else
+		{
+			return rs_plain(song->resample_buffer, vp, countptr);
+		}
+	}
+}
+
+void pre_resample(Renderer *song, Sample *sp)
+{
+	double a, xdiff;
+	int incr, ofs, newlen, count;
+	sample_t *newdata, *dest, *src = sp->data;
+	sample_t v1, v2, v3, v4, *vptr;
+	static const char note_name[12][3] =
+	{
+		"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
+	};
+return;
+	song->ctl->cmsg(CMSG_INFO, VERB_NOISY, " * pre-resampling for note %d (%s%d)",
+		sp->note_to_use,
+		note_name[sp->note_to_use % 12], (sp->note_to_use & 0x7F) / 12);
+
+	a = (sp->sample_rate * note_to_freq(sp->note_to_use)) / (sp->root_freq * song->rate);
+	if (a <= 0)
+		return;
+	newlen = (int)(sp->data_length / a);
+	if (newlen < 0 || (newlen >> FRACTION_BITS) > MAX_SAMPLE_SIZE)
+		return;
+
+	dest = newdata = (sample_t *)safe_malloc(newlen >> (FRACTION_BITS - 2));
+
+	count = (newlen >> FRACTION_BITS) - 1;
+	ofs = incr = (sp->data_length - (1 << FRACTION_BITS)) / count;
+
+	if (--count)
+		*dest++ = src[0];
+
+	/* Since we're pre-processing and this doesn't have to be done in
+	real-time, we go ahead and do the full sliding cubic interpolation. */
+	while (--count)
+	{
+		vptr = src + (ofs >> FRACTION_BITS);
+		v1 = (vptr == src) ? *vptr : *(vptr - 1);
+		v2 = *vptr;
+		v3 = *(vptr + 1);
+		v4 = *(vptr + 2);
+		xdiff = FSCALENEG(ofs & FRACTION_MASK, FRACTION_BITS);
+		*dest++ = sample_t(v2 + (xdiff / 6.0) * (-2 * v1 - 3 * v2 + 6 * v3 - v4 +
+			xdiff * (3 * (v1 - 2 * v2 + v3) + xdiff * (-v1 + 3 * (v2 - v3) + v4))));
+		ofs += incr;
+	}
+
+	if (ofs & FRACTION_MASK)
+	{
+		RESAMPLATION
+	}
+	else
+	{
+		*dest++ = src[ofs >> FRACTION_BITS];
+	}
+
+	sp->data_length = newlen;
+	sp->loop_start = int(sp->loop_start / a);
+	sp->loop_end = int(sp->loop_end / a);
+	free(sp->data);
+	sp->data = newdata;
+	sp->sample_rate = 0;
+}
+
+}
diff --git a/src/timidity/tables.cpp b/src/timidity/tables.cpp
new file mode 100644
index 000000000..2eee67478
--- /dev/null
+++ b/src/timidity/tables.cpp
@@ -0,0 +1,224 @@
+/*
+
+	TiMidity -- Experimental MIDI to WAVE converter
+	Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
+
+	This library is free software; you can redistribute it and/or
+	modify it under the terms of the GNU Lesser General Public
+	License as published by the Free Software Foundation; either
+	version 2.1 of the License, or (at your option) any later version.
+
+	This library 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
+	Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public
+	License along with this library; if not, write to the Free Software
+	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+
+#include "timidity.h"
+
+namespace Timidity
+{
+
+/*	$Id: tables.c 1404 2004-08-21 12:27:02Z slouken $   Greg Lee */
+const BYTE xmap[XMAPMAX][5] =
+{
+	{ SFXBANK, 0, 0, 120, 0 },
+	{ SFXBANK, 0, 1, 120, 1 },
+	{ SFXBANK, 0, 2, 120, 2 },
+	{ SFXBANK, 0, 3, 120, 3 },
+	{ SFXBANK, 0, 4, 120, 4 },
+	{ SFXBANK, 0, 5, 120, 5 },
+	{ SFXBANK, 0, 16, 120, 16 },
+	{ SFXBANK, 0, 32, 120, 32 },
+	{ SFXBANK, 0, 33, 120, 33 },
+	{ SFXBANK, 0, 34, 120, 34 },
+	{ SFXBANK, 0, 35, 120, 35 },
+	{ SFXBANK, 0, 36, 120, 36 },
+	{ SFXBANK, 0, 48, 120, 48 },
+	{ SFXBANK, 0, 49, 120, 49 },
+	{ SFXBANK, 0, 50, 120, 50 },
+	{ SFXBANK, 0, 51, 120, 51 },
+	{ SFXBANK, 0, 52, 120, 52 },
+	{ SFXBANK, 0, 54, 120, 54 },
+	{ SFXBANK, 0, 55, 120, 55 },
+	{ SFXBANK, 0, 64, 120, 64 },
+	{ SFXBANK, 0, 65, 120, 65 },
+	{ SFXBANK, 0, 66, 120, 66 },
+	{ SFXBANK, 0, 67, 120, 67 },
+	{ SFXBANK, 0, 68, 120, 68 },
+	{ SFXBANK, 0, 69, 120, 69 },
+	{ SFXBANK, 0, 70, 120, 70 },
+	{ SFXBANK, 0, 80, 120, 80 },
+	{ SFXBANK, 0, 81, 120, 81 },
+	{ SFXBANK, 0, 82, 120, 82 },
+	{ SFXBANK, 0, 83, 120, 83 },
+	{ SFXBANK, 0, 84, 120, 84 },
+	{ SFXBANK, 0, 85, 120, 85 },
+	{ SFXBANK, 0, 86, 120, 86 },
+	{ SFXBANK, 0, 87, 120, 87 },
+	{ SFXBANK, 0, 88, 120, 88 },
+	{ SFXBANK, 0, 89, 120, 89 },
+	{ SFXBANK, 0, 90, 120, 90 },
+	{ SFXBANK, 0, 96, 120, 96 },
+	{ SFXBANK, 0, 97, 120, 97 },
+	{ SFXBANK, 0, 98, 120, 98 },
+	{ SFXBANK, 0, 99, 120, 99 },
+	{ SFXBANK, 0, 100, 120, 100 },
+	{ SFXBANK, 0, 101, 120, 101 },
+	{ SFXBANK, 0, 112, 120, 112 },
+	{ SFXBANK, 0, 113, 120, 113 },
+	{ SFXBANK, 0, 114, 120, 114 },
+	{ SFXBANK, 0, 115, 120, 115 },
+	{ SFXDRUM1, 0, 36, 121, 36 },
+	{ SFXDRUM1, 0, 37, 121, 37 },
+	{ SFXDRUM1, 0, 38, 121, 38 },
+	{ SFXDRUM1, 0, 39, 121, 39 },
+	{ SFXDRUM1, 0, 40, 121, 40 },
+	{ SFXDRUM1, 0, 41, 121, 41 },
+	{ SFXDRUM1, 0, 52, 121, 52 },
+	{ SFXDRUM1, 0, 68, 121, 68 },
+	{ SFXDRUM1, 0, 69, 121, 69 },
+	{ SFXDRUM1, 0, 70, 121, 70 },
+	{ SFXDRUM1, 0, 71, 121, 71 },
+	{ SFXDRUM1, 0, 72, 121, 72 },
+	{ SFXDRUM1, 0, 84, 121, 84 },
+	{ SFXDRUM1, 0, 85, 121, 85 },
+	{ SFXDRUM1, 0, 86, 121, 86 },
+	{ SFXDRUM1, 0, 87, 121, 87 },
+	{ SFXDRUM1, 0, 88, 121, 88 },
+	{ SFXDRUM1, 0, 90, 121, 90 },
+	{ SFXDRUM1, 0, 91, 121, 91 },
+	{ SFXDRUM1, 1, 36, 122, 36 },
+	{ SFXDRUM1, 1, 37, 122, 37 },
+	{ SFXDRUM1, 1, 38, 122, 38 },
+	{ SFXDRUM1, 1, 39, 122, 39 },
+	{ SFXDRUM1, 1, 40, 122, 40 },
+	{ SFXDRUM1, 1, 41, 122, 41 },
+	{ SFXDRUM1, 1, 42, 122, 42 },
+	{ SFXDRUM1, 1, 52, 122, 52 },
+	{ SFXDRUM1, 1, 53, 122, 53 },
+	{ SFXDRUM1, 1, 54, 122, 54 },
+	{ SFXDRUM1, 1, 55, 122, 55 },
+	{ SFXDRUM1, 1, 56, 122, 56 },
+	{ SFXDRUM1, 1, 57, 122, 57 },
+	{ SFXDRUM1, 1, 58, 122, 58 },
+	{ SFXDRUM1, 1, 59, 122, 59 },
+	{ SFXDRUM1, 1, 60, 122, 60 },
+	{ SFXDRUM1, 1, 61, 122, 61 },
+	{ SFXDRUM1, 1, 62, 122, 62 },
+	{ SFXDRUM1, 1, 68, 122, 68 },
+	{ SFXDRUM1, 1, 69, 122, 69 },
+	{ SFXDRUM1, 1, 70, 122, 70 },
+	{ SFXDRUM1, 1, 71, 122, 71 },
+	{ SFXDRUM1, 1, 72, 122, 72 },
+	{ SFXDRUM1, 1, 73, 122, 73 },
+	{ SFXDRUM1, 1, 84, 122, 84 },
+	{ SFXDRUM1, 1, 85, 122, 85 },
+	{ SFXDRUM1, 1, 86, 122, 86 },
+	{ SFXDRUM1, 1, 87, 122, 87 },
+	{ XGDRUM, 0, 25, 40, 38 },
+	{ XGDRUM, 0, 26, 40, 40 },
+	{ XGDRUM, 0, 27, 40, 39 },
+	{ XGDRUM, 0, 28, 40, 30 },
+	{ XGDRUM, 0, 29, 0, 25 },
+	{ XGDRUM, 0, 30, 0, 85 },
+	{ XGDRUM, 0, 31, 0, 38 },
+	{ XGDRUM, 0, 32, 0, 37 },
+	{ XGDRUM, 0, 33, 0, 36 },
+	{ XGDRUM, 0, 34, 0, 38 },
+	{ XGDRUM, 0, 62, 0, 101 },
+	{ XGDRUM, 0, 63, 0, 102 },
+	{ XGDRUM, 0, 64, 0, 103 },
+	{ XGDRUM, 8, 25, 40, 38 },
+	{ XGDRUM, 8, 26, 40, 40 },
+	{ XGDRUM, 8, 27, 40, 39 },
+	{ XGDRUM, 8, 28, 40, 40 },
+	{ XGDRUM, 8, 29, 8, 25 },
+	{ XGDRUM, 8, 30, 8, 85 },
+	{ XGDRUM, 8, 31, 8, 38 },
+	{ XGDRUM, 8, 32, 8, 37 },
+	{ XGDRUM, 8, 33, 8, 36 },
+	{ XGDRUM, 8, 34, 8, 38 },
+	{ XGDRUM, 8, 62, 8, 101 },
+	{ XGDRUM, 8, 63, 8, 102 },
+	{ XGDRUM, 8, 64, 8, 103 },
+	{ XGDRUM, 16, 25, 40, 38 },
+	{ XGDRUM, 16, 26, 40, 40 },
+	{ XGDRUM, 16, 27, 40, 39 },
+	{ XGDRUM, 16, 28, 40, 40 },
+	{ XGDRUM, 16, 29, 16, 25 },
+	{ XGDRUM, 16, 30, 16, 85 },
+	{ XGDRUM, 16, 31, 16, 38 },
+	{ XGDRUM, 16, 32, 16, 37 },
+	{ XGDRUM, 16, 33, 16, 36 },
+	{ XGDRUM, 16, 34, 16, 38 },
+	{ XGDRUM, 16, 62, 16, 101 },
+	{ XGDRUM, 16, 63, 16, 102 },
+	{ XGDRUM, 16, 64, 16, 103 },
+	{ XGDRUM, 24, 25, 40, 38 },
+	{ XGDRUM, 24, 26, 40, 40 },
+	{ XGDRUM, 24, 27, 40, 39 },
+	{ XGDRUM, 24, 28, 24, 100 },
+	{ XGDRUM, 24, 29, 24, 25 },
+	{ XGDRUM, 24, 30, 24, 15 },
+	{ XGDRUM, 24, 31, 24, 38 },
+	{ XGDRUM, 24, 32, 24, 37 },
+	{ XGDRUM, 24, 33, 24, 36 },
+	{ XGDRUM, 24, 34, 24, 38 },
+	{ XGDRUM, 24, 62, 24, 101 },
+	{ XGDRUM, 24, 63, 24, 102 },
+	{ XGDRUM, 24, 64, 24, 103 },
+	{ XGDRUM, 24, 78, 0, 17 },
+	{ XGDRUM, 24, 79, 0, 18 },
+	{ XGDRUM, 25, 25, 40, 38 },
+	{ XGDRUM, 25, 26, 40, 40 },
+	{ XGDRUM, 25, 27, 40, 39 },
+	{ XGDRUM, 25, 28, 25, 100 },
+	{ XGDRUM, 25, 29, 25, 25 },
+	{ XGDRUM, 25, 30, 25, 15 },
+	{ XGDRUM, 25, 31, 25, 38 },
+	{ XGDRUM, 25, 32, 25, 37 },
+	{ XGDRUM, 25, 33, 25, 36 },
+	{ XGDRUM, 25, 34, 25, 38 },
+	{ XGDRUM, 25, 78, 0, 17 },
+	{ XGDRUM, 25, 79, 0, 18 },
+	{ XGDRUM, 32, 25, 40, 38 },
+	{ XGDRUM, 32, 26, 40, 40 },
+	{ XGDRUM, 32, 27, 40, 39 },
+	{ XGDRUM, 32, 28, 40, 40 },
+	{ XGDRUM, 32, 29, 32, 25 },
+	{ XGDRUM, 32, 30, 32, 85 },
+	{ XGDRUM, 32, 31, 32, 38 },
+	{ XGDRUM, 32, 32, 32, 37 },
+	{ XGDRUM, 32, 33, 32, 36 },
+	{ XGDRUM, 32, 34, 32, 38 },
+	{ XGDRUM, 32, 62, 32, 101 },
+	{ XGDRUM, 32, 63, 32, 102 },
+	{ XGDRUM, 32, 64, 32, 103 },
+	{ XGDRUM, 40, 25, 40, 38 },
+	{ XGDRUM, 40, 26, 40, 40 },
+	{ XGDRUM, 40, 27, 40, 39 },
+	{ XGDRUM, 40, 28, 40, 40 },
+	{ XGDRUM, 40, 29, 40, 25 },
+	{ XGDRUM, 40, 30, 40, 85 },
+	{ XGDRUM, 40, 31, 40, 39 },
+	{ XGDRUM, 40, 32, 40, 37 },
+	{ XGDRUM, 40, 33, 40, 36 },
+	{ XGDRUM, 40, 34, 40, 38 },
+	{ XGDRUM, 40, 38, 40, 39 },
+	{ XGDRUM, 40, 39, 0, 39 },
+	{ XGDRUM, 40, 40, 40, 38 },
+	{ XGDRUM, 40, 42, 0, 42 },
+	{ XGDRUM, 40, 46, 0, 46 },
+	{ XGDRUM, 40, 62, 40, 101 },
+	{ XGDRUM, 40, 63, 40, 102 },
+	{ XGDRUM, 40, 64, 40, 103 },
+	{ XGDRUM, 40, 87, 40, 87 }
+};
+
+}
diff --git a/src/timidity/timidity.cpp b/src/timidity/timidity.cpp
new file mode 100644
index 000000000..4c744339c
--- /dev/null
+++ b/src/timidity/timidity.cpp
@@ -0,0 +1,513 @@
+/*
+
+	TiMidity -- Experimental MIDI to WAVE converter
+	Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
+
+	This library is free software; you can redistribute it and/or
+	modify it under the terms of the GNU Lesser General Public
+	License as published by the Free Software Foundation; either
+	version 2.1 of the License, or (at your option) any later version.
+
+	This library 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
+	Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public
+	License along with this library; if not, write to the Free Software
+	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+	timidity.c
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "timidity.h"
+#include "templates.h"
+#include "m_alloc.h"
+#include "cmdlib.h"
+#include "c_cvars.h"
+#include "i_system.h"
+
+CVAR(String, timidity_config, CONFIG_FILE, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
+
+namespace Timidity
+{
+
+ToneBank *tonebank[MAXBANK], *drumset[MAXBANK];
+
+static FString def_instr_name;
+
+
+#define MAXWORDS 10
+
+static int read_config_file(const char *name)
+{
+	FILE *fp;
+	char tmp[1024], *w[MAXWORDS], *cp;
+	ToneBank *bank = NULL;
+	int i, j, k, line = 0, words;
+	static int rcf_count = 0;
+
+	if (rcf_count > 50)
+	{
+		Printf("Timidity: Probable source loop in configuration files\n");
+		return (-1);
+	}
+
+	if (!(fp = open_file(name, 1, OF_VERBOSE)))
+		return -1;
+
+	while (fgets(tmp, sizeof(tmp), fp))
+	{
+		line++;
+		w[words = 0] = strtok(tmp, " \t\r\n\240");
+		if (!w[0]) continue;
+
+		/* Originally the TiMidity++ extensions were prefixed like this */
+		if (strcmp(w[0], "#extension") == 0)
+			words = -1;
+		else if (*w[0] == '#')
+			continue;
+
+		while (w[words] && *w[words] != '#' && (words < MAXWORDS))
+			w[++words] = strtok(0, " \t\r\n\240");
+
+		/*
+		 * TiMidity++ adds a number of extensions to the config file format.
+		 * Many of them are completely irrelevant to SDL_sound, but at least
+		 * we shouldn't choke on them.
+		 *
+		 * Unfortunately the documentation for these extensions is often quite
+		 * vague, gramatically strange or completely absent.
+		 */
+		if (
+			!strcmp(w[0], "comm")			/* "comm" program second        */
+			|| !strcmp(w[0], "HTTPproxy")	/* "HTTPproxy" hostname:port    */
+			|| !strcmp(w[0], "FTPproxy")	/* "FTPproxy" hostname:port     */
+			|| !strcmp(w[0], "mailaddr")	/* "mailaddr" your-mail-address */
+			|| !strcmp(w[0], "opt")			/* "opt" timidity-options       */
+			)
+		{
+			/*
+			* + "comm" sets some kind of comment -- the documentation is too
+			*   vague for me to understand at this time.
+			* + "HTTPproxy", "FTPproxy" and "mailaddr" are for reading data
+			*   over a network, rather than from the file system.
+			* + "opt" specifies default options for TiMidity++.
+			*
+			* These are all quite useless for our version of TiMidity, so
+			* they can safely remain no-ops.
+			*/
+		}
+		else if (!strcmp(w[0], "timeout"))	/* "timeout" program second */
+		{
+			/*
+			* Specifies a timeout value of the program. A number of seconds
+			* before TiMidity kills the note. This may be useful to implement
+			* later, but I don't see any urgent need for it.
+			*/
+			Printf("FIXME: Implement \"timeout\" in TiMidity config.\n");
+		}
+		else if (!strcmp(w[0], "copydrumset")	/* "copydrumset" drumset */
+			|| !strcmp(w[0], "copybank"))		/* "copybank" bank       */
+		{
+			/*
+			* Copies all the settings of the specified drumset or bank to
+			* the current drumset or bank. May be useful later, but not a
+			* high priority.
+			*/
+			Printf("FIXME: Implement \"%s\" in TiMidity config.\n", w[0]);
+		}
+		else if (!strcmp(w[0], "undef"))		/* "undef" progno */
+		{
+			/*
+			* Undefines the tone "progno" of the current tone bank (or
+			* drum set?). Not a high priority.
+			*/
+			Printf("FIXME: Implement \"undef\" in TiMidity config.\n");
+		}
+		else if (!strcmp(w[0], "altassign")) /* "altassign" prog1 prog2 ... */
+		{
+			/*
+			* Sets the alternate assign for drum set. Whatever that's
+			* supposed to mean.
+			*/
+			Printf("FIXME: Implement \"altassign\" in TiMidity config.\n");
+		}
+		else if (!strcmp(w[0], "soundfont") || !strcmp(w[0], "font"))
+		{
+			/*
+			* I can't find any documentation for these, but I guess they're
+			* an alternative way of loading/unloading instruments.
+			* 
+			* "soundfont" sf_file "remove"
+			* "soundfont" sf_file ["order=" order] ["cutoff=" cutoff]
+			*                     ["reso=" reso] ["amp=" amp]
+			* "font" "exclude" bank preset keynote
+			* "font" "order" order bank preset keynote
+			*/
+			Printf("FIXME: Implmement \"%s\" in TiMidity config.\n", w[0]);
+		}
+		else if (!strcmp(w[0], "progbase"))
+		{
+			/*
+			* The documentation for this makes absolutely no sense to me, but
+			* apparently it sets some sort of base offset for tone numbers.
+			* Why anyone would want to do this is beyond me.
+			*/
+			Printf("FIXME: Implement \"progbase\" in TiMidity config.\n");
+		}
+		else if (!strcmp(w[0], "map")) /* "map" name set1 elem1 set2 elem2 */
+		{
+			/*
+			* This extension is the one we will need to implement, as it is
+			* used by the "eawpats". Unfortunately I cannot find any
+			* documentation whatsoever for it, but it looks like it's used
+			* for remapping one instrument to another somehow.
+			*/
+			Printf("FIXME: Implement \"map\" in TiMidity config.\n");
+		}
+
+		/* Standard TiMidity config */
+
+		else if (!strcmp(w[0], "dir"))
+		{
+			if (words < 2)
+			{
+				Printf("%s: line %d: No directory given\n", name, line);
+				return -2;
+			}
+			for (i = 1; i < words; i++)
+				add_to_pathlist(w[i]);
+		}
+		else if (!strcmp(w[0], "source"))
+		{
+			if (words < 2)
+			{
+				Printf("%s: line %d: No file name given\n", name, line);
+				return -2;
+			}
+			for (i=1; i<words; i++)
+			{
+				rcf_count++;
+				read_config_file(w[i]);
+				rcf_count--;
+			}
+		}
+		else if (!strcmp(w[0], "default"))
+		{
+			if (words != 2)
+			{
+				Printf("%s: line %d: Must specify exactly one patch name\n", name, line);
+				return -2;
+			}
+			def_instr_name = w[1];
+		}
+		else if (!strcmp(w[0], "drumset"))
+		{
+			if (words < 2)
+			{
+				Printf("%s: line %d: No drum set number given\n", name, line);
+				return -2;
+			}
+			i = atoi(w[1]);
+			if (i < 0 || i > 127)
+			{
+				Printf("%s: line %d: Drum set must be between 0 and 127\n", name, line);
+				return -2;
+			}
+			if (drumset[i] == NULL)
+			{
+				drumset[i] = new ToneBank;
+			}
+			bank = drumset[i];
+		}
+		else if (!strcmp(w[0], "bank"))
+		{
+			if (words < 2)
+			{
+				Printf("%s: line %d: No bank number given\n", name, line);
+				return -2;
+			}
+			i = atoi(w[1]);
+			if (i < 0 || i > 127)
+			{
+				Printf("%s: line %d: Tone bank must be between 0 and 127\n", name, line);
+				return -2;
+			}
+			if (tonebank[i] == NULL)
+			{
+				tonebank[i] = new ToneBank;
+			}
+			bank = tonebank[i];
+		}
+		else
+		{
+			if ((words < 2) || (*w[0] < '0' || *w[0] > '9'))
+			{
+				Printf("%s: line %d: syntax error\n", name, line);
+				return -2;
+			}
+			i = atoi(w[0]);
+			if (i < 0 || i > 127)
+			{
+				Printf("%s: line %d: Program must be between 0 and 127\n", name, line);
+				return -2;
+			}
+			if (bank == NULL)
+			{
+				Printf("%s: line %d: Must specify tone bank or drum set before assignment\n", name, line);
+				return -2;
+			}
+			bank->tone[i].name = w[1];
+			bank->tone[i].note = bank->tone[i].amp = bank->tone[i].pan =
+				bank->tone[i].strip_loop = bank->tone[i].strip_envelope =
+				bank->tone[i].strip_tail = -1;
+
+			for (j = 2; j<words; j++)
+			{
+				if (!(cp=strchr(w[j], '=')))
+				{
+					Printf("%s: line %d: bad patch option %s\n", name, line, w[j]);
+					return -2;
+				}
+				*cp++ = 0;
+				if (!strcmp(w[j], "amp"))
+				{
+					k = atoi(cp);
+					if ((k < 0 || k > MAX_AMPLIFICATION) || (*cp < '0' || *cp > '9'))
+					{
+						Printf("%s: line %d: amplification must be between  0 and %d\n", name, line, MAX_AMPLIFICATION);
+						return -2;
+					}
+					bank->tone[i].amp = k;
+				}
+				else if (!strcmp(w[j], "note"))
+				{
+					k = atoi(cp);
+					if ((k < 0 || k > 127) || (*cp < '0' || *cp > '9'))
+					{
+						Printf("%s: line %d: note must be between 0 and 127\n", name, line);
+						return -2;
+					}
+					bank->tone[i].note = k;
+				}
+				else if (!strcmp(w[j], "pan"))
+				{
+					if (!strcmp(cp, "center"))
+						k = 64;
+					else if (!strcmp(cp, "left"))
+						k = 0;
+					else if (!strcmp(cp, "right"))
+						k = 127;
+					else
+						k = ((atoi(cp)+100) * 100) / 157;
+					if ((k < 0 || k > 127) ||
+						(k == 0 && *cp != '-' && (*cp < '0' || *cp > '9')))
+					{
+						Printf("%s: line %d: panning must be left, right, "
+							"center, or between -100 and 100\n", name, line);
+						return -2;
+					}
+					bank->tone[i].pan = k;
+				}
+				else if (!strcmp(w[j], "keep"))
+				{
+					if (!strcmp(cp, "env"))
+						bank->tone[i].strip_envelope = 0;
+					else if (!strcmp(cp, "loop"))
+						bank->tone[i].strip_loop = 0;
+					else
+					{
+						Printf("%s: line %d: keep must be env or loop\n", name, line);
+						return -2;
+					}
+				}
+				else if (!strcmp(w[j], "strip"))
+				{
+					if (!strcmp(cp, "env"))
+						bank->tone[i].strip_envelope = 1;
+					else if (!strcmp(cp, "loop"))
+						bank->tone[i].strip_loop = 1;
+					else if (!strcmp(cp, "tail"))
+						bank->tone[i].strip_tail = 1;
+					else
+					{
+						Printf("%s: line %d: strip must be env, loop, or tail\n", name, line);
+						return -2;
+					}
+				}
+				else
+				{
+					Printf("%s: line %d: bad patch option %s\n", name, line, w[j]);
+					return -2;
+				}
+			}
+		}
+	}
+	if (ferror(fp))
+	{
+		Printf("Can't read %s: %s\n", name, strerror(errno));
+		close_file(fp);
+		return -2;
+	}
+	close_file(fp);
+	return 0;
+}
+
+void FreeAll()
+{
+	free_instruments();
+	for (int i = 0; i < MAXBANK; ++i)
+	{
+		if (tonebank[i] != NULL)
+		{
+			delete tonebank[i];
+			tonebank[i] = NULL;
+		}
+		if (drumset[i] != NULL)
+		{
+			delete drumset[i];
+			drumset[i] = NULL;
+		}
+	}
+}
+
+int LoadConfig()
+{
+	static bool set_initial_path = false;
+
+	if (!set_initial_path)
+	{
+#ifdef _WIN32
+		add_to_pathlist("\\TIMIDITY");
+		add_to_pathlist(progdir);
+#else
+		add_to_pathlist("/usr/local/lib/timidity");
+		add_to_pathlist("/etc/timidity");
+		add_to_pathlist("/etc");
+#endif
+		set_initial_path = true;
+	}
+
+	/* Some functions get aggravated if not even the standard banks are available. */
+	if (tonebank[0] == NULL)
+	{
+		tonebank[0] = new ToneBank;
+		drumset[0] = new ToneBank;
+	}
+
+	return read_config_file(timidity_config);
+}
+
+Renderer::Renderer(float sample_rate)
+{
+	ctl = new ControlMode;
+	rate = sample_rate;
+	patches = NULL;
+	default_instrument = NULL;
+#ifdef FAST_DECAY
+	fast_decay = true;
+#else
+	fast_decay = false;
+#endif
+	resample_buffer_size = 0;
+	resample_buffer = NULL;
+
+	control_ratio = clamp(int(rate / CONTROLS_PER_SECOND), 1, MAX_CONTROL_RATIO);
+	if (def_instr_name.IsNotEmpty())
+		set_default_instrument(def_instr_name);
+
+	voices = DEFAULT_VOICES;
+	memset(voice, 0, sizeof(voice));
+	memset(drumvolume, 0, sizeof(drumvolume));
+	memset(drumpanpot, 0, sizeof(drumpanpot));
+	memset(drumreverberation, 0, sizeof(drumreverberation));
+	memset(drumchorusdepth, 0, sizeof(drumchorusdepth));
+	drumchannels = DEFAULT_DRUMCHANNELS;
+}
+
+Renderer::~Renderer()
+{
+	if (resample_buffer != NULL)
+	{
+		M_Free(resample_buffer);
+	}
+}
+
+void Renderer::ComputeOutput(float *buffer, int count)
+{
+	// count is in samples, not bytes.
+	if (count <= 0)
+	{
+		return;
+	}
+	Voice *v = &voice[0];
+
+	memset(buffer, 0, sizeof(float)*count*2);		// An integer 0 is also a float 0.
+	if (resample_buffer_size < count)
+	{
+		resample_buffer_size = count;
+		resample_buffer = (sample_t *)M_Realloc(resample_buffer, count * sizeof(float) * 2);
+	}
+	for (int i = 0; i < voices; i++, v++)
+	{
+		if (v->status != VOICE_FREE)
+		{
+			if (v->sample_offset == 0 && v->echo_delay_count)
+			{
+				if (v->echo_delay_count >= count)
+				{
+					v->echo_delay_count -= count;
+				}
+				else
+				{
+					mix_voice(this, buffer + v->echo_delay_count, v, count - v->echo_delay_count);
+					v->echo_delay_count = 0;
+				}
+			}
+			else
+			{
+				mix_voice(this, buffer, v, count);
+			}
+		}
+	}
+}
+
+void Renderer::MarkInstrument(int banknum, int percussion, int instr)
+{
+	ToneBank *bank;
+
+	if (banknum >= MAXBANK)
+	{
+		return;
+	}
+	if (percussion)
+	{
+		bank = drumset[banknum];
+	}
+	else
+	{
+		bank = tonebank[banknum];
+	}
+	if (bank == NULL)
+	{
+		return;
+	}
+	if (bank->tone[instr].layer == NULL)
+	{
+		bank->tone[instr].layer = MAGIC_LOAD_INSTRUMENT;
+	}
+}
+
+ControlMode::~ControlMode()
+{
+}
+
+void ControlMode::cmsg(int type, int verbosity_level, const char *fmt, ...)
+{
+}
+
+}
diff --git a/src/timidity/timidity.h b/src/timidity/timidity.h
new file mode 100644
index 000000000..ea8ed4cb2
--- /dev/null
+++ b/src/timidity/timidity.h
@@ -0,0 +1,604 @@
+/*
+	TiMidity -- Experimental MIDI to WAVE converter
+	Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
+
+	This library is free software; you can redistribute it and/or
+	modify it under the terms of the GNU Lesser General Public
+	License as published by the Free Software Foundation; either
+	version 2.1 of the License, or (at your option) any later version.
+
+	This library 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
+	Lesser General Public License for more details.
+
+	You should have received a copy of the GNU Lesser General Public
+	License along with this library; if not, write to the Free Software
+	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef TIMIDITY_H
+#define TIMIDITY_H
+
+#include "doomtype.h"
+#include "zstring.h"
+
+namespace Timidity
+{
+
+/*
+config.h
+*/
+
+/* Acoustic Grand Piano seems to be the usual default instrument. */
+#define DEFAULT_PROGRAM 0
+
+/* 9 here is MIDI channel 10, which is the standard percussion channel.
+   Some files (notably C:\WINDOWS\CANYON.MID) think that 16 is one too. 
+   On the other hand, some files know that 16 is not a drum channel and
+   try to play music on it. This is now a runtime option, so this isn't
+   a critical choice anymore. */
+#define DEFAULT_DRUMCHANNELS (1<<9)
+/*#define DEFAULT_DRUMCHANNELS ((1<<9) | (1<<15))*/
+
+/* Default sampling rate, default polyphony, and maximum polyphony.
+   All but the last can be overridden from the command line. */
+#define DEFAULT_RATE	32000
+#define DEFAULT_VOICES	32
+#define MAX_VOICES		256
+#define MAXCHAN			16
+#define MAXNOTE			128
+
+/* 1000 here will give a control ratio of 22:1 with 22 kHz output.
+   Higher CONTROLS_PER_SECOND values allow more accurate rendering
+   of envelopes and tremolo. The cost is CPU time. */
+#define CONTROLS_PER_SECOND 1000
+
+/* Make envelopes twice as fast. Saves ~20% CPU time (notes decay
+   faster) and sounds more like a GUS. There is now a command line
+   option to toggle this as well. */
+//#define FAST_DECAY
+
+/* How many bits to use for the fractional part of sample positions.
+   This affects tonal accuracy. The entire position counter must fit
+   in 32 bits, so with FRACTION_BITS equal to 12, the maximum size of
+   a sample is 1048576 samples (2 megabytes in memory). The GUS gets
+   by with just 9 bits and a little help from its friends...
+   "The GUS does not SUCK!!!" -- a happy user :) */
+#define FRACTION_BITS 12
+
+/* For some reason the sample volume is always set to maximum in all
+   patch files. Define this for a crude adjustment that may help
+   equalize instrument volumes. */
+#define ADJUST_SAMPLE_VOLUMES
+
+/* The number of samples to use for ramping out a dying note. Affects
+   click removal. */
+#define MAX_DIE_TIME 20
+
+/**************************************************************************/
+/* Anything below this shouldn't need to be changed unless you're porting
+   to a new machine with other than 32-bit, big-endian words. */
+/**************************************************************************/
+
+/* change FRACTION_BITS above, not these */
+#define INTEGER_BITS (32 - FRACTION_BITS)
+#define INTEGER_MASK (0xFFFFFFFF << FRACTION_BITS)
+#define FRACTION_MASK (~ INTEGER_MASK)
+#define MAX_SAMPLE_SIZE (1 << INTEGER_BITS)
+
+/* This is enforced by some computations that must fit in an int */
+#define MAX_CONTROL_RATIO 255
+
+#define MAX_AMPLIFICATION 800
+
+/* The TiMiditiy configuration file */
+#define CONFIG_FILE	"timidity.cfg"
+
+typedef float sample_t;
+typedef float final_volume_t;
+#define FINAL_VOLUME(v) (v)
+
+#define FSCALE(a,b) ((a) * (float)(1<<(b)))
+#define FSCALENEG(a,b) ((a) * (1.0L / (float)(1<<(b))))
+
+/* Vibrato and tremolo Choices of the Day */
+#define SWEEP_TUNING 38
+#define VIBRATO_AMPLITUDE_TUNING 1.0L
+#define VIBRATO_RATE_TUNING 38
+#define TREMOLO_AMPLITUDE_TUNING 1.0L
+#define TREMOLO_RATE_TUNING 38
+
+#define SWEEP_SHIFT 16
+#define RATE_SHIFT 5
+
+#define VIBRATO_SAMPLE_INCREMENTS 32
+
+#ifndef PI
+  #define PI 3.14159265358979323846
+#endif
+
+#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
+// [RH] MinGW's pow() function is terribly slow compared to VC8's
+// (I suppose because it's using an old version from MSVCRT.DLL).
+// On an Opteron running x86-64 Linux, this also ended up being about
+// 100 cycles faster than libm's pow(), which is why I'm using this
+// for GCC in general and not just for MinGW.
+
+extern __inline__ double pow_x87_inline(double x,double y)
+{
+	double result;
+
+	if (y == 0)
+	{
+		return 1;
+	}
+	if (x == 0)
+	{
+		if (y > 0)
+		{
+			return 0;
+		}
+		else
+		{
+			union { double fp; long long ip; } infinity;
+			infinity.ip = 0x7FF0000000000000ll;
+			return infinity.fp;
+		}
+	}
+	__asm__ (
+		"fyl2x\n\t"
+		"fld %%st(0)\n\t"
+		"frndint\n\t"
+		"fxch\n\t"
+		"fsub %%st(1),%%st(0)\n\t"
+		"f2xm1\n\t"
+		"fld1\n\t"
+		"faddp\n\t"
+		"fxch\n\t"
+		"fld1\n\t"
+		"fscale\n\t"
+		"fstp %%st(1)\n\t"
+		"fmulp\n\t"
+		: "=t" (result)
+		: "0" (x), "u" (y)
+		: "st(1)", "st(7)", "%3", "%4" );
+	return result;
+}
+#define pow pow_x87_inline
+#endif
+
+/*
+common.h
+*/
+
+extern FString current_filename;
+
+/* Noise modes for open_file */
+#define OF_SILENT	0
+#define OF_NORMAL	1
+#define OF_VERBOSE	2
+
+extern FILE *open_file(const char *name, int decompress, int noise_mode);
+extern void add_to_pathlist(const char *s);
+extern void close_file(FILE *fp);
+extern void skip(FILE *fp, size_t len);
+extern void *safe_malloc(size_t count);
+
+/*
+controls.h
+*/
+
+#define CMSG_INFO			0
+#define CMSG_WARNING		1
+#define CMSG_ERROR			2
+#define CMSG_FATAL			3
+#define CMSG_TRACE			4
+#define CMSG_TIME			5
+#define CMSG_TOTAL			6
+#define CMSG_FILE			7
+#define CMSG_TEXT			8
+
+#define VERB_NORMAL			0
+#define VERB_VERBOSE		1
+#define VERB_NOISY			2
+#define VERB_DEBUG			3
+#define VERB_DEBUG_SILLY	4
+
+struct ControlMode
+{
+	virtual ~ControlMode();
+	void cmsg(int type, int verbosity_level, const char *fmt, ...);
+};
+
+
+/*
+instrum.h
+*/
+
+struct Sample
+{
+	SDWORD
+		loop_start, loop_end, data_length,
+		sample_rate, low_vel, high_vel, low_freq, high_freq, root_freq;
+	SDWORD
+		envelope_rate[7], envelope_offset[7];
+	float
+		modulation_rate[7], modulation_offset[7];
+	float
+		volume, resonance,
+		modEnvToFilterFc, modEnvToPitch, modLfoToFilterFc;
+	sample_t *data;
+	SDWORD 
+		tremolo_sweep_increment, tremolo_phase_increment,
+		lfo_sweep_increment, lfo_phase_increment,
+		vibrato_sweep_increment, vibrato_control_ratio,
+		cutoff_freq;
+	BYTE
+		reverberation, chorusdepth,
+		tremolo_depth, vibrato_depth,
+		modes,
+		attenuation;
+	WORD
+		freq_center, panning;
+	SBYTE
+		note_to_use, exclusiveClass;
+	SWORD
+		keyToModEnvHold, keyToModEnvDecay,
+		keyToVolEnvHold, keyToVolEnvDecay;
+	SDWORD
+		freq_scale;
+};
+
+void convert_sample_data(Sample *sample, const void *data);
+void free_instruments();
+
+/* Bits in modes: */
+#define MODES_16BIT			(1<<0)
+#define MODES_UNSIGNED		(1<<1)
+#define MODES_LOOPING		(1<<2)
+#define MODES_PINGPONG		(1<<3)
+#define MODES_REVERSE		(1<<4)
+#define MODES_SUSTAIN		(1<<5)
+#define MODES_ENVELOPE		(1<<6)
+#define MODES_FAST_RELEASE	(1<<7)
+
+#define INST_GUS			0
+#define INST_SF2			1
+#define INST_DLS			2
+
+struct Instrument
+{
+	int type;
+	int samples;
+	Sample *sample;
+	int left_samples;
+	Sample *left_sample;
+	int right_samples;
+	Sample *right_sample;
+};
+
+struct InstrumentLayer
+{
+	BYTE lo, hi;
+	Instrument *instrument;
+	InstrumentLayer *next;
+};
+
+struct cfg_type
+{
+	int font_code;
+	int num;
+	const char *name;
+};
+
+#define FONT_NORMAL		0
+#define FONT_FFF		1
+#define FONT_SBK		2
+#define FONT_TONESET	3
+#define FONT_DRUMSET	4
+#define FONT_PRESET		5
+
+struct ToneBankElement
+{
+	ToneBankElement() : layer(NULL), font_type(0), sf_ix(0), tuning(0),
+		note(0), amp(0), pan(0), strip_loop(0), strip_envelope(0), strip_tail(0)
+	{}
+
+	FString name;
+	InstrumentLayer *layer;
+	int font_type, sf_ix, tuning;
+	int note, amp, pan, strip_loop, strip_envelope, strip_tail;
+};
+
+/* A hack to delay instrument loading until after reading the
+entire MIDI file. */
+#define MAGIC_LOAD_INSTRUMENT ((InstrumentLayer *)(-1))
+
+#define MAXPROG			128
+#define MAXBANK			130
+#define SFXBANK			(MAXBANK-1)
+#define SFXDRUM1		(MAXBANK-2)
+#define SFXDRUM2		(MAXBANK-1)
+#define XGDRUM			1
+
+struct ToneBank
+{
+	FString name;
+	ToneBankElement tone[MAXPROG];
+};
+
+
+#define SPECIAL_PROGRAM -1
+
+extern void pcmap(int *b, int *v, int *p, int *drums);
+
+/*
+mix.h
+*/
+
+extern void mix_voice(struct Renderer *song, float *buf, struct Voice *v, int c);
+extern int recompute_envelope(struct Voice *v);
+extern void apply_envelope_to_amp(struct Voice *v);
+
+/*
+playmidi.h
+*/
+
+/* Midi events */
+#define ME_NOTEOFF			0x80
+#define ME_NOTEON			0x90
+#define ME_KEYPRESSURE		0xA0
+#define ME_CONTROLCHANGE	0xB0
+#define ME_PROGRAM			0xC0
+#define ME_CHANNELPRESSURE	0xD0
+#define ME_PITCHWHEEL		0xE0
+
+/* Controllers */
+#define CTRL_BANK_SELECT		0
+#define CTRL_DATA_ENTRY			6
+#define CTRL_VOLUME				7
+#define CTRL_PAN				10
+#define CTRL_EXPRESSION			11
+#define CTRL_SUSTAIN			64
+#define CTRL_HARMONICCONTENT	71
+#define CTRL_RELEASETIME		72
+#define CTRL_ATTACKTIME			73
+#define CTRL_BRIGHTNESS			74
+#define CTRL_REVERBERATION		91
+#define CTRL_CHORUSDEPTH		93
+#define CTRL_NRPN_LSB			98
+#define CTRL_NRPN_MSB			99
+#define CTRL_RPN_LSB			100
+#define CTRL_RPN_MSB			101
+#define CTRL_ALL_SOUNDS_OFF		120
+#define CTRL_RESET_CONTROLLERS	121
+#define CTRL_ALL_NOTES_OFF		123
+
+/* NRPNs */
+#define NRPN_BRIGHTNESS			0x00A0
+#define NRPN_HARMONICCONTENT	0x00A1
+#define NRPN_DRUMVOLUME			(26<<7)		// drum number in low 7 bits
+#define NRPN_DRUMPANPOT			(28<<7)		// "
+#define NRPN_DRUMREVERBERATION	(29<<7)		// "
+#define NRPN_DRUMCHORUSDEPTH	(30<<7)		// "
+
+/* RPNs */
+#define RPN_PITCH_SENS			0x0000
+#define RPN_FINE_TUNING			0x0001
+#define RPN_COARSE_TUNING		0x0002
+#define RPN_RESET				0x3fff
+
+#define SFX_BANKTYPE	64
+
+struct Channel
+{
+	int
+		bank, program, sustain, pitchbend, 
+		mono, /* one note only on this channel -- not implemented yet */
+		/* new stuff */
+		variationbank, reverberation, chorusdepth, harmoniccontent,
+		releasetime, attacktime, brightness, kit, sfx,
+		/* end new */
+		pitchsens;
+	WORD
+		volume, expression;
+	SWORD
+		panning;
+	WORD
+		rpn, nrpn;
+	bool
+		nrpn_mode;
+	char
+		transpose;
+	float
+		pitchfactor; /* precomputed pitch bend factor to save some fdiv's */
+};
+
+/* Causes the instrument's default panning to be used. */
+#define NO_PANNING -1
+/* envelope points */
+#define MAXPOINT 7
+
+struct Voice
+{
+	BYTE
+		status, channel, note, velocity, clone_type;
+	Sample *sample;
+	Sample *left_sample;
+	Sample *right_sample;
+	int clone_voice;
+	float
+		orig_frequency, frequency;
+	int
+		sample_offset, loop_start, loop_end;
+	int
+		envelope_volume, modulation_volume;
+	int
+		envelope_target, modulation_target;
+	int
+		tremolo_sweep, tremolo_sweep_position, tremolo_phase,
+		lfo_sweep, lfo_sweep_position, lfo_phase,
+		vibrato_sweep, vibrato_sweep_position, vibrato_depth,
+		echo_delay_count;
+	int
+		echo_delay,
+		sample_increment,
+		envelope_increment,
+		modulation_increment,
+		tremolo_phase_increment,
+		lfo_phase_increment;
+
+	final_volume_t left_mix, right_mix;
+
+	float
+		left_amp, right_amp,
+		volume, tremolo_volume, lfo_volume;
+	int
+		vibrato_sample_increment[VIBRATO_SAMPLE_INCREMENTS];
+	int
+		envelope_rate[MAXPOINT], envelope_offset[MAXPOINT];
+	int
+		vibrato_phase, vibrato_control_ratio, vibrato_control_counter,
+		envelope_stage, modulation_stage, control_counter,
+		modulation_delay, modulation_counter, panning, panned;
+
+};
+
+/* Voice status options: */
+#define VOICE_FREE 0
+#define VOICE_ON 1
+#define VOICE_SUSTAINED 2
+#define VOICE_OFF 3
+#define VOICE_DIE 4
+
+/* Voice panned options: */
+#define PANNED_MYSTERY 0
+#define PANNED_LEFT 1
+#define PANNED_RIGHT 2
+#define PANNED_CENTER 3
+/* Anything but PANNED_MYSTERY only uses the left volume */
+
+/* Envelope stages: */
+#define ATTACK 0
+#define HOLD 1
+#define DECAY 2
+#define RELEASE 3
+#define RELEASEB 4
+#define RELEASEC 5
+#define DELAY 6
+
+#define ISDRUMCHANNEL(c) ((drumchannels & (1<<(c))))
+
+/*
+resample.h
+*/
+
+extern sample_t *resample_voice(struct Renderer *song, Voice *v, int *countptr);
+extern void pre_resample(struct Renderer *song, Sample *sp);
+
+/* 
+tables.h
+*/
+
+#define sine(x)			(sin((2*PI/1024.0) * (x)))
+#define note_to_freq(x)	(float(8175.7989473096690661233836992789 * pow(2.0, (x) / 12.0)))
+
+// Use TiMidity++'s volume equation rather than TiMidity's, since it's louder.
+//#define calc_vol(x)	(pow(2.0,((x)*6.0 - 6.0)))
+#define calc_vol(x)		(pow((double)(x), (double)1.66096404744))
+
+#define XMAPMAX 800
+extern const BYTE xmap[XMAPMAX][5];
+
+/*
+timidity.h
+*/
+struct DLS_Data;
+extern int LoadConfig();
+extern void FreeAll();
+
+extern ToneBank *tonebank[MAXBANK];
+extern ToneBank *drumset[MAXBANK];
+
+struct Renderer
+{
+	ControlMode *ctl;
+	float rate;
+	DLS_Data *patches;
+	InstrumentLayer *default_instrument;
+	int default_program;
+	bool fast_decay;
+	int resample_buffer_size;
+	sample_t *resample_buffer;
+	Channel channel[16];
+	Voice voice[MAX_VOICES];
+	signed char drumvolume[MAXCHAN][MAXNOTE];
+	signed char drumpanpot[MAXCHAN][MAXNOTE];
+	signed char drumreverberation[MAXCHAN][MAXNOTE];
+	signed char drumchorusdepth[MAXCHAN][MAXNOTE];
+	int control_ratio, amp_with_poly;
+	int drumchannels;
+	int adjust_panning_immediately;
+	int voices;
+	int GM_System_On;
+	int XG_System_On;
+	int GS_System_On;
+	int XG_System_reverb_type;
+	int XG_System_chorus_type;
+	int XG_System_variation_type;
+	int lost_notes, cut_notes;
+
+	Renderer(float sample_rate);
+	~Renderer();
+
+	void HandleEvent(int status, int parm1, int parm2);
+	void HandleLongMessage(const BYTE *data, int len);
+	void HandleController(int chan, int ctrl, int val);
+	void ComputeOutput(float *buffer, int num_samples);
+	void MarkInstrument(int bank, int percussion, int instr);
+	void Reset();
+
+	int load_missing_instruments();
+	int set_default_instrument(const char *name);
+	int convert_tremolo_sweep(BYTE sweep);
+	int convert_vibrato_sweep(BYTE sweep, int vib_control_ratio);
+	int convert_tremolo_rate(BYTE rate);
+	int convert_vibrato_rate(BYTE rate);
+
+	void recompute_amp(Voice *v);
+	int vc_alloc(int not_this_voice);
+	void kill_others(int voice);
+	void clone_voice(Instrument *ip, int v, int note, int vel, int clone_type, int variationbank);
+	void xremap(int *banknumpt, int *this_notept, int this_kit);
+	void start_note(int chan, int note, int vel, int voice);
+
+	void note_on(int chan, int note, int vel);
+	void note_off(int chan, int note, int vel);
+	void all_notes_off(int chan);
+	void all_sounds_off(int chan);
+	void adjust_pressure(int chan, int note, int amount);
+	void adjust_panning(int chan);
+	void drop_sustain(int chan);
+	void adjust_pitchbend(int chan);
+	void adjust_volume(int chan);
+
+	void reset_voices();
+	void reset_controllers(int chan);
+	void reset_midi();
+
+	void select_sample(int voice, Instrument *instr);
+	void select_stereo_samples(int voice, InstrumentLayer *layer);
+	void recompute_freq(int voice);
+
+	void kill_note(int voice);
+	void finish_note(int voice);
+
+	void DataEntryCoarseRPN(int chan, int rpn, int val);
+	void DataEntryFineRPN(int chan, int rpn, int val);
+	void DataEntryCoarseNRPN(int chan, int nrpn, int val);
+	void DataEntryFineNRPN(int chan, int nrpn, int val);
+};
+
+}
+#endif
diff --git a/src/v_draw.cpp b/src/v_draw.cpp
index f063b8994..b07115479 100644
--- a/src/v_draw.cpp
+++ b/src/v_draw.cpp
@@ -56,6 +56,34 @@ int CleanWidth, CleanHeight;
 
 CVAR (Bool, hud_scale, false, CVAR_ARCHIVE);
 
+// For routines that take RGB colors, cache the previous lookup in case there
+// are several repetitions with the same color.
+static int LastPal = -1;
+static uint32 LastRGB;
+
+static int PalFromRGB(uint32 rgb)
+{
+	if (LastPal >= 0 && LastRGB == rgb)
+	{
+		return LastPal;
+	}
+	// Quick check for black and white.
+	if (rgb == MAKEARGB(255,0,0,0))
+	{
+		LastPal = GPalette.BlackIndex;
+	}
+	else if (rgb == MAKEARGB(255,255,255,255))
+	{
+		LastPal = GPalette.WhiteIndex;
+	}
+	else
+	{
+		LastPal = ColorMatcher.Pick(RPART(rgb), GPART(rgb), BPART(rgb));
+	}
+	LastRGB = rgb;
+	return LastPal;
+}
+
 void STACK_ARGS DCanvas::DrawTexture (FTexture *img, int x, int y, int tags_first, ...)
 {
 	va_list tags;
@@ -757,15 +785,7 @@ void DCanvas::DrawLine(int x0, int y0, int x1, int y1, int palColor, uint32 real
 
 	if (palColor < 0)
 	{
-		// Quick check for black.
-		if (realcolor == MAKEARGB(255,0,0,0))
-		{
-			palColor = 0;
-		}
-		else
-		{
-			palColor = ColorMatcher.Pick(RPART(realcolor), GPART(realcolor), BPART(realcolor));
-		}
+		palColor = PalFromRGB(realcolor);
 	}
 
 	Lock();
@@ -918,22 +938,52 @@ void DCanvas::DrawPixel(int x, int y, int palColor, uint32 realcolor)
 {
 	if (palColor < 0)
 	{
-		// Quick check for black.
-		if (realcolor == MAKEARGB(255,0,0,0))
-		{
-			palColor = 0;
-		}
-		else
-		{
-			palColor = ColorMatcher.Pick(RPART(realcolor), GPART(realcolor), BPART(realcolor));
-		}
+		palColor = PalFromRGB(realcolor);
 	}
 
-	Lock();
-	GetBuffer()[GetPitch() * y + x] = (BYTE)palColor;
-	Unlock();
+	Buffer[Pitch * y + x] = (BYTE)palColor;
 }
 
+//==========================================================================
+//
+// DCanvas :: Clear
+//
+// Set an area to a specified color.
+//
+//==========================================================================
+
+void DCanvas::Clear (int left, int top, int right, int bottom, int palcolor, uint32 color)
+{
+	int x, y;
+	BYTE *dest;
+
+	if (left == right || top == bottom)
+	{
+		return;
+	}
+
+	assert(left < right);
+	assert(top < bottom);
+
+	if (palcolor < 0)
+	{
+		if (APART(color) != 255)
+		{
+			Dim(color, APART(color)/255.f, left, top, right - left, bottom - top);
+			return;
+		}
+
+		palcolor = PalFromRGB(color);
+	}
+
+	dest = Buffer + top * Pitch + left;
+	x = right - left;
+	for (y = top; y < bottom; y++)
+	{
+		memset(dest, palcolor, x);
+		dest += Pitch;
+	}
+}
 
 /********************************/
 /*								*/
diff --git a/src/v_video.cpp b/src/v_video.cpp
index f23b9f3b0..fc51f6ad9 100644
--- a/src/v_video.cpp
+++ b/src/v_video.cpp
@@ -284,55 +284,6 @@ void DCanvas::FlatFill (int left, int top, int right, int bottom, FTexture *src,
 	}
 }
 
-//==========================================================================
-//
-// DCanvas :: Clear
-//
-// Set an area to a specified color.
-//
-//==========================================================================
-
-void DCanvas::Clear (int left, int top, int right, int bottom, int palcolor, uint32 color)
-{
-	int x, y;
-	BYTE *dest;
-
-	if (left == right || top == bottom)
-	{
-		return;
-	}
-
-	assert(left < right);
-	assert(top < bottom);
-
-	if (palcolor < 0)
-	{
-		if (APART(color) != 255)
-		{
-			Dim(color, APART(color)/255.f, left, top, right - left, bottom - top);
-			return;
-		}
-
-		// Quick check for black.
-		if (color == MAKEARGB(255,0,0,0))
-		{
-			palcolor = 0;
-		}
-		else
-		{
-			palcolor = ColorMatcher.Pick(RPART(color), GPART(color), BPART(color));
-		}
-	}
-
-	dest = Buffer + top * Pitch + left;
-	x = right - left;
-	for (y = top; y < bottom; y++)
-	{
-		memset(dest, palcolor, x);
-		dest += Pitch;
-	}
-}
-
 //==========================================================================
 //
 // DCanvas :: Dim
diff --git a/src/win32/i_main.cpp b/src/win32/i_main.cpp
index 4119debf5..67673ed39 100644
--- a/src/win32/i_main.cpp
+++ b/src/win32/i_main.cpp
@@ -823,9 +823,18 @@ void DoMain (HINSTANCE hInstance)
 		atterm (I_Quit);
 
 		// Figure out what directory the program resides in.
-		GetModuleFileName (NULL, progdir, 1024);
-		*(strrchr (progdir, '\\') + 1) = 0;
-		FixPathSeperator (progdir);
+		char *program;
+
+		if (_get_pgmptr(&program) != 0)
+		{
+			I_FatalError("Could not determine program location.");
+		}
+		progdir = program;
+		program = progdir.LockBuffer();
+		*(strrchr(program, '\\') + 1) = '\0';
+		FixPathSeperator(program);
+		progdir.Truncate((long)strlen(program));
+		progdir.UnlockBuffer();
 /*
 		height = GetSystemMetrics (SM_CYFIXEDFRAME) * 2 +
 				GetSystemMetrics (SM_CYCAPTION) + 12 * 32;
diff --git a/zdoom.vcproj b/zdoom.vcproj
index a55ae97b5..5d2e0cbb5 100644
--- a/zdoom.vcproj
+++ b/zdoom.vcproj
@@ -2868,6 +2868,10 @@
 				RelativePath="src\sound\music_stream.cpp"
 				>
 			</File>
+			<File
+				RelativePath=".\src\sound\music_timidity_mididevice.cpp"
+				>
+			</File>
 			<File
 				RelativePath=".\src\sound\music_win_mididevice.cpp"
 				>
@@ -2927,6 +2931,122 @@
 			<Filter
 				Name="Timidity"
 				>
+				<File
+					RelativePath=".\src\timidity\common.cpp"
+					>
+				</File>
+				<File
+					RelativePath=".\src\timidity\dls1.h"
+					>
+				</File>
+				<File
+					RelativePath=".\src\timidity\dls2.h"
+					>
+				</File>
+				<File
+					RelativePath=".\src\timidity\instrum.cpp"
+					>
+				</File>
+				<File
+					RelativePath=".\src\timidity\instrum_dls.cpp"
+					>
+				</File>
+				<File
+					RelativePath=".\src\timidity\mix.cpp"
+					>
+				</File>
+				<File
+					RelativePath=".\src\timidity\playmidi.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							EnableEnhancedInstructionSet="0"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath=".\src\timidity\resample.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							EnableEnhancedInstructionSet="0"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath=".\src\timidity\tables.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							ObjectFile="$(IntDir)\$(InputName)1.obj"
+							XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Release|x64"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							ObjectFile="$(IntDir)\$(InputName)1.obj"
+							XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							ObjectFile="$(IntDir)\$(InputName)1.obj"
+							XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|x64"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							ObjectFile="$(IntDir)\$(InputName)1.obj"
+							XMLDocumentationFileName="$(IntDir)\$(InputName)1.xdc"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath=".\src\timidity\timidity.cpp"
+					>
+				</File>
+				<File
+					RelativePath=".\src\timidity\timidity.h"
+					>
+				</File>
+				<Filter
+					Name="Docs"
+					>
+					<File
+						RelativePath=".\src\timidity\CHANGES"
+						>
+					</File>
+					<File
+						RelativePath=".\src\timidity\COPYING"
+						>
+					</File>
+					<File
+						RelativePath=".\src\timidity\FAQ"
+						>
+					</File>
+					<File
+						RelativePath=".\src\timidity\README"
+						>
+					</File>
+				</Filter>
 			</Filter>
 		</Filter>
 		<Filter