/*---------------------------------------------------------
   KBMIDI.C -- Keyboard MIDI Player for Multimedia Windows
               (c) Charles Petzold, 1992
  ---------------------------------------------------------*/


#include <windows.h>
extern "C" {
#include <mmsystem.h>
}
#include <string.h>
typedef unsigned int UINT ;

#define min(a,b) (((a) < (b)) ? (a) : (b))
#define max(a,b) (((a) > (b)) ? (a) : (b))

          // Defines for Menu IDs
          // --------------------

#define IDM_OPEN    0x100
#define IDM_CLOSE   0x101
#define IDM_DEVICE  0x200
#define IDM_CHANNEL 0x300
#define IDM_VOICE   0x400

          // Forward declaration of WndProc
          // ------------------------------

long FAR PASCAL _export WndProc (HWND, UINT, UINT, LONG);

          // Global variables
          // ----------------

char     szAppName [] = "KBMidi" ;
HMIDIOUT hMidiOut ;
int      iDevice = MIDIMAPPER, iChannel = 0, iVoice = 0, iVelocity = 64 ;
int      cxCaps, cyChar, xOffset, yOffset ;

          // Structures for family and instrument names
          // ------------------------------------------

typedef struct
     {
     char * szInst ;
     int    iVoice ;
     }
     INSTRUMENT ;

typedef struct
     {
     char       * szFam ;
     INSTRUMENT   inst [8] ;
     }
     FAMILY ;

FAMILY fam [16] = { "Piano",

                    "Acoustic Grand Piano",        0,
                    "Bright Acoustic Piano",       1,
                    "Electric Grand Piano",        2,
                    "Honky-tonk Piano",            3,
                    "Rhodes Piano",                4,
                    "Chorused Piano",              5,
                    "Harpsichord",                 6,
                    "Clavinet",                    7,

                    "Chromatic Percussion",

                    "Celesta",                     8,
                    "Glockenspiel",                9,
                    "Music box",                   10,
                    "Vibraphone",                  11,
                    "Marimba",                     12,
                    "Xylophone",                   13,
                    "Tubular Bells",               14,
                    "Dulcimer",                    15,

                    "Organ",

                    "Hammond Organ",               16,
                    "Percussive Organ",            17,
                    "Rock Organ",                  18,
                    "Church Organ",                19,
                    "Reed Organ",                  20,
                    "Accordian",                   21,
                    "Harmonica",                   22,
                    "Tango Accordian",             23,

                    "Guitar",

                    "Acoustic Guitar (nylon)",     24,
                    "Acoustic Guitar (steel)",     25,
                    "Electric Guitar (jazz)",      26,
                    "Electric Guitar (clean)",     27,
                    "Electric Guitar (muted)",     28,
                    "Overdriven Guitar",           29,
                    "Distortion Guitar",           30,
                    "Guitar Harmonics",            31,

                    "Bass",

                    "Acoustic Bass",               32,
                    "Electric Bass (finger)",      33,
                    "Electric Bass (pick)",        34,
                    "Fretless Bass",               35,
                    "Slap Bass 1",                 36,
                    "Slap Bass 2",                 37,
                    "Synth Bass 1",                38,
                    "Synth Bass 2",                39,

                    "Strings",

                    "Violin",                      40,
                    "Viola",                       41,
                    "Cello",                       42,
                    "Contrabass",                  43,
                    "Tremolo Strings",             44,
                    "Pizzicato Strings",           45,
                    "Orchestral Harp",             46,
                    "Timpani",                     47,

                    "Ensemble",

                    "String Ensemble 1",           48,
                    "String Ensemble 2",           49,
                    "Synth Strings 1",             50,
                    "Synth Strings 2",             51,
                    "Choir Aahs",                  52,
                    "Voice Oohs",                  53,
                    "Synth Voice",                 54,
                    "Orchestra Hit",               55,

                    "Brass",

                    "Trumpet",                     56,
                    "Trombone",                    57,
                    "Tuba",                        58,
                    "Muted Trumpet",               59,
                    "French Horn",                 60,
                    "Brass Section",               61,
                    "Synth Brass 1",               62,
                    "Synth Brass 2",               63,

                    "Reed",

                    "Soprano Sax",                 64,
                    "Alto Sax",                    65,
                    "Tenor Sax",                   66,
                    "Baritone Sax",                67,
                    "Oboe",                        68,
                    "English Horn",                69,
                    "Bassoon",                     70,
                    "Clarinet",                    71,

                    "Pipe",

                    "Piccolo",                     72,
                    "Flute",                       73,
                    "Recorder",                    74,
                    "Pan Flute",                   75,
                    "Bottle Blow",                 76,
                    "Shakuhachi",                  77,
                    "Whistle",                     78,
                    "Ocarina",                     79,

                    "Synth Lead",

                    "Lead 1 (square)",             80,
                    "Lead 2 (sawtooth)",           81,
                    "Lead 3 (caliope lead)",       82,
                    "Lead 4 (chiff lead)",         83,
                    "Lead 5 (charang)",            84,
                    "Lead 6 (voice)",              85,
                    "Lead 7 (fifths)",             86,
                    "Lead 8 (brass + lead)",       87,

                    "Synth Pad",

                    "Pad 1 (new age)",             88,
                    "Pad 2 (warm)",                89,
                    "Pad 3 (polysynth)",           90,
                    "Pad 4 (choir)",               91,
                    "Pad 5 (bowed)",               92,
                    "Pad 6 (metallic)",            93,
                    "Pad 7 (halo)",                94,
                    "Pad 8 (sweep)",               95,

                    "Synth Effects",

                    "FX 1 (rain)",                 96,
                    "FX 2 (soundtrack)",           97,
                    "FX 3 (crystal)",              98,
                    "FX 4 (atmosphere)",           99,
                    "FX 5 (brightness)",           100,
                    "FX 6 (goblins)",              101,
                    "FX 7 (echoes)",               102,
                    "FX 8 (sci-fi)",               103,

                    "Ethnic",

                    "Sitar",                       104,
                    "Banjo",                       105,
                    "Shamisen",                    106,
                    "Koto",                        107,
                    "Kalimba",                     108,
                    "Bagpipe",                     109,
                    "Fiddle",                      110,
                    "Shanai",                      111,

                    "Percussive",

                    "Tinkle Bell",                 112,
                    "Agogo",                       113,
                    "Steel Drums",                 114,
                    "Woodblock",                   115,
                    "Taiko Drum",                  116,
                    "Melodic Tom",                 117,
                    "Synth Drum",                  118,
                    "Reverse Cymbal",              119,

                    "Sound Effects",

                    "Guitar Fret Noise",           120,
                    "Breath Noise",                121,
                    "Seashore",                    122,
                    "Bird Tweet",                  123,
                    "Telephone Ring",              124,
                    "Helicopter",                  125,
                    "Applause",                    126,
                    "Gunshot",                     127 } ;

          // Data for translating scan codes to octaves and notes
          // ----------------------------------------------------

#define NUMSCANS    (sizeof key / sizeof key[0])

