단소를 연습할 때 누군가 옆에서 악보를 봐준다면 어떨까요? 어느 음을 잘못 불었는지, 박자는 어디서 빨라졌는지 알려주는 사람 말입니다.

얼쑤에서 만들던 채점 기능도 비슷한 역할을 합니다. 단소 연주를 녹음하면 악보와 비교해서 음정과 박자를 알려줍니다.

이 기능을 시험하면서 일부러 틀린 음을 넣었습니다. 악보에 적힌 음보다 한 옥타브 높은 음이었습니다.

그런데 채점 결과는 정답이었습니다.

더 이상한 점도 있었습니다. 같은 높이의 음을 따로 들려주면 제대로 찾았는데, 다른 음 사이에 짧게 넣으면 놓친 겁니다.

같은 높이의 음인데 왜 이런 차이가 생겼을까요?

소리 속에서 음높이 찾기

소리는 공기의 진동입니다. 공기가 1초에 몇 번 진동하는지를 주파수라고 하고, 단위로 Hz를 씁니다.

예를 들어 440Hz는 1초에 440번 진동하는 소리입니다. 880Hz는 그 두 배이고, 음악에서는 440Hz보다 한 옥타브 높은 음에 해당합니다.

하지만 악기 소리에는 한 가지 주파수만 들어 있지 않습니다. 예를 들어 440Hz를 기본으로 하는 소리에는 그 두 배인 880Hz, 세 배인 1,320Hz 성분도 함께 섞입니다.

440Hz     880Hz     1,320Hz
기본음    2배 성분   3배 성분

이때 기본이 되는 진동의 주파수를 기본주파수, 그 위에 함께 나타나는 성분을 배음이라고 합니다. 같은 음을 연주해도 단소와 피아노의 소리가 다른 데에는 배음의 구성과 소리가 시작하고 끝나는 방식 등이 영향을 줍니다.

음높이를 찾는 프로그램은 이 소리에서 기본주파수를 알아내야 합니다. 그런데 소리가 반복되는 간격을 살펴보면 헷갈리는 순간이 있습니다.

높은 음의 반복:   | 파형 | 파형 | 파형 | 파형 |
두 개씩 묶으면:   |   한 묶음   |   한 묶음   |

높은 음의 파형이 한 번 반복되는 간격뿐 아니라, 두 번 반복되는 간격도 규칙적인 반복으로 볼 수 있습니다. 두 배의 간격을 기본 주기로 잘못 고르면, 계산된 주파수는 절반이 됩니다. 즉, 실제보다 한 옥타브 낮게 추정할 수 있습니다.

이렇게 실제 음보다 한 옥타브 높거나 낮게 찾는 문제를 옥타브 오류라고 합니다.

앞뒤 음 사이에서 놓친 높은 음

우리는 음높이를 찾는 데 pYIN이라는 방법을 사용했습니다. 녹음을 짧은 구간으로 나눠 가능한 음높이를 찾고, 앞뒤 음이 이어지는 흐름도 살펴봅니다.

이렇게 하면 잠깐 섞인 잡음 때문에 음높이가 갑자기 튀는 일을 줄이는 데 도움이 됩니다.

하지만 실제로 높은 음이 짧게 끼어들었다면 어떨까요?

실제 입력:       낮은 음 ── 높은 음 ── 낮은 음
찾아낸 음높이:   낮은 음 ── 낮은 음 ── 낮은 음

우리가 만든 합성 입력에서 이런 일이 일어났습니다. 0.21초에서 0.42초 길이의 높은 음은 앞뒤의 낮은 음 사이에 놓였을 때 원래보다 한 옥타브 낮게 추정됐습니다. 같은 음을 따로 들려주면 올바른 높이를 찾았습니다.

짧은 음의 높이를 판단할 때, 앞뒤 음의 흐름이 영향을 줬을 가능성이 있습니다. 다만 결과를 보고 추측한 이유입니다. 프로그램이 왜 그 높이를 골랐는지까지 직접 확인한 것은 아닙니다.

음 하나만 따로 시험해서는 발견하기 어려운 문제였습니다. 실제 연주처럼 음을 이어 놓고도 확인해야 했습니다.

