/*---------------------------------------
   KBMIDI.C -- Keyboard MIDI Player 
               (c) Charles Petzold, 1998
  ---------------------------------------*/
' Ported to OxygenBasic

'#compact

$ filename "KBMidi.exe" 

'uses rtl32
'uses rtl64

uses corewin 'minwin
'uses console
uses MinMidi

typedef MIDIOUTCAPSA MIDIOUTCAPS

% DT_CENTER=1
% DT_SINGLELINE=32
% DT_VCENTER=4

type SIZE
  long cx
  long cy
end type

' Helper functions

macro iif int(R,  A,B,C)
if A then R=B else R=C
end macro


function MAX(int num1, num2) 
  if num1 > num2 then return num1
  return num2
end function

function MIN(int num1, num2)
  if num1 < num2 then return num1
  return num2
end function

function CreateWindow(string lpClassName, lpWindowName,int dwStyle, x, y, nWidth, nHeight,sys hWndParent, hMenu, hInstance, lpParam) as sys
   return CreateWindowEx(0, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)
end function

! FrameRect lib "user32.dll" (sys hDC, RECT *lprc, sys hbr) as int
! GetKeyState lib "user32.dll" (int nVirtKey) as short
! MessageBeep lib "user32.dll" (uint uType) as bool
! SetScrollRange lib "user32.dll" (sys hWnd, int nBar, int nMinPos, int nMaxPos, bool bRedraw) as bool
! SetScrollPos lib "user32.dll" (sys hWnd, int nBar, int nPos,	bool bRedraw) as int
! GetTextExtentPoint32 lib "gdi32.dll" alias"GetTextExtentPoint32A" (sys hdc, char* lpString, int cbString,  SIZE *lpSize) as bool
! midiOutGetDevCaps lib "winmm.dll" alias "midiOutGetDevCapsA" (sys uDeviceID, MIDIOUTCAPSA *pmoc, sys cbmoc) as sys

int s_error=error()
if s_error then
   print "Runtime error: " s_error
   end
end if

indexbase 0

'===========
  'MAIN CODE
'===========
  
dim cmdline as asciiz ptr, hInst as sys
&cmdline=GetCommandLine
hInst=GetModuleHandle(0)


// Defines for Menu IDs
// --------------------

#define IDM_OPEN    0x100
#define IDM_CLOSE   0x101
#define IDM_DEVICE  0x200
#define IDM_CHANNEL 0x300
#define IDM_VOICE   0x400

% MIDIMAPPER = 0xFFFFFFFF

zstring  szAppName = "KBMidi"
sys hMidiOut 
int iDevice = MIDIMAPPER
int iChannel = 0
int iVoice = 0
int iVelocity = 64

int cxCaps, cyChar, xOffset, yOffset

     // Structures and data for showing families and instruments on menu
     // ----------------------------------------------------------------
type INSTRUMENT
   string  szInst
   int     iVoice
end type

type FAMILY
   string      szFam
   INSTRUMENT  inst [8]
end type

dim as FAMILY fam[16]

