diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h
index 52364ab58..ccbab55f7 100644
--- a/src/sound/i_musicinterns.h
+++ b/src/sound/i_musicinterns.h
@@ -600,9 +600,9 @@ public:
 protected:
 	void Heapify();
 
-	unsigned int Parent(unsigned int i) { return (i + 1u) / 2u - 1u; }
-	unsigned int Left(unsigned int i) { return (i + 1u) * 2u - 1u; }
-	unsigned int Right(unsigned int i) { return (i + 1u) * 2u; }
+	unsigned int Parent(unsigned int i) const { return (i + 1u) / 2u - 1u; }
+	unsigned int Left(unsigned int i) const { return (i + 1u) * 2u - 1u; }
+	unsigned int Right(unsigned int i) const { return (i + 1u) * 2u; }
 };
 
 class HMISong : public MIDIStreamer
@@ -630,7 +630,7 @@ protected:
 	struct TrackInfo;
 
 	void ProcessInitialMetaEvents ();
-	DWORD *SendCommand (DWORD *event, TrackInfo *track, DWORD delay);
+	DWORD *SendCommand (DWORD *event, TrackInfo *track, DWORD delay, ptrdiff_t room, bool &sysex_noroom);
 	TrackInfo *FindNextDue ();
 
 	static DWORD ReadVarLenHMI(TrackInfo *);
@@ -673,7 +673,7 @@ protected:
 	void AdvanceSong(DWORD time);
 
 	void ProcessInitialMetaEvents();
-	DWORD *SendCommand (DWORD *event, EventSource track, DWORD delay);
+	DWORD *SendCommand (DWORD *event, EventSource track, DWORD delay, ptrdiff_t room, bool &sysex_noroom);
 	EventSource FindNextDue();
 
 	BYTE *MusHeader;
diff --git a/src/sound/music_hmi_midiout.cpp b/src/sound/music_hmi_midiout.cpp
index ca3ae0905..1796b1372 100644
--- a/src/sound/music_hmi_midiout.cpp
+++ b/src/sound/music_hmi_midiout.cpp
@@ -523,7 +523,12 @@ DWORD *HMISong::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time)
 			// Play all events for this tick.
 			do
 			{
-				DWORD *new_events = SendCommand(events, TrackDue, time);
+				bool sysex_noroom = false;
+				DWORD *new_events = SendCommand(events, TrackDue, time, max_event_p - events, sysex_noroom);
+				if (sysex_noroom)
+				{
+					return events;
+				}
 				TrackDue = FindNextDue();
 				if (new_events != events)
 				{
@@ -568,7 +573,7 @@ void HMISong::AdvanceTracks(DWORD time)
 //
 //==========================================================================
 
-DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
+DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptrdiff_t room, bool &sysex_noroom)
 {
 	DWORD len;
 	BYTE event, data1 = 0, data2 = 0;
@@ -584,10 +589,20 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
 		return events + 3;
 	}
 
+	sysex_noroom = false;
+	size_t start_p = track->TrackP;
+
 	CHECK_FINISHED
 	event = track->TrackBegin[track->TrackP++];
 	CHECK_FINISHED
 
+	// The actual event type will be filled in below. If it's not a NOP,
+	// the events pointer will be advanced once the actual event is written.
+	// Otherwise, we do it at the end of the function.
+	events[0] = delay;
+	events[1] = 0;
+	events[2] = MEVT_NOP << 24;
+
 	if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND && event != 0xFe)
 	{
 		// Normal short message
@@ -626,17 +641,10 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
 			data2 = VolumeControllerChange(event & 15, data2);
 		}
 
-		events[0] = delay;
-		events[1] = 0;
 		if (event != MIDI_META)
 		{
 			events[2] = event | (data1<<8) | (data2<<16);
 		}
