/* reverse.c - WinMain() and WndProc() for REVERSE, along with
 *      initialization and support code.
 *
 * REVERSE is a Windows sample application that illustrates how to use
 *  the low-level waveform playback services. It also shows how to use
 *  the multimedia file I/O services to read data from a WAVE file.
 *
 *  REVERSE plays a WAVE waveform audio file backwards.
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
 *  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
 *  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
 *  A PARTICULAR PURPOSE.
 *
 *  Copyright (C) 1993, 1994 Microsoft Corporation. All Rights Reserved.
 *
 */

#include <windows.h>
#include <mmsystem.h>
#include "reverse.h"

#define MAX_FILENAME_SIZE   128

/* Global variables.
 */
char        szAppName[] = "Reverse";    // application name
HANDLE      hInstApp    = NULL;         // instance handle
HWND        hwndApp     = NULL;         // main window handle
HWND        hwndName    = NULL;         // filename window handle
HWND        hwndPlay    = NULL;         // "Play" button window handle
HWND        hwndQuit    = NULL;         // "Exit" button window handle
HWAVEOUT    hWaveOut    = NULL;
LPWAVEHDR   lpWaveHdr   = NULL;    
VOID        cleanup(LPWAVEINST lpWaveInst);


/* WinMain - Entry point for Reverse.
 */
int PASCAL WinMain(HANDLE hInst, HANDLE hPrev, LPSTR szCmdLine, WORD cmdShow)
{
    MSG         msg;
    WNDCLASS    wc;
    
    hInstApp =  hInst;

    /* Define and register a window class for the main window.
     */
    if (!hPrev)
    {
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hIcon          = LoadIcon(hInst, szAppName);
        wc.lpszMenuName   = szAppName;
        wc.lpszClassName  = szAppName;
        wc.hbrBackground  = GetStockObject(LTGRAY_BRUSH);
        wc.hInstance      = hInst;
        wc.style          = 0;
        wc.lpfnWndProc    = WndProc;
        wc.cbWndExtra     = 0;
        wc.cbClsExtra     = 0;

        if (!RegisterClass(&wc))
        return FALSE;
    }

    /* Create and show the main window.
     */
    hwndApp = CreateWindow (szAppName,              // class name
                            szAppName,              // caption
                            WS_OVERLAPPEDWINDOW,    // style bits
                            CW_USEDEFAULT,          // x position
                            CW_USEDEFAULT,          // y position
                            WMAIN_DX,               // x size
                            WMAIN_DY,               // y size
                            (HWND)NULL,             // parent window
                            (HMENU)NULL,            // use class menu
                            (HANDLE)hInst,          // instance handle
                            (LPSTR)NULL             // no params to pass on
                           );
    /* Create child windows for the "Play" and "Exit" buttons
     * and for an edit field to enter filenames.
     */
    hwndPlay = CreateWindow( "BUTTON", "Play", 
            WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
            PLAY_X, PLAY_Y,
            PLAY_DX, PLAY_DY,
            hwndApp, (HMENU)IDB_PLAY, hInstApp, NULL );
    if( !hwndPlay )
        return( FALSE );

    hwndQuit = CreateWindow( "BUTTON", "Exit",
            WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
            QUIT_X, QUIT_Y,
            QUIT_DX, QUIT_DY,
            hwndApp, (HMENU)IDB_QUIT, hInstApp, NULL );
    if( !hwndQuit )
        return( FALSE );

    hwndName = CreateWindow("EDIT","",
            WS_CHILD|WS_VISIBLE|WS_BORDER|ES_AUTOHSCROLL,
            NAME_X, NAME_Y,
            NAME_DX, NAME_DY,
            hwndApp, (HMENU)IDE_NAME, hInstApp, NULL);
    if( !hwndName )
        return( FALSE );
    SendMessage(hwndName, EM_LIMITTEXT, MAX_FILENAME_SIZE - 1, 0);

    ShowWindow(hwndApp,cmdShow);

    /* Add about dialog to system menu.
     */
    AppendMenu(GetSystemMenu(hwndApp, NULL),
        MF_STRING | MF_ENABLED, IDM_ABOUT, "About Reverse...");


  /* The main message processing loop. Nothing special here.
   */
  while (GetMessage(&msg,NULL,0,0))
  {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
  }
  
  return msg.wParam;
}


/* WndProc - Main window procedure function.
 */
