야, 너도 드럼 칠 수 있어 : 3. FFT 과정
드럼 악보 채보 프로젝트
- 본 글은 제 대학교 3학년 여름 방학이었던 2019년 6월부터 3개월 간 진행했던, “야, 너도 드럼 칠 수 있어” 프로젝트에 관련된 설명 글입니다.
- 편의상 설명 과정은 ‘~했다.’ 로 작성하였습니다.
- 소스 코드 는 다음과 같습니다. (Source)
채보의 사전적 정의 : 음악을 듣고 악보로 옮겨 적는 것
이전 글 “(야, 너도 드럼 칠 수 있어 : 2. 처리 과정 구현)” 에서 이어지는 글입니다.
1. FFT와 Numpy 라이브러리
사실 첫 번째 장에 적었어야 할 내용이지만, FFT 처리과정을 따로 분리한 만큼 이 페이지에 적었다.
1. FFT의 정의
먼저 통신공학, 신호 및 시스템, 혹은 음향 을 공부해 본 이들이라면 한번쯤 Fourier Transform, Fourier Series 를 들어 본 적이 있을 것이다.
여기서 이산적인(Discrete) 데이터의 집합에 Fourier Transform 을 적용하는 것이 DFT(Discrete Fourier Transform) 이고, 단순 반복문으로 계산되므로 O(N2) 의 시간 복잡도를 갖는다.
이러한 이산적인 n 개의 데이터가 주어질 때 O(Nlog2N) 의 연산량만으로 빠르게 DFT 를 수행하는 것이 바로 FFT(Fast Fourier Transform) 이다. 수식을 활용한 상세한 설명은 글의 목적과 맞지 않으므로 건너뛰도록 하겠다.
이러한 특징을 토대로 FFT 는 OFDM , 기계의 잡음 제거 , 영상과 음성의 잡음 제거 등 전반적으로 여러 산업 분야에서 활용된다.
우리가 프로젝트에서 활용한 .wav 샘플 파일은 44.1kHz 나 48kHz 의 샘플링레이트와 16bit 로 PCM 변조 된 파일이다. 따라서 초당 44100, 48000 개의 이산적인 샘플로 이루어진 파일이며, FFT를 통해 주파수를 구할 수 있다.
2. Numpy 라이브러리의 FFT 함수들
Numpy 라이브러리엔 수많은 함수가 있지만, 프로젝트에 활용된 함수는 크게 두 가지 이다.
1. fft(a[, n, axis, norm])
Compute the one-dimensional discrete Fourier Transform
n개의 point를 갖는 1차원 DFT를 수행한다.
- input[a/array] : 복소수도 가능한, array 를 input으로 받는다.
- output[narray] : axis가 사전 입력되었을 경우 axis로 나누어진 array 반환, 아닐 시 FFT 연산의 결괏값 만 반환.
딱히 여러가지의 요소가 필요하진 않았고, FFT 연산으로 계산된 주파수의 진폭값 만 필요했기 때문에 input으로 단순히 배열 하나만 주었다.
2. fftfreq(n[, d])
Return the Discrete Fourier Transform sample frequencies
Sample Frequency를 반환한다.
- input[n/int] : 샘플 Window Size 를 input으로 받는다.
- input[d/scalar] : 샘플링레이트 를 input으로 받는다.
- output[narray] : Window Size 와 샘플링레이트 를 통해 실질적인 주파수 값 을 반환해준다.
위의 np.fft.fft 는 FFT 연산 결괏값의 진폭 만 반환해주므로, 실질적으로 주파수를 반환 해주는 용도로 사용했다.
2. FFT 실행
- 다음과 같이 FFT 를 진행했다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | k = 0 for x in range(i): if nChannel == 2: data1 = ccount[x] spectre = np.fft.fft(data1) freq = np.fft.fftfreq(len(data1), 1/rate) mask = 0 < freq else: data1 = ccount[x] spectre = np.fft.fft(data1) freq = np.fft.fftfreq(len(data1), 1 / rate) mask = 0 < freq | cs |
i는 ccount만큼의 길이를 갖는다.
for x in range(i) 를 통해 range(i) 만큼 for loop 를 반복했다.
nChannel 값을 통해 Mono 일 경우와 Stereo 일 경우를 나누어서 계산을 진행했다.
단위가 샘플일 경우와 주파수일 경우를 나누어, spectre 과 freq 배열에 각각 저장했다.
- data1에 ccount배열의 구간을 설정한다.
- np.fft.fft 를 통해 spectre 배열에 주파수의 진폭 을 저장한다.
- np.fft.fftfreq 를 통해 해당 샘플의 주파수 를 저장한다.
- freq > 0 일 경우 mask 에 대입한다.
- 이후, 주파수가 갖는 진폭의 최댓값 을 구했다.
1 2 3 4 5 | maxamp = np.abs(np.max(spectre[mask])) masks = freq[mask] number = 0 frews = [] | cs |
np.max 를 통해 최댓값을 구한 후, np.abs 를 통해 양의 값으로 바꾸었다. Numpy 는 여러모로 편리하다.
freq > 0 인 mask들을 이후 처리과정을 위해 masks 배열에 다시 저장했다.
복잡한 FFT 연산과정이 np.fft.fft와 np.fft.fftfreq의 두 줄로 끝나는 것이 경이로울 따름이다.
다음으로는 결과값을 분류하고, 분류된 내용에 따라 화면에 출력할 차례이다.
다음 글 “(야, 너도 드럼 칠 수 있어 : 4. 결과 계산 및 출력)” 에서 이어집니다.
댓글남기기