Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1354 lines
40 KiB

//--------------------------------------------------------------------------;
//
// File: grace.cpp
//
// Copyright (c) 1995 Microsoft Corporation. All Rights Reserved.
//
// Abstract:
// This file contains functions related to the mixing of secondary buffers
// into a primary buffer. Collectively, this mixing is referred to as "grace"
// for no good reason other than hoping that it is a gracefull solution to the
// mixing problem. It could easily be called "mixer" but that would be
// ambiguous with the code that actually mixes the samples together.
//
// Contents:
// The contained functions include a thread function that wakes
// periodically to "refresh" the data in the primary buffer by mixing in data
// from secondary buffers. The same thread can be signalled to immediately
// remix data into the primary buffer.
// This also contains functions to initialize and terminate the mixing
// thread, add/remove buffers to/from the list of buffers to be mixed, and
// query the position of secondary buffers that are being mixed.
//
// History:
// 06/15/95 FrankYe Created
//
//--------------------------------------------------------------------------;
#include "dsoundpr.h"
#include "grace.h"
// internal Kernel32 API
extern "C" DWORD WINAPI OpenVxDHandle(HANDLE hSource);
//==========================================================================;
//
// Grace core functions
//
// graceThread
// graceMix
//
// uMixNewBuffer
// uMixLoopingBuffer
// uMixNotLoopingBuffer
// uMixEndingBufferWaitingWrap
// uMixEndingBuffer
//
//==========================================================================;
//--------------------------------------------------------------------------;
//
//
//
//--------------------------------------------------------------------------;
void uMixNewBuffer( LPDSBUFFER pdsb, LPDSBUFFER pdsbP, LONG posPPlay, LONG posPMix, LONG dposPRemix, LONG cPMix );
void uMixLoopingBuffer( LPDSBUFFER pdsb, LPDSBUFFER pdsbP, LONG posPPlay, LONG posPMix, LONG dposPRemix, LONG cPMix );
void uMixNotLoopingBuffer( LPDSBUFFER pdsb, LPDSBUFFER pdsbP, LONG posPPlay, LONG posPMix, LONG dposPRemix, LONG cPMix );
void uMixEndingBufferWaitingWrap( LPDSBUFFER pdsb, LPDSBUFFER pdsbP, LONG posPPlay, LONG posPMix, LONG dposPRemix, LONG cPMix );
void uMixEndingBuffer( LPDSBUFFER pdsb, LPDSBUFFER pdsbP, LONG posPPlay, LONG posPMix, LONG dposPRemix, LONG cPMix );
//--------------------------------------------------------------------------;
//
//
//
//--------------------------------------------------------------------------;
void uMixEndingBuffer(LPDSBUFFER pdsb, LPDSBUFFER pdsbP, LONG posPPlay, LONG posPMix, LONG dposPRemix, LONG cPMix)
{
LPDSOUND pds;
DPF(4, "uMixEndingBuffer");
pds = pdsb->pds;
ASSERT( DSOUNDSIG == pds->dwSig );
// REMIND This assert will fail when a setposition call comes in.
// We should change the setposition stuff and put this assert back in.
//ASSERT(0 == pdsb->posNextMix);
//
// Since this buffer still has status = playing, we need to honor a
// looping change even though the play position may have reached the
// end of this buffer.
//
if (0 != (DSB_INTERNALF_LOOPING & pdsb->fdwDsbI)) {
// We've changed from not looping to looping
pdsb->iMixerState = DSBMIXERSTATE_LOOPING;
//
// If we would have stopped, or if the Mix position is outside of the
// range between the Play and End positions, then we don't remix
// anything, we just mix from the start of the secondary buffer.
// Otherwise, we remix the data from the Mix position up to the
// End position. Note this relies on the NextMix position (posNextMix)
// having already been set to 0 when this buffer changed from
// NOTLOOPING to either ENDING or ENDINGWAITINGPRIMARYWRAP.
//
if ( (posPPlay >= pdsb->posPEnd) || (posPPlay < pdsb->posPPlayLast) ) {
// The play cursor went past the end position of this buffer, so
// we don't remix any data, we just start looping it from the
// start of the buffer.
dposPRemix = 0;
} else {
if ( (posPMix < posPPlay) || (posPMix >= pdsb->posPEnd) ) {
// The mix position is beyond the end position of this buffer,
// so we don't remix any data, we just start looping it from
// the start of the buffer.
dposPRemix = 0;
} else {
// The mix position is before the end position of this buffer,
// so we remix the data from the mix position to the end.
dposPRemix = pdsb->posPEnd - posPMix;
}
}
if (dposPRemix < 0) dposPRemix += pdsb->cSamples;
ASSERT(dposPRemix >= 0);
// DPF(0, "~`S41");
uMixLoopingBuffer(pdsb, pdsbP, posPPlay, posPMix, dposPRemix, cPMix);
return;
}
if ( (posPPlay >= pdsb->posPEnd) || (posPPlay < pdsb->posPPlayLast) ) {
// We've stopped!
// DPF(0, "~`S4-");
IDsbStopI( pdsb, TRUE );
pdsb->posNextMix = 0;
} else {
//
// Haven't reached end yet so let's check for a few remix events...
//
// Check for SETPOSITION signal
if (0 != (DSBMIXERSIGNAL_SETPOSITION & pdsb->fdwMixerSignal)) {
pdsb->iMixerState = DSBMIXERSTATE_NOTLOOPING;
// DPF(0, "~`S42");
uMixNotLoopingBuffer(pdsb, pdsbP, posPPlay, posPMix, 0, cPMix);
return;
}
// Check for remix
if (0 != dposPRemix) {
// If the Mix position is outside of the range between the Play
// and End positions, then we don't remix anything.
if ( (posPMix >= posPPlay) && (posPMix < pdsb->posPEnd) ) {
dposPRemix = pdsb->posPEnd - posPMix;
pdsb->iMixerState = DSBMIXERSTATE_NOTLOOPING;
// DPF(0, "~`S42");
uMixNotLoopingBuffer(pdsb, pdsbP, posPPlay, posPMix, dposPRemix, cPMix);
return;
}
}
// handle substate transition
switch (pdsb->iMixerSubstate) {
case DSBMIXERSUBSTATE_NEW:
ASSERT(FALSE);
break;
case DSBMIXERSUBSTATE_STARTING_WAITINGPRIMARYWRAP:
ASSERT(posPPlay >= pdsb->posPPlayLast);
// A wrap would have been caught above and this buffer stopped
break;
case DSBMIXERSUBSTATE_STARTING:
ASSERT(posPPlay >= pdsb->posPPlayLast);
// A wrap would have been caught above and this buffer stopped
if (posPPlay >= pdsb->posPStart) {
pdsb->iMixerSubstate = DSBMIXERSUBSTATE_STARTED;
}
break;
case DSBMIXERSUBSTATE_STARTED:
break;
default:
ASSERT(FALSE);
}
}
pdsb->posPPlayLast = posPPlay;
return;
}
//--------------------------------------------------------------------------;
//
//
//
//--------------------------------------------------------------------------;
void uMixEndingBufferWaitingWrap(LPDSBUFFER pdsb, LPDSBUFFER pdsbP, LONG posPPlay, LONG posPMix, LONG dposPRemix, LONG cPMix)
{
LPDSOUND pds;
DPF(4, "uMixEndingBufferWaitingWrap");
pds = pdsb->pds;
ASSERT( DSOUNDSIG == pds->dwSig );
if (posPPlay < pdsb->posPPlayLast) {
// handle substate transition
switch (pdsb->iMixerSubstate) {
case DSBMIXERSUBSTATE_NEW:
DPF(0, "uMixEndingBufferWaitingWrap: error: encountered DSBMIXERSUBSTATE_NEW");
// ASSERT(FALSE);
break;
case DSBMIXERSUBSTATE_STARTING_WAITINGPRIMARYWRAP:
pdsb->iMixerSubstate = DSBMIXERSUBSTATE_STARTING;
break;
case DSBMIXERSUBSTATE_STARTING:
pdsb->iMixerSubstate = DSBMIXERSUBSTATE_STARTED;
break;
case DSBMIXERSUBSTATE_STARTED:
break;
default:
ASSERT(FALSE);
}
pdsb->posPPlayLast = posPPlay;
pdsb->iMixerState = DSBMIXERSTATE_ENDING;
// DPF(0, "~`S34");
uMixEndingBuffer(pdsb, pdsbP, posPPlay, posPMix, dposPRemix, cPMix);
return;
}
// Haven't wrapped yet.
if (0 != (DSBMIXERSIGNAL_SETPOSITION & pdsb->fdwMixerSignal)) {
pdsb->iMixerState = DSBMIXERSTATE_NOTLOOPING;
// DPF(0, "~`S32");
uMixNotLoopingBuffer(pdsb, pdsbP, posPPlay, posPMix, 0, cPMix);
return;
}
if (0 != (DSB_INTERNALF_LOOPING & pdsb->fdwDsbI)) {
// We've changed from not looping to looping
pdsb->iMixerState = DSBMIXERSTATE_LOOPING;
if ( (posPMix > pdsb->posPEnd) && (posPMix < posPPlay) ) {
dposPRemix = 0;
} else {
dposPRemix = pdsb->posPEnd - posPMix;
}
if (dposPRemix < 0) dposPRemix += pdsbP->cSamples;
ASSERT(dposPRemix >= 0);
// DPF(0, "~`S31");
uMixLoopingBuffer(pdsb, pdsbP, posPPlay, posPMix, dposPRemix, cPMix);
return;
}
// Check for remix
if (0 != dposPRemix) {
// If the Mix position is outside of the range between the Play
// and End positions, then we don't remix anything.
if ( (posPMix >= posPPlay) || (posPMix < pdsb->posPEnd) ) {
dposPRemix = pdsb->posPEnd - posPMix;
if (dposPRemix < 0) dposPRemix += pdsbP->cSamples;
ASSERT(dposPRemix >= 0);
pdsb->iMixerState = DSBMIXERSTATE_NOTLOOPING;
// DPF(0, "~`S32");
uMixNotLoopingBuffer(pdsb, pdsbP, posPPlay, posPMix, dposPRemix, cPMix);
return;
}
}
// handle substate transition
switch (pdsb->iMixerSubstate) {
case DSBMIXERSUBSTATE_NEW:
ASSERT(FALSE);
break;
case DSBMIXERSUBSTATE_STARTING_WAITINGPRIMARYWRAP:
// A wrap would have been caught above and control sent to
// uMixEndingBuffer.
ASSERT(posPPlay >= pdsb->posPPlayLast);
break;
case DSBMIXERSUBSTATE_STARTING:
// A wrap would have been caught above and control sent to
// uMixEndingBuffer.
ASSERT(posPPlay >= pdsb->posPPlayLast);
if (posPPlay >= pdsb->posPStart) {
pdsb->iMixerSubstate = DSBMIXERSUBSTATE_STARTED;
}
break;
case DSBMIXERSUBSTATE_STARTED:
break;
default:
ASSERT(FALSE);
}
pdsb->posPPlayLast = posPPlay;
return;
}
//--------------------------------------------------------------------------;
//
//
//
//--------------------------------------------------------------------------;
void uMixNotLoopingBuffer(LPDSBUFFER pdsb, LPDSBUFFER pdsbP, LONG posPPlay, LONG posPMix, LONG dposPRemix, LONG cPMix)
{
LPDSOUND pds;
MIXINPUT mixInput;
LONG posMix;
LONG dposEnd;
DWORD dwPosition;
DWORD dwMixLength;
DPF(4, "uMixNotLoopingBuffer");
pds = pdsb->pds;
ASSERT( DSOUNDSIG == pds->dwSig );
if (0 != (DSB_INTERNALF_LOOPING & pdsb->fdwDsbI)) {
// We've switched from not looping to looping
pdsb->iMixerState = DSBMIXERSTATE_LOOPING;
// DPF(0, "~`S21");
uMixLoopingBuffer(pdsb, pdsbP, posPPlay, posPMix, dposPRemix, cPMix);
return;
}
// on a SetPosition, we ignore the remix length and posNextMix will
// contain the new position at which to start mixing the secondary buffer
if (0 != (DSBMIXERSIGNAL_SETPOSITION & pdsb->fdwMixerSignal)) {
pdsb->fdwMixerSignal &= ~DSBMIXERSIGNAL_SETPOSITION;
// DPF(0, "~`S20");
uMixNewBuffer(pdsb, pdsbP, posPPlay, posPMix, 0, cPMix);
return;
}
// handle substate transition
switch (pdsb->iMixerSubstate) {
case DSBMIXERSUBSTATE_NEW:
ASSERT(FALSE);
break;
case DSBMIXERSUBSTATE_STARTING_WAITINGPRIMARYWRAP:
if (posPPlay < pdsb->posPPlayLast) {
if (posPPlay >= pdsb->posPStart) {
pdsb->iMixerSubstate = DSBMIXERSUBSTATE_STARTED;
} else {
pdsb->iMixerSubstate = DSBMIXERSUBSTATE_STARTING;
}
}
break;
case DSBMIXERSUBSTATE_STARTING:
if ((posPPlay >= pdsb->posPStart) || (posPPlay < pdsb->posPPlayLast)) {
pdsb->iMixerSubstate = DSBMIXERSUBSTATE_STARTED;
}
break;
case DSBMIXERSUBSTATE_STARTED:
break;
default:
ASSERT(FALSE);
}
//
if (0 == dposPRemix) {
posMix = pdsb->posNextMix;
} else {
LONG dposRemix;
dposRemix = MulDivRN(dposPRemix, pdsb->uLastFrequency, pdsbP->helInfo.dwSampleRate);
posMix = pdsb->posNextMix - dposRemix;
while (posMix < 0) posMix += pdsb->cSamples;
}
dwPosition = posMix << pdsb->uBlockAlignShift;
ASSERT(0 == (H_LOOP & pdsb->helInfo.hfFormat));
dwMixLength = pdsb->cbBufferSize - dwPosition;
if (pdsb->fMixerMute) {
LONG cMixToPrimary;
DWORD dwMixToPrimary;
cMixToPrimary = MulDivRN(cPMix, pdsb->helInfo.dwSampleRate, pdsbP->helInfo.dwSampleRate);
dwMixToPrimary = ((DWORD)cMixToPrimary) << pdsb->uBlockAlignShift;
dwPosition = dwPosition + min(dwMixLength, dwMixToPrimary);
} else {
mixInput.HALInStrBuf = pdsb->helInfo;
mixInput.pBuffer = pdsb->pDSBuffer;
mixInput.cbBuffer = pdsb->cbBufferSize;
mixInput.pdwInputPos = &dwPosition;
mixInput.dwInputBytes = dwMixLength;
mixInput.dwOutputOffset = 0;
mixMixSession(&mixInput);
}
// Because of the idiosyncrasies of the mixing code when performing
// frequency conversions, we have to watch for a wrap slightly past
// the start of the source buffer.
// DPF(0, "~`S2pos:%08X", dwPosition);
if ( (dwPosition >= pdsb->cbBufferSize) || (dwPosition <= 24) ) {
dwPosition = 0;
// determine position in primary buffer that corresponds to the
// end of this secondary buffer
dposEnd = pdsb->cSamples - posMix;
pdsb->posPEnd = posPMix + MulDivRN(dposEnd, pdsbP->helInfo.dwSampleRate, pdsb->helInfo.dwSampleRate);
while (pdsb->posPEnd >= pdsbP->cSamples) pdsb->posPEnd -= pdsbP->cSamples;
if (pdsb->posPEnd < posPPlay) {
// DPF(0, "~`S23");
pdsb->iMixerState = DSBMIXERSTATE_ENDING_WAITINGPRIMARYWRAP;
} else {
// DPF(0, "~`S24");
pdsb->iMixerState = DSBMIXERSTATE_ENDING;
}
}
pdsb->posPPlayLast = posPPlay;
pdsb->posNextMix = dwPosition >> pdsb->uBlockAlignShift;
pdsb->uLastFrequency = pdsb->helInfo.dwSampleRate;
return;
}
//--------------------------------------------------------------------------;
//
//
//
//--------------------------------------------------------------------------;
void uMixLoopingBuffer(LPDSBUFFER pdsb, LPDSBUFFER pdsbP, LONG posPPlay, LONG posPMix, LONG dposPRemix, LONG cPMix)
{
LPDSOUND pds;
MIXINPUT mixInput;
LONG posMix;
DWORD dwPosition;
DPF(4, "uMixLoopingBuffer");
pds = pdsb->pds;
ASSERT( DSOUNDSIG == pds->dwSig );
if (0 == (DSB_INTERNALF_LOOPING & pdsb->fdwDsbI)) {
// We've switched from looping to non-looping
pdsb->iMixerState = DSBMIXERSTATE_NOTLOOPING;
// DPF(0, "~`S12");
uMixNotLoopingBuffer(pdsb, pdsbP, posPPlay, posPMix, dposPRemix, cPMix);
return;
}
// on a SetPosition, we ignore the remix length and posNextMix will
// contain the new position at which to start mixing the secondary buffer
if (0 != (DSBMIXERSIGNAL_SETPOSITION & pdsb->fdwMixerSignal)) {
pdsb->fdwMixerSignal &= ~DSBMIXERSIGNAL_SETPOSITION;
// DPF(0, "~`S10");
uMixNewBuffer(pdsb, pdsbP, posPPlay, posPMix, 0, cPMix);
return;
}
// handle substate transition
switch (pdsb->iMixerSubstate) {
case DSBMIXERSUBSTATE_NEW:
ASSERT(FALSE);
break;
case DSBMIXERSUBSTATE_STARTING_WAITINGPRIMARYWRAP:
if (posPPlay < pdsb->posPPlayLast) {
if (posPPlay >= pdsb->posPStart) {
pdsb->iMixerSubstate = DSBMIXERSUBSTATE_STARTED;
} else {
pdsb->iMixerSubstate = DSBMIXERSUBSTATE_STARTING;
}
}
break;
case DSBMIXERSUBSTATE_STARTING:
if ((posPPlay >= pdsb->posPStart) || (posPPlay < pdsb->posPPlayLast)) {
pdsb->iMixerSubstate = DSBMIXERSUBSTATE_STARTED;
}
break;
case DSBMIXERSUBSTATE_STARTED:
break;
default:
ASSERT(FALSE);
}
//
if (0 == dposPRemix) {
posMix = pdsb->posNextMix;
} else {
LONG dposRemix;
dposRemix = MulDivRN(dposPRemix, pdsb->uLastFrequency, pdsbP->helInfo.dwSampleRate);
posMix = pdsb->posNextMix - dposRemix;
while (posMix < 0) posMix += pdsb->cSamples;
}
dwPosition = posMix << pdsb->uBlockAlignShift;
ASSERT(H_LOOP & pdsb->helInfo.hfFormat);
if (pdsb->fMixerMute) {
LONG cMixToPrimary;
DWORD dwMixToPrimary;
cMixToPrimary = MulDivRN(cPMix, pdsb->helInfo.dwSampleRate, pdsbP->helInfo.dwSampleRate);
dwMixToPrimary = ((DWORD)cMixToPrimary) << pdsb->uBlockAlignShift;
dwPosition = dwPosition + dwMixToPrimary;
while (dwPosition >= pdsb->cbBufferSize) dwPosition -= pdsb->cbBufferSize;
} else {
mixInput.HALInStrBuf = pdsb->helInfo;
mixInput.pBuffer = pdsb->pDSBuffer;
mixInput.cbBuffer = pdsb->cbBufferSize;
mixInput.pdwInputPos = &dwPosition;
mixInput.dwInputBytes = 0;
mixInput.dwOutputOffset = 0;
mixMixSession(&mixInput);
}
pdsb->posPPlayLast = posPPlay;
pdsb->posNextMix = dwPosition >> pdsb->uBlockAlignShift;
pdsb->uLastFrequency = pdsb->helInfo.dwSampleRate;
return;
}
//--------------------------------------------------------------------------;
//
//
//
//--------------------------------------------------------------------------;
void uMixNewBuffer(LPDSBUFFER pdsb, LPDSBUFFER pdsbP, LONG posPPlay, LONG posPMix, LONG dposPRemix, LONG cPMix)
{
BOOL fLooping;
DPF(4, "uMixNewBuffer");
//
// Determine position in primary buffer at which this buffer starts playing
//
pdsb->posPStart = posPMix;
if (posPPlay < pdsb->posPStart) {
pdsb->iMixerSubstate = DSBMIXERSUBSTATE_STARTING;
} else {
pdsb->iMixerSubstate = DSBMIXERSUBSTATE_STARTING_WAITINGPRIMARYWRAP;
}
pdsb->posPPlayLast = posPPlay;
fLooping = (0 != (DSB_INTERNALF_LOOPING & pdsb->fdwDsbI));
if (fLooping) {
// DPF(0, "~`S01");
pdsb->iMixerState = DSBMIXERSTATE_LOOPING;
uMixLoopingBuffer(pdsb, pdsbP, posPPlay, posPMix, 0, cPMix);
} else {
// DPF(0, "~`S02");
pdsb->iMixerState = DSBMIXERSTATE_NOTLOOPING;
uMixNotLoopingBuffer(pdsb, pdsbP, posPPlay, posPMix, 0, cPMix);
}
return;
}
//--------------------------------------------------------------------------;
//
//
//
//--------------------------------------------------------------------------;
extern "C" void graceMix(LPDSOUND pds, BOOL fRemix, LONG dtimePremixMax, LPLONG pdtimeInvalid)
{
LPDSOUNDEXTERNAL pdseT;
LPDSBUFFER pdsbP;
LPDSBUFFER pdsb;
LPDSBUFFER pdsbMixNext;
LONG posPPlay;
LONG posPWrite;
LONG posPMix;
LONG dposPRemix;
DWORD dwPlayCursor;
DWORD dwWriteCursor;
LONG cPremixMax;
LONG cMix;
LONG cMixThisLoop;
LONG dcMixThisLoop;
MIXSESSION mixSession;
DSVAL dsv;
//
*pdtimeInvalid = MIXER_MAXPREMIX;
//
pdsbP = pds->pdsbPrimary;
if (NULL == pdsbP) goto retClean;
//
if (gpdsinfo->fApmSuspended) goto retClean;
// If a WRITEPRIMARY app is active, we don't need to do anything.
for (pdseT = gpdsinfo->pDSoundExternalObj; pdseT; pdseT = pdseT->pNext) {
if (pdseT->dwPriority >= DSSCL_WRITEPRIMARY) {
if (pdseT->tidSound == gpdsinfo->tidSoundFocus) {
goto retClean;
}
}
}
if (DSB_INTERNALF_STOP & pdsbP->fdwDsbI) goto retClean;
dsv = vxdBufferGetPosition(pdsbP->hBuffer, &dwPlayCursor, &dwWriteCursor);
if (DS_OK != dsv) {
DPF(0, "Couldn't GetCurrentPosition of destination");
goto retClean;
}
// Just make sure we have valid values.
ASSERT( dwPlayCursor < pdsbP->cbBufferSize );
ASSERT( dwWriteCursor < pdsbP->cbBufferSize );
// Convert from byte position to sample position
posPPlay = dwPlayCursor >> pdsbP->uBlockAlignShift;
posPWrite = dwWriteCursor >> pdsbP->uBlockAlignShift;
// Until we write code to actually profile the performance, we'll just
// pad the write position with a hard coded amount
posPWrite += pdsbP->helInfo.dwSampleRate * MIXER_WRITEPAD / 1000;
if (posPWrite >= pdsbP->cSamples) posPWrite -= pdsbP->cSamples;
ASSERT(posPWrite < pdsbP->cSamples);
//
//
//
switch (pdsbP->iMixerState) {
case DSPBMIXERSTATE_LOOPING:
// We can make this assertion because we never mix up to
// the write cursor.
ASSERT(pds->posPWriteLast != pdsbP->posNextMix);
// Under normal conditions, the Write position should be between
// the WriteLast position and the NextMix position. We can check
// for an invalid state (resulting most likely from a very late
// wakeup) by checking whether the Write position is beyond our
// NextMix position. If we find ourselves in this shakey
// situation, then we treat this similar to the START state.
// Note that if our wakeup is so late that the Write position wraps
// all the way around past the WriteLast position, we can't detect
// the fact that we're in a bad situation.
if ( ((pds->posPWriteLast < pdsbP->posNextMix) &&
((posPWrite > pdsbP->posNextMix) || (posPWrite < pds->posPWriteLast))) ||
((pds->posPWriteLast > pdsbP->posNextMix) &&
((posPWrite > pdsbP->posNextMix) && (posPWrite < pds->posPWriteLast))) )
{
// oh shit! we're in trouble
DPF(0, "slept late");
posPMix = posPWrite;
dposPRemix = 0;
break;
}
//
//
//
if (fRemix) {
dposPRemix = pdsbP->posNextMix - posPWrite;
if (dposPRemix < 0) dposPRemix += pdsbP->cSamples;
ASSERT(dposPRemix >= 0);
posPMix = posPWrite;
} else {
posPMix = pdsbP->posNextMix;
dposPRemix = 0;
}
break;
case DSPBMIXERSTATE_START:
pds->posPPlayLast = posPPlay;
pds->posPWriteLast = posPWrite;
posPMix = posPWrite;
dposPRemix = 0;
pdsbP->iMixerState = DSPBMIXERSTATE_LOOPING;
break;
case DSPBMIXERSTATE_RESTART:
pds->posPPlayLast = posPPlay;
pds->posPWriteLast = posPWrite;
posPMix = posPWrite;
dposPRemix = pds->dposPRemix;
pdsbP->iMixerState = DSPBMIXERSTATE_LOOPING;
break;
default:
ASSERT(FALSE);
}
//
// Determine how much to mix.
//
// We don't want to mix more than dtimePremixMax beyond the Write cursor,
// nor do we want to wrap past the Play cursor.
//
// The assertions (cMix > 0) below are valid because:
// -cPremixMax is always growing
// -the Write cursor is always advancing
// -posPMix is never beyond the previous write cursor plus
// the previous cPremixMax.
//
// The only time cPremixMax is not growing is on a remix in which case
// the Mix position is equal to the Write cursor, so the assertions
// are still okay. The only time the write cursor would not appear to be
// advancing is if we had a very late wakeup. A very late wakeup would
// be caught and adjusted for in the DSPBMIXERSTATE_LOOPING handling above.
//
cPremixMax = dtimePremixMax * pdsbP->helInfo.dwSampleRate / 1000;
if (posPWrite <= posPMix) {
cMix = posPWrite + cPremixMax - posPMix;
ASSERT( cMix > 0 );
} else {
cMix = posPWrite + cPremixMax - (posPMix+pdsbP->cSamples);
ASSERT( cMix > 0 );
}
//
// If posPPlay==posPMix, then we think we're executing a mix again before
// the play or write cursors have advanced at all. cMix==0, and we don't
// mix no more!
//
if (posPPlay >= posPMix) {
cMix = min(cMix, posPPlay - posPMix);
} else {
cMix = min(cMix, posPPlay + pdsbP->cSamples - posPMix);
}
ASSERT(cMix < pdsbP->cSamples); // sanity check
ASSERT(cMix >= 0 );
//
// We break the mixing up into small chunks, increasing the size of the
// chunk as we go. By doing this, data gets written into the primary
// buffer sooner. Otherwise, if we have a buttload of data to mix, we'd
// spend a lot of time mixing into the mix buffer before any data gets
// written to the primary buffer and the play cursor might catch up
// to us. Here, we start mixing a 10ms chunk of data and increase the
// chunk size 10ms each iteration.
//
cMixThisLoop = pdsbP->helInfo.dwSampleRate / 100;
dcMixThisLoop = cMixThisLoop;
ASSERT(mxListIsValid(pds));
while (cMix > 0) {
LONG cThisMix;
cThisMix = min (cMix, cMixThisLoop);
cMixThisLoop += dcMixThisLoop;
// Initiate the mix session
mixSession.pBuildBuffer = pdsbP->pMixBuffer;
mixSession.dwBuildSize = pdsbP->cbMixBufferSize;
mixSession.HALOutStrBuf = pdsbP->helInfo;
mixSession.pBuffer = pdsbP->pDSBuffer;
mixSession.cbBuffer = pdsbP->cbBufferSize;
mixSession.nOutputBytes = cThisMix << pdsbP->uBlockAlignShift;
mixBeginSession(&mixSession);
// Get data for each buffer
pdsbMixNext = pds->pdsbMixList;
while (NULL != pdsbMixNext) {
// The uMixXxx buffer mix state handlers called below may cause
// the pdsb to be removed from the pdsbMixList. So, we get the
// pointer to the next pdsb in the pdsbMixList now before any
// of the uMixXxx functions are called.
pdsb = pdsbMixNext;
pdsbMixNext = pdsb->pdsbMixNext;
ASSERT(pdsb != pdsbP);
ASSERT(0 == (DSB_INTERNALF_HARDWARE & pdsb->fdwDsbI));
ASSERT(0 == (DSB_INTERNALF_STOP & pdsb->fdwDsbI));
switch (pdsb->iMixerState) {
case DSBMIXERSTATE_NEW:
uMixNewBuffer(pdsb, pdsbP, posPPlay, posPMix, dposPRemix, cThisMix);
break;
case DSBMIXERSTATE_LOOPING:
uMixLoopingBuffer(pdsb, pdsbP, posPPlay, posPMix, dposPRemix, cThisMix);
break;
case DSBMIXERSTATE_NOTLOOPING:
uMixNotLoopingBuffer(pdsb, pdsbP, posPPlay, posPMix, dposPRemix, cThisMix);
break;
case DSBMIXERSTATE_ENDING_WAITINGPRIMARYWRAP:
uMixEndingBufferWaitingWrap(pdsb, pdsbP, posPPlay, posPMix, dposPRemix, cThisMix);
break;
case DSBMIXERSTATE_ENDING:
uMixEndingBuffer(pdsb, pdsbP, posPPlay, posPMix, dposPRemix, cThisMix);
break;
default:
ASSERT(FALSE);
break;
}
}
//
// Lock the output buffer, and if that is successful then write out
// the mix session.
//
{
LPVOID pBuffer1;
LPVOID pBuffer2;
DWORD cbBufferSize1;
DWORD cbBufferSize2;
DWORD dwWriteOffset;
DWORD dwSize;
BOOL fNeedLock;
HRESULT hr;
dwWriteOffset = posPMix << pdsbP->uBlockAlignShift;
dwSize = cThisMix << pdsbP->uBlockAlignShift;
fNeedLock = (0 == (DSDDESC_DONTNEEDPRIMARYLOCK & pds->fdwDriverDesc));
hr = DS_OK;
if( fNeedLock )
{
LONG iWrapLen = (LONG)(dwWriteOffset+dwSize) -
(LONG)pdsbP->cbBufferSize;
pBuffer1 = pdsbP->pDSBuffer+dwWriteOffset;
pBuffer2 = pdsbP->pDSBuffer;
cbBufferSize1 = (iWrapLen<=0) ? dwSize :
pdsbP->cbBufferSize-dwWriteOffset;
cbBufferSize2 = (iWrapLen<=0) ? 0 : (DWORD)iWrapLen;
hr = vxdBufferLock( pdsbP->hBuffer, &pBuffer1, &cbBufferSize1,
&pBuffer2, &cbBufferSize2, dwWriteOffset,
dwSize, 0 );
DPF(4,"graceMix: lock primary buffer, bufptr=0x%8x, dwWriteOffset=%lu, dwSize=%lu, hr=%lu.",pdsbP->pDSBuffer,dwWriteOffset,dwSize,hr);
// Validate that we really locked what we wanted or got an error.
ASSERT( (DS_OK!=hr) || (pBuffer1==pdsbP->pDSBuffer+dwWriteOffset) );
ASSERT( (DS_OK!=hr) || (pBuffer2==pdsbP->pDSBuffer) || (0==cbBufferSize2) );
ASSERT( (DS_OK!=hr) || (dwSize==cbBufferSize1+cbBufferSize2) );
}
if( DS_OK == hr )
{
ASSERT( dwWriteOffset < pdsbP->cbBufferSize );
mixWriteSession( dwWriteOffset );
if( fNeedLock )
{
DPF(5,"graceMix: unlocking primary buffer");
hr = vxdBufferUnlock( pdsbP->hBuffer, pBuffer1, cbBufferSize1,
pBuffer2, cbBufferSize2 );
}
}
}
pdsbP->posNextMix = posPMix + cThisMix;
if (pdsbP->posNextMix >= pdsbP->cSamples) pdsbP->posNextMix -= pdsbP->cSamples;
ASSERT(pdsbP->posNextMix < pdsbP->cSamples);
dposPRemix = 0;
posPMix = pdsbP->posNextMix;
cMix -= cThisMix;
}
//
// Calculate and return the amount of time from the current Write
// cursor to the NextMix position.
//
if (pdsbP->posNextMix > posPWrite) {
*pdtimeInvalid = (pdsbP->posNextMix - posPWrite);
} else {
*pdtimeInvalid = (pdsbP->posNextMix + pdsbP->cSamples - posPWrite);
}
*pdtimeInvalid = *pdtimeInvalid * 1000 / pdsbP->helInfo.dwSampleRate;
// Remember the last Play and Write positions of the primary buffer.
pds->posPPlayLast = posPPlay;
pds->posPWriteLast = posPWrite;
retClean:
pds->fdwMixerSignal &= ~DSMIXERSIGNAL_REMIX;
return;
}
//--------------------------------------------------------------------------;
//
//
//
//--------------------------------------------------------------------------;
DWORD WINAPI graceThread(LPVOID pThreadParam)
{
LPDSOUND pds = (LPDSOUND)pThreadParam;
HANDLE hEventTerminate;
HANDLE hEventRemix;
TCHAR ach[256];
DWORD dwResult;
BOOL fResult;
LONG dtime;
LONG dtimeSleep;
LONG dtimePremix;
LONG ddtimePremix;
LONG dtimeInvalid;
DPF(0, "Grace is in the building");
// We mangle the event names by prepending the address of the ds
// object for which this thread is running. This allows unique
// event names for each ds object.
wsprintf(ach, STRFORMAT_MIXEVENT_TERMINATE, pds);
hEventTerminate = CreateEvent(NULL, FALSE, FALSE, ach);
DPF(1, "graceThread: terminate event name '%s'", ach);
wsprintf(ach, STRFORMAT_MIXEVENT_REMIX, pds);
hEventRemix = CreateEvent(NULL, FALSE, FALSE, ach);
DPF(1, "graceThread: remix event name '%s'", ach);
// Here we do a simple handshake with the creator of this thread. We
// signal the IAH_TERMINATE event. When our creator sees it, it will
// signal the IAH_REMIX event.
fResult = SetEvent(hEventTerminate);
ASSERT(fResult);
dwResult = WaitForSingleObjectEx(hEventRemix, INFINITE, FALSE);
ASSERT(WAIT_OBJECT_0 == dwResult);
//
//
//
dtimeSleep = MIXER_MAXPREMIX/2;
while (TRUE) {
HANDLE ah[] = {hEventTerminate, hEventRemix};
DWORD dwResult;
ASSERT(dtimeSleep <= MIXER_MAXPREMIX/2);
dwResult = WaitForMultipleObjectsEx(2, ah, FALSE, dtimeSleep, FALSE);
if (WAIT_OBJECT_0 == dwResult) break;
ASSERT(((WAIT_OBJECT_0 + 1) == dwResult) || (WAIT_TIMEOUT == dwResult));
dwResult = ENTER_DLL_CSECT_OR_EVENT(hEventTerminate);
if (WAIT_OBJECT_0 == dwResult) break;
if (pds->fdwMixerSignal & DSMIXERSIGNAL_REMIX) {
ResetEvent(hEventRemix);
dtimePremix = 45;
ddtimePremix = 2;
graceMix(pds, TRUE, dtimePremix, &dtimeInvalid);
DPF(4, "dtPremix = %d dtInvalid = %d", dtimePremix, dtimeInvalid);
dtimeSleep = 15;
} else {
dtimePremix += ddtimePremix;
if (dtimePremix > MIXER_MAXPREMIX) {
dtimePremix = MIXER_MAXPREMIX;
} else {
ddtimePremix += 2;
}
dtime = timeGetTime();
graceMix(pds, FALSE, dtimePremix, &dtimeInvalid);
dtime = timeGetTime() - dtime;
dtimeInvalid -= 2 * dtime;
dtimeSleep = max(0, dtimeInvalid / 2);
}
LEAVE_DLL_CSECT();
}
CloseHandle(hEventRemix);
CloseHandle(hEventTerminate);
DPF(0, "Grace is outta here");
return 0;
}
//==========================================================================;
//
// External interfaces into this module
//
// mxInitialize
// mxSignalRemix
// mxTerminate
// mxGetPosition
// mxListAdd
// msListRemove
//
//==========================================================================;
//--------------------------------------------------------------------------;
//
// mxListIsValid
//
// This function attempts to validate the list of buffers to mix. It is
// intended to be used as a debugging aid. The list should never be invalid.
//
//
//--------------------------------------------------------------------------;
BOOL mxListIsValid(LPDSOUND pds)
{
LPDSBUFFER pdsbT;
pdsbT = pds->pdsbMixList;
while (NULL != pdsbT) {
if (IsBadWritePtr(pdsbT, sizeof(*pdsbT))) break;
if (DSBUFFSIG != pdsbT->dwSig) break;
if (DSB_INTERNALF_STOP & pdsbT->fdwDsbI) break;
if (DSB_INTERNALF_PRIMARY & pdsbT->fdwDsbI) break;
if (DSB_INTERNALF_HARDWARE & pdsbT->fdwDsbI) break;
if (DSB_INTERNALF_WAVEEMULATED & pdsbT->fdwDsbI) break;
pdsbT = pdsbT->pdsbMixNext;
}
return (NULL == pdsbT);
}
//--------------------------------------------------------------------------;
//
// mxListBufferInList
//
// This function determines whether a given buffer is in the list of buffers
// to mix. It is intended to be used as a debugging aid. This function should
// not be used as a means of determining whether a buffer is currently
// being mixed.
//
//--------------------------------------------------------------------------;
BOOL mxListBufferInList(LPDSBUFFER pdsb)
{
LPDSOUND pds;
LPDSBUFFER pdsbT;
pds = pdsb->pds;
if (pdsb == pds->pdsbMixList) return TRUE;
pdsbT = pds->pdsbMixList;
while (NULL != pdsbT) {
if (pdsbT->pdsbMixNext == pdsb) return TRUE;
pdsbT = pdsbT->pdsbMixNext;
}
return FALSE;
}
//--------------------------------------------------------------------------;
//
//
//
//--------------------------------------------------------------------------;
void mxListAdd(LPDSBUFFER pdsb)
{
LPDSOUND pds;
ASSERT(!IsBadWritePtr(pdsb, sizeof(*pdsb)));
ASSERT(DSBUFFSIG == pdsb->dwSig);
ASSERT(0 == (DSB_INTERNALF_STOP & pdsb->fdwDsbI));
ASSERT(0 == (DSB_INTERNALF_PRIMARY & pdsb->fdwDsbI));
ASSERT(0 == (DSB_INTERNALF_HARDWARE & pdsb->fdwDsbI));
ASSERT(0 == (DSB_INTERNALF_WAVEEMULATED & pdsb->fdwDsbI));
ASSERT(NULL == pdsb->pdsbMixNext);
pds = pdsb->pds;
ASSERT(mxListIsValid(pds));
pdsb->iMixerState = DSBMIXERSTATE_NEW;
pdsb->iMixerSubstate = DSBMIXERSUBSTATE_NEW;
pdsb->uLastFrequency = pdsb->helInfo.dwSampleRate;
pdsb->pdsbMixNext = pds->pdsbMixList;
pds->pdsbMixList = pdsb;
}
//--------------------------------------------------------------------------;
//
//
//
//--------------------------------------------------------------------------;
void mxListRemove(LPDSBUFFER pdsb)
{
LPDSOUND pds;
LPDSBUFFER pdsbT;
pds = pdsb->pds;
ASSERT(mxListIsValid(pds));
if (pdsb == pds->pdsbMixList) {
pds->pdsbMixList = pdsb->pdsbMixNext;
pdsb->pdsbMixNext = NULL;
return;
}
pdsbT = pds->pdsbMixList;
while (NULL != pdsbT) {
if (pdsbT->pdsbMixNext == pdsb) {
pdsbT->pdsbMixNext = pdsb->pdsbMixNext;
pdsb->pdsbMixNext = NULL;
return;
}
pdsbT = pdsbT->pdsbMixNext;
}
//
// Couldn't find it in the list!!!
//
ASSERT(FALSE);
}
//--------------------------------------------------------------------------;
//
// mxGetPosition
//
// This function returns the play and write cursor positions of a secondary
// buffer that is being software mixed into a primary buffer. The position is
// computed from the position of the primary buffer into which it is being
// mixed. This function also returns the "mix cursor" which is the next
// position of the secondary buffer from which data will be mixed on a mixer
// refresh event. The region from the write cursor to the mix cursor is the
// premixed region of the buffer. Note that a remix event may cause the grace
// mixer to mix from a position before the mix cursor.
//
//--------------------------------------------------------------------------;
DSVAL mxGetPosition(LPDSBUFFER pdsb, LPDWORD pdwPlayCursor, LPDWORD pdwWriteCursor, LPDWORD pdwMixCursor)
{
LPDSBUFFER pdsbP;
LONG posPPlay;
LONG posPWrite;
LONG dposPPlay;
LONG dposPWrite;
LONG posSPlay;
LONG posSWrite;
LONG dposSPlay;
LONG dposSWrite;
DWORD dwPlayCursorP;
DWORD dwWriteCursorP;
DSVAL dsv;
ASSERT(mxListBufferInList(pdsb));
pdsbP = pdsb->pds->pdsbPrimary;
ASSERT(NULL != pdsbP);
if (NULL != pdwMixCursor) {
*pdwMixCursor = pdsb->posNextMix << pdsb->uBlockAlignShift;
}
if ((NULL == pdwPlayCursor) && (NULL == pdwWriteCursor)) {
return DS_OK;
}
dsv = vxdBufferGetPosition(pdsbP->hBuffer, &dwPlayCursorP, &dwWriteCursorP);
if (DS_OK != dsv) {
DPF(0, "Couldn't GetCurrentPosition of primary");
return dsv;
}
// Convert from byte position to sample position
posPPlay = dwPlayCursorP >> pdsbP->uBlockAlignShift;
posPWrite = dwWriteCursorP >> pdsbP->uBlockAlignShift;
//
//
//
ASSERT(pdsb->uLastFrequency);
switch (pdsb->iMixerSubstate) {
case DSBMIXERSUBSTATE_NEW:
dposSPlay = 0;
dposSWrite = 0;
break;
case DSBMIXERSUBSTATE_STARTING_WAITINGPRIMARYWRAP:
case DSBMIXERSUBSTATE_STARTING:
dposPPlay = pdsbP->posNextMix - pdsb->posPStart;
if (dposPPlay < 0) dposPPlay += pdsbP->cSamples;
ASSERT(dposPPlay >= 0);
dposSPlay = MulDivRD(dposPPlay, pdsb->uLastFrequency, pdsbP->helInfo.dwSampleRate);
dposPWrite = pdsbP->posNextMix - posPWrite;
if (dposPWrite < 0) dposPWrite += pdsbP->cSamples;
ASSERT(dposPWrite >= 0);
dposSWrite = MulDivRD(dposPWrite, pdsb->uLastFrequency, pdsbP->helInfo.dwSampleRate);
break;
case DSBMIXERSUBSTATE_STARTED:
dposPPlay = pdsbP->posNextMix - posPPlay;
if (dposPPlay < 0) dposPPlay += pdsbP->cSamples;
ASSERT(dposPPlay >= 0);
dposSPlay = MulDivRD(dposPPlay, pdsb->uLastFrequency, pdsbP->helInfo.dwSampleRate);
dposPWrite = pdsbP->posNextMix - posPWrite;
if (dposPWrite < 0) dposPWrite += pdsbP->cSamples;
ASSERT(dposPWrite >= 0);
dposSWrite = MulDivRD(dposPWrite, pdsb->uLastFrequency, pdsbP->helInfo.dwSampleRate);
break;
default:
ASSERT(FALSE);
break;
}
//
//
//
posSPlay = pdsb->posNextMix - dposSPlay;
while (posSPlay < 0) posSPlay += pdsb->cSamples;
posSWrite = pdsb->posNextMix - dposSWrite;
posSWrite += pdsb->helInfo.dwSampleRate * MIXER_WRITEPAD / 1000;
while (posSWrite < 0) posSWrite += pdsb->cSamples;
// DPF(0, "SS%d posSPlay=%d, posSWrite=%d", pdsb->iMixerSubstate, posSPlay, posSWrite);
if (NULL != pdwPlayCursor) *pdwPlayCursor = posSPlay << pdsb->uBlockAlignShift;
if (NULL != pdwWriteCursor) *pdwWriteCursor = posSWrite << pdsb->uBlockAlignShift;
return DS_OK;
}
//--------------------------------------------------------------------------;
//
// mxTerminate
//
// This function is called to terminate the grace mixer thread for the
// specified ds object. It returns the handle to the thread that is being
// terminated. After releasing any critical sections that the grace mixer
// thread may be waiting on, the caller should wait for the thread handle
// to become signaled. For Win32 beginners: the thread handle is signalled
// after the thread terminates.
//
//--------------------------------------------------------------------------;
HANDLE mxTerminate(LPDSOUND pds)
{
HANDLE hMixThread;
vxdEventScheduleWin32Event(pds->vxdhMixEventTerminate, 0);
vxdEventCloseVxDHandle(pds->vxdhMixEventTerminate);
vxdEventCloseVxDHandle(pds->vxdhMixEventRemix);
hMixThread = pds->hMixThread;
pds->hMixThread = NULL;
pds->vxdhMixEventTerminate = NULL;
pds->vxdhMixEventRemix = NULL;
return hMixThread;
}
//--------------------------------------------------------------------------;
//
//
//
//--------------------------------------------------------------------------;
void mxSignalRemix(LPDSOUND pds, DWORD dwDelay)
{
if (0 == (DSMIXERSIGNAL_REMIX & pds->fdwMixerSignal)) {
vxdEventScheduleWin32Event(pds->vxdhMixEventRemix, dwDelay);
pds->fdwMixerSignal |= DSMIXERSIGNAL_REMIX;
}
}
//--------------------------------------------------------------------------;
//
//
//
//--------------------------------------------------------------------------;
void mxInitialize(LPDSOUND pds)
{
HANDLE hMixEventTerminate;
HANDLE hMixEventRemix;
TCHAR ach[256];
DWORD dwResult;
BOOL fResult;
DPF(0, "Creating mixer thread");
ASSERT(NULL == pds->hMixThread);
ASSERT(NULL == pds->vxdhMixEventTerminate);
ASSERT(NULL == pds->vxdhMixEventRemix);
// BUGBUG !!!I can't believe I didn't do ANY error
// handling in this function!!!
// We mangle the event names by prepending the address of the ds
// object for which this thread is running. This allows unique
// event names for each ds object.
wsprintf(ach, STRFORMAT_MIXEVENT_TERMINATE, pds);
hMixEventTerminate = CreateEvent(NULL, FALSE, FALSE, ach);
DPF(1, "mxInitialize: terminate event name '%s'", ach);
wsprintf(ach, STRFORMAT_MIXEVENT_REMIX, pds);
hMixEventRemix = CreateEvent(NULL, FALSE, FALSE, ach);
DPF(1, "mxInitialize: remix event name '%s'", ach);
pds->vxdhMixEventTerminate = OpenVxDHandle(hMixEventTerminate);
pds->vxdhMixEventRemix = OpenVxDHandle(hMixEventRemix);
pds->hMixThread = HelperCreateDSMixerThread(graceThread, pds, 0, NULL);
dwResult = WaitForSingleObjectEx(hMixEventTerminate, INFINITE, FALSE);
ASSERT(dwResult == WAIT_OBJECT_0);
fResult = SetEvent(hMixEventRemix);
ASSERT(fResult);
fResult = CloseHandle(hMixEventTerminate);
ASSERT(fResult);
fResult = CloseHandle(hMixEventRemix);
ASSERT(fResult);
}