/*
*) Functions to simplify recording & playback of wave file/data to a line/phone
*) Put the code in TAPI32L.LIB?  Then only apps that need it, get it

    +) tapiMakeNoise(
                      DWORD  Device Type: PHONE/LINE/WAVE, etc?
                      HANDLE Device Handle,
                      DWORD  NoiseType:   BUFFER/FILENAME/HFILE(readfile directly?)/MMIOHANDLE
                      HANDLE hArray - array of type NoiseTypes that are to be played serially
                      DWORD  Flags:
                              fSYNC
                              fSTOP_EXISTING_PLAYING_IF_ANY
                    );

        -) How to handle hardware assist?  IE: Hey, hardware, play prompt #7 - how would an 
                       app know how/when to request that?

        -) What about proprietary wave formats? How to know what proprietary formats the hardware supports?
                Just try it?

        -) What about conversions?  How to know what conversions the hardware can do

        -) How about a notification method?  Such that an app can know when the wave is done.

        -)


*/        
        
        
        
        
#define STRICT

#include "windows.h"
#include "windowsx.h"
#include "mmsystem.h"
#include "tapi.h"


#if DBG

VOID
DbgPrtWave(
    IN DWORD  dwDbgLevel,
    IN PTCHAR lpszFormat,
    IN ...
    )
/*++

Routine Description:

    Formats the incoming debug message & calls DbgPrint

Arguments:

    DbgLevel   - level of message verboseness

    DbgMessage - printf-style format string, followed by appropriate
                 list of arguments

Return Value:


--*/
{
//    if (dwDbgLevel <= gdwDebugLevel)
    {
        TCHAR    buf[1280];
        va_list ap;


        va_start(ap, lpszFormat);

        wsprintf(buf, TEXT("CallUpW (0x%08lx) - "), GetCurrentThreadId() );

        wvsprintf (&buf[23],
                   lpszFormat,
                   ap
                  );

        lstrcat (buf, TEXT("\n"));

        OutputDebugString (buf);

        va_end(ap);
    }
}

#define WDBGOUT(_x_) DbgPrtWave _x_

#else

#define WDBGOUT(_x_)

#endif


//****************************************************************************
//****************************************************************************
//****************************************************************************
unsigned long WINAPI WaveThread( LPVOID junk );

void CALLBACK WaveOutCallback(
    HWAVE  hWave,    // handle of waveform device
    UINT  uMsg,    // sent message
    DWORD  dwInstance,    // instance data
    DWORD  dwParam1,    // application-defined parameter
    DWORD  dwParam2    // application-defined parameter
   );



enum 
{
    DEVICE_WAVEID,
    DEVICE_WAVEHANDLE,
    DEVICE_HLINE,
    DEVICE_HPHONE,
    DEVICE_HCALL
};
enum
{
    SOURCE_WAVEFILE,
    SOURCE_MSDOSFILE,
    SOURCE_MEM
};
class WaveDevice;
class WaveOperation;

#define OPERATIONSTATUS_DONTPLAYTHIS 0x00000001



#define MAX_NUM_BUFFERS (8)
#define BUFFER_SIZE (8192)
typedef    struct {
               ULONG           uBufferLength;
               WaveOperation * poWaveOperation;
               PBYTE           pBuffer;
           } MISCINFO;



//****************************************************************************
//****************************************************************************
LONG gfInited = 0;
BOOLEAN     gfShutdown = FALSE;
WaveDevice *gpoWaveDeviceList = NULL;
HANDLE      ghFreeBufferEvent = 0;
HANDLE      ghWaveThread = NULL;
MISCINFO   *gDoneBuffersToBeProcessed[MAX_NUM_BUFFERS + 1];
CRITICAL_SECTION gCriticalSection;

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

class WaveOperation
{
    public:
    
        DWORD   dwSourceType;
        union
        {
            PTSTR  psz;
            
            PBYTE  pb;
            
            HANDLE h;

            LONG   l;
            
        } SourceThing;
        
        class WaveOperation * pNextWaveOperationInList;
        
        class WaveDevice    * poWaveDevice;
        
        HANDLE    hSyncEvent;
        
        DWORD   dwStatus;
        
        DWORD   cFileSize;
        DWORD   cDataRemaining;
        DWORD   cDataDonePlaying;
        BOOLEAN fInited;

        LONG WaveOperation::InitOperation(
                                            class WaveDevice * poWaveDevice,
                                            DWORD dwSoundTypeIn,
                                            LONG  lSourceThing
                                         );
                                         
        virtual LONG     InitSpecific( void ) = 0;
        virtual ULONG    GetData( PBYTE pBuffer, ULONG uBufferSize ) = 0;
        virtual void     FreeSpecific( void ) = 0;
        
        inline  WaveOperation * GetpNext();
        inline  void            SetpNext( WaveOperation * );
        
        inline  HANDLE GetSyncEvent();
        inline  void   SetSyncEvent( HANDLE );
        
        inline void ProcessDoneBuffer( MISCINFO * pMiscInfo );
        inline ULONG BytesNotDonePlaying( void );
};


//****************************************************************************
//****************************************************************************
//****************************************************************************
class WaveDevice
{
    ULONG     uDeviceId;
    DWORD     dwDeviceType;
    HANDLE    hDevice;
    
    HWAVEOUT  hWaveOut;
    CRITICAL_SECTION  CriticalSection;
    
    ULONG uUsageCount;
    
    class WaveDevice     * pNextWaveDeviceInList;

    class WaveOperation  *  CurrentWaveOperation;
    class WaveOperation  *  LastWaveOperation;
    
    

    ULONG     Head;
    ULONG     Tail;

    ULONG     NumFreeBuffers;
    ULONG     cBufferSize;

    PBYTE     FreeQueue[MAX_NUM_BUFFERS];
    WAVEHDR   WaveHeader[MAX_NUM_BUFFERS];
    MISCINFO  MiscInfo[MAX_NUM_BUFFERS];

    DWORD     dwStatusBits;

    
    public:

        inline ULONG GetNumFreeBuffers( void );
        DWORD GetStatus( void );
        void TerminateAllOperations( void );
        LONG KillWaveDevice( BOOLEAN fWaitForThreadTermination );
        LONG CloseWaveDevice();
        LONG InitWaveDevice( ULONG  uDeviceId );
        LONG OpenWaveDevice( WAVEFORMATEX * pWaveFormat );
    
        inline  WaveDevice * GetpNext();
        inline  void         SetpNext( WaveDevice * );
        
        LONG QueueOperation( class WaveOperation * );
        class WaveOperation * NextOperation();

        ULONG PlaySomeData( BOOL fPrimeOnly );

        inline void ReturnToFreeBufferQueue( PBYTE pBuffer );
        inline void IncrementBytesPlayed( ULONG cCount );
        inline ULONG GetWaveDeviceId( void );
        
