/* 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(); } }