struct
     {
     int    iOctave ;
     int    iNote ;
     int    yPos ;
     int    xPos ;
     char * szKey ;
     }
     key [] =
     {
                              // Scan  Char  Oct  Note
                              // ----  ----  ---  ----
     -1, -1, -1, -1, NULL,    //   0   None
     -1, -1, -1, -1, NULL,    //   1   Esc
     -1, -1,  0,  0, "",      //   2     1
      5,  1,  0,  2, "C#",    //   3     2    5    C#
      5,  3,  0,  4, "D#",    //   4     3    5    D#
     -1, -1,  0,  6, "",      //   5     4
      5,  6,  0,  8, "F#",    //   6     5    5    F#
      5,  8,  0, 10, "G#",    //   7     6    5    G#
      5, 10,  0, 12, "A#",    //   8     7    5    A#
     -1, -1,  0, 14, "",      //   9     8
      6,  1,  0, 16, "C#",    //  10     9    6    C#
      6,  3,  0, 18, "D#",    //  11     0    6    D#
     -1, -1,  0, 20, "",      //  12     -
      6,  6,  0, 22, "F#",    //  13     =    6    F#
     -1, -1, -1, -1, NULL,    //  14    Back

     -1, -1, -1, -1, NULL,    //  15    Tab
      5,  0,  1,  1, "C",     //  16     q    5    C
      5,  2,  1,  3, "D",     //  17     w    5    D
      5,  4,  1,  5, "E",     //  18     e    5    E
      5,  5,  1,  7, "F",     //  19     r    5    F
      5,  7,  1,  9, "G",     //  20     t    5    G
      5,  9,  1, 11, "A",     //  21     y    5    A
      5, 11,  1, 13, "B",     //  22     u    5    B
      6,  0,  1, 15, "C",     //  23     i    6    C
      6,  2,  1, 17, "D",     //  24     o    6    D
      6,  4,  1, 19, "E",     //  25     p    6    E
      6,  5,  1, 21, "F",     //  26     [    6    F
      6,  7,  1, 23, "G",     //  27     ]    6    G
     -1, -1, -1, -1, NULL,    //  28    Ent

     -1, -1, -1, -1, NULL,    //  29    Ctrl
      3,  8,  2,  2, "G#",    //  30     a    3    G#
      3, 10,  2,  4, "A#",    //  31     s    3    A#
     -1, -1,  2,  6, "",      //  32     d
      4,  1,  2,  8, "C#",    //  33     f    4    C#
      4,  3,  2, 10, "D#",    //  34     g    4    D#
     -1, -1,  2, 12, "",      //  35     h
      4,  6,  2, 14, "F#",    //  36     j    4    F#
      4,  8,  2, 16, "G#",    //  37     k    4    G#
      4, 10,  2, 18, "A#",    //  38     l    4    A#
     -1, -1,  2, 20, "",      //  39     ;
      5,  1,  2, 22, "C#",    //  40     '    5    C#
     -1, -1, -1, -1, NULL,    //  41     `

     -1, -1, -1, -1, NULL,    //  42    Shift
     -1, -1, -1, -1, NULL,    //  43     \           (not line continuation)
      3,  9,  3,  3, "A",     //  44     z    3    A
      3, 11,  3,  5, "B",     //  45     x    3    B
      4,  0,  3,  7, "C",     //  46     c    4    C
      4,  2,  3,  9, "D",     //  47     v    4    D
      4,  4,  3, 11, "E",     //  48     b    4    E
      4,  5,  3, 13, "F",     //  49     n    4    F
      4,  7,  3, 15, "G",     //  50     m    4    G
      4,  9,  3, 17, "A",     //  51     ,    4    A
      4, 11,  3, 19, "B",     //  52     .    4    B
      5,  0,  3, 21, "C"      //  53     /    5    C
     } ;

          // Standard WinMain
          // ----------------

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
		    LPSTR lpszCmdLine, int nCmdShow)
     {
     MSG      msg;
     HWND     hwnd ;
     WNDCLASS wndclass ;

     if (!hPrevInstance) 
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "Keyboard MIDI Player",
                          WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
			  CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd); 

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     return msg.wParam ;
     }

          // Create the program's menu (called from WndProc, WM_CREATE)
          // ----------------------------------------------------------