        inline CRITICAL_SECTION * GetCriticalSection( void );

//        static void CALLBACK WaveOutCallback(
//                       HWAVE  hWave,    // handle of waveform device
//                       UINT   uMsg,     // sent message
//                       DWORD  dwInstance,    // instance data
//                       DWORD  dwParam1,    // application-defined parameter
//                       DWORD  dwParam2    // application-defined parameter
//                      );

        void  IncUsageCount( void );
        void  DecUsageCount( void );
        UINT  GetUsageCount( void );
};


//****************************************************************************
LONG WaveDevice::InitWaveDevice( ULONG  uDevId )
{
    LONG lResult = 0;
    ULONG n;


    WDBGOUT((4, "Entering InitWaveDevice"));


    // 
    // Alloc some buffers
    // 
    Head = 0;
    Tail = 0;
    NumFreeBuffers = 0;

    uUsageCount = 0;
    
    dwStatusBits = 0;

    cBufferSize = BUFFER_SIZE;
    
    uDeviceId = uDevId;

    for ( n = 0; n < MAX_NUM_BUFFERS; n++ )
    {
        FreeQueue[n] = (PBYTE)LocalAlloc(LPTR, cBufferSize);
        
        if ( NULL == FreeQueue[n] )
        {
            WDBGOUT((1, "Mem alloc failed.  Size= 0x%08lx", cBufferSize));
        
            while ( n )
            {
                LocalFree( FreeQueue[n-1] );
                n--;
            }
            
            return( LINEERR_NOMEM );
        }
        
        NumFreeBuffers++;

    }


    InitializeCriticalSection( &CriticalSection );
    
    
    CurrentWaveOperation = NULL;
    LastWaveOperation = NULL;

    
    return( lResult );
}

    
//****************************************************************************
inline ULONG WaveDevice::GetWaveDeviceId( void )
{
    return uDeviceId;
}


//****************************************************************************
inline ULONG WaveDevice::GetNumFreeBuffers( void )
{
    return NumFreeBuffers;
}


//****************************************************************************
LONG WaveDevice::OpenWaveDevice( WAVEFORMATEX * pWaveFormat )
{
    ULONG u;
    LONG lResult;
    
    WDBGOUT((4, "Entering OpenWaveDevice"));
    
    lResult = (LONG)waveOutOpen(
                 &hWaveOut,
                 uDeviceId,
                 pWaveFormat,
                 (DWORD)WaveOutCallback,
                 (DWORD)this,
                 CALLBACK_FUNCTION | WAVE_MAPPED
               );

//{
//    TCHAR buf[500];
//    wsprintf( buf, "woo on %lx ret=0x%lx", uDeviceId, lResult);
//    MessageBox(GetFocus(), buf, buf, MB_OK);
//}      


    
    if ( lResult )
    {
        WDBGOUT((1, "waveOutOpen returned 0x%08lx", lResult ));
        return( LINEERR_NOMEM);  //TODO LATER: Diff ret codes?
    }
    
    for ( u = 0; u < NumFreeBuffers; u++ )
    {
        WaveHeader[u].lpData = (LPSTR)FreeQueue[u];

        WaveHeader[u].dwBufferLength = cBufferSize;

        WaveHeader[u].dwFlags = 0;

        lResult = waveOutPrepareHeader(
                              hWaveOut,
                              &(WaveHeader[u]),
                              sizeof(WAVEHDR)
                            );
        if ( lResult )
        {
            WDBGOUT((1, TEXT("waveOutPrepareHeader returned 0x%08lx"), lResult ));
            return( LINEERR_NOMEM);  //TODO LATER: Diff ret codes?
        }
    
    }

    WDBGOUT((4, TEXT("Leaving OpenWaveDevice result = 0x0")));
    return( 0 );
}
    
    
////****************************************************************************
//LONG WaveDevice::RestartDevice( WAVEFORMATEX * pWaveFormat )
//{
//    ULONG n;
//    
//
//    WDBGOUT((4, "Entering RestartDevice"));
//
//
//    //  Reset wave device
//    WDBGOUT((4, TEXT("Resetting the wave device...")));
//    waveOutReset( hWaveOut );
//
//    //
//    // Wait until all of the outstanding buffers are back.
//    //
//    WDBGOUT((4, TEXT("Waiting for all buffers to be returned...")));
//    while ( NumFreeBuffers < MAX_NUM_BUFFERS )
//    {
//        Sleep(0);
//    }
//
//    WDBGOUT((4, TEXT("Closing the wave device...")));
//    waveOutClose( hWaveOut );
//
//    
//
//    return( 0 );
//}
//
    

