/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       shell.c
 *  Content:    Direct Sound show-off.
 *  This app basically uses the direct sound api's and pops up some
 *  controls that the user can play with at runtime to change
 *  the sound frequency, panning, volume, etc.   It has a few
 *  other functions built in.
 *
 *  This app also takes a couple command-line parameters.  The format is:
 *
 *	DSShow [/PLAY [/LOOP]] [file] [file] ...
 *
 *    Specifying /PLAY causes any specified files to be played as they're
 * opened.  Adding the /LOOP causes them to loop as well.  /LOOP without
 * /PLAY means nothing.  Everything else is assumed to be one or more file
 * names.  Filenames can be enclosed in quotes.  This also means you can
 * drag and drop files onto the program's icon
 *
 *
 ***************************************************************************/

#define INITGUID
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <commdlg.h>
#include <stdio.h>

#include <mmsystem.h>
#include <mmreg.h>
#include <msacm.h>
#include <dsound.h>


#include "wassert.h"
#include "wave.h"

#include "resource.h"
#include "shell.h"
#include "dsenum.h"


// =======================================================================
/* Procedure called when the application is loaded for the first time */
// =======================================================================
BOOL ClassInit( hInstance )
HANDLE hInstance;
{
    WNDCLASS    myClass;
	    
    myClass.hCursor             = LoadCursor( NULL, IDC_ARROW );
    myClass.hIcon               = LoadIcon( hInstance, MAKEINTRESOURCE(IDI_ICON3));
    myClass.lpszMenuName        = MAKEINTRESOURCE(IDR_MAINMENU);
    myClass.lpszClassName       = (LPSTR)szAppName;
    myClass.hbrBackground       = (HBRUSH)(COLOR_WINDOW);
    myClass.hInstance           = hInstance;
    myClass.style               = CS_HREDRAW | CS_VREDRAW;
    myClass.lpfnWndProc         = WndProc;
    myClass.cbClsExtra          = 0;
    myClass.cbWndExtra          = 0;

    if (!RegisterClass( &myClass ) )
       return FALSE;

    return TRUE;        /* Initialization succeeded */
}


// =======================================================================
/* This "hook procedure" is called by the common dialog code for certain
 *   events that may occur during the life of our nested dialog structure.
 *	 We nest the Explorer style dialog inside our file open dialog so we
 *   can addd a check box for stick buffers.
 */
 // =======================================================================
UINT CALLBACK FileOpenCustomTemplateDlgProc( hDlg, message, wParam, lParam )
HWND hDlg;
UINT message;
WPARAM wParam;
LPARAM lParam;
{
    static LPOPENFILENAME   lpofn = NULL;

    switch( message )
    {
    case WM_INITDIALOG:
        lpofn = (LPOPENFILENAME)lParam;
		
        /* Set the flag to match the current state of the check box control */
        *((LPBOOL)lpofn->lCustData) = SendDlgItemMessage( hDlg, IDC_FONEST_STICKY,
                                                            BM_GETCHECK, 0, 0 );
        return TRUE;

    case WM_NOTIFY:
        switch(((LPOFNOTIFY)lParam)->hdr.code)
        {
        case CDN_SELCHANGE:
            /* Use this area to process anything that must be updated when the
             * user changes the selection in the Common Dialog Box.
             *   NOTE: Provided only for informational purposes
             */
            return FALSE;

        case CDN_FILEOK:
            /* We can do lots of things in this notification message.  The most
             * important is that we can decide whether the Common Dialog call will
             * go through or whether it will fail.  I decided to handle the checkbox
             * control in this one place versus 4 others... -PRN
             */
            Assert( lpofn != NULL );
            *((LPBOOL)lpofn->lCustData) = SendDlgItemMessage( hDlg, IDC_FONEST_STICKY,
                                                                BM_GETCHECK, 0, 0 );
            /* Returning zero signifies that we "approve" of the OK command,
             * and allows the common dialog to finish.
             */
            return FALSE;
        }
        /* Let the default dialog do/continue processing */
        return FALSE;
    }
    return FALSE;
}


// =======================================================================
// =======================================================================
int PASCAL WinMain( hInstance, hPrevInstance, lpszCmdLine, cmdShow )
HINSTANCE hInstance, hPrevInstance;
LPSTR lpszCmdLine;
int cmdShow;
{
    MSG   msg;
    HWND  hWnd;

    // We must call this to ensure the common controls are setup for
    // this application
    InitCommonControls();

    if (!hPrevInstance) {
	/* Call initialization procedure if this is the first instance */
    if (!ClassInit( hInstance ))
	    return FALSE;
	}

    
    hWnd = CreateWindow((LPSTR)szAppName,
						(LPSTR)szMessage,
						WS_OVERLAPPEDWINDOW,
						CW_USEDEFAULT,    
						CW_USEDEFAULT,    
						DX_MINWINDOW,     
						DY_MINWINDOW,     
						(HWND)NULL,        
						(HMENU)NULL,      
						(HANDLE)hInstance, 
						(LPSTR)NULL        
						);

    if (!hWnd) return (int)msg.wParam;

    // Make a long line across the top.
    CreateWindow(
	"STATIC", 
	"", 
	WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ, 
	0,
	0,
	8000, 
	2,              
	hWnd, 
	(HMENU)0, 
	hInst, 
	NULL);
	

    /* Save instance handle for DialogBox */
    hInst = hInstance;
    
    ShowWindow( hWnd, cmdShow );

    if( lpszCmdLine && *lpszCmdLine )
        if( !ParseCommandLine( lpszCmdLine ))
	    goto Exit_WinMain;
    
    /* Polling messages from event queue */
    while (GetMessage((LPMSG)&msg, NULL, 0, 0)) {
	TranslateMessage((LPMSG)&msg);
	DispatchMessage((LPMSG)&msg);
	}

Exit_WinMain:
    DestroyWindow(hWnd);
    UnregisterClass(szAppName, hInstance);
    return (int)msg.wParam;
}

