diff --git a/docs/rh-log.txt b/docs/rh-log.txt
index b114e8be3..ba1b33b72 100644
--- a/docs/rh-log.txt
+++ b/docs/rh-log.txt
@@ -1,3 +1,17 @@
+March 28, 2008
+- Moved sound sample rate, buffer size, and buffer count to the
+  advanced sound options menu. Removed opl_enable from the menu.
+- Added OPL synth as MIDI device -3. Since this is based on the MUS player
+  code, it only supports those events and controllers supported by MUS.
+  Some of Duke's MIDIs sound awful, but I think that may be more because
+  it's using different instruments... There's a thread in the MIDI streamer
+  class that could be taken out for Linux, since it doesn't need to deal
+  with the Windows Multimedia API, but for now, this is still Windows-only.
+- Changed the output of the OPL emulator from 32-bit integers to 32-bit
+  floats, so I can write its output directly to the stream buffer. In
+  addition, this lets me bring the OPL volume level much closer to the
+  standard MIDI volume.
+
 March 27, 2008
 - Did some restructuring of the OPL code in preparation for turning it
   into a general MIDI player.
diff --git a/src/m_options.cpp b/src/m_options.cpp
index 02b515c95..6b0ae1348 100644
--- a/src/m_options.cpp
+++ b/src/m_options.cpp
@@ -1244,9 +1244,6 @@ static menuitem_t SoundItems[] =
 	{ ediscrete,"Speaker mode",			{&snd_speakermode},		{8.0}, {0.0},	{0.0}, {(value_t *)SpeakerModes} },
 	{ ediscrete,"Resampler",			{&snd_resampler},		{4.0}, {0.0},	{0.0}, {(value_t *)Resamplers} },
 	{ discrete, "HRTF filter",			{&snd_hrtf},			{2.0}, {0.0},	{0.0}, {(value_t *)OnOff} },
-	{ discrete, "Sample rate",			{&snd_samplerate},		{8.0}, {0.0},	{0.0}, {SampleRates} },
-	{ discrete, "Buffer size",			{&snd_buffersize},		{8.0}, {0.0},	{0.0}, {BufferSizes} },
-	{ discrete, "Buffer count",			{&snd_buffercount},		{12.0}, {0.0},	{0.0}, {BufferCounts} },
 
 	{ redtext,	" ",					{NULL},					{0.0}, {0.0},	{0.0}, {NULL} },
 	{ more,		"Advanced options",		{NULL},					{0.0}, {0.0},	{0.0}, {(value_t *)AdvSoundOptions} },
@@ -1274,15 +1271,18 @@ EXTERN_CVAR (Bool, opl_onechip)
 
 static menuitem_t AdvSoundItems[] =
 {
-	{ whitetext,"OPL Synthesis",			{NULL},				{0.0}, {0.0},	{0.0}, {NULL} },
-	{ discrete, "Use FM Synth for MUS music",{&opl_enable},		{2.0}, {0.0},	{0.0}, {OnOff} },
+	{ discrete, "Sample rate",			{&snd_samplerate},		{8.0}, {0.0},	{0.0}, {SampleRates} },
+	{ discrete, "Buffer size",			{&snd_buffersize},		{8.0}, {0.0},	{0.0}, {BufferSizes} },
+	{ discrete, "Buffer count",			{&snd_buffercount},		{12.0}, {0.0},	{0.0}, {BufferCounts} },
+	{ redtext,	" ",					{NULL},					{0.0}, {0.0},	{0.0}, {NULL} },
+	{ whitetext,"OPL Synthesis",		{NULL},					{0.0}, {0.0},	{0.0}, {NULL} },
 	{ discrete, "Only emulate one OPL chip", {&opl_onechip},	{2.0}, {0.0},	{0.0}, {OnOff} },
 };
 
 static menu_t AdvSoundMenu =
 {
 	"ADVANCED SOUND OPTIONS",
-	1,
+	0,
 	countof(AdvSoundItems),
 	0,
 	AdvSoundItems,
diff --git a/src/oplsynth/mlopl.cpp b/src/oplsynth/mlopl.cpp
index 6cd589c60..0622962e9 100644
--- a/src/oplsynth/mlopl.cpp
+++ b/src/oplsynth/mlopl.cpp
@@ -208,7 +208,8 @@ struct OP2instrEntry *musicBlock::getInstrument(uint channel, uchar note)
 		if (note < 35 || note > 81)
 			return NULL;		/* wrong percussion number */
 		instrnumber = note + (128-35);
-	} else
+	}
+	else
 	{
 		instrnumber = driverdata.channelInstr[channel];
 	}
