1.Wave 파일을 컨트롤

본장에서는 Wav 파일을 출력하는 방법에 대해서 설명합니다. 멀티미디어 파일을 출력하기 위해서는 MCI_ 계열의 함수를 이용해야 합니다. 이함수를 이용하여 간단하게 멀티미디어 파일을 출력할수 있습니다. 본장에서는 멀티미디어 파일에 대한 기본개념과 Wave파일을 위주로 MCI 계열의 함수를 출력하는 방법을 설명합니다.

본장에서는 하위 Audio 서비스 함수를 이용하여 웨이브 파일 구조를 분석하고 저장하는 방법을 설명합니다. 웨이브파일을 구조를 알고자 하는 분들에게 도움이 되실것입니다.

대상: 중급,고급

구분: 1,2,3 필수 나머지 선택

공부방법: 1,2,3 항목은 필수적으로 알아두는 것이 좋습니다. 요즘 제작하는 어플리케이션 프로그램치고 사운드를 기본적으로 지원하기 때문입니다. 나머지 항목은 음성을 분석하거나 또는 웨이브 구조가 필요한 분들만 공부하세요.

1.멀티미디어 프로그래밍

멀티미디어란 과연 어디까지이고 어떤 것들이 있는 것일까?

멀티미디어란 동영상, 음악, 텍스트를 자유자재로 이용할 수 있는 매체라고 말할 수 있습니다. 여기에 통신, TV, HOME AUTOMATION 등 각종 기능을 내재하고 상호 대화를 할 수 있는 컴퓨터를 말하기도 합니다.

윈도 95에서는 쉽게 멀티미디어 프로그래밍을 할 수가 있습니다. 윈도 95에서는 MCI(Media control Interface) 서비스를 제공하기 때문에 프로그래머는 MCI만 조절함으로써 영상과 음성을 조작할 수 있습니다. MCI에서 제공하는 매체들은 음성 파장 파일(*.WAV), CD-AUDIO, 동영상 파일(*.AVI), 각종 비디오 매체 등등 입니다. 이런 매체들을 작동할 때 프로그래머가 사용하는 라이브러리는 MMSYSTEM입니다.  MMSYSTEM은 Low-Level 함수와 High-Level 함수 2개로 나눌 수 있습니다.

멀티미디어 지원 프로그램		멀티미디어 프로그램
하이레벨 오디오 비디오 서비스					하이레벨 오디오, 비디오 서비스(MCI, SND)
						사운드 카드, 비디오 카드 제조 업체가 제공하는 MCI 디바이스 드라이버
로레벨 오디오 비디오 서비스		로레벨 오디오, 비디오 서비스(avi, midi, wave)
		사운드 카드, 비디오 카드 제조업체가 제공하는 로레벨 오디오 디바이스 드라이버

(그림 7-1-1)멀티미디어 프로그램 구동 구조

그림에서 보듯이 멀티미디어 프로그램에서 사운드나 비디오 등을 구동시키기 위한 명령을 내리면 그 명령은 각 회사에서 제공하는 멀티미디어 디바이스 드라이버에 전달되고 디바이스 드라이버는 선택된 멀티미디어 매체를 구동시킵니다.

예를 든다면 음성을 스피커로 출력시킬 때 하이레벨 오디오 서비스에 명령을 내리면 하이레벨 오디오 서비스는 로레벨 오디오 서비스에 명령을 전달하고 로레벨 오디오 서비스는 사운드 카드 회사에서 제공하는 오디오 디바이스 드라이버에 명령을 전달하며, 다시 오디오 디바이스 드라이버는 시스템에 부착되어 있는 사운드 카드를 구동시켜 음성이 나오게 됩니다.

프로그래머는 단지 공통적으로 사용하는 일반 명령 함수를 이용하여 디바이스 드라이버에 명령만 전달하면 되고 나머지 부분은 디바이스 드라이버가 담당하게 됩니다.

새로 사운드 카드나 비디오 카드를 사면 윈도용 드라이버를 주죠? 그리고 그것을 윈도에 설치하잖아요. 그런 드라이버들이 바로 로레벨, 하이레벨 미디어 서비스 드라이버입니다.

<1>하이레벨 서비스는 무엇이고 로레벨 서비스는 무엇인가?

음성을 예로 든다면 음성을 출력하고 녹음하고 하는 것들이 전부이지 그 외에 다른 기능들이 없잖아요. 이런 단지 출력과 녹음만을 설정하는 서비스를 하이레벨 서비스라고 합니다. 그런데 음성을 녹음할 때 소프트웨어적으로 에코를 넣는다든지 하는 어떤 데이터 수정이 필요할 경우가 있을 것입니다.

또는 두 개의 음성 데이터를 합성할 필요도 있고요. 이런 것을 하는 기능이 로레벨 서비스입니다. 말하자면 쉽게 간단하게 사용하도록 만든 게 하이레벨 서비스, 좀 어렵지만 세분화해서 사용할 수 있는 게 로레벨 서비스입니다.

로레벨 서비스는 윈도에서는 WAVE, MIDI, AVI 3가지만 지원합니다. 만일 MPEG 비디오 같은 특수 비디오를 구동할 경우에는 자신이 직접 로레벨 서비스를 만들어야 합니다.

본 장에서는 단지 디바이스 드라이버를 이용한 프로그램 구현에 대해 설명을 하겠습니다.

멀티미디어의 핵심 기술은 알 필요없이 단지 구동을 할 경우에는 로레벨 서비스에 대해서 공부를 할 필요는 없다고 봅니다. 물론 자세히 알기 위해서는 필요하지만요. 다음과 같은 경우는 하이레벨 서비스만으로도 충분합니다.

1. 간단한 음성 지원 소프트웨어

2. 멀티미디어 CD-ROM 타이틀

3. 간단한 게임 프로그램

위 3개에 해당하는 프로그램을 제작할 때에는 하이레벨 서비스를 이용하는 게 좋습니다. 물론 로레벨 서비스를 이용해도 되지만 화면에 출력되는 비디오나 음성은 로레벨을 이용하나 하이레벨을 이용하나 똑같습니다. □이거 로레벨 서비스를 이용해서 만들었어!□ 해봤자 실력 좋다고 하지는 않습니다. 또한 로레벨 서비스를 이용하여 잘 만들지 못하면 오히려 하이레벨 서비스보다도 못할 경우가 있습니다.

예를 든다면 10메가급 음성을 하이레벨 서비스를 이용하면 아주 편하게 구동하지만 로레벨 서비스는 10메가를 사용하기 좋게 일일이 잘라서 블록을 구성하고 출력해야 하기 때문에 프로그램을 제작하는 데 시간이 무척 많이 걸립니다. 사소한 일에 목숨 걸어서 좋을 것 없습니다.

윈도 프로그램을 아주 잘한다고 프로그래머가 아닙니다. 알고리듬을 많이 알고 있어야 진정한 프로그래머죠. 발차기나 칼쓰기를 잘한다고 진정한 무인은 아니죠. 단지 무술을 잘하는 사람일 뿐입니다. 기공을 수련해서 내공을 쌓고 더불어 무술을 배운 자가 진정한 무인이 아니겠습니까?

윈도 프로그램 잘 짜려고 고생하는 시간에 알고리듬 하나 더 만들어서 프로그램 하는 게 진정한 프로그래머의 갈길이 아닐까요?

2.WAV 파일을 작동하자

윈도 95 안에서 소리를 지원하는 파형 사운드 파일은 보통 WAVE 파일이라고 합니다.

제1장  WAV 파일을 작동하자

WAVE 파일을 출력하는 방법은 3가지로 볼 수 있습니다.

① SndPlaySound() 등 간단한 함수 사용

② MCI 명령 사용

③ 로레벨 오디오 서비스 함수 사용

①로 실행할 때는 SndPlaySound("test.wav",SND_ASYNC)로 하면 TEST.WAV 파일이 자동적으로 출력됩니다. ②의 경우는 약간 복잡하기는 하지만 몇 가지 함수를 사용하여 간단하게 출력할 수 있습니다. ③과 같은 경우 진짜 복잡하다고 볼 수 있지요. 그럼 각각 어떤 경우에 사용할까요? 한 10초도 안 되는 짧은 음성의 재생시에는 ①만 사용하여 간단하게 출력할 수 있습니다. 그러나 무지막지하게 긴 것 예를 든다면 김건모의 스피드를 WAVE 파일로 녹음하면 ①로 출력하기가 무척 힘듭니다. 간단히 말해서 SndPlaySound("speed.wav",SND_ASYNC)라고 실행하고 약 30분 기다린 후에야 □오 제발! 오 제발!□ 이럴 것입니다. 이럴 때는 ②를 사용합니다. 또한 간단하게 녹음을 할 때도 ②를 사용합니다.

그럼 ③은 어떨 때 사용하는가? WAVE 데이터를 편집할 때 사용합니다. 예를 든다면 WAVE의 파형을 화면에 도시할 때 WAVE 데이터의 값을 알아야 파장을 그리잖아요. 또한 녹음을 하거나 다른 데이터와 믹싱을 할 때 또는 에코를 넣을 때 사용하죠. 3가지 방법 중 어떤 방법이 제일 좋냐고 물어 보는 것은 멍청한 생각입니다. 3가지 방법 다 쓸모가 있기 때문이죠. 이 책에서는 위의 3가지 모두를 해보겠습니다. 이번에 만드는 프로그램은 mciwave.exe와 wavemix.exe입니다. 실행 파일을 직접 실행시켜 보세요.

그림 7-1-1은 WAVEMIX.EXE를 실행한 화면입니다.

(그림 7-1-1)WaveMix.exe 실행 결과

<1> SndPlay 함수

SndPlay 함수는 다음과 같습니다.

WORD sndPlaysound(LPSTR lpszSound,UINT fsSound);

lpszSound는 출력할 sound의 이름을 나타냅니다. 이 이름은 WIN.INI 파일에 기록되어 있는 SOUND 이름일 수도 있고 파일 이름일 수도 있습니다. 또한 메모리 포인터(APSTUDIO에서 만든 ID)일 수도 있습니다. 그럼 lpszSound 이름이 무엇인지 알기 위해서는 어떻게 해야 할까요? fsSound 플래그를 보면 알 수 있습니다. SND_MEMORY라고 설정하면 메모리에 올라가 있는 사운드 이미지의 이름을 이야기하고 그 외에는 모두 파일로 간주합니다.

fsSound 플래그에는 다음과 같은 것들이 있습니다.

□SND_ASYNC : 재생시 다른 작업을 할 수 있게 비동기식으로 작동을 하라는 플래그

□SND_SYNC : 재생이 끝날 때까지 아무일도 하지 못하게 하는 설정 플래그

□SND_LOOP : 재생이 끝나면 자동적으로 처음으로 가서 다시 재생을 하라는 플래그. 카세트의 오토리버스(AUTO REVERSE) 기능을 말한다.

□SND_NODEFAULT : 재생이 실패로 돌아가면 DEFAULT 소리를 내는데 이것을 하지 말라는 플래그

<2>파일에서 읽어서 재생

C:\WINDOWS\SYSTEM\ROCKNROL.WAV가 있을 경우 다음과 같이 하면

sndPlaySound("C:\\WINDOWS\\SYSTEM\\ROCKROL.WAV",SND_ASYNC);

ROCKROL.WAV 파일을 재생합니다. 이 때 ROCKROL.WAV가 없으면 □띵!□ 이런 소리가 납니다. 이 소리는 WIN.INI에 설정되어 있는 소리입니다. 이 소리를 내지 않게 하기 위해서는 다음과 같이 합니다.

sndPlaySound("C:\WINDOWS\SYSTEM\ROCKROL.WAV",SND_ASYNC|SND_NODEFAULT);

그럼 파일이 없으면 아무 소리도 없이 그냥 실행이 종료됩니다. 재생 도중에 아무 작업도 할 수 없게 하려면 다음과 같이 해주면 됩니다.

sndPlaySound("C:\WINDOWS\SYSTEM\ROCKROL.WAV",SND_SYNC|SND_NODEFAULT);

재생이 자동 반복을 하게 만들려면 다음과 같이 합니다.

sndPlaySound("C:\WINDOWS\SYSTEM\ROCKROL.WAV",SND_ASYNC|SND_LOOP);

(잔소리)

SND_SYNC|SND_LOOP 하면 절대 안돼요!

왜냐하면 재생 도중에 아무런 작업도 할 수가 없고 재생이 무한히 반복되기 때문입니다.

컴퓨터를 끄기 전까지 계속적으로 소리를 듣고 싶으면 할 말 없지만 똑같은 소리 반복해서 들으면 10분만 지나도 지겨울 것입니다. 필자는 프로그램 제작중에 음악을 많이 듣는데 같은 음악이 3번만 나와도 지겹더라구요.

(잔소리끝)

<3>메모리에서 읽어서 재생

게임을 만들거나 멀티미디어 CD를 만들 때 자주 사용하는 음성들은 미리 메모리에 올려놓고 있다가 사용하면 매우 편리합니다. 이럴 때 사용하는 것이 RESO- URCE에 음성을 등록시킨 후에 그것을 로드하는 방법입니다. 리소스 파일에 다음과 같이 등록시킵니다.

soundName DING C:\WINDOWS\DING.WAV

이렇게 하고 DING라는 명을 이용하여 메모리에 로드시킵니다. 이 때 사용하는 플래그가 SND_MEMORY입니다.

BOOL PlayRc(LPSTR filename)

{

HANDLE pWaveInfo,hWaveRes;

LPSTR lpWaveRes;

//웨이브를 찾아서

pWaveInfo = FindResource(NULL,filename,"WAVE");

//웨이브 리소스를 로드한다.

hWaveRes = LoadResource(NULL,pWaveInfo);

//웨이브 리소스를 잠근다.

lpWaveRes = LockResource(hWaveRes);

//음성을 출력한다.

sndPlaySound(lpWaveRes,SND_MEMORY|SND_SYNC);

//웨이브 리소스를 푼다.

UnlockResource(hWaveRes);

//웨이브 리소스를 해제한다.

ReeeResource(hWaveRes);

}

<4> MCI 명령 사용

MCI 명령이란 멀티미디어를 구동할 때 일반적으로 사용하는 명령입니다. 이 함수들은 MMSYSTEM.H에 정의되어 있으므로 프로그램을 작성할 때 본 함수를 소스에 인클루드해야 합니다. MMSYSTEM.H를 포함 하기 위해서는  Project메뉴에서 Add To Project를 설정하고 Components and Controls항목을 클랙하여 대화상자가 나오면 Developer  Studio Components 를 선택하고 그림 7-1-2와 같은 화면에서

Windows Multimedia library를 선택하여 삽입시킨면 됩니다.

(그림 7-1-2) Components and Controls Gallery

MCI란 하이레벨 오디오 서비스로서 명령을 하이레벨 오디오 디바이스 드라이버에 전한다는 이야기를 했습니다. 따라서, 명령만 내리면 되죠. 그렇다면 명령을 내리는 함수 하나면 있으면 되겠네요. 그 함수가 바로 mciSendCommand() 함수입니다. 이외에 명령을 내리는 함수가 하나 더 있는데 그 함수명이 mciSendString()입니다. 여기서는 mciSendString을 설명하지 않습니다. mciSendCommand() 함수 하나로 충분하기 때문이죠. 함수의 형태는 다음과 같습니다.

