#include "precomp.h"
#include "WaveDev.h"
#include "WaveIo.h"



// utility function for both waveIndev and waveOutdev
// builds a PCM WaveFormatEx structure for a given sampling rate and size
static MMRESULT MakeWaveFormat(WAVEFORMATEX *pWF, int hertz, int bps)
{
	WAVEFORMATEX waveFormat;

	if ((bps != 8) && (bps != 16))
	{
		return WAVERR_BADFORMAT;
	}

	waveFormat.wFormatTag = WAVE_FORMAT_PCM;
	waveFormat.nChannels = 1;
	waveFormat.nSamplesPerSec = hertz;
	waveFormat.nAvgBytesPerSec = hertz * bps/8;
	waveFormat.nBlockAlign = bps/8;
	waveFormat.wBitsPerSample = (WORD)bps;
	waveFormat.cbSize = 0;

	*pWF = waveFormat;

	return MMSYSERR_NOERROR;
}




waveInDev::waveInDev(UINT uDevId, HANDLE hEvent) :
  m_devID(uDevId), m_hwi(NULL), m_bOpen(FALSE), m_fAllowMapper(TRUE),
  m_hEvent(hEvent)
{
	ZeroMemory(&m_waveFormat, sizeof(m_waveFormat));
	return;
}


waveInDev::~waveInDev()
{
	Close();
}

MMRESULT waveInDev::Open(int hertz, int bps)
{
	MMRESULT mmr;
	WAVEFORMATEX waveFormat;
	DWORD dwCallbackType = (m_hEvent ? CALLBACK_EVENT : CALLBACK_NULL );

	if (m_bOpen == TRUE)
		return MMSYSERR_NOERROR;

	mmr = MakeWaveFormat(&waveFormat, hertz, bps);
	if (mmr != MMSYSERR_NOERROR)
	{
		return mmr;
	}

	mmr = waveInOpen(&m_hwi, m_devID, &waveFormat, (DWORD_PTR)m_hEvent, 0, dwCallbackType);

	// begin hack, try to open wave_mapper
	// this may end up opening a different device!

	if ((mmr == WAVERR_BADFORMAT) && (m_fAllowMapper))
	{
		mmr = waveInOpen(&m_hwi, WAVE_MAPPER, &waveFormat, (DWORD_PTR)m_hEvent,
		                 0, dwCallbackType);
	}

	if (mmr == MMSYSERR_NOERROR)
		m_bOpen = TRUE;

	waveInStart(m_hwi);

	m_waveFormat = waveFormat;

	return mmr;
}


MMRESULT waveInDev::PrepareHeader(WAVEHDR *pWaveHdr)
{
	MMRESULT mmr;

	if (m_bOpen == FALSE)
		return MMSYSERR_INVALHANDLE;

	mmr = waveInPrepareHeader(m_hwi, pWaveHdr, sizeof(WAVEHDR));

	return mmr;

}



MMRESULT waveInDev::UnPrepareHeader(WAVEHDR *pWaveHdr)
{
	MMRESULT mmr;

	if (m_bOpen == FALSE)
		return MMSYSERR_INVALHANDLE;

	mmr = waveInUnprepareHeader(m_hwi, pWaveHdr, sizeof(WAVEHDR));

	return mmr;

}



MMRESULT waveInDev::Reset()
{
	MMRESULT mmr;
	if (m_bOpen == FALSE)
		return MMSYSERR_NOERROR;

	mmr = waveInReset(m_hwi);

	return mmr;
}


MMRESULT waveInDev::Close()
{
	MMRESULT mmr;
	if (m_bOpen == FALSE)
		return MMSYSERR_NOERROR;

	waveInReset(m_hwi);
	mmr = waveInClose(m_hwi);

	if (mmr == MMSYSERR_NOERROR)
		m_bOpen = FALSE;

	return mmr;
}


MMRESULT waveInDev::Record(WAVEHDR *pHdr)
{
	MMRESULT mmr;

	if (m_bOpen == FALSE)
	{
		return MMSYSERR_INVALHANDLE;
	}

	mmr = waveInAddBuffer(m_hwi, pHdr, sizeof(WAVEHDR));

	return mmr;

}


void waveInDev::AllowMapper(BOOL fAllowMapper)
{
	m_fAllowMapper = fAllowMapper;
}




