369 lines
8.9 KiB
C
369 lines
8.9 KiB
C
/****************************************************************************
|
|
*
|
|
* keyb.c
|
|
*
|
|
* Copyright (c) 1991 Microsoft Corporation. All Rights Reserved.
|
|
*
|
|
***************************************************************************/
|
|
|
|
#include <windows.h>
|
|
#include <mmsystem.h>
|
|
#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;
|
|
}
|