/******************************************************************************

   Copyright (C) Microsoft Corporation 1991-1992. All rights reserved.

   Title:   avisound.c - Code for playing audio in AVI files.

*****************************************************************************/
#include "graphic.h"

#define AUDIO_PANIC 10
static UINT nAudioPanic;

//
// redefine StreamFromFOURCC to only handle 0-9 streams!
//
#undef StreamFromFOURCC
#define StreamFromFOURCC(fcc) (UINT)(HIBYTE(LOWORD(fcc)) - (BYTE)'0')

void FAR PASCAL _LOADDS mciaviWaveOutFunc(HWAVEOUT hWaveOut, UINT wMsg,
		    DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);

#ifndef WIN32
#define GetDS() (HGLOBAL)HIWORD((DWORD)(LPVOID)&ghModule)
#endif //WIN16

DWORD FAR PASCAL SetUpAudio(NPMCIGRAPHIC npMCI, BOOL fPlaying)
{
    UINT	w;
    LPWAVEHDR   lpWaveHdr;
    STREAMINFO *psi;

    if (npMCI->nAudioStreams == 0) {
	npMCI->wABs = 0;
	npMCI->wABOptimal = 0;
	return 0L;
    }

    nAudioPanic = GetProfileInt(TEXT("MCIAVI"), TEXT("AudioPanic"), AUDIO_PANIC);

    psi = SI(npMCI->nAudioStream);
    Assert(psi->sh.fccType == streamtypeAUDIO);
    Assert(psi->cbFormat);
    Assert(psi->lpFormat);

    if (!npMCI->pWF) {
        npMCI->pWF = (NPWAVEFORMAT)LocalAlloc(LPTR, (UINT)psi->cbFormat);

	if (!npMCI->pWF) {
	    return MCIERR_OUT_OF_MEMORY;
	}
    }

    hmemcpy(npMCI->pWF,psi->lpFormat,psi->cbFormat);

    npMCI->wEarlyAudio = (UINT)psi->sh.dwInitialFrames;
    npMCI->dwAudioLength = psi->sh.dwLength * psi->sh.dwSampleSize;

    if (npMCI->dwAudioLength < 1000L) {
        DPF(("AudioLength is bogus"));
        npMCI->dwAudioLength = muldiv32((npMCI->pWF->nAvgBytesPerSec + 100) *
                npMCI->lFrames,npMCI->dwMicroSecPerFrame,1000000L);
    }

    //
    // choose the audio playback method depending on how we are going to
    // recive audio data from the file.
    //
    switch (npMCI->wPlaybackAlg) {
        case MCIAVI_ALG_HARDDISK:
	case MCIAVI_ALG_AUDIOONLY:

            if (!npMCI->pf && !npMCI->hmmioAudio) {
                MMIOINFO            mmioInfo;

                _fmemset(&mmioInfo, 0, sizeof(MMIOINFO));
                mmioInfo.htask = (HANDLE) npMCI->hCallingTask; //ntmmsystem bug, should be threadid which is dword
                npMCI->hmmioAudio = mmioOpen(npMCI->szFilename, &mmioInfo,
                                        MMIO_READ | MMIO_DENYWRITE);

                if (npMCI->hmmioAudio == NULL)
                    npMCI->hmmioAudio = mmioOpen(npMCI->szFilename, &mmioInfo,
                                        MMIO_READ);

                if (!npMCI->hmmioAudio) {
		    Assert(0);
		    return MCIERR_DRIVER_INTERNAL;
		}
            }

            // fall through to CDROM

        case MCIAVI_ALG_CDROM:
            //!!!! we need to tune this!!!!
            // !!! We use four 1/2 second buffers.  This is arbitrary.
            npMCI->wABs = 4;
            npMCI->wABOptimal = 0;
            npMCI->dwABSize = npMCI->pWF->nAvgBytesPerSec / 2;
            break;

        case MCIAVI_ALG_INTERLEAVED:
            /* Fix up some values based on the header information */
            npMCI->dwABSize = muldiv32(npMCI->dwMicroSecPerFrame,
                    npMCI->pWF->nAvgBytesPerSec,1000000L) + 2047;

            npMCI->dwABSize &= ~(2047L);

            npMCI->wABs = npMCI->wEarlyAudio + 2 + (WORD) npMCI->dwBufferedVideo;

            /* Soundblaster hack: waveoutdone only accurate to 2K. */

            //!!!!!!!!!! is this right.

            if (npMCI->dwMicroSecPerFrame) {
                npMCI->wABOptimal = npMCI->wABs -
                        (UINT) (muldiv32(2048, 1, muldiv32(npMCI->dwMicroSecPerFrame,
                        npMCI->pWF->nAvgBytesPerSec,1000000L)));
            } else {
                npMCI->wABOptimal = 0;
            }

            //!!! hack so we can do burst reading, up to 1sec
            //npMCI->wABs += (int)muldiv32(1000000l, 1, npMCI->dwMicroSecPerFrame);

            DPF2(("Using %u audio buffers, of which %u should be full.\n", npMCI->wABs, npMCI->wABOptimal));
            break;

        default:
            Assert(0);
            return 0L;
    }

    npMCI->dwABSize -= npMCI->dwABSize % npMCI->pWF->nBlockAlign;

    if (!fPlaying)
	return 0L;

    /* This code adjusts the wave format block to play
    ** the audio at the correct speed to match the frame rate.
    */

    npMCI->pWF->nSamplesPerSec = muldiv32(npMCI->pWF->nSamplesPerSec,
					    npMCI->dwMicroSecPerFrame,
					    npMCI->dwPlayMicroSecPerFrame);

    npMCI->pWF->nAvgBytesPerSec = muldiv32(npMCI->pWF->nAvgBytesPerSec,
					    npMCI->dwMicroSecPerFrame,
					    npMCI->dwPlayMicroSecPerFrame);

    if (npMCI->pWF->wFormatTag == WAVE_FORMAT_PCM) {
	/* Make sure this is exactly right... */
	npMCI->pWF->nAvgBytesPerSec =
            npMCI->pWF->nSamplesPerSec * npMCI->pWF->nBlockAlign;
    }

    /* Kill any currently playing sound */
    sndPlaySound(NULL, 0);

    DPF2(("Opening wave device....\n"));
    /* Try to open a wave device. */
    w = waveOutOpen(&npMCI->hWave, (UINT)WAVE_MAPPER,
		(LPWAVEFORMATEX) npMCI->pWF,
		//(const LPWAVEFORMATEX) npMCI->pWF,
		(DWORD) &mciaviWaveOutFunc,
		(DWORD) (LPMCIGRAPHIC) npMCI,
                (DWORD)CALLBACK_FUNCTION);

    if (w) {
	DPF(("Unable to open wave device.\n"));

	npMCI->hWave = NULL;
        return w == WAVERR_BADFORMAT ?
			    MCIERR_WAVE_OUTPUTSUNSUITABLE :
			    MCIERR_WAVE_OUTPUTSINUSE;
    }

    npMCI->dwFlags &= ~MCIAVI_LOSTAUDIO;

#ifndef WIN32 // No need to lock it on NT - although we could with Virtual mem
              // functions
    //
    // page lock our DS so our wave callback function can
    // touch it without worry. see mciaviWaveOutFunc()
    //
    GlobalPageLock(GetDS());
#endif //WIN16

    /* Pause the wave output device, so it won't start playing
    ** when we're loading up the buffers.
    */
    if (waveOutPause(npMCI->hWave) != 0) {
	DPF(("Error from waveOutPause!\n"));
	return MCIERR_DRIVER_INTERNAL;
    }

    if (npMCI->dwFlags & MCIAVI_VOLUMESET) {
	DeviceSetVolume(npMCI, npMCI->dwVolume);
    } else {
	DeviceGetVolume(npMCI);
    }

    npMCI->lpAudio = GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
		    npMCI->wABs * (npMCI->dwABSize + sizeof(WAVEHDR)));

    if (!npMCI->lpAudio) {
	return MCIERR_OUT_OF_MEMORY;
    }

    npMCI->dwAudioPlayed = 0L;
    npMCI->wNextAB = 0;
    npMCI->dwUsedThisAB = 0;

    /* Allocate and prepare our buffers */
    for (w = 0; w < npMCI->wABs; w++) {
	lpWaveHdr = (LPWAVEHDR) (npMCI->lpAudio + (w * sizeof(WAVEHDR)));

	lpWaveHdr->lpData = (HPSTR) npMCI->lpAudio +
				    npMCI->wABs * sizeof(WAVEHDR) +
				    w * npMCI->dwABSize;
	lpWaveHdr->dwBufferLength = npMCI->dwABSize;
	lpWaveHdr->dwBytesRecorded = 0L;
	lpWaveHdr->dwUser = 0L;
	lpWaveHdr->dwFlags = 0L;
	lpWaveHdr->dwLoops = 0L;
	lpWaveHdr->lpNext = 0L;
	lpWaveHdr->reserved = 0;
    }

    for (w = 0; w < npMCI->wABs; w++) {
	lpWaveHdr = (LPWAVEHDR) (npMCI->lpAudio + (w * sizeof(WAVEHDR)));

	if (waveOutPrepareHeader(npMCI->hWave, lpWaveHdr, sizeof(WAVEHDR))
			!= 0) {
	    return MCIERR_OUT_OF_MEMORY;
	}
	lpWaveHdr->dwFlags |= WHDR_DONE;
    }

    return 0L;
}

