상세 컨텐츠

본문 제목

박한규님 사운드 프로그래밍 강좌

프로그래밍 관련/사운드

by AlrepondTech 2011. 1. 5. 14:55

본문

반응형

 

 

 

 

=================================

=================================

=================================

 

 

 

 
 
안녕하십니까!
저는 LegendarySoft에서 엔진 프로그래머로서 근무하고 있는 박한규라고 합니다.
사회에 첫발을 내딛은지 2개월 되었는데요. 하하하하하! 그냥 조그마한 강좌를 해드릴까 합니다.
아시는 분들께서는 가볍게 읽어 주시고, 전혀 모르셨던 분께 많은 도움이 되어 드렸으면 합니다.
제가 프로그래밍 분야중에서 가장 재밌어 하는 부분이 스크립트 컴파일러 개발과 사운드 프로그래밍
이 었습니다. 여기에 많은 동감을 느끼시는 분들께 정말로 제 작은 수고를 받아 주셨으면 합니다.
그럼 강좌를 시작하겠습니다.
본 강좌는 DirectX 5.0기준으로 한 사운드 프로그래밍입니다.
MCI를 이용한 고급 사운드 프로그래밍이 아닌 스타크 래프트의 멀티 사운드와  3D사운드의
중급 사운드 프로그래밍으로 해드리겠습니다.
본 사운드 강좌에 관심이 있으신 분들을 위하여 최선을 다하는 한규가 되겠습니다.
----------------------------------------------------------------------------

제  목:[PROG/공지] DirectSound 강좌 계획
제가 회사에서 근무하고 있는 관계로 글이 매우 허접일수도 있구요.
그리고, 많은 분량의 강좌는 못해 드립니다.
아래와 같이 강의 할 예정입니다.
[다이렉트 사운드 프로그래밍 계획표]
1. 사운드 개념
2. 다이렉트 사운드 객체
3. WAV파일에 대한 구조 이야기
4. WAV파일과 다이렉트 사운드의 조합
5. 이퀄라이져를 만들어보자. (1)
6. 이퀄라이져를 만들어보자. (2)
7. 자신만의 사운드 파일을 만들어 보자.
이렇게 7단계로서 강좌를 끝내려 합니다.
그럼 이만...


-----------------------------------------------------------------------------
제  목:[PROG/초보] DirectSound (1)
*(1). 사운드 개념
2. 다이렉트 사운드 객체
3. WAV파일에 대한 구조 이야기
4. WAV파일과 다이렉트 사운드의 조합
5. 이퀄라이져를 만들어보자. (1)
6. 이퀄라이져를 만들어보자. (2)
7. 자신만의 사운드 파일을 만들어 보자.
우리들은 흔히 사운드라하면, 효과음, 음악, 음성등을 말 할수가 있다.
효과음이라하면, 침뱉는 소리, 쇠가 부디치는 소리를 말하고,
음악이라하면, 메탈, 락, 댄스, 힙합을 말하며,
음성이라하면, 바로 우리들이 말하는 목소리들을 말한다는 것은
당연히 바보가 아닌이상 알것이다.
이처럼 우리 주위에서 나오는 모든 소리를 우린 사운드라하며, 그런 사운드가
발생하는 기본 개념이 같다는 사실을 한번쯤 생각 해보았는가?
그렇다면, 사운드가 발생하면 우린 왜 귀로 들을 수가 있는 이유는 무엇이었을까?
그렇다! 사운드는 공기의 진동이라고 말할수가 있다.
공기중에서 물체와 공기가 서로 충돌하면, 그곳에서 공기의 진동이 발생하여
전달되게 되면, 그 진동이 우리 귀에 있는 고막을 진동 시키게 한다.
고막 역시 공기 중에 있기 때문에 공기의 진동 흐름이 고막으로 들어와 고막의
작은 진동을 주게 된다. 그러므로서 우리는 그 소리를 들을수가 있는것이다.
진동이 크면 클수록 생생하게 들을수가 있고, 진동이 작으면 작을수가 작게
들린다는 것은 거리에 비례할것이고, 사운드 발생폭에 비례할것이다.
그러면, 대충 이런 사운드의 원리를 접어두고 전자적인 사운드의 원리를 알아보자.
우리가 컴퓨터에서 음악을 틀게 되면, 유일하게 들을 수있는 수단이 스피커,
이어폰,헤드폰일것이다. 여기서 우리는 스피커를 알아보자.
컴퓨터는 스피커에게 연속된 주파수 값과 소리 폭을 전달하게 된다.
그러면, 스피커에서는 그 신호를 받아 스피커 앞에서 적절하게 공기를 진동 시키게된다.
그러므로서 우리는 음악을 들을수가 있는 것이다.
(여기서 주파수는 공기의 진동속도, 소리 폭은 공기의 진동 폭)

이제 서론을 접어두고, 본론적인 전자 사운드에 대해서 알아보도록 하자.
기본적으로 사운드가 재생 되려면, 3가지의 요소가 필요하다.
(1) 주파수, (2) 비트수, (3) 스테레오 혹은 모노
이 3가지를 만족한다면, 사운드로서 인정(?) 받을수 있는 데이터라 할수 있다. ^^;
여기서 주파수라는 것은 앞서 말했듯이 진도이라 하였고,
사운드에서는 음의 표현 량이라고 한다.
그리고, 비트수라는 것은 음의 표현 폭을 말한다.

그리고, 마지막으로 스테레오 혹은 모노라는 것은 스테레오는 최소 2채널 사운드를 말하고,
모노라는 것은 1채널 사운드를 말한다.
예를 들어서 좌측 귀(1번 채널)에서는 마이클 잭슨의 노래가 우측(2번 채널)에서는
메탈리카의 노래가 흘러 나온다면, 이것이 바로 스테레오라 할수있다.
모노는 위글에서 말했듯이 채널이 오직 하나이어서 마이클 잭슨과 메탈리카의 소리가
하나로 모아져서 들릴것이다. 태널이 오직 하나이기 때문이다.
일존의 조금 합성한듯한 느낌을 받을 것이다.
그렇다면, 이것으로 사운드의 기본 3가지를 배웠다. (원래 다 아는 것일수도... ^^)
그러면, 주파수에 대해서 살펴보자.
우리 들이 흔히 사용되는 주파수는 11KHz, 22KHz, 44KHz가 있다.
(1KHz는 1000Hz와 같다는 사실 역시 알것이다. T_T)
여기서 KHz가 크면 클수록 소리 표현량이 그만큼 많아 진다는 이야기다.
즉, 여기서 Hz의 단위는 초당의 진동수를 말합니다.
예를 들어서 초당 3000번의 진동이 생기면, 이것이 바로 3000Hz라는 이야기입니다.
보통 우리가 많이 사용되는 CD음악이 바로 44KHz인데 이것은 1초당 44000번의 진동이
생긴다는 뜻입니다.
즉, 이런 진동이 달라지면서 때로는 음악처럼, 효과음처럼, 음성처럼 느껴질것입니다.
그리고, 진동이 크면 클수록 음의 표현 정보량이 많아 지므로서 그만큼 생생하고,
맑은 소리가 나올것입니다. (물론, 사운드의 종류에 따라서 다르겠지만요.)
그렇다면, 여기서 한가지의 문제를 내겠습니다.
22KHz로 녹음된 사운드 데이터를 44KHz로 재생하면 어떠케 될까요?
네에 너무 쉬운 문제였나요? ^^;
답) 소리가 매우 빨라진다. 음성으로 말하면, 도날드 덕 소리가 난다.
왜 그런지 아시죠? 아시더라도 모르는 분을 위해서 한번 설명 해드리겠습니다.
왜냐하면, 앞서 말했듯이 Hz는 1초동안의 진동수라고 말했습니다.
즉, 22KHz는 1초동안의 진동되어야만 원래의 소리를 들을수가 있습니다.
그런데 이것을 44KHz로 진동 시킨다면, 22KHz의 원소리의 2초 (22KHz * 2)를
1초만의 재생해버리는것과 같기 때문입니다.
그렇다면, 2초 음악을 1초만에 재생해버리면? 네~~~ 바로 도날드 덕 소리가 나겠죠?
그러면, 원래의 주파수에서 낮추어 재생하면?
네에~~~ M(?)의 소리가 나겠죠? 조금 엽기적인? ^^;
이제 주파수에 대한 개념을 아시겠죠?
그러면 이제 비트수에대해서 말해보고자 합니다.
보통 우리가 사용하는 사운드 데이터의 비트수는 8Bit와 16Bit가 있습니다.
여기서 비트수 개념은 위글에서 말했다시피 바로 소리 폭을 말한다 하였습니다.
소리 폭은 곧 진동의 폭을 말하는것이죠! 즉, 소리의 정보를 말합니다.
예를 들어서 진동폭 156 156 156 121 143 194 157이라는 데이터를 1초동안