fam[0].szFam   =  "Piano"
fam[0].inst[0] = {"Acoustic Grand Piano",    0}
fam[0].inst[1] = {"Bright Acoustic Piano",   1}
fam[0].inst[2] = {"Electric Grand Piano",    2}
fam[0].inst[3] = {"Honky-tonk Piano",        3}
fam[0].inst[4] = {"Rhodes Piano",            4}
fam[0].inst[5] = {"Chorused Piano",          5}
fam[0].inst[6] = {"Harpsichord",             6}
fam[0].inst[7] = {"Clavinet",                7}
fam[1].szFam   =  "Chromatic Percussion"
fam[1].inst[0] = {"Celesta",                 8}
fam[1].inst[1] = {"Glockenspiel",            9}
fam[1].inst[2] = {"Music Box",              10}
fam[1].inst[3] = {"Vibraphone",             11}
fam[1].inst[4] = {"Marimba",                12}
fam[1].inst[5] = {"Xylophone",              13}
fam[1].inst[6] = {"Tubular Bells",          14}
fam[1].inst[7] = {"Dulcimer",               15}
fam[2].szFam   =  "Organ"
fam[2].inst[0] = {"Hammond Organ",          16}
fam[2].inst[1] = {"Percussive Organ",       17}
fam[2].inst[2] = {"Rock Organ",             18}
fam[2].inst[3] = {"Church Organ",           19}
fam[2].inst[4] = {"Reed Organ",             20}
fam[2].inst[5] = {"Accordian",              21}
fam[2].inst[6] = {"Harmonica",              22}
fam[2].inst[7] = {"Tango Accordian",        23}
fam[3].szFam   =  "Guitar"
fam[3].inst[0] = {"Acoustic Guitar (nylon)",24}
fam[3].inst[1] = {"Acoustic Guitar (steel)",25}
fam[3].inst[2] = {"Electric Guitar (jazz)", 26}
fam[3].inst[3] = {"Electric Guitar (clean)",27}
fam[3].inst[4] = {"Electric Guitar (muted)",28}
fam[3].inst[5] = {"Overdriven Guitar",      29}
fam[3].inst[6] = {"Distortion Guitar",      30}
fam[3].inst[7] = {"Guitar Harmonics",       31}
fam[4].szFam   =  "Bass"
fam[4].inst[0] = {"Acoustic Bass",          32}
fam[4].inst[1] = {"Electric Bass (finger)", 33}
fam[4].inst[2] = {"Electric Bass (pick)",   34}
fam[4].inst[3] = {"Fretless Bass",          35}
fam[4].inst[4] = {"Slap Bass 1",            36}
fam[4].inst[5] = {"Slap Bass 2",            37}
fam[4].inst[6] = {"Synth Bass 1",           38}
fam[4].inst[7] = {"Synth Bass 2",           39}
fam[5].szFam   =  "Strings"
fam[5].inst[0] = {"Violin",                 40}
fam[5].inst[1] = {"Viola",                  41}
fam[5].inst[2] = {"Cello",                  42}
fam[5].inst[3] = {"Contrabass",             43}
fam[5].inst[4] = {"Tremolo Strings",        44}
fam[5].inst[5] = {"Pizzicato Strings",      45}
fam[5].inst[6] = {"Orchestral Harp",        46}
fam[5].inst[7] = {"Timpani",                47}
fam[6].szFam   =  "Ensemble"
fam[6].inst[0] = {"String Ensemble 1",      48}
fam[6].inst[1] = {"String Ensemble 2",      49}
fam[6].inst[2] = {"Synth Strings 1",        50}
fam[6].inst[3] = {"Synth Strings 2",        51}
fam[6].inst[4] = {"Choir Aahs",             52}
fam[6].inst[5] = {"Voice Oohs",             53}
fam[6].inst[6] = {"Synth Voice",            54}
fam[6].inst[7] = {"Orchestra Hit",          55}
fam[7].szFam   =  "Brass"
fam[7].inst[0] = {"Trumpet",                56}
fam[7].inst[1] = {"Trombone",               57}
fam[7].inst[2] = {"Tuba",                   58}
fam[7].inst[3] = {"Muted Trumpet",          59}
fam[7].inst[4] = {"French Horn",            60}
fam[7].inst[5] = {"Brass Section",          61}
fam[7].inst[6] = {"Synth Brass 1",          62}
fam[7].inst[7] = {"Synth Brass 2",          63}
fam[8].szFam   =  "Reed"
fam[8].inst[0] = {"Soprano Sax",            64}
fam[8].inst[1] = {"Alto Sax",               65}
fam[8].inst[2] = {"Tenor Sax",              66}
fam[8].inst[3] = {"Baritone Sax",           67}
fam[8].inst[4] = {"Oboe",                   68}
fam[8].inst[5] = {"English Horn",           69}
fam[8].inst[6] = {"Bassoon",                70}
fam[8].inst[7] = {"Clarinet",               71}
fam[9].szFam   =  "Pipe"
fam[9].inst[0] = {"Piccolo",                72}
fam[9].inst[1] = {"Flute",                  73}
fam[9].inst[2] = {"Recorder",               74}
fam[9].inst[3] = {"Pan Flute",              75}
fam[9].inst[4] = {"Bottle Blow",            76}
fam[9].inst[5] = {"Shakuhachi",             77}
fam[9].inst[6] = {"Whistle",                78}
fam[9].inst[7] = {"Ocarina",                79}
fam[10].szFam   =  "Synth Lead"
fam[10].inst[0] = {"Lead 1 (square)",       80}
fam[10].inst[1] = {"Lead 2 (sawtooth)",     81}
fam[10].inst[2] = {"Lead 3 (caliope lead)", 82}
fam[10].inst[3] = {"Lead 4 (chiff lead)",   83}
fam[10].inst[4] = {"Lead 5 (charang)",      84}
fam[10].inst[5] = {"Lead 6 (voice)",        85}
fam[10].inst[6] = {"Lead 7 (fifths)",       86}
fam[10].inst[7] = {"Lead 8 (brass + lead)", 87}
fam[11].szFam   =  "Synth Pad"
fam[11].inst[0] = {"Pad 1 (new age)",       88}
fam[11].inst[1] = {"Pad 2 (warm)",          89}
fam[11].inst[2] = {"Pad 3 (polysynth)",     90}
fam[11].inst[3] = {"Pad 4 (choir)",         91}
fam[11].inst[4] = {"Pad 5 (bowed)",         92}
fam[11].inst[5] = {"Pad 6 (metallic)",      93}
fam[11].inst[6] = {"Pad 7 (halo)",          94}
fam[11].inst[7] = {"Pad 8 (sweep)",         95}
fam[12].szFam   =  "Synth Effects"
fam[12].inst[0] = {"FX 1 (rain)",           96}
fam[12].inst[1] = {"FX 2 (soundtrack)",     97}
fam[12].inst[2] = {"FX 3 (crystal)",        98}
fam[12].inst[3] = {"FX 4 (atmosphere)",     99}
fam[12].inst[4] = {"FX 5 (brightness)",    100}
fam[12].inst[5] = {"FX 6 (goblins)",       101}
fam[12].inst[6] = {"FX 7 (echoes)",        102}
fam[12].inst[7] = {"FX 8 (sci-fi)",        103}
fam[13].szFam   =  "Ethnic"
fam[13].inst[0] = {"Sitar",                104}
fam[13].inst[1] = {"Banjo",                105}
fam[13].inst[2] = {"Shamisen",             106}
fam[13].inst[3] = {"Koto",                 107}
fam[13].inst[4] = {"Kalimba",              108}
fam[13].inst[5] = {"Bagpipe",              109}
fam[13].inst[6] = {"Fiddle",               110}
fam[13].inst[7] = {"Shanai",               111}
fam[14].szFam   =  "Percussive"
fam[14].inst[0] = {"Tinkle Bell",          112}
fam[14].inst[1] = {"Agogo",                113}
fam[14].inst[2] = {"Steel Drums",          114}
fam[14].inst[3] = {"Woodblock",            115}
fam[14].inst[4] = {"Taiko Drum",           116}
fam[14].inst[5] = {"Melodic Tom",          117}
fam[14].inst[6] = {"Synth Drum",           118}
fam[14].inst[7] = {"Reverse Cymbal",       119}
fam[15].szFam   =  "Sound Effects"
fam[15].inst[0] = {"Guitar Fret Noise",    120}
fam[15].inst[1] = {"Breath Noise",         121}
fam[15].inst[2] = {"Seashore",             122}
fam[15].inst[3] = {"Bird Tweet",           123}
fam[15].inst[4] = {"Telephone Ring",       124}
fam[15].inst[5] = {"Helicopter",           125}
fam[15].inst[6] = {"Applause",             126}
fam[15].inst[7] = {"Gunshot",              127} 

     // Data for translating scan codes to octaves and notes
     // ----------------------------------------------------