DWORD FAR PASCAL CleanUpAudio(NPMCIGRAPHIC npMCI)
{
    UINT	w;

    /* Clear flags relating to playing audio */
    npMCI->dwFlags &= ~(MCIAVI_WAVEPAUSED);

    if (npMCI->lpAudio) {
        waveOutRestart(npMCI->hWave); // just in case we are paused
	waveOutReset(npMCI->hWave);

	for (w = 0; w < npMCI->wABs; w++) {
	    LPWAVEHDR	lpWaveHdr;

	    lpWaveHdr = (LPWAVEHDR) (npMCI->lpAudio
					    + (w * sizeof(WAVEHDR)));

#if 0
	    lpWaveHdr->lpData = npMCI->lpAudio
				    + npMCI->wABs * sizeof(WAVEHDR)
				    + w * npMCI->dwABSize;
	    lpWaveHdr->dwBufferLength = npMCI->dwABSize;
#endif

	    /* Do we need to check for an error from this? */
	    waveOutUnprepareHeader(npMCI->hWave, lpWaveHdr,
						    sizeof(WAVEHDR));
	}
	GlobalFreePtr(npMCI->lpAudio);
	npMCI->lpAudio = NULL;

	Assert(npMCI->wABFull == 0);
    }

    DPF2(("Closing wave device.\n"));
    waveOutClose(npMCI->hWave);
    npMCI->hWave = 0;

#ifndef WIN32
    GlobalPageUnlock(GetDS());
#endif //WIN16

    return 0L;
}