재생한다면, 이것은 곧, 7Hz 8Bit사운드가 되는것입니다.
또, 2001 3452 3856 1432 0928 6534를 1초동안 재생한다면,
이것은 곧, 6Hz 16Bit사운드가 되는것이죠.
즉, 저 수치가 크면 클수록 각 진동 폭이 커져 더 리얼한 사운드를 들을 수가 있습니다.
이제 비트수에 대한 개념을 아시겠는지요? 바로 CD음악이 16비트로 구성되어 있습니다.
그리고, 예전 과거 도스 시절 게임에서 주로 사용되어왔던 사운드 비트수는 8Bit였죠.
그리고 사운드 카드 자체가 16Bit를 지원하지 못해서 8Bit가 한계였을지도...후후 ^^;
이제 스테레오와 모노를 들어 보면...
스테레오는 일단 모노에 비해서 사운드 량이 2배입니다.
왜냐하면, 기본적으로 좌우에 대한 사운드 데이터를 가지고 있기 때문이죠.
CD음악 역시 스테레오로 구성되어 있습니다.
그럼 CD음악의 최종 구성은? 네에 바로 44KHz 16Bit Stereo로 구성되어 있습니다.
이것으로 사운드에 대한 개념은 끝난듯 하군요.
매우 간단하죠? 저런 조그만 지식만 가져도 사운드 프로그래밍을 할수가 있답니다.
왜냐하면, WAV파일 자체가 저 사운드 구성원으로 되어 있기 때문입니다.
마지막으로 예제를 하나 내겠습니다.
11KHz 8Bit Stereo사운드로 구성된 3초짜리 데이터가 있다.
이 사운드 데이터의 양은 몇 바이트인가?
AHz = 11Khz = 11 * 1000Hz
ABit = 8 = 1Byte
AChannelNum = Stereo = 2Cahnnel
Second = 3
RateSize = (AHz * ABit) * AChannelNum = 22000Byte
DataSize = RateSize * Second = 66000Byte
답) 66000Byte
저 방식을 이용한 마지막으로 문제를 한번 내보겠습니다. 풀어보시길...!
문1) 31KHz 16Bit Mono로 구성된 4.2초짜리의 몇바이트수는 얼마인가?
문2) 코요테 "실연"음악이 1분 5.5초로 녹음하여 CD에 담았다.
CD에는 몇바이트로 기록되어있을까?

------------------------------------------------------------------------------
제  목:[PROG/초보] DirectSound (2)  

1. 사운드 개념
*(2). 다이렉트 사운드 객체
3. WAV파일에 대한 구조 이야기
4. WAV파일과 다이렉트 사운드의 조합
5. 이퀄라이져를 만들어보자. (1)
6. 이퀄라이져를 만들어보자. (2)
7. 자신만의 사운드 파일을 만들어 보자.
자 이번에는 다이렉트 사운드 객체를 이용하여 사운드 프로그래밍에 대한 입문을 해보자.
원래 WAV파일을 먼저 다룬 다음에 다이렉트 사운드 객체를 다루려고 했으나 WAV를 읽고나서
이 소리가 원하는 소리인지 확인해보기 위해서는 사운드 객체를 이용한 중급 출력 사운드
프로그래밍을 알아야 할 것이다. 그래서 우리는 WAV파일보다 사운드 객체와 구성에 대해서
공부해보려 한다. 일단 이번 강좌는 꽤 길어 질것이고, 소스도 첨가 할 것이다.
부디 강좌와 소스를 보시면서 공부하시길...그리고, 사운드 프로그래밍의 결정체인 사운드
편집 프로그램 까지 개발 해보시길...이번 예제와 소스에서는 WAV파일이 아닌 순수 사운드
데이터만 들어있는 화일이다. 음성이 들어간 사운드 데이터인데 이걸 예제로 하기로 했다.
우린 WAV파일을 공부하지 않고, 이번 강좌에서는 다이렉트 사운드 객체를 공부하는 것이기에
WAV와의 혼돈을 피하기 위함이었고, 그만큼 WAV의 궁금증을 증가시킨 필자의 술책일수도 있다.
^^; 크크크크

이번 강좌는 아래와 같이 모두 5단계로 이루어졌다.
(1) 다이렉트 엑스와 다이렉트 사운드란?
(2) 다이렉트 사운드 프로그래밍을 하기 위한 기본 환경과 비교분석
(3) 다이렉트 사운드의 기본 프로그래밍 One!
(4) 다이렉트 사운드의 기본 프로그래밍 Two!
(5) 예제 프로그래밍 소스를 위한 설명
< 1. 다이렉트 엑스와 다이렉트 사운드란? >

우리는 게임을 하다보면, DirectX 7.0이상 패치를 해야만, 게임이 가능하다는
메세지를 많이 보곤 했었고, 이 메세지를 보기도전에 새로운 버젼의 DirectX가
있다면,곧바로 패치하곤 했다. 바로 우리가 사운드 프로그래밍을 하려 하는 것은
모두 DirectX내에 포함된 하나의 Class라고 봐야 할것이다. 기본적으로 DirectX는
class구성원이
DirectX Class
1. DirectDraw ===> 기본적인 2D그래픽을 위한 중급 SDK API
2. DirectInput ===> 키보드, 조이스틱, 마우스를 위한 중급 SDK API
3. Direct3D ===> 3차원 그래픽을 위한 RM(유지모드), IM(직접모드) SDK API
4. DirectPlay ===> 네트워크 플레이를 위한 SDK API
5. DirectSound ===> 사운드 중급 SDK API
6. Direct3DSound ===> 3차원 사운드 중급 SDK API
7. DirectSetup ===> 인스톨을 위한 중급 SDK API
이 7가지가 DirectX의 가장 기본이되는 Class들입니다. 물론, 현재 버젼이 매우 업그레이드가
되어 DirectMusic, DirectShow, DirectAnimation등등의 Class도 많이 존재한다.
이 많은 Class를 한자리에 모았다하여 그들은 이것을 DirectX라 명명하게 된것입니다.
그리고, 이제 우리가 공부 할것은 5번째에 있는 DirectSound클래스를 공부해야 한다는 것이죠.
DirectX에 대해서 심도 있게 공부 해보시려면, 번역책중 괜찮은 책들이 있습니다.
인터넷이 훨 날지도요. 이기회에 게임 엔진 프로그래머로 꿈이나 직업으로 전환하실 생각은?
( 참고도서 )
(1)Inside DirectX, (2)Cantact Direct 3D, (3)DirectX 3, (4)Direct 3D
Programming
DirectX에 대한 기본 클래스 구성원을 알아 보았고, 수많은 클래스중에서 우린 다이렉트 사운드
클래스가 있다는 것을 알았으므로 다이렉트 사운드 클래스에 대한 설명으로 넘어가기로 한다.
우리는 다이렉트 사운드가 클래스로 되어 있는 관계로 우린 이것을 객체로 만들어 사용 할 필요가
있다. 클래스는 어디까지나 모델링(?)에 불과한 것이니 그 모델링을 실체화 시켜서 사용하라고 있는

것이다. ^^; 이것은 매우 당연한 이야기가 되지만, 그냥 문장을 조금 더 유연하게 쓰기 위한
심술인것이다. ^^; 헉 제가 무슨 소릴?

이제 구구절절 쓸데 없는 얘기 집어 치우고, 본론으로 들어가 DirectSound Class에 대한 메소드

잠깐 언급하고 2단계로 넘어 가려한다.
Class: IDirectSound (여기서 I는 인터페이스를 말한다. 자세한것은 COM을 공부하시기를)
1. DirectSoundCreate ===> 다이렉트 사운드 객체 생성 메소드
2. SetCooperativeLevel ===> 다이렉트 사운드 협력 수준 설정 메소드
3. CreateSoundBuffer ===> 다이렉트 사운드 버퍼 생성
4. SetFormat ===> 다이렉트 사운드 포맷 재설정 생성
메소드는 매우 많지만, 기본적으로 알아야 하고, 사용할 메소드가 위의 4가지이다.
그리고, 여기서 가장 눈여겨 봐야 할 메소드가 CreateSoundBuffer라는 점을 잊지 말자.
솔직히 CreateSoundBuffer로 생성된 Buffer는 곧, 메모리와 클래스가 생성된다.
결과적으로 사운드 버퍼는 클래스안에 속해 있다는 말이다.
원하는 설정에 의해서 생성된 버퍼는 또, 다른 메소드와 엄청난 멤버 변수를 가진 구조체와
연결되어 사용된다. 솔직히 DirectSound내부 구성원은 복잡하지 않지만, 많은 구성원들이
내재되어 자유롭게 프로그래밍을 할 수 있는 반면, 그만큼의 노력의 대가가 필요하다는 말이다.
버퍼는 사용자가 원하는 만큼 할당 해줄 수도 있다.
단, 여기서 사운드 카드내에 메모리가 충분하다면, 별 문제가 되지 않지만, 불충분하다면,
시스템 메모리( RAM, 최악의 경우 가상 메모리 )에 할당되어 사운드 끊김 현상같은 혹은 하드를
마구 긁어 되는 현상이 일어 날수가 있다.
하지만, 걱정 하지 않아도 된다. 요즘은 사운드 카드가 매우 좋아졌고, 메모리도 많이 증가되어
무리하게 사운드 프로그래밍 하지 않는 이상 별 문제를 일으키지 않는다.
우리는 지난번 다이렉트 사운드 프로그래밍 (1)에서 사운드 설정에 의한 바이트 값을 계산하는
법을 배웠고, 문제를 내어 푼 사람도 있었다.
열심히 풀거나, 내용을 간파했더나, 여러가지로 신경 쓰신 분들은 이제 그 노력의 결실을 얻을
것이다. 왜냐하면, CreateSoundBuffer가 바로 주파수, 비트수, 모드,시간에 대한 상관성을 계산하
고,
이해한 전제하에 프로그래밍 하기에 많은 도움이 되기 때문이다.
다이렉트 사운드 프로그래밍은 거의 CreateSoundBuffer에 의해서 생성된 버퍼로 프로그래밍 하는

