/*==========================================================================
 *
 *  Copyright (C) 1995-1997 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       DSShow3d.c
 *  Content:    Direct Sound show-off, including 3D Sound.
 *
 *
 ***************************************************************************/

//#pragma warning( disable: 4102 4101 )

#define INITGUID
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <commdlg.h>
#include <cguid.h>
#include <shlobj.h> // for browseinfo
#include <mmsystem.h>
#include <mmreg.h>
#include <msacm.h>
#include <dsound.h>

#include "DSShow3D.h"
#define INIT_GVARS
#include "GVars.h"

#include "MainWnd.h"
#include "FileInfo.h"

#include "wave.h"
#include "debug.h"

static TCHAR szOpenStartDir[MAX_PATH];
static BOOL gfDisable3D = FALSE;

BOOL IsPentiumOrBetter(void);

// Format codes used to compactly represent each possible output format
//
// The code is: FFCBB where...
//
//    FF -> Frequency 8=8000, 11=11025, 22=22050, 44=44100
//    C  -> # of channels (1 or 2)
//    BB -> Bits 08 is 8-bit, 16 is 16-bit
//
// Use the FormatCodeToWFX() function to set a WAVEFORMATEX structure
// based on a format code, or FormatCodeToText() to get a text string
// representing the format.
//

#define FC_GETFREQCODE(fc)	((fc) / 1000)
#define FC_GETBITS(fc)		((fc) % 100)
#define FC_GETCHANNELS(fc)	(((fc) % 1000) / 100)

// Functions limited in scope to this file

static BOOL InitializeDSound( void );
static void FreeDSound( void );
static BOOL InitInstance( HINSTANCE, LPTSTR, int );
static BOOL InitPrimarySoundBuffer( void );
static void GetMediaPath( LPTSTR, int );
static BOOL LoadRegistrySettings( void );
static BOOL SaveRegistrySettings( void );
static BOOL ParseCommandLine( LPTSTR lpszCmdLine );
static BOOL fMatchToken( PSTR pszString, PSTR pszDatum, int cchLen );
static BOOL fGetToken( PSTR pszStart, PSTR *ppszRet, int *pcchRet );


/* InitializeDSound()
 *
 *    Initialize the DirectSound object and other stuff we'll use like the
 * primary buffer and 3D listener object.
 */
BOOL InitializeDSound( void )
    {
    HRESULT hr;

    hr = CoCreateInstance( CLSID_DirectSound, NULL, CLSCTX_INPROC_SERVER,
					IID_IDirectSound, (void**)&gpds );

    if (FAILED(hr) || (NULL == gpds))
	{
	DPF( 0, "Could not create a DSound object (%s)", TranslateDSError(hr));
	MessageBox( AppWnd.GetHwnd(), "Unable to get a DirectSound object",
				    "COM Failure", MB_OK | MB_ICONSTOP );
	goto ID_ExitError;
	}
    DPF( 2, "Got an IDirectSound object" );

    if( grs.fDefaultDevice )
	{
	DPF( 2, "Using default device as first choice" );
	hr = gpds->Initialize(NULL );
	}
    else
	{
	if( FAILED( hr = gpds->Initialize( &grs.guPreferredDevice )))
	    {
	    DPF( 0, "Initialize failed on preferred device, using default" );
	    if( IDNO == MessageBox( GetActiveWindow(),
			"Unable to use preferred device. Use default instead?",
			gszAppName, MB_YESNO ))
		{
		DPF( 2, "User chose not to use default device instead" );
		goto ID_ExitReleaseDS;
		}
	    else
		{
		DPF( 2, "Falling back to default device" );
		hr = gpds->Initialize( NULL );
		}
	    }
	}
    if (FAILED(hr))
	{
	DPF( 0, "Failed Initialize() on IDirectSound object (%s)", TranslateDSError(hr));
	MessageBox( AppWnd.GetHwnd(), "Could not initialize DirectSound object",
				    gszAppName, MB_OK | MB_ICONSTOP );
	goto ID_ExitReleaseDS;
	}

    if( grs.fUseExclusiveMode )
	{
	hr = gpds->SetCooperativeLevel( AppWnd.GetHwnd(), DSSCL_EXCLUSIVE);
        DPF( 3, "Received DSSCL_EXCLUSIVE" );
	}
    else
	{
	hr = gpds->SetCooperativeLevel( AppWnd.GetHwnd(), DSSCL_PRIORITY);
        DPF( 3, "Received DSSCL_PRIORITY" );
	}
    if (FAILED(hr))
	{
	DPF( 0, "Couldn't SetCooperativeLevel() (%s)", TranslateDSError(hr));
	MessageBox( AppWnd.GetHwnd(), "Could not SetCooperativeLevel()",
				    gszAppName, MB_OK | MB_ICONSTOP );
	goto ID_ExitReleaseDS;
	}

    DPF( 3, "Creating Primary Buffer" );

    if( !InitPrimarySoundBuffer())
	{
	DPF( 0, "Failed on call to InitPrimarySoundBuffer()" );
	goto ID_ExitReleaseDS;
	}

    // Return SUCCESS
    return TRUE;

ID_ExitReleaseDS:
    // The InitPrimarySoundBuffer() call should have cleaned up
    // after itself if it failed

    ASSERT( NULL == gp3DListener );
    ASSERT( NULL == gpdsbPrimary );

    if( NULL != gpds )
	{
	gpds->Release();
	gpds = NULL;
	}

ID_ExitError:
    return FALSE;
    }