BOOL NEAR PASCAL WaitForFreeAudioBuffer(NPMCIGRAPHIC npMCI, BOOL FAR *lpfHurry)
{
    LPWAVEHDR   lpWaveHdr;

    lpWaveHdr = (LPWAVEHDR) (npMCI->lpAudio
				+ (npMCI->wNextAB * sizeof(WAVEHDR)));

    /* Use the number of full audio buffers to decide if we're behind. */
    if (npMCI->wABFull < npMCI->wABOptimal) {
        *lpfHurry = TRUE;
    }

    /* If all of the audio buffers are full, we have to wait. */
    if (npMCI->wABFull == npMCI->wABs) {

        DWORD time = timeGetTime();

        #define AUDIO_WAIT_TIMEOUT 2000

        DOUT2("waiting for audio buffer.");

        // we better not wait if the device is not playing!
        Assert(!(npMCI->dwFlags & MCIAVI_WAVEPAUSED));

#ifdef XDEBUG
        GetAsyncKeyState(VK_ESCAPE);
        GetAsyncKeyState(VK_F2);
        GetAsyncKeyState(VK_F3);
        GetAsyncKeyState(VK_F4);
#endif
        while (npMCI->wABFull == npMCI->wABs) {

            if (npMCI->dwFlags & MCIAVI_STOP)
                return FALSE;

            aviTaskYield();

            //
            //  the "Fahrenheit VA Audio Wave Driver" may get confused
            //  if you call waveOutPause() and waveOutRestart() alot
            //  and it will stay paused no matter what you do, it has
            //  all our buffers and it still does not make any sound
            //  you can call waveOutRestart() until you are blue in
            //  the face, it will do nothing.
            //
            //  so this is why this routine can time out, after waiting
            //  2 seconds or so we just toss all the audio in the buffers
            //  and start over.
            //
            if (timeGetTime() - time > AUDIO_WAIT_TIMEOUT) {
                DOUT("Gave up waiting, reseting wave device\n");
                waveOutReset(npMCI->hWave);
                break;
            }

#ifdef XDEBUG
            if (GetAsyncKeyState(VK_ESCAPE) & 0x0001) {
                DPF(("STOPPED WAITING! wABFull = %d, wABs = %d\n", npMCI->wABFull,npMCI->wABs));
                return FALSE;
            }

            if (GetAsyncKeyState(VK_F2) & 0x0001) {
                DOUT("Trying waveOutRestart\n");
                waveOutRestart(npMCI->hWave);
            }

            if (GetAsyncKeyState(VK_F3) & 0x0001) {
                DOUT("Trying waveOutReset\n");
                waveOutReset(npMCI->hWave);
            }

            if (GetAsyncKeyState(VK_F4) & 0x0001) {

                int i,n;

                for (i=n=0; i<(int)npMCI->wABs; i++) {

                    if (((LPWAVEHDR)npMCI->lpAudio)[i].dwFlags & WHDR_DONE) {
                        DPF(("Buffer #%d is done!\n", i));
                        n++;
                    }
                    else {
                        DPF(("Buffer #%d is not done\n", i));
                    }
                }

                if (n > 0)
                    DPF(("%d buffers are done but our callback did not get called!\n", n));
            }
#endif
        }

        DOUT2("done\n");
    }

    /* Debugging check that wave has finished playing--should never happen */
    Assert(lpWaveHdr->dwFlags & WHDR_DONE);

#if 0
    lpWaveHdr->lpData = npMCI->lpAudio +
				npMCI->wABs * sizeof(WAVEHDR) +
				npMCI->wNextAB * npMCI->dwABSize;
#endif

    return TRUE;
}

