본문 바로가기
무언가 만들어보기/펭귄맨

펭귄맨 : 음원의 푸리에 해석

by 도리언 옐로우 2024. 4. 12.

1. 서

펭귄맨은 '음원파일을 넣어주면 악보를 내놓는다'는 프로젝트지만, 딥러닝 모델에 mp3와 같은 파일을 직접 넣어 줄 순 없고, 어떤 변환을 통해 수치화시켜준 이후에야 input이 될 수 있다. 그리고 이러한 input으로 지금 유력하게 생각하고 있는 것이 바로 Spectrogram이다. 당연히 한번도 다뤄본적이 없기 때문에, 이 Spectrogram이 무엇인지 이해하는 시간을 먼저 갖기로 한다. 그리고 이를 위해 선행적으로 알고 있어야 하는 파동 및 푸리에 변환의 개념을 이번 포스팅에서 다룬다.

 

2. 음파의 파형

소리(=음파)는 파동이다. 고등학교 물리시간때 배우는 것 처럼 파동은 파장과 진폭, 진동수 정도를 주요개념으로 하고 있다. 기억할 것은 소리의 진폭이 커지면 소리의 크기가 커진다는 것과, 소리의 진동수가 커지면 소리의 피치가 높아진다는 점이다. 즉 피아노 건반의 경우 오른쪽으로 갈수록 진동수가 높아진다는 것을 기억하자. 파동은 다양한 형태를 가질 수 있는데, 대표적인 형태인 sine파는 다음과 같이 생겼다.

https://www.investopedia.com/terms/s/sinewave.asp

이 그림에서 sine파의 파장, 진폭, 진동수를 확인할 수 있다. x축에 Time이라고 써있고 이는 시간 변화에 따른 신호의 변화를 나타내는 것을 의미한다. 우리는 이러한 시간축에 따른 그래프에 굉장히 익숙할텐데, 이를 time domain에서 나타낸 그래프라고 한다. 

 

 

그런데 우리가 일상적으로 볼 수 있는 소리의 파형은 이렇게 아름다운 모습이 아니다. 이전 포스팅에서 만들었던 5마디의 Can't Help Falling In Love의 파형을 살펴보자. 일단 해당 악보를 44,100Hz의 wav 음원파일로 추출하였다. 여기서 Hz는 주파수(=진동수)의 단위로, 1초에 44100번 진동한다는 의미이다. 당연하지만 원본 음원파일의 피치를 높인다는 의미가 아니고, 음원의 sampling rate가 44100 Hz라는 의미로서 1초당 44100개의 샘플을 취한다는 것을 의미한다. Sampling rate이 높아지면 원본 음원의 디테일을 잘 포착하게 되어 음질이 좋아진다.

44100Hz로 추출하니 2MB나 된다

 

추출된 음원의 time domain에서의 파형을 살펴보자. librosa 라이브러리와 matplotlib 라이브러리를 사용하여 다음과 같은 파이썬 코드를 작성하였다.

import librosa
import matplotlib.pyplot as plt
import numpy as np

# 오디오 파일 로드
audio_path = "../../pianosound/Can't_Help_Falling_In_Love.wav"
y, sr = librosa.load(audio_path)

# 시간 배열 생성
time = np.arange(0, len(y)) / sr

# 파형 시각화
plt.figure(figsize=(14, 5))
plt.plot(time, y)  # x축에 시간 배열 사용
plt.title('Audio Waveform')
plt.xlabel('Time (seconds)')  # x축 레이블을 초 단위로 변경
plt.ylabel('Amplitude')
plt.show()

 

위 코드를 적용시키면 Can't Help Falling In Love의 time domain에서의 음원 파형을 확인할 수 있다.

뭔가 잔뜩 겹쳐져있는 그림이 나왔다

 

파형의 모습을 확인하기 위해 그래프를 확대시켜본다. 대략 250배 정도로 확대시켜보니 아래와 같은 파형의 모습을 확인할 수 있었다.

sine파와 달리 아름답지 않은 파형의 모습을 볼 수 있는데, 이게 일반적인 소리의 모습이다

 

3. 푸리에 해석

푸리에 해석(Fourier Analysis)이란 함수를 주파수 성분으로 분해하여 분석하는 것으로, '임의의 함수는 삼각함수의 급수로 나타낼 수 있다'라는 푸리에의 정리를 기반으로 한다. 푸리에의 정리를 쉽게 말하면, 위의 아름답지 않은 파형을 아름다운 파형의 합으로 나타낼 수 있다는 것이다.

 

$$ F(\mu )=\int_{-\infty }^{\infty }f(t)e^{-2 \pi \mu j t}dt$$