//****************************************************************************
LONG WaveDevice::CloseWaveDevice()
{

    WDBGOUT((4, "Entering CloseWaveDevice"));


    //  Reset wave device
    WDBGOUT((4, TEXT("Resetting the wave device...")));
    waveOutReset( hWaveOut );

    //
    // Wait until all of the outstanding buffers are back.
    //
    WDBGOUT((4, TEXT("Waiting for all buffers to be returned...")));
    while ( NumFreeBuffers < MAX_NUM_BUFFERS )
    {
        Sleep(0);
    }

    WDBGOUT((4, TEXT("Closing the wave device...")));
    waveOutClose( hWaveOut );
    
    return( 0 );
}

    
//****************************************************************************
LONG WaveDevice::KillWaveDevice( BOOLEAN fWaitForThreadTermination )
{
    ULONG n;
    WaveDevice * poTempDevice;

    

    WDBGOUT((4, "Entering KillWaveDevice"));


    
    //  Reset wave device
    WDBGOUT((4, TEXT("Resetting the wave device...")));
    waveOutReset( hWaveOut );

    //
    // Wait until all of the outstanding buffers are back.
    //
    WDBGOUT((4, TEXT("Waiting for all buffers to be returned...")));
    while ( NumFreeBuffers < MAX_NUM_BUFFERS )
    {
        Sleep(0);
    }

    WDBGOUT((4, TEXT("Closing the wave device...")));
    waveOutClose( hWaveOut );
    
    //
    // Free the memory for all of the buffers
    //
    for ( n=0; n<MAX_NUM_BUFFERS; n++ )
    {
        LocalFree( FreeQueue[n] );

        FreeQueue[n] = NULL;
    }
    
    
    //
    // Remove the device from the global list
    //
    poTempDevice = gpoWaveDeviceList;
    
    if ( poTempDevice == this )
    {
        gpoWaveDeviceList = GetpNext();
    }
    else
    {
        while (    poTempDevice
                &&
                  ( (*poTempDevice).GetpNext() != this )
              )
        {
            poTempDevice =(*poTempDevice).GetpNext();
        }

        //
        // The next one in the list is it.  Remove the link.
        //
        if ( poTempDevice != NULL )
        {
           //
           // Adjust the list pointers
           //
           (*poTempDevice).SetpNext( GetpNext() );
        }
    }

    DeleteCriticalSection( &CriticalSection );

    delete this;
    


    //
    // Are all of the devices dead and buried?
    //
    if ( NULL == gpoWaveDeviceList )
    {
        gfShutdown = TRUE;
//TODO NOW: fix this        gfInited   = 0;
    
        //
        // Signal the other thread to come down
        //
        SetEvent( ghFreeBufferEvent );
        
        //
        // Wait 'till the thread is dead?
        //
        if ( fWaitForThreadTermination )
        {
            WaitForSingleObject( ghWaveThread, INFINITE );
        }
        
        CloseHandle( ghWaveThread );
        
        //
        // Zero this so we start fresh next time.
        //
//        ghWaveThread = NULL;
    }
    
    
    
    return( 0 );
}


    
//****************************************************************************
inline DWORD WaveDevice::GetStatus( void )
{
    return dwStatusBits;
}

    
//****************************************************************************
inline void WaveDevice::TerminateAllOperations( void )
{
    WaveOperation *poWaveOperation;
    
    WDBGOUT((3, TEXT("Entering TerminateAllOps")));
    
    EnterCriticalSection( &CriticalSection );
    
    poWaveOperation = CurrentWaveOperation;
    
    while ( poWaveOperation )
    {
        WDBGOUT((4, TEXT("Tainting oper: 0x%08lx"), poWaveOperation ));
        
        (*poWaveOperation).dwStatus |= OPERATIONSTATUS_DONTPLAYTHIS;
        
        poWaveOperation = (*poWaveOperation).GetpNext();
    }

    //
    //  Reset wave device to force all the buffers in
    //
    WDBGOUT((4, TEXT("Resetting the wave device...")));
    waveOutReset( hWaveOut );

    LeaveCriticalSection( &CriticalSection );
    
    WDBGOUT((3, TEXT("Leaving TerminateAllOps")));
}

    
//****************************************************************************
inline CRITICAL_SECTION * WaveDevice::GetCriticalSection( void )
{
   return &CriticalSection;
}


//****************************************************************************
inline WaveDevice * WaveDevice::GetpNext()
{
    return( pNextWaveDeviceInList );
    
}


//****************************************************************************
inline void WaveDevice::SetpNext(WaveDevice * pWaveDevice)
{
    pNextWaveDeviceInList = pWaveDevice;
}


//****************************************************************************
inline void  WaveDevice::IncUsageCount( void )
{
    uUsageCount++;
};
                
                      
//****************************************************************************
inline void  WaveDevice::DecUsageCount( void )
{
    uUsageCount--;
};
                
                      
//****************************************************************************
inline UINT  WaveDevice::GetUsageCount( void )
{
    return uUsageCount;
};
                
                      
//****************************************************************************
LONG WaveDevice::QueueOperation( class WaveOperation *poNewWaveOperation )
{

    WDBGOUT((3, TEXT("Entering QueueOperation")));
    
    EnterCriticalSection( &CriticalSection );
    

    (*poNewWaveOperation).SetpNext( NULL );

    //
    // Add operation to list
    //
    if ( LastWaveOperation )
    {
        (*LastWaveOperation).SetpNext( poNewWaveOperation );
    }

    LastWaveOperation = poNewWaveOperation;

    if ( NULL == CurrentWaveOperation )
    {
        CurrentWaveOperation = poNewWaveOperation;
    }
    
    
    LeaveCriticalSection( &CriticalSection );
    
    WDBGOUT((4, TEXT("Created new oper: 0x%08lx"), poNewWaveOperation));
    
    WDBGOUT((3, TEXT("Leaving QueueOperation")));
    return( 0 );
}
    

//****************************************************************************
class WaveOperation * WaveDevice::NextOperation()
{
    //
    // This function will get rid of the operation at the top of this wave
    // device's operation queue, and will update the queue to reflect the next
    // as now the first.
    //


    WDBGOUT((3, TEXT("Entering NextOperation")));


    EnterCriticalSection( &CriticalSection );
    
    if ( CurrentWaveOperation )
    {
        WaveOperation * poWaveOperation;
        WaveOperation * poTempOperation;
        
        poWaveOperation = (*CurrentWaveOperation).GetpNext();
        delete CurrentWaveOperation;
        
        while ( poWaveOperation )
        {
            //
            // If we can play this operation, break outta this loop
            //
            if ( !( (*poWaveOperation).dwStatus & OPERATIONSTATUS_DONTPLAYTHIS) )
            {
WDBGOUT((55, TEXT("How much break?")));
                break;
            }
            
            //
            // We're not supposed to play this operation
            //
            
            if ( (*poWaveOperation).hSyncEvent )
            {
                WDBGOUT((5, TEXT("Caller was waiting.  Signaling...")));
                SetEvent( (*poWaveOperation).hSyncEvent );
            }

            
            poTempOperation = (*poWaveOperation).GetpNext();
            
            delete poWaveOperation;
            
            poWaveOperation = poTempOperation;
            
        }
        
WDBGOUT((55, TEXT("Not too much")));
        CurrentWaveOperation = poWaveOperation;
    }
WDBGOUT((55, TEXT("was it Too much?")));
   
    //
    // The CurrentWaveOperation may have been "NULLED" out by the previous stuff
    //
    if ( NULL == CurrentWaveOperation )
    {
        LastWaveOperation = NULL;
    }

    LeaveCriticalSection( &CriticalSection );

    WDBGOUT((4, TEXT("Leaving NextOperation - returning 0x%08lx"), CurrentWaveOperation));

    return( CurrentWaveOperation );
}    
        
       

//****************************************************************************
inline void WaveDevice::ReturnToFreeBufferQueue( PBYTE pBuffer )
{
    FreeQueue[Tail] = pBuffer;
    
    //
    // If we're at the end of the list, wrap.
    //
    Tail = ( Tail + 1 )  % MAX_NUM_BUFFERS;

    NumFreeBuffers++;
}                             


//****************************************************************************
inline void WaveDevice::IncrementBytesPlayed( ULONG cCount )
{

//    //
//    // If there is an operation on the dying queue, this must be from it
//    //
//    if ( DyingWaveOperation )
//    {
//        //
//        // Is it dead yet?
//        //
//        if ( 0 == DyingWaveOperation->BytesNotDonePlaying() )
//        {
//           WaveOperation * poNextOperation;
//
//           EnterCriticalSection( &CriticalSection );
//
//           //
//           // Yes, it's dead.
//           //
//           poNextOperation = DyingWaveOperation->GetpNext();
//
//           //
//           // Was the caller waiting (ie: was it sync) ?
//           //
//           if ( (*DyingWaveOperation).GetSyncEvent() )
//           {
//               SetEvent( (*DyingWaveOperation).GetSyncEvent() );
//           }
//        
//           delete DyingWaveOperation;
//
//           DyingWaveOperation = poNextOperation;
//
//           LeaveCriticalSection( &CriticalSection );
//        }
//    }
//  
    //TODO LATER: Keep a total count of bytes played out this device?

}

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