waveOutDev::waveOutDev(UINT uDevID, HWND hwnd)
 : m_devID(uDevID), m_hwo(NULL), m_bOpen(FALSE), m_hWnd(hwnd),
   m_pfBuffer(NULL), m_nBufferSize(0), m_fFileBufferValid(FALSE),
	m_fAllowMapper(TRUE)
{
	ZeroMemory(&m_waveFormat, sizeof(m_waveFormat));
	ZeroMemory(m_szPlayFile, sizeof(m_szPlayFile));
	ZeroMemory(&m_waveHdr, sizeof(m_waveHdr));

	if (hwnd == NULL)
	{
		m_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

		if (m_hEvent == NULL)
		{
			ERROR_OUT(("waveOutDev::waveOutDev - Unable to create event"));
		}
	}
	else
		m_hEvent = NULL;

}

waveOutDev::~waveOutDev()
{
	Close();
	if (m_hEvent)
		CloseHandle(m_hEvent);

	if (m_pfBuffer)
		LocalFree(m_pfBuffer);
}



MMRESULT waveOutDev::Open(int hertz, int bps)
{
	MMRESULT mmr;
	WAVEFORMATEX waveFormat;

	mmr = MakeWaveFormat(&waveFormat, hertz, bps);
	if (mmr != MMSYSERR_NOERROR)
	{
		return mmr;
	}

	return Open(&waveFormat);

}


MMRESULT waveOutDev::Open(WAVEFORMATEX *pWaveFormat)
{
	MMRESULT mmr;

	m_waveFormat = *pWaveFormat;

	if (m_bOpen == TRUE)
		return MMSYSERR_NOERROR;

	if (m_hWnd == NULL)
	{
		mmr = waveOutOpen(&m_hwo, m_devID, &m_waveFormat, (DWORD_PTR)m_hEvent,
		                  0, CALLBACK_EVENT);
	}
	else
	{
		mmr = waveOutOpen(&m_hwo, m_devID, &m_waveFormat, (DWORD_PTR)m_hWnd,
		                  0, CALLBACK_WINDOW);
	}


	// begin hack, try to open wave_mapper
	// this may end up opening a different device!

	if ((mmr == WAVERR_BADFORMAT) && (m_fAllowMapper))
	{
		if (m_hWnd == NULL)
		{
			mmr = waveOutOpen(&m_hwo, WAVE_MAPPER, &m_waveFormat,
			(DWORD_PTR)m_hEvent, 0, CALLBACK_EVENT);
		}
		else
		{
			mmr = waveOutOpen(&m_hwo, WAVE_MAPPER, &m_waveFormat,
			(DWORD_PTR)m_hWnd, 0, CALLBACK_WINDOW);
		}
	}

	if (mmr == MMSYSERR_NOERROR)
		m_bOpen = TRUE;


	return mmr;

}





MMRESULT waveOutDev::Close()
{
	MMRESULT mmr;
	if (m_bOpen == FALSE)
		return MMSYSERR_NOERROR;

	waveOutReset(m_hwo);

	if (m_waveHdr.dwFlags & WHDR_PREPARED)
	{
		waveOutUnprepareHeader(m_hwo, &m_waveHdr, sizeof(m_waveHdr));
		m_waveHdr.dwFlags = 0;
	}

	mmr = waveOutClose(m_hwo);

	if (mmr == MMSYSERR_NOERROR)
		m_bOpen = FALSE;
	else
		ERROR_OUT(("ATW:Close failed"));

	return mmr;
}

MMRESULT waveOutDev::PrepareHeader(WAVEHDR *pWhdr, SHORT *shBuffer, int numSamples)
{
	MMRESULT mmr;

	if (m_bOpen == FALSE)
		return MMSYSERR_INVALHANDLE;

	// if shBuffer is not NULL, we assume the caller wants us to fill in the
	// WAVEHDR struct
	if (shBuffer)
	{
		ZeroMemory(pWhdr, sizeof(WAVEHDR));
		pWhdr->lpData = (LPSTR)shBuffer;
		pWhdr->dwBufferLength = numSamples * m_waveFormat.nBlockAlign;
	}

	mmr = waveOutPrepareHeader(m_hwo, pWhdr, sizeof(WAVEHDR));

	return mmr;

}

MMRESULT waveOutDev::UnprepareHeader(WAVEHDR *pWaveHdr)
{
	MMRESULT mmr;

	if (m_bOpen == FALSE)
		return MMSYSERR_INVALHANDLE;

	mmr = waveOutUnprepareHeader(m_hwo, pWaveHdr, sizeof(WAVEHDR));

	return mmr;

}

