찰스 페졸드의 programming windows 책에 소개된 미디 합성 소스를 사용하여 만들었다.
그리 길지 않은 코딩으로 피아노 음원을 합성 할 수 있다.
//키보드 미디 재생기
//winmm.lib 를 링크 시켜야함
#include <windows.h>
#define ID_TIMER 1
LRESULT CALLBACK WndProc( HWND,UINT,WPARAM,LPARAM );
DWORD MidiOutMessage( HMIDIOUT hMidi, int iStatus, int iChannel, int iData1, int iData2 );
DWORD MidiNoteOff( HMIDIOUT hMidi, int iChannel, int iOct, int iNote, int iVel );
DWORD MidiNoteOn( HMIDIOUT hMidi, int iChannel, int iOct, int iNote, int iVel );
DWORD MidiSetPatch( HMIDIOUT hMidi, int iChannel, int iVoice );
DWORD MidiPitchBend( HMIDIOUT hMidi, int iChannel, int iBend );
VOID Processkey( HWND hWnd, HDC hdc, UINT message, LPARAM lParam );
VOID DrawKey( HWND hWnd, HDC hdc, int iScanCode, BOOL fInvert );
//미디 관련 전역 변수
HMIDIOUT hMidiOut;
int iDevice = MIDIMAPPER, iChannel = 0, iVoice = 0, iVelocity = 64;
int cxCaps, cyChar, xOffset, yOffset;
//메뉴에서 패밀리와 악기를 보여주기 위한 구조체와 데이터
typedef struct
{
TCHAR * szInst;
int iVoice;
}
INSTRUMENT;
typedef struct
{
TCHAR * szFam;
INSTRUMENT inst [8];
}
FAMILY;
FAMILY fam[1] = {
TEXT( "Piano" ),
TEXT( "Acoustic Grand Piano" ), 0,
TEXT( "Bright Acoustic Piano" ), 1,
TEXT( "Electric Grand Piano" ), 2,
TEXT( "Honky-tonk Piano" ), 3,
TEXT( "Rhodes Piano" ), 4,
TEXT( "Chorused Piano" ), 5,
TEXT( "Harpsichord" ), 6,
TEXT( "Clavinet" ), 7,
};
//스캔 코드를 옥타브와 음으로 변환하기 위한 데이터
#define NUMSCANS ( sizeof key / sizeof key[0] )
struct
{
int iOctave;
int iNote;
int yPos;
int xPos;
TCHAR *szKey;
}
key[] =
{
//Scan Char Oct Note
-1,-1,-1,-1,NULL, //0 None
-1,-1,-1,-1,NULL, //1 Esc
-1,-1, 0, 0,TEXT(""), //2 1
5, 1, 0, 2,TEXT("C#"), //3 2 5 C#
5, 3, 0, 4,TEXT("D#"), //4 3 5 D#
-1,-1, 0, 6,TEXT(""), //5 4
5, 6, 0, 8,TEXT("F#"), //6 5 5 F#
5, 8, 0, 10,TEXT("G#"), //7 6 5 G#
5,10, 0, 12,TEXT("A#"), //8 7 5 A#
-1,-1, 0,14,TEXT(""), //9 8
6, 1, 0, 16,TEXT("C#"), //10 9 6 C#
6, 3, 0, 18,TEXT("D#"), //11 0 6 D#
-1,-1, 0, 20,TEXT(""), //12 -
6, 6, 0, 22,TEXT("F#"), //13 = 6 F#
-1,-1,-1, -1,NULL, //14 BACK
-1,-1,-1,-1,NULL, //15 TAB
5, 0, 1, 1,TEXT("C"), //16 Q 5 C
5, 2, 1, 3,TEXT("D"), //17 W 5 D
5, 4, 1, 5,TEXT("E"), //18 E 5 E
5, 5, 1, 7,TEXT("F"), //19 R 5 F
5, 7, 1, 9,TEXT("G"), //20 T 5 G
5, 9, 1, 11,TEXT("A"), //21 Y 5 A
5, 11, 1, 13,TEXT("B"), //22 U 5 B
6, 0, 1, 15,TEXT("C"), //23 I 6 C
6, 2, 1, 17,TEXT("D"), //24 O 6 D
6, 4, 1, 19,TEXT("E"), //25 P 6 E
6, 5, 1, 21,TEXT("F"), //26 [ 6 F
6, 7, 1, 23,TEXT("G"), //27 ] 6 F
-1,-1,-1, -1,NULL, //28 ENT
-1,-1,-1,-1,NULL, //29 CTRL
3, 8, 2, 2,TEXT("G#"), //30 A 3 G#
3,10, 2, 4,TEXT("A#"), //31 S 3 A#
-1,-1, 2, 6,TEXT(""), //32 D
4, 1, 2, 8,TEXT("C#"), //33 F 4 C#
4, 3, 2, 10,TEXT("D#"), //34 G 4 D#
-1,-1, 2, 12,TEXT(""), //35 H
4, 6, 2, 14,TEXT("F#"), //36 J 4 F#
4, 8, 2, 16,TEXT("G#"), //37 K 4 G#
4,10, 2, 18,TEXT("A#"), //38 L 4 A#
-1,-1, 2, 20,TEXT(""), //39 ;
5, 1, 2, 22,TEXT("C#"), //40 ' 5 C#
-1,-1,-1,- 1,NULL, //41 ,
-1,-1,-1,-1,NULL, //42 SHIFT
-1,-1,-1,-1,NULL, //43 \\
3, 9, 3, 3,TEXT("A"), //44 Z 3 A
3,11, 3, 5,TEXT("B"), //45 X 3 B
4, 0, 3, 7,TEXT("C"), //46 C 4 C
4, 2, 3, 9,TEXT("D"), //47 V 4 D
4, 4, 3, 11,TEXT("E"), //48 B 4 E
4, 5, 3, 13,TEXT("F"), //49 N 4 F
4, 7, 3, 15,TEXT("G"), //50 M 4 G
4, 9, 3, 17,TEXT("A"), //51 , 4 A
4, 11,3, 19,TEXT("B"), //52 . 4 B
5, 0, 3, 21,TEXT("C"), //53 / 5 C
};
//윈도우즈 메인
HINSTANCE g_hInst;
LPSTR lpszClass="피아노를 쳐 보아요~^^";
int APIENTRY WinMain( HINSTANCE hInstance,HINSTANCE hPrevInstance
,LPSTR lpszCmdParam,int nCmdShow )
{
HWND hWnd;
MSG Message;
WNDCLASS WndClass;
g_hInst=hInstance;
WndClass.cbClsExtra=0;
WndClass.cbWndExtra=0;
WndClass.hbrBackground=(HBRUSH)GetStockObject( WHITE_BRUSH );
WndClass.hCursor=LoadCursor( NULL,IDC_ARROW );
WndClass.hIcon=LoadIcon( NULL,IDI_APPLICATION );
WndClass.hInstance=hInstance;
WndClass.lpfnWndProc=(WNDPROC)WndProc;
WndClass.lpszClassName=lpszClass;
WndClass.lpszMenuName=NULL;
WndClass.style=CS_HREDRAW | CS_VREDRAW;
RegisterClass( &WndClass );
hWnd=CreateWindow( lpszClass,lpszClass,WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX ,
CW_USEDEFAULT,CW_USEDEFAULT,500,300,
NULL,(HMENU)NULL,hInstance,NULL );
ShowWindow( hWnd,nCmdShow );
while( GetMessage(&Message, 0, 0, 0) )
{
TranslateMessage( &Message );
DispatchMessage( &Message );
}
return Message.wParam;
} //End of WinMain
//윈도우즈 프로시져
LRESULT CALLBACK WndProc( HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam )
{
static BOOL bOpened = TRUE;
HDC hdc;
int i, iNumDevs, cxClient, cyClient;
MIDIOUTCAPS moc;
PAINTSTRUCT ps;
SIZE size;
TCHAR szBuffer[16];
switch(iMessage)
{
case WM_CREATE:
{
//타이머 설치
SetTimer( hWnd,ID_TIMER, 1000, NULL );
//시스템 폰트에서 대문자의 크기를 얻는다.
hdc = GetDC( hWnd );
GetTextExtentPoint(hdc, TEXT("M"), 1, &size);
cxCaps = size.cx;
cyChar = size.cy;
ReleaseDC(hWnd, hdc);
//미디 출력 장치의 개수를 얻고 메뉴를 설정한다.
if( 0 == (iNumDevs = midiOutGetNumDevs() ) )
{
MessageBeep( MB_ICONSTOP );
MessageBox( hWnd, TEXT("미디 출력 장치가 없습니다!"),"오류", MB_OK | MB_ICONSTOP );
return -1;
}
//미디 장치를 연다.
if( midiOutOpen(&hMidiOut, iDevice, 0,0,0) )
{
MessageBeep( MB_ICONEXCLAMATION );
MessageBox( hWnd, TEXT("미디 출력 장치를 열 수 없습니다."),
"오류", MB_OK | MB_ICONEXCLAMATION );
}
else
{
MidiSetPatch( hMidiOut, iChannel, iVoice );
}
} break;
case WM_GETMINMAXINFO: //윈도우 모드일때 사이즈 변형 안되게 함.
{
MINMAXINFO* pMinMax = (MINMAXINFO*)lParam;
pMinMax->ptMinTrackSize.x = 500 ;
pMinMax->ptMinTrackSize.y = 300 ;
pMinMax->ptMaxTrackSize.x = pMinMax->ptMinTrackSize.x;
pMinMax->ptMaxTrackSize.y = pMinMax->ptMinTrackSize.y;
} return 0L;
case WM_SIZE:
{
cxClient = LOWORD( lParam );
cyClient = HIWORD( lParam );
xOffset = ( cxClient - 25 * 3 * cxCaps / 2 ) / 2;
yOffset = ( cyClient - 11 * cyChar ) / 2 + 5 * cyChar;
} break;
case WM_COMMAND:
{
} break;
case WM_KEYUP:
{
} break;
case WM_KEYDOWN:
{
hdc = GetDC( hWnd );
if( bOpened )
Processkey( hWnd, hdc, iMessage, lParam );
ReleaseDC( hWnd, hdc );
} break;
case WM_CHAR: //이스케이프 키를 눌렀을때 모든 음을 끄고 화면을 다시 그린다.
{
if( bOpened && wParam == 27 )
{
for( i = 0 ; i < 16 ; i++ )
{
MidiOutMessage( hMidiOut, 0xB0, i , 123, 0 );
}
InvalidateRect( hWnd, NULL, TRUE );
}
} break;
case WM_PAINT:
{
hdc = BeginPaint( hWnd, &ps );
for( i = 0 ; i < NUMSCANS; i++ )
{
if( key[i].xPos != -1 )
DrawKey( hWnd,hdc, i, FALSE );
}
midiOutGetDevCaps( iDevice, &moc, sizeof(MIDIOUTCAPS) );
wsprintf( szBuffer, TEXT("Channel %i"), iChannel +1 );
TextOut( hdc, cxCaps, 1 * cyChar, "키보드 누르면 소리가 나요^^",lstrlen("키보드 누르면 소리가 나요^^") );
TextOut( hdc, 155, 225, "도",lstrlen("도") );
EndPaint( hWnd,&ps );
} break;
case WM_TIMER:
{
//화면을 깨끗이 갱신한다.
InvalidateRect( hWnd, NULL, TRUE );
} break;
case WM_DESTROY:
{
midiOutClose( hMidiOut );
PostQuitMessage( 0 );
} return 0;
} //End of switch
return ( DefWindowProc(hWnd,iMessage,wParam,lParam) );
}
//미디 출력을 단순화 시키는 루틴
DWORD MidiOutMessage( HMIDIOUT hMidi, int iStatus, int iChannel, int iData1, int iData2 )
{
DWORD dwMessage = iStatus | iChannel | (iData1<<8) | (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 );
}
//키를 윈도우에 그리는 함수
VOID DrawKey( HWND hWnd, 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 , (HBRUSH)GetStockObject(fInvert ? BLACK_BRUSH : WHITE_BRUSH) );
DrawText( hdc, key[iScanCode].szKey, -1 , &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER );
FrameRect( hdc, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH) );
}
//WM_KEYUP 또는 WM_KEYDOWM 메세지를 처리한다.
VOID Processkey( HWND hWnd, HDC hdc, UINT message, LPARAM lParam )
{
int iScanCode, iOctave, iNote;
iScanCode = 0x0FF & HIWORD( lParam );
if(iScanCode >= NUMSCANS) //스캔 코드는 53을 넘지 않아야 한다
return;
if( (iOctave = key[iScanCode].iOctave) == -1 )
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)
{
MidiNoteOff( hMidiOut, iChannel, iOctave, iNote, 0 ); //음끄기
DrawKey( hWnd, hdc, iScanCode, FALSE );
return ;
}
if( 0x40000000 & lParam ) //자동키반복 무시
return ;
MidiNoteOn( hMidiOut, iChannel, iOctave, iNote, iVelocity ); //음켜기
DrawKey( hWnd, hdc, iScanCode, TRUE );
}
마비노기 음유시인 부분 만들수도 있겠다
[출처] [본문스크랩] 가상 피아노 프로그램|작성자 핵이
'[ Programing ] > API' 카테고리의 다른 글
현재 시간 구하기 GetLoacalTime (0) | 2013.07.26 |
---|---|
OutputDebugString() 출력 안될때[디버그] (0) | 2013.05.22 |
_countof() 함수 => 배열의 수를 리턴 (0) | 2013.05.21 |
CPU 갯수 알아보기 (0) | 2011.05.27 |
멀티 모니터 해상도 구하기 (0) | 2011.05.27 |