diff --git a/src/d_main.c b/src/d_main.c
index 2a5993c1776ab4e1d8de58e106912af91cadb14e..29a916863a9231afead3d6722a623374d7afb21d 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1350,6 +1350,7 @@ void D_SRB2Main(void)
 	I_StartupSound();
 	I_InitMusic();
 	S_InitSfxChannels(cv_soundvolume.value);
+	S_InitMusicDefs();
 
 	CONS_Printf("ST_Init(): Init status bar.\n");
 	ST_Init();
diff --git a/src/hu_stuff.c b/src/hu_stuff.c
index 64580c82daf0e7b04d2c5ccb7510cc5cb93931d6..a38e91a78c8a8ffbf264ad1c8f0606d0a979b294 100644
--- a/src/hu_stuff.c
+++ b/src/hu_stuff.c
@@ -2130,12 +2130,18 @@ static void HU_DrawDemoInfo(void)
 //
 static void HU_DrawSongCredits(void)
 {
-	const char *str = va("\x1F"" %s", songCredits[cursongcredit.index].info);
-	INT32 len = V_ThinStringWidth(str, V_ALLOWLOWERCASE|V_6WIDTHSPACE);
-	INT32 destx = (len+7);
+	char *str;
+	INT32 len, destx;
 	INT32 y = (splitscreen ? (BASEVIDHEIGHT/2)-4 : 32);
 	INT32 bgt;
 
+	if (!cursongcredit.def) // No def
+		return;
+
+	str = va("\x1F"" %s", cursongcredit.def->source);
+	len = V_ThinStringWidth(str, V_ALLOWLOWERCASE|V_6WIDTHSPACE);
+	destx = (len+7);
+
 	if (cursongcredit.anim)
 	{
 		if (cursongcredit.trans > 0)
diff --git a/src/k_kart.c b/src/k_kart.c
index 63425d1afbf9365f7c9580d012539a7f34e519be..50d19c199541c8b8d4610f2f9eab5d9eb09ba3d6 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -4446,7 +4446,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
 	if (P_IsLocalPlayer(player) && leveltime == (starttime + (TICRATE/2)))
 	{
 		S_ChangeMusicInternal(mapmusname, true);
-		S_InitMusicCredit();
+		S_ShowMusicCredit();
 	}
 }
 
diff --git a/src/p_setup.c b/src/p_setup.c
index 57a069a1fc7d1db6d0ceba7d82e941f9e84dce4b..90ac3a7cf28189d585ffe1f03ec1e9cb99a221ea 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -3439,6 +3439,11 @@ boolean P_AddWadFile(const char *wadfilename)
 	//
 	R_AddSkins(wadnum); // faB: wadfile index in wadfiles[]
 
+	// 
+	// edit music defs
+	//
+	S_LoadMusicDefs(wadnum);
+
 	//
 	// search for maps
 	//
diff --git a/src/p_spec.c b/src/p_spec.c
index b6661bba0fcf5258b897d3b546fd13ad921cf4e1..e3b54cbeaed301efb46b93fabde0303bad1a2d29 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -2450,7 +2450,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 					mapmusflags |= MUSIC_RELOADRESET;
 
 				S_ChangeMusic(mapmusname, mapmusflags, !(line->flags & ML_EFFECT4));
-				S_InitMusicCredit();
+				S_ShowMusicCredit();
 
 				// Except, you can use the ML_BLOCKMONSTERS flag to change this behavior.
 				// if (mapmusflags & MUSIC_RELOADRESET) then it will reset the music in G_PlayerReborn.
diff --git a/src/s_sound.c b/src/s_sound.c
index 87ab6359ada4f23cedf701d5e435ece9e3005c2d..e3a62768e52f363761b85ba2eeb756240e1a31c0 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -1541,131 +1541,209 @@ static void      *music_data;
 static UINT16    music_flags;
 static boolean   music_looping;
 
-songcredits_t songCredits[] = {
-	// Race maps
-	{"kmap01","Toot Toot Sonic Warrior (Instrumental) - Sonic CD"},
-	{"kmap02","Chao Race (Extended Mix) - Sonic Adventure 2"},
-	{"kmap03","Touhou 10.5: Broken Moon (Jazz Arrangement) - Tokyo Active NEETs"},
-	{"kmap04","Mortvia Fountain - Castlevania: Curse of Darkness"},
-	{"kmap05","Let's Go Away - Daytona USA"},
-	{"kmap06","War Machine - Marvel vs. Capcom"},
-	{"kmap07","Sonic 3D Blast: Green Grove Zone, Act 1 (Remix) - Tee Lopes"},
-	{"kmap08","Sonic Drift 2: Hill Top (Remix) - SeventhSentinel"},
-	{"kmap09","Industrial District - Columns III"},
-	{"kmap10","Dream Uneven Bars - Mario & Sonic at the London 2012 Olympic Games"},
-	{"kmap11","Moon Palace 2 - Drift City"},
-	{"kmap12","Authentic Sky - Tekken 4"},
-	{"kmap13","Plant Man's Stage - Rockman 6 Complete Works"},
-	{"kmap14","Twinkle Cart - Sonic Adventure"},
-	{"kmap15","Pleasure Castle - Sonic Adventure"},
-	{"kmap16","Horizon Heights Zone, Act 2 - Sonic: After the Sequel"},
-	{"kmap17","Frost Man's Stage - Mega Man 8"},
-	{"kmap18","Foliage Furnace Zone, Act 1 - Sonic: After the Sequel"},
-	{"kmap19","Sand Ocean - F-Zero"},
-	{"kmap20","El Anoir Field South - LaTale"},
-	{"kmap21","Western GunRun - LaTale"},
-	{"kmap22","Jr. Street - Mario Hoops 3 on 3"},
-	{"kmap23","Fight or Flight (Air Armada) - Rivals of Aether"},
-	{"kmap24","White Land I - F-Zero"},
-	{"kmap25","Donkey Kong Country: Fear Factory (Remix) - Vincent Rubinetti"},
-	{"kmap26","Desert Palace - Sonic the Hedgehog 3"},
-	{"kmap27","Aurora Atoll Zone - SeventhSentinel"},
-	{"kmap28","Hol Horse - JoJo's Bizarre Adventure (Arcade)"},
-	{"kmap29","Red Barrage Area - Sonic Adventure"},
-	{"kmap30","Reach Out To The Truth - Persona 4 Arena"},
-	{"kmap31","Ave de Rapina - Beatmania IIDX 7th Style"},
-	{"kmap32","Atomic Waste Zone - Michael Staple"},
-	{"kmap33","Desert Area - LaTale"},
-	{"kmap34","DuckTales: The Moon (Remix) - Chris Holland, Luke Kwing"},
-	{"kmap35","Eggman Empire Zone - Michael Staple"},
-	{"kmap36","Shooting Star - Elwood"},
-	{"kmap37","Vigaku (Theme of Ein) - Dead or Alive 2"},
-	{"kmap38","Lovely Gate 3 - Sonic Adventure 2"},
-	{"kmap39","The Biggest Dreamer - Digimon Rumble Arena"},
-	{"kmap40","Soft Collision - Mighty Switch Force 2"},
-	{"kmap41","Ganbare Dochu - Konami Krazy Racers"},
-	{"kmap42","Theme of 'CHAO' - Sonic Adventure"},
-	{"kmap43","Join Us 4 Happy Time - Sonic Adventure"},
-	{"kmap44","Search ~ In the Midst - Ace Attorney: Justice for All"},
-	{"kmap45","Venezia, Italy - Tekken"},
-	{"kmap46","Balloon Panic - Sonic Drift 2"},
-	{"kmap47","Dimension Heist - Sonic Mania"},
-	{"kmap48","MKSC Sky Garden (Remix) - Mario Kart DS"},
-	{"kmap49","MKDS Peach Gardens (Remix) - Mario Kart Wii"},
-	{"kmap50","MKSC Rainbow Road (Remix) - Panman14"},
-	{"kmap51","Donut Plains - Super Mario Kart"},
-	{"kmap52","Mario Circuit - Super Mario Kart"},
-	{"kmap53","Ghost Valley - Super Mario Kart"},
-	{"kmap54","Bowser Castle - Super Mario Kart"},
-	{"kmap55","Vanilla Lake - Super Mario Kart"},
-	// Battle maps
-	{"kmapb0","Tropic Turf Zone, Act 1 - Karl Brueggemann"},
-	{"kmapb1","Seascape - Knuckles' Chaotix"},
-	{"kmapb2","Boss Challenge I - Diddy Kong Racing"},
-	{"kmapb3","Al's Toy Barn - Toy Story 2 (PS1)"},
-	{"kmapb4","SRB2: Techno Hill Zone, Act 1 (Remix) - SeventhSentinel"},
-	{"kmapb5","Sonic the Hedgehog: Marble Zone (Remix) - Tee Lopes"},
-	{"kmapb6","Phi Battle - Sonic Battle"},
-	{"kmapb7","Khan - JoJo's Bizarre Adventure (Arcade)"},
-	{"kmapb8","Underground - Wild Guns Reloaded"},
-	{"kmapb9","Disease Transport - DrTapeworm"},
-	{"kmapba","Shining Force II: Shrine (Remix) - gxf4c3"},
-	{"kmapbb","Bad Taste Aquarium - Sonic Adventure"},
-	{"kmapbc","Space Ship: Strut - Space Channel 5"},
-	{"kmapbd","Strollin' the City - Karl Brueggemann"},
-	{"kmapbe","Fakery Way - Sonic Adventure"},
-	{"kmapbf","Twinkle Circuit - Sonic Adventure"},
-	{"kmapbg","Never Let It Go - Sonic the Fighters"},
-	{"kmapbh","Power Plant - Sonic Heroes"},
-	{"kmapbi","Tails' Lab - Sonic Battle"},
-	{"kmapbj","Mega Man X: Armored Armadillo's Stage (Arranged) - Synthescissor"},
-	{"kmapbk","School Ordeal - Danganronpa V3: Killing Harmony"},
-	{"kmapbl","Mementos - Persona 5"},
-	{"kmapbm","Special Stage (US) - Sonic CD"},
-	{"kmapbn","Battle Mode - Super Mario Kart"},
-	{"kmapbp","Vanilla Lake (Beta) - Super Mario Kart"},
-	{"kmapbr","Choco Mountain - Mario Kart 64"},
-	// Hell maps
-	{"kmaph0","Neo City - Drift City"},
-	{"kmaph1","World Open Finals - Mario Power Tennis"},
-	{"kmaph2","Arid Sands: Day - Sonic Unleashed"},
-	{"kmaph3","Graveyard Gig - Sonic & All-Stars Racing Transformed"},
-	{"kmaph4","Dr. Robotnik's Mean Bean Machine: 2 Player (Remix) - Jonny Atma, The8BitDrummer"},
-	{"kmaph5","Living In One More City - Nib Roc"},
-	{"kmaph6","Ema Skye ~ Scientific Detective - Apollo Justice: Ace Attorney"},
-	{"kmaph7","4 Minutes Before Death - Ghost Trick: Phantom Detective"},
-	{"kmaph8","Napoleon Disappeared!? - Pop'n Music 10"},
-	{"kmaph9","Retro Maze - Pac-Man World Rally"},
-	{"kmapha","Midnight Freeze Zone - Arrow, SSNTails"},
-	{"kmaphb","Townsville Raceway - Cartoon Network Racing (DS)"},
-	{"kmaphc","F-Zero: Silence (Remix) - Tony Thai"},
-	{"kmaphd","Rainbow Road - Super Mario Kart"},
-	{"kmaphe","Meadow Match Zone - Arrow, SSNTails"},
-	{"kmaphf","merge-break - xaki"},
-	// Misc
-	{"titles","Fluvial Beat Deposits - Simon Stalenhag"}, // Stålenhag
-	{"vote",  "Chaotic World - Knuckles' Chaotix"},
-	{"voteea","Decision - Knuckles' Chaotix"},
-	{"voteeb","Decision - Knuckles' Chaotix"},
-	{"kinvnc","Invincibility - Sonic the Hedgehog 3"},
-	{"kgrow", "Surging Power - Knuckles' Chaotix"},
-	{"kstart","Race Start - Diddy Kong Racing"},
-	{"estart","Phantom Ruby Ambience - Sonic Mania"},
-	{"krwin", "Practice - Sora"},
-	{"krok",  "Practice - Sora"},
-	{"krlose","Hit 'Em Up (Instrumental) - 2Pac"},
-	{"krfail","Results - F-Zero"},
-	{"kbwin", "Wild Charabom Defeated! - Bomberman Tournament"},
-	{"kbok",  "Deadly Bomber Base Cleared! - Bomberman Tournament"},
-	{"kblose","You Lose! - Bomberman Tournament"},
-	{"racent","Menu - Metropolis Street Racer"},
-	{"wait2j","Competition Results - Sonic the Hedgehog 3"},
-	{"chalng","Sonic the Hedgehog 3: Minor Boss (Dual PCM) - MarkeyJester"},
-	{"credit","Moot Booxle - Chomp"},
-	{NULL,NULL}
-};
+/// ------------------------
+/// Music Definitions
+/// ------------------------
+
+musicdef_t *musicdefstart = NULL; // First music definition
+struct cursongcredit cursongcredit; // Currently displayed song credit info
+
+//
+// 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;
+	musicdef_t *def, *prev;
+	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';
+
+	def = prev = NULL;
+
+	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
+			{
+				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);
+					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;
+			}
+
+			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")) {
+				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->source);
+			} 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);
+}
 