MMRESULT waveOutDev::Play(WAVEHDR *pWaveHdr)
{
	MMRESULT mmr;
	DWORD dwTimeOut;
	DWORD dwRet;
	int numSamples;

	if (m_bOpen == FALSE)
		return MMSYSERR_INVALHANDLE;

	if (m_hEvent)
		ResetEvent(m_hEvent);

	mmr = waveOutWrite(m_hwo, pWaveHdr, sizeof(WAVEHDR));

	if (mmr != MMSYSERR_NOERROR)
		return mmr;

	if (m_hEvent)
	{
		numSamples = pWaveHdr->dwBufferLength / m_waveFormat.nBlockAlign;

		dwTimeOut = 5 * ((1000 * numSamples) / m_waveFormat.nSamplesPerSec);
	
		dwRet = WaitForSingleObject(m_hEvent, dwTimeOut);

		if ((dwRet != WAIT_ABANDONED) && (dwRet != WAIT_OBJECT_0))
		{
			ERROR_OUT(("waveOutDev::Play() - WaitForSingleObject Failed"));
			return WAVERR_LASTERROR + 1;
		}
	}

	return MMSYSERR_NOERROR;
}



// File io errors or anything unexpected results in -1 being returned
// Otherwise, returns the MMRESULT of the last waveOut call made
MMRESULT waveOutDev::PlayFile(LPCTSTR szFileName)
{
	MMRESULT    mmr;
	WAVEIOCB    waveiocb;
	WIOERR      werr;
	DWORD       dwSize;
	PCHAR       pBuffer;


	// quick optimization
	// if the same file is being played twice in a row
	// the just replay the buffer

	if ((m_fFileBufferValid) && (0 == lstrcmp(szFileName, m_szPlayFile)))
	{
		Close();
		mmr = Open(&m_PlayFileWf);

		if (mmr == MMSYSERR_NOERROR)
		{
			mmr = PrepareHeader(&m_waveHdr, (SHORT*)m_pfBuffer, m_nBufferSize / m_PlayFileWf.nBlockAlign);
			if (mmr == MMSYSERR_NOERROR)
			{
				mmr = Play(&m_waveHdr);
			}
		}

		m_fFileBufferValid = (mmr == MMSYSERR_NOERROR);
		return mmr;
	}


	ZeroMemory(&waveiocb, sizeof(waveiocb));
	werr = wioFileOpen(&waveiocb, szFileName, 0);

	if (werr == WIOERR_NOERROR)
	{
		// prepare to read the samples!

		// quick hack, if the file to play was the same as the last,
		// then use the same buffer

		m_fFileBufferValid = FALSE;

		if (m_pfBuffer == NULL)
		{
			m_pfBuffer = (char *)LocalAlloc(LPTR, waveiocb.dwDataBytes);
		}
		else
		{
		    pBuffer = (char*)LocalReAlloc(m_pfBuffer, waveiocb.dwDataBytes, LMEM_MOVEABLE |LMEM_ZEROINIT);
		    if(NULL != pBuffer)
		    {
			    m_pfBuffer = pBuffer;
		    }
		    else
		    {
				// Failed to reallocate buffer, make sure to clean up
		        LocalFree(m_pfBuffer);
		        m_pfBuffer = NULL;
		    }
		}

		if (m_pfBuffer == NULL)
		{
			wioFileClose(&waveiocb, 0);
			return -1;
		}

		// read
		mmioSeek(waveiocb.hmmio, waveiocb.dwDataOffset, SEEK_SET);
		dwSize = mmioRead(waveiocb.hmmio, m_pfBuffer, waveiocb.dwDataBytes);

		if (dwSize == 0)
			return -1;

		Close();
		mmr = Open(waveiocb.pwfx);
		if (mmr != MMSYSERR_NOERROR)
		{
			wioFileClose(&waveiocb, 0);
			return mmr;
		}

//		mmr = Play((short *)m_pfBuffer, dwSize / (waveiocb.pwfx)->nBlockAlign);

		mmr = PrepareHeader(&m_waveHdr, (SHORT*)m_pfBuffer,
		                           dwSize / (waveiocb.pwfx)->nBlockAlign);
		if (mmr == MMSYSERR_NOERROR)
		{
			mmr = Play(&m_waveHdr);
		}

		m_fFileBufferValid = (mmr == MMSYSERR_NOERROR);
		if (m_fFileBufferValid)
		{
			m_PlayFileWf = *(waveiocb.pwfx);
			lstrcpy(m_szPlayFile, szFileName);
			m_nBufferSize = dwSize;
		}

		wioFileClose(&waveiocb, 0);

		return mmr;
	}

	return -1;

}

void waveOutDev::AllowMapper(BOOL fAllowMapper)
{
	m_fAllowMapper = fAllowMapper;
}