Strict Standards: Non-static method Soojung::addReferer() should not be called statically in /home/lifthrasiir/sites/sapzil.info/soojung/settings.php on line 79

Warning: Cannot modify header information - headers already sent by (output started at /home/lifthrasiir/sites/sapzil.info/soojung/settings.php:79) in /home/lifthrasiir/sites/sapzil.info/soojung/classes/Counter.class.php on line 63

Strict Standards: Non-static method Entry::getEntry() should not be called statically in /home/lifthrasiir/sites/sapzil.info/soojung/entry.php on line 51

Strict Standards: Non-static method Soojung::entryIdToFilename() should not be called statically in /home/lifthrasiir/sites/sapzil.info/soojung/classes/Entry.class.php on line 182

Strict Standards: Non-static method Soojung::queryFilenameMatch() should not be called statically in /home/lifthrasiir/sites/sapzil.info/soojung/classes/Soojung.class.php on line 55
TokigunStudio3 | 블로그: SDL 삽질

내용으로 바로 넘어 가기


TokigunStudio3

1 / 3283   


더 이상 이 블로그는 운영되지 않습니다. 새 블로그로 가 주세요.

SDL 삽질

2005/05/14 PM 11:38 | 개발 | 8 comments | 0 trackbacks | AllBlog: vote, to pocket

Angolmois에 지금까지 해결되고 있지 않았던 몇 가지 문제를 고치느라 삽질 좀 했다. (이제 롱노트와 판정만 남았다!) 삽질을 마친 후에 IRC에서 열심히 노닥거리다가, 심심해져서 Angolmois로 Always (요구르팅 OST) bms를 돌려 보았는데... 아니나 다를까 처음 키음이 완전히 깨져버리는 것이 아닌가. orz

이 현상을 좀 더 자세하게 설명하자면, 음악을 한 2배속 정도로 빠르게 돌리면 소리(정확히는 tone)가 상당히 높아지는 걸 알 수 있다. 이는 소리 자체의 성질 때문에 일어 나는데, 일반적으로 별도의 처리를 하지 않고 음악을 빠르게 돌려 버리는 것을 pitch를 높인다고 하고, 여기에 별도의 처리를 하여 소리의 높이는 그대로 유지하는 것을 stretch한다고 한다. 문제는 Angolmois로 bms들을 돌려 보면 허구한 날에 pitch가 2배 정도로 높아지는 경우가 있다는 것이었다. (물론 그러는 것만 그렇고 안 그러는 건 안 그렇다.)

사실 대충 원인은 짐작하고 있었다. 아마 음악 파일의 Frequency가 제대로 맞지 않아서 그런 게 아닌가 하고 있었지만, 사실 그때만 해도 11025Hz 파일이 깨지는 게 아닌가 하고 추측만 하고 있었을 뿐이다. 그래서 이 참에 직접 고쳐 보자! 라는 생각을 하고 삽질을 시작했다. (결국 대형 삽질의 전주곡이 되어 버리고 말았지만)

phase 1

확실히 맛이 가는 몇몇 bms들을 잡고 확인 작업을 시작했다. Always의 경우 맨 처음의 키음들의 pitch가 깨지는 현상이 생겼는데, 아니나 다를까 그 깨지는 음들이 모조리 32000Hz였던 것이다. -_-; (앞에서 말했던 추측은 깨졌다. 11025Hz나 22050Hz로 된 것들은 정상적으로 잘 나왔다. 참고로 Angolmois 내부 Frequency는 44100Hz이다.) 이 쯤에서 SDL_mixer의 버그라고 단정짓고, SDL_mixer 1.2.6 코드를 받아서 보기 시작했다.

SDL_mixer에서 WAV(RIFF)/AIFF 파일을 담당하는 파일은 wavestream.c였다. (이름만 보면 딱 알기 좋게 되어 있음) 그래서 이 곳을 열심히 뒤져 봤으나 Frequency를 자동으로 고쳐 주는 부분은 따로 없고 대신 다음과 같은 코드가 들어 있었다. (Line 194~196)
music->cvt.len = original_len;
SDL_ConvertAudio(&music->cvt);
SDL_MixAudio(stream, music->cvt.buf, music->cvt.len_cvt, wavestream_volume);
이 쯤에서 SDL_mixer가 아니라 SDL이 범인이라는 것을 알게 되었다. -_-

phase 2