//****************************************************************************
LONG WaveOperation::InitOperation(
                                          class WaveDevice * poWaveDeviceIn,
                                          DWORD dwSourceTypeIn,
                                          LONG  lSourceThing
                                        )
{
    WDBGOUT((4, TEXT("Entering InitOperation")));

    dwSourceType   = dwSourceTypeIn;
    SourceThing.l  = lSourceThing;
    poWaveDevice   = poWaveDeviceIn;
    
    pNextWaveOperationInList = NULL;
    
    (*poWaveDevice).IncUsageCount();

    dwStatus = 0;

    fInited = FALSE;
        
    return(0);
}                                 
                                 
    
//****************************************************************************
inline HANDLE WaveOperation::GetSyncEvent()
{
    return( hSyncEvent );
}


//****************************************************************************
inline void WaveOperation::SetSyncEvent( HANDLE hEvent )
{
    hSyncEvent = hEvent;
    return;
}


//****************************************************************************
inline WaveOperation * WaveOperation::GetpNext()
{
    return( pNextWaveOperationInList );
    
}


//****************************************************************************
inline void WaveOperation::SetpNext(WaveOperation * pWaveOperation)
{
    pNextWaveOperationInList = pWaveOperation;
}


//****************************************************************************
inline void WaveOperation::ProcessDoneBuffer( MISCINFO * pMiscInfo )
{
    ULONG nBytesQueued;
           
    WDBGOUT((3, TEXT("Entering ProcessDoneBuffer")));

    cDataDonePlaying += pMiscInfo->uBufferLength;

    WDBGOUT((11, TEXT("Now - size=0x%08lx  done=0x%08lx"),
                  cFileSize,
                  cDataDonePlaying));


    (*poWaveDevice).IncrementBytesPlayed( pMiscInfo->uBufferLength );
    (*poWaveDevice).ReturnToFreeBufferQueue( pMiscInfo->pBuffer );

    //
    // Has someone decided this wave should stop?
    //
    if ( dwStatus & OPERATIONSTATUS_DONTPLAYTHIS )
    {
        if ( (*poWaveDevice).GetNumFreeBuffers() != MAX_NUM_BUFFERS )
        {
            WDBGOUT((4, TEXT("Bailing from ProcessDoneBuffer - dontplay")));
            return;
        }
        
        cDataDonePlaying = cFileSize;
    }

    //
    // Is this thing already dead?
    //
    if ( cDataDonePlaying >= cFileSize )
    {

        WDBGOUT((4, TEXT("Done playing this:0x%08lx"), this ));


        //
        // Was the caller waiting (ie: was it sync) ?
        //
        if ( hSyncEvent )
        {
            WDBGOUT((5, TEXT("Caller was waiting.  Signaling...")));
            SetEvent( hSyncEvent );
        }

//TODO LATER: PERFORMANCE: If the next format is the same as this one, don't close the device

        (*poWaveDevice).CloseWaveDevice();


        (*poWaveDevice).DecUsageCount();


        EnterCriticalSection( &gCriticalSection );
        
        //
        // Was this the last oper?
        //
        if ( (*poWaveDevice).GetUsageCount() == 0 )
        {
            WDBGOUT((4, TEXT("Last oper out...")));

            (*poWaveDevice).KillWaveDevice(FALSE);
        }
        else
        {
           WaveOperation * pNewOperation;
           
           //
           // Move up the next operation
           //
           while ( TRUE )
           {
               pNewOperation = (*poWaveDevice).NextOperation();
               
               if ( NULL == pNewOperation )
               {
                  if ( (*poWaveDevice).GetUsageCount() == 0 )
                  {
                      WDBGOUT((4, TEXT("No more ops to run...")));

                      (*poWaveDevice).KillWaveDevice(FALSE);
                  }
                  
                  //
                  // All operations done.  Go away.
                  //
                  WDBGOUT((3, TEXT("All operations seem to be done...")));
                  break;
               }
               
               WDBGOUT((3, TEXT("Playing data from new op...")));
               nBytesQueued = (*poWaveDevice).PlaySomeData( FALSE );
               
               if ( nBytesQueued )
               {
                  //
                  // There were some bytes played.  Break the loop...
                  //
                  break;
               }
               
               //
               // Was the caller waiting (ie: was it sync) ?
               //
               if ( pNewOperation->hSyncEvent )
               {
                   WDBGOUT((3, TEXT("No data in new op and caller is waiting...")));
                   SetEvent( pNewOperation->hSyncEvent );
               }

               //
               // Update the counter.  This op is, for all intents and purposes, done.
               //
               (*poWaveDevice).DecUsageCount();
        
               WDBGOUT((3, TEXT("No data in new op.  Looking for next...")));
           }
           
        }

        FreeSpecific();

        delete this;
        
        LeaveCriticalSection( &gCriticalSection );
    }
    else
    {
        WDBGOUT((3, TEXT("Playing data from same op...")));
        (*poWaveDevice).PlaySomeData( FALSE );
    }
    
    WDBGOUT((3, TEXT("Leaving ProcessDoneBuffer")));
}


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

                       
//****************************************************************************
inline ULONG WaveOperation::BytesNotDonePlaying( void )
{
    return cFileSize - cDataDonePlaying;
}

                       
//****************************************************************************
//****************************************************************************
class BufferWave: public WaveOperation
{
    PBYTE   pData;  // Pointer to the data to play
    PBYTE   pCurrentPointer;
    
    public:
        LONG BufferWave::InitSpecific( void );
        ULONG GetData( PBYTE pBuffer, ULONG uBufferSize );
        void BufferWave::FreeSpecific( void );
};


//****************************************************************************
LONG BufferWave::InitSpecific( void )
{
    pData = SourceThing.pb;
    
    pCurrentPointer = pData;
    
    return(0);
}


//****************************************************************************
ULONG BufferWave::GetData( PBYTE pBuffer, ULONG uBufferSize )
{
    ULONG uBytesToPlay;

    uBytesToPlay = (cDataRemaining > uBufferSize) ?
                        uBufferSize    :
                        cDataRemaining;

    cDataRemaining -= uBytesToPlay;
    
    memcpy( pBuffer, pCurrentPointer, uBytesToPlay );
    
    pCurrentPointer += uBytesToPlay;
    
    return( uBytesToPlay );
}



//****************************************************************************
void BufferWave::FreeSpecific( void )
{
    return;
}


