diff --git a/src/d_main.c b/src/d_main.c
index 0a13c6d0b0108bc83acbfc1501e40c92305eaaa4..995704e762e619337c518c7f5a76e95a7c6f5f66 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1286,6 +1286,7 @@ void D_SRB2Main(void)
 		I_StartupSound();
 		I_InitMusic();
 		S_InitSfxChannels(cv_soundvolume.value);
+		S_InitMusicDefs();
 	}
 
 	CONS_Printf("ST_Init(): Init status bar.\n");
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 62c505ee0b726569f63ae6e3a0ec10a8c6b69aa8..7da0aa605ecb4f2100364d0fdcda18da6a0ff744 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -3848,13 +3848,14 @@ static void Gravity_OnChange(void)
 
 static void SoundTest_OnChange(void)
 {
+	INT32 sfxfreeint = (INT32)sfxfree;
 	if (cv_soundtest.value < 0)
 	{
-		CV_SetValue(&cv_soundtest, NUMSFX-1);
+		CV_SetValue(&cv_soundtest, sfxfreeint-1);
 		return;
 	}
 
-	if (cv_soundtest.value >= NUMSFX)
+	if (cv_soundtest.value >= sfxfreeint)
 	{
 		CV_SetValue(&cv_soundtest, 0);
 		return;
diff --git a/src/dehacked.c b/src/dehacked.c
index bf8bf62b524cd73167d858e89408c8285e9fc134..1ac04dc563582a54dbbaa460984e5af2708d4c5c 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -8656,6 +8656,8 @@ static const char *const MENUTYPES_LIST[] = {
 	"SR_LEVELSELECT",
 	"SR_UNLOCKCHECKLIST",
 	"SR_EMBLEMHINT",
+	"SR_PLAYER",
+	"SR_SOUNDTEST",
 
 	// Addons (Part of MISC, but let's make it our own)
 	"AD_MAIN",
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index 3b0620cc134c96c480aade16dce049e4d443e71c..e493dafacbb74406517bc3a4425de717ed11db6e 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -137,7 +137,7 @@ void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option)
 	HWD.pfnDrawPolygon(NULL, v, 4, flags);
 }
 
-void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, INT32 option, const UINT8 *colormap)
+void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap)
 {
 	FOutVector v[4];
 	FBITFIELD flags;
@@ -182,6 +182,8 @@ void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale,
 
 	dupx = dupy = (dupx < dupy ? dupx : dupy);
 	fscalew = fscaleh = FIXED_TO_FLOAT(pscale);
+	if (vscale != pscale)
+		fscaleh = FIXED_TO_FLOAT(vscale);
 
 	// See my comments in v_video.c's V_DrawFixedPatch
 	// -- Monster Iestyn 29/10/18
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index 3a0a58427fbb1d2d8bed6173cb04430b32c45541..fa230289fa9363bff35db7763f28cea63847fa90 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -42,7 +42,7 @@ void HWR_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatlumpnum)
 void HWR_InitTextureMapping(void);
 void HWR_SetViewSize(void);
 void HWR_DrawPatch(GLPatch_t *gpatch, INT32 x, INT32 y, INT32 option);
-void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t scale, INT32 option, const UINT8 *colormap);
+void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 option, const UINT8 *colormap);
 void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t scale, INT32 option, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
 void HWR_MakePatch(const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipmap, boolean makebitmap);
 void HWR_CreatePlanePolygons(INT32 bspnum);
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 865b61e8fcb6cd1206beb4b8d4485dea99cf59c4..62be6128300eac40493c55cfebbefe78cf40d731 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -551,6 +551,33 @@ static int libd_drawScaled(lua_State *L)
 	return 0;
 }
 
