/* midimon.c - WinMain() and WndProc() functions for MIDIMon, along
 *      with some initialization and error reporting functions.
 *
 * MIDIMon is a Multimedia Windows application that records and displays 
 *  incoming MIDI information.  It uses a low-level callback function to 
 *  get timestamped MIDI input.  The callback function puts the incoming
 *  MIDI event information (source device, timestamp, and raw MIDI
 *  data) in a circular input buffer and notifies the application by posting 
 *  a MM_MIDIINPUT message.  When the application processes the MM_MIDIINPUT
 *  message, it removes the MIDI event from the input buffer and puts it in
 *  a display buffer.  Information in the display buffer is converted to
 *  text and displayed in a scrollable window.  Incoming MIDI data can be sent
 *  to the MIDI Mapper if the user chooses.  Filtering is provided for the
 *  display buffer, but not for data sent to the Mapper.
 *
 *  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 "midimon.h"
#include "about.h"
#include "circbuf.h"
#include "display.h"
#include "prefer.h"
#include "instdata.h"
#include "callback.h"
#include "filter.h"

HANDLE hInst;                           // Instance handle for application
char szAppName[20];                     // Application name
HWND hMainWnd;                          // Main window handle
HMIDIOUT hMapper = 0;                   // Handle to MIDI Mapper
WORD wNumDevices = 0;                   // Number of MIDI input devices opened
BOOL bRecordingEnabled = 1;             // Enable/disable recording flag
short nNumBufferLines = 0;              // Number of lines in display buffer
RECT rectScrollClip;                    // Clipping rectangle for scrolling

LPCIRCULARBUFFER lpInputBuffer;         // Input buffer structure
LPDISPLAYBUFFER lpDisplayBuffer;        // Display buffer structure
PREFERENCES preferences;                // User preferences structure
EVENT incomingEvent;                    // Incoming MIDI event structure

MIDIINCAPS midiInCaps[MAX_NUM_DEVICES]; // Device capabilities structures
HMIDIIN hMidiIn[MAX_NUM_DEVICES];       // MIDI input device handles

// Callback instance data pointers
LPCALLBACKINSTANCEDATA lpCallbackInstanceData[MAX_NUM_DEVICES];

// Display filter structure
FILTER filter = {
                    0, 0, 0, 0, 0, 0, 0, 0, 
                    0, 0, 0, 0, 0, 0, 0, 0, 
                    0, 0, 0, 0, 0, 0, 0, 0, 
                    0, 0, 0, 0, 0, 0, 0, 0
                };

// Virtual key to scroll message translation structure
KEYTOSCROLL keyToScroll [] = { 
                                VK_HOME,  WM_VSCROLL, SB_TOP,
                                VK_END,   WM_VSCROLL, SB_BOTTOM,
                                VK_PRIOR, WM_VSCROLL, SB_PAGEUP,
                                VK_NEXT,  WM_VSCROLL, SB_PAGEDOWN,
                                VK_UP,    WM_VSCROLL, SB_LINEUP,
                                VK_DOWN,  WM_VSCROLL, SB_LINEDOWN,
                                VK_LEFT,  WM_HSCROLL, SB_LINEUP,
                                VK_RIGHT, WM_HSCROLL, SB_LINEDOWN 
                             };

#define NUMKEYS (sizeof (keyToScroll) / sizeof (keyToScroll[0]))


                                                                        //
/* WinMain - Entry point for MIDIMon.
 */