SDL_ConvertAudio를 찾기 위해 SDL 1.2.8 소스를 CVS에서 받아서 확인해 보았다. 혹시 내가 하는 말들을 직접 확인해 보고 싶으신 분들을 위해서, 이 파일은 audio/SDL_audiocvt.c에 들어 있다. 그러나 SDL_ConvertAudio를 보니까 SDL_AudioCVT 구조체를 받아서 그냥 거기에 들어 있는 filter 함수들 포인터를 실행시키는 것에 불과했다. -_- 그래서 이 구조체를 만들어 주는 SDL_BuildAudioCVT 함수를 찾다가, 다음과 같은 아주 환장하는 문구를 보게 되었다. (Line 1526~1547)
/* We may need a slow conversion here to finish up */
if ( (lo_rate/100) != (hi_rate/100) ) {
#if 1
    /* The problem with this is that if the input buffer is
       say 1K, and the conversion rate is say 1.1, then the
       output buffer is 1.1K, which may not be an acceptable
       buffer size for the audio driver (not a power of 2)
    */
    /* For now, punt and hope the rate distortion isn't great.
    */
#else
    if ( src_rate < dst_rate ) {
        cvt->rate_incr = (double)lo_rate/hi_rate;
        cvt->len_mult *= 2;
        cvt->len_ratio /= cvt->rate_incr;
    } else {
        cvt->rate_incr = (double)hi_rate/lo_rate;
        cvt->len_ratio *= cvt->rate_incr;
    }
    cvt->filters[cvt->filter_index++] = SDL_RateSLOW;
#endif
}
우아아아아아아앍

OTL


고로 답은 명백해졌다. 변환하려는 Frequency가 2의 거듭제곱만큼 차이나지 않으면 (즉 hi_rate = lo_rate × 2n인 경우) 위의 코드가 실행되어야... 했는데, SDL 개발자들이 buffer size 넘어 간다고 저 코드를 주석처리해 놓은 것이다. -_-;;;; 나는 이 경고를 그냥 때려 넘겨 버리고 주석 해제해서 새로 컴파일하기로 했다.

phase 3

그냥 컴파일을 시도해 보았다. 뭔가 잘 되는가 싶더니, video/windx5/SDL_dx5events.c에서 Error가 난다. -_- (파일 이름이 dx5인 이유는 SDL이 처음 만들어질 때 DirectX 버전이 5.0이라서 그런다.) 내가 뭔가 빼 먹은 게 있나 해서 Building SDL 문서를 뒤지니까 DirectX 5.0 SDK 이상이 있어야 한다길래 MSDN에서 뒤져서 9.0 SDK를 받는데... 응? 뭐 이리 느리냐? 하고 총 파일 크기를 보고 아주 뒤집어 지는 줄 알았다. 200MB가 넘는다. orz

"The latest version of DirectX can be downloaded or purchased on a cheap CD (my recommendation) from Microsoft."라고 Building SDL 문서에 친절하게 쓰여 있는 걸 무시하면 안 되는 것이었다. -_- 하지만 CD를 살 여유도 없고 시간도 없는 관계로 그냥 받기로 했다. 가뜩이나 좁은 노트북 하드디스크를 비집어서 어딘가에 잘 받아 놓고 파일을 실행하니까 WinZip self-extracting archive다. (하긴 한국 MSN Messenger 홈페이지에 올라 간 스크린샷에 빵집도 있더만...) 이래 저래 해서 풀고 msi 파일을 실행해서 설치하는데... 얼라리요?

설치에 실패했댑니다.

...10초도 안 되어서 갑자기 Rolling Back 되더니 설치에 실패했댄다. OTL 그 외에 다른 메시지는 없었다. -_- 머릿속에서 n부 합창으로 마이크로소프트 이 ㅅㅂㄹㅁ를 외치고 있었다. 그러다가 문득 풀린 파일들을 보니까 Include, Lib 디렉토리가 보인다. 안에는 물론 헤더 파일과 라이브러리 파일이 잔뜩 들어 있었다. 어차피 필요한 것들은 이것 뿐이니 그냥 이거 가져 와서 컴파일하자는 생각을 하고 Visual C++ 디렉토리에 덮어 씌웠으나... 별 효과는 없었다. -_-;

phase 4