MCIERROR mciSendCommand(MCIDEVICEID IDDevice, UINT uMsg ,DWORD fdwCommand, DWORD dwParam);

IDDevice란 디바이스 드라이버 ID를 말합니다. 사운드 카드 드라이버, 비디오 카드 드라이버, MPGE 카드 드라이버 등등 제어판에 설정되어 있는 드라이버가 굉장히 많아요.

그럼 명령을 내리는데 어느 드라이버로 할 것인가가 중요해집니다. 소리를 출력하라고 명령을 내리고 ID를 비디오 카드로 해주면 소리가 날까요?

그래서 각 디바이스 드라이버에 ID를 부여해 주었습니다.

UMsg란 명령 메시지입니다. 소리를 내라, 소리를 멈추어라, 음성을 녹음해라 등등 여러 명령 메시지를 설정하는 것입니다.

fdwCommand란 명령을 내릴 때 플래그입니다. 예를 든다면 소리를 내라 - 소리를 낼 때 아무일도 할 수 없게 해라- 여기서 소리를 내라는 명령어는 UMsg에 설정하면 되겠지요. 그럼 소리를 낼 때 아무일도 할 수 없게 하라를 바로 fdwCo- mmand에 설정하면 됩니다.

dwParam란 보고서를 받는 포인터입니다. 예를 든다면 소리를 내라!하고 명령을 내리면 사운드 카드 디바이스 드라이버에서 □예 소리를 냈습니다. 현재 소리는 스테레오이고요, 약 20초 간 나올 것입니다.□라고 보고를 해야 하죠. 바로 이 보고를 받는 포인터입니다.

바로 위 함수 한 개로 모든 하이레벨 오디오 서비스를 제어합니다. 여기에 부과된 두 가지 함수가 있는데 다음과 같습니다.

□mciGetdeviceID() : 디바이스 드라이버 ID를 받는다.

□mciGetErrorString() : 에러가 있을 때 에러 내용을 받는다.

dwParam을 보고서라고 했는데 어떤 실행이 있은 후 서로 메시지를 주고받는 서류가 있어야 할 것 아니겠습니까? 물건을 배달시킬 때 이런 서류를 만들듯이 말입니다.

물류 배달서

6물건명 : 바나나 한 박스

트럭기사 : _____ (인)

발송자 : 이길동 (인)

수납자 : _____ (인)

트럭 기사가 물건을 받고 이름을 쓰고 사인을 하죠? 그리고 수납자에게 가면 수납자는 이름을 쓰고 사인을 하고요. 이렇게 어떤 명령이 실행될 때 서로 주고받는 데이터를 저장할 보고서가 있어야 하지요.

음성 출력시 출력에 대한 보고서, 녹음시 녹음에 대한 보고서, 재생시 재생에 대한 보고서 등등이 있을 것입니다. 그런 보고서를 PARMS라고 하여 MMSYSTEM.H에 만들어 놓은 구조체가 있습니다.

그 구조체는 다음과 같습니다.

□MCI_OPEN_PARMS : 디바이스 드라이버를 열 때 보고서

□MCI_PLAY_PARMS : 음성을 재생할 때 보고서

□MCI_RECORD_PARMS : 음성을 녹음할 때 보고서

□MCI_STATUS_PARMS : 현재 상태를 체크할 때 보고서

uMsg란 명령 메시지라고 했는데 명령 메시지는 다음과 같습니다.

□MCI_OPEN : 디바이스 드라이버를 열어라.	-사용 보고서→MCI_OPEN_PARMS

□MCI_PLAY : 음성을 출력해라.	-사용 보고서 → MCI_PLAY_PARMS

□MCI_RECORD : 음성을 녹음해라.	-사용 보고서 → MCI_RECORD_PARMS

□MCI_STATUS : 현재 상태 체크	-사용 보고서 → MCI_STATUS_PARMS

□MCI_SAVE   : 데이터를 저장	-사용 보고서 → MCI_SAVE_PARMS

□MCI_PAUSE  : 일시 중단

□MCI_SEEK   : 음성의 측정 위치로 가라.

□MCI_CLOSE  : 현재 디바이스를 닫아라.

그럼 명령 내리는 것은 쉽겠네요.

예를 든다면 음성을 출력해라!라고 하려면

mciSendCommand(ID,MCI_PLAY,모름,MCI_PLAY_PARMS 형);

음성 디바이스를 열어라!라고

mciSendCommand(NULL,MCI_OPEN,모름,MCI_OPEN_PARMS 형);

이렇게 하면 되죠. 여기서 모름이란 부명령 플래그입니다. 부명령에는 굉장히 많은 플래그가 있으므로 플래그에 대해서는 함수를 하나하나 만들어 갈 때마다 설명하겠습니다.

<5>사운드 디바이스 드라이버 열기

음성을 출력하기 이전에 제일 먼저 해야 할 일이 사운드 드라이버를 여는 것입니다. 우선 프로그램부터 보겠습니다.

MCI_OPEN_PARMS mciOpenParms;

DWORD wDeviceID;

int erroCode;

mciOpenParms.lpstrElementName = filename;// 파일 설정

mciOpenParms.lpstrDeviceType = "waveaudio";//디바이스를 waveaudio로 설정

erroCode=mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_ELEMENT|MCI_OPEN_TYPE,

(DWORD)(LPVOID)&mciOpenParms);

wDeviceID = mciOpenParms.wDeviceID;//다바이스 ID를 받는다.

사운드 드라이버를 열 때 웨이브 파일 출력으로 열 때가 있고 미디 음악 출력으로 열 때가 있고 CD-AUDIO로 열 때가 있습니다. 따라서, 열어야 하는 형태를 말해 주어야 합니다.

열어야 할 때 여는 내용을 상호 전달하는 보고서는 MCI_OPEN_PARMS 구조체입니다.

MCI_OPEN_PARMS mciOpenParms;

위와 같이 mciOpenParms;를 오픈시 상호 전달 보고서 구조체로 등록합니다.

mciOpenParms.lpstrElementName = filename;

MCI_OPEN_PARMS 구조체 안의 lpstrElementName에 웨이브 파일을 설정해 줍니다.

mciOpenParms.lpstrDeviceType= "waveaudio";

디바이스 형태를 waveaudio로 설정합니다. 즉 웨이브 파일을 출력하는 오디오로 설정하겠다는 이야기입니다. 이제 명령을 내려야겠죠.

mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_ELEMENT|MCI_OPEN_TYPE,

(DWORD)(LPVOID)&mciOpenParms);

위의 명령을 해석하면 □ID를 모르겠지만 mciOpenParms 안에 있는 lpstrDevi- ceType형으로 lpstrElementName 파일을 열되 여는 방법은 하나의 요소를 설정하기 위한 형으로 디바이스 드라이버를 열어라!□라는 이야기입니다. 즉  파형 파일을 출력하는 디바이스 드라이버를 열고 파형 파일 filename을 출력 디바이스에 걸어라 하는 말이죠.

이 때 드라이버가 위 명령을 받고 실행에 성공하면 드라이버는 0을 리턴하고 mciOpenParms 안의 wDeviceID에 현재 설정되어 있는 디바이스 드라이버 ID를 기록합니다.

<6>음성 출력하기

음성을 출력하기 위한 프로그램을 먼저 보겠습니다.

MCI_PLAY_PARMS mciPlayParms;

mciSendCommand(wDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD)(LPVOID) &mciPlayParms);

위에서 받은 ID로 MCI_PLAY 명령을 해주면 재생이 됩니다. □디바이스 드라이버! 재생하다가 무슨 일이 있으면 나에게 보고해라, 알겠나?□하는 이야기가 바로 MCI_NOTIFY입니다. 이 때 보고는 mciPlayParms에 메시지로 기록합니다. mciPl- ayParms.dwCallback에 설정되어 있는 윈도에 MCI_NOTIFY 메시지가 발송됩니다. 그렇게 되면 설정되어 있는 윈도로 메시지 이벤트가 발생됩니다.

그렇다면 위의 함수를 좀 바꾸어야겠죠.

mciPlayParms.dwCallback=m_hWnd;

mciSendCommand(wDeviceID,MCI_PLAY,MCI_NOTIFY,(DWORD)(LPVOID) &mciPlayParms);

사실 음성 출력할 때 뭐 보고할 게 있겠습니까? 그냥 출력이 끝나면 되지 □아! 지금 1초 출력했습니다!□, □아! 지금 2초 출력했습니다!□ 이렇게 메시지를 윈도에 보내면 현재 윈도가 아마 이럴지도 모릅니다.

□야! 지금 딴 메시지 받는 것도 정신없는데... 시끄럽다!□

필자는 그래서 음성으로 출력시키고 아무런 메시지를 받지 않습니다. 그래서 다음과 같이 설정합니다.

mciPlayParms.dwCallback=NULL;

<7>음성 녹음하기

음성을 녹음할 때의 프로그램은 다음과 같습니다.

MCI_RECORD_PARMS mciRecordParms;

mciRecordParms.dwTo=10000;//밀리 세컨드

mciSendCommand(wDeviceID,MCI_RECORD,MCI_TO | MCI_WAIT,(DWORD)(LPVOID)&mciRecordParms))

여기에서 MCI_WAIT란 레코딩하는 중간에 아무런 일도 하지 말고 기다리라는 이야기이고 MCI_TO란 mciRecordParms.dwTo에 설정한 시간까지 녹음을 하라는 말입니다.

<8>음성 저장하기

음성을 저장할 때의 프로그램은 다음과 같습니다.

MCI_SAVE_PARMS mciSaveParms;

mciSaveParms.lpfilename=filename;

mciSendCommand(wDeviceID,MCI_SAVE,MCI_SAVE_FILE | MCI_WAIT,(DWORD)(LPVOID)&mciSaveParms);

MCI_SAVE_FILE은 파일에 저장하라는 플래그이고 MCI_WAIT란 파일에 저장하는 동안 아무런 동작을 하지 말라는 이야기입니다.

<9>음성 데이터 포지션 이동

□처음으로 이동할 경우

mciSendCommand (wDeviceID, MCI_SEEK, MCI_SEEK_TO_START, (DWORD) (LPVOID) NULL);

□마지막으로 이동할 경우

mciSendCommand (wDeviceID, MCI_SEEK, MCI_SEEK_TO_END, (DWORD) (LPVOID) NULL);

<10>재생 멈춤

재생 멈춤은 다음과 같습니다.

mciSendCommand(wDeviceID,MCI_PAUSE,MCI_NOTIFY,(DWORD)(LPVOID) &mciPlayParms);

3. MCI 명령을 사용한 클래스 만들기

MCI 명령을 이용한 클래스를 만들어 두었다가 필요할 때 라이브러리로 사용하면 굉장히 편합니다. 이번에는 MCI 명령을 이용한 클래스를 만들어 보겠습니다. 클래스명은 CMySound로 설정했습니다. 먼저 클래스 헤더를 보겠습니다.

클래스명 : CMySound	파일명 mysound.h

class CMySound

{

public:

CMySound	(LPSTR lpszFileName); // 출력용 생성자

CMySound	(LPSTR lpszFileName,DWORD Sec);//녹음용 생성자

-CMySound	( );//소멸자

BOOL Play( );//재생

void Pause( );//처음으로

BOOL Home( );//마지막으로

UNIT m_nTime;//데이터의 총시간

protected:

MCI_OPEN_PARMS mciOpenParms;

MCI_PLAY_PARMS mciOpenParms;

MCI_RECORD_PARMS mciOpenParms;

MCI_SAVE_PARMS mciOpenParms;

DWORD wDeveceID;

CMySound 클래스에서는 두 가지 생성자를 두었습니다. 음성을 재생할 때 생성자, 음성을 녹음할 때 생성자로요. 재생 생성자의 소스는 다음과 같습니다.

//생성자 웨이브 파일을 출력시키는 생성자

CMySound::CMySound(LPSTR filename)

{

// 재생할 파일명을  설정

mciOpenParms.lpstrElementName = filename;

//디바이스를 waveaudio로 설정한다.

mciOpenParms.lpstrDeviceType = "waveaudio";

//MCI_OPEN 명령을 주어 디바이스를 열고 ID를 받는다.

mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_ELEMENT|MCI_OPEN_TYPE,

(DWORD)(LPVOID)&mciOpenParms);

//열린 디바이스 ID를 wDeviceID에 설정한다.

wDeviceID = mciOpenParms.wDeviceID;

//Callback 함수 없음.

mciPlayParms.dwCallback = NULL;

//이 다음에는 파일의 전체 출력 시간을 얻어 둔다. 프로그램 제작시에 현재

//재생되는 음성의 전체 시간을 아는 것은 매우 유용하다.

// 전체 출력 시간을 알기 위해 m_nTime에 설정한다.

MCI_STATUS_PARMS mciStatusParms;

mciStatusParms.dwItem= MCI_STATUS_LENGTH;

mciSendCommand(wDeviceID,MCI_STATUS,MCI_STATUS_ITEM,(DWORD)(LPVOID)&mciStatusParms);

m_nTime=(LONG)mciStatusParms.dwReturn;

}

녹음 생성자는 다음과 같습니다.

//녹음을 위한 생성자

CMySound::CMySound(LPSTR filename,DWORD Sec)

{

BOOL erroCode;

//파일 이름 없이 디바이스를 연다.

mciOpenParms.lpstrElementName = "";

mciOpenParms.lpstrDeviceType = "waveaudio";

//디바이스를 여는 것은 위의 재생을 위한 생성자와 같다.

if(erroCode=mciSendCommand(NULL,MCI_OPEN,MCI_OPEN_ELEMENT|MCI_OPEN_TYPE,

(DWORD)(LPVOID)&mciOpenParms))

{

return;

}

wDeviceID = mciOpenParms.wDeviceID;

//녹음 시간 설정

mciRecordParms.dwTo=Sec*1000;

//재생 명령이 아닌 녹음 명령을 준다.

//녹음 시작 MCI_WAIT이기 때문에 녹음중에는

//아무 일도 하지 않는다.

if(erroCode=mciSendCommand(wDeviceID,MCI_RECORD,MCI_TO | MCI_WAIT,

(DWORD)(LPVOID)&mciRecordParms))

{

mciSendCommand(wDeviceID,MCI_CLOSE,0,NULL);

return;

}

//녹음이 끝나면 파일에 저장한다.

mciSaveParms.lpfilename=filename;

if(erroCode=mciSendCommand(wDeviceID,MCI_SAVE,MCI_SAVE_FILE | MCI_WAIT,

(DWORD)(LPVOID)&mciSaveParms))

{

mciSendCommand(wDeviceID,MCI_CLOSE,0,NULL);

return;

}

}

나머지 클래스에 대한 소스는 다음과 같습니다.

//소멸자

CMySound::~CMySound()

{

//디바이스를 닫는다.

mciSendCommand(wDeviceID,MCI_CLOSE,0,NULL);

}

//재생

BOOL CMySound::Play()

{

mciSendCommand(wDeviceID,MCI_PLAY,MCI_NOTIFY,

(DWORD)(LPVOID) &mciPlayParms);

return TRUE;

}

//멈춤

void CMySound::Pause()

{

mciSendCommand(wDeviceID,MCI_PAUSE,MCI_NOTIFY,

(DWORD)(LPVOID) &mciPlayParms);

}

//처음으로

BOOL CMySound::Home (void)

{

mciSendCommand (wDeviceID, MCI_SEEK, MCI_SEEK_TO_START, (DWORD) (LPVOID) NULL);

return TRUE;

}

