From 070ec7578548d97e61d9754572ce36107cd75cab Mon Sep 17 00:00:00 2001
From: Randy Heit <rheit@zdoom.fake>
Date: Fri, 3 Sep 2010 05:08:05 +0000
Subject: [PATCH] - Cleaned up the ugly MIDI song creating code a little. -
 Added a generic Standard MIDI File creator that works with any of the
 sequencers. mus2midi.cpp   is no longer used but is kept around as a
 reference.

SVN r2677 (trunk)
---
 src/CMakeLists.txt                |   1 -
 src/mus2midi.cpp                  |   4 +-
 src/mus2midi.h                    |   3 -
 src/sound/i_music.cpp             | 306 +++++++++++++++---------------
 src/sound/i_musicinterns.h        |  16 +-
 src/sound/music_hmi_midiout.cpp   |   6 +-
 src/sound/music_midi_timidity.cpp |  16 +-
 src/sound/music_midistream.cpp    | 169 ++++++++++++++++-
 src/sound/music_mus_midiout.cpp   |   6 +-
 src/sound/music_smf_midiout.cpp   |   6 +-
 src/tarray.h                      |   9 +
 zdoom.vcproj                      |   4 -
 12 files changed, 344 insertions(+), 202 deletions(-)

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 18f13abd6..16a658a10 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -661,7 +661,6 @@ add_executable( zdoom WIN32
 	m_png.cpp
 	m_random.cpp
 	md5.cpp
-	mus2midi.cpp
 	name.cpp
 	nodebuild.cpp
 	nodebuild_classify_nosse2.cpp
diff --git a/src/mus2midi.cpp b/src/mus2midi.cpp
index 63e59e755..2157b4a89 100644
--- a/src/mus2midi.cpp
+++ b/src/mus2midi.cpp
@@ -195,9 +195,9 @@ bool ProduceMIDI (const BYTE *musBuf, int len, TArray<BYTE> &outFile)
 		switch (event & 0x70)
 		{
 		case MUS_NOTEOFF:
-			midStatus |= MIDI_NOTEOFF;
+			midStatus |= MIDI_NOTEON;
 			mid1 = t & 127;
-			mid2 = 64;
+			mid2 = 0;
 			break;
 			
 		case MUS_NOTEON:
diff --git a/src/mus2midi.h b/src/mus2midi.h
index 6dc75dc03..f1d846927 100644
--- a/src/mus2midi.h
+++ b/src/mus2midi.h
@@ -75,7 +75,4 @@ typedef struct
 	// WORD UsedInstruments[NumInstruments];
 } MUSHeader;
 
-bool ProduceMIDI (const BYTE *musBuf, int len, TArray<BYTE> &outFile);
-bool ProduceMIDI (const BYTE *musBuf, int len, FILE *outFile);
-
 #endif //__MUS2MIDI_H__
diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp
index 717f98eaa..172553df5 100644
--- a/src/sound/i_music.cpp
+++ b/src/sound/i_music.cpp
@@ -3,7 +3,7 @@
 ** Plays music
 **
 **---------------------------------------------------------------------------
-** Copyright 1998-2006 Randy Heit
+** Copyright 1998-2010 Randy Heit
 ** All rights reserved.
 **
 ** Redistribution and use in source and binary forms, with or without
@@ -84,6 +84,14 @@ extern void ChildSigHandler (int signum);
 #define GZIP_FNAME		8
 #define GZIP_FCOMMENT	16
 
+enum EMIDIType
+{
+	MIDI_NOTMIDI,
+	MIDI_MIDI,
+	MIDI_HMI,
+	MIDI_MUS
+};
+
 extern int MUSHeaderSearch(const BYTE *head, int len);
 
 EXTERN_CVAR (Int, snd_samplerate)
@@ -305,6 +313,40 @@ MusInfo *I_RegisterURLSong (const char *url)
 	return NULL;
 }
 