-struct cursongcredit cursongcredit;
+//
+// S_ShowMusicCredit
+//
+// Display current song's credit on screen
+//
+void S_ShowMusicCredit(void)
+{
+	musicdef_t *def = musicdefstart;
+
+	if (!cv_songcredits.value)
+		return;
+
+	if (!def) // No definitions
+		return;
+
+	while (def)
+	{
+		if (!stricmp(def->name, music_name))
+		{
+			cursongcredit.def = def;
+			cursongcredit.anim = 5*TICRATE;
+			cursongcredit.x = 0;
+			cursongcredit.trans = NUMTRANSMAPS;
+			return;
+		}
+		else
+			def = def->next;
+	}
+}
 
 /// ------------------------
 /// Music Status
@@ -1731,26 +1809,6 @@ boolean S_SpeedMusic(float speed)
 	return I_SetSongSpeed(speed);
 }
 
-void S_InitMusicCredit(void)
-{
-	UINT16 i;
-
-	if (!cv_songcredits.value)
-		return;
-
-	for (i = 0; songCredits[i].lump; i++)
-	{
-		if (!stricmp(songCredits[i].lump, music_name))
-		{
-			cursongcredit.index = i;
-			cursongcredit.anim = 5*TICRATE;
-			cursongcredit.x = 0;
-			cursongcredit.trans = NUMTRANSMAPS;
-			return; // Don't return when there's SOC support, to see if there's any "replacement" credits?
-		}
-	}
-}
-
 /// ------------------------
 /// Music Playback
 /// ------------------------
