diff --git a/CMakeLists.txt b/CMakeLists.txt
index a7a45d4cd..94ed880db 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1265,10 +1265,19 @@ if(WIN32 AND ENABLE_NSIS AND MAKENSIS_FOUND)
 	add_custom_target(nsis ALL DEPENDS Stratagus-${STRATAGUS_VERSION}${MAKENSIS_SUFFIX})
 endif()
 
+if (WIN32)
+	add_executable(midiplayer WIN32 "src/sound/win32/midiplayer.c")
+	set_target_properties(midiplayer PROPERTIES LINK_FLAGS "/SUBSYSTEM:CONSOLE")
+	set_target_properties(midiplayer PROPERTIES OUTPUT_NAME "stratagus-midiplayer")
+endif()
+
 ########### install files ###############
 
 install(TARGETS stratagus DESTINATION ${GAMEDIR})
 install(TARGETS png2stratagus DESTINATION ${BINDIR})
+if (WIN32)
+	install(TARGETS midiplayer DESTINATION ${GAMEDIR})
+endif()
 
 if(ENABLE_DOC AND DOXYGEN_FOUND)
 	install(FILES doc/stratagus.6 DESTINATION ${MANDIR})
diff --git a/src/include/sound_server.h b/src/include/sound_server.h
index 0aab5e7f8..d86906fe6 100644
--- a/src/include/sound_server.h
+++ b/src/include/sound_server.h
@@ -88,8 +88,6 @@ extern bool IsEffectsEnabled();
 /// Set the music finished callback
 void SetMusicFinishedCallback(void (*callback)());
 /// Play a music file
-extern int PlayMusic(Mix_Music *sample);
-/// Play a music file
 extern int PlayMusic(const std::string &file);
 /// Stop music playing
 extern void StopMusic();
diff --git a/src/sound/sound_server.cpp b/src/sound/sound_server.cpp
index 409acfe4a..55ca83a9e 100644
--- a/src/sound/sound_server.cpp
+++ b/src/sound/sound_server.cpp
@@ -60,6 +60,119 @@ static bool EffectsEnabled = true;
 static double VolumeScale = 1.0;
 static int MusicVolume = 0;
 