+static MusInfo *CreateMIDISong(FILE *file, const char *filename, BYTE *musiccache, int offset, int len, EMIDIDevice devtype, EMIDIType miditype)
+{
+	if (devtype == MIDI_Timidity)
+	{
+		assert(miditype == MIDI_MIDI);
+		return new TimiditySong(file, musiccache, len);
+	}
+	else if (devtype >= MIDI_Null)
+	{
+		assert(miditype == MIDI_MIDI);
+		if (musiccache != NULL)
+		{
+			return new StreamSong((char *)musiccache, -1, len);
+		}
+		else
+		{
+			return new StreamSong(filename, offset, len);
+		}
+	}
+	else if (miditype == MIDI_MUS)
+	{
+		return new MUSSong2(file, musiccache, len, devtype);
+	}
+	else if (miditype == MIDI_MIDI)
+	{
+		return new MIDISong2(file, musiccache, len, devtype);
+	}
+	else if (miditype == MIDI_HMI)
+	{
+		return new HMISong(file, musiccache, len, devtype);
+	}
+	return NULL;
+}
+
 MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int len, int device)
 {
 	FILE *file;
@@ -405,191 +447,147 @@ MusInfo *I_RegisterSong (const char *filename, BYTE *musiccache, int offset, int
 		}
 	}
 