diff --git a/src/s_sound.h b/src/s_sound.h
index ae11065c7cc1a2f4bfef3883d0a5e2bd3b945d48..df90ecb12051fd2b6e40b951ffc8db8c9c7d0584 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -129,23 +129,27 @@ boolean S_MusicExists(const char *mname, boolean checkMIDI, boolean checkDigi);
 boolean S_SpeedMusic(float speed);
 
 // Music credits
+typedef struct musicdef_s
+{
+	char name[7];
+	//char usage[256];
+	char source[256];
+	struct musicdef_s *next;
+} musicdef_t;
+
 extern struct cursongcredit
 {
-	UINT16 index;
+	musicdef_t *def;
 	UINT16 anim;
 	INT32 x;
 	UINT8 trans;
 } cursongcredit;
 
-typedef struct
-{
-	const char *lump;
-	const char *info;
-} songcredits_t;
-
-extern songcredits_t songCredits[];
+extern musicdef_t *musicdefstart;
 
-void S_InitMusicCredit(void);
+void S_LoadMusicDefs(UINT16 wadnum);
+void S_InitMusicDefs(void);
+void S_ShowMusicCredit(void);
 
 //
 // Music Routines
diff --git a/src/w_wad.c b/src/w_wad.c
index 69b4cb2e7d9a7f965d64f0d6dea8b6fc5407b727..63bee97ded616fdf31b92e4b64c42e4757efd22e 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -1738,6 +1738,7 @@ int W_VerifyNMUSlumps(const char *filename)
 
 		{"MKFNT", 5}, // Kart font changes
 		{"K_", 2}, // Kart graphic changes
+		{"MUSICDEF", 8}, // Kart song definitions
 
 		{NULL, 0},
 	};