//****************************************************************************
//****************************************************************************
class WaveFile: public WaveOperation
{
    HMMIO hmmio;
    
    public:
        LONG WaveFile::InitSpecific( void );
        ULONG GetData( PBYTE pBuffer, ULONG uBufferSize );
        void WaveFile::FreeSpecific( void );
};


//****************************************************************************
LONG WaveFile::InitSpecific( void )
{
    MMCKINFO    mmckinfoParent;   /* parent chunk information structure */ 
    MMCKINFO    mmckinfoSubchunk; /* subchunk information structure    */ 
    DWORD       dwFmtSize;        /* size of "fmt" chunk               */ 
    WAVEFORMATEX Format;          /* pointer to memory for "fmt" chunk */ 
    LONG         lResult;


    WDBGOUT((4, TEXT("Entering WaveFile::InitSpecific")));


    hmmio = mmioOpen(
                      SourceThing.psz,
                      NULL,
                      MMIO_READ
                    );

    //
    // Did the open go ok?
    //
    if ( NULL == hmmio )
    {
       //
       // Nope.
       //
       WDBGOUT((1, TEXT("Error during mmioOpen of [%s] - err=0x%08lx"),
                   (SourceThing.psz == NULL) ? "" : SourceThing.psz,
                   GetLastError() ));

       return LINEERR_OPERATIONFAILED;
    }


    /* 
     * Locate a "RIFF" chunk with a "WAVE" form type 
     * to make sure the file is a WAVE file. 
     */ 
    mmckinfoParent.fccType = mmioFOURCC('W', 'A', 'V', 'E'); 

	WDBGOUT((11, TEXT("Descend WAVE")));
    if ( mmioDescend(
                      hmmio,
                      (LPMMCKINFO) &mmckinfoParent,
                      NULL, 
                      MMIO_FINDRIFF)
       )
    { 
       WDBGOUT((1, TEXT("This is not a WAVE file - [%s]"),
                   (SourceThing.psz == NULL) ? "" : SourceThing.psz));
       mmioClose( hmmio, 0); 
       return LINEERR_INVALPARAM; 
    } 


    /* 
     * Find the "fmt " chunk (form type "fmt "); it must be 
     * a subchunk of the "RIFF" parent chunk. 
     */ 
    mmckinfoSubchunk.ckid = mmioFOURCC('f', 'm', 't', ' '); 

	WDBGOUT((11, TEXT("Descend FMT")));
    if ( mmioDescend(
                      hmmio,
                      &mmckinfoSubchunk,
                      &mmckinfoParent,
                      MMIO_FINDCHUNK)
       )
    { 
       WDBGOUT((1, TEXT("WAVE file has no \"fmt\" chunk")));
       mmioClose(hmmio, 0); 
       return LINEERR_INVALPARAM; 
    } 


     /* 
      * Get the size of the "fmt " chunk--allocate and lock memory for it. 
      */ 
     dwFmtSize = mmckinfoSubchunk.cksize; 


	WDBGOUT((11, TEXT("read fmt")));
    /* Read the "fmt " chunk. */ 
     mmioRead(
                   hmmio,
                   (HPSTR)&Format,
                   sizeof(Format) );
 //   {
 //      WDBGOUT((1, TEXT("Failed to read format chunk.")));
 //      mmioClose(pMyWaveFile->hmmio, 0); 
 //      return 1; 
 //   }



	WDBGOUT((11, TEXT("Ascend fmt")));
    /* Ascend out of the "fmt " subchunk. */ 
    mmioAscend(hmmio, &mmckinfoSubchunk, 0); 


    
    /* 
    * Find the data subchunk. The current file position 
    * should be at the beginning of the data chunk. 
    */ 
    mmckinfoSubchunk.ckid = mmioFOURCC('d', 'a', 't', 'a'); 

	WDBGOUT((11, TEXT("Descend DATA")));
    if ( mmioDescend(
                      hmmio,
                      &mmckinfoSubchunk,
                      &mmckinfoParent, 
                      MMIO_FINDCHUNK)
       )
    {
       WDBGOUT((1, TEXT("WAVE file has no data chunk.")));
       mmioClose(hmmio, 0); 
       return LINEERR_INVALPARAM; 
    } 
   
    /* Get the size of the data subchunk. */ 
    cFileSize      = mmckinfoSubchunk.cksize; 
    cDataRemaining = mmckinfoSubchunk.cksize; 

    cDataDonePlaying = 0;

    
	WDBGOUT((11, TEXT("OpenWaveDev")));
    lResult = poWaveDevice->OpenWaveDevice( &Format );
    

//    if ( cDataRemaining == 0L)
//    {
//       WDBGOUT((1, TEXT("The data chunk contains no data.")));
//       mmioClose(hmmio, 0); 
//       return 0;  //TODO LATER: Right?  It's not an error...
// It'll just get 0 bytes on the first read...
//    } 

    return( lResult );
}



//****************************************************************************
ULONG WaveFile::GetData( PBYTE pBuffer, ULONG uBufferSize )
{
    ULONG uBytesToPlay;
    ULONG uBytesRead;


    WDBGOUT((11, TEXT("Entering WaveFile::GetData")));


    //
    // Have we done anything yet?
    //
    if ( !fInited )
    {
        if ( InitSpecific() )
        {
            return( 0 );
        }
        fInited = TRUE;
    }
    
   
    uBytesToPlay = (cDataRemaining > uBufferSize) ?
                        uBufferSize    :
                        cDataRemaining;


    if ( 0 == uBytesToPlay )
    {
        return 0;
    }


    /* Read the waveform data subchunk. */ 
    uBytesRead = mmioRead(
                           hmmio,
                           (LPSTR)pBuffer,
                           uBytesToPlay
                         );


    if ( uBytesRead != uBytesToPlay )
    {
        WDBGOUT((1, TEXT("Failed to properly read data chunk.")));
        mmioClose(hmmio, 0); 
        return 0;
    } 

    cDataRemaining -= uBytesToPlay;
    
    return( uBytesToPlay );
}


//****************************************************************************
void WaveFile::FreeSpecific( void )
{
    mmioClose(hmmio, 0); 
    return;
}


//****************************************************************************
//****************************************************************************
class DosFile: public WaveOperation
{
    HANDLE   hFile;
    
    public:
        LONG DosFile::InitSpecific( void );
        ULONG GetData( PBYTE pBuffer, ULONG uBufferSize );
        void DosFile::FreeSpecific( void );
};

