- 음악과 나 -/『 짬 통 』

저수준 제어를 이용한 WAVE 재생

noon2dy 2006. 5. 6. 01:54

 

 

2002-06-12 오후 1:51:46   /  번호: 4376  / 평점:  (8.5) category: Sound  /  조회: 5,366 
 [API] 저수준 제어를 이용한 WAVE 재생 - 01 [시작] 트론의 유령 / lovesgh  
트론의 유령님께 메시지 보내기  트론의 유령님을 내 주소록에 추가합니다.  트론의 유령님의 블로그가 없습니다  

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

 안녕하십니까? 매일 같이 데브피아에는 놀러오는데, 마땅히 답변해 줄

실력도 안되고, 늘 구경만 하다가 이번에 강좌?랍시고 글 하나  쓰기로

했습니다. 비록 없는 실력이긴 하지만, 열심히 쓰겠습니다.

 

 강좌는 윈도우즈 API 를 이용한 Media( 그 중에서 먼저 WAVE 방식부터

시작 하겠습니다. ) 재생에 관한 내용입니다. API 를 이용한 Media재생

에는 크게 고수준제어와 저수준제어로 나뉘는데, MCI_계열 함수를 이용

한 고수준 제어로는 간단하게 충분한? 성능의 미디어 재생기를 제작 할

수 있지만, 보다 더 세심하고 세밀하게 제어를 원한다면 역시 저수준제

어로 갈 수 밖에 없습니다. WAVE 파일의 임의 영역 삭제라던지, 복사하

기 또는 변형(에쿄 효과 내지는 화이트 노이즈..  그 밖의 여러가지 이

펙트..)과 같은 작업을 위해서는 저수준 제어로 가는 것이 아마도.....

무병장수하는 지름길이 아닐까..합니다.본 강좌는 특정 언어 형식에 구

애 받지 않고 진행하려 했으나!.. 부득이 하게 C 를 이용하여 진행합니

다. ( 할줄 아는게.. ) API 관련 부분은 API 를 이용할 수 있는 어떠한

언어로도 작업이 가능하기 때문에 (비주얼 베이직, 델파이 , C++ 빌더,

그 밖에.. ) 다른 언어를 하고 계신 분이라도 쉽게 적용하실 수 있을겁

니다.

 

 끝으로.. 아직 미숙한 실력으로 그래도 뭔가 도움이 될까 하고 쓰기는

쓰는데, 틀린 부분도 있을 것 이고 어색한 부분도 있을 것입니다. 부탁

드리는 것은  100% 믿지 말고 실제로 작성해 가면서 자신이 느껴보라는

것입니다. 그리고, 잘못된 용어사용에 대한 지적을 해 주신 '고임'님과

데브피아 게시판의 기능을 잘 몰라 편집도 제대로 못하고 있는 저를 위

해 게시판 정리를 해 주신 담당자님께 감사드립니다.

 

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

 

[WAVE FILE의 구조]

 

 WAVE 파일은 사운드 파일중에 한 형식으로 윈도우에서 가장 흔하게 쓰

이는 파일중에 하나이다. 이번에 이 유령넘이 강좌?랍시고 주절 거리는

내용은 이넘의 WAVE 파일을 한번 재생해 보자..는 것이다. WAVE 파일을

재생하는데는, 쉽게는 SoundPlay() 함수였던가? .. 한줄이면 족하다...

물론, 지금 이 강좌는 그렇게 한줄이면 끝나는 정도가 아닌, WAVE 파일

의 밑바닥부터 꼭대기까지 샅샅히 훑어 내려가며, 그 내부를 마구 파헤

쳐 WAVE 파일을 맘대로 주무를 수 있도록 하는 경지?에까지 도달하도록

하는것이 목표다.

 

 일단, WAVE 파일이고 머고 간에, 기본적인 공식?부터 알아보고 넘어가

도록 하자.사운드라는 것에 대해 깊이 있는 지식을 가지고 있는 사람은

볼 필요가 없겠지만, 어디까지나 처음 시작하는 사람들이 볼 것 이라고

가정하고 설명하도록 한다.

 

 WAVE 파일을 재생하면서 가끔 등록정보를 보신 분들은 알겠지만, 대게

WAVE 파일의 등록정보라고 하면, 16 Bit , Stereo , 44.1 kHz...  등등

의 정보가 보일것이다. 이게 뭐가 중요하냐고? 중요하다.. 정말 중요하

다. 이것으로 WAVE 파일이 시작되는 것이기 때문이다.예를 들어서 다음

과 같은 WAVE 파일이 하나 있다고 보자.

 

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

 [어떤 WAVE 파일의 등록정보]

 

 - 곡명 : 교장선생님이라 불러라!

 - 16 Bit

 - Stereo

 - 44.1 khz

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

 

 이 세가지 정보면 WAVE 파일의 모든 것?을 알 수 있다. 일단 이 3가지

기본적인 정보만으로 이 WAVE 파일의 초당전송량(Bit/Second)이나,전체

재생시간등을 계산해 보자. (이거 배워두면 정말 좋다..-_-;;..그냥..)

 

 일단 계산하는 방식은 모두 곱한다. 16 Bit 이므로 16 을 곱하고,스테

레오 방식이기 때문에 (스테레오는 채널이 2개 이기때문에, 2를 곱한다

당연히 모노는 1채널이기 때문에 1을 곱하면 된다.... 1은 안곱해도 되

는구나 --;) 그리고 44.1 kHz 라는 것은 1초에 44100 번 발광을 한다는

소리니까 44100을 곱하면 된다.

 

16 * 2 * 44100 = 1411200 Bit 다.. 다시 바이트로 고쳐주려면, 나누기

8을 하면 된다. (왜 나누기 8을 하냐고 묻는다면.. 아시다시피.. 8 Bit

는 1 Byte 이기 때문이다..) 나누기 8을 한 결과 176400 이라는 숫자가

나왔다.

 

 즉, 이 WAVE 파일은 초당 176400 Byte 의 용량을 전송한다는 얘기다..

숫자가 너무 크다고?.. 다시 KByte로 환산해 보자.. 172.265625가 나오

는데,귀찮으니까 소수점 아랫것들은 잘라버리고 정수부분만 읽어보자면

172 KB 라고 나올 것이다. 그렇다. 이 WAVE 파일은 1초에 172 KB 나 차

지한다. (더럽게 크구만..) 전체 재생시간을 구하는 것 역시 간단하다.

단순히 나누기만 하면 되기 때문이다.  전체 파일 크기를 알고 있다면,

 

 " 전체 파일 크기 / 초당 용량 = 전체 재생 시간 (초 단위) "

 

 라는 공식이 성립되는 것이다.자,이제 자신의 컴퓨터에 들어있는 WAVE

넘들을 모조리 불러다가 그 넘들의 재생시간을 구해보고 실제로 플레이

해본다음 비슷하게 맞는지 비교해 보라.

 

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

 여기서, 숙제.. 다음의 WAVE 파일들의 전체 크기는 얼마나 될까??????

 

 [1]  8 Bit , Mono , 22 kHz

 [2] 16 Bit , Mono , 11 kHz

 [3] 16 Bit , Stereo , 22 KHz

 

 각각의 WAVE 파일들의 재생시간은 총 1분 30초씩 이다. 그렇다면 각각

의 WAVE 파일들의 전체 파일크기는 얼마나 될까?? ( 대한민국 초등교육

과정을 이수한 사람이라면 누구나 풀 수 있는 문제라서 풀이는 생략..)

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

 

 WAVE 파일은 대게 8 Bit , 16 Bit 가 많이 쓰이는데, 24, 32 Bit 파일

은 별도로 치고.. 대표적인 8 / 16 Bit 파일들은 아래의 그림처럼 파일

에 저장되어 있다. 8 Bit 라면 - 1 Byte 이므로 1 Byte 단위 마다 저장

되어 있고, 16 Bit 라면 당연히 2 Byte 마다 저장되어 있다. 아래 그림

을 보면 8 Bit 파일은 전부 16개 (16진수 10까지..) 로 되어 있는 반면

16 Bit 는 같은 크기인데도 8 칸 밖에는 안된다. 이론적으로 16 Bit 는

8 Bit 에 비해 2 배의 용량을 차지하기 때문이다. [ D ] 라고 표기되어

있는 것은 본인이 편의상 구분하기 쉽게 [ DATA ] 를 줄여 표현한 것이

고.. ( Mono 는 L / R 구분이 없다 ) Stereo에서 [ L ] , [ R ] 이라고

표기 한 것은 양 채널 ( Left Channel, Right Channel ) 을 뜻 한다...

 

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

 [ 8 Bit Mono ]

 

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

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

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

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

 

 [ 8 Bit Stereo ]

 

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

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

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

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

 

 

 [ 16Bit Mono ]

 

     1       2       3       4       5       6       7       8

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

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

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

 

 [16Bit Stereo ]

 

     1       2       3       4       5       6       7       8

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

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

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

 

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

 

 이건 어디까지나 2 채널(Stereo)방식만을 봤을때 그렇다는 것이고, 채

널이 많아지면 또 저장구조는 당연히 달라지게 된다. 요즘 흔히 말하는

