From 50a6334e3713d8647553aa8d1809c0243633cb60 Mon Sep 17 00:00:00 2001
From: Shlomi Fish <shlomif@shlomifish.org>
Date: Sat, 4 May 2019 17:02:12 +0300
Subject: [PATCH] Forward patch of MIDI in functionality.

From https://sourceforge.net/p/dosbox/patches/193/ .

Commissioned by Tempus2 - thanks!

-----

Written by Shlomi Fish ( https://www.shlomifish.org/ ).

I hereby disclaim any implicit or explicit ownership of my changes in this
changeset, and put them under a multiple licence consisting of your choice of
one of more of:

- The CC0 / Public Domain - https://creativecommons.org/choose/zero/ .

- The MIT / Expat license - https://en.wikipedia.org/wiki/MIT_License

- The default licence of your project

- The https://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License - version
2.1 or higher

- The https://en.wikipedia.org/wiki/GNU_General_Public_License - version 2 or
higher

- Any licence in the 2018-Aug-27 popular licenses list of
https://opensource.org/licenses

- The https://en.wikipedia.org/wiki/Apache_License version 2.0 or later

- The https://en.wikipedia.org/wiki/Artistic_License version 2.0 or later

- The https://en.wikipedia.org/wiki/ISC_license

- The https://opensource.org/licenses/BSD-2-Clause

Crediting me will be nice, but not mandatory, and you can change the licence
of the project without needing my permission.
---
 README                    |   28 +
 include/midi.h            |   51 +-
 src/dosbox.cpp            |    2 +
 src/gui/midi.cpp          |  260 ++++++--
 src/gui/midi_alsa.h       |  162 ++++-
 src/gui/midi_win32.h      |   89 ++-
 src/hardware/gus.cpp      |  134 +++-
 src/hardware/mpu401.cpp   | 1294 +++++++++++++++++++++++++++++--------
 src/hardware/sblaster.cpp |  148 ++++-
 9 files changed, 1814 insertions(+), 354 deletions(-)

diff --git a/README b/README
index 5323229f..395f2b66 100644
--- a/README
+++ b/README
@@ -103,6 +103,20 @@ FULLSCREEN: How do I change to fullscreen?
     aspect in the configuration file of DOSBox. To get back from fullscreen
     mode: Press alt-enter again.
 
+Q: MIDI output or input is not working
+A: Check your MIDI device configuration in Windows Control Panel. To manually
+    choose a device, modify "config" parameter in [midi] section. First value is
+    id number of a MIDI output device, followed by comma and output device.
+    By default, Dosbox will try to automatically select output device. To override,
+    try with "config=0" (on windows). Input is disabled by default unless you change the
+    "inconfig" parameter. If unsure, try with "inconfig=0" to enable MIDI input.
+    To force a MIDI input or output handler, try for example "device=default,win32".
+    Linux and OS X MIDI input are currently not supported.
+    With the "midioptions" setting you can select emulated MIDI device which receives
+    MIDI input. Available devices are: inputmpu401, inputsbuart, inputgus and inputauto.
+    Other options are separated by a comma: "norealtime" option disables output
+    of realtime MIDI messages, "passthrough" enables MIDI passthrough and "clockout"
+    allows output of MIDI clock realtime messages (only works without "norealtime").
 
 FULLSCREEN: My fullscreen is too large.
     This is can be a problem on Windows 10, if you have display scaling
@@ -155,6 +169,20 @@ CD-ROM: My CD-ROM doesn't work.
                           for CD audio, otherwise it is ignored)
     See also the next question: The game/application can't find its CD-ROM.
 
+Q: MIDI output or input is not working
+A: Check your MIDI device configuration in Windows Control Panel. To manually
+    choose a device, modify "config" parameter in [midi] section. First value is
+    id number of a MIDI output device, followed by comma and output device.
+    By default, Dosbox will try to automatically select output device. To override,
+    try with "config=0" (on windows). Input is disabled by default unless you change the
+    "inconfig" parameter. If unsure, try with "inconfig=0" to enable MIDI input.
+    To force a MIDI input or output handler, try for example "device=default,win32".
+    Linux and OS X MIDI input are currently not supported.
+    With the "midioptions" setting you can select emulated MIDI device which receives
+    MIDI input. Available devices are: inputmpu401, inputsbuart, inputgus and inputauto.
+    Other options are separated by a comma: "norealtime" option disables output
+    of realtime MIDI messages, "passthrough" enables MIDI passthrough and "clockout"
+    allows output of MIDI clock realtime messages (only works without "norealtime").
 
 CD-ROM: The game/application can't find its CD-ROM.
     Be sure to mount the CD-ROM with -t cdrom switch, this will enable the
diff --git a/include/midi.h b/include/midi.h
index ea9afdad..0802d3eb 100644
--- a/include/midi.h
+++ b/include/midi.h
@@ -24,10 +24,13 @@
 #include "programs.h"
 #endif
 
+#include "SDL_thread.h"
+
 class MidiHandler {
 public:
 	MidiHandler();
-	virtual bool Open(const char * /*conf*/) { return true; };
+	virtual bool Open(const char * conf) { return true; };
+	virtual bool OpenInput(const char * inconf) {return false;};
 	virtual void Close(void) {};
 	virtual void PlayMsg(Bit8u * /*msg*/) {};
 	virtual void PlaySysex(Bit8u * /*sysex*/,Bitu /*len*/) {};
@@ -39,20 +42,56 @@ public:
 
 
 #define SYSEX_SIZE 8192
+#define MIDI_DEVS 4
+enum {MOUT_MPU,MOUT_SBUART,MOUT_GUS,MOUT_THRU};
+
+enum {MDEV_MPU,MDEV_SBUART,MDEV_GUS,MDEV_SB16,MDEV_NONE};
+
+void MIDI_RawOutByte(Bit8u data, Bit8u slot);
+void MIDI_RawOutRTByte(Bit8u data);
+void MIDI_RawOutThruRTByte(Bit8u data);
+void MIDI_ClearBuffer(Bit8u slot);
+bool MIDI_Available(void);
+
+void MPU401_InputMsg(Bit8u msg[4]);
+void SB_UART_InputMsg(Bit8u msg[4]);
+void GUS_UART_InputMsg(Bit8u msg[4]);
+
+Bits MPU401_InputSysex(Bit8u* buffer,Bitu len,bool abort);
+Bits SB_UART_InputSysex(Bit8u* buffer,Bitu len,bool abort);
+Bits GUS_UART_InputSysex(Bit8u* buffer,Bitu len,bool abort);
+
+void MPU401_SetupTxHandler(void);
+void MPU401_SetTx(bool status);
+
+void SB16_MPU401_IrqToggle(bool status);
+
+Bit32s MIDI_ToggleInputDevice(Bit32u device,bool status);
+
 struct DB_Midi {
-	Bitu status;
-	Bitu cmd_len;
-	Bitu cmd_pos;
-	Bit8u cmd_buf[8];
 	Bit8u rt_buf[8];
+		Bitu status[MIDI_DEVS];
+		Bitu cmd_r;
+	struct {
+		Bitu len;
+		Bitu pos;
+		Bit8u buf[8];
+	} cmd[MIDI_DEVS];
 	struct {
 		Bit8u buf[SYSEX_SIZE];
 		Bitu used;
 		Bitu delay;
 		Bit32u start;
-	} sysex;
+	} sysex[MIDI_DEVS];
 	bool available;
+	bool in_available;
 	MidiHandler * handler;
+	MidiHandler * in_handler;
+	bool realtime;
+	Bitu inputdev;
+	bool autoinput;
+	bool thruchan;
+	bool clockout;
 };
 
 extern DB_Midi midi;
diff --git a/src/dosbox.cpp b/src/dosbox.cpp
index 1e48915c..78b5a12d 100644
--- a/src/dosbox.cpp
+++ b/src/dosbox.cpp
@@ -569,6 +569,8 @@ void DOSBOX_Init(void) {
 	                  "In that case, add 'delaysysex', for example: midiconfig=2 delaysysex\n"
 	                  "See the README/Manual for more details.");
 
+	secprop->Add_string("inconfig", Property::Changeable::OnlyAtStart, "none");
+	secprop->Add_string("midioptions", Property::Changeable::OnlyAtStart, "autoinput");
 #if C_DEBUG
 	secprop=control->AddSection_prop("debug",&DEBUG_Init);
 #endif
diff --git a/src/gui/midi.cpp b/src/gui/midi.cpp
index b5361051..f119827c 100644
--- a/src/gui/midi.cpp
+++ b/src/gui/midi.cpp
@@ -34,8 +34,11 @@
 #include "hardware.h"
 #include "timer.h"
 
-#define RAWBUF	1024
 
