OpenTTD
dmusic.cpp
Go to the documentation of this file.
1 /* $Id: dmusic.cpp 27380 2015-08-10 20:21:29Z michi_cc $ */
2 
3 /*
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/>.
8  */
9 
12 #ifdef WIN32_ENABLE_DIRECTMUSIC_SUPPORT
13 
14 #define INITGUID
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
18 #endif
19 #include "../debug.h"
20 #include "../os/windows/win32.h"
21 #include "../core/mem_func.hpp"
22 #include "dmusic.h"
23 
24 #include <windows.h>
25 #undef FACILITY_DIRECTMUSIC // Needed for newer Windows SDK version.
26 #include <dmksctrl.h>
27 #include <dmusici.h>
28 #include <dmusicc.h>
29 #include <dmusicf.h>
30 
31 #include "../safeguards.h"
32 
33 static FMusicDriver_DMusic iFMusicDriver_DMusic;
34 
36 static IDirectMusic *music = NULL;
37 
39 static IDirectMusicPerformance *performance = NULL;
40 
42 static IDirectMusicLoader *loader = NULL;
43 
45 static IDirectMusicSegment *segment = NULL;
46 
47 static bool seeking = false;
48 
49 
50 #define M(x) x "\0"
51 static const char ole_files[] =
52  M("ole32.dll")
53  M("CoCreateInstance")
54  M("CoInitialize")
55  M("CoUninitialize")
56  M("")
57 ;
58 #undef M
59 
60 struct ProcPtrs {
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)();
64 };
65 
66 static ProcPtrs proc;
67 
68 
69 const char *MusicDriver_DMusic::Start(const char * const *parm)
70 {
71  if (performance != NULL) return NULL;
72 
73  if (proc.CoCreateInstance == NULL) {
74  if (!LoadLibraryList((Function*)&proc, ole_files)) {
75  return "ole32.dll load failed";
76  }
77  }
78 
79  /* Initialize COM */
80  if (FAILED(proc.CoInitialize(NULL))) {
81  return "COM initialization failed";
82  }
83 
84  /* create the performance object */
85  if (FAILED(proc.CoCreateInstance(
86  CLSID_DirectMusicPerformance,
87  NULL,
88  CLSCTX_INPROC,
89  IID_IDirectMusicPerformance,
90  (LPVOID*)&performance
91  ))) {
92  return "Failed to create the performance object";
93  }
94 
95  /* initialize it */
96  if (FAILED(performance->Init(&music, NULL, NULL))) {
97  return "Failed to initialize performance object";
98  }
99 
100  int port = GetDriverParamInt(parm, "port", -1);
101 
102 #ifndef NO_DEBUG_MESSAGES
103  if (_debug_driver_level > 0) {
104  /* Print all valid output ports. */
105  char desc[DMUS_MAX_DESCRIPTION];
106 
107  DMUS_PORTCAPS caps;
108  MemSetT(&caps, 0);
109  caps.dwSize = sizeof(DMUS_PORTCAPS);
110 
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)" : "");
116  }
117  }
118  }
119 #endif
120 
121  IDirectMusicPort *music_port = NULL; // NULL means 'use default port'.
122 
123  if (port >= 0) {
124  /* Check if the passed port is a valid port. */
125  DMUS_PORTCAPS caps;
126  MemSetT(&caps, 0);
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";
130 
131  /* Create new port. */
132  DMUS_PORTPARAMS params;
133  MemSetT(&params, 0);
134  params.dwSize = sizeof(DMUS_PORTPARAMS);
135  params.dwValidParams = DMUS_PORTPARAMS_CHANNELGROUPS;
136  params.dwChannelGroups = 1;
137 
138  if (FAILED(music->CreatePort(caps.guidPort, &params, &music_port, NULL))) {
139  return "Failed to create port";
140  }
141 
142  /* Activate port. */
143  if (FAILED(music_port->Activate(TRUE))) {
144  music_port->Release();
145  return "Failed to activate port";
146  }
147  }
148 
149  /* Add port to performance. */
150  if (FAILED(performance->AddPort(music_port))) {
151  if (music_port != NULL) music_port->Release();
152  return "AddPort failed";
153  }
154 
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";
161  }
162  /* We don't need the port anymore. */
163  music_port->Release();
164  }
165 
166  /* create the loader object; this will be used to load the MIDI file */
167  if (FAILED(proc.CoCreateInstance(
168  CLSID_DirectMusicLoader,
169  NULL,
170  CLSCTX_INPROC,
171  IID_IDirectMusicLoader,
172  (LPVOID*)&loader
173  ))) {
174  return "Failed to create loader object";
175  }
176 
177  return NULL;
178 }
179 
180 
181 MusicDriver_DMusic::~MusicDriver_DMusic()
182 {
183  this->Stop();
184 }
185 
186 
188 {
189  seeking = false;
190 
191  if (performance != NULL) performance->Stop(NULL, NULL, 0, 0);
192 
193  if (segment != NULL) {
194  segment->SetParam(GUID_Unload, 0xFFFFFFFF, 0, 0, performance);
195  segment->Release();
196  segment = NULL;
197  }
198 
199  if (music != NULL) {
200  music->Release();
201  music = NULL;
202  }
203 
204  if (performance != NULL) {
205  performance->CloseDown();
206  performance->Release();
207  performance = NULL;
208  }
209 
210  if (loader != NULL) {
211  loader->Release();
212  loader = NULL;
213  }
214 
215  proc.CoUninitialize();
216 }
217 
218 
219 void MusicDriver_DMusic::PlaySong(const char *filename)
220 {
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;
227  MultiByteToWideChar(
228  CP_ACP, MB_PRECOMPOSED,
229  filename, -1,
230  obj_desc.wszFileName, lengthof(obj_desc.wszFileName)
231  );
232 
233  /* release the existing segment if we have any */
234  if (segment != NULL) {
235  segment->Release();
236  segment = NULL;
237  }
238 
239  /* make a new segment */
240  if (FAILED(loader->GetObject(
241  &obj_desc, IID_IDirectMusicSegment, (LPVOID*)&segment
242  ))) {
243  DEBUG(driver, 0, "DirectMusic: GetObject failed");
244  return;
245  }
246 
247  /* tell the segment what kind of data it contains */
248  if (FAILED(segment->SetParam(
249  GUID_StandardMIDIFile, 0xFFFFFFFF, 0, 0, performance
250  ))) {
251  DEBUG(driver, 0, "DirectMusic: SetParam (MIDI file) failed");
252  return;
253  }
254 
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");
258  return;
259  }
260 
261  /* start playing the MIDI file */
262  if (FAILED(performance->PlaySegment(segment, 0, 0, NULL))) {
263  DEBUG(driver, 0, "DirectMusic: PlaySegment failed");
264  return;
265  }
266 
267  seeking = true;
268 }
269 
270 
272 {
273  if (FAILED(performance->Stop(segment, NULL, 0, 0))) {
274  DEBUG(driver, 0, "DirectMusic: StopSegment failed");
275  }
276  seeking = false;
277 }
278 
279 
281 {
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) {
285  seeking = false;
286  return true;
287  } else {
288  return seeking;
289  }
290 }
291 
292 
293 void MusicDriver_DMusic::SetVolume(byte vol)
294 {
295  long db = vol * 2000 / 127 - 2000;
296  performance->SetGlobalParam(GUID_PerfMasterVolume, &db, sizeof(db));
297 }
298 
299 
300 #endif /* WIN32_ENABLE_DIRECTMUSIC_SUPPORT */