int PASCAL WinMain(hInstance,hPrevInstance,lpszCmdLine,cmdShow)
HANDLE hInstance,hPrevInstance;
LPSTR lpszCmdLine;
int cmdShow;
{
    MSG msg;
    WORD wRtn;
    PREFERENCES preferences;
    char szErrorText[80];
    WORD i;

    hInst = hInstance;

    /* Get preferred user setup.
     */
    getPreferences(&preferences);
    
    /* Initialize application.
     */
    LoadString(hInstance, IDS_APPNAME, szAppName, sizeof(szAppName)); 
    if (!hPrevInstance) {
        if (! InitFirstInstance(hInstance)) {
            return 1;
        }
    }

    /* Create a display window.
     */
    hMainWnd = CreateWindow(szAppName,
                        szAppName,
                        WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
                        preferences.iInitialX,
                        preferences.iInitialY,
                        preferences.iInitialW,
                        preferences.iInitialH,
                        (HWND)NULL,
                        (HMENU)NULL,
                        hInstance,
                        (LPSTR)NULL);

    if (!hMainWnd)
        return 1;

    /* Hide scroll bars for now.
     */
    SetScrollRange (hMainWnd, SB_VERT, 0, 0, FALSE);
    SetScrollRange (hMainWnd, SB_HORZ, 0, 0, FALSE);
    
    /* Show the display window.
     */
    ShowWindow(hMainWnd, cmdShow);
    UpdateWindow(hMainWnd);
    
    /* Get the number of MIDI input devices.  Then get the capabilities of
     * each device.  We don't use the capabilities information right now,
     * but we could use it to report the name of the device that received
     * each MIDI event.
     */
    wNumDevices = midiInGetNumDevs();
    if(!wNumDevices) {
        Error("There are no MIDI input devices.");
        PostQuitMessage(0);
    }
    for(i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++) {
        wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) &midiInCaps[i],
                                sizeof(MIDIINCAPS));
        if(wRtn) {
            midiInGetErrorText(wRtn, (LPSTR)szErrorText, 
                               sizeof(szErrorText));
            Error(szErrorText);
        }
    }

    /* Allocate a circular buffer for low-level MIDI input.  This buffer
     * is filled by the low-level callback function and emptied by the
     * application when it receives MM_MIDIINPUT messages.
     */
    lpInputBuffer = AllocCircularBuffer(
                        (DWORD) (INPUT_BUFFER_SIZE * sizeof(EVENT)));
    if(lpInputBuffer == NULL) {
        Error("Not enough memory available for input buffer.");
        return 1;
    }

    /* Allocate a display buffer.  Incoming events from the circular input
     * buffer are put into this buffer for display.
     */
    lpDisplayBuffer = AllocDisplayBuffer((DWORD) (DISPLAY_BUFFER_SIZE));
    if(lpDisplayBuffer == NULL) {
        Error("Not enough memory available for display buffer.");
        FreeCircularBuffer(lpInputBuffer);
        return 1;
    }

    /* Open all MIDI input devices after allocating and setting up
     * instance data for each device.  The instance data is used to
     * pass buffer management information between the application and
     * the low-level callback function.  It also includes a device ID,
     * a handle to the MIDI Mapper, and a handle to the application's
     * display window, so the callback can notify the window when input
     * data is available.  A single callback function is used to service
     * all opened input devices.
     */
    for(i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++)
    {
        if((lpCallbackInstanceData[i] = AllocCallbackInstanceData()) == NULL)
        {
            Error("Not enough memory available.");
            FreeCircularBuffer(lpInputBuffer);
            FreeDisplayBuffer(lpDisplayBuffer);
            return 1;
        }
        lpCallbackInstanceData[i]->hWnd = hMainWnd;         
        lpCallbackInstanceData[i]->dwDevice = i;
        lpCallbackInstanceData[i]->lpBuf = lpInputBuffer;
        lpCallbackInstanceData[i]->hMapper = hMapper;
        
        wRtn = midiInOpen((LPHMIDIIN)&hMidiIn[i],
                          i,
                          (DWORD)midiInputHandler,
                          (DWORD)lpCallbackInstanceData[i],
                          CALLBACK_FUNCTION);
        if(wRtn)
        {
            midiInGetErrorText(wRtn, (LPSTR)szErrorText, sizeof(szErrorText));
            Error(szErrorText);
        }
    }

    /* Start MIDI input.
     */
    for(i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++) {
        midiInStart(hMidiIn[i]);
    }

    /* Standard Windows message processing loop.  We don't drop out of
     * this loop until the user quits the application.
     */
    while (GetMessage (&msg, NULL, 0, 0)) {
         TranslateMessage (&msg);
         DispatchMessage (&msg);
    }

    /* Stop, reset, close MIDI input.  Free callback instance data.
     */
    for(i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++) {
        midiInStop(hMidiIn[i]);
        midiInReset(hMidiIn[i]);
        midiInClose(hMidiIn[i]);
        FreeCallbackInstanceData(lpCallbackInstanceData[i]);
    }

    /* Close the MIDI Mapper, if it's open.
     */
    if(hMapper)
        midiOutClose(hMapper);
    
    /* Free input and display buffers.
     */
    FreeCircularBuffer(lpInputBuffer);
    FreeDisplayBuffer(lpDisplayBuffer);

    return (msg.wParam);
}

                                                                        //