+	EMIDIType miditype = MIDI_NOTMIDI;
+
 	// Check for MUS format
 	// Tolerate sloppy wads by searching up to 32 bytes for the header
 	if (MUSHeaderSearch(idstr, sizeof(idstr)) >= 0)
 	{
-		/*	MUS are played as:
-		- OPL: 
-			- if explicitly selected by $mididevice 
-			- when snd_mididevice  is -3 and no midi device is set for the song
+		miditype = MIDI_MUS;
+	}
+	// Check for HMI format
+	else 
+	if (id[0] == MAKE_ID('H','M','I','-') &&
+		id[1] == MAKE_ID('M','I','D','I') &&
+		id[2] == MAKE_ID('S','O','N','G'))
+	{
+		miditype = MIDI_HMI;
+	}
+	// Check for MIDI format
+	else if (id[0] == MAKE_ID('M','T','h','d'))
+	{
+		miditype = MIDI_MIDI;
+	}
 
-		  Timidity: 
-			- if explicitly selected by $mididevice 
-			- when snd_mididevice  is -2 and no midi device is set for the song
+	if (miditype != MIDI_NOTMIDI)
+	{
+		TArray<BYTE> midi;
+		/* MIDI are played as:
+			- OPL: 
+				- if explicitly selected by $mididevice 
+				- when snd_mididevice  is -3 and no midi device is set for the song
 
-		  FMod:
-			- if explicitly selected by $mididevice 
-			- when snd_mididevice  is -1 and no midi device is set for the song
-			- as fallback when both OPL and Timidity failed unless snd_mididevice is >= 0
+			- Timidity: 
+				- if explicitly selected by $mididevice 
+				- when snd_mididevice  is -2 and no midi device is set for the song
 
-		  MMAPI (Win32 only):
-			- if explicitly selected by $mididevice (non-Win32 redirects this to FMOD)
-			- when snd_mididevice  is >= 0 and no midi device is set for the song
-			- as fallback when both OPL and Timidity failed and snd_mididevice is >= 0
+			- FMod:
+				- if explicitly selected by $mididevice 
+				- when snd_mididevice  is -1 and no midi device is set for the song
+				- as fallback when both OPL and Timidity failed unless snd_mididevice is >= 0
+
+			- MMAPI (Win32 only):
+				- if explicitly selected by $mididevice (non-Win32 redirects this to FMOD)
+				- when snd_mididevice  is >= 0 and no midi device is set for the song
+				- as fallback when both OPL and Timidity failed and snd_mididevice is >= 0
 		*/
-		if ((snd_mididevice == -3 && device == MDEV_DEFAULT) || device == MDEV_OPL)
+		EMIDIDevice devtype = MIDI_Null;
+
+		// Choose the type of MIDI device we want.
+		if (device == MDEV_FMOD || (snd_mididevice == -1 && device == MDEV_DEFAULT))
 		{
-			info = new MUSSong2 (file, musiccache, len, MIDI_OPL);
+			devtype = MIDI_FMOD;
 		}
-		else if (device == MDEV_TIMIDITY || (device == MDEV_DEFAULT && snd_mididevice == -2))
+		else if (device == MDEV_TIMIDITY || (snd_mididevice == -2 && device == MDEV_DEFAULT))
 		{
-			info = new TimiditySong (file, musiccache, len);
+			devtype = MIDI_Timidity;
+		}
+		else if (device == MDEV_OPL || (snd_mididevice == -3 && device == MDEV_DEFAULT))
+		{
+			devtype = MIDI_OPL;
 		}
 		else if (snd_mididevice == -4 && device == MDEV_DEFAULT)
 		{
-			info = new MUSSong2(file, musiccache, len, MIDI_Timidity);
+			devtype = MIDI_GUS;
 		}
 #ifdef HAVE_FLUIDSYNTH
 		else if (device == MDEV_FLUIDSYNTH || (snd_mididevice == -5 && device == MDEV_DEFAULT))
 		{
-			info = new MUSSong2(file, musiccache, len, MIDI_Fluid);
+			devtype = MIDI_Fluid;
 		}
 #endif
+#ifdef _WIN32
+		else
+		{
+			devtype = MIDI_Win;
+		}
+#endif
+
+retry_as_fmod:
+		if (miditype != MIDI_MIDI && devtype >= MIDI_Null)
+		{
+			// Convert to standard MIDI for external sequencers.
+			MIDIStreamer *streamer;
+
+			if (miditype == MIDI_MUS)
+			{
+				streamer = new MUSSong2(file, musiccache, len, MIDI_Null);
+			}
+			else
+			{
+				assert(miditype == MIDI_HMI);
+				streamer = new HMISong(file, musiccache, len, MIDI_Null);
+			}
+			if (streamer->IsValid())
+			{
+				streamer->CreateSMF(midi);
+				miditype = MIDI_MIDI;
+				musiccache = &midi[0];
+				len = midi.Size();
+				if (file != NULL)
+				{
+					fclose(file);
+					file = NULL;
+				}
+			}
+			delete streamer;
+		}
+		info = CreateMIDISong(file, filename, musiccache, offset, len, devtype, miditype);
 		if (info != NULL && !info->IsValid())
 		{
 			delete info;
 			info = NULL;
-			device = MDEV_DEFAULT;
 		}
-		if (info == NULL && (snd_mididevice == -1 || device == MDEV_FMOD) && device != MDEV_MMAPI)
+		if (info == NULL && devtype != MIDI_FMOD && snd_mididevice < 0)
 		{
-			TArray<BYTE> midi;
-			bool midi_made = false;
-
-			if (file == NULL)
-			{
-				midi_made = ProduceMIDI((BYTE *)musiccache, len, midi);
-			}
-			else
-			{
-				BYTE *mus = new BYTE[len];
-				size_t did_read = fread(mus, 1, len, file);
-				if (did_read == (size_t)len)
-				{
-					midi_made = ProduceMIDI(mus, len, midi);
-				}
-				fseek(file, -(long)did_read, SEEK_CUR);
-				delete[] mus;
-			}
-			if (midi_made)
-			{
-				info = new StreamSong((char *)&midi[0], -1, midi.Size());
-				if (!info->IsValid())
-				{
-					delete info;
-					info = NULL;
-				}
-			}
+			devtype = MIDI_FMOD;
+			goto retry_as_fmod;
 		}
 #ifdef _WIN32
-		if (info == NULL)
+		if (info == NULL && devtype != MIDI_Win && snd_mididevice >= 0)
 		{
-			info = new MUSSong2 (file, musiccache, len, MIDI_Win);
+			info = CreateMIDISong(file, filename, musiccache, offset, len, MIDI_Win, miditype);
 		}
-#endif // _WIN32
+#endif
 	}
