winRedbook.cpp

Engine/source/platformWin32/winRedbook.cpp

More...

Classes:

Public Functions

Detailed Description

Public Functions

handleRedBookCallback(U32 code, U32 deviceId)

installRedBookDevices()

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#include "platformWin32/platformWin32.h"
 25#include "platform/platformRedBook.h"
 26#include "core/strings/unicode.h"
 27#include "core/strings/stringFunctions.h"
 28
 29class Win32RedBookDevice : public RedBookDevice
 30{
 31   private:
 32      typedef RedBookDevice   Parent;
 33
 34      U32         mDeviceId;
 35
 36      void setLastError(const char *);
 37      void setLastError(U32);
 38
 39      MIXERCONTROLDETAILS           mMixerVolumeDetails;
 40      MIXERCONTROLDETAILS_UNSIGNED  mMixerVolumeValue;
 41
 42     union {
 43      HMIXEROBJ mVolumeDeviceId;
 44      UINT mAuxVolumeDeviceId;
 45     };
 46
 47      U32   mOriginalVolume;
 48      bool  mVolumeInitialized;
 49
 50      bool  mUsingMixer;
 51
 52      void openVolume();
 53      void closeVolume();
 54
 55   public:
 56      Win32RedBookDevice();
 57      ~Win32RedBookDevice();
 58
 59      U32 getDeviceId();
 60
 61      bool open();
 62      bool close();
 63      bool play(U32);
 64      bool stop();
 65      bool getTrackCount(U32 *);
 66      bool getVolume(F32 *);
 67      bool setVolume(F32);
 68};
 69
 70//------------------------------------------------------------------------------
 71// Win32 specific
 72//------------------------------------------------------------------------------
 73void installRedBookDevices()
 74{
 75   U32 bufSize = ::GetLogicalDriveStrings(0,0);
 76
 77   char * buf = new char[bufSize];
 78
 79   ::GetLogicalDriveStringsA(bufSize, buf);
 80
 81   char * str = buf;
 82   while(*str)
 83   {
 84      if(::GetDriveTypeA(str) == DRIVE_CDROM)
 85      {
 86         Win32RedBookDevice * device = new Win32RedBookDevice;
 87         device->mDeviceName = new char[dStrlen(str) + 1];
 88         dStrcpy(device->mDeviceName, str);
 89
 90         RedBook::installDevice(device);
 91      }
 92      str += dStrlen(str) + 1;
 93   }
 94
 95   delete [] buf;
 96}
 97
 98void handleRedBookCallback(U32 code, U32 deviceId)
 99{
100   if(code != MCI_NOTIFY_SUCCESSFUL)
101      return;
102
103   Win32RedBookDevice * device = dynamic_cast<Win32RedBookDevice*>(RedBook::getCurrentDevice());
104   if(!device)
105      return;
106
107   if(device->getDeviceId() != deviceId)
108      return;
109
110   // only installed callback on play (no callback if play is aborted)
111   RedBook::handleCallback(RedBook::PlayFinished);
112}
113
114//------------------------------------------------------------------------------
115// Class: Win32RedBookDevice
116//------------------------------------------------------------------------------
117Win32RedBookDevice::Win32RedBookDevice()
118{
119   mVolumeInitialized = false;
120}
121
122Win32RedBookDevice::~Win32RedBookDevice()
123{
124   close();
125}
126
127U32 Win32RedBookDevice::getDeviceId()
128{
129   return(mDeviceId);
130}
131
132bool Win32RedBookDevice::open()
133{
134   if(mAcquired)
135   {
136      setLastError("Device is already open.");
137      return(false);
138   }
139
140   U32 error;
141
142   // open the device
143   MCI_OPEN_PARMS openParms;
144#ifdef UNICODE
145   openParms.lpstrDeviceType = (LPCWSTR)MCI_DEVTYPE_CD_AUDIO;
146
147   UTF16 buf[512];
148   convertUTF8toUTF16((UTF8 *)mDeviceName, buf);
149   openParms.lpstrElementName = buf;
150#else
151   openParms.lpstrDeviceType = (LPCSTR)MCI_DEVTYPE_CD_AUDIO;
152   openParms.lpstrElementName = mDeviceName;
153#endif
154
155   error = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID, (DWORD_PTR)(LPMCI_OPEN_PARMS)&openParms);
156   if(error)
157   {
158      // attempt to open as a shared device
159      error = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID|MCI_OPEN_SHAREABLE, (DWORD_PTR)(LPMCI_OPEN_PARMS)&openParms);
160      if(error)
161      {
162         setLastError(error);
163         return(false);
164      }
165   }
166
167   // set time mode to milliseconds
168   MCI_SET_PARMS setParms;
169   setParms.dwTimeFormat = MCI_FORMAT_MILLISECONDS;
170
171   error = mciSendCommand(openParms.wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)(LPMCI_SET_PARMS)&setParms);
172   if(error)
173   {
174      setLastError(error);
175      return(false);
176   }
177
178   //
179   mDeviceId = openParms.wDeviceID;
180   mAcquired = true;
181
182   openVolume();
183   setLastError("");
184   return(true);
185}
186
187bool Win32RedBookDevice::close()
188{
189   if(!mAcquired)
190   {
191      setLastError("Device has not been acquired");
192      return(false);
193   }
194
195   stop();
196
197   U32 error;
198
199   MCI_GENERIC_PARMS closeParms;
200   error = mciSendCommand(mDeviceId, MCI_CLOSE, 0, (DWORD_PTR)(LPMCI_GENERIC_PARMS)&closeParms);
201   if(error)
202   {
203      setLastError(error);
204      return(false);
205   }
206
207   mAcquired = false;
208   closeVolume();
209   setLastError("");
210   return(true);
211}
212
213bool Win32RedBookDevice::play(U32 track)
214{
215   if(!mAcquired)
216   {
217      setLastError("Device has not been acquired");
218      return(false);
219   }
220
221   U32 numTracks;
222   if(!getTrackCount(&numTracks))
223      return(false);
224
225   if(track >= numTracks)
226   {
227      setLastError("Track index is out of range");
228      return(false);
229   }
230
231   MCI_STATUS_PARMS statusParms;
232
233   // get track start time
234   statusParms.dwItem = MCI_STATUS_POSITION;
235   statusParms.dwTrack = track + 1;
236
237   U32 error;
238   error = mciSendCommand(mDeviceId, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK|MCI_WAIT,
239      (DWORD_PTR)(LPMCI_STATUS_PARMS)&statusParms);
240
241   if(error)
242   {
243      setLastError(error);
244      return(false);
245   }
246
247   MCI_PLAY_PARMS playParms;
248   playParms.dwFrom = statusParms.dwReturn;
249
250   // get track end time
251   statusParms.dwItem = MCI_STATUS_LENGTH;
252   error = mciSendCommand(mDeviceId, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK|MCI_WAIT,
253      (DWORD_PTR)(LPMCI_STATUS_PARMS)&statusParms);
254
255   if(error)
256   {
257      setLastError(error);
258      return(false);
259   }
260
261   playParms.dwTo = playParms.dwFrom + statusParms.dwReturn;
262
263   // play the track
264   playParms.dwCallback = MAKELONG(getWin32WindowHandle(), 0);
265   error = mciSendCommand(mDeviceId, MCI_PLAY, MCI_FROM|MCI_TO|MCI_NOTIFY,
266      (DWORD_PTR)(LPMCI_PLAY_PARMS)&playParms);
267
268   if(error)
269   {
270      setLastError(error);
271      return(false);
272   }
273
274   setLastError("");
275   return(true);
276}
277
278bool Win32RedBookDevice::stop()
279{
280   if(!mAcquired)
281   {
282      setLastError("Device has not been acquired");
283      return(false);
284   }
285
286   MCI_GENERIC_PARMS genParms;
287
288   U32 error = mciSendCommand(mDeviceId, MCI_STOP, 0, (DWORD_PTR)(LPMCI_GENERIC_PARMS)&genParms);
289   if(error)
290   {
291      setLastError(error);
292      return(false);
293   }
294
295   setLastError("");
296   return(true);
297}
298
299bool Win32RedBookDevice::getTrackCount(U32 * numTracks)
300{
301   if(!mAcquired)
302   {
303      setLastError("Device has not been acquired");
304      return(false);
305   }
306
307   MCI_STATUS_PARMS statusParms;
308
309   statusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
310   U32 error = mciSendCommand(mDeviceId, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD_PTR)(LPMCI_STATUS_PARMS)&statusParms);
311   if(error)
312   {
313      setLastError(error);
314      return(false);
315   }
316
317   *numTracks = statusParms.dwReturn;
318   return(true);
319}
320
321bool Win32RedBookDevice::getVolume(F32 * volume)
322{
323   if(!mAcquired)
324   {
325      setLastError("Device has not been acquired");
326      return(false);
327   }
328
329   if(!mVolumeInitialized)
330   {
331      setLastError("Volume failed to initialize");
332      return(false);
333   }
334
335   U32 vol = 0;
336   if(mUsingMixer)
337   {
338      mixerGetControlDetails(mVolumeDeviceId, &mMixerVolumeDetails, MIXER_GETCONTROLDETAILSF_VALUE);
339      vol = mMixerVolumeValue.dwValue;
340   }
341   else
342      auxGetVolume(mAuxVolumeDeviceId, (unsigned long *)&vol);
343
344   vol &= 0xffff;
345   *volume = F32(vol) / 65535.f;
346
347   setLastError("");
348   return(true);
349}
350
351bool Win32RedBookDevice::setVolume(F32 volume)
352{
353   if(!mAcquired)
354   {
355      setLastError("Device has not been acquired");
356      return(false);
357   }
358
359   if(!mVolumeInitialized)
360   {
361      setLastError("Volume failed to initialize");
362      return(false);
363   }
364
365   // move into a U32 - left/right U16 volumes
366   U32 vol = U32(volume * 65536.f);
367   if(vol > 0xffff)
368      vol = 0xffff;
369
370   if(mUsingMixer)
371   {
372      mMixerVolumeValue.dwValue = vol;
373      mixerSetControlDetails(mVolumeDeviceId, &mMixerVolumeDetails, MIXER_SETCONTROLDETAILSF_VALUE);
374   }
375   else
376   {
377      vol |= vol << 16;
378      auxSetVolume(mAuxVolumeDeviceId, vol);
379   }
380
381   setLastError("");
382   return(true);
383}
384
385//------------------------------------------------------------------------------
386
387void Win32RedBookDevice::openVolume()
388{
389   setLastError("");
390
391   // first attempt to get the volume control through the mixer API
392   S32 i;
393   for(i = mixerGetNumDevs() - 1; i >= 0; i--)
394   {
395      // open the mixer
396      if(mixerOpen((HMIXER*)&mVolumeDeviceId, i, 0, 0, 0) == MMSYSERR_NOERROR)
397      {
398         MIXERLINE lineInfo;
399         memset(&lineInfo, 0, sizeof(lineInfo));
400         lineInfo.cbStruct = sizeof(lineInfo);
401         lineInfo.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC;
402
403         // get the cdaudio line
404         if(mixerGetLineInfo(mVolumeDeviceId, &lineInfo, MIXER_GETLINEINFOF_COMPONENTTYPE) == MMSYSERR_NOERROR)
405         {
406            MIXERLINECONTROLS lineControls;
407            MIXERCONTROL volumeControl;
408
409            memset(&lineControls, 0, sizeof(lineControls));
410            lineControls.cbStruct = sizeof(lineControls);
411            lineControls.dwLineID = lineInfo.dwLineID;
412            lineControls.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
413            lineControls.cControls = 1;
414            lineControls.cbmxctrl = sizeof(volumeControl);
415            lineControls.pamxctrl = &volumeControl;
416
417            memset(&volumeControl, 0, sizeof(volumeControl));
418            volumeControl.cbStruct = sizeof(volumeControl);
419
420            // get the volume control
421            if(mixerGetLineControls(mVolumeDeviceId, &lineControls, MIXER_GETLINECONTROLSF_ONEBYTYPE) == MMSYSERR_NOERROR)
422            {
423               memset(&mMixerVolumeDetails, 0, sizeof(mMixerVolumeDetails));
424               mMixerVolumeDetails.cbStruct = sizeof(mMixerVolumeDetails);
425               mMixerVolumeDetails.dwControlID = volumeControl.dwControlID;
426               mMixerVolumeDetails.cChannels = 1;
427               mMixerVolumeDetails.cbDetails = sizeof(mMixerVolumeValue);
428               mMixerVolumeDetails.paDetails = &mMixerVolumeValue;
429
430               memset(&mMixerVolumeValue, 0, sizeof(mMixerVolumeValue));
431
432               // query the current value
433               if(mixerGetControlDetails(mVolumeDeviceId, &mMixerVolumeDetails, MIXER_GETCONTROLDETAILSF_VALUE) == MMSYSERR_NOERROR)
434               {
435                  mUsingMixer = true;
436                  mVolumeInitialized = true;
437                  mOriginalVolume = mMixerVolumeValue.dwValue;
438                  return;
439               }
440            }
441         }
442      }
443
444      mixerClose((HMIXER)mVolumeDeviceId);
445   }
446
447   // try aux
448   for(i = auxGetNumDevs() - 1; i >= 0; i--)
449   {
450      AUXCAPS caps;
451      auxGetDevCaps(i, &caps, sizeof(AUXCAPS));
452      if((caps.wTechnology == AUXCAPS_CDAUDIO) && (caps.dwSupport & AUXCAPS_VOLUME))
453      {
454         mAuxVolumeDeviceId = i;
455         mVolumeInitialized = true;
456         mUsingMixer = false;
457         auxGetVolume(i, (unsigned long *)&mOriginalVolume);
458         return;
459      }
460   }
461
462   setLastError("Volume failed to initialize");
463}
464
465void Win32RedBookDevice::closeVolume()
466{
467   setLastError("");
468   if(!mVolumeInitialized)
469      return;
470
471   if(mUsingMixer)
472   {
473      mMixerVolumeValue.dwValue = mOriginalVolume;
474      mixerSetControlDetails(mVolumeDeviceId, &mMixerVolumeDetails, MIXER_SETCONTROLDETAILSF_VALUE);
475      mixerClose((HMIXER)mVolumeDeviceId);
476   }
477   else
478      auxSetVolume(mAuxVolumeDeviceId, mOriginalVolume);
479
480   mVolumeInitialized = false;
481}
482
483//------------------------------------------------------------------------------
484
485void Win32RedBookDevice::setLastError(const char * error)
486{
487   RedBook::setLastError(error);
488}
489
490void Win32RedBookDevice::setLastError(U32 errorId)
491{
492   char buffer[256];
493   if(!mciGetErrorStringA(errorId, buffer, sizeof(buffer) - 1))
494      setLastError("Failed to get MCI error string!");
495   else
496      setLastError(buffer);
497}
498
499