본문으로 건너뛰기

닌텐도 DS모르는 용어는 용어집에서

18장 소리 ③ 닌텐도 DS 사운드 구조

DS의 소리는 ARM7이 채널 16개로 울립니다. 음악 파일 SDAT의 악보(SSEQ)·악기 표·파형을 따라가고, 악보의 명령 바이트를 하나씩 읽어 틱이 시간이 되는 과정을 봅니다.

소리는 ARM7이 맡는다 쉽게

슈퍼 패미콤은 소리 전용 CPU(SPC700)가 따로 있고, 본체 CPU와는 포트 네 개로만 주고받았습니다(13장). DS는 두 CPU 가운데 ARM7이 소리를 맡습니다. 소리 칩의 레지스터는 ARM7에서만 보이고, ARM7에서 도는 사운드 드라이버가 악보를 읽어 채널마다 「이 주소의 표본을 이 높이와 음량으로 울려라」를 적어 넣습니다.

DS의 소리 채널은 16개입니다. 채널마다 메모리의 표본을 8비트·16비트 PCM이나 IMA-ADPCM(4비트 압축)으로 읽어 울리고, 8–13번은 사각파, 14–15번은 잡음도 낼 수 있습니다. 게임 음악의 음 하나는 그때 비어 있는 채널 하나에 걸립니다.

음악 파일 SDAT 쉽게

닌텐도 SDK로 만든 게임은 소리를 대개 .sdat 파일 하나에 모아 둡니다. 고스트 트릭은 sound_data.sdat(15,257,056바이트)입니다. 파일 안에도 15장의 롬처럼 이름 표(SYMB), 정보 표(INFO), 위치 표(FAT), 내용(FILE)이 있어서, 곡 이름으로 정보를 찾고 정보 표의 파일 번호로 내용을 찾습니다.

종류고스트 트릭하는 일
SSEQ51개시퀀스(악보). 곡 하나에 하나
SSAR2개시퀀스 묶음. 효과음처럼 짧은 시퀀스 여럿
SBNK537개악기 표(뱅크). 악기 번호마다 어느 파형을 어떤 음 높이·엔벨로프로 울릴지
SWAR2개파형 묶음. 실제 소리 표본

한글패치 페이지의 배경 음악은 37번 시퀀스 SEQ_GHOST_BGM054_B입니다. 정보 표의 이 곡 칸은 「파일 37번, 악기 표 534번, 음량 127」을 가리킵니다. 곡을 울리려면 악보(SSEQ), 악기 표(SBNK), 그 악기 표가 가리키는 파형 묶음(SWAR) 셋이 모두 필요합니다.

악보, 악기 표, 파형SSEQ 악보의 음표가 악기 번호와 음 높이를 정하고, SBNK 악기 표가 그 악기의 파형과 엔벨로프를 정하고, SWAR 파형 묶음의 표본이 소리 채널로 간다.SSEQ악보: 언제 어느 음SBNK악기: 파형·엔벨로프SWAR파형: 소리 표본→ 빈 소리 채널에 걸어 울림
곡 하나를 울리려면 SDAT 안의 세 가지를 차례로 따라갑니다.

악보 SSEQ: 명령 바이트의 줄 쉽게

SSEQ는 MIDI를 바꿔 만든 악보입니다. 트랙마다 명령 바이트가 줄지어 있고, 첫 바이트가 0x00–0x7F면 음표(그 값이 음 높이, 60이 가운데 도), 0x80이면 쉼표, 그 밖은 악기 바꾸기·음량·팬·템포 같은 명령입니다. 시간은 틱으로 세고, 4분음표 하나가 48틱입니다.

이 곡은 트랙마다 처음에 C7 00(폴리)을 둡니다. 이 설정에서는 음표 명령이 「이 길이만큼 울려라」만 적고 바로 다음 명령으로 넘어가서, 시간을 넘기는 것은 쉼표뿐입니다(C7 01 모노에서는 음표가 제 길이만큼 기다립니다). 그래서 음표 둘을 이어 적으면 화음이 되고, 그 뒤의 쉼표 192틱이 한 마디를 넘깁니다. 길이 값은 7비트씩 나눠 적고, 위 비트가 1이면 다음 바이트가 이어집니다. 85 7F는 5 × 128 + 127 = 767틱입니다.

