// WaveOutDev.cpp: implementation of the CWaveOutDev class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "WaveOutDev.h"

#include "audiotap.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif


//////////////////////////////////////////////////////////////////////
// Worker thread
//////////////////////////////////////////////////////////////////////

static UINT WorkerThreadFunction(LPVOID pParam)
{
   CWaveOutDev * pDev = (CWaveOutDev *)pParam;
   ASSERT(pDev != NULL);

   enum
   {
      EVENT_WORK = 0,
      EVENT_EXIT,
      EVENT_COUNT,
   };

   CSyncObject * apEvents[EVENT_COUNT];
   apEvents[EVENT_WORK] = &(pDev->m_evThereIsWorkToBeDone);
   apEvents[EVENT_EXIT] = &(pDev->m_evExitWorkerThread);

   CMultiLock Wait(apEvents, EVENT_COUNT);
   BOOL fKeepGoing = TRUE;

   while (fKeepGoing)
   {
      switch (Wait.Lock(10000, FALSE))
      {
         case WAIT_OBJECT_0 + EVENT_WORK:
         {
            TRACE("WorkerThreadFunction(): Working\n");

            CWaveHdrPtrArray apCleanup;

            // !!! ENTERING CRITICAL SECTION !!!
            pDev->m_csPendingWaveHdrArray.Lock();

            try
            {
               for (int i = 0; i < pDev->m_apPendingWaveHdrs.GetSize(); i++)
               {
                  WAVEHDR * pWhdr = pDev->m_apPendingWaveHdrs[i];
                  ASSERT(pWhdr != NULL);

                  if ((pWhdr != NULL) && (pWhdr->dwFlags & WHDR_DONE))
                  {
                     // this one's done, move it to the cleanup array
                     pDev->m_apPendingWaveHdrs.RemoveAt(i);
                     apCleanup.Add(pWhdr);
                  }
               }
            }
            catch (...)
            {
            }

            pDev->m_csPendingWaveHdrArray.Unlock();
            // !!! EXITING CRITICAL SECTION

            // Cleanup
            for (int i = 0; i < apCleanup.GetSize(); i++)
            {
               WAVEHDR * pWhdr = apCleanup[i];
               ASSERT(pWhdr != NULL);

               if (pWhdr != NULL)
               {
                  waveOutUnprepareHeader(pDev->m_hOut, pWhdr, sizeof(*pWhdr));
                  delete[] pWhdr->lpData;
                  delete pWhdr;
               }
            }
            apCleanup.RemoveAll();

            break;
         }
         
         case WAIT_OBJECT_0 + EVENT_EXIT:
         {
            TRACE("WorkerThreadFunction(): Exiting\n");
            fKeepGoing = FALSE;
            break;
         }

         case WAIT_TIMEOUT:
         {
            TRACE("WorkerThreadFunction(): Timed out\n");
            break;
         }

         default:
         {
            TRACE("WorkerThreadFunction(): What the ... !\n");
            ASSERT(FALSE);
            break;
         }
      }
   }

   return 0;
}

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CWaveOutDev::CWaveOutDev()
{
   m_nId = -1;
   m_hOut = NULL;
   ZeroMemory(&m_woCaps, sizeof(m_woCaps));

   // hard coded wave format
   m_wfxWaveFormat.wFormatTag       = WAVE_FORMAT_PCM;               /* format type */
   m_wfxWaveFormat.nChannels        = 1;                             /* number of channels (i.e. mono, stereo...) */
   m_wfxWaveFormat.nSamplesPerSec   = AUDIOTAP_RATEOUT;              /* sample rate */
   m_wfxWaveFormat.nAvgBytesPerSec  = 16000;                         /* for buffer estimation */
   m_wfxWaveFormat.nBlockAlign      = 2;                             /* block size of data */
   m_wfxWaveFormat.wBitsPerSample   = 16;                            /* Number of bits per sample of mono data */
   m_wfxWaveFormat.cbSize           = 0;                             /* The count in bytes of the size of
                                                                        extra information (after cbSize) */

   m_pWorkerThread = AfxBeginThread(WorkerThreadFunction, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
   ASSERT(m_pWorkerThread != NULL);

   m_pWorkerThread->m_bAutoDelete = FALSE;
   m_pWorkerThread->ResumeThread();
}

CWaveOutDev::~CWaveOutDev()
{
   // close the device if open
   Close();

   // shut down the worker thread
   m_evExitWorkerThread.SetEvent();
   ::WaitForSingleObject(m_pWorkerThread->m_hThread, 10000);
   delete m_pWorkerThread;
}

BOOL CWaveOutDev::FindCompatibleDev()
{
   BOOL fFoundCompatibleDev = FALSE;

   ASSERT(m_nId == -1);

   UINT cWaveOutDevs = waveOutGetNumDevs();
   TRACE("There is %d wave out device(s)\n", cWaveOutDevs);

   for (UINT i = 0; i < cWaveOutDevs; i++)
   {
      TRACE("Checking to see if wave out device #%d is compatible\n", i);

      WAVEOUTCAPS woCaps = {0};
      MMRESULT mmr = waveOutGetDevCaps(i, &woCaps, sizeof(woCaps));
      TRACE("waveOutGetDevCaps() returned %d\n", mmr);

      if (mmr == MMSYSERR_NOERROR)
      {
         TRACE("Device caps:\n"
               "  wMid           = %d\n"
               "  wPid           = %d\n"
               "  vDriverVersion = 0x%08x\n"
               "  szPname        = %s\n"
               "  dwFormats      = 0x%08x\n"
               "  wChannels      = %d\n"
               "  dwSupport      = 0x%08x\n",
               woCaps.wMid, woCaps.wPid, woCaps.vDriverVersion, woCaps.szPname,
               woCaps.dwFormats, woCaps.wChannels, woCaps.dwSupport);

         if (woCaps.dwFormats & m_wfxWaveFormat.wFormatTag)
         {
            TRACE("This device is compatible\n");
            fFoundCompatibleDev = TRUE;

            m_nId = i;
            m_woCaps = woCaps;

            break;
         }
      }
   }

   return fFoundCompatibleDev;
}

BOOL CWaveOutDev::Open(int * const peStatus /* = NULL */, BOOL const fStereo /*=FALSE*/)
{
   BOOL fSuccess = FALSE;

   ASSERT(m_nId != -1);
   ASSERT(m_hOut == NULL);

   if ((m_nId != -1) && (m_hOut == NULL))
   {
      MMRESULT mmr = waveOutOpen(&m_hOut, m_nId, &m_wfxWaveFormat, (DWORD)(HANDLE)m_evThereIsWorkToBeDone, 0, CALLBACK_EVENT);
      TRACE("waveOutOpen() returned %d\n", mmr);

      if (peStatus != NULL)
      {
         *peStatus = (mmr == MMSYSERR_NOERROR)    ? WOD_OPEN_STATUS_SUCCESS :
                     ((mmr == MMSYSERR_ALLOCATED) ? WOD_OPEN_STATUS_IN_USE  :
                                                    WOD_OPEN_STATUS_ERROR);
      }

      fSuccess = (mmr == MMSYSERR_NOERROR);
   }

   return fSuccess;
}

void CWaveOutDev::Close()
{
   if (m_hOut != NULL)
   {
      waveOutReset(m_hOut);

      // !!! ENTERING CRITICAL SECTION !!!
      m_csPendingWaveHdrArray.Lock();

      try
      {
         for (int i = 0; i < m_apPendingWaveHdrs.GetSize(); i++)
         {
            WAVEHDR * pWhdr = m_apPendingWaveHdrs[i];
            ASSERT(pWhdr != NULL);

            if (pWhdr != NULL)
            {
               waveOutUnprepareHeader(m_hOut, pWhdr, sizeof(*pWhdr));
               delete[] pWhdr->lpData;
               delete pWhdr;
            }
         }
         m_apPendingWaveHdrs.RemoveAll();
      }
      catch (...)
      {
      }

      m_csPendingWaveHdrArray.Unlock();
      // !!! EXITING CRITICAL SECTION

      waveOutClose(m_hOut);
      m_hOut = NULL;
   }
}

BOOL CWaveOutDev::Write(BYTE const * const pbData, DWORD const cbData)
{
   BOOL fSuccess = FALSE;

   ASSERT(pbData != NULL);
   ASSERT(cbData > 0);

   ASSERT(m_hOut != NULL);

   if ((pbData != NULL) &&
       (cbData > 0) &&
       (m_hOut != NULL))
   {
      BYTE * pbCopy = new BYTE[cbData];
      ASSERT(pbCopy != NULL);

      CopyMemory(pbCopy, pbData, cbData);

      WAVEHDR * pWhdr = new WAVEHDR;
      ASSERT(pWhdr != NULL);

      pWhdr->lpData = (LPSTR)pbCopy;      /* pointer to locked data buffer */
      pWhdr->dwBufferLength = cbData;     /* length of data buffer */
      pWhdr->dwFlags = 0;                 /* assorted flags (see defines) */
      pWhdr->dwLoops = 0;                 /* loop control counter */

      MMRESULT mmr = waveOutPrepareHeader(m_hOut, pWhdr, sizeof(*pWhdr));
      TRACE("waveOutPrepareHeader() returned %d\n", mmr);

      if (mmr == MMSYSERR_NOERROR)
      {
         // !!! ENTERING CRITICAL SECTION !!!
         m_csPendingWaveHdrArray.Lock();

         try
         {
            m_apPendingWaveHdrs.Add(pWhdr);
         }
         catch (...)
         {
         }

         m_csPendingWaveHdrArray.Unlock();
         // !!! EXITING CRITICAL SECTION

         mmr = waveOutWrite(m_hOut, pWhdr, sizeof(*pWhdr));
         TRACE("waveOutWrite() returned %d\n", mmr);

         fSuccess = (mmr == MMSYSERR_NOERROR);
      }
      else
      {
         delete[] pbCopy;
         delete pWhdr;
      }
   }

   return fSuccess;
}


BOOL CWaveOutDev::GetCaps(WAVEOUTCAPS & woCaps)
{
   BOOL fSuccess = FALSE;

   ASSERT(m_nId != -1);
   if (m_nId != -1)
   {
      woCaps = m_woCaps;
      fSuccess = TRUE;
   }

   return fSuccess;
}

CString CWaveOutDev::GetName()
{
   CString strName = TEXT("");

   ASSERT(m_nId != -1);
   if (m_nId != -1)
   {
      strName = m_woCaps.szPname;
   }

   return strName;
}