type aKey
   int     iOctave
   int     iNote
   int     yPos
   int     xPos
   string  szKey
end type

akey key[] = {
                                ' Scan  Char  Oct  Note
                                ' ----  ----  ---  ----
     {-1, -1, -1, -1, ""},      '   0   None
     {-1, -1, -1, -1, ""},      '   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, ""},      '  14    Back
          
     {-1, -1, -1, -1, ""},      '  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, ""},      '  28    Ent
          
     {-1, -1, -1, -1, ""},      '  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, ""},      '  41     `
          
     {-1, -1, -1, -1, ""},      '  42    Shift
     {-1, -1, -1, -1, ""},      '  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
}          

%NUMSCANS = countof(key)

function WinMain(sys hInst, sys hPrev, asciiz *cmdLine, int cmdShow)

     MSG      msg
     sys      hwnd 
     WNDCLASS wndclass 
     
     wndclass.style         = CS_HREDRAW | CS_VREDRAW 
     wndclass.lpfnWndProc   = @WndProc 
     wndclass.cbClsExtra    = 0 
     wndclass.cbWndExtra    = 0 
     wndclass.hInstance     = hInst 
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) 
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) 
     wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) 
     wndclass.lpszMenuName  = NULL 
     wndclass.lpszClassName = strptr szAppName 
     
     if not RegisterClass (&wndclass) then
          MessageBox (NULL, "RegisterClass failed",
                      szAppName, MB_ICONERROR) 
          return 0 
     end if
     
     hwnd = CreateWindow (szAppName, "Keyboard MIDI Player",
                          WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInst, NULL) 

     if not hwnd then return 0 
     
     ShowWindow (hwnd, CmdShow) 
     UpdateWindow (hwnd) 
     
     while (GetMessage (&msg, NULL, 0, 0))
          TranslateMessage (&msg) 
          DispatchMessage (&msg) 
     wend
     return msg.wParam 
end function

// Create the program's menu (called from WndProc, WM_CREATE)
// ----------------------------------------------------------

function CreateTheMenu (int iNumDevs) as sys

     string      szBuffer 
     sys         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 not midiOutGetDevCaps (MIDIMAPPER, moc, sizeof (moc)) then
          AppendMenu (hMenuPopup, MF_STRING, IDM_DEVICE + MIDIMAPPER, moc.szPname)
     else
          iDevice = 0
     end if
     
          // Add the rest of the MIDI devices
     
     for i = 0 to iNumDevs-1
          midiOutGetDevCaps (i, moc, sizeof (moc)) 
          AppendMenu (hMenuPopup, MF_STRING, IDM_DEVICE + i, moc.szPname) 
     next
     
     CheckMenuItem (hMenuPopup, 0, MF_BYPOSITION | MF_CHECKED) 
     AppendMenu (hMenu, MF_STRING | MF_POPUP,  hMenuPopup, "&Device") 
     
          // Create "Channel" popup menu
     
     hMenuPopup = CreateMenu () 
     
     for i = 0 to 15
          szBuffer=str(i + 1)
          AppendMenu (hMenuPopup, MF_STRING | iif(i, MF_UNCHECKED, MF_CHECKED), IDM_CHANNEL + i, szBuffer)
     next
     
     AppendMenu (hMenu, MF_STRING | MF_POPUP,  hMenuPopup, "&Channel") 
     
          // Create "Voice" popup menu
     
     hMenuPopup = CreateMenu () 
     
     for iFam = 0 to 15 
     
        hMenuSubPopup = CreateMenu () 
          
        family * famt = &fam[ifam]
        for iIns = 0 to 7
          szBuffer= str(iIns+1) "." tab famt.inst[iIns].szInst
        
          AppendMenu (hMenuSubPopup, MF_STRING | iif(famt.inst[iIns].iVoice, MF_UNCHECKED, MF_CHECKED),
                    famt.inst[iIns].iVoice + IDM_VOICE, szBuffer) 
        next
        szBuffer=chr(iFam+65) "."  tab fam[iFam].szFam 
        AppendMenu (hMenuPopup, MF_STRING | MF_POPUP,  hMenuSubPopup, szBuffer) 
     next
     AppendMenu (hMenu, MF_STRING | MF_POPUP,  hMenuPopup, "&Voice") 
     return hMenu
end function

// Routines for simplifying MIDI output
// ------------------------------------

function MidiOutMessage(sys hMidi, int iStatus, iChannel, iData1,  iData2) as dword
   dword dwMessage = iStatus | iChannel | (iData1 << 8) | (iData2 << 16)     
   return midiOutShortMsg(hMidi, dwMessage) 
end function

function MidiNoteOff (sys hMidi, int iChannel,iOct, iNote, iVel) as dword
   return MidiOutMessage (hMidi, 0x080, iChannel, 12 * iOct + iNote, iVel) 
end function

function MidiNoteOn (sys hMidi, int iChannel, iOct, iNote, iVel) as dword
   return MidiOutMessage (hMidi, 0x090, iChannel, 12 * iOct + iNote, iVel) 
end function

function MidiSetPatch (sys hMidi, int iChannel, iVoice) as dword
   return MidiOutMessage (hMidi, 0x0C0, iChannel, iVoice, 0) 
end function

function MidiPitchBend (sys hMidi, int iChannel, iBend) as dword
   return MidiOutMessage (hMidi, 0x0E0, iChannel, iBend & 0x7F, iBend >> 7) 
end function

// Draw a single key on window
// ---------------------------

sub DrawKey (sys 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, iif(fInvert, 0x00FFFFFF, 0x00000000)) 
     SetBkColor   (hdc, iif(fInvert, 0x00000000, 0x00FFFFFF)) 
     
     FillRect (hdc, &rc, GetStockObject (iif(fInvert, BLACK_BRUSH, WHITE_BRUSH))) 
     
     DrawText (hdc, key[iScanCode].szKey, -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER) 
     
     FrameRect (hdc, rc, GetStockObject (BLACK_BRUSH)) 
     
end sub

// Process a Key Up or Key Down message
// ------------------------------------

sub ProcessKey (sys hdc, uint message, sys lParam)

     int iScanCode, iOctave, iNote 
     
     iScanCode = 0x0FF & HIWORD (lParam) 
    
     if iScanCode >= NUMSCANS then return     // No scan codes over 53

     iOctave = key[iScanCode].iOctave         
     if iOctave  = -1 then return             // Non-music key
     
     if (GetKeyState (VK_SHIFT) < 0) then
          iOctave += 0x20000000 & iif(lParam, 2, 1) 
     end if
     
     if (GetKeyState (VK_CONTROL) < 0) then
          iOctave -= 0x20000000 & iif(lParam, 2, 1) 
     end if
     
     iNote = key[iScanCode].iNote 
     
     if message = WM_KEYUP then                                 // For key up
          MidiNoteOff (hMidiOut, iChannel, iOctave, iNote, 0)   // Note off
          DrawKey (hdc, iScanCode, FALSE) 
          return 
     end if
     
     if (0x40000000 & lParam) then return                       // ignore typematics
     
     MidiNoteOn (hMidiOut, iChannel, iOctave, iNote, iVelocity) // Note on
     DrawKey (hdc, iScanCode, TRUE)                             // Draw the inverted key
end sub

// Window Procedure
// ----------------

function WndProc (sys hWnd, uint Msg, sys wParam, sys lParam) as sys callback

     static BOOL bOpened = FALSE 
     sys         hdc 
     sys         hMenu 
     int         i, iNumDevs, iPitchBend, cxClient, cyClient 
     MIDIOUTCAPS moc 
     PAINTSTRUCT ps 
     SIZE        siz 
     string      szBuffer 
     
     switch (Msg)
     
     case WM_CREATE
               // Get size of capital letters in system font
          
          hdc = GetDC (hwnd) 
          
          GetTextExtentPoint32 (hdc, "M", 1, siz) 
          cxCaps = siz.cx 
          cyChar = siz.cy 
          
          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 not iNumDevs := midiOutGetNumDevs() then
          'if (iNumDevs := midiOutGetNumDevs() = 0) then
               MessageBeep (MB_ICONSTOP) 
               MessageBox (hwnd, "No MIDI output devices!",
                                 szAppName, MB_OK | MB_ICONSTOP) 
               return -1 
          end if
          
          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 LOWORD(wParam) = IDM_OPEN && bOpened = FALSE then
          
               if midiOutOpen (hMidiOut, iDevice, 0, 0, 0) then
                    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 
               end if
          
               // "Close" menu command
          
          elseif LOWORD (wParam) = IDM_CLOSE && bOpened then
               CheckMenuItem (hMenu, IDM_OPEN,  MF_UNCHECKED) 
               CheckMenuItem (hMenu, IDM_CLOSE, MF_CHECKED) 
               
                    // Turn all keys off and close device
               
               for i = 0 to 15
                    MidiOutMessage (hMidiOut, 0xB0, i, 123, 0)
               next
               
               midiOutClose (hMidiOut) 
               bOpened = FALSE 
          
               // Change MIDI "Device" menu command
          
          elseif LOWORD (wParam) >= IDM_DEVICE - 1 && LOWORD (wParam) <  IDM_CHANNEL then

               CheckMenuItem (hMenu, IDM_DEVICE + iDevice, MF_UNCHECKED) 
               iDevice = LOWORD (wParam) - IDM_DEVICE 
               CheckMenuItem (hMenu, IDM_DEVICE + iDevice, MF_CHECKED) 
               
                    // Close and reopen MIDI device
               
               if bOpened then
                    SendMessage (hwnd, WM_COMMAND, IDM_CLOSE, 0) 
                    SendMessage (hwnd, WM_COMMAND, IDM_OPEN,  0) 
               end if
          
               // Change MIDI "Channel" menu command
          
          elseif LOWORD (wParam) >= IDM_CHANNEL && LOWORD (wParam) <  IDM_VOICE then
               CheckMenuItem (hMenu, IDM_CHANNEL + iChannel, MF_UNCHECKED)               
               iChannel = LOWORD (wParam) - IDM_CHANNEL 
               CheckMenuItem (hMenu, IDM_CHANNEL + iChannel, MF_CHECKED) 
               
               if bOpened then MidiSetPatch (hMidiOut, iChannel, iVoice) 
         
               // Change MIDI "Voice" menu command
          
          elseif LOWORD (wParam) >= IDM_VOICE then
               CheckMenuItem (hMenu, IDM_VOICE + iVoice, MF_UNCHECKED) 
               iVoice = LOWORD (wParam) - IDM_VOICE 
               CheckMenuItem (hMenu, IDM_VOICE + iVoice, MF_CHECKED) 
               
               if bOpened then MidiSetPatch (hMidiOut, iChannel, iVoice) 

          end if
         
          InvalidateRect (hwnd, NULL, TRUE) 
          return 0 
          
          // Process a Key Up or Key Down message
          
     case WM_KEYUP, WM_KEYDOWN

          hdc = GetDC (hwnd) 
          
          if bOpened then ProcessKey (hdc, Msg, lParam) 
          
          ReleaseDC (hwnd, hdc) 
          return 0 
          
          // For Escape, turn off all notes and repaint
          
     case WM_CHAR
          if bOpened && wParam = 27 then
               for i = 0 to 15
                    MidiOutMessage (hMidiOut, 0xB0, i, 123, 0) 
               next
               InvalidateRect (hwnd, NULL, TRUE) 
          end if
          return 0 
          
          // Horizontal scroll: Velocity
          
     case WM_HSCROLL
          select case LOWORD(wParam)
          case SB_LINEUP         
            iVelocity -= 1 
          case SB_LINEDOWN        
            iVelocity += 1 
          case SB_PAGEUP          
            iVelocity -= 8 
          case SB_PAGEDOWN       
            iVelocity += 8 
          case SB_THUMBPOSITION   
            iVelocity = HIWORD (wParam) 
          case else                
            return 0 
          end select 
          int min_iVel = min (iVelocity, 127)        
          int iVelocity = max (1, min_iVel ) 
          SetScrollPos (hwnd, SB_HORZ, iVelocity, TRUE) 
          return 0 
          
          // Vertical scroll:  Pitch Bend
     
     case WM_VSCROLL
          select case LOWORD(wParam)
          case SB_THUMBTRACK     
            iPitchBend = 16383 - HIWORD (wParam) 
          case SB_THUMBPOSITION  
            int PitchBend = 8191 
          case else               
            return 0 
          end select
          int min_iPitch = min (iPitchBend, 16383)
          int iPitchBend = max (0, min_iPitch ) 
          SetScrollPos (hwnd, SB_VERT, 8191 - iPitchBend, TRUE) 
          
          if bOpened then MidiPitchBend (hMidiOut, iChannel, iPitchBend) 
          return 0 
     
     case WM_PAINT
          hdc = BeginPaint (hwnd, &ps) 
          
          for i = 0 to NUMSCANS-1
               if key[i].xPos != -1 then  DrawKey (hdc, i, FALSE) 
          next
               
          midiOutGetDevCaps (iDevice, moc, sizeof (MIDIOUTCAPS)) 
          szBuffer= "Channel " + str(iChannel + 1)
     
          TextOut (hdc, cxCaps, 1 * cyChar, 
                        iif(bOpened, "Open", "Closed"),
                        iif(bOpened, 4, 6)) 
          TextOut (hdc, cxCaps, 2 * cyChar, moc.szPname, len (moc.szPname)) 'Midi Device
          TextOut (hdc, cxCaps, 3 * cyChar, szBuffer, len (szBuffer)) 

''          TextOut (hdc, cxCaps, 4 * cyChar, fam[iVoice \ 8].inst[mod(iVoice, 8)].szInst, len (fam[iVoice \ 8].inst[mod(iVoice, 8)].szInst)) 
          int iv=iVoice\8
          int miv8=mod(iVoice, 8)
          select case miv8
          case 0 : TextOut (hdc, cxCaps, 4 * cyChar, fam[iv].inst[0].szInst, len (fam[iv].inst[0].szInst))
          case 1 : TextOut (hdc, cxCaps, 4 * cyChar, fam[iv].inst[1].szInst, len (fam[iv].inst[1].szInst))          
          case 2 : TextOut (hdc, cxCaps, 4 * cyChar, fam[iv].inst[2].szInst, len (fam[iv].inst[2].szInst))
          case 3 : TextOut (hdc, cxCaps, 4 * cyChar, fam[iv].inst[3].szInst, len (fam[iv].inst[3].szInst))
          case 4 : TextOut (hdc, cxCaps, 4 * cyChar, fam[iv].inst[4].szInst, len (fam[iv].inst[4].szInst))
          case 5 : TextOut (hdc, cxCaps, 4 * cyChar, fam[iv].inst[5].szInst, len (fam[iv].inst[5].szInst))
          case 6 : TextOut (hdc, cxCaps, 4 * cyChar, fam[iv].inst[6].szInst, len (fam[iv].inst[6].szInst))
          case 7 : TextOut (hdc, cxCaps, 4 * cyChar, fam[iv].inst[7].szInst, len (fam[iv].inst[7].szInst))          
          end select 
     
          EndPaint (hwnd, &ps) 
          return 0 
               
     case WM_DESTROY
          SendMessage (hwnd, WM_COMMAND, IDM_CLOSE, 0) 
          PostQuitMessage (0) 
          return 0 
     end switch
     
     return DefWindowProc (hwnd, Msg, wParam, lParam) 
end function 

WinMain(hInst, 0, cmdline, SW_NORMAL )