#ifndef WIN32
#pragma optimize("", off)
#endif

BOOL NEAR PASCAL ReadSomeAudio(NPMCIGRAPHIC npMCI, BYTE _huge * lpAudio,
				DWORD dwStart, DWORD FAR * pdwLength)
{
    DWORD	dwIndex = 0;
    DWORD	ckidAudio;
    DWORD	dwAudioPos = 0L;
    AVIINDEXENTRY far * lpIndexEntry;

    Assert(npMCI->wPlaybackAlg == MCIAVI_ALG_HARDDISK ||
           npMCI->wPlaybackAlg == MCIAVI_ALG_AUDIOONLY);

    Assert(npMCI->hpIndex);

    /*
    ** Figure out what type of chunk we're looking for,
    */
    ckidAudio = MAKEAVICKID(cktypeWAVEbytes, npMCI->nAudioStream);

    lpIndexEntry = (AVIINDEXENTRY FAR *) npMCI->hpIndex;

    for (dwIndex = 0; dwIndex < npMCI->macIndex;
                dwIndex++, ++((AVIINDEXENTRY _huge *) lpIndexEntry)) {

	if (lpIndexEntry->ckid != ckidAudio)
	    continue;
	
	if (dwAudioPos + lpIndexEntry->dwChunkLength > dwStart) {
	    DWORD	dwLengthNow;
	    DWORD	dwSeekTo;
	
	    dwLengthNow = lpIndexEntry->dwChunkLength;
	    dwSeekTo = lpIndexEntry->dwChunkOffset + 8;
	
	    if (dwAudioPos + dwLengthNow > dwStart + *pdwLength) {
		/* Attempted optimization: If we've already read some
		** data, and we can't read the next whole chunk, let's
		** leave it for later.
		*/
		if (dwAudioPos > dwStart && (!(npMCI->dwFlags & MCIAVI_REVERSE)))
		    break;
		dwLengthNow = dwStart + *pdwLength - dwAudioPos;
	    }
	
	    if (dwAudioPos < dwStart) {
		dwLengthNow -= (dwStart - dwAudioPos);
		dwSeekTo += (dwStart - dwAudioPos);
	    }

            mmioSeek(npMCI->hmmioAudio, dwSeekTo, SEEK_SET);

	    if (mmioRead(npMCI->hmmioAudio, lpAudio, dwLengthNow)
			    != (LONG) dwLengthNow) {
		DPF(("Error reading audio data (%lx bytes at %lx)\n", dwLengthNow, dwSeekTo));
		return FALSE;
	    }
	    lpAudio += dwLengthNow;	
	}
	
	dwAudioPos += lpIndexEntry->dwChunkLength;
	
	if (dwAudioPos >= dwStart + *pdwLength)
	    return TRUE;
    }

    if (dwAudioPos < dwStart)
	*pdwLength = 0;	    // return FALSE?
    else
	*pdwLength = dwAudioPos - dwStart;

    return TRUE;
}
#ifndef WIN32
#pragma optimize("", on)
#endif
	
BOOL NEAR PASCAL ReverseWaveBuffer(NPMCIGRAPHIC npMCI, LPWAVEHDR lpWaveHdr)
{
    DWORD   dwLeft = lpWaveHdr->dwBufferLength;
    BYTE _huge *hp1;
    BYTE _huge *hp2;
    DWORD   dwBlock = npMCI->pWF->nBlockAlign;
    BYTE    bTemp;
    DWORD   dw;

    Assert(npMCI->dwFlags & MCIAVI_REVERSE);
    Assert(npMCI->wPlaybackAlg == MCIAVI_ALG_HARDDISK ||
	   npMCI->wPlaybackAlg == MCIAVI_ALG_AUDIOONLY);

    /* This routine doesn't like it when the data doesn't end on a
    ** block boundary, so make it so.  This should never happen.
    */
    Assert((dwLeft % dwBlock) == 0);
    dwLeft -= dwLeft % dwBlock;

    hp1 = lpWaveHdr->lpData;
    hp2 = ((HPSTR) lpWaveHdr->lpData) + (dwLeft - dwBlock);

    while ((LONG) dwLeft > (LONG) dwBlock) {
	for (dw = 0; dw < dwBlock; dw++) {
	    bTemp = *hp1;
	    *hp1++ = *hp2;
	    *hp2++ = bTemp;
	}
	hp2 -= dwBlock * 2;
	dwLeft -= dwBlock * 2;
    }

    return TRUE;
}