void FreeDSound()
    {
    if( NULL != gpdsbPrimary )
	{
	gpdsbPrimary->Stop();
	gpdsbPrimary->Release();
	gpdsbPrimary = NULL;
	}
    if( NULL != gp3DListener )
	{
	gp3DListener->Release();
	gp3DListener = NULL;
	}
    if( NULL != gpds )
	{
	gpds->Release();
	gpds = NULL;
	}
    }


/* InitInstance()
 *
 *    This function is responsible for all initialization that must occur
 * when a new instance of our application is started.
 */
BOOL InitInstance( HINSTANCE hInstance, LPTSTR lpszCommandLine, int cmdShow )
    {
    WNDCLASS    myClass;

    myClass.hCursor             = LoadCursor(NULL, IDC_ARROW);
    myClass.hIcon               = LoadIcon( hInstance, MAKEINTRESOURCE(IDI_SPEAKER_RGB_3D));
    myClass.lpszMenuName        = MAKEINTRESOURCE(IDR_MAINMENU);
    myClass.lpszClassName       = (LPTSTR)gszAppWndClass;
    myClass.hbrBackground       = (HBRUSH)(COLOR_APPWORKSPACE + 1);
    myClass.hInstance           = hInstance;
    myClass.style               = CS_HREDRAW | CS_VREDRAW;
    myClass.lpfnWndProc         = MainWndProc;
    myClass.cbClsExtra          = 0;
    myClass.cbWndExtra          = 0;

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

    // Load the current registry settings
    LoadRegistrySettings();
    gdwOutputFormat = grs.dwPreferredFormat;

    if( !AppWnd.Create())
	goto II_ExitError;

    AppWnd.ShowWindow( cmdShow );
    AppWnd.UpdateWindow();

    /* Continue doing other initialization stuff */

    // Setup the timer...
    if ((gdwTimer = SetTimer(AppWnd.GetHwnd(), 1, TIMERPERIOD, NULL)) == 0)
	{
	MessageBox(AppWnd.GetHwnd(), "Cannot allocate timer, aborting", gszAppCaption,
			MB_OK|MB_ICONSTOP);
	goto II_ExitError;
	}

    // Get the largest waveformatex structure.
    if (MMSYSERR_NOERROR != acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT,
						    &gcbMaxWaveFormatSize))
        {
	MessageBox(AppWnd.GetHwnd(), "ACM Metrics failed, aborting", gszAppCaption,
		   MB_OK|MB_ICONSTOP);
	goto II_ExitError;
	}

    // Initialize the COM subsystem and create our DirectSound stuff

    if (!SUCCEEDED(CoInitialize(NULL)))
	{
	MessageBox( AppWnd.GetHwnd(), "Failed to initialize COM library",
			gszAppCaption, MB_OK | MB_ICONSTOP);
	goto II_ExitError;
	}
    else
	gfCOMInitialized = TRUE;

    // Initialize the DirectSound global interfaces
    if( !InitializeDSound())
	goto II_ExitShutdownCOM;

    if( !ParseCommandLine( lpszCommandLine ))
	goto II_ExitFreeDSound;

    return TRUE;    // TRUE indicates success


II_ExitFreeDSound:
    FreeDSound();

II_ExitShutdownCOM:
    if( gfCOMInitialized )
	{
	DPF( 0, "Calling CoUninitialize from InitInstance error cleanup" );
	CoUninitialize();
	}

II_ExitError:

    return FALSE;   // FALSE indicates failure on initialization
    }	// InitInstance()


/* WinMain()
 *
 *   Main entry point for this program's execution.  Everything starts here.
 */
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
						LPTSTR lpszCmdLine, int cmdShow)
    {
    MSG   msg;

    DbgInitialize( TRUE );

    InitCommonControls();

    // Save instance handle
    ghInst = hInstance;

    if (!InitInstance(hInstance, lpszCmdLine, cmdShow))
	return 0;

    /* Polling messages from event queue */
    while (GetMessage((LPMSG)&msg, NULL, 0, 0))
	{
	// Only Translate and Dispatch the message if it's not going
	// to one of our many modeless dialog windows
	if( !IsDialogMessage( ghwndListener, &msg )
	    && (NULL == ghDlgActive || !IsDialogMessage( ghDlgActive, &msg )))
	    {
	    TranslateMessage((LPMSG)&msg);
	    DispatchMessage((LPMSG)&msg);
	    }
	}

    UnregisterClass(gszAppWndClass, hInstance);
    return (int)msg.wParam;
    }	// WinMain()


/* InitPrimarySoundBuffer()
 *
 *    Creates and initializes the primary sound buffer for the application.
 * We need the primary buffer in order to get the 3D listener interface and
 * also to select output format type.
 */
