ГлавнаяБлогДетектор высоты тона: как я исправил ошибку в YIN
Алгоритмы

Детектор высоты тона: как я исправил ошибку в YIN

Разбираем баг в детекторе высоты тона на YIN: почему все ноты были выше и как исправить. Читайте и проверьте свой алгоритм!

Al
Редакция Algolitalgolit.ru
10 мин чтения31 августа 2026 г.

Вы когда-нибудь замечали, что ваш детектор высоты тона всегда показывает ноты чуть выше, чем они есть на самом деле? Я столкнулся с этим, когда строил свой инструмент для вокалистов. Оказалось, что проблема не в моём голосе, а в математике, лежащей в основе алгоритма. В этой статье я расскажу, как найти и исправить подобный баг, и дам практические советы, которые помогут вам проверить свой код.

Почему я искал альтернативу Vocal Pitch Monitor

Vocal Pitch Monitor — это Android-приложение, которое рисует ноту на графике в реальном времени. Оно популярно: более миллиона загрузок и 4.1 звезды на Google Play. Но если посмотреть на поисковые подсказки Google, видно, что люди ищут версии для других платформ: "vocal pitch monitor online", "vocal pitch monitor pc", "vocal pitch monitor free". iOS-версия от того же разработчика имеет всего 2.6 звезды и 42 отзыва. Такой разрыв между версиями — сигнал, что есть спрос на альтернативу.

Современные браузеры поддерживают Web Audio API, который может получать сырые аудиоданные с микрофона. Это значит, что такой инструмент больше не обязан быть приложением — он может работать прямо в браузере. Я решил построить свой детектор, но при проверке обнаружил, что он работает неправильно.

Симптомы бага: всегда выше, но по-разному

Я перестал тестировать свой детектор на своём голосе и начал подавать на вход чистые тоны с известной частотой. Результаты меня озадачили: показания всегда были завышены, причём тем сильнее, чем ниже нота. Вот пример:

Частота       Мой детектор
82.41 Гц     +29.8 центов
98 Гц        +15.5 центов
130.81 Гц    +9.1 центов
440 Гц       +2.6 центов

Тридцать центов — это примерно треть полутона. На низкой ноте "ми" мой инструмент врал бы любому, кто проверяет его камертоном. Три симптома указывали на одну причину: знак всегда положительный, ошибка растёт на низких нотах, и она особенно заметна на чистой синусоиде, а не на голосе.

Причина: смещение автокорреляции в FFT

Мой детектор использует алгоритм YIN. Он строит функцию разности:

d(tau) = sum_j (x[j] - x[j+tau])^2

Это можно разложить как:

d(tau) = мощность головного окна
       + мощность хвостового окна
       - 2 * автокорреляция(tau)

Обычная быстрая реализация вычисляет автокорреляцию через БПФ и использует упрощение: предполагает, что обе мощности равны r0 (автокорреляции при нулевом лаге). В итоге получается:

d(tau) = 2*r0 - 2*acf[tau]

Вот здесь и кроется ошибка. Дело в том, что автокорреляция через БПФ смещена. При лаге tau перекрывается только N - tau пар отсчётов, но результат всё равно делится на N. Поэтому acf[tau] искусственно убывает с ростом tau, даже для идеально периодического сигнала. Подставьте это в формулу — и разность завышается на больших лагах, а минимум сдвигается к меньшим лагам. Меньший лаг означает более высокую частоту. В итоге все показания завышены.

Теперь все три симптома объясняются: смещение всегда в одну сторону, поэтому знак всегда плюс; низкие ноты имеют большой период, а значит большой лаг, где смещение максимально; чистая синусоида имеет широкий плоский минимум, который легко сдвинуть, а голос с богатым гармоническим составом — узкий и устойчивый.

Исправление: честное вычисление разности

Вместо того чтобы исправлять автокорреляцию, я пересчитал функцию разности честно, на окне постоянной длины, для нескольких лагов вокруг первой оценки. Вот пример кода на Python:

def difference_function(signal, tau_min, tau_max):
    n = len(signal)
    # Используем половину буфера, чтобы окно было одинаковым для всех лагов
    window_size = n // 2
    d = []
    for tau in range(tau_min, tau_max + 1):
        diff = 0.0
        for j in range(window_size):
            diff += (signal[j] - signal[j + tau]) ** 2
        d.append(diff)
    return d

Постоянная длина — ключевой момент. Каждый лаг оценивается по одному и тому же числу отсчётов, поэтому артефакт счёта исчезает. Затем берём локальный минимум и интерполируем параболой. Это стоит один узкий проход по половине буфера, что немного по сравнению с уже выполненным БПФ.

После исправления таблица стала идеальной:

82.41 Гц     0.0 центов
98 Гц        0.0 центов
130.81 Гц    0.0 центов
440 Гц       0.0 центов

Урок: масштабируйте исправление под масштаб ошибки

Сначала я исправил баг неправильно. Моя первая версия искала три отсчёта в обе стороны от первой оценки. На 82 Гц это не дало никакого эффекта. Ошибка в тридцать центов — это около девяти отсчётов на низкой ноте и меньше одного на высокой. Фиксированная ширина поиска пропускала именно те случаи, где нужна была корректировка, и помогала только там, где она не нужна. Ширина должна была быть пропорциональна периоду:

radius = max(3, round(tau_center * 0.04))

Когда размер бага зависит от какой-то величины, исправление должно зависеть от неё так же. Константа против пропорционального дефекта работает только в одной точке шкалы.

Ещё одна деталь меня спасла: мой поиск возвращает исходную оценку без изменений, если минимум попадает на границу интервала, а не интерполирует с отсутствующим соседом. Эта защита позволила ошибке проявиться как "нет изменений", а не как правдоподобное неверное число. Защита, которая возвращает вход, когда не уверена, ценнее той, что всегда выдаёт ответ.

Две проверки для любого детектора высоты тона

  1. Подавайте известную частоту. Не используйте свой голос — вы не сможете отделить свою ошибку от ошибки инструмента. Я сгенерировал шестнадцать синтетических тонов и нашёл баг, который месяцами не замечал, поя в микрофон.
  2. Используйте чистую синусоиду в басу. Это наименее прощающий вход для методов на основе автокорреляции. Именно его обычно берут скептически настроенные пользователи.

Держите два вида тестов отдельно. У меня есть стенд, который сравнивает мой порт с эталонной реализацией с точностью до десятитысячной цента, и он оставался зелёным всё это время, потому что обе стороны были неправильны одинаково. Тест на соответствие говорит лишь о том, что вы скопировали правильно. Только известная частота говорит, что вы правы.

Практический вывод: что делать прямо сейчас

Если вы разрабатываете или используете детектор высоты тона, немедленно проверьте его на чистых тонах с известной частотой, особенно в низком регистре. Если обнаружите, что показания завышены, посмотрите, как вы вычисляете автокорреляцию: вероятно, там есть смещение из-за БПФ. Исправьте функцию разности, как показано выше, и убедитесь, что ширина поиска масштабируется с периодом. Эти простые шаги сделают ваш инструмент точным и надёжным.

#детектор высоты тона#алгоритм YIN#автокорреляция#обработка звука#Python
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →