Обратите внимание на обнаружение начала

Я разрабатываю систему в помощь музыкантам, выполняющим транскрипцию. Цель состоит в том, чтобы выполнить автоматическую транскрипцию музыки (она не должна быть идеальной, так как пользователь исправит ошибки / ошибки позже) на монофонической записи одного инструмента. У кого-нибудь здесь есть опыт автоматической транскрипции музыки? Или цифровая обработка сигналов вообще? Помощь от кого-либо очень ценится независимо от того, какой у вас опыт.

До сих пор я исследовал использование быстрого преобразования Фурье для обнаружения основного тона, и ряд тестов как в MATLAB, так и в моих собственных тестовых программах на Java показали, что он достаточно быстрый и точный для моих нужд. Другим элементом задачи, которую необходимо решить, является отображение произведенных MIDI-данных в виде нот, но это то, что меня сейчас не касается.

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

6 ответов

Решение

Вот график, который иллюстрирует пороговый подход к обнаружению появления ноты:

альтернативный текст

На этом изображении показан типичный WAV-файл с тремя дискретными нотами, воспроизводимыми последовательно. Красная линия представляет выбранный порог сигнала, а синие линии представляют начальные позиции нот, возвращаемые простым алгоритмом, который отмечает начало, когда уровень сигнала пересекает порог.

Как показывает изображение, выбрать правильный абсолютный порог сложно. В этом случае первая нота подобрана нормально, вторая нота полностью пропущена, а третья (едва) начата очень поздно. В общем, низкий порог заставляет вас выбирать фантомные ноты, а при его повышении вы пропускаете ноты. Одним из решений этой проблемы является использование относительного порога, который запускает запуск, если сигнал увеличивается на определенный процент в течение определенного времени, но это имеет свои собственные проблемы.

Более простое решение состоит в том, чтобы сначала использовать несколько нелогичное название сжатия (не сжатие MP3 - это нечто совсем другое) в вашем волновом файле. Сжатие существенно сглаживает пики в ваших аудиоданных, а затем усиливает все, так что больше звука приближается к максимальным значениям. Эффект на приведенном выше примере будет выглядеть следующим образом (что показывает, почему название "сжатие" не имеет смысла - на звуковом оборудовании оно обычно обозначается как "громкость"):

альтернативный текст

После сжатия подход с абсолютным порогом будет работать намного лучше (хотя его легко сжимать и начинать подбирать вымышленную ноту, тот же эффект, что и при снижении порога). Существует множество волновых редакторов, которые хорошо справляются со сжатием, и лучше позволить им справиться с этой задачей - вам, вероятно, потребуется проделать большую работу, чтобы очистить свои волновые файлы, прежде чем обнаруживать заметки в их в любом случае.

В терминах кодирования WAV-файл, загруженный в память, представляет собой массив двухбайтовых целых чисел, где 0 обозначает отсутствие сигнала, а 32,767 и -32,768 обозначают пики. В своей простейшей форме алгоритм обнаружения порога просто запускается с первой выборки и читает массив, пока не найдет значение, превышающее порог.

short threshold = 10000;
for (int i = 0; i < samples.Length; i++)
{
    if ((short)Math.Abs(samples[i]) > threshold) 
    {
        // here is one note onset point
    }
}

На практике это работает ужасно, так как нормальное аудио имеет всевозможные переходные пики выше заданного порога. Одно из решений состоит в том, чтобы использовать текущую среднюю мощность сигнала (т.е. не отмечать начало, пока среднее значение из последних n выборок не превысит пороговое значение).

short threshold = 10000;
int window_length = 100;
int running_total = 0;
// tally up the first window_length samples
for (int i = 0; i < window_length; i++)
{
    running_total += samples[i];
}
// calculate moving average
for (int i = window_length; i < samples.Length; i++)
{
    // remove oldest sample and add current
    running_total -= samples[i - window_length];
    running_total += samples[i];
    short moving_average = running_total / window_length;
    if (moving_average > threshold)
    {
        // here is one note onset point 
        int onset_point = i - (window_length / 2);
    }
}

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

Обновление: этот график показывает детали обнаружения заметки, которые я пропустил, а именно обнаружение окончания заметки:

альтернативный текст

Желтая линия представляет непороговое значение. Как только алгоритм обнаружил начало ноты, он предполагает, что нота продолжается до тех пор, пока текущее среднее значение сигнала не упадет ниже этого значения (показано здесь фиолетовыми линиями). Это, конечно, еще один источник трудностей, как в случае, когда два или более примечаний перекрываются (полифония).

После того как вы определили начальную и конечную точки каждой ноты, вы можете проанализировать каждый фрагмент данных WAV-файла, чтобы определить высоту тона.

Обновление 2: я только что прочитал ваш обновленный вопрос. Обнаружение основного тона с помощью автокорреляции гораздо проще реализовать, чем FFT, если вы пишете свое собственное с нуля, но если вы уже извлекли и использовали предварительно созданную библиотеку FFT, вам лучше использовать ее наверняка., После того, как вы определили начальную и конечную позиции каждой ноты (и включили некоторые отступы в начале и в конце для пропущенных частей атаки и выпуска), теперь вы можете извлечь каждый фрагмент аудиоданных и передать их функции FFT для определить шаг.

Одним из важных моментов здесь является не использование фрагмента сжатых аудиоданных, а использование фрагмента исходных неизмененных данных. Процесс сжатия искажает звук и может привести к неточному считыванию основного тона.