-		else
-		{
-			events[2] = MEVT_NOP << 24;
-		}
-		events += 3;
 
 		if (ReadVarLen == ReadVarLenHMI && (event & 0x70) == (MIDI_NOTEON & 0x70))
 		{ // HMI note on events include the time until an implied note off event.
@@ -645,13 +653,41 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
 	}
 	else
 	{
-		// Skip SysEx events just because I don't want to bother with them.
-		// The old MIDI player ignored them too, so this won't break
-		// anything that played before.
+		// SysEx events could potentially not have enough room in the buffer...
 		if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
 		{
 			len = ReadVarLen(track);
-			track->TrackP += len;
+			if (len >= (MAX_EVENTS-1)*3*4)
+			{ // This message will never fit. Throw it away.
+				track->TrackP += len;
+			}
+			else if (len + 12 >= (size_t)room * 4)
+			{ // Not enough room left in this buffer. Backup and wait for the next one.
+				track->TrackP = start_p;
+				sysex_noroom = true;
+				return events;
+			}
+			else
+			{
+				BYTE *msg = (BYTE *)&events[3];
+				if (event == MIDI_SYSEX)
+				{ // Need to add the SysEx marker to the message.
+					events[2] = (MEVT_LONGMSG << 24) | (len + 1);
+					*msg++ = MIDI_SYSEX;
+				}
+				else
+				{
+					events[2] = (MEVT_LONGMSG << 24) | len;
+				}
+				memcpy(msg, &track->TrackBegin[track->TrackP], len);
+				msg += len;
+				// Must pad with 0
+				while ((size_t)msg & 3)
+				{
+					*msg++ = 0;
+				}
+				track->TrackP += len;
+			}
 		}
 		else if (event == MIDI_META)
 		{
@@ -677,7 +713,6 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
 					events[0] = delay;
 					events[1] = 0;
 					events[2] = (MEVT_TEMPO << 24) | Tempo;
-					events += 3;
 					break;
 				}
 				track->TrackP += len;
@@ -720,6 +755,18 @@ DWORD *HMISong::SendCommand (DWORD *events, TrackInfo *track, DWORD delay)
 	{
 		track->Delay = ReadVarLen(track);
 	}
+	// Advance events pointer unless this is a non-delaying NOP.
+	if (events[0] != 0 || MEVT_EVENTTYPE(events[2]) != MEVT_NOP)
+	{
+		if (MEVT_EVENTTYPE(events[2]) == MEVT_LONGMSG)
+		{
+			events += 3 + ((MEVT_EVENTPARM(events[2]) + 3) >> 2);
+		}
+		else
+		{
+			events += 3;
+		}
+	}
 	return events;
 }
 
diff --git a/src/sound/music_smf_midiout.cpp b/src/sound/music_smf_midiout.cpp
index 43755eb08..918639399 100644
--- a/src/sound/music_smf_midiout.cpp
+++ b/src/sound/music_smf_midiout.cpp
@@ -384,6 +384,11 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptr
 	event = track->TrackBegin[track->TrackP++];
 	CHECK_FINISHED
 
+	// The actual event type will be filled in below.
+	events[0] = delay;
+	events[1] = 0;
+	events[2] = MEVT_NOP << 24;
+
 	if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND)
 	{
 		// Normal short message
@@ -582,17 +587,10 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptr
 				break;
 			}
 		}
-		events[0] = delay;
-		events[1] = 0;
 		if (event != MIDI_META && (!track->Designated || (track->Designation & DesignationMask)))
 		{
 			events[2] = event | (data1<<8) | (data2<<16);
 		}
-		else
-		{
-			events[2] = MEVT_NOP << 24;
-		}
-		events += 3;
 	}
 	else
 	{
@@ -612,8 +610,6 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptr
 			}
 			else
 			{
-				events[0] = delay;
-				events[1] = 0;
 				BYTE *msg = (BYTE *)&events[3];
 				if (event == MIDI_SYSEX)
 				{ // Need to add the SysEx marker to the message.
@@ -631,7 +627,6 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptr
 				{
 					*msg++ = 0;
 				}
-				events = (DWORD *)msg;
 				track->TrackP += len;
 			}
 		}
@@ -659,7 +654,6 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptr
 					events[0] = delay;
 					events[1] = 0;
 					events[2] = (MEVT_TEMPO << 24) | Tempo;
-					events += 3;
 					break;
 				}
 				track->TrackP += len;