LONG FAR PASCAL WndProc(HWND hWnd, unsigned msg, WORD wParam, LONG lParam)
{
    FARPROC fpfn;
    LPWAVEINST lpWaveInst;

    switch (msg)
    {
    case WM_DESTROY:
        if (hWaveOut)
	{
  	   waveOutReset(hWaveOut);	    
           waveOutUnprepareHeader(hWaveOut, lpWaveHdr, sizeof(WAVEHDR) );
           lpWaveInst = (LPWAVEINST) lpWaveHdr->dwUser;
	   cleanup(lpWaveInst);
           waveOutClose(hWaveOut);	    
        }
        PostQuitMessage(0);
        break;

    case WM_SYSCOMMAND:
        switch (wParam)
        {
        case IDM_ABOUT:
            /* Show ABOUTBOX dialog box.
             */
            fpfn = MakeProcInstance((FARPROC)AppAbout, hInstApp);
            DialogBox(hInstApp, "ABOUTBOX", hWnd, (DLGPROC)fpfn);
            FreeProcInstance(fpfn);
            break;
        }
        break;
                
    /* Process messages sent by the child window controls.
     */
    case WM_SETFOCUS:
        SetFocus(hwndName);
        return 0;

    case WM_COMMAND:
        switch (wParam)
        {
        case IDE_NAME:              // filename edit control
            return( 0L );

        case IDB_PLAY:              // "Play" button
            if (HIWORD(lParam) == BN_CLICKED)
                ReversePlay();
            break;

        case IDB_QUIT:              // "Exit" button
            if (HIWORD(lParam) == BN_CLICKED)
                PostQuitMessage(0);
            break;
        }
        return( 0L );

    case MM_WOM_DONE:
        /* This message indicates a waveform data block has
         * been played and can be freed. Clean up the preparation
         * done previously on the header.
         */
        waveOutUnprepareHeader( (HWAVEOUT) wParam,
                                (LPWAVEHDR) lParam, sizeof(WAVEHDR) );

        /* Get a pointer to the instance data, then unlock and free
         * all memory associated with the data block, including the
         * memory for the instance data itself.
         */
        lpWaveInst = (LPWAVEINST) ((LPWAVEHDR)lParam)->dwUser;
	cleanup(lpWaveInst);
        /* Close the waveform output device.
         */
        waveOutClose( (HWAVEOUT) wParam );
        hWaveOut = NULL;
        
        /* Reenable both button controls.
         */
        EnableWindow( hwndPlay, TRUE );
        EnableWindow( hwndQuit, TRUE );
        SetFocus(hwndName);
    
        break;
    }

    return DefWindowProc(hWnd,msg,wParam,lParam);
}


/* AppAbout -- Dialog procedure for ABOUTBOX  dialog box.
 */
BOOL FAR PASCAL AppAbout(HWND hDlg, unsigned msg, WORD wParam, LONG lParam)
{
    switch (msg)
    {
    case WM_COMMAND:
        if (wParam == IDOK || wParam == IDCANCEL)
            EndDialog(hDlg,TRUE);
        break;

    case WM_INITDIALOG:
        return TRUE;
    }
    return FALSE;
}

/* ReversePlay - Gets a filename from the edit control, then uses
 *  the multimedia file I/O services to read data from the requested
 *  WAVE file. If the file is a proper WAVE file, ReversePlay() calls
 *  the Interchange() function to reverse the order of the waveform
 *  samples in the file. It then plays the reversed waveform data.
 * 
 *  Note that ReversePlay() only handles a single waveform data block.
 *  If the requested WAVE file will not fit in a single data block, it
 *  will not be played. The size of a single data block depends on the
 *  amount of available system memory.
 *
 * Params:  void
 *
 * Return:  void
 */
