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

2491 lines
55 KiB
C++

/* Copyright (c) Microsoft Corporation.
Licensed under the MIT License. */
/***************************************************************************
Author: ShonK
Project: Kauai
Copyright (c) Microsoft Corporation
The midi player device using a Midi Stream.
***************************************************************************/
#include "frame.h"
#include "mdev2pri.h"
ASSERTNAME
RTCLASS(MDWS)
RTCLASS(MSQUE)
RTCLASS(MDPS)
RTCLASS(MSMIX)
RTCLASS(MISI)
RTCLASS(WMS)
RTCLASS(OMS)
const long kdtsMinSlip = kdtsSecond / 30;
const long kcbMaxWmsBuffer = 0x0000FFFF / size(MEV) * size(MEV);
/***************************************************************************
Constructor for the midi stream device.
***************************************************************************/
MDPS::MDPS(void)
{
}
/***************************************************************************
Destructor for the midi stream device.
***************************************************************************/
MDPS::~MDPS(void)
{
AssertBaseThis(0);
ReleasePpo(&_pmsmix);
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of a MDPS.
***************************************************************************/
void MDPS::AssertValid(ulong grf)
{
MDPS_PAR::AssertValid(0);
AssertPo(_pmsmix, 0);
}
/***************************************************************************
Mark memory for the MDPS.
***************************************************************************/
void MDPS::MarkMem(void)
{
AssertValid(0);
MDPS_PAR::MarkMem();
MarkMemObj(_pmsmix);
}
#endif //DEBUG
/***************************************************************************
Static method to create the midi stream device.
***************************************************************************/
PMDPS MDPS::PmdpsNew(void)
{
PMDPS pmdps;
if (pvNil == (pmdps = NewObj MDPS))
return pvNil;
if (!pmdps->_FInit())
ReleasePpo(&pmdps);
AssertNilOrPo(pmdps, 0);
return pmdps;
}
/***************************************************************************
Initialize the midi stream device.
***************************************************************************/
bool MDPS::_FInit(void)
{
AssertBaseThis(0);
if (!MDPS_PAR::_FInit())
return fFalse;
// Create the midi stream output scheduler
if (pvNil == (_pmsmix = MSMIX::PmsmixNew()))
return fFalse;
_Suspend(_cactSuspend > 0 || !_fActive);
AssertThis(0);
return fTrue;
}
/***************************************************************************
Allocate a new midi stream queue.
***************************************************************************/
PSNQUE MDPS::_PsnqueNew(void)
{
AssertThis(0);
return MSQUE::PmsqueNew(_pmsmix);
}
/***************************************************************************
Activate or deactivate the midi stream device.
***************************************************************************/
void MDPS::_Suspend(bool fSuspend)
{
AssertThis(0);
_pmsmix->Suspend(fSuspend);
}
/***************************************************************************
Set the volume.
***************************************************************************/
void MDPS::SetVlm(long vlm)
{
AssertThis(0);
_pmsmix->SetVlm(vlm);
}
/***************************************************************************
Get the current volume.
***************************************************************************/
long MDPS::VlmCur(void)
{
AssertThis(0);
return _pmsmix->VlmCur();
}
/***************************************************************************
Constructor for a midi stream object.
***************************************************************************/
MDWS::MDWS(void)
{
}
/***************************************************************************
Destructor for a Win95 midi stream object.
***************************************************************************/
MDWS::~MDWS(void)
{
ReleasePpo(&_pglmev);
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of a MDWS.
***************************************************************************/
void MDWS::AssertValid(ulong grf)
{
MDWS_PAR::AssertValid(0);
AssertPo(_pglmev, 0);
}
/***************************************************************************
Mark memory for the MDWS.
***************************************************************************/
void MDWS::MarkMem(void)
{
AssertValid(0);
MDWS_PAR::MarkMem();
MarkMemObj(_pglmev);
}
#endif //DEBUG
/***************************************************************************
A baco reader for a midi stream.
***************************************************************************/
bool MDWS::FReadMdws(PCRF pcrf, CTG ctg, CNO cno, PBLCK pblck,
PBACO *ppbaco, long *pcb)
{
AssertPo(pcrf, 0);
AssertPo(pblck, fblckReadable);
AssertNilOrVarMem(ppbaco);
AssertVarMem(pcb);
PMDWS pmdws;
*pcb = pblck->Cb(fTrue);
if (pvNil == ppbaco)
return fTrue;
if (!pblck->FUnpackData())
goto LFail;
if (pvNil == (pmdws = PmdwsRead(pblck)))
{
LFail:
TrashVar(ppbaco);
TrashVar(pcb);
return fFalse;
}
*pcb = pmdws->_pglmev->IvMac() * size(MEV) + size(MDWS);
*ppbaco = pmdws;
return fTrue;
}
/***************************************************************************
Read a midi stream from the given block.
***************************************************************************/
PMDWS MDWS::PmdwsRead(PBLCK pblck)
{
AssertPo(pblck, 0);
PMIDS pmids;
PMDWS pmdws;
if (pvNil == (pmids = MIDS::PmidsRead(pblck)))
return pvNil;
if (pvNil != (pmdws = NewObj MDWS) && !pmdws->_FInit(pmids))
ReleasePpo(&pmdws);
ReleasePpo(&pmids);
AssertNilOrPo(pmdws, 0);
return pmdws;
}
/***************************************************************************
Initialize the MDWS with the midi data in *pmids.
***************************************************************************/
bool MDWS::_FInit(PMIDS pmids)
{
AssertPo(pmids, 0);
MSTP mstp;
ulong tsCur;
MEV rgmev[100];
PMEV pmev, pmevLim;
MIDEV midev;
bool fEvt;
if (pvNil == (_pglmev = GL::PglNew(size(MEV))))
return fFalse;
Assert(MEVT_SHORTMSG == 0,
"this code assumes MEVT_SHORTMSG is 0 and it's not");
ClearPb(rgmev, size(rgmev));
pmev = rgmev;
pmevLim = rgmev + CvFromRgv(rgmev);
// use 1 second per quarter. We'll use 1000 ticks per quarter when
// setting up the stream. The net result is that milliseconds correspond
// to ticks, so no conversion is necessary here.
pmev->dwEvent = ((ulong)MEVT_TEMPO << 24) | 1000000;
pmev++;
mstp.Init(pmids, 0);
tsCur = 0;
for (;;)
{
fEvt = mstp.FGetEvent(&midev);
if (pmev >= pmevLim || !fEvt)
{
// append the MEVs in rgmev to the _pglmev
long imev, cmev;
imev = _pglmev->IvMac();
cmev = pmev - rgmev;
if (!_pglmev->FSetIvMac(imev + cmev))
return fFalse;
CopyPb(rgmev, _pglmev->QvGet(imev), LwMul(cmev, size(MEV)));
if (!fEvt)
{
// Add a final NOP so when we seek and there's only one
// event left, it's not an important one.
rgmev[0].dwDeltaTime = 0;
rgmev[0].dwStreamID = 0;
rgmev[0].dwEvent = (ulong)MEVT_NOP << 24;
if (!_pglmev->FAdd(&rgmev[0]))
return fFalse;
_pglmev->FEnsureSpace(0, fgrpShrink);
break;
}
pmev = rgmev;
}
if (midev.cb > 0)
{
pmev->dwDeltaTime = midev.ts - tsCur;
pmev->dwEvent = midev.lwSend & 0x00FFFFFF;
pmev++;
tsCur = midev.ts;
}
}
_dts = tsCur;
return fTrue;
}
/***************************************************************************
Return a locked pointer to the data.
***************************************************************************/
void *MDWS::PvLockData(long *pcb)
{
AssertThis(0);
AssertVarMem(pcb);
*pcb = LwMul(_pglmev->IvMac(), size(MEV));
return _pglmev->PvLock(0);
}
/***************************************************************************
Balance a call to PvLockData.
***************************************************************************/
void MDWS::UnlockData(void)
{
AssertThis(0);
_pglmev->Unlock();
}
/***************************************************************************
Constructor for a midi stream queue.
***************************************************************************/
MSQUE::MSQUE(void)
{
}
/***************************************************************************
Destructor for a midi stream queue.
***************************************************************************/
MSQUE::~MSQUE(void)
{
if (pvNil != _pmsmix)
{
_pmsmix->FPlay(this);
ReleasePpo(&_pmsmix);
}
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of a MSQUE.
***************************************************************************/
void MSQUE::AssertValid(ulong grf)
{
MSQUE_PAR::AssertValid(0);
AssertPo(_pmsmix, 0);
}
/***************************************************************************
Mark memory for the MSQUE.
***************************************************************************/
void MSQUE::MarkMem(void)
{
AssertValid(0);
MSQUE_PAR::MarkMem();
MarkMemObj(_pmsmix);
}
#endif //DEBUG
/***************************************************************************
Static method to create a new midi stream queue.
***************************************************************************/
PMSQUE MSQUE::PmsqueNew(PMSMIX pmsmix)
{
AssertPo(pmsmix, 0);
PMSQUE pmsque;
if (pvNil == (pmsque = NewObj MSQUE))
return pvNil;
if (!pmsque->_FInit(pmsmix))
ReleasePpo(&pmsque);
AssertNilOrPo(pmsque, 0);
return pmsque;
}
/***************************************************************************
Initialize the midi stream queue.
***************************************************************************/
bool MSQUE::_FInit(PMSMIX pmsmix)
{
AssertPo(pmsmix, 0);
AssertBaseThis(0);
if (!MSQUE_PAR::_FInit())
return fFalse;
_pmsmix = pmsmix;
_pmsmix->AddRef();
AssertThis(0);
return fTrue;
}
/***************************************************************************
Enter the critical section protecting member variables.
***************************************************************************/
void MSQUE::_Enter(void)
{
_mutx.Enter();
}
/***************************************************************************
Leave the critical section protecting member variables.
***************************************************************************/
void MSQUE::_Leave(void)
{
_mutx.Leave();
}
/***************************************************************************
Fetch the given sound chunk as an MDWS.
***************************************************************************/
PBACO MSQUE::_PbacoFetch(PRCA prca, CTG ctg, CNO cno)
{
AssertThis(0);
AssertPo(prca, 0);
return prca->PbacoFetch(ctg, cno, &MDWS::FReadMdws);
}
/***************************************************************************
An item was added to or deleted from the queue.
***************************************************************************/
void MSQUE::_Queue(long isndinMin)
{
AssertThis(0);
SNDIN sndin;
_mutx.Enter();
if (_isndinCur == isndinMin && pvNil != _pglsndin)
{
for ( ; _isndinCur < _pglsndin->IvMac(); _isndinCur++)
{
_pglsndin->Get(_isndinCur, &sndin);
if (0 < sndin.cactPause)
break;
if (0 == sndin.cactPause &&
_pmsmix->FPlay(this, (PMDWS)sndin.pbaco, sndin.sii, sndin.spr,
sndin.cactPlay, sndin.dtsStart, sndin.vlm))
{
_tsStart = TsCurrentSystem() - sndin.dtsStart;
goto LDone;
}
}
_pmsmix->FPlay(this);
}
LDone:
_mutx.Leave();
}
/***************************************************************************
One or more items in the queue were paused.
***************************************************************************/
void MSQUE::_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 MSQUE::_ResumeQueue(long isndinMin)
{
AssertThis(0);
_Queue(isndinMin);
}
/***************************************************************************
Called by the MSMIX to tell us that the indicated sound is done.
WARNING: this is called in an auxillary thread.
***************************************************************************/
void MSQUE::Notify(PMDWS pmdws)
{
AssertThis(0);
SNDIN sndin;
_mutx.Enter();
if (pvNil != _pglsndin && _pglsndin->IvMac() > _isndinCur)
{
_pglsndin->Get(_isndinCur, &sndin);
if (pmdws == sndin.pbaco)
{
_isndinCur++;
_Queue(_isndinCur);
}
}
_mutx.Leave();
}
/***************************************************************************
Constructor for the midi stream output object.
***************************************************************************/
MSMIX::MSMIX(void)
{
_vlmBase = kvlmFull;
_vlmSound = kvlmFull;
}
/***************************************************************************
Destructor for the midi stream output object.
***************************************************************************/
MSMIX::~MSMIX(void)
{
Assert(pvNil == _pmisi || !_pmisi->FActive(), "MISI still active!");
if (hNil != _hth)
{
// tell the thread to end and wait for it to finish
_fDone = fTrue;
SetEvent(_hevt);
WaitForSingleObject(_hth, INFINITE);
}
if (hNil != _hevt)
CloseHandle(_hevt);
if (pvNil != _pglmsos)
{
Assert(_pglmsos->IvMac() == 0, "MSMIX still has active sounds");
ReleasePpo(&_pglmsos);
}
ReleasePpo(&_pmisi);
ReleasePpo(&_pglmevKey);
}
/***************************************************************************
Static method to create a new MSMIX.
***************************************************************************/
PMSMIX MSMIX::PmsmixNew(void)
{
PMSMIX pmsmix;
if (pvNil == (pmsmix = NewObj MSMIX))
return pvNil;
if (!pmsmix->_FInit())
ReleasePpo(&pmsmix);
AssertNilOrPo(pmsmix, 0);
return pmsmix;
}
/***************************************************************************
Initialize the MSMIX - allocate the pglmsos and the midi stream api
object.
***************************************************************************/
bool MSMIX::_FInit(void)
{
AssertBaseThis(0);
ulong luThread;
if (pvNil == (_pglmsos = GL::PglNew(size(MSOS))))
return fFalse;
_pglmsos->SetMinGrow(1);
if (pvNil == (_pmisi = WMS::PwmsNew(_MidiProc, (ulong)this)) &&
pvNil == (_pmisi = OMS::PomsNew(_MidiProc, (ulong)this)))
{
return fFalse;
}
if (hNil == (_hevt = CreateEvent(pvNil, fFalse, fFalse, pvNil)))
return fFalse;
// create the thread
if (hNil == (_hth = CreateThread(pvNil, 1024, MSMIX::_ThreadProc,
this, 0, &luThread)))
{
return fFalse;
}
return fTrue;
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of a MSMIX.
***************************************************************************/
void MSMIX::AssertValid(ulong grf)
{
MSMIX_PAR::AssertValid(0);
_mutx.Enter();
Assert(hNil != _hevt, "nil event");
Assert(hNil != _hth, "nil thread");
AssertPo(_pglmsos, 0);
AssertPo(_pmisi, 0);
AssertNilOrPo(_pglmevKey, 0);
_mutx.Leave();
}
/***************************************************************************
Mark memory for the MSMIX.
***************************************************************************/
void MSMIX::MarkMem(void)
{
AssertValid(0);
MSMIX_PAR::MarkMem();
_mutx.Enter();
MarkMemObj(_pglmsos);
MarkMemObj(_pmisi);
MarkMemObj(_pglmevKey);
_mutx.Leave();
}
#endif //DEBUG
/***************************************************************************
Suspend or resume the midi stream mixer.
***************************************************************************/
void MSMIX::Suspend(bool fSuspend)
{
AssertThis(0);
_mutx.Enter();
if (fSuspend)
_StopStream();
if (!_pmisi->FActivate(!fSuspend) && !fSuspend)
PushErc(ercSndMidiDeviceBusy);
_Restart();
_mutx.Leave();
}
/***************************************************************************
If we're currently playing a midi stream stop it. Assumes the mutx is
already checked out exactly once.
***************************************************************************/
void MSMIX::_StopStream(void)
{
AssertThis(0);
if (!_fPlaying)
return;
// set _fPlaying to false first so the call back knows that we're
// aborting the current stream - so it doesn't notify us.
_fPlaying = fFalse;
_pmisi->StopPlaying();
// Wait for the buffers to be returned
_fWaiting = fTrue;
_mutx.Leave();
while (_cpvOut > 0)
Sleep(0);
_mutx.Enter();
_fWaiting = fFalse;
}
/***************************************************************************
Set the volume for the midi stream output device.
***************************************************************************/
void MSMIX::SetVlm(long vlm)
{
AssertThis(0);
ulong luHigh, luLow;
_mutx.Enter();
_vlmBase = vlm;
MulLu(_vlmBase, _vlmSound, &luHigh, &luLow);
_pmisi->SetVlm(LuHighLow(SuLow(luHigh), SuHigh(luLow)));
_mutx.Leave();
}
/***************************************************************************
Get the current volume.
***************************************************************************/
long MSMIX::VlmCur(void)
{
AssertThis(0);
return _vlmBase;
}
/***************************************************************************
Play the given midi stream from the indicated queue.
***************************************************************************/
bool MSMIX::FPlay(PMSQUE pmsque, PMDWS pmdws, long sii, long spr,
long cactPlay, ulong dtsStart, long vlm)
{
AssertThis(0);
AssertPo(pmsque, 0);
AssertNilOrPo(pmdws, 0);
long imsos;
MSOS msos;
bool fNew = fFalse;
bool fRet = fTrue;
if (pvNil != pmdws && pmdws->Dts() == 0)
return fFalse;
_mutx.Enter();
// stop any current midi stream on this msque
for (imsos = _pglmsos->IvMac(); imsos-- > 0; )
{
_pglmsos->Get(imsos, &msos);
if (msos.pmsque == pmsque)
{
if (0 == imsos)
_StopStream();
_pglmsos->Get(imsos, &msos);
_pglmsos->Delete(imsos);
break;
}
}
// start up the new midi stream
if (pvNil != pmdws)
{
// find the position to insert the new one
for (imsos = 0; imsos < _pglmsos->IvMac(); imsos++)
{
_pglmsos->Get(imsos, &msos);
if (msos.spr < spr || msos.spr == spr && msos.sii < sii)
{
// insert before the current one
break;
}
}
if (0 == imsos)
{
fNew = fTrue;
_StopStream();
}
ClearPb(&msos, size(msos));
msos.pmsque = pmsque;
msos.pmdws = pmdws;
msos.sii = sii;
msos.spr = spr;
msos.cactPlay = cactPlay;
msos.dts = pmdws->Dts();
msos.dtsStart = dtsStart;
msos.vlm = vlm;
msos.tsStart = TsCurrentSystem() - msos.dtsStart;
if (!_pglmsos->FInsert(imsos, &msos))
{
fRet = fFalse;
fNew = fFalse;
}
}
_Restart(fNew);
_mutx.Leave();
return fRet;
}
/***************************************************************************
The sound list changed so make sure we're playing the first tune.
Assumes the mutx is already checked out.
***************************************************************************/
void MSMIX::_Restart(bool fNew)
{
AssertThis(0);
if (_pmisi->FActive() && !_fPlaying && _pglmsos->IvMac() > 0)
{
// start playing the first MSOS
MSOS msos;
ulong tsCur = TsCurrentSystem();
if (fNew)
{
_pglmsos->Get(0, &msos);
msos.tsStart = tsCur - msos.dtsStart;
_pglmsos->Put(0, &msos);
}
_SubmitBuffers(tsCur);
}
// signal the aux thread that the list changed
SetEvent(_hevt);
}
/***************************************************************************
Submit the buffer(s) for the current MSOS. Assumes the mutx is already
checked out.
***************************************************************************/
void MSMIX::_SubmitBuffers(ulong tsCur)
{
Assert(!_fPlaying, "already playing!");
long cb, cbSkip;
MSOS msos;
long imsos;
void *pvData;
long cactSkip;
for (imsos = 0; imsos < _pglmsos->IvMac(); imsos++)
{
_pglmsos->Get(imsos, &msos);
cactSkip = (tsCur - msos.tsStart) / msos.dts;
if (cactSkip > 0)
{
ulong dtsSeek;
// we need to skip at least one loop of this sound
if (msos.cactPlay > 0 && (msos.cactPlay -= cactSkip) <= 0)
goto LTryNext;
dtsSeek = (tsCur - msos.tsStart) % msos.dts;
msos.tsStart = tsCur - dtsSeek;
_pglmsos->Put(imsos, &msos);
}
// Calling SetVlm causes us to tell the MISI about the new volume
_vlmSound = msos.vlm;
SetVlm(_vlmBase);
cbSkip = 0;
if (tsCur != msos.tsStart)
{
// have to seek into the stream
if (!_FGetKeyEvents(msos.pmdws, tsCur - msos.tsStart, &cbSkip))
goto LTryNext;
cb = LwMul(_pglmevKey->IvMac(), size(MEV));
if (0 < cb)
{
pvData = _pglmevKey->PvLock(0);
if (!_pmisi->FQueueBuffer(pvData, cb, 0, 1, 0))
{
// streaming the key events failed
_pglmevKey->Unlock();
goto LTryNext;
}
_cpvOut++;
_fPlaying = fTrue;
}
}
_cpvOut++;
pvData = msos.pmdws->PvLockData(&cb);
if (_pmisi->FQueueBuffer(pvData, cb, cbSkip, msos.cactPlay,
(ulong)msos.pmdws))
{
// it worked!
_fPlaying = fTrue;
break;
}
// submitting the buffer failed
msos.pmdws->UnlockData();
_cpvOut--;
LTryNext:
// make this one disappear
// stop the seek buffer from playing
_StopStream();
// make this MSOS have lowest possible priority and 0 time
// remaining to play - we'll move it to the end of _pglmsos
// in the code below.
msos.tsStart = tsCur - msos.dtsStart;
msos.cactPlay = 1;
msos.sii = klwMin;
msos.spr = klwMin;
_pglmsos->Put(imsos, &msos);
}
if (_fPlaying && imsos > 0)
{
// move the skipped ones to the end of the list
long cmsos = _pglmsos->IvMac();
AssertIn(imsos, 1, cmsos);
SwapBlocks(_pglmsos->QvGet(0), LwMul(imsos, size(MSOS)),
LwMul(cmsos - imsos, size(MSOS)));
}
}
/***************************************************************************
Seek into the pmdws the given amount of time, and accumulate key events
in _pglmevKey.
***************************************************************************/
bool MSMIX::_FGetKeyEvents(PMDWS pmdws, ulong dtsSeek, long *pcbSkip)
{
AssertPo(pmdws, 0);
AssertVarMem(pcbSkip);
// This keeps track of which events we've seen (so we only record the
// most recent one. We record tempo changes, program changes and
// controller changes.
struct MKEY
{
ushort grfbitProgram;
ushort grfbitChannelPressure;
ushort grfbitPitchWheel;
ushort fTempo: 1;
ushort rggrfbitControl[120];
};
MKEY mkey;
PMEV pmev;
PMEV pmevMin;
PMEV pmevLim;
MEV rgmev[100];
PMEV pmevDst;
PMEV pmevLimDst;
long cb;
ulong dts;
long igrfbit;
ushort fbit;
ushort *pgrfbit;
byte bT;
ClearPb(&mkey, size(mkey));
ClearPb(rgmev, size(rgmev));
if (pvNil == _pglmevKey && (pvNil == (_pglmevKey = GL::PglNew(size(MEV)))))
return fFalse;
_pglmevKey->FSetIvMac(0);
pmevMin = (PMEV)pmdws->PvLockData(&cb);
cb = LwRoundToward(cb, size(MEV));
pmevLim = (PMEV)PvAddBv(pmevMin, cb);
dts = 0;
for (pmev = pmevMin; pmev < pmevLim; pmev++)
{
dts += pmev->dwDeltaTime;
if (dts >= dtsSeek)
break;
}
if (pmev + 1 >= pmevLim)
{
// dtsSeek goes past the end!
goto LFail;
}
// get the destination pointer - this walks backwards
pmevLimDst = rgmev + CvFromRgv(rgmev);
pmevDst = pmevLimDst;
// put the first event in the key frame list with a smaller time
--pmevDst;
pmevDst->dwDeltaTime = dts - dtsSeek;
pmevDst->dwEvent = pmev->dwEvent;
*pcbSkip = BvSubPvs(pmev + 1, pmevMin);
for (;;)
{
if (pmevDst <= rgmev || pmev <= pmevMin)
{
// destination buffer is full - write it out
long cmev, cmevNew;
PMEV qrgmev;
cmev = _pglmevKey->IvMac();
cmevNew = pmevLimDst - pmevDst;
if (!_pglmevKey->FSetIvMac(cmev + cmevNew))
{
LFail:
_pglmevKey->FSetIvMac(0);
_pglmevKey->FEnsureSpace(0, fgrpShrink);
pmdws->UnlockData();
return fFalse;
}
qrgmev = (PMEV)_pglmevKey->QvGet(0);
BltPb(qrgmev, qrgmev + cmevNew, LwMul(cmev, size(MEV)));
CopyPb(pmevDst, qrgmev, LwMul(cmevNew, size(MEV)));
if (pmev <= pmevMin)
break;
pmevDst = pmevLimDst;
}
--pmev;
switch (pmev->dwEvent >> 24)
{
case MEVT_SHORTMSG:
bT = (byte)pmev->dwEvent;
// The high nibble of bT is the status value
// The low nibble is the channel
switch (bT & 0xF0)
{
case 0xB0: // control change
igrfbit = BHigh(SuLow(pmev->dwEvent));
if (!FIn(igrfbit, 0, CvFromRgv(mkey.rggrfbitControl)))
break;
pgrfbit = &mkey.rggrfbitControl[igrfbit];
goto LTest;
case 0xC0: // program change
pgrfbit = &mkey.grfbitProgram;
goto LTest;
case 0xD0: // channel pressure
pgrfbit = &mkey.grfbitChannelPressure;
goto LTest;
case 0xE0: // pitch wheel
pgrfbit = &mkey.grfbitPitchWheel;
LTest:
fbit = 1 << (bT & 0x0F);
if (!(*pgrfbit & fbit))
{
// first time we've seen this event on this channel
*pgrfbit |= fbit;
goto LCopy;
}
break;
}
break;
case MEVT_TEMPO:
if (!mkey.fTempo)
{
mkey.fTempo = fTrue;
LCopy:
pmevDst--;
pmevDst->dwDeltaTime = 0;
pmevDst->dwEvent = pmev->dwEvent;
}
break;
}
}
_pglmevKey->FEnsureSpace(0, fgrpShrink);
pmdws->UnlockData();
return fTrue;
}
/***************************************************************************
Call back from the midi stream stuff.
***************************************************************************/
void MSMIX::_MidiProc(ulong luUser, void *pvData, ulong luData)
{
PMSMIX pmsmix;
PMDWS pmdws;
pmsmix = (PMSMIX)luUser;
AssertPo(pmsmix, 0);
pmdws = (PMDWS)luData;
AssertNilOrPo(pmdws, 0);
pmsmix->_Notify(pvData, pmdws);
}
/***************************************************************************
The midi stream is done with the given header.
***************************************************************************/
void MSMIX::_Notify(void *pvData, PMDWS pmdws)
{
AssertNilOrPo(pmdws, 0);
MSOS msos;
_mutx.Enter();
Assert(_cpvOut > 0, "what buffer is this?");
_cpvOut--;
if (pvNil != pmdws)
{
AssertVar(_pglmsos->IvMac() > 0 &&
((MSOS *)_pglmsos->QvGet(0))->pmdws == pmdws,
"Wrong pmdws", &pmdws);
pmdws->UnlockData();
}
else if (pvNil != _pglmevKey && pvData == _pglmevKey->QvGet(0))
_pglmevKey->Unlock();
else
{
Bug("Bad pvData/pmdws combo");
}
if (!_fPlaying)
{
// we don't need to notify or start the next sound.
_mutx.Leave();
return;
}
if (_fPlaying && _cpvOut == 0)
{
// all headers are in and we're supposed to be playing - so notify the
// previous pmdws and start up the next one.
_fPlaying = fFalse;
if (0 < _pglmsos->IvMac())
{
_pglmsos->Get(0, &msos);
_pglmsos->Delete(0);
_mutx.Leave();
// do the notify
msos.pmsque->Notify(msos.pmdws);
_mutx.Enter();
}
if (_pglmsos->IvMac() > 0)
_Restart();
}
_mutx.Leave();
}
/***************************************************************************
AT: Static method. Thread function for the MSMIX object.
***************************************************************************/
ulong __stdcall MSMIX::_ThreadProc(void *pv)
{
PMSMIX pmsmix = (PMSMIX)pv;
AssertPo(pmsmix, 0);
return pmsmix->_LuThread();
}
/***************************************************************************
AT: This thread just sleeps until the next sound is due to expire, then
wakes up and nukes any expired sounds.
***************************************************************************/
ulong MSMIX::_LuThread(void)
{
AssertThis(0);
ulong tsCur;
long imsos;
MSOS msos;
long cactSkip;
ulong dtsNextStop = kluMax;
for (;;)
{
WaitForSingleObject(_hevt, dtsNextStop);
if (_fDone)
return 0;
_mutx.Enter();
if (_fWaiting)
{
// we're waiting for buffers to be returned, so don't touch
// anything!
dtsNextStop = 1;
}
else
{
// See if any sounds have expired...
tsCur = TsCurrentSystem();
dtsNextStop = kluMax;
for (imsos = _pglmsos->IvMac(); imsos-- > 0; )
{
if (imsos == 0 && _fPlaying)
break;
_pglmsos->Get(imsos, &msos);
cactSkip = (tsCur - msos.tsStart) / msos.dts;
if (cactSkip > 0)
{
ulong dtsSeek;
if (msos.cactPlay > 0 && (msos.cactPlay -= cactSkip) <= 0)
{
// this sound is done
_pglmsos->Delete(imsos);
_mutx.Leave();
// do the notify
msos.pmsque->Notify(msos.pmdws);
_mutx.Enter();
dtsNextStop = 0;
break;
}
// adjust the values in the MSOS
dtsSeek = (tsCur - msos.tsStart) % msos.dts;
msos.tsStart = tsCur - dtsSeek;
_pglmsos->Put(imsos, &msos);
}
dtsNextStop = LuMin(dtsNextStop,
msos.dts - (tsCur - msos.tsStart));
}
}
_mutx.Leave();
}
}
/***************************************************************************
Constructor for the MIDI stream interface.
***************************************************************************/
MISI::MISI(PFNMIDI pfn, ulong luUser)
{
AssertBaseThis(0);
Assert(pvNil != pfn, 0);
_pfnCall = pfn;
_luUser = luUser;
_tBogusDriver = tMaybe;
_luVolSys = (ulong)(-1);
_vlmBase = kvlmFull;
}
/***************************************************************************
Reset the midi device.
***************************************************************************/
void MISI::_Reset(void)
{
Assert(hNil != _hms, 0);
long iv;
midiOutReset(_hms);
// Reset channel pressure and pitch wheel on all channels.
// We shouldn't have to do this, but some drivers don't reset these.
for (iv = 0; iv < 16; iv++)
{
midiOutShortMsg(_hms, 0xD0 | iv);
midiOutShortMsg(_hms, 0x004000E0 | iv);
}
}
/***************************************************************************
Get the system volume level.
***************************************************************************/
void MISI::_GetSysVol(void)
{
Assert(hNil != _hms, "calling _GetSysVol with nil _hms");
ulong lu0, lu1, lu2;
switch (_tBogusDriver)
{
case tYes:
// just use vluSysVolFake...
_luVolSys = vluSysVolFake;
return;
case tMaybe:
// need to determine if midiOutGetVolume really works for this
// driver.
// Some drivers will only ever tell us what we last gave them -
// irregardless of what the user has set the value to. Those drivers
// will always give us full volume the first time we ask.
// We also look for drivers that give us nonsense values.
if (0 != midiOutGetVolume((uint)_hms, &_luVolSys) ||
_luVolSys == (ulong)(-1) ||
0 != midiOutSetVolume((uint)_hms, (ulong)0) ||
0 != midiOutGetVolume((uint)_hms, &lu0) ||
0 != midiOutSetVolume((uint)_hms, (ulong)0x7FFF7FFF) ||
0 != midiOutGetVolume((uint)_hms, &lu1) ||
0 != midiOutSetVolume((uint)_hms, (ulong)0xFFFFFFFF) ||
0 != midiOutGetVolume((uint)_hms, &lu2) ||
lu0 >= lu1 || lu1 >= lu2)
{
_tBogusDriver = tYes;
_luVolSys = vluSysVolFake;
}
else
{
_tBogusDriver = tNo;
vluSysVolFake = _luVolSys;
}
midiOutSetVolume((uint)_hms, _luVolSys);
break;
default:
if (0 != midiOutGetVolume((uint)_hms, &_luVolSys))
{
// failed - use the fake value
_luVolSys = vluSysVolFake;
}
else
vluSysVolFake = _luVolSys;
break;
}
}
/***************************************************************************
Set the system volume level.
***************************************************************************/
void MISI::_SetSysVol(ulong luVol)
{
Assert(hNil != _hms, "calling _SetSysVol with nil _hms");
midiOutSetVolume((uint)_hms, luVol);
}
/***************************************************************************
Set the system volume level from the current values of _vlmBase
and _luVolSys. We set the system volume to the result of scaling
_luVolSys by _vlmBase.
***************************************************************************/
void MISI::_SetSysVlm(void)
{
ulong luVol;
luVol = LuVolScale(_luVolSys, _vlmBase);
_SetSysVol(luVol);
}
/***************************************************************************
Set the volume for the midi stream output device.
***************************************************************************/
void MISI::SetVlm(long vlm)
{
AssertThis(0);
if (vlm != _vlmBase)
{
_vlmBase = vlm;
if (hNil != _hms)
_SetSysVlm();
}
}
/***************************************************************************
Get the current volume.
***************************************************************************/
long MISI::VlmCur(void)
{
AssertThis(0);
return _vlmBase;
}
/***************************************************************************
Return whether the midi stream output device is active.
***************************************************************************/
bool MISI::FActive(void)
{
return hNil != _hms;
}
/***************************************************************************
Activate or deactivate the Midi stream output object.
***************************************************************************/
bool MISI::FActivate(bool fActivate)
{
AssertThis(0);
return fActivate ? _FOpen() : _FClose();
}
/***************************************************************************
Constructor for the Win95 Midi stream class.
***************************************************************************/
WMS::WMS(PFNMIDI pfn, ulong luUser) : MISI(pfn, luUser)
{
}
/***************************************************************************
Destructor for the Win95 Midi stream class.
***************************************************************************/
WMS::~WMS(void)
{
if (hNil != _hth)
{
// tell the thread to end and wait for it to finish
_fDone = fTrue;
SetEvent(_hevt);
WaitForSingleObject(_hth, INFINITE);
}
if (hNil != _hevt)
CloseHandle(_hevt);
if (pvNil != _pglpmsir)
{
Assert(0 == _pglpmsir->IvMac(), "WMS still has some active buffers");
ReleasePpo(&_pglpmsir);
}
if (hNil != _hlib)
{
FreeLibrary(_hlib);
_hlib = hNil;
}
}
/***************************************************************************
Create a new WMS.
***************************************************************************/
PWMS WMS::PwmsNew(PFNMIDI pfn, ulong luUser)
{
PWMS pwms;
if (pvNil == (pwms = NewObj WMS(pfn, luUser)))
return pvNil;
if (!pwms->_FInit())
ReleasePpo(&pwms);
return pwms;
}
/***************************************************************************
Initialize the WMS: get the addresses of the stream API.
***************************************************************************/
bool WMS::_FInit(void)
{
OSVERSIONINFO osv;
ulong luThread;
// Make sure we're on Win95 and not NT, since the API exists on NT 3.51
// but it fails.
osv.dwOSVersionInfoSize = size(osv);
if (!GetVersionEx(&osv))
return fFalse;
// Old header files don't have this defined!
#ifndef VER_PLATFORM_WIN32_WINDOWS
#define VER_PLATFORM_WIN32_WINDOWS 1
#endif //!VER_PLATFORM_WIN32_WINDOWS
if (VER_PLATFORM_WIN32_WINDOWS != osv.dwPlatformId)
{
// don't bother trying - NT's scheduler works fine anyway.
return fFalse;
}
if (hNil == (_hlib = LoadLibrary(PszLit("WINMM.DLL"))))
return fFalse;
#define _Get(n) \
if (pvNil == (*(void **)&_pfn##n = \
(void *)GetProcAddress(_hlib, "midiStream" #n))) \
{ \
return fFalse; \
}
_Get(Open);
_Get(Close);
_Get(Property);
_Get(Position);
_Get(Out);
_Get(Pause);
_Get(Restart);
_Get(Stop);
#undef _Get
if (pvNil == (_pglpmsir = GL::PglNew(size(PMSIR))))
return fFalse;
_pglpmsir->SetMinGrow(1);
if (hNil == (_hevt = CreateEvent(pvNil, fFalse, fFalse, pvNil)))
return fFalse;
// create the thread
if (hNil == (_hth = CreateThread(pvNil, 1024, WMS::_ThreadProc,
this, 0, &luThread)))
{
return fFalse;
}
AssertThis(0);
return fTrue;
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of a WMS.
***************************************************************************/
void WMS::AssertValid(ulong grf)
{
WMS_PAR::AssertValid(0);
Assert(hNil != _hlib, 0);
long cpmsir;
_mutx.Enter();
Assert(hNil != _hevt, "nil event");
Assert(hNil != _hth, "nil thread");
AssertPo(_pglpmsir, 0);
cpmsir = _pglpmsir->IvMac();
Assert(_cmhOut >= 0, "negative _cmhOut");
AssertIn(_ipmsirCur, 0, cpmsir + 1);
_mutx.Leave();
}
/***************************************************************************
Mark memory for the WMS.
***************************************************************************/
void WMS::MarkMem(void)
{
AssertValid(0);
PMSIR pmsir;
long ipmsir;
WMS_PAR::MarkMem();
_mutx.Enter();
for (ipmsir = _pglpmsir->IvMac(); ipmsir-- > 0; )
{
_pglpmsir->Get(ipmsir, &pmsir);
AssertVarMem(pmsir);
MarkPv(pmsir);
}
MarkMemObj(_pglpmsir);
_mutx.Leave();
}
#endif //DEBUG
/***************************************************************************
Opens the midi stream and sets the time division to 1000 ticks per
quarter note. It is assumed that the midi data has a tempo record
indicating 1 quarter note per second (1000000 microseconds per quarter).
The end result is that ticks are milliseconds.
***************************************************************************/
bool WMS::_FOpen(void)
{
AssertThis(0);
// MIDIPROPTIMEDIV struct
struct MT
{
DWORD cbStruct;
DWORD dwTimeDiv;
};
MT mt;
UINT uT = MIDI_MAPPER;
_mutx.Enter();
if (hNil != _hms)
goto LDone;
if (MMSYSERR_NOERROR != (*_pfnOpen)(&_hms, &uT, 1, (ulong)_MidiProc,
(ulong)this, CALLBACK_FUNCTION))
{
goto LFail;
}
// We set the time division to 1000 ticks per beat, so clients can
// use 1 beat per second and just use milliseconds for timing.
// We also un-pause the stream.
mt.cbStruct = size(mt);
mt.dwTimeDiv = 1000;
if (MMSYSERR_NOERROR != (*_pfnProperty)(_hms, (byte *)&mt,
MIDIPROP_SET | MIDIPROP_TIMEDIV))
{
(*_pfnClose)(_hms);
LFail:
_hms = hNil;
_mutx.Leave();
return fFalse;
}
// we know there are no buffers submitted
AssertVar(_cmhOut == 0, "why is _cmhOut non-zero?", &_cmhOut);
_cmhOut = 0;
// get the system volume level
_GetSysVol();
// set our volume level
_SetSysVlm();
LDone:
_mutx.Leave();
return fTrue;
}
/***************************************************************************
Close the midi stream.
***************************************************************************/
bool WMS::_FClose(void)
{
AssertThis(0);
_mutx.Enter();
if (hNil == _hms)
{
_mutx.Leave();
return fTrue;
}
if (0 < _cmhOut)
{
BugVar("closing a stream that still has buffers!", &_cmhOut);
_mutx.Leave();
return fFalse;
}
// reset the device
_Reset();
// restore the volume level
_SetSysVol(_luVolSys);
// free the device
(*_pfnClose)(_hms);
_hms = hNil;
_mutx.Leave();
return fTrue;
}
#ifdef STREAM_BUG
/***************************************************************************
Just return the value of our flag, not (hNil != _hms).
***************************************************************************/
bool WMS::FActive(void)
{
return _fActive;
}
/***************************************************************************
Need to set _fActive as well.
***************************************************************************/
bool WMS::FActivate(bool fActivate)
{
bool fRet;
fRet = WMS_PAR::FActivate(fActivate);
if (fRet)
_fActive = FPure(fActivate);
return fRet;
}
#endif //STREAM_BUG
/***************************************************************************
Reset the midi stream so it's ready to accept new input. Assumes we
already have the mutx.
***************************************************************************/
void WMS::_ResetStream(void)
{
if (!FActive())
return;
#ifdef STREAM_BUG
if (hNil == _hms)
_FOpen();
else
{
(*_pfnStop)(_hms);
_FClose();
_FOpen();
}
#else //!STREAM_BUG
(*_pfnStop)(_hms);
_Reset();
#endif //!STREAM_BUG
}
/***************************************************************************
This submits a buffer and restarts the midi stream. If the data is
bigger than 64K, this (in conjunction with _Notify) deals with it.
***************************************************************************/
bool WMS::FQueueBuffer(void *pvData, long cb, long ibStart, long cactPlay,
ulong luData)
{
AssertThis(0);
AssertPvCb(pvData, cb);
AssertIn(ibStart, 0, cb);
Assert(cb % size(MEV) == 0, "bad cb");
Assert(ibStart % size(MEV) == 0, "bad cb");
long ipmsir;
PMSIR pmsir = pvNil;
bool fRet = fTrue;
_mutx.Enter();
if (hNil == _hms)
goto LFail;
if (!FAllocPv((void **)&pmsir, size(MSIR), fmemClear, mprNormal))
goto LFail;
pmsir->pvData = pvData;
pmsir->cb = cb;
pmsir->cactPlay = cactPlay;
pmsir->luData = luData;
pmsir->ibNext = ibStart;
if (_hms == hNil || !_pglpmsir->FAdd(&pmsir, &ipmsir))
goto LFail;
if (0 == _CmhSubmitBuffers() && ipmsir == 0)
{
// submitting the buffers failed
_pglpmsir->Delete(0);
_ipmsirCur = 0;
LFail:
FreePpv((void **)pmsir);
fRet = fFalse;
}
_mutx.Leave();
return fRet;
}
/***************************************************************************
Submits buffers. Assumes the _mutx is already ours.
***************************************************************************/
long WMS::_CmhSubmitBuffers(void)
{
PMSIR pmsir;
long cbMh;
PMH pmh;
long imh;
long cmh = 0;
while (_ipmsirCur < _pglpmsir->IvMac())
{
_pglpmsir->Get(_ipmsirCur, &pmsir);
if (pmsir->ibNext >= pmsir->cb)
{
// see if the sound should be repeated
if (pmsir->cactPlay == 1)
{
_ipmsirCur++;
continue;
}
pmsir->cactPlay--;
pmsir->ibNext = 0;
}
// see if one of the buffers is free
for (imh = 0; ; imh++)
{
if (imh >= kcmhMsir)
{
// all buffers are busy
Assert(_cmhOut >= kcmhMsir, 0);
return cmh;
}
if (pmsir->rgibLim[imh] == 0)
break;
}
// fill the buffer and submit it
pmh = &pmsir->rgmh[imh];
pmh->lpData = (byte *)PvAddBv(pmsir->pvData, pmsir->ibNext);
cbMh = LwMin(pmsir->cb - pmsir->ibNext, kcbMaxWmsBuffer);
pmh->dwBufferLength = cbMh;
pmh->dwBytesRecorded = cbMh;
pmh->dwUser = (ulong)pmsir;
pmsir->ibNext += cbMh;
pmsir->rgibLim[imh] = pmsir->ibNext;
if (_FSubmit(pmh))
cmh++;
else
{
// just play the previous buffers and forget about this one
pmsir->ibNext = pmsir->cb;
pmsir->rgibLim[imh] = 0;
pmsir->cactPlay = 1;
_ipmsirCur++;
}
}
return cmh;
}
/***************************************************************************
Prepare and submit the given buffer. Assumes the mutx is ours.
***************************************************************************/
bool WMS::_FSubmit(PMH pmh)
{
bool fRestart = (0 == _cmhOut);
if (hNil == _hms)
return fFalse;
// prepare and submit the buffer
if (MMSYSERR_NOERROR != midiOutPrepareHeader(_hms, (PMHO)pmh, sizeof(*pmh)))
return fFalse;
if (MMSYSERR_NOERROR != (*_pfnOut)(_hms, (PMHO)pmh, size(*pmh)))
{
midiOutUnprepareHeader(_hms, (PMHO)pmh, size(*pmh));
return fFalse;
}
_cmhOut++;
if (fRestart)
(*_pfnRestart)(_hms);
return fTrue;
}
/***************************************************************************
Stop the midi stream.
***************************************************************************/
void WMS::StopPlaying(void)
{
AssertThis(0);
_mutx.Enter();
if (hNil != _hms && _cmhOut > 0)
{
(*_pfnStop)(_hms);
_ipmsirCur = _pglpmsir->IvMac();
}
_mutx.Leave();
}
/***************************************************************************
Call back from the midi stream. If the number of active buffers returns
to 0, this stops the midi stream. If the indicated sound is done,
we notify the client.
***************************************************************************/
void __stdcall WMS::_MidiProc(HMS hms, ulong msg, ulong luUser,
ulong lu1, ulong lu2)
{
PWMS pwms;
PMH pmh;
if (msg != MOM_DONE)
return;
pwms = (PWMS)luUser;
AssertPo(pwms, 0);
pmh = (PMH)lu1;
AssertVarMem(pmh);
pwms->_Notify(hms, pmh);
}
/***************************************************************************
The this-based callback.
The mmsys guys claim that it's illegal to call midiOutUnprepareHeader,
midiStreamStop and midiOutReset. So we just signal another thread to
do this work.
***************************************************************************/
void WMS::_Notify(HMS hms, PMH pmh)
{
AssertThis(0);
Assert(hNil != hms, 0);
AssertVarMem(pmh);
PMSIR pmsir;
long imh;
_mutx.Enter();
Assert(hms == _hms, "wrong hms");
midiOutUnprepareHeader(hms, (PMHO)pmh, size(*pmh));
pmsir = (PMSIR)pmh->dwUser;
AssertVarMem(pmsir);
AssertPvCb(pmsir->pvData, pmsir->cb);
for (imh = 0; ; imh++)
{
if (imh >= kcmhMsir)
{
Bug("corrupt msir");
_mutx.Leave();
return;
}
if (pmh == &pmsir->rgmh[imh])
break;
}
Assert(pmh->lpData ==
PvAddBv(pmsir->pvData, pmsir->rgibLim[imh] - pmh->dwBufferLength),
"pmh->lpData is wrong");
// mark this buffer free
pmsir->rgibLim[imh] = 0;
// fill and submit buffers
_CmhSubmitBuffers();
// update the submitted buffer count
--_cmhOut;
// wake up the auxillary thread to do callbacks and stop and reset
// the device it there's nothing more to play
SetEvent(_hevt);
_mutx.Leave();
}
/***************************************************************************
AT: Static method. Thread function for the WMS object. This thread
just waits for the event to be triggered, indicating that we got
a callback from the midiStream stuff and it's time to do our callbacks.
***************************************************************************/
ulong __stdcall WMS::_ThreadProc(void *pv)
{
PWMS pwms = (PWMS)pv;
AssertPo(pwms, 0);
return pwms->_LuThread();
}
/***************************************************************************
AT: This thread just sleeps until the next sound is due to expire, then
wakes up and nukes any expired sounds.
***************************************************************************/
ulong WMS::_LuThread(void)
{
AssertThis(0);
for (;;)
{
WaitForSingleObject(_hevt, INFINITE);
if (_fDone)
return 0;
_mutx.Enter();
if (hNil != _hms && 0 == _cmhOut)
_ResetStream();
_DoCallBacks();
_mutx.Leave();
}
}
/***************************************************************************
Check for MSIRs that are done and do the callback on them and free them.
Assumes the _mutx is checked out exactly once.
***************************************************************************/
void WMS::_DoCallBacks()
{
PMSIR pmsir;
AssertIn(_ipmsirCur, 0, _pglpmsir->IvMac() + 1);
while (0 < _ipmsirCur)
{
_pglpmsir->Get(0, &pmsir);
if (_cmhOut > 0)
{
// see if the MSIR is done
long imh;
for (imh = 0; imh < kcmhMsir; imh++)
{
if (0 < pmsir->rgibLim[imh])
{
// this one is busy
return;
}
}
}
// this one is done
_pglpmsir->Delete(0);
_ipmsirCur--;
_mutx.Leave();
// notify the client that we're done with the sound
(*_pfnCall)(_luUser, pmsir->pvData, pmsir->luData);
FreePpv((void **)&pmsir);
_mutx.Enter();
AssertIn(_ipmsirCur, 0, _pglpmsir->IvMac() + 1);
}
}
/***************************************************************************
Constructor for our own midi stream api implementation.
***************************************************************************/
OMS::OMS(PFNMIDI pfn, ulong luUser) : MISI(pfn, luUser)
{
}
/***************************************************************************
Destructor for our midi stream.
***************************************************************************/
OMS::~OMS(void)
{
if (hNil != _hth)
{
// tell the thread to end and wait for it to finish
_fDone = fTrue;
SetEvent(_hevt);
WaitForSingleObject(_hth, INFINITE);
}
_mutx.Enter();
if (hNil != _hevt)
CloseHandle(_hevt);
Assert(_hms == hNil, "Still have an HMS");
Assert(_pglmsb->IvMac() == 0, "Still have some buffers");
ReleasePpo(&_pglmsb);
_mutx.Leave();
}
/***************************************************************************
Create a new OMS.
***************************************************************************/
POMS OMS::PomsNew(PFNMIDI pfn, ulong luUser)
{
POMS poms;
if (pvNil == (poms = NewObj OMS(pfn, luUser)))
return pvNil;
if (!poms->_FInit())
ReleasePpo(&poms);
return poms;
}
/***************************************************************************
Initialize the OMS.
***************************************************************************/
bool OMS::_FInit(void)
{
AssertBaseThis(0);
ulong luThread;
if (pvNil == (_pglmsb = GL::PglNew(size(MSB))))
return fFalse;
_pglmsb->SetMinGrow(1);
if (hNil == (_hevt = CreateEvent(pvNil, fFalse, fFalse, pvNil)))
return fFalse;
// create the thread in a suspended state
if (hNil == (_hth = CreateThread(pvNil, 1024, OMS::_ThreadProc,
this, CREATE_SUSPENDED, &luThread)))
{
return fFalse;
}
SetThreadPriority(_hth, THREAD_PRIORITY_TIME_CRITICAL);
// start the thread
ResumeThread(_hth);
return fTrue;
}
#ifdef DEBUG
/***************************************************************************
Assert the validity of a OMS.
***************************************************************************/
void OMS::AssertValid(ulong grf)
{
OMS_PAR::AssertValid(0);
_mutx.Enter();
Assert(hNil != _hth, "nil thread");
Assert(hNil != _hevt, "nil event");
AssertPo(_pglmsb, 0);
_mutx.Leave();
}
/***************************************************************************
Mark memory for the OMS.
***************************************************************************/
void OMS::MarkMem(void)
{
AssertValid(0);
OMS_PAR::MarkMem();
_mutx.Enter();
MarkMemObj(_pglmsb);
_mutx.Leave();
}
#endif //DEBUG
/***************************************************************************
Open the stream.
***************************************************************************/
bool OMS::_FOpen(void)
{
AssertThis(0);
_mutx.Enter();
if (hNil != _hms)
goto LDone;
_fChanged = _fStop = fFalse;
if (MMSYSERR_NOERROR != midiOutOpen(&_hms, MIDI_MAPPER,
0, 0, CALLBACK_NULL))
{
_hms = hNil;
_mutx.Leave();
return fFalse;
}
// get the system volume level
_GetSysVol();
// set our volume level
_SetSysVlm();
LDone:
_mutx.Leave();
return fTrue;
}
/***************************************************************************
Close the stream.
***************************************************************************/
bool OMS::_FClose(void)
{
AssertThis(0);
_mutx.Enter();
if (hNil == _hms)
{
_mutx.Leave();
return fTrue;
}
if (_pglmsb->IvMac() > 0)
{
Bug("closing a stream that still has buffers!");
_mutx.Leave();
return fFalse;
}
// reset the device
_Reset();
// restore the volume level
_SetSysVol(_luVolSys);
midiOutClose(_hms);
_hms = hNil;
_mutx.Leave();
return fTrue;
}
/***************************************************************************
Queue a buffer to the midi stream.
***************************************************************************/
bool OMS::FQueueBuffer(void *pvData, long cb, long ibStart, long cactPlay,
ulong luData)
{
AssertThis(0);
AssertPvCb(pvData, cb);
AssertIn(ibStart, 0, cb);
Assert(cb % size(MEV) == 0, "bad cb");
Assert(ibStart % size(MEV) == 0, "bad cb");
MSB msb;
_mutx.Enter();
if (hNil == _hms)
goto LFail;
msb.pvData = pvData;
msb.cb = cb;
msb.ibStart = ibStart;
msb.cactPlay = cactPlay;
msb.luData = luData;
if (!_pglmsb->FAdd(&msb))
{
LFail:
_mutx.Leave();
return fFalse;
}
if (1 == _pglmsb->IvMac())
{
// Start the buffer
SetEvent(_hevt);
_fChanged = fTrue;
}
_mutx.Leave();
return fTrue;
}
/***************************************************************************
Stop the stream and release all buffers. The buffer notifies are
asynchronous.
***************************************************************************/
void OMS::StopPlaying(void)
{
AssertThis(0);
_mutx.Enter();
if (hNil != _hms)
{
_fStop = fTrue;
SetEvent(_hevt);
_fChanged = fTrue;
}
_mutx.Leave();
}
/***************************************************************************
AT: Static method. Thread function for the midi stream object.
***************************************************************************/
ulong __stdcall OMS::_ThreadProc(void *pv)
{
POMS poms = (POMS)pv;
AssertPo(poms, 0);
return poms->_LuThread();
}
/***************************************************************************
AT: The midi stream playback thread.
***************************************************************************/
ulong OMS::_LuThread(void)
{
AssertThis(0);
MSB msb;
bool fChanged; // whether the event went off
ulong tsCur;
const long klwInfinite = klwMax;
long dtsWait = klwInfinite;
for (;;)
{
fChanged = dtsWait > 0 && WAIT_TIMEOUT != WaitForSingleObject(_hevt,
dtsWait == klwInfinite ? INFINITE : dtsWait);
if (_fDone)
return 0;
_mutx.Enter();
if (_fChanged && !fChanged)
{
// the event went off before we got the mutx.
dtsWait = klwInfinite;
goto LLoop;
}
_fChanged = fFalse;
if (!fChanged)
{
// play the event
if (_pmev < _pmevLim)
{
if (MEVT_SHORTMSG == (_pmev->dwEvent >> 24))
midiOutShortMsg(_hms, _pmev->dwEvent & 0x00FFFFFF);
_pmev++;
if (_pmev >= _pmevLim)
dtsWait = 0;
else
{
ulong tsNew = TsCurrentSystem();
tsCur += _pmev->dwDeltaTime;
dtsWait = tsCur - tsNew;
if (dtsWait < -kdtsMinSlip)
{
tsCur = tsNew;
dtsWait = 0;
}
}
goto LLoop;
}
// ran out of events in the current buffer - see if we should
// repeat it
_pglmsb->Get(0, &msb);
if (msb.cactPlay == 1)
{
_imsbCur = 1;
_ReleaseBuffers();
}
else
{
// repeat the current buffer
if (msb.cactPlay > 0)
msb.cactPlay--;
msb.ibStart = 0;
_pglmsb->Put(0, &msb);
}
}
else if (_fStop)
{
// release all buffers
_fStop = fFalse;
_imsbCur = _pglmsb->IvMac();
_ReleaseBuffers();
}
if (0 == _pglmsb->IvMac())
{
// no buffers to play
dtsWait = klwInfinite;
}
else
{
// start playing the new buffers
_pglmsb->Get(0, &msb);
_pmev = (PMEV)PvAddBv(msb.pvData, msb.ibStart);
_pmevLim = (PMEV)PvAddBv(msb.pvData, msb.cb);
if (_pmev >= _pmevLim)
dtsWait = 0;
else
{
dtsWait = _pmev->dwDeltaTime;
tsCur = TsCurrentSystem() + dtsWait;
}
}
LLoop:
_mutx.Leave();
}
}
/***************************************************************************
Release all buffers up to _imsbCur. Assumes that we have the mutx
checked out exactly once.
***************************************************************************/
void OMS::_ReleaseBuffers(void)
{
MSB msb;
if (_imsbCur >= _pglmsb->IvMac() && hNil != _hms)
_Reset();
while (_imsbCur > 0)
{
_pglmsb->Get(0, &msb);
_pglmsb->Delete(0);
_imsbCur--;
_mutx.Leave();
// call the notify proc
(*_pfnCall)(_luUser, msb.pvData, msb.luData);
_mutx.Enter();
}
}