void FAR PASCAL BuildVolumeTable(NPMCIGRAPHIC npMCI)
{
    int	    vol;
    int     i;

    if (!npMCI->pWF || npMCI->pWF->wFormatTag != WAVE_FORMAT_PCM)
        return;

    if (((NPPCMWAVEFORMAT) npMCI->pWF)->wBitsPerSample != 8)
        return;

    vol = (LOWORD(npMCI->dwVolume) + HIWORD(npMCI->dwVolume)) / 2;
    vol = (int) (((LONG) vol * 256) / 500);

    if (!npMCI->pVolumeTable)
        npMCI->pVolumeTable = (void *)LocalAlloc(LPTR, 256);

    if (!npMCI->pVolumeTable)
        return;

    for (i = 0; i < 256; i++) {
        npMCI->pVolumeTable[i] = (BYTE) min(255, max(0,
                (int) ((((LONG) (i - 128) * vol) / 256) + 128)));
    }
}

BOOL NEAR PASCAL AdjustVolume(NPMCIGRAPHIC npMCI, LPWAVEHDR lpWaveHdr)
{
    DWORD   dwLeft = lpWaveHdr->dwBufferLength;
    BYTE FAR *pb;

    if (npMCI->pWF->wFormatTag != WAVE_FORMAT_PCM)
	return FALSE;

    if (!npMCI->pVolumeTable)
        return FALSE;

    if (((NPPCMWAVEFORMAT)npMCI->pWF)->wBitsPerSample != 8)
        return FALSE;

    pb = lpWaveHdr->lpData;

#ifndef WIN32
    if (OFFSETOF(pb) + dwLeft > 64l*1024) {
	while (dwLeft--) {
            *pb = npMCI->pVolumeTable[*pb];
            ((BYTE _huge *)pb)++;
	}
    }
    else {
        while ((int)dwLeft--)
            *pb++ = npMCI->pVolumeTable[*pb];
    }
#else
    while ((int)dwLeft--)
        *pb++ = npMCI->pVolumeTable[*pb];
#endif

    return TRUE;
}

BOOL NEAR PASCAL PlaySomeAudio(NPMCIGRAPHIC npMCI, LPWAVEHDR lpWaveHdr)
{
    if (npMCI->pVolumeTable)
        AdjustVolume(npMCI, lpWaveHdr);

    lpWaveHdr->dwFlags &= ~WHDR_DONE;

    /* If we're playing and we've used all of our audio buffers, pause the
    ** wave device until we can fill more of them up.
    **
    ** we need to be carefull not to do this on the last frame!!!
    */
    if ((npMCI->wTaskState == TASKPLAYING) &&
        !(npMCI->dwFlags & MCIAVI_WAVEPAUSED) &&
        (npMCI->wABFull == 0 || npMCI->nAudioBehind > nAudioPanic)) {

        if (npMCI->wABFull > 0) {
            DPF(("Audio panic stop\n"));
        } else {
            DPF(("Audio queue empty; pausing wave device\n"));
        }

        //
        // some audio cards dont like starving it confuses them
        // it is kind of rude any way.  we are going to cause a audio break
        // anyway so if we lose a little bit of audio (a few frames or so)
        // no one will even notice (any worse than the audio break)
        //
        if (npMCI->wABFull <= 1) {
            DOUT("Trying audio hack!\n");
            waveOutReset(npMCI->hWave);
        }

        ++npMCI->dwAudioBreaks;
        waveOutPause(npMCI->hWave);

        ICDrawStop(npMCI->hicDraw);
	npMCI->dwFlags |= MCIAVI_WAVEPAUSED;
    }

    if (waveOutWrite(npMCI->hWave, lpWaveHdr, sizeof(WAVEHDR)) != 0) {
        DPF(("Error from waveOutWrite!\n"));
	npMCI->dwTaskError = MCIERR_AVI_AUDIOERROR;
	return FALSE;
    } else {
	++npMCI->wABFull;

	/* Use the next wave buffer next time */
	++npMCI->wNextAB;
	if (npMCI->wNextAB == npMCI->wABs)
	    npMCI->wNextAB = 0;
	
	npMCI->dwUsedThisAB = 0;
    }

    if (npMCI->wABFull < min(npMCI->wABOptimal, npMCI->wABFull/2))
        npMCI->nAudioBehind++;
    else
        npMCI->nAudioBehind=0;

    /* If we paused the wave device to let ourselves catch up, and
    ** we've caught up enough, restart the device.
    */
    if ((npMCI->dwFlags & MCIAVI_WAVEPAUSED) &&
        npMCI->wTaskState == TASKPLAYING &&
        npMCI->wABFull == npMCI->wABs) {

        DPF2(("restarting wave device\n"));
        waveOutRestart(npMCI->hWave);

	ICDrawStart(npMCI->hicDraw);
        npMCI->dwFlags &= ~(MCIAVI_WAVEPAUSED);
        npMCI->nAudioBehind = 0;
    }

    return TRUE;
}

