1 /* $Id: dmusic.cpp 25269 2013-05-20 13:35:41Z michi_cc $ */
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
10 /** @file dmusic.cpp Playing music via DirectMusic. */
12 #ifdef WIN32_ENABLE_DIRECTMUSIC_SUPPORT
15 #include "../stdafx.h"
16 #ifdef WIN32_LEAN_AND_MEAN
17 #undef WIN32_LEAN_AND_MEAN // Don't exclude rarely-used stuff from Windows headers
20 #include "../os/windows/win32.h"
21 #include "../core/mem_func.hpp"
25 #undef FACILITY_DIRECTMUSIC // Needed for newer Windows SDK version.
31 #include "../safeguards.h"
33 static FMusicDriver_DMusic iFMusicDriver_DMusic
;
35 /** the direct music object manages buffers and ports */
36 static IDirectMusic
*music
= NULL
;
38 /** the performance object controls manipulation of the segments */
39 static IDirectMusicPerformance
*performance
= NULL
;
41 /** the loader object can load many types of DMusic related files */
42 static IDirectMusicLoader
*loader
= NULL
;
44 /** the segment object is where the MIDI data is stored for playback */
45 static IDirectMusicSegment
*segment
= NULL
;
47 static bool seeking
= false;
51 static const char ole_files
[] =
61 unsigned long (WINAPI
* CoCreateInstance
)(REFCLSID rclsid
, LPUNKNOWN pUnkOuter
, DWORD dwClsContext
, REFIID riid
, LPVOID
*ppv
);
62 HRESULT (WINAPI
* CoInitialize
)(LPVOID pvReserved
);
63 void (WINAPI
* CoUninitialize
)();
69 const char *MusicDriver_DMusic::Start(const char * const *parm
)
71 if (performance
!= NULL
) return NULL
;
73 if (proc
.CoCreateInstance
== NULL
) {
74 if (!LoadLibraryList((Function
*)&proc
, ole_files
)) {
75 return "ole32.dll load failed";
80 if (FAILED(proc
.CoInitialize(NULL
))) {
81 return "COM initialization failed";
84 /* create the performance object */
85 if (FAILED(proc
.CoCreateInstance(
86 CLSID_DirectMusicPerformance
,
89 IID_IDirectMusicPerformance
,
92 return "Failed to create the performance object";
96 if (FAILED(performance
->Init(&music
, NULL
, NULL
))) {
97 return "Failed to initialize performance object";
100 int port
= GetDriverParamInt(parm
, "port", -1);
102 #ifndef NO_DEBUG_MESSAGES
103 if (_debug_driver_level
> 0) {
104 /* Print all valid output ports. */
105 char desc
[DMUS_MAX_DESCRIPTION
];
109 caps
.dwSize
= sizeof(DMUS_PORTCAPS
);
111 DEBUG(driver
, 1, "Detected DirectMusic ports:");
112 for (int i
= 0; music
->EnumPort(i
, &caps
) == S_OK
; i
++) {
113 if (caps
.dwClass
== DMUS_PC_OUTPUTCLASS
) {
114 /* Description is UNICODE even for ANSI build. */
115 DEBUG(driver
, 1, " %d: %s%s", i
, convert_from_fs(caps
.wszDescription
, desc
, lengthof(desc
)), i
== port
? " (selected)" : "");
121 IDirectMusicPort
*music_port
= NULL
; // NULL means 'use default port'.
124 /* Check if the passed port is a valid port. */
127 caps
.dwSize
= sizeof(DMUS_PORTCAPS
);
128 if (FAILED(music
->EnumPort(port
, &caps
))) return "Supplied port parameter is not a valid port";
129 if (caps
.dwClass
!= DMUS_PC_OUTPUTCLASS
) return "Supplied port parameter is not an output port";
131 /* Create new port. */
132 DMUS_PORTPARAMS params
;
134 params
.dwSize
= sizeof(DMUS_PORTPARAMS
);
135 params
.dwValidParams
= DMUS_PORTPARAMS_CHANNELGROUPS
;
136 params
.dwChannelGroups
= 1;
138 if (FAILED(music
->CreatePort(caps
.guidPort
, ¶ms
, &music_port
, NULL
))) {
139 return "Failed to create port";
143 if (FAILED(music_port
->Activate(TRUE
))) {
144 music_port
->Release();
145 return "Failed to activate port";
149 /* Add port to performance. */
150 if (FAILED(performance
->AddPort(music_port
))) {
151 if (music_port
!= NULL
) music_port
->Release();
152 return "AddPort failed";
155 /* Assign a performance channel block to the performance if we added
156 * a custom port to the performance. */
157 if (music_port
!= NULL
) {
158 if (FAILED(performance
->AssignPChannelBlock(0, music_port
, 1))) {
159 music_port
->Release();
160 return "Failed to assign PChannel block";
162 /* We don't need the port anymore. */
163 music_port
->Release();
166 /* create the loader object; this will be used to load the MIDI file */
167 if (FAILED(proc
.CoCreateInstance(
168 CLSID_DirectMusicLoader
,
171 IID_IDirectMusicLoader
,
174 return "Failed to create loader object";
181 MusicDriver_DMusic::~MusicDriver_DMusic()
187 void MusicDriver_DMusic::Stop()
191 if (performance
!= NULL
) performance
->Stop(NULL
, NULL
, 0, 0);
193 if (segment
!= NULL
) {
194 segment
->SetParam(GUID_Unload
, 0xFFFFFFFF, 0, 0, performance
);
204 if (performance
!= NULL
) {
205 performance
->CloseDown();
206 performance
->Release();
210 if (loader
!= NULL
) {
215 proc
.CoUninitialize();
219 void MusicDriver_DMusic::PlaySong(const char *filename
)
221 /* set up the loader object info */
222 DMUS_OBJECTDESC obj_desc
;
223 ZeroMemory(&obj_desc
, sizeof(obj_desc
));
224 obj_desc
.dwSize
= sizeof(obj_desc
);
225 obj_desc
.guidClass
= CLSID_DirectMusicSegment
;
226 obj_desc
.dwValidData
= DMUS_OBJ_CLASS
| DMUS_OBJ_FILENAME
| DMUS_OBJ_FULLPATH
;
228 CP_ACP
, MB_PRECOMPOSED
,
230 obj_desc
.wszFileName
, lengthof(obj_desc
.wszFileName
)
233 /* release the existing segment if we have any */
234 if (segment
!= NULL
) {
239 /* make a new segment */
240 if (FAILED(loader
->GetObject(
241 &obj_desc
, IID_IDirectMusicSegment
, (LPVOID
*)&segment
243 DEBUG(driver
, 0, "DirectMusic: GetObject failed");
247 /* tell the segment what kind of data it contains */
248 if (FAILED(segment
->SetParam(
249 GUID_StandardMIDIFile
, 0xFFFFFFFF, 0, 0, performance
251 DEBUG(driver
, 0, "DirectMusic: SetParam (MIDI file) failed");
255 /* tell the segment to 'download' the instruments */
256 if (FAILED(segment
->SetParam(GUID_Download
, 0xFFFFFFFF, 0, 0, performance
))) {
257 DEBUG(driver
, 0, "DirectMusic: failed to download instruments");
261 /* start playing the MIDI file */
262 if (FAILED(performance
->PlaySegment(segment
, 0, 0, NULL
))) {
263 DEBUG(driver
, 0, "DirectMusic: PlaySegment failed");
271 void MusicDriver_DMusic::StopSong()
273 if (FAILED(performance
->Stop(segment
, NULL
, 0, 0))) {
274 DEBUG(driver
, 0, "DirectMusic: StopSegment failed");
280 bool MusicDriver_DMusic::IsSongPlaying()
282 /* Not the nicest code, but there is a short delay before playing actually
283 * starts. OpenTTD makes no provision for this. */
284 if (performance
->IsPlaying(segment
, NULL
) == S_OK
) {
293 void MusicDriver_DMusic::SetVolume(byte vol
)
295 long db
= vol
* 2000 / 127 - 2000; ///< 0 - 127 -> -2000 - 0
296 performance
->SetGlobalParam(GUID_PerfMasterVolume
, &db
, sizeof(db
));
300 #endif /* WIN32_ENABLE_DIRECTMUSIC_SUPPORT */