chordotomy는 녹음을 듣고 박자마다 코드를 적어 준다. 듣는 건 사전 학습된 코드 인식 모델 lv-chordia가 하고, 내가 짠 부분은 그 출력을 읽을 수 있는 타임라인으로 바꾼다.
벤치마크는 평균적으로 얼마나 맞는지만 알려준다. 그래서 곡 하나를 골라 코드 하나하나 확인해 봤다. 鹿乃(Kano)의 「光の道標」를 ChordWiki의 팬 채보와 비교했다.
곡에서 버그가 두 개 나왔고, 분석기가 돌아가는 방식에 대한 질문 하나가 세 번째를 찾았다. 셋 다 모델 잘못이 아니었다.
1. Em7/A가 Asus4로 나왔다
채보에는 Em7/A가 여러 번 나온다. J-pop이 즐겨 쓰는 서스펜디드 도미넌트다. 그런데 분석기는 Asus4라고 했다.
모델은 A:sus4(b7), 즉 7sus4로 들었다. 채보와 거의 같은 코드다. 문제는 모델의 라벨을 더 작은 어휘로 옮기는 내 변환표였다. 그 어휘에 7sus4가 없었다.
"sus4(b7)": "sus4", # loses the seventh
주석에도 7음을 잃는다고 쓰여 있었다. 어휘에 7sus4를 추가했고, 이제 그 자리들은 A7sus4(V7sus4)로 나온다.
벤치마크는 이걸 전혀 몰랐다. 두 데이터셋 모두에서 모델이 7sus4를 한 번도 출력하지 않아서, 점수가 끝자리 하나 바뀌지 않았다. 실제 곡이 아니었으면 못 찾았을 버그다.
2. 있지도 않은 슬래시 코드
브리지에서 분석기는 F♯7/C, Bm7/D♯를 썼다. 채보는 그냥 F♯7, Bm7이다.
베이스는 단순한 규칙으로 골랐다. 어느 정도 큰 음 중 가장 낮은 음. 그런데 바쁜 브리지에서는 이 규칙이 진짜 베이스 밑의 작은 저음을 집었다.
한편 모델에는 베이스만 따로 내는 출력이 있다. F♯7 아래에서 F♯에 0.97을 주고 있었다. 내 프로그램은 매번 이걸 계산해 놓고 한 번도 읽지 않았다.
이제 코드 밖의 베이스는 모델이 뒷받침할 때만 남긴다. 브리지의 가짜 슬래시는 사라졌고, D/E 같은 진짜 슬래시는 그대로 남았다.
3. 박자가 코드를 망치고 있었다
모델은 짧은 프레임마다 라벨을 붙이고, 나는 그 라벨을 박자에 맞춰 정렬한다. 그래야 코드가 음악에 맞게 줄을 선다. 그런데 이 정렬 자체가 정확도를 깎는 건 아닐까 궁금했다.
깎고 있었다. 그런데 데이터셋에 적힌 정답 박자에 맞춰 정렬하면, 오히려 모델의 원래 출력보다 점수가 높았다. 문제는 정렬이 아니라 박자 추적기였다. 한 데이터셋에서는 녹음 180개 중 72개만 박자를 제대로 잡았다.
최근 박자 추적 모델인 Beat This!로 바꾸고, 그 출력을 템포를 하나로 유지하는 추적기에 넣었다. 이제 분석기는 두 데이터셋 모두에서 모델의 원래 출력보다 점수가 높다(0.947 대비 0.950, 0.893 대비 0.900). 그 곡에서는 86 BPM을 그대로 지켰고, 아웃트로에서 곡이 느려지는 곳에서는 박자도 같이 느려진다.
버그는 곡이 찾아도, 판정은 곡이 하면 안 된다
곡 하나에 맞춰 튜닝하면 그 곡에만 맞는 분석기가 된다. 그래서 곡은 문제를 가리키는 데만 썼다. 모든 수정은 공개 데이터셋 두 개에서 점수를 지키고, 합성한 테스트 클립도 통과해야 했다.
그래서 채보를 무시한 적도 있다. 베이스로 코드를 다시 고르는 규칙은 곡에서 두 곳을 고치고 두 곳을 망가뜨렸다. 맞던 D/E가 Esus4가 됐다. 그 규칙은 넣지 않았다.
자세한 내용은 저장소에 있다. chordotomy는 PyPI에 있고(uv tool install "chordotomy[model]"), 뷰어에서 곡을 코드와 함께 재생해 볼 수 있다. 직접 확인해 봐도 좋다.