И последнее замечание о времени атаки на ноты - это может быть меньше проблем, чем вы думаете. Часто в музыке инструмент с медленной атакой (например, мягкий синтезатор) начинает ноту раньше, чем острый инструмент атаки (например, пианино), и обе ноты будут звучать так, как если бы они начинались одновременно. Если вы играете на инструментах подобным образом, алгоритм выберет одинаковое время начала для обоих типов инструментов, что хорошо с точки зрения WAV-MIDI.

Последнее обновление (я надеюсь): Забудьте, что я сказал о включении некоторых образцов отступов из части ранней атаки каждой ноты - я забыл, что на самом деле это плохая идея для определения высоты тона. Части атаки многих инструментов (особенно пианино и других ударных инструментов) содержат переходные процессы, которые не кратны основной высоте, и имеют тенденцию портить определение высоты. По этой причине вы на самом деле хотите начинать каждый срез немного после атаки.

Да, и важно: термин "сжатие" здесь не относится к сжатию в стиле MP3.

Обновите снова: вот простая функция, которая выполняет не динамическое сжатие:

public void StaticCompress(short[] samples, float param)
{
    for (int i = 0; i < samples.Length; i++)
    {
        int sign = (samples[i] < 0) ? -1 : 1;
        float norm = ABS(samples[i] / 32768); // NOT short.MaxValue
        norm = 1.0 - POW(1.0 - norm, param);
        samples[i] = 32768 * norm * sign;
    }
}

Когда param = 1.0, эта функция не будет влиять на звук. Большие значения параметра (2,0 - это хорошо, что будет возводить в квадрат нормализованную разницу между каждым сэмплом и максимальным пиковым значением), что приведет к большей компрессии и более громкому (но дрянному) звучанию. Значения ниже 1.0 вызовут эффект расширения.

Еще один, вероятно, очевидный момент: вы должны записывать музыку в маленькой неэхогенной комнате, поскольку эхо-сигналы часто улавливаются этим алгоритмом в виде фантомных нот.

Обновление: вот версия StaticCompress, которая будет компилироваться в C#, и Explicity приведёт все. Это возвращает ожидаемый результат:

public void StaticCompress(short[] samples, double param)
{
    for (int i = 0; i < samples.Length; i++)
    {
        Compress(ref samples[i], param);
    }
}

public void Compress(ref short orig, double param)
{
    double sign = 1;
    if (orig < 0)
    {
        sign = -1;
    }
    // 32768 is max abs value of a short. best practice is to pre-
    // normalize data or use peak value in place of 32768
    double norm = Math.Abs((double)orig / 32768.0);
    norm = 1.0 - Math.Pow(1.0 - norm, param);
    orig = (short)(32768.0 * norm * sign); // should round before cast,
        // but won't affect note onset detection
}

Извините, мой уровень знаний о Matlab равен 0. Если вы разместили еще один вопрос о том, почему ваша функция Matlab не работает так, как ожидалось, на нее ответят (только не я).

То, что вы хотите сделать, часто называют WAV-to-MIDI (Google "WAV-to-MIDI"). Было много попыток этого процесса, с различными результатами (заметка - одна из трудностей; с полифонией гораздо сложнее иметь дело). Я бы рекомендовал начать с тщательного поиска готовых решений и начинать работу самостоятельно, только если нет ничего приемлемого.

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

Другой ответ: да, я много занимался цифровой обработкой сигналов (см. Программное обеспечение на моем веб-сайте - это программный синтезатор с бесконечным голосом, написанный на VB и C), и я заинтересован помочь вам с этой проблемой. Концептуально часть WAV-to-MIDI не так уж и сложна, она просто делает ее надежной на практике, что сложно. Начало записи - это просто установка порога - ошибки могут быть легко скорректированы вперед или назад во времени, чтобы компенсировать различия в атаках нот. Обнаружение основного тона на записи гораздо проще, чем в режиме реального времени, и включает в себя просто выполнение процедуры автокорреляции.

Вы должны взглянуть на MIRToolbox - он написан для Matlab и имеет встроенный детектор начала - он работает довольно хорошо. Исходный код GPL, так что вы можете реализовать алгоритм на любом языке, который вам подходит. Какой язык будет использовать ваш производственный код?

Эта библиотека сосредоточена вокруг звуковой маркировки:

aubio

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

и мне повезло с этим для обнаружения начала и обнаружения основного тона. Это в c, но есть обертки swig/python.

Кроме того, у автора библиотеки есть страница с диссертацией на странице, которая содержит отличную информацию и информацию о маркировке.

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

СУММА от 0 до N (X^2)

Сделайте это с кусками всего сигнала. Вы должны видеть пики, когда происходят вхождения (размер окна зависит от вас, мое предложение составляет 50 мс или более).

Обширные документы по обнаружению начала:

Для хардкорных инженеров:

http://www.nyu.edu/classes/bello/MIR_files/2005_BelloEtAl_IEEE_TSALP.pdf

Обычному человеку легче понять:

http://bingweb.binghamton.edu/~ahess2/Onset_Detection_Nov302011.pdf

Вы можете попытаться преобразовать WAV-сигнал в график зависимости амплитуды от времени. Тогда способ определения согласованного начала состоит в том, чтобы вычислить пересечение касательной в точке перегиба восходящего фланга сигнала с осью x.

Другие вопросы по тегам