저자: 에릭 하게만(Eric Hagemann), 역 전순재
들어가는 말
우리는 지난 5회에 걸친 기사에서 수치처리 파이썬 모듈을 사용하여 수치 계산을 수행하였고 DISLIN이라는 도표화 패키지를 사용하여 계산 결과들을 도표로 만들었다. 개인용 컴퓨터에서 소리 파일을 분석하는 애플리케이션을 만들어 보는 것을 마지막으로 이 시리즈를 마감하려고 한다. 작기는 하지만 흥미로운 이 프로그램은 우리가 앞서 논의한 바 있는 많은 컴포넌트들을 하나로 조립할 것이다. 이 프로그램의 간결한 특성은 파이썬 언어의 강력함을 잘 보여준다.
배경
디지털 시대인 지금 소리 파일은 물론 디지털적으로 저장된다. 아날로그 카세트 테이프와 축음기가 주를 이루던 시대는 이제 뒤로 하고, 컴퓨터와 컴팩트 디스크(CDs)가 음악을 샘플화된 형태(sampled form)로 저장한다. 본질적으로 음파는 이산 시간 점(discrete points in time)에서 샘플화되거나 측정되어 저장 매체 위에 기록된다. 디지털적으로 저장된 소리들은 기다란 숫자 연속열이기 때문에 컴퓨터를 사용하면 쉽게 처리할 수 있다.
컴퓨터가 아니라 더 거슬러 올라가 디지털 기록장치가 없던 20년대로 돌아가보자. 해리 나이퀴스트(Harry Nyquist)는 샘플화된 소리가 본래의 소리(integrity)를 유지하려면 녹음하기 원하는 최고 주파수의 두 배로 샘플화해야 한다는 것을 보여주었다. 오디오광이라면 오디오 장비가 20Hz와 20,000 Hz사이에서 소리를 기록하고 재생산한다는 것을 알 것이다. (여기에서 Hz는 헤르쯔(Hertz)라고 한다. 세기를 바꾼 과학자 하인리히 헤르쯔(Heinrich Hertz)의 이름에서 유래하였으며 초당 주기(cycles)를 의미한다.) 컴팩트 디스크가 정확하게 초당 44,100 샘플로 오디오를 녹음하도록 현대의 음악 산업이 디자인된 이유는 바로 나이퀴스트(Nyquist)의 이론을 염두에 둔 것이다. 이런 방식으로 CD는 22,050 Hz까지 소리를 녹음하고 재생하는데, 이 정도면 아주 까다로운 오디오광조차도 만족할만하다! 관심이 있다면 웹을 찾아보자. 수 많은 참고 사이트들이 있으니까 말이다. 일단
digital-recordings.com부터 방문해보자.
웨이브 파일
이 도구는 컴퓨터에서 사용할 수 있는 사운드 녹음기에서 얻은 파일을 분석할 것이다. Win32 운영 시스템에서 사운드 녹음기는 시작 메뉴의 보조도구 아래에 보면 찾을 수 있다. (리눅스 사용자들에게는 미안하지만 리눅스에서 우리가 만든 도구를 작동시키려면 약간의 번역작업이 필요할 것이다.) Win32에서 사운드 녹음기의 출력결과는 웨이브(wave) 파일이라고 알려져 있다. 이러한 멀티미디어 파일은 소리 정보뿐만 아니라 소리를 재생산하기 위해 필요한 모든 핵심 정보를 담은 헤더도 갖고있다. 이름도 적절하게
wave라고 붙여진 파이썬 모듈에는 이러한 파일형태를 다루기 위한 함수들이 있다.
녹음된 것을 저장하려면 샘플 비율, 샘플 너비를 비롯하여 모노인지 스테레오인지를 선택해야 한다. 샘플 비율(sample rate)은 44,100 Hz, 22,050 Hz, 11,025 Hz, 또는 8,000 Hz중에서 선택할 수 있다. 이보다 높은 샘플 비율은 더 높은 주파수를 보존할 뿐만 아니라 결과적으로 더 큰 파일을 만들어 낸다. 샘플 형(sample types)에 대해서는 8 비트나 16 비트 너비를 선택할 수 있다. 간단하게 작업하기위해 우리는 8 비트 샘플로 작업을 한정하고자 한다. 마지막으로 스테레오(두 개짜리 채널 녹음) 또는 모노(한 개짜리 채널 녹음)를 선택할 수 있다. 이 기사에서는 모노 모드로 파일 작업을 한정할 것이다.
사운드 녹음기를 사용하면 사운드 파일의 매개변수들은 녹음이 끝난 후에 설정된다. "Save As" 명령어를 사용하여 출력 파일 이름을 선택하라. 대화상자의 아래를 보면 녹음에 필요한 매개변수들을 변경하는 버튼이 있을 것이다. 일단은 "PCM 11.025 kHz, 8 bit, Mono"로 시작하는 것이 좋다. 이렇게 하면 한 개짜리 트랙(mono)에 8 비트 샘플로 녹음되어 11.025 kHz로 샘플화된 파일 하나를 얻게된다(11,025 Hz; k = Kilo = *1000).
도구
도구는 소리분석기(sonogram)를 만들 것이다. 소리분석기는 신호의 주파수 내용을 시간에 대한 함수로 측정하여 그 결과들을 도표화한다. 이 도구를 사용하면 서로 다른 소리들을 시각적으로 볼 수 있을 뿐만 아니라 반대로 소기들의 특징을 시각적으로도 나타낼 수 있다. 이 도구로 음성, 음악, 다른 소리들을 분석할 수 있으며 조화 요소(harmonic components)도
[1] 볼 수 있다. 사람마다 목소리가 다르다고 생각하는가? 이 도구를 사용하면 목소리가 어떻게 다른지를 눈으로 볼 수 있을 것이다.
푸리에 변환(Fourier transform)
측정에 사용할 기본 알고리즘은 푸리에 변환(Fourier transform)이라고 알려져 있다. 푸리에 변환은 일련의 시간 샘플(소리 파일)을 취해 그 주파수 요소(frequency components)들을 측정하여 시간 샘플 연속열에 얼마나 많은 에너지가 나타나는지 다양한 주파수로 계산해 준다. 푸리에 변환(Fourier transform)은 보편적으로 사용되는 테크닉으로 우리가 사용할 실제 알고리즘은 고속 푸리에 변환(fast Fourier transform)이라고 불리며, 다른 말로는 FFT라고 불린다. FFT는 대단히 효율적인 방법으로 푸리에 변환을 계산하며 디지털 컴퓨터에서 사용하기 위해 설계된 것이다. FFT를 사용하는데 있어 한계가 있다면 길이가 2의 제곱인 연속열에 작동한다는 것이다 (2, 4, 8, 16, 32 ...).
FFT 모듈 사용을 제어할 수 있는지 예제 하나를 들어서 알아 보자.
먼저 사용하고자 하는 모듈을 임포트해야 한다.
>>> from Numeric import *
>>> from FFT import *
>>> from dislin import *
다음으로 신호(signal)를 만든다
>>> x=arange(256.0)
>>> sig = sin(2*pi*(1250.0/10000.0)*x) + \ sin(2*pi*(625.0/10000.0)*x)
이제
sig에는 256개의 신호 샘플이 담겨 있는데 이 샘플들은 (sin 함수에 의해서 생성된) 음색 두 개로 구성된다. 안쪽 괄호 안에 있는 숫자들(
1250/10000와
625/10000)은 그 음색의 주파수를 지정한다. 샘플 비율을 10,000 Hz라고 가정하고, 우리는 1,250Hz 음색 하나와 625Hz 음색 하나를 만들었다. 이제 이 신호를 FFT 모듈로 출력한 결과를 도표화해보자.
>>> plot(x[0:129],10*log10(abs(real_fft(sig))))
[그림 1] 신호에 내재한 에너지 도표
혹시 "또다른 127개의 샘플은 어떻게 된 것인가? 출력결과는 원래의 256개의 입력 값들 중에서 129개의 출력결과만을 담고 있지 않은가."라고 물어볼 수도 있다. FFT 모듈은 (실수부와 허수부로 이루어진) 복소수 데이터를 위해 디자인되었고 우리는 실수 데이터를 사용하고 있으므로 그 출력결과는 대칭적이다. 빠진 127개의 샘플은 그냥 출력결과로 나온 연속열의 전반부 127개 샘플의 복사본에 불과하다. 우리가 사용하고 있는
real_fft() 함수는 이점을 이해하고 그 복사본은 계산하지 않기 때문에 노력이 덜 든다.
10*log10(abs(를 사용하면 FFT가 측정한 에너지를 데시벨이라고 부르는 로그 크기 단위의 에너지로 변환한다. 이렇게 하면 더 적절하게 신호 파워를 표시할 수 있다. 두 정점은 이 시간 연속열이 두 가지 음색으로 구성되었다는 것을 말해 준다. x축은 0Hz에서 5,000Hz까지 모든 주파수를 128개의 구간으로 나누어 표현한다. 각 구간은 주파수 스펙트럼의 5000/128 =39.0625 Hz 대역에서 발견되는 에너지를 표현한다. 1 번 정점의 위치는 (5000/128)*16 = 625 Hz이며, 2 번 정점은 (5000/128)*32 = 1250 Hz에 있다. 이 위치는 음색을 만드는데 사용했던 수치와 상응한다. 나머지 구간에서 발견되는 값들은 그저 수치적인 반올림 잡음에 불과하다. 20 dB의 파워를 가지는 음색과 비교하면 그 "잡음"은 파워가 ~160 dB 정도 작다. 기본단위에 비교하면 진폭이 대략 1E-16이다.
도구
자연적인 소리는 다양한 진폭을 가진 많은 주파수로 구성된다. 주파수와 진폭의 복잡한 상호작용이 시간을 따라 변화하면서 듣기에 좋은 소리를 만들어 내는 것이다. 그러나 아무리 소리가 복잡하다고 해도 FFT 모듈을 사용하면 그 소리를 분해할 수 있고 주파수 용량을 가늠할 수 있다. 녹음된 길이 동안 모아진 데이터 샘플에 대하여 이 분석을 수행하면, 시간에 대한 신호 주파수 용량을 측정할 수 있다. 결과 도표는 소노그램(sonogram) 또는 스펙트로그램(spectrogram)이라고 알려져 있다. 도표는 시간(x축), 주파수(y축) 그리고 파워(색깔: 빨간색은 고 파워, 파란색은 저 파워임)의 관계를 보여준다.
다음 도표는 새 소리를 담고 있는 파일을 분석한 것이다(내가 키우는 새를 모사한 것). 녹색과 청색은 저 파워 주파수를 보여준다. 적색은 다양한 주파수의 지저귐을 보여준다.
[그림 2] 새소리를 담고 있는 파일에 대한 분석
프로그램 코드는 다음과 같으며 완전한 프로그램은
여기에서 볼 수 있다. 계속 진행해 나가면서 그 코드를 설명하겠다.
Basic imports
from Numeric import *
from MLab import *
from FFT import *
from dislin import *
import wave
import sys
import struct
wave 모듈에서 얻은
open 메소드를 사용해 웨이브(wave) 파일을 하나 열고 그 파일을 파일 포인터
fp에 할당한다. 그 파일에 관한 정보를 사용하여 분석에 사용될 기본적인 상수들 몇 개를 계산한다.
fft의 길이가 명령어 라인 인수로 넘겨지며 그 길이는 항상 2의 제곱이 되어야 한다.
# 웨이브 파일을 열어라
fp = wave.open(sys.argv[1],"rb")
sample_rate = fp.getframerate()
total_num_samps = fp.getnframes()
fft_length = int(sys.argv[2])
num_fft = (total_num_samps / fft_length ) - 2
임시적인 결과들을 저장하려면 행렬이 필요할 것이다. 여기에서 그 행렬을 만들고 나면 나중에 더욱 효율적인 연산을 할 수 있다.
# 임시로 사용할 작업 배열을 만들어라
temp = zeros((num_fft,fft_length),Float)
웨이브 파일에서 나온 데이터는 이진 형태의 압축된 수로 저장된다. 우리는 그 전체 파일을 방금 만든 임시 배열로 읽어 들일 것이다. 각 섹션을 읽고 나면 압축되었던 그 섹션을 부동 소수점 값으로 다시 풀어낸다. 주목할 것은 풀어낸 그 값이 이차원 배열로 저장되어 있다는 것이다. 저장된 이차원 배열은 각 행이
fft_length 샘플들을 담고 있다.
# 파일로부터 데이터를 읽어 들여라
for i in range(num_fft):
tempb = fp.readframes(fft_length);
temp[i,:] = array(struct.unpack("%dB"%(fft_length), \
tempb),Float) - 128.0
fp.close()
반드시 해야 하는 것은 아니지만 다음 단계는 그 데이터 행렬에서 각 행을 "창틀화(window)"
[2] 함수와 곱하는 것이다. 창틀화(window)는 FFT의 수치적 부작용
[3]을 어느 정도 완화하는데 사용된다. 벡터 하나와 행렬 하나를 곱하고 있으므로 여기에서는 확산(broadcasting)을 이용하고 있다!
# 데이터를 창틀화하라
temp = temp * hamming(fft_length)
다시 또
real_fft() 함수를 이용한다. 이 함수는 (복소수와 대조되는) 실수 데이터에 대해 최적화되어 있다. 여기서 주목할 것은 전체 배열의 FFT를 취하고 있다는 것이며 이는 기본으로 각 행의 FFT를 개별적으로 취하도록 기본으로 설정되어 있다. 출력결과를 로그 크기(logarithmic scale)로 변환하는데
10*log10이 또 다시 사용된다. 상수
(1E-20)를 더하면 정의되어 있지 않은
log10(0)의 문제가 해결된다. 각 인수가 0인지 점검하기 보다는 그냥 작은 불연속 값을 하나 추가하면 0인 경우가 절대로 일어나지 않음이 보장된다. 할 일은 아직도 많이 남아 있다. 이제부터가 본격적인 과정이다. 결과는 각 행(시간 신호의 조각들)에 대하여 각 주파수에서 발견된 에너지 값들을 담은 배열이다.
# FFT를 사용하여 변환하라, 파워를 반환하라
freq_pwr = 10*log10(1e-20+abs(real_fft(temp,fft_length)))
이제 우리는 도표를 출력하기 위해 DISLIN을 사용한다! 대부분의 작업은 크기를 교정하는 작업이다. 등고선(contour) Quickplot 함수를 사용하여 그 배열의 모든 행들을 동시에 살펴 보겠다.
conshade 함수는 열들에 대하여 행들을 칼라 도표에 도표화할 것이다. 도표에서 칼라는 요소의 너비와 관련이 있다(이 경우에는 파일의 특정 구간동안에 특정 주파수로 표현되는 에너지가 바로 그 너비이다).
# 결과를 도표하라
n_out_pts = (fft_length / 2) + 1
y_axis = 0.5*float(sample_rate) / n_out_pts * \
arange(n_out_pts)
x_axis = (total_num_samps / float(sample_rate)) / \
num_fft * arange(num_fft)
setvar("X","Time (sec)")
setvar("Y","Frequency (Hertz)")
conshade(freq_pwr,x_axis,y_axis)
disfin()
이제 모두 끝냈다! 모든 기능이 겨우 40줄의 코드밖에 안된다.
도구 실행하기
명령어들을 직접 타이핑 해넣거나 전체 버전을 다운받자. 여기서 필자는 그 프로그램을
pysono.py라고 부르겠다. 다운로드만 받으면 시험해 보는 것은 아주 쉽다. 먼저 여러분 컴퓨터에 있는 사운드 녹음기를 사용해서 소리 파일을 하나 만들어라(우리가 만든 도구는 60초 길이 이하의 파일에서 최적으로 작동함). 그리고 난 후 명령어 프롬프트에서 다음과 같이 타이핑 해넣자.
pysono.py
filename에 대해서는 분석하고자 하는 그 웨이브 파일 이름을 사용해라. 그리고
fft_size에 대해서는 사용할 FFT의 길이를 사용해라. 그리고 그 길이는 반드시 2의 제곱이어야 한다는 것을 기억해라. 이제 시험해 보자!
우리가 만든 그 도구를 사용하여 나는 나탈리 콜(Natalie Cole)이 부른 노래인 The Very Thought of You의 전반부 60초간을 분석했다. 널찍하게 나란히 있는 라인들은 그녀의 목소리에 여러 주파수가 존재한다는 것을 보여준다. 이 주파수들은 조화요소(harmonics)라고 하며 이 덕분에 소리의 품질이 좋은 것이다.
[그림 3] 나탈리 콜(Natalie Cole) 노래인 The Very Thought of You의 전반부 60초간의 녹음을 분석한 것
게임 끝
수치처리 파이썬과 관련된 이 기사 시리즈 전체를 통하여 우리는 여러 가지 테크닉을 사용해 보았다. 그리고 불과 40줄의 코드로 유용하면서도 완전한 애플리케이션 하나를 만들었다. 도구만 있으면 이제 서로 다른 파동(wave)을 가진 파일도 분석할 수 있다. 같은 단어를 서로 다른 피치(pitches)로 (하나는 정상적인 음색으로, 다른 하나는 가성적인 음색으로) 발음해보자. 음성 분석결과가 같게 보이는가? 같은 파일을 다양한 길이의 FFT들로 분석해보자. 주파수를 측정하는 능력에 차이가 있는 것을 볼 수 있는가? 이 코드에 숨어 있는 수학은 DSP(디지털 신호 처리, digital signal processing)라는 분야에서 유래한다. 더 자세한 정보가 필요하면 웹에서 "DSP"를 검색해보자. 추천할만한 사이트는
Digital Signal Processing Central이다.
에릭 하게만(Eric Hagemann)은 임베디드에서 메인프레임까지 모든 종류의 컴퓨터에서 숫자를 빨리 처리하는 알고리즘을 연구하고 있다.
[1] 조화요소 : 기본 주파수의 배수단위로 주파수를 가지는 주기적인 파동으로 표현된다. 기본요소에 대하여 조화요소들이 서로 겹치면 조화왜곡 효과(Harmonic Distortion)가 나타난다.
예제: 만약 Fundamental Frequency= 60Hz이라면
3rd Harmonic Frequency= 3x 60Hz=180Hz
5th Harmonic Frequency= 5x 60Hz=300Hz
[2] 각 시간 샘플들의 양끝 진폭을 점점 감소시켜 0으로 만들어 자연스럽게 결합시켜 주는 것
[3] 톱니현상, 과잉현상
에릭 하게만 수치처리 파이썬 시리즈 관련 기사