야, 너도 드럼 칠 수 있어 : 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) 이다. 수식을 활용한 상세한 설명은 글의 목적과 맞지 않으므로 건너뛰도록 하겠다.

이러한 특징을 토대로 FFTOFDM , 기계의 잡음 제거 , 영상과 음성의 잡음 제거 등 전반적으로 여러 산업 분야에서 활용된다.

우리가 프로젝트에서 활용한 .wav 샘플 파일은 44.1kHz48kHz 의 샘플링레이트와 16bitPCM 변조 된 파일이다. 따라서 초당 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
= 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 일 경우를 나누어서 계산을 진행했다.

단위가 샘플일 경우와 주파수일 경우를 나누어, spectrefreq 배열에 각각 저장했다.

  1. data1에 ccount배열의 구간을 설정한다.
  2. np.fft.fft 를 통해 spectre 배열에 주파수의 진폭 을 저장한다.
  3. np.fft.fftfreq 를 통해 해당 샘플의 주파수 를 저장한다.
  4. 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. 결과 계산 및 출력)” 에서 이어집니다.

댓글남기기