Ответ 1
Вот графический пример, иллюстрирующий пороговый подход к обнаружению начала записи:
На этом изображении показан типичный WAV файл с тремя отдельными нотами, которые воспроизводятся последовательно. Красная линия представляет собой выбранный порог сигнала, а синие линии представляют собой начальные позиции заметок, возвращенные простым алгоритмом, который отмечает начало, когда уровень сигнала пересекает порог.
Как показывает изображение, выбор правильного абсолютного порога затруднен. В этом случае первое примечание подбирается отлично, второе примечание полностью упускается, а третье примечание (едва) запускается очень поздно. В общем, низкий порог заставляет вас забирать примечания phantom, в то время как повышение его заставляет вас пропускать заметки. Одним из решений этой проблемы является использование относительного порога, запускающего запуск, если сигнал увеличивается на определенный процент в течение определенного времени, но у этого есть свои проблемы.
Более простое решение - сначала использовать в своем волновом файле несколько контринтуитивно называемое сжатие (не сжатие 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-библиотеку, вам лучше использовать ее точно, После того, как вы определили начальную и конечную позиции каждой заметки (и включили некоторые дополнения в начале и конце для пропущенных частей атаки и освобождения), вы можете теперь вытащить каждый фрагмент аудиоданных и передать его функции БПФ, чтобы определить высоту тона.
Один важный момент здесь - не использовать срез сжатых аудиоданных, а скорее использовать срез исходных немодифицированных данных. Процесс сжатия искажает звук и может привести к неточному считыванию показаний тангажа.
Один последний момент, касающийся времени атаки на заметку, заключается в том, что это может быть меньше проблем, чем вы думаете. Часто в музыке инструмент с медленной атакой (например, мягкий синтезатор) начнет запись раньше, чем острый инструмент атаки (например, пианино), и обе ноты будут звучать так, как будто они начинаются в одно и то же время. Если вы играете на инструментах таким образом, алгоритм с тем же временем запуска для обоих видов инструментов, что хорошо с точки зрения WAV-to-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;
}
}
При параметре = 1.0 эта функция не будет влиять на звук. Большие значения параметров (2,0 - это хорошо, что будет квадратизировать нормализованную разницу между каждым образцом и максимальным пиковым значением) приведет к большему сжатию и более громкому общему (но дерьмовому) звуку. Значения под 1.0 будут давать эффект расширения.
Еще один очевидный момент: вы должны записывать музыку в маленькой, неэховой комнате, поскольку эхо часто подбирается этим алгоритмом в виде примечаний phantom.
Обновление: вот версия StaticCompress, которая будет компилироваться в С#, а экспликация - все. Это возвращает ожидаемый результат:
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 не работает должным образом, на это будет получен ответ (только не я).