diff --git a/.travis.yml b/.travis.yml
index 6d4cc4bb7..77a2ea7ce 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,7 +12,7 @@ git:
 matrix:
   include:
     - os: osx
-      osx_image: xcode8.2
+      osx_image: xcode8.3
       env:
         - CMAKE_OPTIONS="-DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_DEPLOYMENT_TARGET=10.7"
 
diff --git a/soundfont/gzdoom.sf2 b/soundfont/gzdoom.sf2
new file mode 100644
index 000000000..27cc01c18
Binary files /dev/null and b/soundfont/gzdoom.sf2 differ
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a9b65cb1d..387d6855f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -517,7 +517,6 @@ set( PLAT_UNIX_SOURCES
 	posix/unix/i_specialpaths.cpp
 	posix/unix/iwadpicker_gtk.cpp )
 set( PLAT_OSX_SOURCES
-	sound/mididevices/music_audiotoolbox_mididevice.cpp
 	posix/osx/iwadpicker_cocoa.mm
 	posix/osx/i_specialpaths.mm
 	posix/osx/zdoom.icns )
@@ -1254,12 +1253,17 @@ if( MSVC )
 	create_default_target_launcher( zdoom WORKING_DIRECTORY ${ZDOOM_OUTPUT_DIR} )
 endif()
 
-if( NOT WIN32 )
+if( NOT WIN32 AND NOT APPLE )
 	FILE( WRITE ${CMAKE_CURRENT_BINARY_DIR}/link-make "if [ ! -e ${ZDOOM_OUTPUT_DIR}/${ZDOOM_EXE_NAME} ]; then ln -sf ${CMAKE_CURRENT_BINARY_DIR}/${ZDOOM_EXE_NAME} ${ZDOOM_OUTPUT_DIR}/${ZDOOM_EXE_NAME}; fi" )
 	add_custom_command( TARGET zdoom POST_BUILD
 		COMMAND chmod +x ${CMAKE_CURRENT_BINARY_DIR}/link-make
 		COMMAND /bin/sh -c ${CMAKE_CURRENT_BINARY_DIR}/link-make )
 endif()
+
+add_custom_command(TARGET zdoom POST_BUILD
+	COMMAND ${CMAKE_COMMAND} -E copy_if_different
+	${CMAKE_SOURCE_DIR}/soundfont/gzdoom.sf2 $<TARGET_FILE_DIR:zdoom>)
+
 if( CMAKE_COMPILER_IS_GNUCXX )
 	# GCC misoptimizes this file
 	set_source_files_properties( oplsynth/fmopl.cpp PROPERTIES COMPILE_FLAGS "-fno-tree-dominator-opts -fno-tree-fre" )
@@ -1278,7 +1282,7 @@ endif()
 
 if( APPLE )
 	set_target_properties(zdoom PROPERTIES
-		LINK_FLAGS "-framework AudioToolbox -framework AudioUnit -framework Carbon -framework Cocoa -framework IOKit -framework OpenGL"
+		LINK_FLAGS "-framework Carbon -framework Cocoa -framework IOKit -framework OpenGL"
 		MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/posix/osx/zdoom-info.plist" )
 
 endif()
diff --git a/src/d_main.cpp b/src/d_main.cpp
index 12acc62f5..650d99ae8 100644
--- a/src/d_main.cpp
+++ b/src/d_main.cpp
@@ -149,7 +149,7 @@ void ParseGLDefs();
 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
 
 void D_DoomLoop ();
-static const char *BaseFileSearch (const char *file, const char *ext, bool lookfirstinprogdir=false);
+const char *BaseFileSearch (const char *file, const char *ext, bool lookfirstinprogdir=false);
 
 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
 
@@ -1693,7 +1693,7 @@ static void D_AddDirectory (TArray<FString> &wadfiles, const char *dir)
 //
 //==========================================================================
 
-static const char *BaseFileSearch (const char *file, const char *ext, bool lookfirstinprogdir)
+const char *BaseFileSearch (const char *file, const char *ext, bool lookfirstinprogdir)
 {
 	static char wad[PATH_MAX];
 
diff --git a/src/gl/scene/gl_portal.cpp b/src/gl/scene/gl_portal.cpp
index 468ecefc2..5bccda3e7 100644
--- a/src/gl/scene/gl_portal.cpp
+++ b/src/gl/scene/gl_portal.cpp
@@ -119,6 +119,7 @@ void GLPortal::ClearScreen()
 	gl_RenderState.mViewMatrix.loadIdentity();
 	gl_RenderState.mProjectionMatrix.ortho(0, SCREENWIDTH, SCREENHEIGHT, 0, -1.0f, 1.0f);
 	gl_RenderState.ApplyMatrices();
+	glVertexAttrib4f(VATTR_COLOR, 0, 0, 0, 1);	// color should be black.
 
 	glDisable(GL_MULTISAMPLE);
 	glDisable(GL_DEPTH_TEST);
diff --git a/src/p_map.cpp b/src/p_map.cpp
index d997cffe6..5ffcbc423 100644
--- a/src/p_map.cpp
+++ b/src/p_map.cpp
@@ -2763,7 +2763,11 @@ bool P_CheckMove(AActor *thing, const DVector2 &pos, int flags)
 	FCheckPosition tm;
 	double		newz = thing->Z();
 
-	if (!P_CheckPosition(thing, pos, tm))
+	auto f1 = thing->flags & MF_PICKUP;
+	thing->flags &= ~MF_PICKUP;
+	auto res = P_CheckPosition(thing, pos, tm);
+	thing->flags |= f1;
+	if (!res)
 	{
 		// Ignore PCM_DROPOFF. Not necessary here: a little later it is.
 		if (!flags || (!(flags & PCM_NOACTORS) && !(flags & PCM_NOLINES)))
diff --git a/src/polyrenderer/poly_renderer.cpp b/src/polyrenderer/poly_renderer.cpp
index a0a0483b3..2da2dbcf9 100644
--- a/src/polyrenderer/poly_renderer.cpp
+++ b/src/polyrenderer/poly_renderer.cpp
@@ -159,8 +159,6 @@ void PolyRenderer::ClearBuffers()
 	PolyStencilBuffer::Instance()->Clear(RenderTarget->GetWidth(), RenderTarget->GetHeight(), 0);
 	PolySubsectorGBuffer::Instance()->Resize(RenderTarget->GetPitch(), RenderTarget->GetHeight());
 	NextStencilValue = 0;
-	SeenLinePortals.clear();
-	SeenMirrors.clear();
 }
 
 void PolyRenderer::SetSceneViewport()
@@ -216,13 +214,3 @@ void PolyRenderer::SetupPerspectiveMatrix()
 
 	WorldToClip = TriMatrix::perspective(fovy, ratio, 5.0f, 65535.0f) * worldToView;
 }
-
-bool PolyRenderer::InsertSeenLinePortal(FLinePortal *portal)
-{
-	return SeenLinePortals.insert(portal).second;
-}
-
-bool PolyRenderer::InsertSeenMirror(line_t *mirrorLine)
-{
-	return SeenMirrors.insert(mirrorLine).second;
-}
diff --git a/src/polyrenderer/poly_renderer.h b/src/polyrenderer/poly_renderer.h
index ead3c8eac..efd1bbf67 100644
--- a/src/polyrenderer/poly_renderer.h
+++ b/src/polyrenderer/poly_renderer.h
@@ -52,9 +52,6 @@ public:
 	
 	uint32_t GetNextStencilValue() { uint32_t value = NextStencilValue; NextStencilValue += 2; return value; }
 
-	bool InsertSeenLinePortal(FLinePortal *portal);
-	bool InsertSeenMirror(line_t *mirrorLine);
-
 	bool DontMapLines = false;
 	
 	RenderMemory FrameMemory;
@@ -75,7 +72,4 @@ private:
 	PolySkyDome Skydome;
 	RenderPolyPlayerSprites PlayerSprites;
 	uint32_t NextStencilValue = 0;
-
-	std::set<FLinePortal *> SeenLinePortals;
-	std::set<line_t *> SeenMirrors;
 };
