본문 바로가기
프로그래밍/PSP

[펌]PSP 프로그래밍 107 - Audio 1

by 베리베리 2008. 7. 28.
이제 PSP에서 소리를 내는 것에 대해 알아보기로 한다. PSPSDK의 다음 위치에 오디오 관련 샘플 중 하나가 있다.

C:\cygwin\usr\local\pspdev\psp\sdk\samples\audio\wavegen

이 샘플은 음파의 주파수와 파형을 변화시켜서 소리를 나게 하는 예제이다. 먼저 Makefile을 살펴보면 지금까지의 샘플과는 달리 추가된 것이 있다. 아래에 빨간색으로 표시된 부분으로 오디오 관련 라이브러리를 같이 링크하도록 추가되어 있다. 나중에 그래픽을 사용할 때라던가 USB를 사용할 때라던가 하면 각각 적절한 라이브러리를 같이 링크해 주어야 한다.

TARGET = alsample
OBJS = main.o

INCDIR =
CFLAGS = -O2 -G0 -Wall
CXXFLAGS = $(CFLAGS) -fno-exceptions -fno-rtti
ASFLAGS = $(CFLAGS)

LIBDIR =
LDFLAGS =
LIBS = -lpspaudiolib -lpspaudio -lm

EXTRA_TARGETS = EBOOT.PBP
PSP_EBOOT_TITLE = Audiolib Wave Generator

PSPSDK=$(shell psp-config --pspsdk-path)
include $(PSPSDK)/lib/build.mak

main.c 파일에서 지금까지의 샘플들과 다른 부분을 살펴보면 전역 변수 몇 개와 오디오와 관련있을 법하게 보이는 함수 3개가 추가되어 있다.

먼저 전역 변수를 보면 상수 파이를 정의하고 샘플링 레이트로 44.1kHz를 지정하고 있다. 아마 CD에 저장된 음악들의 샘플링 레이트가 44.1kHz 정도 였던가? 그리고, 주파수와 시간을 각각 float 형으로 준비해 놓고 있다. 이 예제에서는 아날로그 스틱으로 주파수를 변화시키고 그에 따라 소리가 변화하게 되고, 소리를 내기 위해 오디오 콜백 함수 뿐 아니라 다른 함수에서도 모두 주파수 값을 사용하므로 주파수를 전역 변수로 잡아 놓았다. function 변수는 현재 선택된 파형을 저장하기 위해 준비되어 있다.

const float PI = 3.1415926535897932f;
const int sampleRate = 44100;
float frequency = 440.0f;
float time = 0;
int function = 0;

currentFunction이라는 함수는 현재 선택되어 있는 파형 (사인파, 사각파, 삼각파) 에 따라 현재 시간에 어떤 소리를 내야하는지를 결정하는 함수이다. switch 문으로 function 변수의 값에 따라 적당한 방법으로 값을 구해서 반환하고 있다.

float currentFunction(const float time) {
   double x;
   float t = modf(time / (2 * PI), &x);

   switch(function) {
       case 0: // SINE
           return sinf(time);
       case 1: // SQUARE
           if (t < 0.5f) {
               return -0.2f;
           } else {
               return 0.2f;
           }
       case 2: // TRIANGLE
           if (t < 0.5f) {
               return t * 2.0f - 0.5f;
           } else {
               return 0.5f - (t - 0.5f) * 2.0f;
           }
       default:
           return 0.0f;
   }
}

audioCallback 함수는 main 함수에서 오디오 초기화 후 콜백으로 등록시켜줄 함수인데, PSP의 오디오 버퍼에 값이 채워져야 할 때 불리게 된다. 샘플링 포맷이 16비트 스테레오이므로 short 형 변수 l (left), r (right) 에 currentFunction에서 반환된 값을 이용해 채워넣고 있다.

void audioCallback(void* buf, unsigned int length, void *userdata) {
   const float sampleLength = 1.0f / sampleRate;
   const float scaleFactor = SHRT_MAX - 1.0f;
   static float freq0 = 440.0f;
   sample_t* ubuf = (sample_t*) buf;
   int i;

   if (frequency != freq0) {
       time *= (freq0 / frequency);
   }
   for (i = 0; i < length; i++) {
       short s = (short) (scaleFactor * currentFunction(2.0f * PI * frequency * time));
       ubuf[i].l = s;
       ubuf[i].r = s;
       time += sampleLength;
   }
   if (time * frequency > 1.0f) {
       double d;
       time = modf(time * frequency, &d) / frequency;
   }
   freq0 = frequency;
}