/* WndProc - Main window procedure function.
 */
long FAR PASCAL WndProc(hWnd, message, wParam, lParam)
HWND hWnd;
unsigned message;
WORD wParam;
LONG lParam;
{
    PAINTSTRUCT ps;
    HFONT hFont;
    HBRUSH hBrush;
    HPEN hPen;
    HDC hDC;
    TEXTMETRIC tm;
    static BOOL bWindowCreated = 0;
    static short wChar, hChar;
    static short maxClientWidth;
    static short wClient, hClient;
    static short nVscrollMax = 0;
    static short nHscrollMax = 0;
    static short nVscrollPos = 0;
    static short nHscrollPos = 0;
    static short nNumCharsPerLine = 0;
    static short nNumDisplayLines = 0;
    static short nNumDisplayChars = 0;
    short nVscrollInc, nHscrollInc;
    short nPaintBeg, nPaintEnd;
    int i;
        
    char szDisplayTextBuffer[120];

    switch(message)
    {
        case WM_CREATE:
            hDC = GetDC (hWnd);

            /* Set the font we want to use.
             */
            hFont = GetStockObject(ANSI_FIXED_FONT);
            SelectObject(hDC, hFont);
            
            /* Get text metrics and calculate the number of characters
             * per line and the maximum width required for the client area.
             */
            GetTextMetrics (hDC, &tm);
            wChar = tm.tmAveCharWidth;
            hChar = tm.tmHeight + tm.tmExternalLeading;
            nNumCharsPerLine = sizeof(LABEL) - 1;
            wsprintf(szDisplayTextBuffer, LABEL);
            maxClientWidth = LOWORD(GetTextExtent(hDC, szDisplayTextBuffer,
                                        lstrlen(szDisplayTextBuffer)));
            ReleaseDC(hWnd, hDC);
            
            bWindowCreated = 1;
            break;

        case WM_SIZE:
            hClient = HIWORD (lParam);
            wClient = LOWORD (lParam);

            /* Get new client area and adjust scroll clip rectangle.
             */
            GetClientRect(hWnd, (LPRECT) &rectScrollClip);
            rectScrollClip.top += hChar;
            
            /* Calculate new display metrics.  We subtract 1 from
             * nNumDisplayLines to allow room for the label line.
             */
            nNumDisplayLines = hClient / hChar - 1;
            nNumDisplayChars = wClient / wChar;

            /* Calculate and set new scroll bar calibrations.
             */
            nVscrollMax = max (0, nNumBufferLines - nNumDisplayLines);
            nVscrollPos = min (nVscrollPos, nVscrollMax);
            nHscrollMax = max (0, nNumCharsPerLine - nNumDisplayChars);
            nHscrollPos = min (nHscrollPos, nHscrollMax);
            SetScrollRange (hWnd, SB_VERT, 0, nVscrollMax, FALSE);
            SetScrollPos   (hWnd, SB_VERT, nVscrollPos, TRUE);
            SetScrollRange (hWnd, SB_HORZ, 0, nHscrollMax, FALSE);
            SetScrollPos   (hWnd, SB_HORZ, nHscrollPos, TRUE);
            break;

        case WM_GETMINMAXINFO:
            /* Limit the maximum width of the window.
             */
            if(bWindowCreated)
                *((LPWORD)lParam + 8) = maxClientWidth + 
                                        (2 * GetSystemMetrics(SM_CXFRAME)) +
                                        (GetSystemMetrics(SM_CXVSCROLL));
            break;
            
        case WM_COMMAND:
            /* Process menu messages. 
             */
            CommandMsg(hWnd, wParam, lParam); 
            break;

        case WM_VSCROLL:
            /* Determine how much to scroll vertically.
             */
            switch (wParam)
            {
                case SB_TOP:
                    nVscrollInc = -nVscrollPos;
                    break;
                    
                case SB_BOTTOM:
                    nVscrollInc = nVscrollMax - nVscrollPos;
                    break;

                case SB_LINEUP:
                    nVscrollInc = -1;
                    break;

                case SB_LINEDOWN:
                    nVscrollInc = 1;
                    break;

                case SB_PAGEUP:
                    nVscrollInc = min (-1, -nNumDisplayLines);
                    break;

                case SB_PAGEDOWN:
                    nVscrollInc = max (1, nNumDisplayLines);
                    break;

                case SB_THUMBTRACK:
                    nVscrollInc = LOWORD (lParam) - nVscrollPos;
                    break;

                default:
                    nVscrollInc = 0;
            
            }
            
            /* Limit the scroll range and do the scroll.  We use the
             * rectScrollClip rectangle because we don't want to scroll
             * the entire window, only the part below the display label line.
             */
            if(nVscrollInc = max(-nVscrollPos, 
                                 min(nVscrollInc, nVscrollMax - nVscrollPos)))
            {
                nVscrollPos += nVscrollInc;
                ScrollWindow (hWnd, 0, -hChar * nVscrollInc,
                              (LPRECT) &rectScrollClip,
                              (LPRECT) &rectScrollClip);
                UpdateWindow (hWnd);
                SetScrollPos (hWnd, SB_VERT, nVscrollPos, TRUE);
            }
            break;

        case WM_HSCROLL:
            /* Determine how much to scroll horizontally.
             */
            switch (wParam)
            {
                case SB_LINEUP:
                    nHscrollInc = -1;
                    break;

                case SB_LINEDOWN:
                    nHscrollInc = 1;
                    break;

                case SB_THUMBTRACK:
                    nHscrollInc = LOWORD (lParam) - nHscrollPos;
                    break;

                default:
                    nHscrollInc = 0;
            }
            
            /* Limit the scroll range and to the scroll.
             */
            if(nHscrollInc = max(-nHscrollPos,
                                 min(nHscrollInc, nHscrollMax - nHscrollPos)))
            {
                nHscrollPos += nHscrollInc;
                ScrollWindow (hWnd, -wChar * nHscrollInc, 0, NULL, NULL);
                SetScrollPos (hWnd, SB_HORZ, nHscrollPos, TRUE);
            }
            break;

        case WM_KEYDOWN:
            /* Translate keystrokes to scroll message.
             */
            for (i = 0; i < NUMKEYS; i++)
                if (wParam == keyToScroll[i].wVirtKey)
                {
                    SendMessage(hWnd, keyToScroll[i].iMessage,
                                keyToScroll[i].wRequest, 0L);
                    break;
                }
            break;

        case WM_PAINT:
            BeginPaint(hWnd, &ps);

            /* Set up text attributes.
             */
            hFont = GetStockObject(ANSI_FIXED_FONT);
            SelectObject(ps.hdc, hFont);
            SetBkMode(ps.hdc, OPAQUE);
            SetBkColor(ps.hdc, GetSysColor(COLOR_APPWORKSPACE));
            SetTextColor(ps.hdc, GetSysColor(COLOR_WINDOWTEXT));

            /* Put up the display label if we're asked to repaint the
             * top line of the screen.
             */
            if(ps.rcPaint.top < hChar)
            {
                wsprintf(szDisplayTextBuffer, LABEL);
                TextOut(ps.hdc, wChar * (0 - nHscrollPos),
                        0, szDisplayTextBuffer,
                        lstrlen(szDisplayTextBuffer));
                MoveTo(ps.hdc, wChar * (0 - nHscrollPos), hChar - 1);
                LineTo(ps.hdc, wClient, hChar - 1);

                ps.rcPaint.top = hChar;
            }
                
            /* Calculate the beginning and ending line numbers that we need
             * to paint.  These line numbers refer to lines in the display
             * buffer, not to lines in the display window.
             */
            nPaintBeg = max (0, nVscrollPos + ps.rcPaint.top / hChar - 1);
            nPaintEnd = min (nNumBufferLines,
                              nVscrollPos + ps.rcPaint.bottom / hChar + 1);

            /* Get the appropriate events from the display buffer, convert
             * to a text string and paint the text on the display.
             */
            for (i = nPaintBeg; i < nPaintEnd; i++)
            {
                GetDisplayEvent(lpDisplayBuffer, &incomingEvent, i);
                TextOut(ps.hdc, 
                        wChar * (0 - nHscrollPos),
                        hChar * (1 - nVscrollPos + i),
                        szDisplayTextBuffer, 
                        GetDisplayText(szDisplayTextBuffer, &incomingEvent));
            }
                
            /* If we aren't painting anything, then clear the display.
             */
            if(nPaintBeg == nPaintEnd)
            {
                hPen = GetStockObject(NULL_PEN);
                SelectObject(ps.hdc, hPen);
                hBrush = CreateSolidBrush(GetSysColor(COLOR_APPWORKSPACE));
                SelectObject(ps.hdc, hBrush);
                Rectangle(ps.hdc, ps.rcPaint.left,
                                  ps.rcPaint.top,
                                  ps.rcPaint.right,
                                  ps.rcPaint.bottom);

                SelectObject(ps.hdc, GetStockObject(NULL_BRUSH));
                DeleteObject(hBrush);
            }
            
            EndPaint(hWnd, &ps);
            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case MM_MIDIINPUT:
            /* This is a custom message sent by the low level callback
             * function telling us that there is at least one MIDI event
             * in the input buffer.  We empty the input buffer, and put
             * each event in the display buffer, if it's not filtered.
             * If the input buffer is being filled as fast we can empty
             * it, then we'll stay in this loop and not process any other
             * Windows messages, or yield to other applications.  We need
             * something to restrict the amount of time we spend here...
             */
            while(GetEvent(lpInputBuffer, (LPEVENT) &incomingEvent))
            {
                if(!bRecordingEnabled)
                    continue;

                if(!CheckEventFilter((LPEVENT) &incomingEvent,
                                    (LPFILTER) &filter))
                {
                    AddDisplayEvent(lpDisplayBuffer, 
                                    (LPEVENT) &incomingEvent);
                    ++nNumBufferLines;
                    nNumBufferLines = min(nNumBufferLines,
                                          DISPLAY_BUFFER_SIZE - 1);
                }
            }
            
            /* Recalculate vertical scroll bar range, and force
             * the display to be updated.
             */
            nVscrollMax = max (0, nNumBufferLines - nNumDisplayLines);
            nVscrollPos = nVscrollMax;
            SetScrollRange (hWnd, SB_VERT, 0, nVscrollMax, FALSE);
            SetScrollPos   (hWnd, SB_VERT, nVscrollPos, TRUE);
            InvalidateRect(hMainWnd, (LPRECT) &rectScrollClip, 0);
            UpdateWindow(hMainWnd);
            
            break;

        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
            break;
    }
    return NULL;
}

                                                                        //