SSEQ 명령 읽기

고스트 트릭 SEQ_GHOST_BGM054_B(시퀀스 37번) 첫 트랙의 처음 117바이트를 명령으로 읽습니다. 「다음 명령」을 누르면 명령 하나를 읽고, 그 명령이 몇 틱째에 실행되는지와 아래 악보 그림의 위치를 보여 줍니다.

  1. FE FF 01쓰는 트랙 표시 0x01FF0틱
  2. 93 01 D3 01 00트랙 1 열기: 데이터 +0x1D3부터0틱
  3. 93 02 F4 02 00트랙 2 열기: 데이터 +0x2F4부터0틱
  4. 93 03 16 04 00트랙 3 열기: 데이터 +0x416부터0틱
  5. 93 04 53 05 00트랙 4 열기: 데이터 +0x553부터0틱
  6. 93 05 80 08 00트랙 5 열기: 데이터 +0x880부터0틱
  7. 93 06 AF 0A 00트랙 6 열기: 데이터 +0xAAF부터0틱
  8. 93 07 48 0B 00트랙 7 열기: 데이터 +0xB48부터0틱
  9. 93 08 8C 0C 00트랙 8 열기: 데이터 +0xC8C부터0틱
  10. C7 00모노/폴리 0(0 = 음을 겹쳐 울림)0틱
  11. E1 46 00템포 70(4분음표가 1분에 몇 번)0틱
  12. 81 00악기(프로그램) 0번0틱
  13. CA 00모듈레이션 깊이 00틱
  14. C1 46트랙 음량 700틱
  15. C0 6E팬 110(0 왼쪽, 64 가운데, 127 오른쪽)0틱
  16. 3C 64 85 7F음 C4(60), 세기 100, 길이 767틱0틱
  17. 4F 64 82 7F음 G5(79), 세기 100, 길이 383틱0틱
  18. 80 81 40쉼표 192틱: 이만큼 기다린 뒤 다음 명령0틱
  19. 48 64 82 7F음 C5(72), 세기 100, 길이 383틱192틱
  20. 80 81 40쉼표 192틱: 이만큼 기다린 뒤 다음 명령192틱
  21. 4D 64 82 7F음 F5(77), 세기 100, 길이 383틱384틱
  22. 80 81 40쉼표 192틱: 이만큼 기다린 뒤 다음 명령384틱
  23. 46 64 82 7F음 A#4(70), 세기 100, 길이 383틱576틱
  24. 80 81 40쉼표 192틱: 이만큼 기다린 뒤 다음 명령576틱
  25. 3C 64 85 7F음 C4(60), 세기 100, 길이 767틱768틱
  26. 4B 64 82 7F음 D#5(75), 세기 100, 길이 383틱768틱
  27. 80 81 40쉼표 192틱: 이만큼 기다린 뒤 다음 명령768틱
  28. 44 64 82 7F음 G#4(68), 세기 100, 길이 383틱960틱
  29. 80 81 40쉼표 192틱: 이만큼 기다린 뒤 다음 명령960틱
  30. 4A 64 82 7F음 D5(74), 세기 100, 길이 383틱1152틱
  31. 80 81 40쉼표 192틱: 이만큼 기다린 뒤 다음 명령1152틱
  32. 43 64 81 3F음 G4(67), 세기 100, 길이 191틱1344틱

명령 32개, 마지막 명령은 1344틱(템포 70에서 24.00초)에 실행됩니다.

위 명령의 음을 시간과 음 높이로 그린 그림1마디2마디3마디4마디5마디6마디7마디8마디C4G5

첫 트랙은 FE FF 01로 트랙 0–8을 쓴다고 알리고, 93 명령 여덟 번으로 트랙 1–8이 시작할 위치를 정합니다. 그 뒤 템포 70, 악기 0번을 고르고 음을 적기 시작합니다. 트랙 아홉 개가 같은 틱을 세며 나란히 진행합니다.

더 깊이틱이 실제 시간이 되기까지