@@ -678,6 +672,18 @@ DWORD *MIDISong2::SendCommand (DWORD *events, TrackInfo *track, DWORD delay, ptr
 	{
 		track->Delay = track->ReadVarLen();
 	}
+	// Advance events pointer unless this is a non-delaying NOP.
+	if (events[0] != 0 || MEVT_EVENTTYPE(events[2]) != MEVT_NOP)
+	{
+		if (MEVT_EVENTTYPE(events[2]) == MEVT_LONGMSG)
+		{
+			events += 3 + ((MEVT_EVENTPARM(events[2]) + 3) >> 2);
+		}
+		else
+		{
+			events += 3;
+		}
+	}
 	return events;
 }
 
diff --git a/src/sound/music_xmi_midiout.cpp b/src/sound/music_xmi_midiout.cpp
index 71246f518..fbbf4db53 100644
--- a/src/sound/music_xmi_midiout.cpp
+++ b/src/sound/music_xmi_midiout.cpp
@@ -337,7 +337,12 @@ DWORD *XMISong::MakeEvents(DWORD *events, DWORD *max_event_p, DWORD max_time)
 			// Play all events for this tick.
 			do
 			{
-				DWORD *new_events = SendCommand(events, EventDue, time);
+				bool sysex_noroom = false;
+				DWORD *new_events = SendCommand(events, EventDue, time, max_event_p - events, sysex_noroom);
+				if (sysex_noroom)
+				{
+					return events;
+				}
 				EventDue = FindNextDue();
 				if (new_events != events)
 				{
@@ -382,7 +387,7 @@ void XMISong::AdvanceSong(DWORD time)
 //
 //==========================================================================
 
-DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay)
+DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay, ptrdiff_t room, bool &sysex_noroom)
 {
 	DWORD len;
 	BYTE event, data1 = 0, data2 = 0;
@@ -399,10 +404,20 @@ DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay)
 
 	TrackInfo *track = CurrSong;
 
+	sysex_noroom = false;
+	size_t start_p = track->EventP;
+
 	CHECK_FINISHED
 	event = track->EventChunk[track->EventP++];
 	CHECK_FINISHED
 
+	// The actual event type will be filled in below. If it's not a NOP,
+	// the events pointer will be advanced once the actual event is written.
+	// Otherwise, we do it at the end of the function.
+	events[0] = delay;
+	events[1] = 0;
+	events[2] = MEVT_NOP << 24;
+
 	if (event != MIDI_SYSEX && event != MIDI_META && event != MIDI_SYSEXEND)
 	{
 		// Normal short message
@@ -499,10 +514,6 @@ DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay)
 		{
 			events[2] = event | (data1<<8) | (data2<<16);
 		}
-		else
-		{
-			events[2] = MEVT_NOP << 24;
-		}
 		events += 3;
 
 
