/*==========================================================================
 *
 *  Copyright (C) 1995-1996 Microsoft Corporation. All Rights Reserved.
 *
 *  File:       shell.c
 *  Content:    Direct Sound show-off.
 *  This app basically uses the direct sound api's and pops up some
 *  controls that the user can play with at runtime to change
 *  the sound frequency, panning, volume, etc.   It has a few
 *  other functions built in.
 *
 ***************************************************************************/

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

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


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

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

/* Procedure called when the application is loaded for the first time */

BOOL ClassInit( hInstance )
HANDLE hInstance;
{
    WNDCLASS    myClass;
        
    myClass.hCursor             = LoadCursor( NULL, IDC_ARROW );
    myClass.hIcon               = LoadIcon( hInstance, MAKEINTRESOURCE(IDI_ICON3));
    myClass.lpszMenuName        = MAKEINTRESOURCE(IDR_MAINMENU);
    myClass.lpszClassName       = (LPSTR)szAppName;
    myClass.hbrBackground       = (HBRUSH)(COLOR_WINDOW);
    myClass.hInstance           = hInstance;
    myClass.style               = CS_HREDRAW | CS_VREDRAW;
    myClass.lpfnWndProc         = WndProc;
    myClass.cbClsExtra          = 0;
    myClass.cbWndExtra          = 0;

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

    return TRUE;        /* Initialization succeeded */
}


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

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

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

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


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

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

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

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

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

    /* Save instance handle for DialogBox */
    hInst = hInstance;
    
    ShowWindow( hWnd, cmdShow );
    
    /* Polling messages from event queue */
    while (GetMessage((LPMSG)&msg, NULL, 0, 0)) {
    TranslateMessage((LPMSG)&msg);
    DispatchMessage((LPMSG)&msg);
    }
    
    DestroyWindow(hWnd);
    UnregisterClass(szAppName, hInstance);
    return (int)msg.wParam;
}

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

    lstrcpy(szStatus, pFileInfo->fHardware ? szHW : szSW);
    if (dwStatus & DSBSTATUS_BUFFERLOST)
    {
    lstrcat(szStatus, szLost);
    SendMessage(pFileInfo->hWndStatus_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    }
    else if (dwStatus & DSBSTATUS_PLAYING)
    {
    lstrcat(szStatus, szPlaying);
    SendMessage(pFileInfo->hWndStatus_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    }
    else
    {
    lstrcat(szStatus, szStopped);
    SendMessage(pFileInfo->hWndStatus_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    }
    if (pFileInfo->fSticky)
    {
    lstrcat(szStatus, szSticky);
    SendMessage(pFileInfo->hWndStatus_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    }
    hr = IDirectSoundBuffer_GetCurrentPosition(pFileInfo->pDSB, &dwPlay, &dwWrite);
    if (DS_OK == hr) {
    wsprintf(szStatus, szFmtPlayPosition, dwPlay);
    SendMessage(pFileInfo->hWndPlayPosition_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    wsprintf(szStatus, szFmtWritePosition, dwWrite);
    SendMessage(pFileInfo->hWndWritePosition_TXT, WM_SETTEXT, 0, (LPARAM)szStatus);
    }

    return;
}

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


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

    Input:
    hWnd                - App main window handle

    Output:
    None.

*/

int AppInit(HWND hWnd)

{

    UINT            cT;
    DSBUFFERDESC    dsbd;
    BOOL        fUseGuid;
    HRESULT         hr;
    DWORD           dw;

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

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

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

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

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

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


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



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

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


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

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

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

    if (SUCCEEDED(hr) && (NULL != gpds)) {

                hr = IDirectSound_Initialize(gpds, fUseGuid ? &guID : NULL);
    if (SUCCEEDED(hr)) {

        // Note we need to set the level to be priority to set the
        // format of the primary buffer
        hr = IDirectSound_SetCooperativeLevel(gpds, hWndMain, DSSCL_PRIORITY);
        if (SUCCEEDED(hr)) {

        // Set up the primary direct sound buffer.
        ZeroMemory(&dsbd, sizeof(dsbd));
        dsbd.dwSize = sizeof(dsbd);
        dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
    
        hr = IDirectSound_CreateSoundBuffer(gpds, &dsbd, &(FileInfoFirst.pDSB), NULL);
        if (SUCCEEDED(hr)) {

            hr = IDirectSoundBuffer_Play(FileInfoFirst.pDSB, 0, 0, DSBPLAY_LOOPING);
            if (SUCCEEDED(hr)) {

            UpdateMainStatus();

            } else {
            MessageBox(hWnd, "Cannot play primary buffer",
                   "DirectSound Demo", MB_OK|MB_ICONSTOP);
            IDirectSoundBuffer_Release(FileInfoFirst.pDSB);
            FileInfoFirst.pDSB = NULL;
            }

        } else {
            MessageBox(hWnd, "Cannot create primary buffer",
                   "DirectSound Demo", MB_OK|MB_ICONSTOP);
        }

        } else {
        MessageBox(hWnd, "DirectSound SetCooperativeLevel failed",
               "DirectSound Demo", MB_OK|MB_ICONSTOP);
        }

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

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

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

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

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

    Input:
    hWnd                - Window handle of main window

    Output:
    None.

*/
void AppDestroy( HWND hWnd )
{

    HRESULT     hr = 0;

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


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


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

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

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

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

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

}

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



    switch (message)
    {

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

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

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

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

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


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

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

    Input:
    hWnd            -   Handle of parent window.

    Output:
    None.

*/
void PD_FileOpen( HWND hWnd )
{

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

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

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

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

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

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

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

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

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

    }

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

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

    GlobalFreePtr(pFileInfo);
    pFileInfo = NULL;
    }

DONE_ROUTINE:
    return;

}

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

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

    Output:
    0 if successful, else the error code.

*/

int NewDirectSoundBuffer(
            FILEINFO *pFileInfo
            )
{

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

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

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

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

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

    pbData = NULL;

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

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

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

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

    goto DONE_ROUTINE;

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

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

    return(hr); 

}

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

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

    Output: 
    0 if successful, else the error code.

*/
int ReleaseDirectSoundBuffer( FILEINFO *pFileInfo )
{

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

    return(0);

}

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

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

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

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

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

}

/*
    CreateControl

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

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

    Output:
    0 if successful, else the error code.

*/
int CreateControl(                                                                                                      //This creates Child WAV Windows
            HWND        hWnd, 
            FILEINFO    *pFileInfo,
            DWORD       dwFreq,
            DWORD       dwPan,
            DWORD       dwVol
            )

{

    int             cox, 
            coy;
    int             coxOld,
            coyOld;
    int             nError              = 0;
    DWORD           idBase;
    SIZE            Size;       
    HDC             hDC                 = NULL;
/*  int             cT;
    RECT            rc;*/

    /* Figure out the values of dwFreq, dwPan and dwVol that the track bars like */
    dwFreq = (dwFreq - FREQADD)/FREQMUL;

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

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

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


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

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

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

    // Now create status if required.
    
    #ifdef SHOWSTATUS   

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


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

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

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

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

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

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

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

    coy += Size.cy;

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

    SendMessage(pFileInfo->hWndFreq_TB, TBM_SETRANGE, FALSE, MAKELONG(MINFREQ_TB, MAXFREQ_TB)); 
    SendMessage(pFileInfo->hWndFreq_TB, TBM_SETPAGESIZE, 0, FREQPAGE );
    SendMessage(pFileInfo->hWndFreq_TB, TBM_SETPOS, TRUE, dwFreq);
    pFileInfo->dwFreq = dwFreq;


    coy += DY_FREQ_TB+DY_PANSPACING;    

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

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

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


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

    coy += Size.cy;

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

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


    coy += DY_PAN_TB + DY_VOLSPACING;

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

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

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

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

    coy += Size.cy;

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

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



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

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

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


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

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

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

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

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



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


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

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

    coy += Size.cy + DY_BUTTONSPACING+ DY_BETWEENBUTTONS;


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

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

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

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

    
    SetAllText(pFileInfo);
    UpdateLRVolume(pFileInfo);

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

    return(nError);

}

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

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

    Output:
    0 if successful, else the error code.

*/      

int AddToList(
            FILEINFO *pFileInfoHead, 
            FILEINFO *pFileInfo
            )
{

    pFileInfo->pNext = NULL;    
    pFileInfo->fPlaying = FALSE;

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

    pFileInfoHead->pNext = pFileInfo;

    return(0);

}

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

    Input:
    pFileInfoHead           -   Header of linked list.

    Output:
    # of controls.

*/

int GetNumControls( FILEINFO *pFileInfoHead )
{

    int cT  = 0;

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

    return(cT);

}

/*  This routine will free the whole linked list in pFileInfoFirst,
    including all the
    memory used by the wave file, waveformatex structure, etc.

*/
int FreeAllList(
            HWND hWnd, 
            FILEINFO *pFileInfoFirst
            )
{

    FILEINFO        *pFileInfo, *pFileNext;
    UINT        cT;

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

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

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



    return(0);          


}

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

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

    Output:
    0 if successful, else the error.

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

    FILEINFO        *pFileNext;

    Assert(pFileInfoHead != NULL);

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




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

    return(0);
}

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


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

 */
BOOL OpenFileDialog(
            HWND            hWnd,
            LPSTR           pszFileName,
            int             *nFileName,
            LPBOOL          lpfSticky
            )
{

    BOOL            fReturn,
            fValid;
    OPENFILENAME    ofn;                

    pszFileName[0]          = 0;

    ofn.lStructSize         = sizeof(ofn);
    ofn.hwndOwner           = hWnd;
    ofn.hInstance           = hInst;
    ofn.lpstrFilter         = "Wave Files\0*.wav\0All Files\0*.*\0\0";
    ofn.lpstrCustomFilter   = NULL;
    ofn.nMaxCustFilter      = 0;
    ofn.nFilterIndex        = 1;
    ofn.lpstrFile           = pszFileName;
    ofn.nMaxFile            = MAX_PATH;
    ofn.lpstrFileTitle      = NULL;
    ofn.nMaxFileTitle       = 0;
    ofn.lpstrInitialDir     = ".";
    ofn.lpstrTitle          = "File Open";
    ofn.Flags               = OFN_FILEMUSTEXIST | OFN_EXPLORER
                                | OFN_ENABLETEMPLATE | OFN_ENABLEHOOK | OFN_HIDEREADONLY;
    ofn.nFileOffset         = 0;
    ofn.nFileExtension      = 0;
    ofn.lpstrDefExt         = "wav";
    ofn.lCustData           = (LONG)lpfSticky;
    ofn.lpfnHook            = FileOpenCustomTemplateDlgProc;
    ofn.lpTemplateName      = MAKEINTRESOURCE(IDD_FILEOPEN_NEST);
                
    fValid = FALSE;
    do   {    
    
    if (fReturn = GetOpenFileName(&ofn))
    {                               
    fValid = IsValidWave(pszFileName);
    if (!fValid)
    MessageBox(hWnd, "Wave files must be PCM format!",
           "Invalid Wave File", MB_OK|MB_ICONSTOP);
    else
    *nFileName = ofn.nFileOffset;
    }
    else fValid = TRUE;         // Force break out of loop.
    
    } while (!fValid);

    return(fReturn);     

}

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

    Input:
    pszFileName -   FileName to check.

    Output:
    FALSE if not a valid wave, TRUE if it is.
    
*/
BOOL IsValidWave(
            LPSTR       pszFileName
            )

{ 
    BOOL            fReturn     = FALSE;
    int             nError      = 0;
    HMMIO           hmmio;
    MMCKINFO        mmck;
    WAVEFORMATEX    *pwfx;

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

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

    WaveCloseReadFile(&hmmio, &pwfx);

    fReturn = TRUE;

ERROR_IN_ROUTINE:
    return(fReturn);    

}

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

    FILEINFO    *pFileInfo;
    BOOL        fReturn             = FALSE;

    pFileInfo = FileInfoFirst.pNext;

    Assert(pFileInfo != NULL);

    while (pFileInfo != NULL)
    {

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

    pFileInfo = pFileInfo->pNext;
    
    }

    return (fReturn);

}


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

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

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

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

    FILEINFO    *pFileInfo;
    BOOL        fReturn             = FALSE;

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

    while (pFileInfo != NULL)
    {

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

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

    pFileInfo = pFileInfo->pNext;
    
    }

    return (fReturn);
    


}

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

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

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

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

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

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

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

    pFileInfo = pFileInfoNext;
    
    }
    
    if (!fReturn)
    {

    switch(wParam)
    {

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

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

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

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

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

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

    default:
    return(FALSE);
    
    }
    }

    return(TRUE);


}

