// WaveQueue Module
// Main C++ Source - WAVEQ.CPP
// (c)1994 Red Herring

#define __WAVEQ_CPP

// includes
#include "waveq.h"
#include <assert.h>
#include <string.h>
#include <windowsx.h>

// macros & defines
#define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0]))

// fuction prototypes
inline int									Min(int a,int b) {return a<b ? a:b;}
static BOOL 								WQ_ResetDevice();
static void									WQ_Update();
extern "C"
{
	LRESULT CALLBACK 					WQ_WndProc(HWND,UINT,WPARAM,LPARAM);
	void CALLBACK							WQ_CallBack(HWAVEOUT,UINT,DWORD,DWORD,DWORD);
}

// global variables
const char			_szWQCLASSNAME[]="WaveQueue";
const DWORD			_dwBLOCKSIZE=2048L;
PCMWAVEFORMAT		_pcmWaveFormat;
HWAVEOUT				_hwaveOut=0;
LPWAVEHDR				_lpWaveHdr[2]={0,0};
LPWAVEDATA			_lpDataStart[8],
								_lpData[8],
								_lpDataEnd[8];
int							_nActive=0,_nNumDevicesOpen=0;
HINSTANCE				_hinst;
HWND						_hwndWQ=0;


// DLL support functions
extern "C"
{
	int FAR PASCAL _export WEP(int);
}

int CALLBACK LibMain(HINSTANCE hinst,WORD,WORD wHeapSize,LPSTR)
{
	WNDCLASS		wc;

	_nNumDevicesOpen=0;
	_hinst=hinst;
	if(wHeapSize>0)
		UnlockData(0);
	// initialize format chunk for all waves
	_pcmWaveFormat.wf.wFormatTag=WAVE_FORMAT_PCM;
	_pcmWaveFormat.wf.nChannels=1;
	_pcmWaveFormat.wf.nSamplesPerSec=11025L;
	_pcmWaveFormat.wf.nAvgBytesPerSec=11025L;
	_pcmWaveFormat.wf.nBlockAlign=1;
	_pcmWaveFormat.wBitsPerSample=8;
	for(size_t i=0;i<ARRAYSIZE(_lpWaveHdr);i++)
	{
		_lpWaveHdr[i]=(LPWAVEHDR)GlobalAllocPtr(GMEM_MOVEABLE|GMEM_SHARE,sizeof(WAVEHDR));
		_lpWaveHdr[i]->dwUser=MAKELONG((WORD)GlobalAlloc(GMEM_MOVEABLE|GMEM_SHARE,_dwBLOCKSIZE),0);	
		_lpWaveHdr[i]->lpData=(char far *)GlobalLock((HGLOBAL)_lpWaveHdr[i]->dwUser);
		_lpWaveHdr[i]->dwBufferLength=_dwBLOCKSIZE;
		_lpWaveHdr[i]->dwFlags=WHDR_DONE;
		_lpWaveHdr[i]->dwLoops=0L;
	}
	
	// register WaveQueue window
	wc.style					= CS_GLOBALCLASS;
	wc.lpfnWndProc		= WQ_WndProc;
	wc.cbClsExtra			= 0;
	wc.cbWndExtra			= 0;
	wc.hInstance			= hinst;
	wc.hIcon					= 0;
	wc.hCursor				= 0;
	wc.hbrBackground	= 0;
	wc.lpszMenuName		= 0;
	wc.lpszClassName	= _szWQCLASSNAME;
	RegisterClass(&wc);
	return 1;
}

LRESULT _export CALLBACK WQ_WndProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
	switch(uMsg)
	{
		case WM_CREATE:
			return 0;
			break;
		case MM_WOM_DONE:
			WQ_Update();
			return 0L;
	}
	return DefWindowProc(hwnd,uMsg,wParam,lParam);
}

int FAR PASCAL _export WEP(int)
{
	WQ_CloseDevice();
	for(size_t i=0;i<ARRAYSIZE(_lpWaveHdr);i++)
		if(_lpWaveHdr[i])
		{
			GlobalUnlock((HGLOBAL)_lpWaveHdr[i]->dwUser);
			GlobalFree((HGLOBAL)_lpWaveHdr[i]->dwUser);
			GlobalFreePtr(_lpWaveHdr[i]);
			_lpWaveHdr[i]=0;
		}
	return 1;
}