5.1채널이니 7.1채널이니 하는 것들은, 저것과는 모양새가 또 다르다..

 

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

 

 WAVE 파일에 대해서 간략하게나마 알아봤는데,이제부터는 정말 자세히

파헤쳐 볼 시간이다. WAVE 파일들은 크게 청크 부분과 데이타 부분으로

나뉜다. 실제 사운드가 들어있는 데이타 부분 앞에는 헤더 청크 부분이

있는데, WAVE 파일의 구조를 살펴보면 아래와 같다.

 

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

1. Wave 파일 포맷(I)

 

   1) PCMWAVEFORMAT 구조체의 구조

      -. WAVEFORMAT wf;

      -. WORD       wBitsPerSample;

 

   2) WAVEFORMAT 구조체의 구조

      -. WORD   wFormatTag;

      -. WORD   nChannels;

      -. DWORD  nSamplesPerSec;

      -. DWORD  nAvgBytesPerSec;

      -. WORD   nBlockAlign;

 

   3) 예 : 22kHz 샘플링된 8bit 스테레오 Wave 파일의 구조

 

      PCMWAVEFORMAT PcmWaveFormat;

      

      PcmWaveFormat.wf.wFormatTag = 1;

      PcmWaveFormat.wf.nChannels = 2;       

      PcmWaveFormat.wf.nSamplesPerSec = 22050;  

      PcmWaveFormat.wf.nAvgBytesPerSec = 44100;

      PcmWaveFormat.wf.nBlockAlign = 2;

      PcmWaveFormat.wBitsPerSample = 8;        

 

 

2. Wave 파일 포맷(II)

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

     데이터형 Byte  내용            의미

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

 

 1) RIFF chunk  

     - Char   4   "RIFF"          파일의 종류가 RIFF 파일을 의미

     - DWORD  4   FILE SIZE       현재부터 끝까지의 파일크기

                                  (파일크기-4Byte 또는, 데이터 크기

                                   +40Byte)

     - Char   4   "WAVE"          Wave 파일을 의미

   

 2) FMT sub-chunk

     - Char   4   "fmt "          FMT sub-chunk의 시작

     - DWORD  4   16              현재 포인터(16 Byte)

     - short  2   wFormatTag      PCMWAVEFORMAT의 값

                                  ( 1:Wave Format이 PCM 방식 )

     - short  2   nChannels       채널 수 ( 1:모노, 2:스테레오 )

     - DWORD  4   nSamplesPerSec  샘플링 수

                                  ( 11kHz:11025,

                                    22kHz:22050,

                                    44kHz:44100 )

     - DWORD  4   nAvgBytesperSec 초당 샘플바이트

                                  ( nSamplesPerSec*BlockAlign )

     - short  2   BlockAlign      샘플당 바이트( nChannels*비트/8 )

     - short  2   wBitsPerSample  샘플당 비트수

 

   3) Data sub-chunk

     - Char   4   "data"          데이터청크의 시작

     - DWORD  4   DATA SIZE       데이터의 크기

                  DATA            데이터

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

 

 WAVE 파일들을 EDITOR 로 읽어보면 파일의 내용이 아래와 같이 나온다

원래는 HEX 값이 나와야 하는데 메모장으로 읽어들였더니..아래처럼 나

왔다.. -_-; 그래도 상관없다. 중요한건 제일 처음 " RIFF " 라는 단어

로 시작한다는 것이다. 그 다음에 WAVE 파일 포맷임을 알리는 " WAVE "

라는 단어가 온다. 보통 파일포맷에 따라 헤더에 해당 파일포맷임을 알

리는 식별자가 오는데 GIF나 PCX, 또는 EXE 파일들을 한번씩 열어 보면

같은 단어로 시작한다는 것을 알 수 있을 것이다.

 

[1] RIFF$?WAVEfmt    D쵆X   data? ...

 

[2] RIFF?$WAVEfmt    D??  data? ...

 

[3] RIFF?  WAVEfmt         D   ?     PAD ?                                                                                  

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

 3 개의 WAVE 파일들을 열어봤는데, 모두 RIFF .. WAVE .. fmt 로 시작

하고 있다.이 파일들이 WAVE 파일임을 알 수 있는 것이다. 사실 WAVE파

일에도 ADPCM 이니 PCM 이니 하는 식으로 다양한?포맷이 존재하는데 이

것은 쉽게 생각하면 압축방식의 차이다.  여기서 RIFF 파일 형식이라는

말이 나오는데, RIFF에 대해서 설명하자면..

           

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

[ RIFF 파일(Resource Interchange File Format) ]

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

음성이나 비디오 같은 유의 데이터들은 용량이 매우 크기 때문에  이를

저장할 시에는 비트 단위보다는 블록 단위로 저장을 하게 된다. 이러한

블록은 가변적인 크기를 가질 수 있는데 이를 위해서는 데이터 블록 앞

에 헤더를 사용해 이를 정의해 주어야 한다. 일례로 10 MByte 의  음성

데이터를 파일에 저장 하려할 때 블록 단위로 하지 않으면 데이터를 불

러오는데 10M의 메모리가 필요하게 된다. 이렇게 된다면 불러오기도 힘

들 뿐만 아니라 불러오는데 걸리는 시간이 많이 걸리는 단점이 생긴다.

그러나 10M의 음성데이터를 0.5M 씩 블록으로 나누어 저장한다면 20 개

의 블록을 가질 것이다. 즉, 0.5M씩 메모리에 불러온 후 출력하고,  메

모리를 해제한 후 다시 다음 블록을 불러오면 그만큼 메모리도 절약 할

수 있어 매우 편리하게 된다. 또한 데이터 저장 블록 앞에 블록의 데이

터 크기를 넣어주는데, 이는 예를 들자면 어느 시간동안 모노로 듣다가

후에 스테레오로 들을 수 있는 상황 등에 대처하기 쉽다. 이럴 때 데이

터 블록 앞에 데이터에 대한 정보를 만들어 준다. 각각의 부분 하나 하

나는 청크(Chunk)라고 하고 처음에 나오는 상자를 부모 청크,그 하단에

위치하는 부분을 자식 청크, 데이터들은 데이터 청크라고 한다.이와 같

은 구성 데이터를 저장하는 방식을 RIFF라 하고 위와 같은 구성으로 저

장된 데이터를 RIFF 파일이라 한다.  WAVE  파일이나 AVI  파일이 바로

RIFF 파일이다.

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

 

 다음으로 알아두면 앞으로 강좌를 이해하기에 좋을 압축방식들에 대한

정보들을 소개한다.

 

[ PCM(Pulse Code Modulation) 방식 ]

 

이 방식은 가장 널리 사용되는 방식으로서 음성을 아날로그에서 디지털

로 변환하여 양자화(작은 단위화)한 데이터를 그대로 저장한 후 재생할

때에는 그 데이터를 디지털에서 아날로그로 재변환하여 음성 파형을 만

든다. 이 방식은 양자화를 할 때 생기는 오차가 존재하지만 재생 시 상

당히 우수한 품질을 가진다.이 방식의 특징은 제로 크로스의 방법에 비

해 생성되는 데이터의 양이 많다는 점이다.예를 들어, 샘플링 주파수를

8Khz로 하고 양자화 시 정밀도를 8bit로 하면 8000 * 8 = 64000/sec =

64Kbit/sec로 초당 64KB가 생성된다. 그러나 최근에는 메모리의 가격이

많이 떨어지고 있어서 뛰어난 음성 품질을 보장할수 있는 PCM방식을 많

이 사용하고 있다.

 

 

[ DM(Delta Modulation) 방식 ]

 

DM 방식은 제로 크로스 방식과 PCM 방식의 중간적인 형태로 볼 수 있다

이 방식은 어느 시점n의 파고와 바로 전 시점 (n-1)의 파고를 비교하여

그 차이점을 1,0,-1로 표현한다. DM 방식의 단점은 원파형의 급격한 변

화를 따라가지 못한다는 것이다. DM 방식의 하드웨어 구현은 바로 이전

값에 1 또는 -1을 더하기만 하면 되므로 아주 간단하다.

 

 

[ DPCM(Differencial PCM) 방식 ]

 

우리의 음성 파형을 실제로 보면, 서로 인접한 샘플링 시점의 비교에서

파형이 크게 변하지 않는다. 이점에 착안하여 만든 방식이 DPCM 방식이

다. DPCM 방식은 개선된 PCM 방식이라 할 수 있다. 즉,  PCM 방식은 파

고 값을 그대로 저장하지만 DPCM  방식은 이전의 값과의 차이만을 저장

하는 것이다.  음성의 파형이 크게 변하지 않으므로 차이값도 작아져서

bit-rate를 낮출 수 있다.

 

[ ADPCM(Adaptive Differencial PCM) 방식 ]

 

ADPCM 방식은 위의 여러 가지 방식의 단점을 보완한 것이다. DM 방식이

나 DPCM 방식은 압축된 비트수로 표현 되는 최대의 변화량이 실제 파형

의 변화량보다 작기 때문에 실제로 구현하면 재생 파형이 원 파형의 급