diff --git a/src/polyrenderer/scene/poly_cull.cpp b/src/polyrenderer/scene/poly_cull.cpp
index 623b6e9df..8f19cb901 100644
--- a/src/polyrenderer/scene/poly_cull.cpp
+++ b/src/polyrenderer/scene/poly_cull.cpp
@@ -70,6 +70,20 @@ void PolyCull::CullNode(void *node)
 
 void PolyCull::CullSubsector(subsector_t *sub)
 {
+	// Check if subsector is clipped entirely by the portal clip plane
+	bool visible = false;
+	for (uint32_t i = 0; i < sub->numlines; i++)
+	{
+		seg_t *line = &sub->firstline[i];
+		if (PortalClipPlane.A * line->v1->fX() + PortalClipPlane.B * line->v1->fY() + PortalClipPlane.D > 0.0)
+		{
+			visible = true;
+			break;
+		}
+	}
+	if (!visible)
+		return;
+
 	// Update sky heights for the scene
 	if (!FirstSkyHeight)
 	{
@@ -98,6 +112,13 @@ void PolyCull::CullSubsector(subsector_t *sub)
 			if (pt1.Y * (pt1.X - pt2.X) + pt1.X * (pt2.Y - pt1.Y) >= 0)
 				continue;
 
+			// Skip line if entirely behind portal clipping plane
+			if ((PortalClipPlane.A * line->v1->fX() + PortalClipPlane.B * line->v1->fY() + PortalClipPlane.D <= 0.0) ||
+				(PortalClipPlane.A * line->v2->fX() + PortalClipPlane.B * line->v2->fY() + PortalClipPlane.D <= 0.0))
+			{
+				continue;
+			}
+
 			angle_t angle1, angle2;
 			if (GetAnglesForLine(line->v1->fX(), line->v1->fY(), line->v2->fX(), line->v2->fY(), angle1, angle2))
 			{
@@ -119,11 +140,11 @@ void PolyCull::InvertSegments()
 	angle_t cur = 0;
 	for (const auto &segment : TempInvertSolidSegments)
 	{
-		if (segment.Start != 0 || segment.End != ANGLE_MAX)
+		if (cur < segment.Start)
 			MarkSegmentCulled(cur, segment.Start - 1);
 		cur = segment.End + 1;
 	}
-	if (cur != 0)
+	if (cur < ANGLE_MAX)
 		MarkSegmentCulled(cur, ANGLE_MAX);
 }
 
@@ -241,32 +262,6 @@ bool PolyCull::CheckBBox(float *bspcoord)
 
 bool PolyCull::GetAnglesForLine(double x1, double y1, double x2, double y2, angle_t &angle1, angle_t &angle2) const
 {
-#if 0
-	// Clip line to the portal clip plane
-	float distance1 = Vec4f::dot(PortalClipPlane, Vec4f((float)x1, (float)y1, 0.0f, 1.0f));
-	float distance2 = Vec4f::dot(PortalClipPlane, Vec4f((float)x2, (float)y2, 0.0f, 1.0f));
-	if (distance1 < 0.0f && distance2 < 0.0f)
-	{
-		return false;
-	}
-	else if (distance1 < 0.0f || distance2 < 0.0f)
-	{
-		double t1 = 0.0f, t2 = 1.0f;
-		if (distance1 < 0.0f)
-			t1 = clamp(distance1 / (distance1 - distance2), 0.0f, 1.0f);
-		else
-			t2 = clamp(distance2 / (distance1 - distance2), 0.0f, 1.0f);
-		double nx1 = x1 * (1.0 - t1) + x2 * t1;
-		double ny1 = y1 * (1.0 - t1) + y2 * t1;
-		double nx2 = x1 * (1.0 - t2) + x2 * t2;
-		double ny2 = y1 * (1.0 - t2) + y2 * t2;
-		x1 = nx1;
-		x2 = nx2;
-		y1 = ny1;
-		y2 = ny2;
-	}
-#endif
-
 	angle2 = PointToPseudoAngle(x1, y1);
 	angle1 = PointToPseudoAngle(x2, y2);
 	return !IsSegmentCulled(angle1, angle2);
diff --git a/src/polyrenderer/scene/poly_cull.h b/src/polyrenderer/scene/poly_cull.h
index 64e4d5739..0af3b22f9 100644
--- a/src/polyrenderer/scene/poly_cull.h
+++ b/src/polyrenderer/scene/poly_cull.h
@@ -40,6 +40,8 @@ public:
 	double MaxCeilingHeight = 0.0;
 	double MinFloorHeight = 0.0;
 
+	static angle_t PointToPseudoAngle(double x, double y);
+
 private:
 	struct SolidSegment
 	{
@@ -62,6 +64,5 @@ private:
 
 	PolyClipPlane PortalClipPlane;
 
-	static angle_t PointToPseudoAngle(double x, double y);
 	static angle_t AngleToPseudo(angle_t ang);
 };
diff --git a/src/polyrenderer/scene/poly_portal.cpp b/src/polyrenderer/scene/poly_portal.cpp
index 0e0ffbf38..ff529895f 100644
--- a/src/polyrenderer/scene/poly_portal.cpp
+++ b/src/polyrenderer/scene/poly_portal.cpp
@@ -43,44 +43,10 @@ void PolyDrawSectorPortal::Render(int portalDepth)
 	if (Portal->mType == PORTS_HORIZON || Portal->mType == PORTS_PLANE)
 		return;
 
-	const auto &viewpoint = PolyRenderer::Instance()->Viewpoint;
-
-	PolyClipPlane portalPlane(0.0f, 0.0f, 0.0f, 1.0f);
-	if (Portal->mType != PORTS_SKYVIEWPOINT)
-	{
-		float minHeight;
-		float maxHeight;
-		bool first = true;
-		for (const auto &range : Shape)
-		{
-			for (int i = 0; i < range.Count; i++)
-			{
-				if (first)
-				{
-					minHeight = range.Vertices[i].z;
-					maxHeight = range.Vertices[i].z;
-					first = false;
-				}
-				else
-				{
-					minHeight = MIN(minHeight, range.Vertices[i].z);
-					maxHeight = MAX(maxHeight, range.Vertices[i].z);
-				}
-			}
-		}
-
-		if (!first && minHeight > viewpoint.Pos.Z)
-		{
-			portalPlane = PolyClipPlane(0.0f, 0.0f, 1.0f, -minHeight);
-		}
-		else if (!first && maxHeight < viewpoint.Pos.Z)
-		{
-			portalPlane = PolyClipPlane(0.0f, 0.0f, -1.0f, maxHeight);
-		}
-	}
-
 	SaveGlobals();
 
+	const auto &viewpoint = PolyRenderer::Instance()->Viewpoint;
+
 	// To do: get this information from PolyRenderer instead of duplicating the code..
 	const auto &viewwindow = PolyRenderer::Instance()->Viewwindow;
 	double radPitch = viewpoint.Angles.Pitch.Normalized180().Radians();
@@ -100,6 +66,7 @@ void PolyDrawSectorPortal::Render(int portalDepth)
 		TriMatrix::translate((float)-viewpoint.Pos.X, (float)-viewpoint.Pos.Y, (float)-viewpoint.Pos.Z);
 	TriMatrix worldToClip = TriMatrix::perspective(fovy, ratio, 5.0f, 65535.0f) * worldToView;
 
+	PolyClipPlane portalPlane(0.0f, 0.0f, 0.0f, 1.0f);
 	RenderPortal.SetViewpoint(worldToClip, portalPlane, StencilValue);
 	RenderPortal.SetPortalSegments(Segments);
 	RenderPortal.Render(portalDepth);
@@ -211,14 +178,29 @@ void PolyDrawLinePortal::Render(int portalDepth)
 		worldToView = TriMatrix::scale(-1.0f, 1.0f, 1.0f) * worldToView;
 	TriMatrix worldToClip = TriMatrix::perspective(fovy, ratio, 5.0f, 65535.0f) * worldToView;
 
-	// Calculate plane clipping
+	// Find portal destination line and make sure it faces the right way
 	line_t *clipLine = Portal ? Portal->mDestination : Mirror;
-	DVector2 planePos = clipLine->v1->fPos();
-	DVector2 planeNormal = (clipLine->v2->fPos() - clipLine->v1->fPos()).Rotated90CW();
+	DVector2 pt1 = clipLine->v1->fPos() - viewpoint.Pos;
+	DVector2 pt2 = clipLine->v2->fPos() - viewpoint.Pos;
+	bool backfacing = (pt1.Y * (pt1.X - pt2.X) + pt1.X * (pt2.Y - pt1.Y) >= 0);
+	vertex_t *v1 = backfacing ? clipLine->v1 : clipLine->v2;
+	vertex_t *v2 = backfacing ? clipLine->v2 : clipLine->v1;
+
+	// Calculate plane clipping
+	DVector2 planePos = v1->fPos();
+	DVector2 planeNormal = (v2->fPos() - v1->fPos()).Rotated90CW();
 	planeNormal.MakeUnit();
 	double planeD = -(planeNormal | (planePos + planeNormal * 0.001));
 	PolyClipPlane portalPlane((float)planeNormal.X, (float)planeNormal.Y, (float)0.0f, (float)planeD);
 
+	// Cull everything outside the portal line
+	// To do: this doesn't work for some strange reason..
+	/*angle_t angle1 = PolyCull::PointToPseudoAngle(v1->fX(), v1->fY());
+	angle_t angle2 = PolyCull::PointToPseudoAngle(v2->fX(), v2->fY());
+	Segments.clear();
+	Segments.push_back({ angle1, angle2 });*/
+
+	RenderPortal.LastPortalLine = clipLine;
 	RenderPortal.SetViewpoint(worldToClip, portalPlane, StencilValue);
 	RenderPortal.SetPortalSegments(Segments);
 	RenderPortal.Render(portalDepth);
@@ -296,6 +278,9 @@ void PolyDrawLinePortal::SaveGlobals()
 		P_TranslatePortalXY(src, viewpoint.Path[0].X, viewpoint.Path[0].Y);
 		P_TranslatePortalXY(src, viewpoint.Path[1].X, viewpoint.Path[1].Y);
 
+		if (viewpoint.camera)
+			viewpoint.camera->renderflags &= ~RF_INVISIBLE;
+
 		if (!viewpoint.showviewer && viewpoint.camera && P_PointOnLineSidePrecise(viewpoint.Path[0], dst) != P_PointOnLineSidePrecise(viewpoint.Path[1], dst))
 		{
 			double distp = (viewpoint.Path[0] - viewpoint.Path[1]).Length();
@@ -312,8 +297,8 @@ void PolyDrawLinePortal::SaveGlobals()
 		}
 	}
 
-	//camera = nullptr;
-	//viewsector = R_PointInSubsector(ViewPos)->sector;
+	viewpoint.camera = nullptr;
+	viewpoint.sector = R_PointInSubsector(viewpoint.Pos)->sector;
 	R_SetViewAngle(viewpoint, viewwindow);
 
 	if (Mirror)
diff --git a/src/polyrenderer/scene/poly_scene.cpp b/src/polyrenderer/scene/poly_scene.cpp
index 21ae23fc8..ad0d88d34 100644
--- a/src/polyrenderer/scene/poly_scene.cpp
+++ b/src/polyrenderer/scene/poly_scene.cpp
@@ -219,7 +219,7 @@ void RenderPolyScene::RenderPolySubsector(subsector_t *sub, uint32_t subsectorDe
 			}
 
 			// Render wall, and update culling info if its an occlusion blocker
-			if (RenderPolyWall::RenderLine(WorldToClip, PortalPlane, Cull, line, frontsector, subsectorDepth, StencilValue, TranslucentObjects, LinePortals))
+			if (RenderPolyWall::RenderLine(WorldToClip, PortalPlane, Cull, line, frontsector, subsectorDepth, StencilValue, TranslucentObjects, LinePortals, LastPortalLine))
 			{
 				Cull.MarkSegmentCulled(angle1, angle2);
 			}
@@ -319,7 +319,7 @@ void RenderPolyScene::RenderLine(subsector_t *sub, seg_t *line, sector_t *fronts
 	}
 
 	// Render wall, and update culling info if its an occlusion blocker
-	if (RenderPolyWall::RenderLine(WorldToClip, PortalPlane, Cull, line, frontsector, subsectorDepth, StencilValue, TranslucentObjects, LinePortals))
+	if (RenderPolyWall::RenderLine(WorldToClip, PortalPlane, Cull, line, frontsector, subsectorDepth, StencilValue, TranslucentObjects, LinePortals, LastPortalLine))
 	{
 		Cull.MarkSegmentCulled(angle1, angle2);
 	}
diff --git a/src/polyrenderer/scene/poly_scene.h b/src/polyrenderer/scene/poly_scene.h
index fc89de7cd..8dafc949b 100644
--- a/src/polyrenderer/scene/poly_scene.h
+++ b/src/polyrenderer/scene/poly_scene.h
@@ -80,6 +80,8 @@ public:
 
 	static const uint32_t SkySubsectorDepth = 0x7fffffff;
 
+	line_t *LastPortalLine = nullptr;
+
 private:
 	void ClearBuffers();
 	void RenderPortals(int portalDepth);
diff --git a/src/polyrenderer/scene/poly_wall.cpp b/src/polyrenderer/scene/poly_wall.cpp
index ce9e78456..87ee9ec12 100644
--- a/src/polyrenderer/scene/poly_wall.cpp
+++ b/src/polyrenderer/scene/poly_wall.cpp
@@ -37,36 +37,51 @@
 
 EXTERN_CVAR(Bool, r_drawmirrors)
 
-bool RenderPolyWall::RenderLine(const TriMatrix &worldToClip, const PolyClipPlane &clipPlane, PolyCull &cull, seg_t *line, sector_t *frontsector, uint32_t subsectorDepth, uint32_t stencilValue, std::vector<PolyTranslucentObject*> &translucentWallsOutput, std::vector<std::unique_ptr<PolyDrawLinePortal>> &linePortals)
+bool RenderPolyWall::RenderLine(const TriMatrix &worldToClip, const PolyClipPlane &clipPlane, PolyCull &cull, seg_t *line, sector_t *frontsector, uint32_t subsectorDepth, uint32_t stencilValue, std::vector<PolyTranslucentObject*> &translucentWallsOutput, std::vector<std::unique_ptr<PolyDrawLinePortal>> &linePortals, line_t *lastPortalLine)
 {
+	double frontceilz1 = frontsector->ceilingplane.ZatPoint(line->v1);
+	double frontfloorz1 = frontsector->floorplane.ZatPoint(line->v1);
+	double frontceilz2 = frontsector->ceilingplane.ZatPoint(line->v2);
+	double frontfloorz2 = frontsector->floorplane.ZatPoint(line->v2);
+	double topTexZ = frontsector->GetPlaneTexZ(sector_t::ceiling);
+	double bottomTexZ = frontsector->GetPlaneTexZ(sector_t::floor);
+
 	PolyDrawLinePortal *polyportal = nullptr;
 	if (line->backsector == nullptr && line->linedef && line->sidedef == line->linedef->sidedef[0] && (line->linedef->special == Line_Mirror && r_drawmirrors))
 	{
-		if (PolyRenderer::Instance()->InsertSeenMirror(line->linedef))
+		if (lastPortalLine == line->linedef ||
+			(line->linedef->v1->fX() * clipPlane.A + line->linedef->v1->fY() * clipPlane.B + clipPlane.D <= 0.0f) ||
+			(line->linedef->v2->fX() * clipPlane.A + line->linedef->v2->fY() * clipPlane.B + clipPlane.D <= 0.0f))
 		{
-			linePortals.push_back(std::unique_ptr<PolyDrawLinePortal>(new PolyDrawLinePortal(line->linedef)));
-			polyportal = linePortals.back().get();
+			return false;
 		}
+
+		linePortals.push_back(std::unique_ptr<PolyDrawLinePortal>(new PolyDrawLinePortal(line->linedef)));
+		polyportal = linePortals.back().get();
 	}
 	else if (line->linedef && line->linedef->isVisualPortal())
 	{
-		FLinePortal *portal = line->linedef->getPortal();
-		if (PolyRenderer::Instance()->InsertSeenLinePortal(portal))
+		if (lastPortalLine == line->linedef ||
+			(line->linedef->v1->fX() * clipPlane.A + line->linedef->v1->fY() * clipPlane.B + clipPlane.D <= 0.0f) ||
+			(line->linedef->v2->fX() * clipPlane.A + line->linedef->v2->fY() * clipPlane.B + clipPlane.D <= 0.0f))
 		{
-			for (auto &p : linePortals)
+			return false;
+		}
+
+		FLinePortal *portal = line->linedef->getPortal();
+		for (auto &p : linePortals)
+		{
+			if (p->Portal == portal) // To do: what other criterias do we need to check for?
 			{
-				if (p->Portal == portal) // To do: what other criterias do we need to check for?
-				{
-					polyportal = p.get();
-					break;
-				}
-			}
-			if (!polyportal)
-			{
-				linePortals.push_back(std::unique_ptr<PolyDrawLinePortal>(new PolyDrawLinePortal(portal)));
-				polyportal = linePortals.back().get();
+				polyportal = p.get();
+				break;
 			}
 		}
+		if (!polyportal)
+		{
+			linePortals.push_back(std::unique_ptr<PolyDrawLinePortal>(new PolyDrawLinePortal(portal)));
+			polyportal = linePortals.back().get();
+		}
 	}
 
 	RenderPolyWall wall;
@@ -78,13 +93,6 @@ bool RenderPolyWall::RenderLine(const TriMatrix &worldToClip, const PolyClipPlan
 	wall.SubsectorDepth = subsectorDepth;
 	wall.StencilValue = stencilValue;
 
-	double frontceilz1 = frontsector->ceilingplane.ZatPoint(line->v1);
-	double frontfloorz1 = frontsector->floorplane.ZatPoint(line->v1);
-	double frontceilz2 = frontsector->ceilingplane.ZatPoint(line->v2);
-	double frontfloorz2 = frontsector->floorplane.ZatPoint(line->v2);
-	double topTexZ = frontsector->GetPlaneTexZ(sector_t::ceiling);
-	double bottomTexZ = frontsector->GetPlaneTexZ(sector_t::floor);
-
 	if (line->backsector == nullptr)
 	{
 		if (line->sidedef)
@@ -244,6 +252,14 @@ void RenderPolyWall::Render(const TriMatrix &worldToClip, const PolyClipPlane &c
 		vertices[3].u = (float)texcoordsU.u1;
 		vertices[3].v = (float)texcoordsVLeft.v2;
 	}
+	else
+	{
+		for (int i = 0; i < 4; i++)
+		{
+			vertices[i].u = 0.0f;
+			vertices[i].v = 0.0f;
+		}
+	}
 
 	// Masked walls clamp to the 0-1 range (no texture repeat)
 	if (Masked)
@@ -259,7 +275,7 @@ void RenderPolyWall::Render(const TriMatrix &worldToClip, const PolyClipPlane &c
 	args.SetFaceCullCCW(true);
 	args.SetStencilTestValue(StencilValue);
 	args.SetWriteStencil(true, StencilValue + 1);
-	if (tex)
+	if (tex && !Polyportal)
 		args.SetTexture(tex);
 	args.SetClipPlane(clipPlane);
 
@@ -270,10 +286,6 @@ void RenderPolyWall::Render(const TriMatrix &worldToClip, const PolyClipPlane &c
 		args.SetWriteSubsectorDepth(false);
 		args.DrawArray(vertices, 4, PolyDrawMode::TriangleFan);
 		Polyportal->Shape.push_back({ vertices, 4, true, SubsectorDepth });
-
-		angle_t angle1, angle2;
-		if (cull.GetAnglesForLine(v1.X, v1.Y, v2.X, v2.Y, angle1, angle2))
-			Polyportal->Segments.push_back({ angle1, angle2 });
 	}
 	else if (!Masked)
 	{
@@ -363,20 +375,24 @@ int RenderPolyWall::GetLightLevel()
 
 PolyWallTextureCoordsU::PolyWallTextureCoordsU(FTexture *tex, const seg_t *lineseg, const line_t *line, const side_t *side, side_t::ETexpart texpart)
 {
-	double lineLength = side->TexelLength;
-	double lineStart = 0.0;
-
-	bool entireSegment = ((lineseg->v1 == line->v1) && (lineseg->v2 == line->v2)) || ((lineseg->v2 == line->v1) && (lineseg->v1 == line->v2));
-	if (!entireSegment)
+	double t1, t2;
+	double deltaX = line->v2->fX() - line->v1->fX();
+	double deltaY = line->v2->fY() - line->v1->fY();
+	if (fabs(deltaX) > fabs(deltaY))
 	{
-		lineLength = (lineseg->v2->fPos() - lineseg->v1->fPos()).Length();
-		lineStart = (lineseg->v1->fPos() - line->v1->fPos()).Length();
+		t1 = (lineseg->v1->fX() - line->v1->fX()) / deltaX;
+		t2 = (lineseg->v2->fX() - line->v1->fX()) / deltaX;
+	}
+	else
+	{
+		t1 = (lineseg->v1->fY() - line->v1->fY()) / deltaY;
+		t2 = (lineseg->v2->fY() - line->v1->fY()) / deltaY;
 	}
 
 	int texWidth = tex->GetWidth();
 	double uscale = side->GetTextureXScale(texpart) * tex->Scale.X;
-	u1 = lineStart + side->GetTextureXOffset(texpart);
-	u2 = u1 + lineLength;
+	u1 = t1 * side->TexelLength + side->GetTextureXOffset(texpart);
+	u2 = t2 * side->TexelLength + side->GetTextureXOffset(texpart);
 	u1 *= uscale;
 	u2 *= uscale;
 	u1 /= texWidth;
diff --git a/src/polyrenderer/scene/poly_wall.h b/src/polyrenderer/scene/poly_wall.h
index 8a38447c8..f519b4a55 100644
--- a/src/polyrenderer/scene/poly_wall.h
+++ b/src/polyrenderer/scene/poly_wall.h
@@ -31,7 +31,7 @@ class PolyCull;
 class RenderPolyWall
 {
 public:
-	static bool RenderLine(const TriMatrix &worldToClip, const PolyClipPlane &clipPlane, PolyCull &cull, seg_t *line, sector_t *frontsector, uint32_t subsectorDepth, uint32_t stencilValue, std::vector<PolyTranslucentObject*> &translucentWallsOutput, std::vector<std::unique_ptr<PolyDrawLinePortal>> &linePortals);
+	static bool RenderLine(const TriMatrix &worldToClip, const PolyClipPlane &clipPlane, PolyCull &cull, seg_t *line, sector_t *frontsector, uint32_t subsectorDepth, uint32_t stencilValue, std::vector<PolyTranslucentObject*> &translucentWallsOutput, std::vector<std::unique_ptr<PolyDrawLinePortal>> &linePortals, line_t *lastPortalLine);
 	static void Render3DFloorLine(const TriMatrix &worldToClip, const PolyClipPlane &clipPlane, PolyCull &cull, seg_t *line, sector_t *frontsector, uint32_t subsectorDepth, uint32_t stencilValue, F3DFloor *fakeFloor, std::vector<PolyTranslucentObject*> &translucentWallsOutput);
 
 	void SetCoords(const DVector2 &v1, const DVector2 &v2, double ceil1, double floor1, double ceil2, double floor2);
diff --git a/src/portal.cpp b/src/portal.cpp
index 9926f289e..41158e0ee 100644
--- a/src/portal.cpp
+++ b/src/portal.cpp
@@ -1027,8 +1027,17 @@ void P_CreateLinkedPortals()
 	{
 		if (linePortals[i].mType == PORTT_LINKED)
 		{
-			if (CollectSectors(id, linePortals[i].mOrigin->frontsector)) id++;
-			if (CollectSectors(id, linePortals[i].mDestination->frontsector)) id++;
+			if (linePortals[i].mDestination == nullptr)
+			{
+				Printf("Linked portal on line %d is unconnected and will be disabled\n", linePortals[i].mOrigin->Index());
+				linePortals[i].mOrigin->portalindex = UINT_MAX;
+				linePortals[i].mType = PORTT_VISUAL;
+			}
+			else
+			{
+				if (CollectSectors(id, linePortals[i].mOrigin->frontsector)) id++;
+				if (CollectSectors(id, linePortals[i].mDestination->frontsector)) id++;
+			}
 		}
 	}
 
@@ -1237,6 +1246,7 @@ bool P_CollectConnectedGroups(int startgroup, const DVector3 &position, double u
 		{
 			int othergroup = wsec->GetOppositePortalGroup(sector_t::ceiling);
 			DVector2 pos = Displacements.getOffset(startgroup, othergroup) + position;
+			if (processMask.getBit(othergroup)) break;
 			processMask.setBit(othergroup);
 			out.Add(othergroup | FPortalGroupArray::UPPER);
 			wsec = P_PointInSector(pos);	// get upper sector at the exact spot we want to check and repeat
@@ -1247,6 +1257,7 @@ bool P_CollectConnectedGroups(int startgroup, const DVector3 &position, double u
 		{
 			int othergroup = wsec->GetOppositePortalGroup(sector_t::floor);
 			DVector2 pos = Displacements.getOffset(startgroup, othergroup) + position;
+			if (processMask.getBit(othergroup)) break;
 			processMask.setBit(othergroup);
 			out.Add(othergroup | FPortalGroupArray::LOWER);
 			wsec = P_PointInSector(pos);	// get lower sector at the exact spot we want to check and repeat
diff --git a/src/posix/sdl/sdlglvideo.cpp b/src/posix/sdl/sdlglvideo.cpp
index 0aacb8454..e2318a1fb 100644
--- a/src/posix/sdl/sdlglvideo.cpp
+++ b/src/posix/sdl/sdlglvideo.cpp
@@ -555,3 +555,38 @@ int SDLGLFB::GetClientHeight()
 	SDL_GL_GetDrawableSize(Screen, nullptr, &height);
 	return height;
 }
+
+void SDLGLFB::ScaleCoordsFromWindow(int16_t &x, int16_t &y)
+{
+	int w, h;
+	SDL_GetWindowSize (Screen, &w, &h);
+
+	// Detect if we're doing scaling in the Window and adjust the mouse
+	// coordinates accordingly. This could be more efficent, but I
+	// don't think performance is an issue in the menus.
+	if(IsFullscreen())
+	{
+		int realw = w, realh = h;
+		ScaleWithAspect (realw, realh, SCREENWIDTH, SCREENHEIGHT);
+		if (realw != SCREENWIDTH || realh != SCREENHEIGHT)
+		{
+			double xratio = (double)SCREENWIDTH/realw;
+			double yratio = (double)SCREENHEIGHT/realh;
+			if (realw < w)
+			{
+				x = (x - (w - realw)/2)*xratio;
+				y *= yratio;
+			}
+			else
+			{
+				y = (y - (h - realh)/2)*yratio;
+				x *= xratio;
+			}
+		}
+	}
+	else
+	{
+		x = (int16_t)(x*Width/w);
+		y = (int16_t)(y*Height/h);
+	}
+}
diff --git a/src/posix/sdl/sdlglvideo.h b/src/posix/sdl/sdlglvideo.h
index 69472c848..8fcc184e6 100644
--- a/src/posix/sdl/sdlglvideo.h
+++ b/src/posix/sdl/sdlglvideo.h
@@ -72,6 +72,8 @@ public:
 	int GetClientWidth();
 	int GetClientHeight();
 
+	virtual void ScaleCoordsFromWindow(int16_t &x, int16_t &y);
+
 	SDL_Window *GetSDLWindow() override { return Screen; }
 
 protected:
diff --git a/src/posix/sdl/sdlvideo.cpp b/src/posix/sdl/sdlvideo.cpp
index 5186b2553..828a47f74 100644
--- a/src/posix/sdl/sdlvideo.cpp
+++ b/src/posix/sdl/sdlvideo.cpp
@@ -112,31 +112,6 @@ static cycle_t SDLFlipCycles;
 
 // CODE --------------------------------------------------------------------
 
-void ScaleWithAspect (int &w, int &h, int Width, int Height)
-{
-	int resRatio = CheckRatio (Width, Height);
-	int screenRatio;
-	CheckRatio (w, h, &screenRatio);
-	if (resRatio == screenRatio)
-		return;
-
-	double yratio;
-	switch(resRatio)
-	{
-		case 0: yratio = 4./3.; break;
-		case 1: yratio = 16./9.; break;
-		case 2: yratio = 16./10.; break;
-		case 3: yratio = 17./10.; break;
-		case 4: yratio = 5./4.; break;
-		default: return;
-	}
-	double y = w/yratio;
-	if (y > h)
-		w = h*yratio;
-	else
-		h = y;
-}
-
 // FrameBuffer implementation -----------------------------------------------
 
 SDLFB::SDLFB (int width, int height, bool bgra, bool fullscreen, SDL_Window *oldwin)
diff --git a/src/sound/i_sound.cpp b/src/sound/i_sound.cpp
index 054062033..661af0270 100644
--- a/src/sound/i_sound.cpp
+++ b/src/sound/i_sound.cpp
@@ -236,11 +236,6 @@ public:
 	{
 		return "Null sound module has no stats.";
 	}
-
-	virtual MIDIDevice* CreateMIDIDevice() const override
-	{
-		return nullptr;
-	}
 };
 
 void I_InitSound ()
diff --git a/src/sound/i_sound.h b/src/sound/i_sound.h
index d6c4a7d78..333eda229 100644
--- a/src/sound/i_sound.h
+++ b/src/sound/i_sound.h
@@ -168,8 +168,6 @@ public:
 
 	virtual void DrawWaveDebug(int mode);
 
-	virtual MIDIDevice* CreateMIDIDevice() const = 0;
-
     static SoundDecoder *CreateDecoder(FileReader *reader);
 };
 
diff --git a/src/sound/mididevices/music_audiotoolbox_mididevice.cpp b/src/sound/mididevices/music_audiotoolbox_mididevice.cpp
deleted file mode 100644
index 859fdd31c..000000000
--- a/src/sound/mididevices/music_audiotoolbox_mididevice.cpp
+++ /dev/null
@@ -1,344 +0,0 @@
-//
-//---------------------------------------------------------------------------
-//
-// MIDI device for Apple's macOS using AudioToolbox framework
-// Copyright(C) 2017 Alexey Lysiuk
-// All rights reserved.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with this program.  If not, see http://www.gnu.org/licenses/
-//
-//--------------------------------------------------------------------------
-//
-
-// Implementation is loosely based on macOS native MIDI support from SDL_mixer
-
-#ifdef __APPLE__
-
-#include <AudioToolbox/AudioToolbox.h>
-#include "i_musicinterns.h"
-#include "templates.h"
-
-// AudioToolbox implementation of a MIDI output device ----------------------
-
-class AudioToolboxMIDIDevice : public MIDIDevice
-{
-public:
-	virtual int Open(MidiCallback, void *userData) override;
-	virtual void Close() override;
-	virtual bool IsOpen() const override;
-	virtual int GetTechnology() const override;
-	virtual int SetTempo(int tempo) override;
-	virtual int SetTimeDiv(int timediv) override;
-	virtual int StreamOut(MidiHeader *data) override;
-	virtual int StreamOutSync(MidiHeader *data) override;
-	virtual int Resume() override;
-	virtual void Stop() override;
-	virtual int PrepareHeader(MidiHeader* data) override;
-	virtual bool FakeVolume() override { return true; }
-	virtual bool Pause(bool paused) override;
-	virtual bool Preprocess(MIDIStreamer *song, bool looping) override;
-
-private:
-	MusicPlayer m_player = nullptr;
-	MusicSequence m_sequence = nullptr;
-	AudioUnit m_audioUnit = nullptr;
-	CFRunLoopTimerRef m_timer = nullptr;
-	MusicTimeStamp m_length = 0;
-
-	MidiCallback m_callback = nullptr;
-	void* m_userData = nullptr;
-
-	static void TimerCallback(CFRunLoopTimerRef timer, void* info);
-};
-
-
-
-#define AT_MIDI_CHECK_ERROR(CALL,...)                              \
-{                                                                  \
-	const OSStatus result = CALL;                                  \
-	if (noErr != result)                                           \
-	{                                                              \
-		DPrintf(DMSG_ERROR,                                        \
-			"Failed with error 0x%08X at " __FILE__ ":%d:\n> %s",  \
-			int(result), __LINE__, #CALL);                         \
-		return __VA_ARGS__;                                        \
-	}                                                              \
-}
-
-int AudioToolboxMIDIDevice::Open(MidiCallback callback, void *userData)
-{
-	AT_MIDI_CHECK_ERROR(NewMusicPlayer(&m_player), false);
-	AT_MIDI_CHECK_ERROR(NewMusicSequence(&m_sequence), false);
-	AT_MIDI_CHECK_ERROR(MusicPlayerSetSequence(m_player, m_sequence), false);
-
-	CFRunLoopTimerContext context = { 0, this, nullptr, nullptr, nullptr };
-	m_timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0.1, 0, 0, TimerCallback, &context);
-
-	if (nullptr == m_timer)
-	{
-		DPrintf(DMSG_ERROR, "Failed with create timer for MIDI playback");
-		return 1;
-	}
-
-	CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_timer, kCFRunLoopDefaultMode);
-
-	m_callback = callback;
-	m_userData = userData;
-
-	return 0;
-}
-
-void AudioToolboxMIDIDevice::Close()
-{
-	m_length = 0;
-	m_audioUnit = nullptr;
-
-	m_callback = nullptr;
-	m_userData = nullptr;
-
-	if (nullptr != m_timer)
-	{
-		CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), m_timer, kCFRunLoopDefaultMode);
-
-		CFRelease(m_timer);
-		m_timer = nullptr;
-	}
-
-	if (nullptr != m_sequence)
-	{
-		DisposeMusicSequence(m_sequence);
-		m_sequence = nullptr;
-	}
-
-	if (nullptr != m_player)
-	{
-		DisposeMusicPlayer(m_player);
-		m_player = nullptr;
-	}
-}
-
-bool AudioToolboxMIDIDevice::IsOpen() const
-{
-	return nullptr != m_player
-		&& nullptr != m_sequence
-		&& nullptr != m_timer;
-}
-
-int AudioToolboxMIDIDevice::GetTechnology() const
-{
-	return MIDIDEV_SWSYNTH;
-}
-
-int AudioToolboxMIDIDevice::SetTempo(int tempo)
-{
-	return 0;
-}
-
-int AudioToolboxMIDIDevice::SetTimeDiv(int timediv)
-{
-	return 0;
-}
-
-int AudioToolboxMIDIDevice::StreamOut(MidiHeader* data)
-{
-	return 0;
-}
-
-int AudioToolboxMIDIDevice::StreamOutSync(MidiHeader* data)
-{
-	return 0;
-}
-
-int AudioToolboxMIDIDevice::Resume()
-{
-	AT_MIDI_CHECK_ERROR(MusicPlayerSetTime(m_player, 0), false);
-	AT_MIDI_CHECK_ERROR(MusicPlayerPreroll(m_player), false);
-
-	if (nullptr == m_audioUnit)
-	{
-		AUGraph graph;
-		AT_MIDI_CHECK_ERROR(MusicSequenceGetAUGraph(m_sequence, &graph), false);
-
-		UInt32 nodecount;
-		AT_MIDI_CHECK_ERROR(AUGraphGetNodeCount(graph, &nodecount), false);
-
-		for (UInt32 i = 0; i < nodecount; ++i)
-		{
-			AUNode node;
-			AT_MIDI_CHECK_ERROR(AUGraphGetIndNode(graph, i, &node), false);
-
-			AudioUnit audioUnit = nullptr;
-#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050
-			ComponentDescription desc = {};
-			UInt32 classdatasize = 0;
-			void *classdata = nullptr;
-			AT_MIDI_CHECK_ERROR(AUGraphGetNodeInfo(graph, node, &desc, &classdatasize, &classdata, &audioUnit), false);
-#else // 10.5 and above
-			AudioComponentDescription desc = {};
-			AT_MIDI_CHECK_ERROR(AUGraphNodeInfo(graph, node, &desc, &audioUnit), false);
-#endif // prior to 10.5
-
-			if (   kAudioUnitType_Output           != desc.componentType
-				|| kAudioUnitSubType_DefaultOutput != desc.componentSubType)
-			{
-				continue;
-			}
-
-			const float volume = clamp<float>(snd_musicvolume * relative_volume, 0.f, 1.f);
-			AT_MIDI_CHECK_ERROR(AudioUnitSetParameter(audioUnit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, volume, 0), false);
-
-			m_audioUnit = audioUnit;
-			break;
-		}
-	}
-
-	AT_MIDI_CHECK_ERROR(MusicPlayerStart(m_player), false);
-
-	return 0;
-}
-
-void AudioToolboxMIDIDevice::Stop()
-{
-	AT_MIDI_CHECK_ERROR(MusicPlayerStop(m_player));
-}
-
-int AudioToolboxMIDIDevice::PrepareHeader(MidiHeader* data)
-{
-	MidiHeader* events = data;
-	uint32_t position = 0;
-
-	while (nullptr != events)
-	{
-		uint32_t* const event = reinterpret_cast<uint32_t*>(events->lpData + position);
-		const uint32_t message = event[2];
-
-		if (0 == MEVENT_EVENTTYPE(message))
-		{
-			static const uint32_t VOLUME_CHANGE_EVENT = 7;
-
-			const uint32_t status =  message        & 0xFF;
-			const uint32_t param1 = (message >>  8) & 0x7F;
-			const uint32_t param2 = (message >> 16) & 0x7F;
-
-			if (nullptr != m_audioUnit && MIDI_CTRLCHANGE == status && VOLUME_CHANGE_EVENT == param1)
-			{
-				AT_MIDI_CHECK_ERROR(AudioUnitSetParameter(m_audioUnit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, param2 / 100.f, 0), false);
-			}
-		}
-
-		// Advance to next event
-		position += 12 + ( (message < 0x80000000)
-			? 0
-			: ((MEVENT_EVENTPARM(message) + 3) & ~3) );
-
-		// Did we use up this buffer?
-		if (position >= events->dwBytesRecorded)
-		{
-			events = events->lpNext;
-			position = 0;
-		}
-
-		if (nullptr == events)
-		{
-			break;
-		}
-	}
-
-	return 0;
-}
-
-bool AudioToolboxMIDIDevice::Pause(bool paused)
-{
-	return false;
-}
-
-static MusicTimeStamp GetSequenceLength(MusicSequence sequence)
-{
-	UInt32 trackCount;
-	AT_MIDI_CHECK_ERROR(MusicSequenceGetTrackCount(sequence, &trackCount), 0);
-
-	MusicTimeStamp result = 0;
-
-	for (UInt32 i = 0; i < trackCount; ++i)
-	{
-		MusicTrack track;
-		AT_MIDI_CHECK_ERROR(MusicSequenceGetIndTrack(sequence, i, &track), 0);
-
-		MusicTimeStamp trackLength = 0;
-		UInt32 trackLengthSize = sizeof trackLength;
-
-		AT_MIDI_CHECK_ERROR(MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength, &trackLength, &trackLengthSize), 0);
-
-		if (result < trackLength)
-		{
-			result = trackLength;
-		}
-	}
-
-	return result;
-}
-
-bool AudioToolboxMIDIDevice::Preprocess(MIDIStreamer* song, bool looping)
-{
-	assert(nullptr != song);
-
-	TArray<uint8_t> midi;
-	song->CreateSMF(midi, looping ? 0 : 1);
-
-	CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, &midi[0], midi.Size(), kCFAllocatorNull);
-	if (nullptr == data)
-	{
-		DPrintf(DMSG_ERROR, "Failed with create CFDataRef for MIDI song");
-		return false;
-	}
-
-#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050
-	AT_MIDI_CHECK_ERROR(MusicSequenceLoadSMFDataWithFlags(m_sequence, data, 0), false);
-#else // 10.5 and above
-	AT_MIDI_CHECK_ERROR(MusicSequenceFileLoadData(m_sequence, data, kMusicSequenceFile_MIDIType, 0), CFRelease(data), false);
-#endif // prior to 10.5
-
-	CFRelease(data);
-
-	m_length = GetSequenceLength(m_sequence);
-
-	return true;
-}
-
-void AudioToolboxMIDIDevice::TimerCallback(CFRunLoopTimerRef timer, void* info)
-{
-	AudioToolboxMIDIDevice* const self = static_cast<AudioToolboxMIDIDevice*>(info);
-
-	if (nullptr != self->m_callback)
-	{
-		self->m_callback(self->m_userData);
-	}
-
-	MusicTimeStamp currentTime = 0;
-	AT_MIDI_CHECK_ERROR(MusicPlayerGetTime(self->m_player, &currentTime));
-
-	if (currentTime > self->m_length)
-	{
-		MusicPlayerSetTime(self->m_player, 0);
-	}
-}
-
-#undef AT_MIDI_CHECK_ERROR
-
-MIDIDevice *CreateAudioToolboxMIDIDevice()
-{
-	return new AudioToolboxMIDIDevice();
-}
-
-#endif // __APPLE__
diff --git a/src/sound/mididevices/music_fluidsynth_mididevice.cpp b/src/sound/mididevices/music_fluidsynth_mididevice.cpp
index 47378ff56..07102c43d 100644
--- a/src/sound/mididevices/music_fluidsynth_mididevice.cpp
+++ b/src/sound/mididevices/music_fluidsynth_mididevice.cpp
@@ -43,6 +43,7 @@
 #include "m_swap.h"
 #include "w_wad.h"
 #include "v_text.h"
+#include "version.h"
 #include "cmdlib.h"
 
 // MACROS ------------------------------------------------------------------
@@ -91,6 +92,8 @@ extern "C" unsigned __stdcall GetSystemDirectoryA(char *lpBuffer, unsigned uSize
 
 // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
 
+const char *BaseFileSearch(const char *file, const char *ext, bool lookfirstinprogdir = false);
+
 // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
 
 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
@@ -315,44 +318,54 @@ FluidSynthMIDIDevice::FluidSynthMIDIDevice(const char *args)
 	int res = 0;
 	if (args != NULL && *args != 0)
 	{
-		res = LoadPatchSets(args);
+		if (LoadPatchSets(args)) return;
 	}
 
-	if (res == 0 && 0 == LoadPatchSets(fluid_patchset))
+	if (LoadPatchSets(fluid_patchset))
 	{
-#ifdef __unix__
-		// This is the standard location on Ubuntu.
-		if (0 == LoadPatchSets("/usr/share/sounds/sf2/FluidR3_GS.sf2:/usr/share/sounds/sf2/FluidR3_GM.sf2"))
-		{
-#endif
-#ifdef _WIN32
-		// On Windows, look for the 4 megabyte patch set installed by Creative's drivers as a default.
-		char sysdir[MAX_PATH+sizeof("\\CT4MGM.SF2")];
-		uint32_t filepart;
-		if (0 != (filepart = GetSystemDirectoryA(sysdir, MAX_PATH)))
-		{
-			strcat(sysdir, "\\CT4MGM.SF2");
-			if (0 == LoadPatchSets(sysdir))
-			{
-				// Try again with CT2MGM.SF2
-				sysdir[filepart + 3] = '2';
-				if (0 == LoadPatchSets(sysdir))
-				{
-#endif
-					Printf("Failed to load any MIDI patches.\n");
-					delete_fluid_synth(FluidSynth);
-					FluidSynth = NULL;
-#ifdef _WIN32
-				}
-			}
-		}
-#endif
-#ifdef __unix__
-		}
-#endif
+		return;
 	}
+#ifdef __unix__
+	// This is the standard location on Ubuntu.
+	if (LoadPatchSets("/usr/share/sounds/sf2/FluidR3_GS.sf2:/usr/share/sounds/sf2/FluidR3_GM.sf2"))
+	{
+		return;
+	}
+#endif
+#ifdef _WIN32
+	// On Windows, look for the 4 megabyte patch set installed by Creative's drivers as a default.
+	char sysdir[MAX_PATH + sizeof("\\CT4MGM.SF2")];
+	uint32_t filepart;
+	if (0 != (filepart = GetSystemDirectoryA(sysdir, MAX_PATH)))
+	{
+		strcat(sysdir, "\\CT4MGM.SF2");
+		if (LoadPatchSets(sysdir))
+		{
+			return;
+		}
+		// Try again with CT2MGM.SF2
+		sysdir[filepart + 3] = '2';
+		if (LoadPatchSets(sysdir))
+		{
+			return;
+		}
+	}
+
+#endif
+	// Last try the base sound font which should be provided by the GZDoom binary package.
+	auto wad = BaseFileSearch(BASESF, NULL, true);
+	if (wad != NULL && 	LoadPatchSets(wad))
+	{
+		return;
+	}
+
+	Printf("Failed to load any MIDI patches.\n");
+	delete_fluid_synth(FluidSynth);
+	FluidSynth = NULL;
+
 }
 
+
 //==========================================================================
 //
 // FluidSynthMIDIDevice Destructor
@@ -513,14 +526,21 @@ int FluidSynthMIDIDevice::LoadPatchSets(const char *patches)
 		{
 			path = NicePath(tok);
 		}
-		if (FLUID_FAILED != fluid_synth_sfload(FluidSynth, path, count == 0))
+		if (FileExists(path))
 		{
-			DPrintf(DMSG_NOTIFY, "Loaded patch set %s.\n", tok);
-			count++;
+			if (FLUID_FAILED != fluid_synth_sfload(FluidSynth, path, count == 0))
+			{
+				DPrintf(DMSG_NOTIFY, "Loaded patch set %s.\n", tok);
+				count++;
+			}
+			else
+			{
+				DPrintf(DMSG_ERROR, "Failed to load patch set %s.\n", tok);
+			}
 		}
 		else
 		{
-			DPrintf(DMSG_ERROR, "Failed to load patch set %s.\n", tok);
+			DPrintf(DMSG_ERROR, "Could not find patch set %s.\n", tok);
 		}
 		tok = strtok(NULL, delim);
 	}
diff --git a/src/sound/music_midi_base.cpp b/src/sound/music_midi_base.cpp
index c437af696..10ee4a2c2 100644
--- a/src/sound/music_midi_base.cpp
+++ b/src/sound/music_midi_base.cpp
@@ -51,9 +51,9 @@ static uint32_t	nummididevices;
 static bool		nummididevicesset;
 
 #ifdef HAVE_FLUIDSYNTH
-#define NUM_DEF_DEVICES 6
-#else
 #define NUM_DEF_DEVICES 5
+#else
+#define NUM_DEF_DEVICES 4
 #endif
 
 static void AddDefaultMidiDevices(FOptionValues *opt)
@@ -75,8 +75,6 @@ static void AddDefaultMidiDevices(FOptionValues *opt)
 	pair[p+2].Value = -2.0;
 	pair[p+3].Text = "WildMidi";
 	pair[p+3].Value = -6.0;
-	pair[p+4].Text = "Sound System";
-	pair[p+4].Value = -1.0;
 
 }
 
@@ -117,10 +115,16 @@ void MIDIDeviceChanged(int newdev, bool force)
 	if (!force) oldmididev = newdev;
 }
 
+#ifdef HAVE_FLUIDSYNTH 
+#define DEF_MIDIDEV -5
+#else
+#define DEF_MIDIDEV -3
+#endif
+
 #ifdef _WIN32
 unsigned mididevice;
 
-CUSTOM_CVAR (Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
+CUSTOM_CVAR (Int, snd_mididevice, DEF_MIDIDEV, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
 {
 	if (!nummididevicesset)
 		return;
@@ -131,10 +135,11 @@ CUSTOM_CVAR (Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
 		if (self != 0)
 		{
 			Printf("ID out of range. Using default device.\n");
-			self = 0;
+			self = DEF_MIDIDEV;
 		}
 		return;
 	}
+	else if (self == -1) self = DEF_MIDIDEV;
 	mididevice = MAX<UINT>(0, self);
 	MIDIDeviceChanged(self);
 }
@@ -215,7 +220,6 @@ CCMD (snd_listmididevices)
 	PrintMidiDevice (-4, "Gravis Ultrasound Emulation", MIDIDEV_SWSYNTH, 0);
 	PrintMidiDevice (-3, "Emulated OPL FM Synth", MIDIDEV_FMSYNTH, 0);
 	PrintMidiDevice (-2, "TiMidity++", MIDIDEV_SWSYNTH, 0);
-	PrintMidiDevice (-1, "Sound System", 0, 0);
 	if (nummididevices != 0)
 	{
 		for (id = 0; id < nummididevices; ++id)
@@ -237,12 +241,12 @@ CCMD (snd_listmididevices)
 
 // Everything but Windows uses this code.
 
-CUSTOM_CVAR(Int, snd_mididevice, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
+CUSTOM_CVAR(Int, snd_mididevice, DEF_MIDIDEV, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
 {
 	if (self < -6)
 		self = -6;
-	else if (self > -1)
-		self = -1;
+	else if (self > -2)
+		self = -2;
 	else
 		MIDIDeviceChanged(self);
 }
@@ -261,6 +265,5 @@ CCMD (snd_listmididevices)
 	Printf("%s-4. Gravis Ultrasound Emulation\n", -4 == snd_mididevice ? TEXTCOLOR_BOLD : "");
 	Printf("%s-3. Emulated OPL FM Synth\n", -3 == snd_mididevice ? TEXTCOLOR_BOLD : "");
 	Printf("%s-2. TiMidity++\n", -2 == snd_mididevice ? TEXTCOLOR_BOLD : "");
-	Printf("%s-1. Sound System\n", -1 == snd_mididevice ? TEXTCOLOR_BOLD : "");
 }
 #endif
diff --git a/src/sound/musicformats/music_hmi_midiout.cpp b/src/sound/musicformats/music_hmi_midiout.cpp
index f5322d2c1..791c8ec3d 100644
--- a/src/sound/musicformats/music_hmi_midiout.cpp
+++ b/src/sound/musicformats/music_hmi_midiout.cpp
@@ -651,7 +651,7 @@ uint32_t *HMISong::SendCommand (uint32_t *events, TrackInfo *track, uint32_t del
 		if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
 		{
 			len = ReadVarLen(track);
-			if (len >= (MAX_EVENTS-1)*3*4 || DeviceType == MDEV_SNDSYS)
+			if (len >= (MAX_EVENTS-1)*3*4)
 			{ // This message will never fit. Throw it away.
 				track->TrackP += len;
 			}
diff --git a/src/sound/musicformats/music_midistream.cpp b/src/sound/musicformats/music_midistream.cpp
index e23c03a3c..2a8abdae9 100644
--- a/src/sound/musicformats/music_midistream.cpp
+++ b/src/sound/musicformats/music_midistream.cpp
@@ -181,11 +181,6 @@ EMidiDevice MIDIStreamer::SelectMIDIDevice(EMidiDevice device)
 			- if explicitly selected by $mididevice 
 			- when snd_mididevice  is -2 and no midi device is set for the song
 
-		- Sound System:
-			- if explicitly selected by $mididevice 
-			- when snd_mididevice  is -1 and no midi device is set for the song
-			- as fallback when both OPL and Timidity failed unless snd_mididevice is >= 0
-
 		- MMAPI (Win32 only):
 			- if explicitly selected by $mididevice (non-Win32 redirects this to Sound System)
 			- when snd_mididevice  is >= 0 and no midi device is set for the song
@@ -233,16 +228,19 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype)
 		assert(0);
 		// Intentional fall-through for non-Windows systems.
 
+	case MDEV_GUS:
+		return new TimidityMIDIDevice(Args);
+
 #ifdef HAVE_FLUIDSYNTH
 	case MDEV_FLUIDSYNTH:
 		return new FluidSynthMIDIDevice(Args);
 #endif
 
 	case MDEV_SNDSYS:
-		return GSnd->CreateMIDIDevice();
-
-	case MDEV_GUS:
-		return new TimidityMIDIDevice(Args);
+#ifdef HAVE_FLUIDSYNTH
+		return new FluidSynthMIDIDevice(nullptr);
+#endif
+		// if no FluidSynth, fall through to OPL.
 
 	case MDEV_OPL:
 		try
@@ -252,8 +250,13 @@ MIDIDevice *MIDIStreamer::CreateMIDIDevice(EMidiDevice devtype)
 		catch (CRecoverableError &err)
 		{
 			// The creation of an OPL MIDI device can abort with an error if no GENMIDI lump can be found.
-			Printf("Unable to create OPL MIDI device: %s\nFalling back to Sound System playback", err.GetMessage());
-			return GSnd->CreateMIDIDevice();
+			Printf("Unable to create OPL MIDI device: %s\nFalling back to default playback", err.GetMessage());
+#ifdef HAVE_FLUIDSYNTH
+			return new FluidSynthMIDIDevice(nullptr);
+#else
+			// Someone dared to compile GZDoom without FluidSynth support and then started an IWAD without GENMIDI support. Ugh...
+			return nullptr;
+#endif
 		}
 
 	case MDEV_TIMIDITY:
diff --git a/src/sound/musicformats/music_smf_midiout.cpp b/src/sound/musicformats/music_smf_midiout.cpp
index 32de991b3..7d2131fc9 100644
--- a/src/sound/musicformats/music_smf_midiout.cpp
+++ b/src/sound/musicformats/music_smf_midiout.cpp
@@ -592,7 +592,7 @@ uint32_t *MIDISong2::SendCommand (uint32_t *events, TrackInfo *track, uint32_t d
 		if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
 		{
 			len = track->ReadVarLen();
-			if (len >= (MAX_EVENTS-1)*3*4 || DeviceType == MDEV_SNDSYS)
+			if (len >= (MAX_EVENTS-1)*3*4)
 			{ // This message will never fit. Throw it away.
 				track->TrackP += len;
 			}
diff --git a/src/sound/musicformats/music_xmi_midiout.cpp b/src/sound/musicformats/music_xmi_midiout.cpp
index fd595cb09..a4e23423b 100644
--- a/src/sound/musicformats/music_xmi_midiout.cpp
+++ b/src/sound/musicformats/music_xmi_midiout.cpp
@@ -522,7 +522,7 @@ uint32_t *XMISong::SendCommand (uint32_t *events, EventSource due, uint32_t dela
 		if (event == MIDI_SYSEX || event == MIDI_SYSEXEND)
 		{
 			len = track->ReadVarLen();
-			if (len >= (MAX_EVENTS-1)*3*4 || DeviceType == MDEV_SNDSYS)
+			if (len >= (MAX_EVENTS-1)*3*4)
 			{ // This message will never fit. Throw it away.
 				track->EventP += len;
 			}
diff --git a/src/sound/oalsound.cpp b/src/sound/oalsound.cpp
index 6a0c9ff1c..e9fde9263 100644
--- a/src/sound/oalsound.cpp
+++ b/src/sound/oalsound.cpp
@@ -2320,18 +2320,6 @@ void OpenALSoundRenderer::PrintDriversList()
 	}
 }
 
-MIDIDevice* OpenALSoundRenderer::CreateMIDIDevice() const
-{
-#ifdef _WIN32
-	extern unsigned mididevice;
-	return CreateWinMIDIDevice(mididevice);
-#elif defined __APPLE__
-	return CreateAudioToolboxMIDIDevice();
-#else
-	return new OPLMIDIDevice(nullptr);
-#endif
-}
-
 void OpenALSoundRenderer::PurgeStoppedSources()
 {
 	// Release channels that are stopped
diff --git a/src/sound/oalsound.h b/src/sound/oalsound.h
index 7a1ed2a23..92d2235aa 100644
--- a/src/sound/oalsound.h
+++ b/src/sound/oalsound.h
@@ -175,8 +175,6 @@ public:
 	virtual void PrintDriversList();
 	virtual FString GatherStats();
 
-	virtual MIDIDevice* CreateMIDIDevice() const override;
-
 private:
     struct {
         bool EXT_EFX;
diff --git a/src/v_video.cpp b/src/v_video.cpp
index 651d3d7de..a3c7d3878 100644
--- a/src/v_video.cpp
+++ b/src/v_video.cpp
@@ -1747,6 +1747,32 @@ bool AspectTallerThanWide(float aspect)
 	return aspect < 1.333f;
 }
 
+void ScaleWithAspect (int &w, int &h, int Width, int Height)
+{
+	int resRatio = CheckRatio (Width, Height);
+	int screenRatio;
+	CheckRatio (w, h, &screenRatio);
+	if (resRatio == screenRatio)
+		return;
+
+	double yratio;
+	switch(resRatio)
+	{
+		case 0: yratio = 4./3.; break;
+		case 1: yratio = 16./9.; break;
+		case 2: yratio = 16./10.; break;
+		case 3: yratio = 17./10.; break;
+		case 4: yratio = 5./4.; break;
+		case 6: yratio = 21./9.; break;
+		default: return;
+	}
+	double y = w/yratio;
+	if (y > h)
+		w = h*yratio;
+	else
+		h = y;
+}
+
 void IVideo::DumpAdapters ()
 {
 	Printf("Multi-monitor support unavailable.\n");
diff --git a/src/v_video.h b/src/v_video.h
index c4f9bdb95..fc4d3924d 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -565,6 +565,8 @@ int AspectBaseHeight(float aspect);
 double AspectPspriteOffset(float aspect);
 int AspectMultiplier(float aspect);
 bool AspectTallerThanWide(float aspect);
+void ScaleWithAspect(int &w, int &h, int Width, int Height);
+
 int GetUIScale(int altval);
 
 EXTERN_CVAR(Int, uiscale);
diff --git a/src/version.h b/src/version.h
index ab4a53c0a..19347fafe 100644
--- a/src/version.h
+++ b/src/version.h
@@ -96,6 +96,7 @@ const char *GetVersionString();
 // This is so that derivates can use the same savegame versions without worrying about engine compatibility
 #define GAMESIG "QZDOOM"
 #define BASEWAD "qzdoom.pk3"
+#define BASESF "gzdoom.sf2"
 
 // More stuff that needs to be different for derivatives.
 #define GAMENAME "QZDoom"
diff --git a/tools/updaterevision/updaterevision.c b/tools/updaterevision/updaterevision.c
index 7260f4131..bbb6dddf3 100644
--- a/tools/updaterevision/updaterevision.c
+++ b/tools/updaterevision/updaterevision.c
@@ -51,7 +51,7 @@ int main(int argc, char **argv)
 	// on a tag, it returns that tag. Otherwise it returns <most recent tag>-<number of
 	// commits since the tag>-<short hash>.
 	// Use git log to get the time of the latest commit in ISO 8601 format and its full hash.
-	stream = popen("git describe --tags && git log -1 --format=%ai*%H", "r");
+	stream = popen("git describe --tags --dirty=-m && git log -1 --format=%ai*%H", "r");
 
 	if (NULL != stream)
 	{