diff --git a/src/i_sound.h b/src/i_sound.h
index 4e6a253333aed70eaafdaaa817188ff781706c78..50b9653254e245e08275bdcaa76707049ff17d44 100644
--- a/src/i_sound.h
+++ b/src/i_sound.h
@@ -249,6 +249,12 @@ boolean I_SetSongTrack(INT32 track);
 
 void I_SetInternalMusicVolume(UINT8 volume);
 
+void I_StopFadingMusic(void);
+
+boolean I_FadeMusicFromLevel(UINT8 target_volume, INT16 source_volume, UINT32 ms);
+
+#define I_FadeMusic(a, b) I_FadeMusicFromLevel(a, -1, b)
+
 /**	\brief The I_StartDigSong function
 
 	\param	musicname	music lump name
diff --git a/src/s_sound.c b/src/s_sound.c
index 2d4de70b1c5b114d0bf9461a0edb0b41759e1273..d7321a618637280413b527067dfb63405996b88e 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -1481,6 +1481,16 @@ void S_SetInternalMusicVolume(INT32 volume)
 	I_SetInternalMusicVolume(min(max(volume, 0), 100));
 }
 
+void S_StopFadingMusic(void)
+{
+	I_StopFadingMusic();
+}
+
+boolean S_FadeMusicFromLevel(UINT8 target_volume, INT16 source_volume, UINT32 ms)
+{
+	return I_FadeMusicFromLevel(target_volume, source_volume, ms);
+}
+
 void S_SetDigMusicVolume(INT32 volume)
 {
 	if (volume < 0 || volume > 31)
diff --git a/src/s_sound.h b/src/s_sound.h
index 19b616ab2487800b6eb90c1e11ff915a5d885b8f..20a0f81e076eb0f921f2481c3063b1c1fdf1cef5 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -173,6 +173,10 @@ const char *S_MusicName(void);
 // Checks if music name exists
 boolean S_MusicExists(const char *mname, boolean checkMIDI, boolean checkDigi);
 
+void S_StopFadingMusic(void);
+boolean S_FadeMusicFromLevel(UINT8 target_volume, INT16 source_volume, UINT32 ms);
+#define S_FadeMusic(a, b) S_FadeMusicFromLevel(a, -1, b)
+
 //
 // Updates music & sounds
 //
diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c
index 5de1831ce8dba13bdc262a8f7613b73bfcf5e99c..485ecec16f24d36e1e407fe3c50ec1e24cc67440 100644
--- a/src/sdl/mixer_sound.c
+++ b/src/sdl/mixer_sound.c
@@ -72,6 +72,8 @@ static UINT32 music_bytes;
 static boolean is_looping;
 static boolean is_fading;
 static UINT8 fading_target;
+static UINT32 fading_steps;
+static INT16 fading_volume_step;
 static INT32 fading_id;
 
 #ifdef HAVE_LIBGME
@@ -82,7 +84,8 @@ static INT32 current_track;
 static void varcleanup(void)
 {
 	loop_point = music_length =\
-	 music_bytes = 0;
+	 music_bytes = fading_target =\
+	 fading_steps = fading_volume_step = 0;
 
 	songpaused = is_looping =\
 	 is_fading = midimode = false;
@@ -103,7 +106,7 @@ void I_StartupSound(void)
 
 	// EE inits audio first so we're following along.
 	if (SDL_WasInit(SDL_INIT_AUDIO) == SDL_INIT_AUDIO)
-		CONS_Printf("SDL Audio already started\n");
+		CONS_Debug(DBG_BASIC, "SDL Audio already started\n");
 	else if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
 	{
 		CONS_Alert(CONS_ERROR, "Error initializing SDL Audio: %s\n", SDL_GetError());
@@ -507,18 +510,28 @@ static void music_loop(void)
 
 static UINT32 music_fade(UINT32 interval, void *param)
 {
-	if (!is_fading || internal_volume == fading_target)
+	if (!is_fading ||
+		internal_volume == fading_target ||
+		fading_steps == 0 ||
+		fading_volume_step == 0)
+	{
+		I_StopFadingMusic();
 		return 0;
-	else if (internal_volume > fading_target) // fading out
+	}
+	else if (
+		(internal_volume > fading_target && internal_volume + fading_volume_step <= fading_target) || // finish fade out
+		(internal_volume < fading_target && internal_volume + fading_volume_step >= fading_target)) // finish fade in
 	{
-		CONS_Printf("Fading out\n");
+		internal_volume = fading_target;
+		Mix_VolumeMusic(get_real_volume(midimode ? midi_volume : music_volume));
+		return 0;
 	}
-	else //if (internval_volume < fading_target) // fading in
+	else
 	{
-		CONS_Printf("Fading in\n");
+		internal_volume += fading_volume_step;
+		Mix_VolumeMusic(get_real_volume(midimode ? midi_volume : music_volume));
+		return interval;
 	}
-
-	return interval;
 }
 
 #ifdef HAVE_LIBGME
@@ -1168,26 +1181,64 @@ void I_SetInternalMusicVolume(UINT8 volume)
 	Mix_VolumeMusic(get_real_volume(midimode ? midi_volume : music_volume));
 }
 
-void I_StopFadingMusic()
+void I_StopFadingMusic(void)
 {
 	if (fading_id)
 		SDL_RemoveTimer(fading_id);
 	is_fading = false;
-	fading_target = fading_id = 0;
+	fading_target = fading_steps = fading_volume_step = fading_id = 0;
 }
 
-void I_FadeMusic(UINT8 fading_target_in)
+boolean I_FadeMusicFromLevel(UINT8 target_volume, INT16 source_volume, UINT32 ms)
 {
+	UINT32 target_steps, ms_per_step;
+	INT16 target_volume_step, volume_delta;
+
+	source_volume = min(source_volume, 100);
+	volume_delta = (INT16)(target_volume - (source_volume < 0 ? internal_volume : source_volume));
+
 	I_StopFadingMusic();
-	if (!is_fading || fading_target != fading_target_in)
+
+	if (!ms)
+	{
+		I_SetInternalMusicVolume(target_volume);
+		return true;
+	}
+	else if (!volume_delta)
+		return true;
+
+	// Round MS to nearest 10
+	// If n - lower > higher - n, then round up
+	ms = (ms - ((ms / 10) * 10) > (((ms / 10) * 10) + 10) - ms) ?
+		(((ms / 10) * 10) + 10) // higher
+		: ((ms / 10) * 10); // lower
+
+	ms_per_step = max(10, ms / abs(volume_delta));
+		// 10ms is the usual minimum timer granularity, but platform-dependent
+	target_steps = ms/ms_per_step;
+	target_volume_step = volume_delta / (INT16)target_steps;
+
+	if (!target_steps || !target_volume_step)
+		I_SetInternalMusicVolume(target_volume);
+	else if (source_volume != target_volume)
 	{
-		fading_id = SDL_AddTimer(1, music_fade, NULL);
+		fading_id = SDL_AddTimer(ms_per_step, music_fade, NULL);
 		if (fading_id)
 		{
 			is_fading = true;
-			fading_target = fading_target_in;
+			fading_target = target_volume;
+			fading_steps = target_steps;
+			fading_volume_step = target_volume_step;
+
+			if (source_volume >= 0 && internal_volume != source_volume)
+				I_SetInternalMusicVolume(source_volume);
 		}
 	}
+
+	CONS_Printf("Target %d> Source %d> MS %d> Steps %d> MSPer %d> VolPer %d> Fading %d\n",
+		target_volume, internal_volume, ms, target_steps, ms_per_step, target_volume_step, is_fading);
+
+	return is_fading;
 }
 
 //