-	else 
+
+	// Check for various raw OPL formats
+	else if (
+		(id[0] == MAKE_ID('R','A','W','A') && id[1] == MAKE_ID('D','A','T','A')) ||		// Rdos Raw OPL
+		(id[0] == MAKE_ID('D','B','R','A') && id[1] == MAKE_ID('W','O','P','L')) ||		// DosBox Raw OPL
+		(id[0] == MAKE_ID('A','D','L','I') && *((BYTE *)id + 4) == 'B'))		// Martin Fernandez's modified IMF
 	{
-		// Check for HMI format
-		if (id[0] == MAKE_ID('H','M','I','-') &&
-			id[1] == MAKE_ID('M','I','D','I') &&
-			id[2] == MAKE_ID('S','O','N','G'))
-		{
-			if ((snd_mididevice == -3 && device == MDEV_DEFAULT) || device == MDEV_OPL)
-			{
-				info = new HMISong(file, musiccache, len, MIDI_OPL);
-			}
-			else if (snd_mididevice == -4 && device == MDEV_DEFAULT)
-			{
-				info = new HMISong(file, musiccache, len, MIDI_Timidity);
-			}
-#ifdef HAVE_FLUIDSYNTH
-			else if (device == MDEV_FLUIDSYNTH || (snd_mididevice == -5 && device == MDEV_DEFAULT))
-			{
-				info = new HMISong(file, musiccache, len, MIDI_Fluid);
-			}
-#endif
-#ifdef _WIN32
-			else
-			{
-				info = new HMISong(file, musiccache, len, MIDI_Win);
-			}
-#endif
-		}
-		// Check for MIDI format
-		else if (id[0] == MAKE_ID('M','T','h','d'))
-		{
-			// This is a midi file
-
-			/*	MIDI are played as:
-			  OPL: 
-				- if explicitly selected by $mididevice 
-				- when snd_mididevice  is -3 and no midi device is set for the song
-
-			  Timidity: 
-				- if explicitly selected by $mididevice 
-				- when snd_mididevice  is -2 and no midi device is set for the song
-
-			  FMOD:
-				- if explicitly selected by $mididevice 
-				- when snd_mididevice  is -1 and no midi device is set for the song
-				- as fallback when Timidity failed unless snd_mididevice is >= 0
-
-			  MMAPI (Win32 only):
-				- if explicitly selected by $mididevice (non-Win32 redirects this to FMOD)
-				- when snd_mididevice  is >= 0 and no midi device is set for the song
-				- as fallback when Timidity failed and snd_mididevice is >= 0
-			*/
-			if (device == MDEV_OPL || (snd_mididevice == -3 && device == MDEV_DEFAULT))
-			{
-				info = new MIDISong2 (file, musiccache, len, MIDI_OPL);
-			}
-			else if (device == MDEV_TIMIDITY || (snd_mididevice == -2 && device == MDEV_DEFAULT))
-			{
-				info = new TimiditySong (file, musiccache, len);
-			}
-			else if (snd_mididevice == -4 && device == MDEV_DEFAULT)
-			{
-				info = new MIDISong2(file, musiccache, len, MIDI_Timidity);
-			}
-#ifdef HAVE_FLUIDSYNTH
-			else if (device == MDEV_FLUIDSYNTH || (snd_mididevice == -5 && device == MDEV_DEFAULT))
-			{
-				info = new MIDISong2(file, musiccache, len, MIDI_Fluid);
-			}
-#endif
-			if (info != NULL && !info->IsValid())
-			{
-				delete info;
-				info = NULL;
-				device = MDEV_DEFAULT;
-			}
-#ifdef _WIN32
-			if (info == NULL && device != MDEV_FMOD && (snd_mididevice >= 0 || device == MDEV_MMAPI))
-			{
-				info = new MIDISong2 (file, musiccache, len, MIDI_Win);
-			}
-#endif // _WIN32
-		}
-		// Check for various raw OPL formats
-		else if (
-			(id[0] == MAKE_ID('R','A','W','A') && id[1] == MAKE_ID('D','A','T','A')) ||		// Rdos Raw OPL
-			(id[0] == MAKE_ID('D','B','R','A') && id[1] == MAKE_ID('W','O','P','L')) ||		// DosBox Raw OPL
-			(id[0] == MAKE_ID('A','D','L','I') && *((BYTE *)id + 4) == 'B'))		// Martin Fernandez's modified IMF
-		{
-			info = new OPLMUSSong (file, musiccache, len);
-		}
-		// Check for game music
-		else if ((fmt = GME_CheckFormat(id[0])) != NULL && fmt[0] != '\0')
-		{
-			info = GME_OpenSong(file, musiccache, len, fmt);
-		}
-		// Check for module formats
-		else
-		{
-			info = MOD_OpenSong(file, musiccache, len);
-		}
+		info = new OPLMUSSong (file, musiccache, len);
+	}
+	// Check for game music
+	else if ((fmt = GME_CheckFormat(id[0])) != NULL && fmt[0] != '\0')
+	{
+		info = GME_OpenSong(file, musiccache, len, fmt);
+	}
+	// Check for module formats
+	else
+	{
+		info = MOD_OpenSong(file, musiccache, len);
 	}
 
 	if (info == NULL)
diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h
index 6bfe9c2f4..8c6507167 100644
--- a/src/sound/i_musicinterns.h
+++ b/src/sound/i_musicinterns.h
@@ -333,8 +333,13 @@ enum EMIDIDevice
 {
 	MIDI_Win,
 	MIDI_OPL,
-	MIDI_Timidity,
-	MIDI_Fluid
+	MIDI_GUS,
+	MIDI_Fluid,
+
+	// only used by I_RegisterSong 
+	MIDI_Null,
+	MIDI_FMOD,
+	MIDI_Timidity
 };
 
 class MIDIStreamer : public MusInfo
@@ -357,6 +362,7 @@ public:
 	void FluidSettingInt(const char *setting, int value);
 	void FluidSettingNum(const char *setting, double value);
 	void FluidSettingStr(const char *setting, const char *value);
+	void CreateSMF(TArray<BYTE> &file);
 
 protected:
 	MIDIStreamer(const char *dumpname, EMIDIDevice type);
@@ -369,7 +375,7 @@ protected:
 	static void Callback(unsigned int uMsg, void *userdata, DWORD dwParam1, DWORD dwParam2);
 
 	// Virtuals for subclasses to override
-	virtual void CheckCaps();
+	virtual void CheckCaps(int tech);
 	virtual void DoInitialSetup() = 0;
 	virtual void DoRestart() = 0;
 	virtual bool CheckDone() = 0;
@@ -457,7 +463,7 @@ public:
 protected:
 	MIDISong2(const MIDISong2 *original, const char *filename, EMIDIDevice type);	// file dump constructor
 
-	void CheckCaps();
+	void CheckCaps(int tech);
 	void DoInitialSetup();
 	void DoRestart();
 	bool CheckDone();
@@ -494,7 +500,7 @@ public:
 protected:
 	HMISong(const HMISong *original, const char *filename, EMIDIDevice type);	// file dump constructor
 
-	void CheckCaps();
+	void CheckCaps(int tech);
 	void DoInitialSetup();
 	void DoRestart();
 	bool CheckDone();
diff --git a/src/sound/music_hmi_midiout.cpp b/src/sound/music_hmi_midiout.cpp
index 078e98c94..c73c9f619 100644
--- a/src/sound/music_hmi_midiout.cpp
+++ b/src/sound/music_hmi_midiout.cpp
@@ -273,10 +273,8 @@ HMISong::~HMISong ()
 //
 //==========================================================================
 