몇 번 더 DX9 SDK 설치를 시도했으나 번번이 Rolling Back 되어서 아주 환장하기 일보 직전이던 상황에서, 잠시 에러 메시지를 들여 보았다. ...음?
sdl12\src\video\windx5\sdl_dx5events.c(172) : warning C4013: 'GetAncestor' undefined; assuming extern returning int
sdl12\src\video\windx5\sdl_dx5events.c(172) : error C2065: 'GA_ROOT' : undeclared identifier
sdl12\src\video\windx5\sdl_dx5events.c(172) : warning C4047: '=' : 'struct HWND__ *' differs in levels of indirection from 'int '
sdl12\src\video\windx5\sdl_dx5events.c(656) : warning C4047: '=' : 'struct HWND__ *' differs in levels of indirection from 'int '
....

DirectX 문제가 아니잖아 저거

GetAncestor는 Win32 API 함수다. -_- 갑자기 정신이 들어서 헤더를 확인해 보니까 #include "directx.h"만 달랑 있다. 혹시나 싶어서 #include <windows.h>도 함께 넣어 봤는데 별로 변한 건 없다. -_-;;;; 설마 해서 GetAncestor의 prototype이 들어 있는 winuser.h를 넣어 봤더니 windows.h에서 뭔가 전처리를 마구 하는 모양인지 별의별 에러가 다 튀어 나온다.

...그래서 다음과 같은 꼼수를 쓰기로 했다.
#include <windows.h>
#define     GA_MIC          1
#define     GA_PARENT       1
#define     GA_ROOT         2
#define     GA_ROOTOWNER    3
#define     GA_MAC          4

HWND
WINAPI
GetAncestor(
    HWND hwnd,
    UINT gaFlags
);

#include "directx.h"
그리고 컴파일해 보니까 아무 말 없이 컴파일이 깔끔하게 된다! 떨리는 가슴을 진정시키고 dll 파일을 복사해서 Angolmois를 돌려 보니... 소리가 아주 아무 일 없었다는 듯이 깔끔하게 잘 나온다. 삽질 하면서 이렇게 감동적인 순간은 처음이다. OTL

Epilogue


이번 삽질을 통해 얻어 낸 SDL precompiled binary는 여기에서 받을 수 있다. (슬쩍 리소스 파일 고쳐서 내 이름 집어 넣었다. -_-) win32 말고 다른 운영체제라면 DirectX 같은 초난감한-_- dependency는 없을 것 같으니까 그냥 audio/SDL_audiocvt.c에서 해당 부분의 주석을 지워 주고 컴파일하면 제대로 될 것이다.

그리고 하나 더 깨달은 것. 하드디스크 정리 제발 좀 빨리 하자. orz

TrackBack URL: http://sapzil.info/soojung/trackback.php?blogid=602

Comment: mithrandir (2005/05/15 AM 02:03)

결론은 미완성된 코드가 그럭저럭 잘 돌아간다는 건가요?

Comment: 토끼군 (2005/05/15 AM 02:24)

mithrandir: 그런 셈이지요. -.- 대충 문제 있던 것들 다 돌려 보니까 일단 큰 문제는 없는 것 같습니다. (실제로 저 주석 빼 놓은 채로 컴파일된 경우도 있더군요. 그런 경우에는 저런 삽질 없이 잘 돌아 가고요.) 아무튼... orz

Comment: 정태영 (2005/05/15 PM 05:42)

음악을 한 2배속 정도로 빠르게 돌리면 소리(정확히는 tone)가 상당히 높아지는 걸 알 수 있다.

위 부분과 관련된 이론적 부분은... Fourier Transform 의 성질에 대해서 찾아보시면 설명이 가능할 듯 하군요 ;)

time-domain 을 frequency-domain 으로 투영시키기 위한 방법으로 사용되는게 fourier-transform 이고 time-scaling property 를 보면 :)

x(at) -F.T- (1/|a|)X(f/a) 가 되는 것을 확인할 수 있거든요 ;)

Comment: 토끼군 (2005/05/15 PM 06:07)

정태영: 대충 알고 있었는데 대신 설명해 주셔서 감사.... =3=3=3=3=333 (저는 fourier-transform에 대해서 개념만 대충 압니다.)

Comment: daybreaker (2005/05/15 PM 11:06)

삽질 축하. =3=33

Comment: 토끼군 (2005/05/16 AM 02:20)

daybreaker: 뷁-ㅆ-

Comment: 신현석 (2005/05/16 AM 10:19)

삽질 결과가 소리로 나와서 감동이 더했을 듯ㅎㅎ 축하

Comment: 토끼군 (2005/05/16 AM 11:02)

신현석: OTL 그나저나 SDL 2.0이 cvs에 있던데 여기서는 처리 했을 지 궁금하군요. -.-

Copyright (c) 1995-2005, Kang Seonghoon (Tokigun).