//마지막으로

BOOL CMySound::End (void)

{

mciSendCommand (wDeviceID, MCI_SEEK, MCI_SEEK_TO_END, (DWORD) (LPVOID) NULL);

return TRUE;

}

<1>MCI를 이용한 프로그램 제작

위에서 제작한 CMySound 클래스와 sndPlay 함수를 이용하여 녹음하고 재생하는 프로그램을 제작해 보겠습니다. 그림 7-1-3가 본 프로그램의 실행 모습입니다.

(그림 7-1-3)MCIWAVE 실행결과

파일 부르기 버튼을 눌러서 웨이브 파일을 선택하고 SndPlaySound() 버튼을 눌러서 SndPlaySound() 함수를 실행하고 MCI_PLAY 버튼을 눌러서 현재 음성을 재생하고 <= 버튼을 누르면 재생중에 처음으로, => 버튼을 누르면 재생중에 마지막으로 이동하고 MCI_RECORD를 누르면 녹음이 되는 프로그램입니다.

본 프로그램은 Application 형태를 Dialog based로 작성합니다.

<2>	프로그램 소스

(프로그램 소스시작)

// mciwaveDlg.h : header file

// 하이레벨 오디오 서비스를 이용한 WAVE 파일 작동법

//

///////////////////////////////////////////////////////////////////////////

// CMciwaveDlg dialog

class CMciwaveDlg : public CDialog

{

// Construction

public:

CMciwaveDlg(CWnd* pParent = NULL); // standard constructor

// Dialog Data

//{{AFX_DATA(CMciwaveDlg)

enum { IDD = IDD_MCIWAVE_DIALOG };

int m_nStatus;

CMySound *m_pOutSound; //하이레벨 오디오 서비스 클래스 출력용

//}}AFX_DATA

char m_strFileName[80];// 파일명을 저장

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMciwaveDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

HICON m_hIcon;

// Generated message map functions

//{{AFX_MSG(CMciwaveDlg)

virtual BOOL OnInitDialog();

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

afx_msg void OnFileopen();

afx_msg void OnMciplay();

afx_msg void OnSndplay();

afx_msg void OnStop();

afx_msg void OnRwind();

afx_msg void OnFwind();

afx_msg void OnRecord();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

// mciwaveDlg.cpp : implementation file

// 하이레벨 오디오 서비스를 이용한 WAVE 파일 작동법

// 제작자 : 이상엽

//

#include "stdafx.h"

#include "mciwave.h"

#include "mmsystem.h"

#include "mysound.h"

#include "mciwaveDlg.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

#define PR_NONE 0

#define PR_MCIPLAY 1

#define PR_SNDPLAY 2

#define PR_RECORD 3

///////////////////////////////////////////////////////////////////////////

// CMciwaveDlg dialog

CMciwaveDlg::CMciwaveDlg(CWnd* pParent /*=NULL*/)

: CDialog(CMciwaveDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CMciwaveDlg)

//}}AFX_DATA_INIT

m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

}

void CMciwaveDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CMciwaveDlg)

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CMciwaveDlg, CDialog)

//{{AFX_MSG_MAP(CMciwaveDlg)

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_BN_CLICKED(IDC_FILEOPEN, OnFileopen)

ON_BN_CLICKED(IDC_MCIPLAY, OnMciplay)

ON_BN_CLICKED(IDC_SNDPLAY, OnSndplay)

ON_BN_CLICKED(IDC_STOP, OnStop)

ON_BN_CLICKED(IDC_RWIND, OnRwind)

ON_BN_CLICKED(IDC_FWIND, OnFwind)

ON_BN_CLICKED(IDC_RECORD, OnRecord)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////

// CMciwaveDlg message handlers

BOOL CMciwaveDlg::OnInitDialog()

{

CDialog::OnInitDialog();

SetIcon(m_hIcon, TRUE);   // Set big icon

SetIcon(m_hIcon, FALSE);  // Set small icon

//모든 버튼을 비활성화 한다.

GetDlgItem(IDC_SNDPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_MCIPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_RWIND)->EnableWindow(FALSE);

GetDlgItem(IDC_FWIND)->EnableWindow(FALSE);

GetDlgItem(IDC_STOP)->EnableWindow(FALSE);

GetDlgItem(IDC_RECORD)->EnableWindow(FALSE);

//파일명을 NONAME.WAV로 설정한다.

lstrcpy((LPSTR)m_strFileName,(LPSTR)"NONAME.WAV");

return TRUE;  // return TRUE  unless you set the focus to a control

}

void CMciwaveDlg::OnPaint()

{

if (IsIconic())

{

CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle

int cxIcon = GetSystemMetrics(SM_CXICON);

int cyIcon = GetSystemMetrics(SM_CYICON);

CRect rect;

GetClientRect(&rect);

int x = (rect.Width() - cxIcon + 1) / 2;

int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon

dc.DrawIcon(x, y, m_hIcon);

}

else

{

CPaintDC dc(this);

//글자 출력시 바탕에 투명하게 출력

dc.SetBkMode(TRANSPARENT);

//IDC_FILENAME 자원의 박스 좌표를 구한다.

CRect rect;

CWnd* pWnd =GetDlgItem( IDC_FILENAME );

pWnd->GetWindowRect( &rect );

ScreenToClient( &rect );

//그 위치에 파일명을 출력한다.

dc.TextOut(rect.left+40,rect.top+10,m_strFileName);

}

}

// The system calls this to obtain the cursor to display while the user drags

//  the minimized window.

HCURSOR CMciwaveDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

void CMciwaveDlg::OnFileopen()

{

//파일 다이얼로그 실행

CString szFileName;

CFileDialog dlg(TRUE,_T("WAV"),_T("*.WAV"),OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

_T("WAVE FILE (*.WAV)|*.WAV|ALL FILE (*.*)|*.*|"));

if(dlg.DoModal()==IDOK)

{

//파일명을 설정하면

szFileName=dlg.GetPathName();

//파일명에 해당하는 파일명을 설정한다.

lstrcpy((LPSTR)m_strFileName,(LPSTR)szFileName.operator const char*());

//모든 버튼을 활성화한다.

GetDlgItem(IDC_SNDPLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_MCIPLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_RWIND)->EnableWindow(TRUE);

GetDlgItem(IDC_FWIND)->EnableWindow(TRUE);

GetDlgItem(IDC_STOP)->EnableWindow(TRUE);

//레코드는 활성화 하지 않는다.

GetDlgItem(IDC_RECORD)->EnableWindow(FALSE);

}

}

void CMciwaveDlg::OnMciplay()

{

//MCI로 재생

m_pOutSound=new CMySound((LPSTR)m_strFileName);

m_pOutSound->Play() ;

m_nStatus=PR_MCIPLAY;

GetDlgItem(IDC_SNDPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_MCIPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_RECORD)->EnableWindow(FALSE);

}

//sndPlaySound 함수로 재생

void CMciwaveDlg::OnSndplay()

{

sndPlaySound(m_strFileName,SND_ASYNC);

m_nStatus=PR_SNDPLAY;

GetDlgItem(IDC_MCIPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_SNDPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_RECORD)->EnableWindow(FALSE);

GetDlgItem(IDC_RWIND)->EnableWindow(FALSE);

GetDlgItem(IDC_FWIND)->EnableWindow(FALSE);

}

//멈춤을 눌렀을 때

void CMciwaveDlg::OnStop()

{

switch(m_nStatus)

{

//만약 sndPlay 함수이면

case PR_SNDPLAY:

sndPlaySound(NULL,NULL);

break;

//MCI로 녹음이나 재생이면

case PR_MCIPLAY:

delete m_pOutSound;

break;

case PR_RECORD:

delete m_pOutSound;

break;

}

//모든 버튼을 활성화한다.

m_nStatus=PR_NONE;

GetDlgItem(IDC_SNDPLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_MCIPLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_RWIND)->EnableWindow(TRUE);

GetDlgItem(IDC_FWIND)->EnableWindow(TRUE);

GetDlgItem(IDC_STOP)->EnableWindow(TRUE);

GetDlgItem(IDC_RECORD)->EnableWindow(TRUE);

}

//처음으로

void CMciwaveDlg::OnRwind()

{

// TODO: Add your control notification handler code here

if( m_nStatus=PR_MCIPLAY)

{

m_pOutSound->Home();

m_pOutSound->Play();

}

}

//마지막으로

void CMciwaveDlg::OnFwind()

{

if( m_nStatus=PR_MCIPLAY)

m_pOutSound->End();

}

//녹음

void CMciwaveDlg::OnRecord()

{

//파일 다이얼로그 실행

CString szFileName;

CFileDialog dlg(FALSE,_T("WAV"),_T("*.WAV"),OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

_T("WAVE FILE (*.WAV)|*.WAV|ALL FILE (*.*)|*.*|"));

if(dlg.DoModal()==IDOK)

{

//파일명을 설정하면

szFileName=dlg.GetPathName();

//파일명에 해당하는 파일명을 설정한다.

lstrcpy((LPSTR)m_strFileName,(LPSTR)szFileName.operator const char*());

//모든 버튼을 활성화한다.

GetDlgItem(IDC_SNDPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_MCIPLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_RWIND)->EnableWindow(FALSE);

GetDlgItem(IDC_FWIND)->EnableWindow(FALSE);

GetDlgItem(IDC_STOP)->EnableWindow(TRUE);

GetDlgItem(IDC_RECORD)->EnableWindow(FALSE);

m_pOutSound = new CMySound((LPSTR)m_strFileName,(DWORD)10);

m_nStatus=PR_RECORD;

}

}

4. 로레벨 오디오 서비스

상위 레벨 오디오 서비스인 MCI 함수는 한 개의 명령을 받아 수행할 때 여러 개의 작업을 수행합니다. 이 때 하위 레벨 오디오 서비스 함수를 호출하여 프로그램을 수행합니다. WAVE 파일을 한 개 출력시킬 때 MCI를 호출하면 많은 하위 레벨 오디오 서비스 함수를 호출합니다. 하지만 MCI 자체는 녹음과 재생만 할 뿐 WAVE 파일을 수정하거나 특정 부분만 출력하는 등 복잡한 기능을 제공해 주지는 않습니다. WAVE STUDIO와 같이 녹음하고 데이터를 변조시키고 웨이브 파형을 도시시킬 때는 로레벨 오디오 서비스를 이용합니다.

로레벨 오디오 서비스를 이용한 파형 오디오의 재생 및 녹음시킬 때 두 가지 파트로 분리되어 있습니다.

① 파일에서 읽고 쓰는 PART(RIFF 파일 읽기)

② 데이터를 컨트롤하는 PART(wave 함수)

여기서는 로레벨 오디오 서비스를 이용하여 파형을 화면에 도시하고 녹음하고 출력하고 스피커 볼륨을 조절하는 기능을 만들어 보겠습니다.

<1>RIFF 파일(Resource Interchange File Format)

음성이나 비디오 같은 데이터들은 용량이 매우 큰 데이터입니다. 이런 데이터를 저장할 때는 블럭 단위로 저장하는 것이 매우 편합니다. 예를 든다면 10메가의 음성 데이터를 파일에 저장한다고 가정했을 때 블럭 단위로 하지 않으면 데이터를 로드하는 데 10메가의 메모리가 필요하게 되지요. 그렇게 되면 로드하기도 힘들 뿐 아니라 로드하는 데 시간이 매우 많이 걸립니다. 10메가의 데이터를 0.5메가씩 블럭으로 나누어 저장한다면 20개의 블럭이 되겠지요. 그럼 그림과 같이 될것입니다.

0.5메가 음성(블럭 1)

1메가 음성(블럭 2)

1.5메가 음성(블럭 3)

10메가 음성(블럭 20)

위와 같이 20개의 블럭으로 저장한 후 0.5메가씩 메모리에 로드한 후에 출력하고 메모리를 해제한 후 다시 다음 블럭을 로드하면 매우 편리합니다. 그런데 □난 1024바이트씩을 한 블럭으로 하고 싶어요.□ 하는 사람은 자신이 원하는 형태로 블럭을 저장하도록 합니다.

그렇다면 헤더를 만들어 넣고 위의 구조체를 바꾸어야 하겠죠.

블럭 사이즈 1024

1024바이트 음성(블럭 1)

2048바이트 음성(블럭 2)

마지막 음성(블럭 100)

이처럼 데이터 저장 블럭 앞에 블럭에 대한 크기를 넣으면 좋을 것입니다. 그런데 이럴 경우 음성을 녹음했는데 전체 음성 중 약 10분 동안은 모노로 나오다가 10분 후에 스테레오로 나오거나 처음 데이터는 샘플링을 11kHz로 했는데 10분 후에는 22kHz로 했을 경우, 또는 처음 데이터는 8비트 사운드인데 나중 데이터는 16비트 사운드일 경우 이렇게 매우 복잡하게 데이터가 변할 수도 있습니다.

이럴 땐 데이터 블럭 앞에 데이터에 대한 정보를 만들어 주는 게 좋겠지요.

블록 사이즈 :1024 스테레오

샘플링 : 11kHz 비트 16비트

1024 바이스 음성(블록 1)

블록 사이즈 : 1024 모노

샘플링 : 22kHz 비트 16비트

1024 바이스 음성(블록 1)

위의 그림처럼 데이터 블럭과 데이터 블럭에 대한 정보를 하나의 쌍으로 저장하면 매우 편리합니다. 그럼 bmp 파일도 앞에 bmp 파일이라는 정보를 알려주기 위해 파일 맨 위에 □BM□라고 써 주듯이 본 데이터는 웨이브 파일이라고 헤더를 만들어야겠지요.

"WAVE FILE"	(부모 청크)

전체 크기 10메가

블록 사이즈 :1024 스테레오(자식 청크1)

샘플링 : 11kHz 비트 16비트

1024 바이스 음성(블록 1)(데이터 청크1)

블록 사이즈 :1024 모노 (자식 청크 2)

샘플링 : 22kHz 비트 16비트

1024바이스 음성(블록 1) (데이터 청크2)

위 그림처럼 파일에 저장하면 매우 편할 것입니다. 여기서 박스 하나하나를 모두 청크(Chunk)라고 합니다. 처음에 나오는 박스 □WAVE FILE□이란 헤더가 있는 블럭을 부모 청크라고 하고 그 하단부에 있는 청크를 자식 청크라고 하죠. 그리고 데이터들을 데이터 청크라고 합니다.

위와 같은 구성으로 데이터를 저장하는 방식을 RIFF(Resource Interchange File Format)라고 하고 위와 같은 구성으로 저장된 데이터를 RIFF 파일이라고 합니다. WAVE 파일이나 AVI 파일이 바로 RIFF 파일입니다.

<2>WAVE 파일 구조

웨이브 파일의 구조는 부모 청크 1개와 자식 청크 1개 그리고 데이터 청크 1개로 구성되어 있습니다. 부모 청크에는 □RIFF□-데이터 크기(4바이트)-□WAVE□로 설정되어 있습니다.

여기서 □RIFF□란 웨이브 파일이 RIFF 파일이라는 말이며 현재 4바이트이고 그 다음 파일 끝까지가 몇 바이트라는 것이 데이터 크기에 DWORD형으로 저장되어 있습니다. 그 다음에 WAVE 파일이라는 □WAVE□가 기록되어 있습니다.

