/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:   dsstream.c
 *  Content:   Illustrates streaming data from a disk WAVE file to a
 *             DirectSound secondary buffer for playback.
 *
 ***************************************************************************/
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsound.h>
#include <commctrl.h>
#include <commdlg.h>
#include <memory.h>
#include <cderr.h>

#include "dsstream.h"
#include "debug.h"

char szAppClass[] = "DSStreamWndClass";
char szAppName[]  = "DSStream";

char szAppTitle[64];
char szAppCaption[64];
char szPan[32];
char szVolume[32];
char szProgress[32];
char szOpenFilter[128];
char szOpenDLGTitle[64];
char szCDStartPath[MAX_PATH];

char szTemp[256];
char szDebug[128];
char szFileBuffer[MAX_PATH];
char szFileTitle[MAX_PATH];
// Registry Key and Value names that allow us to retrive a path something like
// "C:\DXSDK\SDK\MEDIA", but matching the current install.
static const TCHAR gszRegKeyDirect3D[] = TEXT("Software\\Microsoft\\Direct3D");
static const TCHAR gszRegValueD3DPath[] = TEXT("D3D Path");

// Size of longest token below
#define MAX_TOKEN_LEN   7


static TCHAR gszPlayToken[] = TEXT("PLAY");
static TCHAR gszLoopToken[] = TEXT("LOOP");
static TCHAR gszCloseToken[] = TEXT("CLOSE");
static TCHAR gszStickyToken[] = TEXT("STICKY");
static TCHAR gszGlobalToken[] = TEXT("GLOBAL");


LPDIRECTSOUND           lpDS = NULL;
LPDIRECTSOUNDBUFFER     lpDSBStreamBuffer = NULL;

WAVEINFOCA              wiWave;

HWND    hWndMain, hWndPan, hWndPanText, hWndVol, hWndVolText;
HWND    hWndBar, hWndPlay, hWndStop, hWndLoopCheck, hWndProg;
HWND    hWndProgText;

#ifdef DEBUG
HWND                    hWndList;
#endif

HINSTANCE       hInst;

static BOOL     bFileOpen = FALSE, bPlaying = FALSE, bTimerInstalled = FALSE;
static BOOL     bEnumDrivers = FALSE, gfCloseOnDone = FALSE;
static UINT     uTimerID = 0, uLastPercent = 100;
static GUID     guID;
static DWORD    gdwFocus = 0;

static BOOL InitApp( HINSTANCE );
static BOOL InitInstance( HINSTANCE, int );
static void BuildTitleBarText( void );
// Call this in initialization to set the szCDStartPath[] global variable
static void GetMediaStartPath( void );
static void FillDataBuffer( void );
static void LoadFromCommandLine( LPSTR lpszCmd );
static void UpdateControlText( void );

/******************************************************************************
 * WinMain()
 *
 * Entry point for all Windows programs - performs initialization, message loop
 */
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
					LPSTR lpCmdLine, int nCmdShow )
    {
    MSG     msg;

    hInst = hInstance;

    /* Make sure the common controls are loaded for our use */
    InitCommonControls();



    if( !hPrevInstance )
	if( !InitApp( hInstance ))
	    {
	    ErrorMessageBox( IDS_ERROR_APPINIT, MB_ICONSTOP );
	    return( FALSE );
	    }

    if( !InitInstance( hInstance, nCmdShow ))
	{
// Redundant, since all possible cases in InitInstance will inform the user properly
//        ErrorMessageBox( IDS_ERROR_INSTANCEINIT, MB_ICONSTOP );
	return( FALSE );
	}
    // We know how to take exactly one file name from the command-line and open
    // it as a file to play.  We can also accept a couple command like /play,
    // /close, and /loop
    if( lpCmdLine[0] )
	LoadFromCommandLine( lpCmdLine );


    while( GetMessage((LPMSG)&msg, NULL, 0, 0 ))
	{
	TranslateMessage( &msg );
	DispatchMessage( &msg );
	}

    UnregisterClass( szAppClass, hInstance );
    return( msg.wParam );
    } /* End of WinMain() */


/*****************************************************************************/
/* InitApp()                                                                 */
/*                                                                           */
/*   Inits things that only need to be created once for the this application */
/* (like creating the window class).                                         */
/*****************************************************************************/
static BOOL InitApp( HINSTANCE hInstance )
    {
    WNDCLASS    wc;

    /* Set up and register a window class */
    wc.style            = CS_HREDRAW | CS_VREDRAW;
    wc.lpszClassName    = szAppClass;
    wc.lpfnWndProc      = (WNDPROC)MainWindowProc;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = sizeof( DWORD );
    wc.hInstance        = hInstance;
    wc.hIcon            = LoadIcon( hInstance, MAKEINTRESOURCE( IDI_ICON1 ));
    wc.hCursor          = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground    = (HBRUSH)(COLOR_WINDOW);
    wc.lpszMenuName     = MAKEINTRESOURCE( IDR_MAINMENU );

    if( !RegisterClass( &wc ))
	{
	ErrorMessageBox( IDS_ERROR_REGISTERCLASS, MB_ICONSTOP );
	return( FALSE );
	}
    return( TRUE );
    } /* End of InitApp() */


