#include "pch.h"
#include "soundbase.h"
#include "redbooksound.h"
#include "ds3dutil.h"
using std::list;
namespace SoundEngine {
class CDVolume
{
public:
enum { c_nMaxChannels = 8 };
private:
struct MixerLineData
{
HMIXEROBJ hmixer;
DWORD cChannels;
MIXERCONTROL mixercontrol;
MIXERCONTROLDETAILS_UNSIGNED vmixercontrolsOld[c_nMaxChannels];
};
public:
list<MixerLineData> m_listMixerLines;
CDVolume()
{
UINT uMaxDevices = mixerGetNumDevs();
for (UINT uDeviceID = 0; uDeviceID < uMaxDevices; ++uDeviceID)
{
HMIXEROBJ hmixer;
if (MMSYSERR_NOERROR != mixerOpen((LPHMIXER)&hmixer, uDeviceID, NULL, NULL, MIXER_OBJECTF_MIXER))
{
debugf("Failed to open mixer %d\n", uDeviceID);
continue;
}
MIXERLINE mixerline;
mixerline.cbStruct = sizeof(mixerline);
mixerline.dwComponentType = MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC;
if (MMSYSERR_NOERROR != mixerGetLineInfo(hmixer, &mixerline, MIXER_GETLINEINFOF_COMPONENTTYPE)
|| mixerline.cControls == 0)
{
debugf("Failed to find CD line on mixer %d\n", uDeviceID);
mixerClose((HMIXER)hmixer);
continue;
}
MIXERLINECONTROLS mixerlinecontrols;
MixerLineData mixerlinedata;
mixerlinecontrols.cbStruct = sizeof(mixerlinecontrols);
mixerlinecontrols.dwLineID = mixerline.dwLineID;
mixerlinecontrols.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
mixerlinecontrols.cControls = 1;
mixerlinecontrols.cbmxctrl = sizeof(MIXERCONTROL);
mixerlinecontrols.pamxctrl = &(mixerlinedata.mixercontrol);
mixerlinedata.hmixer = hmixer;
mixerlinedata.cChannels = mixerline.cChannels;
if (MMSYSERR_NOERROR !=
mixerGetLineControls(hmixer, &mixerlinecontrols, MIXER_GETLINECONTROLSF_ONEBYTYPE))
{
debugf("Failed to find CD volume fader on mixer %d\n", uDeviceID);
mixerClose((HMIXER)hmixer);
continue;
}
if (mixerlinedata.cChannels > c_nMaxChannels)
mixerlinedata.cChannels = 1;
MIXERCONTROLDETAILS mixercontroldetails;
mixercontroldetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
mixercontroldetails.dwControlID = mixerlinedata.mixercontrol.dwControlID;
mixercontroldetails.cChannels = mixerlinedata.cChannels;
mixercontroldetails.cMultipleItems = 0;
mixercontroldetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
mixercontroldetails.paDetails = &(mixerlinedata.vmixercontrolsOld);
if (MMSYSERR_NOERROR !=
mixerGetControlDetails(hmixer, &mixercontroldetails, MIXER_GETCONTROLDETAILSF_VALUE))
{
debugf("Failed to get previous volume levels for mixer %d\n", uDeviceID);
mixerClose((HMIXER)hmixer);
continue;
}
m_listMixerLines.push_back(mixerlinedata);
}
}
~CDVolume()
{
while (!m_listMixerLines.empty())
{
MixerLineData& mixerlinedata = m_listMixerLines.back();
MIXERCONTROLDETAILS mixercontroldetails;
mixercontroldetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
mixercontroldetails.dwControlID = mixerlinedata.mixercontrol.dwControlID;
mixercontroldetails.cChannels = mixerlinedata.cChannels;
mixercontroldetails.cMultipleItems = 0;
mixercontroldetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
mixercontroldetails.paDetails = &(mixerlinedata.vmixercontrolsOld);
ZVerify(mixerSetControlDetails(mixerlinedata.hmixer, &mixercontroldetails, MIXER_SETCONTROLDETAILSF_VALUE)
== MMSYSERR_NOERROR);
ZVerify(mixerClose((HMIXER)mixerlinedata.hmixer) == MMSYSERR_NOERROR);
m_listMixerLines.pop_back();
}
}
HRESULT SetGain(float fGain)
{
if (fGain > 0.0f)
{
return E_INVALIDARG;
}
const float fMinGain = -40;
float fClippedGain = max(fMinGain, fGain);
std::list<MixerLineData>::iterator mixerline;
for (mixerline = m_listMixerLines.begin(); mixerline != m_listMixerLines.end(); ++mixerline)
{
MixerLineData& mixerlinedata = *mixerline;
MIXERCONTROLDETAILS_UNSIGNED volume;
volume.dwValue = (DWORD)(mixerlinedata.mixercontrol.Bounds.dwMinimum
+ (mixerlinedata.mixercontrol.Bounds.dwMaximum - mixerlinedata.mixercontrol.Bounds.dwMinimum)
* (1 - fClippedGain/fMinGain));
MIXERCONTROLDETAILS mixercontroldetails;
mixercontroldetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
mixercontroldetails.dwControlID = mixerlinedata.mixercontrol.dwControlID;
mixercontroldetails.cChannels = 1;
mixercontroldetails.cMultipleItems = 0;
mixercontroldetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
mixercontroldetails.paDetails = &volume;
ZVerify(mixerSetControlDetails(mixerlinedata.hmixer, &mixercontroldetails, MIXER_SETCONTROLDETAILSF_VALUE)
== MMSYSERR_NOERROR);
}
return S_OK;
}
};
class DiskPlayerImpl : public IDiskPlayer, private WorkerThread
{
UINT m_idDevice;
ZString m_strElementName;
CriticalSection m_csTrack;
enum { trackEmpty = -1, trackStop = 0 };
volatile int m_nQueuedTrack;
volatile int m_nCurrentTrack;
CDVolume m_cdvolume;
public:
DiskPlayerImpl() :
m_nQueuedTrack(trackEmpty),
m_nCurrentTrack(trackStop)
{
};
~DiskPlayerImpl()
{
StopThread();
}
HRESULT Init(const ZString& strDevice)
{
DWORD dwError;
MCI_OPEN_PARMS mciOpenParms;
DWORD dwFlags;
mciOpenParms.lpstrDeviceType = (LPCSTR) MCI_DEVTYPE_CD_AUDIO;
dwFlags = MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID;
if (!strDevice.IsEmpty())
{
m_strElementName = TranslateElementName(strDevice);
mciOpenParms.lpstrElementName = m_strElementName;
dwFlags |= MCI_OPEN_ELEMENT;
}
dwError = mciSendCommand(NULL, MCI_OPEN, dwFlags, (UINT_PTR)&mciOpenParms);
if (dwError)
{
char cbError[256];
mciGetErrorString(dwError, cbError, 256);
debugf("Open failed for CD Audio device '%c': %s\n", (const char*)strDevice, cbError);
return E_FAIL;
}
mciSendCommand(mciOpenParms.wDeviceID, MCI_CLOSE, 0, NULL);
StartThread(THREAD_PRIORITY_NORMAL, 200);
return S_OK;
}
void ThreadInit()
{
DWORD dwError;
MCI_OPEN_PARMS mciOpenParms;
DWORD dwFlags;
ZString strElementName;
mciOpenParms.lpstrDeviceType = (LPCSTR) MCI_DEVTYPE_CD_AUDIO;
dwFlags = MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID;
if (!m_strElementName.IsEmpty())
{
mciOpenParms.lpstrElementName = m_strElementName;
dwFlags |= MCI_OPEN_ELEMENT;
}
dwError = mciSendCommand(NULL, MCI_OPEN, dwFlags, (UINT_PTR)&mciOpenParms);
if (dwError)
{
char cbError[256];
mciGetErrorString(dwError, cbError, 256);
debugf("Open failed for CD Audio device '%': %s\n", (const char*)m_strElementName, cbError);
}
m_idDevice = mciOpenParms.wDeviceID;
MCI_SET_PARMS mciSetParms;
mciSetParms.dwTimeFormat = MCI_FORMAT_TMSF;
dwError = mciSendCommand(m_idDevice, MCI_SET, MCI_SET_TIME_FORMAT, (UINT_PTR)&mciSetParms);
if (dwError)
{
char cbError[256];
mciGetErrorString(dwError, cbError, 256);
debugf("Set format failed for CD Audio device: %s\n", cbError);
}
}
bool ThreadIteration()
{
int nRequestedTrack;
int nOldTrack;
{
CriticalSectionLock lock(m_csTrack);
nOldTrack = m_nCurrentTrack;
nRequestedTrack = m_nQueuedTrack;
if (nRequestedTrack != trackEmpty)
m_nCurrentTrack = nRequestedTrack;
m_nQueuedTrack = trackEmpty;
}
if (nRequestedTrack != trackEmpty)
{
if (nRequestedTrack == trackStop)
StopImpl();
else
PlayImpl(nRequestedTrack);
}
else
{
if ((nOldTrack != trackStop) && (IsPlayingImpl() != S_OK))
{
CriticalSectionLock lock(m_csTrack);
m_nCurrentTrack = trackStop;
}
}
return true;
}
void ThreadCleanup()
{
StopImpl();
mciSendCommand(m_idDevice, MCI_CLOSE, 0, NULL);
}
ZString TranslateElementName(const ZString& strDevice)
{
if (strDevice.Find(':') != -1)
{
return strDevice;
}
else
{
char cTemp;
int nDrivesStringLength = GetLogicalDriveStrings(1, &cTemp);
if (nDrivesStringLength == 0)
{
ZError("Error getting drives list\n");
return strDevice;
}
char* cbDrives = (char*)_alloca(nDrivesStringLength);
nDrivesStringLength = GetLogicalDriveStrings(nDrivesStringLength, cbDrives);
if (nDrivesStringLength == 0)
{
ZError("Error getting drives list\n");
return strDevice;
}
while (cbDrives[0] != '\0')
{
const int c_nVolumeNameLength = 1024;
char cbVolumeName[c_nVolumeNameLength];
if (GetDriveType(cbDrives) == DRIVE_CDROM
&& GetVolumeInformation(cbDrives, cbVolumeName,
c_nVolumeNameLength, NULL, NULL, NULL, NULL, 0))
{
if (_stricmp(strDevice, cbVolumeName) == 0)
{
return cbDrives;
}
}
cbDrives += strlen(cbDrives) + 1;
}
return strDevice;
}
}
virtual HRESULT Play(int nTrack)
{
CriticalSectionLock lock(m_csTrack);
if (nTrack <= 0)
{
return E_INVALIDARG;
}
m_nQueuedTrack = nTrack;
return S_OK;
}
virtual HRESULT Stop()
{
CriticalSectionLock lock(m_csTrack);
m_nQueuedTrack = trackStop;
return S_OK;
}
virtual HRESULT IsPlaying()
{
CriticalSectionLock lock(m_csTrack);
int nTrack;
if (m_nQueuedTrack != trackEmpty)
{
nTrack = m_nQueuedTrack;
}
else
{
nTrack = m_nCurrentTrack;
}
return (nTrack == trackStop) ? S_FALSE : S_OK;
}
virtual HRESULT GetCurrentTrack(int& nTrack)
{
CriticalSectionLock lock(m_csTrack);
if (m_nQueuedTrack != trackEmpty)
{
nTrack = m_nQueuedTrack;
}
else
{
nTrack = m_nCurrentTrack;
}
return (nTrack != trackStop) ? S_OK : E_FAIL;
}
virtual HRESULT SetGain(float fGain)
{
return m_cdvolume.SetGain(fGain);
};
HRESULT PlayImpl(int nTrack)
{
MCI_PLAY_PARMS mciPlayParms;
mciPlayParms.dwFrom = MCI_MAKE_TMSF(nTrack, 0, 0, 0);
mciPlayParms.dwTo = MCI_MAKE_TMSF(nTrack + 1, 0, 0, 0);
DWORD dwError = mciSendCommand(m_idDevice, MCI_PLAY, MCI_FROM | MCI_TO, (UINT_PTR)&mciPlayParms);
if (dwError == MCIERR_OUTOFRANGE)
dwError = mciSendCommand(m_idDevice, MCI_PLAY, MCI_FROM, (UINT_PTR)&mciPlayParms);
if (dwError)
{
char cbError[256];
mciGetErrorString(dwError, cbError, 256);
debugf("Play track %d failed for CD Audio device: %s\n", nTrack, cbError);
return E_FAIL;
}
return S_OK;
};
HRESULT StopImpl()
{
DWORD dwError = mciSendCommand(m_idDevice, MCI_STOP, 0, NULL);
if (dwError)
{
char cbError[256];
mciGetErrorString(dwError, cbError, 256);
debugf("Stop failed for CD Audio device: %s\n", cbError);
return E_FAIL;
}
return S_OK;
};
HRESULT IsPlayingImpl()
{
MCI_STATUS_PARMS mciStatusParams;
mciStatusParams.dwItem = MCI_STATUS_MODE;
DWORD dwError = mciSendCommand(m_idDevice, MCI_STATUS, MCI_STATUS_ITEM, (UINT_PTR)&mciStatusParams);
if (dwError)
{
char cbError[256];
mciGetErrorString(dwError, cbError, 256);
debugf("Status:Mode failed for CD Audio device: %s\n", cbError);
return E_FAIL;
}
return (mciStatusParams.dwReturn == MCI_MODE_PLAY) ? S_OK : S_FALSE;
};
HRESULT GetCurrentTrackImpl(int& nTrack)
{
MCI_STATUS_PARMS mciStatusParams;
mciStatusParams.dwItem = MCI_STATUS_CURRENT_TRACK;
DWORD dwError = mciSendCommand(m_idDevice, MCI_STATUS, MCI_STATUS_ITEM, (UINT_PTR)&mciStatusParams);
if (dwError)
{
char cbError[256];
mciGetErrorString(dwError, cbError, 256);
debugf("Status:Track failed for CD Audio device: %s\n", cbError);
return E_FAIL;
}
nTrack = mciStatusParams.dwReturn;
return S_OK;
};
};
HRESULT CreateDiskPlayer(TRef<IDiskPlayer>& pdiskplayer, const ZString& strDevice)
{
TRef<DiskPlayerImpl> pdiskplayerimpl = new DiskPlayerImpl();
HRESULT hr = pdiskplayerimpl->Init(strDevice);
if (SUCCEEDED(hr))
pdiskplayer = pdiskplayerimpl;
return hr;
};
class DummyDiskPlayer : public IDiskPlayer
{
public:
virtual HRESULT Play(int nTrack)
{
return S_OK;
};
virtual HRESULT Stop()
{
return S_OK;
};
virtual HRESULT IsPlaying()
{
return S_FALSE;
};
virtual HRESULT GetCurrentTrack(int& nTrack)
{
nTrack = 1;
return S_OK;
};
virtual HRESULT SetGain(float fGain)
{
return S_OK;
};
};
HRESULT CreateDummyDiskPlayer(TRef<IDiskPlayer>& pdiskplayer)
{
pdiskplayer = new DummyDiskPlayer();
return S_OK;
}
class RedbookSoundTemplate : public ISoundTemplate
{
private:
TRef<IDiskPlayer> m_pdiskplayer;
int m_nTrack;
class RedbookSoundInstance : public ISoundInstance
{
TRef<IDiskPlayer> m_pdiskplayer;
int m_nTrack;
public:
RedbookSoundInstance(TRef<IDiskPlayer> pdiskplayer, int nTrack) :
m_pdiskplayer(pdiskplayer),
m_nTrack(nTrack)
{
pdiskplayer->Play(nTrack);
}
virtual HRESULT Stop(bool bForceNow = false)
{
HRESULT hr = IsPlaying();
if (hr == S_OK)
{
return m_pdiskplayer->Stop();
}
else if (SUCCEEDED(hr))
return S_OK;
else
return hr;
}
virtual HRESULT IsPlaying()
{
HRESULT hr = m_pdiskplayer->IsPlaying();
if (hr == S_OK)
{
int nTrack;
hr = m_pdiskplayer->GetCurrentTrack(nTrack);
if (FAILED(hr))
return hr;
return (nTrack == m_nTrack) ? S_OK : S_FALSE;
}
else
return hr;
}
virtual IEventSource* GetFinishEventSource()
{
ZError("NYI");
return NULL;
}
virtual TRef<ISoundTweakable> GetISoundTweakable()
{
return NULL;
}
virtual TRef<ISoundTweakable3D> GetISoundTweakable3D()
{
return NULL;
}
};
public:
HRESULT Init(TRef<IDiskPlayer> pdiskplayer, int nTrack)
{
if (!pdiskplayer)
{
ZAssert(false);
return E_POINTER;
}
if (nTrack <= 0)
{
ZAssert(false);
return E_INVALIDARG;
}
m_pdiskplayer = pdiskplayer;
m_nTrack = nTrack;
return S_OK;
};
virtual HRESULT CreateSound(TRef<ISoundInstance>& psoundNew,
ISoundBufferSource* pbufferSource, ISoundPositionSource* psource = NULL)
{
if (!pbufferSource)
{
ZAssert(false);
return E_POINTER;
}
if (psource)
{
ZAssert(false);
return E_NOTIMPL;
}
psoundNew = new RedbookSoundInstance(m_pdiskplayer, m_nTrack);
return S_OK;
}
};
HRESULT CreateRedbookSoundTemplate(TRef<ISoundTemplate>& pstDest, TRef<IDiskPlayer> pdiskplayer, int nTrack)
{
TRef<RedbookSoundTemplate> ptemplate = new RedbookSoundTemplate();
HRESULT hr = ptemplate->Init(pdiskplayer, nTrack);
if (ZSucceeded(hr))
pstDest = ptemplate;
return hr;
}
};