BOOL InitPrimarySoundBuffer( void )
    {
    HRESULT	    hr;
    DSBUFFERDESC    dsbd;
    int		    nCurFormat;

    ZeroMemory( &dsbd, sizeof(DSBUFFERDESC));
    
    gpwfxFormat = (LPWAVEFORMATEX)GlobalAlloc(GPTR, sizeof(WAVEFORMATEX));
    if( NULL == gpwfxFormat )
	return FALSE;

    ZeroMemory( gpwfxFormat, sizeof(WAVEFORMATEX));

    gpwfxFormat->wFormatTag = WAVE_FORMAT_PCM;

    FormatCodeToWFX( gdwOutputFormat, gpwfxFormat );
    DPF( 2, "Initial format code: %lu", gdwOutputFormat );

    dsbd.dwSize = sizeof(DSBUFFERDESC);
    dsbd.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_PRIMARYBUFFER;

    if( FAILED( hr = gpds->CreateSoundBuffer( &dsbd, &gpdsbPrimary, NULL )))
	{
	DPF( 0, "Couldn't create primary buffer (%s)", TranslateDSError(hr));
	goto IPSB_ExitError;
	}

    if( FAILED( hr = gpdsbPrimary->SetFormat( gpwfxFormat )))
	{
		DisableFormatCode( gdwOutputFormat );

		// If we couldn't load the desired format, then try everything starting
		// at really high quality, and degrading to 8M8.
		nCurFormat = 0;
		DPF( 2, "Unable to SetFormat() preferred format to %lu", gdwOutputFormat );

		while( FAILED( hr ) && nCurFormat < NUM_FORMATENTRIES )
		{
			gdwOutputFormat = aFormatOrder[nCurFormat];
			FormatCodeToWFX( gdwOutputFormat, gpwfxFormat );
			DPF( 2, "Trying format code: %lu", gdwOutputFormat );

			hr = gpdsbPrimary->SetFormat( gpwfxFormat );
			if( FAILED(hr))
			{
				DisableFormatCode( gdwOutputFormat );
				DPF( 2, "Return from SetFormat on primary buffer = %s", TranslateDSError(hr));
			}
			else
			{
				EnableFormatCode( gdwOutputFormat );
				DPF( 2, "Succeeded on SetFormat() for code %lu", gdwOutputFormat );
			}

			nCurFormat++;
		}//while
	}

    if( FAILED( hr ))
	{
		DPF( 0, "Failed SetFormat() on all formats!" );
		goto IPSB_ExitError;
	}

    if( FAILED( hr = gpdsbPrimary->QueryInterface( IID_IDirectSound3DListener,
						(void**)&gp3DListener)))
	{
	DPF( 0, "Couldn't QI primary buffer for 3D listener interface (%s)", TranslateDSError(hr));
	goto IPSB_ExitError;
	}
    if( FAILED( hr = gpdsbPrimary->Play( 0, 0, DSBPLAY_LOOPING )))
	{
	DPF( 0, "Couldn't play primary buffer (%s)", TranslateDSError(hr));
	goto IPSB_ExitRelease;
	}

    return TRUE;

IPSB_ExitRelease:
    if( gp3DListener )
	{
	DPF( 0, "Releasing 3D Listener in InitPrimarySoundBuffer() error cleanup" );
	gp3DListener->Release();
	gp3DListener = NULL;
	}
    if( gpdsbPrimary )
	{
	DPF( 0, "Releasing Primary in InitPrimarySoundBuffer() error cleanup" );
	gpdsbPrimary->Stop();
	gpdsbPrimary->Release();
	gpdsbPrimary = NULL;
	}

IPSB_ExitError:
    return FALSE;
    }


/*  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 >= MAX_PATH long.


    Output:
	TRUE if a file was picked successfully or FALSE if the user didn't
	pick a file)

 */