/*  This routine will handle the timer messages.

    Input:
    Standard input.

    Output: 
    TRUE if processed message, otherwise FALSE

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

    FILEINFO        *pFileInfo;
    BOOL            fReturn             = FALSE;
    DWORD           dwStatus            = 0;

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

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

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

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

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

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

        if (DS_OK == hr) {

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

        IDirectSoundBuffer_GetStatus(pFileInfo->pDSB, &dwStatus);

        }
    }
    }
    }

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

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

    pFileInfo->fLost = dwStatus & DSBSTATUS_BUFFERLOST;

    fReturn = TRUE;
    }

    return (fReturn);

}   



/*  This routine will start a sound to be played.  

    Input:
    hWnd        -   Of parent window.
    pFileInfo   -   Pointer to file to start,
        which is loaded and the
        data is filled in the structure,
        such as pbData, 
        etc.

    Output:
    0 if successful, else the error code.

*/
int StartDSound(
    HWND hWnd, 
    FILEINFO *pFileInfo
    )
{

    HRESULT     hr              = 0;
    DWORD           dwLooped;
    DWORD           dwStatus                = 0;

    // Already playing?

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

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

    return(hr);


}

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

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

    Output:
    0 if successful, else the error code.

*/
int StopDSound(     HWND hWnd, 
        FILEINFO *pFileInfo
        )