void ReversePlay() 
{
    HANDLE          hWaveHdr;
    LPWAVEINST      lpWaveInst;    
    HMMIO           hmmio;
    MMCKINFO        mmckinfoParent;
    MMCKINFO        mmckinfoSubchunk;
    DWORD           dwFmtSize;
    char            szFileName[ MAX_FILENAME_SIZE ];
    WORD            wResult;
    HANDLE          hFormat;
    WAVEFORMATEX    *pFormat;
    DWORD           dwDataSize;
    HPSTR           hpch1, hpch2;
    WORD            wBlockSize;
    HANDLE          hWaveInst;
    HANDLE          hData       = NULL;
    HPSTR           lpData      = NULL;
        
    /* Get the filename from the edit control.
     */
    if (!GetWindowText( hwndName, (LPSTR)szFileName, MAX_FILENAME_SIZE))
    {
        MessageBox(hwndApp, "Failed to Get Filename",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        return;
    }
    
    /* Open the given file for reading using buffered I/O.
     */
    if(!(hmmio = mmioOpen(szFileName, NULL, MMIO_READ | MMIO_ALLOCBUF)))
    {
        MessageBox(hwndApp, "Failed to open file.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        return;
    }

    /* Locate a 'RIFF' chunk with a 'WAVE' form type 
     * to make sure it's a WAVE file.
     */
    mmckinfoParent.fccType = mmioFOURCC('W', 'A', 'V', 'E');
    if (mmioDescend(hmmio, (LPMMCKINFO) &mmckinfoParent, NULL, MMIO_FINDRIFF))
    {
        MessageBox(hwndApp, "This is not a WAVE file.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        mmioClose(hmmio, 0);
        return;
    }
    
    /* Now, find the format chunk (form type 'fmt '). It should be
     * a subchunk of the 'RIFF' parent chunk.
     */
    mmckinfoSubchunk.ckid = mmioFOURCC('f', 'm', 't', ' ');
    if (mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent, 
        MMIO_FINDCHUNK))
    {
        MessageBox(hwndApp, "WAVE file is corrupted.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        mmioClose(hmmio, 0);
        return;
    }

    /* Get the size of the format chunk, allocate and lock memory for it.
     */
    dwFmtSize = mmckinfoSubchunk.cksize;
    hFormat = LocalAlloc(LMEM_MOVEABLE, LOWORD(dwFmtSize));
    if (!hFormat)
    {
        MessageBox(hwndApp, "Out of memory.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        mmioClose(hmmio, 0);
        return;
    }
    pFormat = (WAVEFORMATEX *) LocalLock(hFormat);
    if (!pFormat)
    {
        MessageBox(hwndApp, "Failed to lock memory for format chunk.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        LocalFree( hFormat );
        mmioClose(hmmio, 0);
        return;
    }

    /* Read the format chunk.
     */
    if (mmioRead(hmmio, (HPSTR) pFormat, dwFmtSize) != (LONG) dwFmtSize)
    {
        MessageBox(hwndApp, "Failed to read format chunk.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        LocalUnlock( hFormat );
        LocalFree( hFormat );
        mmioClose(hmmio, 0);
        return;
    }
    
    /* Make sure it's a PCM file.
     */
    if (pFormat->wFormatTag != WAVE_FORMAT_PCM)
    {
        LocalUnlock( hFormat );
        LocalFree( hFormat );
        mmioClose(hmmio, 0);
        MessageBox(hwndApp, "The file is not a PCM file.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        return;
    }

    /* Make sure a waveform output device supports this format.
     */
    if (waveOutOpen(&hWaveOut, WAVE_MAPPER, (LPWAVEFORMATEX)pFormat, NULL, 0L, 
                    WAVE_FORMAT_QUERY))
    {
        LocalUnlock( hFormat );
        LocalFree( hFormat );
        mmioClose(hmmio, 0);
        MessageBox(hwndApp, "The waveform device can't play this format.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        return;
    }
    
    /* Ascend out of the format subchunk.
     */
    mmioAscend(hmmio, &mmckinfoSubchunk, 0);
    
    /* Find the data subchunk.
     */
    mmckinfoSubchunk.ckid = mmioFOURCC('d', 'a', 't', 'a');
    if (mmioDescend(hmmio, &mmckinfoSubchunk, &mmckinfoParent,
        MMIO_FINDCHUNK))
    {
        MessageBox(hwndApp, "WAVE file has no data chunk.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        LocalUnlock( hFormat );
        LocalFree( hFormat );
        mmioClose(hmmio, 0);
        return;
    }

    /* Get the size of the data subchunk.
     */
    dwDataSize = mmckinfoSubchunk.cksize;
    if (dwDataSize == 0L)
    {
        MessageBox(hwndApp, "The data chunk has no data.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        LocalUnlock( hFormat );
        LocalFree( hFormat );
        mmioClose(hmmio, 0);
        return;
    }
    
    /* Open a waveform output device.
     */
    if (waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER, 
                  (LPWAVEFORMATEX)pFormat, (UINT)hwndApp, 0L, CALLBACK_WINDOW))
    {
        MessageBox(hwndApp, "Failed to open waveform output device.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        LocalUnlock( hFormat );
        LocalFree( hFormat );
        mmioClose(hmmio, 0);
        return;
    }

    /* Save block alignment info for later use.
     */
    wBlockSize = pFormat->nBlockAlign;

    /* We're done with the format header, free it.
     */
    LocalUnlock( hFormat );
    LocalFree( hFormat );
    
    /* Allocate and lock memory for the waveform data.
     */
    hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, dwDataSize );
    if (!hData)
    {
        MessageBox(hwndApp, "Out of memory.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        mmioClose(hmmio, 0);
        return;
    }
    lpData = GlobalLock(hData);
    if (!lpData)
    {
        MessageBox(hwndApp, "Failed to lock memory for data chunk.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        GlobalFree( hData );
        mmioClose(hmmio, 0);
        return;
    }

    /* Read the waveform data subchunk.
     */
    if(mmioRead(hmmio, (HPSTR) lpData, dwDataSize) != (LONG) dwDataSize)
    {
        MessageBox(hwndApp, "Failed to read data chunk.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        GlobalUnlock( hData );
        GlobalFree( hData );
        mmioClose(hmmio, 0);
        return;
    }

    /* We're done with the file, close it.
     */
    mmioClose(hmmio, 0);

    /* Reverse the sound for playing.
     */
    hpch1 = lpData;
    hpch2 = lpData + dwDataSize - 1;
    while (hpch1 < hpch2)
    {
        Interchange( hpch1, hpch2, wBlockSize );
        hpch1 += wBlockSize;
        hpch2 -= wBlockSize;
    }
    
    /* Allocate a waveform data header. The WAVEHDR must be 
     * globally allocated and locked.
     */
    hWaveHdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,
                           (DWORD) sizeof(WAVEHDR));
    if (!hWaveHdr)
    {
        GlobalUnlock( hData );
        GlobalFree( hData );
        MessageBox(hwndApp, "Not enough memory for header.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        return;
    }
    lpWaveHdr = (LPWAVEHDR) GlobalLock(hWaveHdr);
    if (!lpWaveHdr)
    {
        GlobalUnlock( hData );
        GlobalFree( hData );
        GlobalFree( hWaveHdr );
        MessageBox(hwndApp, "Failed to lock memory for header.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        return;
    }

    /* Allocate and set up instance data for waveform data block.
     * This information is needed by the routine that frees the
     * data block after it has been played.
     */
    hWaveInst = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,
                            (DWORD) sizeof(WAVEHDR));
    if (!hWaveInst)
    {
        GlobalUnlock( hData );
        GlobalFree( hData );
        GlobalUnlock( hWaveHdr );
        GlobalFree( hWaveHdr );
        MessageBox(hwndApp, "Not enough memory for instance data.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        return;
    }
    lpWaveInst = (LPWAVEINST) GlobalLock(hWaveInst);
    if (!lpWaveInst)
    {
        GlobalUnlock( hData );
        GlobalFree( hData );
        GlobalUnlock( hWaveHdr );
        GlobalFree( hWaveHdr );
        GlobalFree( hWaveInst );
        MessageBox(hwndApp, "Failed to lock memory for instance data.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        return;
    }
    lpWaveInst->hWaveInst = hWaveInst;
    lpWaveInst->hWaveHdr = hWaveHdr;
    lpWaveInst->hWaveData = hData;
        
    /* Set up WAVEHDR structure and prepare it to be written to wave device.
     */
    lpWaveHdr->lpData = lpData;
    lpWaveHdr->dwBufferLength = dwDataSize;
    lpWaveHdr->dwFlags = 0L;
    lpWaveHdr->dwLoops = 0L;
    lpWaveHdr->dwUser = (DWORD) lpWaveInst;
    if(waveOutPrepareHeader(hWaveOut, lpWaveHdr, sizeof(WAVEHDR)))
    {
        GlobalUnlock( hData );
        GlobalFree( hData );
        GlobalUnlock( hWaveHdr );
        GlobalFree( hWaveHdr );
        GlobalUnlock( hWaveInst );
        GlobalFree( hWaveInst );
        MessageBox(hwndApp, "Unable to prepare wave header.",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
               
        return;
    }
        
    /* Then the data block can be sent to the output device.
     */
    wResult = waveOutWrite(hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
    if (wResult != 0)
    {
        waveOutUnprepareHeader( hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
        GlobalUnlock( hData );
        GlobalFree( hData );
        MessageBox(hwndApp, "Failed to write block to device",
                   NULL, MB_OK | MB_ICONEXCLAMATION);
        return;
    }

    /* Disable input to the button controls.
     */
    EnableWindow(hwndPlay, FALSE);
    EnableWindow(hwndQuit, FALSE);
}

/* Interchange - Interchanges two samples at the given positions.
 *
 * Params:  hpchPos1 - Points to one sample.
 *          hpchPos2 - Points to the other sample.
 *          wLength  - The length of a sample in bytes.
 *
 * Return:  void
 */
void Interchange(HPSTR hpchPos1, HPSTR hpchPos2, WORD wLength)
{
    WORD    wPlace;
    BYTE    bTemp;
    
    for (wPlace = 0; wPlace < wLength; wPlace++)
    {
        bTemp = hpchPos1[wPlace];
        hpchPos1[wPlace] = hpchPos2[wPlace];
        hpchPos2[wPlace] = bTemp;
    }
}

VOID cleanup(LPWAVEINST lpWaveInst)
{
    GlobalUnlock( lpWaveInst->hWaveData );
    GlobalFree( lpWaveInst->hWaveData );
    GlobalUnlock( lpWaveInst->hWaveHdr );
    GlobalFree( lpWaveInst->hWaveHdr );
    GlobalUnlock( lpWaveInst->hWaveInst );
    GlobalFree( lpWaveInst->hWaveInst );
}