/* CommandMsg - Processes WM_COMMAND messages.
 *
 * Params:  hWnd - Handle to the window receiving the message.
 *          wParam - The WORD parameter of the WM_COMMAND message. 
 *          lParam - The DWORD parameter of the WM_COMMAND message. 
 *
 * Return:  void
 */
void CommandMsg(hWnd, wParam, lParam)
HWND hWnd;
WORD wParam;
LONG lParam;
{
    PREFERENCES preferences;
    RECT rectWindow;
    WORD wRtn;
    HMENU hMenu;
    WORD i;
    char szErrorText[80];
    
    /* Process any WM_COMMAND messages we want */
    switch (wParam) {
        case IDM_ABOUT:
            About(hInst, hWnd);
            break;

        case IDM_EXIT:
            PostMessage(hWnd, WM_CLOSE, 0, 0l);
            break;

        case IDM_SENDTOMAPPER:
            /* We use hMapper as a toggle between sending events to the
             * Mapper and not sending events.
             */
            if(hMapper) {
                /* Close the Mapper and reset hMapper.  Uncheck the menu item.
                 * Clear Mapper handle in the instance data for each device.
                 */
                wRtn = midiOutClose(hMapper);
                if(wRtn != 0)
                {
                    midiOutGetErrorText(wRtn, (LPSTR)szErrorText, 
                                        sizeof(szErrorText));
                    Error(szErrorText);
                }
                hMapper = 0;

                for(i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++)
                    lpCallbackInstanceData[i]->hMapper = hMapper;

                DoMenuItemCheck(hWnd, wParam, FALSE);
            }
            
            else {
                /* Open the MIDI Mapper, put the Mapper handle in the instance
                 * data for each device and check the menu item.
                 */
                wRtn = midiOutOpen((LPHMIDIOUT) &hMapper, MIDIMAPPER, 
                                    0L, 0L, 0L);
                if(wRtn != 0) {
                    midiOutGetErrorText(wRtn, (LPSTR)szErrorText, 
                                        sizeof(szErrorText));
                    Error(szErrorText);
                    hMapper = 0;
                }

                for(i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++)
                    lpCallbackInstanceData[i]->hMapper = hMapper;

                DoMenuItemCheck(hWnd, wParam, TRUE);
            }
            
            break;
            
        case IDM_SAVESETUP:
            /* Save the current location and size of the display window
             * in the MIDIMON.INI file.
             */
            GetWindowRect(hMainWnd, (LPRECT) &rectWindow);
            preferences.iInitialX = rectWindow.left;
            preferences.iInitialY = rectWindow.top;
            preferences.iInitialW = rectWindow.right - rectWindow.left;
            preferences.iInitialH = rectWindow.bottom - rectWindow.top;

            setPreferences((LPPREFERENCES) &preferences);
            break;

        case IDM_STARTSTOP:
            /* Toggle between recording into the display buffer and not
             * recording.  Tobble the menu item between "Start" to "Stop"
             * accordingly.
             */
            hMenu = GetMenu(hWnd);
            if(bRecordingEnabled)
            {
                ModifyMenu(hMenu, IDM_STARTSTOP, MF_BYCOMMAND, IDM_STARTSTOP,
                           "&Start");
                bRecordingEnabled = 0;
            }
            else
            {
                ModifyMenu(hMenu, IDM_STARTSTOP, MF_BYCOMMAND, IDM_STARTSTOP,
                           "&Stop");
                bRecordingEnabled = 1;
            }
            DrawMenuBar(hWnd);
            break;

        case IDM_CLEAR:
            /* Reset the display buffer, recalibrate the scroll bars,
             * and force an update of the display.
             */
            ResetDisplayBuffer(lpDisplayBuffer);
            nNumBufferLines = 0;
            SetScrollRange (hWnd, SB_VERT, 0, 0, FALSE);

            InvalidateRect(hWnd, (LPRECT) &rectScrollClip, 0);
            UpdateWindow(hWnd);

            break;
            
        /* Set up filter structure for MIDI channel filtering.
         */
        case IDM_FILTCHAN1:
        case IDM_FILTCHAN2:
        case IDM_FILTCHAN3:
        case IDM_FILTCHAN4:
        case IDM_FILTCHAN5:
        case IDM_FILTCHAN6:
        case IDM_FILTCHAN7:
        case IDM_FILTCHAN8:
        case IDM_FILTCHAN9:
        case IDM_FILTCHAN10:
        case IDM_FILTCHAN11:
        case IDM_FILTCHAN12:
        case IDM_FILTCHAN13:
        case IDM_FILTCHAN14:
        case IDM_FILTCHAN15:
        case IDM_FILTCHAN16:
            filter.channel[wParam - IDM_FILTCHAN1] = 
                !filter.channel[wParam - IDM_FILTCHAN1];
            DoMenuItemCheck(hWnd, wParam, 
                filter.channel[wParam - IDM_FILTCHAN1]);
            break;
            
        /* Setup filter structure for MIDI event filtering.
         */
        case IDM_NOTEOFF:
            filter.event.noteOff = !filter.event.noteOff;
            DoMenuItemCheck(hWnd, wParam, filter.event.noteOff);
            break;
        case IDM_NOTEON:
            filter.event.noteOn = !filter.event.noteOn;
            DoMenuItemCheck(hWnd, wParam, filter.event.noteOn);
            break;
        case IDM_POLYAFTERTOUCH:
            filter.event.keyAftertouch = !filter.event.keyAftertouch;
            DoMenuItemCheck(hWnd, wParam, filter.event.keyAftertouch);
            break;
        case IDM_CONTROLCHANGE:
            filter.event.controller = !filter.event.controller;
            DoMenuItemCheck(hWnd, wParam, filter.event.controller);
            break;
        case IDM_PROGRAMCHANGE:
            filter.event.progChange = !filter.event.progChange;
            DoMenuItemCheck(hWnd, wParam, filter.event.progChange);
            break;
        case IDM_CHANNELAFTERTOUCH:
            filter.event.chanAftertouch = !filter.event.chanAftertouch;
            DoMenuItemCheck(hWnd, wParam, filter.event.chanAftertouch);
            break;
        case IDM_PITCHBEND:
            filter.event.pitchBend = !filter.event.pitchBend;
            DoMenuItemCheck(hWnd, wParam, filter.event.pitchBend);
            break;
        case IDM_CHANNELMODE:
            filter.event.channelMode = !filter.event.channelMode;
            DoMenuItemCheck(hWnd, wParam, filter.event.channelMode);
            break;
        case IDM_SYSTEMEXCLUSIVE:
            filter.event.sysEx = !filter.event.sysEx;
            DoMenuItemCheck(hWnd, wParam, filter.event.sysEx);
            break;
        case IDM_SYSTEMCOMMON:
            filter.event.sysCommon = !filter.event.sysCommon;
            DoMenuItemCheck(hWnd, wParam, filter.event.sysCommon);
            break;
        case IDM_SYSTEMREALTIME:
            filter.event.sysRealTime = !filter.event.sysRealTime;
            DoMenuItemCheck(hWnd, wParam, filter.event.sysRealTime);
            break;
        case IDM_ACTIVESENSE:
            filter.event.activeSense = !filter.event.activeSense;
            DoMenuItemCheck(hWnd, wParam, filter.event.activeSense);
            break;

        default:
            break;
    }
}

                                                                        //