격한 변화를 나타내지 못한다. 이를 막기 위하여 양자화할때 시간 간격

을 작게 하면 bit-rate를 증가시키는 결과를 가져오게 된다.ADPCM 방식

은 파형의 변화량이 급격히 변할 때는 양자화 할 때의 단위를 크게하여

차분값을 이용하는 것으로 파형의 진폭이 클경우 약간의 잡음이 있어도

사람이 잘 감지하지 못하는 점을 이용한 것이다.

 

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

 

 

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

 이상으로 첫번째 강좌?를 마치겠습니다. 틀린부분이 있으면,저의 개인

이메일 주소( lovesgh@empal.com )로 메일 주세요(스팸은 제발..싫어!)

첫번째 강좌?에서는 간단하게 여러가지 WAVE 파일 포맷과 또 파일 구조

등에 대해서 알아봤는데, 이것 들은 앞으로 미디어 재생기를 제작 할때

꼭 필요한 이론적 배경지식이 되므로 꼭 숙지?하셔야 합니다.두번째 강

좌?에서는 이제 실제로 WAVE 파일들을 불러다가 그 내부 정보를 파헤쳐

보는 시간을 갖도록 하겠습니다..

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

 

이 글에 평점 주기:  

 

  2002-06-12 오후 1:52:47   /  번호: 4377  / 평점:  (7.0) category: Sound  /  조회: 3,373 
 [API] 저수준 제어를 이용한 WAVE 재생 - 02 트론의 유령 / lovesgh  
트론의 유령님께 메시지 보내기  트론의 유령님을 내 주소록에 추가합니다.  트론의 유령님의 블로그가 없습니다  

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

 

 WAVE 파일을 재생하기 위해서는 일단 WAVE 파일에 대한 정보를 알아내

야 하는데, 첫번째 강좌에서 설명한 방식대로 각각의 청크 부분 만큼을

떼어내서 읽어내도 되지만, 유령넘이 그렇게 ( 도스 콘솔모드 상태에서

써볼려고.. ) 해봤는데, 결국 십이지장만 꼬이는 결과가 나왔다.안되는

경우도 있는데, 100% 그 방식을 따르지 않는 일부 불량? WAVE파일도 있

거니와 또 다른 방식의 WAVE 도 있고, 구구절절 설명하자면 또 다른 강

좌를 해야 하므로, 일단은 아래와 같은 구조체를 익혀두는 것이 십이지

장이 꼬이지 않는 편안한 삶의 지름길이라 생각하고 유령넘말 믿어보고

한번 살펴보자.

 

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

 

[ WAVEFORMATEX ]

 

typedef struct {

 

    WORD wFormatTag;       // 웨이브 폼 형식 = WAVE_FORMAT_PCM

    WORD nChannels;        // 채널 수 : 1 = Mono ,  2 = Stereo

    DWORD nSamplesPerSec;  // 샘플링 비율

    DWORD nAvgBytesPerSec; // 초당 Byte 수

    WORD nBlockAlign;      // 블록 정렬

    WORD wBitsPerSample;   // 샘플당 Bit 수 = 8 , 16

    WORD cbSize;           // PCM의 경우는 0

 

} WAVEFORMATEX;          

 

 위의 구조체는 샘플링비율(nSamplesPerSec),샘플크기(wBitsPerSample)

및 모노 스테레오를 지정할 수 있다.  이 구조체에 있는 정보를 가지고

앞으로 WAVE 파일을 마구마구 주물러댈 것 이다. .. -_-a ?? 일단 각각

의 멤버들에 대한설명은 위의 주석만으로도 충분할 것이고.. 실제 사용

법에 대해서 짤막한 소스로 알아보도록 하겠다.

 

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

 

MMCKINFO mmCkInfoRIFF;

MMCKINFO mmCkInfoChunk;

HMMIO hMMIO;

DWORD waveSize;        //-- Wave Data(순수 WaveData)의 총 길이

WAVEFORMATEX waveFormatEx; //-- WAVEFORMATEX 구조체 선언

 

 

//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                              ||

//-- int READ_CHUNK(OPENFILENAME OFN)                           ||

//--------------------------------------------------------------||

//                                                              ||

//-- OPENFILENAME OFN-OFN.lpstrFile 로 전달 받은 파일의 위치에서||

//                      WAVE 파일의 Chunk 헤더를 읽는다         ||

//                                                              ||

//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

int READ_CHUNK(OPENFILENAME OFN)

{

    // Open the wave file.

    hMMIO = mmioOpen(OFN.lpstrFile, NULL, MMIO_READ |

             MMIO_ALLOCBUF);

    if (hMMIO == NULL)  

        return FALSE;

    

    // OFN.lpstrFile 에는 지정해둔 디렉토리의 파일위치 경로가

    // 저장되어 있다.  int READ_CHUNK (OPENFILENAME OFN) 함수

    // 헤더에서 보면 OPENFILENAME OFN 으로 파일정보를 전달 받

    // 도록 되어있다. ( FILE_OPEN을 해서 파일위치만 전달해 주

    // 면 되는데, 다른 언어에서는 어떻게 처리하는지 잘 모르겠

    // 다 -_-;; 아무튼 파일의 위치정보만 전달해 주면 된다. 파

    // 일이 있는 디렉토리의 절대경로와 파일 이름(확장자 포함)

    // mmioOpen 함수로 해당위치에서 WAVE 파일을 읽어온다.실패

    // 할 경우에는 FALSE를 리턴해 준다. mmioOpen 의 각 인자는

    // 다음과 같다.

    //

    // HMMIO mmioOpen (

    //  LPSTR szFilename,       

    //  LPMMIOINFO lpmmioinfo,  

    //  DWORD dwOpenFlags       

    // );

 

 

    // RIFF chunk를 찾아간다.

    mmCkInfoRIFF.fccType = mmioFOURCC ('W', 'A', 'V', 'E');

    if (mmioDescend(hMMIO, &mmCkInfoRIFF, NULL, MMIO_FINDRIFF)

        != MMSYSERR_NOERROR)

        

        return FALSE;

    

    // format chunk를 찾아간다.

    mmCkInfoChunk.ckid = mmioFOURCC ('f', 'm', 't', ' ');   

    if (mmioDescend(hMMIO, &mmCkInfoChunk, &mmCkInfoRIFF,

        MMIO_FINDCHUNK) != MMSYSERR_NOERROR)

        

        return FALSE;

                

    // WAVEFORMATEX 구조체로 부터 포맷정보를 읽어온다.

    if (mmioRead(hMMIO, (char*)&waveFormatEx,

        sizeof(WAVEFORMATEX)) == -1)

        

        return FALSE;

    

    // Ascend out of the format chunk.

    if (mmioAscend(hMMIO, &mmCkInfoChunk, 0)

        != MMSYSERR_NOERROR)

        return FALSE;

    

    // data chunk로 찾아간다.

    mmCkInfoChunk.ckid = mmioFOURCC ('d', 'a', 't', 'a');

    if (mmioDescend(hMMIO, &mmCkInfoChunk, &mmCkInfoRIFF,

        MMIO_FINDCHUNK) != MMSYSERR_NOERROR)

        return FALSE;

 

    // 여기까지의 처리가 헤더청크를 읽는 부분이다.이제 남은 처

    // 리는 본 데이타(사운드 부분)를 읽어서 재생시키는 일이다.

 

 

    // WAV 데이터의 크기를 저장해둔다.

    waveSize = mmCkInfoChunk.cksize;

 

    return TRUE;

}

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

 

 휴.. 정말 머리가 아프다. 아무튼 이제 WAVE 파일에서 앞대가리 쓸모?

없는 부분은 다 읽어냈다. 이 이후에 프로그래밍에서 WAVE 파일에 대한

정보가 필요하면 위에서 설명한  WAVEFORMATEX 구조체를 이용해서 해당

값들을 읽어와서 화면에 뿌려주기만 하면 되는 것이다.

 

[ WAVEFORMATEX ]

 

typedef struct {

 

  WORD wFormatTag;       // 웨이브 폼 형식 = WAVE_FORMAT_PCM

  WORD nChannels;        // 채널 수 : 1 = Mono ,  2 = Stereo

  DWORD nSamplesPerSec;  // 샘플링 비율

  DWORD nAvgBytesPerSec; // 초당 Byte 수

  WORD nBlockAlign;      // 블록 정렬

  WORD wBitsPerSample;   // 샘플당 Bit 수 = 8 , 16

  WORD cbSize;           // PCM의 경우는 0

 

} WAVEFORMATEX;    

 

 예를 들어,현재 WAVE파일이 몇 비트로 샘플링 되어있는지 알고 싶다면

출력지정자 ( WAVEFORMATEX.wBitsPerSample 를 출력하면 된다. ); 나머

지도 같은 방법으로 출력해서 보여줄 수 있다. WAVE 파일 등록정보라고

나오는 메뉴를 구성할 때 쓰기 바란다.

 

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

 

 두번째 강좌? 끝.. 한꺼번에 너무 많이 하면 하는 사람도 보는 사람도

지례 질려버릴지 모른다는.. 핑계로 막아두고.. ^_^ 놀러가야 겠습니다