것이 대다수이다. 버퍼를 알게 되면, 사운드를 이해하고, 다이렉트 사운드 프로그래밍을 이해하는
것과 같기 때문이다.
이번 단계에서는 DirectSound프로그래밍 기본 구성원을 보았고, 중요한 부분이 무엇인지 알았을
것이다. 상세한 설명을 안한다하여 필자를 꾸짓지 말라. 3단계 4단계에서 프로그래밍 방법과 설명을

다시 할것이고, 훌륭한 보물인 예제 소스를 첨가한다는 것을 잊지 말도록 했으면 한다.

< 2. 다이렉트 사운드 프로그래밍을 하기 위한 기본 환경과 비교 분석 >
이 강좌에 첨부되는 예제 소스 화일은 비쥬얼씨 6.0으로 짜여져 있고, SDK Library와 헤더 화일은
6.0으로 되어 있다. SDK 6.0 헤더 화일과 라이브러리를 원한다면, 메일로 보내주시길...

그럼 곧바로 보내드립니다.
이 두가지를 만족한다면, 프로그래밍을 짤 준비가 되었다. Alt + F7을 눌러보자.
그러면, 프로젝트 세팅 화면이 나올것이다. 거기서 Link를 선택하면 Objects/LibraryModules
탭란에서 dsound.lib과 winmm.lib를 추가 하자. 추가를 했으면, 이제 프로그래밍을 짜기 위한
준비는 다되었다.

매운 간단하고, 기본적일수도 있지만, 일부 초보 윈도우 프로그래밍하시는 분을 위하여 난 타자를
몇번 더 치게 되었다. T_T
참고로 MCI를 이용한 사운드 재생과 다이렉트 사운드를 이용한 재생을 비교 해보면,MCI가 훨씬
고급이어서 프로그래밍 하는데 많은 짐을 덜어준다.
하지만, 부디 저급에 가까운 중급 다이렉트 사운드를 사용하는 이유는 무엇일까?
MCI는 한번에 WAV를 2개 이상 가동 하기가 어렵다. 하지만, 다이렉트 사운드에서는
멀티 사운드 개념으로 만들어졌다. 다중 음성 출력과 다중 음악 재생이 가능 하다는 것이다.
물론, 다중 음성이야 탄성이 저절로 흘러 나올 만큼의 강력하다고, 느끼겠지만,
다중 음악은 능력이 아무리 막강해도, 소리는 듣기 싫을 것이다.
(울트라맨이야와 코요테의 실연이 같이 동시에 나온다고 생각 한다면...^^;)

다이렉트 사운드 프로그래밍은 도스 시절 사운드 카드를 제어하면서 각 서비스
번호를 어셈블리로 접근하여 (물론, C로도 접근이 가능하지만) 서비스를 이용하고, 다중 음성
출력시 적절한 버퍼 믹싱을 한후 사운드 카드 한테 보내주는 노가다(으 그시절은 악몽?)를 했지만,
다이렉트 사운드 프로그래밍은 하드웨어 직접 접근을 알아서 다 해준다.
그렇지만, 기본 도스 시절 사운드 프로그래밍 알고리즘 구성은 매우 비슷하다.
어쩌면, DirectX 프로그래밍이 게임 프로그래머들 사이에서 쉽게 흡수되고, 인기
있었던 이유가 도스 시절 프로그래밍 방식과 약간 흡사한 이유에 있지 않을까라는
생각이든다.
DirectX는 게임을 위한 것이 아닌 멀티미디어 프로그래밍을 하기 위한 SDK API이다.
기존 API보다 좀더 저급에 가까운 프로그래밍을 할 수 있으므로, API에 비해서
SDK API가 더 자유로운 프로그래밍을 제공 한다는데 많은 매력이 있는것 같다.
이기회에 게임이 아니더라도, 멀티미디어 프로그래밍에 관심이 있다면, DirectX프로그래밍을
한번 공부 해볼만하다. DirectX에 대한 강좌와 정보는 하이텔 게임제작동호회에 가면,자료가
꽤있고, 실력파 전문가들이 매우 많이 있다.
꼭 제가 Microsoft의 사원이 되어 DirectX를 홍보하는 기분이 드는군요! ^^;

 
< 3. 다이렉트 사운드의 기본 프로그래밍 One! >
이제 존격적인 다이렉트 사운드 프로그래밍에 들어 가겠습니다.
참고로 필자도 즉흥(?)적으로 짜기때문에 오묘한 알고리즘과 최적화에 신경 쓰지 않았다.
필자의 모범(^^; 그냥 이렇게 부르고 싶어서...죄송) 프로그래밍을 다른 사람들에게 보여
주기를 조금 꺼려하고, 사실 쪽팔린다. 하지만, 즉흥(?)적으로 짠것은 아무래도 "즉시적이니깐
이렇게 짰겠지?" 라는 생각을 갖게 된다는 기대 때문일까? ^^;
예제 역시 필자의 기교를 부린 예제소스도 아니다. 단, 다이렉트 사운드 프로그래밍의 예제만
보여 줄뿐이다. 이 글을 보시는 독자분들이 관심이 있으시다면, 반드시 훌륭한 사운드 엔진을
개발 할수가 있을것이고, 궁극적으로 가서는 Cool Editor와 같은 진보된 사운드 툴 엔진 개발을
할 것이다. 엔진과 툴 엔진은 다르다.
툴 엔진은 특정한 프로그램을 개발하기 위해서 툴을 만들게 되는데 이것을 툴
엔진이라 하고, 궁극적인 프로그램에서 가동될 실질적인 저급과 중급 기능들을 고급화 시켜
놓으면, 메인 프로그래머가 그 기능을 고급 프로그래밍으로 이용될 수 있도록 하는것이 바로
엔진이라 하겠다. 물론, 아시는 분도 계실것이고, 이제 초보도 계시지만, 저 역시 초보들을
위해서 몇타 더 적는 고통을 감수했다.
#define WIN32_LEAN_AND_MEAN //윈도 32를 위한 API만 싸그리 읽으랑
#defein STRICT //확실한 포인터로 컴파일 하그랑
#pragma hdrstop //읽은 헤더 화일 또 읽지마!
#include
#include //다이렉트 사운드 프로그래밍 정보
(1)메인 컬백 함수 만들고...
(2)기타 기본 윈도우에 필요한 전역 변수들 만들고
LPDIRECTSOUND lpdsRED; //다이렉트 사운드 객체 전역 변수
LPDIRECTSOUNDBUFFER lpdsBufferREDPrimary; //다이렉트 사운드 버퍼 재생
int DirectSoundInstall( HWND hwnd ); //다이렉트 사운드 생성
void DirectSoundUninstall( void ); //다이렉트 사운드 해제
int WINAPI WinMain( HINSTANCE hInstance,..........전형적인 윈 메인 함수 )
{
. MSG msg;
. (3) 윈도우를 일단 만들자.
. (4) if( DirectSoundInstall( hwnd ) == FALSE ) return FALSE;
. (5) 메세지 루프 (특별한 메세지 시스템도 아니고, 전형적인 메세지 시스템이다.)
. return msg.wParam;
}
메인 컬백 함수 부분( HWND hwnd, UINT message...... )
{
. switch( message ) {
. case WM_DESTROY:
. DirectSoundUninstall();
.
. PostMesage( hwnd, WM_QUIT, 0, 0 );
.
. return NULL;
. }
}
int DirectSoundInstall( HWND hwnd, HINSTANCE hInstance )
{
. (1) 다이렉트 사운드 객체 생성
. (2) 다이렉트 사운드 협력 수준 설정
. (3) 다이렉트 사운드 기본 사운드 버퍼 생성
. (4) 다이렉트 재생 설정
}
void DirectSoundUninstall( void )
{
. (1) 사운드 버퍼 해제
. (2) 다이렉트 사운드 객체 해제
}
쩜은 무시하셔요! 보기 안좋아서 그러것 뿐입니다.
자아~~~ 저 프로그래밍 구조를 보십시요! 그다지 어렵지는 않죠?
모든 예제는 저런 형식을 취할 것입니다. 기본적으로 윈도우 프로그래밍을 해보신 분이라면,
저런 형태의 프로그래밍을 아실것입니다. 어쩌면, 입문서의 처음장부터 설명하는 구조가
저런거였으니까요! ^^; 아니 모른신다고요? 흠...윈도우즈 프로그래밍 입문서를 한번 잘
살펴 보셔요!
자아!!! 그럼 우리가 여기서 눈여겨 봐야 할 부분은? 네에? 뭐라고요?
DirectSoundInstall함수랑 DirectSoundUninstall함수라고요?
역시 독자님께서는 탁월한 판단과 훌륭한 프로그래머가 되실 인재군요.
네에 맞습니다. 다이렉트 사운드를 먼저 설치하고, 해제 하는 법부터 배워야 할 것입니다.
그럼 DirectSoundInstall부분부터 설명과 간단한 프로그래밍을 해보도록 하겠습니다.
BOOL DirectSoundInstall( HWND hwnd )
{
. HRESULT hr;
. if( lpdsRED != NULL ) return TRUE;
. // Create DirectSound.
. if( FAILED( hr = DirectSoundCreate( NULL, &lpdsRED, NULL ) ) ) return FALSE;
. // Set cooperative level.
. if ( FAILED( hr = lpdsRED->SetCooperativeLevel( hwnd, DSSCL_PRIORITY ) ) ) {
. return FALSE;
. }
. return TRUE;
}
코드를 점검하기에 앞서서 간단한 매크로 설명을 하겠습니다.
여기서 FAILED라는 것이 의문이 생기실텐데요. FAILED는 매크로입니다.
즉, 오류를 체크 해주는 것이죠. 에러가 났다면, TRUE가 생성되고, 에러가 아니면,
FALSE를 생성하게 됩니다. 그리고, HRESULT는 에러값을 보관하는 변수로 사용됩니다.
사실 HRESULT로 선언된 hr값으로 무슨 에러가 났는지 알아 낼수가 있습니다.
자세한것은 다이렉트 엑스 책을 보시면 알고요. 여기서는 에러 사항을 자세히
알필요 없구 상업용 엔진이나 소프트웨어를 개발할때나 이용됩니다. 그냥 에러가 있는지
없는지만 알면 되요.
사운드 카드에게 특별한 기능을 요구 하지 않는 이상... ^^;
일단 DirectSoundCreate메소드로 다이렉트 사운드 객체를 만들고요.
그런다음 협력 수준을 설정 해야합니다. 여기서 말하는 협력 수준이란 바로 다이렉트 사운드
객체에게 어떤 부수적으로 특별한 서비스를 요구 하는 것입니다.
현재 SetCooperativeLevel에서 두번째 인자에다가 DSSCL_PRIORITY상수를 집어 넣었는데요.
이것은 1차 포맷을 변경 한다는 뜻입니다.
처음에 사운드 데이터를 읽어서 버퍼에 올릴때 그 포맷에 맡는 버퍼를 생성하고,
또 설정에 맞게 재생해야만 합니다. 그래서 다이렉트 사운드에게 포맷의 변경이 많으니
그것을 좀 협력해달라는 요구를 하는 것입니다.
녹음이라던가 특정한 창에서만 사운드가 나오게 하려는 부수적인 서비스는 모두
SetCooperativeLevel메소드를 통해서 정해진답니다.
여기에 들어가는 상수 값은 다이렉트 엑스 책을 참고하시고, 여기에서 사용되는
옵션 상수는 일반적인 사운드 재생에만 신경쓰기때문에 요거 하나로 만족 합시다.
다음으로 다이렉트 사운드 해제를 하는 함수 입니다.
void DirectSoundUninstall( void )
{
. if( lpdsBufferREDPrimary ) {
. lpdsBufferREDPrimary->Release();
. lpdsBufferREDPrimary = NULL;
. }
.
. if( lpdsRED ) {
. lpdsRED->Release();
. lpdsRED = NULL;
. }
}
 

 

 

 