// WaveQueue functions
BOOL __export FAR PASCAL WQ_OpenDevice()
{
	if(_nNumDevicesOpen==0)
	{
		// this is the first device being opened...initialize
		assert(_hwaveOut==0);
		if(_hwaveOut && waveOutClose(_hwaveOut))
			return FALSE;
		_hwndWQ=CreateWindow(_szWQCLASSNAME,"",WS_POPUP|WS_DISABLED,0,0,1,1,HWND_DESKTOP,0,_hinst,0);
		if(_hwndWQ)
			ShowWindow(_hwndWQ,SW_HIDE);
		else
			return FALSE;
		if(waveOutOpen((LPHWAVEOUT)&_hwaveOut,(UINT)WAVE_MAPPER,(LPWAVEFORMAT)&_pcmWaveFormat,(DWORD)WQ_CallBack,0L,CALLBACK_FUNCTION))
		{
			_hwaveOut=0;
			DestroyWindow(_hwndWQ);
			_hwndWQ=0;
			return FALSE;
		}
	}
	_nNumDevicesOpen++;
	return TRUE;
}

void CALLBACK _export WQ_CallBack(HWAVEOUT hwaveOut,UINT uMsg,DWORD,DWORD dwParam1,DWORD)
{
	if(uMsg==WOM_DONE)
	{
		PostMessage(_hwndWQ,MM_WOM_DONE,(WPARAM)hwaveOut,(LPARAM)dwParam1);
	}
}

BOOL __export FAR PASCAL WQ_CloseDevice()
{
	if(_nNumDevicesOpen<=0)
	{
		assert(_nNumDevicesOpen==0);
		_nNumDevicesOpen=0;
		return FALSE;
	}
	if(_nNumDevicesOpen==1)
	{
		// this is the last devices closing...clean up
		if(_hwndWQ)
		{
			DestroyWindow(_hwndWQ);
			_hwndWQ=0;
		}
		if(WQ_ResetDevice())
		{
			if(_hwaveOut && waveOutClose(_hwaveOut))
				return FALSE;
			_hwaveOut=0;
		}
		else
			return FALSE;
	}
	_nNumDevicesOpen--;
	return TRUE;
}

BOOL __export FAR PASCAL WQ_Add(LPWAVEDATA lpD,DWORD dwSize,BOOL bRepeat)
{
	if(_nActive<ARRAYSIZE(_lpData))
	{
		_lpDataStart[_nActive]=bRepeat ? lpD:0;
		_lpData[_nActive]=lpD;
		_lpDataEnd[_nActive++]=lpD+dwSize;
		WQ_Update();
		return TRUE;
	}
	return FALSE;
}

void __export FAR PASCAL WQ_Kill(LPWAVEDATA lpD)
{
	for(int i=0;i<_nActive;i++)
		if(_lpDataStart[i]==lpD)
		{
			_lpDataStart[i]=0;
			break;
		}
}

void __export FAR PASCAL WQ_Flush()
{
	_nActive=0;
}

static void WQ_Update()
{
	LPWAVEHDR						lpWH;
	register LPWAVEDATA	lpD;
	LPWAVEDATA					lpDEnd;
	register WORD				wSum;
	int									i,j,k,n,nSum;
	
	if(_hwaveOut)
	{
		for(i=0;i<ARRAYSIZE(_lpWaveHdr);i++)
		{
			lpWH=_lpWaveHdr[i];
			if(lpWH->dwFlags & WHDR_DONE)
			{
				if(lpWH->dwFlags & WHDR_PREPARED)
					waveOutUnprepareHeader(_hwaveOut,lpWH,sizeof(WAVEHDR));
	  
	  		if(_nActive)
	  		{
					for(lpDEnd=(lpD=lpWH->lpData)+_dwBLOCKSIZE;lpD<lpDEnd;lpD++)
					{
						nSum=n=Min(_nActive,ARRAYSIZE(_lpData)/2);
						for(j=wSum=0;j<n;j++)
						{
							wSum+=(BYTE)(*_lpData[j]);
							if(++_lpData[j]>=_lpDataEnd[j])
								if(_lpDataStart[j])
									_lpData[j]=_lpDataStart[j];
								else
								{
									_nActive--;
									n--;
									for(k=j--;k<_nActive;k++)
									{
										_lpData[k]=_lpData[k+1];
										_lpDataEnd[k]=_lpDataEnd[k+1];
										_lpDataStart[k]=_lpDataStart[k+1];
									}
								}
						}
						*lpD = nSum ? (BYTE)(wSum/nSum):0x80;
					}
					waveOutPrepareHeader(_hwaveOut,lpWH,sizeof(WAVEHDR));
					waveOutWrite(_hwaveOut,lpWH,sizeof(WAVEHDR));
				}
			}
		}
	}
}

