diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj
index 818e1d7bdb..1d038b37b2 100644
--- a/projects/openttd_vs100.vcxproj
+++ b/projects/openttd_vs100.vcxproj
@@ -216,7 +216,7 @@
Size
true
..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include;%(AdditionalIncludeDirectories)
- WIN32;NDEBUG;_CONSOLE;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;WITH_ASSERT;%(PreprocessorDefinitions)
+ WIN32;NDEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;WITH_ASSERT;%(PreprocessorDefinitions)
true
Sync
MultiThreaded
@@ -274,7 +274,7 @@
/MP %(AdditionalOptions)
Disabled
..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include;%(AdditionalIncludeDirectories)
- WIN32;_DEBUG;_CONSOLE;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;%(PreprocessorDefinitions)
+ WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;%(PreprocessorDefinitions)
EnableFastChecks
MultiThreadedDebug
diff --git a/projects/openttd_vs100.vcxproj.in b/projects/openttd_vs100.vcxproj.in
index 76db416679..aab1340756 100644
--- a/projects/openttd_vs100.vcxproj.in
+++ b/projects/openttd_vs100.vcxproj.in
@@ -216,7 +216,7 @@
Size
true
..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include;%(AdditionalIncludeDirectories)
- WIN32;NDEBUG;_CONSOLE;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;WITH_ASSERT;%(PreprocessorDefinitions)
+ WIN32;NDEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;WITH_ASSERT;%(PreprocessorDefinitions)
true
Sync
MultiThreaded
@@ -274,7 +274,7 @@
/MP %(AdditionalOptions)
Disabled
..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include;%(AdditionalIncludeDirectories)
- WIN32;_DEBUG;_CONSOLE;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;%(PreprocessorDefinitions)
+ WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;%(PreprocessorDefinitions)
EnableFastChecks
MultiThreadedDebug
diff --git a/projects/openttd_vs140.vcxproj b/projects/openttd_vs140.vcxproj
index cd0cc55af8..5c33544067 100644
--- a/projects/openttd_vs140.vcxproj
+++ b/projects/openttd_vs140.vcxproj
@@ -228,7 +228,7 @@
Size
true
..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include;%(AdditionalIncludeDirectories)
- WIN32;NDEBUG;_CONSOLE;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;WITH_ASSERT;%(PreprocessorDefinitions)
+ WIN32;NDEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;WITH_ASSERT;%(PreprocessorDefinitions)
true
Sync
MultiThreaded
@@ -291,7 +291,7 @@
Disabled
true
..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include;%(AdditionalIncludeDirectories)
- WIN32;_DEBUG;_CONSOLE;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;%(PreprocessorDefinitions)
+ WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;%(PreprocessorDefinitions)
EnableFastChecks
MultiThreadedDebug
diff --git a/projects/openttd_vs140.vcxproj.in b/projects/openttd_vs140.vcxproj.in
index 93debc8672..1dd1e35219 100644
--- a/projects/openttd_vs140.vcxproj.in
+++ b/projects/openttd_vs140.vcxproj.in
@@ -228,7 +228,7 @@
Size
true
..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include;%(AdditionalIncludeDirectories)
- WIN32;NDEBUG;_CONSOLE;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;WITH_ASSERT;%(PreprocessorDefinitions)
+ WIN32;NDEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;WITH_ASSERT;%(PreprocessorDefinitions)
true
Sync
MultiThreaded
@@ -291,7 +291,7 @@
Disabled
true
..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include;%(AdditionalIncludeDirectories)
- WIN32;_DEBUG;_CONSOLE;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;%(PreprocessorDefinitions)
+ WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;%(PreprocessorDefinitions)
EnableFastChecks
MultiThreadedDebug
diff --git a/projects/openttd_vs141.vcxproj b/projects/openttd_vs141.vcxproj
index a6d422be18..7ac5fb5590 100644
--- a/projects/openttd_vs141.vcxproj
+++ b/projects/openttd_vs141.vcxproj
@@ -228,7 +228,7 @@
Size
true
..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include;%(AdditionalIncludeDirectories)
- WIN32;NDEBUG;_CONSOLE;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;WITH_ASSERT;%(PreprocessorDefinitions)
+ WIN32;NDEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;WITH_ASSERT;%(PreprocessorDefinitions)
true
Sync
MultiThreaded
@@ -291,7 +291,7 @@
Disabled
true
..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include;%(AdditionalIncludeDirectories)
- WIN32;_DEBUG;_CONSOLE;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;%(PreprocessorDefinitions)
+ WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;%(PreprocessorDefinitions)
EnableFastChecks
MultiThreadedDebug
diff --git a/projects/openttd_vs141.vcxproj.in b/projects/openttd_vs141.vcxproj.in
index dc6dcd17f9..c470ffcdde 100644
--- a/projects/openttd_vs141.vcxproj.in
+++ b/projects/openttd_vs141.vcxproj.in
@@ -228,7 +228,7 @@
Size
true
..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include;%(AdditionalIncludeDirectories)
- WIN32;NDEBUG;_CONSOLE;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;WITH_ASSERT;%(PreprocessorDefinitions)
+ WIN32;NDEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;WITH_ASSERT;%(PreprocessorDefinitions)
true
Sync
MultiThreaded
@@ -291,7 +291,7 @@
Disabled
true
..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include;%(AdditionalIncludeDirectories)
- WIN32;_DEBUG;_CONSOLE;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;%(PreprocessorDefinitions)
+ WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_XAUDIO2;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR="OpenTTD";_SQ64;%(PreprocessorDefinitions)
EnableFastChecks
MultiThreadedDebug
diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj
index e81ce76562..1ee5d91aa2 100644
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -261,7 +261,7 @@
FavorSizeOrSpeed="2"
OmitFramePointers="true"
AdditionalIncludeDirectories="..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include"
- PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64;WITH_ASSERT"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64;WITH_ASSERT"
StringPooling="true"
ExceptionHandling="1"
RuntimeLibrary="0"
@@ -368,7 +368,7 @@
AdditionalOptions="/MP"
Optimization="0"
AdditionalIncludeDirectories="..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include"
- PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64"
BasicRuntimeChecks="3"
RuntimeLibrary="1"
UsePrecompiledHeader="0"
diff --git a/projects/openttd_vs80.vcproj.in b/projects/openttd_vs80.vcproj.in
index 00c330d22f..8b187ad857 100644
--- a/projects/openttd_vs80.vcproj.in
+++ b/projects/openttd_vs80.vcproj.in
@@ -261,7 +261,7 @@
FavorSizeOrSpeed="2"
OmitFramePointers="true"
AdditionalIncludeDirectories="..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include"
- PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64;WITH_ASSERT"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64;WITH_ASSERT"
StringPooling="true"
ExceptionHandling="1"
RuntimeLibrary="0"
@@ -368,7 +368,7 @@
AdditionalOptions="/MP"
Optimization="0"
AdditionalIncludeDirectories="..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include"
- PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64"
BasicRuntimeChecks="3"
RuntimeLibrary="1"
UsePrecompiledHeader="0"
diff --git a/projects/openttd_vs90.vcproj b/projects/openttd_vs90.vcproj
index 51416e5e3c..9c6d2a042b 100644
--- a/projects/openttd_vs90.vcproj
+++ b/projects/openttd_vs90.vcproj
@@ -259,7 +259,7 @@
FavorSizeOrSpeed="2"
OmitFramePointers="true"
AdditionalIncludeDirectories="..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include"
- PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64;WITH_ASSERT"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64;WITH_ASSERT"
StringPooling="true"
ExceptionHandling="1"
RuntimeLibrary="0"
@@ -365,7 +365,7 @@
AdditionalOptions="/MP"
Optimization="0"
AdditionalIncludeDirectories="..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include"
- PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64"
BasicRuntimeChecks="3"
RuntimeLibrary="1"
UsePrecompiledHeader="0"
diff --git a/projects/openttd_vs90.vcproj.in b/projects/openttd_vs90.vcproj.in
index 0ebf4fcbed..39be14875f 100644
--- a/projects/openttd_vs90.vcproj.in
+++ b/projects/openttd_vs90.vcproj.in
@@ -259,7 +259,7 @@
FavorSizeOrSpeed="2"
OmitFramePointers="true"
AdditionalIncludeDirectories="..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include"
- PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64;WITH_ASSERT"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64;WITH_ASSERT"
StringPooling="true"
ExceptionHandling="1"
RuntimeLibrary="0"
@@ -365,7 +365,7 @@
AdditionalOptions="/MP"
Optimization="0"
AdditionalIncludeDirectories="..\objs\langs;..\objs\settings;..\src\3rdparty\squirrel\include"
- PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;WIN32_ENABLE_DIRECTMUSIC_SUPPORT;WITH_SSE;WITH_ZLIB;WITH_LZO;WITH_LZMA;LZMA_API_STATIC;WITH_PNG;WITH_FREETYPE;WITH_ICU_SORT;WITH_ICU_LAYOUT;U_STATIC_IMPLEMENTATION;ENABLE_NETWORK;WITH_PERSONAL_DIR;PERSONAL_DIR=\"OpenTTD\";_SQ64"
BasicRuntimeChecks="3"
RuntimeLibrary="1"
UsePrecompiledHeader="0"
diff --git a/src/debug.cpp b/src/debug.cpp
index d4d547c884..f8f3f0ad56 100644
--- a/src/debug.cpp
+++ b/src/debug.cpp
@@ -17,6 +17,10 @@
#include "fileio_func.h"
#include "settings_type.h"
+#if defined(WIN32) || defined(WIN64)
+#include "os/windows/win32.h"
+#endif
+
#include
#if defined(ENABLE_NETWORK)
@@ -136,7 +140,9 @@ static void debug_print(const char *dbg, const char *buf)
char buffer[512];
seprintf(buffer, lastof(buffer), "%sdbg: [%s] %s\n", GetLogPrefix(), dbg, buf);
#if defined(WIN32) || defined(WIN64)
- _fputts(OTTD2FS(buffer, true), stderr);
+ TCHAR system_buf[512];
+ convert_to_fs(buffer, system_buf, lengthof(system_buf), true);
+ _fputts(system_buf, stderr);
#else
fputs(buffer, stderr);
#endif
diff --git a/src/fileio_func.h b/src/fileio_func.h
index 443460b2d3..2dc9afed1d 100644
--- a/src/fileio_func.h
+++ b/src/fileio_func.h
@@ -149,4 +149,17 @@ static inline DIR *ttd_opendir(const char *path)
return opendir(OTTD2FS(path));
}
+
+/** Auto-close a file upon scope exit. */
+class FileCloser {
+ FILE *f;
+
+public:
+ FileCloser(FILE *_f) : f(_f) {}
+ ~FileCloser()
+ {
+ fclose(f);
+ }
+};
+
#endif /* FILEIO_FUNC_H */
diff --git a/src/music/dmusic.cpp b/src/music/dmusic.cpp
index 471fb3f366..934bb9fe26 100644
--- a/src/music/dmusic.cpp
+++ b/src/music/dmusic.cpp
@@ -19,86 +19,1095 @@
#include "../debug.h"
#include "../os/windows/win32.h"
#include "../core/mem_func.hpp"
+#include "../thread/thread.h"
+#include "../fileio_func.h"
#include "dmusic.h"
+#include "midifile.hpp"
+#include "midi.h"
#include
-#undef FACILITY_DIRECTMUSIC // Needed for newer Windows SDK version.
#include
-#include
#include
-#include
#include "../safeguards.h"
-static FMusicDriver_DMusic iFMusicDriver_DMusic;
-
-/** the direct music object manages buffers and ports */
-static IDirectMusic *music = NULL;
-
-/** the performance object controls manipulation of the segments */
-static IDirectMusicPerformance *performance = NULL;
-
-/** the loader object can load many types of DMusic related files */
-static IDirectMusicLoader *loader = NULL;
-
-/** the segment object is where the MIDI data is stored for playback */
-static IDirectMusicSegment *segment = NULL;
-
-static bool seeking = false;
+#pragma comment(lib, "ole32.lib")
-#define M(x) x "\0"
-static const char ole_files[] =
- M("ole32.dll")
- M("CoCreateInstance")
- M("CoInitialize")
- M("CoUninitialize")
- M("")
-;
-#undef M
+static const int MS_TO_REFTIME = 1000 * 10; ///< DirectMusic time base is 100 ns.
+static const int MIDITIME_TO_REFTIME = 10; ///< Time base of the midi file reader is 1 us.
-struct ProcPtrs {
- unsigned long (WINAPI * CoCreateInstance)(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv);
- HRESULT (WINAPI * CoInitialize)(LPVOID pvReserved);
- void (WINAPI * CoUninitialize)();
+
+#define FOURCC_INFO mmioFOURCC('I','N','F','O')
+#define FOURCC_fmt mmioFOURCC('f','m','t',' ')
+#define FOURCC_data mmioFOURCC('d','a','t','a')
+
+/** A DLS file. */
+struct DLSFile {
+ /** An instrument region maps a note range to wave data. */
+ struct DLSRegion {
+ RGNHEADER hdr;
+ WAVELINK wave;
+ WSMPL wave_sample;
+
+ std::vector wave_loops;
+ std::vector articulators;
+ };
+
+ /** Instrument definition read from a DLS file. */
+ struct DLSInstrument {
+ INSTHEADER hdr;
+
+ std::vector articulators;
+ std::vector regions;
+ };
+
+ /** Wave data definition from a DLS file. */
+ struct DLSWave {
+ long file_offset;
+
+ PCMWAVEFORMAT fmt;
+ std::vector data;
+
+ WSMPL wave_sample;
+ std::vector wave_loops;
+
+ bool operator ==(long offset) const {
+ return this->file_offset == offset;
+ }
+ };
+
+ std::vector instruments;
+ std::vector pool_cues;
+ std::vector waves;
+
+ /** Try loading a DLS file into memory. */
+ bool LoadFile(const TCHAR *file);
+
+private:
+ /** Load an articulation structure from a DLS file. */
+ bool ReadDLSArticulation(FILE *f, DWORD list_length, std::vector &out);
+ /** Load a list of regions from a DLS file. */
+ bool ReadDLSRegionList(FILE *f, DWORD list_length, DLSInstrument &instrument);
+ /** Load a single region from a DLS file. */
+ bool ReadDLSRegion(FILE *f, DWORD list_length, std::vector &out);
+ /** Load a list of instruments from a DLS file. */
+ bool ReadDLSInstrumentList(FILE *f, DWORD list_length);
+ /** Load a single instrument from a DLS file. */
+ bool ReadDLSInstrument(FILE *f, DWORD list_length);
+ /** Load a list of waves from a DLS file. */
+ bool ReadDLSWaveList(FILE *f, DWORD list_length);
+ /** Load a single wave from a DLS file. */
+ bool ReadDLSWave(FILE *f, DWORD list_length, long offset);
};
-static ProcPtrs proc;
+#pragma pack(2)
+/** A RIFF chunk header. */
+struct ChunkHeader {
+ FOURCC type; ///< Chunk type.
+ DWORD length; ///< Length of the chunk, not including the chunk header itself.
+};
+
+/** Buffer format for a DLS wave download. */
+struct WAVE_DOWNLOAD {
+ DMUS_DOWNLOADINFO dlInfo;
+ ULONG ulOffsetTable[2];
+ DMUS_WAVE dmWave;
+ DMUS_WAVEDATA dmWaveData;
+};
+#pragma pack()
+
+struct PlaybackSegment {
+ uint32 start, end;
+ size_t start_block;
+ bool loop;
+};
+
+static struct {
+ bool shutdown; ///< flag to indicate playback thread shutdown
+ bool playing; ///< flag indicating that playback is active
+ bool do_start; ///< flag for starting playback of next_file at next opportunity
+ bool do_stop; ///< flag for stopping playback at next opportunity
+
+ int preload_time; ///< preload time for music blocks.
+ byte new_volume; ///< volume setting to change to
+
+ MidiFile next_file; ///< upcoming file to play
+ PlaybackSegment next_segment; ///< segment info for upcoming file
+} _playback;
+
+/** Handle to our worker thread. */
+static ThreadObject *_dmusic_thread = NULL;
+/** Event to signal the thread that it should look at a state change. */
+static HANDLE _thread_event = NULL;
+/** Lock access to playback data that is not thread-safe. */
+static ThreadMutex *_thread_mutex = NULL;
+
+/** The direct music object manages buffers and ports. */
+static IDirectMusic *_music = NULL;
+/** The port object lets us send MIDI data to the synthesizer. */
+static IDirectMusicPort *_port = NULL;
+/** The buffer object collects the data to sent. */
+static IDirectMusicBuffer *_buffer = NULL;
+/** List of downloaded DLS instruments. */
+static std::vector _dls_downloads;
+
+
+static FMusicDriver_DMusic iFMusicDriver_DMusic;
+
+
+bool DLSFile::ReadDLSArticulation(FILE *f, DWORD list_length, std::vector &out)
+{
+ while (list_length > 0) {
+ ChunkHeader chunk;
+ if (fread(&chunk, sizeof(chunk), 1, f) != 1) return false;
+ list_length -= chunk.length + sizeof(chunk);
+
+ if (chunk.type == FOURCC_ART1) {
+ CONNECTIONLIST conns;
+ if (fread(&conns, sizeof(conns), 1, f) != 1) return false;
+ fseek(f, conns.cbSize - sizeof(conns), SEEK_CUR);
+
+ /* Read all defined articulations. */
+ for (ULONG i = 0; i < conns.cConnections; i++) {
+ CONNECTION con;
+ if (fread(&con, sizeof(con), 1, f) != 1) return false;
+ out.push_back(con);
+ }
+ } else {
+ fseek(f, chunk.length, SEEK_CUR);
+ }
+ }
+
+ return true;
+}
+
+bool DLSFile::ReadDLSRegion(FILE *f, DWORD list_length, std::vector &out)
+{
+ out.push_back(DLSRegion());
+ DLSRegion ®ion = out.back();
+
+ /* Set default values. */
+ region.wave_sample.cbSize = 0;
+
+ while (list_length > 0) {
+ ChunkHeader chunk;
+ if (fread(&chunk, sizeof(chunk), 1, f) != 1) return false;
+ list_length -= chunk.length + sizeof(chunk);
+
+ if (chunk.type == FOURCC_LIST) {
+ /* Unwrap list header. */
+ if (fread(&chunk.type, sizeof(chunk.type), 1, f) != 1) return false;
+ chunk.length -= sizeof(chunk.type);
+ }
+
+ switch (chunk.type) {
+ case FOURCC_RGNH:
+ if (fread(®ion.hdr, sizeof(region.hdr), 1, f) != 1) return false;
+ break;
+
+ case FOURCC_WSMP:
+ if (fread(®ion.wave_sample, sizeof(region.wave_sample), 1, f) != 1) return false;
+ fseek(f, region.wave_sample.cbSize - sizeof(region.wave_sample), SEEK_CUR);
+
+ /* Read all defined sample loops. */
+ for (ULONG i = 0; i < region.wave_sample.cSampleLoops; i++) {
+ WLOOP loop;
+ if (fread(&loop, sizeof(loop), 1, f) != 1) return false;
+ region.wave_loops.push_back(loop);
+ }
+ break;
+
+ case FOURCC_WLNK:
+ if (fread(®ion.wave, sizeof(region.wave), 1, f) != 1) return false;
+ break;
+
+ case FOURCC_LART: // List chunk
+ if (!this->ReadDLSArticulation(f, chunk.length, region.articulators)) return false;
+ break;
+
+ case FOURCC_INFO:
+ /* We don't care about info stuff. */
+ fseek(f, chunk.length, SEEK_CUR);
+ break;
+
+ default:
+ DEBUG(driver, 7, "DLS: Ignoring unkown chunk %c%c%c%c", chunk.type & 0xFF, (chunk.type >> 8) & 0xFF, (chunk.type >> 16) & 0xFF, (chunk.type >> 24) & 0xFF);
+ fseek(f, chunk.length, SEEK_CUR);
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool DLSFile::ReadDLSRegionList(FILE *f, DWORD list_length, DLSInstrument &instrument)
+{
+ while (list_length > 0) {
+ ChunkHeader chunk;
+ if (fread(&chunk, sizeof(chunk), 1, f) != 1) return false;
+ list_length -= chunk.length + sizeof(chunk);
+
+ if (chunk.type == FOURCC_LIST) {
+ FOURCC list_type;
+ if (fread(&list_type, sizeof(list_type), 1, f) != 1) return false;
+
+ if (list_type == FOURCC_RGN) {
+ this->ReadDLSRegion(f, chunk.length - sizeof(list_type), instrument.regions);
+ } else {
+ DEBUG(driver, 7, "DLS: Ignoring unkown list chunk of type %c%c%c%c", list_type & 0xFF, (list_type >> 8) & 0xFF, (list_type >> 16) & 0xFF, (list_type >> 24) & 0xFF);
+ fseek(f, chunk.length - sizeof(list_type), SEEK_CUR);
+ }
+ } else {
+ DEBUG(driver, 7, "DLS: Ignoring chunk %c%c%c%c", chunk.type & 0xFF, (chunk.type >> 8) & 0xFF, (chunk.type >> 16) & 0xFF, (chunk.type >> 24) & 0xFF);
+ fseek(f, chunk.length, SEEK_CUR);
+ }
+ }
+
+ return true;
+}
+
+bool DLSFile::ReadDLSInstrument(FILE *f, DWORD list_length)
+{
+ this->instruments.push_back(DLSInstrument());
+ DLSInstrument &instrument = this->instruments.back();
+
+ while (list_length > 0) {
+ ChunkHeader chunk;
+ if (fread(&chunk, sizeof(chunk), 1, f) != 1) return false;
+ list_length -= chunk.length + sizeof(chunk);
+
+ if (chunk.type == FOURCC_LIST) {
+ /* Unwrap list header. */
+ if (fread(&chunk.type, sizeof(chunk.type), 1, f) != 1) return false;
+ chunk.length -= sizeof(chunk.type);
+ }
+
+ switch (chunk.type) {
+ case FOURCC_INSH:
+ if (fread(&instrument.hdr, sizeof(instrument.hdr), 1, f) != 1) return false;
+ break;
+
+ case FOURCC_LART: // List chunk
+ if (!this->ReadDLSArticulation(f, chunk.length, instrument.articulators)) return false;
+ break;
+
+ case FOURCC_LRGN: // List chunk
+ if (!this->ReadDLSRegionList(f, chunk.length, instrument)) return false;
+ break;
+
+ case FOURCC_INFO:
+ /* We don't care about info stuff. */
+ fseek(f, chunk.length, SEEK_CUR);
+ break;
+
+ default:
+ DEBUG(driver, 7, "DLS: Ignoring unkown chunk %c%c%c%c", chunk.type & 0xFF, (chunk.type >> 8) & 0xFF, (chunk.type >> 16) & 0xFF, (chunk.type >> 24) & 0xFF);
+ fseek(f, chunk.length, SEEK_CUR);
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool DLSFile::ReadDLSInstrumentList(FILE *f, DWORD list_length)
+{
+ while (list_length > 0) {
+ ChunkHeader chunk;
+ if (fread(&chunk, sizeof(chunk), 1, f) != 1) return false;
+ list_length -= chunk.length + sizeof(chunk);
+
+ if (chunk.type == FOURCC_LIST) {
+ FOURCC list_type;
+ if (fread(&list_type, sizeof(list_type), 1, f) != 1) return false;
+
+ if (list_type == FOURCC_INS) {
+ DEBUG(driver, 6, "DLS: Reading instrument %d", (int)instruments.size());
+
+ if (!this->ReadDLSInstrument(f, chunk.length - sizeof(list_type))) return false;
+ } else {
+ DEBUG(driver, 7, "DLS: Ignoring unkown list chunk of type %c%c%c%c", list_type & 0xFF, (list_type >> 8) & 0xFF, (list_type >> 16) & 0xFF, (list_type >> 24) & 0xFF);
+ fseek(f, chunk.length - sizeof(list_type), SEEK_CUR);
+ }
+ } else {
+ DEBUG(driver, 7, "DLS: Ignoring chunk %c%c%c%c", chunk.type & 0xFF, (chunk.type >> 8) & 0xFF, (chunk.type >> 16) & 0xFF, (chunk.type >> 24) & 0xFF);
+ fseek(f, chunk.length, SEEK_CUR);
+ }
+ }
+
+ return true;
+}
+
+bool DLSFile::ReadDLSWave(FILE *f, DWORD list_length, long offset)
+{
+ this->waves.push_back(DLSWave());
+ DLSWave &wave = this->waves.back();
+
+ /* Set default values. */
+ MemSetT(&wave.wave_sample, 0);
+ wave.wave_sample.cbSize = sizeof(WSMPL);
+ wave.wave_sample.usUnityNote = 60;
+ wave.file_offset = offset; // Store file offset so we can resolve the wave pool table later on.
+
+ while (list_length > 0) {
+ ChunkHeader chunk;
+ if (fread(&chunk, sizeof(chunk), 1, f) != 1) return false;
+ list_length -= chunk.length + sizeof(chunk);
+
+ if (chunk.type == FOURCC_LIST) {
+ /* Unwrap list header. */
+ if (fread(&chunk.type, sizeof(chunk.type), 1, f) != 1) return false;
+ chunk.length -= sizeof(chunk.type);
+ }
+
+ switch (chunk.type) {
+ case FOURCC_fmt:
+ if (fread(&wave.fmt, sizeof(wave.fmt), 1, f) != 1) return false;
+ if (chunk.length > sizeof(wave.fmt)) fseek(f, chunk.length - sizeof(wave.fmt), SEEK_CUR);
+ break;
+
+ case FOURCC_WSMP:
+ if (fread(&wave.wave_sample, sizeof(wave.wave_sample), 1, f) != 1) return false;
+ fseek(f, wave.wave_sample.cbSize - sizeof(wave.wave_sample), SEEK_CUR);
+
+ /* Read all defined sample loops. */
+ for (ULONG i = 0; i < wave.wave_sample.cSampleLoops; i++) {
+ WLOOP loop;
+ if (fread(&loop, sizeof(loop), 1, f) != 1) return false;
+ wave.wave_loops.push_back(loop);
+ }
+ break;
+
+ case FOURCC_data:
+ wave.data.resize(chunk.length);
+ if (fread(&wave.data[0], sizeof(BYTE), wave.data.size(), f) != wave.data.size()) return false;
+ break;
+
+ case FOURCC_INFO:
+ /* We don't care about info stuff. */
+ fseek(f, chunk.length, SEEK_CUR);
+ break;
+
+ default:
+ DEBUG(driver, 7, "DLS: Ignoring unkown chunk %c%c%c%c", chunk.type & 0xFF, (chunk.type >> 8) & 0xFF, (chunk.type >> 16) & 0xFF, (chunk.type >> 24) & 0xFF);
+ fseek(f, chunk.length, SEEK_CUR);
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool DLSFile::ReadDLSWaveList(FILE *f, DWORD list_length)
+{
+ long base_offset = ftell(f);
+
+ while (list_length > 0) {
+ long chunk_offset = ftell(f);
+
+ ChunkHeader chunk;
+ if (fread(&chunk, sizeof(chunk), 1, f) != 1) return false;
+ list_length -= chunk.length + sizeof(chunk);
+
+ if (chunk.type == FOURCC_LIST) {
+ FOURCC list_type;
+ if (fread(&list_type, sizeof(list_type), 1, f) != 1) return false;
+
+ if (list_type == FOURCC_wave) {
+ DEBUG(driver, 6, "DLS: Reading wave %d", (int)waves.size());
+
+ if (!this->ReadDLSWave(f, chunk.length - sizeof(list_type), chunk_offset - base_offset)) return false;
+ } else {
+ DEBUG(driver, 7, "DLS: Ignoring unkown list chunk of type %c%c%c%c", list_type & 0xFF, (list_type >> 8) & 0xFF, (list_type >> 16) & 0xFF, (list_type >> 24) & 0xFF);
+ fseek(f, chunk.length - sizeof(list_type), SEEK_CUR);
+ }
+ } else {
+ DEBUG(driver, 7, "DLS: Ignoring chunk %c%c%c%c", chunk.type & 0xFF, (chunk.type >> 8) & 0xFF, (chunk.type >> 16) & 0xFF, (chunk.type >> 24) & 0xFF);
+ fseek(f, chunk.length, SEEK_CUR);
+ }
+ }
+
+ return true;
+}
+
+bool DLSFile::LoadFile(const TCHAR *file)
+{
+ DEBUG(driver, 2, "DMusic: Try to load DLS file %s", FS2OTTD(file));
+
+ FILE *f = _tfopen(file, _T("rb"));
+ if (f == NULL) return false;
+
+ FileCloser f_scope(f);
+
+ /* Check DLS file header. */
+ ChunkHeader hdr;
+ FOURCC dls_type;
+ if (fread(&hdr, sizeof(hdr), 1, f) != 1) return false;
+ if (fread(&dls_type, sizeof(dls_type), 1, f) != 1) return false;
+ if (hdr.type != FOURCC_RIFF || dls_type != FOURCC_DLS) return false;
+
+ hdr.length -= sizeof(FOURCC);
+
+ DEBUG(driver, 2, "DMusic: Parsing DLS file");
+
+ DLSHEADER header;
+ MemSetT(&header, 0);
+
+ /* Iterate over all chunks in the file. */
+ while (hdr.length > 0) {
+ ChunkHeader chunk;
+ if (fread(&chunk, sizeof(chunk), 1, f) != 1) return false;
+ hdr.length -= chunk.length + sizeof(chunk);
+
+ if (chunk.type == FOURCC_LIST) {
+ /* Unwrap list header. */
+ if (fread(&chunk.type, sizeof(chunk.type), 1, f) != 1) return false;
+ chunk.length -= sizeof(chunk.type);
+ }
+
+ switch (chunk.type) {
+ case FOURCC_COLH:
+ if (fread(&header, sizeof(header), 1, f) != 1) return false;
+ break;
+
+ case FOURCC_LINS: // List chunk
+ if (!this->ReadDLSInstrumentList(f, chunk.length)) return false;
+ break;
+
+ case FOURCC_WVPL: // List chunk
+ if (!this->ReadDLSWaveList(f, chunk.length)) return false;
+ break;
+
+ case FOURCC_PTBL:
+ POOLTABLE ptbl;
+ if (fread(&ptbl, sizeof(ptbl), 1, f) != 1) return false;
+ fseek(f, ptbl.cbSize - sizeof(ptbl), SEEK_CUR);
+
+ /* Read all defined cues. */
+ for (ULONG i = 0; i < ptbl.cCues; i++) {
+ POOLCUE cue;
+ if (fread(&cue, sizeof(cue), 1, f) != 1) return false;
+ this->pool_cues.push_back(cue);
+ }
+ break;
+
+ case FOURCC_INFO:
+ /* We don't care about info stuff. */
+ fseek(f, chunk.length, SEEK_CUR);
+ break;
+
+ default:
+ DEBUG(driver, 7, "DLS: Ignoring unkown chunk %c%c%c%c", chunk.type & 0xFF, (chunk.type >> 8) & 0xFF, (chunk.type >> 16) & 0xFF, (chunk.type >> 24) & 0xFF);
+ fseek(f, chunk.length, SEEK_CUR);
+ break;
+ }
+ }
+
+ /* Have we read as many instruments as indicated? */
+ if (header.cInstruments != this->instruments.size()) return false;
+
+ /* Resolve wave pool table. */
+ for (std::vector::iterator cue = this->pool_cues.begin(); cue != this->pool_cues.end(); cue++) {
+ std::vector::iterator w = std::find(this->waves.begin(), this->waves.end(), cue->ulOffset);
+ if (w != this->waves.end()) {
+ cue->ulOffset = (ULONG)(w - this->waves.begin());
+ } else {
+ cue->ulOffset = 0;
+ }
+ }
+
+ return true;
+}
+
+
+static byte ScaleVolume(byte original, byte scale)
+{
+ return original * scale / 127;
+}
+
+static void TransmitChannelMsg(IDirectMusicBuffer *buffer, REFERENCE_TIME rt, byte status, byte p1, byte p2 = 0)
+{
+ if (buffer->PackStructured(rt, 0, status | (p1 << 8) | (p2 << 16)) == E_OUTOFMEMORY) {
+ /* Buffer is full, clear it and try again. */
+ _port->PlayBuffer(buffer);
+ buffer->Flush();
+
+ buffer->PackStructured(rt, 0, status | (p1 << 8) | (p2 << 16));
+ }
+}
+
+static void TransmitSysex(IDirectMusicBuffer *buffer, REFERENCE_TIME rt, byte *&msg_start, size_t &remaining)
+{
+ /* Find end of message. */
+ byte *msg_end = msg_start;
+ while (*msg_end != MIDIST_ENDSYSEX) msg_end++;
+ msg_end++; // Also include SysEx end byte.
+
+ if (buffer->PackUnstructured(rt, 0, msg_end - msg_start, msg_start) == E_OUTOFMEMORY) {
+ /* Buffer is full, clear it and try again. */
+ _port->PlayBuffer(buffer);
+ buffer->Flush();
+
+ buffer->PackUnstructured(rt, 0, msg_end - msg_start, msg_start);
+ }
+
+ /* Update position in buffer. */
+ remaining -= msg_end - msg_start;
+ msg_start = msg_end;
+}
+
+static void TransmitSysexConst(IDirectMusicBuffer *buffer, REFERENCE_TIME rt, byte *msg_start, size_t length)
+{
+ TransmitSysex(buffer, rt, msg_start, length);
+}
+
+/** Transmit 'Note off' messages to all MIDI channels. */
+static void TransmitNotesOff(IDirectMusicBuffer *buffer, REFERENCE_TIME block_time, REFERENCE_TIME cur_time)
+{
+ for (int ch = 0; ch < 16; ch++) {
+ TransmitChannelMsg(_buffer, block_time + 10, MIDIST_CONTROLLER | ch, MIDICT_MODE_ALLNOTESOFF, 0);
+ TransmitChannelMsg(_buffer, block_time + 10, MIDIST_CONTROLLER | ch, MIDICT_SUSTAINSW, 0);
+ TransmitChannelMsg(_buffer, block_time + 10, MIDIST_CONTROLLER | ch, MIDICT_MODE_RESETALLCTRL, 0);
+ }
+ /* Explicitly flush buffer to make sure the note off messages are processed
+ * before we send any additional control messages. */
+ _port->PlayBuffer(_buffer);
+ _buffer->Flush();
+
+ /* Some songs change the "Pitch bend range" registered parameter. If
+ * this doesn't get reset, everything else will start sounding wrong. */
+ for (int ch = 0; ch < 16; ch++) {
+ /* Running status, only need status for first message
+ * Select RPN 00.00, set value to 02.00, and de-select again */
+ TransmitChannelMsg(_buffer, block_time + 10, MIDIST_CONTROLLER | ch, MIDICT_RPN_SELECT_LO, 0x00);
+ TransmitChannelMsg(_buffer, block_time + 10, MIDICT_RPN_SELECT_HI, 0x00);
+ TransmitChannelMsg(_buffer, block_time + 10, MIDICT_DATAENTRY, 0x02);
+ TransmitChannelMsg(_buffer, block_time + 10, MIDICT_DATAENTRY_LO, 0x00);
+ TransmitChannelMsg(_buffer, block_time + 10, MIDICT_RPN_SELECT_LO, 0x7F);
+ TransmitChannelMsg(_buffer, block_time + 10, MIDICT_RPN_SELECT_HI, 0x7F);
+
+ _port->PlayBuffer(_buffer);
+ _buffer->Flush();
+ }
+
+ /* Wait until message time has passed. */
+ Sleep(Clamp((block_time - cur_time) / MS_TO_REFTIME, 5, 1000));
+}
+
+static void MidiThreadProc(void *)
+{
+ DEBUG(driver, 2, "DMusic: Entering playback thread");
+
+ REFERENCE_TIME last_volume_time = 0; // timestamp of the last volume change
+ REFERENCE_TIME block_time = 0; // timestamp of the last block sent to the port
+ REFERENCE_TIME playback_start_time; // timestamp current file began playback
+ MidiFile current_file; // file currently being played from
+ PlaybackSegment current_segment; // segment info for current playback
+ size_t current_block; // next block index to send
+ byte current_volume = 0; // current effective volume setting
+ byte channel_volumes[16]; // last seen volume controller values in raw data
+
+ /* Get pointer to the reference clock of our output port. */
+ IReferenceClock *clock;
+ _port->GetLatencyClock(&clock);
+
+ REFERENCE_TIME cur_time;
+ clock->GetTime(&cur_time);
+
+ /* Standard "Enable General MIDI" message */
+ static byte gm_enable_sysex[] = { 0xF0, 0x7E, 0x00, 0x09, 0x01, 0xF7 };
+ TransmitSysexConst(_buffer, cur_time, &gm_enable_sysex[0], sizeof(gm_enable_sysex));
+ /* Roland-specific reverb room control, used by the original game */
+ static byte roland_reverb_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x30, 0x02, 0x04, 0x00, 0x40, 0x40, 0x00, 0x00, 0x09, 0xF7 };
+ TransmitSysexConst(_buffer, cur_time, &roland_reverb_sysex[0], sizeof(roland_reverb_sysex));
+
+ _port->PlayBuffer(_buffer);
+ _buffer->Flush();
+
+ DWORD next_timeout = 1000;
+ while (true) {
+ /* Wait for a signal from the GUI thread or until the time for the next event has come. */
+ DWORD wfso = WaitForSingleObject(_thread_event, next_timeout);
+
+ if (_playback.shutdown) {
+ _playback.playing = false;
+ break;
+ }
+
+ if (_playback.do_stop) {
+ DEBUG(driver, 2, "DMusic thread: Stopping playback");
+
+ /* Turn all notes off and wait a bit to allow the messages to be handled. */
+ clock->GetTime(&cur_time);
+ TransmitNotesOff(_buffer, block_time, cur_time);
+
+ _playback.playing = false;
+ _playback.do_stop = false;
+ block_time = 0;
+ next_timeout = 1000;
+ continue;
+ }
+
+ if (wfso == WAIT_OBJECT_0) {
+ if (_playback.do_start) {
+ DEBUG(driver, 2, "DMusic thread: Starting playback");
+ {
+ /* New scope to limit the time the mutex is locked. */
+ ThreadMutexLocker lock(_thread_mutex);
+
+ current_file.MoveFrom(_playback.next_file);
+ std::swap(_playback.next_segment, current_segment);
+ current_segment.start_block = 0;
+ current_block = 0;
+ _playback.playing = true;
+ _playback.do_start = false;
+ }
+
+ /* Turn all notes off in case we are seeking between music titles. */
+ clock->GetTime(&cur_time);
+ TransmitNotesOff(_buffer, block_time, cur_time);
+
+ MemSetT(channel_volumes, 127, lengthof(channel_volumes));
+
+ /* Take the current time plus the preload time as the music start time. */
+ clock->GetTime(&playback_start_time);
+ playback_start_time += _playback.preload_time * MS_TO_REFTIME;
+ }
+ }
+
+ if (_playback.playing) {
+ /* skip beginning of file? */
+ if (current_segment.start > 0 && current_block == 0 && current_segment.start_block == 0) {
+ /* find first block after start time and pretend playback started earlier
+ * this is to allow all blocks prior to the actual start to still affect playback,
+ * as they may contain important controller and program changes */
+ size_t preload_bytes = 0;
+ for (size_t bl = 0; bl < current_file.blocks.size(); bl++) {
+ MidiFile::DataBlock &block = current_file.blocks[bl];
+ preload_bytes += block.data.Length();
+ if (block.ticktime >= current_segment.start) {
+ if (current_segment.loop) {
+ DEBUG(driver, 2, "DMusic: timer: loop from block %d (ticktime %d, realtime %.3f, bytes %d)", (int)bl, (int)block.ticktime, ((int)block.realtime) / 1000.0, (int)preload_bytes);
+ current_segment.start_block = bl;
+ break;
+ } else {
+ DEBUG(driver, 2, "DMusic: timer: start from block %d (ticktime %d, realtime %.3f, bytes %d)", (int)bl, (int)block.ticktime, ((int)block.realtime) / 1000.0, (int)preload_bytes);
+ playback_start_time -= block.realtime * MIDITIME_TO_REFTIME;
+ break;
+ }
+ }
+ }
+ }
+
+ /* Get current playback timestamp. */
+ REFERENCE_TIME current_time;
+ clock->GetTime(¤t_time);
+
+ /* Check for volume change. */
+ if (current_volume != _playback.new_volume) {
+ if (current_time - last_volume_time > 10 * MS_TO_REFTIME) {
+ DEBUG(driver, 2, "DMusic thread: volume change");
+ current_volume = _playback.new_volume;
+ last_volume_time = current_time;
+ for (int ch = 0; ch < 16; ch++) {
+ int vol = ScaleVolume(channel_volumes[ch], current_volume);
+ TransmitChannelMsg(_buffer, block_time + 1, MIDIST_CONTROLLER | ch, MIDICT_CHANVOLUME, vol);
+ }
+ _port->PlayBuffer(_buffer);
+ _buffer->Flush();
+ }
+ }
+
+ while (current_block < current_file.blocks.size()) {
+ MidiFile::DataBlock &block = current_file.blocks[current_block];
+
+ /* check that block is not in the future */
+ REFERENCE_TIME playback_time = current_time - playback_start_time;
+ if (block.realtime * MIDITIME_TO_REFTIME > playback_time + 3 *_playback.preload_time * MS_TO_REFTIME) {
+ /* Stop the thread loop until we are at the preload time of the next block. */
+ next_timeout = Clamp(((int64)block.realtime * MIDITIME_TO_REFTIME - playback_time) / MS_TO_REFTIME - _playback.preload_time, 0, 1000);
+ DEBUG(driver, 9, "DMusic thread: Next event in %u ms (music %u, ref %lld)", next_timeout, block.realtime * MIDITIME_TO_REFTIME, playback_time);
+ break;
+ }
+ /* check that block isn't at end-of-song override */
+ if (current_segment.end > 0 && block.ticktime >= current_segment.end) {
+ if (current_segment.loop) {
+ DEBUG(driver, 2, "DMusic thread: Looping song");
+ current_block = current_segment.start_block;
+ playback_start_time = current_time - current_file.blocks[current_block].realtime * MIDITIME_TO_REFTIME;
+ } else {
+ _playback.do_stop = true;
+ }
+ next_timeout = 0;
+ break;
+ }
+
+ /* Timestamp of the current block. */
+ block_time = playback_start_time + block.realtime * MIDITIME_TO_REFTIME;
+ DEBUG(driver, 9, "DMusic thread: Streaming block %Iu (cur=%lld, block=%lld)", current_block, (long long)(current_time / MS_TO_REFTIME), (long long)(block_time / MS_TO_REFTIME));
+
+ byte *data = block.data.Begin();
+ size_t remaining = block.data.Length();
+ byte last_status = 0;
+ while (remaining > 0) {
+ /* MidiFile ought to have converted everything out of running status,
+ * but handle it anyway just to be safe */
+ byte status = data[0];
+ if (status & 0x80) {
+ last_status = status;
+ data++;
+ remaining--;
+ } else {
+ status = last_status;
+ }
+ switch (status & 0xF0) {
+ case MIDIST_PROGCHG:
+ case MIDIST_CHANPRESS:
+ /* 2 byte channel messages */
+ TransmitChannelMsg(_buffer, block_time, status, data[0]);
+ data++;
+ remaining--;
+ break;
+ case MIDIST_NOTEOFF:
+ case MIDIST_NOTEON:
+ case MIDIST_POLYPRESS:
+ case MIDIST_PITCHBEND:
+ /* 3 byte channel messages */
+ TransmitChannelMsg(_buffer, block_time, status, data[0], data[1]);
+ data += 2;
+ remaining -= 2;
+ break;
+ case MIDIST_CONTROLLER:
+ /* controller change */
+ if (data[0] == MIDICT_CHANVOLUME) {
+ /* volume controller, adjust for user volume */
+ channel_volumes[status & 0x0F] = data[1];
+ int vol = ScaleVolume(data[1], current_volume);
+ TransmitChannelMsg(_buffer, block_time, status, data[0], vol);
+ } else {
+ /* handle other controllers normally */
+ TransmitChannelMsg(_buffer, block_time, status, data[0], data[1]);
+ }
+ data += 2;
+ remaining -= 2;
+ break;
+ case 0xF0:
+ /* system messages */
+ switch (status) {
+ case MIDIST_SYSEX: /* system exclusive */
+ TransmitSysex(_buffer, block_time, data, remaining);
+ break;
+ case MIDIST_TC_QFRAME: /* time code quarter frame */
+ case MIDIST_SONGSEL: /* song select */
+ data++;
+ remaining--;
+ break;
+ case MIDIST_SONGPOSPTR: /* song position pointer */
+ data += 2;
+ remaining -= 2;
+ break;
+ default: /* remaining have no data bytes */
+ break;
+ }
+ break;
+ }
+ }
+
+ current_block++;
+ }
+
+ /* Anything in the playback buffer? Send it down the port. */
+ DWORD used_buffer = 0;
+ _buffer->GetUsedBytes(&used_buffer);
+ if (used_buffer > 0) {
+ _port->PlayBuffer(_buffer);
+ _buffer->Flush();
+ }
+
+ /* end? */
+ if (current_block == current_file.blocks.size()) {
+ if (current_segment.loop) {
+ current_block = 0;
+ clock->GetTime(&playback_start_time);
+ } else {
+ _playback.do_stop = true;
+ }
+ next_timeout = 0;
+ }
+ }
+ }
+
+ DEBUG(driver, 2, "DMusic: Exiting playback thread");
+
+ /* Turn all notes off and wait a bit to allow the messages to be handled by real hardware. */
+ clock->GetTime(&cur_time);
+ TransmitNotesOff(_buffer, block_time, cur_time);
+ Sleep(_playback.preload_time * 4);
+
+ clock->Release();
+}
+
+static void * DownloadArticulationData(int base_offset, void *data, const std::vector &artic)
+{
+ DMUS_ARTICULATION2 *art = (DMUS_ARTICULATION2 *)data;
+ art->ulArtIdx = base_offset + 1;
+ art->ulFirstExtCkIdx = 0;
+ art->ulNextArtIdx = 0;
+
+ CONNECTIONLIST *con_list = (CONNECTIONLIST *)(art + 1);
+ con_list->cbSize = sizeof(CONNECTIONLIST);
+ con_list->cConnections = (ULONG)artic.size();
+ MemCpyT((CONNECTION *)(con_list + 1), &artic.front(), artic.size());
+
+ return (CONNECTION *)(con_list + 1) + artic.size();
+}
+
+static const char *LoadDefaultDLSFile(const char *user_dls)
+{
+ DMUS_PORTCAPS caps;
+ MemSetT(&caps, 0);
+ caps.dwSize = sizeof(DMUS_PORTCAPS);
+ _port->GetCaps(&caps);
+
+ /* Nothing to unless it is a synth with instrument download that doesn't come with GM voices by default. */
+ if ((caps.dwFlags & (DMUS_PC_DLS | DMUS_PC_DLS2)) != 0 && (caps.dwFlags & DMUS_PC_GMINHARDWARE) == 0) {
+ DLSFile dls_file;
+
+ if (user_dls == NULL) {
+ /* Try loading the default GM DLS file stored in the registry. */
+ HKEY hkDM;
+ if (SUCCEEDED(RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\DirectMusic"), 0, KEY_READ, &hkDM))) {
+ TCHAR dls_path[MAX_PATH];
+ DWORD buf_size = sizeof(dls_path); // Buffer size as to be given in bytes!
+ if (SUCCEEDED(RegQueryValueEx(hkDM, _T("GMFilePath"), NULL, NULL, (LPBYTE)dls_path, &buf_size))) {
+ TCHAR expand_path[MAX_PATH * 2];
+ ExpandEnvironmentStrings(dls_path, expand_path, lengthof(expand_path));
+ if (!dls_file.LoadFile(expand_path)) DEBUG(driver, 1, "Failed to load default GM DLS file from registry");
+ }
+ RegCloseKey(hkDM);
+ }
+
+ /* If we couldn't load the file from the registry, try again at the default install path of the GM DLS file. */
+ if (dls_file.instruments.size() == 0) {
+ static const TCHAR *DLS_GM_FILE = _T("%windir%\\System32\\drivers\\gm.dls");
+ TCHAR path[MAX_PATH];
+ ExpandEnvironmentStrings(DLS_GM_FILE, path, lengthof(path));
+
+ if (!dls_file.LoadFile(path)) return "Can't load GM DLS collection";
+ }
+ } else {
+ if (!dls_file.LoadFile(OTTD2FS(user_dls))) return "Can't load GM DLS collection";
+ }
+
+ /* Get download port and allocate download IDs. */
+ IDirectMusicPortDownload *download_port = NULL;
+ if (FAILED(_port->QueryInterface(IID_IDirectMusicPortDownload, (LPVOID *)&download_port))) return "Can't get download port";
+
+ DWORD dlid_wave = 0, dlid_inst = 0;
+ if (FAILED(download_port->GetDLId(&dlid_wave, (DWORD)dls_file.waves.size())) || FAILED(download_port->GetDLId(&dlid_inst, (DWORD)dls_file.instruments.size()))) {
+ download_port->Release();
+ return "Can't get enough DLS ids";
+ }
+
+ DWORD dwAppend = 0;
+ download_port->GetAppend(&dwAppend);
+
+ /* Download wave data. */
+ for (DWORD i = 0; i < dls_file.waves.size(); i++) {
+ IDirectMusicDownload *dl_wave = NULL;
+ if (FAILED(download_port->AllocateBuffer((DWORD)(sizeof(WAVE_DOWNLOAD) + dwAppend * dls_file.waves[i].fmt.wf.nBlockAlign + dls_file.waves[i].data.size()), &dl_wave))) {
+ download_port->Release();
+ return "Can't allocate wave download buffer";
+ }
+
+ WAVE_DOWNLOAD *wave;
+ DWORD wave_size = 0;
+ if (FAILED(dl_wave->GetBuffer((LPVOID *)&wave, &wave_size))) {
+ dl_wave->Release();
+ download_port->Release();
+ return "Can't get wave download buffer";
+ }
+
+ /* Fill download data. */
+ MemSetT(wave, 0);
+ wave->dlInfo.dwDLType = DMUS_DOWNLOADINFO_WAVE;
+ wave->dlInfo.cbSize = wave_size;
+ wave->dlInfo.dwDLId = dlid_wave + i;
+ wave->dlInfo.dwNumOffsetTableEntries = 2;
+ wave->ulOffsetTable[0] = offsetof(WAVE_DOWNLOAD, dmWave);
+ wave->ulOffsetTable[1] = offsetof(WAVE_DOWNLOAD, dmWaveData);
+ wave->dmWave.ulWaveDataIdx = 1;
+ MemCpyT((PCMWAVEFORMAT *)&wave->dmWave.WaveformatEx, &dls_file.waves[i].fmt, 1);
+ wave->dmWaveData.cbSize = (DWORD)dls_file.waves[i].data.size();
+ MemCpyT(wave->dmWaveData.byData, &dls_file.waves[i].data[0], dls_file.waves[i].data.size());
+
+ _dls_downloads.push_back(dl_wave);
+ if (FAILED(download_port->Download(dl_wave))) {
+ download_port->Release();
+ return "Downloading DLS wave failed";
+ }
+ }
+
+ /* Download instrument data. */
+ for (DWORD i = 0; i < dls_file.instruments.size(); i++) {
+ DWORD offsets = 1 + (DWORD)dls_file.instruments[i].regions.size();
+
+ /* Calculate download size for the instrument. */
+ size_t i_size = sizeof(DMUS_DOWNLOADINFO) + sizeof(DMUS_INSTRUMENT);
+ if (dls_file.instruments[i].articulators.size() > 0) {
+ /* Articulations are stored as two chunks, one containing meta data and one with the actual articulation data. */
+ offsets += 2;
+ i_size += sizeof(DMUS_ARTICULATION2) + sizeof(CONNECTIONLIST) + sizeof(CONNECTION) * dls_file.instruments[i].articulators.size();
+ }
+
+ for (std::vector::iterator rgn = dls_file.instruments[i].regions.begin(); rgn != dls_file.instruments[i].regions.end(); rgn++) {
+ if (rgn->articulators.size() > 0) {
+ offsets += 2;
+ i_size += sizeof(DMUS_ARTICULATION2) + sizeof(CONNECTIONLIST) + sizeof(CONNECTION) * rgn->articulators.size();
+ }
+
+ /* Region size depends on the number of wave loops. The size of the
+ * declared structure already accounts for one loop. */
+ if (rgn->wave_sample.cbSize != 0) {
+ i_size += sizeof(DMUS_REGION) - sizeof(DMUS_REGION::WLOOP) + sizeof(WLOOP) * rgn->wave_loops.size();
+ } else {
+ i_size += sizeof(DMUS_REGION) - sizeof(DMUS_REGION::WLOOP) + sizeof(WLOOP) * dls_file.waves[dls_file.pool_cues[rgn->wave.ulTableIndex].ulOffset].wave_loops.size();
+ }
+ }
+
+ i_size += offsets * sizeof(ULONG);
+
+ /* Allocate download buffer. */
+ IDirectMusicDownload *dl_inst = NULL;
+ if (FAILED(download_port->AllocateBuffer((DWORD)i_size, &dl_inst))) {
+ download_port->Release();
+ return "Can't allocate instrument download buffer";
+ }
+
+ void *instrument;
+ DWORD inst_size = 0;
+ if (FAILED(dl_inst->GetBuffer((LPVOID *)&instrument, &inst_size))) {
+ dl_inst->Release();
+ download_port->Release();
+ return "Can't get instrument download buffer";
+ }
+ char *inst_base = (char *)instrument;
+
+ /* Fill download header. */
+ DMUS_DOWNLOADINFO *d_info = (DMUS_DOWNLOADINFO *)instrument;
+ d_info->dwDLType = DMUS_DOWNLOADINFO_INSTRUMENT2;
+ d_info->cbSize = inst_size;
+ d_info->dwDLId = dlid_inst + i;
+ d_info->dwNumOffsetTableEntries = offsets;
+ instrument = d_info + 1;
+
+ /* Download offset table; contains the offsets of all chunks relative to the buffer start. */
+ ULONG *offset_table = (ULONG *)instrument;
+ instrument = offset_table + offsets;
+ int last_offset = 0;
+
+ /* Instrument header. */
+ DMUS_INSTRUMENT *inst_data = (DMUS_INSTRUMENT *)instrument;
+ MemSetT(inst_data, 0);
+ offset_table[last_offset++] = (char *)inst_data - inst_base;
+ inst_data->ulPatch = dls_file.instruments[i].hdr.Locale.ulBank & F_INSTRUMENT_DRUMS | ((dls_file.instruments[i].hdr.Locale.ulBank & 0x7F7F) << 8) | dls_file.instruments[i].hdr.Locale.ulInstrument & 0x7F;
+ instrument = inst_data + 1;
+
+ /* Write global articulations. */
+ if (dls_file.instruments[i].articulators.size() > 0) {
+ inst_data->ulGlobalArtIdx = last_offset;
+ offset_table[last_offset++] = (char *)instrument - inst_base;
+ offset_table[last_offset++] = (char *)instrument + sizeof(DMUS_ARTICULATION2) - inst_base;
+
+ instrument = DownloadArticulationData(inst_data->ulGlobalArtIdx, instrument, dls_file.instruments[i].articulators);
+ assert((char *)instrument - inst_base <= (ptrdiff_t)inst_size);
+ }
+
+ /* Write out regions. */
+ inst_data->ulFirstRegionIdx = last_offset;
+ for (uint j = 0; j < dls_file.instruments[i].regions.size(); j++) {
+ DLSFile::DLSRegion &rgn = dls_file.instruments[i].regions[j];
+
+ DMUS_REGION *inst_region = (DMUS_REGION *)instrument;
+ offset_table[last_offset++] = (char *)inst_region - inst_base;
+ inst_region->RangeKey = rgn.hdr.RangeKey;
+ inst_region->RangeVelocity = rgn.hdr.RangeVelocity;
+ inst_region->fusOptions = rgn.hdr.fusOptions;
+ inst_region->usKeyGroup = rgn.hdr.usKeyGroup;
+ inst_region->ulFirstExtCkIdx = 0;
+
+ ULONG wave_id = dls_file.pool_cues[rgn.wave.ulTableIndex].ulOffset;
+ inst_region->WaveLink = rgn.wave;
+ inst_region->WaveLink.ulTableIndex = wave_id + dlid_wave;
+
+ /* The wave sample data will be taken from the region, if defined, otherwise from the wave itself. */
+ if (rgn.wave_sample.cbSize != 0) {
+ inst_region->WSMP = rgn.wave_sample;
+ if (rgn.wave_loops.size() > 0) MemCpyT(inst_region->WLOOP, &rgn.wave_loops.front(), rgn.wave_loops.size());
+
+ instrument = (char *)(inst_region + 1) - sizeof(DMUS_REGION::WLOOP) + sizeof(WLOOP) * rgn.wave_loops.size();
+ } else {
+ inst_region->WSMP = rgn.wave_sample;
+ if (dls_file.waves[wave_id].wave_loops.size() > 0) MemCpyT(inst_region->WLOOP, &dls_file.waves[wave_id].wave_loops.front(), dls_file.waves[wave_id].wave_loops.size());
+
+ instrument = (char *)(inst_region + 1) - sizeof(DMUS_REGION::WLOOP) + sizeof(WLOOP) * dls_file.waves[wave_id].wave_loops.size();
+ }
+
+ /* Write local articulator data. */
+ if (rgn.articulators.size() > 0) {
+ inst_region->ulRegionArtIdx = last_offset;
+ offset_table[last_offset++] = (char *)instrument - inst_base;
+ offset_table[last_offset++] = (char *)instrument + sizeof(DMUS_ARTICULATION2) - inst_base;
+
+ instrument = DownloadArticulationData(inst_region->ulRegionArtIdx, instrument, rgn.articulators);
+ } else {
+ inst_region->ulRegionArtIdx = 0;
+ }
+ assert((char *)instrument - inst_base <= (ptrdiff_t)inst_size);
+
+ /* Link to the next region unless this was the last one.*/
+ inst_region->ulNextRegionIdx = j < dls_file.instruments[i].regions.size() - 1 ? last_offset : 0;
+ }
+
+ _dls_downloads.push_back(dl_inst);
+ if (FAILED(download_port->Download(dl_inst))) {
+ download_port->Release();
+ return "Downloading DLS instrument failed";
+ }
+ }
+
+ download_port->Release();
+ }
+
+ return NULL;
+}
const char *MusicDriver_DMusic::Start(const char * const *parm)
{
- if (performance != NULL) return NULL;
-
- if (proc.CoCreateInstance == NULL) {
- if (!LoadLibraryList((Function*)&proc, ole_files)) {
- return "ole32.dll load failed";
- }
- }
-
/* Initialize COM */
- if (FAILED(proc.CoInitialize(NULL))) {
- return "COM initialization failed";
- }
+ if (FAILED(CoInitializeEx(NULL, COINITBASE_MULTITHREADED))) return "COM initialization failed";
- /* create the performance object */
- if (FAILED(proc.CoCreateInstance(
- CLSID_DirectMusicPerformance,
+ /* Create the DirectMusic object */
+ if (FAILED(CoCreateInstance(
+ CLSID_DirectMusic,
NULL,
CLSCTX_INPROC,
- IID_IDirectMusicPerformance,
- (LPVOID*)&performance
+ IID_IDirectMusic,
+ (LPVOID*)&_music
))) {
- return "Failed to create the performance object";
+ return "Failed to create the music object";
}
- /* initialize it */
- if (FAILED(performance->Init(&music, NULL, NULL))) {
- return "Failed to initialize performance object";
- }
+ /* Assign sound output device. */
+ if (FAILED(_music->SetDirectSound(NULL, NULL))) return "Can't set DirectSound interface";
- int port = GetDriverParamInt(parm, "port", -1);
+ /* MIDI events need to be send to the synth in time before their playback time
+ * has come. By default, we try send any events at least 50 ms before playback. */
+ _playback.preload_time = GetDriverParamInt(parm, "preload", 50);
+ int pIdx = GetDriverParamInt(parm, "port", -1);
if (_debug_driver_level > 0) {
/* Print all valid output ports. */
char desc[DMUS_MAX_DESCRIPTION];
@@ -108,69 +1117,56 @@ const char *MusicDriver_DMusic::Start(const char * const *parm)
caps.dwSize = sizeof(DMUS_PORTCAPS);
DEBUG(driver, 1, "Detected DirectMusic ports:");
- for (int i = 0; music->EnumPort(i, &caps) == S_OK; i++) {
+ for (int i = 0; _music->EnumPort(i, &caps) == S_OK; i++) {
if (caps.dwClass == DMUS_PC_OUTPUTCLASS) {
/* Description is UNICODE even for ANSI build. */
- DEBUG(driver, 1, " %d: %s%s", i, convert_from_fs(caps.wszDescription, desc, lengthof(desc)), i == port ? " (selected)" : "");
+ DEBUG(driver, 1, " %d: %s%s", i, convert_from_fs(caps.wszDescription, desc, lengthof(desc)), i == pIdx ? " (selected)" : "");
}
}
}
- IDirectMusicPort *music_port = NULL; // NULL means 'use default port'.
-
- if (port >= 0) {
+ GUID guidPort;
+ if (pIdx >= 0) {
/* Check if the passed port is a valid port. */
DMUS_PORTCAPS caps;
MemSetT(&caps, 0);
caps.dwSize = sizeof(DMUS_PORTCAPS);
- if (FAILED(music->EnumPort(port, &caps))) return "Supplied port parameter is not a valid port";
+ if (FAILED(_music->EnumPort(pIdx, &caps))) return "Supplied port parameter is not a valid port";
if (caps.dwClass != DMUS_PC_OUTPUTCLASS) return "Supplied port parameter is not an output port";
-
- /* Create new port. */
- DMUS_PORTPARAMS params;
- MemSetT(¶ms, 0);
- params.dwSize = sizeof(DMUS_PORTPARAMS);
- params.dwValidParams = DMUS_PORTPARAMS_CHANNELGROUPS;
- params.dwChannelGroups = 1;
-
- if (FAILED(music->CreatePort(caps.guidPort, ¶ms, &music_port, NULL))) {
- return "Failed to create port";
- }
-
- /* Activate port. */
- if (FAILED(music_port->Activate(TRUE))) {
- music_port->Release();
- return "Failed to activate port";
- }
+ guidPort = caps.guidPort;
+ } else {
+ if (FAILED(_music->GetDefaultPort(&guidPort))) return "Can't query default music port";
}
- /* Add port to performance. */
- if (FAILED(performance->AddPort(music_port))) {
- if (music_port != NULL) music_port->Release();
- return "AddPort failed";
- }
+ /* Create new port. */
+ DMUS_PORTPARAMS params;
+ MemSetT(¶ms, 0);
+ params.dwSize = sizeof(DMUS_PORTPARAMS);
+ params.dwValidParams = DMUS_PORTPARAMS_CHANNELGROUPS;
+ params.dwChannelGroups = 1;
+ if (FAILED(_music->CreatePort(guidPort, ¶ms, &_port, NULL))) return "Failed to create port";
+ /* Activate port. */
+ if (FAILED(_port->Activate(TRUE))) return "Failed to activate port";
- /* Assign a performance channel block to the performance if we added
- * a custom port to the performance. */
- if (music_port != NULL) {
- if (FAILED(performance->AssignPChannelBlock(0, music_port, 1))) {
- music_port->Release();
- return "Failed to assign PChannel block";
- }
- /* We don't need the port anymore. */
- music_port->Release();
- }
+ /* Create playback buffer. */
+ DMUS_BUFFERDESC desc;
+ MemSetT(&desc, 0);
+ desc.dwSize = sizeof(DMUS_BUFFERDESC);
+ desc.guidBufferFormat = KSDATAFORMAT_SUBTYPE_DIRECTMUSIC;
+ desc.cbBuffer = 1024;
+ if (FAILED(_music->CreateMusicBuffer(&desc, &_buffer, NULL))) return "Failed to create music buffer";
- /* create the loader object; this will be used to load the MIDI file */
- if (FAILED(proc.CoCreateInstance(
- CLSID_DirectMusicLoader,
- NULL,
- CLSCTX_INPROC,
- IID_IDirectMusicLoader,
- (LPVOID*)&loader
- ))) {
- return "Failed to create loader object";
- }
+ /* On soft-synths (e.g. the default DirectMusic one), we might need to load a wavetable set to get music. */
+ const char *dls = LoadDefaultDLSFile(GetDriverParam(parm, "dls"));
+ if (dls != NULL) return dls;
+
+ /* Create playback thread and synchronization primitives. */
+ _thread_event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (_thread_event == NULL) return "Can't create thread shutdown event";
+ _thread_mutex = ThreadMutex::New();
+ if (_thread_mutex == NULL) return "Can't create thread mutex";
+
+ if (!ThreadObject::New(&MidiThreadProc, this, &_dmusic_thread, "ottd:dmusic")) return "Can't create MIDI output thread";
return NULL;
}
@@ -184,114 +1180,81 @@ MusicDriver_DMusic::~MusicDriver_DMusic()
void MusicDriver_DMusic::Stop()
{
- seeking = false;
-
- if (performance != NULL) performance->Stop(NULL, NULL, 0, 0);
-
- if (segment != NULL) {
- segment->SetParam(GUID_Unload, 0xFFFFFFFF, 0, 0, performance);
- segment->Release();
- segment = NULL;
+ if (_dmusic_thread != NULL) {
+ _playback.shutdown = true;
+ SetEvent(_thread_event);
+ _dmusic_thread->Join();
}
- if (music != NULL) {
- music->Release();
- music = NULL;
+ /* Unloaded any instruments we loaded. */
+ if (_dls_downloads.size() > 0) {
+ IDirectMusicPortDownload *download_port = NULL;
+ _port->QueryInterface(IID_IDirectMusicPortDownload, (LPVOID *)&download_port);
+
+ /* Instruments refer to waves. As the waves are at the beginning of the download list,
+ * do the unload from the back so that references are cleared properly. */
+ for (std::vector::reverse_iterator i = _dls_downloads.rbegin(); download_port != NULL && i != _dls_downloads.rend(); i++) {
+ download_port->Unload(*i);
+ (*i)->Release();
+ }
+ _dls_downloads.clear();
+
+ if (download_port != NULL) download_port->Release();
}
- if (performance != NULL) {
- performance->CloseDown();
- performance->Release();
- performance = NULL;
+ if (_buffer != NULL) {
+ _buffer->Release();
+ _buffer = NULL;
}
- if (loader != NULL) {
- loader->Release();
- loader = NULL;
+ if (_port != NULL) {
+ _port->Activate(FALSE);
+ _port->Release();
+ _port = NULL;
}
- proc.CoUninitialize();
+ if (_music != NULL) {
+ _music->Release();
+ _music = NULL;
+ }
+
+ CloseHandle(_thread_event);
+ delete _thread_mutex;
+
+ CoUninitialize();
}
void MusicDriver_DMusic::PlaySong(const char *filename)
{
- /* set up the loader object info */
- DMUS_OBJECTDESC obj_desc;
- ZeroMemory(&obj_desc, sizeof(obj_desc));
- obj_desc.dwSize = sizeof(obj_desc);
- obj_desc.guidClass = CLSID_DirectMusicSegment;
- obj_desc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME | DMUS_OBJ_FULLPATH;
- MultiByteToWideChar(
- CP_ACP, MB_PRECOMPOSED,
- filename, -1,
- obj_desc.wszFileName, lengthof(obj_desc.wszFileName)
- );
+ ThreadMutexLocker lock(_thread_mutex);
- /* release the existing segment if we have any */
- if (segment != NULL) {
- segment->Release();
- segment = NULL;
- }
+ _playback.next_file.LoadFile(filename);
+ _playback.next_segment.start = 0;
+ _playback.next_segment.end = 0;
+ _playback.next_segment.loop = false;
- /* make a new segment */
- if (FAILED(loader->GetObject(
- &obj_desc, IID_IDirectMusicSegment, (LPVOID*)&segment
- ))) {
- DEBUG(driver, 0, "DirectMusic: GetObject failed");
- return;
- }
-
- /* tell the segment what kind of data it contains */
- if (FAILED(segment->SetParam(
- GUID_StandardMIDIFile, 0xFFFFFFFF, 0, 0, performance
- ))) {
- DEBUG(driver, 0, "DirectMusic: SetParam (MIDI file) failed");
- return;
- }
-
- /* tell the segment to 'download' the instruments */
- if (FAILED(segment->SetParam(GUID_Download, 0xFFFFFFFF, 0, 0, performance))) {
- DEBUG(driver, 0, "DirectMusic: failed to download instruments");
- return;
- }
-
- /* start playing the MIDI file */
- if (FAILED(performance->PlaySegment(segment, 0, 0, NULL))) {
- DEBUG(driver, 0, "DirectMusic: PlaySegment failed");
- return;
- }
-
- seeking = true;
+ _playback.do_start = true;
+ SetEvent(_thread_event);
}
void MusicDriver_DMusic::StopSong()
{
- if (FAILED(performance->Stop(segment, NULL, 0, 0))) {
- DEBUG(driver, 0, "DirectMusic: StopSegment failed");
- }
- seeking = false;
+ _playback.do_stop = true;
+ SetEvent(_thread_event);
}
bool MusicDriver_DMusic::IsSongPlaying()
{
- /* Not the nicest code, but there is a short delay before playing actually
- * starts. OpenTTD makes no provision for this. */
- if (performance->IsPlaying(segment, NULL) == S_OK) {
- seeking = false;
- return true;
- } else {
- return seeking;
- }
+ return _playback.playing || _playback.do_start;
}
void MusicDriver_DMusic::SetVolume(byte vol)
{
- long db = vol * 2000 / 127 - 2000; ///< 0 - 127 -> -2000 - 0
- performance->SetGlobalParam(GUID_PerfMasterVolume, &db, sizeof(db));
+ _playback.new_volume = vol;
}
diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm
index f8412e3877..d165610245 100644
--- a/src/video/cocoa/cocoa_v.mm
+++ b/src/video/cocoa/cocoa_v.mm
@@ -214,6 +214,12 @@ static void setupApplication()
}
#endif
+ /* Disable the system-wide tab feature as we only have one window. */
+ if ([ NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:) ]) {
+ /* We use nil instead of NO as withObject requires an id. */
+ [ NSWindow performSelector:@selector(setAllowsAutomaticWindowTabbing:) withObject:nil];
+ }
+
/* Become the front process, important when start from the command line. */
[ [ NSApplication sharedApplication ] activateIgnoringOtherApps:YES ];
diff --git a/src/window.cpp b/src/window.cpp
index f17fccf932..e2ce845426 100644
--- a/src/window.cpp
+++ b/src/window.cpp
@@ -2457,20 +2457,20 @@ static EventState HandleViewportScroll()
}
Point delta;
- if (_settings_client.gui.scroll_mode != VSM_VIEWPORT_RMB_FIXED) {
- delta.x = -_cursor.delta.x;
- delta.y = -_cursor.delta.y;
- } else {
- delta.x = _cursor.delta.x;
- delta.y = _cursor.delta.y;
- }
-
if (scrollwheel_scrolling) {
/* We are using scrollwheels for scrolling */
delta.x = _cursor.h_wheel;
delta.y = _cursor.v_wheel;
_cursor.v_wheel = 0;
_cursor.h_wheel = 0;
+ } else {
+ if (_settings_client.gui.scroll_mode != VSM_VIEWPORT_RMB_FIXED) {
+ delta.x = -_cursor.delta.x;
+ delta.y = -_cursor.delta.y;
+ } else {
+ delta.x = _cursor.delta.x;
+ delta.y = _cursor.delta.y;
+ }
}
/* Create a scroll-event and send it to the window */
@@ -2866,7 +2866,12 @@ static void MouseLoop(MouseClick click, int mousewheel)
}
if (vp != NULL) {
- if (scrollwheel_scrolling) click = MC_RIGHT; // we are using the scrollwheel in a viewport, so we emulate right mouse button
+ if (scrollwheel_scrolling && !(w->flags & WF_DISABLE_VP_SCROLL)) {
+ _scrolling_viewport = true;
+ _cursor.fix_at = true;
+ return;
+ }
+
switch (click) {
case MC_DOUBLE_LEFT:
case MC_LEFT:
@@ -2885,10 +2890,6 @@ static void MouseLoop(MouseClick click, int mousewheel)
_scrolling_viewport = true;
_cursor.fix_at = (_settings_client.gui.scroll_mode == VSM_VIEWPORT_RMB_FIXED ||
_settings_client.gui.scroll_mode == VSM_MAP_RMB_FIXED);
-
- /* clear 2D scrolling caches before we start a 2D scroll */
- _cursor.h_wheel = 0;
- _cursor.v_wheel = 0;
return;
}
break;
@@ -2903,7 +2904,7 @@ static void MouseLoop(MouseClick click, int mousewheel)
case MC_LEFT:
case MC_DOUBLE_LEFT:
DispatchLeftClickEvent(w, x - w->left, y - w->top, click == MC_DOUBLE_LEFT ? 2 : 1);
- break;
+ return;
default:
if (!scrollwheel_scrolling || w == NULL || w->window_class != WC_SMALLMAP) break;
@@ -2911,11 +2912,19 @@ static void MouseLoop(MouseClick click, int mousewheel)
* Simulate a right button click so we can get started. */
FALLTHROUGH;
- case MC_RIGHT: DispatchRightClickEvent(w, x - w->left, y - w->top); break;
+ case MC_RIGHT:
+ DispatchRightClickEvent(w, x - w->left, y - w->top);
+ return;
- case MC_HOVER: DispatchHoverEvent(w, x - w->left, y - w->top); break;
+ case MC_HOVER:
+ DispatchHoverEvent(w, x - w->left, y - w->top);
+ break;
}
}
+
+ /* We're not doing anything with 2D scrolling, so reset the value. */
+ _cursor.h_wheel = 0;
+ _cursor.v_wheel = 0;
}
/**