반응형

 

 

728x90
 
 
일단 첫 단계에서 버퍼가 잡혀있다면, Release메소드를 통해서 해제 시킨후,
그런 다음에 다이렉트 사운드 객체를 해제 시키면 됩니다.
버퍼를 생성하고, 재생 하는 부분이 안나와있는데요!
음...제 4단계에서 설명 해드리겠습니다.
일단 제 3단계에서 다이렉트 사운드 객체를 만들어 보고 해제 시키는 프로그래밍을
스스로 해봅시다.
일하다가 점심시간을 이용하여 계속적으로 사이트에서 쓰고 있습니다.
3단계까지는 그리 어렵지 않은거고, 어떤 누구나 다 할수 있는 부분이라 생각됩니다.
하지만, 원래 상업용으로 개발 되게 되면, 사운드 인스톨 부분에서 엄청난 체크를
하게 됩니다.
예를 들어서 게임에서 원하는 사운드를 구현하기 위해서 하드웨어의 특별한 능력을
요구할때에 그 하드웨어에서 지원여부를 확인하는것등의 작업이 추가 됩니다.
이제 제 4단계에서는 버퍼를 생성하고, 사운드 데이터를 전송하는 법을 배워 보도록
하겠습니다.
< 4. 다이렉트 사운드의 기본 프로그래밍 Two! >
우리는 3단계에서 다이렉트 사운드의 객체를 생성하고, 우리가 원하는 협력 수준
설정을 하고, 해제 하는 것까지의 예제를 보았고, 알았다.
이제 다이렉트 사운드의 중요한 버퍼를 생성하는 법과 사운드 데이터 전송에 대해서
알아보도록 하자. 4단계 역시 그리 어렵지 않은 부분이다. 우리는 지금 다이렉트 사운드
프로그래밍의 결정체를 보는것이 아니다. 단, 사용법만 배우고 있을뿐이다.
이 단계에서 필요한 지식은 강좌 (1)에서 배웠던 사운드 기본 개념이라는 것이다.
앞서 말했듯이 버퍼를 만드려면, 사운드의 구성 주파수, 비트, 모드가 필요하다했다.
이제 강좌 (1)에서 배웠던것을 써먹어 보자.
일단은 DSBUFFERDESC구조체와 WAVEFORMATEX라는 구조체를 알아야만 한다.
DSBUFFERDESC는 버퍼를 생성하기 위한 설정 정보 구조체이고, WAVEFORMATEX구조체는 사운

데이터의 정보를 담는 곳이다.
우선적으로 우리는 WAVEFORMATEX구조체에 대한 것을 공부해야할 필요가 있다.
그리고나서 DSBUFFERDESC구조체를 공부해보자.

typedef struct {
. BYTE cbSize; //추가 정보에 대한 비트수값(확장할때 유용)
. int nAvgBytesPerSec; //초당 정송될 바이트수
. int nBlockAlign; //주파수당 바이트 값
. int nChannels; //스테레오( 2 ) 냐? 모노( 1 )이냐?
. int nSamplesPerSec; //주파수 값
. WORD wBitsPerSample; //주파수당 비트값
. WORD wFormatTag; //사운드 데이터 형식 (주로 WAVE_FORMAT_PCM)
}WAVEFORMATEX;
이런식으로 구성되어 있다.
여기서 cbSize은 항상 0값으로, wFormatTag는 1값으로 넣어주어야 한다.
wFormatTag는 사운드 버퍼 새성시 정보 전달할때에 이것이 1인지 0인지를
파악해서 버퍼를 생성 하는가 안하는가에 대한 여부를 결정한다.
우선, 우리는 사운드 데이터의 구성 요소를 알고 있다면, WAVEFORMATEX를 설계하는
것은 쉽다. 자 봅시다! 원하는 사운드 데이터는 11KHz, 8Bit, Mono라는것을 알게
되면, 이미 답은 나왔다는 것이다. 자아 한번 필자가 시범을 보이겠다.
cbSize = 0;
wFormatTag = 1;
nSamplesPerSec = 11000; //11KHz = 11 * 1000 = 11000Hz
wBitsPerSample = 8; //8Bit이다.
nChannels = 1; //Mono는 1이고, Stereo는 2이다.
nBlockAlign = (nChannels * (wBitsPerSample / 8)); //바이트수로 나타내기 위한 것.
( 즉, wBitsPerSample이 8이었으니 이것은 곧 비트수를 나타내는 수치였다.
  결과적으로 말해서 8비트는 1바이트이니까 wBitsPerSample을 8로 나눈것이다.)

nAvgBytesPerSec = nBlockAlign * nSamplesPerSec; //초당 전송될 바이트수
자아~~~ 강좌(1)의 기초 지식이 확실하니까 이렇게 쉽게 WAVEFORMATEX를 쉽게
설계가 되죠? 후후!!

다음 강좌 (3)에서 WAVE를 읽고 재생할때 WAVE안에 사운드 정보를 읽어서
저런식으로 설계만하면 됩니다.
이제 마지막으로 DSBUFFERDESC를 설계하여 버퍼를 생성 해봅시다.
typedef struct {
  DWORD dwSize;
  DWORD dwFlags;
  DWORD dwBufferBytes;
  LPWAVEFORMATEX lpwfxFormat;
}DSBUFFERDESC;