시퀀서는 ARM7 타이머로 약 5.21ms마다 깨어납니다(33,513,982Hz ÷ (64 × 2,728) ≈ 191.96Hz). 깨어날 때마다 템포 값을 더하고, 합이 240 이상이 되면 240을 빼고 악보를 한 틱 진행합니다. 템포 70이면 1초에 191.96 × 70 ÷ 240 ≈ 55.99틱입니다.

BGM054_B는 모든 트랙이 도입 4,608틱 뒤 3,072틱을 되풀이합니다. 박자로 계산하면 3,072 ÷ 48 = 64박, 템포 70에서 54.857초인데, 실제 타이머로는 3,072 × 240 ÷ (70 × 191.956) ≈ 54.870초입니다. 반복 구간을 표본 단위로 맞출 때는 이 13ms 차이가 커서, 합성 도구는 실제 타이머 값을 씁니다.

음의 세기 변화는 악기마다 정한 엔벨로프(어택·디케이·서스테인·릴리스)를 같은 주기로 갱신해 만듭니다. 고스트 트릭의 이 곡이 쓰는 악기 종류는 표본 하나로 모든 음을 내는 것(종류 1, 흔히 PCM 악기라 부름), 음 높이마다 다른 표본(드럼 세트, 0x10), 음역을 나눠 표본을 바꾸는 것(키 분할, 0x11) 세 가지입니다. 「PCM」은 악기 종류의 이름일 뿐이고, 표본의 저장 형식은 모두 IMA-ADPCM입니다.

실제 패치에서 실제 패치에서

고스트 트릭: 소리는 바꾸지 않았다

v1.1과 v1.2 모두 sound_data.sdat를 일본판과 바이트까지 같게 둡니다. 번역에 소리를 고칠 일이 없었습니다.

한글패치 페이지의 배경 음악: 녹음 대신 합성

다른 게임 페이지는 에뮬레이터 소리를 녹음해 반복 구간을 찾았습니다(14장). 이 곡은 녹음으로는 이음매가 맞지 않았습니다. 녹음본은 반복할 때마다 소리가 조금씩 달랐고, 녹음에서 상관으로 잡은 반복 길이는 55.51초로 실제 54.87초와 달랐습니다. 반복 한 바퀴가 시퀀서 갱신 주기로 10,532.57번이라 정수가 아니어서, 회차마다 틱과 엔벨로프 갱신이 어긋나는 것도 이유일 수 있습니다.

그래서 위의 형식대로 악보·악기 표·파형을 직접 읽어 합성했습니다. 반복 한 바퀴가 정수 개 표본이 되게 틱 간격을 맞추니, 도입부 음의 꼬리가 사라진 뒤로는 첫 반복과 둘째 반복이 표본 하나까지 같아졌습니다. 그 첫 지점(83.59초)을 반복 시작으로 두었습니다(노트). 곡 후보를 고를 때는 정보 표의 모든 곡 칸을 한 곡으로 바꾸고 효과음 음량을 0으로 만든 시험 롬을 곡마다 만들어 녹음했습니다.

직접 해 보기 직접 해 보기

SDAT를 꺼내고 kr-patches 저장소의 고스트 트릭 도구로 곡을 합성합니다. nds/ghost-trick 폴더에서 툴킷과 numpy가 깔린 파이썬(저장소 README의 ../../.venv/bin/python)으로 실행합니다.

krpatch nds unpack game.nds work/
../../.venv/bin/python tools/sseq_render.py work/data/sound_data.sdat 37 bgm054_b.wav --intro 4608 --loop 3072
# 같은 이름의 .json 에 loopStart·loopEnd(초)가 적힌다

곡 하나만 효과음 없이 나오는 시험 롬을 만들어 200초를 녹음하는 도구도 있습니다. 결과는 out/37_SEQ_GHOST_BGM054_B.wav입니다. desmume libretro 코어를 README대로 compat/cores/에 빌드해 두어야 합니다.

../../.venv/bin/python tools/bgm_dump.py game.nds out/ 37 --quiet

이 장에서

  1. 소리는 ARM7이 맡는다
  2. 음악 파일 SDAT
  3. 악보 SSEQ: 명령 바이트의 줄
  4. 실제 패치에서
  5. 고스트 트릭: 소리는 바꾸지 않았다
  6. 한글패치 페이지의 배경 음악: 녹음 대신 합성
  7. 직접 해 보기