// =======================================================================
/*  This function updates the status window by writing the specified
    string to the window, prepended by a string indicating whether
    the buffer is in hardware or software
*/
// =======================================================================
void UpdateStatus(FILEINFO *pFileInfo, DWORD dwStatus)
{
    TCHAR szStatus[200];
    DWORD dwPlay, dwWrite;
    HRESULT hr;

    lstrcpy(szStatus, pFileInfo->fHardware ? szHW : szSW);
    if (dwStatus & DSBSTATUS_BUFFERLOST)
    {
    lstrcat(szStatus, szLost);
    SendMessage(pFileInfo->hWndStatus_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    }
    else if (dwStatus & DSBSTATUS_PLAYING)
    {
    lstrcat(szStatus, szPlaying);
    SendMessage(pFileInfo->hWndStatus_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    }
    else
    {
    lstrcat(szStatus, szStopped);
    SendMessage(pFileInfo->hWndStatus_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    }

    if (pFileInfo->fSticky)
	{
	lstrcat(szStatus, szSticky);
    SendMessage(pFileInfo->hWndStatus_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
	}
    hr = IDirectSoundBuffer_GetCurrentPosition(pFileInfo->pDSB, &dwPlay, &dwWrite);
    if (DS_OK == hr) {
    wsprintf(szStatus, szFmtPlayPosition, dwPlay);
    SendMessage(pFileInfo->hWndPlayPosition_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    wsprintf(szStatus, szFmtWritePosition, dwWrite);
    SendMessage(pFileInfo->hWndWritePosition_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    }

    return;
}

// =======================================================================
/*  This function updates the main window title to show some
    relevant information about the direct sound object
*/
// =======================================================================
void UpdateMainStatus()
{
    DSCAPS  dsc;
    TCHAR   szTitle[200];
    
    // Update main window title with some relevant info
    dsc.dwSize = sizeof(dsc);
    IDirectSound_GetCaps(gpds, &dsc);
    wsprintf(szTitle, "%s : free hw memory = %dKb, free hw buffers = %d",
	 szMessage, (dsc.dwFreeHwMemBytes+512)/1024,
	 dsc.dwFreeHwMixingAllBuffers);
    SendMessage(hWndMain, WM_SETTEXT, 0, (LPARAM)szTitle);
    return;
}


// =======================================================================
/*  This routine will set up everything needed for the app to run.

    Input:
	hWnd                - App main window handle

    Output:
	None.

*/
// =======================================================================
int AppInit(HWND hWnd)
{
    UINT            cT;
    DSBUFFERDESC    dsbd;
    BOOL	    fUseGuid;
    HRESULT         hr;
    DWORD           dw;

    // Set up the global window handle.
    hWndMain = hWnd;

    // Set up the global File...Open dialog's start directory
    GetMediaStartPath();

    // Set up the file info header
    FileInfoFirst.pNext = NULL;
    FileInfoFirst.pwfx = NULL;
    FileInfoFirst.cox = COX_STARTCONTROL;
    FileInfoFirst.coy = COY_STARTCONTROL;

    // Clear the coordinate buffer.  Used to find the next available
    // position to use for a new control.  -1 is the invalid value.
    for (cT=0; cT<MAXCONTROLS; cT++) rgfcoxAvail[cT] = FALSE;

    // Setup the timer...
    if ((dwTimer = SetTimer(hWnd, 1, TIMERPERIOD, NULL)) == 0) 
	{
		MessageBox(hWnd, "Cannot allocate timer, aborting", "DirectSound Demo", MB_OK|MB_ICONSTOP);
		return -1;
    }

    // Now set up all the direct sound stuff...

    // Get the largest waveformatex structure.
    if (MMSYSERR_NOERROR != acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &dw))
    {
	MessageBox(hWnd, "ACM Metrics failed, aborting", "DirectSound Demo",
		   MB_OK|MB_ICONSTOP);
	return -1;
    }


    // Setup the format, frequency, volume, etc.
    if ((FileInfoFirst.pwfx = GlobalAllocPtr(GPTR, dw)) == NULL)
    {
	MessageBox(hWnd, "Out of Memory", "DirectSound Demo",
		   MB_OK|MB_ICONSTOP);
	return -1;
    }



    FileInfoFirst.pwfx->wFormatTag = WAVE_FORMAT_PCM;
    FileInfoFirst.pwfx->nChannels = 2;
    FileInfoFirst.pwfx->nSamplesPerSec = 22050;
    FileInfoFirst.pwfx->nAvgBytesPerSec = 22050*2*2;
    FileInfoFirst.pwfx->nBlockAlign = 4;
    FileInfoFirst.pwfx->wBitsPerSample = 16;
    FileInfoFirst.pwfx->cbSize = 0;
    
#ifdef STARTEIGHTBITS

    FileInfoFirst.pwfx->wFormatTag = WAVE_FORMAT_PCM;
    FileInfoFirst.pwfx->nChannels = 2;
    FileInfoFirst.pwfx->nSamplesPerSec = 22050;
    FileInfoFirst.pwfx->nAvgBytesPerSec = 22050*1*2;
    FileInfoFirst.pwfx->nBlockAlign = 2;
    FileInfoFirst.pwfx->wBitsPerSample = 8;
    FileInfoFirst.pwfx->cbSize = 0;
#endif

#ifdef STARTMONO    
    FileInfoFirst.pwfx->wFormatTag = WAVE_FORMAT_PCM;
    FileInfoFirst.pwfx->nChannels = 1;
    FileInfoFirst.pwfx->nSamplesPerSec = 22050;
    FileInfoFirst.pwfx->nAvgBytesPerSec = 22050*1*2;
    FileInfoFirst.pwfx->nBlockAlign = 2;
    FileInfoFirst.pwfx->wBitsPerSample = 16;
    FileInfoFirst.pwfx->cbSize = 0;
#endif
    
    // Optionally enumerate DSOUND devices and allow the user to pick one...

    if (!SUCCEEDED(CoInitialize(NULL))) {
	MessageBox(hWnd, "Failed to initialize COM library", "DirectSound Demo", MB_OK | MB_ICONSTOP);
	return -1;
    }
    
    fEnumDrivers = (BOOL)GetProfileInt( "DSSHOW", "EnumDrivers", FALSE );
	fUseGuid = (fEnumDrivers && !DoDSoundEnumerate(&guID));

	// Create the direct sound object.
    hr = CoCreateInstance(&CLSID_DirectSound, NULL, CLSCTX_INPROC_SERVER,
			  &IID_IDirectSound, &gpds);

    if (SUCCEEDED(hr) && (NULL != gpds))
	{
		hr = IDirectSound_Initialize(gpds, fUseGuid ? &guID : NULL);
		if (SUCCEEDED(hr)) 
		{
			// Note we need to set the level to be priority to set the
			// format of the primary buffer
			hr = IDirectSound_SetCooperativeLevel(gpds, hWndMain, DSSCL_PRIORITY);
			if (SUCCEEDED(hr)) 
			{

			// Set up the primary direct sound buffer.
			ZeroMemory(&dsbd, sizeof(dsbd));
			dsbd.dwSize = sizeof(dsbd);
			dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
    
			hr = IDirectSound_CreateSoundBuffer(gpds, &dsbd, &(FileInfoFirst.pDSB), NULL);
			if (SUCCEEDED(hr)) 
			{
				hr = IDirectSoundBuffer_Play(FileInfoFirst.pDSB, 0, 0, DSBPLAY_LOOPING);
				if (SUCCEEDED(hr)) 
				{
					UpdateMainStatus();
				} 
				else 
				{
					MessageBox(hWnd, "Cannot play primary buffer","DirectSound Demo", MB_OK|MB_ICONSTOP);
					IDirectSoundBuffer_Release(FileInfoFirst.pDSB);
					FileInfoFirst.pDSB = NULL;
			    }		
			} 
			else 
			{
				MessageBox(hWnd, "Cannot create primary buffer","DirectSound Demo", MB_OK|MB_ICONSTOP);
			}
	    } 
		else 
		{
		MessageBox(hWnd, "DirectSound SetCooperativeLevel failed","DirectSound Demo", MB_OK|MB_ICONSTOP);
	    }
	} //initialize
	else 
	{
	    MessageBox(hWnd, "Failed to Initialize DirectSound object", "DirectSound Demo", MB_OK | MB_ICONSTOP);
	}

	if (!SUCCEEDED(hr)) 
	{
	    IDirectSound_Release(gpds);
	    gpds = NULL;
	}

 } 
else 
{
	MessageBox(hWnd, "Failed to create DirectSound COM object",
		   "DirectSound Demo", MB_OK | MB_ICONSTOP);
}

if (SUCCEEDED(hr)) 
{
	return 0;
} 
else 
{
	CoUninitialize();
	return -1;
}
}

// =======================================================================
/*  This will destroy all the created objects, allocated memory, etc.  Must be called
    before termination of app.

    Input:
	hWnd                - Window handle of main window

    Output:
	None.

*/
// =======================================================================
void AppDestroy( HWND hWnd )
{

    HRESULT     hr = 0;

    if (dwTimer != 0)
    {
	KillTimer(hWnd, dwTimer);
	dwTimer = 0;
    }


    StopAllDSounds(hWnd, &FileInfoFirst);
    FreeAllList(hWnd, &FileInfoFirst);


    // Destroy the direct sound buffer.
    if(FileInfoFirst.pDSB != NULL) 
    {
	IDirectSoundBuffer_Stop(FileInfoFirst.pDSB);
	IDirectSoundBuffer_Release(FileInfoFirst.pDSB);
	FileInfoFirst.pDSB = NULL;
    }

    // Destroy the direct sound object.
    if (gpds != NULL)
    {
	IDirectSound_Release(gpds);
	gpds = NULL;
	CoUninitialize();
    }

    if (FileInfoFirst.pwfx != NULL)
    {
	GlobalFreePtr(FileInfoFirst.pwfx);
	FileInfoFirst.pwfx = NULL;
    }

    if (FileInfoFirst.pbData != NULL)
    {
	GlobalFreePtr(FileInfoFirst.pbData);
	FileInfoFirst.pbData = NULL;
    }

    WriteProfileString( "DSSHOW", "EnumDrivers", fEnumDrivers ? "1" : "0" );

}

// =======================================================================
/* Procedures which make up the window class. */
// =======================================================================
long FAR PASCAL WndProc( hWnd, message, wParam, lParam )
HWND hWnd;
unsigned message;
WPARAM wParam;
LPARAM lParam;
{



    switch (message)
	{

	case WM_CREATE:
	    if (AppInit(hWnd)) return (-1);
	    break;

	case WM_TIMER:  
	    if (!UIMainWindowTimerHandler(hWnd, wParam, lParam))
		return(DefWindowProc(hWnd, message, wParam, lParam));                           
	    break;
	    
    
	case WM_HSCROLL:
	    if (!UIMainWindowHSBHandler(hWnd, wParam, lParam))
		return(DefWindowProc(hWnd, message, wParam, lParam));
		    
	    break;

	case WM_VSCROLL:
	    if (!UIMainWindowVSBHandler(hWnd, wParam, lParam))
		return(DefWindowProc(hWnd, message, wParam, lParam));
	    break;
		    

		case WM_INITMENU:
			if((HMENU)wParam != GetMenu( hWnd ))
			    break;
			CheckMenuItem((HMENU)wParam, IDPD_ENUMDRIVERS,
				fEnumDrivers ? MF_CHECKED : MF_UNCHECKED );
			break;

		case WM_COMMAND:
	    if (!UIMainWindowCMDHandler(hWnd, wParam, lParam))
		return(DefWindowProc(hWnd, message, wParam, lParam));
	    break;
	    
	    break;
	
	/*case WM_PAINT:
	    {           
	    
	    break;
	    }*/


	case WM_DESTROY:
	    AppDestroy(hWnd);
	    PostQuitMessage( 0 );
	    break;
	
	default:
	    return DefWindowProc( hWnd, message, wParam, lParam );
	    break;
	    
	}
    
    return(0L);
}

// =======================================================================
/*  This routine will pop up the open file dialog and open a file, and make any internal
    arrangements so we know the file is loaded.

    Input:
	hWnd            -   Handle of parent window.

    Output:
	None.

*/
// =======================================================================
void PD_FileOpen( HWND hWnd )
{

    char            szFileName[MAX_PATH];
    DWORD           cSamples;
    FILEINFO        *pFileInfo                  = NULL;
    int             nFileName;
    BOOL            fSticky;

    if (GetNumControls(&FileInfoFirst) >= MAXCONTROLS)
    {
    MessageBox(hWnd, "No more controls allowed",
	   "Hold on a sec...", MB_OK);
    return;
    }

    // Open the file, and check its format, etc.
    if (OpenFileDialog(hWnd, szFileName, &nFileName, &fSticky))
    {

    // Allocate the memory for the structure.
    if ((pFileInfo = GlobalAllocPtr(GPTR, sizeof(FILEINFO))) == NULL)
    {
	MessageBox(hWnd, "Cannot add this file",
	       "Out of Memory", MB_OK|MB_ICONSTOP);
	goto ERROR_DONE_ROUTINE;
    }

    pFileInfo->pbData   = NULL;
    pFileInfo->pwfx     = NULL;
    pFileInfo->pDSB     = NULL;
    pFileInfo->fSticky  = fSticky;
    strcpy(pFileInfo->szFileName, szFileName);
    
    if (WaveLoadFile(szFileName, &pFileInfo->cbSize, 
	    &cSamples, &pFileInfo->pwfx, &pFileInfo->pbData) != 0)
    {
	MessageBox(hWnd, "Bad wave file or file too big to fit in memory",
		"Cannot load wave", MB_OK|MB_ICONSTOP);
	goto ERROR_DONE_ROUTINE;
    }

    GetNextControlCoords(&FileInfoFirst,
		     &pFileInfo->cox, &pFileInfo->coy);

    if (NewDirectSoundBuffer(pFileInfo) != 0)
    {
	MessageBox(hWnd, "Cannot create new buffer",
	       "Direct Sound Error", MB_OK|MB_ICONSTOP);
	goto ERROR_DONE_ROUTINE;
    }
	
    Assert(pFileInfo->pbData != NULL);

    // If we fail after this, make sure to update the list!!!
    if (AddToList(&FileInfoFirst, pFileInfo) != 0)
    {
	MessageBox(hWnd, "Cannot add file to list",
		   "Out of Memory", MB_OK|MB_ICONSTOP);
	goto ERROR_DONE_ROUTINE;
    }

    pFileInfo->nFileName = nFileName;
    CreateControl(hWnd, pFileInfo, pFileInfo->pwfx->nSamplesPerSec,
	      (MAXPAN_TB-MINPAN_TB)/2, MINVOL_TB );
    ChangeOutputVol(pFileInfo);
    ChangeOutputFreq(pFileInfo);
    ChangeOutputPan(pFileInfo);
    UpdateMainStatus();

    }

    goto DONE_ROUTINE;
	   
ERROR_DONE_ROUTINE:
    if (pFileInfo != NULL)
    {
	
    ReleaseDirectSoundBuffer(pFileInfo);

    if (pFileInfo->pwfx != NULL)
    {
	GlobalFreePtr(pFileInfo->pwfx);
	    
    }
    if (pFileInfo->pbData != NULL)
    {
	GlobalFreePtr(pFileInfo->pbData);           
    }

    GlobalFreePtr(pFileInfo);
    pFileInfo = NULL;
    }

DONE_ROUTINE:
    return;

}

// =======================================================================
/*  This routine will initialize a new direct sound buffer,
    set the data in the buffer, 
    set the rate, format, etc...

    Input:
	pFileInfo   -   Pointer to file info with all
	nessecary info filled, 
	like pbData, cbData, etc...

    Output:
	0 if successful, else the error code.

*/
// =======================================================================
int NewDirectSoundBuffer(FILEINFO *pFileInfo)
{

    DSBUFFERDESC        dsbd;
    DSBCAPS         dsbc;
    HRESULT         hr;
    BYTE            *pbData         = NULL;
    BYTE            *pbData2        = NULL;
    DWORD           dwLength;
    DWORD           dwLength2;

    // Set up the direct sound buffer. 
    memset(&dsbd, 0, sizeof(DSBUFFERDESC));
    dsbd.dwSize                 = sizeof(DSBUFFERDESC);
    dsbd.dwFlags                = 0;
    dsbd.dwFlags                |= DSBCAPS_STATIC;
    // Use new GetCurrentPosition() accuracy (DirectX 2 feature)
    dsbd.dwFlags                |= DSBCAPS_CTRLDEFAULT | DSBCAPS_GETCURRENTPOSITION2;
    if (pFileInfo->fSticky)
        dsbd.dwFlags |= DSBCAPS_STICKYFOCUS;
    dsbd.dwBufferBytes               = pFileInfo->cbSize;
    dsbd.lpwfxFormat            = pFileInfo->pwfx;
    if ((hr = gpds->lpVtbl->CreateSoundBuffer(gpds,
			  &dsbd,
			  &(pFileInfo->pDSB),
			  NULL )) != 0)
	{
	goto ERROR_IN_ROUTINE;
	}

    // Ok, lock the sucker down, and copy the memory to it.
    if ((hr = pFileInfo->pDSB->lpVtbl->Lock(pFileInfo->pDSB,
			0,
			pFileInfo->cbSize,
			&pbData,
			&dwLength,
			&pbData2,
			&dwLength2,
					    0L)) != 0)
    {
    goto ERROR_IN_ROUTINE;
    }

    Assert(pbData != NULL);
    memcpy(pbData, pFileInfo->pbData, pFileInfo->cbSize);

    // Ok, now unlock the buffer, we don't need it anymore.
    if ((hr = pFileInfo->pDSB->lpVtbl->Unlock(pFileInfo->pDSB,
					      pbData, pFileInfo->cbSize,
					      NULL, 0)) != 0)
    {
    goto ERROR_IN_ROUTINE;
    }

    pbData = NULL;

    if ((hr = pFileInfo->pDSB->lpVtbl->SetVolume(pFileInfo->pDSB,
	    MAXVOL_VAL)) != 0)
    {
    goto ERROR_IN_ROUTINE;
    }

    if ((hr = pFileInfo->pDSB->lpVtbl->SetPan(pFileInfo->pDSB,
	    MIDPAN_VAL)) != 0)
    {
    goto ERROR_IN_ROUTINE;
    }

    dsbc.dwSize = sizeof(dsbc);
    if (hr = IDirectSoundBuffer_GetCaps(pFileInfo->pDSB, &dsbc))
    {
    goto ERROR_IN_ROUTINE;
    }

    if (dsbc.dwFlags & DSBCAPS_LOCHARDWARE) {
    pFileInfo->fHardware = TRUE;
    } else {
    pFileInfo->fHardware = FALSE;
    }

    goto DONE_ROUTINE;

ERROR_IN_ROUTINE:
    if (pbData != NULL)
    {
    hr = pFileInfo->pDSB->lpVtbl->Unlock(pFileInfo->pDSB, pbData,
						pFileInfo->cbSize, NULL, 0);
    pbData = NULL;
    }

    if (pFileInfo->pDSB != NULL)
    {
    pFileInfo->pDSB->lpVtbl->Release(pFileInfo->pDSB);
    pFileInfo->pDSB = NULL;
    }
    
DONE_ROUTINE:

    return(hr); 

}

// =======================================================================
/*  This routine will release a direct sound buffer,
    freeing up memory, resources, 
    whatever.

    Input:
	pFileInfo   -   Pointer to the file info,
		with the proper stuff set.

    Output: 
	0 if successful, else the error code.

*/
// =======================================================================
int ReleaseDirectSoundBuffer( FILEINFO *pFileInfo )
{

    if (pFileInfo->pDSB != NULL)
    {
    pFileInfo->pDSB->lpVtbl->Release(pFileInfo->pDSB);
    pFileInfo->pDSB = NULL; 
    }

    return(0);

}

// =======================================================================
/*  This routine will find the next x and y coordinates to
    write the control to.
    The rgfcoxAvail is an array of booleans.
    If false, then the index can be 
    used as an x coordinate.

    Input:
	pFileInfoHead - Header of the linked list.
	pcox, pcoy    - Filled upon return with next
		coordinates to use.
	
    Output:
	Only pcox and pcoy change.
	
*/
// =======================================================================	
void GetNextControlCoords(                     
		    FILEINFO    *pFileInfoHead, 
		    int         *pcox, 
		    int         *pcoy
		    )
{
    UINT            cT;

    for (cT=0; cT<MAXCONTROLS; cT++)
    {
    if (rgfcoxAvail[cT] == FALSE)
    {
	rgfcoxAvail[cT] = TRUE;
	break;
    }
	    
    }

    if (cT == MAXCONTROLS)
    {
    Assert(FALSE);
    // Couldn't find a place to put control, shouldn't happen though.
    cT = 666;       // Well, at least put it off screen.
    }

    *pcox = cT*DX_CONTROLSPACING+COX_STARTCONTROL;      //Offsetting the text from the border
    *pcoy = COY_STARTCONTROL;
    

}

// =======================================================================
/*  CreateControl

    This will create the control used for the window, actually it is a
    bundle of controls put together.  I was thinking of a good way to
    figure out id codes for the controls but found no good way except a
    "funny" way...I'm going to use the x coordinate of the control as the
    id for the first control, then id+1 for the second control.  Since
    all the controls have different x coordinates, this is fine, as long
    as the # of windows in the control is not more than the spacing of
    the controls.

    Input:
	hWnd                -   Parent Window.
	pFileInfo           -   Pointer to FileInfo structure with the cox and coy filled.
	dwFreq, dwPan, dwVol-   Default track bar values.

    Output:
	0 if successful, else the error code.

*/
// =======================================================================
int CreateControl(HWND hWnd, FILEINFO *pFileInfo, DWORD dwFreq,DWORD dwPan, DWORD dwVol)
{
    int		cox, coy;
    int     coxOld, coyOld;
    int     nError = 0;
    DWORD	idBase;
    SIZE    Size;       
    HDC     hDC = NULL;
	DWORD dwMinFreq, dwMaxFreq;

    /* Figure out the values of dwPan and dwVol that the track bars like */

    idBase = pFileInfo->cox;
    Assert(pFileInfo != NULL);
    cox = pFileInfo->cox+DX_TEXTSPACING;
    coy = pFileInfo->coy+DY_TEXTSPACING;        //We may have to shift this

    coxOld = cox;
    coyOld = coy;
	coy -= 8;                       //We must adjust to fit the text in the border

    if ((hDC = GetDC(hWnd)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}


    if (!GetTextExtentPoint32(hDC, pFileInfo->szFileName+pFileInfo->nFileName, strlen(pFileInfo->szFileName+pFileInfo->nFileName), &Size))
	{
	nError = -1;
	goto DONE_ROUTINE;
	}

    //Creates the Filename window
    if ((pFileInfo->hWndFileName_TXT = CreateWindow(
	"STATIC", 
	pFileInfo->szFileName+pFileInfo->nFileName, 
	WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP, 
	cox,
	coy,                                                                    
	DX_FILENAME_TXT,                                                                        
	Size.cy,              
	hWnd, 
	(HMENU)0, 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   
																		//Create line under Filename            
    cox += DX_LOOPEDSPACING;
    coy += Size.cy + DY_TEXTSPACING + DY_LOOPEDSPACING;

    if ((pFileInfo->hWndFileName_EDGE = CreateWindow(
	"STATIC", 
	"", 
	WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ, 
	cox,
	coy - (DY_LOOPEDSPACING+DY_TEXTSPACING)/2,
	DX_LINEEDGE, 
	DY_LINEEDGE,                
	hWnd, 
	(HMENU)0, 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   

    // Now create status if required.
    
    #ifdef SHOWSTATUS   

    if (!GetTextExtentPoint32(hDC, szPlaying, strlen(szPlaying), &Size))
	{
	nError = -1;
	goto DONE_ROUTINE;
	}


    //Creates Status Window
    if ((pFileInfo->hWndStatus_TXT = CreateWindow(
	"STATIC", 
	"",
	WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP, 
	cox,
	coy,
	DX_STATUS_TXT, 
	Size.cy, // + DY_TEXTSPACING,               
	hWnd, 
	(HMENU)0, 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   

    cox += DX_LOOPEDSPACING;
    coy += Size.cy + DY_TEXTSPACING + DY_LOOPEDSPACING;

    //Create line under Status
    if ((pFileInfo->hWndStatus_EDGE = CreateWindow(
	"STATIC", 
	"", 
	WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ, 
	cox,
	coy - (DY_LOOPEDSPACING+DY_TEXTSPACING)/2,
	DX_LINEEDGE, 
	DY_LINEEDGE,                
	hWnd, 
	(HMENU)0, 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}

    //Creates PlayPos Window
    if ((pFileInfo->hWndPlayPosition_TXT = CreateWindow(
	"STATIC", "",
	WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
	cox, 
	coy,
	DX_STATUS_TXT, 
	Size.cy,
	hWnd, 
	NULL, 
	hInst, 
	NULL)) == NULL)
    {
	nError = -1;
	goto DONE_ROUTINE;
    }

    cox += DX_LOOPEDSPACING;
    coy += Size.cy + DY_TEXTSPACING + DY_LOOPEDSPACING; //Create line under PlayPos
    
    if ((pFileInfo->hWndPlayPosition_EDGE = CreateWindow(
	"STATIC", "", 
	WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ, 
	cox, 
	coy - (DY_LOOPEDSPACING+DY_TEXTSPACING)/2,
	DX_LINEEDGE, 
	DY_LINEEDGE,               
	hWnd, 
	NULL, 
	hInst, 
	NULL)) == NULL)
    {
	nError = -1;
	goto DONE_ROUTINE;
    }
    
    //Creates WritePos Window
    if ((pFileInfo->hWndWritePosition_TXT = CreateWindow(
	"STATIC", "",
	WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
	cox, 
	coy,
	DX_STATUS_TXT, 
	Size.cy,
	hWnd, 
	NULL, 
	hInst, 
	NULL)) == NULL)
    {
	nError = -1;
	goto DONE_ROUTINE;
    }

    cox += DX_LOOPEDSPACING;
    coy += Size.cy + DY_TEXTSPACING + DY_LOOPEDSPACING;
    
    //Create line under WritePos
    if ((pFileInfo->hWndWritePosition_EDGE = CreateWindow(
	"STATIC", "", 
	WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ, 
	cox, 
	coy - (DY_LOOPEDSPACING+DY_TEXTSPACING)/2,
	DX_LINEEDGE,
	DY_LINEEDGE,               
	hWnd,
	NULL, 
	hInst, 
	NULL)) == NULL)
    {
	nError = -1;
	goto DONE_ROUTINE;
    }
    
    #endif      
	
    //Set up the Freq Text
    if (!GetTextExtentPoint32(hDC, szFreq, strlen(szFreq), &Size))
	{
	nError = -1;
	goto DONE_ROUTINE;
	}

    // Make the frequency text there.
    if ((pFileInfo->hWndFreq_TXT = CreateWindow(
	"STATIC", 
	szFreq, 
	WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP, 
	cox,
	coy,
	DX_FREQ_TXT, 
	Size.cy,                
	hWnd, 
	(HMENU)0, 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   

    coy += Size.cy;

    // Make the frequency trackbar.
    if ((pFileInfo->hWndFreq_TB = CreateWindow(
	TRACKBAR_CLASS, 
	"", 
	WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_BOTH, 
	cox,
	coy,
	DX_FREQ_TB, 
	DY_FREQ_TB,             
	hWnd, 
	(HMENU)(idBase+idFreqTB), 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   

	// get the min and max range that the sound card supports.
	// If the buffer is in hardware query the card, else use
	// our ifdef'd values.
	if (pFileInfo->fHardware)
	{
		DSCAPS dsc;
		memset(&dsc, 0, sizeof(DSCAPS));
		dsc.dwSize = sizeof(DSCAPS);
	    nError = IDirectSound_GetCaps(gpds, &dsc);
		Assert(nError == DS_OK);
		dwMinFreq = dsc.dwMinSecondarySampleRate;
		dwMaxFreq = dsc.dwMaxSecondarySampleRate;
	}
	else
	{	
		dwMinFreq = DSBFREQUENCY_MIN;	
		dwMaxFreq = DSBFREQUENCY_MAX;
	}

	SendMessage(pFileInfo->hWndFreq_TB, TBM_SETRANGEMIN, FALSE, dwMinFreq / FREQFACTOR );
	SendMessage(pFileInfo->hWndFreq_TB, TBM_SETRANGEMAX, FALSE, dwMaxFreq / FREQFACTOR );
	
    SendMessage(pFileInfo->hWndFreq_TB, TBM_SETPAGESIZE, 0, FREQPAGE );
    SendMessage(pFileInfo->hWndFreq_TB, TBM_SETPOS, TRUE, (dwFreq + FREQADD)/FREQFACTOR);
    pFileInfo->dwFreq = dwFreq;


    coy += DY_FREQ_TB+DY_PANSPACING;    

    if ((pFileInfo->hWndFreq_EDGE = CreateWindow(
	"STATIC", 
	"", 
	WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ, 
	cox,
	coy - (DY_PANSPACING+DY_TEXTSPACING)/2,
	DX_LINEEDGE, 
	DY_LINEEDGE,                
	hWnd, 
	(HMENU)0, 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   

    //Adjusts the relative position of the Text    
    coy -= (((DY_PANSPACING+DY_TEXTSPACING)/2)-((DY_LOOPEDSPACING+DY_TEXTSPACING)/2));

    if (!GetTextExtentPoint32(hDC, szPan, strlen(szPan), &Size))
	{
	nError = -1;
	goto DONE_ROUTINE;
	}


    // Make the pan text there.
    if ((pFileInfo->hWndPan_TXT = CreateWindow(
	"STATIC", 
	szPan, 
	WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP, 
	cox,
	coy,
	DX_PAN_TXT, 
	Size.cy,                
	hWnd, 
	(HMENU)0, 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   

    coy += Size.cy;

    // Make the pan trackbar.
    if ((pFileInfo->hWndPan_TB = CreateWindow(
	TRACKBAR_CLASS, 
	"", 
	WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_BOTH, 
	cox,
	coy,
	DX_PAN_TB, 
	DY_PAN_TB,              
	hWnd, 
	(HMENU)(idBase+idPanTB), 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   

    SendMessage(pFileInfo->hWndPan_TB, TBM_SETRANGE, FALSE, MAKELONG(MINPAN_TB, MAXPAN_TB)); 
    SendMessage(pFileInfo->hWndPan_TB, TBM_SETPOS, TRUE, dwPan);
	SendMessage(pFileInfo->hWndPan_TB, TBM_SETPAGESIZE, 0, PANPAGE );
    pFileInfo->dwPan = dwPan;


    coy += DY_PAN_TB + DY_VOLSPACING;

    if ((pFileInfo->hWndPan_EDGE = CreateWindow(
	"STATIC", 
	"", 
	WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ, 
	cox,
	coy - (DY_VOLSPACING+DY_TEXTSPACING)/2,
	DX_LINEEDGE, 
	DY_LINEEDGE,                
	hWnd, 
	(HMENU)0, 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   

    //Adjusts the relative position of the Text
    coy -= (((DY_PANSPACING+DY_TEXTSPACING)/2)-((DY_LOOPEDSPACING+DY_TEXTSPACING)/2));

    if (!GetTextExtentPoint32(hDC, szVolume, strlen(szVolume), &Size))
	{
	nError = -1;
	goto DONE_ROUTINE;
	}

    // Make the volume text there.
    if ((pFileInfo->hWndVol_TXT = CreateWindow(
	"STATIC", 
	szVolume, 
	WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP, 
	cox,
	coy,
	DX_VOL_TXT, 
	Size.cy,                
	hWnd, 
	(HMENU)idBase, 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   

    coy += Size.cy;

    // Make the volume trackbars.
    // Create main volume bar.
    if ((pFileInfo->hWndVolM_TB = CreateWindow(
	TRACKBAR_CLASS, 
	"", 
	WS_CHILD | WS_VISIBLE | TBS_VERT | TBS_BOTH, 
	cox,
	coy,
	DX_VOL_TB, 
	DY_VOL_TB,              
	hWnd, 
	(HMENU)(idBase+idVolMTB), 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   

    SendMessage(pFileInfo->hWndVolM_TB, TBM_SETRANGE, FALSE, MAKELONG(MINVOL_TB, MAXVOL_TB)); 
    SendMessage(pFileInfo->hWndVolM_TB, TBM_SETPOS, TRUE, dwVol);
    pFileInfo->dwVol = MAXVOL_TB - dwVol;



    // Now the left volume.
    if ((pFileInfo->hWndVolL_TB = CreateWindow(
	TRACKBAR_CLASS, 
	"", 
	WS_CHILD | WS_VISIBLE |WS_DISABLED| TBS_VERT | TBS_BOTH, 
	cox+DX_VOL_TB+DX_VOLSPACING_TB,
	coy,
	DX_VOL_TB, 
	DY_VOL_TB,              
	hWnd, 
	(HMENU)(idBase+idVolLTB), 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   

    SendMessage(pFileInfo->hWndVolL_TB, TBM_SETRANGE, FALSE, MAKELONG(MINVOL_TB, MAXVOL_TB)); 
    SendMessage(pFileInfo->hWndVolL_TB, TBM_SETPOS, TRUE, MAXVOL_TB);

    if ((pFileInfo->hWndVolL_TXT = CreateWindow(
	"STATIC", 
	"L", 
	WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP, 
	cox+DX_VOL_TB*3/2+DX_VOLSPACING_TB/2,
	coy+DY_VOL_TB+DY_VOLSPACINGY,
	DX_VOLUMECHAR, 
	Size.cy,                
	hWnd, 
	(HMENU)0, 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   


    // And right volume.
    if ((pFileInfo->hWndVolR_TB = CreateWindow(
	TRACKBAR_CLASS, 
	"", 
	WS_CHILD | WS_VISIBLE | WS_DISABLED | TBS_VERT | TBS_BOTH, 
	cox+DX_VOL_TB*2+DX_VOLSPACING_TB*2,
	coy,
	DX_VOL_TB, 
	DY_VOL_TB,              
	hWnd, 
	(HMENU)(idBase+idVolRTB), 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   

    SendMessage(pFileInfo->hWndVolR_TB,
	    TBM_SETRANGE, FALSE, MAKELONG(MINVOL_TB, MAXVOL_TB)); 
    SendMessage(pFileInfo->hWndVolR_TB,
	    TBM_SETPOS, TRUE, MAXVOL_TB);

    if ((pFileInfo->hWndVolR_TXT = CreateWindow(
	"STATIC", 
	"R", 
	WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP, 
	cox+DX_VOL_TB*5/2+DX_VOLSPACING_TB/2+2,
			    // +2 to look nice.
	coy+DY_VOL_TB+DY_VOLSPACINGY,
	DX_VOLUMECHAR, 
	Size.cy,                
	hWnd, 
	(HMENU)0, 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   

 
    coy += DY_VOL_TB + DY_BEFOREFIRSTBUTTON;    //Line under L & R

    if ((pFileInfo->hWndVol_EDGE = CreateWindow(
	"STATIC", 
	"", 
	WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ, 
	cox,
	coy - (DY_BEFOREFIRSTBUTTON)/2,
	DX_LINEEDGE, 
	DY_LINEEDGE,                
	hWnd, 
	(HMENU)0, 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   



    if (!GetTextExtentPoint32(hDC, szPlay, strlen(szPlay), &Size))
	{
	nError = -1;
	goto DONE_ROUTINE;
	}


    //Create Play Button
    if ((pFileInfo->hWndPlay_BN = CreateWindow(
	"BUTTON", 
	szPlay, 
	WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 
	cox,
	coy,
	DX_BUTTONSPACING, 
	Size.cy + DY_BUTTONSPACING,             
	hWnd, 
	(HMENU)(idBase+idPlayBN), 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}       

    //coy += Size.cy + DY_BUTTONSPACING + DY_BETWEENBUTTONS;
    
    if (!GetTextExtentPoint32(hDC, szPlay, strlen(szPlay), &Size))
	{
	nError = -1;
	goto DONE_ROUTINE;
	}
     
    //Make Remove button
    if ((pFileInfo->hWndRemove_BN = CreateWindow(
	"BUTTON", 
	szRemove, 
	WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 
	cox + DX_BUTTONSPACING + DY_BETWEENBUTTONS,
	coy,
	DX_BUTTONSPACING, 
	Size.cy + DY_BUTTONSPACING,             
	hWnd, 
	(HMENU)(idBase+idRemoveBN), 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}       

    coy += Size.cy + DY_BUTTONSPACING+ DY_BETWEENBUTTONS;


    //Set up Looped Checkbox 
    if (!GetTextExtentPoint32(hDC, szLooped, strlen(szLooped), &Size))
	{
	nError = -1;
	goto DONE_ROUTINE;
	}

    //Create Looped Checkbox window
    if ((pFileInfo->hWndLooped_BN = CreateWindow(
	"BUTTON", 
	szLooped, 
	WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, 
	cox,
	coy,
	DX_LOOPED_TXT, 
	Size.cy + DY_TEXTSPACING -2,               
	hWnd, 
	(HMENU)(idBase+idLoopedBN), 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}       
	  

    // Don't need the between buttons spacing
    //  because there are no more controls.
    coy += Size.cy;// + DY_BUTTONSPACING; //+ DY_BETWEENBUTTONS;

    if ((pFileInfo->hWndWhole_EDGE = CreateWindow(
	"STATIC", 
	"", 
	WS_CHILD | WS_VISIBLE | SS_ETCHEDFRAME, 
	coxOld-DX_FRAMEEDGE,
	coyOld-DY_FRAMEEDGE,
	DX_CONTROLSPACING-DX_FRAMEEDGEINNER, 
	coy - coyOld + DY_FRAMEEDGE*2,              
	hWnd, 
	(HMENU)0, 
	hInst, 
	NULL)) == NULL)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}   

    
    SetAllText(pFileInfo);
    UpdateLRVolume(pFileInfo);

    
DONE_ROUTINE:   
    if (hDC != NULL)
    {
	if (ReleaseDC(hWnd, hDC) == 0)
	{
	nError = -1;
	goto DONE_ROUTINE;
	}
    }

    return(nError);

}

// =======================================================================
/*  This will add to the linked list of FileInfo's.
    The FileInfo's keep track of the
    files loaded, and this is done in a linked list format

    Input:
	pFileInfoHead   -   Top of linked list.
	pFileInfo   -   Pointer to entry to add.

    Output:
	0 if successful, else the error code.

*/      
// =======================================================================
int AddToList(FILEINFO *pFileInfoHead, FILEINFO *pFileInfo)
{
    pFileInfo->pNext = NULL;    
    pFileInfo->fPlaying = FALSE;

    while (pFileInfoHead->pNext != NULL)
	{
	pFileInfoHead = pFileInfoHead->pNext;
	}

    pFileInfoHead->pNext = pFileInfo;

    return(0);

}

// =======================================================================
/*  This routine will get the number of controls in the window.
    Can be used to determine new size of window.

    Input:
	pFileInfoHead           -   Header of linked list.

    Output:
	# of controls.
*/
// =======================================================================
int GetNumControls( FILEINFO *pFileInfoHead )
{

    int cT  = 0;

    while (pFileInfoHead->pNext != NULL)
    {
    pFileInfoHead = pFileInfoHead->pNext;
    cT++;
    }

    return(cT);

}

// =======================================================================
/*  This routine will free the whole linked list in pFileInfoFirst,
    including all the
    memory used by the wave file, waveformatex structure, etc.
*/
// =======================================================================
int FreeAllList(HWND hWnd, FILEINFO *pFileInfoFirst)
{

    FILEINFO        *pFileInfo, *pFileNext;
    UINT        cT;

    Assert(pFileInfoFirst != NULL);
    pFileInfo = pFileInfoFirst->pNext;

    while (pFileInfo != NULL)
    {
    ReleaseDirectSoundBuffer(pFileInfo);
    GlobalFreePtr(pFileInfo->pwfx);
    GlobalFreePtr(pFileInfo->pbData);
    pFileNext = pFileInfo->pNext;
    GlobalFreePtr(pFileInfo);
    pFileInfo = pFileNext;
    }

    for (cT=0; cT<MAXCONTROLS; cT++)
    rgfcoxAvail[cT] = FALSE;



    return(0);          


}

// =======================================================================
/*  This routine will remove an entry from the list, i.e. will remove
    pFileInfo and all its allocated memory from the list pointed by the header
    by pFileInfoHead

    Input:
	pFileInfo               -   Pointer to entry to remove.
	pFileInfoHead           -   Head, first entry.

    Output:
	0 if successful, else the error.

*/
// =======================================================================
int RemoveFromList(FILEINFO *pFileInfo, FILEINFO *pFileInfoHead)
{

    FILEINFO        *pFileNext;

    Assert(pFileInfoHead != NULL);

    // This used to be pFileInfoHead != NULL
    while (pFileInfoHead->pNext != NULL)
	{
	if (pFileInfoHead->pNext == pFileInfo)
	    {
	    Assert(pFileInfo->cox/DX_CONTROLSPACING < MAXCONTROLS);
	    rgfcoxAvail[pFileInfo->cox/DX_CONTROLSPACING] = FALSE;
	   
	    DestroyWindow(pFileInfo->hWndFileName_TXT); 
	    DestroyWindow(pFileInfo->hWndFreq_TB);      
	    DestroyWindow(pFileInfo->hWndFreq_TXT);     
	    DestroyWindow(pFileInfo->hWndPan_TB);           
	    DestroyWindow(pFileInfo->hWndPan_TXT);      
	    DestroyWindow(pFileInfo->hWndVol_TXT);      
	    DestroyWindow(pFileInfo->hWndVolL_TB);      
	    DestroyWindow(pFileInfo->hWndVolR_TB);      
	    DestroyWindow(pFileInfo->hWndVolM_TB);      
	    DestroyWindow(pFileInfo->hWndLooped_BN);        
	    DestroyWindow(pFileInfo->hWndPlay_BN);      
	    DestroyWindow(pFileInfo->hWndRemove_BN);
	    DestroyWindow(pFileInfo->hWndFileName_EDGE);
	    DestroyWindow(pFileInfo->hWndLooped_EDGE);  
	    DestroyWindow(pFileInfo->hWndFreq_EDGE);        
	    DestroyWindow(pFileInfo->hWndPan_EDGE);     
	    DestroyWindow(pFileInfo->hWndVol_EDGE);     
	    DestroyWindow(pFileInfo->hWndWhole_EDGE);       
	    DestroyWindow(pFileInfo->hWndVolL_TXT);     
	    DestroyWindow(pFileInfo->hWndVolR_TXT);     
	    #ifdef SHOWSTATUS
	    DestroyWindow(pFileInfo->hWndStatus_TXT);
	    DestroyWindow(pFileInfo->hWndStatus_EDGE);
	    DestroyWindow(pFileInfo->hWndPlayPosition_TXT);
	    DestroyWindow(pFileInfo->hWndPlayPosition_EDGE);
	    DestroyWindow(pFileInfo->hWndWritePosition_TXT);
	    DestroyWindow(pFileInfo->hWndWritePosition_EDGE);
	    #endif




	    GlobalFree(pFileInfoHead->pNext->pwfx);
	    GlobalFree(pFileInfoHead->pNext->pbData);
	    pFileNext = pFileInfoHead->pNext->pNext;
	    GlobalFreePtr(pFileInfoHead->pNext);
	    pFileInfoHead->pNext = pFileNext;                                                         
	    break;
	    }
	pFileInfoHead = pFileInfoHead->pNext;
	}

    return(0);
}

// =======================================================================
/*  This will pop up the open file dialog and allow the user to pick one file. 
    
    Input:  
	hWnd            -   Handle of parent window.
	pszFileName         -   String to store filename in, must be at least MAX_PATH long.


    Output:
	TRUE if a file was  picked successfully, else FALSE (user didn't pick a file)

 */
// =======================================================================
BOOL OpenFileDialog(HWND hWnd, LPSTR pszFileName, int *nFileName, LPBOOL lpfSticky)
{
    BOOL            fReturn,
		    fValid;
    OPENFILENAME    ofn;                

    pszFileName[0]          = 0;

    ofn.lStructSize         = sizeof(ofn);
    ofn.hwndOwner           = hWnd;
    ofn.hInstance           = hInst;
    ofn.lpstrFilter         = "Wave Files\0*.wav\0All Files\0*.*\0\0";
    ofn.lpstrCustomFilter   = NULL;
    ofn.nMaxCustFilter      = 0;
    ofn.nFilterIndex        = 1;
    ofn.lpstrFile           = pszFileName;
    ofn.nMaxFile            = MAX_PATH;
    ofn.lpstrFileTitle      = NULL;
    ofn.nMaxFileTitle       = 0;
    ofn.lpstrInitialDir     = gszCDStartPath;
    ofn.lpstrTitle          = "File Open";
    ofn.Flags               = OFN_FILEMUSTEXIST | OFN_EXPLORER
                                | OFN_ENABLETEMPLATE | OFN_ENABLEHOOK | OFN_HIDEREADONLY;
    ofn.nFileOffset         = 0;
    ofn.nFileExtension      = 0;
    ofn.lpstrDefExt         = "wav";
    ofn.lCustData           = (LONG)lpfSticky;
    ofn.lpfnHook            = FileOpenCustomTemplateDlgProc;
    ofn.lpTemplateName      = MAKEINTRESOURCE(IDD_FILEOPEN_NEST);
				
    fValid = FALSE;
    do   {    
	
    if (fReturn = GetOpenFileName(&ofn))
    {                               
	// Set the start path for the next time this dialog is opened
        lstrcpy( gszCDStartPath, pszFileName );
        gszCDStartPath[ofn.nFileOffset] = '\0';

	fValid = IsValidWave(pszFileName);
	if (!fValid)
	{
	MessageBox(hWnd, "Wave files must be PCM format!",
	       "Invalid Wave File", MB_OK|MB_ICONSTOP);
	}
	else
	*nFileName = ofn.nFileOffset;
    }
    else fValid = TRUE;         // Force break out of loop.
    
    } while (!fValid);

    return(fReturn);     

}

// =======================================================================
/*  This function will determine if the filename passed
    in is a valid wave for this
    app, that is a PCM wave.

    Input:
	pszFileName -   FileName to check.

    Output:
	FALSE if not a valid wave, TRUE if it is.
    
*/
// =======================================================================
BOOL IsValidWave(LPSTR pszFileName)
{ 
    BOOL            fReturn     = FALSE;
    int             nError      = 0;
    HMMIO           hmmio;
    MMCKINFO        mmck;
    WAVEFORMATEX    *pwfx;

    if ((nError = WaveOpenFile(pszFileName, &hmmio, &pwfx, &mmck)) != 0)
	{       
	goto ERROR_IN_ROUTINE;
	}

    if (pwfx->wFormatTag != WAVE_FORMAT_PCM) 
	{
	goto ERROR_IN_ROUTINE;
	}

    WaveCloseReadFile(&hmmio, &pwfx);

    fReturn = TRUE;

ERROR_IN_ROUTINE:
    return(fReturn);    

}

// =======================================================================
// =======================================================================
BOOL UIMainWindowVSBHandler(HWND hWnd, WPARAM wParam, LPARAM lParam)
{

    FILEINFO    *pFileInfo;
    BOOL        fReturn             = FALSE;

    pFileInfo = FileInfoFirst.pNext;

    Assert(pFileInfo != NULL);

    while (pFileInfo != NULL)
    {

    if ((HWND)lParam == pFileInfo->hWndVolM_TB)
    {
	pFileInfo->dwVol = MAXVOL_TB -
	SendMessage(pFileInfo->hWndVolM_TB, TBM_GETPOS, 0, 0);
	ChangeOutputVol(pFileInfo);
	SetAllText(pFileInfo);
	UpdateLRVolume(pFileInfo);
	fReturn = TRUE;
    }

    pFileInfo = pFileInfo->pNext;
    
    }

    return (fReturn);

}


// =======================================================================
/*  This routine will handle all the calls to the WM_HSCROLL
    for the main window, that
    is, all the horizontal scrollbar (and trackbar) messages.

    Input:
	Standard parameters (minus the "message" parameter)
	for a window callback, though
	this is called from the window callback.

    Output:
	FALSE if the message isn't processed, else TRUE if it is.
	If FALSE, the
	return procedure should call the default windows procedure.
*/
// =======================================================================
BOOL UIMainWindowHSBHandler(HWND hWnd, WPARAM wParam, LPARAM lParam)
{

    FILEINFO    *pFileInfo;
    BOOL        fReturn             = FALSE;

    pFileInfo = FileInfoFirst.pNext;
    
    Assert(pFileInfo != NULL);

    while (pFileInfo != NULL)
    {

    if ((HWND)lParam == pFileInfo->hWndFreq_TB)
    {
	pFileInfo->dwFreq = (SendMessage(pFileInfo->hWndFreq_TB,
			TBM_GETPOS, 0, 0) * FREQFACTOR) - FREQADD;
	ChangeOutputFreq(pFileInfo);
	SetAllText(pFileInfo);          
	fReturn = TRUE;
    }

    else if ((HWND)lParam == pFileInfo->hWndPan_TB)
    {
	pFileInfo->dwPan = SendMessage(pFileInfo->hWndPan_TB,
		       TBM_GETPOS, 0, 0);
	ChangeOutputPan(pFileInfo);
	SetAllText(pFileInfo);
	UpdateLRVolume(pFileInfo);
	fReturn = TRUE;
    }

    pFileInfo = pFileInfo->pNext;
    
    }

    return (fReturn);
	


}

// =======================================================================
/*  This routine will handle all the calls to the WM_COMMAND
    for the main window.

    Input:
    Standard parameters (minus the "message" parameter)
    for a window callback, though
    this is called from the window callback.

    Output:
	FALSE if the message isn't processed, else TRUE if it is.
	If FALSE, the
	return procedure should call the default windows procedure.
	

*/
// =======================================================================
BOOL UIMainWindowCMDHandler(HWND hWnd, WPARAM wParam, LPARAM lParam)
{

    BOOL        fReturn     = FALSE;
    FILEINFO        *pFileInfo;
    FILEINFO        *pFileInfoNext;
    DWORD       dwLooping;

    pFileInfo = FileInfoFirst.pNext;
    while (pFileInfo != NULL)
    {
    
    pFileInfoNext = pFileInfo->pNext;
    
    if ((HWND)lParam == pFileInfo->hWndLooped_BN)
    {
	pFileInfo->fLooped = SendMessage(pFileInfo->hWndLooped_BN,
			 BM_GETCHECK, 0, 0);
	// If it is playing then reset the looping to be proper
	if( pFileInfo->fPlaying ) {
	if( pFileInfo->fLooped ) {
	    dwLooping = DSBPLAY_LOOPING;
	} else {
		    dwLooping = 0;
	}
		pFileInfo->pDSB->lpVtbl->Play(pFileInfo->pDSB,
						0, 0, dwLooping );
	} 
	fReturn = TRUE;
    }
    else if ((HWND)lParam == pFileInfo->hWndPlay_BN)
    {
	if (pFileInfo->fPlaying)
	{
	if (StopDSound(hWnd, pFileInfo) == 0)
	{
	    SendMessage((HWND)lParam,
		WM_SETTEXT, 0, (LPARAM)(LPCTSTR)szPlay);

#ifdef SHOWSTATUS
	    UpdateStatus(pFileInfo, 0);
#endif
	    
	    fReturn = TRUE;
	    break;
	}
	
	}
	else            
	{
	if (StartDSound(hWnd, pFileInfo) == 0)
	{
	    SendMessage((HWND)lParam,
		WM_SETTEXT, 0, (LPARAM)(LPCTSTR)szStop);
#ifdef SHOWSTATUS
	    UpdateStatus(pFileInfo, DSBSTATUS_PLAYING);
#endif
		    
	    fReturn = TRUE;
	    break;
	}
	
	}
	fReturn = TRUE;
    }
	
    else if ((HWND)lParam == pFileInfo->hWndRemove_BN)
    {
	ReleaseDirectSoundBuffer(pFileInfo);
	RemoveFromList(pFileInfo, &FileInfoFirst);
        UpdateMainStatus();
	
	fReturn = TRUE;
    }
	

    pFileInfo = pFileInfoNext;
    
    }
    
    if (!fReturn)
    {

    switch(wParam)
    {

	case IDPD_FILE_EXIT:    
	PostMessage(hWnd, WM_CLOSE, 0, 0);
	break;

	case IDPD_FILE_OPEN:
	PD_FileOpen(hWnd);
	break;
	    
	case IDPD_HELP_ABOUT:
	DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUT),
	      hWnd, (DLGPROC)DLGHelpAbout);
	break;

	case IDPD_OPTIONS_OUTPUTTYPE:
	DialogBox(hInst, MAKEINTRESOURCE(IDD_OUTPUTBUFFERTYPE),
	      hWnd, (DLGPROC)DLGOutputBufferType);
	break;

	case IDPD_CHECKLATENCY:
	StopAllDSounds(hWnd, &FileInfoFirst);
	// Now fake that we're on in each voice so the
	//timer will update the 
	// strings in the window.
	pFileInfo = FileInfoFirst.pNext;
	while (pFileInfo != NULL)
	{                                           
	    pFileInfo->fPlaying = TRUE;
	    pFileInfo = pFileInfo->pNext;       
	}

	DialogBox(hInst, MAKEINTRESOURCE(IDD_CHECKLATENCY),
	      hWnd, (DLGPROC)DLGCheckLatency);
	break;

	    case IDPD_ENUMDRIVERS:
		fEnumDrivers = !fEnumDrivers;
		if( fEnumDrivers )
		    {
		    MessageBox( hWnd,
		    "Drivers will not be enumerated until DSSHOW is run again.",
		    szAppName, MB_OK );
		    }
		break;

	default:
	return(FALSE);
	
    }
    }

    return(TRUE);


}

// =======================================================================
/*  This routine will handle the timer messages.

    Input:
	Standard input.

    Output: 
	TRUE if processed message, otherwise FALSE
*/
// =======================================================================
BOOL UIMainWindowTimerHandler(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    FILEINFO        *pFileInfo;
    BOOL            fReturn             = FALSE;
    DWORD           dwStatus            = 0;

    for (pFileInfo = FileInfoFirst.pNext; pFileInfo != NULL; pFileInfo = pFileInfo->pNext)
    {
    HRESULT hr;

    hr = IDirectSoundBuffer_GetStatus(pFileInfo->pDSB, &dwStatus);
    if (DS_OK != hr) continue;

    if (dwStatus & DSBSTATUS_BUFFERLOST) {
	LPBYTE pbData, pbData2;
	DWORD  dwLength, dwLength2;
	
	//
	//  Restore the buffer, rewrite data, and play
	//
	hr = IDirectSoundBuffer_Restore(pFileInfo->pDSB);
	if (DS_OK == hr) {

	hr = IDirectSoundBuffer_Lock(pFileInfo->pDSB, 0,
			 pFileInfo->cbSize,
			 &pbData, &dwLength,
			 &pbData2, &dwLength2,
			 0);

	if (DS_OK == hr) {
	
	    Assert(pbData != NULL);
	    Assert(pFileInfo->pbData != NULL);
	    memcpy(pbData, pFileInfo->pbData, pFileInfo->cbSize);

	    hr = IDirectSoundBuffer_Unlock(pFileInfo->pDSB,
				       pbData, dwLength,
				       NULL, 0);

	    if (DS_OK == hr) {

	    if (pFileInfo->fPlaying) {
		if (pFileInfo->fLooped) {
		IDirectSoundBuffer_Play( pFileInfo->pDSB, 0, 0,
					 DSBPLAY_LOOPING );
		} else {
		IDirectSoundBuffer_Play( pFileInfo->pDSB, 0, 0,
					 0 );
		}
	    }

	    IDirectSoundBuffer_GetStatus(pFileInfo->pDSB, &dwStatus);

	    }
	}
	}
    }

#ifdef SHOWSTATUS
    UpdateStatus(pFileInfo, dwStatus);
#endif

    if (!(dwStatus & DSBSTATUS_BUFFERLOST))
    {
	if ((pFileInfo->fPlaying) && (!(dwStatus & DSBSTATUS_PLAYING)) )
	{
	if (StopDSound(hWnd, pFileInfo) == 0)
	{
	    SendMessage(pFileInfo->hWndPlay_BN,
		WM_SETTEXT, 0, (LPARAM)(LPCTSTR)szPlay);
	}
	}
    }

    pFileInfo->fLost = dwStatus & DSBSTATUS_BUFFERLOST;

    fReturn = TRUE;
    }

    return (fReturn);

}   



// =======================================================================
/*  This routine will start a sound to be played.  
    Input:
	hWnd        -   Of parent window.
	pFileInfo   -   Pointer to file to start,
		which is loaded and the
		data is filled in the structure,
		such as pbData, 
		etc.

    Output:
	0 if successful, else the error code.
*/
// =======================================================================
int StartDSound(HWND hWnd, FILEINFO *pFileInfo)
{
    HRESULT     hr              = 0;
    DWORD           dwLooped;
    DWORD           dwStatus                = 0;

    // Already playing?

    // Start sound here....
    dwLooped = 0;
    if (pFileInfo->fLooped) {
    dwLooped = DSBPLAY_LOOPING;
    }
		    

    if ((hr = pFileInfo->pDSB->lpVtbl->GetStatus(pFileInfo->pDSB,
			 &dwStatus)) == 0)
    {
    if ((dwStatus&DSBSTATUS_PLAYING) == DSBSTATUS_PLAYING)
    {
	// Don't bother playing, just restart
	if ((hr = pFileInfo->pDSB->lpVtbl->SetCurrentPosition(
	    pFileInfo->pDSB, 0)) != 0)
	{
	MessageBox(hWnd, "Cannot set current position",
	       "Direct Sound Error", MB_OK);
	}
    }
    // Yes gotos are bad but this is real life not school.
    else goto PLAY_THE_THING;           
    }
    
    else
    {
PLAY_THE_THING:
    if ((hr = pFileInfo->pDSB->lpVtbl->Play(pFileInfo->pDSB,
					    0, 0, dwLooped)) != 0)
    {
    MessageBox(hWnd, "Cannot start playing",
	   "Direct Sound Error", MB_OK);
    }
    else
    pFileInfo->fPlaying = TRUE;
    }

    return(hr);


}

// =======================================================================
/*  This routine will stop a sound which is playing.

    Input:
	hWnd        - Of parent window.
	pFileInfo       - Pointer to file to stop playing.

    Output:
	0 if successful, else the error code.

*/
// =======================================================================
int StopDSound(HWND hWnd, FILEINFO *pFileInfo)
{
    HRESULT     hr          = 0;

    if (!pFileInfo->fPlaying)
    return(0);
	   

    // Stop sound here...
    if ((hr = pFileInfo->pDSB->lpVtbl->Stop(pFileInfo->pDSB)) != 0) 
    {
    MessageBox(hWnd, "Cannot stop sound",
	   "Direct Sound Error", MB_OK);        
    }
    else
    pFileInfo->fPlaying = FALSE;    

    return(hr);

}

// =======================================================================
/*  This routine will stop all the sounds which are playing.

    Input:
	hWnd        - Of parent window.
	pFileInfo   - Pointer to file to stop playing.
		(i.e. the head)

    Output:
	0 if successful, else the error code.

*/
// =======================================================================
int StopAllDSounds(HWND hWnd, FILEINFO *pFileInfo)
{
    while (pFileInfo->pNext != NULL)
	{
	StopDSound(hWnd, pFileInfo->pNext);
	pFileInfo = pFileInfo->pNext;       
	}

    return(0);

}



// =======================================================================
/*  This routine will set the freq, vol and pan slider text
    according to the value 
    passed in.

    Input:
	pFileInfo   -   File pointer to set frequency for.

    The dwFreq in the pFileInfo structure must be set.
    This also uses the window handle
    in the pFileInfo structure.
    
    Output:
	None.
*/
// =======================================================================
void SetAllText(FILEINFO    *pFileInfo)
{
    char            szBufT[128];

    sprintf(szBufT, "%s: %lu Hz     ",
	szFreq, pFileInfo->dwFreq);
    SetWindowText(pFileInfo->hWndFreq_TXT, szBufT);

    // Change PAN val to show full range
    sprintf(szBufT, "%s: %ld", szPan,
	(((LONG)(pFileInfo->dwPan) + SHIFTPAN_TB) * MULTPAN_TB ) );
    SetWindowText(pFileInfo->hWndPan_TXT, szBufT);

    // Change VOLUME val to show full range
    sprintf(szBufT, "%s: %ld", szVolume,
	(((LONG)(pFileInfo->dwVol) + SHIFTVOL_TB) * MULTVOL_TB ));
    SetWindowText(pFileInfo->hWndVol_TXT, szBufT);


}

// =======================================================================
/*  This routine will update the left and right
    volume according to main volume 
    and pan.

    Input:
	pFileInfo   - Pointer to fileinfo to update.

    Output:
	Nothing worth using.
*/
// =======================================================================
void UpdateLRVolume(FILEINFO *pFileInfo)
{

    int             volLeft, volRight;

    if (pFileInfo->dwPan < MIDPAN_TB)
    {
    volLeft = pFileInfo->dwVol;
    volRight = (((int)pFileInfo->dwPan)
	    *(int)pFileInfo->dwVol)/((int)MIDPAN_TB);
    }
    else
    {
    volLeft = ((((int)pFileInfo->dwPan - MAXPAN_TB)*-1)
	   *(int)pFileInfo->dwVol)/((int)MIDPAN_TB);
    volRight = pFileInfo->dwVol;
    }

	

    SendMessage(pFileInfo->hWndVolL_TB, TBM_SETPOS, TRUE, MAXVOL_TB-volLeft);
    SendMessage(pFileInfo->hWndVolR_TB, TBM_SETPOS, TRUE, MAXVOL_TB-volRight);
    
	

}

// =======================================================================
/*  This will change the output panning position for a certain FILEINFO.
    This is 
    done by sending messages to the direct sound driver 

    Input:  
	pFileInfo   -   FileInfo to set.  This must contain the
		panning value to set.

    Output:
	0 if successful, else the error code.

*/
// =======================================================================
int ChangeOutputPan(FILEINFO *pFileInfo)
{

    HRESULT     hr      = 0;

    // Change PAN val  since TB does not go full range
    if ((hr = pFileInfo->pDSB->lpVtbl->SetPan(pFileInfo->pDSB,
	(((pFileInfo->dwPan) + SHIFTPAN_TB) * MULTPAN_TB) )) != 0)
    {
	goto ERROR_DONE_ROUTINE;
    }

ERROR_DONE_ROUTINE:
    return(hr);

}

// =======================================================================
/*  This will change the output freq for a certain FILEINFO.  This is 
    done by sending messages to the direct sound driver 

    Input:  
	pFileInfo                   -   FileInfo to set.  This must contain the
					freq value to set.

    Output:
	0 if successful, else the error code.

*/
// =======================================================================
int ChangeOutputFreq(FILEINFO *pFileInfo)
{

    HRESULT     hr      = 0;

    if ((hr = pFileInfo->pDSB->lpVtbl->SetFrequency(pFileInfo->pDSB, pFileInfo->dwFreq)) != 0)
	{
	goto ERROR_DONE_ROUTINE;
	}

ERROR_DONE_ROUTINE:
    return(hr);

}



// =======================================================================
/*  This will change the output vol for a certain FILEINFO.  This is 
    done by sending messages to the direct sound driver 

    Input:  
	pFileInfo                   -   FileInfo to set.  This must contain the
					freq value to set.

    Output:
	0 if successful, else the error code.

*/
// =======================================================================
int ChangeOutputVol(FILEINFO *pFileInfo)
{

    HRESULT     hr      = 0;

    // Shift VOLUME val by 4 bits since TB does not go full range
    if ((hr = pFileInfo->pDSB->lpVtbl->SetVolume(pFileInfo->pDSB,
	(((pFileInfo->dwVol) + SHIFTVOL_TB) * MULTVOL_TB) )) != 0)
	{
	goto ERROR_DONE_ROUTINE;
	}

ERROR_DONE_ROUTINE:
    return(hr);

}


// =======================================================================
/*  This is the dialog box handler for the check latency dialog box.

    Input:
	Standard dialog box input.

    Output:
	Standard dialog box output.

*/
// =======================================================================
long FAR PASCAL DLGCheckLatency(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{

    static HWND     hWndFiles_LB;
    FILEINFO        *pFileInfo              = NULL;
    int         nSelected;
    int         cT;


    switch(uMsg)
    {
    case WM_INITDIALOG:
	hWndFiles_LB = GetDlgItem(hWnd, IDC_FILES_LB);
	
	pFileInfo = FileInfoFirst.pNext;
	while (pFileInfo != NULL)
	{               
	SendMessage(hWndFiles_LB,
		LB_ADDSTRING,
		0,
		(LPARAM)(pFileInfo->szFileName
		     + pFileInfo->nFileName));
		pFileInfo = pFileInfo->pNext;       
	}

	break;      
	
    case WM_COMMAND:
	switch(wParam)
	{
	case ID_DONE:                   
	    PostMessage(hWnd, WM_CLOSE, 0, 0);
	    break;
	    
	case ID_PLAY:                       
	    if ((nSelected = SendMessage(hWndFiles_LB,
			 LB_GETCURSEL, 0, 0))
		!= LB_ERR)
	    {
	    for (cT=0, pFileInfo = FileInfoFirst.pNext;
		pFileInfo != NULL;
		pFileInfo = pFileInfo->pNext, cT++)
	    {
		if (cT == nSelected)
		{
		StartDSound(hWnd, pFileInfo);
		break;
		}
	    }
	    
	    }
		    
	    break;
		    
	case ID_STOP:
	    StopAllDSounds(hWnd, &FileInfoFirst);
	    break;
	    
	default:
	    break;
	    
	}
	break;

    case WM_CLOSE:
	StopAllDSounds(hWnd, &FileInfoFirst);
	EndDialog(hWnd, 0);
	break;

    default:
	return(0);
	break;               
	
    }
    
    return(1);

}


// =======================================================================
/*  The help about dialog procedure.  
    
    Input:
	Standard windows dialog procedure.

    Output:
	Standard windows dialog procedure.

*/
// =======================================================================
long FAR PASCAL DLGHelpAbout(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
    case WM_INITDIALOG:
	break;      
	
    case WM_COMMAND:
	switch(wParam)
	{
	case ID_OK:                 
	    PostMessage(hWnd, WM_CLOSE, 0, 0);
	    break;
	    
	default:
	    break;
	    
	}
	break;
	
    case WM_CLOSE:
	EndDialog(hWnd, 0);
	break;

    default:
	return(0);
	break;               

    }
    
    return(1);

}


// =======================================================================
/*  The help about dialog procedure.  
    
    Input:
	Standard windows dialog procedure.

    Output:
	Standard windows dialog procedure.

*/
// =======================================================================
long FAR PASCAL DLGOutputBufferType(HWND hWnd,UINT uMsg, WPARAM wParam, LPARAM lParam)
{

    static HWND     hWndFormats_LB          = NULL;
    int         cT;
    int         nSelection;

	     
    switch(uMsg)
    {
    case WM_INITDIALOG:
	// Get the windows we need.
	hWndFormats_LB = GetDlgItem(hWnd, IDC_FORMATS);
	
	// Put the strings in the list box.
	for (cT=0; cT<C_DROPDOWNPCMFORMATS; cT++)
	SendMessage(hWndFormats_LB,
		LB_ADDSTRING, 0, (LPARAM)rgszTypes[cT]);

	// Get the current format and highlight it in the list box.
	if ((nSelection = FormatToIndex(hWnd, &FileInfoFirst)) != LB_ERR)
	{
	SendMessage(hWndFormats_LB, LB_SETCURSEL, nSelection, 0);
	}


	break;      
	
    case WM_COMMAND:
	    switch(LOWORD(wParam))
	{
		case IDC_FORMATS:
		    if( HIWORD( wParam ) == LBN_DBLCLK )
			{
			SendMessage( hWnd, WM_COMMAND, MAKEWPARAM( ID_OK, 0 ),
									0L );
			}
		    break;

		case ID_OK:
	    if ((nSelection = SendMessage(hWndFormats_LB,
		LB_GETCURSEL, 0, 0)) != LB_ERR)
	    {
	    if (IndexToFormat(hWnd, &FileInfoFirst, nSelection)
		    == 0)
		PostMessage(hWnd, WM_CLOSE, 0, 0);
	    }
	    break;
		
	case ID_CANCEL:                 
	    PostMessage(hWnd, WM_CLOSE, 0, 0);
	    break;

	case ID_APPLY:                  
	    if ((nSelection = SendMessage(hWndFormats_LB,
		LB_GETCURSEL, 0, 0)) != LB_ERR)
	    IndexToFormat(hWnd, &FileInfoFirst, nSelection);
			
	    break;


	default:
	    break;

	}
	break;

    case WM_CLOSE:
	EndDialog(hWnd, 0);
	break;

    default:
	return(0);
	break;               

    }
		
    return(1);

}

// =======================================================================
/*  This routine will determine the output format in
    terms of an integer from the
    current output rate, type, etc.
    stored in the direct sound routines.   Integer
    values designate the string # in rgszTypes,
    i.e. index 0 is 8000kHz, 8 bit mono, 
    etc...

    Input:
	hWnd    - Handle of the current window.
	pFileInfo   - Pointer to the file info to retrieve the format for.

    Output:
	The index of the format, LB_ERR if undetermined.

*/
// =======================================================================
int FormatToIndex(HWND hWnd, FILEINFO *pFileInfo)
{

    WAVEFORMATEX    wfx;
    DWORD       dwWaveStyle;
    DWORD       dwSize;
    int         nError              = 0;

    // Get the format.
    if ((nError = pFileInfo->pDSB->lpVtbl->GetFormat(pFileInfo->pDSB,
	    &wfx, sizeof(wfx), &dwSize)) != 0)
    {
    goto ERROR_IN_ROUTINE;
    }
    if( dwSize > sizeof( wfx ) ) {
    nError = DSERR_GENERIC;
    goto ERROR_IN_ROUTINE;
    }


    // Change wfx to an integer.
    // Assume theres an error and check all parameters to 
    // see if its valid.
    nError = LB_ERR;
    dwWaveStyle = 0;

    if (wfx.wFormatTag != WAVE_FORMAT_PCM)
	   goto ERROR_IN_ROUTINE;

    // Check the channels
	switch (wfx.nChannels)
	{
	case 1:
		break;

	case 2:
	    dwWaveStyle |= 1;
		break;

    default:
		goto ERROR_IN_ROUTINE;
	}

    // Check the bits...
	switch (wfx.wBitsPerSample)
	{
	case 8:
		break;

	case 16:
		dwWaveStyle |= 2;
		break;

    default:
		goto ERROR_IN_ROUTINE;
    }

    // Check the rate.
	switch(wfx.nSamplesPerSec)
	{
	case 8000:
		break;

	case 11025:
	    dwWaveStyle |= 4;
		break;

	case 22050:
	    dwWaveStyle |= 8;
		break;

	case 44100:
	    dwWaveStyle |= 12;
		break;

    default:
		goto ERROR_IN_ROUTINE;
    }

    nError = (int)dwWaveStyle;

ERROR_IN_ROUTINE:
    return(nError);
}


// =======================================================================
/*  This will convert an index (from a list box for instance)
    to a format by passing
    in the format to direct sound.

    Input:
	hWnd        -   Handle to window.
	pFileInfo   -   Pointer to current file info.
	index       -   Index value to convert to a
		    waveformat structure.

    Output:
	0 if successful, else the error code.
*/
// =======================================================================
int IndexToFormat(HWND hWnd, FILEINFO *pFileInfo, int index)
{

    int         nError      = 0;


    pFileInfo->pwfx->wFormatTag = WAVE_FORMAT_PCM;

    pFileInfo->pwfx->nChannels = 2;                                     // Assume stereo.
    if ((index%2) == 0)
    pFileInfo->pwfx->nChannels = 1;                                 // Its mono.
	
    // Assume 16 bit    
    pFileInfo->pwfx->nBlockAlign = 2*pFileInfo->pwfx->nChannels;
    pFileInfo->pwfx->wBitsPerSample = 16;
    if ((index%4) < 2) {
    // Its 8 bit.
    pFileInfo->pwfx->nBlockAlign = 1*pFileInfo->pwfx->nChannels;
    pFileInfo->pwfx->wBitsPerSample = 8;
    }
    
    pFileInfo->pwfx->nSamplesPerSec = 44100;    // Assume 44.1 kHz
    if (index < 4)
	pFileInfo->pwfx->nSamplesPerSec = 8000;
    else if (index < 8)
	pFileInfo->pwfx->nSamplesPerSec = 11025;
    else if (index < 12)
	pFileInfo->pwfx->nSamplesPerSec = 22050;
    
    
    pFileInfo->pwfx->nAvgBytesPerSec = pFileInfo->pwfx->nSamplesPerSec *
		       pFileInfo->pwfx->nBlockAlign;                                        
    pFileInfo->pwfx->cbSize = 0;

    if ((nError = pFileInfo->pDSB->lpVtbl->SetFormat(pFileInfo->pDSB,
			pFileInfo->pwfx)) != DS_OK)         {
	MessageBox(hWnd, "Cannot set format buffer",
	   "Direct Sound Error", MB_OK);
    goto ERROR_DONE_ROUTINE;

    }

ERROR_DONE_ROUTINE:
    return(nError);

}


// =======================================================================
/* GetMediaStartPath()                                                      */
/*                                                                          */
/*   This helper function attempts to get the media directory for Direct3D, */
/* which is where all the installed DX wave files go. If it can't find that */
/* it settles for the media sub-directory of the Windows directory.         */
// =======================================================================
void GetMediaStartPath( void )
    {
    HKEY    hReg;
    DWORD   cbStartPathLen;

    if( ERROR_SUCCESS != RegOpenKeyEx( HKEY_LOCAL_MACHINE,
			gszRegKeyDirect3D,
			0, KEY_READ, &hReg ))
	{
        goto REG_OPEN_FAILED;
	}
    else
	{
	// Query the Registry for the path to the media directory
	cbStartPathLen = sizeof( gszCDStartPath );
	if( ERROR_SUCCESS != RegQueryValueEx( hReg, gszRegValueD3DPath,
				NULL, NULL,
				(LPBYTE)gszCDStartPath, &cbStartPathLen ))
	    {
	    goto REG_OPEN_FAILED;
	    }
	RegCloseKey( hReg );
	hReg = NULL;
	}

    return;

REG_OPEN_FAILED:
    // Start off by getting the Windows directory -- we're trying to build a
    // file path like "C:\WINDOWS\MEDIA", but the WINDOWS directory could be
    // named anything, so we must ask.
    GetWindowsDirectory( gszCDStartPath, sizeof(gszCDStartPath));
    // If there's no trailing backslash, append one
    if( lstrcmp( &gszCDStartPath[lstrlen(gszCDStartPath)], TEXT("\\") ))
	lstrcat( gszCDStartPath, TEXT("\\"));
    // Now add on the MEDIA part of the path
    lstrcat( gszCDStartPath, TEXT("MEDIA"));
    }


// =======================================================================
// fGetToken()
//
//    Parses the command-line string "in place" starting at pszStart.  A ptr
// to the start of the next token and it's length will be the out parameters,
// or NULL and 0 if no token.  Note that *ppszRet will NOT be NULL-terminated
// since the string is part of another string.  That's what then length is for.
//
// Returns: TRUE if a token was retrieved, or FALSE if there was no token.
// =======================================================================
BOOL fGetToken( PSTR pszStart, PSTR *ppszRet, int *pcchRet )
    {
    PSTR  pszCur = pszStart;
    PSTR  pszTokStart;

    if( !pszStart || NULL == ppszRet || NULL == pcchRet )
	return FALSE;

    // Skip leading whitespace
    while( *pszCur && (*pszCur == ' ' || *pszCur == '\t'))
	pszCur++;

    *ppszRet = NULL;
    *pcchRet = 0;

    if( *pszCur )
	{
	pszTokStart = pszCur;

	while( *pszCur && *pszCur != ' ' && *pszCur != '\t' )
	    pszCur++;

	*ppszRet = pszTokStart;
	*pcchRet = (int)(pszCur - pszTokStart);
	}

    if( *pcchRet != 0 )
	return TRUE;
    else
	return FALSE;
    }


// =======================================================================
// fMatchToken()
//
//    Attempts to match the first cchLen characters of pszDatum to the
// string at pszString.  The comparison is case-insensitive (this function
// is designed for command-line switch matching).
//
// Returns: TRUE if the first cchLen characters are a match, else FALSE.
// =======================================================================
BOOL fMatchToken( PSTR pszString, PSTR pszDatum, int cchLen )
    {
    int	i;

    for( i = 0; i < cchLen; i++ )
	{
	if( CharLower( (LPTSTR)MAKELONG( pszString[i], 0 ))
		    != CharLower( (LPTSTR)MAKELONG( pszDatum[i], 0 )))
	    return FALSE;
	}
    return TRUE;
    }


// =======================================================================
// ParseCommandLine()
//
//    Given a command-line string without the module name, this function will
// parse the command line and takes action on whatever it finds there.
//
// Returns: TRUE if successful, or FALSE if there was an error.
// =======================================================================
BOOL ParseCommandLine(LPSTR lpszCmdLine)
    {
    PSTR	pszCur,pszToken;
    PSTR	ppszFiles[MAXCONTROLS];
    BOOL	fStartPlaying = FALSE, fStartLooping = FALSE;
    int		cchTokLen = 0, i, nFilesFound;

    pszCur = lpszCmdLine;

    // First get all the command line switches
    while( fGetToken(pszCur, &pszToken, &cchTokLen) &&
	   (pszToken[0] == '/' || pszToken[0] == '-' ))
	{
	pszCur = pszToken + cchTokLen;
	pszToken++;

	if( fMatchToken( pszToken, "PLAY", 4 ))
	    {
	    fStartPlaying = TRUE;
	    }
	else if( fMatchToken( pszToken, "LOOP", 4 ))
	    {
	    fStartLooping = TRUE;
	    }
	else
	    {
	    // We don't recognize this mysterious switch, so eat it and move on
	    }
	}

    // Anything left on the command-line will be treated as a filename and
    // we'll attempt to open it after we've found them all
    nFilesFound = 0;
    while( fGetToken(pszCur, &pszToken, &cchTokLen) && nFilesFound < MAXCONTROLS )
	{
	pszCur = pszToken + cchTokLen;
	ppszFiles[nFilesFound] = GlobalAllocPtr( GPTR, (cchTokLen+1)*sizeof(char));
	// Copy the token out of the command-line string and into our buffer
	CopyMemory( ppszFiles[nFilesFound], pszToken, cchTokLen*sizeof(char));
	// Append a NULL terminator to what we just copied (to be safe)
	*(ppszFiles[nFilesFound] + cchTokLen) = 0;
	nFilesFound++;
	}
    // This function will take the array of strings we've created and open
    // each string as a file.  It will obey the global fStartPlaying and
    // fStartLooping flags we may have already set above
    if( nFilesFound )
	BatchOpenFiles( ppszFiles, nFilesFound, fStartPlaying, fStartLooping );

    // Free the space we allocated
    for( i = 0; i < nFilesFound; i++ )
	{
	GlobalFreePtr( ppszFiles[i] );
	ppszFiles[i] = NULL;
	}

    // Returning TRUE means the caller should continue doing what they
    // were doing: we succeeded.
    return TRUE;
    }


// =======================================================================
// BatchOpenFiles()
//
//    Takes an array of string pointers and tries to open each as a file to
// playback.  If fPlay is TRUE, the files will be played as they are being
// opened.  If fLoop is TRUE, they will also be set to loop.
//
// Returns: FALSE in the event of catastrophic failure, otherwise TRUE.
// =======================================================================
BOOL BatchOpenFiles( PSTR *ppszFiles, int nFiles, BOOL fPlay, BOOL fLoop )
    {
    int i;
    FILEINFO *pfi;
    DWORD cSamples;

    // Cap the number of files we can load out of the given set if we'd load
    // too many otherwise
    if( GetNumControls(&FileInfoFirst) + nFiles > MAXCONTROLS )
	nFiles = MAXCONTROLS - GetNumControls(&FileInfoFirst);
	
    for( i = 0; i < nFiles; i++ )
	{
	if(( pfi = GlobalAllocPtr(GPTR, sizeof(FILEINFO))) == NULL )
	    goto BOF_Fail;

	ZeroMemory( pfi, sizeof(FILEINFO));
	strcpy( pfi->szFileName, ppszFiles[i] );

	if( WaveLoadFile( ppszFiles[i], &pfi->cbSize, &cSamples,
			  &pfi->pwfx, &pfi->pbData ) != 0 )
	    goto BOF_LoopError;

	GetNextControlCoords( &FileInfoFirst, &pfi->cox, &pfi->coy );
	if( NewDirectSoundBuffer(pfi) != 0)
	    goto BOF_LoopError;
	Assert( pfi->pbData != NULL );

	if( AddToList( &FileInfoFirst, pfi ) != 0 )
	    goto BOF_LoopError;

	pfi->nFileName = 0;

	if( CreateControl( hWndMain, pfi, pfi->pwfx->nSamplesPerSec,
		       (MAXPAN_TB-MINPAN_TB)/2, MINVOL_TB ) != 0 )
	    {
	    ReleaseDirectSoundBuffer(pfi);
	    RemoveFromList( pfi, &FileInfoFirst );
	    // RemoveFromList will do all the cleanup
	    pfi = NULL;
	    goto BOF_LoopError;
	    }
	ChangeOutputVol(pfi);
	ChangeOutputFreq(pfi);
	ChangeOutputPan(pfi);

	// LOOP is only obeyed if PLAY was also specified
	if( fPlay )
	    {
	    if( fLoop )
		{
		pfi->fLooped = TRUE;
		SendMessage( pfi->hWndLooped_BN, BM_SETCHECK, TRUE, 0L );
		}
	    SendMessage( hWndMain, WM_COMMAND, 0, (LPARAM)pfi->hWndPlay_BN );
	    }

	// Avoid the in-loop error cleanup by using a continue statement here
	// to jump back up to the top
	continue;
	
	// Cleanup code in case we fail to open a particular file -- we should
	// just ignore this one and continue because we might still be able to
	// open other files
    BOF_LoopError:
	if( NULL != pfi )
	    {
	    if( NULL != pfi->pwfx )
		{
		GlobalFreePtr(pfi->pwfx);
		pfi->pwfx = NULL;
		}
	    if( NULL != pfi->pbData )
		{
		GlobalFreePtr(pfi->pbData);
		pfi->pbData = NULL;
		}

	    ReleaseDirectSoundBuffer(pfi);
	    GlobalFreePtr(pfi);
	    pfi = NULL;
	    }
	}

    UpdateMainStatus();

    return TRUE;

BOF_Fail:
    return FALSE;
    }





