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         dsize_t deviceNameLen = dStrlen(str) + 1;
 88         device->mDeviceName = new char[deviceNameLen];
 89         dStrcpy(device->mDeviceName, str, deviceNameLen);
 90
 91         RedBook::installDevice(device);
 92      }
 93      str += dStrlen(str) + 1;
 94   }
 95
 96   delete [] buf;
 97}
 98
 99void handleRedBookCallback(U32 code, U32 deviceId)
100{
101   if(code != MCI_NOTIFY_SUCCESSFUL)
102      return;
103
104   Win32RedBookDevice * device = dynamic_cast<Win32RedBookDevice*>(RedBook::getCurrentDevice());
105   if(!device)
106      return;
107
108   if(device->getDeviceId() != deviceId)
109      return;
110
111   // only installed callback on play (no callback if play is aborted)
112   RedBook::handleCallback(RedBook::PlayFinished);
113}
114
115//------------------------------------------------------------------------------
116// Class: Win32RedBookDevice
117//------------------------------------------------------------------------------
118Win32RedBookDevice::Win32RedBookDevice()
119{
120   mVolumeInitialized = false;
121}
122
123Win32RedBookDevice::~Win32RedBookDevice()
124{
125   close();
126}
127
128U32 Win32RedBookDevice::getDeviceId()
129{
130   return(mDeviceId);
131}
132
133bool Win32RedBookDevice::open()
134{
135   if(mAcquired)
136   {
137      setLastError("Device is already open.");
138      return(false);
139   }
140
141   U32 error;
142
143   // open the device
144   MCI_OPEN_PARMS openParms;
145#ifdef UNICODE
146   openParms.lpstrDeviceType = (LPCWSTR)MCI_DEVTYPE_CD_AUDIO;
147
148   UTF16 buf[512];
149   convertUTF8toUTF16((UTF8 *)mDeviceName, buf);
150   openParms.lpstrElementName = buf;
151#else
152   openParms.lpstrDeviceType = (LPCSTR)MCI_DEVTYPE_CD_AUDIO;
153   openParms.lpstrElementName = mDeviceName;
154#endif
155
156   error = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID, (DWORD_PTR)(LPMCI_OPEN_PARMS)&openParms);
157   if(error)
158   {
159      // attempt to open as a shared device
160      error = mciSendCommand(NULL, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID|MCI_OPEN_SHAREABLE, (DWORD_PTR)(LPMCI_OPEN_PARMS)&openParms);
161      if(error)
162      {
163         setLastError(error);
164         return(false);
165      }
166   }
167
168   // set time mode to milliseconds
169   MCI_SET_PARMS setParms;
170   setParms.dwTimeFormat = MCI_FORMAT_MILLISECONDS;
171
172   error = mciSendCommand(openParms.wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)(LPMCI_SET_PARMS)&setParms);
173   if(error)
174   {
175      setLastError(error);
176      return(false);
177   }
178
179   //
180   mDeviceId = openParms.wDeviceID;
181   mAcquired = true;
182
183   openVolume();
184   setLastError("");
185   return(true);
186}
187
188bool Win32RedBookDevice::close()
189{
190   if(!mAcquired)
191   {
192      setLastError("Device has not been acquired");
193      return(false);
194   }
195
196   stop();
197
198   U32 error;
199
200   MCI_GENERIC_PARMS closeParms;
201   error = mciSendCommand(mDeviceId, MCI_CLOSE, 0, (DWORD_PTR)(LPMCI_GENERIC_PARMS)&closeParms);
202   if(error)
203   {
204      setLastError(error);
205      return(false);
206   }
207
208   mAcquired = false;
209   closeVolume();
210   setLastError("");
211   return(true);
212}
213
214bool Win32RedBookDevice::play(U32 track)
215{
216   if(!mAcquired)
217   {
218      setLastError("Device has not been acquired");
219      return(false);
220   }
221
222   U32 numTracks;
223   if(!getTrackCount(&numTracks))
224      return(false);
225
226   if(track >= numTracks)
227   {
228      setLastError("Track index is out of range");
229      return(false);
230   }
231
232   MCI_STATUS_PARMS statusParms;
233
234   // get track start time
235   statusParms.dwItem = MCI_STATUS_POSITION;
236   statusParms.dwTrack = track + 1;
237
238   U32 error;
239   error = mciSendCommand(mDeviceId, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK|MCI_WAIT,
240      (DWORD_PTR)(LPMCI_STATUS_PARMS)&statusParms);
241
242   if(error)
243   {
244      setLastError(error);
245      return(false);
246   }
247
248   MCI_PLAY_PARMS playParms;
249   playParms.dwFrom = statusParms.dwReturn;
250
251   // get track end time
252   statusParms.dwItem = MCI_STATUS_LENGTH;
253   error = mciSendCommand(mDeviceId, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK|MCI_WAIT,
254      (DWORD_PTR)(LPMCI_STATUS_PARMS)&statusParms);
255
256   if(error)
257   {
258      setLastError(error);
259      return(false);
260   }
261
262   playParms.dwTo = playParms.dwFrom + statusParms.dwReturn;
263
264   // play the track
265   playParms.dwCallback = MAKELONG(getWin32WindowHandle(), 0);
266   error = mciSendCommand(mDeviceId, MCI_PLAY, MCI_FROM|MCI_TO|MCI_NOTIFY,
267      (DWORD_PTR)(LPMCI_PLAY_PARMS)&playParms);
268
269   if(error)
270   {
271      setLastError(error);
272      return(false);
273   }
274
275   setLastError("");
276   return(true);
277}
278
279bool Win32RedBookDevice::stop()
280{
281   if(!mAcquired)
282   {
283      setLastError("Device has not been acquired");
284      return(false);
285   }
286
287   MCI_GENERIC_PARMS genParms;
288
289   U32 error = mciSendCommand(mDeviceId, MCI_STOP, 0, (DWORD_PTR)(LPMCI_GENERIC_PARMS)&genParms);
290   if(error)
291   {
292      setLastError(error);
293      return(false);
294   }
295
296   setLastError("");
297   return(true);
298}
299
300bool Win32RedBookDevice::getTrackCount(U32 * numTracks)
301{
302   if(!mAcquired)
303   {
304      setLastError("Device has not been acquired");
305      return(false);
306   }
307
308   MCI_STATUS_PARMS statusParms;
309
310   statusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
311   U32 error = mciSendCommand(mDeviceId, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD_PTR)(LPMCI_STATUS_PARMS)&statusParms);
312   if(error)
313   {
314      setLastError(error);
315      return(false);
316   }
317
318   *numTracks = statusParms.dwReturn;
319   return(true);
320}
321
322bool Win32RedBookDevice::getVolume(F32 * volume)
323{
324   if(!mAcquired)
325   {
326      setLastError("Device has not been acquired");
327      return(false);
328   }
329
330   if(!mVolumeInitialized)
331   {
332      setLastError("Volume failed to initialize");
333      return(false);
334   }
335
336   U32 vol = 0;
337   if(mUsingMixer)
338   {
339      mixerGetControlDetails(mVolumeDeviceId, &mMixerVolumeDetails, MIXER_GETCONTROLDETAILSF_VALUE);
340      vol = mMixerVolumeValue.dwValue;
341   }
342   else
343      auxGetVolume(mAuxVolumeDeviceId, (unsigned long *)&vol);
344
345   vol &= 0xffff;
346   *volume = F32(vol) / 65535.f;
347
348   setLastError("");
349   return(true);
350}
351
352bool Win32RedBookDevice::setVolume(F32 volume)
353{
354   if(!mAcquired)
355   {
356      setLastError("Device has not been acquired");
357      return(false);
358   }
359
360   if(!mVolumeInitialized)
361   {
362      setLastError("Volume failed to initialize");
363      return(false);
364   }
365
366   // move into a U32 - left/right U16 volumes
367   U32 vol = U32(volume * 65536.f);
368   if(vol > 0xffff)
369      vol = 0xffff;
370
371   if(mUsingMixer)
372   {
373      mMixerVolumeValue.dwValue = vol;
374      mixerSetControlDetails(mVolumeDeviceId, &mMixerVolumeDetails, MIXER_SETCONTROLDETAILSF_VALUE);
375   }
376   else
377   {
378      vol |= vol << 16;
379      auxSetVolume(mAuxVolumeDeviceId, vol);
380   }
381
382   setLastError("");
383   return(true);
384}
385
386//------------------------------------------------------------------------------
387
388void Win32RedBookDevice::openVolume()
389{
390   setLastError("");
391
392   // first attempt to get the volume control through the mixer API
393   S32 i;
394   for(i = mixerGetNumDevs() - 1; i >= 0; i--)
395   {
396      // open the mixer
397      if(mixerOpen((HMIXER*)&mVolumeDeviceId, i, 0, 0, 0) == MMSYSERR_NOERROR)
398      {
399         MIXERLINE lineInfo;
400         memset(&lineInfo, 0, sizeof(lineInfo));
401         lineInfo.cbStruct = sizeof(lineInfo);
402         lineInfo.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC;
403
404         // get the cdaudio line
405         if(mixerGetLineInfo(mVolumeDeviceId, &lineInfo, MIXER_GETLINEINFOF_COMPONENTTYPE) == MMSYSERR_NOERROR)
406         {
407            MIXERLINECONTROLS lineControls;
408            MIXERCONTROL volumeControl;
409
410            memset(&lineControls, 0, sizeof(lineControls));
411            lineControls.cbStruct = sizeof(lineControls);
412            lineControls.dwLineID = lineInfo.dwLineID;
413            lineControls.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
414            lineControls.cControls = 1;
415            lineControls.cbmxctrl = sizeof(volumeControl);
416            lineControls.pamxctrl = &volumeControl;
417
418            memset(&volumeControl, 0, sizeof(volumeControl));
419            volumeControl.cbStruct = sizeof(volumeControl);
420
421            // get the volume control
422            if(mixerGetLineControls(mVolumeDeviceId, &lineControls, MIXER_GETLINECONTROLSF_ONEBYTYPE) == MMSYSERR_NOERROR)
423            {
424               memset(&mMixerVolumeDetails, 0, sizeof(mMixerVolumeDetails));
425               mMixerVolumeDetails.cbStruct = sizeof(mMixerVolumeDetails);
426               mMixerVolumeDetails.dwControlID = volumeControl.dwControlID;
427               mMixerVolumeDetails.cChannels = 1;
428               mMixerVolumeDetails.cbDetails = sizeof(mMixerVolumeValue);
429               mMixerVolumeDetails.paDetails = &mMixerVolumeValue;
430
431               memset(&mMixerVolumeValue, 0, sizeof(mMixerVolumeValue));
432
433               // query the current value
434               if(mixerGetControlDetails(mVolumeDeviceId, &mMixerVolumeDetails, MIXER_GETCONTROLDETAILSF_VALUE) == MMSYSERR_NOERROR)
435               {
436                  mUsingMixer = true;
437                  mVolumeInitialized = true;
438                  mOriginalVolume = mMixerVolumeValue.dwValue;
439                  return;
440               }
441            }
442         }
443      }
444
445      mixerClose((HMIXER)mVolumeDeviceId);
446   }
447
448   // try aux
449   for(i = auxGetNumDevs() - 1; i >= 0; i--)
450   {
451      AUXCAPS caps;
452      auxGetDevCaps(i, &caps, sizeof(AUXCAPS));
453      if((caps.wTechnology == AUXCAPS_CDAUDIO) && (caps.dwSupport & AUXCAPS_VOLUME))
454      {
455         mAuxVolumeDeviceId = i;
456         mVolumeInitialized = true;
457         mUsingMixer = false;
458         auxGetVolume(i, (unsigned long *)&mOriginalVolume);
459         return;
460      }
461   }
462
463   setLastError("Volume failed to initialize");
464}
465
466void Win32RedBookDevice::closeVolume()
467{
468   setLastError("");
469   if(!mVolumeInitialized)
470      return;
471
472   if(mUsingMixer)
473   {
474      mMixerVolumeValue.dwValue = mOriginalVolume;
475      mixerSetControlDetails(mVolumeDeviceId, &mMixerVolumeDetails, MIXER_SETCONTROLDETAILSF_VALUE);
476      mixerClose((HMIXER)mVolumeDeviceId);
477   }
478   else
479      auxSetVolume(mAuxVolumeDeviceId, mOriginalVolume);
480
481   mVolumeInitialized = false;
482}
483
484//------------------------------------------------------------------------------
485
486void Win32RedBookDevice::setLastError(const char * error)
487{
488   RedBook::setLastError(error);
489}
490
491void Win32RedBookDevice::setLastError(U32 errorId)
492{
493   char buffer[256];
494   if(!mciGetErrorStringA(errorId, buffer, sizeof(buffer) - 1))
495      setLastError("Failed to get MCI error string!");
496   else
497      setLastError(buffer);
498}
499
500