구조체는 이런식으로 되어 있습니다.
dwSize는 DSBUFFERDESC를 전체적으로 초기화 할때 구조체의 크기를 말하고,
dwFlag는 버퍼 재생 형식 옵션을 넣어줍니다. 그리고, dwBufferBytes는
버퍼 크기 수치를 말하고요. lpwfxFormat은 좀전에 설계한 WAVEFORMATEX 구조체
포인터 변수입니다. 자아 그럼 이게 어떻게 사용 되는지 볼까요?
ex) 11Khz, 8Bit, Mono구성으로 된 사운드 데이터가 약 2초짜리라면?
DSBUFFERDESC dsbd;
WAVEFORMATEX wfx;

.........................................
wfx구조체를 위처럼 설계한다.
.........................................
ZeroMemory( &dsbd, sizeof( DSBUFFERDESC); //0으로 모두 초기화 한다.
dsbd.dwSize = sizeof( DSBUFFERDESC ); //구조체의 전체 크기 구한다.
dsbd.dwFlag = DSBCAPS_STATIC; //재생 형식 설정
dsbd.dwBufferBytes = wfx.nAvgBytesPerSec * 2; //초당 전송 바이트수 * 2 (2초니까)
dsbd.lpwfxFormat = &wfx; // 여기서 DSBCAPS_STATIC이란 버퍼 크기만큼만 재생하고, 자동으
로 스톱된다.
사운드 재생 중에서는 스트리밍 방식과 스태틱 방식이 있는ㄷ 이거에 대한
자세한 사항은 WAVE파일에 대해서 강좌할때 자세하게 설명 해드리겠습니다.
일단, 이 사운드 재생 형식은 스태틱형식이랍니다. 스트리밍은 긴 화일을 재생할때
스태틱은 짧고 자주 사용되는 사운드 데이터를 재생할때 사용된다는 정도만... ^^;
그러면, 이제 버퍼를 생성해보자.
if( FAILED( lpdsRED->CreateSoundBuffer( &dsbdesc,
. &lpdsBufferREDPrimary, NULL ) ) ) {
. return FALSE;
}
CreateSoundBuffer라는 것은 말그대로 사운드 버퍼를 만들어 주는 메소드이다.
첫번째 인자는 DSBUFFERDESC구조체 변수를 넘겨준다. 그리고, 두번째 인자에다가는
lpdsBufferREDPrimary를 넣었는데 그것은 기억력이 좋으시다면, 3단계에서 전역 변수로
잡아 주었던 사운드 버퍼 객체 포인터 변수였다는 것과 lpdsRED는 다이렉트 사운드 객체
변수 였다는 것을 다시 한번 기억하자. 여기 까지 사운드 버퍼를 만드는것이 끝났다.
DirectSoundUninstall함수에서 lpdsBufferREDPrimary버퍼 변수가 생성 되었다면,
lpdsBufferREDPrimary->Release()함수를 통해서 해제 시켰더것도 기엇하기 바란다.
그리고, 마지막 인자는 업그레이드 예비 인자로서 그냥 NULL로 대처하기 바란다.
이제 마지막으로 버퍼에다가 사운드 데이터를 전송하는 일과 재생만 남았다.
그것은 여짓까지 공부 한것보다 더 쉽다.
WAVE사운드 화일이 아닌 일반 순수한 사운드 화일을 읽어 메모리로 로딩을 해놓는다.
그리고, 다이렉트 사운드 버퍼에 전소하기전에 Lock을 걸고 전송한후에 다시 재생하기전
Unlock을 걸어놔야 한다는 사실만 기억하자.
Lock메소드와 Unlock, Play, Stop메소드는 모두 다이렉트 사운드 버퍼 클래스에
속해있는 멤버 함수들이다. 즉, 우린 위에서 이미 사운드 버퍼를 만들었던것과 동시에
객체가 생성된 것이다. 즉, 그 버퍼 객체를 통해서 저 메소드를 사용 할 수 있는 것이다.
다음 코드를 살펴 보자.
if( FAILED( lpdsBufferREDPrimary->Lock( 0, // 재생 시작 주소
                                        0, // 재생 시작 크기
             &lpvData // 사운드 데이터 저장 일반 주소
             &dwBytes, //사운드 데이터 길이
             NULL, //사운드 재생 끝난후 돌아올 주소
             NULL, //사운드 재생 끝난후 점프될 바이트수
             DSBLOCK_ENTIREBUFFER ) ) ) { //전체 버퍼 재생
             return FALSE;
}

memcpy( (BYTE *)lpvData, (BYTE *)soundBufferData, soundBufferLength );
lpdsBufferREDPrimary->Unlock( lpvData, dwBytes, NULL, 0 );
}
즉, 미리 사운드 데이터를 읽어서 그것을 포인터 변수로 가지고 있다가, 다이렉트 사운드를
다 설치하고, 거기에 맡는 버퍼가 다 완성 되었을때 Lock을 통해서 사운드 데이터 포인트
위치를 알려준다. 그러므로서 사운드 버퍼를 절대적 주소로 알게 되면서 그 주소를 통해
사운드 데이터를 전송 할 수 있는 것이다.
그리고, dwBytes라는 변수가 있는데 예제 소스에서는 이것을 파일길이로 설정해놓았다.
파일 자체가 사우드 데이터이니까, 재생 할 사운드 길이 역시 파일 길이와 같아야 겠다.
물론, WAV파일에서는 약간의 코드가 더 들어가지만, 앞서 말했듯이 예제에서 제공 되는것은
순수 화일이다. 아무런 포맷도 없는 사운드 데이터이다. 이 데이터는 필자가 임의로 다이렉트
사운드의 예제로서 만들어 놓은것이다.
마지막으로 Unlock메소드의 첫번째 인자는 Lock이 될 위치를 말하고, 두 번째인자는 길이를 말한
다.
Lock은 사운드 메모리를 일반적인 RAM형식의 물리적 기억장치 형태로 변환하는 수단도 된다.
Unlock메소드를 호출 하기 전에는 절대로 Play메소드 호출이 불가능하다는 점을 알아 두도록 하자.

필자가 예전 도스 시절 게이 프로그래밍 할때에 XMS메모리를 제어 했던것이 볼때에도 XMS메모리
에다가
Lock걸어 주므로서 Based Memory와 XMS메모리 간의 절대 주소 교환(?)이 가능 하고, Unlock
이전에는
교체가 완벽히 끝나지 않았기에 XMS를 사용 할수 없는 것 처럼 다이렉트 사운드 형식도 같다고 봐

할 것이다.
이제 사운드 설치도 끝났고, 버퍼도 만들고, 데이터도 전송 했으니 이제 재생만 남았다.
재생은 매우 쉽다. 다이렉트 사운드 버퍼에 속해 있는 메소드 Play를 한번만 호출하면 된다.
lpdsBufferREDPrimary->Play( 0, 0, 0 )이라 하면 된다. 여기서 첫번째, 두 번째 인자는
무조건 0이다. 왜냐하면, 확장용 업그레이드 가변 인자이기 때문이다.
이 경우는 차후에 버젼 업할때 미리 예약 해두는 경우를 말하는데 신경 쓸 인자는 아니다.
이상한 값을 넣어도 아무런 영향이 가지 않는다.
우리가 봐야 할것은 3번째 인자인다. 이것이 0이면, 한번만 재생하고, 1이면, 반복 재생한다.
그런데 사운드 데이터가 매우 짧고, 반복되길 원하지 않으므로, 0으로 설정 하였다.
그리고, 사운드를 정지 시킬때는 Stop이라는 메소드를 사용하면된다.
lpdsBufferREDPrimary->Stop()하면 된다.
이제 강좌 2의 4단계가 끝났다. 천천히 차분히 읽어 나가자.
모르는 부분이 있다면, 서슴치 말고, 필자에게 메일을 보내기 바란다.
사운드 프로그래밍에 관심이 있는 분이라면, 나이에 상관없이 같은 동료로 생각하는 사람들중에
한사람이기 때문이다.
< 5. 예제 프로그래밍 소스를 위한 설명 >