/*****************************************************************************/
/* InitInstance()                                                            */
/*                                                                           */
/* Performs initialization that must be done for each application instance.  */
/*                                                                           */
/*****************************************************************************/
static BOOL InitInstance( HINSTANCE hInstance, int nCmdShow )
    {
    HWND        hWnd;
    HRESULT     dsRetVal;
    RECT        crect;
    int         cx, cy;
    UINT        uCharsRead;
    BOOL        fUseGuid = FALSE;
    HMENU       hSysMenu;
    DSCAPS      dscaps;

    // Initialize the Media start path for common open boxes
    GetMediaStartPath();

    LoadString( hInstance, IDS_APP_TITLE, szAppTitle, sizeof(szAppTitle));
    LoadString( hInstance, IDS_APP_CAPTION, szAppCaption, sizeof(szAppCaption));
    LoadString( hInstance, IDS_TBTITLE_PAN, szPan, sizeof(szPan));
    LoadString( hInstance, IDS_TBTITLE_VOLUME, szVolume, sizeof(szVolume));
   
    LoadString( hInstance, IDS_TBTITLE_PROGRESS,
					    szProgress, sizeof(szProgress));
    LoadString( hInstance, IDS_OPEN_DLGTITLE,
					szOpenDLGTitle, sizeof(szOpenDLGTitle));
/* This is a little trick designed to allow us to load a common dialog box
 * filter string, which is really a concatentation of several NULL-terminated
 * strings. Note that while is is possible to enter something else into the
 * resource as placeholders for the NULL characters, this has the undesireable
 * effect of forcing us to search-and-replace byte-by-byte and doesn't make it
 * as easy to internationalize our strings...
 */
    memset( szOpenFilter, 0, sizeof(szOpenFilter));
    uCharsRead = LoadString( hInstance, IDS_OPEN_FILTER1,
				szOpenFilter, sizeof(szOpenFilter)) + 1;
    uCharsRead += LoadString( hInstance, IDS_OPEN_FILTER2,
				&szOpenFilter[uCharsRead],
				sizeof(szOpenFilter) - uCharsRead ) + 1;
    uCharsRead += LoadString( hInstance, IDS_OPEN_FILTER3,
				&szOpenFilter[uCharsRead],
				sizeof(szOpenFilter) - uCharsRead ) + 1;
    LoadString( hInstance, IDS_OPEN_FILTER4,
				&szOpenFilter[uCharsRead],
				sizeof(szOpenFilter) - uCharsRead );

    /* Calculate the size of the client window */
    cx = CONTROL_SPACE_CX + 2*BORDER_SPACE_CX + BUTTON_CX + PAN_TB_CX
	+ 2*GetSystemMetrics( SM_CXBORDER ) + PAN_TEXT_CX + TEXT_SPACE_CX;

    cy = 2*(BORDER_SPACE_CY + GetSystemMetrics( SM_CYBORDER ))
	+ PAN_TB_CY + VOL_TB_CY + PROG_TB_CY
	+ GetSystemMetrics( SM_CYMENU ) + 2*CONTROL_SPACE_CY
	+ GetSystemMetrics( SM_CYCAPTION );

    /* Create an application window */
#ifdef DEBUG
    hWnd = CreateWindow( szAppClass,            /* class name */
			szAppCaption,           /* caption for window */
			WS_OVERLAPPEDWINDOW,    /* style */
			CW_USEDEFAULT,          /* x position */
			CW_USEDEFAULT,          /* y position */
			cx,                     /* width */
			cy + 200,               /* height */
			NULL,                   /* parent window */
			NULL,                   /* menu */
			hInstance,              /* instance */
			NULL );                 /* parms */
#else
    hWnd = CreateWindow( szAppClass,            /* class name */
			szAppCaption,           /* caption for window */
			WS_OVERLAPPEDWINDOW,    /* style */
			CW_USEDEFAULT,          /* x position */
			CW_USEDEFAULT,          /* y position */
			cx,                     /* width */
			cy,                     /* height */
			NULL,                   /* parent window */
			NULL,                   /* menu */
			hInstance,              /* instance */
			NULL );                 /* parms */
#endif

    if( !hWnd )
	{
	ErrorMessageBox( IDS_ERROR_MAINWNDCREATE, MB_ICONSTOP );
	return( FALSE );
	}

    hWndMain = hWnd;
    GetClientRect( hWndMain, &crect );
    // Apparently WM_INITMENU isn't called for the main window's context version
    // of the system menu, so we must gray these at startup as well.
    hSysMenu = GetSystemMenu( hWndMain, FALSE );
    EnableMenuItem( hSysMenu, SC_SIZE, MF_BYCOMMAND | MF_GRAYED );
    EnableMenuItem( hSysMenu, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED );


#ifdef DEBUG
    cy = 2*BORDER_SPACE_CY + PAN_TB_CY + VOL_TB_CY + PROG_TB_CY
		+ 2*CONTROL_SPACE_CY;

    hWndList = CreateWindow( "listbox", NULL, WS_CHILD | WS_VISIBLE
				| LBS_NOINTEGRALHEIGHT | WS_VSCROLL,
				0, cy, crect.right-crect.left,
				crect.bottom - crect.top - cy,
				hWnd, NULL, hInstance, NULL );
#endif

    /* Create some controls for things like volume, panning, etc. */
    if( CreateChildren( crect ))
	{
	ErrorMessageBox( IDS_ERROR_CREATECHILDREN, MB_ICONSTOP );
	return( FALSE );
	}

    ShowWindow( hWnd, nCmdShow );
    UpdateWindow( hWnd );

    /* Create the main DirectSound object */

    bEnumDrivers = GetProfileInt( "DSSTREAM", "EnumDrivers", FALSE );

    if( bEnumDrivers ) 
    {
	if( !DoDSoundEnumerate( &guID )) 
	{
	    dsRetVal = DirectSoundCreate( &guID, &lpDS, NULL );
	} 
	else 
	{
	    dsRetVal = DirectSoundCreate( NULL, &lpDS, NULL );
	}
    }
    else
    {
	dsRetVal = DirectSoundCreate( NULL, &lpDS, NULL );
    }



    if( dsRetVal != DS_OK )
	{
	ErrorMessageBox( IDS_ERROR_DSCREATE, MB_ICONSTOP );
	return( FALSE );
	}

    ZeroMemory( &dscaps, sizeof(dscaps));
    dscaps.dwSize = sizeof(dscaps);

    if( FAILED( dsRetVal = lpDS->lpVtbl->GetCaps( lpDS, &dscaps )))
	{
	DPF(0,"DirectSound::GetCaps failed");
	ErrorMessageBox( IDS_ERROR_DSCAPS, MB_ICONSTOP );
	return FALSE;
	}

    if( dscaps.dwFlags & DSCAPS_EMULDRIVER )
    {   
	ErrorMessageBox( IDS_ERROR_EMULATEDCARD, MB_ICONINFORMATION );
	return FALSE;
    }

    dsRetVal = lpDS->lpVtbl->SetCooperativeLevel( lpDS,
						hWndMain,
						DSSCL_WRITEPRIMARY );
    if( dsRetVal != DS_OK )
	{
	ErrorMessageBox( IDS_ERROR_DSCOOPERATIVE, MB_ICONSTOP );
	return( FALSE );
	}

    // This function picks a starting directory for the Open box
    // and puts it in the global szCDStartPath for later use.
    GetMediaStartPath();

    return( TRUE );
    } /* End of InitInstance() */