던젼시즈도 해야되고.. 룰루.. 일단은 두번째 강좌까지만으로도 WAVE파

일에 대한 정보들은 읽어서 볼수가 있기 때문에, 여러분 스스로 WAVE파

일 등록정보 뷰어 같은 간단한 프로그램 하나씩 만들어 보세요........

 

 다음 세번째 강좌?는 이제 실제로 WAVE파일을 돌려보면서 소리를 들어

봐야 하는데, 첫번째 강좌에서 얘기 했다시피 저수준 오디어 제어를 목

표로 하는 강좌이기 때문에.. 조금 어렵??습니다. 다음 강좌는 링 버퍼

시스템과 WAVEHDR 구조체에 대해서 알아볼텐데,더블버퍼링 기법과 같은

것들은 지금처럼 WAVE 재생 뿐만 아니라, 게임에서의 그래픽 처리 같은

곳에서도 자주 사용하는 기법이므로,잘 봐두시면 오래오래 행복하게 잘

살지도 모릅니다.

 

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

이 글에 평점 주기:  

 

2002-06-12 오후 1:55:11   /  번호: 4378  / 평점:  (9.0) category: Sound  /  조회: 3,103 
 [API] 저수준 제어를 이용한 WAVE 재생 - 03 트론의 유령 / lovesgh  
트론의 유령님께 메시지 보내기  트론의 유령님을 내 주소록에 추가합니다.  트론의 유령님의 블로그가 없습니다  

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

 저번 강좌에서는 WAVE파일을 불러다가 정보들을 살펴보고 WAVE 파일의

구조를 알아봤는데, 이제는 실제로 소리를 내보도록 하자. WAVE 파일을

불러다가 실제로 스피커에서 소리가 나오도록 하기위해서는, waveOut..

으로 시작하는 함수들을 사용한다. 이와 반대로 waveIn..으로 시작하는

함수들은 녹음(그냥, 쉬운 표현으로 '녹음'이라 하겠습니다)할 때 사용

하는 것이다. waveOut..에 대한 함수들을 대충 살펴보자면 다음과 같이

 

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

waveOutClose

waveOutGetDevCaps

waveOutGetErrorText

waveOutGetID

waveOutGetNumDevs

waveOutGetPitch

waveOutGetPitchRate

waveOutGetPosition

waveOutGetVolume

waveOutMessage

waveOutOpen

waveOutPause

waveOutPrepareHeader

waveOutProc

waveOutReset

waveOutRestart

waveOutSetPitch

waveOutSetPlaybackRate

waveOutSetVolume

waveOutUnprepareHeader

waveOutWrite

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

 

여러개가 있다. 헉.. 너무 많다고? -_-; 그러나, 걱정하지 않아도 된다

사실상 지금 수준에서 (WAVE 재생을 목표로 한) 사용할 함수는 몇 가지

안되기 때문이다. 이 함수들을 이용해서 WAVE 파일들을 열고 재생 하고

멈추고 닫고 .. 이런 일들을 할 것인데, 그보다 먼저 '버퍼'에 대해 짚

고 넘어가야 할 것 같다. 저 함수들 중에서 실제로 소리를 내도록 하는

함수는 waveOutWrite 라는 넘이다. 저넘에 대해서 MSDN 을 뒤져보면 다

음처럼 나온다. (제길.. 한글로는 안 나오나..)

 

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

[ waveOutWrite ]

 

The waveOutWrite function sends a data block to the given waveform

-audio output device.

 

고수님의 바른 번역 : WaveOutWrite함수는 주어진 웨이브폼으로 데이터

                     블록을 전송한다..

 

 

MMRESULT waveOutWrite (

    

    HWAVEOUT hwo,  

    LPWAVEHDR pwh,

    UINT cbwh      

 

);

 

[Parameters]

hwo  - Handle to the waveform-audio output device.

pwh  - Pointer to a WAVEHDR structure containing information about

       the data block.

cbwh - Size, in bytes, of the WAVEHDR structure.

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

 

다른 것 볼 필요없이 두번째 인자를 보자.  LPWAVEHDR pwh 라고 나오고

그에 대한 설명으로 아래와 같이 골아픈 영어로 되어 있다. ( 머리야 )

 

" Pointer to a WAVEHDR structure containing information about  the

  data block.  "

 

여기서,유령이 대충 해석해 보자면, 이런 뜻이 아닐까 한다..맞는지 잘

모르겠지만..

 

" 데이타 블록에 대한 정보를 저장하고 있는 WAVEHDR구조체의 포인터 "

 

여기서 데이타블록 이라는 넘은 그냥 다른 생각할 것 없이(머리 아프니

까!) '버퍼'와 같은 거라고 생각하자. (틀릴수도 있지만, 이 강좌는 어

디까지나 처음 시작하는 사람들을 위해 쉬운 개념으로 설명해보자는 취

지 이므로 고수분들의 양해를 바랍니다.) 사운드를 재생 시키기 위해서

는 WAVE 파일을 불러와야 하지만, 불러오는 것으로 끝나는 것이 아니라

실제로 원활한 재생을 위해서는 전체 WAVE를 여러개로 쪼개버리는 것이

좋다. '버퍼'라는 것에 대해서는 이미 다들 개념이 잡혀 있을 것이므로

구구절절한 설명은 하지 않겠다. 잘 몰라도 대충..'뭔가를 담아두는..'

이라고만 생각해도 좋다. 그럼 뭘 담느냐? 바로 잘개 쪼개진 WAVE 파일

들의 조각이다. 왜 쪼개지? ( 길가다가 괜히 실실 쪼개면 싸움 납니다)

쪼개는 이유는 여러가지가 있지만, 가장 맘에 드는 말은 역시.. ' 원활

한 재생을 위해서..' 일 것이다. 그럼 쪼개지 않으면 원활 하게 재생이

되지 않는가??유령넘이 직접 저수준 오디오 제어로 여러개로 쪼개지 않

고 파일 통째로 하나의 버퍼에 담아서 재생해 보았는데..  난리도 아니

었다. 몇 메가 ~ 몇십 메가 까지는 그럭저럭 되는데, 이것이 수백 메가

짜리 WAVE 파일을 던져줬더니만, 컴터가 투쟁을 하기 시작했다. 달래도

보고 협박도 해봤지만, 쉽게 해결이 나지 않길래.. 그러지 않기로 했다

물론.. MCI_ 계열의 고수준 제어를 한다면.. 쪼개고 말고, 더블 버퍼링

기법이고 이딴거 필요없다. RIFF 파일 포맷이 어쩌고, WAVE헤더가 어쩌

고.. 이런것도.. MCI 를 이용해서 제작해 봤는데, 그것들은 별 다른 기

법없이도, 몇백 메가짜리 파일들을 잘도 재생해 냈다.(역시..그래서 고  

수준 이라고 하는 것이다..) 그러나, 우리는 애석하게도 저수준을 지향

하므로 머리를 좀 아프게 해 줄 필요가 있다.일단 대충이라도 여러개로

쪼개야 한다! 라는 사실에는 동의?한 것으로 알겠다. 그 쪼갠 덩어리들

을 담아둘 버퍼를 설정하기 위해서는 [ WAVEHDR ] 라는 넘에 대해서 또

알아야 한다. ( 정말 더럽게 알아야 될 것도 많구만.. - 하지만 이것이

프로그래밍 하는 즐거움 아니겠습니까.. -_-a ..그렇다고 해두죠 머..)

 

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

[ WAVEHDR ]

 

typedef struct {

 

    LPSTR  lpData;                 // 데이타 버퍼에 대한 포인터

    DWORD  dwBufferLength;         // 데이타 버퍼의 길이

    DWORD  dwBytesRecorded;        // 녹음할 때 사용

    DWORD  dwUser;                 // 프로그램에서 사용

    DWORD  dwFlags;                // 플래그

    DWORD  dwLoops;                // 반복 수

    struct wavehdr_tag * lpNext;   // 예약

    DWORD  reserved;               // 예약

 

} WAVEHDR;

 

참고 : 페졸드의 [프로그래밍 윈도우즈]

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

 

이 WAVEHDR 구조체에 위에 달린 주석내용대로.. 이것저것 집어 넣어 준

다음에 이 넘으로 waveOutWrite 로 다시 집어 넣어 주면 그제서야 소리

를 내기 시작할 것이다. ( 참, 소리 한번 내기 힘들죠?? )제일 처음 인

자가 "데이타 버퍼에 대한 포인터" 이다.버퍼를 만들어서 그 버퍼에 잘

개 쪼갠 WAVE 덩어리들을 집어 넣은 다음 이 WAVEHDR 구조체에 던져 주

면 되는 것이다. 참고로 본인은 버퍼 선언을 다음과 같이 했다. ( 버퍼

를 전부 6개를 사용한다.. )

 

static PBYTE     pBuffer [6] ;

static PWAVEHDR  pWaveHdr[6] ;

 

이렇게 버퍼를 선언한 다음에는 이 버퍼를 위해 메모리를 할당 해야 한

다. 여기서는 C 에서 자주 애용하던 malloc() 를 이용해서 메모리를 잡