이제 마지막 단계에서는 크게 말할것은 없다.
단, 예제는 사운드 데이터가 WAV가 아니라는 사실을 다시 한번 말해주고 싶고,
다이렉트 사운드 프로그래밍에 대한 이해를 위하여 짠것 뿐이다.
그리고, 코딩을 한 20분 정도밖에 안걸려서 마구 짰다.
결과적으로 큰 프로그램도 아니고, 단계 별로 짰으니 분석(?)이랄것까지야 없지만,
어째튼 아주 쉬운 프로그래밍이다.
소스는 게제동 자료실 소스 부분에 있다. 코드도 매우 형편이 없다 T_T
그렇지만, 초보자들도 보기 쉬울 만큼 정리를 나름대로 열심히 했다.
여어 주석으로 달았긴했는데 이건 초등 영어 수준이다. T_T
마지막으로 사운드 프로그래밍 강좌를 해보질 않아서 아직은 미숙한게 많다.
두서가 없고, 미숙하더라도 많은 양해를 구하는 바이다.
어디까지나...초보들을 위한...T_T
------------------------------------------------------------------------------
제  목:[PROG/초보] DirectSound (3)  
1. 사운드 개념
2. 다이렉트 사운드 객체
(3). WAV파일에 대한 구조 이야기
4. WAV파일과 다이렉트 사운드의 조합
5. 이퀄라이져를 만들어보자. (1)
6. 이퀄라이져를 만들어보자. (2)
7. 자신만의 사운드 파일을 만들어 보자.
안녕하십니까? ^^;
그동안 회사일로 많이 바쁘고, 글도 잘쓰지도 못해서 미루어 왔는데, 우연찮게도 몇몇
인터넷 사용자 분들의 격려로 하루 빨리 써야 겠다는 생각에 오늘 하루를 작정하고,
강좌에만 매달리기로 결심했습니다. 물론, 눈치를 조금씩 보면서 말입니다.
팀장님 오시면, 일하는척! 다시 게시물 작성...으하하하하하
정말로 스타크 뺨치는 혈전이군요...크크
자아~~~ 그동안 제 강좌를 기다려주셨던 몇몇분들을 위해서 그동안 미루어왔던 강좌를
쓰겠습니다. 보잘것 없는 제게 힘을 붇돋아 주신 분들 정말로 감사합니다.
메일보고 많이 감동을...카아~~~
(이제 여자친구만 생기면 좋을텐데... ^^);

아! 참! 하이텔에 있는 게임제작동호회 go gma를 하시면, 훌륭한 전문가들이 많고,
많은 새로운 정보들이 있습니다.(제가 게제동 회원이라서 그런것은 아닙니다. ^^;)
(모든 인터넷 게임 프로그래머들이 게제동으로 오는 그날까지...)
한번 가보시기를...

그럼 이제 제가 그동안의 우여곡절에 대한 변명을 그만하고, 이번 강좌는 WAV에 대해서
이야기 해보도록 하겠습니다.
그동안 강좌 (1)과 강좌 (2)부분에서의 사운드 기본 정보지식은 곧 우리가 앞으로 다루려
하는 WAV에 파일에대한 목적일 수도 있고, 앞으로 더나아가 다른 포맷을 가진 사운드
파일일 수도 있습니다. 여기서 어떠한 포맷을 가진 사운드 파일이라 해도 결국 사운드의
원개념은 달라지지 않습니다.
(돌발 상황)
간혹 어떤분들께서는 이런 질문을 하시더군요... "midi화일은 용량이 작아서 좋지만, 요즘
대용량화 시대에 그런 음질 않좋고 이상한 화일을 쓸필요가 있을까요?
그냥 자연음인 WAV를 사용해도 별 걱정이 되지 않을까요?"
라는 질문을 하신 분들이 있었는데요...
여기서 한가지 명확하게 짚고 넘어 가야 할부분은 바로 midi화일은 절대로 사운드 화일과는
다르다는 점입니다.
왜냐하면, midi화일은 사운드 정보가 담긴 화일이 아닌 음악 곡 정보가 들어 있는 화일이기
때문입니다. 즉, 음악가 입장에서는 이 화일을 곡 소스라고도 부르는데요.
보통 이러한 곡소스들은 컴퓨터 음악 장비들에게 어떠한 음색을 발생하라는 신호와 강도, 높이,
기타 효과등에 대한 명령 메세지가 담게 되어있는것이죠.
음악가들이 사용하는 외부 장비를 갖고 있지 않는 이상 음악가의 음악 사상이 깊게 담겨져
있는 음질을 제대로 들을수가 없답니다.
그래서 보통 음악가들이 미디 작업을 한후에 스튜디오 같은곳에 가서 미디 장비에서 나오는
음색들을 녹음시켜 하나의 WAV파일이나 CD음악을 만들수가 있는 것입니다.
mid -> 좋은 장비에서 녹음 -> WAV나 음악 씨디.
이제 미디에 대한 궁금증이 있으신 분들의 고민을 해결해드렸는지? ^^;
핫~~~그런데 이번 강좌의 내용은 WAV에대한 것이었죠? ^^;
하하 제가 잠시 미궁에 빠져 버렸군요.
(앗 이런 정신 차려야지...이러고 있을때가 아니양~~~)
<---------------------------------------------->
<-----------진짜 여기서부터 본론---------------->
<---------------------------------------------->
페이스 조절하고...마음을 다시 가다듬은 체...요이! 땅!
(초등학교시절 운동회때가 생각나는군...허~~~)
이번 강좌 (3)에서는 약속한대로 WAV구조에 대한 이야기를 하겠다.
구조라고 하여 무슨 특별한 내용도 아니다.
단, 평소에 궁금해 했던 WAV파일이 도대체 어떻게 생겨 먹은 놈이기에
사람 속을 애태우게 만들었던가...( ㅡ,ㅡ)
그런데...(^,^) 막상 알고 보니까 솔직히 별내용이 담겨진 파일이 아니었다.
(T_T "아~~~ 나는 뭘위해 삽질 한거지...") 아마도 여러분들께서는 특별하지도
않은 그 내용을 알게 되었을 때 두가지의 모습이 상상이 되게된다.
1--> 오옷 이런거 였구나...하하하하하하하
2--> 우띠~~~ 젠장 이것두 솔루션이여?
이번 강좌는 내용이 길지 않다는 것과 독자들에게는 특별한 내용이 아닐수도 있다는 것을
다시 한번 강조하며, WAV구조를 설명하겠다.
(크...이번 강좌로 인해서 조회수가 급격히 줄으면...흑흑)
우리는 강좌 (1)에서 배웠던 주파수며 비트수며 그런 기초적인것을 배웠다.
그것으로 강좌 (2)에서 무사히 다이렉트 엑스 기본 프로그래밍을 쉽게 배우기도 하였다.
이번 강좌 (3)에서도 강좌(1)의 기본이 확실하다면, WAV파일 구조에대해서도 그렇게 어려워
할 부분이 아니라는 것을 곧 느끼게 될것이다.
강좌(2)에서 잠깐 WAV를 언급했었을때 우린 WAV파일내에 사운드 정보가 담겨있었다고 했다.
그 사운드 정보라하면, 역시 주파수, 비트수, 스테레오 일것이다.
자아 그러면 WAV파일의 사운드 구조를 공개 하겠다.
기대하시라...두그두그두그~~~짠!

1. WAV 구조 (어라?)
하하 너무 글이 딱딱해진것 같아서 장난 한번 해보았다. ^^;
(썰렁한건지 주책인지는 모르겠지만, 돌은 던지지 말아주길 바란다.)
우선 먼저 WAV의 기본 구조를 살펴보고, 세세한 부분을 살펴보도록 하자.
1. WAV파일 진짜 기본 구조
.   +---------------------------------------------------+
.   | RIFF Chunk                                        |
.   | +-------------------------------------------------|
.   | | WAVE Chunk                                      |
.   | | +-----------------------------------------------|
.   | | | "fmt " Chunk                                  |
.   | | +-----------------------------------------------|
.   | | +-----------------------------------------------|
.   | | | data chunk                                    |
.   | | +-----------------------------------------------|
.   | +-------------------------------------------------|
.   +---------------------------------------------------|
.   | Sound Data                                        |
.   | ...................                               |
.   +---------------------------------------------------+
자아~~~ 보았는가...? 엥? 그러나 예상대로 좋은 기대 상황이
나오리라 생각치 않았다. T_T
여기서 저렇게 나열하거나 청크별로 나누어서 설명하는 것이
일반적으로 나온 책자들과 같다.
(솔직히 나도 강좌라는 것을 해보고 싶어서 따라 해본것이다. T_T)
우린 저런 형태의 구조를 MMIO 시스템 함수들을 사용하여 WAV파일들을
읽어 들이지만, 솔직히 프로그래머들 사이에서는 고질적으로 다른 사람들이나
이미 컴파일러 제작 회사에서
제공되는 라이브러리를 사용하길 꺼려하는 분이 적지 않은 걸로 안다.
그렇다! 필자 역시 모두다 만들어서 써보려고 한다.
*여기서 MMIO라는 것은 Multimedia file I/O support로서
*말그대로 멀티미디어 입출력을 다루는 함수들을 말한다.
본 강좌에서는 MMIO함수를 사용하지 않고, 자신이 직접 함수를 만들어
사용할수 있도록, 차후 엔진 개발하기에도 많은 도움이 될 수 있는 밑거름이
될수 있도록 유도 할것이다.
단, 여기서 알아야 할것은 각 청크에 대한 의미일 것이다.
RIFF Chunk는 멀티미디어 파일이라는 것을 나타내는 정보가
들어 있다. WAVE Chunk라는 것은 멀티미디어 파일중에서
WAV파일이라는 것을 나타내는 정보가 담겼다.
그리고, WAVE청크에 포함되는 나머지 두 청크가 있는데,
"fmt "청크는 웨이브 파일 정보 (그러니까 강좌 (2)에서 사용한
WAVEFORMAT구조체 멤버들에서 사용되었다.)가 담겼다.
그리고, data청크가 있는데 이곳은 순순한 웸이브 데이터가
저장되어 있는 부분이다.
청크라는 것은 멀티미디어 파일을 읽기, 쓰기, 관리에 편리 수단으로서 사용되는거라고
생각하자.
여기서 한가지 사실을 알아 두도록 하자.
순순 사운드 데이터 파일 오프셋은 무조건 44바이트이다.
물론, PCM포맷에서 무조건 이라는 것이다.
그렇다면, 파일 헤더 부분이 44바이트라는 것인데.
이 44바이트에 WAV에 고나한 정보가 담겨져있다.
그런데, 보통 책에서는 그 44바이트를 위해서 부디 MMIO함수들을
사용하고 있는데, 어차피 WAV파일만 재생할꺼라면, WAV파일에
대한 정보를 알고 있을때 44바이트 건너뛰면서 사운드 데이터를
읽어 들인다면, 간단해질것이다.