@@ -295,15 +296,14 @@ void musicBlock::OPLchangeControl(uint channel, uchar controller, int value)
 {
 	uint i;
 	uint id = channel;
-	struct OPLdata *data = &driverdata;
 
 	switch (controller)
 	{
 	case ctrlPatch:			/* change instrument */
-		data->channelInstr[channel] = value;
+		OPLprogramChange(channel, value);
 		break;
 	case ctrlModulation:
-		data->channelModulation[channel] = value;
+		driverdata.channelModulation[channel] = value;
 		for(i = 0; i < io->OPLchannels; i++)
 		{
 			struct channelEntry *ch = &channels[i];
@@ -325,7 +325,7 @@ void musicBlock::OPLchangeControl(uint channel, uchar controller, int value)
 		}
 		break;
 	case ctrlVolume:		/* change volume */
-		data->channelVolume[channel] = value;
+		driverdata.channelVolume[channel] = value;
 		for(i = 0; i < io->OPLchannels; i++)
 		{
 			struct channelEntry *ch = &channels[i];
@@ -338,7 +338,7 @@ void musicBlock::OPLchangeControl(uint channel, uchar controller, int value)
 		}
 		break;
 	case ctrlPan:			/* change pan (balance) */
-		data->channelPan[channel] = value -= 64;
+		driverdata.channelPan[channel] = value -= 64;
 		for(i = 0; i < io->OPLchannels; i++)
 		{
 			struct channelEntry *ch = &channels[i];
@@ -350,22 +350,26 @@ void musicBlock::OPLchangeControl(uint channel, uchar controller, int value)
 		}
 		break;
 	case ctrlSustainPedal:		/* change sustain pedal (hold) */
-		data->channelSustain[channel] = value;
+		driverdata.channelSustain[channel] = value;
 		if (value < 0x40)
 			releaseSustain(channel);
 		break;
 	}
 }
 
+void musicBlock::OPLprogramChange(uint channel, int value)
+{
+	driverdata.channelInstr[channel] = value;
+}
 