/* Play the current record's audio */
BOOL NEAR PASCAL PlayRecordAudio(NPMCIGRAPHIC npMCI, BOOL FAR *pfHurryUp,
				    BOOL FAR *pfPlayedAudio)
{
    LPWAVEHDR	lpWaveHdr;
    FOURCC	ckid;
    DWORD	cksize;
    LPSTR	lpSave;
    LPSTR	lpData;
    BOOL	fRet = TRUE;
////BOOL        fSilence;
    LONG        len;
    DWORD	dwBytesTotal = 0L;
    DWORD       dwBytesThisChunk;

    Assert(npMCI->wPlaybackAlg == MCIAVI_ALG_INTERLEAVED);

    lpSave = npMCI->lp;

    lpWaveHdr = ((LPWAVEHDR)npMCI->lpAudio) + npMCI->wNextAB;

    *pfPlayedAudio = FALSE;

    /* Remember!
    **
    ** In the new file format, things shouldn't necessarily need to
    ** be ordered with the wave stuff always first.
    */

    len = (LONG)npMCI->dwThisRecordSize;

    while (len > 3 * sizeof(DWORD)) {

	/* Look at the next chunk */
	ckid = GET_DWORD();
        cksize = GET_DWORD();

        lpData = npMCI->lp;

        len -= ((cksize + 1) & ~1) + 8;
        SKIP_BYTES((cksize + 1) & ~1);

        if (StreamFromFOURCC(ckid) != (UINT)npMCI->nAudioStream)
            continue;

        dwBytesThisChunk = cksize;

	if (!dwBytesTotal) {
	    if (!WaitForFreeAudioBuffer(npMCI, pfHurryUp))
		/* We had to stop waiting--the stop flag was probably set. */
		goto exit;
        }

        if (dwBytesThisChunk > npMCI->dwABSize - dwBytesTotal) {
            DPF(("Audio Record is too big!\n"));
            dwBytesThisChunk = npMCI->dwABSize - dwBytesTotal;
        }

        hmemcpy((BYTE _huge *)lpWaveHdr->lpData + dwBytesTotal,
                lpData, dwBytesThisChunk);

	dwBytesTotal += dwBytesThisChunk;
    }

    if (dwBytesTotal) {
	*pfPlayedAudio = TRUE;	
	lpWaveHdr->dwBufferLength = dwBytesTotal;
	
	fRet = PlaySomeAudio(npMCI, lpWaveHdr);
    }

    /* Use the number of full audio buffers to decide if we're behind. */
    if (npMCI->wABFull >= npMCI->wABOptimal) {
         *pfHurryUp = FALSE;
    }

exit:
    npMCI->lp = lpSave;

    return fRet;
}

