home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Windows Gam…ming Gurus (2nd Edition) / Disc2.iso / msdn_vcb / samples / vc98 / sdk / graphics / audio / midimon / midimon.c < prev    next >
Encoding:
C/C++ Source or Header  |  1997-10-05  |  30.2 KB  |  878 lines

  1. /**************************************************************************
  2.  *
  3.  *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
  4.  *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  5.  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
  6.  *  PURPOSE.
  7.  *
  8.  *  Copyright (C) 1993 - 1997  Microsoft Corporation.  All Rights Reserved.
  9.  * 
  10.  **************************************************************************/
  11.  
  12. /* midimon.c - WinMain() and WndProc() functions for MIDIMon, along
  13.  *      with some initialization and error reporting functions.
  14.  *
  15.  * MIDIMon is a Windows with Multimedia application that records and displays 
  16.  *  incoming MIDI information.  It uses a low-level callback function to 
  17.  *  get timestamped MIDI input.  The callback function puts the incoming
  18.  *  MIDI event information (source device, timestamp, and raw MIDI
  19.  *  data) in a circular input buffer and notifies the application by posting 
  20.  *  a MM_MIDIINPUT message.  When the application processes the MM_MIDIINPUT
  21.  *  message, it removes the MIDI event from the input buffer and puts it in
  22.  *  a display buffer.  Information in the display buffer is converted to
  23.  *  text and displayed in a scrollable window.  Incoming MIDI data can be sent
  24.  *  to the MIDI Mapper if the user chooses.  Filtering is provided for the
  25.  *  display buffer, but not for data sent to the Mapper.
  26.  *
  27.  
  28.  */
  29.  
  30. #include <windows.h>
  31. #include <mmsystem.h>
  32. #include <stdio.h>
  33. #include "midimon.h"
  34. #include "about.h"
  35. #include "circbuf.h"
  36. #include "display.h"
  37. #include "prefer.h"
  38. #include "instdata.h"
  39. #include "callback.h"
  40. #include "filter.h"
  41.  
  42. HANDLE hInst;                           // Instance handle for application
  43. char szAppName[20];                     // Application name
  44. HWND hMainWnd;                          // Main window handle
  45. HMIDIOUT hMapper = 0;                   // Handle to MIDI Mapper
  46. UINT wNumDevices = 0;             // Number of MIDI input devices opened
  47. BOOL bRecordingEnabled = 1;             // Enable/disable recording flag
  48. int  nNumBufferLines = 0;         // Number of lines in display buffer
  49. RECT rectScrollClip;                    // Clipping rectangle for scrolling
  50.  
  51. LPCIRCULARBUFFER lpInputBuffer;         // Input buffer structure
  52. LPDISPLAYBUFFER lpDisplayBuffer;        // Display buffer structure
  53. PREFERENCES preferences;                // User preferences structure
  54. EVENT incomingEvent;                    // Incoming MIDI event structure
  55.  
  56. MIDIINCAPS midiInCaps[MAX_NUM_DEVICES]; // Device capabilities structures
  57. HMIDIIN hMidiIn[MAX_NUM_DEVICES];       // MIDI input device handles
  58.  
  59. // Callback instance data pointers
  60. LPCALLBACKINSTANCEDATA lpCallbackInstanceData[MAX_NUM_DEVICES];
  61.  
  62. // Display filter structure
  63. FILTER filter = {
  64.                     0, 0, 0, 0, 0, 0, 0, 0, 
  65.                     0, 0, 0, 0, 0, 0, 0, 0, 
  66.                     0, 0, 0, 0, 0, 0, 0, 0, 
  67.                     0, 0, 0, 0, 0, 0, 0, 0
  68.                 };
  69.  
  70. // Virtual key to scroll message translation structure
  71. KEYTOSCROLL keyToScroll [] = { 
  72.                                 VK_HOME,  WM_VSCROLL, SB_TOP,
  73.                                 VK_END,   WM_VSCROLL, SB_BOTTOM,
  74.                                 VK_PRIOR, WM_VSCROLL, SB_PAGEUP,
  75.                                 VK_NEXT,  WM_VSCROLL, SB_PAGEDOWN,
  76.                                 VK_UP,    WM_VSCROLL, SB_LINEUP,
  77.                                 VK_DOWN,  WM_VSCROLL, SB_LINEDOWN,
  78.                                 VK_LEFT,  WM_HSCROLL, SB_LINEUP,
  79.                                 VK_RIGHT, WM_HSCROLL, SB_LINEDOWN 
  80.                              };
  81.  
  82. #define NUMKEYS (sizeof (keyToScroll) / sizeof (keyToScroll[0]))
  83.  
  84.  
  85.                                                                         //
  86. /* WinMain - Entry point for MIDIMon.
  87.  */
  88. int PASCAL WinMain(hInstance,hPrevInstance,lpszCmdLine,cmdShow)
  89. HANDLE hInstance,hPrevInstance;
  90. LPSTR lpszCmdLine;
  91. int cmdShow;
  92. {
  93.     MSG msg;
  94.     UINT  wRtn;
  95.     PREFERENCES preferences;
  96.     char szErrorText[256];
  97.     unsigned int i;
  98.  
  99.     hInst = hInstance;
  100.  
  101.     /* Get preferred user setup.
  102.      */
  103.     getPreferences(&preferences);
  104.     
  105.     /* Initialize application.
  106.      */
  107.     LoadString(hInstance, IDS_APPNAME, szAppName, sizeof(szAppName)); 
  108.     if (hPrevInstance || !InitFirstInstance(hInstance))
  109.         return 0;
  110.  
  111.     /* Create a display window.
  112.      */
  113.     hMainWnd = CreateWindow(szAppName,
  114.                         szAppName,
  115.                         WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
  116.                         preferences.iInitialX,
  117.                         preferences.iInitialY,
  118.                         preferences.iInitialW,
  119.                         preferences.iInitialH,
  120.                         (HWND)NULL,
  121.                         (HMENU)NULL,
  122.                         hInstance,
  123.                         (LPSTR)NULL);
  124.  
  125.     if (!hMainWnd)
  126.         return 1;
  127.  
  128.     /* Hide scroll bars for now.
  129.      */
  130.     SetScrollRange(hMainWnd, SB_VERT, 0, 0, FALSE);
  131.     SetScrollRange(hMainWnd, SB_HORZ, 0, 0, FALSE);
  132.     
  133.     /* Show the display window.
  134.      */
  135.     ShowWindow(hMainWnd, cmdShow);
  136.     UpdateWindow(hMainWnd);
  137.     
  138.     /* Get the number of MIDI input devices.  Then get the capabilities of
  139.      * each device.  We don't use the capabilities information right now,
  140.      * but we could use it to report the name of the device that received
  141.      * each MIDI event.
  142.      */
  143.     wNumDevices = midiInGetNumDevs();
  144.     if (!wNumDevices) {
  145.         Error(GetStringRes(IDS_NOMIDIIN));
  146.         PostQuitMessage(0);
  147.     }
  148.     for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++) {
  149.         wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) &midiInCaps[i],
  150.                                 sizeof(MIDIINCAPS));
  151.         if(wRtn) {
  152.             midiInGetErrorText(wRtn, (LPSTR)szErrorText, 
  153.                                sizeof(szErrorText));
  154.             Error(szErrorText);
  155.             Error("midiInGetDevCaps");
  156.         }
  157.     }
  158.  
  159.     /* Allocate a circular buffer for low-level MIDI input.  This buffer
  160.      * is filled by the low-level callback function and emptied by the
  161.      * application when it receives MM_MIDIINPUT messages.
  162.      */
  163.     lpInputBuffer = AllocCircularBuffer(
  164.                         (DWORD)(INPUT_BUFFER_SIZE * sizeof(EVENT)));
  165.     if (lpInputBuffer == NULL) {
  166.         Error(GetStringRes(IDS_NOMEM_IBUF));
  167.         return 1;
  168.     }
  169.  
  170.     /* Allocate a display buffer.  Incoming events from the circular input
  171.      * buffer are put into this buffer for display.
  172.      */
  173.     lpDisplayBuffer = AllocDisplayBuffer((DWORD)(DISPLAY_BUFFER_SIZE));
  174.     if (lpDisplayBuffer == NULL) {
  175.         Error(GetStringRes(IDS_NOMEM_DBUF));
  176.         FreeCircularBuffer(lpInputBuffer);
  177.         return 1;
  178.     }
  179.  
  180.     /* Open all MIDI input devices after allocating and setting up
  181.      * instance data for each device.  The instance data is used to
  182.      * pass buffer management information between the application and
  183.      * the low-level callback function.  It also includes a device ID,
  184.      * a handle to the MIDI Mapper, and a handle to the application's
  185.      * display window, so the callback can notify the window when input
  186.      * data is available.  A single callback function is used to service
  187.      * all opened input devices.
  188.      */
  189.     for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++)
  190.     {
  191.         if ((lpCallbackInstanceData[i] = AllocCallbackInstanceData()) == NULL)
  192.         {
  193.             Error(GetStringRes(IDS_NOMEM));
  194.             FreeCircularBuffer(lpInputBuffer);
  195.             FreeDisplayBuffer(lpDisplayBuffer);
  196.             return 1;
  197.         }
  198.         lpCallbackInstanceData[i]->hWnd = hMainWnd;         
  199.         lpCallbackInstanceData[i]->dwDevice = i;
  200.         lpCallbackInstanceData[i]->lpBuf = lpInputBuffer;
  201.         lpCallbackInstanceData[i]->hMapper = hMapper;
  202.         
  203.         wRtn = midiInOpen((LPHMIDIIN)&hMidiIn[i],
  204.                           i,
  205.                           (DWORD)midiInputHandler,
  206.                           (DWORD)lpCallbackInstanceData[i],
  207.                           CALLBACK_FUNCTION);
  208.         if(wRtn)
  209.         {
  210.             FreeCallbackInstanceData(lpCallbackInstanceData[i]);
  211.             midiInGetErrorText(wRtn, (LPSTR)szErrorText, sizeof(szErrorText));
  212.             Error(szErrorText);
  213.             wsprintf(szErrorText, "midiInOpen(%u)", i);
  214.             Error(szErrorText);
  215.         }
  216.     }
  217.  
  218.     /* Start MIDI input.
  219.      */
  220.     for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++) {
  221.         if (hMidiIn[i])
  222.             midiInStart(hMidiIn[i]);
  223.     }
  224.  
  225.     /* Standard Windows message processing loop.  We don't drop out of
  226.      * this loop until the user quits the application.
  227.      */
  228.     while (GetMessage(&msg, NULL, 0, 0)) {
  229.          TranslateMessage(&msg);
  230.          DispatchMessage(&msg);
  231.     }
  232.  
  233.     /* Stop, reset, close MIDI input.  Free callback instance data.
  234.      */
  235.     for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++) {
  236.         if (hMidiIn[i]) {
  237.             midiInStop(hMidiIn[i]);
  238.             midiInReset(hMidiIn[i]);
  239.             midiInClose(hMidiIn[i]);
  240.             FreeCallbackInstanceData(lpCallbackInstanceData[i]);
  241.         }
  242.     }
  243.  
  244.     /* Close the MIDI Mapper, if it's open.
  245.      */
  246.     if (hMapper)
  247.         midiOutClose(hMapper);
  248.     
  249.     /* Free input and display buffers.
  250.      */
  251.     FreeCircularBuffer(lpInputBuffer);
  252.     FreeDisplayBuffer(lpDisplayBuffer);
  253.  
  254.     return (msg.wParam);
  255. }
  256.  
  257.                                                                         //
  258. /* WndProc - Main window procedure function.
  259.  */
  260. LRESULT FAR PASCAL WndProc(
  261.   HWND      hWnd,
  262.   UINT      message,
  263.   WPARAM  wParam,
  264.   LPARAM  lParam)
  265. {
  266.     PAINTSTRUCT ps;
  267.     HFONT hFont;
  268.     HBRUSH hBrush;
  269. //!!    HPEN hPen;
  270.     HDC hDC;
  271.     TEXTMETRIC tm;
  272.     static BOOL  bWindowCreated = 0;
  273.     static int      wChar, hChar;
  274.     static int   maxClientWidth;
  275.     static int      wClient, hClient;
  276.     static int      nVscrollMax = 0;
  277.     static int      nHscrollMax = 0;
  278.     static int      nVscrollPos = 0;
  279.     static int      nHscrollPos = 0;
  280.     static int   nNumCharsPerLine = 0;
  281.     static int   nNumDisplayLines = 0;
  282.     static int   nNumDisplayChars = 0;
  283.     BOOL     bNeedToUpdate = FALSE;
  284.     int      nVscrollInc, nHscrollInc;
  285.     int      nPaintBeg, nPaintEnd;
  286.     int          i;
  287.     SIZE  size;
  288.         
  289.     char szDisplayTextBuffer[120];
  290.  
  291.     switch(message)
  292.     {
  293.         case WM_CREATE:
  294.             hDC = GetDC(hWnd);
  295.  
  296.             /* Set the font we want to use.
  297.              */
  298.             hFont = GetStockObject(SYSTEM_FIXED_FONT);
  299.             SelectObject(hDC, hFont);
  300.             
  301.             /* Get text metrics and calculate the number of characters
  302.              * per line and the maximum width required for the client area.
  303.              */
  304.             GetTextMetrics(hDC, &tm);
  305.         wChar = tm.tmAveCharWidth;
  306.         hChar = tm.tmHeight + tm.tmExternalLeading;
  307.         nNumCharsPerLine = strlen(GetStringRes(IDS_LABEL)) - 1;
  308.  
  309.         GetTextExtentPoint(hDC,
  310.                    szDisplayTextBuffer,
  311.                    sprintf(szDisplayTextBuffer, GetStringRes(IDS_LABEL)),
  312.                    &size);
  313.         maxClientWidth = size.cx;
  314.  
  315.             ReleaseDC(hWnd, hDC);
  316.             
  317.             bWindowCreated = 1;
  318.             break;
  319.  
  320.         case WM_SIZE:
  321.         hClient = (int)HIWORD(lParam);
  322.         wClient = (int)LOWORD(lParam);
  323.  
  324.             /* Get new client area and adjust scroll clip rectangle.
  325.              */
  326.             GetClientRect(hWnd, (LPRECT)&rectScrollClip);
  327.             rectScrollClip.top += hChar;
  328.             
  329.             /* Calculate new display metrics.  We subtract 1 from
  330.              * nNumDisplayLines to allow room for the label line.
  331.              */
  332.             nNumDisplayLines = hClient / hChar - 1;
  333.             nNumDisplayChars = wClient / wChar;
  334.  
  335.             /* Calculate and set new scroll bar calibrations.
  336.              */
  337.         nVscrollMax = max(0, nNumBufferLines - nNumDisplayLines);
  338.         nVscrollPos = min(nVscrollPos, nVscrollMax);
  339.         nHscrollMax = max(0, nNumCharsPerLine - nNumDisplayChars);
  340.         nHscrollPos = min(nHscrollPos, nHscrollMax);
  341.             SetScrollRange(hWnd, SB_VERT, 0, nVscrollMax, FALSE);
  342.             SetScrollPos(hWnd, SB_VERT, nVscrollPos, TRUE);
  343.             SetScrollRange(hWnd, SB_HORZ, 0, nHscrollMax, FALSE);
  344.             SetScrollPos(hWnd, SB_HORZ, nHscrollPos, TRUE);
  345.             break;
  346.  
  347.         case WM_GETMINMAXINFO:
  348.             /* Limit the maximum width of the window.
  349.              */
  350.         if(bWindowCreated) {
  351.         ((LPPOINT)lParam)[4].x = maxClientWidth +
  352.                      (2 * GetSystemMetrics(SM_CXFRAME)) +
  353.                      (GetSystemMetrics(SM_CXVSCROLL));
  354.         }
  355.  
  356.   //        *((LPWORD)lParam + 8) = maxClientWidth +
  357.   //                     (2 * GetSystemMetrics(SM_CXFRAME)) +
  358.   //                    (GetSystemMetrics(SM_CXVSCROLL));
  359.             break;
  360.             
  361.         case WM_COMMAND:
  362.             /* Process menu messages. 
  363.              */
  364.             CommandMsg(hWnd, wParam, lParam); 
  365.             break;
  366.  
  367.         case WM_VSCROLL:
  368.             /* Determine how much to scroll vertically.
  369.              */
  370.         switch (LOWORD(wParam))
  371.             {
  372.                 case SB_TOP:
  373.                     nVscrollInc = -nVscrollPos;
  374.                     break;
  375.                     
  376.                 case SB_BOTTOM:
  377.                     nVscrollInc = nVscrollMax - nVscrollPos;
  378.                     break;
  379.  
  380.                 case SB_LINEUP:
  381.                     nVscrollInc = -1;
  382.                     break;
  383.  
  384.                 case SB_LINEDOWN:
  385.                     nVscrollInc = 1;
  386.                     break;
  387.  
  388.                 case SB_PAGEUP:
  389.                     nVscrollInc = min (-1, -nNumDisplayLines);
  390.                     break;
  391.  
  392.                 case SB_PAGEDOWN:
  393.             nVscrollInc = max(1, nNumDisplayLines);
  394.                     break;
  395.  
  396.                 case SB_THUMBTRACK:
  397.             nVscrollInc = ((int)HIWORD(wParam) - nVscrollPos);
  398.                     break;
  399.  
  400.                 default:
  401.                     nVscrollInc = 0;
  402.             
  403.             }
  404.             
  405.             /* Limit the scroll range and do the scroll.  We use the
  406.              * rectScrollClip rectangle because we don't want to scroll
  407.              * the entire window, only the part below the display label line.
  408.              */
  409.         if(nVscrollInc = max(-nVscrollPos,
  410.                  min(nVscrollInc,
  411.                      nVscrollMax - nVscrollPos)))
  412.             {
  413.                 nVscrollPos += nVscrollInc;
  414.                 ScrollWindow(hWnd, 0, -hChar * nVscrollInc,
  415.                               (LPRECT)&rectScrollClip,
  416.                               (LPRECT)&rectScrollClip);
  417.                 UpdateWindow(hWnd);
  418.                 SetScrollPos(hWnd, SB_VERT, nVscrollPos, TRUE);
  419.             }
  420.             break;
  421.  
  422.         case WM_HSCROLL:
  423.             /* Determine how much to scroll horizontally.
  424.              */
  425.         switch (LOWORD(wParam))
  426.             {
  427.                 case SB_LINEUP:
  428.                     nHscrollInc = -1;
  429.                     break;
  430.  
  431.                 case SB_LINEDOWN:
  432.                     nHscrollInc = 1;
  433.                     break;
  434.  
  435.                 case SB_PAGEUP:
  436.             nHscrollInc = min(-1, -nNumDisplayChars);
  437.                     break;
  438.  
  439.                 case SB_PAGEDOWN:
  440.             nHscrollInc = max(1, nNumDisplayChars);
  441.                     break;
  442.  
  443.                 case SB_THUMBTRACK:
  444.             nHscrollInc = ((int)HIWORD(wParam) - nHscrollPos);
  445.                     break;
  446.  
  447.                 default:
  448.                     nHscrollInc = 0;
  449.             }
  450.             
  451.             /* Limit the scroll range and to the scroll.
  452.              */
  453.         if(nHscrollInc = max(-nHscrollPos,
  454.                  min(nHscrollInc,
  455.                      nHscrollMax - nHscrollPos)))
  456.             {
  457.                 nHscrollPos += nHscrollInc;
  458.                 ScrollWindow(hWnd, -wChar * nHscrollInc, 0, NULL, NULL);
  459.                 UpdateWindow(hWnd);
  460.                 SetScrollPos(hWnd, SB_HORZ, nHscrollPos, TRUE);
  461.             }
  462.             break;
  463.  
  464.         case WM_KEYDOWN:
  465.             /* Translate keystrokes to scroll message.
  466.              */
  467.             for (i = 0; i < NUMKEYS; i++)
  468.                 if (wParam == keyToScroll[i].wVirtKey)
  469.                 {
  470.                     SendMessage(hWnd, keyToScroll[i].iMessage,
  471.                                 keyToScroll[i].wRequest, 0L);
  472.                     break;
  473.                 }
  474.             break;
  475.  
  476.         case WM_PAINT:
  477.             BeginPaint(hWnd, &ps);
  478.  
  479.             hBrush = CreateSolidBrush(GetSysColor(COLOR_APPWORKSPACE));
  480.             FillRect(ps.hdc, &ps.rcPaint, hBrush);
  481.             DeleteObject(hBrush);
  482.  
  483.             /* Set up text attributes.
  484.              */
  485.             hFont = GetStockObject(SYSTEM_FIXED_FONT);
  486.             SelectObject(ps.hdc, hFont);
  487.             SetBkMode(ps.hdc, TRANSPARENT);
  488.  
  489.             /* Put up the display label if we're asked to repaint the
  490.              * top line of the screen.
  491.              */
  492.         if(ps.rcPaint.top < hChar)
  493.             {
  494.                 TextOut(ps.hdc, wChar * (0 - nHscrollPos),
  495.                         0, szDisplayTextBuffer,
  496.                         sprintf(szDisplayTextBuffer, GetStringRes(IDS_LABEL)));
  497.         MoveToEx(ps.hdc, wChar * (0 - nHscrollPos), hChar - 1, NULL);
  498.                 LineTo(ps.hdc, wClient, hChar - 1);
  499.  
  500.                 ps.rcPaint.top = hChar;
  501.             }
  502.                 
  503.             /* Calculate the beginning and ending line numbers that we need
  504.              * to paint.  These line numbers refer to lines in the display
  505.              * buffer, not to lines in the display window.
  506.              */
  507.         nPaintBeg = max (0, nVscrollPos + ps.rcPaint.top / hChar - 1);
  508.             nPaintEnd = min(nNumBufferLines,
  509.                               nVscrollPos + ps.rcPaint.bottom / hChar + 1);
  510.  
  511.             /* Get the appropriate events from the display buffer, convert
  512.              * to a text string and paint the text on the display.
  513.              */
  514.             for (i = nPaintBeg; i < nPaintEnd; i++)
  515.             {
  516.                 GetDisplayEvent(lpDisplayBuffer, (LPEVENT)&incomingEvent, i);
  517.                 TextOut(ps.hdc, 
  518.                         wChar * (0 - nHscrollPos),
  519.                         hChar * (1 - nVscrollPos + i),
  520.                         szDisplayTextBuffer, 
  521.                         GetDisplayText(szDisplayTextBuffer,
  522.                                        (LPEVENT)&incomingEvent));
  523.             }
  524.                 
  525.             EndPaint(hWnd, &ps);
  526.             break;
  527.  
  528.         case WM_DESTROY:
  529.             PostQuitMessage(0);
  530.             break;
  531.  
  532.         case MM_MIDIINPUT:
  533.             /* This is a custom message sent by the low level callback
  534.              * function telling us that there is at least one MIDI event
  535.              * in the input buffer.  We empty the input buffer, and put
  536.              * each event in the display buffer, if it's not filtered.
  537.              * If the input buffer is being filled as fast we can empty
  538.              * it, then we'll stay in this loop and not process any other
  539.              * Windows messages, or yield to other applications.  We need
  540.              * something to restrict the amount of time we spend here...
  541.              */
  542.             while(GetEvent(lpInputBuffer, (LPEVENT)&incomingEvent))
  543.             {
  544.                 if(!bRecordingEnabled)
  545.                     continue;
  546.  
  547.                 if(!CheckEventFilter((LPEVENT)&incomingEvent,
  548.                                     (LPFILTER)&filter))
  549.                 {
  550.                     AddDisplayEvent(lpDisplayBuffer, 
  551.                                     (LPEVENT)&incomingEvent);
  552.                     ++nNumBufferLines;
  553.                     nNumBufferLines = min(nNumBufferLines,
  554.                       DISPLAY_BUFFER_SIZE - 1);
  555.             bNeedToUpdate = TRUE;
  556.                 }
  557.             }
  558.             
  559.             /* Recalculate vertical scroll bar range, and force
  560.              * the display to be updated.
  561.          */
  562.  
  563.         if (bNeedToUpdate) {
  564.           nVscrollMax = max(0, nNumBufferLines - nNumDisplayLines);
  565.           nVscrollPos = nVscrollMax;
  566.           SetScrollRange(hWnd, SB_VERT, 0, nVscrollMax, FALSE);
  567.           SetScrollPos(hWnd, SB_VERT, nVscrollPos, TRUE);
  568.           InvalidateRect(hMainWnd, (LPRECT)&rectScrollClip, 0);
  569.           UpdateWindow(hMainWnd);
  570.         }
  571.             
  572.             break;
  573.  
  574.         default:
  575.             return DefWindowProc(hWnd, message, wParam, lParam);
  576.             break;
  577.     }
  578.     return 0;
  579. }
  580.  
  581.                                                                         //
  582. /* CommandMsg - Processes WM_COMMAND messages.
  583.  *
  584.  * Params:  hWnd - Handle to the window receiving the message.
  585.  *        wParam - Parameter of the WM_COMMAND message.
  586.  *        lParam - Parameter of the WM_COMMAND message.
  587.  *
  588.  * Return:  void
  589.  */
  590. VOID  CommandMsg(
  591.   HWND      hWnd,
  592.   WPARAM  wParam,
  593.   LPARAM  lParam)
  594. {
  595.     PREFERENCES preferences;
  596.     RECT rectWindow;
  597.     UINT  wRtn;
  598.     HMENU hMenu;
  599.     unsigned int i;
  600.     char szErrorText[256];
  601.     WORD  wCommand;
  602.  
  603.     wCommand = LOWORD(wParam);
  604.     
  605.     /* Process any WM_COMMAND messages we want */
  606.     switch (wCommand) {
  607.         case IDM_ABOUT:
  608.             About(hInst, hWnd);
  609.             break;
  610.  
  611.         case IDM_EXIT:
  612.             PostMessage(hWnd, WM_CLOSE, 0, 0l);
  613.             break;
  614.  
  615.         case IDM_SENDTOMAPPER:
  616.             /* We use hMapper as a toggle between sending events to the
  617.              * Mapper and not sending events.
  618.              */
  619.             if(hMapper) {
  620.                 /* Close the Mapper and reset hMapper.  Uncheck the menu item.
  621.                  * Clear Mapper handle in the instance data for each device.
  622.                  */
  623.                 wRtn = midiOutClose(hMapper);
  624.                 if(wRtn != 0)
  625.                 {
  626.                     midiOutGetErrorText(wRtn, (LPSTR)szErrorText, 
  627.                                         sizeof(szErrorText));
  628.                     Error(szErrorText);
  629.                     Error("midiOutClose");
  630.                 }
  631.                 hMapper = 0;
  632.  
  633.                 for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++)
  634.                     lpCallbackInstanceData[i]->hMapper = hMapper;
  635.  
  636.         DoMenuItemCheck(hWnd, wCommand, FALSE);
  637.             }
  638.             
  639.             else {
  640.                 /* Open the MIDI Mapper, put the Mapper handle in the instance
  641.                  * data for each device and check the menu item.
  642.                  */
  643.         wRtn = midiOutOpen((LPHMIDIOUT) &hMapper, (UINT) MIDIMAPPER,
  644.                                     0L, 0L, 0L);
  645.                                 
  646.                 if(wRtn != 0) {             // error opening Mapper
  647.                     midiOutGetErrorText(wRtn, (LPSTR)szErrorText, 
  648.                                         sizeof(szErrorText));
  649.                     Error("midiOutOpen");
  650.                     Error(szErrorText);
  651.                     hMapper = 0;
  652.                 }
  653.  
  654.                 else {                      // Mapper opened successfully
  655.                     for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++)
  656.                         lpCallbackInstanceData[i]->hMapper = hMapper;
  657.  
  658.             DoMenuItemCheck(hWnd, wCommand, TRUE);
  659.                 }
  660.             }
  661.             
  662.             break;
  663.             
  664.         case IDM_SAVESETUP:
  665.             /* Save the current location and size of the display window
  666.              * in the MIDIMON.INI file.
  667.              */
  668.             GetWindowRect(hMainWnd, (LPRECT)&rectWindow);
  669.             preferences.iInitialX = rectWindow.left;
  670.             preferences.iInitialY = rectWindow.top;
  671.             preferences.iInitialW = rectWindow.right - rectWindow.left;
  672.             preferences.iInitialH = rectWindow.bottom - rectWindow.top;
  673.  
  674.             setPreferences((LPPREFERENCES) &preferences);
  675.             break;
  676.  
  677.         case IDM_STARTSTOP:
  678.             /* Toggle between recording into the display buffer and not
  679.              * recording.  Toggle the menu item between "Start" to "Stop"
  680.              * accordingly.
  681.              */
  682.             hMenu = GetMenu(hWnd);
  683.             if(bRecordingEnabled)
  684.             {
  685.                 ModifyMenu(hMenu, IDM_STARTSTOP, MF_BYCOMMAND, IDM_STARTSTOP,
  686.                            GetStringRes(IDS_START));
  687.                 bRecordingEnabled = 0;
  688.             }
  689.             else
  690.             {
  691.                 ModifyMenu(hMenu, IDM_STARTSTOP, MF_BYCOMMAND, IDM_STARTSTOP,
  692.                            GetStringRes(IDS_STOP));
  693.                 bRecordingEnabled = 1;
  694.             }
  695.             DrawMenuBar(hWnd);
  696.             break;
  697.  
  698.         case IDM_CLEAR:
  699.             /* Reset the display buffer, recalibrate the scroll bars,
  700.              * and force an update of the display.
  701.              */
  702.             ResetDisplayBuffer(lpDisplayBuffer);
  703.             nNumBufferLines = 0;
  704.             SetScrollRange(hWnd, SB_VERT, 0, 0, FALSE);
  705.  
  706.             InvalidateRect(hWnd, (LPRECT)&rectScrollClip, 0);
  707.             UpdateWindow(hWnd);
  708.  
  709.             break;
  710.             
  711.         /* Set up filter structure for MIDI channel filtering.
  712.          */
  713.         case IDM_FILTCHAN0:
  714.         case IDM_FILTCHAN1:
  715.         case IDM_FILTCHAN2:
  716.         case IDM_FILTCHAN3:
  717.         case IDM_FILTCHAN4:
  718.         case IDM_FILTCHAN5:
  719.         case IDM_FILTCHAN6:
  720.         case IDM_FILTCHAN7:
  721.         case IDM_FILTCHAN8:
  722.         case IDM_FILTCHAN9:
  723.         case IDM_FILTCHAN10:
  724.         case IDM_FILTCHAN11:
  725.         case IDM_FILTCHAN12:
  726.         case IDM_FILTCHAN13:
  727.         case IDM_FILTCHAN14:
  728.         case IDM_FILTCHAN15:
  729.         filter.channel[wCommand - IDM_FILTCHAN0] =
  730.         !filter.channel[wCommand - IDM_FILTCHAN0];
  731.         DoMenuItemCheck(hWnd, wCommand,
  732.         filter.channel[wCommand - IDM_FILTCHAN0]);
  733.             break;
  734.             
  735.         /* Setup filter structure for MIDI event filtering.
  736.          */
  737.         case IDM_NOTEOFF:
  738.             filter.event.noteOff = !filter.event.noteOff;
  739.         DoMenuItemCheck(hWnd, wCommand, filter.event.noteOff);
  740.             break;
  741.         case IDM_NOTEON:
  742.             filter.event.noteOn = !filter.event.noteOn;
  743.         DoMenuItemCheck(hWnd, wCommand, filter.event.noteOn);
  744.             break;
  745.         case IDM_POLYAFTERTOUCH:
  746.             filter.event.keyAftertouch = !filter.event.keyAftertouch;
  747.         DoMenuItemCheck(hWnd, wCommand, filter.event.keyAftertouch);
  748.             break;
  749.         case IDM_CONTROLCHANGE:
  750.             filter.event.controller = !filter.event.controller;
  751.         DoMenuItemCheck(hWnd, wCommand, filter.event.controller);
  752.             break;
  753.         case IDM_PROGRAMCHANGE:
  754.             filter.event.progChange = !filter.event.progChange;
  755.         DoMenuItemCheck(hWnd, wCommand, filter.event.progChange);
  756.             break;
  757.         case IDM_CHANNELAFTERTOUCH:
  758.             filter.event.chanAftertouch = !filter.event.chanAftertouch;
  759.         DoMenuItemCheck(hWnd, wCommand, filter.event.chanAftertouch);
  760.             break;
  761.         case IDM_PITCHBEND:
  762.             filter.event.pitchBend = !filter.event.pitchBend;
  763.         DoMenuItemCheck(hWnd, wCommand, filter.event.pitchBend);
  764.             break;
  765.         case IDM_CHANNELMODE:
  766.             filter.event.channelMode = !filter.event.channelMode;
  767.         DoMenuItemCheck(hWnd, wCommand, filter.event.channelMode);
  768.             break;
  769.         case IDM_SYSTEMEXCLUSIVE:
  770.             filter.event.sysEx = !filter.event.sysEx;
  771.         DoMenuItemCheck(hWnd, wCommand, filter.event.sysEx);
  772.             break;
  773.         case IDM_SYSTEMCOMMON:
  774.             filter.event.sysCommon = !filter.event.sysCommon;
  775.         DoMenuItemCheck(hWnd, wCommand, filter.event.sysCommon);
  776.             break;
  777.         case IDM_SYSTEMREALTIME:
  778.             filter.event.sysRealTime = !filter.event.sysRealTime;
  779.         DoMenuItemCheck(hWnd, wCommand, filter.event.sysRealTime);
  780.             break;
  781.         case IDM_ACTIVESENSE:
  782.             filter.event.activeSense = !filter.event.activeSense;
  783.         DoMenuItemCheck(hWnd, wCommand, filter.event.activeSense);
  784.             break;
  785.  
  786.         default:
  787.             break;
  788.     }
  789. }
  790.  
  791.                                                                         //
  792. /* InitFirstInstance - Performs initializaion for the first instance 
  793.  *      of the application.
  794.  *
  795.  * Params:  hInstance - Instance handle.
  796.  *
  797.  * Return:  Returns 1 if there were no errors.  Otherwise, returns 0.
  798.  */
  799. BOOL InitFirstInstance(hInstance)
  800. HANDLE hInstance;
  801. {
  802.     WNDCLASS wc;
  803.     
  804.     /* Define the class of window we want to register.
  805.      */
  806.     wc.lpszClassName    = szAppName;
  807.     wc.style            = CS_HREDRAW | CS_VREDRAW;
  808.     wc.hCursor          = LoadCursor(NULL, IDC_ARROW);
  809.     wc.hIcon            = LoadIcon(hInstance,"Icon");
  810.     wc.lpszMenuName     = "Menu";
  811.     wc.hbrBackground    = (HBRUSH)(COLOR_APPWORKSPACE + 1);
  812.     wc.hInstance        = hInstance;
  813.     wc.lpfnWndProc      = WndProc;
  814.     wc.cbClsExtra       = 0;
  815.     wc.cbWndExtra       = 0;
  816.     
  817.     if(!RegisterClass(&wc))
  818.         return FALSE;
  819.     
  820.     return TRUE;
  821. }
  822.  
  823.                                                                         //
  824. /* DoMenuItemCheck - Checks and unchecks menu items.
  825.  *
  826.  * Params:  hWnd - Window handle for window associated with menu items.
  827.  *          wMenuItem - The menu ID for the menu item.
  828.  *          newState - The new checked/unchecked state of the menu item.
  829.  *
  830.  * Return:  void
  831. */
  832. void  DoMenuItemCheck(
  833.   HWND    hWnd,
  834.   WORD    wMenuItem,
  835.   BOOL    newState)
  836. {
  837.     HMENU hMenu;
  838.     
  839.     hMenu = GetMenu(hWnd);
  840.     CheckMenuItem(hMenu, wMenuItem, (newState ? MF_CHECKED: MF_UNCHECKED));
  841. }
  842.  
  843.  
  844. /* Error - Beeps and shows an error message.
  845.  *
  846.  * Params:  szMsg - Points to a NULL-terminated string containing the
  847.  *              error message.
  848.  *
  849.  * Return:  Returns the return value from the MessageBox() call.
  850.  *          Since this message box has only a single button, the
  851.  *          return value isn't too meaningful.
  852.  */
  853. int Error(szMsg)
  854. LPSTR szMsg;
  855. {
  856.     MessageBeep(0);
  857.     return MessageBox(hMainWnd, szMsg, szAppName, MB_OK);
  858. }
  859.  
  860. /******************************************************************************\
  861. *
  862. *  FUNCTION:    GetStringRes (int id INPUT ONLY)
  863. *
  864. *  COMMENTS:    Load the resource string with the ID given, and return a
  865. *        pointer to it.    Notice that the buffer is common memory so
  866. *        the string must be used before this call is made a second time.
  867. *
  868. \******************************************************************************/
  869.  
  870. LPTSTR     GetStringRes (int id)
  871. {
  872.   static TCHAR buffer[MAX_PATH];
  873.  
  874.   buffer[0]=0;
  875.   LoadString (GetModuleHandle (NULL), id, buffer, MAX_PATH);
  876.   return buffer;
  877. }
  878.