BOOL OpenFileDialog( HWND hWnd, LPTSTR pszFileName,
					int *nFileName, LPDWORD lpdwFlags )
    {
    BOOL            fReturn,
		    fValid;
    OPENFILENAME    ofn;

    pszFileName[0]          = 0;

    ofn.lStructSize         = sizeof(ofn);
    ofn.hwndOwner           = hWnd;
    ofn.hInstance           = ghInst;
    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     = grs.szInitialDir;
    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)lpdwFlags;
    ofn.lpfnHook            = FileOpenCustomTemplateDlgProc;
    ofn.lpTemplateName      = MAKEINTRESOURCE(IDD_FILEOPEN_NEST);

    fValid = FALSE;
    do
    {
    if( fReturn = GetOpenFileName( &ofn ))
	{
	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( LPTSTR 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;
    }


/* AboutDlgProc()
 *
 *    Standard dialog procedure for the About box.  As simple as can be.
 */
BOOL CALLBACK AboutDlgProc( 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 FALSE;
	    break;

	}

    return TRUE;
}


/* FileOpenCustomTemplateDlgProc()
 *
 * 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 add a check box for stick buffers.
 */
UINT CALLBACK FileOpenCustomTemplateDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
    {
    static LPOPENFILENAME   lpofn = NULL;
    static HWND		    hStickyRadio, h3DCheck, hLocalRadio, hGlobalRadio;
    static HWND	        hGetPosRadio, hGetPos2Radio, hMuteAtMaxDist, hNotifyCheck;

    switch( message )
    {
    case WM_INITDIALOG:
	{
        lpofn = (LPOPENFILENAME)lParam;

		h3DCheck = GetDlgItem( hDlg, IDC_FONEST_3D );
		hLocalRadio = GetDlgItem( hDlg, IDC_FONEST_LOCAL_RADIO );
		hStickyRadio = GetDlgItem( hDlg, IDC_FONEST_STICKY_RADIO );
		hGlobalRadio = GetDlgItem( hDlg, IDC_FONEST_GLOBAL_RADIO );
		hGetPosRadio = GetDlgItem( hDlg, IDC_FONEST_GETPOS_RADIO );
		hGetPos2Radio = GetDlgItem( hDlg, IDC_FONEST_GETPOS2_RADIO );
		hNotifyCheck = GetDlgItem(hDlg, IDC_FONEST_NOTIFYCHECK);
		hMuteAtMaxDist = GetDlgItem(hDlg, IDC_FONEST_MUTEATMAX);

		Button_SetCheck( hGetPos2Radio, TRUE );

		if( grs.dwDefaultFocusFlag & DSBCAPS_STICKYFOCUS )
			Button_SetCheck( hStickyRadio, TRUE );
		else if( grs.dwDefaultFocusFlag & DSBCAPS_GLOBALFOCUS )
			Button_SetCheck( hGlobalRadio, TRUE );
		else
			{
			ASSERT( grs.dwDefaultFocusFlag == 0 );
			Button_SetCheck( hLocalRadio, TRUE );
			}

		if (gfDisable3D)
		{
			grs.fOpen3D = FALSE;
		}
		
		Static_Enable(h3DCheck, !gfDisable3D);
		Button_SetCheck( h3DCheck, grs.fOpen3D );

		Static_Enable(hMuteAtMaxDist, Button_GetCheck(h3DCheck));

		*((LPDWORD)lpofn->lCustData) = 0;
	}
    return TRUE;


	case WM_COMMAND:
		if(LOWORD(wParam) == IDC_FONEST_3D)
		{
			Static_Enable(hMuteAtMaxDist, Button_GetCheck(h3DCheck));
		}
		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...
             */
            ASSERT( lpofn != NULL );
	    /* Set flags to match the current state of the check box controls */

	    /* Use normal GetPosition */
	    *((LPDWORD)lpofn->lCustData) |=
		Button_GetCheck(hGetPosRadio)? OPENFILENAME_F_GETPOS : 0;
	    /* Use DSBCAPS_GETCURRENTPOSITION2 */
	    *((LPDWORD)lpofn->lCustData) |=
		Button_GetCheck(hGetPos2Radio)? OPENFILENAME_F_GETPOS2 : 0;

	    /* Local buffer focus */
	    *((LPDWORD)lpofn->lCustData) |=
		Button_GetCheck(hStickyRadio)? OPENFILENAME_F_LOCALFOCUS : 0;
	    /* Sticky buffer focus */
	    *((LPDWORD)lpofn->lCustData) |=
		Button_GetCheck(hStickyRadio)? OPENFILENAME_F_STICKYFOCUS : 0;
	    /* Global sound focus */
	    *((LPDWORD)lpofn->lCustData) |=
		Button_GetCheck(hGlobalRadio)? OPENFILENAME_F_GLOBALFOCUS : 0;
	    /* 3D Buffer */
	    *((LPDWORD)lpofn->lCustData) |=
		    Button_GetCheck(h3DCheck)? OPENFILENAME_F_3D : 0;

		// Are Notifies On
	    *((LPDWORD)lpofn->lCustData) |=
		    Button_GetCheck(hNotifyCheck)? OPENFILENAME_F_NOTIFY : 0;

        /* Mute at max distance */
        *((LPDWORD)lpofn->lCustData) |=
            Button_GetCheck(hMuteAtMaxDist)? OPENFILENAME_F_MUTEMAX : 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;
}


/* SettingsDlgProc()
 *
 *    DialogBox procedure for the Settings dialog, which sets a bunch of
 * the application-global settings stored in our global REGSETTINGS struct.
 */
BOOL CALLBACK SettingsDlgProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
    {
    static BOOL fSettingsSaved;
    static HWND	hInitialDirEdit, hDeviceCombo, hDeviceText, hDefaultCheck;
    static HWND hFormatCombo, hExclusiveCheck, hOpen3DCheck, hFocusCombo;
	static BOOL fDefaultDevice, fXMode;
	static DWORD dwFormat;
	static BOOL fInited = FALSE;
    int	    ndx, i, idxLocal, idxSticky, idxGlobal;
    TCHAR   szFormat[32];
    LPGUID  lpguTemp;

    switch( message )
	{
	case WM_INITDIALOG:
		{
		WAVEFORMATEX wfx, wfxActual;
		HRESULT hr = DS_OK;
		ZeroMemory(&wfx, sizeof(wfx));
		ZeroMemory(&wfxActual, sizeof(wfxActual));

		hInitialDirEdit = GetDlgItem( hDlg, IDC_SETTINGS_INITIALDIR_EDIT );
		hDeviceCombo = GetDlgItem( hDlg, IDC_SETTINGS_DSD_DEVICE_COMBO );
		hFormatCombo = GetDlgItem( hDlg, IDC_SETTINGS_DSD_FORMAT_COMBO );
		hFocusCombo = GetDlgItem( hDlg, IDC_SETTINGS_FOCUS_COMBO );
		hDeviceText = GetDlgItem( hDlg, IDC_SETTINGS_DSD_DEVICE_TEXT );
		hDefaultCheck = GetDlgItem( hDlg, IDC_SETTINGS_DSD_DEFAULT_CHECK );
		hExclusiveCheck = GetDlgItem( hDlg, IDC_SETTINGS_EXCLUSIVE_CHECK );
		hOpen3DCheck = GetDlgItem( hDlg, IDC_SETTINGS_OPEN3D_CHECK );

	    // We use this flag to determine the return value from DialogBox()
	    // FALSE indicates no change, TRUE means a change occurred.
	    fSettingsSaved = FALSE;

	    // Add all the format strings to the combo box
	    for( i = 0; i < NUM_FORMATENTRIES; i ++ )
		{
			// Only add formats that are available on this card (this will
			// ignore any formats we couldn't SetFormat() with at startup,
			// and any we have since found to be unusable as well).
			if( !fdFormats[i].fEnable )
				continue;

			wfx.wFormatTag = WAVE_FORMAT_PCM;
			if( FormatCodeToText(fdFormats[i].dwCode, szFormat, sizeof(szFormat)))
				{
				// Do a set and get format to see what the card supports.
				FormatCodeToWFX(fdFormats[i].dwCode, &wfx);
				hr = gpdsbPrimary->SetFormat(&wfx);				
				if (hr != DS_OK)
					DPF(0, "SettingsDlgProc: SetFormat failed");
				hr = gpdsbPrimary->GetFormat(&wfxActual, sizeof(WAVEFORMATEX), NULL);
				if (hr != DS_OK)
					DPF(0, "SettingsDlgProc: GetFormat failed");
				if (!IsSameFormat(&wfx, &wfxActual))
				{
					char szTemp[2] = "*";
					strcat(szFormat, szTemp);
				}
				ndx = ComboBox_AddString( hFormatCombo, szFormat );
				ComboBox_SetItemData( hFormatCombo, ndx, fdFormats[i].dwCode );
				}
		} //for

		// restore the format.
		FormatCodeToWFX(gdwOutputFormat, &wfx);
		hr = gpdsbPrimary->SetFormat(&wfx);
		ASSERT(hr == DS_OK);

	    DirectSoundEnumerate( (LPDSENUMCALLBACK)DSEnumProc, (LPVOID)&hDeviceCombo );

	    // Add the three focus choices to the listbox, and set the item data
	    // for each to the appropriate flag
	    idxLocal = ComboBox_AddString( hFocusCombo, "Local" );
	    ComboBox_SetItemData( hFocusCombo, idxLocal, 0 );
	    idxSticky = ComboBox_AddString( hFocusCombo, "Sticky" );
	    ComboBox_SetItemData( hFocusCombo, idxSticky, DSBCAPS_STICKYFOCUS );
	    idxGlobal = ComboBox_AddString( hFocusCombo, "Global" );
	    ComboBox_SetItemData( hFocusCombo, idxGlobal, DSBCAPS_GLOBALFOCUS );

	    // Select the proper Drop List item
	    if( grs.dwDefaultFocusFlag == DSBCAPS_STICKYFOCUS )
		ComboBox_SetCurSel( hFocusCombo, idxSticky );
	    else if( grs.dwDefaultFocusFlag == DSBCAPS_GLOBALFOCUS )
		ComboBox_SetCurSel( hFocusCombo, idxGlobal );
	    else
		ComboBox_SetCurSel( hFocusCombo, idxLocal );

	    // Set the states of checkboxes and controls that depend on them
	    Button_SetCheck( hDefaultCheck, grs.fDefaultDevice );
	    Button_SetCheck( hExclusiveCheck, grs.fUseExclusiveMode );
		// is 3D on or off?
		if (gfDisable3D)
		{
			grs.fOpen3D = FALSE;
			Static_Enable(hOpen3DCheck, FALSE);
		}
	    Button_SetCheck( hOpen3DCheck, grs.fOpen3D );
	    Static_Enable( hDeviceCombo, !grs.fDefaultDevice );
	    Static_Enable( hDeviceText, !grs.fDefaultDevice );

	    Edit_LimitText( hInitialDirEdit, sizeof(grs.szInitialDir));
	    Edit_SetText( hInitialDirEdit, grs.szInitialDir );

	    if (FormatCodeToText(grs.dwPreferredFormat, szFormat, sizeof(szFormat)))
		{
		ComboBox_SetCurSel( hFormatCombo, ComboBox_FindString( hFormatCombo,
								    -1, szFormat ));
		}
		if (!fInited)
		{	// remember the values we started with.
			fDefaultDevice = grs.fDefaultDevice;
			fXMode = grs.fUseExclusiveMode;
			dwFormat = grs.dwPreferredFormat;
			fInited = TRUE;
		}
			
		return TRUE;
		}

	case WM_COMMAND:
	    switch( LOWORD(wParam))
		{
		case IDC_SETTINGS_BROWSE_INITIALDIR_BUTTON:
		    {
				BROWSEINFO              bi;
				TCHAR                   szDisplayName[MAX_PATH];
				LPITEMIDLIST            pidl;
				LPMALLOC  pMalloc = NULL;

				ZeroMemory(&bi, sizeof(bi));
    
				bi.hwndOwner = GetParent(hDlg);
				bi.pszDisplayName = szDisplayName;
				bi.lpszTitle = TEXT("Select folder");
				bi.ulFlags = BIF_RETURNONLYFSDIRS;

				pidl = SHBrowseForFolder(&bi);

				if(pidl)
				{					
					SHGetPathFromIDList(pidl, szDisplayName);
						// set the directory name.
					strcpy(grs.szInitialDir, szDisplayName);
				    Edit_SetText( hInitialDirEdit, grs.szInitialDir );
				}
				// free the pidl using the shell's task allocator.
				if (SHGetMalloc(&pMalloc) == NOERROR)
				{
					// free it.
					pMalloc->Free((void *)pidl);
				}
		    }
		    break;

		case IDC_SETTINGS_DSD_DEFAULT_CHECK:
			{
		    grs.fDefaultDevice = !grs.fDefaultDevice;
		    Static_Enable( hDeviceCombo, !grs.fDefaultDevice );
		    Static_Enable( hDeviceText, !grs.fDefaultDevice );
		    // By selecting the first item, we wipe out the state where
		    // the user could come in with the box checked, uncheck it,
		    // and leave without selecting anything
		    ComboBox_SetCurSel( hDeviceCombo, 0 );
			}
		    break;

		case IDOK:
			{
				BOOL fDeviceChanged, fXModeChanged, fFormatChanged;
				fDeviceChanged = fXModeChanged = fFormatChanged = FALSE;

				grs.fDefaultDevice = Button_GetCheck( hDefaultCheck );
				if( !grs.fDefaultDevice )
				{
				lpguTemp = (LPGUID)ComboBox_GetItemData( hDeviceCombo,
							ComboBox_GetCurSel( hDeviceCombo ));
				if( NULL != lpguTemp )
					grs.guPreferredDevice = *lpguTemp;
				else
					grs.guPreferredDevice = GUID_NULL;
				}
				else
				grs.guPreferredDevice = GUID_NULL;

				if (!gfDisable3D)
				{
					grs.fOpen3D = Button_GetCheck( hOpen3DCheck );
				}
				grs.fUseExclusiveMode = Button_GetCheck( hExclusiveCheck );

				grs.dwPreferredFormat = ComboBox_GetItemData( hFormatCombo,ComboBox_GetCurSel( hFormatCombo ));

				grs.dwDefaultFocusFlag = ComboBox_GetItemData( hFocusCombo,ComboBox_GetCurSel( hFocusCombo ));
				Edit_GetText( hInitialDirEdit, grs.szInitialDir,sizeof(grs.szInitialDir));

				if (grs.fDefaultDevice != fDefaultDevice)
				{
					fDeviceChanged = TRUE;
				}

				if (grs.fUseExclusiveMode != fXMode)
				{
					fXModeChanged = TRUE;
				}

				if (dwFormat != grs.dwPreferredFormat)
				{
					fFormatChanged = TRUE;
				}

				if (fDeviceChanged | fXModeChanged | fFormatChanged)
				{
					char sz[70] = "Settings will take effect after you restart the app.";
					MessageBox(hDlg, sz, "", MB_OK|MB_ICONEXCLAMATION);
				}

				SaveRegistrySettings();
				fSettingsSaved = TRUE;
				SendMessage( hDlg, WM_CLOSE, 0, 0 );
			}
			break;

		case IDCANCEL:
		    fSettingsSaved = FALSE;
		    SendMessage( hDlg, WM_CLOSE, 0, 0 );
		    break;

		default:
		    return FALSE;
		}
	    return TRUE;
	    break;

	case WM_CLOSE:
	    EndDialog( hDlg, fSettingsSaved );
	    return TRUE;

	case WM_DESTROY:
	    while( ComboBox_GetCount( hDeviceCombo ))
		{
		lpguTemp = (LPGUID)ComboBox_GetItemData( hDeviceCombo, 0 );
		if( NULL != lpguTemp )
		    GlobalFree(lpguTemp);
		ComboBox_DeleteString( hDeviceCombo, 0 );
		}
	    return TRUE;

	default:
	    return FALSE;
	}
    ASSERT( FALSE );
    return FALSE;
    }


/* LoadRegistrySettings()
 *
 *    Load application global REGSETTINGS structure values from the registry.
 */
BOOL LoadRegistrySettings( void )
    {
    HKEY	hReg;
    DWORD	dwVal;
    DWORD	cbValSize;
    BOOL	fRet = TRUE;

    // Load current settings from our registry key
    if( ERROR_SUCCESS != RegOpenKeyEx( HKEY_CURRENT_USER, REG_SETTINGS_KEY,
					0, KEY_READ, &hReg ))
	{
	GetMediaPath( grs.szInitialDir, sizeof(grs.szInitialDir));
	
	grs.fDefaultDevice = TRUE;
	grs.fUseExclusiveMode = FALSE;
	grs.fOpen3D = FALSE;
	if(!IsPentiumOrBetter())
	{
		gfDisable3D = TRUE;	// it's false by default otherwise.
	}
	grs.dwDefaultFocusFlag = 0;
	grs.szInitialDir[0] = '\0'; 
	grs.dwPreferredFormat = aFormatOrder[0];
	fRet = TRUE;
	goto LRS_Return;
	}

    // Load the "Use DirectSound Default Device" flag
    cbValSize = sizeof(dwVal);
    if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_SETTING_DEVICE_DEFAULT,
					    NULL, NULL, (LPBYTE)&dwVal,
					    &cbValSize ))
	{
	grs.fDefaultDevice = TRUE;
	fRet = FALSE;
	}
    else
	{
	grs.fDefaultDevice = (BOOL)dwVal;

	if( !grs.fDefaultDevice )
	    {
	    // Load the GUID for the preferred device (only if it's not the default)
	    cbValSize = sizeof(grs.guPreferredDevice);
	    if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_SETTING_DEVICE_GUID,
						    NULL, NULL,
						    (LPBYTE)&grs.guPreferredDevice,
						    &cbValSize ))
		{
		// Copy GUID_NULL into the guPreferredDevice (only works in C++)
		grs.guPreferredDevice = GUID_NULL;
		fRet = FALSE;
		}
	    }
	}

    cbValSize = sizeof(dwVal);
    if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_SETTING_USE_EXCLUSIVE,
					    NULL, NULL, (LPBYTE)&dwVal,
					    &cbValSize ))
	{
	grs.fUseExclusiveMode = FALSE;
	fRet = FALSE;
	}
    else
	grs.fUseExclusiveMode = (BOOL)dwVal;

    // Load the flag telling us whether to default to 2D or 3D
	if (!IsPentiumOrBetter())
	{
		gfDisable3D = TRUE;
		grs.fOpen3D = FALSE;
	}
	else
	{
		gfDisable3D = FALSE;
		cbValSize = sizeof(dwVal);
		if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_SETTING_OPEN3D,
							NULL, NULL, (LPBYTE)&dwVal,
							&cbValSize ))
		{
		grs.fOpen3D = FALSE;
		fRet = FALSE;
		}
		else
		grs.fOpen3D = (BOOL)dwVal;
	}

    // Load the coded version of the preferred output format
    cbValSize = sizeof(grs.dwPreferredFormat);
    if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_SETTING_OUTPUT_FORMAT,
					    NULL, NULL,
					    (LPBYTE)&grs.dwPreferredFormat,
					    &cbValSize ))
	{
	grs.dwPreferredFormat = aFormatOrder[0];
	fRet = FALSE;
	}

    // Load the default focus DSBCAPS flags
    cbValSize = sizeof(grs.dwDefaultFocusFlag);
    if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_SETTING_FOCUS_FLAG,
					    NULL, NULL,
					    (LPBYTE)&grs.dwDefaultFocusFlag,
					    &cbValSize ))
	{
	grs.dwDefaultFocusFlag = 0;
	fRet = FALSE;
	}

    // Load the initial directory for WAVE files
    cbValSize = sizeof(grs.szInitialDir);
    if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_SETTING_INITIAL_DIR,
					    NULL, NULL,
					    (LPBYTE)&grs.szInitialDir,
					    &cbValSize ))
	{
	GetMediaPath( grs.szInitialDir, sizeof(grs.szInitialDir));
	fRet = FALSE;
	}

    if( hReg != NULL )
	{
	RegCloseKey( hReg );
	hReg = NULL;
	}