@@ -513,13 +524,41 @@ DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay)
 	}
 	else
 	{
-		// Skip SysEx events just because I don't want to bother with them.
-		// The old MIDI player ignored them too, so this won't break
-		// anything that played before.
+		// SysEx events could potentially not have enough room in the buffer...
 		if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
 		{
-			len = track->ReadVarLen ();
-			track->EventP += len;
+			len = track->ReadVarLen();
+			if (len >= (MAX_EVENTS-1)*3*4)
+			{ // This message will never fit. Throw it away.
+				track->EventP += len;
+			}
+			else if (len + 12 >= (size_t)room * 4)
+			{ // Not enough room left in this buffer. Backup and wait for the next one.
+				track->EventP = start_p;
+				sysex_noroom = true;
+				return events;
+			}
+			else
+			{
+				BYTE *msg = (BYTE *)&events[3];
+				if (event == MIDI_SYSEX)
+				{ // Need to add the SysEx marker to the message.
+					events[2] = (MEVT_LONGMSG << 24) | (len + 1);
+					*msg++ = MIDI_SYSEX;
+				}
+				else
+				{
+					events[2] = (MEVT_LONGMSG << 24) | len;
+				}
+				memcpy(msg, &track->EventChunk[track->EventP++], len);
+				msg += len;
+				// Must pad with 0
+				while ((size_t)msg & 3)
+				{
+					*msg++ = 0;
+				}
+				track->EventP += len;
+			}
 		}
 		else if (event == MIDI_META)
 		{
@@ -551,6 +590,18 @@ DWORD *XMISong::SendCommand (DWORD *events, EventSource due, DWORD delay)
 	{
 		track->Delay = track->ReadDelay();
 	}
+	// Advance events pointer unless this is a non-delaying NOP.
+	if (events[0] != 0 || MEVT_EVENTTYPE(events[2]) != MEVT_NOP)
+	{
+		if (MEVT_EVENTTYPE(events[2]) == MEVT_LONGMSG)
+		{
+			events += 3 + ((MEVT_EVENTPARM(events[2]) + 3) >> 2);
+		}
+		else
+		{
+			events += 3;
+		}
+	}
 	return events;
 }
 
diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp
index 6a33bf595..51a920c8e 100644
--- a/src/thingdef/thingdef_codeptr.cpp
+++ b/src/thingdef/thingdef_codeptr.cpp
@@ -4265,6 +4265,7 @@ enum T_Flags
 	TF_USEACTORFOG =	0x00000100, // Use the actor's TeleFogSourceType and TeleFogDestType fogs.
 	TF_NOJUMP =			0x00000200, // Don't jump after teleporting.
 	TF_OVERRIDE =		0x00000400, // Ignore NOTELEPORT.
+	TF_SENSITIVEZ =		0x00000800, // Fail if the actor wouldn't fit in the position (for Z).
 };
 
 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport)
@@ -4329,6 +4330,20 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport)
 		return;
 	}
 
+	
+	// [MC] By default, the function adjusts the actor's Z if it's below the floor or above the ceiling.
+	// This can be an issue as actors designed to maintain specific z positions wind up teleporting
+	// anyway when they should not, such as a floor rising above or ceiling lowering below the position
+	// of the spot.
+	if (flags & TF_SENSITIVEZ)
+	{
+		fixed_t posz = (flags & TF_USESPOTZ) ? spot->Z() : spot->floorz;
+		if ((posz + ref->height > spot->ceilingz) || (posz < spot->floorz))
+		{
+			ACTION_SET_RESULT(false);
+			return;
+		}
+	}
 	fixedvec3 prev = ref->Pos();
 	fixed_t aboveFloor = spot->Z() - spot->floorz;
 	fixed_t finalz = spot->floorz + aboveFloor;
@@ -4339,7 +4354,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport)
 		finalz = spot->floorz;
 
 	//Take precedence and cooperate with telefragging first.
-	bool tele_result = P_TeleportMove(ref, spot->X(), spot->Y(), finalz, flags & TF_TELEFRAG);
+	bool tele_result = P_TeleportMove(ref, spot->X(), spot->Y(), finalz, !!(flags & TF_TELEFRAG));
 
 	if (!tele_result && (flags & TF_FORCED))
 	{
@@ -4380,7 +4395,6 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Teleport)
 		}
 		
 		ref->SetZ((flags & TF_USESPOTZ) ? spot->Z() : ref->floorz, false);
-		self->SetZ((flags & TF_USESPOTZ) ? spot->Z() : self->floorz, false);
 
 		if (!(flags & TF_KEEPANGLE))
 			ref->angle = spot->angle;
diff --git a/wadsrc/static/actors/constants.txt b/wadsrc/static/actors/constants.txt
index 213e2109d..a88290451 100644
--- a/wadsrc/static/actors/constants.txt
+++ b/wadsrc/static/actors/constants.txt
@@ -208,6 +208,7 @@ enum
 	TF_USEACTORFOG =	0x00000100, // Use the actor's TeleFogSourceType and TeleFogDestType fogs.
 	TF_NOJUMP =			0x00000200, // Don't jump after teleporting.
 	TF_OVERRIDE =		0x00000400, // Ignore NOTELEPORT.
+	TF_SENSITIVEZ =		0x00000800, // Fail if the actor wouldn't fit in the position (for Z).
 
 	TF_KEEPORIENTATION = TF_KEEPVELOCITY|TF_KEEPANGLE,
 	TF_NOFOG = TF_NOSRCFOG|TF_NODESTFOG,