- 음악과 나 -/『 짬 통 』

저수준 제어를 이용한 WAVE 재생 - 피크메타 제작

noon2dy 2006. 5. 18. 02:57

 

 

2002-07-03 오후 2:18:23   /  번호: 4462  / 평점:  (-) category: Sound  /  조회: 2,270 
 첨부파일 있음[API] 저수준 제어를 이용한 WAVE 재생 - 피크메타 제작[완결] 트론의 유령 / lovesgh  
트론의 유령님께 메시지 보내기  트론의 유령님을 내 주소록에 추가합니다.  트론의 유령님의 블로그가 없습니다  

-----------------------------------------------------------

피크메타 제작                               - 트론의 유령 -

-----------------------------------------------------------

 

 안녕하십니까? 저번에 약속?한대로 이번엔 피크메타 제작에 관

한 강좌?를 올리겠습니다.미약하나마 여러분들에게 도움이 됐으

면 하는 바램입니다. 역시 강좌?는 편의상 경어를 생략하겠습니

다. 여러분들의 양해를 구하며.. 시작합니다. ^^

 

-----------------------------------------------------------

 

 피크메타? - 피크메타란 무엇인가..일단 그것보다 알고 넘어가

는 것이 순서가 아닐까 한다.. 피크메타란..사전을 찾아보니 그

뜻이 아래와 같다.

 

-----------------------------------------------------------

[peak]

 

1 (뾰족한) 산꼭대기, 봉우리

3 절정, 최고점, 극치; 최대량

4 성수기; (성수기의) 할증 요금

5 (군모 등의) 챙

6 음성 (음절의) 핵(核)

 

[meter]

 

1 미터 ((길이의 단위; =100cm))

2「계(기); 미터」의 뜻

-----------------------------------------------------------

 

 아마도 내가 말하려는 피크메타에서의 피크는.. 절정, 최고점,

극치;최대량.. 의 피크일 것이다. 메타.. 미터..발음의 관한 문

제 이므로.. 본 발음이 '피크미터' 인지 '피크메타' 인지는  잘

모르겠으나, 여기 강좌에서는 그냥 '피크메타'로 통일해서 부르

기로 한다. 메타는 기기의 뜻으로.. 피크메타라 함은, 최고점..

극치;최대량을 보여주는 기기(기계)정도로만 생각하면 될 것 같

다. 말로만 설명하려니까 본인의 머리에서 세포들이 노조파업을

하려고 하고 여러분들의 머리속 세포들 역시 파업에 동참하려고

준비중인 것 같다.그래서 그림으로 한번 보여주는게 빠를 것 같

아 그림을 첨부했다.

 

-----------------------------------------------------------

    

 

[그림 1-1] 피크메타란.. 대충 저런 것이다..(본인의 디자인 실

           력에 감탄했다고?..-_- 본인 역시 감탄하는 중이다)

           본인이 이번에 제작중인 프로그램인데...  저번부터

           꼭 한번 우드(Wood) 스타일의 스킨을 써보고 싶었다

           그런데, 결론은.. '꽝' 이다.. ㅠㅠ

-----------------------------------------------------------

 

 현재 재생중인 음악의 이진데이타(WAVE 파일)에서 최고치를 뽑

아내어 그래프로 보여 주는 것이다. 저걸 어떻게 만드냐고?  지

난번까지 올라온 강좌( 1편 - 5편 )를 잘 보신 분이라면, 별 어

려움 없이 만들 수 있을 것이라 본다. WAVE 파일의 구조와 처리

방법에 대해서는 본인이 올린 지난 강좌를 참조하기 바란다.(검

색에서.. 제목에 [API].. 하면 나올 것이다..) 피크메타를 제작

하기 위해서는 일단 현재 진행중인 곡에서 각 부분 마다 일정한

주기로 최대값을 뽑아내야 하는데..  본인은 그 시간을 30 분의

1초 ( 1/30 Secs )로 정했다. 여기서 필요한 것이 TIMER 함수인

데..

 