+static int libd_drawStretched(lua_State *L)
+{
+	fixed_t x, y, hscale, vscale;
+	INT32 flags;
+	patch_t *patch;
+	const UINT8 *colormap = NULL;
+
+	HUDONLY
+	x = luaL_checkinteger(L, 1);
+	y = luaL_checkinteger(L, 2);
+	hscale = luaL_checkinteger(L, 3);
+	if (hscale < 0)
+		return luaL_error(L, "negative horizontal scale");
+	vscale = luaL_checkinteger(L, 4);
+	if (vscale < 0)
+		return luaL_error(L, "negative vertical scale");
+	patch = *((patch_t **)luaL_checkudata(L, 5, META_PATCH));
+	flags = luaL_optinteger(L, 6, 0);
+	if (!lua_isnoneornil(L, 7))
+		colormap = *((UINT8 **)luaL_checkudata(L, 7, META_COLORMAP));
+
+	flags &= ~V_PARAMMASK; // Don't let crashes happen.
+
+	V_DrawStretchyFixedPatch(x, y, hscale, vscale, flags, patch, colormap);
+	return 0;
+}
+
 static int libd_drawNum(lua_State *L)
 {
 	INT32 x, y, flags, num;
@@ -902,6 +929,7 @@ static luaL_Reg lib_draw[] = {
 	// drawing
 	{"draw", libd_draw},
 	{"drawScaled", libd_drawScaled},
+	{"drawStretched", libd_drawStretched},
 	{"drawNum", libd_drawNum},
 	{"drawPaddedNum", libd_drawPaddedNum},
 	{"drawFill", libd_drawFill},
diff --git a/src/m_menu.c b/src/m_menu.c
index f3528d8e5cb33874a58203e0f832020c7696514f..6c066fe30ba72a0938b8cda873709e3f724122c3 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -228,6 +228,7 @@ static void M_GetAllEmeralds(INT32 choice);
 static void M_DestroyRobots(INT32 choice);
 static void M_LevelSelectWarp(INT32 choice);
 static void M_Credits(INT32 choice);
+static void M_SoundTest(INT32 choice);
 static void M_PandorasBox(INT32 choice);
 static void M_EmblemHints(INT32 choice);
 static void M_HandleChecklist(INT32 choice);
@@ -336,8 +337,8 @@ static void M_DrawGenericMenu(void);
 static void M_DrawGenericScrollMenu(void);
 static void M_DrawCenteredMenu(void);
 static void M_DrawAddons(void);
-static void M_DrawSkyRoom(void);
 static void M_DrawChecklist(void);
+static void M_DrawSoundTest(void);
 static void M_DrawEmblemHints(void);
 static void M_DrawPauseMenu(void);
 static void M_DrawServerMenu(void);
@@ -706,6 +707,11 @@ static menuitem_t SR_UnlockChecklistMenu[] =
 	{IT_KEYHANDLER | IT_STRING, NULL, "", M_HandleChecklist, 0},
 };
 
+static menuitem_t SR_SoundTestMenu[] =
+{
+	{IT_KEYHANDLER | IT_STRING, NULL, "", M_HandleSoundTest, 0},
+};
+
 static menuitem_t SR_EmblemHintMenu[] =
 {
 	{IT_STRING|IT_CVAR,         NULL, "Emblem Radar", &cv_itemfinder, 10},
@@ -1603,18 +1609,8 @@ menu_t SR_PandoraDef =
 	0,
 	M_ExitPandorasBox
 };
-menu_t SR_MainDef =
-{
-	MN_SR_MAIN,
-	"M_SECRET",
-	sizeof (SR_MainMenu)/sizeof (menuitem_t),
-	&MainDef,
-	SR_MainMenu,
-	M_DrawSkyRoom,
-	60, 40,
-	0,
-	NULL
-};
+
+menu_t SR_MainDef = DEFAULTMENUSTYLE(MN_SR_MAIN, "M_SECRET", SR_MainMenu, &MainDef, 60, 40);
 
 menu_t SR_LevelSelectDef = MAPPLATTERMENUSTYLE(
 	MN_SR_MAIN + (MN_SR_LEVELSELECT << 6),
@@ -1632,6 +1628,20 @@ menu_t SR_UnlockChecklistDef =
 	0,
 	NULL
 };
+
+menu_t SR_SoundTestDef =
+{
+	MN_SR_MAIN + (MN_SR_SOUNDTEST << 6),
+	NULL,
+	sizeof (SR_SoundTestMenu)/sizeof (menuitem_t),
+	&SR_MainDef,
+	SR_SoundTestMenu,
+	M_DrawSoundTest,
+	60, 150,
+	0,
+	NULL
+};
+
 menu_t SR_EmblemHintDef =
 {
 	MN_SR_MAIN + (MN_SR_EMBLEMHINT << 6),
@@ -2319,6 +2329,11 @@ void M_InitMenuPresTables(void)
 			strncpy(menupres[i].musname, "_nitat", 7);
 		else if (i == MN_SP_PLAYER || i == MN_SR_PLAYER)
 			strncpy(menupres[i].musname, "_chsel", 7);
+		else if (i == MN_SR_SOUNDTEST)
+		{
+			*menupres[i].musname = '\0';
+			menupres[i].musstop = true;
+		}
 	}
 }
 
@@ -6973,7 +6988,7 @@ static void M_DrawEmblemHints(void)
 	M_DrawGenericMenu();
 }
 
