더 나은 얼후(Erhu) 튜너 만들기: 왜 브라우저에서는 자기상관(Autocorrelation)이 표준 음고 검출보다 뛰어난가
요약
Web Audio API를 활용한 악기 튜너 제작 시, 활을 사용하는 악기의 노이즈와 배음 문제를 해결하기 위해 FFT 대신 자기상관(Autocorrelation) 알고리즘을 사용하는 방법을 설명합니다.
핵심 포인트
- FFT 기반 방식은 활 쓰기 시 발생하는 고주파 노이즈와 복잡한 배음에 취약함
- 자기상관 알고리즘은 시간 영역에서 신호의 주기성을 분석하여 정확도를 높임
- 프렛 없는 악기의 정밀한 조율을 위해 1Hz 미만의 높은 정확도 확보가 필수적임
만약 여러분이 Web Audio API를 사용하여 웹 기반 악기 튜너를 만들어 본 적이 있다면, 아마 대부분의 사람들이 그러하듯 다음과 같은 방식으로 시작했을 것입니다: 마이크 데이터를 AnalyserNode에 입력하고, 고속 푸리에 변환 (FFT)을 실행한 뒤, 가장 높은 주파수 피크를 찾는 방식 말입니다.
이는 전형적이고 직관적인 접근 방식입니다. 그리고 합성된 톤(synthesized tones)이나 단순하게 튕기는 현악기(plucked strings)의 경우에는 상당히 잘 작동합니다.
하지만 최근에 저는 얼후(Erhu) (중국의 전통적인 두 줄 현악기)를 위해 특별히 설계된 브라우저 기반 튜너를 제작했고, 표준 FFT 기반 음고 검출(pitch detection)이 활(bow)을 사용하는 상황에서는 완전히 무너진다는 사실을 빠르게 깨달았습니다.
활 쓰기(bowing)가 왜 표준 튜너를 망가뜨리는지, 그리고 시간 영역(time-domain) 자기상관 (Autocorrelation) 알고리즘으로 전환하는 것이 어떻게 이 문제를 해결했는지 설명하겠습니다.
문제점: 활 쓰기의 과도 응답(Transients)과 노이즈
얼후에는 지판(fingerboard)도 없고 프렛(frets)도 없습니다. 줄은 공중에 매달려 있으며, 연주자는 줄을 눌러 음고를 변화시킵니다. 이 때문에 개방현(open strings)을 조율하는 것이 매우 결정적입니다. 만약 개방현인 D4나 A4가 단 1Hz라도 어긋나면, 음정을 잡기 위한 연주자의 전체적인 근육 기억(muscle memory)이 손상됩니다.
얼후를 제대로 조율하려면 연주자는 반드시 활을 사용해야 합니다. 줄을 튕기는 것(Plucking)은 다른 장력과 약간 다른 음고를 만들어냅니다.
하지만 송진이 발라진 말총이 강철 줄을 훑고 지나갈 때, 주파수 스펙트럼에는 혼란이 발생합니다:
고주파 긁힘 소리: 활의 마찰은 지속적이고 비조화적인(non-harmonic) 고주파 노이즈를 생성합니다.
과도 응답 (Transients): 활이 처음 줄을 물 때 발생하는 초기 충격은 배음(overtones)의 폭발을 일으킵니다.
복잡한 배음 (Complex Harmonics): 활을 사용하는 악기는 종종 매우 강력한 2차 및 3차 배음을 가지며, 이는 FFT가 기본 음고(fundamental pitch)가 한 옥타브 올라갔다고 착각하게 만들 수 있습니다.
이 신호를 기본적인 제로 크로싱 (zero-crossing) 알고리즘이나 단순한 FFT (Fast Fourier Transform)를 통해 실행하면, 튜너의 디지털 바늘이 불규칙하게 튈 것입니다. 노이즈 플로어 (noise floor)가 변동하고, 배음 (harmonics)이 피크 피커 (peak-picker)를 혼란스럽게 만들어, 사용자는 자신의 음고 (pitch)가 실제로 어디에 위치하는지 추측만 해야 하는 상황에 놓이게 됩니다.
해결책: 시간 영역 (Time Domain)에서의 자기상관 (Autocorrelation)
프렛이 없는 악기 (fretless instruments)에 요구되는 1Hz 미만의 정확도를 얻으려면, 주파수 노이즈를 무시하고 주기성 (periodicity)에만 엄격하게 집중하는 알고리즘이 필요합니다.
여기서 자기상관 (Autocorrelation)이 등장합니다.
오디오를 주파수 영역 (frequency domain)으로 변환하는 대신 (FFT와 같이), 자기상관은 시간 영역 (time domain)에 머무릅니다. 자기상관의 핵심은 다음과 같은 간단한 수학적 질문을 던지는 것입니다: "이 오디오 신호가 시간 이동(time-shifted)된 자기 자신의 모습과 얼마나 닮았는가?"
주파수 대역을 분석하는 대신, 이 알고리즘을 파형의 동일한 투명 필름 두 장을 서로 겹쳐서 미끄러지듯 움직이는 것이라고 생각해보세요. 시스템은 오디오의 짧은 스냅샷을 포착하여 디지털 복사본을 만든 뒤, 그 복사본을 아주 작은 시간 단계(또는 샘플)씩 앞으로 이동시키기 시작합니다. 각 이동 단계마다, 시스템은 겹치는 데이터 포인트들을 수학적으로 비교합니다. 이동된 복사본의 봉우리(peaks)와 골(valleys)이 원본 스냅샷의 다음 봉우리 및 골과 완벽하게 맞물릴 때, 계산 결과는 '상관 피크 (correlation peak)'라고 알려진 거대한 수학적 스파이크를 생성합니다. 해당 이동 거리("지연 (lag)")는 음파의 한 완전한 주기 길이를 나타냅니다. 오디오의 샘플 레이트 (sample rate)를 이 지연 거리로 나누기만 하면, 헤르츠 (Hz) 단위의 믿을 수 없을 정도로 정밀한 기본 주파수 (fundamental frequency)를 즉시 얻을 수 있습니다.
이 방식이 활악기 (bowed instruments)에 매우 효과적인 이유:
활 노이즈 (bow noise, 긁히는 소리)는 무작위적이고 비주기적이기 때문에, 이동 시 자기 자신과 일치하지 않습니다. 자기상관 알고리즘은 이러한 과도 노이즈 (transient noise)를 자연스럽게 걸러내어, 긁히는 소리는 완전히 무시하고 현의 반복되는 기본 파형에만 집중합니다.
브라우저에서의 구현
UI 스레드(UI thread)를 지연시키지 않고 이를 원활하게 작동시키려면, 무거운 작업들을 분리해야 합니다. 과거에는 개발자들이 ScriptProcessorNode를 사용했지만, 이는 현재 지원 중단(deprecated)되었습니다.
현대적인 구현에서는 AudioWorklet 인터페이스를 사용해야 합니다. 자기상관(Autocorrelation) 연산을 전용 Worklet 스레드로 넘김으로써, 튜너 다이얼의 DOM 애니메이션을 처리하는 메인 JavaScript 스레드와 완전히 격리된 상태로 오디오 버퍼를 실시간으로 처리할 수 있습니다.
"옥타브 오류(Octave Error)" 해결하기
자기상관이 완벽한 것은 아닙니다. 가장 큰 결함은 "옥타브 오류(Octave Error)"입니다.
만약 파형이 100개 샘플의 이동(shift)에서 자신과 완벽하게 일치한다면, 200개, 300개 샘플 등에서도 자신과 완벽하게 일치하게 됩니다. 때때로 악기의 배음 구조(harmonic makeup)로 인해, 알고리즘이 하위 배음(sub-harmonic)에 고정되어 음고를 실제보다 정확히 한 옥타브 낮게 잘못 보고할 수 있습니다.
이를 방지하기 위해, 견고한 튜너들은 피크 피킹 휴리스틱(Peak Picking Heuristics)을 구현합니다. 단순히 가장 높은 상관관계 값만을 가져오는 대신, 특정 임계값(threshold)을 넘어서는 첫 번째 유의미한 피크를 분석하고, 안정성을 보장하기 위해 주변 프레임과 대조하여 검증합니다.
결과
시간 영역(time-domain) 자기상관과 부드러운 UI 보간(interpolation)을 결합함으로써, 우리는 음고가 튀지 않고, 송진 가루 소리(rosin scratch)에 혼동되지 않으며, 얼후 연주자가 D4와 A4를 완벽하게 맞출 수 있는 튜너를 얻을 수 있습니다.
웹을 위한 오디오 도구를 제작하고 있다면, 맹목적으로 FFT를 선택하지 마세요. 만약 당신의 목표가 음고 검출(pitch detection)이라면—특히 유기적인 악기, 활로 켜는 악기, 또는 소음이 있는 악기라면—자기상관(autocorrelation)이 거의 항상 더 나은 선택입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기