/* For "preload audio" or "random access audio" modes, do what needs
** to be done to keep our buffers full.
*/
BOOL NEAR PASCAL KeepPlayingAudio(NPMCIGRAPHIC npMCI)
{
    LPWAVEHDR	lpWaveHdr;
    DWORD	dwBytesTotal = 0L;
    LONG        lNewAudioPos;
////BOOL        fFirstTime = TRUE;

    Assert(npMCI->wPlaybackAlg == MCIAVI_ALG_HARDDISK ||
	   npMCI->wPlaybackAlg == MCIAVI_ALG_AUDIOONLY);

PlayMore:
    lpWaveHdr = ((LPWAVEHDR)npMCI->lpAudio) + npMCI->wNextAB;

    if (npMCI->dwFlags & MCIAVI_REVERSE) {
	lNewAudioPos = npMCI->dwAudioPos - npMCI->dwABSize;
	if (lNewAudioPos < 0)
	    lNewAudioPos = 0;
	dwBytesTotal = npMCI->dwAudioPos - lNewAudioPos;
    } else {
	lNewAudioPos = npMCI->dwAudioPos + npMCI->dwABSize;
	if (lNewAudioPos > (LONG) npMCI->dwAudioLength)
	    lNewAudioPos = npMCI->dwAudioLength;
	dwBytesTotal = lNewAudioPos - npMCI->dwAudioPos;
    }

    if (dwBytesTotal == 0) {

        if (npMCI->dwFlags & MCIAVI_WAVEPAUSED) {

            DOUT("no more audio to play, restarting wave device\n");

            waveOutRestart(npMCI->hWave);
            ICDrawStart(npMCI->hicDraw);
            npMCI->dwFlags &= ~(MCIAVI_WAVEPAUSED);
            npMCI->nAudioBehind = 0;
        }

        return TRUE;
    }

    /* If all of the audio buffers are full, we have nothing to do */
    if (npMCI->wABFull == npMCI->wABs)
	return TRUE;

#if 0
    //!!!! Should we be yielding at all in here?
    //!!! NO NO! not if updating!!!!
    if (!fFirstTime) {
	aviTaskYield();
    }
    fFirstTime = FALSE;
#endif

    if (npMCI->dwFlags & MCIAVI_REVERSE)
	npMCI->dwAudioPos = lNewAudioPos;

#ifdef USEAVIFILE
    if (npMCI->pf) {
	LONG	    lPos;
	LONG	    lLength;

        lPos = npMCI->dwAudioPos / SH(npMCI->nAudioStream).dwSampleSize;
	lLength = dwBytesTotal / SH(npMCI->nAudioStream).dwSampleSize;

        AVIStreamRead(SI(npMCI->nAudioStream)->ps,
		      lPos, lLength,
		      lpWaveHdr->lpData,
		      npMCI->dwABSize,
		      NULL, NULL);
    }
    else
#endif
    {
	if (!ReadSomeAudio(npMCI, lpWaveHdr->lpData,
			npMCI->dwAudioPos,
			&dwBytesTotal))
	    return FALSE;
		
	if (dwBytesTotal == 0)
		return TRUE;
    }

    if (!(npMCI->dwFlags & MCIAVI_REVERSE))
	npMCI->dwAudioPos += dwBytesTotal;

    lpWaveHdr->dwBufferLength = dwBytesTotal;

    if (npMCI->dwFlags & MCIAVI_REVERSE) {
	ReverseWaveBuffer(npMCI, lpWaveHdr);
    }

    if (!PlaySomeAudio(npMCI, lpWaveHdr))
	return FALSE;

//  return TRUE;

    goto PlayMore;
}


/* Play the current chunk's audio */
BOOL NEAR PASCAL HandleAudioChunk(NPMCIGRAPHIC npMCI)
{
    LPWAVEHDR	lpWaveHdr;
    FOURCC	ckid;
    DWORD	cksize;
    BYTE _huge *lpData;
    BOOL	fRet = TRUE;
    BOOL	fSilence;
    DWORD	dwBytesTotal = 0L;
    DWORD       dwBytesThisChunk;
    DWORD       dwBytesThisBuffer;
    BOOL        fHurryUp;

    Assert(npMCI->wPlaybackAlg == MCIAVI_ALG_CDROM);

    while ((DWORD) (npMCI->lp - npMCI->lpBuffer)
            < npMCI->dwThisRecordSize - 3 * sizeof(DWORD)) {

	/* Look at the next chunk */
	ckid = GET_DWORD();
	cksize = GET_DWORD();

	lpData = npMCI->lp;
	SKIP_BYTES(cksize + (cksize & 1));

	fSilence = (TWOCCFromFOURCC(ckid) == cktypeWAVEsilence);

	if (fSilence) {
	    if (cksize != sizeof(DWORD)) {
		DPF(("Wave silence chunk of bad length!\n"));
		fRet = FALSE;
		npMCI->dwTaskError = MCIERR_INVALID_FILE;
		goto exit;
	    }
	    dwBytesThisChunk = PEEK_DWORD();
	} else {
	    dwBytesThisChunk = cksize;
	}

	while (dwBytesThisChunk > 0) {
	    lpWaveHdr = ((LPWAVEHDR)npMCI->lpAudio) + npMCI->wNextAB;

	    if (!WaitForFreeAudioBuffer(npMCI, &fHurryUp))
		/* We had to stop waiting--the stop flag was probably set. */
		goto exit;
	
	    dwBytesThisBuffer = min(dwBytesThisChunk,
			    npMCI->dwABSize - npMCI->dwUsedThisAB);

	    if (!fSilence) {
		/* Move the data into the buffer */
		hmemcpy((BYTE _huge *) lpWaveHdr->lpData + npMCI->dwUsedThisAB,
			lpData,
			dwBytesThisBuffer);
		lpData += dwBytesThisBuffer;
	    } else {
		/* Fill the buffer with silence */
		/* This isn't right for 16-bit! */
#ifndef WIN32
    #pragma message("WAVE silence chunks don't work right now.")
#endif
	//      fmemfill((BYTE _huge *)lpWaveHdr->lpData + npMCI->dwUsedThisAB,
	//				dwBytesThisBuffer, 0x80);
	    }
	
	    dwBytesThisChunk -= dwBytesThisBuffer;
	    npMCI->dwUsedThisAB += dwBytesThisBuffer;

//	    if (npMCI->dwUsedThisAB == npMCI->dwABSize) {
		lpWaveHdr->dwBufferLength = npMCI->dwUsedThisAB;

		fRet = PlaySomeAudio(npMCI, lpWaveHdr);
//	    }
	}
    }

exit:
    return fRet;
}