아 줬다.

 

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

for( int i=0; i < BufferNum; i++)  //여기서 BufferNum은 6으로 설정

{

    pBuffer[i]  = (char*) malloc (read_size);

    pWaveHdr[i] = malloc (sizeof (WAVEHDR)) ;

}

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

 

자, 여기서 궁금한 것이 있는가? (있어야 하는데..) 다음 한 줄을 살펴

보자.

 

    pBuffer[i]  = (char*) malloc (read_size);

 

read_size 라고 되어 있는데, 이것은 한번에 읽어들일 크기를 저장하고

있는 변수이름이다. (저런 형식으로 지원하는 것이 아니라 제가 임의로

설정한 것입니다.) 버퍼의 크기는 한번에 읽어들일 크기(즉, 잘개 쪼갤

크기만큼)로 설정하는 것인데, read_size는 다음과 같은 계산으로 구해

진다.

 

read_size=((waveFormatEx.nSamplesPerSec * waveFormatEx.nChannels *

           (waveFormatEx.wBitsPerSample / 8 ) / 10 ));

 

헉.. 더럽게 복잡하다고? .. 이 역시 하나하나 풀어보면 별 것 아니다.

일단 지금까지의 강좌를 제대로 이해한 사람이라면, 초당용량이 얼마나

되는지 구하는 공식쯤은 알 것이라고 본다.

 

초당 차지하는 용량 : nSamplesPerSec * nChannels * wBitsPerSample

 

인데, 끝에 나누기 8 은 이것을 Byte로 환산하기 위해서이다.(메모리를

Bit 단위가 아니라 Byte 단위로 잡을 것이기 때문이다.)그리고 또 나누

기 10을 했는데, 이것은 본인이 보다 더! 원활한 재생을 위해서 10분의

1초 (1/10 Sec..) 크기로 또 쪼갠 것이다. 즉, 버퍼 하나에는 WAVE파일

중에 사운드 부분을 1/10 초 단위로 쪼개서 저장한다는 것이다. 얼마를

쪼개던지 그것은 쪼개는 사람의 취향 이므로 별 상관 없겠지만, 일단은

10분의 1초로 쪼갰으니, 그대로 하기로 하자. 이제 버퍼도 설정했고,이

안에 쪼개버린 WAVE 덩어리도 담았고 했으니 남은 일은 이것 들을 아까

위에서 살펴본 [ WAVEHDR ] 이라는 넘에게 넘겨주는 것이다.  위에서는

 

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

typedef struct {

 

    LPSTR  lpData;                 // 데이타 버퍼에 대한 포인터

    DWORD  dwBufferLength;         // 데이타 버퍼의 길이

    DWORD  dwBytesRecorded;        // 녹음할 때 사용

    DWORD  dwUser;                 // 프로그램에서 사용

    DWORD  dwFlags;                // 플래그

    DWORD  dwLoops;                // 반복 수

    struct wavehdr_tag * lpNext;   // 예약

    DWORD  reserved;               // 예약

 

} WAVEHDR;

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

 

와 같이 여러개의 멤버가 있는데,사실상 재생을 위해서는 다 쓰지도 않

고 이번 프로젝트?에서는 다음과 같이 3개만 쓰기로 한다. ( 간단하게)

 

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

for( int i=0; i < BufferNum; i++)  //여기서 BufferNum은 6으로 설정

{

    pWaveHdr[i]->lpData          = pBuffer[i] ;

    pWaveHdr[i]->dwBufferLength  = read_size ;

    pWaveHdr[i]->dwFlags         = WHDR_DONE;

}

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

 

" 데이타 버퍼에 대한 포인터 , 데이타 버퍼의 길이 , 플래그 " 이렇게

3가지만 설정하고 사용한다.플래그에는 여러가지가 있는데, 플래그들에

대한 설명은 MSDN 참고.. 아래와 같다.

 

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

dwFlags : Flags supplying information about the buffer.

          The following values are defined:

 

[WHDR_BEGINLOOP ]

This buffer is the first buffer in a loop.  This flag is used only

with output buffers.

 

[WHDR_DONE]

Set by the device driver to indicate that it  is finished with the

buffer and is returning it to the application.

 

[WHDR_ENDLOOP]

This buffer is the last buffer in a loop.  This flag is  used only

with output buffers.

 

[WHDR_INQUEUE]

Set by Windows to indicate that the buffer is queued for playback.

 

[WHDR_PREPARED]

Set by Windows to indicate that the buffer has  been prepared with

the waveInPrepareHeader or waveOutPrepareHeader function.

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

 

영어 해석하려고 했는데,  별로 어렵지 않은 내용이라 여러분들도 능히

알아볼 수 있을거라고 믿고 싶어서 그냥 원문만 기재했다. 이렇게 모든

설정이 끝났으면.. 다 끝난것이 아니다. ㅠㅠ .. 정말 길고도 험란하기

만 하다.. [waveOutPrepareHeader] 라는 넘을 또 알아야 한다. (언제까

지 알아야만 하는가.. - 올 크리스마스까지는 재생되나? 걱정마시라...

조만간 알아야 할 것도 바닥난다. ) waveOutPrepareHeader라는 넘은 사

운드를 재생시키위해서 waveform-audio data block 을 준비해주는 녀석

이다. 역시 MSDN 의 설명..

 

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

[ waveOutPrepareHeader ]

The "waveOutPrepareHeader" function prepares a waveform-audio data

block for playback.

 

MMRESULT waveOutPrepareHeader (

 

    HWAVEOUT hwo,  

    LPWAVEHDR pwh,

    UINT cbwh      

 

);

 

[Parameters]

 

hwo :Handle to the waveform-audio output device.

pwh :Pointer to a WAVEHDR structure that identifies the data block

     to be prepared.

cbwh:Size, in bytes, of the WAVEHDR structure.

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

 

static HWAVEOUT hWaveOut ;

 

waveOutPrepareHeader ( hWaveOut, pWaveHdr[i], sizeof (WAVEHDR) ) ;

 

다음과 같이 설정하고 사용한다. 첫번째 인자로 넘겨주는 " hWaveOut "

은 HWAVEOUT형식(웨이브폼 오디오 출력의 핸들)의 변수로의 포인터이다

 

자.. 이제 길고긴 험난한 여정을 끝으로 waveOutWrite라는 넘을 불러다

가 실제로 소리를 내기 전에.. ( 아직도 알야야 할 것이 많다. )  더블

버퍼링 기법에 대해서 알고 넘어가자. 저렇게 버퍼에 쪼개진 WAVE 조각

을 담아두고 재생시키면 버퍼 재생이 다 끝난후 다시 다음 버퍼를 읽어

들이는 과정에서 음이 끊어지는 현상이 발생 할 수 있다.그렇기 때문에

버퍼를 여러 개 두고 (예를 들어 버퍼가 2개 있다고 가정 할 때)1번 버

퍼의 재생이 끝나면 바로 2번 버퍼의 재생이 들어가고(이때 1번 버퍼에

는 3번째 조각이 들어가게 된다.) 이런식으로 반복 재생을 하는 기법이

소위 "더블 버퍼링" 이라는 기법이다. 간단하게 그림으로 설명하자면..

 

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

버퍼의 번호 :   1   2   1   2   1   2   1   2   1   2   ...

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

파일의 내용 : | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | . | . | ...

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

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

 

... 원래는 전국미술대회에도 나가서 상도 타본 넘인데, 이렇게 코드로

만 그리려니까 잘 안되는군.. ㅜ_ㅠ..  어쨋든, 위에 번호는 버퍼순 이

다 버퍼의 재생 진행 순서.. 1 -> 2 -> 1 -> 2 -> 1 -> 2 ...그 아래는

버퍼에 들어갈 쪼개진 덩어리 순서이다. 대충 이해가 갔으리라 믿는다.

아래는 버퍼 설정과 WAVEHDR 설정 , 더블버퍼링 기법이 모두 구현된 함

수의 소스이다.

 

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

//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                              ||

//-- int MM_WOM_OPEN_FUNCTION (HWAVEOUT hWaveOut, int BufferNum)||

//--------------------------------------------------------------||

//                                                              ||

//-- hWaveOut 과 BufferNum을 넘겨 받는다.                       ||

//   - hWaveOut : hWaveOut 핸들                                 ||

//   - BufferNum : 버퍼의 갯수                                  ||

//                                                              ||

//-- waveOutWrite : 버퍼 재생이 끝나면  MM_WOM_DONE: 메세지를   ||

//                  발생시킨다.                                 ||

//                                                              ||

//                                            - 트론의 유령 -   ||

//                                                              ||

//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

 

int MM_WOM_OPEN_FUNCTION( HWAVEOUT hWaveOut, int BufferNum )

{

    for ( int i;=0; i < BufferNum; i++ )

    {

 

        pBuffer[i]  = (char*) malloc (read_size);

        pWaveHdr[i] = malloc (sizeof (WAVEHDR)) ;

        pWaveHdr[i]->lpData          = pBuffer[i] ;

        pWaveHdr[i]->dwBufferLength  = read_size ;

        pWaveHdr[i]->dwFlags         = WHDR_DONE;

 

        mmioRead(hMMIO, (char*) pBuffer[i], read_size);

 

        waveOutPrepareHeader ( hWaveOut, pWaveHdr[i],

                               sizeof (WAVEHDR) ) ;

    }

 

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

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

 

    return TRUE;

}

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

 