{
    HRESULT     hr          = 0;

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

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

    return(hr);

}

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

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

    Output:
    0 if successful, else the error code.

*/
int StopAllDSounds(
            HWND hWnd, 
            FILEINFO *pFileInfo
            )

{

    while (pFileInfo->pNext != NULL)
    {
    StopDSound(hWnd, pFileInfo->pNext);
    pFileInfo = pFileInfo->pNext;       
    }

    return(0);

}



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

    Input:
    pFileInfo   -   File pointer to set frequency for.

    The dwFreq in the pFileInfo structure must be set.
    This also uses the window handle
    in the pFileInfo structure.
    
    Output:
    None.

*/
void SetAllText(
            FILEINFO    *pFileInfo
            )
{
    char            szBufT[128];

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

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

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


}

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

    Input:
    pFileInfo   - Pointer to fileinfo to update.

    Output:
    Nothing worth using.
            

*/
void UpdateLRVolume(
            FILEINFO *pFileInfo
            )
{

    int             volLeft, volRight;

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

    

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

}

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

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

    Output:
    0 if successful, else the error code.

*/
int ChangeOutputPan(
            FILEINFO *pFileInfo
            )

{

    HRESULT     hr      = 0;


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

ERROR_DONE_ROUTINE:
    return(hr);

}

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

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

    Output:
    0 if successful, else the error code.