//****************************************************************************
LONG DosFile::InitSpecific( void )
{
    BOOL fResult;
//    WIN32_FILE_ATTRIBUTE_DATA FileInfo;
    BY_HANDLE_FILE_INFORMATION FileInfo;
    
    hFile = CreateFile(
                        SourceThing.psz,
                        GENERIC_READ,
                        FILE_SHARE_READ,
                        NULL,
                        OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL
                      );

    if ( 0 == hFile )
    {
        WDBGOUT((1, TEXT("Error doing OpenFile( lpszName ) GetLastError=0x%)8lx"),
                       SourceThing.psz, GetLastError() ));
                       
        return( LINEERR_OPERATIONFAILED );
    }

//    fResult = GetFileAttributesEx( SourceThing.psz,
//                                   GetFileExInfoStandard,
//                                   (PVOID) &FileInfo
//                                 );
  
    fResult = GetFileInformationByHandle( hFile, &FileInfo );
    
    if ( fResult )
    {
        //TODO LATER: Handle > 4 gig files

        //
        // Uh, we don't really handle gigabyte files...
        //
        if ( FileInfo.nFileSizeHigh )
        {
            cFileSize      = (DWORD)-1;
            cDataRemaining = (DWORD)-1;
        }
        else
        {
            cFileSize      = FileInfo.nFileSizeLow;
            cDataRemaining = FileInfo.nFileSizeLow;
        }
    }
    else
    {
        cFileSize      = 0;
        cDataRemaining = 0;
    }

    cDataDonePlaying = 0;

    return(0);
}


//****************************************************************************
ULONG DosFile::GetData( PBYTE pBuffer, ULONG uBufferSize )
{
    BOOL fResult;
    UINT uBytesRead = 0;

    fResult = ReadFile( hFile,
                        pBuffer,
                        uBufferSize,
                        (LPDWORD)&uBytesRead,
                        NULL
                      );

    if ( fResult )
    {
        if ( 0 == uBytesRead )
        {
            //
            // We're at the end of the file
            //
            cDataRemaining = 0;
        }
        else
        {
            cDataRemaining -= uBytesRead;
        }
    }

    return( uBytesRead );
}


//****************************************************************************
void DosFile::FreeSpecific( void )
{
    CloseHandle( hFile );
    return;
}




//****************************************************************************
//****************************************************************************
//****************************************************************************
ULONG WaveDevice::PlaySomeData( BOOL fPrimeOnly )
{
    ULONG uBufferedBytes = 0;
    ULONG uTotalQueuedSize = 0;
    PBYTE pBuffer = NULL;
    LONG lResult;
    CRITICAL_SECTION *pCriticalSection;

        
    WDBGOUT((3, TEXT("Entering PlaySomeData")));

    pCriticalSection = &CriticalSection;
    EnterCriticalSection( pCriticalSection );

    if ( NULL != CurrentWaveOperation )
    {
    
        //
        // Is it OK to play this thing?
        //
        if ( !((*CurrentWaveOperation).dwStatus & OPERATIONSTATUS_DONTPLAYTHIS) )
        {
            while ( NumFreeBuffers )
            {
                uBufferedBytes = (*CurrentWaveOperation).GetData( FreeQueue[Head], cBufferSize );
                              
                WDBGOUT((11, "GetData on 0x%08lx gave %ld bytes for buffer #%d",
                          CurrentWaveOperation,
                          uBufferedBytes,
                          Head));

                if ( 0 == uBufferedBytes )
                {
					WDBGOUT((10, TEXT("breakin 'cause 0 bytes...")));
                    break;
                }
                
				WDBGOUT((10, TEXT("past if...")));
                uTotalQueuedSize += uBufferedBytes;
                
                MiscInfo[Head].uBufferLength    = uBufferedBytes;
                MiscInfo[Head].poWaveOperation  = CurrentWaveOperation;
                MiscInfo[Head].pBuffer          = FreeQueue[Head];
                WaveHeader[Head].dwUser         = (DWORD) &MiscInfo[Head];
                WaveHeader[Head].dwBufferLength = uBufferedBytes;
   
                lResult = waveOutWrite( hWaveOut,
                                        &WaveHeader[Head],
                                        sizeof(WAVEHDR)
                                      );
                if ( lResult )
                {
                    //
                    // Something's wrong.  Quit this operation.
                    //
                    uTotalQueuedSize = 0;
                    uBufferedBytes = 0;
                    WDBGOUT((1, TEXT("waveOutWrite returned 0x%08lx"), lResult));
                    break;
                }

                Head = (Head + 1) % MAX_NUM_BUFFERS;

                NumFreeBuffers--;

                //
                // Are we just "priming" the pump?
                //
//                if ( fPrimeOnly )
//                {
//                    WDBGOUT((4, TEXT("Leaving PlaySomeData - primed (size=%08ld)"), uTotalQueuedSize ));
//                    LeaveCriticalSection( pCriticalSection );
//                    return uTotalQueuedSize;
//                }

            }
        }
#if DBG
        else
        {
			WDBGOUT((10, TEXT("I've been asked not to play this operation (0x%08lx)"), CurrentWaveOperation));
        }
        
#endif        
        
		WDBGOUT((10, TEXT("past while numfreebuffers...")));
        

        //
        // We got here because we're out of buffers, or the operation is done
        //
        if ( 0 != uBufferedBytes )
        {
            //
            // Must be here because we ran out of buffers...
            //
            LeaveCriticalSection( pCriticalSection );
            return( uTotalQueuedSize );
        }

        
        //
        // We get here when the current operation is all done
        // (or, at least, all of its remaining data is queued in the
        // wave driver)
        //
    }    
    
    //
    // If we got here, it's because we're out of things to do
    //

    LeaveCriticalSection( pCriticalSection );


    WDBGOUT((4, TEXT("Leaving PlaySomeData - no currop (size=%08ld)"), uTotalQueuedSize ));

    return uTotalQueuedSize;
//    return( 0 );
}


//****************************************************************************
//****************************************************************************
//****************************************************************************
void CALLBACK WaveOutCallback(
    HWAVE  hWave,    // handle of waveform device
    UINT  uMsg,    // sent message
    DWORD  dwInstance,    // instance data
    DWORD  dwParam1,    // application-defined parameter
    DWORD  dwParam2    // application-defined parameter
   )
{
    UINT n;

    switch ( uMsg )
    {
        case WOM_DONE:
        {
            class WaveDevice * poWaveDevice =
                        (class WaveDevice *)dwInstance;

            MISCINFO * pMiscInfo = (MISCINFO *)((LPWAVEHDR)dwParam1)->dwUser;

            
            WDBGOUT((11, TEXT("Got DoneWithBuff msg for 0x%08lx in 0x%08lx"),
                           *(LPDWORD)dwParam1,
                           dwParam1));


//            EnterCriticalSection( &gBufferCriticalSection );

            n = 0;

//TODO NOW: If this buffer won't fit, it'll get lost.  This can easily happen
//            when there are >1 wave devices playing.

            while (
                     ( n < MAX_NUM_BUFFERS )
                   &&
                     ( gDoneBuffersToBeProcessed[n] != NULL )
                  )
            {
               n++;
            }

            gDoneBuffersToBeProcessed[n] = pMiscInfo;

//            LeaveCriticalSection( &gBufferCriticalSection );

            SetEvent( ghFreeBufferEvent );
        }    
        break;

            
        case WOM_OPEN:
            WDBGOUT((11, TEXT("Got Waveout Open")));
            break;

            
        case WOM_CLOSE:
            WDBGOUT((11, TEXT("Got Waveout Close")));
            break;
    }
}    