HMENU CreateTheMenu (int iNumDevs)
     {
     char        szBuffer [32] ;
     HMENU       hMenu, hMenuPopup, hMenuSubPopup ;
     int         i, iFam, iIns ;
     MIDIOUTCAPS moc ;

     hMenu = CreateMenu () ;

               // Create "On/Off" popup menu

     hMenuPopup = CreateMenu () ;

     AppendMenu (hMenuPopup, MF_STRING             , IDM_OPEN,  "&Open") ;
     AppendMenu (hMenuPopup, MF_STRING | MF_CHECKED, IDM_CLOSE, "&Closed") ;

     AppendMenu (hMenu, MF_STRING | MF_POPUP, hMenuPopup, "&Status") ;

               // Create "Device" popup menu

     hMenuPopup = CreateMenu () ;

                         // Put MIDI Mapper on menu if it's installed

     if (!midiOutGetDevCaps (MIDIMAPPER, &moc, sizeof (moc)))
          AppendMenu (hMenuPopup, MF_STRING, IDM_DEVICE + MIDIMAPPER,
                      moc.szPname) ;
     else
          iDevice = 0 ;

                         // Add the rest of the MIDI devices

     for (i = 0 ; i < iNumDevs ; i++)
          {
          midiOutGetDevCaps (i, &moc, sizeof (moc)) ;
          AppendMenu (hMenuPopup, MF_STRING, IDM_DEVICE + i, moc.szPname) ;
          }

     CheckMenuItem (hMenuPopup, 0, MF_BYPOSITION | MF_CHECKED) ;
     AppendMenu (hMenu, MF_STRING | MF_POPUP, hMenuPopup, "&Device") ;

               // Create "Channel" popup menu

     hMenuPopup = CreateMenu () ;

     for (i = 0 ; i < 16 ; i++)
          {
          wsprintf (szBuffer, "%d", i + 1) ;
          AppendMenu (hMenuPopup,
                      MF_STRING | (i ? MF_UNCHECKED : MF_CHECKED),
                      IDM_CHANNEL + i, szBuffer) ;
          }

     AppendMenu (hMenu, MF_STRING | MF_POPUP, hMenuPopup, "&Channel") ;

               // Create "Voice" popup menu

     hMenuPopup = CreateMenu () ;

     for (iFam = 0 ; iFam < 16 ; iFam++)
          {
          hMenuSubPopup = CreateMenu () ;

          for (iIns = 0 ; iIns < 8 ; iIns++)
               {
               wsprintf (szBuffer, "&%d.\t%s", iIns + 1,
                         (LPSTR) fam[iFam].inst[iIns].szInst) ;
               AppendMenu (hMenuSubPopup,
                           MF_STRING | (fam[iFam].inst[iIns].iVoice ?
                                        MF_UNCHECKED : MF_CHECKED),
                           fam[iFam].inst[iIns].iVoice + IDM_VOICE,
                           szBuffer) ;
               }

          wsprintf (szBuffer, "&%c.\t%s", 'A' + iFam,
                    (LPSTR) fam[iFam].szFam) ;
          AppendMenu (hMenuPopup, MF_STRING | MF_POPUP, hMenuSubPopup,
                      szBuffer) ;
          }

     AppendMenu (hMenu, MF_STRING | MF_POPUP, hMenuPopup, "&Voice") ;

     return hMenu ;
     }

          // Routines for simplifying MIDI output
          // ------------------------------------

DWORD MidiOutMessage (HMIDIOUT hMidi, int iStatus, int iChannel,
                                      int iData1,  int iData2)
     {
     DWORD dwMessage ;

     dwMessage = iStatus | iChannel | (iData1 << 8) | ((long) iData2 << 16) ;

     return midiOutShortMsg (hMidi, dwMessage) ;
     }

DWORD MidiNoteOff (HMIDIOUT hMidi, int iChannel, int iOct, int iNote, int iVel)
     {
     return MidiOutMessage (hMidi, 0x080, iChannel, 12 * iOct + iNote, iVel) ;
     }

DWORD MidiNoteOn (HMIDIOUT hMidi, int iChannel, int iOct, int iNote, int iVel)
     {
     return MidiOutMessage (hMidi, 0x090, iChannel, 12 * iOct + iNote, iVel) ;
     }