-static void M_DrawSkyRoom(void)
+/*static void M_DrawSkyRoom(void)
 {
 	INT32 i, y = 0;
 
@@ -7000,6 +7015,266 @@ static void M_DrawSkyRoom(void)
 	}
 	if (cv_soundtest.value)
 		V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + y + 8, V_YELLOWMAP, S_sfx[cv_soundtest.value].name);
+}*/
+
+static musicdef_t *curplaying = NULL;
+static INT32 st_sel = 0, st_cc = 0;
+static tic_t st_time = 0;
+static patch_t* st_radio[9];
+static patch_t* st_launchpad[4];
+
+static void M_SoundTest(INT32 choice)
+{
+	INT32 ul = skyRoomMenuTranslations[choice-1];
+	UINT8 i;
+	char buf[8];
+
+	soundtestpage = (UINT8)(unlockables[ul].variable);
+	if (!soundtestpage)
+		soundtestpage = 1;
+
+	if (!S_PrepareSoundTest())
+	{
+		M_StartMessage(M_GetText("No selectable tracks found.\n"),NULL,MM_NOTHING);
+		return;
+	}
+
+	STRBUFCPY(buf, "M_RADIOn");
+	for (i = 0; i < 9; i++)
+	{
+		if (st_radio[i])
+			W_UnlockCachedPatch(st_radio[i]);
+		buf[7] = (char)('0'+i);
+		st_radio[i] = W_CachePatchName(buf, PU_STATIC);
+	}
+
+	STRBUFCPY(buf, "M_LPADn");
+	for (i = 0; i < 4; i++)
+	{
+		if (st_launchpad[i])
+			W_UnlockCachedPatch(st_launchpad[i]);
+		buf[6] = (char)('0'+i);
+		st_launchpad[i] = W_CachePatchName(buf, PU_STATIC);
+	}
+
+	curplaying = NULL;
+	st_time = 0;
+
+	st_sel = 0;
+
+	st_cc = cv_closedcaptioning.value; // hack;
+	cv_closedcaptioning.value = 1; // hack
+
+	M_SetupNextMenu(&SR_SoundTestDef);
+}
+
+static void M_DrawSoundTest(void)
+{
+	INT32 x, y, i;
+	fixed_t hscale = FRACUNIT/2, vscale = FRACUNIT/2, bounce = 0;
+	UINT8 frame[4] = {0, 0, -1, SKINCOLOR_RUBY};
+
+	// let's handle the ticker first. ideally we'd tick this somewhere else, BUT...
+	if (curplaying)
+	{
+		if (curplaying == &soundtestsfx)
+		{
+			if (cv_soundtest.value)
+			{
+				frame[1] = (2-st_time);
+				frame[2] = ((cv_soundtest.value - 1) % 9);
+				frame[3] += (((cv_soundtest.value - 1) / 9) % (MAXSKINCOLORS - frame[3]));
+				if (st_time < 2)
+					st_time++;
+			}
+		}
+		else
+		{
+			if (curplaying->stoppingtics && st_time >= curplaying->stoppingtics)
+			{
+				curplaying = NULL;
+				st_time = 0;
+			}
+			else
+			{
+				fixed_t work, bpm = curplaying->bpm;
+				angle_t ang;
+				//bpm = FixedDiv((60*TICRATE)<<FRACBITS, bpm); -- bake this in on load
+
+				work = st_time<<FRACBITS;
+				work %= bpm;
+
+				if (st_time >= (FRACUNIT>>1)) // prevent overflow jump - takes about 15 minutes of loop on the same song to reach
+					st_time = (work>>FRACBITS);
+
+				work = FixedDiv(work*180, bpm);
+				frame[0] = 8-(work/(20<<FRACBITS));
+				ang = (FixedAngle(work)>>ANGLETOFINESHIFT) & FINEMASK;
+				bounce = (FINESINE(ang) - FRACUNIT/2);
+				hscale -= bounce/16;
+				vscale += bounce/16;
+
+				st_time++;
+			}
+		}
+	}
+
+	x = 90<<FRACBITS;
+	y = (BASEVIDHEIGHT-32)<<FRACBITS;
+
+	V_DrawStretchyFixedPatch(x, y,
+		hscale, vscale,
+		0, st_radio[frame[0]], NULL);
+
+	V_DrawFixedPatch(x, y, FRACUNIT/2, 0, st_launchpad[0], NULL);
+
+	for (i = 0; i < 9; i++)
+	{
+		if (i == frame[2])
+		{
+			UINT8 *colmap = R_GetTranslationColormap(TC_RAINBOW, frame[3], GTC_CACHE);
+			V_DrawFixedPatch(x, y + (frame[1]<<FRACBITS), FRACUNIT/2, 0, st_launchpad[frame[1]+1], colmap);
+		}
+		else
+			V_DrawFixedPatch(x, y, FRACUNIT/2, 0, st_launchpad[1], NULL);
+
+		if ((i % 3) == 2)
+		{
+			x -= ((2*28) + 25)<<(FRACBITS-1);
+			y -= ((2*7) - 11)<<(FRACBITS-1);
+		}
+		else
+		{
+			x += 28<<(FRACBITS-1);
+			y += 7<<(FRACBITS-1);
+		}
+	}
+
+	y = (BASEVIDWIDTH-(vid.width/vid.dupx))/2;
+
+	V_DrawFill(y, 20, vid.width/vid.dupx, 24, 159);
+	{
+		static fixed_t st_scroll = -1;
+		const char* titl;
+		x = 16;
+		V_DrawString(x, 10, 0, "NOW PLAYING:");
+		if (curplaying)
+		{
+			if (curplaying->alttitle[0])
+				titl = va("%s - %s - ", curplaying->title, curplaying->alttitle);
+			else
+				titl = va("%s - ", curplaying->title);
+		}
+		else
+			titl = "NONE - ";
+
+		i = V_LevelNameWidth(titl);
+
+		if (++st_scroll >= i)
+			st_scroll %= i;
+
+		x -= st_scroll;
+
+		while (x < BASEVIDWIDTH-y)
+			x += i;
+		while (x > y)
+		{
+			x -= i;
+			V_DrawLevelTitle(x, 24, 0, titl);
+		}
+
+		if (curplaying)
+			V_DrawRightAlignedString(BASEVIDWIDTH-16, 46, V_ALLOWLOWERCASE, curplaying->authors);
+	}
+
+	V_DrawFill(165, 60, 140, 112, 159);
+
+	{
+		INT32 t, b, q, m = 112;
+
+		if (numsoundtestdefs <= 7)
+		{
+			t = 0;
+			b = numsoundtestdefs - 1;
+			i = 0;
+		}
+		else
+		{
+			q = m;
+			m = (5*m)/numsoundtestdefs;
+			if (st_sel < 3)
+			{
+				t = 0;
+				b = 6;
+				i = 0;
+			}
+			else if (st_sel >= numsoundtestdefs-4)
+			{
+				t = numsoundtestdefs - 7;
+				b = numsoundtestdefs - 1;
+				i = q-m;
+			}
+			else
+			{
+				t = st_sel - 3;
+				b = st_sel + 3;
+				i = (t * (q-m))/(numsoundtestdefs - 7);
+			}
+		}
+
+		V_DrawFill(165+140-1, 60 + i, 1, m, 0);
+
+		if (t != 0)
+			V_DrawString(165+140+4, 60+4 - (skullAnimCounter/5), V_YELLOWMAP, "\x1A");
+
+		if (b != numsoundtestdefs - 1)
+			V_DrawString(165+140+4, 60+112-12 + (skullAnimCounter/5), V_YELLOWMAP, "\x1B");
+
+		x = 169;
+		y = 64;
+
+		while (t <= b)
+		{
+			if (t == st_sel)
+				V_DrawFill(165, y-4, 140-1, 16, 155);
+			if (!soundtestdefs[t]->allowed)
+			{
+				V_DrawString(x, y, (t == st_sel ? V_YELLOWMAP : 0)|V_ALLOWLOWERCASE, "???");
+			}
+			else if (soundtestdefs[t] == &soundtestsfx)
+			{
+				const char *sfxstr = va("SFX %s", cv_soundtest.string);
+				V_DrawString(x, y, (t == st_sel ? V_YELLOWMAP : 0), sfxstr);
+				if (t == st_sel)
+				{
+					V_DrawCharacter(x - 10 - (skullAnimCounter/5), y,
+						'\x1C' | V_YELLOWMAP, false);
+					V_DrawCharacter(x + 2 + V_StringWidth(sfxstr, 0) + (skullAnimCounter/5), y,
+						'\x1D' | V_YELLOWMAP, false);
+				}
+
+				if (curplaying == soundtestdefs[t])
+				{
+					sfxstr = (cv_soundtest.value) ? S_sfx[cv_soundtest.value].name : "N/A";
+					i = V_StringWidth(sfxstr, 0);
+					V_DrawFill(165+140-9-i, y-4, i+8, 16, 150);
+					V_DrawRightAlignedString(165+140-5, y, V_YELLOWMAP, sfxstr);
+				}
+			}
+			else
+			{
+				V_DrawString(x, y, (t == st_sel ? V_YELLOWMAP : 0)|V_ALLOWLOWERCASE, soundtestdefs[t]->title);
+				if (curplaying == soundtestdefs[t])
+				{
+					V_DrawFill(165+140-9, y-4, 8, 16, 150);
+					//V_DrawCharacter(165+140-8, y, '\x19' | V_YELLOWMAP, false);
+					V_DrawFixedPatch((165+140-9)<<FRACBITS, (y<<FRACBITS)-(bounce*4), FRACUNIT, 0, hu_font['\x19'-HU_FONTSTART], V_GetStringColormap(V_YELLOWMAP));
+				}
+			}
+			t++;
+			y += 16;
+		}
+	}
 }
 
 static void M_HandleSoundTest(INT32 choice)
@@ -7009,27 +7284,102 @@ static void M_HandleSoundTest(INT32 choice)
 	switch (choice)
 	{
 		case KEY_DOWNARROW:
-			M_NextOpt();
-			S_StartSound(NULL, sfx_menu1);
+			if (st_sel++ >= numsoundtestdefs-1)
+				st_sel = 0;
+			{
+				cv_closedcaptioning.value = st_cc; // hack
+				S_StartSound(NULL, sfx_menu1);
+				cv_closedcaptioning.value = 1; // hack
+			}
 			break;
 		case KEY_UPARROW:
-			M_PrevOpt();
-			S_StartSound(NULL, sfx_menu1);
+			if (!st_sel--)
+				st_sel = numsoundtestdefs-1;
+			{
+				cv_closedcaptioning.value = st_cc; // hack
+				S_StartSound(NULL, sfx_menu1);
+				cv_closedcaptioning.value = 1; // hack
+			}
+			break;
+		case KEY_PGDN:
+			if (st_sel < numsoundtestdefs-1)
+			{
+				st_sel += 3;
+				if (st_sel >= numsoundtestdefs-1)
+					st_sel = numsoundtestdefs-1;
+				cv_closedcaptioning.value = st_cc; // hack
+				S_StartSound(NULL, sfx_menu1);
+				cv_closedcaptioning.value = 1; // hack
+			}
+			break;
+		case KEY_PGUP:
+			if (st_sel)
+			{
+				st_sel -= 3;
+				if (st_sel < 0)
+					st_sel = 0;
+				cv_closedcaptioning.value = st_cc; // hack
+				S_StartSound(NULL, sfx_menu1);
+				cv_closedcaptioning.value = 1; // hack
+			}
 			break;
 		case KEY_BACKSPACE:
+			if (curplaying)
+			{
+				S_StopSounds();
+				S_StopMusic();
+				curplaying = NULL;
+				st_time = 0;
+				cv_closedcaptioning.value = st_cc; // hack
+				S_StartSound(NULL, sfx_skid);
+				cv_closedcaptioning.value = 1; // hack
+			}
+			break;
 		case KEY_ESCAPE:
 			exitmenu = true;
 			break;
 
 		case KEY_RIGHTARROW:
-			CV_AddValue(&cv_soundtest, 1);
+			if (soundtestdefs[st_sel] == &soundtestsfx && soundtestdefs[st_sel]->allowed)
+			{
+				S_StopSounds();
+				S_StopMusic();
+				curplaying = soundtestdefs[st_sel];
+				st_time = 0;
+				CV_AddValue(&cv_soundtest, 1);
+			}
 			break;
 		case KEY_LEFTARROW:
-			CV_AddValue(&cv_soundtest, -1);
+			if (soundtestdefs[st_sel] == &soundtestsfx && soundtestdefs[st_sel]->allowed)
+			{
+				S_StopSounds();
+				S_StopMusic();
+				curplaying = soundtestdefs[st_sel];
+				st_time = 0;
+				CV_AddValue(&cv_soundtest, -1);
+			}
 			break;
 		case KEY_ENTER:
 			S_StopSounds();
-			S_StartSound(NULL, cv_soundtest.value);
+			S_StopMusic();
+			st_time = 0;
+			if (soundtestdefs[st_sel]->allowed)
+			{
+				curplaying = soundtestdefs[st_sel];
+				if (curplaying == &soundtestsfx)
+				{
+					// S_StopMusic() -- is this necessary?
+					if (cv_soundtest.value)
+						S_StartSound(NULL, cv_soundtest.value);
+				}
+				else
+					S_ChangeMusicInternal(curplaying->name, !curplaying->stoppingtics);
+			}
+			else
+			{
+				curplaying = NULL;
+				S_StartSound(NULL, sfx_lose);
+			}
 			break;
 
 		default:
@@ -7037,6 +7387,11 @@ static void M_HandleSoundTest(INT32 choice)
 	}
 	if (exitmenu)
 	{
+		Z_Free(soundtestdefs);
+		soundtestdefs = NULL;
+
+		cv_closedcaptioning.value = st_cc; // undo hack
+
 		if (currentMenu->prevMenu)
 			M_SetupNextMenu(currentMenu->prevMenu);
 		else
@@ -7111,8 +7466,8 @@ static void M_SecretsMenu(INT32 choice)
 					SR_MainMenu[i].itemaction = M_Credits;
 					break;
 				case SECRET_SOUNDTEST:
-					SR_MainMenu[i].status = IT_STRING|IT_KEYHANDLER;
-					SR_MainMenu[i].itemaction = M_HandleSoundTest;
+					SR_MainMenu[i].status = IT_STRING|IT_CALL;
+					SR_MainMenu[i].itemaction = M_SoundTest;
 				default:
 					break;
 			}
