블로그는 나의 힘!
[ Programing ]/API2013. 5. 22. 00:35
출처 공부해요 ^.^ | 시그널
원문 http://blog.naver.com/hinghihi/50005151592

  찰스 페졸드의 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 ); 

 

 

 

 

마비노기 음유시인 부분 만들수도 있겠다 

Posted by Mister_Q