DWORD MidiSetPatch (HMIDIOUT hMidi, int iChannel, int iVoice)
     {
     return MidiOutMessage (hMidi, 0x0C0, iChannel, iVoice, 0) ;
     }

DWORD MidiPitchBend (HMIDIOUT hMidi, int iChannel, int iBend)
     {
     return MidiOutMessage (hMidi, 0x0E0, iChannel, iBend & 0x7F, iBend >> 7) ;
     }

          // Draw a single key on window
          // ---------------------------

VOID DrawKey (HDC hdc, int iScanCode, BOOL fInvert)
     {
     RECT rc ;

     rc.left   = 3 * cxCaps * key[iScanCode].xPos / 2 + xOffset ;
     rc.top    = 3 * cyChar * key[iScanCode].yPos / 2 + yOffset ;
     rc.right  = rc.left + 3 * cxCaps ;
     rc.bottom = rc.top  + 3 * cyChar / 2 ;

     SetTextColor (hdc, fInvert ? 0x00FFFFFFul : 0x00000000ul) ;
     SetBkColor   (hdc, fInvert ? 0x00000000ul : 0x00FFFFFFul) ;

     FillRect (hdc, &rc, GetStockObject (fInvert ? BLACK_BRUSH : WHITE_BRUSH)) ;

     DrawText (hdc, key[iScanCode].szKey, -1, &rc,
               DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;

     FrameRect (hdc, &rc, GetStockObject (BLACK_BRUSH)) ;
     }

          // Process a Key Up or Key Down message
          // ------------------------------------

VOID ProcessKey (HDC hdc, WORD message, LONG lParam)
     {
     int iScanCode, iOctave, iNote ;

     iScanCode = 0x0FF & HIWORD (lParam) ;

     if (iScanCode >= NUMSCANS)                       // No scan codes over 53
          return ;

     if ((iOctave = key[iScanCode].iOctave) == -1)    // Non-music key
          return ;

     if (GetKeyState (VK_SHIFT) < 0)
          iOctave += 0x20000000 & lParam ? 2 : 1 ;

     if (GetKeyState (VK_CONTROL) < 0)
          iOctave -= 0x20000000 & lParam ? 2 : 1 ;

     iNote = key[iScanCode].iNote ;

     if (message == WM_KEYUP)                           // For key up
          {
          MidiNoteOff (hMidiOut, iChannel, iOctave, iNote, 0) ;   // Note off
          DrawKey (hdc, iScanCode, FALSE) ;
          return ;
          }

     if (0x40000000 & lParam)                          // ignore typematics
          return ;

     MidiNoteOn (hMidiOut, iChannel, iOctave, iNote, iVelocity) ; // Note on
     DrawKey (hdc, iScanCode, TRUE) ;                 // Draw the inverted key
     }

          // Window Procedure
          // ----------------

long FAR PASCAL _export WndProc (HWND hwnd, UINT message, UINT wParam,
                                                          LONG lParam)
     {
     static BOOL bOpened = FALSE ;
     char        szBuffer [16] ;
     DWORD       dwExtent ;
     HDC         hdc ;
     HMENU       hMenu ;
     MIDIOUTCAPS moc ;
     PAINTSTRUCT ps ;
     short       i, iNumDevs, iPitchBend, cxClient, cyClient ;

     switch (message)
          {
          case WM_CREATE:
                         // Get size of capital letters in system font

               hdc = GetDC (hwnd) ;

               dwExtent = GetTextExtent (hdc, "M", 1) ;
               cxCaps   = LOWORD (dwExtent) ;
               cyChar   = HIWORD (dwExtent) ;

               ReleaseDC (hwnd, hdc) ;

                         // Initialize "Volume" scroll bar

               SetScrollRange (hwnd, SB_HORZ, 1, 127, FALSE) ;
               SetScrollPos   (hwnd, SB_HORZ, iVelocity, TRUE) ;

                         // Initialize "Pitch Bend" scroll bar

               SetScrollRange (hwnd, SB_VERT, 0, 16383, FALSE) ;
               SetScrollPos   (hwnd, SB_VERT, 8192, TRUE) ;

                         // Get number of MIDI output devices and set up menu

               if (0 == (iNumDevs = midiOutGetNumDevs ()))
                    {
                    MessageBeep (MB_ICONSTOP) ;
                    MessageBox (hwnd, "No MIDI output devices",
                                szAppName, MB_OK | MB_ICONSTOP) ;
                    DestroyWindow (hwnd) ;
                    }
               else
                    {
                    SetMenu (hwnd, CreateTheMenu (iNumDevs)) ;
                    }

               return 0 ;

          case WM_SIZE:
               cxClient = LOWORD (lParam) ;
               cyClient = HIWORD (lParam) ;

               xOffset = (cxClient - 25 * 3 * cxCaps / 2) / 2 ;
               yOffset = (cyClient - 11 * cyChar) / 2 + 5 * cyChar ;
               return 0 ;

          case WM_COMMAND:
               hMenu = GetMenu (hwnd) ;

                         // "Open" menu command

               if (wParam == IDM_OPEN && !bOpened)
                    {
                    if (midiOutOpen (&hMidiOut, iDevice, NULL, 0L, 0L))
                         {
                         MessageBeep (MB_ICONEXCLAMATION) ;
                         MessageBox (hwnd, "Cannot open MIDI device",
                                     szAppName, MB_OK | MB_ICONEXCLAMATION) ;
                         }
                    else
                         {
                         CheckMenuItem (hMenu, IDM_OPEN,  MF_CHECKED) ;
                         CheckMenuItem (hMenu, IDM_CLOSE, MF_UNCHECKED) ;

                         MidiSetPatch (hMidiOut, iChannel, iVoice) ;
                         bOpened = TRUE ;
                         }
                    }

                         // "Close" menu command

               else if (wParam == IDM_CLOSE && bOpened)
                    {
                    CheckMenuItem (hMenu, IDM_OPEN,  MF_UNCHECKED) ;
                    CheckMenuItem (hMenu, IDM_CLOSE, MF_CHECKED) ;

                              // Turn all keys off and close device

                    for (i = 0 ; i < 16 ; i++)
                         MidiOutMessage (hMidiOut, 0xB0, i, 123, 0) ;

                    midiOutClose (hMidiOut) ;
                    bOpened = FALSE ;
                    }

                         // Change MIDI "Device" menu command

               else if (wParam >= IDM_DEVICE - 1 && wParam < IDM_CHANNEL)
                    {
                    CheckMenuItem (hMenu, IDM_DEVICE + iDevice, MF_UNCHECKED) ;
                    iDevice = wParam - IDM_DEVICE ;
                    CheckMenuItem (hMenu, IDM_DEVICE + iDevice, MF_CHECKED) ;

                              // Close and reopen MIDI device

                    if (bOpened)
                         {
                         SendMessage (hwnd, WM_COMMAND, IDM_CLOSE, 0L) ;
                         SendMessage (hwnd, WM_COMMAND, IDM_OPEN,  0L) ;
                         }
                    }

                         // Change MIDI "Channel" menu command

               else if (wParam >= IDM_CHANNEL && wParam < IDM_VOICE)
                    {
                    CheckMenuItem (hMenu, IDM_CHANNEL + iChannel, MF_UNCHECKED);
                    iChannel = wParam - IDM_CHANNEL ;
                    CheckMenuItem (hMenu, IDM_CHANNEL + iChannel, MF_CHECKED) ;

                    if (bOpened)
                         MidiSetPatch (hMidiOut, iChannel, iVoice) ;
                    }

                         // Change MIDI "Voice" menu command

               else if (wParam >= IDM_VOICE)
                    {
                    CheckMenuItem (hMenu, IDM_VOICE + iVoice, MF_UNCHECKED) ;
                    iVoice = wParam - IDM_VOICE ;
                    CheckMenuItem (hMenu, IDM_VOICE + iVoice, MF_CHECKED) ;

                    if (bOpened)
                         MidiSetPatch (hMidiOut, iChannel, iVoice) ;
                    }

               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;

                    // Process a Key Up or Key Down message

          case WM_KEYUP:
          case WM_KEYDOWN:
               hdc = GetDC (hwnd) ;

               if (bOpened)
                    ProcessKey (hdc, message, lParam) ;

               ReleaseDC (hwnd, hdc) ;
               return 0 ;

                    // For Escape, turn off all notes and repaint

          case WM_CHAR:
               if (bOpened && wParam == 27)
                    {
                    for (i = 0 ; i < 16 ; i++)
                         MidiOutMessage (hMidiOut, 0xB0, i, 123, 0) ;

                    InvalidateRect (hwnd, NULL, TRUE) ;
                    }
               return 0 ;

                    // Horizontal scroll: Velocity

          case WM_HSCROLL:
               switch (wParam)
                    {
                    case SB_LINEUP:         iVelocity -= 1 ;  break ;
                    case SB_LINEDOWN:       iVelocity += 1 ;  break ;
                    case SB_PAGEUP:         iVelocity -= 8 ;  break ;
                    case SB_PAGEDOWN:       iVelocity += 8 ;  break ;
                    case SB_THUMBPOSITION:
                         iVelocity = LOWORD (lParam) ;
                         break ;

                    default:
                         return 0 ;
                    }

               iVelocity = max (1, min (iVelocity, 127)) ;
               SetScrollPos (hwnd, SB_HORZ, iVelocity, TRUE) ;
               return 0 ;

                    // Vertical scroll:  Pitch Bend

          case WM_VSCROLL:
               switch (wParam)
                    {
                    case SB_THUMBTRACK:
                         iPitchBend = 16383 - LOWORD (lParam) ;
                         break ;

                    case SB_THUMBPOSITION:
                         iPitchBend = 8191 ;
                         break ;

                    default:
                         return 0 ;
                    }

               iPitchBend = max (0, min (iPitchBend, 16383)) ;
               SetScrollPos (hwnd, SB_VERT, 16383 - iPitchBend, TRUE) ;

               if (bOpened)
                    MidiPitchBend (hMidiOut, iChannel, iPitchBend) ;
               return 0 ;

          case WM_PAINT:
               hdc = BeginPaint (hwnd, &ps) ;

               for (i = 0 ; i < NUMSCANS ; i++)
                    if (key[i].xPos != -1)
                         DrawKey (hdc, i, FALSE) ;

               midiOutGetDevCaps (iDevice, &moc, sizeof (MIDIOUTCAPS)) ;
               wsprintf (szBuffer, "Channel %d", iChannel + 1) ;

               TextOut (hdc, cxCaps, 1 * cyChar, bOpened ? "Open" : "Closed",
                                                 bOpened ? 4 : 6) ;
               TextOut (hdc, cxCaps, 2 * cyChar, moc.szPname,
                                                 strlen (moc.szPname)) ;
               TextOut (hdc, cxCaps, 3 * cyChar, (LPSTR) szBuffer,
                                                 strlen (szBuffer)) ;
               TextOut (hdc, cxCaps, 4 * cyChar,
                        (LPSTR) fam[iVoice / 8].inst[iVoice % 8].szInst,
                        strlen (fam[iVoice / 8].inst[iVoice % 8].szInst)) ;

               EndPaint (hwnd, &ps) ;
               return 0 ;

          case WM_DESTROY :
               SendMessage (hwnd, WM_COMMAND, IDM_CLOSE, 0L) ;

               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }
