OpenTTD
dmusic.cpp
Go to the documentation of this file.
1 /* $Id: dmusic.cpp 26482 2014-04-23 20:13:33Z rubidium $ */
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 #include <dmksctrl.h>
26 #include <dmusici.h>
27 #include <dmusicc.h>
28 #include <dmusicf.h>
29 
30 #include "../safeguards.h"
31 
32 static FMusicDriver_DMusic iFMusicDriver_DMusic;
33 
35 static IDirectMusic *music = NULL;
36 
38 static IDirectMusicPerformance *performance = NULL;
39 
41 static IDirectMusicLoader *loader = NULL;
42 
44 static IDirectMusicSegment *segment = NULL;
45 
46 static bool seeking = false;
47 
48 
49 #define M(x) x "\0"
50 static const char ole_files[] =
51  M("ole32.dll")
52  M("CoCreateInstance")
53  M("CoInitialize")
54  M("CoUninitialize")
55  M("")
56 ;
57 #undef M
58 
59 struct ProcPtrs {
60  unsigned long (WINAPI * CoCreateInstance)(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv);
61  HRESULT (WINAPI * CoInitialize)(LPVOID pvReserved);
62  void (WINAPI * CoUninitialize)();
63 };
64 
65 static ProcPtrs proc;
66 
67 
68 const char *MusicDriver_DMusic::Start(const char * const *parm)
69 {
70  if (performance != NULL) return NULL;
71 
72  if (proc.CoCreateInstance == NULL) {
73  if (!LoadLibraryList((Function*)&proc, ole_files)) {
74  return "ole32.dll load failed";
75  }
76  }
77 
78  /* Initialize COM */
79  if (FAILED(proc.CoInitialize(NULL))) {
80  return "COM initialization failed";
81  }
82 
83  /* create the performance object */
84  if (FAILED(proc.CoCreateInstance(
85  CLSID_DirectMusicPerformance,
86  NULL,
87  CLSCTX_INPROC,
88  IID_IDirectMusicPerformance,
89  (LPVOID*)&performance
90  ))) {
91  return "Failed to create the performance object";
92  }
93 
94  /* initialize it */
95  if (FAILED(performance->Init(&music, NULL, NULL))) {
96  return "Failed to initialize performance object";
97  }
98 
99  int port = GetDriverParamInt(parm, "port", -1);
100 
101 #ifndef NO_DEBUG_MESSAGES
102  if (_debug_driver_level > 0) {
103  /* Print all valid output ports. */
104  char desc[DMUS_MAX_DESCRIPTION];
105 
106  DMUS_PORTCAPS caps;
107  MemSetT(&caps, 0);
108  caps.dwSize = sizeof(DMUS_PORTCAPS);
109 
110  DEBUG(driver, 1, "Detected DirectMusic ports:");
111  for (int i = 0; music->EnumPort(i, &caps) == S_OK; i++) {
112  if (caps.dwClass == DMUS_PC_OUTPUTCLASS) {
113  /* Description is UNICODE even for ANSI build. */
114  DEBUG(driver, 1, " %d: %s%s", i, convert_from_fs(caps.wszDescription, desc, lengthof(desc)), i == port ? " (selected)" : "");
115  }
116  }
117  }
118 #endif
119 
120  IDirectMusicPort *music_port = NULL; // NULL means 'use default port'.
121 
122  if (port >= 0) {
123  /* Check if the passed port is a valid port. */
124  DMUS_PORTCAPS caps;
125  MemSetT(&caps, 0);
126  caps.dwSize = sizeof(DMUS_PORTCAPS);
127  if (FAILED(music->EnumPort(port, &caps))) return "Supplied port parameter is not a valid port";
128  if (caps.dwClass != DMUS_PC_OUTPUTCLASS) return "Supplied port parameter is not an output port";
129 
130  /* Create new port. */
131  DMUS_PORTPARAMS params;
132  MemSetT(&params, 0);
133  params.dwSize = sizeof(DMUS_PORTPARAMS);
134  params.dwValidParams = DMUS_PORTPARAMS_CHANNELGROUPS;
135  params.dwChannelGroups = 1;
136 
137  if (FAILED(music->CreatePort(caps.guidPort, &params, &music_port, NULL))) {
138  return "Failed to create port";
139  }
140 
141  /* Activate port. */
142  if (FAILED(music_port->Activate(TRUE))) {
143  music_port->Release();
144  return "Failed to activate port";
145  }
146  }
147 
148  /* Add port to performance. */
149  if (FAILED(performance->AddPort(music_port))) {
150  if (music_port != NULL) music_port->Release();
151  return "AddPort failed";
152  }
153 
154  /* Assign a performance channel block to the performance if we added
155  * a custom port to the performance. */
156  if (music_port != NULL) {
157  if (FAILED(performance->AssignPChannelBlock(0, music_port, 1))) {
158  music_port->Release();
159  return "Failed to assign PChannel block";
160  }
161  /* We don't need the port anymore. */
162  music_port->Release();
163  }
164 
165  /* create the loader object; this will be used to load the MIDI file */
166  if (FAILED(proc.CoCreateInstance(
167  CLSID_DirectMusicLoader,
168  NULL,
169  CLSCTX_INPROC,
170  IID_IDirectMusicLoader,
171  (LPVOID*)&loader
172  ))) {
173  return "Failed to create loader object";
174  }
175 
176  return NULL;
177 }
178 
179 
180 MusicDriver_DMusic::~MusicDriver_DMusic()
181 {
182  this->Stop();
183 }
184 
185 
187 {
188  seeking = false;
189 
190  if (performance != NULL) performance->Stop(NULL, NULL, 0, 0);
191 
192  if (segment != NULL) {
193  segment->SetParam(GUID_Unload, 0xFFFFFFFF, 0, 0, performance);
194  segment->Release();
195  segment = NULL;
196  }
197 
198  if (music != NULL) {
199  music->Release();
200  music = NULL;
201  }
202 
203  if (performance != NULL) {
204  performance->CloseDown();
205  performance->Release();
206  performance = NULL;
207  }
208 
209  if (loader != NULL) {
210  loader->Release();
211  loader = NULL;
212  }
213 
214  proc.CoUninitialize();
215 }
216 
217 
218 void MusicDriver_DMusic::PlaySong(const char *filename)
219 {
220  /* set up the loader object info */
221  DMUS_OBJECTDESC obj_desc;
222  ZeroMemory(&obj_desc, sizeof(obj_desc));
223  obj_desc.dwSize = sizeof(obj_desc);
224  obj_desc.guidClass = CLSID_DirectMusicSegment;
225  obj_desc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME | DMUS_OBJ_FULLPATH;
226  MultiByteToWideChar(
227  CP_ACP, MB_PRECOMPOSED,
228  filename, -1,
229  obj_desc.wszFileName, lengthof(obj_desc.wszFileName)
230  );
231 
232  /* release the existing segment if we have any */
233  if (segment != NULL) {
234  segment->Release();
235  segment = NULL;
236  }
237 
238  /* make a new segment */
239  if (FAILED(loader->GetObject(
240  &obj_desc, IID_IDirectMusicSegment, (LPVOID*)&segment
241  ))) {
242  DEBUG(driver, 0, "DirectMusic: GetObject failed");
243  return;
244  }
245 
246  /* tell the segment what kind of data it contains */
247  if (FAILED(segment->SetParam(
248  GUID_StandardMIDIFile, 0xFFFFFFFF, 0, 0, performance
249  ))) {
250  DEBUG(driver, 0, "DirectMusic: SetParam (MIDI file) failed");
251  return;
252  }
253 
254  /* tell the segment to 'download' the instruments */
255  if (FAILED(segment->SetParam(GUID_Download, 0xFFFFFFFF, 0, 0, performance))) {
256  DEBUG(driver, 0, "DirectMusic: failed to download instruments");
257  return;
258  }
259 
260  /* start playing the MIDI file */
261  if (FAILED(performance->PlaySegment(segment, 0, 0, NULL))) {
262  DEBUG(driver, 0, "DirectMusic: PlaySegment failed");
263  return;
264  }
265 
266  seeking = true;
267 }
268 
269 
271 {
272  if (FAILED(performance->Stop(segment, NULL, 0, 0))) {
273  DEBUG(driver, 0, "DirectMusic: StopSegment failed");
274  }
275  seeking = false;
276 }
277 
278 
280 {
281  /* Not the nicest code, but there is a short delay before playing actually
282  * starts. OpenTTD makes no provision for this. */
283  if (performance->IsPlaying(segment, NULL) == S_OK) {
284  seeking = false;
285  return true;
286  } else {
287  return seeking;
288  }
289 }
290 
291 
292 void MusicDriver_DMusic::SetVolume(byte vol)
293 {
294  long db = vol * 2000 / 127 - 2000;
295  performance->SetGlobalParam(GUID_PerfMasterVolume, &db, sizeof(db));
296 }
297 
298 
299 #endif /* WIN32_ENABLE_DIRECTMUSIC_SUPPORT */