하지만, 융통성있는 프로그램을 작성하기 위해서는 MMIO함수가
필요 할지도 모른다. 파일을 병합할때에 MMIO함수가 편할것이다.
왜냐하면, MMIO함수는 자동으로 청크를 찾아내주고, 정보 읽고
하기때문에 파일 병합시에도 쉽게 WAV파일 위치를 색출가능할것이다.
하지만, 이번 강좌와 다음 강좌에서는 청크별로 WAV파일을 읽어 들이는
것이 아니라 위에서 설명한 웨이브 헤더 44(4개의 청크를 합친) 바이트에
대한 정보를 가지고 프로그램을 만들어 나갈것이다..
그러기 위해서는 위와 같은 WAV구조 형태를 보여주기보다는
필드로 끊어서 확실하게 옷을 벗겨 버리는 것이다.
으하하하하하하 (참고로 필자는 변태가 절대로 아니다. --+)
.
.
1. 누드 WAV파일 구조 (필드 형식)
.   (파일 오프셋은 당연히 0부터 시작합니다. ^^;)
.
.   wavRIFFChunk             (4Byte)
.   wavSize                  (4Byte)
.   wavWAVEChunk             (4Byte)
.   wavFMTChunk              (4Byte)
.   wavFormatSize            (4Byte)
.   wavPCMFormatFlag         (2Byte)
.   wavChannel               (2Byte)
.   wavSampleRate            (4Byte)
.   wavSampleRatePerSec      (4Byte)
.   wavPerSecScale           (2Byte)
.   wavBits                  (2Byte)
.   wavDATAChunk             (4Byte)
.   wavDATASize              (4Byte)
.
.
이것이 바로 WAV헤더 부분에 대한 44바이트 필드들이다.
.
이 필드만 가진다면, 우리가 강좌 (2)에서 배운대로 WAVFORMATEX구조체를
꾸밀수 있지 않은가?
우린 이런 필드 정보들만 가지고도 자신만의 사운드 파일을 만든다든가 WAV파일
판별도 되고, 원하는 사운드는 모두 들을수가 있을것이다.
이것이 진정으로 월매나 멋진 일이 아니던가...
(WAV파일 분석할때 사용한 프로그램은 HexEditor를 이용했습니다.)
그럼 이제 본격적으로 저 필드들에서 알아 보자.
wavRIFFChunk의 4바이트는 바로 RIFF라는 문자열을 읽어 들이기 위한 필드이다.
모든 마이크로소프트 멀티미디어 파일들은 대부분 RIFF라는 문자열로 시작한다.
보통 EXE실행화일이 MZ으로 시작하는 것 처럼 말이다.
여기서 많은 설명이 없어도 될것으로 알고, 다음 필드를 보겠다.
그 다음으로는 wavSize가 나오게 되는데 이것은 바로 WAV파일 길이 정보 수치가
들어있다. 단, RIFFChunk길이를 뺀 값이다.
여기서 RIFFChunk에 해당 되는 필드가 바로 wavRIFFChunk와 wavSize필드이기 때문에  
이 두 필드의 합이 8바이트가 나오게 된다. 그렇다면, wav파일 크기에서 8을 뺀
수치가 바로 이 필드에 저장 되는 것이다.
EX) 웨이브 파일 크기(500바이트) - RIFF Chunk크기(8바이트) = 492바이트
이제 아시겠죠? ^^;

그럼 이제 다음 필드를 보면, wavWAVEChunk와 wavFMTChunk가 있는데 이것 역시 크게
중요한 부분이 아니고, 차후에 읽어들일 파일이 WAV화일인지에 대해서 판별할때 사용될
수도 있습니다.
wavWAVEChunk에 WAVE라는 문자열이 들어가게되는데 역시 WAV화일이어야만 이 필드에
그 문자열이 들어 갈것이다.
그리고 다음 청크 wavFMTChunk가 있는데 이곳에는 "fmt " (공백을 합한)라는 청크가
들어 가게 됩니다.

청크는 모두 4바이트로 되어있습니다.물론 이 4바이트가 문자열로 되어 있죠.
문자열로 읽어 들여서 문자열 비교 해보면 되겠죠? ^^;
여짓까지의 필드 설명은 청크라는 것밖에 없었다.
그리고, 청크라는 것은 곧, 레코드로 생각 할수도 있다.
그러니까 즉, RIFF라는 청크 안에 wavSize필드가 속하는것이고,
WAVE청크 안에 "fmt "청크와 data청크가 있었다.

원래 이 청크들은 MMIO함수를 사용할때에는 매우 큰 의미가
되지만, WAV를 완전히 발가 벗겨 자신만의 함수를
만드려면, 이러한 Chunk는 그렇게 큰 의미가 되지 못하고 있다.
(일반적으로는...^^)

그럼 이제 "fmt "청크까지 알아 보았고, 그다음으로는 "fmt "청크에 속하는
필드들을 살펴보자.
"fmt "청크 안에 속하는 필드들은 바로 강좌 (1)에서 배웠던 사운드 정보들이
들어있다.
맨 처음에 wavFormatSize필드가 나오게 되는데 이 필드는 웨이브 정보 필드 길이를
말한다. 그러니까, 주파수 정보 바이트수, 비트 정보 바이트수 등등을 합한 필요로하는
모든 필드들의 바이트수를 보여준다.
일반적으로 크게 바뀌지 않는 이상 이곳에는 16이라는 수치가 저장 되는데 이 수치가
바로 웨이브 포맷 정보 바이트수가 16바이트라는 것이다. 왜 16바이트인지 보도록 하자.

.   wavFormatSize             (4Byte)
---------------------------------
.   wavPCMFormatFlag         (2Byte)
.   wavChannel                (2Byte)
.   wavSampleRate            (4Byte)
.   wavSampleRatePerSec     (4Byte)
.   wavPerSecScale           (2Byte)
.   wavBits                     (2Byte)
==================================
자아 wavFormatSize필드를 뺀 나머지 필드들을 계산 해보십시오.
16바이트가 나오게 되죠? ^^;
그럼 이제 나머지 필드들을 살펴 보도록 하자.
wavPCMFormatFlag필드는 강좌 (1)을 되새겨 보면, PCM형식의 WAV파일은
무조건 1이라 했습니다. 바로 그렇습니다. 우리가 원하는 그 WAV파일은
PCM형식이기때문에 1이라는 수치가 이 필드에 들어 올것이다.
(PCM, ADPCM등등의 데이터 형식이 많습니다.)
(ADPCM같은 경우는 압축한 경우를 말합니다.)
이제 wavChannel필드가 있는데 이것은 스테레오냐 모노냐 하는 정보 필드입니다.
스테레오면, 2라는 수치가 모노면 1이라는 수치가 저장 될것이다.
이제 그다음으로 wavSampleRate이라는 필드가 나오는데 이것은 바로 주파수
수치가 들어옵니다.
22KHz라면, 1000 * 22 = 22000이니 이곳에 22000의 수치가 들어가 있을것이다.
그 다음 필드로는 wavSampleRatePerSec가 있다. 이것은 초당 전송되는 바이트수
값이 들어 있습니다.
22KHz, Stereo, 16Bit사운드라면...
이 필드에 22000 * 2 * 2 = 88000라는 수치가 들어 갈것이다.
이제 그 다음으로는 wavPerSecScale필드가 있다.
이 필드는 그다지 신경 쓸 필드는 아니지만,
바로 기본 주파수 수치에서 wavSampleRatePerSec수치가 나올수 있도록, 계산하기
위한 수치이다.
여기서 한가지 예를 들어보면, stereo는 좌우가 있기 때문에 전송될 바이트수가 동일한
주파수의 2배를 해주어야 한다. 그러면 여기까지는 wavPerSecScale필드가 2이다.
그리고, 다음으로 비트수가 16비트라면, 기본 8비트의 2배가 되기때문에 또 최종 전송
바이트수에 또 2배를 해야 한다는 것은 강좌(1)과 강좌(2)에서 설명하였다.
즉, 결과적으로 wavPerSecScale으로 4가 된다.
ex 1) 22Khz, stereo, 8bit
      wavPerSecScale 필드 = 2