/******************************************************************************
*****************************************************************************/

/***************************************************************************
 *
 * @doc INTERNAL MCIAVI
 *
 * @api BOOL | StealWaveDevice | steal the audio device from another
 * instance of MCIAVI.
 *
 * @parm NPMCIGRAPHIC | npMCI | near ptr to the instance data
 *
 ***************************************************************************/

BOOL FAR PASCAL StealWaveDevice(NPMCIGRAPHIC npMCI)
{
    extern NPMCIGRAPHIC npMCIList; // in graphic.c
    NPMCIGRAPHIC np;

    Assert(npMCI->hWave == NULL);

    DPF(("StealWaveDevice '%s' hTask=%04X\n", (LPSTR)npMCI->szFilename, GetCurrentTask()));

    //
    //  walk the list of open MCIAVI instances and find one that
    //  will give up the wave device
    //
    for (np=npMCIList; np; np = np->npMCINext) {

        if (np->hWave) {
            DPF(("**** Stealing the wave device from '%s'.\n", (LPSTR)np->szFilename));

            //!!!should we call DeviceMute() or just call cleanup audio?
            //
            //!!!can this cause evil reenter cases?
            //
            //!!!we are calling this from another task, will this work ok?
            //!!!even in WIN32? mabey we should use SendMessage()
#if 1
            SendMessage(np->hwndDefault, WM_AUDIO_OFF, 0, 0);
#else
            np->dwFlags |= MCIAVI_LOSTAUDIO;
            DeviceMute(np, TRUE);
            np->dwFlags |= MCIAVI_LOSTAUDIO;
#endif
            return TRUE;
        }
    }

    DPF(("StealWaveDevice can't find a device to steal\n"));

    return FALSE;
}

/***************************************************************************
 *
 * @doc INTERNAL MCIAVI
 *
 * @api BOOL | GiveWaveDevice | give away the audio device
 * instance of MCIAVI.
 *
 * @parm NPMCIGRAPHIC | npMCI | near ptr to the instance data
 *
 ***************************************************************************/

BOOL FAR PASCAL GiveWaveDevice(NPMCIGRAPHIC npMCI)
{
    extern NPMCIGRAPHIC npMCIList; // in graphic.c
    NPMCIGRAPHIC np;

    Assert(npMCI->hWave == NULL);

    DPF(("GiveWaveDevice '%s' hTask=%04X\n", (LPSTR)npMCI->szFilename, GetCurrentTask()));

    //
    //  walk the list of open MCIAVI instances and find one that
    //  will give up the wave device
    //
    for (np=npMCIList; np; np = np->npMCINext) {

        if (np->dwFlags & MCIAVI_LOSTAUDIO) {
            DPF(("**** Giving the wave device to '%s'.\n", (LPSTR)np->szFilename));

            PostMessage(np->hwndDefault, WM_AUDIO_ON, 0, 0);

            return TRUE;
        }
    }

    return FALSE;
}



#ifndef WIN32
#pragma alloc_text(FIX, mciaviWaveOutFunc)
#pragma optimize("", off)
#endif

void FAR PASCAL _LOADDS mciaviWaveOutFunc(HWAVEOUT hWaveOut, UINT wMsg,
		    DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
    NPMCIGRAPHIC npMCI;
    LPWAVEHDR    lpwh;

#ifndef WIN32
#ifndef WANT_286
        // If compiling -G3 we need to save the 386 registers
        _asm _emit 0x66  ; pushad
        _asm _emit 0x60
#endif
#endif

    npMCI = (NPMCIGRAPHIC)(UINT)dwInstance;
    lpwh = (LPWAVEHDR) dwParam1;

    switch(wMsg) {
	case MM_WOM_DONE:
	
            npMCI->wABFull--;
            npMCI->dwAudioPlayed += lpwh->dwBufferLength;
	    npMCI->dwTimingStart = timeGetTime();
	    break;
    }

#ifndef WIN32
#ifndef WANT_286
        // If compiling -G3 we need to restore the 386 registers
        _asm _emit 0x66  ; popad
        _asm _emit 0x61
#endif
#endif
}

#ifndef WIN32
#pragma optimize("", off)
#endif