/****************************************************************************/
/* MainWindowProc()                                                         */
/*                                                                          */
/*    Messages for our main window are handled here                         */
/*                                                                          */
/****************************************************************************/
extern LONG lInTimer;
LRESULT CALLBACK MainWindowProc( HWND hWnd, unsigned uMsg,
						WPARAM wParam, LPARAM lParam )
    {
#ifndef DEBUG
    LPMINMAXINFO lpMinMax;
#endif
    DWORD   dwCDErr = 0, dwProgressTemp;
    float   fPercent;
    UINT    uPercent;
    BOOL    bResult = FALSE;
    int     nChkErr;
    HRESULT dsrval;

    switch( uMsg )
	{
	case WM_DSSTREAM_PROGRESS:
	{
	    LONG lVol;
	    LONG lPan;

	    // Protect against invalid calls, since it's possible we won't
	    // process this message out of the queue until after the actual
	    // IDirectSoundBuffer object has been released.
	    if( NULL != lpDSBStreamBuffer )
		{
		IDirectSoundBuffer_GetVolume(lpDSBStreamBuffer, &lVol);
		lVol = lVol/VOL_MULT - VOL_SHIFT;
		SendMessage( hWndVol, TBM_SETPOS, TRUE, lVol);

		IDirectSoundBuffer_GetPan(lpDSBStreamBuffer, &lPan);
		lPan = lPan/PAN_MULT - PAN_SHIFT;
		SendMessage( hWndPan, TBM_SETPOS, TRUE, lPan);

		UpdateControlText();
		}

	    dwProgressTemp = (DWORD)lParam;
	    dwProgressTemp  = dwProgressTemp % wiWave.mmckInRIFF.cksize;
	    fPercent = (float)((dwProgressTemp * 100)
					/ wiWave.mmckInRIFF.cksize);
	    SendMessage( hWndProg, TBM_SETPOS,
			TRUE, (DWORD)(float)(fPercent*(float)PROG_MULTIPLIER));
	    uPercent = (UINT)fPercent;
	    if( uPercent != uLastPercent )
		{
		uLastPercent = uPercent;
		wsprintf( szTemp, "%s: %u%%", szProgress, uPercent );
		Static_SetText( hWndProgText, szTemp );
#ifdef DEBUG
		ListBox_AddString( hWndList, szTemp );
#endif
		}
	    break;
	}

	/*
	 *      This message will be posted by the helper DLL when the TimeFunc
	 * is done streaming the WAVE file. It serves as notification that the
	 * caller should terminate WAVE playback and end the MM timer event.
	 */
	case WM_DSSTREAM_DONE:
	    /* Emulate a WM_COMMAND to ourselves */
	    SendMessage( hWnd, WM_COMMAND, MAKEWPARAM( IDM_STOP, 0 ), 0L );
	    break;

#ifdef DEBUG
	case WM_DSSTREAM_DEBUG:
	    if( LOWORD(wParam) == DEBUGF_PLAYPOSITION )
		{
		wsprintf( szDebug, "pp = %li", lParam );
		ListBox_AddString( hWndList, szDebug );
		DPF( 4, szDebug );
		}
	    else if( LOWORD(wParam) == DEBUGF_WRITEPOSITION )
		{
		wsprintf( szDebug, "wp = %li", lParam );
		ListBox_AddString( hWndList, szDebug );
		DPF( 4, szDebug );
		}
	    else if( LOWORD(wParam) == DEBUGF_NEXTWRITE )
		{
		wsprintf( szDebug, "nw = %li", lParam );
		ListBox_AddString( hWndList, szDebug );
		DPF( 4, szDebug );
		}
	    else if( LOWORD(wParam) == DEBUGF_SKIP )
		{
		ListBox_AddString( hWndList, "Skipped segment read" );
		DPF( 5, szDebug );
		}
	    break;
#endif

	case WM_COMMAND:
	    switch( LOWORD( wParam ))
		{
		case IDM_FILE_OPEN:
		    {
		    OPENFILENAME        ofn;
    /*
     * Clear out and fill in an OPENFILENAME structure in preparation
     * for creating a common dialog box to open a file.
     */
		    memset( &ofn, 0, sizeof(OPENFILENAME));
		    ofn.lStructSize     = sizeof(OPENFILENAME);
		    ofn.hwndOwner       = hWnd;
		    ofn.hInstance       = hInst;
		    ofn.lpstrFilter     = szOpenFilter;
		    ofn.nFilterIndex    = 1;
		    szFileBuffer[0]     = '\0';
		    ofn.lpstrFile       = szFileBuffer;
		    ofn.nMaxFile        = sizeof(szFileBuffer);
		    ofn.lpstrFileTitle  = szFileTitle;
		    ofn.nMaxFileTitle   = sizeof(szFileTitle);
		    ofn.lpstrDefExt     = "WAV";
		    ofn.lpstrTitle      = szOpenDLGTitle;
		    ofn.lpstrInitialDir = szCDStartPath;
		    ofn.Flags           = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;

		    bResult = GetOpenFileName( &ofn ); /* Do the dialog box */

    /*
     *  A return of TRUE indicates that the user did not select a filename.
     * The possible reasons are: Cancel was clicked, or an error occured.
     * If Cancel was clicked, the CommDlgExtendedError() function will not
     * return a valid code.  For anything else, an error code will come back.
     */
		    if( bResult == FALSE )
			{
			dwCDErr = CommDlgExtendedError();
			if( dwCDErr )
			    {
			    /* Handle a common dialog box error */
			    HandleCommDlgError( dwCDErr );
			    }
			else    /* Clicked Cancel, so finish msg processing */
			    return( 0 );
			}
		    else
			{
			// Copy the directory name we opened from so that we start there
			// next time we open the dialog box...
			lstrcpy( szCDStartPath, szFileBuffer );
			szCDStartPath[ofn.nFileOffset] = '\0';
			if( bFileOpen )
			    {
			    // Before we force a close, disable auto close
			    // because the user has picked a new file and is
			    // obviously not ready for us to go away yet.
			    gfCloseOnDone = FALSE;
    /* Need to close the previous file before we open a new one.  The best
     * way to do this is by faking a menu command, so that we only have the
     * actual code in one place and it can easily be changed.
     */
			    SendMessage( hWnd, WM_COMMAND,
					MAKEWPARAM( IDM_FILE_CLOSE, 0 ), 0L );
			    }

			if(( nChkErr = StreamBufferSetup()) != 0 )
			    {
			    // Error opening the WAVE file so abort
			    break;
			    }
			else
			    {
			    bFileOpen = TRUE;
			    EnableMenuItem( GetMenu( hWnd ), IDM_PLAY,
						MF_BYCOMMAND | MF_ENABLED );
			    EnableWindow( hWndPlay, TRUE );
			    EnableMenuItem( GetMenu( hWnd ), IDM_FILE_CLOSE,
						MF_BYCOMMAND | MF_ENABLED );
			    DrawMenuBar( hWnd );
			    BuildTitleBarText();
			    }
			}
		    }
		    break;

		case IDM_FILE_CLOSE:
		    SendMessage( hWnd, WM_COMMAND,
				MAKEWPARAM( IDM_STOP,
					    DSSTREAM_STOPF_NOREOPEN ), 0L );
		    BuildTitleBarText();
		    break;

		case IDM_OPTIONS_ENUMDRIVERS:
		    bEnumDrivers = !bEnumDrivers;
		    if( bEnumDrivers )
			{
			LoadString( hInst, IDS_ENUMWARNING, szTemp, sizeof(szTemp));

			MessageBox( hWnd, szTemp, szAppCaption, MB_OK );
			}
		    break;

		case IDM_HELP_ABOUT:
		    DialogBox( hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWndMain,
				(DLGPROC)DLG_About );
		    break;

		case IDC_LOOPCHECK:
		    wiWave.bLoopFile = !wiWave.bLoopFile;
		    Button_SetCheck( hWndLoopCheck, wiWave.bLoopFile );
		    if( !bPlaying && bFileOpen )
			ResetWavePlayer();
		    break;

		case IDM_PLAY:
#ifdef DEBUG
		    wsprintf( szDebug, "  bFileOpen = %s",
				bFileOpen == TRUE ? "TRUE" : "FALSE" );
		    DPF( 3, "IDM_PLAY Debug" );
		    DPF( 3, szDebug );
		    wsprintf( szDebug, "  bTimerInstalled = %s",
				bTimerInstalled == TRUE ? "TRUE" : "FALSE" );
		    DPF( 3, szDebug );
		    wsprintf( szDebug, "  bPlaying = %s",
				bPlaying == TRUE ? "TRUE" : "FALSE" );
		    DPF( 3, szDebug );
#endif
		    if( bPlaying )
		    {
			gfCloseOnDone = FALSE;
			SendMessage( hWnd, WM_COMMAND,
					MAKEWPARAM( IDM_STOP, 0 ), 0L );
		    }
		    if( bFileOpen && lpDSBStreamBuffer )
			{
			// Ensure that position is at 0, ready to go
			dsrval = lpDSBStreamBuffer->lpVtbl->SetCurrentPosition(
				 lpDSBStreamBuffer,
				 0 );
			dsrval = lpDSBStreamBuffer->lpVtbl->Play( lpDSBStreamBuffer,
						      0, 0, DSBPLAY_LOOPING );
			}
		    else
			{
			bPlaying = bTimerInstalled = FALSE;
			break;
			}

		    if( timeBeginPeriod( wiWave.dwTimerPeriod
					    / PLAYBACK_OVERSAMPLE ) != 0 )
			{
			/* Can't create timer! */
			dsrval = lpDSBStreamBuffer->lpVtbl->Stop( lpDSBStreamBuffer );
			bPlaying = bTimerInstalled = FALSE;
			break;
			}
		    else
			{
			if(( uTimerID = timeSetEvent( wiWave.dwTimerPeriod
						       / PLAYBACK_OVERSAMPLE,
						       PLAYBACK_TIMER_ACCURACY,
						       TimeFunc, (DWORD)0,
						       TIME_PERIODIC )) != 0 )
			    bTimerInstalled = TRUE;
			}
		    bPlaying = TRUE;
		    EnableMenuItem( GetMenu( hWnd ), IDM_STOP,
					MF_BYCOMMAND | MF_ENABLED );
		    EnableWindow( hWndStop, TRUE );
		    DrawMenuBar( hWnd );
		    break;

		case IDM_STOP:
#ifdef DEBUG
		    wsprintf( szDebug, "  bFileOpen = %s",
				    bFileOpen == TRUE ? "TRUE" : "FALSE" );
		    DPF( 3, "IDM_STOP Debug" );
		    DPF( 3, szDebug );
		    wsprintf( szDebug, "  bTimerInstalled = %s",
				bTimerInstalled == TRUE ? "TRUE" : "FALSE" );
		    DPF( 3, szDebug );
		    wsprintf( szDebug, "  bPlaying = %s",
				bPlaying == TRUE ? "TRUE" : "FALSE" );
		    DPF( 3, szDebug );
#endif
		    wiWave.bDonePlaying = TRUE;
		    if( bTimerInstalled )
			{
			timeKillEvent( uTimerID );
			timeEndPeriod( wiWave.dwTimerPeriod
					/ PLAYBACK_OVERSAMPLE );
			// Busy wait for timer function to exit
			while (InterlockedExchange(&lInTimer, FALSE)) Sleep(100);
			bTimerInstalled = FALSE;
			}
		    if( bPlaying )
			{
			bPlaying = FALSE;
			dsrval = lpDSBStreamBuffer->lpVtbl->Stop( lpDSBStreamBuffer );
			EnableMenuItem( GetMenu( hWnd ), IDM_STOP,
					    MF_BYCOMMAND | MF_GRAYED );
			EnableWindow( hWndStop, FALSE );
			DrawMenuBar( hWnd );
			}
		    // Short circuit to allow command-line forced shutdown
		    if(!( HIWORD(wParam) & DSSTREAM_STOPF_NOREOPEN ) && !gfCloseOnDone )
			{
			ResetWavePlayer();
			break;
			}
		    else
			{
			if( bFileOpen )
			    {
			    WaveCloseReadFile( &wiWave.hmmio,
							    &wiWave.pwfx );
			    if( lpDSBStreamBuffer )
				dsrval = lpDSBStreamBuffer->lpVtbl->Release(
							   lpDSBStreamBuffer );
			    lpDSBStreamBuffer = NULL;
			    bFileOpen = FALSE;
			    // The file is closed, so disable the close option
			    EnableMenuItem( GetMenu( hWnd ), IDM_FILE_CLOSE,
					    MF_BYCOMMAND | MF_GRAYED );
			    EnableMenuItem( GetMenu( hWnd ), IDM_PLAY,
					    MF_BYCOMMAND | MF_GRAYED );
			    EnableWindow( hWndPlay, FALSE );
			    PostMessage( hWnd, WM_DSSTREAM_PROGRESS, 0L, 0L );
			    if( gfCloseOnDone &&
				    !(HIWORD(wParam) && DSSTREAM_STOPF_NOEXIT))
				SendMessage( hWnd, WM_COMMAND,
					MAKEWPARAM( IDM_FILE_EXIT, 0 ), 0L );

			    }
			}
		    break;

		case IDM_FILE_EXIT:
		    DestroyWindow( hWnd );
		    break;
		}
	    break;
#ifndef DEBUG
	case WM_GETMINMAXINFO:
    /*
     * We know exactly how big this window should be, and it's sort of a
     * little pop-up control panel, so we can disable window sizing by
     * forcing all the minimum and maximum sizes to be the calculated size.
     */
	    lpMinMax = (LPMINMAXINFO)lParam;

	    lpMinMax->ptMaxSize.x = CONTROL_SPACE_CX + 2*BORDER_SPACE_CX
				    + BUTTON_CX + PAN_TB_CX + PAN_TEXT_CX
				    + TEXT_SPACE_CX
				    + 2*GetSystemMetrics( SM_CXBORDER );
	    lpMinMax->ptMaxSize.y = 2*(BORDER_SPACE_CY
				    + GetSystemMetrics( SM_CYBORDER ))
				    + PAN_TB_CY + VOL_TB_CY
				    + PROG_TB_CY + 2*CONTROL_SPACE_CY
				    + GetSystemMetrics( SM_CYMENU )
				    + GetSystemMetrics( SM_CYCAPTION );

	    lpMinMax->ptMinTrackSize.x = lpMinMax->ptMaxTrackSize.x
						    = lpMinMax->ptMaxSize.x;

	    lpMinMax->ptMinTrackSize.y = lpMinMax->ptMaxTrackSize.y
						    = lpMinMax->ptMaxSize.y;
	    break;
#endif
	case WM_HSCROLL:
	    if(((HWND)lParam == hWndPan) && lpDSBStreamBuffer )
		{
		HandlePanScroll( (int)LOWORD(wParam), (int)HIWORD(wParam));
		}
	    else if(((HWND)lParam == hWndVol) && lpDSBStreamBuffer )
		{
		HandleVolScroll( (int)LOWORD(wParam), (int)HIWORD(wParam));
		}
	    break;

	case WM_INITMENU:
	    {
		HMENU   hSysMenu = GetSystemMenu( hWnd, FALSE );

		EnableMenuItem( hSysMenu, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED );
		EnableMenuItem( hSysMenu, SC_SIZE, MF_BYCOMMAND | MF_GRAYED );

		if((HMENU)wParam != GetMenu( hWnd ))
		break;

		CheckMenuItem((HMENU)wParam, IDM_OPTIONS_ENUMDRIVERS,
				bEnumDrivers ? MF_CHECKED : MF_UNCHECKED );
	    }
	    break;

	case WM_DESTROY:
    /*
     * Free all the DirectSound objects we created
     */
	    SendMessage( hWnd, WM_COMMAND,
			  MAKEWPARAM( IDM_STOP, DSSTREAM_STOPF_NOREOPEN 
						| DSSTREAM_STOPF_NOEXIT ), 0 );
	    if( bTimerInstalled )
	    {
		timeKillEvent( uTimerID );
		timeEndPeriod( PLAYBACK_TIMER_PERIOD / PLAYBACK_OVERSAMPLE );
		// Busy wait for timer func to exit
		while (InterlockedExchange(&lInTimer, TRUE)) Sleep(100);
		bTimerInstalled = FALSE;
	    }

	    if( lpDS )
		dsrval = lpDS->lpVtbl->Release( lpDS );

	    WriteProfileString( "DSSTREAM", "EnumDrivers",
					bEnumDrivers ? "1" : "0" );

	    PostQuitMessage( 0 );
	    break;

	default:
	    return DefWindowProc( hWnd, uMsg, wParam, lParam );
	}
    return 0L;
    } /* WindowProc */