LRS_Return:
    return fRet;
    }


/* SaveRegistrySettings()
 *
 *    Write the values in the REGSETTINGS global structure to the registry.
 */
BOOL SaveRegistrySettings( void )
    {
    HKEY	hReg;
    DWORD	dwVal, dwCreateDisposition;
    BOOL	fRet = TRUE;

    // Save current settings to our registry key
    if( ERROR_SUCCESS != RegCreateKeyEx( HKEY_CURRENT_USER, REG_SETTINGS_KEY,
					0, NULL, 0, KEY_WRITE, NULL, &hReg,
					&dwCreateDisposition ))
	{
	fRet = FALSE;
	goto SRS_Return;
	}

    // Save the "Use DirectSound Default Device" flag
    dwVal = (DWORD)grs.fDefaultDevice;
    if( ERROR_SUCCESS != RegSetValueEx( hReg, REG_SETTING_DEVICE_DEFAULT,
					    0, REG_DWORD, (LPBYTE)&dwVal,
					    sizeof(DWORD)))
	{
	fRet = FALSE;
	}
    else
	{
	if( !grs.fDefaultDevice )
	    {
	    // Save the GUID for the preferred device (only if it's not the default)
	    if( ERROR_SUCCESS != RegSetValueEx( hReg, REG_SETTING_DEVICE_GUID,
						    0, REG_BINARY,
						    (LPBYTE)&grs.guPreferredDevice,
						    sizeof(GUID)))
		{
		fRet = FALSE;
		}
	    }
	}

    // Use DSSCL_EXCLUSIVE ??
    dwVal = (DWORD)grs.fUseExclusiveMode;
    if( ERROR_SUCCESS != RegSetValueEx( hReg, REG_SETTING_USE_EXCLUSIVE,
					    0, REG_DWORD, (LPBYTE)&dwVal,
					    sizeof(DWORD)))
	{
	fRet = FALSE;
	}

#ifdef DEBUG
	if (grs.fOpen3D)
	{
		ASSERT(gfDisable3D == FALSE);
	}
#endif

    dwVal = (DWORD)grs.fOpen3D;
    if( ERROR_SUCCESS != RegSetValueEx( hReg, REG_SETTING_OPEN3D,
					    0, REG_DWORD, (LPBYTE)&dwVal,
					    sizeof(DWORD)))
	{
	fRet = FALSE;
	}

    // Save the coded version of the preferred output format
    if( ERROR_SUCCESS != RegSetValueEx( hReg, REG_SETTING_OUTPUT_FORMAT,
					    0, REG_DWORD,
					    (LPBYTE)&grs.dwPreferredFormat,
					    sizeof(DWORD)))
	{
	fRet = FALSE;
	}

    // Save the coded version of the preferred output format
    if( ERROR_SUCCESS != RegSetValueEx( hReg, REG_SETTING_FOCUS_FLAG,
					    0, REG_DWORD,
					    (LPBYTE)&grs.dwDefaultFocusFlag,
					    sizeof(DWORD)))
	{
	fRet = FALSE;
	}

    // Save the initial directory for WAVE files
    if( ERROR_SUCCESS != RegSetValueEx( hReg, REG_SETTING_INITIAL_DIR,
					    0, REG_SZ,
					    (LPBYTE)&grs.szInitialDir,
					    lstrlen(grs.szInitialDir)))
	{
	fRet = FALSE;
	}

    RegCloseKey( hReg );
    hReg = NULL;

SRS_Return:
    return fRet;
    }


