Microsoft-3D-Movie-Maker/kauai/SRC/SNDAM.CPP

1023 lines
24 KiB
C++

/* Copyright (c) Microsoft Corporation.
Licensed under the MIT License. */
/***************************************************************************
Author: ShonK
Project: Kauai
Copyright (c) Microsoft Corporation
Audioman based sound classes.
***************************************************************************/
#include "frame.h"
#include "audioman.h"
#include "sndampri.h"
ASSERTNAME
// CHECK_AUDIO_DEVCAPS turns on using waveOutGetDevCaps to check for
// audio device characteristics, to workaround AudioMan 1.0 always returning
// whatever device was asked for, regardless if device actually supports format.
// In case of asking for a 16 bit device, and there is only an 8 bit device, the
// open will succeed, causing sub-optimal audioman performance on an 8 bit card.
// With AudioMan 1.5, this should go away.
#define CHECK_AUDIO_DEVCAPS
// Initialize the maximum mem footprint of wave sounds.
long SDAM::vcbMaxMemWave = 40 * 1024;
static IAMMixer *_pamix; // the audioman mixer
static ulong _luGroup; // the group number
static bool _fGrouped; // whether new sounds are grouped
static long _cactGroup; // group nesting count
static ulong _luFormat; // format mixer is in
static ulong _luCacheTime; // buffer size for mixer
RTCLASS(SDAM)
RTCLASS(CAMS)
RTCLASS(AMQUE)
/***************************************************************************
Constructor for a streamed block.
***************************************************************************/
STBL::STBL(void)
{
AssertThisMem();
// WARNING: this is not allocated using our NewObj because STBL is not
// based on BASE. So fields are not automatically initialized to 0.
_cactRef = 1;
_ib = 0;
}
/***************************************************************************
Destructor for a streamed block.
***************************************************************************/
STBL::~STBL(void)
{
AssertThisMem();
}
/***************************************************************************
QueryInterface for STBL.
***************************************************************************/
STDMETHODIMP STBL::QueryInterface(REFIID riid, void ** ppv)
{
AssertThis(0);
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IStream))
{
*ppv = (void *)this;
AddRef();
return S_OK;
}
*ppv = pvNil;
return E_NOINTERFACE;
}
/***************************************************************************
Increment the reference count.
***************************************************************************/
STDMETHODIMP_(ULONG) STBL::AddRef(void)
{
AssertThis(0);
return ++_cactRef;
}
/***************************************************************************
Decrement the reference count.
***************************************************************************/
STDMETHODIMP_(ULONG) STBL::Release(void)
{
AssertThis(0);
long cactRef;
if ((cactRef = --_cactRef) == 0)
delete this;
return cactRef;
}
/***************************************************************************
Read some stuff.
***************************************************************************/
STDMETHODIMP STBL::Read(void * pv, ULONG cb, ULONG * pcb)
{
AssertThis(0);
AssertPvCb(pv, cb);
AssertNilOrVarMem(pcb);
cb = LwMin(_blck.Cb() - _ib, cb);
if (_blck.FReadRgb(pv, cb, _ib))
{
_ib += cb;
if (pvNil != pcb)
*pcb = cb;
return NOERROR;
}
if (pvNil != pcb)
*pcb = 0;
return ResultFromScode(STG_E_READFAULT);
}
/***************************************************************************
Seek to a place.
***************************************************************************/
STDMETHODIMP STBL::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin,
ULARGE_INTEGER * plibNewPosition)
{
AssertThis(0);
AssertNilOrVarMem(plibNewPosition);
switch (dwOrigin)
{
case STREAM_SEEK_SET:
break;
case STREAM_SEEK_CUR:
dlibMove.QuadPart += _ib;
break;
case STREAM_SEEK_END:
dlibMove.QuadPart += _blck.Cb();
break;
}
if (dlibMove.QuadPart < 0 || dlibMove.QuadPart > _blck.Cb())
{
if (pvNil != plibNewPosition)
plibNewPosition->QuadPart = _ib;
return E_INVALIDARG;
}
_ib = (long)dlibMove.QuadPart;
if (pvNil != plibNewPosition)
plibNewPosition->QuadPart = _ib;
return S_OK;
}
/***************************************************************************
Static method to create a new stream wrapper around a flo.
***************************************************************************/
PSTBL STBL::PstblNew(FLO *pflo, bool fPacked)
{
AssertPo(pflo, ffloReadable);
PSTBL pstbl;
BLCK blck;
PBLCK pblck;
if (pvNil == (pstbl = new STBL))
return pvNil;
pblck = &pstbl->_blck;
if (fPacked)
{
// unpack the block
pblck->Set(pflo, fPacked);
if (!pblck->FUnpackData())
{
delete pstbl;
return pvNil;
}
// see if it's too big to keep in memory
if (pstbl->CbMem() > SDAM::vcbMaxMemWave)
{
// try to put the sound on disk
HQ hq = pblck->HqFree();
AssertHq(hq);
if (pblck->FSetTemp(CbOfHq(hq), fTrue) &&
pblck->FWriteHq(hq, 0))
{
FreePhq(&hq);
}
else
pblck->SetHq(&hq);
}
}
else
{
// see if it's on a removeable disk
FNI fni;
pflo->pfil->GetFni(&fni);
if (fni.Grfvk() & (fvkFloppy | fvkCD | fvkRemovable))
{
// cache to the hard drive or memory, depending on the size
BLCK blck(pflo);
if (!pblck->FSetTemp(pflo->cb,
blck.Cb() + size(STBL) > SDAM::vcbMaxMemWave) ||
!blck.FWriteToBlck(pblck))
{
delete pstbl;
return pvNil;
}
}
else
pblck->Set(pflo);
}
AssertPo(pstbl, 0);
return pstbl;
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of a STBL.
***************************************************************************/
void STBL::AssertValid(ulong grf)
{
AssertThisMem();
AssertPo(&_blck, 0);
AssertIn(_ib, 0, _blck.Cb() + 1);
}
/***************************************************************************
Mark memory for the STBL.
***************************************************************************/
void STBL::MarkMem(void)
{
AssertValid(0);
MarkMemObj(&_blck);
}
#endif //DEBUG
/***************************************************************************
Constructor for a cached AudioMan sound.
***************************************************************************/
CAMS::CAMS(void)
{
AssertBaseThis(fobjAllocated);
}
/***************************************************************************
Destructor for a cached AudioMan sound.
***************************************************************************/
CAMS::~CAMS(void)
{
AssertBaseThis(fobjAllocated);
ReleasePpo(&psnd);
ReleasePpo(&_pstbl);
}
/***************************************************************************
Static BACO reader method to put together a Cached AudioMan sound.
***************************************************************************/
bool CAMS::FReadCams(PCRF pcrf, CTG ctg, CNO cno, PBLCK pblck,
PBACO *ppbaco, long *pcb)
{
AssertPo(pcrf, 0);
AssertPo(pblck, 0);
AssertNilOrVarMem(ppbaco);
AssertVarMem(pcb);
FLO flo;
bool fPacked;
PCAMS pcams = pvNil;
PSTBL pstbl = pvNil;
*pcb = size(CAMS) + size(STBL);
if (pvNil == ppbaco)
return fTrue;
*ppbaco = pvNil;
if (!pcrf->Pcfl()->FFindFlo(ctg, cno, &flo))
return fFalse;
fPacked = pcrf->Pcfl()->FPacked(ctg, cno);
if (pvNil == (pstbl = STBL::PstblNew(&flo, fPacked)))
return fFalse;
*pcb = size(CAMS) + pstbl->CbMem();
if (pvNil == (pcams = NewObj CAMS) ||
FAILED(AllocSoundFromStream(&pcams->psnd, pstbl, fTrue, pvNil)))
{
ReleasePpo(&pcams);
}
if (pvNil != pcams)
pcams->_pstbl = pstbl;
else
ReleasePpo(&pstbl);
AssertNilOrPo(pcams, 0);
*ppbaco = pcams;
return pvNil != *ppbaco;
}
/***************************************************************************
Static BACO reader method to put together a Cached AudioMan sound.
***************************************************************************/
PCAMS CAMS::PcamsNewLoop(PCAMS pcamsSrc, long cactPlay)
{
AssertPo(pcamsSrc, 0);
Assert(cactPlay != 1, "bad loop count");
PCAMS pcams = pvNil;
if (pvNil == (pcams = NewObj CAMS) ||
FAILED(AllocLoopFilter(&pcams->psnd, pcamsSrc->psnd,
cactPlay - 1)))
{
ReleasePpo(&pcams);
}
if (pvNil != pcams)
{
pcams->_pstbl = pcamsSrc->_pstbl;
pcams->_pstbl->AddRef();
}
AssertNilOrPo(pcams, 0);
return pcams;
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of a CAMS.
***************************************************************************/
void CAMS::AssertValid(ulong grf)
{
CAMS_PAR::AssertValid(0);
AssertPo(_pstbl, 0);
Assert(psnd != pvNil, 0);
}
/***************************************************************************
Mark memory for the CAMS.
***************************************************************************/
void CAMS::MarkMem(void)
{
AssertValid(0);
CAMS_PAR::MarkMem();
if (pvNil != _pstbl)
_pstbl->MarkMem();
}
#endif //DEBUG
/***************************************************************************
Constructor for our notify sink.
***************************************************************************/
AMNOT::AMNOT(void)
{
_cactRef = 1;
_pamque = pvNil;
}
/***************************************************************************
Set the AMQUE that we're to notify.
***************************************************************************/
void AMNOT::Set(PAMQUE pamque)
{
AssertNilOrVarMem(pamque);
_pamque = pamque;
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of a AMNOT.
***************************************************************************/
void AMNOT::AssertValid(ulong grf)
{
AssertThisMem();
AssertNilOrVarMem(_pamque);
}
#endif //DEBUG
/***************************************************************************
QueryInterface for AMNOT.
***************************************************************************/
STDMETHODIMP AMNOT::QueryInterface(REFIID riid, void ** ppv)
{
AssertThis(0);
if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IAMNotifySink))
{
*ppv = (void *)this;
AddRef();
return S_OK;
}
*ppv = pvNil;
return E_NOINTERFACE;
}
/***************************************************************************
Increment the reference count.
***************************************************************************/
STDMETHODIMP_(ULONG) AMNOT::AddRef(void)
{
AssertThis(0);
return ++_cactRef;
}
/***************************************************************************
Decrement the reference count.
***************************************************************************/
STDMETHODIMP_(ULONG) AMNOT::Release(void)
{
AssertThis(0);
long cactRef;
if ((cactRef = --_cactRef) == 0)
delete this;
return cactRef;
}
/***************************************************************************
The indicated sound is done. Just tell the AMQUE that we got a notify.
***************************************************************************/
STDMETHODIMP_(void) AMNOT::OnCompletion(LPSOUND pSound, DWORD dwPosition)
{
AssertThis(0);
if (pvNil != _pamque)
_pamque->Notify(pSound);
}
/***************************************************************************
Constructor for an audioman queue.
***************************************************************************/
AMQUE::AMQUE(void)
{
}
/***************************************************************************
Destructor for an audioman queue.
***************************************************************************/
AMQUE::~AMQUE(void)
{
if (pvNil != _pchan)
StopAll();
ReleasePpo(&_pchan);
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of a AMQUE.
***************************************************************************/
void AMQUE::AssertValid(ulong grf)
{
AMQUE_PAR::AssertValid(0);
Assert(pvNil != _pchan, 0);
}
#endif //DEBUG
/***************************************************************************
Static method to create a new audioman queue.
***************************************************************************/
PAMQUE AMQUE::PamqueNew(void)
{
PAMQUE pamque;
if (pvNil == (pamque = NewObj AMQUE))
return pvNil;
if (!pamque->_FInit())
ReleasePpo(&pamque);
AssertNilOrPo(pamque, 0);
return pamque;
}
/***************************************************************************
Initialize the audioman queue. Allocate the audioman channel and the
_pglsndin.
***************************************************************************/
bool AMQUE::_FInit(void)
{
AssertBaseThis(0);
if (!AMQUE_PAR::_FInit())
return fFalse;
if (FAILED(_pamix->AllocChannel(&_pchan)))
{
_pchan = pvNil;
return fFalse;
}
if (pvNil == _pchan)
{
Bug("Audioman messed up!");
return fFalse;
}
_amnot.Set(this);
if (FAILED(_pchan->RegisterNotify(&_amnot, NOTIFYSINK_ONCOMPLETION)))
return fFalse;
AssertThis(0);
return fTrue;
}
/***************************************************************************
Enter the critical section protecting member variables.
***************************************************************************/
void AMQUE::_Enter(void)
{
_mutx.Enter();
}
/***************************************************************************
Leave the critical section protecting member variables.
***************************************************************************/
void AMQUE::_Leave(void)
{
_mutx.Leave();
}
/***************************************************************************
Fetch the given sound chunk as a CAMS.
***************************************************************************/
PBACO AMQUE::_PbacoFetch(PRCA prca, CTG ctg, CNO cno)
{
AssertThis(0);
AssertPo(prca, 0);
return prca->PbacoFetch(ctg, cno, &CAMS::FReadCams);
}
/***************************************************************************
An item was added to or deleted from the queue.
***************************************************************************/
void AMQUE::_Queue(long isndinMin)
{
AssertThis(0);
SNDIN sndin;
long isndin;
_Enter();
if (pvNil != _pglsndin)
{
PCAMS pcams;
for (isndin = isndinMin; isndin < _pglsndin->IvMac(); isndin++)
{
_pglsndin->Get(isndin, &sndin);
if (1 == sndin.cactPlay)
continue;
// put a loop filter around it to get seamless sample based looping
if (pvNil != (pcams = CAMS::PcamsNewLoop((PCAMS)sndin.pbaco,
sndin.cactPlay)))
{
sndin.cactPlay = 1; // now it's just one sound
ReleasePpo(&sndin.pbaco);
sndin.pbaco = pcams;
_pglsndin->Put(isndin, &sndin);
}
}
}
if (_isndinCur == isndinMin && pvNil != _pglsndin)
{
for ( ; _isndinCur < _pglsndin->IvMac(); _isndinCur++)
{
_pglsndin->Get(_isndinCur, &sndin);
if (0 <= sndin.cactPause)
break;
}
if (_isndinCur < _pglsndin->IvMac() && 0 == sndin.cactPause)
{
// stop the channel
_pchan->Stop();
// set the volume
_pchan->SetVolume(LuVolScale((ulong)(-1), sndin.vlm));
// if the sound is in memory
if (((PCAMS)sndin.pbaco)->FInMemory())
{
// set the sound source, with no cache (Since it's in memory)
_pchan->SetSoundSrc(((PCAMS)sndin.pbaco)->psnd);
}
else
{
CacheConfig cc;
cc.dwSize = size(cc);
cc.fSrcFormat = fTrue;
cc.lpFormat = pvNil;
cc.dwFormat = _luFormat;
cc.dwCacheTime = 2*_luCacheTime;
// set the sound src, using cache cause it's not in memory
_pchan->SetCachedSrc(((PCAMS)sndin.pbaco)->psnd, &cc);
}
// if there is a starting offset, apply it
if (sndin.dtsStart != 0)
_pchan->SetTimePos(sndin.dtsStart);
if (!_fGrouped || FAILED(_pamix->EnlistGroup(_pchan, _luGroup)))
{
// start the channel
_pchan->Play();
}
_tsStart = TsCurrentSystem() - sndin.dtsStart;
}
else
{
_pchan->Stop();
_pchan->SetSoundSrc(pvNil);
}
}
_Leave();
}
/***************************************************************************
One or more items in the queue were paused.
***************************************************************************/
void AMQUE::_PauseQueue(long isndinMin)
{
AssertThis(0);
SNDIN sndin;
_mutx.Enter();
if (_isndinCur == isndinMin && _pglsndin->IvMac() > _isndinCur)
{
_pglsndin->Get(_isndinCur, &sndin);
sndin.dtsStart = TsCurrentSystem() - _tsStart;
_pglsndin->Put(_isndinCur, &sndin);
_Queue(isndinMin);
}
_mutx.Leave();
}
/***************************************************************************
One or more items in the queue were resumed.
***************************************************************************/
void AMQUE::_ResumeQueue(long isndinMin)
{
AssertThis(0);
_Queue(isndinMin);
}
/***************************************************************************
Called by our notify sink to tell us that the indicated sound is done.
WARNING: this is called in an auxillary thread.
***************************************************************************/
void AMQUE::Notify(LPSOUND psnd)
{
AssertThis(0);
SNDIN sndin;
_Enter();
if (pvNil != _pglsndin && _pglsndin->IvMac() > _isndinCur)
{
_pglsndin->Get(_isndinCur, &sndin);
if (psnd == ((PCAMS)sndin.pbaco)->psnd)
{
if (--sndin.cactPlay == 0)
{
_isndinCur++;
_Queue(_isndinCur);
}
else
{
// play the sound again
_pglsndin->Put(_isndinCur, &sndin);
_pchan->SetSoundSrc(((PCAMS)sndin.pbaco)->psnd);
_tsStart = TsCurrentSystem();
}
}
}
_Leave();
}
/***************************************************************************
Constructor for the audioman device.
***************************************************************************/
SDAM::SDAM(void)
{
_vlm = kvlmFull;
_luVolSys = (ulong)(-1);
}
/***************************************************************************
Destructor for the audioman device.
***************************************************************************/
SDAM::~SDAM(void)
{
AssertBaseThis(0);
if (_fAudioManInited && 0 == _pamix->Release())
_pamix = pvNil;
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of a SDAM.
***************************************************************************/
void SDAM::AssertValid(ulong grf)
{
SDAM_PAR::AssertValid(0);
Assert(_pamix != pvNil, 0);
}
#endif //DEBUG
/***************************************************************************
Static method to create the audioman device.
***************************************************************************/
PSDAM SDAM::PsdamNew(long wav)
{
PSDAM psdam;
if (pvNil == (psdam = NewObj SDAM))
return pvNil;
if (!psdam->_FInit(wav))
ReleasePpo(&psdam);
AssertNilOrPo(psdam, 0);
return psdam;
}
static long _mpwavfmt[] =
{
WAVE_FORMAT_1M08,
WAVE_FORMAT_2M08,
WAVE_FORMAT_4M08,
WAVE_FORMAT_1S08,
WAVE_FORMAT_2S08,
WAVE_FORMAT_4S08,
WAVE_FORMAT_1M16,
WAVE_FORMAT_2M16,
WAVE_FORMAT_4M16,
WAVE_FORMAT_1S16,
WAVE_FORMAT_2S16,
WAVE_FORMAT_4S16,
};
#ifdef CHECK_AUDIO_DEVCAPS
/******************************************************************************
@func WORD | wHaveWaveDevice |
Do we have a wave device capable of playing the passed PCM format(s).
@parm DWORD | dwFormats | WAVE formats needed to be supported. These can be
a bitwise combination of WAVE_FORMAT_???? flags
which are defined in mmsystem.h. If you don't
care what formats are supported you can pass zero.
******************************************************************************/
bool FHaveWaveDevice(DWORD dwReqFormats)
{
WORD wNumWavDev;
WAVEOUTCAPS WOC;
WORD wDevID;
WORD wErr;
// Determine how many WAVE devices are in the user's system
wNumWavDev = waveOutGetNumDevs();
// If there are none, return indicating that
if (0 == wNumWavDev)
return(fFalse);
// Cycle through the WAVE devices to determine if any support
// the desired format.
for (wDevID = 0; wDevID < wNumWavDev; wDevID++)
{
wErr = waveOutGetDevCaps(wDevID, &WOC, sizeof(WAVEOUTCAPS));
// If we obtain a WAVE device's capabilities OK
// and it supports the desired format
if ((0 == wErr) && ((WOC.dwFormats & dwReqFormats) == dwReqFormats))
{
// then return success - we have a device that supports what we want
return fTrue;
}
}
// it doesn't support this device
return fFalse;
}
#endif
/***************************************************************************
Initialize the audioman device.
***************************************************************************/
bool SDAM::_FInit(long wav)
{
AssertBaseThis(0);
MIXERCONFIG mixc;
ADVMIXCONFIG amxc;
if (!SDAM_PAR::_FInit())
return fFalse;
// get IAMMixer interface
if (pvNil != _pamix)
{
_pamix->AddRef();
_fAudioManInited = fTrue;
}
else
{
if (pvNil == (_pamix = GetAudioManMixer()))
return fFalse;
_fAudioManInited = fTrue;
// REVIEW shonk: what values should we use?
mixc.dwSize = size(mixc);
mixc.lpFormat = pvNil;
if (!FIn(wav, 0, kwavLim))
wav = kwav22M16;
mixc.dwFormat = _mpwavfmt[wav];
amxc.dwSize = size(amxc);
amxc.uVoices = 12;
amxc.fRemixEnabled = fTrue;
amxc.uBufferTime = 600;
#ifdef CHECK_AUDIO_DEVCAPS
// if we don't have a device of this format...
if (!FHaveWaveDevice(mixc.dwFormat))
{
// failed, so try dropping to 8 bit
wav += kwav22M8 - kwav22M16;
if (!FIn(wav, 0, kwavLim))
return fFalse;
mixc.dwFormat = _mpwavfmt[wav];
// we'll try to open at 8, cause if the card doesn't
// support it, then WAVE_MAPPER will actually convert
// to the 8 bit format.
}
_luFormat = mixc.dwFormat;
_luCacheTime = amxc.uBufferTime;
// initialize it (done only once...)
if (FAILED(_pamix->Init(vwig.hinst, pvNil, &mixc, &amxc)))
return fFalse;
#else
_luFormat = mixc.dwFormat;
_luCacheTime = amxc.uBufferTime;
// initialize it (done only once...)
if (FAILED(_pamix->Init(vwig.hinst, pvNil, &mixc, &amxc)))
{
// failed, so try at 8 bit
wav += kwav22M8 - kwav22M16;
if (!FIn(wav, 0, kwavLim))
return fFalse;
if (FAILED(_pamix->Init(vwig.hinst, pvNil, &mixc, &amxc)))
return fFalse;
}
#endif
if (FAILED(_pamix->Activate(fTrue)))
return fFalse;
}
_Suspend(_cactSuspend > 0 || !_fActive);
AssertThis(0);
return fTrue;
}
/***************************************************************************
Allocate a new audioman queue.
***************************************************************************/
PSNQUE SDAM::_PsnqueNew(void)
{
AssertThis(0);
return AMQUE::PamqueNew();
}
/***************************************************************************
Activate or deactivate audioman.
***************************************************************************/
void SDAM::_Suspend(bool fSuspend)
{
AssertThis(0);
if (fSuspend)
_pamix->SetMixerVolume(_luVolSys);
if (FAILED(_pamix->Suspend(fSuspend)) && !fSuspend)
PushErc(ercSndamWaveDeviceBusy);
else if (!fSuspend)
{
// becoming active
_pamix->GetMixerVolume(&_luVolSys);
vluSysVolFake = _luVolSys;
_pamix->SetMixerVolume(LuVolScale(_luVolSys, _vlm));
}
}
/***************************************************************************
Set the volume.
***************************************************************************/
void SDAM::SetVlm(long vlm)
{
AssertThis(0);
if (_vlm != vlm)
{
_vlm = vlm;
if (_cactSuspend <= 0 && _fActive)
_pamix->SetMixerVolume(LuVolScale(_luVolSys, vlm));
}
}
/***************************************************************************
Get the current volume.
***************************************************************************/
long SDAM::VlmCur(void)
{
AssertThis(0);
return _vlm;
}
/***************************************************************************
Begin a synchronization group.
***************************************************************************/
void SDAM::BeginSynch(void)
{
AssertThis(0);
if (0 == _cactGroup++)
_fGrouped = SUCCEEDED(_pamix->AllocGroup(&_luGroup));
}
/***************************************************************************
End a synchronization group.
***************************************************************************/
void SDAM::EndSynch(void)
{
AssertThis(0);
if ((0 == --_cactGroup) && _fGrouped)
{
_pamix->StartGroup(_luGroup, fTrue);
_pamix->FreeGroup(_luGroup);
_fGrouped = fFalse;
}
}