얼쑤에서 백엔드의 분석 요청을 받아 음표별 판정을 돌려주는 API를 구현했습니다. 검증할 때 악보의 모든 음보다 20센트 낮게 만든 합성음을 넣었습니다. 반음이 100센트이므로, 같은 간격으로 음높이만 조금 낮춘 입력입니다.

분석은 HTTP 200과 DONE으로 끝났습니다. 그런데 응답의 조율 보정값 tuningOffsetCent는 −15.0센트였습니다. 입력을 만들 때 정한 값과 채점기가 실제로 적용한 값 사이에 5센트 차이가 있었습니다.

pYIN 추정에서 조율 보정까지

로컬에서 실제 분석 API를 띄우고, 저장소 역할을 하는 로컬 HTTP 서버가 합성 M4A를 보내도록 했습니다. API는 오디오를 16kHz 모노로 디코딩한 뒤 pYIN으로 짧은 구간마다 기본 주파수를 추정합니다. 당시 요청에는 기준음이 없어서 황의 기준 주파수를 311.13Hz로 두고, 악보의 각 음이 거기서 몇 반음 떨어져 있는지로 목표 음높이를 구했습니다.

녹음과 악보를 시간축에서 맞춘 뒤에는 각 음표에 대응하는 녹음 프레임 중 소리가 있는 가운데 구간을 골랐습니다. 시김새가 붙은 음을 제외하고, 골라 둔 프레임마다 목표 음과의 차이를 센트로 계산해 한데 모았습니다. 그 중앙값이 녹음 전체의 조율 보정값입니다. 당시 구현에서는 보정값의 절댓값이 100센트 이내이면 음정 오차를 계산할 때 그 값을 뺐고, 범위를 넘으면 조율 범위 초과를 표시하고 보정량 0을 적용했습니다.

이 과정에서 pYIN은 연속적인 주파수 범위를 10센트 간격의 후보로 나눠 추정합니다. 채점기가 응답으로 내보내는 값은 그 후보에서 출발해 악보에 대응시킨 뒤 중앙값을 구한 결과입니다. 그래서 응답의 소수 자릿수만으로 음높이를 얼마나 세밀하게 읽었는지 판단하기 어렵습니다. 당시 결과에 프레임별 추정값은 남아 있지 않아, 이 차이가 어느 단계에서 얼마나 생겼는지까지는 확인하기 어렵습니다. librosa의 pYIN 문서

합성음에서 평가한 네 음표는 모두 CORRECT였고, 응답의 음정 오차는 보정 뒤 각각 0.0센트였습니다. 이 결과는 같은 방향으로 낮춘 합성음을 보정한 뒤 악보의 음과 일치한다고 판정했음을 보여줍니다. 보정값 자체의 정확도를 보려면 합성할 때 정한 이동량과 따로 비교해야 합니다.

단소 녹음에 남은 음별 편차

팀에서는 별도로 국립국악원의 단소 음원 다섯 건을 분석했습니다. 한 연주자가 한 악기로 분 녹음입니다. 채점에서 쓰는 pYIN 설정으로 10ms 간격의 음높이를 추정하고, 라벨로 나눈 음마다 소리 있는 가운데 60%의 프레임을 사용했습니다. 녹음 전체의 보정값과 함께, 그 값을 뺀 뒤 각 음에 남는 차이도 계산했습니다. 국립국악원 국악기 디지털 음원

녹음녹음 전체의 보정값보정 뒤 음별 잔차의 최대 절댓값
평취, 강하게+15센트10센트
평취, 중간 세기로+5센트10센트
평취, 약하게−5센트10센트
역취−25센트30센트
평취와 역취를 섞은 음계·요성−5센트30센트

평취에서는 가장 강하게 분 녹음과 가장 약하게 분 녹음의 보정값이 20센트 차이 났습니다. 역취 녹음은 전체 보정값을 뺀 뒤에도 어떤 음은 목표보다 30센트 높고 다른 음은 10센트 낮았습니다. 녹음에 공통으로 더해진 차이는 보정값 하나로 줄일 수 있지만, 음마다 다른 차이는 그 값을 뺀 뒤에도 남습니다.

합성음 검증은 공통 이동량을 가진 입력이 API와 채점을 거쳐 어떻게 판정되는지 보여줬습니다. 단소 녹음에서는 거기에 음별 편차가 더해졌습니다. 이 다섯 건에서 확인한 편차의 크기는 해당 연주자와 악기의 관찰값입니다.