이제 이것으로 끝났으면 좋겠지만, 아직이다.. --; 정말 멀다.. 그러나

누누히 얘기 했지만, 이런 고생끝에 소리가 나면 진짜 소리가 되는것이

다. ( 내 손으로 울리게 하는 소리.. 정말 멋지지 않은가.. )조금만 더

참고 다음회를 기다리자.. (이제 이번 강좌는 여기서 끝이라는 소리다)

 

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

 

 

 

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

 

 날씨가 많이 더워졌습니다. 안그래도 짜증나는데,  이 강좌를 보니 더

짜증난다고 투덜대는 분도 많이 계실거라고 봅니다. 이 강좌가 별로 도

움이 안되는 분도 많으실테지만, 제가 바라는 것은 단 한분만이라도 이

런 미디어 재생에 관심이 있어서 자료를 찾던 중 제 강좌가 조금이라도

도움이 되어서 멋진 플레이어를 완성했으면 하는 바램입니다. 다음회에

서는 본격적으로 WINDOWS 프로그래밍쪽에 대해서 알아보겠습니다. 지금

까지는 사실상 원론?적인 내용들이었던 것에 반해 다음회의 강좌부터는

WndProc() 내부에서 펼쳐지는 온갖 잡다한 코드들에 대해서 알아보겠습

니다. 단순히 waveOutWrite( )만 불러온다고 소리가 나는 것이 아니라,

그넘과 관련된 여러메세지를 다루어야 하고 처리해 줘야 합니다.그런것

들에 대해서 알아본다는 것입니다. 강좌가 끝날때쯤에 엉성하기는 하지

만 그래도 소리는 제대로 나는 플레이어의 풀소스를 함께 공개하겠습니

다.그때까지는 혼자서 구현도 해보고 여기저기 정보도 찾아보면서 공부

하시면 정말 큰 도움이 될 것입니다.미숙한 저의 강좌를 그래도 끝까지

봐주신 여러분. 정말 감사드립니다. ( 다음 네번째 강좌는 언제 올릴까

나..)

 

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

 

이 글에 평점 주기:  

 

2002-06-12 오후 1:56:18   /  번호: 4379  / 평점:  (9.0) category: Sound  /  조회: 2,637 
 [API] 저수준 제어를 이용한 WAVE 재생 - 04 트론의 유령 / lovesgh  
트론의 유령님께 메시지 보내기  트론의 유령님을 내 주소록에 추가합니다.  트론의 유령님의 블로그가 없습니다  

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

 지금까지의 강좌로  WAVE 파일의 내부 구조라던지, 재생 원리, 버퍼의

구현등에 대해서 잘? 알게 되었을 거라고 .. 믿고 싶습니다. 이번 네번

째 강좌에서는 저번 강좌에 이어 실질적으로 윈도우 프로시저 내부에서

일어나는 메세지들을 처리하며 소리를 출력하는 과정을 살펴 보도록 하

겠습니다.

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

 

 WAVE 파일을 재생하기 위해서는 일단 파일의 위치에서 WAVE 파일을 읽

어 헤더정보를 알아내야 한다. 대략의 과정을 단순하게 표현 해 보자면

다음과 같다.

 

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

[처리]

 

파일을 여는 부분 :

 

    파일의 위치에서 파일을 읽는다.

 

    if ( 파일을 읽는데 성공했으면 )

    {       

        if ( 다른 곡이 재생 중이면 )

        {

            현재 재생 중인 곡의 버퍼를 비우고..

        }

    }

    

    return TRUE ;

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

 

 WAVE 파일을 읽는데 성공했으면 이제 PLAY 버튼을 눌렀을 때 일어나는

동작에 대해서 구현할 차례인데..

        

플레이 버튼을 눌렀음:               

    READ_CHUNK ( OFN ) ; // WAVE파일의 헤더를 읽음 ( 소스는 아래 )

 

    if ( waveOutOpen (&hWaveOut, WAVE_MAPPER, &waveFormatEx,

         (DWORD) hwnd, 0, CALLBACK_WINDOW) )

    {

        MessageBeep ( MB_ICONEXCLAMATION ) ;

        MessageBox ( hwnd, szOpenError, szAppName,

                     MB_ICONEXCLAMATION | MB_OK) ;

    }                   

    return TRUE ;

 

 

여기서 waveOutOpen( ) 함수는 MM_WOM_OPEN 메세지를 발생 시킨다는 것

이 중요하다. ( 일단 위의 구현대로라면 그렇다. ) 이제 MM_WOM_OPEN메

세지를 처리해 줘야 하는데..여기서 더블 버퍼링 기법으로 버퍼를 설정

하고 WAVEHDR 을 설정해 준 다음에 read_size 만큼씩 WAVE 조각을 읽어

온다.

 

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

 

//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                              ||

//-- int MM_WOM_OPEN_FUNCTION(HWAVEOUT hWaveOut, int BufferNum )||

//--------------------------------------------------------------||

//                                                              ||

//-- hWaveOut 과 BufferNum을 넘겨 받는다.                       ||

//   - hWaveOut : hWaveOut 핸들                                 ||

//   - BufferNum : 버퍼의 갯수                                  ||

//                                                              ||

//-- waveOutWrite : 버퍼 재생이 끝나면  MM_WOM_DONE: 메세지를   ||

//                  발생시킨다.                                 ||

//                                                              ||

//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

int MM_WOM_OPEN_FUNCTION ( HWAVEOUT hWaveOut, int BufferNum )

{           

    read_size = ( ( waveFormatEx.nSamplesPerSec * waveFormatEx.nChannels *

                  ( waveFormatEx.wBitsPerSample / 8 ) / 10 )  );

 

    if( read_size )

    {

        for( int i=0; i < BufferNum; i++)

        {

            pWaveHdr[i] = (WAVEHDR *) malloc (sizeof (WAVEHDR)) ;

            pBuffer[i]  = (unsigned char *) malloc (read_size);

            pWaveHdr[i]->lpData          = (char *) pBuffer[i] ;

            pWaveHdr[i]->dwBufferLength  = read_size ;

            pWaveHdr[i]->dwFlags         = WHDR_DONE;

 

            mmioRead(hMMIO, (char*) pBuffer[i], read_size);

            waveOutPrepareHeader (hWaveOut, pWaveHdr[i], sizeof (WAVEHDR)) ;

        }

 

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

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

    }   

    return TRUE ;

}

 

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

 

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

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

 

 

이 부분이 실질적으로 소리를 출력하는 부분이다. 각 버퍼의 재생이 끝

나면 MM_WOM_DONE 이라는 메세지가 발생하는데,MM_WOM_DONE메세지의 처

리는 다음과 같이 해주면 된다. ( 원래는 MM_WOM_DONE : 메세지 처리부

분에서 해결해도 되나, 본인은 외부 함수로 빼내어 처리했다.. -> 윈도

프로시저 함수가 자꾸만 불어나서.. -_-a .. 터질까봐.. )

 

 

--[ MM_WOM_DONE_FUNCTION ]----------------------------------------

 

//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                              ||

//-- int MM_WOM_DONE_FUNCTION (HWAVEOUT hWaveOut,int BufferNum) ||

//--------------------------------------------------------------||

//                                                              ||

//                                                              ||

//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

int MM_WOM_DONE_FUNCTION(HWND hwnd,HWAVEOUT hWaveOut,int BufferNum)

{

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

 

    if ( ( mRet == -1 )  || ( mRet == 0 ) ) // 1 이면 에러 || 0 이면 파일 끝

    {

        waveOutClose ( hWaveOut ) ;

        return FALSE;

    }

    

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

 

    CNT++;             // 전체 버퍼 카운터 증가

    CNT %= BufferNum ; // 버퍼가 차례대로 돌아 가도록..

 

    return TRUE ;

}

 

버퍼 재생을 끝마쳤으면,  waveOutClose ( )를 이용해서 할당한 메모리

를 반납해주는 마무리 작업을 해줘야 하는데,  이 역시 외부 함수로 빼

내었다.

 

--[ MM_WOM_CLOSE_FUNCTION 의 소스 ]-------------------------------

 

//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                              ||

//-- int MM_WOM_CLOSE_FUNCTION (HWAVEOUT hWaveOut,int BufferNum)||

//--------------------------------------------------------------||

//                                                              ||

//                                                              ||

//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

int MM_WOM_CLOSE_FUNCTION ( HWAVEOUT hWaveOut,int BufferNum )

{

    for ( int i=0; i < BufferNum; i++ )

    {

        waveOutUnprepareHeader ( hWaveOut, pWaveHdr[i],

                                 sizeof ( WAVEHDR ) ) ;

        free ( pWaveHdr[i] ) ;

        free ( pBuffer[i] ) ;

    }           

   

    mioClose ( hMMIO, 0 ) ;

    return TRUE ;

}

 

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

 