-void HMISong::CheckCaps()
+void HMISong::CheckCaps(int tech)
 {
-	int tech = MIDI->GetTechnology();
-
 	// What's the equivalent HMI device for our technology?
 	if (tech == MOD_FMSYNTH)
 	{
@@ -851,7 +849,7 @@ MusInfo *HMISong::GetOPLDumper(const char *filename)
 
 MusInfo *HMISong::GetWaveDumper(const char *filename, int rate)
 {
-	return new HMISong(this, filename, MIDI_Timidity);
+	return new HMISong(this, filename, MIDI_GUS);
 }
 
 //==========================================================================
diff --git a/src/sound/music_midi_timidity.cpp b/src/sound/music_midi_timidity.cpp
index ea725d0da..6e2acb32d 100644
--- a/src/sound/music_midi_timidity.cpp
+++ b/src/sound/music_midi_timidity.cpp
@@ -207,7 +207,7 @@ TimiditySong::TimiditySong (FILE *file, BYTE *musiccache, int len)
 
 	BYTE *buf;
 	
-	if (file!=NULL) 
+	if (file != NULL) 
 	{
 		buf = new BYTE[len];
 		fread (buf, 1, len, file);
@@ -217,18 +217,8 @@ TimiditySong::TimiditySong (FILE *file, BYTE *musiccache, int len)
 		buf = musiccache;
 	}
 
-
-	// The file type has already been checked before this class instance was
-	// created, so we only need to check one character to determine if this
-	// is a MUS or MIDI file and write it to disk as appropriate.
-	if (buf[1] == 'T')
-	{
-		success = (fwrite (buf, 1, len, f) == (size_t)len);
-	}
-	else
-	{
-		success = ProduceMIDI (buf, len, f);
-	}
+	// Write to temporary file
+	success = (fwrite (buf, 1, len, f) == (size_t)len);
 	fclose (f);
 	if (file != NULL) 
 	{
diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp
index ddba36006..085c996e2 100644
--- a/src/sound/music_midistream.cpp
+++ b/src/sound/music_midistream.cpp
@@ -49,6 +49,8 @@
 
 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
 
+static void WriteVarLen (TArray<BYTE> &file, DWORD value);
+
 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
 
 EXTERN_CVAR(Float, snd_musicvolume)
@@ -57,8 +59,21 @@ EXTERN_CVAR(Float, snd_musicvolume)
 extern UINT mididevice;
 #endif
 
+extern char MIDI_EventLengths[7];
+
 // PRIVATE DATA DEFINITIONS ------------------------------------------------
 
+static const BYTE StaticMIDIhead[] =
+{
+	'M','T','h','d', 0, 0, 0, 6,
+	0, 0, // format 0: only one track
+	0, 1, // yes, there is really only one track
+	0, 0, // divisions (filled in)
+	'M','T','r','k', 0, 0, 0, 0,
+	// The first event sets the tempo (filled in)
+	0, 255, 81, 3, 0, 0, 0
+};
+
 // PUBLIC DATA DEFINITIONS -------------------------------------------------
 
 // CODE --------------------------------------------------------------------
@@ -172,7 +187,7 @@ bool MIDIStreamer::IsValid() const
 //
 //==========================================================================
 
-void MIDIStreamer::CheckCaps()
+void MIDIStreamer::CheckCaps(int tech)
 {
 }
 
@@ -200,7 +215,7 @@ void MIDIStreamer::Play(bool looping, int subsong)
 		{
 			MIDI = new OPLDumperMIDIDevice(DumpFilename);
 		}
-		else if (DeviceType == MIDI_Timidity)
+		else if (DeviceType == MIDI_GUS)
 		{
 			MIDI = new TimidityWaveWriterMIDIDevice(DumpFilename, 0);
 		}
@@ -221,13 +236,17 @@ void MIDIStreamer::Play(bool looping, int subsong)
 		break;
 #endif
 
-	case MIDI_Timidity:
+	case MIDI_GUS:
 		MIDI = new TimidityMIDIDevice;
 		break;
 
 	case MIDI_OPL:
 		MIDI = new OPLMIDIDevice;
 		break;
+
+	default:
+		MIDI = NULL;
+		break;
 	}
 	
 #ifndef _WIN32
@@ -240,9 +259,9 @@ void MIDIStreamer::Play(bool looping, int subsong)
 		return;
 	}
 
-	CheckCaps();
+	CheckCaps(MIDI->GetTechnology());
 	Precache();
-	IgnoreLoops = true;
+	IgnoreLoops = false;
 
 	// Set time division and tempo.
 	if (0 != MIDI->SetTimeDiv(Division) ||
@@ -515,7 +534,7 @@ void MIDIStreamer::OutputVolume (DWORD volume)
 int MIDIStreamer::VolumeControllerChange(int channel, int volume)
 {
 	ChannelVolumes[channel] = volume;
-	return ((volume + 1) * Volume) >> 16;
+	return IgnoreLoops ? volume : ((volume + 1) * Volume) >> 16;
 }
 
 //==========================================================================
@@ -834,9 +853,9 @@ int MIDIStreamer::FillBuffer(int buffer_num, int max_events, DWORD max_time)
 //
 // MIDIStreamer :: Precache
 //
-// Generates a list of instruments this song uses them and passes them to
-// the MIDI device for precaching. The default implementation here pretends
-// to play the song and watches for program change events on normal channels
+// Generates a list of instruments this song uses and passes them to the
+// MIDI device for precaching. The default implementation here pretends to
+// play the song and watches for program change events on normal channels
 // and note on events on channel 10.
 //
 //==========================================================================
@@ -933,6 +952,138 @@ void MIDIStreamer::Precache()
 	MIDI->PrecacheInstruments(&packed[0], packed.Size());
 }
 
+//==========================================================================
+//
+// MIDIStreamer :: CreateSMF
+//
+// Simulates playback to create a Standard MIDI File.
+//
+//==========================================================================
+
+void MIDIStreamer::CreateSMF(TArray<BYTE> &file)
+{
+	DWORD delay = 0;
+	BYTE running_status = 0;
+
+	// Always create songs aimed at GM devices.
+	CheckCaps(MOD_MIDIPORT);
+	IgnoreLoops = true;
+	DoRestart();
+
+	file.Reserve(sizeof(StaticMIDIhead));
+	memcpy(&file[0], StaticMIDIhead, sizeof(StaticMIDIhead));
+	file[12] = Division >> 8;
+	file[13] = Division & 0xFF;
+	file[26] = InitialTempo >> 16;
+	file[27] = InitialTempo >> 8;
+	file[28] = InitialTempo;
+
+	while (!CheckDone())
+	{
+		DWORD *event_end = MakeEvents(Events[0], &Events[0][MAX_EVENTS*3], 1000000*600);
+		for (DWORD *event = Events[0]; event < event_end; )
+		{
+			delay += event[0];
+			if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO)
+			{
+				WriteVarLen(file, delay);
+				delay = 0;
+				DWORD tempo = MEVT_EVENTPARM(event[2]);
+				file.Push(MIDI_META);
+				file.Push(MIDI_META_TEMPO);
+				file.Push(3);
+				file.Push(BYTE(tempo >> 16));
+				file.Push(BYTE(tempo >> 8));
+				file.Push(BYTE(tempo));
+			}
+			else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG)
+			{
+				WriteVarLen(file, delay);
+				delay = 0;
+				DWORD len = MEVT_EVENTPARM(event[2]);
+				BYTE *bytes = (BYTE *)&event[3];
+				if (bytes[0] == MIDI_SYSEX)
+				{
+					len--;
+					file.Push(MIDI_SYSEX);
+					WriteVarLen(file, len);
+					memcpy(&file[file.Reserve(len - 1)], bytes, len);
+				}
+			}
+			else if (MEVT_EVENTTYPE(event[2]) == 0)
+			{
+				WriteVarLen(file, delay);
+				delay = 0;
+				BYTE status = BYTE(event[2]);
+				if (status != running_status)
+				{
+					running_status = status;
+					file.Push(status);
+				}
+				file.Push(BYTE((event[2] >> 8) & 0x7F));
+				if (MIDI_EventLengths[(status >> 4) & 7] == 2)
+				{
+					file.Push(BYTE((event[2] >> 16) & 0x7F));
+				}
+			}
+			// Advance to next event
+			if (event[2] < 0x80000000)
+			{ // short message
+				event += 3;
+			}
+			else
+			{ // long message
+				event += 3 + ((MEVT_EVENTPARM(event[2]) + 3) >> 2);
+			}
+		}
+	}
+
+	// End track
+	WriteVarLen(file, delay);
+	file.Push(MIDI_META);
+	file.Push(MIDI_META_EOT);
+	file.Push(0);
+
+	// Fill in track length
+	DWORD len = file.Size() - 22;
+	file[18] = BYTE(len >> 24);
+	file[19] = BYTE(len >> 16);
+	file[20] = BYTE(len >> 8);
+	file[21] = BYTE(len & 255);
+
+	IgnoreLoops = false;
+}
+
+//==========================================================================
+//
+// WriteVarLen
+//
+//==========================================================================
+
+static void WriteVarLen (TArray<BYTE> &file, DWORD value)
+{
+   DWORD buffer = value & 0x7F;
+
+   while ( (value >>= 7) )
+   {
+     buffer <<= 8;
+     buffer |= (value & 0x7F) | 0x80;
+   }
+
+   for (;;)
+   {
+	   file.Push(BYTE(buffer));
+	   if (buffer & 0x80)
+	   {
+		   buffer >>= 8;
+	   }
+	   else
+	   {
+		   break;
+	   }
+   }
+}
+
 //==========================================================================
 //
 // MIDIStreamer :: GetStats