/* InitFirstInstance - Performs initializaion for the first instance 
 *      of the application.
 *
 * Params:  hInstance - Instance handle.
 *
 * Return:  Returns 1 if there were no errors.  Otherwise, returns 0.
 */
BOOL InitFirstInstance(hInstance)
HANDLE hInstance;
{
    WNDCLASS wc;
    
    /* Define the class of window we want to register.
     */
    wc.lpszClassName    = szAppName;
    wc.style            = CS_HREDRAW | CS_VREDRAW;
    wc.hCursor          = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon            = LoadIcon(hInstance,"Icon");
    wc.lpszMenuName     = "Menu";
    wc.hbrBackground    = CreateSolidBrush(GetSysColor(COLOR_APPWORKSPACE));
    wc.hInstance        = hInstance;
    wc.lpfnWndProc      = WndProc;
    wc.cbClsExtra       = 0;
    wc.cbWndExtra       = 0;
    
    if(!RegisterClass(&wc))
        return FALSE;
    
    return TRUE;
}

                                                                        //
/* DoMenuItemCheck - Checks and unchecks menu items.
 *
 * Params:  hWnd - Window handle for window associated with menu items.
 *          wMenuItem - The menu ID for the menu item.
 *          newState - The new checked/unchecked state of the menu item.
 *
 * Return:  void
*/
void DoMenuItemCheck(HWND hWnd, WORD wMenuItem, BOOL newState)
{
    HMENU hMenu;
    
    hMenu = GetMenu(hWnd);
    CheckMenuItem(hMenu, wMenuItem, (newState ? MF_CHECKED: MF_UNCHECKED));
}


/* Error - Beeps and shows an error message.
 *
 * Params:  szMsg - Points to a NULL-terminated string containing the
 *              error message.
 *
 * Return:  Returns the return value from the MessageBox() call.
 *          Since this message box has only a single button, the
 *          return value isn't too meaningful.
 */
int Error(szMsg)
LPSTR szMsg;
{
    MessageBeep(0);
    return MessageBox(hMainWnd, szMsg, szAppName, MB_OK);
}