+static void (*MusicFinishedCallback)();
+
+#ifdef USE_WIN32
+static volatile bool threadWaiting = false;
+static std::string externalFile;
+static HANDLE hWaitingThread;
+static PROCESS_INFORMATION pi;
+
+DWORD WINAPI MyThreadFunction(LPVOID lpParam) {
+	WaitForSingleObject(pi.hProcess, INFINITE);
+	CloseHandle(pi.hProcess);
+	CloseHandle(pi.hThread);
+	if (threadWaiting) {
+		MusicFinishedCallback();
+		threadWaiting = false;
+	}
+	return 0;
+}
+
+static void killPlayingProcess() {
+	if (threadWaiting) {
+		threadWaiting = false;
+		TerminateProcess(pi.hProcess, 0);
+		WaitForSingleObject(hWaitingThread, INFINITE);
+		threadWaiting = false;
+	} else {
+		TerminateProcess(pi.hProcess, 0);
+	}
+}
+
+static bool External_Play(const std::string &file) {
+	if (threadWaiting && file == externalFile) {
+		return true;
+	}
+
+	static std::string midi = ".mid";
+	auto it = midi.begin();
+	if (file.size() > midi.size() &&
+			std::all_of(std::next(file.begin(), file.size() - midi.size()), file.end(), [&it](const char & c) { return c == ::tolower(*(it++)); })) {
+		// midi file, use external player, since windows vista+ does not allow midi volume control independent of process volume
+		std::string full_filename = LibraryFileName(file.c_str());
+
+		static const char* midiplayerExe = "stratagus-midiplayer.exe";
+		static const int midiplayerExeSz = strlen(midiplayerExe);
+		
+		// set up a job so our children die with us
+		static bool firstRun = true;
+		static HANDLE hJob;
+		if (firstRun) {
+			hJob = CreateJobObject(NULL, NULL);
+			JOBOBJECT_BASIC_LIMIT_INFORMATION limitInfo;
+			ZeroMemory(&limitInfo, sizeof(limitInfo));
+			limitInfo.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+			SetInformationJobObject(hJob, JobObjectBasicLimitInformation, &limitInfo, sizeof(limitInfo));
+			AssignProcessToJobObject(hJob, GetCurrentProcess());
+			firstRun = false;
+		}
+
+		int sz = midiplayerExeSz + 2 + 3 + 2 + full_filename.size() + 1; // exe + 2 spaces + 3 volume + 2 quotes + filename + nullbyte
+		char *cmdline = new char[sz];
+		snprintf(cmdline, sz, "%s %3d \"%s\"", midiplayerExe, std::min(MusicVolume, 255), full_filename.c_str());
+		DebugPrint("Using external command to play midi on windows: %s\n" _C_ cmdline);
+		killPlayingProcess();
+		STARTUPINFO si;
+		ZeroMemory(&si, sizeof(si));
+		si.cb = sizeof(si);
+		ZeroMemory(&pi, sizeof(pi));
+		bool result = true;
+		if (CreateProcess(NULL, cmdline, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
+			AssignProcessToJobObject(hJob, pi.hProcess);
+			externalFile = file;
+			hWaitingThread = CreateThread(NULL, 0, MyThreadFunction, NULL, 0, NULL);
+			threadWaiting = true;
+		} else {
+			result = false;
+			DebugPrint("CreateProcess failed (%d).\n" _C_ GetLastError());
+		}
+		delete[] cmdline;
+		return result;
+	}
+	killPlayingProcess();
+	return false;
+}
+
+static bool External_IsPlaying() {
+	return threadWaiting;
+}
+
+static bool External_Stop() {
+	if (External_IsPlaying()) {
+		killPlayingProcess();
+		return true;
+	}
+	return false;
+}
+
+static bool External_Volume(int volume, int oldVolume) {
+	if (External_IsPlaying() && externalFile.size() > 0) {
+		if (oldVolume != volume) {
+			External_Stop();
+			External_Play(externalFile);
+		}
+		return true;
+	}
+	return false;
+}
+#else
+#define External_Play(file) false
+#define External_IsPlaying() false
+#define External_Stop()
+#define External_Volume(volume) false
+#endif
+
 extern volatile bool MusicFinished;
 
 /// Channels for sound effects and unit speech
@@ -334,30 +447,10 @@ bool IsEffectsEnabled()
 */
 void SetMusicFinishedCallback(void (*callback)())
 {
+	MusicFinishedCallback = callback;
 	Mix_HookMusicFinished(callback);
 }
 
-/**
-**  Play a music file.
-**
-**  @param sample  Music sample.
-**
-**  @return        0 if music is playing, -1 if not.
-*/
-int PlayMusic(Mix_Music *sample)
-{
-	if (sample) {
-		Mix_VolumeMusic(MusicVolume);
-		MusicFinished = false;
-		Mix_PlayMusic(sample, 0);
-		Mix_VolumeMusic(MusicVolume / 4.0);
-		return 0;
-	} else {
-		DebugPrint("Could not play sample\n");
-		return -1;
-	}
-}
-
 /**
 **  Play a music file.
 **
@@ -371,8 +464,13 @@ int PlayMusic(const std::string &file)
 		return -1;
 	}
 	DebugPrint("play music %s\n" _C_ file.c_str());
-	Mix_Music *music = LoadMusic(file);
 
+	if (External_Play(file)) {
+		MusicFinished = false;
+		return 0;
+	}
+
+	Mix_Music *music = LoadMusic(file);
 	if (music) {
 		MusicFinished = false;
 		Mix_FadeInMusic(music, 0, 200);
@@ -388,6 +486,9 @@ int PlayMusic(const std::string &file)
 */
 void StopMusic()
 {
+	if (External_Stop()) {
+		return;
+	}
 	Mix_FadeOutMusic(200);
 }
 
@@ -400,7 +501,11 @@ void SetMusicVolume(int volume)
 {
 	// due to left-right separation, sound effect volume is effectively halfed,
 	// so we adjust the music
+	int oldVolume = MusicVolume;
 	MusicVolume = volume;
+	if (External_Volume(volume, oldVolume)) {
+		return;
+	}
 	Mix_VolumeMusic(volume / 4.0);
 }
 
diff --git a/src/sound/win32/midiplayer.c b/src/sound/win32/midiplayer.c
new file mode 100644
index 000000000..3aecb17a2
--- /dev/null
+++ b/src/sound/win32/midiplayer.c
@@ -0,0 +1,411 @@
+/*
+ * mididemo.c
+ *
+ *  Created on: Dec 21, 2011
+ *      Author: David J. Rager
+ *       Email: djrager@fourthwoods.com
+ *
+ * This code is hereby released into the public domain per the Creative Commons
+ * Public Domain dedication.
+ *
+ * http://http://creativecommons.org/publicdomain/zero/1.0/
+ */
+#pragma comment(lib, "winmm.lib")
+
+#include <windows.h>
+#include <mmsystem.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define MAX_BUFFER_SIZE (512 * 12)
+HANDLE event;
+
+#pragma pack(push, 1)
+
+struct _mid_header {
+	unsigned int	id;		// identifier "MThd"
+	unsigned int	size;	// always 6 in big-endian format
+	unsigned short	format;	// big-endian format
+	unsigned short  tracks;	// number of tracks, big-endian
+	unsigned short	ticks;	// number of ticks per quarter note, big-endian
+};
+
+struct _mid_track {
+	unsigned int	id;		// identifier "MTrk"
+	unsigned int	length;	// track length, big-endian
+};
+
+#pragma pack(pop)
+
+struct trk {
+	struct _mid_track* track;
+	unsigned char* buf;
+	unsigned char last_event;
+	unsigned int absolute_time;
+};
+
+struct evt {
+	unsigned int absolute_time;
+	unsigned char* data;
+	unsigned char event;
+};
+
+static unsigned char* load_file(const unsigned char* filename, unsigned int* len)
+{
+	unsigned char* buf;
+	unsigned int ret;
+	FILE* f = fopen((char*)filename, "rb");
+	if(f == NULL)
+		return 0;
+
+	fseek(f, 0, SEEK_END);
+	*len = ftell(f);
+	fseek(f, 0, SEEK_SET);
+
+	buf = (unsigned char*)malloc(*len);
+	if(buf == 0)
+	{
+		fclose(f);
+		return 0;
+	}
+
+	ret = fread(buf, 1, *len, f);
+	fclose(f);
+
+	if(ret != *len)
+	{
+		free(buf);
+		return 0;
+	}
+
+	return buf;
+}
+
+static unsigned long read_var_long(unsigned char* buf, unsigned int* bytesread)
+{
+	unsigned long var = 0;
+	unsigned char c;
+
+	*bytesread = 0;
+
+	do
+	{
+		c = buf[(*bytesread)++];
+		var = (var << 7) + (c & 0x7f);
+	}
+	while(c & 0x80);
+
+	return var;
+}
+
+static unsigned short swap_bytes_short(unsigned short in)
+{
+	return ((in << 8) | (in >> 8));
+}
+
+static unsigned long swap_bytes_long(unsigned long in)
+{
+	unsigned short *p;
+	p = (unsigned short*)&in;
+
+	return (  (((unsigned long)swap_bytes_short(p[0])) << 16) |
+				(unsigned long)swap_bytes_short(p[1]));
+}
+
+static struct evt get_next_event(const struct trk* track)
+{
+	unsigned char* buf;
+	struct evt e;
+	unsigned int bytesread;
+	unsigned int time;
+
+	buf = track->buf;
+
+	time = read_var_long(buf, &bytesread);
+	buf += bytesread;
+
+	e.absolute_time = track->absolute_time + time;
+	e.data = buf;
+	e.event = *e.data;
+
+	return e;
+}
+
+static int is_track_end(const struct evt* e)
+{
+	if(e->event == 0xff) // meta-event?
+		if(*(e->data + 1) == 0x2f) // track end?
+			return 1;
+
+	return 0;
+}
+
+static void CALLBACK example9_callback(HMIDIOUT out, UINT msg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
+{
+    switch (msg)
+    {
+	case MOM_DONE:
+ 		SetEvent(event);
+		break;
+	case MOM_POSITIONCB:
+	case MOM_OPEN:
+	case MOM_CLOSE:
+		break;
+    }
+}
+
+static unsigned int get_buffer(struct trk* tracks, unsigned int ntracks, unsigned int* out, unsigned int* outlen)
+{
+	MIDIEVENT e, *p;
+	unsigned int streamlen = 0;
+	unsigned int i;
+	static unsigned int current_time = 0;
+
+	if(tracks == NULL || out == NULL || outlen == NULL)
+		return 0;
+
+	*outlen = 0;
+
+	while(TRUE)
+	{
+		unsigned int time = (unsigned int)-1;
+		unsigned int idx = -1;
+		struct evt evt;
+		unsigned char c;
+
+		if(((streamlen + 3) * sizeof(unsigned int)) >= MAX_BUFFER_SIZE)
+			break;
+
+		// get the next event
+		for(i = 0; i < ntracks; i++)
+		{
+			evt = get_next_event(&tracks[i]);
+			if(!(is_track_end(&evt)) && (evt.absolute_time < time))
+			{
+				time = evt.absolute_time;
+				idx = i;
+			}
+		}
+
+		// if idx == -1 then all the tracks have been read up to the end of track mark
+		if(idx == -1)
+			break; // we're done
+
+		e.dwStreamID = 0; // always 0
+
+		evt = get_next_event(&tracks[idx]);
+
+		tracks[idx].absolute_time = evt.absolute_time;
+		e.dwDeltaTime = tracks[idx].absolute_time - current_time;
+		current_time = tracks[idx].absolute_time;
+
+		if(!(evt.event & 0x80)) // running mode
+		{
+			unsigned char last = tracks[idx].last_event;
+			c = *evt.data++; // get the first data byte
+			e.dwEvent = ((unsigned long)MEVT_SHORTMSG << 24) |
+						((unsigned long)last) |
+						((unsigned long)c << 8);
+			if(!((last & 0xf0) == 0xc0 || (last & 0xf0) == 0xd0))
+			{
+				c = *evt.data++; // get the second data byte
+				e.dwEvent |= ((unsigned long)c << 16);
+			}
+
+			p = (MIDIEVENT*)&out[streamlen];
+			*p = e;
+
+			streamlen += 3;
+
+			tracks[idx].buf = evt.data;
+		}
+		else if(evt.event == 0xff) // meta-event
+		{
+			evt.data++; // skip the event byte
+			unsigned char meta = *evt.data++; // read the meta-event byte
+			unsigned int len;
+
+			switch(meta)
+			{
+			case 0x51: // only care about tempo events
+				{
+					unsigned char a, b, c;
+					len = *evt.data++; // get the length byte, should be 3
+					a = *evt.data++;
+					b = *evt.data++;
+					c = *evt.data++;
+
+					e.dwEvent = ((unsigned long)MEVT_TEMPO << 24) |
+								((unsigned long)a << 16) |
+								((unsigned long)b << 8) |
+								((unsigned long)c << 0);
+
+					p = (MIDIEVENT*)&out[streamlen];
+					*p = e;
+
+					streamlen += 3;
+				}
+				break;
+			default: // skip all other meta events
+				len = *evt.data++; // get the length byte
+				evt.data += len;
+				break;
+			}
+
+			tracks[idx].buf = evt.data;
+		}
+		else if((evt.event & 0xf0) != 0xf0) // normal command
+		{
+			tracks[idx].last_event = evt.event;
+			evt.data++; // skip the event byte
+			c = *evt.data++; // get the first data byte
+			e.dwEvent = ((unsigned long)MEVT_SHORTMSG << 24) |
+						((unsigned long)evt.event << 0) |
+						((unsigned long)c << 8);
+			if(!((evt.event & 0xf0) == 0xc0 || (evt.event & 0xf0) == 0xd0))
+			{
+				c = *evt.data++; // get the second data byte
+				e.dwEvent |= ((unsigned long)c << 16);
+			}
+
+			p = (MIDIEVENT*)&out[streamlen];
+			*p = e;
+
+			streamlen += 3;
+
+			tracks[idx].buf = evt.data;
+		}
+
+	}
+
+	*outlen = streamlen * sizeof(unsigned int);
+
+	return 1;
+}
+
+unsigned int example9(char* filename, int volume)
+{
+	unsigned char* midibuf = NULL;
+	unsigned int midilen = 0;
+
+	struct _mid_header* hdr = NULL;
+
+	unsigned int i;
+
+	unsigned short ntracks = 0;
+	struct trk* tracks = NULL;
+
+	unsigned int streambufsize = MAX_BUFFER_SIZE;
+	unsigned int* streambuf = NULL;
+	unsigned int streamlen = 0;
+
+	HMIDISTRM out;
+	MIDIPROPTIMEDIV prop;
+	MIDIHDR mhdr;
+	unsigned int device = 0;
+
+	midibuf = load_file((unsigned char*)filename, &midilen);
+	if(midibuf == NULL)
+	{
+		printf("could not open %s\n", filename);
+		return 0;
+	}
+
+	hdr = (struct _mid_header*)midibuf;
+	midibuf += sizeof(struct _mid_header);
+	ntracks = swap_bytes_short(hdr->tracks);
+
+	tracks = (struct trk*)malloc(ntracks * sizeof(struct trk));
+	if(tracks == NULL)
+		goto error1;
+
+	for(i = 0; i < ntracks; i++)
+	{
+		tracks[i].track = (struct _mid_track*)midibuf;
+		tracks[i].buf = midibuf + sizeof(struct _mid_track);
+		tracks[i].absolute_time = 0;
+		tracks[i].last_event = 0;
+
+		midibuf += sizeof(struct _mid_track) + swap_bytes_long(tracks[i].track->length);
+	}
+
+	streambuf = (unsigned int*)malloc(sizeof(unsigned int) * streambufsize);
+	if(streambuf == NULL)
+		goto error2;
+
+	memset(streambuf, 0, sizeof(unsigned int) * streambufsize);
+
+    if ((event = CreateEvent(0, FALSE, FALSE, 0)) == NULL)
+    	goto error3;
+
+	if (midiStreamOpen(&out, &device, 1, (DWORD)example9_callback, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
+		goto error4;
+
+	prop.cbStruct = sizeof(MIDIPROPTIMEDIV);
+	prop.dwTimeDiv = swap_bytes_short(hdr->ticks);
+	if(midiStreamProperty(out, (LPBYTE)&prop, MIDIPROP_SET|MIDIPROP_TIMEDIV) != MMSYSERR_NOERROR)
+		goto error5;
+
+	mhdr.lpData = (char*)streambuf;
+	mhdr.dwBufferLength = mhdr.dwBytesRecorded = streambufsize;
+	mhdr.dwFlags = 0;
+
+	if(midiOutPrepareHeader((HMIDIOUT)out, &mhdr, sizeof(MIDIHDR)) != MMSYSERR_NOERROR)
+		goto error5;
+
+	if(midiStreamRestart(out) != MMSYSERR_NOERROR)
+		goto error6;
+
+	if (midiOutSetVolume((HMIDIOUT)out, (DWORD)((volume & 0xFF) << 4)) != MMSYSERR_NOERROR)
+		goto error6;
+
+	printf("buffering...\n");
+	get_buffer(tracks, ntracks, streambuf, &streamlen);
+	while(streamlen > 0)
+	{
+		mhdr.dwBytesRecorded = streamlen;
+
+		if(midiStreamOut(out, &mhdr, sizeof(MIDIHDR)) != MMSYSERR_NOERROR)
+			goto error7;
+
+		WaitForSingleObject(event, INFINITE);
+
+		printf("buffering...\n");
+		get_buffer(tracks, ntracks, streambuf, &streamlen);
+	}
+	printf("done.\n");
+
+error7:
+	midiOutReset((HMIDIOUT)out);
+
+error6:
+	midiOutUnprepareHeader((HMIDIOUT)out, &mhdr, sizeof(MIDIHDR));
+
+error5:
+	midiStreamClose(out);
+
+error4:
+	CloseHandle(event);
+
+error3:
+	free(streambuf);
+
+error2:
+	free(tracks);
+
+error1:
+	free(hdr);
+
+	return(0);
+}
+
+int main(int argc, char* argv[])
+{
+	if(argc != 3) {
+		return printf("Usage: %s <volume 0-255> <filename.mid>\n", argv[0]);
+	}
+	
+	example9(argv[2], atoi(argv[1]));
+
+	return 0;
+}
diff --git a/src/video/movie.cpp b/src/video/movie.cpp
index bf89def97..f1f849108 100644
--- a/src/video/movie.cpp
+++ b/src/video/movie.cpp
@@ -361,10 +361,7 @@ int PlayMovie(const std::string &name)
 	}
 
 	StopMusic();
-	Mix_Music *sample = LoadMusic(filename);
-	if (sample) {
-		PlayMusic(sample);
-	}
+	PlayMusic(filename);
 
 	EventCallback callbacks;
 
diff --git a/src/win32/stratagus.nsi b/src/win32/stratagus.nsi
index 53572fcf4..2e1355eb2 100644
--- a/src/win32/stratagus.nsi
+++ b/src/win32/stratagus.nsi
@@ -215,6 +215,7 @@ Section "${NAME}"
 
 	SetOutPath $INSTDIR
 	File "${EXE}"
+	File midiplayer.exe
 	File *.dll
 	WriteRegStr HKLM "${REGKEY}" "DisplayName" "${NAME}"
 	WriteRegStr HKLM "${REGKEY}" "UninstallString" "$\"$INSTDIR\${UNINSTALL}$\""