/* GetMediaPath()
 *
 *    In the absence of a registry value, this function is called to pick a
 * starting point for the File|Open dialog.  Usually \DXSDK\SDK\MEDIA.
 */
void GetMediaPath( LPTSTR lpszBuf, int nBuf )
    {
    HKEY    hReg;
    DWORD   cbStartPathLen;

    if( ERROR_SUCCESS != RegOpenKeyEx( HKEY_LOCAL_MACHINE,
					REG_DIRECT3D_KEY,
					0, KEY_READ, &hReg ))
	{
        goto REG_OPEN_FAILED;
	}
    else
	{
	// Query the Registry for the path to the media directory
	cbStartPathLen = sizeof( szOpenStartDir );
	if( ERROR_SUCCESS != RegQueryValueEx( hReg, REG_D3DPATH_VAL,
						NULL, NULL,
						(LPBYTE)szOpenStartDir,
						&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( szOpenStartDir, sizeof(szOpenStartDir));
    // If there's no trailing backslash, append one
    if( lstrcmp( &szOpenStartDir[lstrlen(szOpenStartDir)], TEXT("\\") ))
	lstrcat( szOpenStartDir, TEXT("\\"));
    // Now add on the MEDIA part of the path
    lstrcat( szOpenStartDir, TEXT("MEDIA"));
    }


/* FormatCodeToText()
 *
 *    This function reads format codes and puts out a text string for them.
 * It does not check for invalid codes.  FALSE return means the buffer was
 * invalid in some way, TRUE means success.
 *
 */
BOOL FormatCodeToText( DWORD dwFormat, LPTSTR lpszBuf, int nBufSize )
    {
    DWORD   dwFreq;

    // The longest string we'll ever put in is 21 characters (including NULL)
    if( NULL == lpszBuf || nBufSize < 21 )
	return FALSE;

    // Extract the sample rate
    dwFreq = FC_GETFREQCODE(dwFormat);
    dwFreq = ( dwFreq == 8 ? 8000 : (dwFreq / 11) * 11025);

    wsprintf( lpszBuf, "%u Hz, %u-bit %s", dwFreq, FC_GETBITS(dwFormat),
		FC_GETCHANNELS(dwFormat) == 1 ? "Mono" : "Stereo" );

    return TRUE;
    }


/* FormatCodeToWFX()
 *
 *    This function reads format codes and fills most of the fields of a
 * WAVEFORMATEX structure based on the values read.  It does not fill the
 * wFormatTag or cbSize members.
 *
 */
BOOL FormatCodeToWFX( DWORD dwFormat, PWAVEFORMATEX pwfx )
    {
    DWORD   dwFreq;

    if( NULL == pwfx )
	return FALSE;

    // Extract the sample rate
    dwFreq = FC_GETFREQCODE(dwFormat);
    pwfx->nSamplesPerSec = ( dwFreq == 8 ? 8000 : (dwFreq / 11) * 11025);

    pwfx->wBitsPerSample = (WORD)FC_GETBITS(dwFormat);
    pwfx->nChannels = (WORD)FC_GETCHANNELS(dwFormat);

    // The nBlockAlign calculation below only works for whole-byte samples
    ASSERT( pwfx->wBitsPerSample % 8 == 0 );

    pwfx->nBlockAlign = pwfx->nChannels * (pwfx->wBitsPerSample / 8);
    pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec;

    return TRUE;
    }


/* FormatCodeFromCommandID()
 *
 *    Returns the Format Code that matches the given command ID.
 */
DWORD FormatCodeFromCommandID( WORD wID )
    {
    int i;

    for( i = 0; i < NUM_FORMATENTRIES; i++ )
	{
	if( fdFormats[i].wCommandID == wID )
	    return fdFormats[i].dwCode;
	}
    return 0;
    }


/* CommandIDFromFormatCode()
 *
 *    Searchs our FORMATDATA array and returns the Command ID corresponding
 * to the given format code, or 0 if there is no such valid format code.
 */
WORD CommandIDFromFormatCode( DWORD dwCode )
    {
    int i;

    for( i = 0; i < NUM_FORMATENTRIES; i++ )
	{
	if( fdFormats[i].dwCode == dwCode )
	    return fdFormats[i].wCommandID;
	}
    return 0;
    }


void DisableFormatCode( DWORD dwCode )
    {
    int	i;

    for( i = 0; i < NUM_FORMATENTRIES; i++ )
	{
	if( fdFormats[i].dwCode == dwCode )
	    {
	    fdFormats[i].fEnable = FALSE;
	    break;
	    }
	}
    }


void EnableFormatCode( DWORD dwCode )
    {
    int	i;

    for( i = 0; i < NUM_FORMATENTRIES; i++ )
	{
	if( fdFormats[i].dwCode == dwCode )
	    {
	    fdFormats[i].fEnable = TRUE;
	    break;
	    }
	}
    }


/* DSEnumProc()
 *
 *   DirectSoundEnumerate() callback procedure which fills a combo box with
 * the description strings of all devices and attachs a pointer to a GUID,
 * which must be freed later by calling delete.
 */
BOOL CALLBACK DSEnumProc( LPGUID lpguDevice, LPCTSTR lpszDesc,
                                LPCTSTR lpszDrvName, LPVOID lpContext )
    {
    HWND   hCombo = *(HWND *)lpContext;
    LPGUID lpguTemp = NULL;
    int	    idx;

    if( NULL != lpguDevice )
        {
        lpguTemp = (LPGUID)GlobalAlloc(GPTR, sizeof(GUID));
	// We failed to allocate storage, so continue with next device
	if( NULL == lpguTemp )
	    return( TRUE );

        CopyMemory( lpguTemp, lpguDevice, sizeof(GUID));
	}

    idx = ComboBox_AddString( hCombo, lpszDesc );
    ComboBox_SetItemData( hCombo,
    			ComboBox_FindString( hCombo, 0, lpszDesc ),
    			lpguTemp );

    if( !grs.fDefaultDevice )
	{
	if( NULL == lpguTemp )
	    {
	    if( grs.guPreferredDevice == GUID_NULL )
		ComboBox_SetCurSel( hCombo, idx );
	    }
	else if( *lpguTemp == grs.guPreferredDevice )
	    ComboBox_SetCurSel( hCombo, idx );
	}
	
    return( TRUE );
    }


/* 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(LPTSTR 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] = (LPTSTR)GlobalAlloc(GPTR, (cchTokLen+1) * sizeof(TCHAR));
	// Copy the token out of the command-line string and into our buffer
	CopyMemory( ppszFiles[nFilesFound], pszToken, cchTokLen*sizeof(TCHAR));
	// 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 )
	AppWnd.BatchOpenFiles( ppszFiles, nFilesFound, fStartPlaying, fStartLooping );

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

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


// ===============================================================
// IsPentiumOrBetter
//	returns FALSE if the machine is less than a Pentium.
// ===============================================================
BOOL IsPentiumOrBetter(void)
{
	BOOL fRet = TRUE;
	SYSTEM_INFO si;

	ZeroMemory(&si, sizeof(SYSTEM_INFO));

	GetSystemInfo(&si);

		// should work with pentiums and higher.
	if (si.dwProcessorType == PROCESSOR_INTEL_386 ||
			si.dwProcessorType == PROCESSOR_INTEL_486)
	{
		fRet = FALSE;
	}

	return fRet;
}