hTimer = ( HANDLE ) SetTimer ( hwnd, 1, (1000/30), NULL ) ;

 

으로 설정하면 아마도.. 30분의 1초마다 TIMER 메세지가 발생할

것이다. 이 부분을 WM_CREATE 부분에 설정해 주는데.. 윈도우프

로그래밍에서 WndProc (윈도우 프로시저)에서 제일 처음으로 처

리하는 메세지는 WM_CREATE이다. 이 메세지는 윈도우가 처음 생

성될 때 발생하는데 이 메세지에서 프로그램 시작시 꼭 한 번만

초기화, 설정 되어야 할 처리를 해 주게 된다. 프로그램 실행시

필요한 메모리를 잡아준다던가 전역변수에 초기값을 대입한다던

가 하는 일 처리를 여기서 해주면 된다.

 

LRESULT CALLBACK WndProc ( HWND hwnd, UINT iMessage, WPARAM

                           wParam, LPARAM lParam )

{    

     switch(iMessage)

     {  

          case WM_CREATE : hTimer = ( HANDLE ) SetTimer

                           ( hwnd, 1, (1000/30), NULL ) ;

 

 이렇게 처리하면, WM_CREATE 메세지에서 SetTimer 함수를 사용

하여 타이머를 생성시킨다. 즉,  프로그램이 시작하면서 동시에

타이머가 생성된다는 말이다. 이제 30분의 1초마다 hWnd 윈도우

에 WM_TIMER 메세지가 전달될 것이다. 남은 일은 WM_TIMER 에서

30분의 1초마다 피크메타를 그려주면 되는 것이다. 일단 피크메

타를 그리기 전에 그 정보를 얻어와야 하는 것이 일의 순서일텐

데.. 어떻게 얻어 오는가..

 

 저번 강좌를 보신 분이라면 MM_WOM_DONE 이라는 메세지를 기억

할 것이다. 이 MM_WOM_DONE메세지 부분에서 일정한 크기로 나눈

WAVE 파일 조각을 읽고 재생하고 다시 또 읽고 이 짓을 WAVE 데

이타가 끝날때까지 하도록 설정 했었는데,  핵심 부분만 간단히

복습하고 넘어가자.

 

-----------------------------------------------------------

[ MM_WOM_DONE 메시지 처리 부분 중요 코드 ]

 

...

 

mRet = mmioRead ( hMMIO, (char*) pBuffer[CNT], read_size );

 

if ( ( mRet == -1 )  || ( mRet == 0 ) )

{

     waveOutClose ( hWaveOut ) ;

     return FALSE;

}

 

waveOutWrite ( hWaveOut, pWaveHdr[CNT], sizeof (WAVEHDR) );

 

 

GET_PEAK_MAX ( waveFormatEx.wBitsPerSample,

               waveFormatEx.nChannels,

               pBuffer[CNT],

               read_size,

               &maxL,

               &maxR );

 

 

CNT++;

CNT %= BufferNum ;

 

...

-----------------------------------------------------------

 

 이 부분이다. WAVE 파일의 데이타가 끝날때까지 read_size만큼

계속해서 waveOutWrite () 로 출력해 내는 부분이었다. 이 크기

는 여러분 마음대로 정해도 되지만, 지난번 강좌에서는 이 크기

를 아래와 같이 정했다.

 

-----------------------------------------------------------

[ read_size 의 크기 ]

 

read_size = (( waveFormatEx.Format.nSamplesPerSec *

               waveFormatEx.Format.nChannels *

               waveFormatEx.Format.wBitsPerSample /8 / 10);

-----------------------------------------------------------

 

read_size 의 크기안에서 최대치를 뽑아내면 되는 것이다. 이제

어떻게 그 값을 뽑아낼지 생각해 보자. 일단 GET_PEAK_MAX 라는

함수를 하나 만들자. 넘겨줄 인자로는 wBitsPerSample값과 채널

그리고, 현재 재생중인 데이타가 담긴 버퍼, read_size, 그리고

마지막으로 maxL, maxR 이라는 변수를 넘겨준다.

 

-----------------------------------------------------------

[ GET_PEAK_MAX() 함수의 인자 ]

 

GET_PEAK_MAX ( waveFormatEx.wBitsPerSample,

               waveFormatEx.nChannels,

               pBuffer[CNT],

               read_size,

               &maxL,

               &maxR );

-----------------------------------------------------------

 

 여기서 maxL, maxR 은 최대치 값(피크)을 저장할 변수이다. 이

렇게 선언한 뒤에 함수를 실제로 만들어 보자.함수를 만들기 전

에 일단 WAVE 데이타의 샘플당 비트수와 채널에 따라 저장이 다

르게 된다는 점을 상기하기 바란다. (중요하다)지난번 강좌에도

그렸었지만.. 이번에 한번 더 그려서 설명한다. (심심해서..--)

 

-----------------------------------------------------------

[ 샘플당 비트수와 채널에 따른 저장 공간의 차이 ]

 

 

 [ 8  Bit Mono ]

 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   

 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+

 : D : D : D : D : D : D : D : D : D : D : D : D : D : D :

 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+

 

 [ 8  Bit Stereo ]

 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   

 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+

 : L : R : L : R : L : R : L : R : L : R : L : R : L : R :

 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+

 

 

 [ 16 Bit Mono ]

 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E    

 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+

 :   D   :   D   :   D   :   D   :   D   :   D   :   D   :

 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+

 

 

 [ 16 Bit Stereo ]

 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E    

 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+

 :   L   :   R   :   L   :   R   :   L   :   R   :   L   :

 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+

 

 

 [ 32 Bit Mono ]

 0   1   2   3   4   5   6   7   ...     

 +---+---+---+---+---+---+---+-- ...

 :               D           :   ...         

 +---+---+---+---+---+---+---+-- ...

 

 32 Bit Mono 데이타는 4 Byte 나 먹는다. ( 0 ~ 4294967295 )

 

----------------------------------------------------------      

 

 이 그림같지도 않은 그림?을 괜히 심심하다고 그린건 절대 아

니다 -_-+.. 이 그림이 중요하기 때문에 다시 상기시키려고 그

린 것이다. ( 그러나, 자세히 보면 저번 강좌때하고는 좀 다르

다. 신경써서 좀 고쳤는데, 별로 티도 안난다.. )이 크기에 따

라 피크를 뽑아낼 방법이 달라지기 때문이다.

 

----------------------------------------------------------

 

 

 

----------------------------------------------------------

 

[ 본격적인 [피크메타] 제작으로

 

----------------------------------------------------------

 

 자,이제 피크메타를 제작하기 위한 준비작업은 모두 끝마쳤고

실제로 구현에 들어갈 차례가 왔다.. ( 드디어.. )일단은 간단

하게 8 Bit 만 구현해 보자.-> 참고로 피크메타를 제작하는 방

법이야 많이 있을 것이다. 그 원리도 다를테고..여기서 강좌라

고 본인이 쓰는 글은 100% 이것이 정답이다!라고 쓰는 것이 아

니다. 절대! 아니다! 다만, 본인은 이렇게도 구현해 봤는데 이

런 방법에 대해서 여러분에게 알려주고 싶은 것이다.별로 도움

이 될지 안될지는 모르지만..

 

----------------------------------------------------------

 

 아까 피크메타를 뽑아낼 함수를 제작하면서 그 선언을 다음과

같이 했었다.

 

-----------------------------------------------------------

[ GET_PEAK_MAX() 함수의 인자 ]

 

GET_PEAK_MAX ( waveFormatEx.wBitsPerSample,

               waveFormatEx.nChannels,

               pBuffer[CNT],

               read_size,

               &maxL,

               &maxR );

-----------------------------------------------------------

 

 그럼, 이제 함수 몸체를 만들어 보자. 일단, 넘겨 받은 샘플당

비트수와 채널에 따른 분리를 해야 하므로,그에 대한 처리를 해

야 하는 것이 당연하다. 여기서는 8 Bit만 알아보기로 했으므로

그에 대한 처리부분에 대해 설명한다. ( 나머지 16, 24, 32 Bit

는 행복하게도? 여러분의 몫으로 남겨두겠다. ) 자 바로 다음이

다!

 

 

-----------------------------------------------------------

 

GET_PEAK_MAX ( int waveFormatEx.wBitsPerSample,

               int waveFormatEx.nChannels,

               void *pBuffer,

               LONG read_size,

               LONG &maxL,

               LONG &maxR )

{

 

BYTE  *pB, bMaxL, bMaxR ;   // 1 Byte (BYTE)  - bMaxL, bMaxR

 

int i;

 

LONG cnt = read_size / ( waveFormatEx.wBitsPerSample / 8 ) ;  

                    

cnt /= waveFormatEx.nChannels ;

 

//-- 8 Bit 에 대한 처리 부분

if ( waveFormatEx.wBitsPerSample == 8 )

{

     pB = ( BYTE * ) pBuffer;

     bMaxL = bMaxR = 0 ;

 

     if ( waveFormatEx.nChannels == 1 ) //-- Mono

     {

          for ( i = 0; i < cnt; i++ )

          {

               if ( *pB > bMaxL )

                    bMaxL = *pB ;

         

               pB++ ;

          }

 

          *maxL = *maxR = bMaxL ;

          //wsprintf ( L_MAX, "%03d - Mono", bMaxL ) ;

          //wsprintf ( R_MAX, "%03d - Mono", bMaxL ) ;

     }

 

     else if ( waveFormatEx.nChannels == 2 ) //-- Stereo

     {

          for ( i = 0; i < cnt; i++ )

          {

               if ( *pB > bMaxL )

                    bMaxL = *pB ;

               

               if ( *( pB+1 ) > bMaxR )

                    bMaxR = *( pB+1 );

        

               pB += 2 ;            

          }

         

          *maxL = bMaxL ;

          *maxR = bMaxR ;

 

          //wsprintf ( L_MAX, "%03d - Stereo", bMaxL ) ;

          //wsprintf ( R_MAX, "%03d - Stereo", bMaxR ) ;

     }

}

 

 

}

-----------------------------------------------------------

 

 주석 처리한 " wsprintf ( L_MAX, "%03d - Mono", bMaxL ) ; "

부분은 프로그램의 진행상 별로 필요는 없는것이지만,실제로 뽑

아낸 피크값이 얼마인지 보기위해 만들어 둔 것이다.WM_PAINT메

시지에서 TextOut (hdc,X,Y,L_MAX,strlen(L_MAX));과 같이 해서

그 값을 볼수 있다.함수를 보면 대충 그 기능이 이해가 갈 것이

다. 제일 처음에 포인터 pB 에 ( BYTE * ) 단위로 pBuffer를 저

장한다. 이때 주의할 점은 앞으로 16 / 24 / 32 Bit를 처리할때

그에 맞게 형 변환 해줘야 하므로 넘겨받을 pBuffer를 void형으

로 받아야 한다는 것이다.함수헤더를 보면 이해가 갈 것이다.그

리고 bMaxL 와 bMaxR 를 '0' 으로 초기화 해준다.

 

 

-[ 쉬다 갈까나? ]------------------------------------------

 

 'C'프로그램을 하다 보면 자잘한 버그와의 싸움으로 밤을 새는

경우가 허다한데, 대부분의 초보시절에는 이런 변수선언에서 초

기화하는 부분 에서도 애를 먹기도 한다. 여기서  bMaxL, bMaxR

은 함수 안에서 선언된 지역변수(Local) 이면서 자동변수(Auto)

이다. 자동변수는 대게 auto  라는 형지정자를 생략하기도 하는

데, 보통 쓰는 int a; , int i; 하는 변수들이다.  선언된 지점

에서 블록이 끝나는 시점에서 자동으로 파괴?되기 때문에  그런

이름이 붙었다. 중요한 점은 저놈들은 절대 만들어지는 순간'0'

으로 초기화 되지 않는다는 것이다.

 선언된 순간 표현한계까지의 수 중에서 임의의 수로 채워져 있

을것이다. (흔히들 '쓰레기값' 이라 하는 그것이다. ) 자동변수

는 선언되었을때 하나의 기본동작만이 일어난다. 즉, 선언된 각

각의 변수에 대해 기억공간이 할당될 뿐이다. ( 아니 왜 갑자기

변수 얘기로 빠져들었지.. --+ ?? ) .. 잠깐 한박자 쉬고..선언

했으면 초기화! 하자..

 

-----------------------------------------------------------

 

 

그리고 if ( waveFormatEx.nChannels == 1 )      //-- Mono

       else if ( waveFormatEx.nChannels == 2 ) //-- Stereo

 

에 따라 모노일때와 스테레오일때의 피크메타 처리를 해주면 된

다. 몇줄 안되는 부분이고 간단한 부분이므로 별다른 설명은 없

다.중요한 점은 이제 maxL, maxR 에는 피크 값이 들어가 있으므

로 그 값으로 선을 그리던지 네모를 그리던지..뭘 그리던지간에

그 처리만 해주면 되는 것이다. 본인은 아래와 같이 그냥..사각

형 모양으로 그래프를 처리했다.

 

RectBrush = (HBRUSH)GetStockObject(GRAY_BRUSH) ;

OldBrush = (HBRUSH)SelectObject(hdc, RectBrush) ;

Rectangle(hdc, 30, PEAK_MAX_HEIGHT_1, 33, PEAK_MAX_BOTTOM);

 

 비트맵으로 처리하면 더 이쁠텐데, 위에서 본인의 디자인 실력

에 감탄한지라.. 더 이상 감탄했다가는 심장마비에 걸려 죽을것

같아 비트맵 처리는 당분간 보류 하기로 했다.

 

-----------------------------------------------------------

 

     

-[ 끝내고 나서.. ] ----------------------------------------

 

 어땠습니까??  이번 강좌?도 나름대로 쓸만한 정보를 드리려고

노력했는데,다시 읽어 보니까 과연 여기서 뭘 얻어갈 수 있을지

의문이.. 어쨋든, 대강의 흐름은 아셨을 겁니다.  어설프게나마

피크메타를 그리는 법을 설명했는데 도움이 됐으면 좋겠습니다.

다음에는 무슨 강좌를 할까요?? 음..아마도 사인파형 그려서 보

여주기를 하지 않을까 싶네요. ( 강좌 하나 했으니 또 놀아야죠

.. 워크3도 하고.. ^^.. ) 강좌에서 사용한 용어들이 잘못 쓰인

부분이 있을지도 모릅니다. 그때 그때 지적해 주시면 바로 교정

하겠습니다. 아직 저도 미숙한지라.. 실수가 있을 수 있습니다.

다음 강좌때까지.. 모두들 열심히 프로그래밍! 합시다..

 

 

[ E-Mail ]  :  lovesgh@empal.com            - 트론의 유령 -

 

-----------------------------------------------------------

이 글에 평점 주기:  
 
  2002-08-08 오전 11:43:19   /  번호: 4814  / 평점:  (-)  
 Re: 왜 프로그램이 실행이 안되죠? 김주생 / salmon74  
김주생님께 메시지 보내기  김주생님을 내 주소록에 추가합니다.  김주생님의 블로그 가기   
강좌 정말 잘 보고 있습니다.
그런데  WavePlayerii.exe를 실행하면 
32비트 프로그램이 아니라고 경고가뜨는데.
윈도 xp라 그런가요?
98은 되나?
암튼 윈도xp에서는 아되네요.......

'- 음악과 나 - > 『 짬 통 』' 카테고리의 다른 글

뎁퍄 질답들. 음악관련 주제들.  (0) 2006.05.22
vc6 팁들  (0) 2006.05.18
전체화면으로 전환  (0) 2006.05.18
fps 계산하기  (0) 2006.05.18
저수준제어를 이용한 wave재생 - 소스 , wavescope  (0) 2006.05.18