다음 자식 청크에는 □fmt □-현재포인터-WAVEFORMAT-채널(스테레오,모노)-샘플링-초당 샘플바이트 -샘플당바이트-  비트(8비트, 16비트 등등)가 기록되어 있습니다. 초당 샘플바이트란   샘플링×채널×(비트/8)입니다. 즉 1초당 필요한 바이트를 말합니다.

만약 8bit스테레오이면 2개의 샘플이 필요하니까 11kHz이면 22Kbyte가 필요하고 16비트이면 한 개의 데이터를 2바이트로 설정하므로 44Kbyte가 필요하게 되는 것입니다.

다음 데이터 청크에는 □data□-데이터 크기-데이터가 기록되어 있습니다.

WAVEFORMAT란 웨이브 형태를 말합니다. PCM_WAVE 형태의 값은 1입니다. 현재 포인터란 현재 위치를 말하는데 청크의 cksize라고도 하au 위의 구조대로 계산한다면 다음과 같습니다.

부모 청크 = 4+4+4 = 12

□현재 포인터□이전까지 자식 청크 = 4

위의 값을 모두 더하면  12／4이므로 16이죠. 그래서 16값을 넣습니다. 사실 wave 파일의 구조에서는 위 값dp 변함이 없습니다. 변함이 없는 값은 WAVEFORMAT와 현재 포인터 두 개입니다. WAVEFORMAT에는 1을, 현재 포인터에는 16을 넣으면 됩니다.

데이터 형	크기	내용	설명
Char	4	"RIFF"	RIFF 파일이라는 지정
DWORD	4	DATASIZE	현재부터 마지막까지의 파일 크기, 즉 파일 크기-4
Char	4	"WAVE"	웨이브 파일
Char	4	"fmt"	자식 청크 시작
DWORD	4	16	웨이브 포맷 사이즈
short	2	1	PCM WAVE 포맷
short	2	1	채널 수
DWORD	4	SIMPLING	샘플링의 수
DWORD	4	AVE SAMPLEING	초당 샘플바이트
short	2	1	샘플당 바이트
short	2	비트	웨이브 비트
Char	4	"data"	데이터 청크 시작
UNIT	4	DATA SIZE	데이터의 크기
		데이터	데이터

(표 1) 웨이브 파일 구조

<3>RIFF 이용 함수

RIFF 파일을 읽기 위해서 사용하는 파일 입출력 함수는 mmio_계열 함수입니다.

함수 리스트는 다음과 같습니다.

□mmioOpen(): RIFF 파일을 연다.

□mmioClose(): RIFF 파일을 닫는다.

□mmioSeek(): RIFF 파일의 특정 위치를 찾는다.

□mmioRead(): RIFF 파일에서 데이터를 읽는다.

□mmioWrite(): RIFF 파일에서 데이터를 쓴다.

□mmioDescent(): 현재 위치에서 하위 청크로 내려간다.

□mmioAscent(): 현재 위치에서 위 청크로 올라간다.

□mmioCreateChunk(): 새로운 청크를 만든다.

□mmioFOURCC(): 청크 이름을 등록한다.

□mmioStringToFOURCC(): 문자열을 청크 이름으로 등록시킨다.

<4>웨이브 파일 읽기

웨이브 파일을 읽고 쓰는 방법은 mmio 방법과 그냥 일반적인 방법 두 가지가 있습니다. 먼저 mmio를 이용해서 파일을 읽고 표준 함수를 이용하여 파일에 저장해 보겠습니다.

우선 파일을 읽어야 하겠죠. 이 때 mmioOpen 함수를 사용합니다.

HMMIO hmmio = mmioOpen((LPSTR)"test.wav",NULL,MMIO_READ);

표준 입출력 파일형은 HFILE이지만 mmio 함수에서 사용하는 파일 핸들러형은 HMMIO입니다. mmioOpen 함수 실행시 test.wav를 여는 데 성공하면 hmmio에 핸들을 넘겨주고 아닐 때는 NULL을 리턴합니다.

NULL은 바로 MMCKINFO형인 청크 정보 구조체를 말합니다. 파일을 열 때는 청크 정보 구조체가 없으므로 NULL을 설정합니다. MMIO_READ란 읽기 위해 파일을 개방한다는 말입니다. 만일 읽고 쓰기 위해서는 MMIO_READWRITE 하면 되겠지요.

파일을 열고 난 후 제일 먼저 부모 청크를 찾습니다. 먼저 MMCKINFO인 청크 구조체로 부모 청크형을 선언해 준 다음

MMCKINFO MMCkInfoParent;

청크형에 □WAVE□를 기록합니다.

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

다음 부모 청크를 찾으라는 이야기입니다.

mmioDescend(hmmio,&MMCkInfoParent,NULL,MMIO_FINDRIFF);

찾으면 본 파일은 WAVE 파일이고 만약 아니면 웨이브 파일이 아니겠죠. mmioDescend는 성공하면 0을, 실패하면 error code를 리턴합니다.

부모 청크를 찾은 다음에는 자식 청크를 찾아야겠죠. 자식 청크의 처음은 □fmt □이니까 이것을 먼저 찾아야 합니다. 이 때는 다음과 같이 합니다.

MMCKINFO MMCkInfoChild

//자식 청크를 찾기 위해서는 fmt를 찾아야 하므로 fmt를 ckid에 저장

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

//자식 청크를 찾아라.

mmioDescend(hmmio,&MMCkInfoChild,&MMCkInfoParent,MMIO_FINDCHUNK);

□fmt□ 다음부터 본 웨이브 파일의 구조가 들어 있습니다. 따라서, 정보를 읽어야겠죠.

PCMWAVEFORMAT WaveRecord;

mmioRead(hmmio,(LPSTR)&WaveRecord,MMCkInfoChild.cksize);

이렇게 되면 WaveRecord 안에 현재 정보가 들어갑니다. 여기서 PCMWAVE- FORMAT 구조체에 대해서 설명하겠습니다. PCMWAVEFORMAT 구조체는 다음과 같이 MSYSTEM.H에 정의되어 있습니다.

typedef struct {

WAVEFORMAT wf;              // 웨이브 레코드 포맷

WORD       wBitsPerSample;  // 샘플당 비트 (8비트, 16비트, 32비트 등등)

} PCMWAVEFORMAT;

여기에서 WAVEFORMAT는 다음과 같습니다.

typedef struct {

WORD  wFormatTag;       // PCMWAVEFORMAT 값=1

WORD  nChannels;        // 채널

DWORD nSamplesPerSec;   // 샘플링값

DWORD nAvgBytesPerSec;  // 초당 샘플링 바이트

WORD  nBlockAlign;      // 1개의 샘플링 바이트

} WAVEFORMAT;

만일 22kHz 샘플링에 8비트 사운드 데이터이고 스테레오일 경우 위의 WaveRecord는 다음과 같이 등록될 것입니다.

WaveRecord.wf.wFormatTag =1;

WaveRecord.wf.nChannels = 2; // 스테레오

WaveRecord.wf.nSamplesPerSec = 22050;//22kHz

WaveRecord.wf.nAvgBytesPerSec = 22050* nChannels * nBlockAlign = 44100

WaveRecord.wf.nBlockAlign =1;

WaveRecord.wBitsPerSample =8;//8비트이다.

그러므로 □fmt□ 청크를 찾아서 웨이브 데이터의 포맷을 알고 난 후에는 다시 위의 부모 청크로 이동합니다.

mmioAscend(hmmio,&MMCkInfoChild,0);

다음 데이터 청크를 찾습니다.

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

mmioDescend(hmmio,&MMCkInfoChild,&MMCkInfoParent,MMIO_FINDCHUNK);

위의 함수를 실행하면 MMCkInfoChild.cksize 안에 데이터의 크기가 설정되어 있습니다. 그러므로 데이터 크기만큼 데이터를 읽으면 됩니다.

DWORD lDatasize;

//데이터 크기를 얻는다.

lDatasize = MMCkInfoChild.cksize;

LPSTR pWave;

LPSTR WaveDataBlock;

//lDatasize만큼 메모리를 설정하고

waveDataBlock = ::GlobalAlloc(GMEM_MOVEABLE,lDatasize);

//메모리를 pWave에 연결시킨다.

pWave = (LPBYTE)::GlobalLock(waveDataBlock);

//데이터를 읽는다.

mmioRead(hmmio,(LPSTR)pWave,lDatasize);

데이터는 pWave에 설정되어 있고 WaveRecord 안에 웨이브 포맷이 적재되었습니다. 이제 출력을 합니다.

<5>웨이브 데이터 출력

웨이브 데이터 출력을 하기 위해서는 먼저 디바이스를 열어야 합니다.

HWAVEIN hWaveInn;//녹음하기 위한 디바이스

waveInOpen((LPHWAVEIN)&hWaveInn,WAVE_MAPPER,(tWAVEFORMATEX*)&WaveRecord,0L,0L,0L);

위에서 WaveRecord에 저장한 포맷 형태로 디바이스를 엽니다. 다음 출력하기 위해 미리 헤더를 보냅니다. 이 때 웨이브 헤더를 미리 준비해야 합니다.

WAVEHDR WaveHeader; //웨이브 데이터 헤더

WaveHeader.lpData =(LPSTR) pWave;  //음성 데이터 설정

WaveHeader.dwBufferLength = lDatasize; //데이터 크기 설정

WaveHeader.dwFlags=0L;  //현재는 데이터의 처음이므로 0

WaveHeader.dwLoops =0L; //루프 플래그

WaveHeader.dwBytesRecorded=lDatasize; //데이터 크기 설정

다음 헤더를 준비하라는 함수를 실행합니다.

waveOutPrepareHeader(hWaveOut,&WaveHeader, sizeof(WaveHeader));

본 헤더에 설정하는 방법에 따라 데이터 포지션이 바뀝니다.

int m_nPassTime;

이렇게 하나의 시간 변수를 만든 다음 현 데이터에서 10초 후의 데이터를 출력시키려면 다음과 같이 하면 됩니다.

m_nPassTime=10; //현재 출력할 위치를 10초 후의 데이터로 선정하고

//포인터를 pWave에서 10초 후의 포인터로 이동

WaveHeader.lpData =(LPSTR) pWave+((m_nPassTime)*WaveRecord.wf.nAvgBytesPerSec);

//데이터 크기 변환

WaveHeader.dwBufferLength = lDatasize-((m_nPassTime)*WaveRecord.wf.nAvgBytesPerSec);

WaveHeader.dwFlags=0L;

WaveHeader.dwLoops =0L;

//데이터 크기 변환

위와 같이 하는 이유는 현재 데이터에서 앞으로 전진이나 뒤로 후진을 할 때 사용하기 위해서입니다. 즉 재생하는 도중에 FOR WIND 버튼을 누르면 10초 앞으로 움직여야 하죠. 이 때

m_nPassTime=m_nPass+10;

위와 설정하고 waveOutPrepareHeader(); 함수를 실행시키고 10초 뒤로 이동하려면

m_nPassTime=m_nPass-10;

위와 같이 설정한 다음 waveOutPrepareHeader(); 함수를 실행시키면 됩니다.

이제 출력을 하면 되는데 출력하는 함수는 waveOutWrite입니다.

waveOutWrite(hWaveOut,&WaveHeader,sizeof(WaveHeader));

위와 같이 하면 출력이 됩니다.

<6>웨이브 데이터 멈춤

출력하던 웨이브 데이터를 멈추는 함수는 다음과 같습니다.

waveOutPause(hWaveOut);

다음 완전히 디바이스를 닫으려면 다음과 같이 합니다.

//웨이브 디바이스를 다시 세팅하고

waveOutReset(hWaveOut);

//헤더를 Unprepare시키고

waveOutUnprepareHeader(hWaveOut,&WaveHeader,sizeof(WaveHeader));

//디바이스를 닫는다.

waveOutClose(hWaveOut);

<7>웨이브 데이터 녹음

웨이브 데이터를 녹음시킬 때는 먼저 어떤 포맷으로 녹음시킬 것인가를 정합니다.

예를 들어, 22kHz에 스테레오에 8비트로 하려면 다음과 같습니다.

WaveRecord.wf.wFormatTag =1;

WaveRecord.wf.nChannels = 2; // 스테레오

WaveRecord.wf.nSamplesPerSec = 22050;//22kHz

WaveRecord.wf.nAvgBytesPerSec = 22050* nChannels * nBlockAlign = 44100

WaveRecord.wf.nBlockAlign =1;

WaveRecord.wBitsPerSample =8;//8비트이므로

레코드하기 위한 디바이스를 엽니다.

waveInOpen((LPHWAVEIN)&hWaveInn,WAVE_MAPPER,(tWAVEFORMATEX*)&WaveRecord,0L,0L,0L);

헤더를 설정합니다.

WAVEHDR WaveHeader; //웨이브 데이터 헤더

WaveHeader.lpData =(LPSTR) pWave;  //음성 데이터 설정

WaveHeader.dwBufferLength = lDatasize; //데이터 크기 설정

WaveHeader.dwFlags=0L;  //현재는 데이터의 처음이므로 0

WaveHeader.dwLoops =0L; //루프 플래그

WaveHeader.dwBytesRecorded=lDatasize; //데이터 크기 설정

waveInPrepareHeader(hWaveInn,&WaveHeader, sizeof(WaveHeader));

녹음할 버퍼를 넣습니다.

waveInAddBuffer(hWaveInn,&WaveHeader,sizeof(WaveHeader));

녹음을 시작합니다.

waveInStart(hWaveInn);

녹음이 되면서 pWave 안에 녹음된 데이터가 기록됩니다. 녹음을 중단하려면 다음과 같이 합니다.

waveInStop(hWaveInn);

<8>웨이브 데이터 저장

저장은 mmio 함수를 사용하지 않고 표준 함수를 사용하겠습니다. 사실 웨이브 파일 정도는 표준 함수로도 읽을 수 있기 때문이죠.

위의 웨이브 데이터 파일 구조를 참조하세요.

HFILE Inn;

DWORD len;

int byte;

Inn=_lcreat(filename,0);//파일을 만들고

//RIFF, 즉 헤더를 넣는다.

_lwrite(Inn,(LPSTR)"RIFF",4);

len=40+lDatasize;

//전체 크기 -4를 넣는다.

_lwrite(Inn,(LPSTR)&len,4);

//WAVE라는 글자를 쓴다.

_lwrite(Inn,(LPSTR)"WAVE",4);

//fmt라고 기록하여 정보 처음을 알린다.

_lwrite(Inn,(LPSTR)"fmt ",4);

//웨이브 포맷 청크를 넣는다. 웨이브 포맷 청크는 16

len=16;

_lwrite(Inn,(LPSTR)&len,4);

short i;

i=1;

//웨이브 포맷이라는 정보를 넣는다. 웨이브 포맷=1

_lwrite(Inn,(LPSTR)&i,2);

//스테레오 모노 정보를 넣는다.

i=(short)WaveRecord.wf.nChannels;

_lwrite(Inn,(LPSTR)&i,2);

//샘플링수를 넣는다.

_lwrite(Inn,(LPSTR)&WaveRecord.wf.nSamplesPerSec,4);

//초당 필요한 바이트수를 넣는다.

_lwrite(Inn,(LPSTR)&WaveRecord.wf.nAvgBytesPerSec,4);

//한 개의 샘플바이트

i=(short)WaveRecord.wf.nBlockAlign;

_lwrite(Inn,(LPSTR)&i,2);

//샘플당 비트수

i=(short)WaveRecord.wBitsPerSample;