위 식은 푸리에 변환을 나타낸다. 푸리에 해석은 이 푸리에 변환을 이용해 함수를 해석하는 것이다. 관련 개념을 간단히 정리하면, 푸리에 급수(Fourier series)는 주기적인 신호를 sine파와 cos파의 합으로 표현하는 방법이고, 푸리에 변환(Fourier transform)은 푸리에 급수를 비주기적인 신호에 적용할 수 있도록 일반화한 것으로 이해할 수 있다. 이 푸리에 변환에 의해 time domain의 신호가 frequency domain으로 변환된다는 점이 중요하다. 이 frequency domain(주파수 영역)이라는게 와닿지 않을 수 있는데, 아래의 그림을 보면 이해가 쉽다.

https://t-m.kostech.net/theory/?q=YToxOntzOjEyOiJrZXl3b3JkX3R5cGUiO3M6MzoiYWxsIjt9&bmode=view&idx=6269674&t=board

 

이번엔 Can't Help Falling In Love의 음원 파형을 Fourier transform해보자. 파이썬으로 아래의 코드를 작성하였다. 참고로 여기서 등장하는 fft는 고속 푸리에 변환(Fast Fourier Transform)이라는 알고리즘을 의미하는데, FFT는 이산 푸리에 변환을 O(n log n)의 시간복잡도로 계산할 수 있게 한다. 조만간 관련된 백준 문제를 풀어보면서 구현해볼 계획이다.

import numpy as np
import matplotlib.pyplot as plt
import librosa

# 오디오 파일 로드
audio_path = "../../pianosound/Can't_Help_Falling_In_Love.wav"
y, sr = librosa.load(audio_path)

# 푸리에 변환 수행
fft_result = np.fft.fft(y)
magnitude = np.abs(fft_result)  # 진폭 스펙트럼 계산
frequency = np.linspace(0, sr, len(magnitude))  # 주파수 축 생성

# 주파수 축을 절반으로 줄이기 (나이퀴스트 주파수 이상은 의미 없음)
half_frequency = frequency[: len(frequency) // 2]
half_magnitude = magnitude[: len(magnitude) // 2]

# 주파수 스펙트럼 시각화
plt.figure(figsize=(10, 5))
plt.plot(half_frequency, half_magnitude)
plt.title("Frequency Domain")
plt.xlabel("Frequency (Hz)")
plt.ylabel("Magnitude")
plt.grid()
plt.show()

 

위 코드를 적용하여 아래의 frequenct domain에서의 그래프를 확인할 수 있었다.

Can't Help Falling In Love의 주파수

 

 

5마디의 분량이지만 들어간 note가 꽤 많기 때문에 수많은 주파수를 확인할 수 있다. 좀더 직관적으로 frequency domain을 느낄 수 있도록 4옥타브의 '솔' 이라는 하나의 note로 이루어진 음원에 대하여 Fourier transform을 적용해보자.

note G의 주파수

 

4옥타브의 솔은 약 392 Hz의 주파수를 갖는다. 이는 위 그래프에서도 확인할 수 있는데, 가장 높은 진폭을 보여주고 있다. 그런데 이외에도 몇개의 주파수가 보인다. 솔만으로 이루어진 음원이니까 392 Hz의 주파수만 보여야 할 것 같은데, 왜 다른 주파수도 보이는걸까?

 

그래프는 거짓말을 하지 않고, 다른 주파수가 보이는 것은 자연스러운 현상이다. 기본 주파수(Fundamental frequency)와 고조파(Harmonics)라는 개념을 알아야 하는데, 기본 주파수는 가장 두드러지게 나타나는 주파수로 음의 높낮이를 결정하는 주파수이고, 고조파는 기본 주파수의 정수배에 해당하는 주파수이다. 위 그래프를 다시보면 기본주파수(392 Hz)를 기본단위로 하는 일정한 간격의 주파수들이 나타남을 확인할 수 있고, 이들이 고조파임을 알 수 있다. 이러한 고조파의 패턴, 강도 등에 의해 음색이 결정되게 된다. 피아노의 솔과 바이올린의 솔이 다르게 들리는 것은 바로 이 고조파의 분포가 다르기 때문이다.

 

4. 결어

푸리에 변환을 굉장히 오랜만에 다시 마주하게 되어 몹시 반가웠다. 특히 과거에 별 생각 없이 써오던 것을 뭔가 실제 문제를 해결하기 위해 활용하고 있다는 점이 의외로 뿌듯한 부분이었다. 어쨌든 드디어 spectrogram을 다룰 준비가 끝났으니, 얼른 정리해서 다음 포스팅을 진행해보자.