MIDIMON.C

/************************************************************************** 
*
* 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 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
**************************************************************************/

/* midimon.c - WinMain() and WndProc() functions for MIDIMon, along
* with some initialization and error reporting functions.
*
* MIDIMon is a Windows with Multimedia 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.
*

*/

#include <windows.h>
#include <mmsystem.h>
#include <stdio.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
UINT wNumDevices = 0; // Number of MIDI input devices opened
BOOL bRecordingEnabled = 1; // Enable/disable recording flag
int 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)
HINSTANCE hInstance,hPrevInstance;
LPSTR lpszCmdLine;
int cmdShow;
{
MSG msg;
UINT wRtn;
PREFERENCES preferences;
char szErrorText[256];
unsigned int i;

hInst = hInstance;

/* Get preferred user setup.
*/
getPreferences(&preferences);

/* Initialize application.
*/
LoadString(hInstance, IDS_APPNAME, szAppName, sizeof(szAppName));
if (hPrevInstance || !InitFirstInstance(hInstance))
return 0;

/* 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(GetStringRes(IDS_NOMIDIIN));
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);
Error("midiInGetDevCaps");
}
}

/* 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(GetStringRes(IDS_NOMEM_IBUF));
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(GetStringRes(IDS_NOMEM_DBUF));
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(GetStringRes(IDS_NOMEM));
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)
{
FreeCallbackInstanceData(lpCallbackInstanceData[i]);
midiInGetErrorText(wRtn, (LPSTR)szErrorText, sizeof(szErrorText));
Error(szErrorText);
wsprintf(szErrorText, "midiInOpen(%u)", i);
Error(szErrorText);
}
}

/* Start MIDI input.
*/
for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++) {
if (hMidiIn[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++) {
if (hMidiIn[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.
*/
LRESULT FAR PASCAL WndProc(
HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
PAINTSTRUCT ps;
HFONT hFont;
HBRUSH hBrush;
//!! HPEN hPen;
HDC hDC;
TEXTMETRIC tm;
static BOOL bWindowCreated = 0;
static int wChar, hChar;
static int maxClientWidth;
static int wClient, hClient;
static int nVscrollMax = 0;
static int nHscrollMax = 0;
static int nVscrollPos = 0;
static int nHscrollPos = 0;
static int nNumCharsPerLine = 0;
static int nNumDisplayLines = 0;
static int nNumDisplayChars = 0;
BOOL bNeedToUpdate = FALSE;
int nVscrollInc, nHscrollInc;
int nPaintBeg, nPaintEnd;
int i;
SIZE size;

char szDisplayTextBuffer[120];

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

/* Set the font we want to use.
*/
hFont = GetStockObject(SYSTEM_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 = strlen(GetStringRes(IDS_LABEL)) - 1;

GetTextExtentPoint(hDC,
szDisplayTextBuffer,
sprintf(szDisplayTextBuffer, GetStringRes(IDS_LABEL)),
&size);
maxClientWidth = size.cx;

ReleaseDC(hWnd, hDC);

bWindowCreated = 1;
break;

case WM_SIZE:
hClient = (int)HIWORD(lParam);
wClient = (int)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) {
((LPPOINT)lParam)[4].x = maxClientWidth +
(2 * GetSystemMetrics(SM_CXFRAME)) +
(GetSystemMetrics(SM_CXVSCROLL));
}

//*((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 (LOWORD(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 = ((int)HIWORD(wParam) - 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 (LOWORD(wParam))
{
case SB_LINEUP:
nHscrollInc = -1;
break;

case SB_LINEDOWN:
nHscrollInc = 1;
break;

case SB_PAGEUP:
nHscrollInc = min(-1, -nNumDisplayChars);
break;

case SB_PAGEDOWN:
nHscrollInc = max(1, nNumDisplayChars);
break;

case SB_THUMBTRACK:
nHscrollInc = ((int)HIWORD(wParam) - 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);
UpdateWindow(hWnd);
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);

hBrush = CreateSolidBrush(GetSysColor(COLOR_APPWORKSPACE));
FillRect(ps.hdc, &ps.rcPaint, hBrush);
DeleteObject(hBrush);

/* Set up text attributes.
*/
hFont = GetStockObject(SYSTEM_FIXED_FONT);
SelectObject(ps.hdc, hFont);
SetBkMode(ps.hdc, TRANSPARENT);

/* Put up the display label if we're asked to repaint the
* top line of the screen.
*/
if(ps.rcPaint.top < hChar)
{
TextOut(ps.hdc, wChar * (0 - nHscrollPos),
0, szDisplayTextBuffer,
sprintf(szDisplayTextBuffer, GetStringRes(IDS_LABEL)));
MoveToEx(ps.hdc, wChar * (0 - nHscrollPos), hChar - 1, NULL);
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, (LPEVENT)&incomingEvent, i);
TextOut(ps.hdc,
wChar * (0 - nHscrollPos),
hChar * (1 - nVscrollPos + i),
szDisplayTextBuffer,
GetDisplayText(szDisplayTextBuffer,
(LPEVENT)&incomingEvent));
}

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);
bNeedToUpdate = TRUE;
}
}

/* Recalculate vertical scroll bar range, and force
* the display to be updated.
*/

if (bNeedToUpdate) {
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 0;
}

//
/* CommandMsg - Processes WM_COMMAND messages.
*
* Params: hWnd - Handle to the window receiving the message.
* wParam - Parameter of the WM_COMMAND message.
* lParam - Parameter of the WM_COMMAND message.
*
* Return: void
*/
VOID CommandMsg(
HWND hWnd,
WPARAM wParam,
LPARAM lParam)
{
PREFERENCES preferences;
RECT rectWindow;
UINT wRtn;
HMENU hMenu;
unsigned int i;
char szErrorText[256];
WORD wCommand;

wCommand = LOWORD(wParam);

/* Process any WM_COMMAND messages we want */
switch (wCommand) {
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);
Error("midiOutClose");
}
hMapper = 0;

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

DoMenuItemCheck(hWnd, wCommand, 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, (UINT) MIDIMAPPER,
0L, 0L, 0L);

if(wRtn != 0) { // error opening Mapper
midiOutGetErrorText(wRtn, (LPSTR)szErrorText,
sizeof(szErrorText));
Error("midiOutOpen");
Error(szErrorText);
hMapper = 0;
}

else { // Mapper opened successfully
for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++)
lpCallbackInstanceData[i]->hMapper = hMapper;

DoMenuItemCheck(hWnd, wCommand, 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. Toggle the menu item between "Start" to "Stop"
* accordingly.
*/
hMenu = GetMenu(hWnd);
if(bRecordingEnabled)
{
ModifyMenu(hMenu, IDM_STARTSTOP, MF_BYCOMMAND, IDM_STARTSTOP,
GetStringRes(IDS_START));
bRecordingEnabled = 0;
}
else
{
ModifyMenu(hMenu, IDM_STARTSTOP, MF_BYCOMMAND, IDM_STARTSTOP,
GetStringRes(IDS_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_FILTCHAN0:
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:
filter.channel[wCommand - IDM_FILTCHAN0] =
!filter.channel[wCommand - IDM_FILTCHAN0];
DoMenuItemCheck(hWnd, wCommand,
filter.channel[wCommand - IDM_FILTCHAN0]);
break;

/* Setup filter structure for MIDI event filtering.
*/
case IDM_NOTEOFF:
filter.event.noteOff = !filter.event.noteOff;
DoMenuItemCheck(hWnd, wCommand, filter.event.noteOff);
break;
case IDM_NOTEON:
filter.event.noteOn = !filter.event.noteOn;
DoMenuItemCheck(hWnd, wCommand, filter.event.noteOn);
break;
case IDM_POLYAFTERTOUCH:
filter.event.keyAftertouch = !filter.event.keyAftertouch;
DoMenuItemCheck(hWnd, wCommand, filter.event.keyAftertouch);
break;
case IDM_CONTROLCHANGE:
filter.event.controller = !filter.event.controller;
DoMenuItemCheck(hWnd, wCommand, filter.event.controller);
break;
case IDM_PROGRAMCHANGE:
filter.event.progChange = !filter.event.progChange;
DoMenuItemCheck(hWnd, wCommand, filter.event.progChange);
break;
case IDM_CHANNELAFTERTOUCH:
filter.event.chanAftertouch = !filter.event.chanAftertouch;
DoMenuItemCheck(hWnd, wCommand, filter.event.chanAftertouch);
break;
case IDM_PITCHBEND:
filter.event.pitchBend = !filter.event.pitchBend;
DoMenuItemCheck(hWnd, wCommand, filter.event.pitchBend);
break;
case IDM_CHANNELMODE:
filter.event.channelMode = !filter.event.channelMode;
DoMenuItemCheck(hWnd, wCommand, filter.event.channelMode);
break;
case IDM_SYSTEMEXCLUSIVE:
filter.event.sysEx = !filter.event.sysEx;
DoMenuItemCheck(hWnd, wCommand, filter.event.sysEx);
break;
case IDM_SYSTEMCOMMON:
filter.event.sysCommon = !filter.event.sysCommon;
DoMenuItemCheck(hWnd, wCommand, filter.event.sysCommon);
break;
case IDM_SYSTEMREALTIME:
filter.event.sysRealTime = !filter.event.sysRealTime;
DoMenuItemCheck(hWnd, wCommand, filter.event.sysRealTime);
break;
case IDM_ACTIVESENSE:
filter.event.activeSense = !filter.event.activeSense;
DoMenuItemCheck(hWnd, wCommand, 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 = (HBRUSH)(COLOR_APPWORKSPACE + 1);
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(
HWNDhWnd,
WORDwMenuItem,
BOOLnewState)
{
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);
}

/******************************************************************************\
*
* FUNCTION:GetStringRes (int id INPUT ONLY)
*
* COMMENTS:Load the resource string with the ID given, and return a
*pointer to it.Notice that the buffer is common memory so
*the string must be used before this call is made a second time.
*
\******************************************************************************/

LPTSTR GetStringRes (int id)
{
static TCHAR buffer[MAX_PATH];

buffer[0]=0;
LoadString (GetModuleHandle (NULL), id, buffer, MAX_PATH);
return buffer;
}