_lwrite(Inn,(LPSTR)&i,2);

//데이터 청크라고 설정

_lwrite(Inn,(LPSTR)"data",4);

//데이터 크기를 넣는다.

_lwrite(Inn,(LPSTR)&lDatasize,4);

//데이터를 넣는다.

if(lDatasize!=_lwrite(Inn,(LPSTR)pWave,lDatasize))

{

AfxMessageBox("저장할 용량이 없습니다.");

return FALSE;

}

_lclose(Inn);

<9>웨이브 데이터 분석

위의 pWave 안에 웨이브 데이터가 있는데 과연 어떤 구조인지가 매우 궁금할 것입니다. 8비트에서는 한 개의 바이트가 음성의 한 개의 피치를 나타냅니다. 0∼255값의 피치가 있는데 중간값인 128은 아무 소리도 안 나는 것이고 255값은 매우 카랑카랑한 소리이며 0값은 베이스처럼 묵직한 소리입니다.

즉 음성 파장을 256개로 나누어 값을 매긴 것입니다.

파장을 256개로 나눔(8비트)

파장을 65,535개로 나눔(16비트)

파장을 4,294,967,296개로 나눔(32비트)

8비트 사운드보다 16비트가, 16비트보다 32비트가 음성 파장을 더욱 세분하기 때문에 더욱 섬세합니다. 따라서, 8비트 사운드 카드보다 32비트 사운드 카드가 음질이 더욱 깨끗하게 되는 것이죠. 또한 32비트 사운드 카드는 한 번에 데이터를 4바이트씩 처리하므로 8비트보다 데이터 전송이 4배 빠릅니다.

<10> 로레벨 오디오 서비스를 이용한 클래스 만들기

로레벨 서비스를 이용한 클래스를 CMyWave로 만들어 놓으면 나중에는 CMy- Wave 함수만 이용하면 되므로 편리합니다.

본 클래스의 헤더와 소스입니다. 전에 설명한 내용을 참조하면 이해가 쉬울 것입니다. 녹음과 재생 그리고 화면에 웨이브 파장을 그리는 함수까지 만들었습니다.

(프로그램 소스시작)

/*===============================================================

Low-Level Wave 서비스를 이용한 wave 출력 입력 저장 클래스 헤더

================================================================*/

class CMyWave

{

public:

CMyWave();

~CMyWave();

int erroCode;//에라 코드

/*

wAllocMemory()

내부 함수 데이터 크기만큼 메모리에 alloc시킨다.

*/

int wAllocMemory();

/*

wFreeMemory()

메모리를 해제한다.

*/

void wFreeMemory();

/*

wSetWaveHeader()

웨이브 디바이스에 보낼 헤더를 설정한다.

pWave는 음성 데이터가 적재되어 있는 메모리 포인터

lDatasize 음성 데이터의 크기

*/

void wSetWaveHeader();

/*

LoadWaveFile(LPSTR filename)

파일을 지정하면 지정한 파일에서 웨이브 파일을 읽고

데이터는 pWave에, 데이터 크기는 lDatasize에 저장하고

웨이브 디바이스에 보낼 헤더를 설정한다.

*/

int LoadWaveFile(LPSTR filename);

/*

wOpenOutDevice()

내부 함수 출력 디바이스를 연다.

*/

int wOpenOutDevice();

/*

SetDataSize(int sec);

데이터 크기를 설정한다.

sec는 초단위

본 함수는 레코딩할 때 몇 초로 할 것인지를 나타냄.

*/

void SetDataSize(int sec);

/*

wOpenRecDevice()

녹음을 하기 위한 웨이브 디바이스를 연다.

*/

int wOpenRecDevice();

/*

SetWaveInfo(int MonoStero,int Sampling,int BitSample)

웨이브 파일을 녹음하기 위해서는 일단 정보가 필요하다.

몇 비트로, 몇 kHz로, 모노인지 스테레오인지 등등을 설정해야

한다.

*/

void SetWaveInfo(int MonoStero,int Sampling,int BitSample);

/*

OpenWaveDevice(int mode)

웨이브 디바이스를 연다.

mode가 TRUE이면 출력하기 위해 열고

mode가 FALSE이면 레코딩하기 위해 연다.

*/

int OpenWaveDevice(int mode);

/*

PlayWave()

웨이브를 출력한다.

출력시킬 경우 안에 디바이스를 여는 함수가 내장되어 있으므로

OpenWaveDevice 함수를 쓸 필요없다.

*/

int PlayWave();

/*

Play()

디바이스가 열린 상태에서 플레이

본 함수는 OpenWaveDevice가 미리 설정되어 있어야 한다.

*/

int Play();

/*

PlayPause()

현재 출력을 잠시 중단한다.

*/

int PlayPause();

/*

wCloseOutDevice()

출력 디바이스를 닫는다.

*/

int wCloseOutDevice();

/*

StopRecord();

레코드 하는 것을 멈춘다.

*/

int StopRecord();

/*

wCloseRecDevice()

레코드 디바이스를 닫는다.

*/

int wCloseRecDevice();

/*

CloseWaveDevice(int mode)

웨이브 디바이스를 닫는다.

*/

int CloseWaveDevice(int mode);

/*

RecordWave()

웨이브를 녹음한다.

디바이스를 열 필요없음.

*/

int RecordWave();

/*

Record()

웨이브를 녹음한다.

디바이스 열 필요 있음.

*/

int Record();

/*

DrawWave(CDC *pDC,CRect rect,COLORREF Color,int step,int pos);

rect로 설정한 박스에 천연의 색으로 웨이브 파장을 그려라! pos는 0.5초 단위의 위치, step은 몇 바이트씩 건너뛰면서 그릴 것인지 만약 1이면 1바이트, 1바이트 모두 그리기 때문에 파장은 진하고 그리는 속도는 느리다. */

void DrawWave(CDC *pDC,CRect rect,COLORREF Color,int step,int pos);

/*

Bit8Value(int num)

8비트일 때 현재 바이트값을 정한다.

*/

int Bit8Value(int num);

/*

Bit16Value(int num)

16비트일 때 현재 바이트값을 정한다.

*/

int Bit16Value(int num);

/*

Bit32Value(int num)

32비트일 때 현재 바이트값을 정한다.

*/

int Bit32Value(int num);

/*

SetWavePos(int point)

웨이브의 위치를 변화시킨다.

*/

int SetWavePos(int point);

/*

GetdwFlags()

헤더에 dwFlags를 리턴한다.

*/

int GetdwFlags();

/*

SaveWaveFile(LPSTR filename)

지정된 파일에 데이터를 저장한다.

*/

int SaveWaveFile(LPSTR filename);

public:

PCMWAVEFORMAT WaveRecord;//웨이브 정보

long lDatasize;//웨이브 데이터 크기

int m_nSec; //녹음하려고 하는 데이터의 시간 초단위

protected:

//웨이브 디바이스

HWAVEIN hWaveInn;//녹음하기 위한 디바이스

HWAVEOUT hWaveOut;//출력하기 위한 디바이스

//웨이브 헤더 정보

WAVEHDR WaveHeader; //웨이브 데이터 헤더

//웨이브 데이터

int m_nPassTime;//웨이브 데이터의 처음 포인터

HANDLE waveDataBlock;//웨이브 데이터 블록

LPBYTE pWave;//웨이브 데이터 포인터

// 파일을 로드할 경우 멤버

char m_strFileName[80];//파일을 로드할 경우 사용하는 파일명 데이터

MMCKINFO MMCkInfoParent;//부모 청크 정보 구조체

MMCKINFO MMCkInfoChild; //자직 청크 정보 구조체

};

/*===============================================================

Low-Level Wave 서비스를 이용한 wave 출력 입력 저장 클래스

================================================================*/

#include "stdafx.h"

#include "mywave.h"

#include "mmsystem.h"

//생성자

CMyWave::CMyWave()

{

pWave=NULL;

waveDataBlock=NULL;

m_nPassTime=0;

}

CMyWave::~CMyWave()

{

wFreeMemory();

}

/*

wAllocMemory()

내부 함수 데이터 크기만큼 메모리에 alloc시킨다.

*/

int CMyWave::wAllocMemory()

{

//lDatasize만큼 메모리 설정

waveDataBlock = ::GlobalAlloc(GMEM_MOVEABLE,lDatasize);

if(waveDataBlock == NULL)

return FALSE;

//메모리를 pOutWave에 연결시킨다.

pWave = (LPBYTE)::GlobalLock(waveDataBlock);

return TRUE;

}

/*

wFreeMemory()

메모리를 해제한다.

*/

void CMyWave::wFreeMemory()

{

//현재 데이터 블럭이 설정되어 있으면 해제하라.

if(waveDataBlock)

{

::GlobalUnlock(waveDataBlock);

::GlobalFree(waveDataBlock);

waveDataBlock=NULL;

pWave=NULL;

}

}

/*

wSetWaveHeader()

웨이브 디바이스에 보낼 헤더를 설정한다.

pWave는 음성 데이터가 적재되어 있는 메모리 포인터

lDatasize 음성 데이터의 크기

*/

void CMyWave::wSetWaveHeader()

{

WaveHeader.lpData=(LPSTR) pWave+((m_nPassTime/2)*WaveRecord.wf.nAvgBytesPerSec);

WaveHeader.dwBufferLength = lDatasize-((m_nPassTime/2)*WaveRecord.wf.nAvgBytesPerSec);

WaveHeader.dwFlags=0L;

WaveHeader.dwLoops =0L;

WaveHeader.dwBytesRecorded=lDatasize-((m_nPassTime/2)*WaveRecord.wf.nAvgBytesPerSec);

}

/*

LoadWaveFile(LPSTR filename)

파일을 지정하면 지정한 파일에서 웨이브 파일을 읽고

데이터는 pWave에, 데이터 크기는 lDatasize에 저장하고

웨이브 디바이스에 보낼 헤더를 설정한다.

*/

int CMyWave::LoadWaveFile(LPSTR filename)

{

//파일명을 클래스 맴버에 저장

lstrcpy((LPSTR)m_strFileName,(LPSTR)filename);

//파일을 열고

HMMIO hmmio = mmioOpen((LPSTR)m_strFileName,NULL,MMIO_READ);

//파일이 없으면

if(!hmmio)

{

AfxMessageBox("파일이 없습니다.!");

return FALSE;

}

//부모 청크를 찾기 위해서 키워드는 WAVE이므로 일단 WAVE를

// fccType에 저장한다.

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

//부모 청크를 찾아라.

erroCode = mmioDescend(hmmio,&MMCkInfoParent,NULL,MMIO_FINDRIFF);

//부모 청크를 못 찾으면

if(erroCode)

{

AfxMessageBox("웨이브 파일이 아닙니다.");

mmioClose(hmmio,0);

return FALSE;

}

//자식 청크를 찾기 위해서는 fmt를 찾아야 하므로 fmt

//를 ckid에 저장

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

//자식 청크를 찾아라.

erroCode = mmioDescend(hmmio,&MMCkInfoChild,&MMCkInfoParent,MMIO_FINDCHUNK);

//못 찾으면

if(erroCode)

{

AfxMessageBox("fmt Descending Error");

mmioClose(hmmio,0);

return FALSE;

}

//찾은 다음부터가 웨이브 정보이므로 읽어야 한다.

DWORD bytesRead = mmioRead(hmmio,(LPSTR)&WaveRecord,MMCkInfoChild.cksize);

//읽지 못하면

if(bytesRead<=0)

{

AfxMessageBox("wave format read Error");

mmioClose(hmmio,0);

return FALSE;

}

//이제 다시 올라와서

erroCode = mmioAscend(hmmio,&MMCkInfoChild,0);

//못 올라오면

if(erroCode)

{

AfxMessageBox("Error Ascending in file");

mmioClose(hmmio,0);

return FALSE;

}

//데이터를 찾아야 하므로 ckid에 data 설정

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

//데이터를 찾는다.

erroCode = mmioDescend(hmmio,&MMCkInfoChild,&MMCkInfoParent,MMIO_FINDCHUNK);

//못 찾으면

if(erroCode)

{

AfxMessageBox("data 청크를 찾을 수가 없습니다.");

mmioClose(hmmio,0);

return FALSE;

}

//데이터 크기를 얻는다.

lDatasize = MMCkInfoChild.cksize;

//메모리에 데이터를 넣을 장소 만들기

if(!wAllocMemory())

{

AfxMessageBox("적재할 메모리가 부족합니다");

mmioClose(hmmio,0);

return FALSE;

}

//데이터를 읽는다.

if(mmioRead(hmmio,(LPSTR)pWave,lDatasize) != lDatasize)

{

AfxMessageBox("데이터를 읽을 수가 없습니다.");

mmioClose(hmmio,0);

return FALSE;

}

//웨이브 디바이스에 보낼 헤더를 설정한다.

wSetWaveHeader();

//파일을 찾는다.

mmioClose(hmmio,0);

m_nSec=(int)lDatasize/WaveRecord.wf.nAvgBytesPerSec;

return TRUE;

}

/*

SaveWaveFile(LPSTR filename)

지정된 파일에 데이터를 저장한다.

*/

int CMyWave::SaveWaveFile(LPSTR filename)

{

HFILE Inn;

DWORD len;

int byte;

Inn=_lcreat(filename,0);

if(Inn==NULL)

{

AfxMessageBox("공유원칙을 어겼습니다.");

return FALSE;

}

//RIFF, 즉 헤더를 넣는다.

_lwrite(Inn,(LPSTR)"RIFF",4);

len=40+lDatasize;

//전체 크기 -4를 넣는다.

_lwrite(Inn,(LPSTR)&len,4);

//WAVE라는 글자를 쓴다.

_lwrite(Inn,(LPSTR)"WAVE",4);

//fmt라고 기록하여 정보 처음을 알린다.

_lwrite(Inn,(LPSTR)"fmt ",4);

//웨이브 포맷 청크를 넣는다. 웨이브 포맷 청크는 16

len=16;

_lwrite(Inn,(LPSTR)&len,4);

short i;

i=1;

//웨이브 포맷이라는 정보를 넣는다. 웨이브 포맷=1

_lwrite(Inn,(LPSTR)&i,2);

//스테레오 모노 정보를 넣는다.

i=(short)WaveRecord.wf.nChannels;

_lwrite(Inn,(LPSTR)&i,2);

//샘플링수를 넣는다.

_lwrite(Inn,(LPSTR)&WaveRecord.wf.nSamplesPerSec,4);

//초당 필요한 바이트수를 넣는다.

_lwrite(Inn,(LPSTR)&WaveRecord.wf.nAvgBytesPerSec,4);

//한 개의 샘플바이트

i=(short)WaveRecord.wf.nBlockAlign;

_lwrite(Inn,(LPSTR)&i,2);

//샘플당 비트수

i=(short)WaveRecord.wBitsPerSample;

_lwrite(Inn,(LPSTR)&i,2);

//데이터 청크라고 설정

_lwrite(Inn,(LPSTR)"data",4);

//데이터 크기를 넣는다.

_lwrite(Inn,(LPSTR)&lDatasize,4);

//데이터를 넣는다.

if(lDatasize!=_lwrite(Inn,(LPSTR)pWave,lDatasize))

{

AfxMessageBox("저장할 용량이 없습니다.");

return FALSE;

}

_lclose(Inn);

return TRUE;

}

/*

wOpenOutDevice()

내부 함수 출력 디바이스를 연다.

*/

int CMyWave::wOpenOutDevice()