+INLINE void MIDI_InputMsg(Bit8u msg[4]);
+INLINE Bits MIDI_InputSysex(Bit8u *sysex,Bitu len,bool abort);
+
+static Bit8u MIDI_InSysexBuf[SYSEX_SIZE];
 Bit8u MIDI_evt_len[256] = {
   0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x00
   0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x10
@@ -93,10 +96,21 @@ MidiHandler Midi_none;
 
 DB_Midi midi;
 
-void MIDI_RawOutByte(Bit8u data) {
-	if (midi.sysex.start) {
-		Bit32u passed_ticks = GetTicks() - midi.sysex.start;
-		if (passed_ticks < midi.sysex.delay) SDL_Delay(midi.sysex.delay - passed_ticks);
+void MIDI_RawOutRTByte(Bit8u data) {
+	if (!midi.realtime) return;
+	if (!midi.clockout && data == 0xf8) return;
+	midi.cmd_r=data<<24;
+	midi.handler->PlayMsg((Bit8u*)&midi.cmd_r);
+}
+
+void MIDI_RawOutThruRTByte(Bit8u data) {
+	if (midi.thruchan) MIDI_RawOutRTByte(data);
+}
+
+void MIDI_RawOutByte(Bit8u data, Bit8u slot) {
+	if (midi.sysex[slot].start) {
+		Bit32u passed_ticks = GetTicks() - midi.sysex[slot].start;
+		if (passed_ticks < midi.sysex[slot].delay) SDL_Delay(midi.sysex[slot].delay - passed_ticks);
 	}
 
 	/* Test for a realtime MIDI message */
@@ -106,55 +120,105 @@ void MIDI_RawOutByte(Bit8u data) {
 		return;
 	}
 	/* Test for a active sysex tranfer */
-	if (midi.status==0xf0) {
+	if (midi.status[slot]==0xf0) {
 		if (!(data&0x80)) {
-			if (midi.sysex.used<(SYSEX_SIZE-1)) midi.sysex.buf[midi.sysex.used++] = data;
+			if (midi.sysex[slot].used<(SYSEX_SIZE-1)) midi.sysex[slot].buf[midi.sysex[slot].used++]=data;
 			return;
 		} else {
-			midi.sysex.buf[midi.sysex.used++] = 0xf7;
+			midi.sysex[slot].buf[midi.sysex[slot].used++] = 0xf7;
 
-			if ((midi.sysex.start) && (midi.sysex.used >= 4) && (midi.sysex.used <= 9) && (midi.sysex.buf[1] == 0x41) && (midi.sysex.buf[3] == 0x16)) {
+			if ((midi.sysex[slot].start) && (midi.sysex[slot].used >= 4) && (midi.sysex[slot].used <= 9) && (midi.sysex[slot].buf[1] == 0x41) && (midi.sysex[slot].buf[3] == 0x16)) {
 				LOG(LOG_ALL,LOG_ERROR)("MIDI:Skipping invalid MT-32 SysEx midi message (too short to contain a checksum)");
 			} else {
 //				LOG(LOG_ALL,LOG_NORMAL)("Play sysex; address:%02X %02X %02X, length:%4d, delay:%3d", midi.sysex.buf[5], midi.sysex.buf[6], midi.sysex.buf[7], midi.sysex.used, midi.sysex.delay);
-				midi.handler->PlaySysex(midi.sysex.buf, midi.sysex.used);
-				if (midi.sysex.start) {
-					if (midi.sysex.buf[5] == 0x7F) {
-						midi.sysex.delay = 290; // All Parameters reset
-					} else if (midi.sysex.buf[5] == 0x10 && midi.sysex.buf[6] == 0x00 && midi.sysex.buf[7] == 0x04) {
-						midi.sysex.delay = 145; // Viking Child
-					} else if (midi.sysex.buf[5] == 0x10 && midi.sysex.buf[6] == 0x00 && midi.sysex.buf[7] == 0x01) {
-						midi.sysex.delay = 30; // Dark Sun 1
-					} else midi.sysex.delay = (Bitu)(((float)(midi.sysex.used) * 1.25f) * 1000.0f / 3125.0f) + 2;
-					midi.sysex.start = GetTicks();
+			midi.sysex[slot].buf[midi.sysex[slot].used++]=0xf7;
+			midi.handler->PlaySysex(midi.sysex[slot].buf,midi.sysex[slot].used);
+				if (midi.sysex[slot].start) {
+					if (midi.sysex[slot].buf[5] == 0x7F) {
+						midi.sysex[slot].delay = 290; // All Parameters reset
+					} else if (midi.sysex[slot].buf[5] == 0x10 && midi.sysex[slot].buf[6] == 0x00 && midi.sysex[slot].buf[7] == 0x04) {
+						midi.sysex[slot].delay = 145; // Viking Child
+					} else if (midi.sysex[slot].buf[5] == 0x10 && midi.sysex[slot].buf[6] == 0x00 && midi.sysex[slot].buf[7] == 0x01) {
+						midi.sysex[slot].delay = 30; // Dark Sun 1
+					} else midi.sysex[slot].delay = (Bitu)(((float)(midi.sysex[slot].used) * 1.25f) * 1000.0f / 3125.0f) + 2;
+					midi.sysex[slot].start = GetTicks();
 				}
 			}
 
-			LOG(LOG_ALL,LOG_NORMAL)("Sysex message size %d", static_cast<int>(midi.sysex.used));
+			LOG(LOG_ALL,LOG_NORMAL)("Sysex message size %d", static_cast<int>(midi.sysex[slot].used));
 			if (CaptureState & CAPTURE_MIDI) {
-				CAPTURE_AddMidi( true, midi.sysex.used-1, &midi.sysex.buf[1]);
+				/* don't capture from MIDI passthrough channel */
+				if (slot!=MOUT_THRU) CAPTURE_AddMidi( true, midi.sysex[slot].used-1, &midi.sysex[slot].buf[1]);
 			}
 		}
 	}
 	if (data&0x80) {
-		midi.status=data;
-		midi.cmd_pos=0;
-		midi.cmd_len=MIDI_evt_len[data];
-		if (midi.status==0xf0) {
-			midi.sysex.buf[0]=0xf0;
-			midi.sysex.used=1;
+		midi.status[slot]=data;
+		midi.cmd[slot].pos=0;
+		midi.cmd[slot].len=MIDI_evt_len[data];
+		if (midi.status[slot]==0xf0) {
+			midi.sysex[slot].buf[0]=0xf0;
+			midi.sysex[slot].used=1;
 		}
 	}
-	if (midi.cmd_len) {
-		midi.cmd_buf[midi.cmd_pos++]=data;
-		if (midi.cmd_pos >= midi.cmd_len) {
+	if (midi.cmd[slot].len) {
+		midi.cmd[slot].buf[midi.cmd[slot].pos++]=data;
+		if (midi.cmd[slot].pos >= midi.cmd[slot].len) {
 			if (CaptureState & CAPTURE_MIDI) {
-				CAPTURE_AddMidi(false, midi.cmd_len, midi.cmd_buf);
+				if (slot!=MOUT_THRU) CAPTURE_AddMidi(false, midi.cmd[slot].len, midi.cmd[slot].buf);
 			}
-			midi.handler->PlayMsg(midi.cmd_buf);
-			midi.cmd_pos=1;		//Use Running status
+			if (midi.thruchan || slot!=MOUT_THRU) midi.handler->PlayMsg(midi.cmd[slot].buf);
+			midi.cmd[slot].pos=1;		//Use Running status
+		}
+	}
+}
+
+//allow devices to catch input in autodetection mode
+Bit32s MIDI_ToggleInputDevice(Bit32u device,bool status) {
+	if (!midi.autoinput) return -1;
+	if (midi.inputdev == device) {
+		if (status==false) {
+			midi.inputdev = MDEV_NONE;
+			return 2;
 		}
+		return 1;
 	}
+	midi.inputdev = device;
+	return 0;
+}
+
+INLINE void MIDI_InputMsg(Bit8u msg[4]) {
+	switch (midi.inputdev) {
+		case MDEV_MPU:
+			MPU401_InputMsg(msg);
+			break;
+		case MDEV_SBUART:
+			SB_UART_InputMsg(msg);
+			break;
+		case MDEV_GUS:
+			GUS_UART_InputMsg(msg);
+			break;
+	}
+}
+
+INLINE Bits MIDI_InputSysex(Bit8u *sysex,Bitu len, bool abort) {
+	switch (midi.inputdev) {
+		case MDEV_MPU:
+			return MPU401_InputSysex(sysex,len,abort);
+		case MDEV_SBUART:
+			return SB_UART_InputSysex(sysex,len,abort);
+		case MDEV_GUS:
+			return GUS_UART_InputSysex(sysex,len,abort);
+		default:
+			return 0;
+	}
+}
+
+void MIDI_ClearBuffer(Bit8u slot) {
+	midi.sysex[slot].used=0;
+	midi.status[slot]=0x00;
+	midi.cmd[slot].pos=0;
+	midi.cmd[slot].len=0;
 }
 
 bool MIDI_Available(void)  {
@@ -165,23 +229,86 @@ class MIDI:public Module_base{
 public:
 	MIDI(Section* configuration):Module_base(configuration){
 		Section_prop * section=static_cast<Section_prop *>(configuration);
-		const char * dev=section->Get_string("mididevice");
+		const char * dev = "";
+		char * sel_indevice = 0;
+		const char * dev_list=section->Get_string("mididevice");
 		std::string fullconf=section->Get_string("midiconfig");
+		const char * inconf=section->Get_string("inconfig");
+		char * mflags=(char*)section->Get_string("midioptions");
+		const char * conf = fullconf.c_str();
+
+		midi.realtime = true;
+		midi.inputdev = MDEV_MPU;
+		midi.autoinput = true;
+		midi.thruchan = false;
+		midi.clockout = false;
+
+		std::string devstr;
+		std::string indevstr;
+
+		while (dev_list!=NULL) {
+			char * devtoken = strtok((char*)dev_list,",");
+			if (devtoken==NULL) break;
+			devstr = devtoken;
+			devstr = devstr.erase(devstr.find_last_not_of(" ")+1);
+			devstr = devstr.erase(0,devstr.find_first_not_of(" "));
+			dev = (char*)devstr.c_str();
+
+			devtoken = strtok(NULL,",");
+			if (devtoken==NULL) break;
+			indevstr = devtoken;
+			indevstr = indevstr.erase(indevstr.find_last_not_of(" ")+1);
+			indevstr = indevstr.erase(0,indevstr.find_first_not_of(" "));
+			sel_indevice = (char*)indevstr.c_str();
+			break;
+		}
+
+		if (mflags!=NULL) {
+			char * mflagtoken = strtok(mflags,",");
+			while (mflagtoken!=NULL) {
+				std::string flag(mflagtoken);
+				flag = flag.erase(flag.find_last_not_of(" ")+1);
+				flag = flag.erase(0,flag.find_first_not_of(" "));
+				const char * flag_w = flag.c_str();
+
+				//input to internal device options
+				if (!strcasecmp(flag_w,"autoinput")) midi.autoinput=true;
+				if (!strcasecmp(flag_w,"inputmpu401")) {midi.inputdev = MDEV_MPU;midi.autoinput=false;}
+				if (!strcasecmp(flag_w,"inputsbuart")) {midi.inputdev = MDEV_SBUART;midi.autoinput=false;}
+				if (!strcasecmp(flag_w,"inputgus")) {midi.inputdev = MDEV_GUS;midi.autoinput=false;}
+
+				if (!strcasecmp(flag_w,"norealtime")) midi.realtime = false;
+				if (!strcasecmp(flag_w,"passthrough")) midi.thruchan = true;
+				if (!strcasecmp(flag_w,"clockout")) midi.clockout = true;
+				if (!strcasecmp(flag_w,"throttle")) MPU401_SetupTxHandler();
+				//TODO:add remaining options
+				mflagtoken = strtok(NULL,",");
+			}
+		}
+
 		/* If device = "default" go for first handler that works */
 		MidiHandler * handler;
 //		MAPPER_AddHandler(MIDI_SaveRawEvent,MK_f8,MMOD1|MMOD2,"caprawmidi","Cap MIDI");
-		midi.sysex.delay = 0;
-		midi.sysex.start = 0;
+		for (Bitu slot=0;slot<MIDI_DEVS;slot++) {
+			midi.status[slot]=0x00;
+			midi.cmd[slot].pos=0;
+			midi.cmd[slot].len=0;
+			midi.sysex[slot].used=0;
+			midi.sysex[slot].delay=0;
+			midi.sysex[slot].start=0;
+		if (fullconf.find("delaysysex") != std::string::npos) {
+			midi.sysex[slot].start = GetTicks();
+        }
+		}
+		if (!strcasecmp(dev,"none")) {
+			if (sel_indevice) goto midiin;
+			else return;
+		}
 		if (fullconf.find("delaysysex") != std::string::npos) {
-			midi.sysex.start = GetTicks();
 			fullconf.erase(fullconf.find("delaysysex"));
 			LOG_MSG("MIDI: Using delayed SysEx processing");
 		}
 		trim(fullconf);
-		const char * conf = fullconf.c_str();
-		midi.status=0x00;
-		midi.cmd_pos=0;
-		midi.cmd_len=0;
 		if (!strcasecmp(dev,"default")) goto getdefault;
 		handler=handler_list;
 		while (handler) {
@@ -193,7 +320,7 @@ public:
 				midi.handler=handler;
 				midi.available=true;
 				LOG_MSG("MIDI: Opened device:%s",handler->GetName());
-				return;
+				goto midiin;
 			}
 			handler=handler->next;
 		}
@@ -205,16 +332,61 @@ getdefault:
 				midi.available=true;
 				midi.handler=handler;
 				LOG_MSG("MIDI: Opened device:%s",handler->GetName());
-				return;
+				goto midiin;
 			}
 			handler=handler->next;
 		}
-		/* This shouldn't be possible */
+midiin:
+		if (sel_indevice && !strcasecmp(sel_indevice,"none")) return;
+		if (strcasecmp(inconf,"none")) {
+			if ((sel_indevice) && strcasecmp(sel_indevice,"")) {
+				MidiHandler * sel_handler=handler_list;
+				while (sel_handler) {
+					if (!strcasecmp(sel_indevice,sel_handler->GetName())) {
+						if (sel_handler->OpenInput(inconf)) {
+							LOG_MSG("MIDI:Opened input device:%s.",sel_handler->GetName());
+							midi.in_available=true;
+							midi.in_handler=sel_handler;
+						}
+						else LOG_MSG("MIDI:Can't open input device:%s.",sel_handler->GetName());
+						return;
+					}
+					sel_handler=sel_handler->next;
+				}
+				LOG_MSG("MIDI:Can't find input device:%s.",sel_handler->GetName());
+				return;
+			}
+			//first try to open same handler
+			MidiHandler * output_handler=0;
+			if (midi.available) {
+					output_handler=handler;
+					if (handler->OpenInput(inconf)) {
+						LOG_MSG("MIDI:Opened input device:%s.",handler->GetName());
+						midi.in_available=true;
+						midi.in_handler=handler;
+						return;
+					}
+			}
+			handler=handler_list;
+			while (handler) {
+				if (output_handler!=handler)
+					if (handler->OpenInput(inconf)) {
+						LOG_MSG("MIDI:Opened input device:%s.",handler->GetName());
+						midi.in_available=true;
+						midi.in_handler=handler;
+						return;
+					}
+				handler=handler->next;
+			}
+        }
 	}
 	~MIDI(){
+		if (midi.in_available && midi.in_handler!=midi.handler) midi.in_handler->Close();
 		if(midi.available) midi.handler->Close();
 		midi.available = false;
+		midi.in_available = false;
 		midi.handler = 0;
+		midi.in_handler = 0;
 	}
 };
 
diff --git a/src/gui/midi_alsa.h b/src/gui/midi_alsa.h
index 9bf4b4f9..1cf4a33d 100644
--- a/src/gui/midi_alsa.h
+++ b/src/gui/midi_alsa.h
@@ -28,18 +28,104 @@
 #if ((SND_LIB_MINOR >= 6) && (SND_LIB_MAJOR == 0)) || (SND_LIB_MAJOR >= 1)
 #define snd_seq_flush_output(x) snd_seq_drain_output(x)
 #define snd_seq_set_client_group(x,name)	/*nop */
-#define my_snd_seq_open(seqp) snd_seq_open(seqp, "hw", SND_SEQ_OPEN_OUTPUT, 0)
+#define my_snd_seq_open(seqp) snd_seq_open(seqp, "hw", SND_SEQ_OPEN_DUPLEX, 0)
 #else
 /* SND_SEQ_OPEN_OUT causes oops on early version of ALSA */
 #define my_snd_seq_open(seqp) snd_seq_open(seqp, SND_SEQ_OPEN)
 #endif
 
+SDL_Thread * input_thread;
+snd_seq_t * mySeqHandle;
+int input_exit;
+
+int input_handler(void * val) {
+	snd_seq_event_t * ev_in = NULL;
+
+#if SIZEOF_UNSIGNED_LONG == 8
+	int shift = 3;
+#else
+	int shift = 2;
+#endif
+
+	Bit8u msg[4];
+	Bitu * m_ptr = (Bitu*) msg;
+	Bitu len;
+	int tval,status,channel;
+	Bit8u * sysex_pos;
+	while (!input_exit) {
+		status = snd_seq_event_input(mySeqHandle, &ev_in);
+		if (input_exit) break;
+		if (status < 0) continue;
+		//LOG_MSG("Alsa sequencer event %d",ev_in->type);
+		switch (ev_in->type) {
+			case SND_SEQ_EVENT_NOTEON:
+				channel = ev_in->data.note.channel;
+				 *m_ptr = 
+					((Bit32u(0x90 | ev_in->data.note.channel))) |
+					((ev_in->data.note.note & 0xff)<<8) |
+					((ev_in->data.note.velocity & 0xff)<<16) | 0x03000000 ;
+				MIDI_InputMsg(msg);
+				break;
+			case SND_SEQ_EVENT_NOTEOFF:
+				channel = ev_in->data.note.channel;
+				 *m_ptr = 
+					(0x80 | ev_in->data.note.channel) |
+					((ev_in->data.note.note & 0xff)<<8) |
+					((ev_in->data.note.velocity & 0xff)<<16) | 0x03000000;
+				MIDI_InputMsg(msg);
+				break;
+			case SND_SEQ_EVENT_CONTROLLER:
+				*m_ptr =
+					(0xB0 | ev_in->data.note.channel) |
+					((ev_in->data.control.param & 0xff)<<8) |
+					((ev_in->data.control.value & 0xff)<<16) | 0x03000000 ;
+				MIDI_InputMsg(msg);
+				break;
+			case SND_SEQ_EVENT_PGMCHANGE:
+				*m_ptr =
+					(0xC0 | ev_in->data.note.channel) |
+					((ev_in->data.control.value & 0xff)<<8) | 0x02000000 ;
+				MIDI_InputMsg(msg);
+				break;
+			case SND_SEQ_EVENT_PITCHBEND:
+				tval = (int)ev_in->data.control.value + (int)0x2000;
+				*m_ptr =
+					(0xE0 | ev_in->data.control.channel) |
+					((tval & 0x7F)<<8),
+					((tval<<9) & 0x7f0000) | 0x03000000 ;
+				MIDI_InputMsg(msg);
+				break;
+			case SND_SEQ_EVENT_SYSEX:
+				len=ev_in->data.ext.len << shift;
+				sysex_pos=(Bit8u *) ev_in->data.ext.ptr;
+				{
+					Bitu cnt=5;
+					while (cnt) { //abort if timeout
+						Bitu ret = Bitu(MIDI_InputSysex(sysex_pos,len,false));
+						if (!ret) {len=0;break;}
+						if (len==ret) cnt--; else cnt=5;
+						sysex_pos+=len-ret;
+						len=ret;
+						usleep(5000);//usec
+					}
+					if (len) MIDI_InputSysex(sysex_pos,0,true);
+				}
+				break;
+		}
+	}
+    return 0;
+}
+
 class MidiHandler_alsa : public MidiHandler {
 private:
 	snd_seq_event_t ev;
 	snd_seq_t *seq_handle;
 	int seq_client, seq_port;
 	int my_client, my_port;
+
+	int seqin_client, seqin_port;
+	int myin_client, myin_port;
+
 	void send_event(int do_flush) {
 		snd_seq_ev_set_direct(&ev);
 		snd_seq_ev_set_source(&ev, my_port);
@@ -125,8 +211,10 @@ public:
 	}	
 
 	void Close(void) {
+		if (input_thread) input_exit=1;
 		if (seq_handle)
 			snd_seq_close(seq_handle);
+		seq_handle=0;
 	}
 
 	bool Open(const char * conf) {
@@ -166,6 +254,7 @@ public:
 		          SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
 		if (my_port < 0) {
 			snd_seq_close(seq_handle);
+			seq_handle=0;
 			LOG_MSG("ALSA:Can't create ALSA port");
 			return false;
 		}
@@ -178,20 +267,85 @@ public:
 					if(snd_seq_connect_to(seq_handle,my_port,seq_client,seq_port) < 0) { //Try 128:0 Timidity port as well
 //						seq_client = 128; seq_port = 0; //Update reported values
 //						if(snd_seq_connect_to(seq_handle,my_port,seq_client,seq_port) < 0) {
+						seq_client=128; seq_port=0; //try timidity defalt
+						if (snd_seq_connect_to(seq_handle,my_port,seq_client,seq_port) < 0) {
 							snd_seq_close(seq_handle);
-							LOG_MSG("ALSA:Can't subscribe to MIDI port (65:0) nor (17:0)");
+							seq_handle=0;
+							LOG_MSG("ALSA:Can't subscribe to MIDI port (65:0),  (17:0) nor (128:0)");
 							return false;
-//						}
+						}
 					}
 				} else {
 					snd_seq_close(seq_handle);
+					seq_handle=0;
 					LOG_MSG("ALSA:Can't subscribe to MIDI port (%d:%d)", seq_client, seq_port);
 					return false;
 				}
 			}
 		}
 
-		LOG_MSG("ALSA:Client initialised [%d:%d]", seq_client, seq_port);
+		LOG_MSG("ALSA:MIDI Client initialised [%d:%d]", seq_client, seq_port);
+		return true;
+	}
+	bool OpenInput(const char * conf) {
+		LOG_MSG("opening input...");
+		char var[10];
+		unsigned int caps;
+
+		// try to use port specified in config file
+		if (conf) {
+			if  (conf[0]) { 
+				safe_strncpy(var, conf, 10);
+				if (parse_addr(var, &seqin_client, &seqin_port) < 0) {
+					LOG_MSG("ALSA:Invalid input alsa port %s", var);
+					return false;
+				}
+			}
+			else seqin_client=-1;
+		} else return false;
+
+		bool openedOutput = true;
+
+		if (!seq_handle) {
+			if (my_snd_seq_open(&seq_handle)) {
+				LOG_MSG("ALSA:Can't open sequencer for input");
+				return false;
+			}
+
+			bool openedOutput = false;
+			my_client = snd_seq_client_id(seq_handle);
+			snd_seq_set_client_name(seq_handle, "DOSBOX");
+			snd_seq_set_client_group(seq_handle, "input");
+		}
+
+		caps = SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
+
+		myin_port =
+			snd_seq_create_simple_port(seq_handle, "DOSBOX_IN", caps,
+					SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION);
+					//SND_SEQ_PORT_TYPE_SYNTH | SND_SEQ_PORT_TYPE_MIDI_GENERIC);
+		if (myin_port < 0) {
+			if (!openedOutput) {snd_seq_close(seq_handle);seq_handle=0;}
+			LOG_MSG("ALSA:Can't create ALSA input port");
+			return false;
+		}
+
+		if (seqin_client != SND_SEQ_ADDRESS_SUBSCRIBERS && seqin_client>=0) {
+			/* subscribe to MIDI port */
+			if (snd_seq_connect_to(seq_handle, myin_port, seqin_client, seqin_port) < 0) {
+				LOG_MSG("ALSA:Can't subscribe to MIDI port (%d:%d) for input", seqin_client, seqin_port);
+				seqin_client=-1;
+				//return false;
+			}
+		}
+
+		//start receiving thread
+		input_exit=0;
+		mySeqHandle = seq_handle;
+		input_thread = SDL_CreateThread(input_handler, NULL);
+
+		if (seqin_client>=0) LOG_MSG("ALSA:MIDI input client connected [%d:%d]", seqin_client, seqin_port);
+		else LOG_MSG("ALSA:MIDI receiving client initialised");
 		return true;
 	}
 
diff --git a/src/gui/midi_win32.h b/src/gui/midi_win32.h
index b7180f6d..b3b9edba 100644
--- a/src/gui/midi_win32.h
+++ b/src/gui/midi_win32.h
@@ -25,14 +25,60 @@
 #include <string>
 #include <sstream>
 
+void CALLBACK Win32_midiInCallback(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
+	//LOG_MSG("wMsg:%x %x %x",wMsg, dwParam1, dwParam2);
+	Bit8u msg[4] = {(Bit8u(dwParam1&0xff)),(Bit8u((dwParam1&0xff00)>>8)),
+					(Bit8u((dwParam1&0xff0000)>>16)),MIDI_evt_len[(Bit8u(dwParam1&0xff))]};
+	Bit8u *sysex;
+	Bitu len;
+	MIDIHDR *t_hdr;
+	switch (wMsg) {
+		case MM_MIM_DATA:  /* 0x3C3 - midi message */
+			MIDI_InputMsg(msg);
+			break;
+		case MM_MIM_OPEN:  /* 0x3C1 */
+			break;
+		case MM_MIM_CLOSE: /* 0x3C2 */
+			break;
+		case MM_MIM_LONGDATA: /* 0x3C4 - sysex */
+			t_hdr=(MIDIHDR*)dwParam1;
+			sysex=(Bit8u*)t_hdr->lpData; 
+			len=(Bitu)t_hdr->dwBytesRecorded;
+			{
+				Bitu cnt=5;
+				while (cnt) { //abort if timed out
+					Bitu ret = Bitu(MIDI_InputSysex(sysex,len,false));
+					if (!ret) {len=0;break;}
+					if (len==ret) cnt--; else cnt=5;
+					sysex+=len-ret;
+					len=ret;
+					Sleep(5);//msec
+				}
+				if (len) MIDI_InputSysex(sysex,0,false);
+			}
+			midiInUnprepareHeader(hMidiIn,t_hdr,sizeof(*t_hdr));
+			t_hdr->dwBytesRecorded = 0 ;
+			midiInPrepareHeader(hMidiIn,t_hdr,sizeof(*t_hdr));
+			break;
+		case MM_MIM_ERROR:
+		case MM_MIM_LONGERROR:
+			break;
+		default:
+			LOG(LOG_MISC, LOG_NORMAL) ("MIDI: Unhandled input type %x",wMsg);
+	}
+};
+
 class MidiHandler_win32: public MidiHandler {
 private:
 	HMIDIOUT m_out;
+	HMIDIIN m_in;
 	MIDIHDR m_hdr;
+	MIDIHDR m_inhdr;
 	HANDLE m_event;
 	bool isOpen;
+	bool isOpenInput;
 public:
-	MidiHandler_win32() : MidiHandler(),isOpen(false) {};
+	MidiHandler_win32() : isOpen(false),isOpenInput(false),MidiHandler() {};
 	const char * GetName(void) { return "win32";};
 	bool Open(const char * conf) {
 		if (isOpen) return false;
@@ -71,12 +117,45 @@ public:
 		isOpen=true;
 		return true;
 	};
+	bool OpenInput(const char *inconf) {
+		if (isOpenInput) return false;
+		MMRESULT res;
+		if(inconf && *inconf) {
+			std::string strinconf(inconf);
+			std::istringstream configmidiin(strinconf);
+			unsigned int nummer = midiInGetNumDevs();
+			configmidiin >> nummer;
+			if(nummer < midiInGetNumDevs()){
+				MIDIINCAPS mididev;
+				midiInGetDevCaps(nummer, &mididev, sizeof(MIDIINCAPS));
+				LOG_MSG("MIDI:win32 selected input %s",mididev.szPname);
+				res = midiInOpen (&m_in, nummer, (DWORD_PTR)Win32_midiInCallback, 0, CALLBACK_FUNCTION);
+			}
+		} else {
+			res = midiInOpen(&m_in, MIDI_MAPPER, (DWORD_PTR)Win32_midiInCallback, 0, CALLBACK_FUNCTION);
+		}
+		if (res != MMSYSERR_NOERROR) return false;
 
+		m_inhdr.lpData = (char*)&MIDI_InSysexBuf[0];
+		m_inhdr.dwBufferLength = SYSEX_SIZE;
+		m_inhdr.dwBytesRecorded = 0 ;
+		m_inhdr.dwUser = 0;
+		midiInPrepareHeader(m_in,&m_inhdr,sizeof(m_inhdr));
+		midiInStart(m_in);
+		isOpenInput=true;
+		return true;
+	};
 	void Close(void) {
-		if (!isOpen) return;
-		isOpen=false;
-		midiOutClose(m_out);
-		CloseHandle (m_event);
+		if (isOpen) {
+			isOpen=false;
+			midiOutClose(m_out);
+			CloseHandle (m_event);
+		}
+		if (isOpenInput) {
+			isOpenInput=false;
+			midiInStop(m_in);
+			midiInClose(m_in);
+		}
 	};
 	void PlayMsg(Bit8u * msg) {
 		midiOutShortMsg(m_out, *(Bit32u*)msg);
diff --git a/src/hardware/gus.cpp b/src/hardware/gus.cpp
index d1d7e424..073707d0 100644
--- a/src/hardware/gus.cpp
+++ b/src/hardware/gus.cpp
@@ -23,6 +23,7 @@
 #include "dosbox.h"
 #include "inout.h"
 #include "mixer.h"
+#include "midi.h"
 #include "dma.h"
 #include "pic.h"
 #include "setup.h"
@@ -41,6 +42,8 @@ using namespace std;
 #define RAMP_FRACT (10)
 #define RAMP_FRACT_MASK ((1 << RAMP_FRACT)-1)
 
+#define GUS_UART_QUEUE 64
+
 #define GUS_BASE myGUS.portbase
 #define GUS_RATE myGUS.rate
 #define LOG_GUS 0
@@ -105,9 +108,19 @@ struct GFGus {
 	Bit8u IRQChan;
 	Bit32u RampIRQ;
 	Bit32u WaveIRQ;
+	//UART (MIDI)
+	bool uart_in,uart_out,uart_sysex;
+	Bit8u uart_queue[GUS_UART_QUEUE];
+	Bitu uart_used, uart_pos;
 } myGUS;
 
+SDL_mutex *GUSLock;
+
 Bitu DEBUG_EnableDebugger(void);
+void MIDI_RawOutByte(Bit8u data, Bit8u cb);
+
+static void GUS_UART_Queue(Bit8u data);
+static void GUS_UART_ClrQueue(void);
 
 static void GUS_DMA_Callback(DmaChannel * chan,DMAEvent event);
 
@@ -389,7 +402,7 @@ static void GUSReset(void) {
 }
 
 static INLINE void GUS_CheckIRQ(void) {
-	if (myGUS.IRQStatus && (myGUS.mixControl & 0x08)) 
+	if ((myGUS.IRQStatus & 0xfc) && (myGUS.mixControl & 0x08)) 
 		PIC_ActivateIRQ(myGUS.irq1);
 
 }
@@ -621,6 +634,7 @@ static void ExecuteGlobRegister(void) {
 
 static Bitu read_gus(Bitu port,Bitu iolen) {
 //	LOG_MSG("read from gus port %x",port);
+	Bit8u ret;
 	switch(port - GUS_BASE) {
 	case 0x206:
 		return myGUS.IRQStatus;
@@ -650,6 +664,27 @@ static Bitu read_gus(Bitu port,Bitu iolen) {
 		} else {
 			return 0;
 		}
+	case 0x300:
+		return  ( (myGUS.IRQStatus&0x3 ? 0x80:0) |
+			(myGUS.uart_in && myGUS.uart_used>=GUS_UART_QUEUE ? 0x20:0) |
+			((myGUS.uart_used && myGUS.uart_in)?1:0) | (myGUS.uart_out?2:0)
+			) ;
+	case 0x301:
+		ret=0;
+		if (myGUS.uart_in) {
+			SDL_mutexP(GUSLock);
+			if (myGUS.uart_used) {
+				if (myGUS.uart_pos>=GUS_UART_QUEUE) myGUS.uart_pos-=GUS_UART_QUEUE;
+				ret=myGUS.uart_queue[myGUS.uart_pos];
+				myGUS.uart_pos++;myGUS.uart_used--;
+			}
+			SDL_mutexV(GUSLock);
+			if (!myGUS.uart_used) {
+				myGUS.IRQStatus&=~0x2;
+			}
+		}
+		//LOG_MSG("GUS UART *READ data %x", ret);
+		return ret;	
 	default:
 #if LOG_GUS
 		LOG_MSG("Read GUS at port 0x%x", port);
@@ -732,6 +767,35 @@ static void write_gus(Bitu port,Bitu val,Bitu iolen) {
 	case 0x307:
 		if(myGUS.gDramAddr < sizeof(GUSRam)) GUSRam[myGUS.gDramAddr] = (Bit8u)val;
 		break;
+	case 0x300: //write command
+		//LOG_MSG("GUS UART cmd %x",val);
+		myGUS.uart_out=true;
+		if (val==3 || !val) {  //"normal operation" or reset
+				myGUS.uart_in=false;
+				myGUS.IRQStatus&=~0x3;
+				myGUS.uart_used=0;
+				//PIC_DeActivateIRQ(myGUS.irq2);
+			return;
+		}
+		if (val&0x20 /*&& !(val&0x40)*/) {
+			myGUS.IRQStatus|=0x01;
+		} else myGUS.IRQStatus&=~0x01;
+
+		if (val&0x80) { //start receiving
+			//enable host MIDI input (only in auto mode)
+			MIDI_ToggleInputDevice(MDEV_GUS,true);
+			myGUS.uart_in=true;
+		} else myGUS.uart_in=false;
+		break;
+	case 0x301: //write data
+		//LOG_MSG("GUS UART data %x",val);
+		if (myGUS.uart_out) {
+			MIDI_RawOutByte(val,MDEV_GUS);
+			PIC_ActivateIRQ(myGUS.irq2);
+			//if (myGUS.mixControl&0x8)
+			//	PIC_ActivateIRQ(myGUS.irq1);
+		}
+		break;
 	default:
 #if LOG_GUS
 		LOG_MSG("Write GUS at port 0x%x with %x", port, val);
@@ -809,10 +873,48 @@ static void MakeTables(void) {
 	}
 }
 
+static void GUS_UART_Queue(Bit8u data) {
+	if (myGUS.uart_used<GUS_UART_QUEUE) {
+		Bitu pos=myGUS.uart_used+myGUS.uart_pos;
+		if (pos>=GUS_UART_QUEUE) pos-=GUS_UART_QUEUE;
+		myGUS.uart_queue[pos]=data;
+		myGUS.uart_used++;
+	}
+	if (GUSLock) SDL_mutexV(GUSLock);
+}
+
+void GUS_UART_InputMsg(Bit8u msg[4]) {
+	if (myGUS.uart_sysex) return;
+	if (myGUS.uart_in) {
+		if (GUSLock) SDL_mutexP(GUSLock);
+		myGUS.IRQStatus|=0x2;
+		for (Bitu i=0;i<msg[3];i++) GUS_UART_Queue(msg[i]);
+		PIC_ActivateIRQ(myGUS.irq2);
+		if (GUSLock) SDL_mutexV(GUSLock);
+	}
+}
+
+Bits GUS_UART_InputSysex(Bit8u* buffer,Bitu len,bool abort) {
+	if (abort) {
+		myGUS.uart_used=0;
+		myGUS.uart_sysex=false;
+		return 0;
+	}
+	myGUS.uart_sysex=true;
+	if (GUSLock) SDL_mutexP(GUSLock);
+	for (Bitu i=0;i<len;i++) {
+	if (myGUS.uart_used>=GUS_UART_QUEUE) {if (GUSLock) SDL_mutexV(GUSLock);return (len-i);}
+		GUS_UART_Queue(buffer[i]);
+	}
+	if (GUSLock) SDL_mutexV(GUSLock);
+	myGUS.uart_sysex=false;
+	return 0;
+}
+
 class GUS:public Module_base{
 private:
-	IO_ReadHandleObject ReadHandler[8];
-	IO_WriteHandleObject WriteHandler[9];
+	IO_ReadHandleObject ReadHandler[10];
+	IO_WriteHandleObject WriteHandler[11];
 	AutoexecObject autoexecline[2];
 	MixerObject MixerChan;
 public:
@@ -860,15 +962,29 @@ public:
 	
 		WriteHandler[6].Install(0x307 + GUS_BASE,write_gus,IO_MB);
 		ReadHandler[6].Install(0x307 + GUS_BASE,read_gus,IO_MB);
-	
+
+		if (GUS_BASE!=0x30) {
+
+			//GUS MIDI port (command, status)
+			WriteHandler[7].Install(0x300 + GUS_BASE,write_gus,IO_MB);
+			ReadHandler[7].Install(0x300 + GUS_BASE,read_gus,IO_MB);
+			//GUS MIDI port (data)
+			WriteHandler[8].Install(0x301 + GUS_BASE,write_gus,IO_MB);
+			ReadHandler[8].Install(0x301 + GUS_BASE,read_gus,IO_MB);
+			myGUS.uart_out=true;
+		}
+		else LOG_MSG("GUS:Disabled UART: try different irq2 and/or gusbase");
+
 		// Board Only 
 	
-		WriteHandler[7].Install(0x200 + GUS_BASE,write_gus,IO_MB);
-		ReadHandler[7].Install(0x20A + GUS_BASE,read_gus,IO_MB);
-		WriteHandler[8].Install(0x20B + GUS_BASE,write_gus,IO_MB);
+		WriteHandler[9].Install(0x200 + GUS_BASE,write_gus,IO_MB);
+		ReadHandler[9].Install(0x20A + GUS_BASE,read_gus,IO_MB);
+		WriteHandler[10].Install(0x20B + GUS_BASE,write_gus,IO_MB);
 	
 	//	DmaChannels[myGUS.dma1]->Register_TC_Callback(GUS_DMA_TC_Callback);
-	
+
+		GUSLock = SDL_CreateMutex();
+
 		MakeTables();
 	
 		for (Bit8u chan_ct=0; chan_ct<32; chan_ct++) {
@@ -908,6 +1024,8 @@ public:
 
 		memset(&myGUS,0,sizeof(myGUS));
 		memset(GUSRam,0,1024*1024);
+		SDL_DestroyMutex(GUSLock);
+		GUSLock=0;
 	}
 };
 
diff --git a/src/hardware/mpu401.cpp b/src/hardware/mpu401.cpp
index 4afa5691..b77d273e 100644
--- a/src/hardware/mpu401.cpp
+++ b/src/hardware/mpu401.cpp
@@ -24,24 +24,43 @@
 #include "setup.h"
 #include "cpu.h"
 #include "support.h"
+#include "mixer.h"
+#include "midi.h"
 
-void MIDI_RawOutByte(Bit8u data);
-bool MIDI_Available(void);
+SDL_mutex * MPULock;
+
+//metronome sound
+#define MPU401_METRONOME 1
 
 static void MPU401_Event(Bitu);
 static void MPU401_Reset(void);
 static void MPU401_ResetDone(Bitu);
 static void MPU401_EOIHandler(Bitu val=0);
 static void MPU401_EOIHandlerDispatch(void);
+static void MPU401_IntelligentOut(Bitu data);
+static void MPU401_WriteCommand(Bitu port,Bitu val,Bitu iolen);
+static void MPU401_WriteData(Bitu port,Bitu val,Bitu iolen);
+static void MPU401_MetronomeSound(bool accented);
+static void MPU401_NotesOff(Bitu chan);
+static void MPU401_RecQueueBuffer(Bit8u * buf, Bitu len,bool block);
 
 #define MPU401_VERSION	0x15
 #define MPU401_REVISION	0x01
-#define MPU401_QUEUE 32
+#define MPU401_QUEUE 64
+#define MPU401_INPUT_QUEUE 1024
 #define MPU401_TIMECONSTANT (60000000/1000.0f)
 #define MPU401_RESETBUSY 14.0f
 
+//helpers
+#define M_GETKEY key[key/32]&(1<<(key%32))
+#define M_SETKEY key[key/32]|=(1<<(key%32))
+#define M_DELKEY key[key/32]&=~(1<<(key%32))
+
 enum MpuMode { M_UART,M_INTELLIGENT };
 enum MpuDataType {T_OVERFLOW,T_MARK,T_MIDI_SYS,T_MIDI_NORM,T_COMMAND};
+enum RecState { M_RECOFF,M_RECSTB,M_RECON };
+static Bitu MPUClockBase[8]={48,72,96,120,144,168,192};
+static Bit8u cth_data[16]={0,0,0,0,1,0,0,0,1,0,1,0,1,1,1,0};
 
 static void MPU401_WriteData(Bitu port,Bitu val,Bitu iolen);
 
@@ -59,70 +78,212 @@ static void MPU401_WriteData(Bitu port,Bitu val,Bitu iolen);
 
 static struct {
 	bool intelligent;
-	MpuMode mode;
 	Bitu irq;
+	bool midi_thru;
+	IO_ReadHandleObject ReadHandler[2];
+	IO_WriteHandleObject WriteHandler[2];
+
+#ifdef MPU401_METRONOME
+	struct {
+		Bit8u emptyBuf[MIXER_BUFSIZE];
+		Bit8u majorBeat[MIXER_BUFSIZE];
+		Bit8u minorBeat[MIXER_BUFSIZE];
+		MixerChannel * mixerChan;
+		MixerObject mixObj;
+		Bit32s duration;
+		bool gen;
+		bool accent;
+	} metronome;
+#endif
+
+} mpuhw;
+
+static struct {
+	MpuMode mode;
 	Bit8u queue[MPU401_QUEUE];
 	Bitu queue_pos,queue_used;
+
+	Bit8u rec_queue[MPU401_INPUT_QUEUE];
+	Bitu rec_queue_pos,rec_queue_used;
 	struct track {
 		Bits counter;
-		Bit8u value[8],sys_val;
-		Bit8u vlength,length;
+		Bit8u value[3],sys_val;
+		Bit8u length;
 		MpuDataType type;
 	} playbuf[8],condbuf;
 	struct {
-		bool conductor,cond_req,cond_set, block_ack;
-		bool playing,reset;
 		bool wsd,wsm,wsd_start;
 		bool run_irq,irq_pending;
+		bool tx_ready;
+		bool conductor,cond_req,cond_set;
+		bool track_req;
+		bool block_ack;
+		bool playing;
 		bool send_now;
+		bool clock_to_host;
+		bool sync_in;
+		bool sysex_in_finished;
+		bool rec_copy;
+		RecState rec;
 		bool eoi_scheduled;
 		Bits data_onoff;
 		Bitu command_byte,cmd_pending;
 		Bit8u tmask,cmask,amask;
 		Bit16u midi_mask;
 		Bit16u req_mask;
-		Bit8u channel,old_chan;
+		Bitu track,old_track;
+		Bit8u last_rtcmd;
 	} state;
 	struct {
 		Bit8u timebase,old_timebase;
 		Bit8u tempo,old_tempo;
 		Bit8u tempo_rel,old_tempo_rel;
 		Bit8u tempo_grad;
-		Bit8u cth_rate,cth_counter;
-		bool clock_to_host,cth_active;
+		Bit8u cth_rate[4],cth_mode;
+		Bit8u midimetro,metromeas;
+		Bitu metronome_state;
+		Bitu cth_counter,cth_old;
+		Bitu rec_counter;
+		Bit32s measure_counter,meas_old;
+		Bitu metronome_counter;
+		Bit32s freq;
+		Bits ticks_in;
+		float freq_mod;
+		bool active;
 	} clock;
+	struct {
+		bool all_thru,midi_thru,sysex_thru,commonmsgs_thru;
+		bool modemsgs_in, commonmsgs_in, bender_in, sysex_in;
+		bool allnotesoff_out;
+		bool rt_affection, rt_out,rt_in;
+		bool timing_in_stop;
+		bool data_in_stop;
+		bool rec_measure_end;
+		Bit8u prchg_buf[16];
+		Bit16u prchg_mask;
+	} filter;
+	Bitu ch_toref[16];
+	struct {
+		Bit8u chan;
+		Bit32u key[4];
+		Bit8u trmask;
+		bool on;
+	}chanref[5],inputref[16];
 } mpu;
 
+static void MPU401_ReCalcClock(void) {
+	Bit32s maxtempo=240, mintempo=16;
+	if (mpu.clock.timebase>=168) maxtempo=179;
+	if (mpu.clock.timebase==144) maxtempo=208;
+	if (mpu.clock.timebase>=120) mintempo=8;
+	mpu.clock.freq=(Bit32u(mpu.clock.tempo*2*mpu.clock.tempo_rel))>>6;
 
-static void QueueByte(Bit8u data) {
-	if (mpu.state.block_ack) {mpu.state.block_ack=false;return;}
-	if (mpu.queue_used==0 && mpu.intelligent) {
-		mpu.state.irq_pending=true;
-		PIC_ActivateIRQ(mpu.irq);
+	mpu.clock.freq=mpu.clock.timebase*
+		(mpu.clock.freq<(mintempo*2) ? mintempo : 
+		((mpu.clock.freq/2)<maxtempo?(mpu.clock.freq/2):maxtempo) );
+
+	if (mpu.state.sync_in) {
+		Bit32s freq= Bit32s(float(mpu.clock.freq)*mpu.clock.freq_mod);
+		if (freq>mpu.clock.timebase*mintempo && freq<mpu.clock.timebase*maxtempo) 
+			mpu.clock.freq=freq;
 	}
+}
+
+static INLINE void MPU401_StartClock(void) {
+	if (mpu.clock.active) return;
+	if (!(mpu.state.clock_to_host || mpu.state.playing || mpu.state.rec==M_RECON)) return;
+	mpu.clock.active=true;
+	PIC_RemoveEvents(MPU401_Event);
+	PIC_AddEvent(MPU401_Event,MPU401_TIMECONSTANT/mpu.clock.freq);
+}
+
+static void MPU401_StopClock(void) {
+	if (mpu.state.playing || mpu.state.rec!=M_RECON || mpu.state.clock_to_host)  return;
+	mpu.clock.active=false;
+	PIC_RemoveEvents(MPU401_Event);
+}
+
+static INLINE void MPU401_RunClock(void) {
+	if (!mpu.clock.active) return;
+	PIC_RemoveEvents(MPU401_Event);
+	PIC_AddEvent(MPU401_Event,MPU401_TIMECONSTANT/(mpu.clock.freq));
+}
+
+static INLINE void MPU401_QueueByte(Bit8u data) {
+	if (mpu.state.block_ack) {mpu.state.block_ack=false;return;}
 	if (mpu.queue_used<MPU401_QUEUE) {
 		Bitu pos=mpu.queue_used+mpu.queue_pos;
-		if (mpu.queue_pos>=MPU401_QUEUE) mpu.queue_pos-=MPU401_QUEUE;
 		if (pos>=MPU401_QUEUE) pos-=MPU401_QUEUE;
 		mpu.queue_used++;
 		mpu.queue[pos]=data;
-	} else LOG(LOG_MISC,LOG_NORMAL)("MPU401:Data queue full");
+	}
 }
 
-static void ClrQueue(void) {
+static void MPU401_RecQueueBuffer(Bit8u * buf, Bitu len,bool block) {
+	if (block) {
+		if (MPULock) SDL_mutexP(MPULock);
+		else return;
+	}
+	Bitu cnt=0;
+	while (cnt<len) {
+		if (mpu.rec_queue_used<MPU401_INPUT_QUEUE) {
+			Bitu pos=mpu.rec_queue_used+mpu.rec_queue_pos;
+			if (pos>=MPU401_INPUT_QUEUE) {pos-=MPU401_INPUT_QUEUE;}
+			mpu.rec_queue[pos]=buf[cnt];
+			mpu.rec_queue_used++;
+			if (!mpu.state.sysex_in_finished && buf[cnt]==MSG_EOX) {//finish sysex
+				mpu.state.sysex_in_finished=true;
+				break;
+			}
+			cnt++;
+		}
+	}
+	if (mpu.queue_used==0) {
+		if (mpu.state.rec_copy || mpu.state.irq_pending) {
+			if (block && MPULock) SDL_mutexV(MPULock);
+			return;
+		}
+		mpu.state.rec_copy=true;
+		if (mpu.rec_queue_pos>=MPU401_INPUT_QUEUE) mpu.rec_queue_pos-=MPU401_INPUT_QUEUE;
+		MPU401_QueueByte(mpu.rec_queue[mpu.rec_queue_pos]);
+		mpu.rec_queue_used--;
+		mpu.rec_queue_pos++;
+	}
+	if (block && MPULock) SDL_mutexV(MPULock);
+}
+
+static void MPU401_ClrQueue(void) {
 	mpu.queue_used=0;
 	mpu.queue_pos=0;
+	mpu.rec_queue_used=0;
+	mpu.rec_queue_pos=0;
+	mpu.state.sysex_in_finished=true;
+	mpu.state.irq_pending=false;
+	SB16_MPU401_IrqToggle(false);
 }
 
 static Bitu MPU401_ReadStatus(Bitu port,Bitu iolen) {
 	Bit8u ret=0x3f;	/* Bits 6 and 7 clear */
 	if (mpu.state.cmd_pending) ret|=0x40;
-	if (!mpu.queue_used) ret|=0x80;
-	return ret;
+	return (ret | (mpu.queue_used ? 0: 0x80));
+}
+
+static Bitu MPU401_ReadStatusTx(Bitu port,Bitu iolen) {
+	return (0x3f | (mpu.queue_used ? 0: 0x80) | (mpu.state.tx_ready ? 0: 0x40));
+}
+
+//setup alternative status port handler for throttling
+void MPU401_SetupTxHandler(void) {
+	mpuhw.ReadHandler[1].Install(0x331,&MPU401_ReadStatusTx,IO_MB);
+}
+
+void MPU401_SetTx(bool status) {
+	mpu.state.tx_ready=status;
 }
 
 static void MPU401_WriteCommand(Bitu port,Bitu val,Bitu iolen) {
 	if (mpu.mode==M_UART && val!=0xff) return;
+#if 0
 	if (mpu.state.reset) {
 		if (mpu.state.cmd_pending || val!=0xff) {
 			mpu.state.cmd_pending=val+1;
@@ -131,160 +292,332 @@ static void MPU401_WriteCommand(Bitu port,Bitu val,Bitu iolen) {
 		PIC_RemoveEvents(MPU401_ResetDone);
 		mpu.state.reset=false;
 	}
-	if (val<=0x2f) {
-		switch (val&3) { /* MIDI stop, start, continue */
-			case 1: {MIDI_RawOutByte(0xfc);break;}
-			case 2: {MIDI_RawOutByte(0xfa);break;}
-			case 3: {MIDI_RawOutByte(0xfb);break;}
-		}
-		if (val&0x20) LOG(LOG_MISC,LOG_ERROR)("MPU-401:Unhandled Recording Command %x",val);
-		switch (val&0xc) {
-			case  0x4:	/* Stop */
-				PIC_RemoveEvents(MPU401_Event);
-				mpu.state.playing=false;
-				for (Bitu i=0xb0;i<0xbf;i++) {	/* All notes off */
-					MIDI_RawOutByte(i);
-					MIDI_RawOutByte(0x7b);
-					MIDI_RawOutByte(0);
-				}
-				break;
-			case 0x8:	/* Play */
-				LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Intelligent mode playback started");
-				mpu.state.playing=true;
-				PIC_RemoveEvents(MPU401_Event);
-				PIC_AddEvent(MPU401_Event,MPU401_TIMECONSTANT/(mpu.clock.tempo*mpu.clock.timebase));
-				ClrQueue();
-				break;
-		}
-	}
+#endif
+	//LOG(LOG_MISC,LOG_NORMAL)("MPU401:command %x",val);
+	SDL_mutexP(MPULock);
+
+	//hack:enable midi through after the first mpu401 command is written
+	mpuhw.midi_thru=true;
+
+	if (val<=0x2f) { /* Sequencer state */
+		bool send_prchg=false;
+		if ((val&0xf)<0xc) {
+			switch (val&3) { /* MIDI realtime messages */
+				case 1:
+					mpu.state.last_rtcmd=0xfc;
+					if (mpu.filter.rt_out) MIDI_RawOutRTByte(0xfc);
+					mpu.clock.meas_old=mpu.clock.measure_counter;
+					mpu.clock.cth_old=mpu.clock.cth_counter;
+					break;
+				case 2: 
+					mpu.state.last_rtcmd=0xfa;
+					if (mpu.filter.rt_out) MIDI_RawOutRTByte(0xfb);
+					mpu.clock.measure_counter=mpu.clock.meas_old=0;
+					mpu.clock.cth_counter=mpu.clock.cth_old=0;
+					break;
+				case 3:
+					mpu.state.last_rtcmd=0xfc;
+					if (mpu.filter.rt_out) MIDI_RawOutRTByte(0xfa);
+					mpu.clock.measure_counter=mpu.clock.meas_old;
+					mpu.clock.cth_counter=mpu.clock.cth_old;
+					break;
+			}
+			switch (val&0xc) { /* Playing */
+				case  0x4:	/* Stop */
+					mpu.state.playing=false;
+					MPU401_StopClock();
+					for (Bitu i=0;i<16;i++) MPU401_NotesOff(i);
+					mpu.filter.prchg_mask=0;
+					break;
+				case 0x8:	/* Start */
+					LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Intelligent mode playback");
+					mpu.state.playing=true;
+					MPU401_StartClock();
+			}
+			switch (val&0x30) { /* Recording */
+				case 0: //check if it waited for MIDI RT command
+					//if (val&8 && mpu.state.rec!=M_RECON) mpu.clock.rec_counter=0;
+					if ((val&3)<2 || !mpu.filter.rt_affection || mpu.state.rec!=M_RECSTB) break;
+					mpu.state.rec=M_RECON;
+					MPU401_StartClock();
+					if (mpu.filter.prchg_mask) send_prchg=true;
+					break;
+				case 0x10:  /* Stop */
+					//if (val&8 && mpu.state.rec!=M_RECON) mpu.clock.rec_counter=0;
+					mpu.state.rec=M_RECOFF;
+					MPU401_StopClock();	
+					MPU401_QueueByte(MSG_MPU_ACK);
+					MPU401_QueueByte(mpu.clock.rec_counter);
+					MPU401_QueueByte(MSG_MPU_END);
+					mpu.filter.prchg_mask=0;
+					mpu.clock.rec_counter=0;
+					SDL_mutexV(MPULock);
+					return;					
+				case 0x20:  /* Start */
+					LOG(LOG_MISC,LOG_NORMAL)("MPU-401: intelligent mode recording");
+					if (!(mpu.state.rec==M_RECON)) {
+						mpu.clock.rec_counter=0;
+						mpu.state.rec=M_RECSTB;
+					}
+					if (mpu.state.last_rtcmd==0xfa || mpu.state.last_rtcmd==0xfb) {
+						mpu.clock.rec_counter=0;
+						mpu.state.rec=M_RECON;
+						if (mpu.filter.prchg_mask) send_prchg=true;
+						MPU401_StartClock();
+					}
+			}
+ 		}
+		MPU401_QueueByte(MSG_MPU_ACK);
+		//record counter hack: needed by Prism, but sent only on cmd 0x20/0x26 (or breaks Ballade)
+		Bit8u rec_cnt=mpu.clock.rec_counter;
+		if ((val==0x20 || val==0x26) && mpu.state.rec==M_RECON) 
+			MPU401_RecQueueBuffer(&rec_cnt,1,false);
+
+		if (send_prchg) for (Bitu i=0;i<16;i++)
+			if (mpu.filter.prchg_mask&(1<<i)) {
+				Bit8u recmsg[3]={mpu.clock.rec_counter,0xc0|i,mpu.filter.prchg_buf[i]};
+				MPU401_RecQueueBuffer(recmsg,3,false);
+				mpu.filter.prchg_mask&=~(1<<i);
+			}
+		SDL_mutexV(MPULock);
+		return;
+    }
 	else if (val>=0xa0 && val<=0xa7) {	/* Request play counter */
-		if (mpu.state.cmask&(1<<(val&7))) QueueByte(mpu.playbuf[val&7].counter);
+		//if (mpu.state.cmask&(1<<(val&7)))
+		MPU401_QueueByte(mpu.playbuf[val&7].counter);
 	}
 	else if (val>=0xd0 && val<=0xd7) {	/* Send data */
-		mpu.state.old_chan=mpu.state.channel;
-		mpu.state.channel=val&7;
+		mpu.state.old_track=mpu.state.track;
+		mpu.state.track=val&7;
 		mpu.state.wsd=true;
 		mpu.state.wsm=false;
 		mpu.state.wsd_start=true;
 	}
+	else if (val<0x80 && val>=0x40) { /* Set reference table channel */
+		mpu.chanref[(val>>4)-4].on=true;
+		mpu.chanref[(val>>4)-4].chan=val&0x0f;
+		mpu.chanref[(val>>4)-4].trmask=0;
+		for (Bitu i=0;i<4;i++) mpu.chanref[(val>>4)-4].key[i]=0;
+		for (Bitu i=0;i<16;i++) if (mpu.ch_toref[i]==((val>>4)-4)) mpu.ch_toref[i]=4;
+		mpu.ch_toref[val&0x0f]=(val>>4)-4;
+	}
 	else
 	switch (val) {
-		case 0xdf:	/* Send system message */
-			mpu.state.wsd=false;
-			mpu.state.wsm=true;
-			mpu.state.wsd_start=true;
+		case 0x30: /* Configuration 0x30 - 0x39 */
+			mpu.filter.allnotesoff_out=false;
 			break;
-		case 0x8e:	/* Conductor */
-			mpu.state.cond_set=false;
+		case 0x32:
+			mpu.filter.rt_out=false;
 			break;
-		case 0x8f:
-			mpu.state.cond_set=true;
+		case 0x33:
+			mpu.filter.all_thru=false;
+			mpu.filter.commonmsgs_thru=false;
+			mpu.filter.midi_thru=false;
+			for (Bitu i=0;i<16;i++) {
+				mpu.inputref[i].on=false;
+				for (Bitu j=0;j<4;j++) mpu.inputref[i].key[j]=0;
+			}
 			break;
-		case 0x94: /* Clock to host */
-			mpu.clock.clock_to_host=false;
+		case 0x34:
+			mpu.filter.timing_in_stop=true;
 			break;
-		case 0x95:
-			mpu.clock.clock_to_host=true;
+		case 0x35:
+			mpu.filter.modemsgs_in=true;
+			break;
+		case 0x37:
+			mpu.filter.sysex_thru=true;
+			break;
+		case 0x38:
+			mpu.filter.commonmsgs_in=true;
 			break;
-		case 0xc2: /* Internal timebase */
-			mpu.clock.timebase=48;
+		case 0x39:
+			mpu.filter.rt_in=true;
 			break;
-		case 0xc3:
-			mpu.clock.timebase=72;
+		case 0x3f:	/* UART mode */
+			LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Set UART mode %X",val);
+			mpu.mode=M_UART;
+			mpuhw.midi_thru=false;
 			break;
-		case 0xc4:
-			mpu.clock.timebase=96;
+
+
+		case 0x80:  /* Internal clock */
+			if (mpu.clock.active && mpu.state.sync_in) {
+				PIC_AddEvent(MPU401_Event,MPU401_TIMECONSTANT/(mpu.clock.freq));
+				mpu.clock.freq_mod=1.0;
+			}
+			mpu.state.sync_in=false;
 			break;
-		case 0xc5:
-			mpu.clock.timebase=120;
+//		case 0x81:  /* Sync to tape signal */
+		case 0x82:  /* Sync to MIDI */
+			mpu.clock.ticks_in=0;
+			mpu.state.sync_in=true;
 			break;
-		case 0xc6:
-			mpu.clock.timebase=144;
+		case 0x83: /* Metronome on without accents */
+			mpu.clock.metronome_state=1;
 			break;
-		case 0xc7:
-			mpu.clock.timebase=168;
+		case 0x84: /* Metronome off */
+			mpu.clock.metronome_state=0;
 			break;
-		case 0xc8:
-			mpu.clock.timebase=192;
+		case 0x85: /* Metronome on with accents */
+			mpu.clock.metronome_state=2;
 			break;
-		/* Commands with data byte */
-		case 0xe0: case 0xe1: case 0xe2: case 0xe4: case 0xe6:
-		case 0xe7: case 0xec: case 0xed: case 0xee: case 0xef:
-			mpu.state.command_byte=val;
+		case 0x86: case 0x87: /* Bender */
+			mpu.filter.bender_in=bool(val&1);
+			break;
+		case 0x88: case 0x89:/* MIDI through */
+			mpu.filter.midi_thru=bool(val&1);
+			for (Bitu i=0;i<16;i++) {
+				mpu.inputref[i].on=mpu.filter.midi_thru;
+				if (!(val&1)) for (Bitu j=0;j<4;j++) mpu.inputref[i].key[j]=0;
+			}
+			break;
+		case 0x8a: case 0x8b: /* Data in stop */
+			mpu.filter.data_in_stop=bool(val&1);
+			break;
+		case 0x8c: case 0x8d: /* Send measure end */
+			mpu.filter.rec_measure_end=bool(val&1);
+			break;
+		case 0x8e: case 0x8f: /* Conductor */
+			mpu.state.cond_set=bool(val&1);
+			break;
+		case 0x90: case 0x91: /* Realtime affection */
+			mpu.filter.rt_affection=bool(val&1);
+			break;
+//		case 0x92: /* Tape */
+//		case 0x93:
+		case 0x94: /* Clock to host */
+			mpu.state.clock_to_host=false;
+			MPU401_StopClock();
+			break;
+		case 0x95:
+			mpu.state.clock_to_host=true;
+			MPU401_StartClock();
+			break;
+		case 0x96: case 0x97: /* Sysex input allow */
+			mpu.filter.sysex_in=bool(val&1);
+			if (val&1) mpu.filter.sysex_thru=false;
+			break;
+		case 0x98:case 0x99:case 0x9a: case 0x9b: /* Reference tables on/off */ 
+		case 0x9c:case 0x9d:case 0x9e: case 0x9f:
+			mpu.chanref[(val-0x98)/2].on=bool(val&1);
 			break;
 		/* Commands 0xa# returning data */
 		case 0xab:	/* Request and clear recording counter */
-			QueueByte(MSG_MPU_ACK);
-			QueueByte(0);
+			MPU401_QueueByte(MSG_MPU_ACK);
+			MPU401_QueueByte(0);
+			SDL_mutexV(MPULock);
 			return;
 		case 0xac:	/* Request version */
-			QueueByte(MSG_MPU_ACK);
-			QueueByte(MPU401_VERSION);
+			MPU401_QueueByte(MSG_MPU_ACK);
+			MPU401_QueueByte(MPU401_VERSION);
+			SDL_mutexV(MPULock);
 			return;
 		case 0xad:	/* Request revision */
-			QueueByte(MSG_MPU_ACK);
-			QueueByte(MPU401_REVISION);
+			MPU401_QueueByte(MSG_MPU_ACK);
+			MPU401_QueueByte(MPU401_REVISION);
+			SDL_mutexV(MPULock);
 			return;
 		case 0xaf:	/* Request tempo */
-			QueueByte(MSG_MPU_ACK);
-			QueueByte(mpu.clock.tempo);
+			MPU401_QueueByte(MSG_MPU_ACK);
+			MPU401_QueueByte(mpu.clock.tempo);
+			SDL_mutexV(MPULock);
 			return;
 		case 0xb1:	/* Reset relative tempo */
-			mpu.clock.tempo_rel=40;
+			mpu.clock.tempo_rel=0x40;
 			break;
-		case 0xb9:	/* Clear play map */
 		case 0xb8:	/* Clear play counters */
-			for (Bitu i=0xb0;i<0xbf;i++) {	/* All notes off */
-				MIDI_RawOutByte(i);
-				MIDI_RawOutByte(0x7b);
-				MIDI_RawOutByte(0);
-			}
+			mpu.state.last_rtcmd=0;
 			for (Bitu i=0;i<8;i++) {
 				mpu.playbuf[i].counter=0;
 				mpu.playbuf[i].type=T_OVERFLOW;
 			}
 			mpu.condbuf.counter=0;
 			mpu.condbuf.type=T_OVERFLOW;
-			if (!(mpu.state.conductor=mpu.state.cond_set)) mpu.state.cond_req=0;
+
 			mpu.state.amask=mpu.state.tmask;
-			mpu.state.req_mask=0;
-			mpu.state.irq_pending=true;
+			mpu.state.conductor=mpu.state.cond_set;
+			mpu.clock.cth_counter=mpu.clock.cth_old=0;
+			mpu.clock.measure_counter=mpu.clock.meas_old=0;
+			break;
+		case 0xb9:	/* Clear play map */
+			for (Bitu i=0;i<16;i++) MPU401_NotesOff(i);
+			for (Bitu i=0;i<8;i++) {
+				mpu.playbuf[i].counter=0;
+				mpu.playbuf[i].type=T_OVERFLOW;
+			}
+			mpu.state.last_rtcmd=0;
+			mpu.clock.cth_counter=mpu.clock.cth_old=0;
+			mpu.clock.measure_counter=mpu.clock.meas_old=0;
+			break;
+		case 0xba: /* Clear record counter */
+			mpu.clock.rec_counter=0;
+			break;
+		case 0xc2: case 0xc3: case 0xc4: /* Internal timebase */
+		case 0xc5: case 0xc6: case 0xc7: case 0xc8:
+			mpu.clock.timebase=MPUClockBase[val-0xc2];
+			MPU401_ReCalcClock();
+			break;
+		case 0xdf:	/* Send system message */
+			mpu.state.wsd=false;
+			mpu.state.wsm=true;
+			mpu.state.wsd_start=true;
+			break;
+		case 0xe0: case 0xe1: case 0xe2: case 0xe4: case 0xe6: /* Commands with data byte */
+		case 0xe7: case 0xec: case 0xed: case 0xee: case 0xef:
+			mpu.state.command_byte=val;
 			break;
 		case 0xff:	/* Reset MPU-401 */
-			LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Reset %X",val);
-			PIC_AddEvent(MPU401_ResetDone,MPU401_RESETBUSY);
-			mpu.state.reset=true;
-			if (mpu.mode==M_UART) {
-				MPU401_Reset();
-				return;	//do not send ack in UART mode
+			LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Reset");
+			if (CPU_Cycles > 5) { //It came from the desert wants a fast irq
+				CPU_CycleLeft += CPU_Cycles;
+				CPU_Cycles = 5;
 			}
 			MPU401_Reset();
 			break;
-		case 0x3f:	/* UART mode */
-			LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Set UART mode %X",val);
-			mpu.mode=M_UART;
-			break;
 		default:;
-			//LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Unhandled command %X",val);
+			//LOG(LOG_MISC,LOG_WARN)("MPU-401:Unhandled command %X",val);
 	}
-	QueueByte(MSG_MPU_ACK);
+	MPU401_QueueByte(MSG_MPU_ACK);
+	SDL_mutexV(MPULock);
 }
 
 static Bitu MPU401_ReadData(Bitu port,Bitu iolen) {
 	Bit8u ret=MSG_MPU_ACK;
+	SDL_mutexP(MPULock);
 	if (mpu.queue_used) {
 		if (mpu.queue_pos>=MPU401_QUEUE) mpu.queue_pos-=MPU401_QUEUE;
 		ret=mpu.queue[mpu.queue_pos];
 		mpu.queue_pos++;mpu.queue_used--;
 	}
-	if (!mpu.intelligent) return ret;
 
-	if (mpu.queue_used == 0) PIC_DeActivateIRQ(mpu.irq);
+	if (mpu.mode==M_UART) {
+		if (!mpu.queue_used) SB16_MPU401_IrqToggle(false);
+		//else SB16_MPU401_IrqToggle(true);
+		if (mpuhw.intelligent && !mpu.queue_used) PIC_DeActivateIRQ(mpuhw.irq);
+		SDL_mutexV(MPULock);
+		return ret;
+	}
+	if (mpu.state.rec_copy && !mpu.rec_queue_used) {
+		mpu.state.rec_copy=false;
+		MPU401_EOIHandler();
+		SDL_mutexV(MPULock);
+		//LOG(LOG_MISC,LOG_NORMAL)("MPU401:read data eoi %x", ret);
+		return ret;
+	}
 
+	//copy from recording buffer
+	if (!mpu.queue_used && mpu.rec_queue_used) {
+		mpu.state.rec_copy=true;
+		if (mpu.rec_queue_pos>=MPU401_INPUT_QUEUE) mpu.rec_queue_pos-=MPU401_INPUT_QUEUE;
+		MPU401_QueueByte(mpu.rec_queue[mpu.rec_queue_pos]);
+		mpu.rec_queue_pos++;mpu.rec_queue_used--;
+	}
+	if (!mpu.queue_used) PIC_DeActivateIRQ(mpuhw.irq);
 	if (ret>=0xf0 && ret<=0xf7) { /* MIDI data request */
-		mpu.state.channel=ret&7;
+		mpu.state.track=ret&7;
 		mpu.state.data_onoff=0;
 		mpu.state.cond_req=false;
+		mpu.state.track_req=true;
 	}
 	if (ret==MSG_MPU_COMMAND_REQ) {
 		mpu.state.data_onoff=0;
@@ -293,33 +626,55 @@ static Bitu MPU401_ReadData(Bitu port,Bitu iolen) {
 			mpu.state.block_ack=true;
 			MPU401_WriteCommand(0x331,mpu.condbuf.value[0],1);
 			if (mpu.state.command_byte) MPU401_WriteData(0x330,mpu.condbuf.value[1],1);
+            mpu.condbuf.type=T_OVERFLOW;
 		}
-	mpu.condbuf.type=T_OVERFLOW;
 	}
-	if (ret==MSG_MPU_END || ret==MSG_MPU_CLOCK || ret==MSG_MPU_ACK) {
-		mpu.state.data_onoff=-1;
+	if (ret==MSG_MPU_END || ret==MSG_MPU_CLOCK || ret==MSG_MPU_ACK || ret==MSG_MPU_OVERFLOW) {
 		MPU401_EOIHandlerDispatch();
 	}
+
+	SDL_mutexV(MPULock);
+	//LOG(LOG_MISC,LOG_NORMAL)("MPU401:read data %x", ret);
 	return ret;
 }
 
 static void MPU401_WriteData(Bitu port,Bitu val,Bitu iolen) {
-	if (mpu.mode==M_UART) {MIDI_RawOutByte(val);return;}
+	//LOG(LOG_MISC,LOG_NORMAL)("MPU401:write data %x", val);
+	if (mpu.mode==M_UART) {MIDI_RawOutByte(val,MOUT_MPU);return;}
+	static Bitu length,cnt;
+
 	switch (mpu.state.command_byte) {	/* 0xe# command data */
 		case 0x00:
 			break;
 		case 0xe0:	/* Set tempo */
 			mpu.state.command_byte=0;
-			mpu.clock.tempo=val;
+			if (mpu.clock.tempo<8) mpu.clock.tempo=8;
+			else if (mpu.clock.tempo>250) mpu.clock.tempo=250;
+				else mpu.clock.tempo=val;
+			MPU401_ReCalcClock();
 			return;
 		case 0xe1:	/* Set relative tempo */
 			mpu.state.command_byte=0;
-			if (val!=0x40) //default value
-				LOG(LOG_MISC,LOG_ERROR)("MPU-401:Relative tempo change not implemented");
+			mpu.clock.tempo_rel=val;
+			MPU401_ReCalcClock();
+			return;
+		case 0xe2:	/* Set gradation for relative tempo */
+			mpu.clock.tempo_grad=val;
+			mpu.state.command_byte=0;
 			return;
+		case 0xe4:	/* Set MIDI clocks per metronome tick */
+			mpu.state.command_byte=0;
+			mpu.clock.midimetro=val;
+			return;
+		case 0xe6:	/* Set metronome ticks per measure */
+			mpu.state.command_byte=0;
+			mpu.clock.metromeas=val;
+            return;
 		case 0xe7:	/* Set internal clock to host interval */
 			mpu.state.command_byte=0;
-			mpu.clock.cth_rate=val>>2;
+			if (!val) val=64;
+			for (Bitu i=0;i<4;i++) mpu.clock.cth_rate[i]=(val>>2)+cth_data[(val&3)*4+i];
+			mpu.clock.cth_mode=0;
 			return;
 		case 0xec:	/* Set active track mask */
 			mpu.state.command_byte=0;
@@ -339,46 +694,47 @@ static void MPU401_WriteData(Bitu port,Bitu val,Bitu iolen) {
 			mpu.state.midi_mask&=0x00ff;
 			mpu.state.midi_mask|=((Bit16u)val)<<8;
 			return;
-		//case 0xe2:	/* Set graduation for relative tempo */
-		//case 0xe4:	/* Set metronome */
-		//case 0xe6:	/* Set metronome measure length */
 		default:
 			mpu.state.command_byte=0;
 			return;
 	}
-	static Bitu length,cnt,posd;
-	if (mpu.state.wsd) {	/* Directly send MIDI message */
+
+	if (mpu.state.wsd && !mpu.state.track_req && !mpu.state.cond_req) {	/* Directly send MIDI message */
 		if (mpu.state.wsd_start) {
 			mpu.state.wsd_start=0;
 			cnt=0;
-				switch (val&0xf0) {
-					case 0xc0:case 0xd0:
-						mpu.playbuf[mpu.state.channel].value[0]=val;
-						length=2;
-						break;
-					case 0x80:case 0x90:case 0xa0:case 0xb0:case 0xe0:
-						mpu.playbuf[mpu.state.channel].value[0]=val;
-						length=3;
-						break;
-					case 0xf0:
-						LOG(LOG_MISC,LOG_ERROR)("MPU-401:Illegal WSD byte");
-						mpu.state.wsd=0;
-						mpu.state.channel=mpu.state.old_chan;
-						return;
-					default: /* MIDI with running status */
-						cnt++;
-						MIDI_RawOutByte(mpu.playbuf[mpu.state.channel].value[0]);
-				}
+			switch (val&0xf0) {
+				case 0xc0:case 0xd0:
+					length=mpu.playbuf[mpu.state.track].length=2;
+					mpu.playbuf[mpu.state.track].type=T_MIDI_NORM;
+					break;
+				case 0x80:case 0x90:case 0xa0:case 0xb0:case 0xe0:
+					length=mpu.playbuf[mpu.state.track].length=3;
+					mpu.playbuf[mpu.state.track].type=T_MIDI_NORM;
+					break;
+				case 0xf0:
+					LOG(LOG_MISC,LOG_WARN)("MPU-401:Illegal MIDI voice message: %x",val);
+					mpu.state.wsd=0;
+					mpu.state.track=mpu.state.old_track;
+					return;
+				default: /* MIDI with running status */
+					cnt++;
+					length=mpu.playbuf[mpu.state.track].length;
+					mpu.playbuf[mpu.state.track].type=T_MIDI_NORM;
+			}
 		}
-		if (cnt<length) {MIDI_RawOutByte(val);cnt++;}
+		if (cnt<length) {
+			mpu.playbuf[mpu.state.track].value[cnt]=val;
+			cnt++;
+        }
 		if (cnt==length) {
+			MPU401_IntelligentOut(mpu.state.track);
 			mpu.state.wsd=0;
-			mpu.state.channel=mpu.state.old_chan;
+			mpu.state.track=mpu.state.old_track;
 		}
 		return;
 	}
-	if (mpu.state.wsm) {	/* Directly send system message */
-		if (val==MSG_EOX) {MIDI_RawOutByte(MSG_EOX);mpu.state.wsm=0;return;}
+	if (mpu.state.wsm && !mpu.state.track_req && !mpu.state.cond_req) {	/* Send system message */
 		if (mpu.state.wsd_start) {
 			mpu.state.wsd_start=0;
 			cnt=0;
@@ -388,24 +744,37 @@ static void MPU401_WriteData(Bitu port,Bitu val,Bitu iolen) {
 				case 0xf6:{ length=1; break;}
 				case 0xf0:{ length=0; break;}
 				default:
-					length=0;
+					LOG(LOG_MISC,LOG_WARN)
+					("MPU401:Illegal MIDI system common/exclusive message: %x",val);
+					mpu.state.wsm=0;
+					return;
 			}
+		} else if (val&0x80) {
+			MIDI_RawOutByte(MSG_EOX,MOUT_MPU);
+			mpu.state.wsm=0;
+			return;
+		}
+		if (!length || cnt<length) {
+			MIDI_RawOutByte(val,MOUT_MPU);
+			cnt++;
 		}
-		if (!length || cnt<length) {MIDI_RawOutByte(val);cnt++;}
 		if (cnt==length) mpu.state.wsm=0;
 		return;
 	}
+	SDL_mutexP(MPULock);
 	if (mpu.state.cond_req) { /* Command */
 		switch (mpu.state.data_onoff) {
 			case -1:
+				SDL_mutexV(MPULock);
 				return;
 			case  0: /* Timing byte */
-				mpu.condbuf.vlength=0;
+				mpu.condbuf.length=0;
 				if (val<0xf0) mpu.state.data_onoff++;
 				else {
+					mpu.state.cond_req=false;
 					mpu.state.data_onoff=-1;
 					MPU401_EOIHandlerDispatch();
-					return;
+                    break;
 				}
 				if (val==0) mpu.state.send_now=true;
 				else mpu.state.send_now=false;
@@ -413,106 +782,149 @@ static void MPU401_WriteData(Bitu port,Bitu val,Bitu iolen) {
 				break;
 			case  1: /* Command byte #1 */
 				mpu.condbuf.type=T_COMMAND;
-				if (val==0xf8 || val==0xf9) mpu.condbuf.type=T_OVERFLOW;
-				mpu.condbuf.value[mpu.condbuf.vlength]=val;
-				mpu.condbuf.vlength++;
-				if ((val&0xf0)!=0xe0) MPU401_EOIHandlerDispatch();
+				if (val==0xf8 || val==0xf9 || val==0xfc) mpu.condbuf.type=T_OVERFLOW;
+				mpu.condbuf.value[mpu.condbuf.length]=val;
+				mpu.condbuf.length++;
+				if ((val&0xf0)!=0xe0) { //no cmd data byte
+					MPU401_EOIHandler();
+					mpu.state.data_onoff=-1;
+					mpu.state.cond_req=false;
+				}
 				else mpu.state.data_onoff++;
 				break;
 			case  2:/* Command byte #2 */
-				mpu.condbuf.value[mpu.condbuf.vlength]=val;
-				mpu.condbuf.vlength++;
-				MPU401_EOIHandlerDispatch();
+				mpu.condbuf.value[mpu.condbuf.length]=val;
+				mpu.condbuf.length++;
+				MPU401_EOIHandler();
+				mpu.state.data_onoff=-1;
+				mpu.state.cond_req=false;
 				break;
 		}
+		SDL_mutexV(MPULock);
 		return;
 	}
 	switch (mpu.state.data_onoff) { /* Data */
 		case   -1:
-			return;
+            break;
 		case    0: /* Timing byte */
-			if (val<0xf0) mpu.state.data_onoff=1;
+			if (val<0xf0) mpu.state.data_onoff++;
 			else {
 				mpu.state.data_onoff=-1;
 				MPU401_EOIHandlerDispatch();
+				mpu.state.track_req=false;
+				SDL_mutexV(MPULock);
 				return;
 			}
 			if (val==0) mpu.state.send_now=true;
 			else mpu.state.send_now=false;
-			mpu.playbuf[mpu.state.channel].counter=val;
-			break;
-		case    1: /* MIDI */
-			mpu.playbuf[mpu.state.channel].vlength++;
-			posd=mpu.playbuf[mpu.state.channel].vlength;
-			if (posd==1) {
-				switch (val&0xf0) {
-					case 0xf0: /* System message or mark */
-						if (val>0xf7) {
-							mpu.playbuf[mpu.state.channel].type=T_MARK;
-							mpu.playbuf[mpu.state.channel].sys_val=val;
-							length=1;
-						} else {
-							LOG(LOG_MISC,LOG_ERROR)("MPU-401:Illegal message");
-							mpu.playbuf[mpu.state.channel].type=T_MIDI_SYS;
-							mpu.playbuf[mpu.state.channel].sys_val=val;
-							length=1;
-						}
-						break;
-					case 0xc0: case 0xd0: /* MIDI Message */
-						mpu.playbuf[mpu.state.channel].type=T_MIDI_NORM;
-						length=mpu.playbuf[mpu.state.channel].length=2;
-						break;
-					case 0x80: case 0x90: case 0xa0:  case 0xb0: case 0xe0:
-						mpu.playbuf[mpu.state.channel].type=T_MIDI_NORM;
-						length=mpu.playbuf[mpu.state.channel].length=3;
-						break;
-					default: /* MIDI data with running status */
-						posd++;
-						mpu.playbuf[mpu.state.channel].vlength++;
-						mpu.playbuf[mpu.state.channel].type=T_MIDI_NORM;
-						length=mpu.playbuf[mpu.state.channel].length;
-						break;
+			mpu.playbuf[mpu.state.track].counter=val;
+			break;
+		case 1: /* MIDI */
+			cnt=0;
+			mpu.state.data_onoff++;
+			switch (val&0xf0) {
+				case 0xc0:case 0xd0:
+					length=mpu.playbuf[mpu.state.track].length=2;
+					mpu.playbuf[mpu.state.track].type=T_MIDI_NORM;
+					break;
+				case 0x80:case 0x90:case 0xa0:case 0xb0:case 0xe0:
+					length=mpu.playbuf[mpu.state.track].length=3;
+					mpu.playbuf[mpu.state.track].type=T_MIDI_NORM;
+					break;
+				case 0xf0:
+					mpu.playbuf[mpu.state.track].sys_val=val;
+					if (val>0xf7) {
+						mpu.playbuf[mpu.state.track].type=T_MARK;
+						if (val==0xf9) mpu.clock.measure_counter=0;
+					} else {
+						LOG(LOG_MISC,LOG_WARN)("MPU-401:Illegal message");
+						mpu.playbuf[mpu.state.track].type=T_OVERFLOW;
+					}
+					mpu.state.data_onoff=-1;
+					MPU401_EOIHandler();
+					mpu.state.track_req=false;
+					SDL_mutexV(MPULock);
+					return;
+				default: /* MIDI with running status */
+					cnt++;
+					length=mpu.playbuf[mpu.state.track].length;
+					mpu.playbuf[mpu.state.track].type=T_MIDI_NORM;
 				}
+		case 2:
+			if (cnt<length) {
+				mpu.playbuf[mpu.state.track].value[cnt]=val;
+				cnt++;
 			}
-			if (!(posd==1 && val>=0xf0)) mpu.playbuf[mpu.state.channel].value[posd-1]=val;
-			if (posd==length) MPU401_EOIHandlerDispatch();
+			if (cnt==length) {
+				mpu.state.data_onoff=-1;
+				mpu.state.track_req=false;
+				MPU401_EOIHandler();
+			}
+            break;
 	}
+	SDL_mutexV(MPULock);
+	return;
 }
 
-static void MPU401_IntelligentOut(Bit8u chan) {
-	Bitu val;
-	switch (mpu.playbuf[chan].type) {
+static void MPU401_IntelligentOut(Bitu track) {
+	Bitu chan,chrefnum;
+	Bit8u key,msg;
+	bool send,retrigger;
+	switch (mpu.playbuf[track].type) {
 		case T_OVERFLOW:
 			break;
 		case T_MARK:
-			val=mpu.playbuf[chan].sys_val;
-			if (val==0xfc) {
-				MIDI_RawOutByte(val);
-				mpu.state.amask&=~(1<<chan);
-				mpu.state.req_mask&=~(1<<chan);
+			if (mpu.playbuf[track].sys_val==0xfc) {
+				MIDI_RawOutRTByte(mpu.playbuf[track].sys_val);
+				mpu.state.amask&=~(1<<track);
 			}
 			break;
 		case T_MIDI_NORM:
-			for (Bitu i=0;i<mpu.playbuf[chan].vlength;i++)
-				MIDI_RawOutByte(mpu.playbuf[chan].value[i]);
-			break;
-		default:
-			break;
+				chan=mpu.playbuf[track].value[0]&0xf;
+				key=mpu.playbuf[track].value[1]&0x7f;
+				chrefnum=mpu.ch_toref[chan];
+				send=true;
+				retrigger=false;
+			switch (msg=mpu.playbuf[track].value[0]&0xf0) {
+				case 0x80: //note off
+					if (mpu.inputref[chan].on && (mpu.inputref[chan].M_GETKEY)) send=false;
+					if (mpu.chanref[chrefnum].on && (!mpu.chanref[chrefnum].M_GETKEY)) send=false;
+					mpu.chanref[chrefnum].M_DELKEY;
+					break;
+				case 0x90: //note on
+					if (mpu.inputref[chan].on && (mpu.inputref[chan].M_GETKEY)) retrigger=true;
+					if (mpu.chanref[chrefnum].on && (mpu.chanref[chrefnum].M_GETKEY)) retrigger=true;
+					mpu.chanref[chrefnum].M_SETKEY;
+					break;
+				case 0xb0:			
+					if (mpu.playbuf[track].value[1]==123) {/* All notes off */
+						MPU401_NotesOff(mpu.playbuf[track].value[0]&0xf);
+						return;
+					}
+					break;
+			}
+			if (retrigger) {
+					MIDI_RawOutByte(0x80|chan,MOUT_MPU);
+					MIDI_RawOutByte(key,MOUT_MPU);
+					MIDI_RawOutByte(0,MOUT_MPU);
+			}
+			if (send) for (Bitu i=0;i<mpu.playbuf[track].length;i++)
+					MIDI_RawOutByte(mpu.playbuf[track].value[i],MOUT_MPU);
 	}
 }
 
-static void UpdateTrack(Bit8u chan) {
-	MPU401_IntelligentOut(chan);
-	if (mpu.state.amask&(1<<chan)) {
-		mpu.playbuf[chan].vlength=0;
-		mpu.playbuf[chan].type=T_OVERFLOW;
-		mpu.playbuf[chan].counter=0xf0;
-		mpu.state.req_mask|=(1<<chan);
+static void UpdateTrack(Bit8u track) {
+	MPU401_IntelligentOut(track);
+	if (mpu.state.amask&(1<<track)) {
+		mpu.playbuf[track].type=T_OVERFLOW;
+		mpu.playbuf[track].counter=0xf0;
+		mpu.state.req_mask|=(1<<track);
 	} else {
 		if (mpu.state.amask==0 && !mpu.state.conductor) mpu.state.req_mask|=(1<<12);
 	}
 }
 
+#if 0
 static void UpdateConductor(void) {
 	if (mpu.condbuf.value[0]==0xfc) {
 		mpu.condbuf.value[0]=0;
@@ -525,32 +937,74 @@ static void UpdateConductor(void) {
 	mpu.condbuf.counter=0xf0;
 	mpu.state.req_mask|=(1<<9);
 }
+#endif
 
+//Timer event for hw sequencer
 static void MPU401_Event(Bitu val) {
+
 	if (mpu.mode==M_UART) return;
+
 	if (mpu.state.irq_pending) goto next_event;
-	for (Bitu i=0;i<8;i++) { /* Decrease counters */
-		if (mpu.state.amask&(1<<i)) {
-			mpu.playbuf[i].counter--;
-			if (mpu.playbuf[i].counter<=0) UpdateTrack(i);
+	if (mpu.state.playing) {
+		for (Bitu i=0;i<8;i++) { /* Decrease counters */
+			if (mpu.state.amask&(1<<i)) {
+				mpu.playbuf[i].counter--;
+				if (mpu.playbuf[i].counter<=0) UpdateTrack(i);
+			}
+		}
+		if (mpu.state.conductor) {
+			mpu.condbuf.counter--;
+			if (mpu.condbuf.counter<=0) {
+					mpu.condbuf.counter=0xf0;
+					mpu.state.req_mask|=(1<<9);
+			}
 		}
 	}
-	if (mpu.state.conductor) {
-		mpu.condbuf.counter--;
-		if (mpu.condbuf.counter<=0) UpdateConductor();
-	}
-	if (mpu.clock.clock_to_host) {
+	if (mpu.state.clock_to_host) {
 		mpu.clock.cth_counter++;
-		if (mpu.clock.cth_counter >= mpu.clock.cth_rate) {
+		if (mpu.clock.cth_counter >= mpu.clock.cth_rate[mpu.clock.cth_mode]) {
 			mpu.clock.cth_counter=0;
+			mpu.clock.cth_mode=(++mpu.clock.cth_mode)%4;
 			mpu.state.req_mask|=(1<<13);
 		}
 	}
-	if (!mpu.state.irq_pending && mpu.state.req_mask) MPU401_EOIHandler();
+	if (mpu.state.rec==M_RECON) { //recording
+		mpu.clock.rec_counter++;
+		if (mpu.clock.rec_counter>=240) {
+			mpu.clock.rec_counter=0;
+			mpu.state.req_mask|=(1<<8);
+		}
+	}
+	bool accented;
+	if (mpu.state.playing || mpu.state.rec==M_RECON) {
+		accented=false;
+		Bits max_meascnt = (mpu.clock.timebase*mpu.clock.midimetro*mpu.clock.metromeas)/24;
+		if (max_meascnt!=0) { //measure end
+			if (++mpu.clock.measure_counter>=max_meascnt) {
+				if (mpu.filter.rt_out) MIDI_RawOutRTByte(0xf8);
+				mpu.clock.measure_counter=0;
+				if (mpu.filter.rec_measure_end && mpu.state.rec==M_RECON) 
+					mpu.state.req_mask|=(1<<12);
+
+				#ifdef MPU401_METRONOME
+				if (mpu.clock.metronome_state==2) MPU401_MetronomeSound(accented=true);
+				#endif
+			}
+		}
+		#ifdef MPU401_METRONOME
+		if (mpu.clock.metronome_state && !accented)
+			if (!(mpu.clock.measure_counter%(mpu.clock.timebase/24*mpu.clock.midimetro)))
+				MPU401_MetronomeSound(false);
+		#endif
+	}
+	if (!mpu.state.irq_pending && mpu.state.req_mask) {
+		SDL_mutexP(MPULock);
+		MPU401_EOIHandler();
+		SDL_mutexV(MPULock);
+	}
 next_event:
-	Bitu new_time;
-	if ((new_time=mpu.clock.tempo*mpu.clock.timebase)==0) return;
-	PIC_AddEvent(MPU401_Event,MPU401_TIMECONSTANT/new_time);
+	MPU401_RunClock();
+	if (mpu.state.sync_in) mpu.clock.ticks_in++;
 }
 
 
@@ -567,65 +1021,349 @@ static void MPU401_EOIHandler(Bitu val) {
 	mpu.state.eoi_scheduled=false;
 	if (mpu.state.send_now) {
 		mpu.state.send_now=false;
-		if (mpu.state.cond_req) UpdateConductor();
-		else UpdateTrack(mpu.state.channel);
+		if (mpu.state.cond_req) {
+				mpu.condbuf.counter=0xf0;
+				mpu.state.req_mask|=(1<<9);
+		}
+		else UpdateTrack(mpu.state.track);
 	}
+	if (mpu.state.rec_copy || !mpu.state.sysex_in_finished) return;
+
 	mpu.state.irq_pending=false;
-	if (!mpu.state.playing || !mpu.state.req_mask) return;
+	if (!(mpu.state.req_mask && mpu.clock.active)) return;
+
 	Bitu i=0;
 	do {
 		if (mpu.state.req_mask&(1<<i)) {
-			QueueByte(0xf0+i);
+			MPU401_QueueByte(0xf0+i);
 			mpu.state.req_mask&=~(1<<i);
 			break;
 		}
 	} while ((i++)<16);
 }
+static INLINE void MPU401_NotesOff(Bitu i) {
+	if (mpu.filter.allnotesoff_out && !(mpu.inputref[i].on &&
+		(mpu.inputref[i].key[0]|mpu.inputref[i].key[1]|
+		mpu.inputref[i].key[2]|mpu.inputref[i].key[3]))) {
+		for (Bitu j=0;j<4;j++) mpu.chanref[mpu.ch_toref[i]].key[j]=0;
+		//if (mpu.chanref[mpu.ch_toref[i]].on) {
+		MIDI_RawOutByte(0xb0|i,MOUT_MPU);
+		MIDI_RawOutByte(123,MOUT_MPU);
+		MIDI_RawOutByte(0,MOUT_MPU);
+	}
+	else if (mpu.chanref[mpu.ch_toref[i]].on) 
+			for (Bitu key=0;key<128;key++) {
+				if ((mpu.chanref[mpu.ch_toref[i]].M_GETKEY) &&
+					!(mpu.inputref[i].on && (mpu.inputref[i].M_GETKEY))) {
+					MIDI_RawOutByte(0x80|i,MOUT_MPU);
+					MIDI_RawOutByte(key,MOUT_MPU);
+					MIDI_RawOutByte(0,MOUT_MPU);
+				}
+			mpu.chanref[mpu.ch_toref[i]].M_DELKEY;
+		}
+}
+
+//Input handler for SysEx
+Bits MPU401_InputSysex(Bit8u* buffer,Bitu len,bool abort) {
+	if (mpu.filter.sysex_in) {
+		if (abort) {
+			mpu.state.sysex_in_finished=true;
+			mpu.rec_queue_used=0;//reset also the input queue
+			return 0;
+		}
+		if (mpu.state.sysex_in_finished) {
+			if (mpu.rec_queue_used>=MPU401_INPUT_QUEUE) return len;
+			Bit8u val_ff=0xff;
+			MPU401_RecQueueBuffer(&val_ff,1,true);
+			mpu.state.sysex_in_finished=false;
+			mpu.clock.rec_counter=0;
+		}
+		if (mpu.rec_queue_used>=MPU401_INPUT_QUEUE) return len;
+		Bitu available=MPU401_INPUT_QUEUE-mpu.rec_queue_used;
+
+		if (available>=len) {
+			MPU401_RecQueueBuffer(buffer,len,true);
+			return 0;
+		}
+		else {
+			MPU401_RecQueueBuffer(buffer,available,true);
+			if (mpu.state.sysex_in_finished) return 0;
+			return (len-available);
+		}
+	}
+	else if (mpu.filter.sysex_thru && mpuhw.midi_thru) {
+		MIDI_RawOutByte(0xf0,MOUT_THRU);
+		for (Bitu i=0;i<len;i++) MIDI_RawOutByte(*(buffer+i),MOUT_THRU);
+	}
+	return 0;
+}
+
+//Input handler for MIDI
+void MPU401_InputMsg(Bit8u msg[4]) {
+	//abort if sysex transfer is in progress
+	if (!mpu.state.sysex_in_finished) return;
+	static Bit8u old_msg=0;
+	Bit8u len=msg[3];
+	bool send=true;
+	bool send_thru=false;
+	bool retrigger_thru=false;
+	bool midistatus=false;
+	if (mpu.mode==M_INTELLIGENT) {
+		if (msg[0]<0x80) {			// Expand running status
+			midistatus=true;
+			msg[2]=msg[1];msg[1]=msg[0];msg[0]=old_msg;
+		}
+		old_msg=msg[0];
+		Bitu chan=msg[0]&0xf;
+		Bitu chrefnum=mpu.ch_toref[chan];
+		Bit8u key=msg[1]&0x7f;
+		if (msg[0]<0xf0) { //if non-system msg
+			if (!(mpu.state.midi_mask&(1<<chan)) && mpu.filter.all_thru) send_thru=true;
+			else if (mpu.filter.midi_thru) send_thru=true;
+			switch (msg[0]&0xf0) {
+				case 0x80: //note off
+					if (send_thru) {
+						if (mpu.chanref[chrefnum].on && (mpu.chanref[chrefnum].M_GETKEY))
+							send_thru=false;
+						if (!mpu.filter.midi_thru) break;
+						if  (!(mpu.inputref[chan].M_GETKEY)) send_thru=false;
+						 mpu.inputref[chan].M_DELKEY;
+					}
+					break;
+				case 0x90: //note on
+					if (send_thru) {
+						if (mpu.chanref[chrefnum].on && (mpu.chanref[chrefnum].M_GETKEY))
+							retrigger_thru=true;
+						if (!mpu.filter.midi_thru) break;
+						if (mpu.inputref[chan].M_GETKEY) retrigger_thru=true;
+						mpu.inputref[chan].M_SETKEY;
+					}
+					break;
+				case 0xb0:
+					if (msg[1]>=120) {
+						send_thru=false;
+						if (msg[1]==123)  /* All notes off */
+							for (key=0;key<128;key++) {
+								if (!(mpu.chanref[chrefnum].on && (mpu.chanref[chrefnum].M_GETKEY)))
+									if (mpu.inputref[chan].on && mpu.inputref[chan].M_GETKEY) {
+										MIDI_RawOutByte(0x80|chan,MOUT_THRU);
+										MIDI_RawOutByte(key,MOUT_THRU);
+										MIDI_RawOutByte(0,MOUT_THRU);
+									}
+								mpu.inputref[chan].M_DELKEY;
+							}
+					}
+					break;
+			}
+		}
+		if (msg[0]>=0xf0 || (mpu.state.midi_mask&(1<<chan)))
+			switch (msg[0]&0xf0) {
+				case 0xa0: //aftertouch
+					if (!mpu.filter.bender_in) send=false;
+					break;
+				case 0xb0: //control change
+					if (!mpu.filter.bender_in && msg[1]<64) send=false;
+					if (msg[1]>=120) if (mpu.filter.modemsgs_in) send=true;
+					break;
+				case 0xc0: //program change
+					if (mpu.state.rec!=M_RECON && !mpu.filter.data_in_stop) {
+							mpu.filter.prchg_buf[chan]=msg[1];
+							mpu.filter.prchg_mask|=1<<chan;
+						}
+					break;
+				case 0xd0: //ch pressure
+				case 0xe0: //pitch wheel
+					if (!mpu.filter.bender_in) send=false;
+					break;
+				case 0xf0: //system message
+					if (msg[0]==0xf8) {
+						send=false;
+						if (mpu.clock.active && mpu.state.sync_in) {
+							send = false;//don't pass to host in this mode?
+							Bits tick=mpu.clock.timebase/24;
+							if (mpu.clock.ticks_in!=tick) {
+								if (!mpu.clock.ticks_in || mpu.clock.ticks_in>tick*2) mpu.clock.freq_mod*=2.0;
+								else {
+									if (abs(mpu.clock.ticks_in-tick)==1)
+										mpu.clock.freq_mod/=mpu.clock.ticks_in/float(tick*2);
+									else
+										mpu.clock.freq_mod/=mpu.clock.ticks_in/float(tick);
+								}
+								MPU401_ReCalcClock();
+							}
+							mpu.clock.ticks_in=0;
+						}
+					}
+					else if (msg[0]>0xf8) { //realtime
+						if (!(mpu.filter.rt_in && msg[0]<=0xfc && msg[0]>=0xfa)) {
+							Bit8u recdata[2]={0xff,msg[0]};
+							MPU401_RecQueueBuffer(recdata,2,true);
+							send=false;
+						}
+					}
+					else { //common or system
+						send=false;
+						if (msg[0]==0xf2 || msg[0]==0xf3 || msg[0]==0xf6) {
+							if (mpu.filter.commonmsgs_in) send=true;
+							if (mpu.filter.commonmsgs_thru) 
+								for (Bitu i=0;i<len;i++) MIDI_RawOutByte(msg[i],MOUT_THRU);
+						}
+					}
+					if (send) {
+						Bit8u recmsg[4]={0xff,msg[0],msg[1],msg[2]};
+						MPU401_RecQueueBuffer(recmsg,len+1,true);
+					}
+					if (mpu.filter.rt_affection) switch(msg[0]) {
+						case 0xf2:case 0xf3:
+							mpu.state.block_ack=true;
+							MPU401_WriteCommand(0x331,0xb8,1);//clear play counters
+							break;
+						case 0xfa:
+							mpu.state.block_ack=true;
+							MPU401_WriteCommand(0x331,0xa,1);//start,play
+							if (mpu.filter.rt_out) MIDI_RawOutThruRTByte(msg[0]);
+							break;
+						case 0xfb:
+							mpu.state.block_ack=true;
+							MPU401_WriteCommand(0x331,0xb,1);//continue,play
+							if (mpu.filter.rt_out) MIDI_RawOutThruRTByte(msg[0]);
+							break;
+						case 0xfc:
+							mpu.state.block_ack=true;
+							MPU401_WriteCommand(0x331,0xd,1);//stop: play,rec,midi
+							if (mpu.filter.rt_out) MIDI_RawOutThruRTByte(msg[0]);
+							break;
+					}
+					return;
+			}
+		if (send_thru && mpuhw.midi_thru) {
+			if (retrigger_thru) {
+				MIDI_RawOutByte(0x80|(msg[0]&0xf),MOUT_THRU);
+				MIDI_RawOutByte(msg[1],MOUT_THRU);
+				MIDI_RawOutByte(msg[2],MOUT_THRU);
+			}
+			for (Bitu i=0/*((midistatus && !retrigger_thru)? 1:0)*/;i<len;i++) 
+				MIDI_RawOutByte(msg[i],MOUT_THRU);
+		}
+		if (send) {
+			if (mpu.state.rec==M_RECON) {
+				Bit8u recmsg[4]={mpu.clock.rec_counter,msg[0],msg[1],msg[2]};
+				MPU401_RecQueueBuffer(recmsg,len+1,true);
+				mpu.clock.rec_counter=0;
+			}
+			else if (mpu.filter.data_in_stop) {
+				if (mpu.filter.timing_in_stop) {
+					Bit8u recmsg[4]={0,msg[0],msg[1],msg[2]};
+					MPU401_RecQueueBuffer(recmsg,len+1,true);
+				}
+				else {
+					Bit8u recmsg[4]={msg[0],msg[1],msg[2],0};
+					MPU401_RecQueueBuffer(recmsg,len,true);
+				}
+			}
+		}
+		return;
+	}
+	//UART mode input
+	SDL_mutexP(MPULock);
+	for (Bitu i=0;i<len;i++) MPU401_QueueByte(msg[i]);
+	SDL_mutexV(MPULock);
+	SB16_MPU401_IrqToggle(true);
+}
+
 
 static void MPU401_ResetDone(Bitu) {
+#if 0
 	mpu.state.reset=false;
+#endif
 	if (mpu.state.cmd_pending) {
 		MPU401_WriteCommand(0x331,mpu.state.cmd_pending-1,1);
 		mpu.state.cmd_pending=0;
 	}
 }
 static void MPU401_Reset(void) {
-	PIC_DeActivateIRQ(mpu.irq);
-	mpu.mode=(mpu.intelligent ? M_INTELLIGENT : M_UART);
-	PIC_RemoveEvents(MPU401_EOIHandler);
-	mpu.state.eoi_scheduled=false;
-	mpu.state.wsd=false;
-	mpu.state.wsm=false;
-	mpu.state.conductor=false;
-	mpu.state.cond_req=false;
-	mpu.state.cond_set=false;
-	mpu.state.playing=false;
-	mpu.state.run_irq=false;
+	PIC_DeActivateIRQ(mpuhw.irq);
 	mpu.state.irq_pending=false;
-	mpu.state.cmask=0xff;
-	mpu.state.amask=mpu.state.tmask=0;
+	memset(&mpu,0,sizeof(mpu));
+	mpu.mode=(mpuhw.intelligent ? M_INTELLIGENT : M_UART);
+	mpuhw.midi_thru=false;
+	mpu.state.rec=M_RECOFF;
 	mpu.state.midi_mask=0xffff;
-	mpu.state.data_onoff=-1;
-	mpu.state.command_byte=0;
-	mpu.state.block_ack=false;
 	mpu.clock.tempo=mpu.clock.old_tempo=100;
 	mpu.clock.timebase=mpu.clock.old_timebase=120;
-	mpu.clock.tempo_rel=mpu.clock.old_tempo_rel=40;
-	mpu.clock.tempo_grad=0;
-	mpu.clock.clock_to_host=false;
-	mpu.clock.cth_rate=60;
-	mpu.clock.cth_counter=0;
-	ClrQueue();
-	mpu.state.req_mask=0;
-	mpu.condbuf.counter=0;
+	mpu.clock.tempo_rel=mpu.clock.old_tempo_rel=0x40;
+	mpu.clock.freq_mod=1.0;
+	MPU401_StopClock();
+	MPU401_ReCalcClock();
+	for (Bitu i=0;i<4;i++) mpu.clock.cth_rate[i]=60;
+	mpu.clock.midimetro=12;
+	mpu.clock.metromeas=8;
+	mpu.filter.rec_measure_end=true;
+	mpu.filter.rt_out=true;
+	mpu.filter.rt_affection=true;
+	mpu.filter.allnotesoff_out=true;
+	mpu.filter.all_thru=true;
+	mpu.filter.midi_thru=true;
+	mpu.filter.commonmsgs_thru=true;
+	//reset channel reference and input tables
+	for (Bitu i=0;i<4;i++) {
+		mpu.chanref[i].on=true;
+		mpu.chanref[i].chan=i;
+		mpu.ch_toref[i]=i;
+	}
+	for (Bitu i=0;i<16;i++) {
+		mpu.inputref[i].on=true;
+		mpu.inputref[i].chan=i;
+		if (i>3) mpu.ch_toref[i]=4;//dummy reftable
+	}
+	MPU401_ClrQueue();
+	mpu.state.data_onoff=-1;
 	mpu.condbuf.type=T_OVERFLOW;
-	for (Bitu i=0;i<8;i++) {mpu.playbuf[i].type=T_OVERFLOW;mpu.playbuf[i].counter=0;}
+	for (Bitu i=0;i<8;i++) mpu.playbuf[i].type=T_OVERFLOW;
+	//clear MIDI buffers, terminate notes
+	MIDI_ClearBuffer(MOUT_MPU);
+	MIDI_ClearBuffer(MOUT_THRU);
+	for (Bitu i=0xb0;i<=0xbf;i++){
+		MIDI_RawOutByte(i,MOUT_MPU);
+		MIDI_RawOutByte(0x7b,MOUT_MPU);
+		MIDI_RawOutByte(0,MOUT_MPU);
+	}
 }
 
+#ifdef MPU401_METRONOME
+
+static void MPU401_MetronomeCallback(Bitu len) {
+	if (!mpuhw.metronome.gen) return;
+	if (mpuhw.metronome.duration--)
+		if (mpuhw.metronome.accent)
+			mpuhw.metronome.mixerChan->AddSamples_s16(len,(Bit16s*)mpuhw.metronome.majorBeat);
+		else
+			mpuhw.metronome.mixerChan->AddSamples_s16(len,(Bit16s*)mpuhw.metronome.minorBeat);
+	else {
+		mpuhw.metronome.mixerChan->AddSamples_s16(len,(Bit16s*)mpuhw.metronome.emptyBuf);
+		mpuhw.metronome.duration=0;
+	}
+	if (!(mpu.state.playing || mpu.state.rec==M_RECON)) {
+		mpuhw.metronome.mixerChan->Enable(false);
+		mpuhw.metronome.gen=false;
+	}
+}
+
+static void MPU401_MetronomeSound(bool accented) {
+	if (!mpuhw.metronome.gen) {
+		if (!mpuhw.metronome.mixerChan)
+			mpuhw.metronome.mixerChan=mpuhw.metronome.mixObj.
+				Install(MPU401_MetronomeCallback,11025,"Metronome");
+		mpuhw.metronome.mixerChan->Enable(true);
+		mpuhw.metronome.gen=true;
+		mpuhw.metronome.accent=accented;
+	}
+	mpuhw.metronome.duration=20;
+}
+#endif
+
 class MPU401:public Module_base{
 private:
-	IO_ReadHandleObject ReadHandler[2];
-	IO_WriteHandleObject WriteHandler[2];
 	bool installed; /*as it can fail to install by 2 ways (config and no midi)*/
 public:
 	MPU401(Section* configuration):Module_base(configuration){
@@ -639,28 +1377,42 @@ public:
 		/*Enabled and there is a Midi */
 		installed = true;
 
-		WriteHandler[0].Install(0x330,&MPU401_WriteData,IO_MB);
-		WriteHandler[1].Install(0x331,&MPU401_WriteCommand,IO_MB);
-		ReadHandler[0].Install(0x330,&MPU401_ReadData,IO_MB);
-		ReadHandler[1].Install(0x331,&MPU401_ReadStatus,IO_MB);
+		MPULock=SDL_CreateMutex();
+
+		mpuhw.WriteHandler[0].Install(0x330,&MPU401_WriteData,IO_MB);
+		mpuhw.WriteHandler[1].Install(0x331,&MPU401_WriteCommand,IO_MB);
+		mpuhw.ReadHandler[0].Install(0x330,&MPU401_ReadData,IO_MB);
+		mpuhw.ReadHandler[1].Install(0x331,&MPU401_ReadStatus,IO_MB);
 
 		mpu.queue_used=0;
 		mpu.queue_pos=0;
+		mpu.state.irq_pending=false;
 		mpu.mode=M_UART;
-		mpu.irq=9;	/* Princess Maker 2 wants it on irq 9 */
+		mpuhw.irq=9;	/* Princess Maker 2 wants it on irq 9 */
 
-		mpu.intelligent = true;	//Default is on
-		if(strcasecmp(s_mpu,"uart") == 0) mpu.intelligent = false;
-		if (!mpu.intelligent) return;
+		mpuhw.intelligent = true;	//Default is on
+		if(strcasecmp(s_mpu,"uart") == 0) mpuhw.intelligent = false;
+		if (!mpuhw.intelligent) return;
 		/*Set IRQ and unmask it(for timequest/princess maker 2) */
-		PIC_SetIRQMask(mpu.irq,false);
+		PIC_SetIRQMask(mpuhw.irq,false);
 		MPU401_Reset();
+
+#ifdef MPU401_METRONOME
+		/* Generate sound (square wave) for metronome */
+		for (Bitu i=0;i<1105;i++) {
+			*(((Bit16s*)mpuhw.metronome.majorBeat)+i)=11000*((i%20)>=10?1:-1)-5500;
+			*(((Bit16s*)mpuhw.metronome.minorBeat)+i)=8000*((i%24)>=12?1:-1)-4000;
+		}
+#endif
 	}
 	~MPU401(){
 		if(!installed) return;
+		PIC_RemoveEvents(MPU401_Event);
+		SDL_DestroyMutex(MPULock);
+		MPULock=0;
 		Section_prop * section=static_cast<Section_prop *>(m_configuration);
 		if(strcasecmp(section->Get_string("mpu401"),"intelligent")) return;
-		PIC_SetIRQMask(mpu.irq,true);
+		PIC_SetIRQMask(mpuhw.irq,true);
 		}
 };
 
diff --git a/src/hardware/sblaster.cpp b/src/hardware/sblaster.cpp
index e4a9b029..2f04741c 100644
--- a/src/hardware/sblaster.cpp
+++ b/src/hardware/sblaster.cpp
@@ -24,6 +24,7 @@
 #include "dosbox.h"
 #include "inout.h"
 #include "mixer.h"
+#include "midi.h"
 #include "dma.h"
 #include "pic.h"
 #include "hardware.h"
@@ -32,8 +33,6 @@
 #include "shell.h"
 using namespace std;
 
-void MIDI_RawOutByte(Bit8u data);
-bool MIDI_Available(void);
 
 #define SB_PIC_EVENTS 0
 
@@ -71,7 +70,6 @@ enum DSP_MODES {
 	MODE_DMA,
 	MODE_DMA_PAUSE,
 	MODE_DMA_MASKED
-
 };
  
 enum DMA_MODES {
@@ -108,6 +106,7 @@ struct SB_INFO {
 	struct {
 		bool pending_8bit;
 		bool pending_16bit;
+		bool pending_mpuirq;
 	} irq;
 	struct {
 		Bit8u state;
@@ -122,6 +121,14 @@ struct SB_INFO {
 		} in,out;
 		Bit8u test_register;
 		Bitu write_busy;
+		Bitu midi_timestamp;
+		bool midiin_sysex;
+		bool midiin_poll;
+		bool midiin_poll_once;
+		bool midiin_irq;
+		bool midiout_raw;
+		bool midiin_timestamp;
+		
 	} dsp;
 	struct {
 		Bit16s data[DSP_DACSIZE+1];
@@ -158,6 +165,8 @@ struct SB_INFO {
 
 static SB_INFO sb;
 
+SDL_mutex * SBLock;
+
 static char const * const copyright_string="COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992.";
 
 // number of bytes in input for commands (sb/sbpro)
@@ -265,12 +274,26 @@ static INLINE void SB_RaiseIRQ(SB_IRQS type) {
 		break;
 	default:
 		break;
+	case SB_IRQ_MPU:
+		sb.irq.pending_mpuirq=true;
+		break;
 	}
 }
 
 static INLINE void DSP_FlushData(void) {
 	sb.dsp.out.used=0;
 	sb.dsp.out.pos=0;
+	sb.dsp.midiin_sysex=false;
+}
+
+static void SB_MIDIIN_OFF(bool prepare_input) {
+	if (prepare_input) MIDI_ToggleInputDevice(MDEV_SBUART,true);
+	sb.dsp.midiin_poll_once=false;
+	sb.dsp.midiin_poll=false;
+	sb.dsp.midiin_irq=false;
+	sb.dsp.midiin_timestamp=false;
+	sb.dsp.midiout_raw=false;
+	DSP_FlushData();
 }
 
 static double last_dma_callback = 0.0f;
@@ -473,7 +496,7 @@ static void GenerateDMASound(Bitu size) {
 			read=sb.dma.chan->Read(size,&sb.dma.buf.b8[sb.dma.remain_size]);
 			Bitu total=read+sb.dma.remain_size;
             if (!sb.dma.sign)  sb.chan->AddSamples_s8(total>>1,sb.dma.buf.b8);
-            else sb.chan->AddSamples_s8s(total>>1,(Bit8s*)sb.dma.buf.b8); 
+            else sb.chan->AddSamples_s8s(total>>1,(Bit8s*)sb.dma.buf.b8);
 			if (total&1) {
 				sb.dma.remain_size=1;
 				sb.dma.buf.b8[0]=sb.dma.buf.b8[total-1];
@@ -759,6 +782,8 @@ static void DSP_Reset(void) {
 	sb.dsp.cmd_len=0;
 	sb.dsp.in.pos=0;
 	sb.dsp.write_busy=0;
+	SB_MIDIIN_OFF(false);
+	MIDI_ClearBuffer(MOUT_SBUART);
 	PIC_RemoveEvents(DSP_FinishReset);
 
 	sb.dma.left=0;
@@ -831,6 +856,7 @@ Bitu DEBUG_EnableDebugger(void);
 #define DSP_SB2_ABOVE if (sb.type <= SBT_1) { LOG(LOG_SB,LOG_ERROR)("DSP:Command %2X requires SB2 or above",sb.dsp.cmd); break; } 
 
 static void DSP_DoCommand(void) {
+    bool tmpmidi = false;
 //	LOG_MSG("DSP Command %X",sb.dsp.cmd);
 	switch (sb.dsp.cmd) {
 	case 0x04:
@@ -908,9 +934,6 @@ static void DSP_DoCommand(void) {
 		DSP_SB2_ABOVE; /* Note: 0x90 is documented only for DSP ver.2.x and 3.x, not 4.x */
 		DSP_PrepareDMA_Old(DSP_DMA_8,true,false);
 		break;
-	case 0x38:  /* Write to SB MIDI Output */
-		if (sb.midi == true) MIDI_RawOutByte(sb.dsp.in.data[0]);
-		break;
 	case 0x40:	/* Set Timeconstant */
 		DSP_ChangeRate(1000000 / (256 - sb.dsp.in.data[0]));
 		break;
@@ -1061,12 +1084,43 @@ static void DSP_DoCommand(void) {
 		DSP_FlushData();
 		DSP_AddData(0);
 		break;
-	case 0x30: case 0x31:
-		LOG(LOG_SB,LOG_ERROR)("DSP:Unimplemented MIDI I/O command %2X",sb.dsp.cmd);
-		break;
-	case 0x34: case 0x35: case 0x36: case 0x37:
+	case 0x30: case 0x32:   /* MIDI In Poll */
+		//LOG(LOG_SB,LOG_NORMAL)("MIDI cmd:%x",sb.dsp.cmd);
+		if (!sb.midi) break;
+		SB_MIDIIN_OFF(true);
+		sb.dsp.midiin_poll_once=true;
+		if (sb.dsp.cmd==0x32) {sb.dsp.midiin_timestamp=true;LOG(LOG_SB,LOG_WARN)("MIDI timestamp mode");}
+		break;
+	case 0x31: case 0x33:  /* MIDI In Irq */
+		//LOG(LOG_SB,LOG_NORMAL)("MIDI cmd:%x",sb.dsp.cmd);
+		if (!sb.midi) break;
+		tmpmidi=sb.dsp.midiin_irq;
+		SB_MIDIIN_OFF(true);
+		if (!tmpmidi) sb.dsp.midiin_irq=true;
+		else sb.dsp.midiin_irq=false;
+		if (sb.dsp.cmd==0x33) sb.dsp.midiin_timestamp=sb.dsp.midiin_irq;
+		if (sb.dsp.midiin_timestamp) LOG(LOG_SB,LOG_WARN)("MIDI timestamp mode");
+		break;
+	case 0x34: case 0x36:/* MIDI In Poll + Out Raw */
+		//LOG(LOG_SB,LOG_NORMAL)("MIDI cmd:%x",sb.dsp.cmd);
 		DSP_SB2_ABOVE;
-		LOG(LOG_SB,LOG_ERROR)("DSP:Unimplemented MIDI UART command %2X",sb.dsp.cmd);
+		if (!sb.midi) break;
+		SB_MIDIIN_OFF(true);
+		sb.dsp.midiin_poll=true;
+		sb.dsp.midiout_raw=true;
+		if (sb.dsp.cmd==0x36) {sb.dsp.midiin_timestamp=true;LOG(LOG_SB,LOG_WARN)("MIDI timestamp mode");}
+		break;
+	case 0x35: case 0x37:  /* MIDI In Irq + Out Raw */
+		//LOG(LOG_SB,LOG_NORMAL)("MIDI cmd:%x",sb.dsp.cmd);
+		DSP_SB2_ABOVE;
+		if (!sb.midi) break;
+		SB_MIDIIN_OFF(true);
+		sb.dsp.midiin_irq=true;
+		sb.dsp.midiout_raw=true;
+		if (sb.dsp.cmd==0x37) {sb.dsp.midiin_timestamp=true;LOG(LOG_SB,LOG_WARN)("MIDI timestamp mode");}
+		break;
+	case 0x38:  /* Write to SB MIDI Output (Raw) */
+		if (sb.midi) MIDI_RawOutByte(sb.dsp.in.data[0],MOUT_SBUART);
 		break;
 	case 0x7f: case 0x1f:
 		DSP_SB2_ABOVE;
@@ -1147,12 +1201,14 @@ static void DSP_DoWrite(Bit8u val) {
 
 static Bit8u DSP_ReadData(void) {
 /* Static so it repeats the last value on succesive reads (JANGLE DEMO) */
+	SDL_mutexP(SBLock);
 	if (sb.dsp.out.used) {
 		sb.dsp.out.lastval=sb.dsp.out.data[sb.dsp.out.pos];
 		sb.dsp.out.pos++;
 		if (sb.dsp.out.pos>=DSP_BUFSIZE) sb.dsp.out.pos-=DSP_BUFSIZE;
 		sb.dsp.out.used--;
 	}
+	SDL_mutexV(SBLock);
 	return sb.dsp.out.lastval;
 }
 
@@ -1452,8 +1508,9 @@ static Bit8u CTMIXER_Read(void) {
 		}
 		return ret;
 	case 0x82:		/* IRQ Status */
-		return	(sb.irq.pending_8bit ? 0x1 : 0) |
-				(sb.irq.pending_16bit ? 0x2 : 0) | 
+		return	(sb.irq.pending_8bit   ? 0x1 : 0) |
+				(sb.irq.pending_16bit  ? 0x2 : 0) |
+				(sb.irq.pending_mpuirq ? 0x4 : 0) |
 				((sb.type == SBT_16) ? 0x20 : 0);
 	default:
 		if (	((sb.type == SBT_PRO1 || sb.type == SBT_PRO2) && sb.mixer.index==0x0c) || /* Input control on SBPro */
@@ -1474,6 +1531,11 @@ static Bitu read_sb(Bitu port,Bitu /*iolen*/) {
 	case MIXER_DATA:
 		return CTMIXER_Read();
 	case DSP_READ_DATA:
+		if (sb.dsp.midiin_poll_once) sb.dsp.midiin_poll_once=false;
+		if (sb.dsp.midiin_irq) {
+				if (sb.dsp.out.used<=1) sb.irq.pending_8bit=false;
+				else DSP_RaiseIRQEvent(0);
+		}
 		return DSP_ReadData();
 	case DSP_READ_STATUS:
 		//TODO See for high speed dma :)
@@ -1513,7 +1575,8 @@ static void write_sb(Bitu port,Bitu val,Bitu /*iolen*/) {
 		DSP_DoReset(val8);
 		break;
 	case DSP_WRITE_DATA:
-		DSP_DoWrite(val8);
+		if (sb.dsp.midiout_raw) MIDI_RawOutByte(val,MOUT_SBUART);
+		else DSP_DoWrite(val);
 		break;
 	case MIXER_INDEX:
 		sb.mixer.index=val8;
@@ -1544,6 +1607,55 @@ bool SB_Get_Address(Bitu& sbaddr, Bitu& sbirq, Bitu& sbdma) {
 	}
 }
 
+void SB_UART_InputMsg(Bit8u msg[4]) {
+	if (sb.dsp.midiin_sysex) return;
+	if (SBLock) SDL_mutexP(SBLock);
+	Bit8u len=msg[3];
+	if (sb.dsp.midiin_timestamp) {
+		//TODO: calculate timestamps
+		DSP_AddData(0);DSP_AddData(0);DSP_AddData(0);
+	}
+	if (sb.dsp.midiin_poll_once) {
+		for (Bitu i=0;i<len;i++) DSP_AddData(msg[i]);
+	}
+	if (sb.dsp.midiin_irq) {
+		for (Bitu i=0;i<len;i++) DSP_AddData(msg[i]);
+		if (!sb.irq.pending_8bit) DSP_RaiseIRQEvent(0);
+	}
+	else if (sb.dsp.midiin_poll) {
+		for (Bitu i=0;i<len;i++) DSP_AddData(msg[i]);
+	}
+	if (SBLock) SDL_mutexV(SBLock);
+}
+
+Bits SB_UART_InputSysex(Bit8u *buffer,Bitu len,bool abort) {
+	if (abort) {
+		DSP_FlushData();
+		sb.dsp.midiin_sysex=false;
+		return 0;
+	}
+	sb.dsp.midiin_sysex=true;
+	if (SBLock) SDL_mutexP(SBLock);
+	for (Bitu i=0;i<len;i++) {
+		if (sb.dsp.out.used>=DSP_BUFSIZE) {if (SBLock) SDL_mutexV(SBLock);return (len-i);}
+		DSP_AddData(buffer[i]);
+	}
+	if (SBLock) SDL_mutexV(SBLock);
+	sb.dsp.midiin_sysex=false;
+	return 0;
+}
+
+void SB16_MPU401_IrqToggle(bool status) {
+	if (sb.type!=SBT_16) return;
+	if  (status) {
+		if (!sb.irq.pending_mpuirq) SB_RaiseIRQ(SB_IRQ_MPU);
+	}
+	else if (sb.irq.pending_mpuirq) {
+		sb.irq.pending_mpuirq=false;
+		PIC_DeActivateIRQ(sb.hw.irq);
+	}
+}
+
 static void SBLASTER_CallBack(Bitu len) {
 	switch (sb.mode) {
 	case MODE_NONE:
@@ -1703,6 +1815,8 @@ public:
 		/* Soundblaster midi interface */
 		if (!MIDI_Available()) sb.midi = false;
 		else sb.midi = true;
+
+		SBLock = SDL_CreateMutex();
 	}	
 	
 	~SBLASTER() {
@@ -1722,7 +1836,9 @@ public:
 			break;
 		}
 		if (sb.type==SBT_NONE || sb.type==SBT_GB) return;
-		DSP_Reset(); // Stop everything	
+		DSP_Reset(); //Stop everything
+		SDL_DestroyMutex(SBLock);
+		SBLock=0;
 	}	
 }; //End of SBLASTER class
 
-- 
2.21.0

