123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329 |
- //
- // ds3dbuffer.cpp: low-level classes representing actual DirectSound3D buffers
- //
- #include "pch.h"
- #include "soundbase.h"
- #include "ds3dutil.h"
- #include "ds3dbuffer.h"
- namespace SoundEngine {
- /////////////////////////////////////////////////////////////////////////////
- //
- // DS3DSoundBuffer
- //
- /////////////////////////////////////////////////////////////////////////////
- // convert a gain in the range -100 to 0 dB to a direct sound volume
- inline LONG DS3DSoundBuffer::DSoundVolume(float fGain)
- {
- ZAssert((fGain >= -100) && (fGain <= 0));
- return (LONG)(100 * fGain);
- }
- // initializes the object, creating the DSoundBuffer itself and
- // initializing local variables.
- HRESULT DS3DSoundBuffer::CreateBuffer(IDirectSound* pDirectSound, ISoundPCMData* pdata,
- DWORD dwBufferSize, bool bStatic, bool bSupport3D, ISoundEngine::Quality quality,
- bool bAllowHardware)
- {
- HRESULT hr;
-
- // check the arguments
- if (!pdata || !pDirectSound)
- {
- ZAssert(false);
- return E_POINTER;
- }
- // set a few state variables with the new info we have
- m_b3D = bSupport3D;
- m_dwSampleRate = pdata->GetSampleRate();
- // describe the new buffer we want
- WAVEFORMATEX waveformatex;
- DSBUFFERDESC dsbufferdesc;
- waveformatex.cbSize = sizeof(waveformatex);
- waveformatex.wFormatTag = WAVE_FORMAT_PCM;
- waveformatex.nChannels = pdata->GetNumberOfChannels();
- waveformatex.nSamplesPerSec = m_dwSampleRate;
- waveformatex.wBitsPerSample = pdata->GetBitsPerSample();
- waveformatex.nBlockAlign = waveformatex.wBitsPerSample / 8 * waveformatex.nChannels;
- waveformatex.nAvgBytesPerSec = waveformatex.nSamplesPerSec * waveformatex.nBlockAlign;
- dsbufferdesc.dwSize = sizeof(dsbufferdesc);
- dsbufferdesc.dwFlags =
- (bSupport3D ? DSBCAPS_CTRL3D | DSBCAPS_MUTE3DATMAXDISTANCE : 0)
- | DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLVOLUME
- | DSBCAPS_GETCURRENTPOSITION2
- | (bStatic ? DSBCAPS_STATIC : 0);
- dsbufferdesc.dwBufferBytes = dwBufferSize;
- dsbufferdesc.dwReserved = 0;
- dsbufferdesc.lpwfxFormat = &waveformatex;
- #if DIRECTSOUND_VERSION >= 0x0700
- if (bAllowHardware)
- dsbufferdesc.dwFlags |= DSBCAPS_LOCDEFER;
- else
- dsbufferdesc.dwFlags |= DSBCAPS_LOCSOFTWARE;
- if (bSupport3D)
- {
- switch (quality)
- {
- case ISoundEngine::minQuality:
- dsbufferdesc.guid3DAlgorithm = DS3DALG_NO_VIRTUALIZATION;
- break;
- case ISoundEngine::midQuality:
- dsbufferdesc.guid3DAlgorithm = DS3DALG_DEFAULT;
- break;
- case ISoundEngine::maxQuality:
- dsbufferdesc.guid3DAlgorithm = DS3DALG_HRTF_LIGHT;
- break;
- };
- }
- else
- {
- dsbufferdesc.guid3DAlgorithm = GUID_NULL;
- }
- #endif
- // create the new buffer
- hr = pDirectSound->CreateSoundBuffer(&dsbufferdesc, &m_pdirectsoundbuffer, NULL);
- if (FAILED(hr)) return hr;
- // get a handle to the 3D buffer, if this is 3D
- if (bSupport3D)
- {
- hr = m_pdirectsoundbuffer->QueryInterface(IID_IDirectSound3DBuffer, (void**)&m_pdirectsound3Dbuffer);
- if (ZFailed(hr)) return hr;
- }
- return S_OK;
- };
- // Use an exisiting to initialize this buffer. Note that the buffers will
- // share memory, so this only really works for static buffers.
- HRESULT DS3DSoundBuffer::DuplicateBuffer(IDirectSound* pDirectSound, DS3DSoundBuffer* pBuffer)
- {
- HRESULT hr;
- // check the arguments
- if (!pBuffer || !pDirectSound)
- {
- ZAssert(false);
- return E_POINTER;
- }
- if (!pBuffer->m_pdirectsoundbuffer)
- {
- ZAssert(false);
- return E_INVALIDARG;
- }
- // copy the basic info
- m_b3D = pBuffer->m_b3D;
- m_bListenerRelative = pBuffer->m_bListenerRelative;
- m_dwSampleRate = pBuffer->m_dwSampleRate;
- // duplicate the buffer
- hr = pDirectSound->DuplicateSoundBuffer(pBuffer->m_pdirectsoundbuffer, &m_pdirectsoundbuffer);
- if (FAILED(hr)) return hr;
- // reset the 2D info.
- hr = m_pdirectsoundbuffer->SetVolume(DSoundVolume(m_fGain));
- if (ZFailed(hr)) return hr;
- hr = m_pdirectsoundbuffer->SetFrequency((LONG)(m_fPitch*m_dwSampleRate));
- if (ZFailed(hr)) return hr;
- // if this is 3D
- if (pBuffer->m_pdirectsound3Dbuffer != NULL)
- {
- DS3DBUFFER ds3dbuf;
- // get a handle to the 3D buffer
- hr = m_pdirectsoundbuffer->QueryInterface(IID_IDirectSound3DBuffer, (void**)&m_pdirectsound3Dbuffer);
- if (ZFailed(hr)) return hr;
- // reset the 3D info.
- ds3dbuf.dwSize = sizeof(ds3dbuf);
- ConvertVector(ds3dbuf.vPosition, m_vectPosition);
- ConvertVector(ds3dbuf.vVelocity, m_vectVelocity);
- ds3dbuf.dwInsideConeAngle = (LONG)m_fInnerAngle;
- ds3dbuf.dwOutsideConeAngle = (LONG)m_fOuterAngle;
- ConvertVector(ds3dbuf.vConeOrientation, m_vectOrientation);
- ds3dbuf.lConeOutsideVolume = DSoundVolume(m_fOutsideGain);
- ds3dbuf.flMaxDistance = m_fMinimumDistance * c_fMinToMaxDistanceRatio;
- ds3dbuf.flMinDistance = m_fMinimumDistance;
- ds3dbuf.dwMode = m_b3D
- ? (m_bListenerRelative ? DS3DMODE_HEADRELATIVE : DS3DMODE_NORMAL)
- : DS3DMODE_DISABLE;
- hr = m_pdirectsound3Dbuffer->SetAllParameters(&ds3dbuf, DS3D_IMMEDIATE);
- if (ZFailed(hr)) return hr;
- }
- return S_OK;
- };
- // Start the buffer, starting as a looping buffer if requested.
- HRESULT DS3DSoundBuffer::StartImpl(bool bLooping)
- {
- HRESULT hr;
- DWORD dwFlags = bLooping ? DSBPLAY_LOOPING : 0;
- // DX7 bug: if the given sound is frequency shifted, we need to unshift
- // it before starting the buffer.
- if (m_fPitch != 1.0)
- {
- hr = m_pdirectsoundbuffer->SetFrequency((LONG)m_dwSampleRate);
- if (ZFailed(hr)) return hr;
- }
- // try starting the buffer
- hr = m_pdirectsoundbuffer->Play(0, 0, dwFlags);
- if (hr == DSERR_BUFFERLOST)
- {
- // the buffer was lost
- debugf("DSound buffer lost.\n");
- // try reloading it
- hr = RestoreBuffer();
- if (ZFailed(hr)) return hr;
- // try starting it again
- hr = m_pdirectsoundbuffer->Play(0, 0, dwFlags);
- }
- if (FAILED(hr))
- {
- ZAssert(hr == DSERR_INVALIDCALL);
- debugf("Error starting sound: %X\n", hr);
- return hr;
- }
- // DX7 bug: now reshift it since the buffer has been started
- if (m_fPitch != 1.0)
- {
- hr = m_pdirectsoundbuffer->SetFrequency((LONG)(m_fPitch*m_dwSampleRate));
- if (ZFailed(hr)) return hr;
- }
- return S_OK;
- }
- DS3DSoundBuffer::DS3DSoundBuffer() :
- m_vectPosition(0, 0, 0),
- m_vectVelocity(0, 0, 0),
- m_vectOrientation(1.1f, 0, 0), // should be reset before playing
- m_fMinimumDistance(DS3D_DEFAULTMINDISTANCE + 0.01f), // should be reset
- m_fInnerAngle(360), m_fOuterAngle(360), m_fOutsideGain(0),
- m_fGain(0),
- m_fPitch(1.0f),
- m_bListenerRelative(false)
- {
- }
- DS3DSoundBuffer::~DS3DSoundBuffer()
- {
- // make sure these are released before the DirectSound object.
- m_pdirectsoundbuffer = NULL;
- m_pdirectsound3Dbuffer = NULL;
- }
- // Sets the volume and pitch iff the new values differ from the old.
- HRESULT DS3DSoundBuffer::UpdateState(float fGain, float fPitch, bool bCanDefer)
- {
- HRESULT hr;
- if (m_fGain != fGain)
- {
- if (fGain < -100 || fGain > 0)
- {
- ZAssert(false);
- return E_INVALIDARG;
- }
- m_fGain = fGain;
- hr = m_pdirectsoundbuffer->SetVolume(DSoundVolume(m_fGain));
- if (ZFailed(hr)) return hr;
- }
- if (m_fPitch != fPitch)
- {
- if (fPitch < (DSBFREQUENCY_MIN / m_dwSampleRate)
- || (fPitch > DSBFREQUENCY_MAX / m_dwSampleRate))
- {
- ZAssert(false);
- return E_INVALIDARG;
- }
- m_fPitch = fPitch;
- hr = m_pdirectsoundbuffer->SetFrequency((LONG)(m_fPitch*m_dwSampleRate));
- if (ZFailed(hr)) return hr;
- }
- return S_OK;
- };
- // Sets the 3D info iff the new values differ from the old.
- HRESULT DS3DSoundBuffer::UpdateState3D(
- const Vector& vectPosition,
- const Vector& vectVelocity,
- const Vector& vectOrientation,
- float fMinimumDistance,
- float fInnerAngle,
- float fOuterAngle,
- float fOutsideGain,
- bool b3D,
- bool bListenerRelative,
- bool bCanDefer
- )
- {
- // if this is not a 3D-capable sound, we should not be called
- if (m_pdirectsound3Dbuffer == NULL)
- {
- ZAssert(FALSE);
- return E_NOTIMPL;
- }
- // if something has changed...
- if ((m_vectPosition != vectPosition)
- || (m_vectVelocity != vectVelocity)
- || (m_vectOrientation != vectOrientation)
- || (m_fMinimumDistance != fMinimumDistance)
- || (m_fInnerAngle != fInnerAngle)
- || (m_fOuterAngle != fOuterAngle)
- || (m_fOutsideGain != fOutsideGain)
- || (m_b3D != b3D)
- || (m_bListenerRelative != bListenerRelative)
- )
- {
- HRESULT hr;
- DS3DBUFFER ds3dbuf;
- // check the arguments
- ZAssertIsUnitVector(vectOrientation);
- if ((fMinimumDistance <= 0)
- || (fInnerAngle < 0 || fInnerAngle > 360)
- || (fOuterAngle < 0 || fOuterAngle > 360)
- || (fOutsideGain < -100 || fOutsideGain > 0)
- )
- {
- ZAssert(false);
- return E_INVALIDARG;
- }
- // set the cached parameters to the new info
- m_vectPosition = vectPosition;
- m_vectVelocity = vectVelocity;
- m_vectOrientation = vectOrientation;
- m_fMinimumDistance = fMinimumDistance;
- m_fInnerAngle = fInnerAngle;
- m_fOuterAngle = fOuterAngle;
- m_fOutsideGain = fOutsideGain;
- m_b3D = b3D;
- m_bListenerRelative = bListenerRelative;
- // set all of the parameters at once
- ds3dbuf.dwSize = sizeof(ds3dbuf);
- ConvertVector(ds3dbuf.vPosition, m_vectPosition);
- ConvertVector(ds3dbuf.vVelocity, m_vectVelocity);
- ds3dbuf.dwInsideConeAngle = (LONG)m_fInnerAngle;
- ds3dbuf.dwOutsideConeAngle = (LONG)m_fOuterAngle;
- ConvertVector(ds3dbuf.vConeOrientation, m_vectOrientation);
- ds3dbuf.lConeOutsideVolume = DSoundVolume(m_fOutsideGain);
- ds3dbuf.flMaxDistance = m_fMinimumDistance * c_fMinToMaxDistanceRatio;
- ds3dbuf.flMinDistance = m_fMinimumDistance;
- ds3dbuf.dwMode = m_b3D
- ? (m_bListenerRelative ? DS3DMODE_HEADRELATIVE : DS3DMODE_NORMAL)
- : DS3DMODE_DISABLE;
- hr = m_pdirectsound3Dbuffer->SetAllParameters(
- &ds3dbuf, bCanDefer ? DS3D_DEFERRED : DS3D_IMMEDIATE);
- if (ZFailed(hr)) return hr;
- }
- return S_OK;
- };
- // gets the current status of the buffer
- HRESULT DS3DSoundBuffer::GetStatus(bool& bPlaying, bool& bBufferLost)
- {
- HRESULT hr;
- DWORD dwStatus;
- hr = m_pdirectsoundbuffer->GetStatus(&dwStatus);
- bPlaying = (dwStatus & DSBSTATUS_PLAYING) != 0;
- bBufferLost = (dwStatus & DSBSTATUS_BUFFERLOST) != 0;
- return hr;
- };
- #ifdef _DEBUG
- // return a human-readable description of the object, prepending
- // strIndent to the beginning of each line.
- ZString DS3DSoundBuffer::DebugDump(const ZString& strIndent)
- {
- DWORD dwStatus;
- m_pdirectsoundbuffer->GetStatus(&dwStatus);
- return strIndent + "DS3DSoundBuffer: "
- + (m_b3D ? " 3D" : " 2D")
- + ((dwStatus & DSBSTATUS_PLAYING) ? " playing" : "")
- + ((dwStatus & DSBSTATUS_LOOPING) ? " looping" : "")
- #if DIRECTSOUND_VERSION >= 0x0700
- + ((dwStatus & DSBSTATUS_LOCHARDWARE) ? " in hardware" : "")
- + ((dwStatus & DSBSTATUS_LOCSOFTWARE) ? " in software" : "")
- #endif
- + (m_b3D ? ("\n" + strIndent + " Pos("
- + m_vectPosition.X() + ", " + m_vectPosition.Y() + ", "
- + m_vectPosition.Z() + ")")
- + (m_bListenerRelative ? " listener rel" : "")
- : ZString(""))
- + "\n";
- }
- #endif
- /////////////////////////////////////////////////////////////////////////////
- //
- // DS3DStaticSoundBuffer
- //
- /////////////////////////////////////////////////////////////////////////////
- // a cache of currently playing static sound buffers
- DS3DStaticSoundBuffer::BufferCache DS3DStaticSoundBuffer::bufferCache;
- // restore the contents of the buffer after a buffer loss
- HRESULT DS3DStaticSoundBuffer::RestoreBuffer()
- {
- HRESULT hr;
- // restore the buffer
- hr = m_pdirectsoundbuffer->Restore();
- if (ZFailed(hr)) return hr;
- // restore the contents of the buffer
- hr = LoadData();
- if (ZFailed(hr)) return hr;
- return S_OK;
- };
- // fills the buffer with data from the data source
- HRESULT DS3DStaticSoundBuffer::LoadData()
- {
- void *pvBlock1Data, *pvBlock2Data;
- DWORD dwBlock1Length, dwBlock2Length;
- HRESULT hr;
- // try locking the sound buffer
- hr = m_pdirectsoundbuffer->Lock(
- 0, m_pdata->GetSize(),
- &pvBlock1Data, &dwBlock1Length,
- &pvBlock2Data, &dwBlock2Length,
- 0
- );
- if (hr == DSERR_BUFFERLOST)
- {
- //
- // if the buffer was lost, try reloading it and starting it again.
- //
-
- // restore the buffer
- hr = m_pdirectsoundbuffer->Restore();
- if (ZFailed(hr)) return hr;
- // try locking it again
- hr = m_pdirectsoundbuffer->Lock(
- 0, m_pdata->GetSize(),
- &pvBlock1Data, &dwBlock1Length,
- &pvBlock2Data, &dwBlock2Length,
- 0
- );
- }
- if (ZFailed(hr)) return hr;
- // fill in the contents of the buffer
- m_pdata->GetData(pvBlock1Data, 0, dwBlock1Length);
- if (pvBlock2Data)
- {
- m_pdata->GetData(pvBlock2Data, dwBlock1Length, dwBlock2Length);
- }
- // unlock the buffer
- hr = m_pdirectsoundbuffer->Unlock(
- pvBlock1Data, dwBlock1Length,
- pvBlock2Data, dwBlock2Length
- );
- if (ZFailed(hr)) return hr;
- return S_OK;
- }
- // Constructor
- DS3DStaticSoundBuffer::DS3DStaticSoundBuffer() :
- m_bHasBeenPlayed(false)
- {
- m_iterSelf = bufferCache.end();
- }
- // Destructor
- DS3DStaticSoundBuffer::~DS3DStaticSoundBuffer()
- {
- // remove this buffer from the buffer cache.
- if (m_iterSelf != bufferCache.end())
- {
- bufferCache.erase(m_iterSelf);
- }
- }
- // Initializes this object with the given wave data, 3D support, and sound
- // quality.
- HRESULT DS3DStaticSoundBuffer::Init(IDirectSound* pDirectSound, ISoundPCMData* pdata,
- bool bLooping, bool bSupport3D, ISoundEngine::Quality quality, bool bAllowHardware
- )
- {
- HRESULT hr;
- m_bLooping = bLooping;
- m_pdata = pdata;
- // see if we have an instance of this buffer which we can simply clone
- CacheKey cacheKey(pdata, bSupport3D, quality, bAllowHardware);
- BufferCache::iterator iterCache = bufferCache.find(cacheKey);
- if (iterCache != bufferCache.end())
- {
- hr = DuplicateBuffer(pDirectSound, (*iterCache).second);
- if (FAILED(hr)) return hr;
- }
- else
- {
- // create a new buffer for the sound
- hr = CreateBuffer(pDirectSound, pdata, pdata->GetSize(), true, bSupport3D, quality, bAllowHardware);
- if (FAILED(hr)) return hr;
- // for static buffers, fill the buffer at creation time
- hr = LoadData();
- if (ZFailed(hr)) return hr;
- }
- m_iterSelf = bufferCache.insert(BufferCache::value_type(cacheKey, this));
- return S_OK;
- }
- // starts the given buffer playing at the given position.
- HRESULT DS3DStaticSoundBuffer::Start(DWORD dwPosition, bool bIsStopping)
- {
- HRESULT hr;
- ZAssert(dwPosition <= m_pdata->GetSize());
- if (m_bHasBeenPlayed || dwPosition != 0)
- {
- hr = m_pdirectsoundbuffer->SetCurrentPosition(dwPosition);
- if (ZFailed(hr)) return hr;
- }
- hr = StartImpl(m_bLooping);
- if (FAILED(hr)) return hr;
- m_bHasBeenPlayed = true;
- return S_OK;
- };
- // stops the given buffer.
- HRESULT DS3DStaticSoundBuffer::Stop(bool bForceNow)
- {
- return m_pdirectsoundbuffer->Stop();
- };
- // Gets the current position of the sound
- HRESULT DS3DStaticSoundBuffer::GetPosition(DWORD& dwPosition)
- {
- // for static sounds, the buffer position is the source position
- return m_pdirectsoundbuffer->GetCurrentPosition(&dwPosition, NULL);
- };
- /////////////////////////////////////////////////////////////////////////////
- //
- // DS3DStreamingSoundBuffer
- //
- /////////////////////////////////////////////////////////////////////////////
- // determines if a buffer cursor starting at dwStart and ending at dwEnd
- // would have crossed event dwTrigger.
- inline bool DS3DStreamingSoundBuffer::CrossedBoundary(DWORD dwTrigger, DWORD dwStart, DWORD dwEnd)
- {
- return dwTrigger < m_dwBufferSize
- && ((dwStart <= dwTrigger && (dwEnd >= dwTrigger || dwEnd < dwStart))
- || (dwStart > dwTrigger && (dwEnd < dwStart && dwEnd >= dwTrigger)));
- }
- // updates anything waiting for the play/read pointer to cross a certain
- // point in the buffer.
- void DS3DStreamingSoundBuffer::ReadPointerUpdate(DWORD dwReadOffset)
- {
- if (CrossedBoundary(m_dwStopOffset, m_dwLastReadOffset, dwReadOffset))
- {
- m_bPlayingSilence = true;
- }
- // update the last known read offset
- m_dwLastReadOffset = dwReadOffset;
- }
- // gets the last played source offset from the last played buffer offset.
- DWORD DS3DStreamingSoundBuffer::GetPlayedSourceOffset(DWORD dwLastPlayedPosition)
- {
- if (m_bPlayingSilence)
- {
- return m_pdata->GetSize();
- }
- else
- {
- // figure out the last offset where any real data was written
- DWORD dwWriteOffset = (m_dwStopOffset < m_dwBufferSize)
- ? m_dwStopOffset : m_dwWriteOffset;
- if (dwLastPlayedPosition > dwWriteOffset)
- {
- int nOffset = (int)(m_dwSourceOffset - m_dwBufferSize - dwWriteOffset
- + dwLastPlayedPosition);
- if (nOffset < 0)
- return nOffset + m_dwBufferSize;
- else
- return nOffset;
- }
- else
- {
- return m_dwSourceOffset - dwWriteOffset + dwLastPlayedPosition;
- }
- }
- };
- // restore the contents of the buffer after a buffer loss
- HRESULT DS3DStreamingSoundBuffer::RestoreBuffer()
- {
- HRESULT hr;
- // restore the buffer
- hr = m_pdirectsoundbuffer->Restore();
- if (ZFailed(hr)) return hr;
- // get the last known good playback position
- DWORD dwLastPlayedPosition;
- hr = m_pdirectsoundbuffer->GetCurrentPosition(&dwLastPlayedPosition, NULL);
- if (ZFailed(hr)) return hr;
- // reset the buffer write pointer to the same point
- m_dwWriteOffset = dwLastPlayedPosition;
- // update the read pointer flags, as appropriate
- ReadPointerUpdate(dwLastPlayedPosition);
- // reset the source offset to the last played position (taking into
- // account buffer wraparound).
- m_dwSourceOffset = GetPlayedSourceOffset(dwLastPlayedPosition);
- // restore the contents of the buffer from that position onwards.
- hr = UpdateBufferContents();
- if (ZFailed(hr)) return hr;
- return S_OK;
- };
- // Fills the buffer with as much data as it can handle.
- //
- // bTrustWritePtr is a bit of a hack for ASR sounds. Basically, the
- // problem is that when stop is called, an ASR sound needs to call
- // GetCurrentPosition to figure out the first safe place it can start
- // writing the sound's release. When UpdateBufferContents calls
- // GetCurrentPosition a moment later, however, it may return a slightly
- // larger offset for the minimum write position. We want to use the result
- // of the first call to GetCurrentPosition in this case, and not assert
- // that the write pointer is after the result returned by the second call
- // to GetCurrentPosition.
- HRESULT DS3DStreamingSoundBuffer::UpdateBufferContents(bool bTrustWritePtr)
- {
- HRESULT hr;
- DWORD dwReadOffset, dwMinWriteOffset;
- // get the boundaries of the sound buffer where we can write
- hr = m_pdirectsoundbuffer->GetCurrentPosition(&dwReadOffset, &dwMinWriteOffset);
- if (ZFailed(hr)) return hr;
- // make sure we are writing _after_ the last writable Offset
- // (messy because we have to take into account buffer wraparound)
- if ((((dwReadOffset < m_dwWriteOffset)
- && (dwMinWriteOffset < dwReadOffset || dwMinWriteOffset > m_dwWriteOffset))
- || ((dwReadOffset >= m_dwWriteOffset)
- && (dwMinWriteOffset < dwReadOffset && dwMinWriteOffset > m_dwWriteOffset))
- )
- && !bTrustWritePtr
- )
- {
- // Doh! We've played incorrect sound!
- debugf("sound buffer underflow; read %d, write %d, min write %d\n",
- dwReadOffset, m_dwWriteOffset, dwMinWriteOffset);
- // recover as best we can.
- m_dwWriteOffset = dwMinWriteOffset;
- }
- // Check to see if we've crossed any events waiting on the play pointer
- ReadPointerUpdate(dwReadOffset);
- // figure out the length of data we want.
- DWORD dwLength = ((dwReadOffset > m_dwWriteOffset)
- ? (dwReadOffset - m_dwWriteOffset)
- : (m_dwBufferSize + dwReadOffset - m_dwWriteOffset));
- void *pvBlock1Data, *pvBlock2Data;
- DWORD dwBlock1Length, dwBlock2Length;
- // try locking the sound buffer
- hr = m_pdirectsoundbuffer->Lock(
- m_dwWriteOffset, dwLength,
- &pvBlock1Data, &dwBlock1Length,
- &pvBlock2Data, &dwBlock2Length,
- 0
- );
- if (hr == DSERR_BUFFERLOST)
- {
- //
- // if the buffer was lost, try reloading it and starting it again.
- //
-
- // restore the buffer
- hr = m_pdirectsoundbuffer->Restore();
- if (ZFailed(hr)) return hr;
- // try locking it again
- hr = m_pdirectsoundbuffer->Lock(
- m_dwWriteOffset, dwLength,
- &pvBlock1Data, &dwBlock1Length,
- &pvBlock2Data, &dwBlock2Length,
- 0
- );
- }
- if (ZFailed(hr)) return hr;
- // fill in the contents of the buffer
- hr = StreamData(pvBlock1Data, dwBlock1Length);
- if (ZFailed(hr)) return hr;
- if (pvBlock2Data)
- {
- hr = StreamData(pvBlock2Data, dwBlock2Length);
- if (ZFailed(hr)) return hr;
- }
- // unlock the buffer
- hr = m_pdirectsoundbuffer->Unlock(
- pvBlock1Data, dwBlock1Length,
- pvBlock2Data, dwBlock2Length
- );
- if (ZFailed(hr)) return hr;
- return S_OK;
- }
- // streams the given length of data out to the buffer pointed to by pvBuffer
- HRESULT DS3DStreamingSoundBuffer::StreamData(void *pvBuffer, DWORD dwLength)
- {
- ZAssert(pvBuffer && dwLength != 0);
- DWORD dwLengthRemaining = dwLength;
- BYTE* pcWritePtr = (BYTE*)pvBuffer;
- DWORD dwSourceSize = m_pdata->GetSize();
- // just copy the source to the buffer until the buffer is filled or,
- // for non-looping sounds, we reach the end of the source.
- do {
- // copy data up until the end of the sound.
- DWORD dwCopyLength = min(dwLengthRemaining, dwSourceSize - m_dwSourceOffset);
- // copy the source
- if (dwCopyLength > 0)
- m_pdata->GetData(pcWritePtr, m_dwSourceOffset, dwCopyLength);
-
- // update the pointers
- pcWritePtr += dwCopyLength;
- m_dwSourceOffset += dwCopyLength;
- dwLengthRemaining -= dwCopyLength;
- // if we are looping and we've reached the end, go back the start.
- if (m_dwSourceOffset == dwSourceSize && m_bLooping)
- m_dwSourceOffset = 0;
- } while (dwLengthRemaining > 0 && m_bLooping);
-
- // if we have more that we need to fill in...
- if (dwLengthRemaining != 0)
- {
- // it's past the end of the sound, so fill it with 0's
- BYTE nFillValue = (m_pdata->GetBitsPerSample() == 8) ? 0x80 : 0;
- memset(pcWritePtr, nFillValue, dwLengthRemaining);
- // mark where the sound stops
- if (m_dwStopOffset >= m_dwBufferSize)
- {
- m_dwStopOffset = m_dwWriteOffset + dwLength - dwLengthRemaining;
- }
- }
- // update the write offset
- m_dwWriteOffset = (m_dwWriteOffset + dwLength) % m_dwBufferSize;
- return S_OK;
- }
- // Initializes this object with the given wave data, 3D support, sound
- // quality, and buffer length (in seconds)
- HRESULT DS3DStreamingSoundBuffer::Init(IDirectSound* pDirectSound, ISoundPCMData* pdata,
- bool bLooping, bool bSupport3D, ISoundEngine::Quality quality, bool bAllowHardware,
- float fBufferLength
- )
- {
- HRESULT hr;
- m_bLooping = bLooping;
- m_pdata = pdata;
- m_dwBufferSize = (DWORD)(fBufferLength * pdata->GetBytesPerSec());
- // create a new buffer for the sound
- hr = CreateBuffer(pDirectSound, pdata, m_dwBufferSize, false, bSupport3D, quality, bAllowHardware);
- if (FAILED(hr)) return hr;
- return S_OK;
- }
- // Updates the contents of the streaming buffer.
- bool DS3DStreamingSoundBuffer::Execute()
- {
- HRESULT hr = UpdateBufferContents();
- ZAssert(SUCCEEDED(hr));
- // if we are playing silence, stop the buffer.
- if (m_bPlayingSilence)
- {
- HRESULT hr = m_pdirectsoundbuffer->Stop();
- ZAssert(SUCCEEDED(hr));
- return false;
- }
- else
- {
- return true;
- }
- }
- // Constructor
- DS3DStreamingSoundBuffer::DS3DStreamingSoundBuffer() :
- m_bHasBeenPlayed(false)
- {
- }
- // Destructor
- DS3DStreamingSoundBuffer::~DS3DStreamingSoundBuffer()
- {
- // if this has been played at least once
- if (m_bHasBeenPlayed)
- {
- // make sure we are not on the update list before we go away
- m_threadUpdate.RemoveTask(this);
- }
- }
- // Initializes this object with the given wave data, 3D support, and sound
- // quality.
- HRESULT DS3DStreamingSoundBuffer::Init(IDirectSound* pDirectSound, ISoundPCMData* pdata,
- bool bLooping, bool bSupport3D, ISoundEngine::Quality quality, bool bAllowHardware
- )
- {
- return Init(pDirectSound, pdata, bLooping, bSupport3D, quality, bAllowHardware, 5.0f);
- }
- // starts the given buffer playing at the given position.
- HRESULT DS3DStreamingSoundBuffer::Start(DWORD dwPosition, bool bIsStopping)
- {
- HRESULT hr;
- // make sure this is not already playing
- ZAssert(!m_threadUpdate.HasTask(this));
- if (m_bHasBeenPlayed)
- {
- hr = m_pdirectsoundbuffer->SetCurrentPosition(0);
- if (ZFailed(hr)) return hr;
- }
- ZAssert(dwPosition <= m_pdata->GetSize());
- m_dwSourceOffset = dwPosition;
- m_dwWriteOffset = 0;
- m_dwLastReadOffset = 0;
- m_dwStopOffset = m_dwBufferSize;
- m_bPlayingSilence = false;
- UpdateBufferContents();
- hr = StartImpl(true);
- if (FAILED(hr)) return hr;
- m_bHasBeenPlayed = true;
- // add it to the update thread's list
- m_threadUpdate.AddTask(this);
- return S_OK;
- };
- // stops the given buffer.
- HRESULT DS3DStreamingSoundBuffer::Stop(bool bForceNow)
- {
- // remove this from the update thread's task list
- m_threadUpdate.RemoveTask(this);
- return m_pdirectsoundbuffer->Stop();
- };
- // Gets the current position of the sound
- HRESULT DS3DStreamingSoundBuffer::GetPosition(DWORD& dwPosition)
- {
- DWORD dwBufferPosition;
- // get the buffer position is the source position
- HRESULT hr = m_pdirectsoundbuffer->GetCurrentPosition(&dwBufferPosition, NULL);
- if (ZFailed(hr)) return hr;
- // update the read pointer flags, as appropriate
- ReadPointerUpdate(dwBufferPosition);
- // translate from that to the source position
- dwPosition = GetPlayedSourceOffset(dwBufferPosition);
- return S_OK;
- };
- // gets the current status of the buffer
- HRESULT DS3DStreamingSoundBuffer::GetStatus(bool& bPlaying, bool& bBufferLost)
- {
- HRESULT hr;
- DWORD dwStatus;
- hr = m_pdirectsoundbuffer->GetStatus(&dwStatus);
- if (ZFailed(hr)) return hr;
- bPlaying = (dwStatus & DSBSTATUS_PLAYING) != 0;
- bBufferLost = (dwStatus & DSBSTATUS_BUFFERLOST) != 0;
- return S_OK;
- };
- // The update thread which handles filling the buffers as they play
- TaskListThread DS3DStreamingSoundBuffer::m_threadUpdate(THREAD_PRIORITY_TIME_CRITICAL, 500);
- /////////////////////////////////////////////////////////////////////////////
- //
- // DS3DASRSoundBuffer
- //
- /////////////////////////////////////////////////////////////////////////////
- // Destructor
- DS3DASRSoundBuffer::~DS3DASRSoundBuffer()
- {
- // if this has been played at least once
- if (m_bHasBeenPlayed)
- {
- // make sure we are not on the update list before we go away
- m_threadUpdate.RemoveTask(this);
- m_bHasBeenPlayed = false;
- }
- }
- // updates anything waiting for the play/read pointer to cross a certain
- // point in the buffer.
- void DS3DASRSoundBuffer::ReadPointerUpdate(DWORD dwReadOffset)
- {
- if (CrossedBoundary(m_dwSustainBufferOffset, m_dwLastReadOffset, dwReadOffset))
- {
- m_bPlayingSustain = true;
- }
- if (CrossedBoundary(m_dwReleaseBufferOffset, m_dwLastReadOffset, dwReadOffset))
- {
- ZAssert(m_bEnding);
- m_bPlayingRelease = true;
- }
- DS3DStreamingSoundBuffer::ReadPointerUpdate(dwReadOffset);
- }
- // gets the last played source offset from the last played buffer offset.
- DWORD DS3DASRSoundBuffer::GetPlayedSourceOffset(DWORD dwLastPlayedPosition)
- {
- if (m_bPlayingSilence)
- {
- return m_pdata->GetSize();
- }
- else
- {
- // figure out the buffer offset and source offset that are most relevant
- DWORD dwReferenceOffset;
- DWORD dwSourceOffset;
-
- // if we are in the attack portion
- if (!m_bPlayingSustain)
- {
- // if we have written part of the sustain section
- if (m_dwSustainBufferOffset < m_dwBufferSize)
- {
- // use the start of the sustain section as our reference point.
- dwReferenceOffset = m_dwSustainBufferOffset;
- dwSourceOffset = m_dwLoopOffset;
- }
- else
- {
- // use the real values
- dwReferenceOffset = m_dwWriteOffset;
- dwSourceOffset = m_dwSourceOffset;
- }
- }
- // if we are in the sustain portion
- else if (!m_bPlayingRelease)
- {
- // if we have written part of the release section
- if (m_dwReleaseBufferOffset < m_dwBufferSize)
- {
- // use the start of the release section as our reference point.
- dwReferenceOffset = m_dwReleaseBufferOffset;
- dwSourceOffset = m_dwLoopOffset + m_dwLoopLength;
- }
- else
- {
- // use the real values
- dwReferenceOffset = m_dwWriteOffset;
- dwSourceOffset = m_dwSourceOffset;
- }
- }
- // if we are in the release portion heading towards silence
- else
- {
- // if we have started writing silence
- if (m_dwStopOffset < m_dwBufferSize)
- {
- // use the end of the sound as our reference point.
- dwReferenceOffset = m_dwStopOffset;
- dwSourceOffset = m_pdata->GetSize();
- }
- else
- {
- // use the real values
- dwReferenceOffset = m_dwWriteOffset;
- dwSourceOffset = m_dwSourceOffset;
- }
- }
-
- // figure out the space between the buffer and the reference point
- int nDelta = int(dwReferenceOffset) - int(dwLastPlayedPosition);
- if (nDelta <= 0)
- {
- nDelta += m_dwBufferSize;
- if (nDelta < 0)
- {
- ZAssert(false);
- nDelta = 0;
- }
- }
- // use that to figure out the offset
- // if we are in the sustain loop
- if (m_bPlayingSustain && !m_bPlayingRelease)
- {
- // we need to figure out where in the sustain loop this puts us
- int nOffset = dwSourceOffset - (nDelta % m_dwLoopLength);
- if (nOffset < m_dwLoopOffset)
- nOffset += m_dwLoopLength;
- return nOffset;
- }
- else
- {
- // just adjust in the obvious way
- ZAssert(int(dwSourceOffset) - nDelta >= 0);
- return int(dwSourceOffset) - nDelta;
- }
- }
- };
- // streams the given length of data out to the buffer pointed to by pvBuffer
- HRESULT DS3DASRSoundBuffer::StreamData(void *pvBuffer, DWORD dwLength)
- {
- ZAssert(pvBuffer && dwLength != 0);
- DWORD dwSourceSize = m_pdata->GetSize();
- DWORD dwLengthRemaining = dwLength;
- BYTE* pcWritePtr = (BYTE*)pvBuffer;
- DWORD dwCopyLength;
- // if we are writing the attack portion of the sound...
- if (m_dwSourceOffset < m_dwLoopOffset)
- {
- // copy data up until the end of the attack or end of the write block.
- dwCopyLength = min(dwLengthRemaining, m_dwLoopOffset - m_dwSourceOffset);
- // copy the source
- if (dwCopyLength > 0)
- {
- m_pdata->GetData(pcWritePtr, m_dwSourceOffset, dwCopyLength);
- }
-
- // update the pointers
- pcWritePtr += dwCopyLength;
- m_dwSourceOffset += dwCopyLength;
- dwLengthRemaining -= dwCopyLength;
- // If we've finished writing the attack, mark the beginning of the
- // sustain portion.
- if (m_dwSourceOffset >= m_dwLoopOffset)
- {
- ZAssert(m_dwSourceOffset == m_dwLoopOffset);
- m_dwSustainBufferOffset =
- m_dwWriteOffset + dwLength - dwLengthRemaining;
- }
- }
- // write 0 or more sustain loops. Do this by finishing any loop which
- // we have not completed, and then write additional loops if we are not
- // trying to end the sound.
- while (dwLengthRemaining > 0 && (
- (m_dwSourceOffset == m_dwLoopOffset && !m_bEnding)
- || (m_dwSourceOffset > m_dwLoopOffset
- && (m_dwSourceOffset < m_dwLoopOffset + m_dwLoopLength))))
- {
- // copy data up until the end of a sustain loop or end of the write block.
- dwCopyLength = min(dwLengthRemaining,
- m_dwLoopOffset + m_dwLoopLength - m_dwSourceOffset);
- // copy the source
- ZAssert(dwCopyLength > 0);
- m_pdata->GetData(pcWritePtr, m_dwSourceOffset, dwCopyLength);
-
- // update the pointers
- pcWritePtr += dwCopyLength;
- m_dwSourceOffset += dwCopyLength;
- dwLengthRemaining -= dwCopyLength;
- // If we've finished an entire loop...
- if (m_dwSourceOffset >= m_dwLoopOffset + m_dwLoopLength)
- {
- ZAssert(m_dwSourceOffset == m_dwLoopOffset + m_dwLoopLength);
-
- // reset the source offset to the beginning of the loop
- m_dwSourceOffset = m_dwLoopOffset;
- // if this is the end of the sustain section, keep track of
- // that.
- if (m_bEnding)
- {
- m_dwReleaseBufferOffset =
- m_dwWriteOffset + dwLength - dwLengthRemaining;
- }
- }
- }
- // write the release section of the loop, if appropriate
- if (dwLengthRemaining > 0 && m_dwSourceOffset < dwSourceSize)
- {
- ZAssert(m_bEnding);
- ZAssert(m_dwSourceOffset == m_dwLoopOffset
- || (m_dwSourceOffset >= m_dwLoopOffset + m_dwLoopLength));
-
- // when we first get here, adjust the read pointer from the
- // beginning of the sustain loop to the begining of the release.
- if (m_dwSourceOffset < m_dwLoopOffset + m_dwLoopLength)
- m_dwSourceOffset = m_dwLoopOffset + m_dwLoopLength;
-
- // copy data up until the end or end of the write block.
- dwCopyLength = min(dwLengthRemaining, dwSourceSize - m_dwSourceOffset);
- // copy the source
- if (dwCopyLength > 0)
- {
- m_pdata->GetData(pcWritePtr, m_dwSourceOffset, dwCopyLength);
- }
-
- // update the pointers
- pcWritePtr += dwCopyLength;
- m_dwSourceOffset += dwCopyLength;
- dwLengthRemaining -= dwCopyLength;
- // If we've finished writing the release, mark where the sound ends
- if (m_dwSourceOffset >= dwSourceSize)
- {
- ZAssert(m_dwSourceOffset == dwSourceSize);
- m_dwStopOffset = m_dwWriteOffset + dwLength - dwLengthRemaining;
- }
- }
- // if we have more that we need to fill in, then we need to fill with silence
- if (dwLengthRemaining != 0)
- {
- // it's past the end of the sound, so fill it with 0's
- BYTE nFillValue = (m_pdata->GetBitsPerSample() == 8) ? 0x80 : 0;
- memset(pcWritePtr, nFillValue, dwLengthRemaining);
- }
- // update the write offset
- m_dwWriteOffset = (m_dwWriteOffset + dwLength) % m_dwBufferSize;
- return S_OK;
- }
- // Initializes this object with the given wave data, 3D support, and sound
- // quality.
- HRESULT DS3DASRSoundBuffer::Init(IDirectSound* pDirectSound, ISoundPCMData* pdata,
- DWORD dwLoopOffset, DWORD dwLoopLength, bool bSupport3D,
- ISoundEngine::Quality quality, bool bAllowHardware
- )
- {
- ZAssert(dwLoopOffset + dwLoopLength < pdata->GetSize());
- ZAssert(dwLoopLength > 0);
- HRESULT hr;
- m_pdata = pdata;
- m_dwLoopOffset = dwLoopOffset;
- m_dwLoopLength = dwLoopLength;
- // REVIEW: what size buffer would work best for these sounds?
- hr = DS3DStreamingSoundBuffer::Init(
- pDirectSound, pdata, false, bSupport3D, quality, bAllowHardware, 1.5f);
- if (ZFailed(hr)) return hr;
- return S_OK;
- }
- // starts the given buffer playing at the given position.
- HRESULT DS3DASRSoundBuffer::Start(DWORD dwPosition, bool bIsStopping)
- {
- ZAssert(bIsStopping || dwPosition < m_dwLoopOffset + m_dwLoopLength);
- DWORD dwSourceLength = m_pdata->GetSize();
- m_dwSustainBufferOffset = m_dwBufferSize;
- m_bPlayingSustain = dwPosition >= m_dwLoopOffset;
- m_dwReleaseBufferOffset = m_dwBufferSize;
- m_bPlayingRelease = dwPosition >= m_dwLoopOffset + m_dwLoopLength;
- ZAssert(m_bPlayingRelease ? bIsStopping : true);
- m_bEnding = bIsStopping;
- DS3DStreamingSoundBuffer::Start(dwPosition, bIsStopping);
- return S_OK;
- };
- // stops the given buffer.
- HRESULT DS3DASRSoundBuffer::Stop(bool bForceNow)
- {
- if (bForceNow)
- {
- return DS3DStreamingSoundBuffer::Stop(bForceNow);
- }
- else if (!m_bEnding)
- {
- // remove this from the update thread's task list to avoid race
- // conditions.
- m_threadUpdate.RemoveTask(this);
- // transition to the release as soon as possible.
- // to do this, we just invalidate the buffer as far back as possible
- // and rewrite it with the knowledge that we are ending the sound.
- HRESULT hr;
- // get the first possible write position
- DWORD dwWritePosition;
- hr = m_pdirectsoundbuffer->GetCurrentPosition(NULL, &dwWritePosition);
- if (ZFailed(hr)) return hr;
- // reset the source offset to the new write position (taking into
- // account buffer wraparound).
- int nWriteDelta = dwWritePosition - m_dwLastReadOffset;
- if (nWriteDelta < 0)
- {
- nWriteDelta += m_dwBufferSize;
- }
- ZAssert(nWriteDelta >= 0);
- DWORD dwLastPlayedSourceOffset = GetPlayedSourceOffset(m_dwLastReadOffset);
- if (dwLastPlayedSourceOffset + nWriteDelta > m_dwLoopOffset)
- {
- m_dwSourceOffset =
- (dwLastPlayedSourceOffset + nWriteDelta - m_dwLoopOffset)
- % m_dwLoopLength + m_dwLoopOffset;
- }
- else
- {
- m_dwSourceOffset = dwLastPlayedSourceOffset + nWriteDelta;
- }
- // reset the buffer write pointer to the same point
- m_dwWriteOffset = dwWritePosition;
- // restore the contents of the buffer from that position onwards.
- m_bEnding = true;
- hr = UpdateBufferContents(true);
- if (ZFailed(hr)) return hr;
- // put it back in the update loop
- m_threadUpdate.AddTask(this);
- }
- return S_OK;
- };
- };
|