/**************************************************************************** * * keyb.c * * Copyright (c) 1991 Microsoft Corporation. All Rights Reserved. * ***************************************************************************/ #include #include #include "wincom.h" #include "sbtest.h" // make these multiples of 12 #define NUMKEYS 36 #define FIRSTKEY 48 // 60 is middle C (includes semi-tones) /**************************************************************************** * * public data * ***************************************************************************/ HWND hKeyWnd; // keyboard window /**************************************************************************** * * local data * ***************************************************************************/ typedef struct _key { RECT rc; // the key's area BYTE midinote; // which midi key number BYTE note; // which (alphabetical) note BYTE noteon; // is the note on? } KEY, *PKEY; static UINT wWidth; // client area width static UINT wHeight; // client area height static int iWhiteKeys; // number of white keys static BYTE bLastNote; static char keyadd = 0; static KEY keys[128]; static TEXTMETRIC tm; /**************************************************************************** * * internal function prototypes * ***************************************************************************/ static char WhiteKey(int key); static void MidiNoteOn(BYTE note); static void MidiNoteOff(BYTE note); static BYTE GetNote(LONG pos); static void noteChanged(HWND hWnd, BYTE note); static void Paint(HWND hWnd, HDC hDC); static void Size(HWND hWnd); static char WhiteKey(int key) { switch (key % 12) { case 0: return 'C'; case 2: return 'D'; case 4: return 'E'; case 5: return 'F'; case 7: return 'G'; case 9: return 'A'; case 11: return 'B'; default: return 0; } } void CreateKeyboard(HWND hParent) { UINT x, y, dx, dy; int i; RECT rc; // initialize key array for (i = 0; i < 128; i++) { keys[i].noteon = FALSE; keys[i].note = WhiteKey(i); keys[i].midinote = (BYTE)i; } // initial size and location of keyboard dx = 600; dy = 150; x = 20; GetWindowRect(hParent, &rc); y = rc.bottom - GetSystemMetrics(SM_CYFRAME) - dy; hKeyWnd = CreateWindow("SYNTHKEYS", "", WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME, x, y, dx, dy, hParent, NULL, ghInst, (LPSTR)NULL); } static void MidiNoteOn(BYTE note) { SHORTMSG sm; if (hMidiOut) { sm.b[0] = (BYTE) 0x90 + bChannel; sm.b[1] = note; sm.b[2] = NOTEVELOCITY; midiOutShortMsg(hMidiOut, sm.dw); bLastNote = note; keys[note].noteon = TRUE; if (ISBITSET(fDebug, DEBUG_NOTE)) sprintf(ach, "\nNote On %d Channel %d", note, bChannel); dbgOut; } } static void MidiNoteOff(BYTE note) { SHORTMSG sm; if (hMidiOut) { sm.b[0] = (BYTE) 0x80 + bChannel; sm.b[1] = note; sm.b[2] = 0; midiOutShortMsg(hMidiOut, sm.dw); keys[note].noteon = FALSE; if (ISBITSET(fDebug, DEBUG_NOTE)) sprintf(ach, "\nNote Off %d Channel %d", note, bChannel); dbgOut; } } static BYTE GetNote(LONG pos) { POINT pt; BYTE i; pt.x = LOWORD(pos); pt.y = HIWORD(pos); for (i = FIRSTKEY; i < FIRSTKEY + NUMKEYS; i++) { if (!keys[i].note) { // black notes are 'over' white ones if (PtInRect(&(keys[i].rc), pt)) return (i + keyadd); } } for (i = FIRSTKEY; i < FIRSTKEY + NUMKEYS; i++) { if (keys[i].note) { // look at white notes if (PtInRect(&(keys[i].rc), pt)) return (i + keyadd); } } // shouldn't get here... return 0; } static void noteChanged(HWND hWnd, BYTE note) { RECT onRC; CopyRect(&onRC, &(keys[note].rc)); onRC.top = onRC.bottom - tm.tmHeight * 2; // set top of ON rect to not overlap black keys if (WhiteKey(note)) onRC.top = max(onRC.top, wHeight * 2/3); InflateRect(&onRC, -2, -2); InvalidateRect(hWnd, &onRC, WhiteKey(note)); } static void Paint(HWND hWnd, HDC hDC) { int i; RECT onRC; HBRUSH hBlackBrush, hOldBrush; char buf[4]; GetTextMetrics(hDC, &tm); hBlackBrush = GetStockObject(BLACK_BRUSH); for (i = FIRSTKEY; i < (FIRSTKEY + NUMKEYS); i++) { CopyRect(&onRC, &(keys[i].rc)); onRC.top = onRC.bottom - 2 * tm.tmHeight; if (keys[i].note) { // paint white keys MoveToEx(hDC, keys[i].rc.right, keys[i].rc.top, NULL); LineTo(hDC, keys[i].rc.right, keys[i].rc.bottom); wsprintf(buf, "%u", i + keyadd); TextOut(hDC, keys[i].rc.left + (keys[i].rc.right - keys[i].rc.left - tm.tmAveCharWidth*lstrlen(buf)) / 2, wHeight - 2 * tm.tmHeight, (LPSTR)buf, lstrlen(buf)); TextOut(hDC, keys[i].rc.right - (keys[i].rc.right - keys[i].rc.left) / 2 - tm.tmAveCharWidth / 2, wHeight - tm.tmHeight, (LPSTR)&(keys[i].note), 1); // set top of ON rect to not overlap black keys onRC.top = max(onRC.top, wHeight * 2/3); } else { // paint black keys hOldBrush = SelectObject(hDC, hBlackBrush); Rectangle(hDC, keys[i].rc.left, keys[i].rc.top, keys[i].rc.right, keys[i].rc.bottom); SelectObject(hDC, hOldBrush); } if (keys[i+keyadd].noteon) { InflateRect(&onRC, -2, -2); InvertRect(hDC, &onRC); } } } static void Size(HWND hWnd) { int i, x, dx; // count the number of white keys visible iWhiteKeys = 0; for (i = FIRSTKEY; i < FIRSTKEY + NUMKEYS; i++) { if (WhiteKey(i)) iWhiteKeys++; } // x is the right side of the white, dx is the width of a white key. x = dx = wWidth / iWhiteKeys; for (i = FIRSTKEY; i < FIRSTKEY + NUMKEYS; i++) { if (keys[i].note) { // it's a white key SetRect(&(keys[i-NUMKEYS].rc), x - dx, 0, x, wHeight); SetRect(&(keys[i].rc), x - dx, 0, x, wHeight); SetRect(&(keys[i+NUMKEYS].rc), x - dx, 0, x, wHeight); x += dx; } else { // it's a black key SetRect(&(keys[i-NUMKEYS].rc), x-dx/3-dx, 0, x+dx/3-dx, wHeight*2/3); SetRect(&(keys[i].rc), x-dx/3-dx, 0, x+dx/3-dx, wHeight*2/3); SetRect(&(keys[i+NUMKEYS].rc), x-dx/3-dx, 0, x+dx/3-dx, wHeight*2/3); } } } long FAR PASCAL KeyWndProc(HWND hWnd, unsigned message, UINT wParam, LONG lParam) { PAINTSTRUCT ps; // paint structure HMENU hMenu; BYTE note; RECT rc; // process any messages we want switch(message) { case WM_CREATE: hMenu = GetMenu(hMainWnd); CheckMenuItem(hMenu, IDM_KEYBOARD, MF_CHECKED); break; case WM_SIZE: wWidth = LOWORD(lParam); wHeight = HIWORD(lParam); Size(hWnd); break; case WM_LBUTTONDOWN: note = GetNote(lParam); if (note) { MidiNoteOn(note); noteChanged(hWnd, note); } break; case WM_LBUTTONUP: note = GetNote(lParam); if (note) { MidiNoteOff(note); noteChanged(hWnd, note); } break; case WM_KEYDOWN: GetWindowRect(hWnd, &rc); rc.right = rc.right - rc.left; rc.left = 0; rc.top = wHeight - 2 * tm.tmHeight; rc.bottom = wHeight - tm.tmHeight; if (wParam == VK_CONTROL) { keyadd = -NUMKEYS; if (!(lParam & 0x40000000)) InvalidateRect(hWnd, &rc, TRUE); } else if (wParam == VK_SHIFT) { keyadd = NUMKEYS; if (!(lParam & 0x40000000)) InvalidateRect(hWnd, &rc, TRUE); } break; case WM_KEYUP: GetWindowRect(hWnd, &rc); rc.right = rc.right - rc.left; rc.left = 0; rc.top = wHeight - 2 * tm.tmHeight; rc.bottom = wHeight - tm.tmHeight; keyadd = 0; InvalidateRect(hWnd, &rc, TRUE); break; case WM_MOUSEMOVE: if (wParam & MK_LBUTTON) { note = GetNote(lParam); if (note && (note != bLastNote)) { MidiNoteOff(bLastNote); noteChanged(hWnd, bLastNote); MidiNoteOn(note); noteChanged(hWnd, note); } } break; case WM_PAINT: BeginPaint(hWnd, &ps); Paint(hWnd, ps.hdc); EndPaint(hWnd, &ps); break; case WM_DESTROY: hMenu = GetMenu(hMainWnd); CheckMenuItem(hMenu, IDM_KEYBOARD, MF_UNCHECKED); hKeyWnd = NULL; break; default: return DefWindowProc(hWnd, message, wParam, lParam); break; } return NULL; }