diff --git a/src/m_menu.h b/src/m_menu.h
index 51c734a43c8a6199c37857a622c69c2a567113f7..ec7915cc29ee070689fe800787658cfc097c1ecd 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -106,6 +106,7 @@ typedef enum
 	MN_SR_UNLOCKCHECKLIST,
 	MN_SR_EMBLEMHINT,
 	MN_SR_PLAYER,
+	MN_SR_SOUNDTEST,
 
 	// Addons (Part of MISC, but let's make it our own)
 	MN_AD_MAIN,
diff --git a/src/p_setup.c b/src/p_setup.c
index d844a29e6b0b29247a30315b9f3ad037da7d5fcd..79391c3e427298bdd977696c09069e6da5ac56fe 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -3477,6 +3477,11 @@ boolean P_AddWadFile(const char *wadfilename)
 	R_PatchSkins(wadnum); // toast: PATCH PATCH
 	ST_ReloadSkinFaceGraphics();
 
+	//
+	// edit music defs
+	//
+	S_LoadMusicDefs(wadnum);
+
 	//
 	// search for maps
 	//
diff --git a/src/s_sound.c b/src/s_sound.c
index 8b87bd08328b9d00e0de2a0f0d4bbbc5b4f9d2e8..ab4bde6171ce53fb80590e0f8d511c44dc1030c5 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -38,6 +38,7 @@ extern INT32 msg_id;
 #include "p_local.h" // camera info
 #include "fastcmp.h"
 #include "m_misc.h" // for tunes command
+#include "m_cond.h" // for conditionsets
 
 #if defined(HAVE_BLUA) && defined(HAVE_LUA_MUSICPLUS)
 #include "lua_hook.h" // MusicChange hook
@@ -1425,6 +1426,274 @@ static UINT32    queue_fadeinms;
 
 static tic_t     pause_starttic;
 
+/// ------------------------
+/// Music Definitions
+/// ------------------------
+
+musicdef_t soundtestsfx = {
+	"_STSFX", // prevents exactly one valid track name from being used on the sound test
+	"Sound Effects",
+	"",
+	"SEGA, Sonic Team Jr, other sources",
+	1, // show on soundtest page 1
+	0, // with no conditions
+	0,
+	0,
+	false,
+	NULL
+};
+
+musicdef_t *musicdefstart = &soundtestsfx;
+
+//
+// search for music definition in wad
+//
+static UINT16 W_CheckForMusicDefInPwad(UINT16 wadid)
+{
+	UINT16 i;
+	lumpinfo_t *lump_p;
+
+	lump_p = wadfiles[wadid]->lumpinfo;
+	for (i = 0; i < wadfiles[wadid]->numlumps; i++, lump_p++)
+		if (memcmp(lump_p->name, "MUSICDEF", 8) == 0)
+			return i;
+
+	return INT16_MAX; // not found
+}
+
+void S_LoadMusicDefs(UINT16 wadnum)
+{
+	UINT16 lump;
+	char *buf;
+	char *buf2;
+	char *stoken;
+	char *value;
+	size_t size;
+	INT32 i;
+	musicdef_t *def = NULL;
+	UINT16 line = 1; // for better error msgs
+
+	lump = W_CheckForMusicDefInPwad(wadnum);
+	if (lump == INT16_MAX)
+		return;
+
+	buf = W_CacheLumpNumPwad(wadnum, lump, PU_CACHE);
+	size = W_LumpLengthPwad(wadnum, lump);
+
+	// for strtok
+	buf2 = malloc(size+1);
+	if (!buf2)
+		I_Error("S_LoadMusicDefs: No more free memory\n");
+	M_Memcpy(buf2,buf,size);
+	buf2[size] = '\0';
+
+	stoken = strtok (buf2, "\r\n ");
+	// Find music def
+	while (stoken)
+	{
+		/*if ((stoken[0] == '/' && stoken[1] == '/')
+			|| (stoken[0] == '#')) // skip comments
+		{
+			stoken = strtok(NULL, "\r\n"); // skip end of line
+			if (def)
+				stoken = strtok(NULL, "\r\n= ");
+			else
+				stoken = strtok(NULL, "\r\n ");
+			line++;
+		}
+		else*/ if (!stricmp(stoken, "lump"))
+		{
+			value = strtok(NULL, "\r\n ");
+
+			if (!value)
+			{
+				CONS_Alert(CONS_WARNING, "MUSICDEF: Lump '%s' is missing name. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line);
+				stoken = strtok(NULL, "\r\n"); // skip end of line
+				goto skip_lump;
+			}
+
+			// No existing musicdefs
+			/*if (!musicdefstart)
+			{
+				musicdefstart = Z_Calloc(sizeof (musicdef_t), PU_STATIC, NULL);
+				STRBUFCPY(musicdefstart->name, value);
+				strlwr(musicdefstart->name);
+				def = musicdefstart;
+				//CONS_Printf("S_LoadMusicDefs: Initialized musicdef w/ song '%s'\n", def->name);
+			}
+			else*/
+			{
+				musicdef_t *prev = NULL;
+				def = musicdefstart;
+
+				// Search if this is a replacement
+				//CONS_Printf("S_LoadMusicDefs: Searching for song replacement...\n");
+				while (def)
+				{
+					if (!stricmp(def->name, value))
+					{
+						//CONS_Printf("S_LoadMusicDefs: Found song replacement '%s'\n", def->name);
+						break;
+					}
+
+					prev = def;
+					def = def->next;
+				}
+
+				// Nothing found, add to the end.
+				if (!def)
+				{
+					def = Z_Calloc(sizeof (musicdef_t), PU_STATIC, NULL);
+					STRBUFCPY(def->name, value);
+					strlwr(def->name);
+					def->bpm = TICRATE<<(FRACBITS-1); // FixedDiv((60*TICRATE)<<FRACBITS, 120<<FRACBITS)
+					if (prev != NULL)
+						prev->next = def;
+					//CONS_Printf("S_LoadMusicDefs: Added song '%s'\n", def->name);
+				}
+			}
+
+skip_lump:
+			stoken = strtok(NULL, "\r\n ");
+			line++;
+		}
+		else
+		{
+			value = strtok(NULL, "\r\n= ");
+
+			if (!value)
+			{
+				CONS_Alert(CONS_WARNING, "MUSICDEF: Field '%s' is missing value. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line);
+				stoken = strtok(NULL, "\r\n"); // skip end of line
+				goto skip_field;
+			}
+
+			if (!def)
+			{
+				CONS_Alert(CONS_ERROR, "MUSICDEF: No music definition before field '%s'. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line);
+				free(buf2);
+				return;
+			}
+
+			i = atoi(value);
+
+			if (!stricmp(stoken, "usage")) {
+#if 0 // Ignore for now
+				STRBUFCPY(def->usage, value);
+				for (value = def->usage; *value; value++)
+					if (*value == '_') *value = ' '; // turn _ into spaces.
+				//CONS_Printf("S_LoadMusicDefs: Set usage to '%s'\n", def->usage);
+#endif
+			} else if (!stricmp(stoken, "source")) {
+#if 0 // Ignore for now
+				STRBUFCPY(def->source, value);
+				for (value = def->source; *value; value++)
+					if (*value == '_') *value = ' '; // turn _ into spaces.
+				//CONS_Printf("S_LoadMusicDefs: Set source to '%s'\n", def->usage);
+#endif
+			} else if (!stricmp(stoken, "title")) {
+				STRBUFCPY(def->title, value);
+				for (value = def->title; *value; value++)
+					if (*value == '_') *value = ' '; // turn _ into spaces.
+				//CONS_Printf("S_LoadMusicDefs: Set title to '%s'\n", def->source);
+			} else if (!stricmp(stoken, "alttitle")) {
+				STRBUFCPY(def->alttitle, value);
+				for (value = def->alttitle; *value; value++)
+					if (*value == '_') *value = ' '; // turn _ into spaces.
+				//CONS_Printf("S_LoadMusicDefs: Set alttitle to '%s'\n", def->source);
+			} else if (!stricmp(stoken, "authors")) {
+				STRBUFCPY(def->authors, value);
+				for (value = def->authors; *value; value++)
+					if (*value == '_') *value = ' '; // turn _ into spaces.
+				//CONS_Printf("S_LoadMusicDefs: Set authors to '%s'\n", def->source);
+			} else if (!stricmp(stoken, "soundtestpage")) {
+				def->soundtestpage = (UINT8)i;
+			} else if (!stricmp(stoken, "soundtestcond")) {
+				// Convert to map number
+				if (value[0] >= 'A' && value[0] <= 'Z' && value[2] == '\0')
+					i = M_MapNumber(value[0], value[1]);
+				def->soundtestcond = (INT16)i;
+			} else if (!stricmp(stoken, "stoppingtime")) {
+				double stoppingtime = atof(value)*TICRATE;
+				def->stoppingtics = (tic_t)stoppingtime;
+			} else if (!stricmp(stoken, "bpm")) {
+				double bpm = atof(value);
+				fixed_t bpmf = FLOAT_TO_FIXED(bpm);
+				if (bpmf > 0)
+					def->bpm = FixedDiv((60*TICRATE)<<FRACBITS, bpmf);
+			} else {
+				CONS_Alert(CONS_WARNING, "MUSICDEF: Invalid field '%s'. (file %s, line %d)\n", stoken, wadfiles[wadnum]->filename, line);
+			}
+
+skip_field:
+			stoken = strtok(NULL, "\r\n= ");
+			line++;
+		}
+	}
+
+	free(buf2);
+	return;
+}
+
+//
+// S_InitMusicDefs
+//
+// Simply load music defs in all wads.
+//
+void S_InitMusicDefs(void)
+{
+	UINT16 i;
+	for (i = 0; i < numwadfiles; i++)
+		S_LoadMusicDefs(i);
+}
+
+musicdef_t **soundtestdefs = NULL;
+INT32 numsoundtestdefs = 0;
+UINT8 soundtestpage = 1;
+
+//
+// S_PrepareSoundTest
+//
+// Prepare sound test. What am I, your butler?
+//
+boolean S_PrepareSoundTest(void)
+{
+	musicdef_t *def;
+	INT32 pos = numsoundtestdefs = 0;
+
+	for (def = musicdefstart; def; def = def->next)
+	{
+		if (!(def->soundtestpage & soundtestpage))
+			continue;
+		def->allowed = false;
+		numsoundtestdefs++;
+	}
+
+	if (!numsoundtestdefs)
+		return false;
+
+	if (soundtestdefs)
+		Z_Free(soundtestdefs);
+
+	if (!(soundtestdefs = Z_Malloc(numsoundtestdefs*sizeof(musicdef_t *), PU_STATIC, NULL)))
+		I_Error("S_PrepareSoundTest(): could not allocate soundtestdefs.");
+
+	for (def = musicdefstart; def /*&& i < numsoundtestdefs*/; def = def->next)
+	{
+		if (!(def->soundtestpage & soundtestpage))
+			continue;
+		soundtestdefs[pos++] = def;
+		if (def->soundtestcond > 0 && !(mapvisited[def->soundtestcond-1] & MV_BEATEN))
+			continue;
+		if (def->soundtestcond < 0 && !M_Achieved(1-def->soundtestcond))
+			continue;
+		def->allowed = true;
+	}
+
+	return true;
+}
+
+
 /// ------------------------
 /// Music Status
 /// ------------------------
diff --git a/src/s_sound.h b/src/s_sound.h
index c46c9fa089b40b02965c9dd142469543cf1f8fe2..d1551df0bdcad2210517dd3ae894fa7848169da2 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -188,6 +188,39 @@ boolean S_MusicExists(const char *mname, boolean checkMIDI, boolean checkDigi);
 // Set Speed of Music
 boolean S_SpeedMusic(float speed);
 
+// Music definitions
+typedef struct musicdef_s
+{
+	char name[7];
+	char title[32];
+	char alttitle[64];
+	char authors[256];
+	//char usage[256]; -- probably never going to be relevant to vanilla
+	/*
+	the trouble here is that kart combines what we call "title"
+	and "authors" into one string. we need to split it for sound
+	test reasons. they might split it later like we did, but...
+	*/
+	//char source[256];
+	UINT8 soundtestpage;
+	INT16 soundtestcond; // +ve for map, -ve for conditionset, 0 for already here
+	tic_t stoppingtics;
+	fixed_t bpm;
+	boolean allowed; // question marks or listenable on sound test?
+	struct musicdef_s *next;
+} musicdef_t;
+
+extern musicdef_t soundtestsfx;
+extern musicdef_t *musicdefstart;
+extern musicdef_t **soundtestdefs;
+extern INT32 numsoundtestdefs;
+extern UINT8 soundtestpage;
+
+void S_LoadMusicDefs(UINT16 wadnum);
+void S_InitMusicDefs(void);
+
+boolean S_PrepareSoundTest(void);
+
 //
 // Music Seeking
 //
diff --git a/src/sounds.c b/src/sounds.c
index 791a7571e432b23343fcb39b63ca5a6d9c86bd7f..197bfc0660a303dc213d139ca6c7137439f303b4 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -857,35 +857,45 @@ void S_InitRuntimeSounds (void)
 	}
 }
 
+sfxenum_t sfxfree = sfx_freeslot0;
+
 // Add a new sound fx into a free sfx slot.
 //
 sfxenum_t S_AddSoundFx(const char *name, boolean singular, INT32 flags, boolean skinsound)
 {
-	sfxenum_t i, slot;
+	sfxenum_t i;
 
 	if (skinsound)
-		slot = sfx_skinsoundslot0;
-	else
-		slot = sfx_freeslot0;
-
-	for (i = slot; i < NUMSFX; i++)
 	{
-		if (!S_sfx[i].priority)
+		for (i = sfx_skinsoundslot0; i < NUMSFX; i++)
 		{
-			strncpy(freeslotnames[i-sfx_freeslot0], name, 6);
-			S_sfx[i].singularity = singular;
-			S_sfx[i].priority = 60;
-			S_sfx[i].pitch = flags;
-			S_sfx[i].volume = -1;
-			S_sfx[i].lumpnum = LUMPERROR;
-			S_sfx[i].skinsound = -1;
-			S_sfx[i].usefulness = -1;
-
-			/// \todo if precached load it here
-			S_sfx[i].data = NULL;
-			return i;
+			if (S_sfx[i].priority)
+				continue;
+			break;
 		}
 	}
+	else
+		i = sfxfree;
+
+	if (i < NUMSFX)
+	{
+		strncpy(freeslotnames[i-sfx_freeslot0], name, 6);
+		S_sfx[i].singularity = singular;
+		S_sfx[i].priority = 60;
+		S_sfx[i].pitch = flags;
+		S_sfx[i].volume = -1;
+		S_sfx[i].lumpnum = LUMPERROR;
+		S_sfx[i].skinsound = -1;
+		S_sfx[i].usefulness = -1;
+
+		/// \todo if precached load it here
+		S_sfx[i].data = NULL;
+
+		if (!skinsound)
+			sfxfree++;
+
+		return i;
+	}
 	CONS_Alert(CONS_WARNING, M_GetText("No more free sound slots\n"));
 	return 0;
 }
diff --git a/src/sounds.h b/src/sounds.h
index 9f6e0bab640128b2e8c4a36182479bc2b28f7cbe..d48deb5404d8e2a279e1794ba85e0ff114bdecc8 100644
--- a/src/sounds.h
+++ b/src/sounds.h
@@ -879,6 +879,7 @@ typedef enum
 
 void S_InitRuntimeSounds(void);
 sfxenum_t S_AddSoundFx(const char *name, boolean singular, INT32 flags, boolean skinsound);
+extern sfxenum_t sfxfree; // sound test and slotting
 void S_RemoveSoundFx(sfxenum_t id);
 
 #endif
diff --git a/src/v_video.c b/src/v_video.c
index 7f99cfd326c8089b878e4624a90c9f0e295edf87..d7dc0e50ff4c9d19789018e2211e120bb87d9864 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -541,12 +541,12 @@ static inline UINT8 transmappedpdraw(const UINT8 *dest, const UINT8 *source, fix
 }
 
 // Draws a patch scaled to arbitrary size.
-void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t *patch, const UINT8 *colormap)
+void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap)
 {
 	UINT8 (*patchdrawfunc)(const UINT8*, const UINT8*, fixed_t);
 	UINT32 alphalevel = 0;
 
-	fixed_t col, ofs, colfrac, rowfrac, fdup;
+	fixed_t col, ofs, colfrac, rowfrac, fdup, vdup;
 	INT32 dupx, dupy;
 	const column_t *column;
 	UINT8 *desttop, *dest, *deststart, *destend;
@@ -563,7 +563,7 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 	//if (rendermode != render_soft && !con_startup)		// Why?
 	if (rendermode != render_soft)
 	{
-		HWR_DrawFixedPatch((GLPatch_t *)patch, x, y, pscale, scrn, colormap);
+		HWR_DrawStretchyFixedPatch((GLPatch_t *)patch, x, y, pscale, vscale, scrn, colormap);
 		return;
 	}
 #endif
@@ -618,9 +618,11 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 
 	// only use one dup, to avoid stretching (har har)
 	dupx = dupy = (dupx < dupy ? dupx : dupy);
-	fdup = FixedMul(dupx<<FRACBITS, pscale);
+	fdup = vdup = FixedMul(dupx<<FRACBITS, pscale);
+	if (vscale != pscale)
+		vdup = FixedMul(dupx<<FRACBITS, vscale);
 	colfrac = FixedDiv(FRACUNIT, fdup);
-	rowfrac = FixedDiv(FRACUNIT, fdup);
+	rowfrac = FixedDiv(FRACUNIT, vdup);
 
 	// So it turns out offsets aren't scaled in V_NOSCALESTART unless V_OFFSET is applied ...poo, that's terrible
 	// For now let's just at least give V_OFFSET the ability to support V_FLIP
@@ -637,7 +639,7 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 
 		// top offset
 		// TODO: make some kind of vertical version of V_FLIP, maybe by deprecating V_OFFSET in future?!?
-		offsety = FixedMul(SHORT(patch->topoffset)<<FRACBITS, pscale);
+		offsety = FixedMul(SHORT(patch->topoffset)<<FRACBITS, vscale);
 
 		if ((scrn & (V_NOSCALESTART|V_OFFSET)) == (V_NOSCALESTART|V_OFFSET)) // Multiply by dupx/dupy for crosshairs
 		{
@@ -653,13 +655,14 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 	if (splitscreen && (scrn & V_PERPLAYER))
 	{
 		fixed_t adjusty = ((scrn & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)<<(FRACBITS-1);
-		fdup >>= 1;
+		vdup >>= 1;
 		rowfrac <<= 1;
 		y >>= 1;
 #ifdef QUADS
 		if (splitscreen > 1) // 3 or 4 players
 		{
 			fixed_t adjustx = ((scrn & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)<<(FRACBITS-1));
+			fdup >>= 1;
 			colfrac <<= 1;
 			x >>= 1;
 			if (stplyr == &players[displayplayer])
@@ -825,7 +828,7 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t
 			dest = desttop;
 			if (scrn & V_FLIP)
 				dest = deststart + (destend - desttop);
-			dest += FixedInt(FixedMul(topdelta<<FRACBITS,fdup))*vid.width;
+			dest += FixedInt(FixedMul(topdelta<<FRACBITS,vdup))*vid.width;
 
 			for (ofs = 0; dest < deststop && (ofs>>FRACBITS) < column->length; ofs += rowfrac)
 			{
diff --git a/src/v_video.h b/src/v_video.h
index 01d50cd57957479dfe74700aba40212caa215996..cd32ac5f89ed5879f01a0e44adfae872e1f2c908 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -140,7 +140,8 @@ extern RGBA_t *pMasterPalette;
 #define V_DrawSmallTranslucentPatch(x,y,s,p) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT/2, s, p, NULL)
 #define V_DrawTinyTranslucentPatch(x,y,s,p) V_DrawFixedPatch((x)<<FRACBITS, (y)<<FRACBITS, FRACUNIT/4, s, p, NULL)
 #define V_DrawSciencePatch(x,y,s,p,sc) V_DrawFixedPatch(x,y,sc,s,p,NULL)
-void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t *patch, const UINT8 *colormap);
+#define V_DrawFixedPatch(x,y,sc,s,p,c) V_DrawStretchyFixedPatch(x,y,sc,sc,s,p,c)
+void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap);
 void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t *patch, fixed_t sx, fixed_t sy, fixed_t w, fixed_t h);
 
 void V_DrawContinueIcon(INT32 x, INT32 y, INT32 flags, INT32 skinnum, UINT8 skincolor);
diff --git a/src/w_wad.c b/src/w_wad.c
index ea9310580a08dd2e6d97302023d06d96866e4f6c..b4ee772d96f2ea877ebfc06c2440e478d1266b53 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -1891,6 +1891,7 @@ int W_VerifyNMUSlumps(const char *filename)
 		{"STT", 3}, // Acceptable HUD changes (Score Time Rings)
 		{"YB_", 3}, // Intermission graphics, goes with the above
 		{"M_", 2}, // As does menu stuff
+		{"MUSICDEF", 8}, // Song definitions (thanks kart)
 
 		{NULL, 0},
 	};