diff --git a/src/sound/music_mus_midiout.cpp b/src/sound/music_mus_midiout.cpp
index b89d9fd67..44a0d45e6 100644
--- a/src/sound/music_mus_midiout.cpp
+++ b/src/sound/music_mus_midiout.cpp
@@ -283,9 +283,9 @@ DWORD *MUSSong2::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time)
 		switch (event & 0x70)
 		{
 		case MUS_NOTEOFF:
-			status |= MIDI_NOTEOFF;
+			status |= MIDI_NOTEON;
 			mid1 = t;
-			mid2 = 64;
+			mid2 = 0;
 			break;
 			
 		case MUS_NOTEON:
@@ -382,7 +382,7 @@ MusInfo *MUSSong2::GetOPLDumper(const char *filename)
 
 MusInfo *MUSSong2::GetWaveDumper(const char *filename, int rate)
 {
-	return new MUSSong2(this, filename, MIDI_Timidity);
+	return new MUSSong2(this, filename, MIDI_GUS);
 }
 
 //==========================================================================
diff --git a/src/sound/music_smf_midiout.cpp b/src/sound/music_smf_midiout.cpp
index 2607de341..0f01be75b 100644
--- a/src/sound/music_smf_midiout.cpp
+++ b/src/sound/music_smf_midiout.cpp
@@ -216,10 +216,8 @@ MIDISong2::~MIDISong2 ()
 //
 //==========================================================================
 
-void MIDISong2::CheckCaps()
+void MIDISong2::CheckCaps(int tech)
 {
-	int tech = MIDI->GetTechnology();
-
 	DesignationMask = 0xFF0F;
 	if (tech == MOD_FMSYNTH)
 	{
@@ -801,7 +799,7 @@ MusInfo *MIDISong2::GetOPLDumper(const char *filename)
 
 MusInfo *MIDISong2::GetWaveDumper(const char *filename, int rate)
 {
-	return new MIDISong2(this, filename, MIDI_Timidity);
+	return new MIDISong2(this, filename, MIDI_GUS);
 }
 
 //==========================================================================
diff --git a/src/tarray.h b/src/tarray.h
index 9ee5eff2b..3eab7be33 100644
--- a/src/tarray.h
+++ b/src/tarray.h
@@ -141,6 +141,15 @@ public:
 		::new((void*)&Array[Count]) T(item);
 		return Count++;
 	}
+	bool Pop ()
+	{
+		if (Count > 0)
+		{
+			Array[--Count].~T();
+			return true;
+		}
+		return false;
+	}
 	bool Pop (T &item)
 	{
 		if (Count > 0)
diff --git a/zdoom.vcproj b/zdoom.vcproj
index c61cfa9d7..4df675804 100644
--- a/zdoom.vcproj
+++ b/zdoom.vcproj
@@ -712,10 +712,6 @@
 				RelativePath=".\src\md5.cpp"
 				>
 			</File>
-			<File
-				RelativePath=".\src\mus2midi.cpp"
-				>
-			</File>
 			<File
 				RelativePath=".\src\name.cpp"
 				>