BOOL __export FAR PASCAL WQ_IsWaveSupported()
{
	if(waveOutOpen((LPHWAVEOUT)NULL,(UINT)WAVE_MAPPER,(LPWAVEFORMAT)&_pcmWaveFormat,NULL,0L,(DWORD)WAVE_FORMAT_QUERY))
		return FALSE;
	else
		return TRUE;
}

BOOL __export FAR PASCAL WQ_GetWaveData(HINSTANCE hinst,LPCSTR szResName,LPWAVEDATA FAR *lpD,DWORD FAR *dwWaveSize)
{
	HWAVE				hwave;
	LPWAVEDATA	lpResWave;
	HMMIO				hmmio;
	MMIOINFO		mmioInfo;
	MMCKINFO		mmckinfoParent;
	MMCKINFO		mmckinfoSubchunk;

	if(!(hwave=WQ_LoadWave(hinst,szResName)))
		return FALSE;
	lpResWave=(char far *)LockResource((HGLOBAL)hwave);
	memset(&mmioInfo,0,sizeof(mmioInfo));
	mmioInfo.pIOProc=NULL;
	mmioInfo.fccIOProc=FOURCC_MEM;
	mmioInfo.pchBuffer=lpResWave;
	mmioInfo.cchBuffer=WQ_SizeofWave(hinst,szResName);
	mmioInfo.adwInfo[0]=NULL;
	hmmio=mmioOpen(NULL,&mmioInfo,MMIO_READ|MMIO_ALLOCBUF);
	// find wave chunk
	mmckinfoParent.fccType=mmioFOURCC('W','A','V','E');
	mmioDescend(hmmio,(LPMMCKINFO)&mmckinfoParent,NULL,MMIO_FINDRIFF);
	// find data subchunk
	mmckinfoSubchunk.ckid=mmioFOURCC('d','a','t','a');
	mmioDescend(hmmio,&mmckinfoSubchunk,&mmckinfoParent,MMIO_FINDCHUNK);
	// get size of data subchunk
	*dwWaveSize=mmckinfoSubchunk.cksize;
	// allocate and lock memory for waveform data
	*lpD=(char far *)GlobalAllocPtr(GMEM_MOVEABLE|GMEM_SHARE,*dwWaveSize);
	// read waveform data subchunk
	mmioRead(hmmio,(HPSTR)*lpD,*dwWaveSize);
	// close wave resource
	mmioClose(hmmio,0);
	UnlockResource((HGLOBAL)hwave);
	FreeResource((HGLOBAL)hwave);	
	return TRUE;
}

void __export FAR PASCAL WQ_FreeWaveData(LPWAVEDATA lpD)
{
	GlobalFreePtr(lpD);
}

DWORD	__export FAR PASCAL WQ_SizeofWave(HINSTANCE hinst,LPCWAVEDATA lpD)
{
	HRSRC		hResInfo;

	hResInfo=(HRSRC)FindResource(hinst,lpD,"WAVE");
	if(!hResInfo)
		return NULL;
	return SizeofResource(hinst,hResInfo);
}

HWAVE __export FAR PASCAL WQ_LoadWave(HINSTANCE hinst,LPCWAVEDATA lpD)
{
	HRSRC		hResInfo;
	
	hResInfo=FindResource(hinst,lpD,"WAVE");
	if(!hResInfo)
		return (HWAVE)NULL;
	return (HWAVE)LoadResource(hinst,hResInfo);
}

HWAVE __export FAR PASCAL WQ_PlayWave(HINSTANCE hinst,LPCSTR lpszWaveName)
{
	HWAVE				hwave;
	LPWAVEDATA	lpD;
		
	hwave=WQ_LoadWave(hinst,lpszWaveName);
	lpD=(char far *)LockResource((HGLOBAL)hwave);
	if(lpD)
	{
		sndPlaySound(NULL,0);
		sndPlaySound(lpD,SND_MEMORY|SND_ASYNC|SND_NODEFAULT);
		UnlockResource((HGLOBAL)hwave);
	}
	return hwave;
}

static BOOL WQ_ResetDevice()
{
	if(_hwaveOut)
		if(waveOutReset(_hwaveOut))
			return FALSE;
	return TRUE;
}