*/
int ChangeOutputFreq(
            FILEINFO *pFileInfo
            )

{

    HRESULT     hr      = 0;


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

ERROR_DONE_ROUTINE:
    return(hr);

}



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

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

    Output:
    0 if successful, else the error code.

*/
int ChangeOutputVol(
            FILEINFO *pFileInfo
            )

{

    HRESULT     hr      = 0;


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

ERROR_DONE_ROUTINE:
    return(hr);

}


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

    Input:
    Standard dialog box input.

    Output:
    Standard dialog box output.

*/

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

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


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

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

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

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

}


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

    Output:
    Standard windows dialog procedure.

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

    default:
    return(0);
    break;               

    }
    
    return(1);

}


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

    Output:
    Standard windows dialog procedure.

*/

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

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

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

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


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

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

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


    default:
        break;

    }
    break;

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

    default:
    return(0);
    break;               

    }
        
    return(1);

}

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

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

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

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

{

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

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


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

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

    // Check the channels
    if (wfx.nChannels == 1);
    else if (wfx.nChannels == 2)
    dwWaveStyle |= 1;
    else
    goto ERROR_IN_ROUTINE;

    // Check the bits...
    if (wfx.wBitsPerSample == 8);
    else if (wfx.wBitsPerSample == 16)
    dwWaveStyle |= 2;
    else
    goto ERROR_IN_ROUTINE;
    
    // Check the rate.
    if (wfx.nSamplesPerSec == 8000);
    else if (wfx.nSamplesPerSec == 11025)
    dwWaveStyle |= 4;
    else if (wfx.nSamplesPerSec == 22050)
    dwWaveStyle |= 8;
    else if (wfx.nSamplesPerSec == 44100)
    dwWaveStyle |= 12;
    else
    goto ERROR_IN_ROUTINE;
    
    nError = (int)dwWaveStyle;

ERROR_IN_ROUTINE:
    return(nError);
}


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

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

    Output:
    0 if successful, else the error code.

*/
int IndexToFormat(
            HWND        hWnd, 
            FILEINFO    *pFileInfo,
            int         index
            )

{

    int         nError      = 0;


    pFileInfo->pwfx->wFormatTag = WAVE_FORMAT_PCM;

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

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

    }

ERROR_DONE_ROUTINE:
    return(nError);

}