//****************************************************************************
//****************************************************************************
//****************************************************************************
//LONG tapiMakeNoise(
//                      DWORD  Device Type: PHONE/LINE/WAVE, etc?
//                      HANDLE Device Handle,
//                      DWORD  NoiseType:   BUFFER/FILENAME/HFILE(readfile directly?)/MMIOHANDLE
//                      HANDLE hArray - array of type NoiseTypes that are to be played serially
//                      DWORD  Flags:
//                              fSYNC
//                              fSTOP_EXISTING_PLAYING_IF_ANY
//                    );


// SOME FLAGS FOR THIS FUNC
#define PLAY_SYNC 0x00000001
#define KILL_ALL_NOISE 0x80000000

#ifdef __cplusplus
extern "C" {            /* Assume C declarations for C++ */
#endif  /* __cplusplus */

LONG WINAPI tapiPlaySound(
                    DWORD  dwDeviceType,
                    HANDLE hDevice,
                    DWORD  dwSoundType,
                    HANDLE hArray,
                    DWORD  dwFlags
                  )
{
    HANDLE hSyncEvent = NULL;
    class WaveDevice * poWaveDevice;
    class WaveOperation * poWaveOperation;
    LONG fAreWeInited;
    LONG lResult = 0;
    ULONG uNormalizedWaveId = 0;
//    BOOLEAN fNeedToPrimeDevice = FALSE;

    WDBGOUT((3, "Entering tapiPlaySound"));
    WDBGOUT((5, "    dwDeviceType: %ld", dwDeviceType));
    WDBGOUT((5, "    hDevice:      0x%08lx", hDevice));
    WDBGOUT((5, "    dwSoundType:  %ld", dwSoundType));
    WDBGOUT((5, "    hArray:       0x%08lx", hArray));
    WDBGOUT((5, "    dwFlags:      0x%08lx", dwFlags));

    
    fAreWeInited = InterlockedExchange(
                                        &gfInited,
                                        TRUE
                                      );  
    
    if ( 0 == fAreWeInited )
    {
        InitializeCriticalSection( &gCriticalSection );
    }
    

    if ( 0 == ghFreeBufferEvent )
    {
        ghFreeBufferEvent = CreateEvent(
                                        NULL,
                                        FALSE,
                                        FALSE,
                                        NULL
                                      );
            

        if ( NULL == ghFreeBufferEvent )
        {
            WDBGOUT((1, "CreateEvent2 failed: GetLastError = 0x%08lx", GetLastError()));
            return  LINEERR_NOMEM;
        }
    }


    //
    // Normalize to a wave device (and validate dwDeviceType at the same time)
    //
    switch ( dwDeviceType )
    {
        case DEVICE_WAVEID:
        {
            uNormalizedWaveId = (ULONG) hDevice;
        }
        break;


        case DEVICE_WAVEHANDLE:
        {
        }
        break;


        case DEVICE_HLINE:
        case DEVICE_HCALL:
        {
        
           DWORD  VarString[ 8 ] = 
           {
             sizeof(VarString),
             0,
             0,
             STRINGFORMAT_BINARY,
             0,
             0,
             0
           };


           if ( 0 == (lResult = lineGetID(
              (HLINE)hDevice,
              0,
              (HCALL)hDevice,
              (DEVICE_HCALL == dwDeviceType) ?
                    LINECALLSELECT_CALL :
                    LINECALLSELECT_LINE,
              (LPVARSTRING)&VarString,
              TEXT("wave/out")
            ) ) )
           {
              uNormalizedWaveId = (DWORD) ((LPBYTE)VarString)[ ((LPVARSTRING)&VarString)->dwStringOffset ];
           }
           else 
           {
              WDBGOUT((1, "lineGetID failed - 0x%08lx", lResult));
              
              return  LINEERR_INVALPARAM;
           }

        }
        break;


        case DEVICE_HPHONE:
        {
        }
        break;


        default:
        WDBGOUT((1, "Invalid dwDeviceType (0x%08lx) passed in.", dwDeviceType));
        return LINEERR_BADDEVICEID;
    }


    EnterCriticalSection( &gCriticalSection );

    poWaveDevice = gpoWaveDeviceList;

    while ( poWaveDevice )
    {
        if ( (*poWaveDevice).GetWaveDeviceId() == uNormalizedWaveId )
        {
            //
            // We found it!
            //
            break;
        }

        //
        // ...and I still haven't found what I'm lookin' for.
        //
        poWaveDevice = (*poWaveDevice).GetpNext();
    }


    //
    // So, was it not in our list already?
    //
    if ( NULL == poWaveDevice )
    {
       //
       // No, add a new device object to the list
       //

       poWaveDevice = new WaveDevice;

       lResult = (*poWaveDevice).InitWaveDevice( uNormalizedWaveId );

       if ( lResult )
       {
            WDBGOUT((1, TEXT("InitWaveDevice returned 0x%08lx"), lResult));
//TODO: Diff error codes for diff causes...
            LeaveCriticalSection( &gCriticalSection );
            return LINEERR_RESOURCEUNAVAIL;
       }

       (*poWaveDevice).SetpNext( gpoWaveDeviceList );
       
       gpoWaveDeviceList = poWaveDevice;
    }
    


    //
    // If the caller wants to cancel all currently queued and playing
    // sound on this device, do it now
    //
    if ( KILL_ALL_NOISE & dwFlags )
    {
        (*poWaveDevice).TerminateAllOperations();
        WDBGOUT((4, "Caller was asking to terminate the wave device.  Done."));
        
//        LeaveCriticalSection( &gCriticalSection );
//            
//	  	return( 0 );
    }



// t-mperh 6/30 was all commented before - not sure why
//
    //
    // If the user passed in a NULL for hArray, we'll (for now?) assume
    // he wants a no-op (or 'twas a TERMINATE request).
    //
    if ( NULL == hArray )
    {
        WDBGOUT((3, "Leaving tapiPlaySound - NULL thing"));
        LeaveCriticalSection( &gCriticalSection );
        return  0;
    }

//**************************************************************
//NOTE: The above code fixed a problem of passing in NULL names.
// This caused an OPEN to fail and this stuff would get stuck.
// There must still be a bug that will show up when someone calls with
// a bad filename or a file that plays 0 bytes.
//**************************************************************



    switch ( dwSoundType )
    {
        case SOURCE_WAVEFILE:
        {
            poWaveOperation = new WaveFile;
        }
        break;
        
        
        case SOURCE_MSDOSFILE:
        {
            poWaveOperation = new DosFile;
        }
        break;
    
        
        case SOURCE_MEM:
        {
            poWaveOperation = new BufferWave;
        }
        break;
    
        
        default:
        {
            WDBGOUT((1, "Invalid dwSourceType - 0x%08lx", dwSoundType));
            LeaveCriticalSection( &gCriticalSection );
            return LINEERR_INVALPARAM;
        }
    }
   
    
    if ( NULL == ghWaveThread )
    {
        DWORD dwThreadID;

        ghWaveThread = CreateThread(
                                    NULL,
                                    0,
                                    WaveThread,
                                    NULL,
                                    0,
                                    &dwThreadID
                                  );
        if ( 0 != lResult )
        {
            WDBGOUT((1, "Create thread failed! GetLastError()=0x%lx", GetLastError() ));
            LeaveCriticalSection( &gCriticalSection );
            return  LINEERR_NOMEM;
        }

    }


    //
    // Init global operation
    //
    (*poWaveOperation).InitOperation(
                                      poWaveDevice,
                                      dwSoundType,
                                      (LONG)hArray
                                    );  
    
    (*poWaveDevice).QueueOperation( poWaveOperation );
    
    
    if ( dwFlags & PLAY_SYNC )
    {
        hSyncEvent = CreateEvent(
                                  NULL,
                                  TRUE,
                                  FALSE,
                                  NULL
                                );
        
        if ( NULL == hSyncEvent )
        {
            WDBGOUT((1, TEXT("CreateEvent failed: GetLastError = 0x%08lx"), GetLastError()));
            
            delete poWaveOperation;
            LeaveCriticalSection( &gCriticalSection );
            return( LINEERR_NOMEM );
        }
        
        (*poWaveOperation).SetSyncEvent( hSyncEvent );
    }
    
    
    //
    // If all of the buffers are idle, we'll have to prime...
    //
    if ( MAX_NUM_BUFFERS == (*poWaveDevice).GetNumFreeBuffers() )
    {
        WDBGOUT((4, TEXT("Priming")));
        
        if ( 0 == (*poWaveDevice).PlaySomeData( TRUE ) )
        {
            WaveOperation * poWaveOperation;
            
            WDBGOUT((4, TEXT("No data played for this wave!")));
            
            
            poWaveOperation = (*poWaveDevice).NextOperation();
            
            while (poWaveOperation)
            {
               if ( (*poWaveDevice).PlaySomeData(TRUE) )
               {
                  break;
               }
               
               poWaveOperation = (*poWaveDevice).NextOperation();
            }
            
            //
            // If fNeedToPrimeDevice was true, this must be the first (and only,
            // since we're still in the critical section) operation
            // And, since there was no data (or we failed for any reason),
            // we should shut down the wave device here.
            
            // Now leave the critical section so we can wait for the WAVETHREAD
            // to finish and so that thread can do work to clean up
            LeaveCriticalSection( &gCriticalSection );
            (*poWaveDevice).KillWaveDevice(TRUE);
            EnterCriticalSection( &gCriticalSection );
            
            //
            // Fake out the event
            //
            if ( hSyncEvent )
            {
                WDBGOUT((5, TEXT("Faking hSyncEvent...")));
                SetEvent( hSyncEvent );
            }
        }
    }
#if DBG
    else
    {
        WDBGOUT((4, TEXT("Not priming because %ln buffers are out"),
                    (*poWaveDevice).GetNumFreeBuffers() ));
    }    
#endif    

    
    LeaveCriticalSection( &gCriticalSection );
    
    if ( hSyncEvent )
    {
        WDBGOUT((5, TEXT("Waiting for the wave to finish (event=0x%08lx)"),
                      hSyncEvent));
                      
        WaitForSingleObject( hSyncEvent, INFINITE );
        
        //
        // When it gets back, the thing is done playing
        //
        CloseHandle( hSyncEvent );
    }
    
    
    WDBGOUT((4, TEXT("Leaving tapiPlaySound - retcode = 0x0")));
    return( 0 );
}    

    


#ifdef __cplusplus
}            /* End Assume C declarations for C++ */
#endif  /* __cplusplus */


    
//****************************************************************************
//****************************************************************************
//****************************************************************************
unsigned long WINAPI WaveThread( LPVOID junk )
{
    UINT n;

    WDBGOUT((3, "WaveThread starting..."));

    do
    {
        WDBGOUT((3, "WaveThread waiting..."));
        WaitForSingleObject( ghFreeBufferEvent, INFINITE );


        //
        // First, deal with any finished buffers
        //
        n = 0;
//        while ( gDoneBuffersToBeProcessed[n] != NULL )

        EnterCriticalSection( &gCriticalSection );
                
        while ( n < MAX_NUM_BUFFERS )
        {
            if ( gDoneBuffersToBeProcessed[n] != NULL )
            {
                MISCINFO *pMiscInfo = gDoneBuffersToBeProcessed[n];

                pMiscInfo->poWaveOperation->ProcessDoneBuffer( pMiscInfo );
                gDoneBuffersToBeProcessed[n] = NULL;
            }

            n++;
        }

        LeaveCriticalSection( &gCriticalSection );

//        poWaveDevice = gpoWaveDeviceList;
//
//        while ( poWaveDevice )
//        {
//            UINT nBytesQueued = 0;
//
//            while ( nBytesQueued == 0 )
//            {
//                //
//                // Now play some new data
//                //
//                nBytesQueued = (*poWaveDevice).PlaySomeData( FALSE );
//
//                //
//                // And is the entire wave done?
//                //
//                if ( 0 == nBytesQueued )
//                {
//                    WaveOperation * poNewCurrent;
//                
//                    poNewCurrent = (*poWaveDevice).NextOperation();
//                        
//                    if ( NULL == poNewCurrent )
//                    {
//    if ( NULL == gpoWaveDeviceList )
//    {
//        gfShutdown = TRUE;
//        gfInited   = 0;
//    }
//                        break;
//                    }
//                }
//            }
//
//
//            poWaveDevice = (*poWaveDevice).GetpNext();
//        }

    } while ( !gfShutdown );

    WDBGOUT((5, TEXT("Oh, I guess we're done now...")));
    
    CloseHandle( ghFreeBufferEvent );
    ghFreeBufferEvent = 0;

    gfShutdown = FALSE;
    
    
    WDBGOUT((3, TEXT("WaveThread ending...")));

    ghWaveThread = NULL;
       
    return 0;
}