높은 음을 찾고 나서 생긴 문제

음높이를 제대로 찾으면 끝날 줄 알았습니다. 그런데 다른 문제가 남아 있었습니다.

높은 음을 찾았는데, 채점기는 그 음을 연주하지 않았다고 판단했습니다. 심지어 옆에 있는 정상적인 음까지 박자가 틀렸다고 표시하기도 했습니다.

문제는 음높이를 찾은 다음 단계에 있었습니다.

채점기는 먼저 녹음의 어느 부분이 악보의 어느 음인지 찾아야 합니다. 그래야 각 음을 맞게 불었는지 비교할 수 있습니다.

그런데 연주자는 악보에 적힌 시간대로 정확히 불기 어렵습니다. 어떤 음은 조금 길게, 다음 음은 짧게 불 수 있습니다. 같은 시각끼리만 비교하면 제대로 연주한 음도 어긋날 수 있습니다.

우리는 악보와 녹음의 시간 흐름을 맞춰 보며 어느 부분을 연결할지 찾았습니다. 이때 사용한 방법이 동적 시간 정렬, 줄여서 DTW입니다.

악보:       첫 음 ───── 둘째 음 ───── 셋째 음
녹음:       첫 음 ───── 높은 음 ───── 셋째 음
                         ↑
                원래 둘째 음에 대응해야 함

문제는 높은 음과 악보의 둘째 음이 한 옥타브나 차이 난다는 점이었습니다. 기존 비교 방식에서는 이 차이를 매우 큰 비용으로 계산했습니다. 그 결과, 높은 음을 둘째 음에 연결하기보다 그 구간을 앞뒤 음으로 넘기는 경로가 선택됐습니다.

그러자 둘째 음은 거의 연주되지 않은 것처럼 보이고, 주변 음의 시간도 함께 어긋났습니다.

틀린 음을 찾아내도 악보의 잘못된 위치에 연결하면, 사용자에게 전하는 설명은 달라질 수 있었습니다.

음을 연결하는 일과 채점하는 일

두 곳을 고쳤습니다. 음높이를 다시 살피는 부분과, 소리를 악보에 연결하는 부분입니다.

1. 한 옥타브 낮게 잡힌 음 다시 살펴보기

먼저 음높이를 찾는 프로그램이 고른 높이와 그 두 배 높이에서 소리의 크기를 비교했습니다.

찾아낸 높이의 소리는 아주 약한데 두 배 높이의 소리가 훨씬 강하다면 어떨까요? 실제보다 한 옥타브 낮게 찾았을 가능성이 있습니다.

우리는 두 높이의 소리 크기 비가 0.1보다 작을 때 두 배 높이로 다시 판단했습니다.

프로그램이 찾은 높이 f의 크기
────────────────────────────── < 0.1 이면 2f를 다시 선택
       두 배 높이 2f의 크기

이 숫자는 우리가 살펴본 합성 음원과 단소 녹음을 바탕으로 정했습니다.

2. 음을 연결할 때 옥타브 차이 줄이기

다음으로 악보와 녹음을 연결하는 기준을 바꿨습니다.

한 옥타브 차이 때문에 소리를 옆 음으로 넘기지 않도록, 연결할 때 계산하는 차이를 줄였습니다.

여기서 중요한 구분이 있습니다.

  • 연결할 때: 이 소리가 악보의 어느 음에 해당하는가?
  • 채점할 때: 해당 음의 높이가 악보와 맞는가?

연결할 때 차이를 줄였다고 해서 높은 음을 정답으로 처리하지는 않았습니다. 일단 올바른 음표에 연결한 뒤, 원래 음높이를 사용해 한 옥타브 높은 오류로 판정했습니다.

비유하면 시험지에서 답을 채점하기 전에 먼저 답이 어느 문제의 답인지 찾아야 하는 것과 비슷합니다. 2번 문제를 틀렸다고 해서 그 답을 1번이나 3번으로 옮겨 채점해서는 안 됩니다.

함께 비교해 본 방법