이런식으로 크게 3개의 메세지를 처리하면서  WAVE 파일이 재생이 반복

처리된다. 이번 강좌는 본인이 짠 함수 위주로 소스기재에 치우친 경향

이 있는데,사실 메세지를 처리하는 부분을 그냥 글로 설명하는 것 보다

는 실제로 어떤 함수들이 어떻게 연결되고 동작하는지를 보여주는 것이

훨씬 이해가 빠를 것이라 생각해서 소스 위주로 기재했다.  이제  모든

강좌는 끝이 났다. 여기 기재한 소스만으로도 재생을 위한 플레이어 정

도는 충분히 만들 수 있을 것이라 믿는다. 보너스?로 다음 회에는 소스

전량을 기재한다. -> 아직 완성된 것이 아니고.. 취미삼아 만들어 보는

수준이므로 여러가지 문제점 ( 본인이 발견한 문제점만도 여러가지.. )

이 있을 수 있으므로.. 이걸 해결하는 것은 여러분의 몫이다.( ^_^.. )

 

 

--[ READ_CHUNK 의 소스 ]------------------------------------------

 

//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                              ||

//-- int READ_CHUNK ( OPENFILENAME OFN )                        ||

//--------------------------------------------------------------||

//                                                              ||

//-- OPENFILENAME OFN- OFN.lpstrFile로 전달 받은파일의 위치에서 ||

//                     WAVE 파일의 Chunk 헤더를 읽는다          ||

//                                                              ||

//||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

int READ_CHUNK ( OPENFILENAME OFN )

{

    // Open the wave file.

    hMMIO = mmioOpen ( OFN.lpstrFile, NULL, MMIO_READ | MMIO_ALLOCBUF);

    if (hMMIO == NULL)  

        return FALSE;

    

    // Descend into the RIFF chunk.

    mmCkInfoRIFF.fccType = mmioFOURCC ('W', 'A', 'V', 'E');

    if ( mmioDescend(hMMIO, &mmCkInfoRIFF, NULL, MMIO_FINDRIFF) != MMSYSERR_NOERROR)

        return FALSE;

    

    // Descend into the format chunk.

    mmCkInfoChunk.ckid = mmioFOURCC ('f', 'm', 't', ' ');   

    if ( mmioDescend ( hMMIO, &mmCkInfoChunk, &mmCkInfoRIFF, MMIO_FINDCHUNK)

         != MMSYSERR_NOERROR)

        return FALSE;

                    

    // Read the format information into the WAVEFORMATEX structure.

    if (mmioRead(hMMIO, (char*)&waveFormatEx, sizeof(WAVEFORMATEX)) == -1)

        return FALSE;

 

    // Ascend out of the format chunk.

    if (mmioAscend(hMMIO, &mmCkInfoChunk, 0) != MMSYSERR_NOERROR)

        return FALSE;

    

    // Descend into the data chunk.  

    mmCkInfoChunk.ckid = mmioFOURCC ('d', 'a', 't', 'a');

    if ( mmioDescend(hMMIO, &mmCkInfoChunk, &mmCkInfoRIFF, MMIO_FINDCHUNK)

         != MMSYSERR_NOERROR)

        return FALSE;

    

    // WAV 데이터의 크기를 저장해둔다.

    waveSize = mmCkInfoChunk.cksize;

    

    return TRUE;

}

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

 

 

 

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

 

안녕하십니까!? 어설픈 WAVE 재생에 관한 강좌?를 끝내고.. 어떠셨는지

모르겠습니다. 본인도 아직 윈도우 프로그래밍의 달인은 커녕 걸인수준

도 안되는 관계로 여러가지 미흡한 부분도 있고, 글을 쓰면서도 마음속

에서는 계속해서.. ' 이게 아닌데.. -_- ' 하는 후회막급..  다음 강좌

에는 [ 시스템 볼륨 조절기 ] 나, 피크 메타 제작법, WAVE 파일을 읽어

들여 파형(웨이브 곡선) 그리기.. 등에 대한 강좌를 쓸까도 생각중이지

만, 일단은 반응을 살펴 본 뒤에.. ^_^ 쓰기로 하겠습니다.어떤 사람에

게는 쓰레기가 어떤 사람에게는 보물이 된다고 하죠.제 강좌가 어떤 분

들에게는 정말 쓰레기 같이 쓸모 없는 것일지라도.., 또 어떤 분에게는

( 단 한 사람이라도 )정말 중요한 자료가 되었으면 하는 바램으로 이번

강좌를 마치겠습니다. 감사합니다..!! ... 아싸.. 이제 좀 놀아야지...

 

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

 

                                                   - 트론의 유령 -

 

이 글에 평점 주기:  

 

2002-06-12 오후 1:57:06   /  번호: 4380  / 평점:  (8.0) category: Sound  /  조회: 3,680 
 [API] 저수준 제어를 이용한 WAVE 재생 - 05 [ 끝 ] 트론의 유령 / lovesgh  
트론의 유령님께 메시지 보내기  트론의 유령님을 내 주소록에 추가합니다.  트론의 유령님의 블로그가 없습니다  

#include

#include

#include

#include "resource.h"

 

 

int CNT=0;

 

 

//-- WndProc 및 외부 변수 선언

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

HINSTANCE g_hInst;

TCHAR szAppName[] = TEXT ("WAVE PLAYER II");

 

 

//-- 버퍼의 갯수

const int BufferNum = 6;

 

 

//-- Wave 파일 재생시 Double Buffering 구현을 위한 버퍼

static PBYTE        pBuffer[6] ;

static PWAVEHDR     pWaveHdr[6] ;

 

 

//-- Wave 파일에서 각 버퍼마다 한번에 읽어들일 크기

LONG read_size;

 

 

//-- Wave 처리용 MMIO

MMCKINFO mmCkInfoRIFF;

MMCKINFO mmCkInfoChunk;

HMMIO hMMIO;

DWORD waveSize;

WAVEFORMATEX waveFormatEx;

 

 

//-- 파일 처리(읽기)

OPENFILENAME OFN;

char lpstrFile[MAX_PATH]="";

char FileLocation[100];

 

 

static BOOL bRecording, bReverse, bPaused, bEnding, bPlaying,

            bTerminating ;

 

 

static DWORD dwDataLength ;

static TCHAR szOpenError[] = TEXT ("Error opening waveform audio!");

static TCHAR szMemError [] = TEXT ("Error allocating memory!") ;

static BOOL bMute ;

 

static HWAVEIN  hWaveIn ;

static HWAVEOUT hWaveOut ;

 

LONG mRet;  

 

 

 

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                                 ||

//-- int READ_CHUNK ( OPENFILENAME OFN )                           ||

//-----------------------------------------------------------------||

//                                                                 ||

//-- OPENFILENAME OFN - OFN.lpstrFile 로 전달 받은 파일의 위치에서 ||

//                      WAVE 파일의 Chunk 헤더를 읽는다            ||

//                                                                 ||

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

int READ_CHUNK ( OPENFILENAME OFN )

{

    // Open the wave file.

    hMMIO = mmioOpen(OFN.lpstrFile, NULL, MMIO_READ | MMIO_ALLOCBUF);

    if (hMMIO == NULL)  

        return FALSE;

    

    // Descend into the RIFF chunk.

    mmCkInfoRIFF.fccType = mmioFOURCC ('W', 'A', 'V', 'E');

    if (mmioDescend(hMMIO, &mmCkInfoRIFF, NULL, MMIO_FINDRIFF) != MMSYSERR_NOERROR)

        return FALSE;

    

    // Descend into the format chunk.

    mmCkInfoChunk.ckid = mmioFOURCC ('f', 'm', 't', ' ');   

    if (mmioDescend(hMMIO, &mmCkInfoChunk, &mmCkInfoRIFF, MMIO_FINDCHUNK)

        != MMSYSERR_NOERROR)

        return FALSE;

                    

    // Read the format information into the WAVEFORMATEX structure.

    if (mmioRead(hMMIO, (char*)&waveFormatEx, sizeof(WAVEFORMATEX)) == -1)

        return FALSE;

    

    // Ascend out of the format chunk.

    if (mmioAscend(hMMIO, &mmCkInfoChunk, 0) != MMSYSERR_NOERROR)

        return FALSE;

    

    // Descend into the data chunk.

    mmCkInfoChunk.ckid = mmioFOURCC ('d', 'a', 't', 'a');

    if (mmioDescend(hMMIO, &mmCkInfoChunk, &mmCkInfoRIFF, MMIO_FINDCHUNK)

        != MMSYSERR_NOERROR)

        return FALSE;

    

    // WAV 데이터의 크기를 저장해둔다.

    waveSize = mmCkInfoChunk.cksize;

 

    return TRUE;

}

 

 

 

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                                 ||

//-- int MM_WOM_OPEN_FUNCTION ( HWAVEOUT hWaveOut, int BufferNum ) ||

//-----------------------------------------------------------------||

//                                                                 ||

//-- hWaveOut 과 BufferNum을 넘겨 받는다.                          ||

//   - hWaveOut : hWaveOut 핸들                                    ||

//   - BufferNum : 버퍼의 갯수                                     ||

