#include "pch.h"
#include "soundbase.h"
#include "ds3dutil.h"
#include "ds3dbuffer.h"
#include "ds3dvirtualbuffer.h"
#include "ds3dengine.h"
#include "soundutil.h"
namespace SoundEngine {
class DefaultListener : public ISoundListener
{
public:
HRESULT GetPosition(Vector& vectPosition)
{
vectPosition = Vector(0, 0, 0);
return S_OK;
};
HRESULT GetVelocity(Vector& vectVelocity)
{
vectVelocity = Vector(0, 0, 0);
return S_OK;
};
virtual HRESULT GetOrientation(Vector& vectOrientation)
{
vectOrientation = Vector(0, 0, 1);
return S_OK;
};
virtual HRESULT IsListenerRelative()
{
return S_FALSE; };
virtual HRESULT IsPlaying()
{
return S_OK;
};
virtual HRESULT GetUpDirection(Vector& vectUp)
{
vectUp = Vector(0, 1, 0);
return S_OK;
}
};
void DS3DSoundEngine::DumpCaps()
{
debugf(
"Directsound driver is %scertified by Microsoft "
"and is %semulated.\n",
(m_dscaps.dwFlags & DSCAPS_CERTIFIED) ? "" : "not ",
(m_dscaps.dwFlags & DSCAPS_EMULDRIVER) ? "" : "not "
);
debugf(
" The primary buffer can be%s%s%s%s.\n",
(m_dscaps.dwFlags & DSCAPS_PRIMARY8BIT) ? " 8 bit" : "",
(m_dscaps.dwFlags & DSCAPS_PRIMARY16BIT) ? " 16 bit" : "",
(m_dscaps.dwFlags & DSCAPS_PRIMARYMONO) ? " mono" : "",
(m_dscaps.dwFlags & DSCAPS_PRIMARYSTEREO) ? " stereo" : ""
);
debugf(
" The secondary buffers can range %s from %d Hz to %d Hz, and can be %s%s%s%s.\n",
(m_dscaps.dwFlags & DSCAPS_CONTINUOUSRATE) ? "continuously" : "in steps",
m_dscaps.dwMinSecondarySampleRate,
m_dscaps.dwMaxSecondarySampleRate,
(m_dscaps.dwFlags & DSCAPS_SECONDARY8BIT) ? " 8 bit" : "",
(m_dscaps.dwFlags & DSCAPS_SECONDARY16BIT) ? " 16 bit" : "",
(m_dscaps.dwFlags & DSCAPS_SECONDARYMONO) ? " mono" : "",
(m_dscaps.dwFlags & DSCAPS_SECONDARYSTEREO) ? " stereo" : ""
);
debugf(
" There is hardware support for mixing %d buffers, including %d static and %d streaming buffers.\n",
m_dscaps.dwMaxHwMixingAllBuffers,
m_dscaps.dwMaxHwMixingStaticBuffers,
m_dscaps.dwMaxHwMixingStreamingBuffers
);
debugf(
" There is hardware support for %d 3D buffers, including %d static 3D and %d streaming 3D buffers.\n",
m_dscaps.dwMaxHw3DAllBuffers,
m_dscaps.dwMaxHw3DStaticBuffers,
m_dscaps.dwMaxHw3DStreamingBuffers
);
};
HRESULT DS3DSoundEngine::SetPrimaryBufferFormat(int nSampleRate, int nNumberOfBits, int nChannels)
{
HRESULT hr;
WAVEFORMATEX waveformatex;
memset(&waveformatex, 0, sizeof(WAVEFORMATEX)); waveformatex.cbSize = sizeof(WAVEFORMATEX);
waveformatex.wFormatTag = WAVE_FORMAT_PCM;
waveformatex.nChannels = nChannels;
waveformatex.nSamplesPerSec = nSampleRate;
waveformatex.wBitsPerSample = nNumberOfBits;
waveformatex.nBlockAlign = waveformatex.wBitsPerSample / 8 * waveformatex.nChannels;
waveformatex.nAvgBytesPerSec = waveformatex.nSamplesPerSec * waveformatex.nBlockAlign;
hr = m_pPrimaryBuffer->SetFormat(&waveformatex);
if (ZFailed(hr)) return hr;
#ifdef _DEBUG
m_pPrimaryBuffer->GetFormat(&waveformatex, sizeof(waveformatex), NULL);
debugf(
"Primary buffer set to %d Hz, %d bits, %d channels (attempted %d Hz, %d bits, %d channels)\n",
waveformatex.nSamplesPerSec, waveformatex.wBitsPerSample, waveformatex.nChannels,
nSampleRate, nNumberOfBits, nChannels
);
#endif
return S_OK;
};
HRESULT DS3DSoundEngine::UpdateListener()
{
HRESULT hr;
DS3DLISTENER listenerdata;
listenerdata.dwSize = sizeof(listenerdata);
listenerdata.flDistanceFactor = m_fDistanceFactor;
listenerdata.flDopplerFactor = m_fDopplerFactor;
listenerdata.flRolloffFactor = m_fRolloffFactor;
Vector vectPosition;
hr = m_plistener->GetPosition(vectPosition);
if (ZFailed(hr)) return hr;
ConvertVector(listenerdata.vPosition, vectPosition);
Vector vectVelocity;
hr = m_plistener->GetVelocity(vectVelocity);
if (ZFailed(hr)) return hr;
ConvertVector(listenerdata.vVelocity, vectVelocity);
Vector vectOrientation;
hr = m_plistener->GetOrientation(vectOrientation);
if (ZFailed(hr)) return hr;
ZAssertIsUnitVector(vectOrientation);
ConvertVector(listenerdata.vOrientFront, vectOrientation);
Vector vectUp;
hr = m_plistener->GetUpDirection(vectUp);
if (ZFailed(hr)) return hr;
ZAssertIsUnitVector(vectUp);
ConvertVector(listenerdata.vOrientTop, vectUp);
return m_pDSListener->SetAllParameters(&listenerdata, DS3D_DEFERRED);
};
bool DS3DSoundEngine::IsSeriousError(HRESULT hr)
{
switch (hr)
{
case S_OK:
case S_FALSE:
return false;
case DSERR_BUFFERLOST:
debugf("Sound buffer lost.\n");
return false;
case DSERR_OUTOFMEMORY:
debugf("Out of sound memory.\n");
return false;
default:
return FAILED(hr);
}
}
DS3DSoundEngine::DS3DSoundEngine() :
m_quality(midQuality),
m_bAllowHardware(true),
m_fRolloffFactor(1.0f),
m_fDopplerFactor(1.0f),
m_fDistanceFactor(1.0f),
m_pDirectSound8(NULL),
m_pDirectSound(NULL)
{
m_plistener = new DefaultListener();
m_peventsourceUpdate = new IntegerEventSourceImpl();
m_dwLastUpdateTime = timeGetTime();
m_pBufferSourceDelegate = new SoundBufferSourceDelegate(this);
}
DS3DSoundEngine::~DS3DSoundEngine()
{
HRESULT hr;
m_peventsourceUpdate = NULL;
if (m_pPrimaryBuffer)
{
hr = m_pPrimaryBuffer->Stop();
ZFailed(hr);
}
m_pPrimaryBuffer->Release();
m_pPrimaryBuffer = NULL;
m_pDSListener = NULL;
}
HRESULT DS3DSoundEngine::Init(HWND hwnd, bool bUseDSound8)
{
HRESULT hr;
if(bUseDSound8)
hr = DirectSoundCreate8(NULL, &m_pDirectSound8, NULL);
else
hr = DirectSoundCreate(NULL, &m_pDirectSound, NULL);
if (hr == DSERR_NODRIVER || hr == DSERR_ALLOCATED || ZFailed(hr)) return hr;
if(bUseDSound8)
hr = m_pDirectSound8->SetCooperativeLevel(hwnd, DSSCL_PRIORITY);
else
hr = m_pDirectSound->SetCooperativeLevel(hwnd, DSSCL_PRIORITY);
if (hr == DSERR_ALLOCATED)
{
debugf("Failure: unable to get DSSCL_PRIORITY access to DSound. Failing over to DSSCL_NORMAL.\n");
if(bUseDSound8)
hr = m_pDirectSound->SetCooperativeLevel(hwnd, DSSCL_NORMAL);
else
hr = m_pDirectSound->SetCooperativeLevel(hwnd, DSSCL_NORMAL);
}
if (ZFailed(hr)) return hr;
if(!bUseDSound8)
m_pDirectSound->Compact(); DSBUFFERDESC dsbufferdesc;
memset(&dsbufferdesc, 0, sizeof(DSBUFFERDESC));
dsbufferdesc.dwSize = sizeof(dsbufferdesc);
dsbufferdesc.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_PRIMARYBUFFER;
dsbufferdesc.dwBufferBytes = 0; dsbufferdesc.lpwfxFormat = NULL;
if(bUseDSound8)
hr = m_pDirectSound8->CreateSoundBuffer(&dsbufferdesc, &m_pPrimaryBuffer, NULL);
else
hr = m_pDirectSound->CreateSoundBuffer(&dsbufferdesc, &m_pPrimaryBuffer, NULL);
if (ZFailed(hr)) return hr;
hr = m_pPrimaryBuffer->QueryInterface(IID_IDirectSound3DListener, (LPVOID *)&m_pDSListener);
if (ZFailed(hr)) return hr;
memset(&m_dscaps, 0, sizeof(DSCAPS));
m_dscaps.dwSize = sizeof(DSCAPS);
if(bUseDSound8)
hr = m_pDirectSound8->GetCaps(&m_dscaps);
else
hr = m_pDirectSound->GetCaps(&m_dscaps);
if (ZFailed(hr)) return hr;
DumpCaps();
hr = SetQuality(midQuality);
if (ZFailed(hr)) return hr;
hr = m_pPrimaryBuffer->Play(0, 0, DSBPLAY_LOOPING);
if ((hr != DSERR_PRIOLEVELNEEDED) && ZFailed(hr)) return hr;
return S_OK;
}
HRESULT DS3DSoundEngine::Update()
{
HRESULT hr;
Vector vectListenerPosition;
DWORD dwUpdateTime = timeGetTime();
DWORD dwUpdatePeriod = dwUpdateTime - m_dwLastUpdateTime;
if(dwUpdatePeriod < 30)
return S_OK;
m_peventsourceUpdate->Trigger(dwUpdatePeriod);
typedef std::vector<DSVirtualSoundBuffer*> TempSoundList;
hr = m_plistener->GetPosition(vectListenerPosition);
if (ZFailed(hr)) return hr;
TempSoundList listPrevSounds;
listPrevSounds.reserve(m_nNumBuffersMax);
VirtualSoundList::iterator iterSound;
for (iterSound = m_listActiveSounds.begin();
iterSound != m_listActiveSounds.end();)
{
hr = (*iterSound)->Update(dwUpdatePeriod, vectListenerPosition,
m_fRolloffFactor);
if (ZFailed(hr)) return hr;
if ((*iterSound)->IsStopped())
{
iterSound = m_listActiveSounds.erase(iterSound);
}
else
{
if ((*iterSound)->HasPlayingBuffer())
listPrevSounds.push_back(
(TRef<DSVirtualSoundBuffer>)(*iterSound)
);
++iterSound;
}
}
TempSoundList listNewSounds;
if (!m_listActiveSounds.empty())
{
TempSoundList listAllSounds(m_listActiveSounds.size());
std::transform(m_listActiveSounds.begin(), m_listActiveSounds.end(),
listAllSounds.begin(), RefToPtr<DSVirtualSoundBuffer>());
std::make_heap(listAllSounds.begin(), listAllSounds.end(), std::not2(SoundPriorityCompare()));
listNewSounds.reserve(m_nNumBuffersDesired);
for (int nSound = 0; nSound < min(m_nNumBuffersDesired, listAllSounds.size()); nSound++)
{
std::pop_heap(listAllSounds.begin(), listAllSounds.end() - nSound,
std::not2(SoundPriorityCompare()));
DSVirtualSoundBuffer* pbuffer = (*(listAllSounds.end() - nSound - 1));
if (pbuffer->IsAudible())
listNewSounds.push_back(pbuffer);
}
}
{
std::sort(listPrevSounds.begin(), listPrevSounds.end(), SoundPriorityCompare());
TempSoundList::iterator iterDead =
(listNewSounds.empty()) ? listPrevSounds.begin() :
std::find_if(listPrevSounds.begin(), listPrevSounds.end(),
std::bind1st(SoundPriorityCompare(), *(listNewSounds.end() - 1)));
for (; iterDead != listPrevSounds.end(); iterDead++)
{
hr = (*iterDead)->StopBuffer();
if (ZFailed(hr)) return hr;
}
for (TempSoundList::iterator iterNew = listNewSounds.begin();
iterNew != listNewSounds.end(); ++iterNew)
{
if (!(*iterNew)->HasPlayingBuffer())
{
if(m_pDirectSound8)
hr = (*iterNew)->StartBuffer8(m_pDirectSound8, m_quality, m_bAllowHardware);
else
hr = (*iterNew)->StartBuffer(m_pDirectSound, m_quality, m_bAllowHardware);
if (IsSeriousError(hr))
{
debugf("Serious error during update while starting sound: %X\n", hr);
}
}
};
}
hr = UpdateListener();
if (ZFailed(hr)) return hr;
hr = m_pDSListener->CommitDeferredSettings();
if (ZFailed(hr)) return hr;
m_dwLastUpdateTime = dwUpdateTime;
return S_OK;
}
HRESULT DS3DSoundEngine::GetNumPlayingVirtualBuffers(int& nBuffers)
{
nBuffers = m_listActiveSounds.size();
return S_OK;
}
HRESULT DS3DSoundEngine::SetQuality(Quality quality)
{
HRESULT hr;
VirtualSoundList::iterator iterSound;
for (iterSound = m_listActiveSounds.begin();
iterSound != m_listActiveSounds.end(); ++iterSound)
{
if ((*iterSound)->HasPlayingBuffer())
{
hr = (*iterSound)->StopBuffer();
if (ZFailed(hr)) return hr;
}
}
switch (quality)
{
case minQuality:
hr = SetPrimaryBufferFormat(22050, 8, 1);
if ((hr != DSERR_PRIOLEVELNEEDED) && ZFailed(hr)) return hr;
m_nNumBuffersDesired = 8;
m_nNumBuffersMax = 8;
break;
case midQuality:
default:
hr = SetPrimaryBufferFormat(22050, 16, 2);
if ((hr != DSERR_PRIOLEVELNEEDED) && ZFailed(hr)) return hr;
if (m_bAllowHardware)
{
m_nNumBuffersDesired = max(16, (int)m_dscaps.dwMaxHwMixingStreamingBuffers * 2 / 3);
m_nNumBuffersMax = max(24, (int)m_dscaps.dwMaxHwMixingStreamingBuffers);
}
else
{
m_nNumBuffersDesired = 16;
m_nNumBuffersMax = 24;
}
break;
case maxQuality:
hr = SetPrimaryBufferFormat(44100, 16, 2);
if ((hr != DSERR_PRIOLEVELNEEDED) && ZFailed(hr)) return hr;
if (m_bAllowHardware)
{
m_nNumBuffersDesired = max(24, (int)m_dscaps.dwMaxHwMixingStreamingBuffers - 8);
m_nNumBuffersMax = max(32, (int)m_dscaps.dwMaxHwMixingStreamingBuffers);
}
else
{
m_nNumBuffersDesired = 24;
m_nNumBuffersMax = 32;
}
break;
}
m_quality = quality;
return S_OK;
};
HRESULT DS3DSoundEngine::EnableHardware(bool bEnable)
{
if (m_bAllowHardware != bEnable)
{
m_bAllowHardware = bEnable;
return SetQuality(m_quality);
}
else
{
return S_OK;
}
}
HRESULT DS3DSoundEngine::SetListener(ISoundListener* plistener)
{
if (plistener == NULL)
{
ZAssert(false);
return E_POINTER;
}
m_plistener = plistener;
return S_OK;
}
HRESULT DS3DSoundEngine::SetDistanceFactor(float fMetersPerUnit)
{
if (fMetersPerUnit <= 0)
{
ZAssert(false);
return E_INVALIDARG;
};
m_fDistanceFactor = fMetersPerUnit;
return S_OK;
}
HRESULT DS3DSoundEngine::SetRolloffFactor(float fRolloffFactor)
{
if (fRolloffFactor < DS3D_MINROLLOFFFACTOR
|| fRolloffFactor > DS3D_MAXROLLOFFFACTOR)
{
ZAssert(false);
return E_INVALIDARG;
};
m_fRolloffFactor = fRolloffFactor;
return S_OK;
}
HRESULT DS3DSoundEngine::SetDopplerFactor(float fDopplerFactor)
{
if (fDopplerFactor < DS3D_MINDOPPLERFACTOR
|| fDopplerFactor > DS3D_MAXDOPPLERFACTOR)
{
ZAssert(false);
return E_INVALIDARG;
};
m_fDopplerFactor = fDopplerFactor;
return S_OK;
}
ISoundBufferSource* DS3DSoundEngine::GetBufferSource()
{
return m_pBufferSourceDelegate;
};
HRESULT DS3DSoundEngine::CreateStaticBuffer(TRef<ISoundInstance>& psoundNew,
ISoundPCMData* pcmdata, bool bLooping, ISoundPositionSource* psource)
{
TRef<DSVirtualSoundBuffer> pvirtualsound;
if (psource != NULL)
{
pvirtualsound = new DS3DVirtualSoundBuffer(pcmdata, bLooping, psource);
}
else
{
pvirtualsound = new DSVirtualSoundBuffer(pcmdata, bLooping);
}
m_listActiveSounds.push_back(pvirtualsound);
psoundNew = pvirtualsound;
return S_OK;
}
HRESULT DS3DSoundEngine::CreateASRBuffer(TRef<ISoundInstance>& psoundNew,
ISoundPCMData* pcmdata, unsigned uLoopStart, unsigned uLoopLength,
ISoundPositionSource* psource)
{
if (pcmdata == NULL)
{
ZAssert(false);
return E_POINTER;
}
if (uLoopStart + uLoopLength > pcmdata->GetSize())
{
ZAssert(false);
return E_INVALIDARG;
}
TRef<DSVirtualSoundBuffer> pvirtualsound;
if (psource != NULL)
{
pvirtualsound = new DS3DVirtualSoundBuffer(pcmdata, uLoopStart, uLoopLength, psource);
}
else
{
pvirtualsound = new DSVirtualSoundBuffer(pcmdata, uLoopStart, uLoopLength);
}
m_listActiveSounds.push_back(pvirtualsound);
psoundNew = pvirtualsound;
return S_OK;
}
IIntegerEventSource* DS3DSoundEngine::GetUpdateEventSource()
{
return m_peventsourceUpdate;
};
#ifdef _DEBUG
ZString DS3DSoundEngine::DebugDump(const ZString& strIndent)
{
ZString strResult = strIndent + "DS3DSoundEngine: \n";
VirtualSoundList::iterator iterSound;
for (iterSound = m_listActiveSounds.begin();
iterSound != m_listActiveSounds.end(); ++iterSound)
{
strResult += SoundDebugDump(
(ISoundInstance*)(TRef<DSVirtualSoundBuffer>&)(*iterSound),
strIndent + " ");
}
return strResult;
}
#endif
};