음높이가 크게 바뀌는 것을 더 잘 허용하도록 pYIN의 설정을 조정하는 방법도 살펴봤습니다.

일부 설정에서는 0.42초 길이의 높은 음을 찾았지만, 0.21초 길이의 음은 여전히 놓쳤습니다. 변화 허용 범위를 더 크게 늘린 설정에서는 짧은 음뿐 아니라 정상적인 음의 추정까지 나빠졌습니다.

각 구간을 앞뒤와 덜 묶어서 판단하는 방법도 비교했습니다. 짧은 높은 음은 찾았지만, 우리가 만든 합성 시험 묶음에서는 정상 음을 오류로 판단하는 사례가 더 많았습니다.

짧은 높은 음을 찾는 것만으로 방법을 고르지는 않았습니다. 기존에 맞던 음은 그대로 맞게 판단하는지도 함께 봤습니다.

수정 전후 결과

아리랑 악보를 바탕으로 만든 합성 음원에 의도적으로 옥타브 오류를 넣어 수정 전후를 비교했습니다. 음원은 사인파와 배음을 조합해 만들었습니다.

옥타브 오류 15개에 대한 결과수정 전수정 후
옥타브 오류라고 정확히 판단6개15개
오류는 찾았지만 종류를 다르게 판단2개0개
오류를 놓침7개0개

음높이 보정만 적용해 다섯 음짜리 합성 구간을 다시 검사했을 때는 세 번째 음이 MISSED, 그다음 정상 음이 TIMING으로 남았습니다. 정렬 비용까지 바꾼 뒤에는 세 번째 음만 옥타브가 의심되는 PITCH로 판정됐습니다. 두 단계가 왜 필요했는지 같은 입력에서 확인할 수 있었습니다.

같은 아리랑 합성 묶음을 판정 허용값 16조합으로 비교했을 때, 정상 음 733개 중 잘못 오류로 표시한 수는 조합에 따라 수정 전 9~10개, 수정 후 4~5개였습니다. 높은 음을 찾았을 때 이웃 음의 판정도 함께 확인한 결과입니다.

다만 이 표를 보고 “실제 단소 연주의 옥타브 오류를 모두 찾아낸다”고 말할 수는 없습니다.

이 실험은 한 곡을 바탕으로 만든 합성 입력이고, 옥타브 오류도 15개입니다. 실제 단소 소리는 연주자의 호흡, 악기, 녹음 환경에 따라 달라집니다.

실제 단소 녹음 다섯 개도 확인했지만, 한 연주자가 한 악기로 길게 분 단음이었습니다. 낮은 음 사이에 짧은 높은 음이 들어간 실제 연주는 아직 확인하지 못했습니다. 연주 속도가 흔들릴 때 바뀐 정렬 방식이 미치는 영향도 더 살펴봐야 합니다.

이번 실험에서 확인한 건 여기까지입니다. 준비한 입력에서 문제를 다시 볼 수 있었고, 수정 후에는 같은 오류가 관찰되지 않았습니다.

채점에서 함께 살펴봐야 할 두 가지

처음에 우리는 높은 음을 틀리게 찾은 이유만 고치면 된다고 생각했습니다. 실제로는 두 질문에 각각 답해야 했습니다.

이 소리는 몇 Hz인가?

이 소리는 악보의 어느 음에 해당하는가?

첫 번째 답이 맞더라도 두 번째 답이 어긋나면, 사용자는 엉뚱한 피드백을 받습니다. 한 음을 잘못 불었는데 앞뒤 음까지 틀렸다는 말을 들을 수도 있습니다.

음악을 채점한다는 것은 소리와 악보 사이의 차이를 숫자로 만드는 일입니다. 하지만 그 숫자가 도움이 되려면 먼저 어떤 소리가 어떤 음에 대응하는지, 그리고 그 차이를 어떤 실수로 설명할지가 맞아야 합니다.

이제는 실제 단소 연주에서도 확인해야 합니다. 여러 사람이 연주한 녹음에서도 틀린 음을 제대로 찾고, 맞는 음은 잘 지켜 주는지 살펴보려고 합니다.

참고 자료