//                                                                 ||

//-- waveOutWrite : 버퍼 재생이 끝나면  MM_WOM_DONE: 메세지를      ||

//                  발생시킨다.                                    ||

//                                                                 ||

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

int MM_WOM_OPEN_FUNCTION ( HWAVEOUT hWaveOut, int BufferNum )

{           

        read_size = ( ( waveFormatEx.nSamplesPerSec * waveFormatEx.nChannels *

            ( waveFormatEx.wBitsPerSample / 8 ) / 10 )  );

 

        if( read_size )

        {

            //MessageBox(hwnd, "MM_WOM_OPEN_FUNCTION ", "Message!", MB_OK);

    

            for( int i=0; i < BufferNum; i++)

            {

                // Allocate memory for wave header

                pWaveHdr[i] = (WAVEHDR *) malloc (sizeof (WAVEHDR)) ;

 

                pBuffer[i]  = (unsigned char *) malloc (read_size);

                

                pWaveHdr[i]->lpData          = (char *) pBuffer[i] ;

                pWaveHdr[i]->dwBufferLength  = read_size ;

                pWaveHdr[i]->dwFlags         = WHDR_DONE;

 

                mmioRead(hMMIO, (char*) pBuffer[i], read_size);

 

                waveOutPrepareHeader (hWaveOut, pWaveHdr[i], sizeof (WAVEHDR)) ;

            }

 

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

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

            

        }   

        return TRUE ;

}

 

 

    

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                                 ||

//-- int MM_WOM_DONE_FUNCTION ( HWAVEOUT hWaveOut, int BufferNum ) ||

//-----------------------------------------------------------------||

//                                                                 ||

//                                                                 ||

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

int MM_WOM_DONE_FUNCTION ( HWND hwnd, HWAVEOUT hWaveOut , int BufferNum )

{

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

 

    if ( ( mRet == -1 )  || ( mRet == 0 ) ) // 1 is Error || 0 is File End

    {

        waveOutClose ( hWaveOut ) ;

        return FALSE;

    }

        

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

 

    CNT++;

 

    CNT %= BufferNum ;

 

    bEnding = FALSE ;

    bPlaying = TRUE ;

    

    return TRUE ;

}

 

 

 

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                                 ||

//-- int MM_WOM_CLOSE_FUNCTION (HWAVEOUT hWaveOut, int BufferNum ) ||

//-----------------------------------------------------------------||

//                                                                 ||

//                                                                 ||

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

int MM_WOM_CLOSE_FUNCTION ( HWAVEOUT hWaveOut,int BufferNum )

{

 

    for ( int i=0; i < BufferNum; i++ )

    {

        waveOutUnprepareHeader ( hWaveOut, pWaveHdr[i], sizeof ( WAVEHDR ) ) ;

        free ( pWaveHdr[i] ) ;

        free ( pBuffer[i] ) ;

    }           

    mmioClose ( hMMIO, 0 ) ;

        

    //MessageBox(hwnd, "MM_WOM_CLOSE_FUNCTION ", "Message!", MB_OK);

 

    bPaused = FALSE ;

    bPlaying = FALSE ;

    bEnding = TRUE ;

                

    /*

    if (bTerminating)

        SendMessage (hwnd, WM_SYSCOMMAND, SC_CLOSE, 0L) ;

    */  

    return TRUE ;

}

 

 

 

 

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                                 ||

//-- int PLAY_PAUSE ( HWAVEOUT hWaveOut )                          ||

//-----------------------------------------------------------------||

//                                                                 ||

//-- 재생 중 잠시 멈춤 기능                                        ||

//                                                                 ||

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

int PLAY_PAUSE ( HWAVEOUT hWaveOut )

{

 

    // Pause or restart output          

    if ( !bPaused )

    {

        waveOutPause ( hWaveOut ) ;

        bPaused = TRUE ;

    }

    else if ( bPaused )

    {

        waveOutRestart ( hWaveOut ) ;

        bPaused = FALSE ;

        //MessageBox(hwnd, "bPaused = FALSE", "Message", MB_OK );

    }

    

    return TRUE ;

}

 

 

 

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                                 ||

//-- int PLAY_END ( HWAVEOUT hWaveOut )                            ||

//-----------------------------------------------------------------||

//                                                                 ||

//-- 재생 중지                                                     ||

//                                                                 ||

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

int PLAY_END ( HWAVEOUT hWaveOut )

{

    waveOutReset ( hWaveOut ) ;

    waveOutClose ( hWaveOut ) ;

 

    bEnding = TRUE ;

 

    return TRUE ;

}

 

 

 

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                                 ||

//  LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM)          ||

//                                                                 ||

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

 

LRESULT CALLBACK WndProc (HWND hwnd,UINT iMessage,WPARAM wParam,LPARAM lParam)

{    

    switch(iMessage)

    {

        case WM_COMMAND:

            switch (LOWORD (wParam))

            {

                case IDC_FILEEXIT:

                    PostQuitMessage(0) ;

                    return 0;

        

                case IDC_FILEOPEN:

                    memset(&OFN, 0, sizeof(OPENFILENAME)) ;

                    OFN.lStructSize = sizeof(OPENFILENAME) ;

                    OFN.hwndOwner=hwnd ;

                    OFN.lpstrFilter="WAV 파일(*.WAV)\0*.WAV\0" ;

                    OFN.lpstrFile=lpstrFile ;

                    OFN.nMaxFile=MAX_PATH ;

 

                    if ( GetOpenFileName(&OFN) != 0 )

                    {       

                        if ( bPlaying )

                        {       

                            waveOutReset (hWaveOut) ;

                            waveOutClose (hWaveOut) ;   

                        }                   

                    }

                    return TRUE ;

 

                case IDC_PLAY_BEG:              

                READ_CHUNK ( OFN ) ;

                    

                    if ( waveOutOpen ( &hWaveOut, WAVE_MAPPER, &waveFormatEx,

                                       (DWORD) hwnd, 0, CALLBACK_WINDOW) )

                    {

                        MessageBeep (MB_ICONEXCLAMATION) ;

                        MessageBox (hwnd, szOpenError, szAppName, MB_ICONEXCLAMATION | MB_OK) ;

                    }                   

                    bPlaying = TRUE ;       

                

                    return TRUE ;

            

                case IDC_PLAY_PAUSE:

                    PLAY_PAUSE ( hWaveOut ) ;   

                    return TRUE ;              

              

                case IDC_PLAY_END:

                    PLAY_END ( hWaveOut ) ;

                    return TRUE ;            

 

            }

            break; //-- InSide : case WM_COMMAND:

 

        case MM_WOM_OPEN:                           

            MM_WOM_OPEN_FUNCTION ( hWaveOut, BufferNum ) ;      

            return TRUE ;           

 

        case MM_WOM_DONE:

            MM_WOM_DONE_FUNCTION ( hwnd, hWaveOut , BufferNum ) ;   

            return TRUE ;           

 

        case MM_WOM_CLOSE:  

            MM_WOM_CLOSE_FUNCTION ( hWaveOut, BufferNum ) ; 

            return TRUE ;

 

        case WM_DESTROY:        

            PostQuitMessage(0);     

            return 0;   

        }

        return(DefWindowProc(hwnd,iMessage,wParam,lParam));

}

 

 

 

 

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

//                                                                 ||

//  WinMain()                                                      ||

//                                                                 ||

//|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

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.hIcon=LoadIcon(hInstance,MAKEINTRESOURCE(IDI_ICON13));

    WndClass.hInstance=hInstance;

    WndClass.lpfnWndProc=(WNDPROC)WndProc;

    WndClass.lpszClassName=szAppName;

    WndClass.lpszMenuName=MAKEINTRESOURCE(IDR_MENU1);

    WndClass.style=CS_HREDRAW | CS_VREDRAW;

    RegisterClass(&WndClass);

 

    hwnd=CreateWindow(szAppName,szAppName, //WS_POPUPWINDOW, //

        ,WS_OVERLAPPEDWINDOW,

          0, 0, 306, 250,

          NULL,(HMENU)NULL,hInstance,NULL);

    ShowWindow(hwnd,nCmdShow);

    

    while(GetMessage(&Message,0,0,0)) {

        TranslateMessage(&Message);

        DispatchMessage(&Message);

    }

    return Message.wParam;

}

 

위의 파일을 xxxx.cpp 로 저장하신 후 Project에 추가 한 뒤에

 

[MENU]

 

IDC_FILEOPEN

IDC_FILEEXIT

IDC_PLAY_BEG

IDC_PLAY_PAUSE

IDC_PLAY_END

 

Project -> Add to Project -> Files -> xxxx.rc 추가

 

Project -> Settings -> Link -> Object/library modules -> winmm.lib 추가

 

이상의 작업 후 컴파일 하면 됩니다.

VC++ 6.0 에서 테스트 했는데, 이상없이 컴파일/작동 확인 했습니다.

 

 

이 글에 평점 주기:  

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

슈팅게임  (0) 2006.05.06
네모네모 로직  (0) 2006.05.06
음성채팅  (0) 2006.05.06
FFT  (0) 2006.05.06
wave 변환  (0) 2006.05.06