ex 2) 44Khz, mono, 16Bit
      wavPerSecScale 필드 = 2
ex 3) 11Khz, mono, 8Bit
       wavPerSecScale 필드 = 1
가만히 살펴보게되면, 주파수에 상관 없이 모노냐 스테레오냐
8비트냐 16비트냐에 따라 전송될 바이트수의 스케일이 결정된다.
그러므로, wavSampleRatePerSec필드는
wavSampleRatePerSec = wavPerSecScale * wavSampleRate으로
wavSampleRatePerSec필드가 결정 되어진 것이다.
이 정보 필드가 존재 하는 것은 사운드 버퍼 크기를 결정할때
스테레오, 모노, 판별을 하지 않고도 이 필드만으로도
쉽게 계산이 되므로 편리상 놓여진듯하다.
이제 마지막으로 wavBits라는 필드가 남았는데 이 필드는 설명을 안해도 다 알것이다.
16비트라면, 16수치가 저장되고, 8비트라면, 8수치가 저장된다.
이것으로 "fmt "청크는 모두 끝난듯 하다.
이제 마지막으로 wavDATAChunk와 wavDATASize가 남았다.
여기서 wavDATASize는 DATA청크의 필드이다.
wavDataChunk필드에는 문자열 4바이트 "data"가 들어간다.
바로 이 청크 부터는 사운드 데이터들이 저장 된곳이다.
이제 wavDataChunk필드에 "data"청크 문자열을 읽었으면, 마지막 필드인
wavDataSize필드가 있는데... 이 필드에는 사운드 데이터 길이가 들어 있다.
앞서 설명한 wavSize필드에는 사운드 파일 길이 수치가 담겨졌고, wavDataSize 필드에는
순수 사운드 데이터 길이 수치가 정장 된것이다.
보통 WAV헤더 부분은 44Byte이고, RIFF청크 길이가 8바이트 인데 파일길이가 500바이트라면,
wavSize 필드 = 492
wavDataSize 필드 = 456
라는 수치가 저장 된다.
이제 WAV화일 헤더 부분에 대한 필드들은 다 읽었다.
혹시라도 청크와 필드가 헷깔리신다면, 청크라는 개념을 아예 버리고, 헤더 부분 모든
데이터들을 필드로 생각해서 접근 하기 바란다.
( 필자가 워낙 문장 실력이 없어서 청크와 필드를 적절히 표현하지 못해 헤깔리는거기 때문에
모두 필자를 탓하겠다. T_T)
이제 헤더를 읽게 되면, 곧바로 파일 포이터 위치가 순수 사운드 데이터 앞에 까지 와있다.
이제 여기서 데이터를 읽어 사운드 버퍼에 보내 주기만 하면 된다.
  2. 사운드 데이터 이야기
우리가 재생 하려는 사운드 데이터가 모노, 8Bit이거나, 스테레오 8Bit, 모노 16Bit,
스테레오 16bit의 모드가 많다.한가지 여기서 알아 두워야 할 점은 이 모드에 따라서
사운드 데이터가 다르다는 것이다.
만약에 8비트 모노라면, 채널도 없고 1바이트 사운드 표현 범위기 때문에 연속적으로
1바이트만 읽어 가면, 계속적인 사운드가 재생된다.
만약에 스테레오 8비트라면, 데이터를 읽어 사운드 버퍼에 전송 할 때에 사운드 데이터는
맨 처음 1바이트가 왼쪽 스피커로 전송되고, 그리고, 그 다음 1바이트가 오른쪽 스피커로
전송되고, 또 다시 그 다음 바이트는 다시 왼쪽 스피커로 전송된다.
이렇게 지그재그 형식으로 데이터가 뿌려져 스테레오를 구사 하는것이다.
또 16비트 모노라면, 2바이트씩 일련으로 신호를 보내며 소리가 발생 하지만,
스테레오 일때는 처음 2바이트는 왼쪽 스피커에 그 다음 2바이트는 오른쪽 스피커에 소리가
발생 되는 것이다.
이제 이번 강좌 (3)에서의 WAV구조 이야기를 마칠까 한다.
이글을 읽어 주신 분들께 다신 한번 감사의 말씀을 드리고, 다음 강좌 (4)에서 WAV를 읽어
재생하는 프로그래밍을 해보자. 그동안 바쁘다는 핑계로 강좌에 힘쓰지 않았던 점에 대해서
정말로 사과의 말씀을 드리고, 제 강좌를 보시고, 같이
궁리하는 사람이 한명도 없을때까지 이 강좌는 계속 이어질것이다.
다행히 프로젝트 하나가 마무리 되어 요즘은 신간적 여유가 생겨 강좌에 신경을 쓰고자 한다.
그럼 다음 강좌 (4)에서 뵈요~~~ ^.-
------------------------------------------------------------------------------
제  목:[PROG/초보] WAV CHUNK에 대한 이야기... ^^;  
강좌 (3)에서 WAV에대한 이야기를 하였다.
제가 미처 빼먹은 부분이 있어 추가하고자 한다. 바로 청크라는 부분인데...
본 강좌는 여러가지의 WAV파일이 아닌 Window PCM포맷을 사용한다는 전제하여 강좌를
하여 청크라는 것을 빼먹게 되어 사과의 문을 쓰겠습니다.
보통 흔히 사용하는 WAV파일 포맷들이 Window PCM을 많이 사용하고 있습니다.
(필자 역시 그 포맷을 즐겨 사용합니다.)
보통 MP3화일을 WAV파일로 Decoding을 하여 얻는 파일 포맷이 바로
Window PCM포맷입니다.
현재 강좌를 꾸준히 보셨던 독자들께서는 이런 포맷들에 대해서는 그렇게 신경을 안쓰시고,
44바이트 건너뛰면 곧바로 data청크를 만날수가 있습니다.
하지만, 제게 청크에 대한 질문을 하셨던 내용은 다음 사항이 많았습니다.
"어떤 WAV파일은 읽어지고, 어떤 WAV는 읽어 지지 않습니다."
"이건 확실히 mmio를 이용한 청크 검색별로 읽어 가야하지 않을까요?"
오호...이런 질문 메일이 음청나게 많더군여...T_T;
네에 제게 질문을 주셨던 분들의 말이 맞습니다.
WAV파일의 포맷 종류가 매우 다양하기 때문입니다.
"참고로 COOL EDITOR라는 사운드 프로그램을 사용해보시길..."
제가 알고 있는 사운드 파이 포맷은 한 5가지 정도입니다.

그 중에서 일반적으로 널리 쓰이는 Window PCM 포맷을 초점을 맞추어
강좌를 써나간 겁니다. 다시 한번 이점에 대해서 양해를...
[WAV FILE FORMAT]
1. Window PCM
2. Window ADPCM
3. IMA ADPCM
4. ACM Wave
5. Mu/Law Wave
이렇게 다섯가지 입니다.
여기서 위 5가지의 포맷은 그렇게 크기 바뀌지 않습니다.
Window PCM에서 보았던 청크들중에서 한가지가 더 추가 한 것뿐입니다.
바로 "fact"라는 청크인데여... 이 청크에 대한 의미는 정확히 저도 잘 모르겠습니다.

그런데 한가지 저 청크를 조심스럽게 간단히 스킵을 하구...읽게 되니까...
사운드는 제대로 나오더군여... ^^;
그리고, ADPCM같은 경우의 포맷은 주로, 동영상 제작에 사용되는 사운드 포맷으로 널리
알려져 있는 포맷이기도 합니다.
압축된 포맷이죠...그래서 이 포맷은 일반 WAV포맷과 별다른 내용은 없지만, 그냥 데이터
필터링을 하지않구 그냥 출력하게되면, 치지익 하는 굉음이 나오게됩니다.
그리고, WAV파일을 재생하기 위한 필수 기본조건은 정확한 헤더를 얻는 방법을 아는것이라고
생각됩니다. 사운드를 재생하기에 앞서 재생하기 위한 충분한 정보가 제공되어야 하니깐여...^^;
ADPCM포맷의 WAV파일의 데이터 청크 내용은 저도 잘 모르겠습니다. ^^;
아~~~ 강좌 (3)에서는 조회수가 떨어질거라구 예상했었는데..흑흑...
역시나...너무 내용이 빈약 했던것 같군여...^^;
ADPCM이외의 포맷은 강좌 (4)에서 다루도록 하겠습니다.
그리고, 이쁘장한 재생기 프로그램 소스를 제공 해드리겠습니다.
좀 강좌 (4)의 내용은 음청나게 길어질듯...^^;
즐거운 시간 되십시여...^^;

 

 

=================================

=================================

=================================

 

 

 

 

반응형


관련글 더보기

댓글 영역