{

//출력 디바이스를 연다.

erroCode = waveOutOpen((LPHWAVEOUT)&hWaveOut,NULL,(tWAVEFORMATEX*)&WaveRecord,

0L,0L,0L);

if(erroCode)

{

AfxMessageBox("Out 디바이스를 열 수 없습니다.");

return FALSE;

}

erroCode=waveOutPrepareHeader(hWaveOut,&WaveHeader, sizeof(WaveHeader));

if(erroCode)

{

AfxMessageBox("Preapre Header Error");

return FALSE;

}

return TRUE;

}

/*

SetDataSize(int sec);

데이터 크기를 설정한다.

sec는 초단위

본 함수는 레코딩할 때 몇 초로 할 것인지를 나타냄.

*/

void CMyWave::SetDataSize(int sec)

{

m_nSec = sec;

lDatasize=(DWORD)WaveRecord.wf.nAvgBytesPerSec*sec;

}

/*

wOpenRecDevice()

녹음을 하기 위한 웨이브 디바이스를 연다.

*/

int CMyWave::wOpenRecDevice()

{

erroCode=waveInOpen((LPHWAVEIN)&hWaveInn,WAVE_MAPPER,(tWAVEFORMATEX*)&WaveRecord,

0L,0L,0L);

if(erroCode)

{

AfxMessageBox("Record 디바이스를 열 수 없습니다.");

return FALSE;

}

erroCode=waveInPrepareHeader(hWaveInn,&WaveHeader, sizeof(WaveHeader));

if(erroCode)

{

AfxMessageBox("wave in prepare header error");

return FALSE;

}

return TRUE;

}

/*

SetWaveInfo(int MonoStero,int Sampling,int BitSample)

웨이브 파일을 녹음하기 위해서는 일단 정보가 필요하다.

몇 비트로, 몇 kHz로, 모노인지 스테레오인지 등등을 설정해야

한다.

*/

void CMyWave::SetWaveInfo(int MonoStero,int Sampling,int BitSample)

{

int chk,byte;

//비트 샘플을 보고 바이트로 환산한다.

//8비트는 1바이트

if(BitSample==8)

chk=1;

//16비트는 2바이트

else  if(BitSample==16)

chk=2;

//32비트는 4바이트

else

chk=4;

byte = chk*MonoStero;

WaveRecord.wf.wFormatTag=1;//웨이브 파일 포맷을 읽을 것

WaveRecord.wf.nChannels=MonoStero;//모노냐 스테레오냐

WaveRecord.wf.nSamplesPerSec=Sampling;//샘플링 비율

// 11kHz=11025 22kHz=22050 44kHz=44100 8kHz = 8000

//초당 필요한 바이트수

// 샘플링×모노 스테레오×chk

WaveRecord.wf.nAvgBytesPerSec=Sampling*MonoStero*chk;

// 한 개의 정보 블럭

WaveRecord.wf.nBlockAlign=byte;

// 비트 샘플 8비트 16비트 32비트

WaveRecord.wBitsPerSample=BitSample;

}

/*

OpenWaveDevice(int mode)

웨이브 디바이스를 연다.

모드가 TRUE면 출력하기 위해 열고

모드가 FALSE면 레코딩하기 위해 연다.

*/

int CMyWave::OpenWaveDevice(int mode)

{

if(mode)

erroCode=wOpenOutDevice();

else

erroCode=wOpenRecDevice();

return erroCode;

}

/*

PlayWave()

웨이브를 출력한다.

출력시킬 경우 안에 디바이스를 여는 함수가 내장되어 있으므로

OpenWaveDevice 함수를 쓸 필요없다.

*/

int CMyWave::PlayWave()

{

//헤더를 설정하고

wSetWaveHeader();

//디바이스를 연 다음

if(!wOpenOutDevice())

return FALSE;

//출력한다.

if(!Play())

return FALSE;

return TRUE;

}

/*

Play()

디바이스가 열린 상태에서 플레이

본 함수는 OpenWaveDevice가 미리 설정되어 있어야 한다.

*/

int CMyWave::Play()

{

//출력한다.

erroCode = waveOutWrite(hWaveOut,&WaveHeader,sizeof(WaveHeader));

if(erroCode)

{

AfxMessageBox("음성 파일 출력 실패");

return FALSE;

}

return TRUE;

}

/*

PlayPause()

현재 출력을 잠시 중단한다.

*/

int CMyWave::PlayPause()

{

erroCode=waveOutPause(hWaveOut);

if(erroCode)

{

AfxMessageBox("임시 멈춤 실패");

return FALSE;

}

wCloseOutDevice();

return TRUE;

}

/*

wCloseOutDevice()

출력 디바이스를 닫는다.

*/

int CMyWave::wCloseOutDevice()

{

erroCode=waveOutReset(hWaveOut);

if(erroCode)

{

AfxMessageBox("재설정 실패");

return FALSE;

}

erroCode=waveOutUnprepareHeader(hWaveOut,&WaveHeader,sizeof(WaveHeader));

if(erroCode)

{

AfxMessageBox("헤더 해제 실패");

return FALSE;

}

erroCode=waveOutClose(hWaveOut);

if(erroCode)

{

AfxMessageBox("웨이브 디바이스 닫기 실패");

return FALSE;

}

return TRUE;

}

/*

StopRecord();

레코드 하는 것을 멈춘다.

*/

int CMyWave::StopRecord()

{

erroCode=waveInStop(hWaveInn);

if(erroCode)

{

AfxMessageBox("wave stop error");

return FALSE;

}

erroCode=waveInReset(hWaveInn);

if(erroCode)

{

AfxMessageBox("wave Reset error");

return FALSE;

}

wCloseRecDevice();

return TRUE;

}

/*

wCloseRecDevice()

레코드 디바이스를 닫는다.

*/

int CMyWave::wCloseRecDevice()

{

erroCode=waveInUnprepareHeader(hWaveInn,&WaveHeader,sizeof(WaveHeader));

if(erroCode)

{

AfxMessageBox("wave Unprepare error");

return FALSE;

}

erroCode=waveInClose(hWaveInn);

if(erroCode)

{

AfxMessageBox("wave close error");

return FALSE;

}

return TRUE;

}

/*

CloseWaveDevice(int mode)

웨이브 디바이스를 닫는다.

*/

int CMyWave::CloseWaveDevice(int mode)

{

if(mode)

erroCode=wCloseOutDevice();

else

erroCode=wCloseRecDevice();

return erroCode;

}

/*

GetdwFlags()

헤더에 dwFlags를 리턴한다.

*/

int CMyWave::GetdwFlags()

{

return WaveHeader.dwFlags;

}

/*

SetWavePos(int point)

웨이브의 위치를 변화시킨다.

*/

int CMyWave::SetWavePos(int point)

{

m_nPassTime=point;

return TRUE;

}

/*

RecordWave()

웨이브를 녹음한다.

디바이스를 열 필요없음.

*/

int CMyWave::RecordWave()

{

//헤더를 설정하고

wSetWaveHeader();

//디바이스를 연 다음

if(!wOpenRecDevice())

return FALSE;

if(!Record())

return FALSE;

return TRUE;

}

/*

Record()

웨이브를 녹음한다.

디바이스 열 필요 있음.

*/

int CMyWave::Record()

{

erroCode=waveInAddBuffer(hWaveInn,&WaveHeader,sizeof(WaveHeader));

if(erroCode)

{

AfxMessageBox("waveInAddBuffer Error");

return FALSE;

}

erroCode=waveInStart(hWaveInn);

if(erroCode)

{

AfxMessageBox("wave record error");

return FALSE;

}

return TRUE;

}

/*

DrawWave(CDC *pDC,CRect rect,COLORREF Color,int step,int pos);

rect로 설정한 박스에 천연의 색으로 웨이브 파장을 그려라!

pos는 0.5초 단위의 위치, step는 몇 바이트씩 건너뛰면서

그릴 것인지 만약 1이면 1바이트 1바이트 모두 그리기 때문에

파장은 진하고 그리는 속도는 느리다. */

void CMyWave::DrawWave(CDC *pDC,CRect rect,COLORREF Color,int step,int pos)

{

int i,xpos;

int gab;

float j;

float yper,averloop;

CPen Pen;

CPen *pOldPen;

Pen.CreatePen(PS_SOLID,0,RGB(0,255,0));

pOldPen=(CPen *)pDC->SelectObject(&Pen);

yper=(float)(rect.bottom-rect.top)/256;

averloop= (float)lDatasize/(rect.right-rect.left);

i=(pos-1)*(WaveRecord.wf.nAvgBytesPerSec/2);

xpos=i/averloop;

int k=0;

int middle;

for(;i<(int)(pos)*((int)WaveRecord.wf.nAvgBytesPerSec/2);)

{

for(j=0;j<averloop+0.5;j=j+step)

{

if(WaveRecord.wBitsPerSample==8)

gab=Bit8Value(i);

else if(WaveRecord.wBitsPerSample==16)

gab=Bit16Value(i);

else

gab=Bit32Value(i);

i=i+step;

middle=rect.bottom-((rect.bottom-rect.top)/2);

if(k==0)

pDC->MoveTo(xpos+rect.left,(int)middle+(gab*yper));

else

pDC->LineTo(xpos+rect.left,(int)middle+(gab*yper));

k++;

}

xpos++;

}

pDC->SelectObject(pOldPen);

Pen.DeleteObject();

}

/*

Bit8Value(int num)

8비트일 때 현재 바이트값을 정한다.

*/

int CMyWave::Bit8Value(int num)

{

int gab;

if(WaveHeader.lpData[num]&0x80)

gab=-(128+WaveHeader.lpData[num]);

else

gab=128-WaveHeader.lpData[num];

return gab;

}

/*

Bit16Value(int num)

16비트일 때 현재 바이트값을 정한다.

*/

int CMyWave::Bit16Value(int num)

{

union ci{

char c[2];

short gab;

}charint;

int gab;

charint.c[0]=WaveHeader.lpData[num];

charint.c[1]=WaveHeader.lpData[num+1];

gab=65535-charint.gab;

//이 부분 256은 위의 DrawWave에서 256이 있기 때문

gab=gab/256;

gab=gab-256;

return gab;

}

/*

Bit32Value(int num)

32비트일 때 현재 바이트값을 정한다.

*/

int CMyWave::Bit32Value(int num)

{

union ci{

char c[4];

DWORD gab;

}charint;

int gab;

charint.c[0]=WaveHeader.lpData[num];

charint.c[1]=WaveHeader.lpData[num+1];

charint.c[2]=WaveHeader.lpData[num+2];

charint.c[3]=WaveHeader.lpData[num+3];

gab=0xFFFF-charint.gab;

//이 부분 65535은 위의 DrawWave에서 256이 있기 때문

gab=gab/65535;

gab=gab-65535;

return gab;

}

(프로그램 소스끝)

4.로레벨 오디오 서비스를 이용한 프로그램 WAVEMIX.EXE

로레벨 오디오 서비스를 이용한 프로그램을 제작해 보겠습니다. 녹음을 하면 녹음이 되는 순간 현재의 음성 파장이 화면에 도시되고 파일을 선택하면 파일의 음성 파장이 파일에 출력되면서 전후로 커서를 이동할 수 있으며 또한 사운드 볼륨을 조절할 수 있게 하는 프로그램을 제작해 보겠습니다. 이 제작하는 프로그램은 WAVEMIX입니다.

그림 7-1-1은 이 프로그램을 실행 모습입니다.

출력에서 마이크를 선택하고 재생 버튼을 누르면 현재 마이크로 녹음한 데이터가 재생되며 파일을 선택하고 재생 버튼을 누르면 현재 선택된 파일이 재생됩니다. 파일 저장을 누르면 현재 선택된 데이터를 디스크에 저장합니다. 재생, 일시 정지, 멈춤, 전으로,

후로, 녹음 버튼의 기능을 가지고 있으며 현재 선택되어 있는 데이터의 정보를 샘플링 비율, 녹음 입력, 샘플 크기 등으로 설정해 주었으며 볼륨바가 있어서 음성의 크기 조절을 할 수 있도록 되어 있습니다. 에코 기능과 전체 음성

기능은 화면에 도시될 뿐 아무 기능도 하지 않으나 현재 프로그램만 이해한다면 충분히 만들 수 있을 것이라 봅니다.

본 프로그램은 앞에서 작성한 CMyWave 클래스를 이용하여 작성했습니다. 애플리케이션 위자드로 프로젝트를 제작할 때는 Application style에서 Dialog based를 선택해야 합니다.

<1>볼륨 조절하기

시스템에서 볼륨의 크기를 받고 싶을 때는 다음과 같이 합니다.

DWORD m_nVolume;

waveOutGetVolume(NULL,&m_nVolume);

m_nVolume 안에 상위 바이트 16에는 스테레오에서 우측 볼륨, 하위 바이트 16에는 좌측 볼륨이 설정되어 있습니다. 만일 모노 사운드 카드를 가지고 있을 때는 우측 볼륨이 실질적인 볼륨이 됩니다.

볼륨의 크기는 0∼0xfff값입니다. 제일 큰 소리의 값은 0×fff, 제일 작은 소리는 0이 됩니다.

m_nVolume에서 우측 볼륨값과 좌측 볼륨값을 받기 위해서는 LOWORD, HIWORD 함수를 사용합니다.

erroCode=waveOutGetVolume(NULL,&m_nVolume);//현재 볼륨을 받고

m_nleftVolume=LOWORD(m_nVolume);//좌측 볼륨값을 저장하고

m_nrightVolume=HIWORD(m_nVolume);//우측 볼륨값을 저장

시스템에 볼륨을 설정하는 방법은 다음과 같습니다.

waveOutSetVolume(NULL,m_nVolume);

여기에서 우측 볼륨과 좌측 볼륨을 설정하고 다시 그것을 m_nVolume로 바꾸는 방법은 다음과 같습니다.

m_nVolume=m_nrightVolume; //먼저 우측 볼륨값을 넣고

m_nVolume=m_nVolume << 16;//좌측으로 16바이트 시프트하고

m_nVolume=m_nVolume+m_nleftVolume;//현재값에 좌측 볼륨값을 더한다.

waveOutSetVolume(NULL,m_nVolume);//현재 볼륨값을 정한다.

<2>프로그램 소스

나머지 부분은 앞에서 모두 설명한 내용입니다. 소스를 확인해 보면 이해가 될 것입니다. 본 소스는 WAVEMIX의 대화상자 부분만입니다. 나머지 소스는 CD를 확인하세요.

(프로그램 소스)

// wavemixDlg.h : header file

//

///////////////////////////////////////////////////////////////////////////

// CWavemixDlg dialog

class CWavemixDlg : public CDialog