-void musicBlock::OPLplayMusic()
+void musicBlock::OPLplayMusic(int vol)
 {
 	uint i;
 	struct OPLdata *data = &driverdata;
 
 	for (i = 0; i < CHANNELS; i++)
 	{
-		data->channelVolume[i] = 127;	/* default volume 127 (full volume) */
+		data->channelVolume[i] = vol;	/* default volume 127 for MUS (full volume) */
 		data->channelSustain[i] = 0;
 		data->channelLastVolume[i] = 64;
 		data->channelPitch[i] = 64;
diff --git a/src/oplsynth/music_opl_mididevice.cpp b/src/oplsynth/music_opl_mididevice.cpp
new file mode 100644
index 000000000..e8383c371
--- /dev/null
+++ b/src/oplsynth/music_opl_mididevice.cpp
@@ -0,0 +1,481 @@
+/*
+** music_opl_mididevice.cpp
+** Provides an emulated OPL implementation of a MIDI output 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.
+**---------------------------------------------------------------------------
+**
+** Uses Vladimir Arnost's MUS player library.
+*/
+
+// HEADER FILES ------------------------------------------------------------
+
+#include "i_musicinterns.h"
+#include "templates.h"
+#include "doomdef.h"
+#include "m_swap.h"
+#include "w_wad.h"
+
+// MACROS ------------------------------------------------------------------
+
+#if defined(_DEBUG) && defined(_WIN32)
+#define UNIMPL(m,c,s,t) \
+	{ char foo[128]; sprintf(foo, m, c, s, t); OutputDebugString(foo); }
+#else
+#define UNIMPL(m,c,s,t)
+#endif
+
+// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
+
+// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
+
+// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
+
+// EXTERNAL DATA DECLARATIONS ----------------------------------------------
+
+extern OPLmusicBlock *BlockForStats;
+
+// PRIVATE DATA DEFINITIONS ------------------------------------------------
+
+// PUBLIC DATA DEFINITIONS -------------------------------------------------
+
+// CODE --------------------------------------------------------------------
+
+//==========================================================================
+//
+// OPLMIDIDevice Contructor
+//
+//==========================================================================
+
+OPLMIDIDevice::OPLMIDIDevice()
+{
+	Stream = NULL;
+	Tempo = 0;
+	Division = 0;
+	Events = NULL;
+	Started = false;
+
+	FWadLump data = Wads.OpenLumpName("GENMIDI");
+	OPLloadBank(data);
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice Destructor
+//
+//==========================================================================
+
+OPLMIDIDevice::~OPLMIDIDevice()
+{
+	Close();
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: Open
+//
+// Returns 0 on success.
+//
+//==========================================================================
+
+int OPLMIDIDevice::Open(void (*callback)(UINT, void *, DWORD, DWORD), void *userdata)
+{
+	if (io == NULL || io->OPLinit(TwoChips + 1, uint(OPL_SAMPLE_RATE)))
+	{
+		return 1;
+	}
+
+	Stream = GSnd->CreateStream(FillStream, int(OPL_SAMPLE_RATE / 14) * 4,
+		SoundStream::Mono | SoundStream::Float, int(OPL_SAMPLE_RATE), this);
+	if (Stream == NULL)
+	{
+		return 2;
+	}
+
+	Callback = callback;
+	CallbackData = userdata;
+	Tempo = 500000;
+	Division = 100;
+	CalcTickRate();
+
+	OPLstopMusic();
+	OPLplayMusic(100);
+
+	return 0;
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: Close
+//
+//==========================================================================
+
+void OPLMIDIDevice::Close()
+{
+	if (Stream != NULL)
+	{
+		delete Stream;
+		Stream = NULL;
+	}
+	io->OPLdeinit();
+	Started = false;
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: IsOpen
+//
+//==========================================================================
+
+bool OPLMIDIDevice::IsOpen() const
+{
+	return Stream != NULL;
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: GetTechnology
+//
+//==========================================================================
+
+int OPLMIDIDevice::GetTechnology() const
+{
+	return MOD_FMSYNTH;
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: SetTempo
+//
+//==========================================================================
+
+int OPLMIDIDevice::SetTempo(int tempo)
+{
+	Tempo = tempo;
+	CalcTickRate();
+	return 0;
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: SetTimeDiv
+//
+//==========================================================================
+
+int OPLMIDIDevice::SetTimeDiv(int timediv)
+{
+	Division = timediv;
+	CalcTickRate();
+	return 0;
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: CalcTickRate
+//
+// Tempo is the number of microseconds per quarter note.
+// Division is the number of ticks per quarter note.
+//
+//==========================================================================
+
+void OPLMIDIDevice::CalcTickRate()
+{
+	SamplesPerTick = OPL_SAMPLE_RATE / (1000000.0 / Tempo) / Division;
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: Resume
+//
+//==========================================================================
+
+int OPLMIDIDevice::Resume()
+{
+	if (!Started)
+	{
+		if (Stream->Play(true, 1, false))
+		{
+			Started = true;
+			BlockForStats = this;
+			return 0;
+		}
+		return 1;
+	}
+	return 0;
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: Stop
+//
+//==========================================================================
+
+void OPLMIDIDevice::Stop()
+{
+	if (Started)
+	{
+		Stream->Stop();
+		Started = false;
+		BlockForStats = NULL;
+	}
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: StreamOut
+//
+//==========================================================================
+
+int OPLMIDIDevice::StreamOut(MIDIHDR *header)
+{
+	Serialize();
+	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;
+	}
+	Unserialize();
+	return 0;
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: PrepareHeader
+//
+//==========================================================================
+
+int OPLMIDIDevice::PrepareHeader(MIDIHDR *header)
+{
+	return 0;
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: UnprepareHeader
+//
+//==========================================================================
+
+int OPLMIDIDevice::UnprepareHeader(MIDIHDR *header)
+{
+	return 0;
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: FakeVolume
+//
+// Since the OPL output is rendered as a normal stream, its volume is
+// controlled through the GSnd interface, not here.
+//
+//==========================================================================
+
+bool OPLMIDIDevice::FakeVolume()
+{
+	return false;
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: Pause
+//
+//==========================================================================
+
+bool OPLMIDIDevice::Pause(bool paused)
+{
+	if (Stream != NULL)
+	{
+		return Stream->SetPaused(paused);
+	}
+	return true;
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: PlayTick
+//
+// event[0] = delta time
+// event[1] = unused
+// event[2] = event
+//
+//==========================================================================
+
+int OPLMIDIDevice::PlayTick()
+{
+	DWORD delay = 0;
+
+	while (delay == 0 && Events != NULL)
+	{
+		DWORD *event = (DWORD *)(Events->lpData + Position);
+		if (MEVT_EVENTTYPE(event[2]) == MEVT_TEMPO)
+		{
+			Tempo = MEVT_EVENTPARM(event[2]);
+			CalcTickRate();
+		}
+		else if (MEVT_EVENTTYPE(event[2]) == MEVT_LONGMSG)
+		{ // Should I handle master volume changes?
+		}
+		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;
+			HandleEvent(status, parm1, parm2);
+		}
+
+		// 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;
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: HandleEvent
+//
+// Processes a normal MIDI event.
+//
+//==========================================================================
+
+void OPLMIDIDevice::HandleEvent(int status, int parm1, int parm2)
+{
+	int command = status & 0xF0;
+	int channel = status & 0x0F;
+
+	// Swap channels 9 and 15, because their roles are reversed
+	// in MUS and MIDI formats.
+	if (channel == 9)
+	{
+		channel = 15;
+	}
+	else if (channel == 15)
+	{
+		channel = 9;
+	}
+
+	switch (command)
+	{
+	case MIDI_NOTEOFF:
+		playingcount--;
+		OPLreleaseNote(channel, parm1);
+		break;
+
+	case MIDI_NOTEON:
+		playingcount++;
+		OPLplayNote(channel, parm1, parm2);
+		break;
+
+	case MIDI_POLYPRESS:
+		UNIMPL("Unhandled note aftertouch: Channel %d, note %d, value %d\n", channel, parm1, parm2);
+		break;
+
+	case MIDI_CTRLCHANGE:
+		switch (parm1)
+		{
+		case 0:		OPLchangeControl(channel, ctrlBank, parm2);			break;
+		case 1:		OPLchangeControl(channel, ctrlModulation, parm2);	break;
+		case 7:		OPLchangeControl(channel, ctrlVolume, parm2);		break;
+		case 10:	OPLchangeControl(channel, ctrlPan, parm2);			break;
+		case 11:	OPLchangeControl(channel, ctrlExpression, parm2);	break;
+		case 64:	OPLchangeControl(channel, ctrlSustainPedal, parm2);	break;
+		case 67:	OPLchangeControl(channel, ctrlSoftPedal, parm2);	break;
+		case 91:	OPLchangeControl(channel, ctrlReverb, parm2);		break;
+		case 93:	OPLchangeControl(channel, ctrlChorus, parm2);		break;
+		case 120:	OPLchangeControl(channel, ctrlSoundsOff, parm2);	break;
+		case 121:	OPLchangeControl(channel, ctrlResetCtrls, parm2);	break;
+		case 123:	OPLchangeControl(channel, ctrlNotesOff, parm2);		break;
+		case 126:	OPLchangeControl(channel, ctrlMono, parm2);			break;
+		case 127:	OPLchangeControl(channel, ctrlPoly, parm2);			break;
+		default:
+			UNIMPL("Unhandled controller: Channel %d, controller %d, value %d\n", channel, parm1, parm2);
+			break;
+		}
+		break;
+
+	case MIDI_PRGMCHANGE:
+		OPLprogramChange(channel, parm1);
+		break;
+
+	case MIDI_CHANPRESS:
+		UNIMPL("Unhandled channel aftertouch: Channel %d, value %d\n", channel, parm1, 0);
+		break;
+
+	case MIDI_PITCHBEND:
+		// MUS pitch is 8 bit, but MIDI pitch is 14-bit
+		OPLpitchWheel(channel, (parm1 | (parm2 >> 1)) >> (14 - 8));
+		break;
+	}
+}
+
+//==========================================================================
+//
+// OPLMIDIDevice :: FillStream										static
+//
+//==========================================================================
+
+bool OPLMIDIDevice::FillStream(SoundStream *stream, void *buff, int len, void *userdata)
+{
+	OPLMIDIDevice *device = (OPLMIDIDevice *)userdata;
+	return device->ServiceStream (buff, len);
+}
diff --git a/src/oplsynth/muslib.h b/src/oplsynth/muslib.h
index 4a6d34e2f..7128ccdc8 100644
--- a/src/oplsynth/muslib.h
+++ b/src/oplsynth/muslib.h
@@ -199,7 +199,8 @@ struct musicBlock {
 	void OPLreleaseNote(uint channel, uchar note);
 	void OPLpitchWheel(uint channel, int pitch);
 	void OPLchangeControl(uint channel, uchar controller, int value);
-	void OPLplayMusic();
+	void OPLprogramChange(uint channel, int value);
+	void OPLplayMusic(int vol);
 	void OPLstopMusic();
 	void OPLchangeVolume(uint volume);
 
diff --git a/src/oplsynth/opl_mus_player.cpp b/src/oplsynth/opl_mus_player.cpp
index 3fde91a2d..22ad6cc9a 100644
--- a/src/oplsynth/opl_mus_player.cpp
+++ b/src/oplsynth/opl_mus_player.cpp
@@ -19,7 +19,7 @@
 
 EXTERN_CVAR (Bool, opl_onechip)
 
-static OPLmusicBlock *BlockForStats;
+OPLmusicBlock *BlockForStats;
 
 OPLmusicBlock::OPLmusicBlock()
 {
@@ -55,17 +55,18 @@ OPLmusicBlock::~OPLmusicBlock()
 	delete io;
 }
 
-void OPLmusicBlock::ResetChips ()
+void OPLmusicBlock::Serialize()
 {
-	TwoChips = !opl_onechip;
 #ifdef _WIN32
 	EnterCriticalSection (&ChipAccess);
 #else
 	if (SDL_mutexP (ChipAccess) != 0)
 		return;
 #endif
-	io->OPLdeinit ();
-	io->OPLinit (TwoChips + 1, uint(OPL_SAMPLE_RATE));
+}
+
+void OPLmusicBlock::Unserialize()
+{
 #ifdef _WIN32
 	LeaveCriticalSection (&ChipAccess);
 #else
@@ -73,10 +74,19 @@ void OPLmusicBlock::ResetChips ()
 #endif
 }
 
+void OPLmusicBlock::ResetChips ()
+{
+	TwoChips = !opl_onechip;
+	Serialize();
+	io->OPLdeinit ();
+	io->OPLinit (TwoChips + 1, uint(OPL_SAMPLE_RATE));
+	Unserialize();
+}
+
 void OPLmusicBlock::Restart()
 {
 	OPLstopMusic ();
-	OPLplayMusic ();
+	OPLplayMusic (127);
 	MLtime = 0;
 	playingcount = 0;
 }
@@ -229,15 +239,12 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
 	samples1 = samples;
 	memset(buff, 0, numbytes);
 
-#ifdef _WIN32
-	EnterCriticalSection (&ChipAccess);
-#else
-	if (SDL_mutexP (ChipAccess) != 0)
-		return true;
-#endif
+	Serialize();
 	while (numsamples > 0)
 	{
-		int samplesleft = MIN (numsamples, int(NextTickIn));
+		double ticky = NextTickIn;
+		int tick_in = int(NextTickIn);
+		int samplesleft = MIN(numsamples, tick_in);
 
 		if (samplesleft > 0)
 		{
@@ -246,6 +253,7 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
 			{
 				YM3812UpdateOne (1, samples1, samplesleft);
 			}
+			assert(NextTickIn == ticky);
 			NextTickIn -= samplesleft;
 			assert (NextTickIn >= 0);
 			numsamples -= samplesleft;
@@ -254,7 +262,8 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
 		
 		if (NextTickIn < 1)
 		{
-			int next = PlayTick ();
+			int next = PlayTick();
+			assert(next >= 0);
 			if (next == 0)
 			{ // end of song
 				if (!Looping || prevEnded)
@@ -286,11 +295,7 @@ bool OPLmusicBlock::ServiceStream (void *buff, int numbytes)
 			}
 		}
 	}
-#ifdef _WIN32
-	LeaveCriticalSection (&ChipAccess);
-#else
-	SDL_mutexV (ChipAccess);
-#endif
+	Unserialize();
 	return res;
 }
 
@@ -447,7 +452,7 @@ OPLmusicWriter::OPLmusicWriter (const char *songname, const char *filename)
 	io = new DiskWriterIO ();
 	if (((DiskWriterIO *)io)->OPLinit (filename) == 0)
 	{
-		OPLplayMusic ();
+		OPLplayMusic (127);
 		score = scoredata + ((MUSheader *)scoredata)->scoreStart;
 		Go ();
 	}
diff --git a/src/oplsynth/opl_mus_player.h b/src/oplsynth/opl_mus_player.h
index 30cc6f1ba..c82b6e952 100644
--- a/src/oplsynth/opl_mus_player.h
+++ b/src/oplsynth/opl_mus_player.h
@@ -25,6 +25,9 @@ public:
 protected:
 	virtual int PlayTick() = 0;
 
+	void Serialize();
+	void Unserialize();
+
 	double NextTickIn;
 	double SamplesPerTick;
 	bool TwoChips;
diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp
index ef597fa74..4fcdc57f0 100644
--- a/src/sound/i_music.cpp
+++ b/src/sound/i_music.cpp
@@ -305,6 +305,7 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le
 			- OPL: 
 				- if explicitly selected by $mididevice 
 				- when opl_enable is true and no midi device is set for the song
+				- when snd_mididevice  is -3 and no midi device is set for the song
 
 			  Timidity: 
 				- if explicitly selected by $mididevice 
@@ -320,7 +321,7 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le
 				- 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 ((opl_enable && device == MDEV_DEFAULT) || device == MDEV_OPL)
+			if (((opl_enable || snd_mididevice == -3) && device == MDEV_DEFAULT) || device == MDEV_OPL)
 			{
 				info = new OPLMUSSong (file, musiccache, len);
 			}
@@ -379,10 +380,13 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le
 		if (id == MAKE_ID('M','T','h','d'))
 		{
 			// This is a midi file
-			// MIDI can't be played with OPL so use default.
-			if (device == MDEV_OPL) device = MDEV_DEFAULT;
 
 			/*	MIDI are played as:
+			  OPL: 
+				- if explicitly selected by $mididevice 
+				- when opl_enable is true and no midi device is set for the song
+				- 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
@@ -397,21 +401,24 @@ void *I_RegisterSong (const char *filename, char *musiccache, int offset, int le
 				- 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_TIMIDITY || (snd_mididevice == -2 && device == MDEV_DEFAULT)) && GSnd != NULL)
+			if ((device == MDEV_OPL || (snd_mididevice == -3 && device == MDEV_DEFAULT)) && GSnd != NULL)
+			{
+				info = new MIDISong2 (file, musiccache, len, true);
+			}
+			else if ((device == MDEV_TIMIDITY || (snd_mididevice == -2 && device == MDEV_DEFAULT)) && GSnd != NULL)
 			{
 				info = new TimiditySong (file, musiccache, len);
-				if (!info->IsValid())
-				{
-					delete info;
-					info = NULL;
-					device = MDEV_DEFAULT;
-				}
+			}
+			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);
+				info = new MIDISong2 (file, musiccache, len, false);
 			}
 #endif // _WIN32
 		}
diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h
index 883cc8573..a26992747 100644
--- a/src/sound/i_musicinterns.h
+++ b/src/sound/i_musicinterns.h
@@ -66,8 +66,28 @@ struct MIDIHDR
 	BYTE *lpData;
 	DWORD dwBufferLength;
 	DWORD dwBytesRecorded;
-	MIDIHDR *Next;
+	MIDIHDR *lpNext;
 };
+
+enum
+{
+	MOD_MIDIPORT = 1,
+	MOD_SYNTH,
+	MOD_SQSYNTH,
+	MOD_FMSYNTH,
+	MOD_MAPPER,
+	MOD_WAVETABLE,
+	MOD_SWSYNTH
+};
+
+#define MEVT_TEMPO			((BYTE)1)
+#define MEVT_NOP			((BYTE)2)
+#define MEVT_LONGMSG		((BYTE)128)
+
+#define MEVT_EVENTTYPE(x)	((BYTE)((x) >> 24))
+#define MEVT_EVENTPARM(x)   ((x) & 0xffffff)
+
+#define MOM_DONE			969
 #endif
 
 class MIDIDevice
@@ -87,6 +107,8 @@ public:
 	virtual void Stop() = 0;
 	virtual int PrepareHeader(MIDIHDR *data) = 0;
 	virtual int UnprepareHeader(MIDIHDR *data) = 0;
+	virtual bool FakeVolume() = 0;
+	virtual bool Pause(bool paused) = 0;
 };
 
 // WinMM implementation of a MIDI output device -----------------------------
@@ -107,6 +129,8 @@ public:
 	void Stop();
 	int PrepareHeader(MIDIHDR *data);
 	int UnprepareHeader(MIDIHDR *data);
+	bool FakeVolume();
+	bool Pause(bool paused);
 
 protected:
 	static void CALLBACK CallbackFunc(HMIDIOUT, UINT, DWORD_PTR, DWORD, DWORD);
@@ -138,10 +162,25 @@ public:
 	void Stop();
 	int PrepareHeader(MIDIHDR *data);
 	int UnprepareHeader(MIDIHDR *data);
+	bool FakeVolume();
+	bool Pause(bool paused);
 
 protected:
+	static bool FillStream(SoundStream *stream, void *buff, int len, void *userdata);
+
 	void (*Callback)(unsigned int, void *, DWORD, DWORD);
 	void *CallbackData;
+
+	void CalcTickRate();
+	void HandleEvent(int status, int parm1, int parm2);
+	int PlayTick();
+
+	SoundStream *Stream;
+	double Tempo;
+	double Division;
+	MIDIHDR *Events;
+	bool Started;
+	DWORD Position;
 };
 
 // Base class for streaming MUS and MIDI files ------------------------------
@@ -149,7 +188,7 @@ protected:
 class MIDIStreamer : public MusInfo
 {
 public:
-	MIDIStreamer();
+	MIDIStreamer(bool opl);
 	~MIDIStreamer();
 
 	void MusicVolumeChanged();
@@ -208,6 +247,7 @@ protected:
 	int InitialTempo;
 	BYTE ChannelVolumes[16];
 	DWORD Volume;
+	bool UseOPLDevice;
 };
 
 // MUS file played with a MIDI stream ---------------------------------------
@@ -235,7 +275,7 @@ protected:
 class MIDISong2 : public MIDIStreamer
 {
 public:
-	MIDISong2 (FILE *file, char *musiccache, int length);
+	MIDISong2 (FILE *file, char *musiccache, int length, bool opl);
 	~MIDISong2 ();
 
 protected:
diff --git a/src/sound/music_midi_base.cpp b/src/sound/music_midi_base.cpp
index f168e53cf..e85919a79 100644
--- a/src/sound/music_midi_base.cpp
+++ b/src/sound/music_midi_base.cpp
@@ -21,7 +21,7 @@ CUSTOM_CVAR (Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
 	if (!nummididevicesset)
 		return;
 
-	if ((self >= (signed)nummididevices) || (self < -2))
+	if ((self >= (signed)nummididevices) || (self < -3))
 	{
 		Printf ("ID out of range. Using default device.\n");
 		self = 0;
@@ -76,43 +76,38 @@ void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues)
 {
 	if (*outValues == NULL)
 	{
-		int count = 1 + nummididevices + (nummididevices > 0);
+		int count = 3 + nummididevices;
 		value_t *values;
+		UINT id;
+		int p;
 
 		*outValues = values = new value_t[count];
 
-		values[0].name = "TiMidity++";
-		values[0].value = -2.0;
-		values[1].name = "FMOD";
-		values[1].value = -1.0;
-		if (nummididevices > 0)
+		values[0].name = "OPL Synth Emulation";
+		values[0].value = -3.0;
+		values[1].name = "TiMidity++";
+		values[1].value = -2.0;
+		values[2].name = "FMOD";
+		values[2].value = -1.0;
+		for (id = 0, p = 3; id < nummididevices; ++id)
 		{
-			UINT id;
-			int p;
+			MIDIOUTCAPS caps;
+			MMRESULT res;
 
-			for (id = 0, p = 2; id < nummididevices; ++id)
+			res = midiOutGetDevCaps (id, &caps, sizeof(caps));
+			if (res == MMSYSERR_NOERROR)
 			{
-				MIDIOUTCAPS caps;
-				MMRESULT res;
+				size_t len = strlen (caps.szPname) + 1;
+				char *name = new char[len];
 
-				res = midiOutGetDevCaps (id, &caps, sizeof(caps));
-				if (res == MMSYSERR_NOERROR)
-				{
-					size_t len = strlen (caps.szPname) + 1;
-					char *name = new char[len];
-
-					memcpy (name, caps.szPname, len);
-					values[p].name = name;
-					values[p].value = (float)id;
-					++p;
-				}
+				memcpy (name, caps.szPname, len);
+				values[p].name = name;
+				values[p].value = (float)id;
+				++p;
 			}
-			*numValues = (float)p;
-		}
-		else
-		{
-			*numValues = 2.f;
 		}
+		assert(p == count);
+		*numValues = float(count);
 	}
 }
 
@@ -185,8 +180,8 @@ CCMD (snd_listmididevices)
 
 CUSTOM_CVAR(Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
 {
-	if (self < -2)
-		self = -2;
+	if (self < -3)
+		self = -3;
 	else if (self > -1)
 		self = -1;
 }
@@ -195,16 +190,17 @@ void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues)
 {
 	if (*outValues == NULL)
 	{
-		int count = 1 + nummididevices + (nummididevices > 0);
 		value_t *values;
 
-		*outValues = values = new value_t[count];
+		*outValues = values = new value_t[3];
 
-		values[0].name = "TiMidity++";
-		values[0].value = -2.0;
-		values[1].name = "FMOD";
-		values[1].value = -1.0;
-		*numValues = 2.f;
+		values[0].name = "OPL Synth Emulation";
+		values[0].value = -3.0;
+		values[1].name = "TiMidity++";
+		values[1].value = -2.0;
+		values[2].name = "FMOD";
+		values[2].value = -1.0;
+		*numValues = 3.f;
 	}
 }
 
diff --git a/src/sound/music_midi_midiout.cpp b/src/sound/music_midi_midiout.cpp
index 1993a2c4b..333c15740 100644
--- a/src/sound/music_midi_midiout.cpp
+++ b/src/sound/music_midi_midiout.cpp
@@ -87,8 +87,6 @@ struct MIDISong2::TrackInfo
 
 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
 
-extern UINT mididevice;
-
 // PRIVATE DATA DEFINITIONS ------------------------------------------------
 
 static BYTE EventLengths[7] = { 2, 2, 2, 2, 1, 1, 2 };
@@ -106,8 +104,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)
-: MusHeader(0), Tracks(0)
+MIDISong2::MIDISong2 (FILE *file, char *musiccache, int len, bool opl)
+: MIDIStreamer(opl), MusHeader(0), Tracks(0)
 {
 	int p;
 	int i;
diff --git a/src/sound/music_midistream.cpp b/src/sound/music_midistream.cpp
index 087ce2c8f..fa959c2ab 100644
--- a/src/sound/music_midistream.cpp
+++ b/src/sound/music_midistream.cpp
@@ -69,9 +69,9 @@ extern UINT mididevice;
 //
 //==========================================================================
 
-MIDIStreamer::MIDIStreamer()
+MIDIStreamer::MIDIStreamer(bool opl)
 : MIDI(0), PlayerThread(0), ExitEvent(0), BufferDoneEvent(0),
-  Division(0), InitialTempo(500000)
+  Division(0), InitialTempo(500000), UseOPLDevice(opl)
 {
 	BufferDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
 	if (BufferDoneEvent == NULL)
@@ -164,7 +164,14 @@ void MIDIStreamer::Play (bool looping)
 	InitialPlayback = true;
 
 	assert(MIDI == NULL);
-	MIDI = new WinMIDIDevice(mididevice);
+	if (UseOPLDevice)
+	{
+		MIDI = new OPLMIDIDevice;
+	}
+	else
+	{
+		MIDI = new WinMIDIDevice(mididevice);
+	}
 
 	if (0 != MIDI->Open(Callback, this))
 	{
@@ -251,7 +258,10 @@ void MIDIStreamer::Pause ()
 	if (m_Status == STATE_Playing)
 	{
 		m_Status = STATE_Paused;
-		OutputVolume(0);
+		if (!MIDI->Pause(true))
+		{
+			OutputVolume(0);
+		}
 	}
 }
 
@@ -268,7 +278,10 @@ void MIDIStreamer::Resume ()
 {
 	if (m_Status == STATE_Paused)
 	{
-		OutputVolume(Volume);
+		if (!MIDI->Pause(false))
+		{
+			OutputVolume(Volume);
+		}
 		m_Status = STATE_Playing;
 	}
 }
@@ -328,12 +341,18 @@ bool MIDIStreamer::IsPlaying ()
 
 void MIDIStreamer::MusicVolumeChanged ()
 {
-	float realvolume = clamp<float>(snd_musicvolume * relative_volume, 0.f, 1.f);
-	DWORD onechanvol = clamp<DWORD>((DWORD)(realvolume * 65535.f), 0, 65535);
-	Volume = onechanvol;
+	if (MIDI->FakeVolume())
+	{
+		float realvolume = clamp<float>(snd_musicvolume * relative_volume, 0.f, 1.f);
+		Volume = clamp<DWORD>((DWORD)(realvolume * 65535.f), 0, 65535);
+	}
+	else
+	{
+		Volume = 0xFFFF;
+	}
 	if (m_Status == STATE_Playing)
 	{
-		OutputVolume(onechanvol);
+		OutputVolume(Volume);
 	}
 }
 
@@ -347,8 +366,11 @@ void MIDIStreamer::MusicVolumeChanged ()
 
 void MIDIStreamer::OutputVolume (DWORD volume)
 {
-	NewVolume = volume;
-	VolumeChanged = true;
+	if (MIDI->FakeVolume())
+	{
+		NewVolume = volume;
+		VolumeChanged = true;
+	}
 }
 
 //==========================================================================
diff --git a/src/sound/music_mus_midiout.cpp b/src/sound/music_mus_midiout.cpp
index 3fb8b9d9c..d2ee0dd81 100644
--- a/src/sound/music_mus_midiout.cpp
+++ b/src/sound/music_mus_midiout.cpp
@@ -42,8 +42,6 @@
 
 // MACROS ------------------------------------------------------------------
 
-#define MAX_TIME		(140/20)	// Each stream buffer lasts only 1/20 of a second
-
 // TYPES -------------------------------------------------------------------
 
 // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
@@ -91,7 +89,7 @@ static const BYTE CtrlTranslate[15] =
 //==========================================================================
 
 MUSSong2::MUSSong2 (FILE *file, char *musiccache, int len)
-: MusHeader(0), MusBuffer(0)
+: MIDIStreamer(false), MusHeader(0), MusBuffer(0)
 {
 	if (ExitEvent == NULL)
 	{
diff --git a/src/sound/music_win_mididevice.cpp b/src/sound/music_win_mididevice.cpp
index fb4f7378f..f164f6a2e 100644
--- a/src/sound/music_win_mididevice.cpp
+++ b/src/sound/music_win_mididevice.cpp
@@ -203,6 +203,20 @@ void WinMIDIDevice::Stop()
 	}
 }
 
+//==========================================================================
+//
+// WinMIDIDevice :: Pause
+//
+// Some docs claim pause is unreliable and can cause the stream to stop
+// functioning entirely. Truth or fiction?
+//
+//==========================================================================
+
+bool WinMIDIDevice::Pause(bool paused)
+{
+	return false;
+}
+
 //==========================================================================
 //
 // WinMIDIDevice :: StreamOut
@@ -236,6 +250,20 @@ int WinMIDIDevice::UnprepareHeader(MIDIHDR *header)
 	return midiOutUnprepareHeader((HMIDIOUT)MidiOut, header, sizeof(MIDIHDR));
 }
 
+//==========================================================================
+//
+// WinMIDIDevice :: FakeVolume
+//
+// Because there are too many MIDI devices out there that don't support
+// global volume changes, fake the volume for all of them.
+//
+//==========================================================================
+
+bool WinMIDIDevice::FakeVolume()
+{
+	return true;
+}
+
 //==========================================================================
 //
 // WinMIDIDevice :: CallbackFunc									static
diff --git a/zdoom.vcproj b/zdoom.vcproj
index b470fc18e..0337faee4 100644
--- a/zdoom.vcproj
+++ b/zdoom.vcproj
@@ -2840,6 +2840,10 @@
 				RelativePath="src\sound\music_mus_opl.cpp"
 				>
 			</File>
+			<File
+				RelativePath=".\src\oplsynth\music_opl_mididevice.cpp"
+				>
+			</File>
 			<File
 				RelativePath="src\sound\music_spc.cpp"
 				>