controlFrequency 함수는 사용자 입력을 처리하는 함수이다. 지금까지의 예제에서는 main 함수 내에서 처리하였지만 이 예제에서는 별도의 함수를 만들어서 처리하고 있다. 아날로그 스틱의 값을 읽은 후 그 값에 따라 주파수 값을 많이 변화시키거나 적게 변화시키거나 아예 변화시키지 않거나 하는 것을 볼 수 있다. 이를 위해 zones라는 배열과 response라는 배열을 잡아놓고 있는데, 중앙 위치에서 상대적으로 30 정도의 아날로그 스틱 값으로는 response가 0이기 때문에 변화가 없고 값이 늘어날 수록 response도 늘어나게 되어서 아날로그 스틱을 많이 밀었을 때 주파수가 팍팍 변하는 것을 구현하고 있다. 그리고 엑스 버튼을 눌렀을 때 파형을 변경하도록 하고 있는데, 지금까지의 버튼 입력 방법과는 좀 차이가 있는 것이 changedButtons와 oldButtons 변수의 사용일 것이다. PSP의 버튼이 눌렸을 때 sceCtrlReadBufferPositive 함수의 호출이 단 1번만 이루어질 것이라는 가정이 없기 때문에 (실상은 매우 빠르게 실행되므로 지금까지와 같이 루프를 돌며 버튼값을 가져오게 한다면 일상적으로 버튼을 한번 눌렀다고 해도 몇번씩 실행될 수 있다. 즉 누르고 있는 중에는 파형이 계속 변경될 것이고, 짧게 눌렀다고 해도 파형이 몇번씩 휙휙 바뀌게 된다. 이를 방지하기 위해서 이전에 가져온 버튼 값을 저장해 놓은 다음 이전 버튼 값과 동일하다면 누르고 있는 중이므로 무시하게 하는 것이다.

void controlFrequency() {
   static int oldButtons = 0;
   const int zones[6] = {30, 70, 100, 112, 125, 130};
   const float response[6] = {0.0f, 0.1f, 0.5f, 1.0f, 4.0f, 8.0f};
   const float minFreq = 32.0f;
   const float maxFreq = 7040.0f;
   SceCtrlData pad;
   float direction;
   int changedButtons;
   int i, v;

   sceCtrlReadBufferPositive(&pad, 1);

   v = pad.Ly - 128;
   if (v < 0) {
       direction = 1.0f;
       v = -v;
   } else {
       direction = -1.0f;
   }

   for (i = 0; i < 6; i++) {
       if (v < zones[i]) {
           frequency += response[i] * direction;
           break;
       }
   }

   if (frequency < minFreq) {
       frequency = minFreq;
   } else if (frequency > maxFreq) {
       frequency = maxFreq;
   }

   changedButtons = pad.Buttons & (~oldButtons);
   if (changedButtons & PSP_CTRL_CROSS) {
       function++;
       if (function > 2) {
           function = 0;
       }
   }
   oldButtons = pad.Buttons;
}

그리고 main 함수에서는 아래와 같은 코드로 오디오를 초기화하고 오디오 콜백 함수를 등록한다. 0번째 채널에 audioCallback 함수를 콜백으로 지정하는 것이다. 채널은 총 4개까지 사용이 가능하다.

   pspAudioInit();
   pspAudioSetChannelCallback(0, audioCallback, NULL);

이 샘플을 빌드하고 PSP에서 동작시켜 보면 아날로그 스틱과 엑스 버튼에 따라 기괴한 소리가 날 것이다. 그리고 개중에는 어디선가 많이 듣던 소리도 나게 할 수 있을 것이고.

이제 소리까지 낼 수 있게 되었다, 축!

출처 : http://www.onlinegamer.co.kr/ 지금은 사라져서;; 공개해두 괜찮을지;;;

댓글