{

// Construction

public:

CWavemixDlg(CWnd* pParent = NULL); // standard constructor

//리소스 자원과 연결되는 요소

CBitmapButton m_bitButtonSetting;

CBitmapButton m_bitButtonOpen;

CBitmapButton m_bitButtonSave;

CBitmapButton m_bitButtonOk;

CBitmapButton m_bitButtonPlay;

CBitmapButton m_bitButtonRwind;

CBitmapButton m_bitButtonFwind;

CBitmapButton m_bitButtonStop;

CBitmapButton m_bitButtonRecord;

CBitmapButton m_bitButtonEcho;

CBitmapButton m_bitButtonPause;

CSliderCtrl   m_sliderRecord;

CSliderCtrl   m_sliderVolume;

CSliderCtrl   m_sliderFile;

int m_nStero;

int m_nMono;

int m_nSample8;

int m_nSample11;

int m_nSample22;

int m_nSample44;

int m_nBit8;

int m_nBit16;

int m_nMike;

int m_nFile;

public:

// 웨이브 구조체

CMyWave *m_waveInn;

CMyWave *m_waveOut;

//레코드 정보

int m_nRecordTime;//녹음 시간

int m_nMonoStero;//모노 스테레오값

int m_nSampling;//샘플링의 값

int m_nBitSample;//비트값

int m_nRecordPos;//레코드 위치

int m_nStatus;//현재 가동 상태

public:

void ChkVolume();//볼륨을 체크한다.

void DrawRecordBox();//레코드 박스를 그린다.

void DrawOutBox();//파일에서 출력하는 웨이브 파장 박스를 그린다.

void RecordCount();//녹음을 할 때 슬라이더 위치와 파장을 그린다.

void RecordPlay();//레코드한 것을 재생한다.

void RecPlayCount();//재생시 슬라이더 위치를 그린다.

void FilePlay();//파일을 재생한다.

void FilePlayCount();//재생시 슬라이더 위치를 그린다.

void ChkRecSlider();//레코드 슬라이더를 체크한다.

void ChangeInfoOut();//파일의 정보 형태로 전환한다.

void ChangeInfoRec();//레코드 정보 형태로 전환한다.

public:

int erroCode;

public:

char m_strFileName[80];

int m_nFilePos;//파일 출력 장치의 위치

int m_nFileTime;//파일 출력 시간

public:

DWORD m_nVolume;

int m_nleftVolume;

int m_nrightVolume;

int m_nVolumePos;

// Dialog Data

//{{AFX_DATA(CWavemixDlg)

enum { IDD = IDD_WAVEMIX_DIALOG };

// NOTE: the ClassWizard will add data members here

//}}AFX_DATA

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CWavemixDlg)

protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

//}}AFX_VIRTUAL

// Implementation

protected:

HICON m_hIcon;

// Generated message map functions

//{{AFX_MSG(CWavemixDlg)

virtual BOOL OnInitDialog();

afx_msg void OnPaint();

afx_msg HCURSOR OnQueryDragIcon();

afx_msg void OnClose();

afx_msg void OnTimer(UINT nIDEvent);

afx_msg void OnRecord();

afx_msg void OnPlay();

afx_msg void OnPause();

afx_msg void OnStop();

afx_msg void OnFwind();

afx_msg void OnRwind();

afx_msg void OnFileopen();

afx_msg void OnMike();

afx_msg void OnFile();

afx_msg void OnFilesave();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

// wavemixDlg.cpp : implementation file

//

#include "stdafx.h"

#include "wavemix.h"

#include "mmsystem.h"

#include "mywave.h"

#include "wavemixDlg.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

#define PR_NONE 0

#define PR_RECORD 1

#define PR_PLAY 2

#define PR_RECPLAY 3

///////////////////////////////////////////////////////////////////////////

// CWavemixDlg dialog

CWavemixDlg::CWavemixDlg(CWnd* pParent /*=NULL*/)

: CDialog(CWavemixDlg::IDD, pParent)

{

//{{AFX_DATA_INIT(CWavemixDlg)

//마이크와 파일을 위한 CMyWave 구조체 설정

m_waveOut=new CMyWave;

m_waveInn=new CMyWave;

//모노 스테레오 버튼 모노로 설정

m_nStero=0;

m_nMono=1;

//샘플링 버튼은 22kHz

m_nSample8=0;

m_nSample11=0;

m_nSample22=1;

m_nSample44=0;

//샘플 바이트 버튼은 8

m_nBit8=1;

m_nBit16=0;

//출력을 마이크로 설정

m_nMike=1;

m_nFile=0;

//녹음 시간을 10초, 1단위를 0.5로 설정

m_nRecordTime=20;

//모노인지 스테레오인지 변수에 모노 설정

m_nMonoStero=1;

//샘플링값 22050 설정

m_nSampling=22050;

m_nBitSample=8;

//녹음 데이터 위치는 0

m_nRecordPos=0;

//현재 상태는 아무것도 안 함.

m_nStatus=PR_NONE;

//}}AFX_DATA_INIT

m_nFileTime=0;

}

void CWavemixDlg::DoDataExchange(CDataExchange* pDX)

{

CDialog::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CWavemixDlg)

DDX_Control(pDX,IDC_FILEOPEN,m_bitButtonOpen);

DDX_Control(pDX,IDC_FILESAVE,m_bitButtonSave);

DDX_Control(pDX,IDC_SETTING,m_bitButtonSetting);

DDX_Control(pDX,IDC_PLAY,m_bitButtonPlay);

DDX_Control(pDX,IDC_PAUSE,m_bitButtonPause);

DDX_Control(pDX,IDC_RWIND,m_bitButtonRwind);

DDX_Control(pDX,IDC_FWIND,m_bitButtonFwind);

DDX_Control(pDX,IDC_STOP,m_bitButtonStop);

DDX_Control(pDX,IDC_RECORD,m_bitButtonRecord);

DDX_Control(pDX,IDC_ECHO,m_bitButtonEcho);

DDX_Control(pDX,IDOK,m_bitButtonOk);

DDX_Check(pDX,IDC_STERO,m_nStero);

DDX_Check(pDX,IDC_MONO,m_nMono);

DDX_Check(pDX,IDC_SAMPLE8,m_nSample8);

DDX_Check(pDX,IDC_SAMPLE11,m_nSample11);

DDX_Check(pDX,IDC_SAMPLE22,m_nSample22);

DDX_Check(pDX,IDC_SAMPLE44,m_nSample44);

DDX_Check(pDX,IDC_BIT8,m_nBit8);

DDX_Check(pDX,IDC_BIT16,m_nBit16);

DDX_Check(pDX,IDC_MIKE,m_nMike);

DDX_Check(pDX,IDC_FILE,m_nFile);

DDX_Control(pDX,IDC_SLIDERRECORD,m_sliderRecord);

DDX_Control(pDX,IDC_SLIDERVOLUME,m_sliderVolume);

DDX_Control(pDX,IDC_SLIDERFILE,m_sliderFile);

//}}AFX_DATA_MAP

}

BEGIN_MESSAGE_MAP(CWavemixDlg, CDialog)

//{{AFX_MSG_MAP(CWavemixDlg)

ON_WM_PAINT()

ON_WM_QUERYDRAGICON()

ON_WM_CLOSE()

ON_WM_TIMER()

ON_BN_CLICKED(IDC_RECORD, OnRecord)

ON_BN_CLICKED(IDC_PLAY, OnPlay)

ON_BN_CLICKED(IDC_PAUSE, OnPause)

ON_BN_CLICKED(IDC_STOP, OnStop)

ON_BN_CLICKED(IDC_FWIND, OnFwind)

ON_BN_CLICKED(IDC_RWIND, OnRwind)

ON_BN_CLICKED(IDC_FILEOPEN, OnFileopen)

ON_BN_CLICKED(IDC_MIKE, OnMike)

ON_BN_CLICKED(IDC_FILE, OnFile)

ON_BN_CLICKED(IDC_FILESAVE, OnFilesave)

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////

// CWavemixDlg message handlers

BOOL CWavemixDlg::OnInitDialog()

{

CDialog::OnInitDialog();

SetIcon(m_hIcon, TRUE);   // Set big icon

SetIcon(m_hIcon, FALSE);  // Set small icon

//비트맵 버튼을 설정하는 곳

VERIFY(m_bitButtonOpen.LoadBitmaps("FILEOPENU","FILEOPEND","FILEOPENU","FILEOPENE"));

VERIFY(m_bitButtonSave.LoadBitmaps("FILESAVEU","FILESAVED","FILESAVEU","FILESAVEE"));

VERIFY(m_bitButtonSetting.LoadBitmaps("SETTINGU","SETTINGD","SETTINGU","SETTINGE"));

VERIFY(m_bitButtonOk.LoadBitmaps("EXITU","EXITD","EXITU","EXITD"));

VERIFY(m_bitButtonPlay.LoadBitmaps("PLAYU","PLAYD","PLAYU","PLAYE"));

VERIFY(m_bitButtonPause.LoadBitmaps("PAUSEU","PAUSED","PAUSEU","PAUSEE"));

VERIFY(m_bitButtonRwind.LoadBitmaps("RWINDU","RWINDD","RWINDU","RWINDE"));

VERIFY(m_bitButtonFwind.LoadBitmaps("FWINDU","FWINDD","FWINDU","FWINDE"));

VERIFY(m_bitButtonStop.LoadBitmaps("STOPU","STOPD","STOPU","STOPE"));

VERIFY(m_bitButtonRecord.LoadBitmaps("RECORDU","RECORDD","RECORDU","RECORDE"));

VERIFY(m_bitButtonEcho.LoadBitmaps("ECHOU","ECHOD","ECHOU","ECHOE"));

//레코드 슬라이더의 범위를 정하고

m_sliderRecord.SetRange(0,m_nRecordTime);

//현재 위치를 0으로 한 다음

m_sliderRecord.SetPos(0);

//볼륨의 슬라이더의 범위를 정하고

m_sliderVolume.SetRange(0,16);

//볼륨의 페이지 크기를 2로 한다.

m_sliderVolume.SetPageSize(2);

//현재 볼륨을 받고

erroCode=waveOutGetVolume(NULL,&m_nVolume);

//좌측 볼륨값 저장

m_nleftVolume=LOWORD(m_nVolume);

//우측 볼륨값 저장

m_nrightVolume=HIWORD(m_nVolume);

//볼륨 크기를 슬라이더에 위치하려면 한쪽 볼륨값을 가지고

//0xfff로 나눈다. 왜냐하면 0~0xFFF 값이 볼륨 레인지이므로

m_nVolumePos=m_nleftVolume/0xFFF;

//상단이 0이고 하단이 16이므로 바꾸어서 위치한다.

m_nVolumePos=16-m_nVolumePos;

m_sliderVolume.SetPos(m_nVolumePos);

//타이머를 설정하고 매 시간마다 볼륨 위치를 확인한다.

//그리고 레코드바 위치를 확인한다.

SetTimer(4,100,NULL);

int i;

//레코드 슬라이더 눈금 매기기

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

m_sliderRecord.SetTic(i);

//볼륨 슬라이더 눈금 매기기

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

m_sliderVolume.SetTic(i);

UpdateData(FALSE);

return TRUE;

}

void CWavemixDlg::ChkVolume()

{

int num;

//볼륨 슬라이더 위치를 확인해서

num=m_sliderVolume.GetPos();

//볼륨 슬라이더를 움직였다면

if(num==m_nVolumePos)

return;

//볼륨을 새로 설정한다.

m_nVolumePos=num;

num=16-num;

num=num*0xFFF;

m_nleftVolume=num;

m_nrightVolume=num;

//먼저 우측 볼륨을 넣고

m_nVolume=m_nrightVolume;

//좌측으로 16바이트 시프트하고

m_nVolume=m_nVolume << 16;

//현재값에 좌측 볼륨값을 넣는다.

m_nVolume=m_nVolume+m_nleftVolume;

//다음 현재 볼륨을 설정한다.

waveOutSetVolume(NULL,m_nVolume);

}

void CWavemixDlg::ChkRecSlider()

{

int num;

//레코드 슬라이더바를 움직이면

//새로운 레코드 포인터로 위치한다.

num=m_sliderRecord.GetPos();

if(num==m_nRecordPos)

return;

switch(m_nStatus)

{

//현재 레코드한 것을 재생하는 부분이면

case PR_RECPLAY:

if(m_nRecordPos ==num+1)

break;

m_nRecordPos=num;

//레코드 포지션을 새로 정하고

m_sliderRecord.SetPos(m_nRecordPos);

//출력을 멈추고

m_waveInn->PlayPause();

KillTimer(2);

//새로 설정한 곳부터 다시 출력하라.

RecordPlay();

break;

}

}

// If you add a minimize button to your dialog, you will need the code below

//  to draw the icon.  For MFC applications using the document/view model,

//  this is automatically done for you by the framework.

void CWavemixDlg::OnPaint()

{

if (IsIconic())

{

CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle

int cxIcon = GetSystemMetrics(SM_CXICON);

int cyIcon = GetSystemMetrics(SM_CYICON);

CRect rect;

GetClientRect(&rect);

int x = (rect.Width() - cxIcon + 1) / 2;

int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon

dc.DrawIcon(x, y, m_hIcon);

}

else

{

CDialog::OnPaint();

DrawRecordBox();

DrawOutBox();

}

}

// The system calls this to obtain the cursor to display while the user drags

//  the minimized window.

HCURSOR CWavemixDlg::OnQueryDragIcon()

{

return (HCURSOR) m_hIcon;

}

//현재 녹음되는 파장을 그린다.

void CWavemixDlg::DrawRecordBox()

{

CPen Pen;

CPen *pOldPen;

CBrush hBursh,*pOldBursh;

CRect rect = CRect(40,84,339,126);

CDC *pDC=GetDC();

hBursh.CreateSolidBrush(RGB(0,0,0));

pOldBursh=(CBrush *)pDC->SelectObject(&hBursh);

pDC->Rectangle(rect);

pDC->SelectObject(pOldBursh);

hBursh.DeleteObject();

Pen.CreatePen(PS_SOLID,0,RGB(0,255,0));

pOldPen=(CPen *)pDC->SelectObject(&Pen);

pDC->MoveTo(rect.left,rect.top+(rect.bottom-rect.top)/2);

pDC->LineTo(rect.right,rect.top+(rect.bottom-rect.top)/2);

pDC->SelectObject(pOldPen);

Pen.DeleteObject();

ReleaseDC(pDC);

}

//파일에서 출력하는 박스를 그린다.

void CWavemixDlg::DrawOutBox()

{

CWnd* pWnd =GetDlgItem( IDC_OUTPUTBOX );

CPen Pen;

CPen *pOldPen;

CBrush hBursh,*pOldBursh;

CRect rect;

pWnd->GetWindowRect( &rect );

ScreenToClient( &rect );

CDC *pDC=GetDC();

hBursh.CreateSolidBrush(RGB(0,0,0));

pOldBursh=(CBrush *)pDC->SelectObject(&hBursh);

pDC->Rectangle(rect);

pDC->SelectObject(pOldBursh);

hBursh.DeleteObject();

Pen.CreatePen(PS_SOLID,0,RGB(0,255,0));

pOldPen=(CPen *)pDC->SelectObject(&Pen);

pDC->MoveTo(rect.left,rect.top+(rect.bottom-rect.top)/2);

pDC->LineTo(rect.right,rect.top+(rect.bottom-rect.top)/2);

pDC->SelectObject(pOldPen);

Pen.DeleteObject();

ReleaseDC(pDC);

}

//프로그램이 끝날 때 웨이브 구조체를 지운다.

void CWavemixDlg::OnClose()

{

KillTimer(4);

if(m_waveInn)

delete m_waveInn;

if(m_waveOut)

delete m_waveOut;

CDialog::OnClose();

}

void CWavemixDlg::OnTimer(UINT nIDEvent)

