시간이 별로 없는 프로젝트였기 때문에 1차적 목표를 피아노 소리를 MIDI note로 따내는 것으로 설정했다.
음원은 아이폰/아이패드의 Garage Band 앱으로 피아노를 연주해서 m4a파일을 얻었다.
비트와 음의 길이를 모두 무시하고 음만 먼저 따내는 일에는 madmom.features.notes 안에있는 함수들만을 이용하면 충분했다.
잘은 모르겠지만 두가지 방법이 있는듯했다.
- RNNPianoNoteProcessor + NoteOnsetPeakPickingProcessor
- CNNPianoNoteProcessor + ADSRNoteTrackingProcessor
RNNPianoNoteProcessor + NoteOnsetPeakPickingProcessor
처음에는 MIDI note를 추출할 샘플 음원으로 madmom github에 올라와있는 stereo_sample.wav 파일을 이용했다. 사실 이 음원은 화음이 들어가 있었다. 단음 2개, 2단 화음 2개, 총 6개의 음으로 이루어진 샘플 파일로 추출을 시도했다. 우선 추출 전에 음원 데이터를 array로 변환한 결과를 보고가자.
from madmom.features import *
proc = RNNPianoNoteProcessor()
act = proc('tests/data/audio/stereo_sample.wav')
print(act.shape)
print(act)
실행 결과
(415, 88)
[[ 5.6179601e-04 5.7703583e-04 3.4437981e-04 ... 6.6896435e-04
-8.4263738e-04 1.6938150e-04]
[ 9.3447394e-05 -3.6145560e-05 3.0633062e-05 ... 3.6225654e-05
-7.1371906e-05 -4.8864633e-05]
[ 1.6996113e-04 -6.8070367e-06 -1.1721067e-04 ... -1.9978732e-05
8.1769191e-05 4.8941001e-05]
...
[-2.3070141e-05 -8.9600217e-06 -5.0096773e-05 ... -1.4978461e-05
4.0986575e-05 -9.8720193e-08]
[-6.0462044e-05 -4.6007801e-05 -3.5163015e-05 ... -1.1020340e-05
1.3598707e-04 -1.6275793e-05]
[-4.3501484e-04 5.9543061e-04 -4.8070401e-04 ... -3.1812768e-04
7.8679062e-05 1.8898398e-05]]
act.shape을 출력한 결과로 (415, 88)이 출력되었다. RNNPianoNoteProcessor는 100fps로 음원을 프레임으로 쪼갠다. 415는 그렇게 쪼개진 frame의 개수이다. column 88개는 각 프레임이 피아노 건반 88개 중 하나에 해당할 transition probability인 듯 하다. 그런데 확률이면 왜 음수도 나오는지는 잘 모르겠다. 여튼 이 결과를 우리가 손으로 직접 해석할 일은 없으니 안심하자. 다음의 코드를 실행하면 한번에 실제 MIDI note를 추출한 결과를 얻을 수 있다. pitch_offset=21로 설정해주어야 피아노 음을 정확하게 인식할 수 있다고 한다. 사실 여기까지는 도큐먼트에 있는 대로 따라하면 된다.
proc = NoteOnsetPeakPickingProcessor(fps=100, pitch_offset=21)
act = RNNPianoNoteProcessor()('tests/data/audio/stereo_sample.wav')
print(proc(act))
결과
[[ 0.14 72. ]
[ 1.56 41. ]
[ 3.37 75. ]]
0번 column은 각 note의 attack timing인 듯 하고 1번 column은 각 note의 MIDI number이다. 분명 note가 6개 있는 음원 파일을 넘겨줬는데 3개밖에 찾지 못했다. 그래서 필자는 2번 방법도 시도해보았다.
CNNPianoNoteProcessor + ADSRNoteTrackingProcessor
proc = CNNPianoNoteProcessor()
adsr = ADSRNoteTrackingProcessor(pitch_offset=21)
act = proc('test/data/audio/stereo_sample.wav')
MIDI_notes = adsr(act)
print(MIDI_notes)
ADSRNoteTrackingProcessor의 사용법은 document에 자세히 나와있지는 않지만 1번 방법때처럼 pitch_offset=21로 주어보았다.
결과
[[ 0.12 72. 1.44]
[ 1.54 41. 1.84]
[ 2.5 77. 1. ]
[ 2.52 65. 0.96]
[ 2.54 60. 0.82]
[ 2.58 56. 0.82]
[ 3.34 75. 0.82]
[ 3.42 43. 0.74]]
이번엔 음 6개짜리 샘플 음원에서 8개의 음을 찾아냈다. 나는 차라리 음을 많이 찾고 줄여나가는 것이 쉽겠다고 판단하여 CNN + ADSR 조합을 조금 더 연구해보기로 했다. array의 첫번째 두번째 column은 아까와 같은 듯 하고 세번째 column은 무슨 숫자인지 아직 모르겠다.
소스코드를 열러본 결과 굉장히 많은 parameter를 조정해줄 수 있었고, 하나씩 다 해본 끝에 onset_threshold=0.9 를 넘겨주었을 때 비로소 올바른 결과값을 출력할 수 있었다. onset_threshold는 생성자에서 0.5로 기본적으로 초기화되고 있던 값이었다. onset이란 음의 시작점을 말하는데 이 한계값을 적절히 높여주었더니 음으로 인식되면 안되는 노이즈들이 제거되어 올바른 결과가 도출된 것 같다.
출력 결과는 다음과 같다.
[[ 0.12 72. 1.44]
[ 1.54 41. 1.84]
[ 2.5 77. 1. ]
[ 2.52 65. 0.96]
[ 3.34 75. 0.82]
[ 3.42 43. 0.74]]
여기서 주목할 점은 마지막 4개의 음은 사실 2단 화음 2개라는 점이다. 각 화음끼리의 attack timing은 각각 0.02, 0.08 간격이다. 악보를 그릴 때 각 음간의 간격이 0.08이하이면 화음으로 처리한다 등의 규칙으로 활용할 수 있겠다. (필자의 경우 0.03을 기준으로 하였다.)
필자의 경우 위의 과정들을 수행할 때에 stereo_sample.wav가 아닌 다른 wav파일이나 m4a, mp3 파일들을 변환하려고 하면 에러가 떴다. 이를 위해서 ffmpeg를 설치해야했다. pip으로 설치하는 것이 아니라 인터넷에 검색을 해서 컴퓨터에 직접설치를 하고 컴퓨터를 재부팅 했더니 venv임에도 반영이 잘 되었던 것 같은데 기억이 확실하지 않다. 주의하시길 바란다.
악보 그리기는 vexflow 카테고리에서 이어진다.
'코딩 > madmom' 카테고리의 다른 글
[madmom] pip 안 쓰고 수동으로 최신버전 설치하기 (python) (0) | 2021.08.06 |
---|---|
음원 분석 쉽게 시작하기 (madmom python) (0) | 2021.08.06 |
댓글