/*****************************************************************************/
/* DLG_About()                                                               */
/*                                                                           */
/*   Dialog procedure for the Help...About... box which simply pops up a     */
/* little copyright message and brief program description.                   */
/*                                                                           */
/*****************************************************************************/
BOOL CALLBACK DLG_About( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
    {
    switch( msg )
	{
	case WM_INITDIALOG:
	    break;

	case WM_COMMAND:
	    switch( LOWORD(wParam))
		{
		case IDOK:
		    EndDialog( hDlg, FALSE );
		    return( TRUE );

		default:
		    break;
		}
	    break;

	default:
	    return( FALSE );
	}

    return( FALSE );
    }


/*****************************************************************************/
/* HandleCommDlgError()                                                      */
/*                                                                           */
/*    The function translates extended common dialog error codes into a      */
/* string resource ID, loads that string from our module, and displays it in */
/* a message box. This implementation only covers the general CD error codes.*/
/*                                                                           */
/*****************************************************************************/
int HandleCommDlgError( DWORD dwError )
    {
    char szTitle[128];
    UINT uMsgID;

    if( dwError == CDERR_DIALOGFAILURE )
	uMsgID = IDS_CDERR_DIALOGFAILURE;
    else
	uMsgID = (UINT)dwError + IDS_CDERR_GENERAL_BASE;

    LoadString( hInst, uMsgID, szTemp, sizeof(szTemp));
    LoadString( hInst, IDS_CDERR_TITLESTRING, szTitle, sizeof(szTitle));


    MessageBox( GetActiveWindow(), szTemp, szTitle,
		    MB_OK | MB_ICONEXCLAMATION );

    return( 0 );
    }


/*****************************************************************************/
/* StreamBufferSetup()                                                       */
/*                                                                           */
/*    This function uses the filename stored in the global character array to*/
/* open a WAVE file. Then it creates a secondary DirectSoundBuffer object    */
/* which will later be used to stream that file from disk during playback.   */
/*                                                                           */
/*****************************************************************************/
int StreamBufferSetup( void )
    {
    DSBUFFERDESC dsbd;
    DSBCAPS      dsbc;
    HRESULT      dsRetVal;

    int nChkErr;

    /* This portion of the WAVE I/O is patterned after what's in DSTRWAVE, which
     * was in turn adopted from WAVE.C which is part of the DSSHOW sample.
     */

    if(( nChkErr = WaveOpenFile( szFileBuffer, &wiWave.hmmio, &wiWave.pwfx, &wiWave.mmckInRIFF )) != 0 )
	{
	ErrorMessageBox( IDS_ERROR_WAVEFILEOPEN, MB_ICONEXCLAMATION );
	return( ERR_WAVE_OPEN_FAILED );
	}

    if( wiWave.pwfx->wFormatTag != WAVE_FORMAT_PCM )
	{
	ErrorMessageBox( IDS_ERROR_WAVENOTPCM, MB_ICONEXCLAMATION );
	WaveCloseReadFile( &wiWave.hmmio, &wiWave.pwfx );
	return( ERR_WAVE_INVALID_FORMAT );
		}
    /* Seek to the data chunk */
    if(( nChkErr = WaveStartDataRead( &wiWave.hmmio, &wiWave.mmck, &wiWave.mmckInRIFF )) != 0 )
	{
	ErrorMessageBox( IDS_ERROR_WAVESEEKFAILED, MB_ICONEXCLAMATION );
	WaveCloseReadFile( &wiWave.hmmio, &wiWave.pwfx );
	return( ERR_WAVE_CORRUPTED_FILE );
	}
    /* As a side note, mmck.ckSize will be the size of all the data in this file.
     * That's something which might be handy when calculating the length... */

    /*
     * Create the secondary DirectSoundBuffer object to receive our sound data.
     */
    memset( &dsbd, 0, sizeof( DSBUFFERDESC ));
    dsbd.dwSize = sizeof( DSBUFFERDESC );
    // Use new GetCurrentPosition() accuracy (DirectX 2 feature)
    dsbd.dwFlags = DSBCAPS_CTRLVOLUME |
		   DSBCAPS_CTRLPAN |
		   DSBCAPS_GETCURRENTPOSITION2 |
		   DSBCAPS_PRIMARYBUFFER |
		   gdwFocus;
    dsbd.dwBufferBytes = 0;

    /* Create the buffer and Set Format properties according to
     * the WAVE file we just opened */
    dsRetVal = lpDS->lpVtbl->CreateSoundBuffer( lpDS,
						&dsbd,
						&lpDSBStreamBuffer,
						NULL );

    if (DS_OK == dsRetVal) {
	dsRetVal = IDirectSoundBuffer_SetFormat( lpDSBStreamBuffer, wiWave.pwfx );
    }

    if (DS_OK == dsRetVal) {
	dsbc.dwSize = sizeof(dsbc);
	dsRetVal = IDirectSoundBuffer_GetCaps( lpDSBStreamBuffer, &dsbc );
    }

    if( dsRetVal != DS_OK )
	{
	ErrorMessageBox( IDS_ERROR_DSBCREATE, MB_ICONEXCLAMATION );
	return( ERR_CREATEDSB_FAILED );
	}


    /* Calculate a buffer length, making sure it is an exact multiple of the
     * buffer segment size.
     */
    wiWave.dwBufferSize = dsbc.dwBufferBytes;
    wiWave.dwTimerPeriod = dsbc.dwBufferBytes * 1000
			   / wiWave.pwfx->nAvgBytesPerSec / NUM_BUFFER_SEGMENTS;

#ifdef DEBUG
    wsprintf( szDebug, "BufferSize = %lu", wiWave.dwBufferSize );
    ListBox_AddString( hWndList, szDebug );
#endif

    wiWave.lpDSBStreamBuffer = lpDSBStreamBuffer;
    wiWave.bFoundEnd = FALSE;
    wiWave.cbBytesRemaining = 0;

    // Avoid multiple code paths by calling this mutual function instead of using
    // a copy of the same code.
    FillDataBuffer();

    wiWave.bDonePlaying = FALSE;
    wiWave.bLoopFile = Button_GetCheck( hWndLoopCheck );

#ifdef DEBUG
    wsprintf( szDebug, "wiWave.dwBufferSize = %lu", wiWave.dwBufferSize );
    DPF( 3, "StreamBufferSetup Debug" );
    DPF( 3, szDebug );
#endif

    SendMessage( hWndVol, TBM_SETPOS, TRUE, VOL_MAX );
    SendMessage( hWndPan, TBM_SETPOS, TRUE, PAN_CENTER );
    PostMessage( hWndMain, WM_DSSTREAM_PROGRESS, 0L, 0L );
    UpdateFromControls();
    return( 0 );
    }


/*****************************************************************************/
/* ResetWavePlayer()                                                         */
/*                                                                           */
/*   This function simply sets up a few things and calls FillDataBuffer to   */
/* refill the sound buffer from the start.  The specific things it does are  */
/* steps like rewinding the read position and clearing any "done" state vars.*/
/*                                                                           */
/*****************************************************************************/
void ResetWavePlayer( void )
    {
    WaveStartDataRead( &wiWave.hmmio, &wiWave.mmck, &wiWave.mmckInRIFF );
    wiWave.bFoundEnd = FALSE;
    wiWave.cbBytesRemaining = 0;

    FillDataBuffer();

    wiWave.bDonePlaying = FALSE;
    PostMessage( hWndMain, WM_DSSTREAM_PROGRESS, 0L, 0L );
    }

/*****************************************************************************/
/* FillDataBuffer()                                                          */
/*                                                                           */
/*   This function fills the sound buffer with a block of data, starting at  */
/* the current read position.  The entire buffer is filled.                  */
/*                                                                           */
/*****************************************************************************/
void FillDataBuffer( void )
    {
    LPBYTE      lpWrite1, lpWrite2;
    DWORD       dwLen1, dwLen2, cbReadLoopTotal;
    UINT        uActualBytesWritten;
    int         nChkErr;
    HRESULT     dsrval;

    dsrval = lpDSBStreamBuffer->lpVtbl->Lock( lpDSBStreamBuffer,
					0, wiWave.dwBufferSize,
					&((LPVOID)lpWrite1), &dwLen1,
					&((LPVOID)lpWrite2), &dwLen2,
					0 );

    if( dsrval != DS_OK )
    {
	lpWrite1 = lpWrite2 = NULL;
	dwLen1 = dwLen2 = 0;
    }

    if( dwLen1 )
	{
	nChkErr = WaveReadFile( wiWave.hmmio, (UINT)dwLen1, lpWrite1,
				&wiWave.mmck, &uActualBytesWritten );
	if( uActualBytesWritten < dwLen1 )
	    {
	    if( wiWave.bLoopFile )
		{
    /* If the file is shorter than the buffer and we're looping, we need to
     * read the file in again so that we don't get a block of silence before
     * the timer loops playback.
     */
		LPBYTE lpTemp = lpWrite1;
				cbReadLoopTotal = dwLen1;

		do
		    {
		    /* Continue decrementing our count and moving our temp
		     * pointer forward until we've read the file enough times
		     * to fill the buffer.  NOTE: It's probably not efficient
		     * to bother with the overhead of streaming a file that's
		     * not at least as large as the buffer... */
		    lpTemp += uActualBytesWritten;
			cbReadLoopTotal -= uActualBytesWritten;
		    nChkErr = WaveStartDataRead( &wiWave.hmmio,
						    &wiWave.mmck,
						    &wiWave.mmckInRIFF );
		    nChkErr = WaveReadFile( wiWave.hmmio, (UINT)cbReadLoopTotal,
					    lpTemp,
					    &wiWave.mmck, &uActualBytesWritten );
		    } while( uActualBytesWritten < cbReadLoopTotal );
		}
	    else
		{
		wiWave.bFoundEnd = TRUE;
		wiWave.cbBytesRemaining = (DWORD)uActualBytesWritten;
		DPF3( 3,"Setting bFoundEnd in load, cbBytesRem = %i",
			wiWave.cbBytesRemaining );

		// Cover ourselves by filling the rest of the buffer space
		// with 8 or 16 bit silence, in case we can't stop the playback
		// exactly on a block boundary and we run a bit into NULL data
				FillMemory( lpWrite1+uActualBytesWritten,
							dwLen1-uActualBytesWritten,
							(BYTE)(wiWave.pwfx->wBitsPerSample == 8 ? 128 : 0 ));
		}
	    }
	}
    dsrval = lpDSBStreamBuffer->lpVtbl->Unlock( lpDSBStreamBuffer,
					(LPVOID)lpWrite1, dwLen1,
					(LPVOID)lpWrite2, 0 );

    wiWave.dwNextWriteOffset = wiWave.dwProgress = 0;
    }


/*****************************************************************************/
/* CreateChildren()                                                          */
/*                                                                           */
/*   This function creates a bunch of child controls for the main window.    */
/* Most of them are used for controling various things about a playing sound */
/* file, like volume and panning. Returns FALSE if no errors, TRUE otherwise.*/
/*                                                                           */
/*****************************************************************************/
int CreateChildren( RECT crect )
    {
    SIZE  Size;
    HDC   hDC;
    int   x, y;
    UINT  uType;
    char  szTemplate[128], szType[32];
    LPSTR lpszControl;

    LoadString( hInst, IDS_ERROR_CHILDTEMPLATE, szTemplate, sizeof(szTemplate));

    /* Don't handle failure for this one, because the app will still run fine */
    hWndBar = CreateWindow( "static", NULL,
			    WS_CHILD | WS_VISIBLE | SS_ETCHEDHORZ,
			    0, 0, crect.right, 2, hWndMain,
			    (HMENU)0, hInst, NULL );

    hDC = GetDC( hWndMain );
    if( !GetTextExtentPoint32( hDC, szPan, strlen(szPan), &Size ))
	{
	ErrorMessageBox( IDS_ERROR_GETTEXTEXTENT, MB_ICONEXCLAMATION );
	ReleaseDC( hWndMain, hDC );
	return( TRUE );
	}
    ReleaseDC( hWndMain, hDC );

    y = BORDER_SPACE_CY;

    /* STATIC control -- text label for the pan trackbar */
    if(( hWndPanText = CreateWindow( "static", szPan,
				    WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
				    BORDER_SPACE_CX + PAN_TB_CX + TEXT_SPACE_CX,
				    y + (PAN_TB_CY - Size.cy)/2,
				    PAN_TEXT_CX, Size.cy,
				    hWndMain, (HMENU)0, hInst, NULL)) == NULL )
	{
	lpszControl = szPan;
	uType = IDS_ERROR_STATICTEXT;
	goto DISPLAY_CREATE_FAILURE;
	}

    /* PAN (left to right balance) trackbar control */
    if(( hWndPan = CreateWindow( TRACKBAR_CLASS, NULL,
				WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_BOTTOM,
				BORDER_SPACE_CX,
				y, PAN_TB_CX, PAN_TB_CY,
				hWndMain, (HMENU)0, hInst, NULL)) == NULL )
	{
	lpszControl = szPan;
	uType = IDS_ERROR_TRACKBAR;
	goto DISPLAY_CREATE_FAILURE;
	}

    SendMessage( hWndPan, TBM_SETRANGE, FALSE, MAKELONG( PAN_MIN, PAN_MAX ));
    SendMessage( hWndPan, TBM_SETPOS, TRUE, PAN_CENTER );
    SendMessage( hWndPan, TBM_SETPAGESIZE, 0L, PAN_PAGESIZE );

    y += PAN_TB_CY + CONTROL_SPACE_CY;

    /* STATIC control -- text label for the volume trackbar */
    if(( hWndVolText = CreateWindow( "static", szVolume,
				    WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
				    BORDER_SPACE_CX + VOL_TB_CX + TEXT_SPACE_CX,
				    y + (VOL_TB_CY - Size.cy)/2,
				    VOL_TEXT_CX, Size.cy,
				    hWndMain, (HMENU)0, hInst, NULL)) == NULL )
	{
	lpszControl = szVolume;
	uType = IDS_ERROR_STATICTEXT;
	goto DISPLAY_CREATE_FAILURE;
	}

    /* Create the VOLUME trackbar */
    if(( hWndVol = CreateWindow( TRACKBAR_CLASS, NULL,
				WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_BOTTOM,
				BORDER_SPACE_CX,
				y, VOL_TB_CX, VOL_TB_CY,
				hWndMain, (HMENU)0, hInst, NULL)) == NULL )
	{
	lpszControl = szVolume;
	uType = IDS_ERROR_TRACKBAR;
	goto DISPLAY_CREATE_FAILURE;
	}

    SendMessage( hWndVol, TBM_SETRANGE, FALSE,
					MAKELONG( VOL_MIN, VOL_MAX ));
    SendMessage( hWndVol, TBM_SETPOS, TRUE, VOL_MAX );
    SendMessage( hWndVol, TBM_SETPAGESIZE, 0L, VOL_PAGESIZE );

    y += VOL_TB_CY + CONTROL_SPACE_CY;

    /* STATIC control -- text label for the progress trackbar */
    if(( hWndProgText = CreateWindow( "static", szProgress,
				    WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
				    BORDER_SPACE_CX + PROG_TB_CX + TEXT_SPACE_CX,
				    y + (PROG_TB_CY - Size.cy)/2,
				    PROG_TEXT_CX, Size.cy,
				    hWndMain, (HMENU)0, hInst, NULL)) == NULL )
	{
	lpszControl = szProgress;
	uType = IDS_ERROR_STATICTEXT;
	goto DISPLAY_CREATE_FAILURE;
	}

    /* Create the PROGRESSS trackbar */
    if(( hWndProg = CreateWindow( TRACKBAR_CLASS, NULL,
				WS_CHILD | WS_VISIBLE | TBS_HORZ
				| TBS_BOTTOM | WS_DISABLED,
				BORDER_SPACE_CX,
				y, PROG_TB_CX, PROG_TB_CY,
				hWndMain, (HMENU)0, hInst, NULL)) == NULL )
	{
	lpszControl = szProgress;
	uType = IDS_ERROR_TRACKBAR;
	goto DISPLAY_CREATE_FAILURE;
	}

    SendMessage( hWndProg, TBM_SETRANGE,
			FALSE, MAKELPARAM( PROG_MIN, PROG_MAX ));
    SendMessage( hWndProg, TBM_SETPOS, TRUE, 0L );

    x = BORDER_SPACE_CX + PAN_TEXT_CX + TEXT_SPACE_CX
		+ PAN_TB_CX + CONTROL_SPACE_CX;
    y += PROG_TB_CY;
    y -= 2*(BUTTON_CY + BUTTON_SPACE_CY) + CHECK_CY;

    /* Create the LOOPED CHECKBOX */
    LoadString( hInst, IDS_CHECK_LOOPED, szTemp, sizeof(szTemp));
    if(( hWndLoopCheck = CreateWindow( "button", szTemp,
				WS_CHILD | WS_VISIBLE | BS_CHECKBOX,
				x, y, CHECK_CX, CHECK_CY, hWndMain,
				(HMENU)IDC_LOOPCHECK, hInst, NULL )) == NULL )
	{
	lpszControl = szTemp;
	uType = IDS_ERROR_CHECK;
	goto DISPLAY_CREATE_FAILURE;
	}
    y += CHECK_CY + BUTTON_SPACE_CY;

    /* Create the PLAY BUTTON */
    LoadString( hInst, IDS_BUTTON_PLAY, szTemp, sizeof(szTemp));
    if(( hWndPlay = CreateWindow( "button", szTemp,
				    WS_CHILD | WS_VISIBLE | WS_DISABLED,
				    x, y, BUTTON_CX, BUTTON_CY, hWndMain,
				    (HMENU)IDM_PLAY, hInst, NULL )) == NULL )
	{
	lpszControl = szTemp;
	uType = IDS_ERROR_BUTTON;
	goto DISPLAY_CREATE_FAILURE;
	}
    y += BUTTON_CY + BUTTON_SPACE_CY;

    /* Create the STOP BUTTON */
    LoadString( hInst, IDS_BUTTON_STOP, szTemp, sizeof(szTemp));
    if(( hWndStop = CreateWindow( "button", szTemp,
				    WS_CHILD | WS_VISIBLE | WS_DISABLED,
				    x, y, BUTTON_CX, BUTTON_CY, hWndMain,
				    (HMENU)IDM_STOP, hInst, NULL )) == NULL )
	{
	lpszControl = szTemp;
	uType = IDS_ERROR_BUTTON;
	goto DISPLAY_CREATE_FAILURE;
	}

    UpdateFromControls();
    goto RETURN_NORMAL;

DISPLAY_CREATE_FAILURE:
    LoadString( hInst, uType, szType, sizeof(szType));
    wsprintf( szTemp, szTemplate, lpszControl, szType );
    MessageBox( GetActiveWindow(), szTemp,
			szAppTitle, MB_OK | MB_ICONEXCLAMATION );
    return( TRUE );

RETURN_NORMAL:
    return( FALSE );
    }


/********************************************************************************/
/* UpdateFromControls()                                                         */
/*                                                                              */
/*    This function gets all the required values from the DirectSoundBuffer and */
/* updates the screen interface controls.                                       */
/*                                                                              */
/********************************************************************************/
void UpdateFromControls( void )
    {
    long        lPan, lVol;
    HRESULT     dsRetVal;

    lPan = (LONG)SendMessage( hWndPan, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );
    lVol = (LONG)SendMessage( hWndVol, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );

    /* Set the volume and then the pan */
    if( lpDSBStreamBuffer )
	{
	/* Set the volume */
	wsprintf( szTemp, "%s: %lidB", szVolume,
					    ( lVol + VOL_SHIFT ) / VOL_DIV );
	Static_SetText( hWndVolText, szTemp );

	dsRetVal = lpDSBStreamBuffer->lpVtbl->SetVolume( lpDSBStreamBuffer,
					    (((lVol+VOL_SHIFT) * VOL_MULT)) );
	if( dsRetVal != 0 )
	    DPF( 0, "Unable to SetVolume in UpdateFromControls()" );
	else
	    {
	    wsprintf( szDebug, "Set volume to %lidB",
					    ( lVol + VOL_SHIFT ) / VOL_DIV );
	    DPF( 3, szDebug );
	    }

	/* Set the Pan */
	wsprintf( szTemp, "%s: %lidB", szPan, ( lPan + PAN_SHIFT ) / PAN_DIV );
	Static_SetText( hWndPanText, szTemp );

	dsRetVal = lpDSBStreamBuffer->lpVtbl->SetPan( lpDSBStreamBuffer,
					    (((lPan+PAN_SHIFT) * PAN_MULT)) );
	if( dsRetVal != 0 )
	    DPF( 0, "Unable to SetPan in UpdateFromControls()" );
	else
	    {
	    wsprintf( szDebug, "Set pan to %lidB",
					    ( lPan + PAN_SHIFT ) / PAN_DIV );
	    DPF( 3, szDebug );
	    }

	}
	return;
    }


/********************************************************************************/
/* UpdateControlText()                                                          */
/*                                                                              */
/*    This function gets all the required values from the DirectSoundBuffer and */
/* updates the screen text controls to reflect those values.  It does not move  */
/* the trackbar settings.                                                       */
/*                                                                              */
/********************************************************************************/
void UpdateControlText( void )
    {
    long        lPan, lVol;

    lPan = (LONG)SendMessage( hWndPan, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );
    lVol = (LONG)SendMessage( hWndVol, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );

    /* Set the volume and then the pan */
    if( lpDSBStreamBuffer )
	{
	/* Set the volume */
	wsprintf( szTemp, "%s: %lidB", szVolume,
					    ( lVol + VOL_SHIFT ) / VOL_DIV );
	Static_SetText( hWndVolText, szTemp );

	/* Set the Pan */
	wsprintf( szTemp, "%s: %lidB", szPan, ( lPan + PAN_SHIFT ) / PAN_DIV );
	Static_SetText( hWndPanText, szTemp );
	}
	return;
    }


/********************************************************************************/
/* HandlePanScroll()                                                            */
/*                                                                              */
/*   Handles the pan trackbar scroll when a WM_HSCROLL is received.             */
/*                                                                              */
/********************************************************************************/
void HandlePanScroll( int nCode, int nPos )
    {
    long  lPan, lDelta;

    lPan = (LONG)SendMessage( hWndPan, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );

    switch( nCode )
	{
	case TB_LINEUP:
	    if( lPan >= PAN_MIN - 1 )
		lDelta = -1;
	    break;
	case TB_LINEDOWN:
	    if( lPan <= PAN_MAX + 1 )
		lDelta = 1;
	    break;
	case TB_PAGEUP:
	    if( lPan >= PAN_MIN - PAN_PAGESIZE )
		lDelta = -16;
	    break;
	case TB_PAGEDOWN:
	    if( lPan <= PAN_MAX + PAN_PAGESIZE )
		lDelta = 16;
	    break;
	case TB_ENDTRACK:
	    return;
	default:
	    lDelta = 0;
	}

    if( lDelta )
	SendMessage( hWndPan, TBM_SETPOS, TRUE, lPan + lDelta );
    else
	SendMessage( hWndPan, TBM_SETPOS, TRUE, (long)nPos );
    UpdateFromControls();
    }


/********************************************************************************/
/* HandleVolScroll()                                                            */
/*                                                                              */
/*   Handles the volume trackbar scrolling when a WM_HSCROLL is received.       */
/*                                                                              */
/********************************************************************************/
void HandleVolScroll( int nCode, int nPos )
    {
    long  lVol, lDelta;

    lVol = (LONG)SendMessage( hWndVol, TBM_GETPOS, (WPARAM)0, (LPARAM)0 );

    switch( nCode )
	{
	case TB_LINEDOWN:
	    if( lVol <= VOL_MAX - 1 )
		lDelta = 1;
	    break;
	case TB_LINEUP:
	    if( lVol >= VOL_MIN + 1 )
		lDelta = -1;
	    break;
	case TB_PAGEDOWN:
	    if( lVol <= VOL_MAX - VOL_PAGESIZE )
		lDelta = 10;
	    break;
	case TB_PAGEUP:
	    if( lVol >= VOL_MIN + VOL_PAGESIZE )
		lDelta = -10;
	    break;
	case TB_ENDTRACK:
	    return;
	default:
	    lDelta = 0;
	}

    if( lDelta )
	SendMessage( hWndVol, TBM_SETPOS, TRUE, (lVol + lDelta));
    else
	SendMessage( hWndVol, TBM_SETPOS, TRUE, (long)nPos );
    UpdateFromControls();
    }


/****************************************************************************/
/* ErrorMessageBox()                                                        */
/*                                                                          */
/*   A little routine to load error messages from the string resource table */
/* and pop them up in a MessageBox() for the world to see. The dwMBFlags    */
/* parameter allows the caller to specify the type of icon to use.          */
/*                                                                          */
/****************************************************************************/
void ErrorMessageBox( UINT uID, DWORD dwMBFlags )
    {
    LoadString( hInst, uID, szTemp, sizeof(szTemp));
    MessageBox( GetActiveWindow(), szTemp, szAppTitle, MB_OK | dwMBFlags );
    }


/****************************************************************************/
/* BuildTitleBar()                                                          */
/*                                                                          */
/*   Small routine to build and set the title bar text depending on whether */
/* or not a file is open.                                                   */
/****************************************************************************/
void BuildTitleBarText( void )
    {
    char szTitle[ sizeof(szAppCaption) + MAX_PATH + sizeof(" - ")];

    lstrcpy( szTitle, szAppCaption );
    if( bFileOpen )
	{
	lstrcat( szTitle, " - " );
	lstrcat( szTitle, szFileTitle );
	}
    SetWindowText( hWndMain, szTitle );
    }


/****************************************************************************/
/* 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( szCDStartPath );
	if( ERROR_SUCCESS != RegQueryValueEx( hReg, gszRegValueD3DPath,
				NULL, NULL,
				(LPBYTE )szCDStartPath, &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( szCDStartPath, sizeof(szCDStartPath));
    // If there's no trailing backslash, append one
    if( lstrcmp( &szCDStartPath[lstrlen(szCDStartPath)], TEXT("\\") ))
	lstrcat( szCDStartPath, TEXT("\\"));
    // Now add on the MEDIA part of the path
    lstrcat( szCDStartPath, TEXT("MEDIA"));
    }

void LoadFromCommandLine( LPSTR lpszCmd )
    {
    LPSTR lpsz = lpszCmd;
    LPSTR lpToken;
    BOOL  fStartPlaying = FALSE, fStartLooping = FALSE;
    char  szToken[MAX_TOKEN_LEN+1];
    int   i;

    if( !lpsz )
	return;

    // Clear leading spaces
    while( *lpsz == ' ' )
	lpsz++;
    
    // If we need to accept more command-line parameters later, we can
    // extend the code below into a loop that searchs for each one.
    while( *lpsz == '/' || *lpsz == '-' )
	{
	// Don't advance lpsz until we're sure we really should be reading
	// this string (i.e. we recognize that it's the play command
	lpToken = ++lpsz;
	for( i = 0; i < MAX_TOKEN_LEN; i++ )
	    {
	    if( !*lpToken || *lpToken == ' ' )
		break;
	    szToken[i] = *lpToken++;
	    }
	szToken[i] = 0;

	if( !lstrcmpi( szToken, gszPlayToken ))
	    {
	    // Automagically start playing the file
	    fStartPlaying = TRUE;
	    lpsz = lpToken;
	    }
	else if( !lstrcmpi( szToken, gszLoopToken ))
	    {
	    // Set the player in looping mode at startup
	    fStartLooping = TRUE;
	    lpsz = lpToken;
	    }
	else if( !lstrcmpi( szToken, gszStickyToken ))
	    {
	    // Use Sticky Focus for the buffer
	    gdwFocus = DSBCAPS_STICKYFOCUS;
	    lpsz = lpToken;
	    }
	else if( !lstrcmpi( szToken, gszGlobalToken ))
	    {
	    // Use Global Focus for the buffer
	    gdwFocus = DSBCAPS_GLOBALFOCUS;
	    lpsz = lpToken;
	    }
	else if( !lstrcmpi( szToken, gszCloseToken ))
	    {
	    // "/close" will cause the program to shutdown after it's done
	    // playing the file that was presumably loaded at the command-line
	    gfCloseOnDone = TRUE;
	    lpsz = lpToken;
	    }
	else
	    {
	    // Unrecognized parameter followed the slash, so skip over it
	    // and find the next break
	    while( *lpsz && *lpsz != ' ' )
		lpsz++;
	    }
	// Clear any spaces out again
	while( *lpsz == ' ' )
	    lpsz++;
	}

    // If that's all that was on the command-line, simply return
    if( !*lpsz )
	return;

    // ASSUMPTION: We assume that a single filename is the only remaining
    // parameter.  This works out okay because anything else will fail in the
    // file load inside StreamBufferSetup();
    lstrcpy( szFileBuffer, lpsz );

    // Search backwards and find the last backslash, stopping at the
    // beginning of the file name
    lpsz = &szFileBuffer[lstrlen(szFileBuffer)];

    while( lpsz > szFileBuffer )
	{
	if( *(lpsz-1) == '\\' )
	    {
	    break;
	    }
	lpsz--;
	}
    // Fake the szFileTitle, which normally gets set by the Common Dialog
    lstrcpy( szFileTitle, lpsz );
    lstrcpy( szCDStartPath, szFileBuffer );
    szCDStartPath[lpsz-szFileBuffer] = 0;

    if( fStartLooping )
	{
	// Allowing auto-close when the user will have plenty of time to click
	// stop would cause the app to shutdown right as they hit the button,
	// which is weird behavior.
	gfCloseOnDone = FALSE;
	Button_SetCheck( hWndLoopCheck, TRUE );
	}

    if( StreamBufferSetup() != 0 )
	{
	// Error opening the WAVE file so abort
	return;
	}
    else
	{
	bFileOpen = TRUE;
	EnableMenuItem( GetMenu( hWndMain ), IDM_PLAY,
			MF_BYCOMMAND | MF_ENABLED );
	EnableWindow( hWndPlay, TRUE );
	EnableMenuItem( GetMenu( hWndMain ), IDM_FILE_CLOSE,
			MF_BYCOMMAND | MF_ENABLED );
	DrawMenuBar( hWndMain );
	BuildTitleBarText();

	if( fStartPlaying )
	    SendMessage( hWndMain, WM_COMMAND, MAKEWPARAM( IDM_PLAY, 0 ), 0L );
	}
    }