{

switch(nIDEvent)

{

case 6://파일 웨이브파 다시 그리기

{

CRect rect = CRect(40,84,339,126);

KillTimer(6);

CDC *pDC=GetDC();

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

m_waveInn->DrawWave(pDC,rect,RGB(0,255,0),10,i+1);

ReleaseDC(pDC);

}

break;

case 5://녹음 웨이브파 다시 그리기

{

CWnd* pWnd =GetDlgItem( IDC_OUTPUTBOX );

CRect rect;

pWnd->GetWindowRect( &rect );

ScreenToClient( &rect );

KillTimer(5);

CDC *pDC=GetDC();

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

m_waveOut->DrawWave(pDC,rect,RGB(0,255,0),10,i+1);

ReleaseDC(pDC);

}

break;

case 4://볼륨과 녹음 슬라이더바 체크

ChkVolume();

ChkRecSlider();

break;

//녹음중일 때

case 1:

RecordCount();

break;

//녹음한 것을 재생할 때

case 2:

RecPlayCount();

break;

case 3://파일을 재생할 때

FilePlayCount();

break;

}

CDialog::OnTimer(nIDEvent);

}

//0.5초당 레코드될 때마다 웨이브를 그려 준다.

void CWavemixDlg::RecordCount()

{

m_nRecordPos++;

CDC *pDC = GetDC();

m_waveInn->DrawWave(pDC,CRect(40,84,339,126),RGB(0,255,0),10,m_nRecordPos);

ReleaseDC(pDC);

m_sliderRecord.SetPos(m_nRecordPos);

//만약 현재가 마지막이면

if(m_nRecordPos==m_nRecordTime)

{

KillTimer(1);

m_nRecordPos=0;

m_sliderRecord.SetPos(m_nRecordPos);

m_waveInn->StopRecord();

//정지된 기능을 풀어 준다.

GetDlgItem(IDC_PLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_RWIND)->EnableWindow(TRUE);

GetDlgItem(IDC_FWIND)->EnableWindow(TRUE);

GetDlgItem(IDC_PAUSE)->EnableWindow(TRUE);

GetDlgItem(IDC_ECHO)->EnableWindow(TRUE);

GetDlgItem(IDC_FILEOPEN)->EnableWindow(TRUE);

GetDlgItem(IDC_FILESAVE)->EnableWindow(TRUE);

GetDlgItem(IDC_SETTING)->EnableWindow(TRUE);

GetDlgItem(IDOK)->EnableWindow(TRUE);

//현재 상태는 아무것도 아님.

m_nStatus=PR_NONE;

RecordPlay();

}

}

//녹음을 재생할 때 0.5초마다 포지션을 이동한다.

void CWavemixDlg::RecPlayCount()

{

m_nRecordPos++;

m_sliderRecord.SetPos(m_nRecordPos);

//만약 녹음 출력이 끝나면

if((m_waveInn->GetdwFlags() & WHDR_DONE))

{

m_waveInn->PlayPause();

KillTimer(2);

m_nRecordPos=0;

m_sliderRecord.SetPos(m_nRecordPos);

m_nStatus=PR_NONE;

//다른 버튼 enable

GetDlgItem(IDC_PLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_RECORD)->EnableWindow(TRUE);

GetDlgItem(IDC_FILEOPEN)->EnableWindow(TRUE);

GetDlgItem(IDC_FILESAVE)->EnableWindow(TRUE);

GetDlgItem(IDC_SETTING)->EnableWindow(TRUE);

GetDlgItem(IDOK)->EnableWindow(TRUE);

}

}

//파일을 재생할 때마다 슬라이더를 다시 그린다.

void CWavemixDlg::FilePlayCount()

{

m_nFilePos++;

m_sliderFile.SetPos(m_nFilePos);

//파일 출력이 끝났으면

if((m_waveOut->GetdwFlags() & WHDR_DONE))

{

m_waveOut->PlayPause();

KillTimer(3);

m_nFilePos=0;

m_sliderFile.SetPos(m_nFilePos);

m_nStatus=PR_NONE;

//다른 버튼 enable

GetDlgItem(IDC_PLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_RECORD)->EnableWindow(TRUE);

GetDlgItem(IDC_FILEOPEN)->EnableWindow(TRUE);

GetDlgItem(IDC_FILESAVE)->EnableWindow(TRUE);

GetDlgItem(IDC_SETTING)->EnableWindow(TRUE);

GetDlgItem(IDOK)->EnableWindow(TRUE);

}

}

void CWavemixDlg::RecordPlay()

{

//레코드 위치를 맞춘다.

m_waveInn->SetWavePos(m_nRecordPos);

//레코드를 플레이시킨다.

m_waveInn->PlayWave();

//타이머를 설정한다.

m_nRecordPos++;

SetTimer(2,500,NULL);

m_nStatus=PR_RECPLAY;

//플레이 도중에 실행되지 않아야 할 것

GetDlgItem(IDC_PLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_RECORD)->EnableWindow(FALSE);

GetDlgItem(IDC_FILEOPEN)->EnableWindow(FALSE);

GetDlgItem(IDC_FILESAVE)->EnableWindow(FALSE);

GetDlgItem(IDC_SETTING)->EnableWindow(FALSE);

GetDlgItem(IDOK)->EnableWindow(FALSE);

GetDlgItem(IDC_PAUSE)->EnableWindow(TRUE);

}

void CWavemixDlg::FilePlay()

{

//레코드 위치를 맞춘다.

m_waveOut->SetWavePos(m_nFilePos);

//레코드를 플레이시킨다.

m_waveOut->PlayWave();

//타이머를 설정한다.

m_nFilePos++;

SetTimer(3,500,NULL);

m_nStatus=PR_PLAY;

//플레이 도중에 실행되지 않아야 할 것

GetDlgItem(IDC_PLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_RECORD)->EnableWindow(FALSE);

GetDlgItem(IDC_FILEOPEN)->EnableWindow(FALSE);

GetDlgItem(IDC_FILESAVE)->EnableWindow(FALSE);

GetDlgItem(IDC_SETTING)->EnableWindow(FALSE);

GetDlgItem(IDOK)->EnableWindow(FALSE);

GetDlgItem(IDC_PAUSE)->EnableWindow(TRUE);

}

void CWavemixDlg::OnRecord()

{

//먼저 레코드 포맷을 설정하고

m_waveInn->SetWaveInfo(m_nMonoStero,m_nSampling,m_nBitSample);

//데이터 크기를 설정한 다음

m_waveInn->SetDataSize(m_nRecordTime/2);

//데이터를 메모리에 설정하고

m_waveInn->wAllocMemory();

//웨이브 헤더를 설정한다.

m_waveInn->wSetWaveHeader();

//레코드 포스를 초기화하고

m_nRecordPos=0;

//웨이브 파형을 그릴 박스를 깨끗하게 하고

DrawRecordBox();

//현재 상태는 레코드 상태

m_nStatus=PR_RECORD;

//녹음을 한다.

m_waveInn->RecordWave();

//타이머를 설정한다.

SetTimer(1,500,NULL);

//녹음 도중에는 재생도 안 되고 몇 가지 기능이 정지되어야 한다.

GetDlgItem(IDC_PLAY)->EnableWindow(FALSE);

GetDlgItem(IDC_RWIND)->EnableWindow(FALSE);

GetDlgItem(IDC_FWIND)->EnableWindow(FALSE);

GetDlgItem(IDC_PAUSE)->EnableWindow(FALSE);

GetDlgItem(IDC_ECHO)->EnableWindow(FALSE);

GetDlgItem(IDC_FILEOPEN)->EnableWindow(FALSE);

GetDlgItem(IDC_FILESAVE)->EnableWindow(FALSE);

GetDlgItem(IDC_SETTING)->EnableWindow(FALSE);

GetDlgItem(IDOK)->EnableWindow(FALSE);

}

//재생 버튼을 눌렀을 때

void CWavemixDlg::OnPlay()

{

if(m_nMike)//마이크로 설정되어 있으면 녹음 데이터 재생

RecordPlay();

else//파일로 되어 있으면 파일 데이터 재생

FilePlay();

}

//일시 멈춤

void CWavemixDlg::OnPause()

{

switch(m_nStatus)

{

case PR_RECPLAY:

//PLAY는 가능하게 PAUSE는 쓸 수 없게

GetDlgItem(IDC_PLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_PAUSE)->EnableWindow(FALSE);

m_waveInn->PlayPause();

KillTimer(2);

break;

case PR_PLAY:

GetDlgItem(IDC_PLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_PAUSE)->EnableWindow(FALSE);

m_waveOut->PlayPause();

KillTimer(3);

break;

}

}

//멈춤 버튼을 누르면

void CWavemixDlg::OnStop()

{

//정지된 기능을 풀어 준다.

GetDlgItem(IDC_PLAY)->EnableWindow(TRUE);

GetDlgItem(IDC_RWIND)->EnableWindow(TRUE);

GetDlgItem(IDC_FWIND)->EnableWindow(TRUE);

GetDlgItem(IDC_PAUSE)->EnableWindow(TRUE);

GetDlgItem(IDC_ECHO)->EnableWindow(TRUE);

GetDlgItem(IDC_FILEOPEN)->EnableWindow(TRUE);

GetDlgItem(IDC_FILESAVE)->EnableWindow(TRUE);

GetDlgItem(IDC_SETTING)->EnableWindow(TRUE);

GetDlgItem(IDOK)->EnableWindow(TRUE);

switch(m_nStatus)

{

case PR_RECPLAY:

m_waveInn->PlayPause();

KillTimer(2);

m_nRecordPos=0;

m_sliderRecord.SetPos(m_nRecordPos);

break;

case PR_RECORD:

KillTimer(1);

m_nRecordPos=0;

m_sliderRecord.SetPos(m_nRecordPos);

m_waveInn->StopRecord();

break;

case PR_PLAY:

m_waveOut->PlayPause();

KillTimer(3);

m_nFilePos=0;

m_sliderFile.SetPos(m_nFilePos);

break;

}

//현재 상태는 아무것도 아님

m_nStatus=PR_NONE;

}

//□한칸 앞으로□를 누르면

void CWavemixDlg::OnFwind()

{

switch(m_nStatus)

{

case PR_RECPLAY:

m_waveInn->PlayPause();

KillTimer(2);

if(m_nRecordPos<m_nRecordTime)

{

m_nRecordPos+=2;

m_sliderRecord.SetPos(m_nRecordPos);

RecordPlay();

}

break;

case PR_NONE:

if(m_nMike)

if(m_nRecordPos<m_nRecordTime)

{

m_nRecordPos++;

m_sliderRecord.SetPos(m_nRecordPos);

break;

}

break;

}

}

//□한칸 뒤로□를 누르면

void CWavemixDlg::OnRwind()

{

switch(m_nStatus)

{

case PR_RECPLAY:

m_waveInn->PlayPause();

KillTimer(2);

if(m_nRecordPos>0)

m_nRecordPos-=2;

m_sliderRecord.SetPos(m_nRecordPos);

RecordPlay();

break;

case PR_NONE:

if(m_nMike)

if(m_nRecordPos<m_nRecordTime)

{

m_nRecordPos--;

m_sliderRecord.SetPos(m_nRecordPos);

break;

}

break;

}

}

//□파일 오픈□을 누르면

void CWavemixDlg::OnFileopen()

{

CString szFileName;

CFileDialog dlg(TRUE,_T("WAV"),_T("*.WAV"),OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

_T("WAVE FILE (*.WAV)|*.WAV|ALL FILE (*.*)|*.*|"));

if(dlg.DoModal()==IDOK)

{

szFileName=dlg.GetPathName();

lstrcpy((LPSTR)m_strFileName,(LPSTR)szFileName.operator const char*());

m_waveOut->LoadWaveFile(m_strFileName);

m_nFilePos=0;

ChangeInfoOut();

DrawOutBox();

}

//파일 다이얼로그에 의해서 지워졌던 파장을 다시 그린다.

SetTimer(5,500,NULL);

SetTimer(6,500,NULL);

}

//마이크에서 -> 파일로 바뀌면 모든 정보를 바꾼다.

void CWavemixDlg::ChangeInfoOut()

{

int i;

m_nStero=0;

m_nMono=0;

if(m_waveOut->WaveRecord.wf.nChannels==1)

m_nMono=1;

else

m_nStero=1;

m_nSample8=0;

m_nSample11=0;

m_nSample22=0;

m_nSample44=0;

if(m_waveOut->WaveRecord.wf.nSamplesPerSec==8000)

m_nSample8=1;

else if(m_waveOut->WaveRecord.wf.nSamplesPerSec==11025)

m_nSample11=1;

else if(m_waveOut->WaveRecord.wf.nSamplesPerSec==22050)

m_nSample22=1;

else

m_nSample44=1;

m_nBit8=0;

m_nBit16=0;

if(m_waveOut->WaveRecord.wBitsPerSample==8)

m_nBit8=1;

if(m_waveOut->WaveRecord.wBitsPerSample==16)

m_nBit16=1;

m_nFileTime=m_waveOut->m_nSec*2;

m_nMike=0;

m_nFile=1;

//레코드 슬라이더의 범위를 정하고

m_sliderFile.SetRange(0,m_nFileTime);

//현재 위치를 0으로 한 다음

m_sliderFile.SetPos(m_nFilePos);

//레코드 슬라이더 눈금 매기기

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

m_sliderFile.SetTic(i);

UpdateData(FALSE);

}

//파일에서 마이크로 옮기면 모든 데이터를 바꾼다.

void CWavemixDlg::ChangeInfoRec()

{

m_nStero=0;

m_nMono=0;

if(m_waveInn->WaveRecord.wf.nChannels==1)

m_nMono=1;

else

m_nStero=1;

m_nSample8=0;

m_nSample11=0;

m_nSample22=0;

m_nSample44=0;

if(m_waveInn->WaveRecord.wf.nSamplesPerSec==8000)

m_nSample8=1;

else if(m_waveInn->WaveRecord.wf.nSamplesPerSec==11025)

m_nSample11=1;

else if(m_waveInn->WaveRecord.wf.nSamplesPerSec==22050)

m_nSample22=1;

else

m_nSample44=1;

m_nBit8=0;

m_nBit16=0;

if(m_waveInn->WaveRecord.wBitsPerSample==8)

m_nBit8=1;

if(m_waveInn->WaveRecord.wBitsPerSample==16)

m_nBit16=0;

m_nMike=1;

m_nFile=0;

UpdateData(FALSE);

}

//마이크 버튼을 눌렀을 때

void CWavemixDlg::OnMike()

{

m_nMike=1-m_nMike;

m_nFile=1-m_nFile;

UpdateData(FALSE);

if(m_nMike)

ChangeInfoRec();

else

ChangeInfoOut();

}

//파일 버튼을 눌렀을 때

void CWavemixDlg::OnFile()

{

m_nMike=1-m_nMike;

m_nFile=1-m_nFile;

UpdateData(FALSE);

if(m_nMike)

ChangeInfoRec();

else

ChangeInfoOut();

}

//파일 저장을 눌렀을 때

void CWavemixDlg::OnFilesave()

{

CString szFileName;

CFileDialog dlg(FALSE,_T("WAV"),_T("*.WAV"),OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,

_T("WAVE FILE (*.WAV)|*.WAV|ALL FILE (|*.*|"));

if(dlg.DoModal()==IDOK)

{

szFileName=dlg.GetPathName();

if(m_nMike)

m_waveInn->SaveWaveFile((LPSTR)szFileName.operator const char*());

else

m_waveOut->SaveWaveFile((LPSTR)szFileName.operator const char*());

}

SetTimer(5,500,NULL);

SetTimer(6,500,NULL);

}