Обнаружение основного тона через автокорреляцию не удается на более высоких частотах
Я пытаюсь получить класс высоты звука из записанного голоса (44,1 кГц), используя автокорреляцию. То, что я делаю, в основном описано здесь: http://cnx.org/content/m11714/latest/ а также реализовано здесь: http://code.google.com/p/yaalp/source/browse/trunk/csaudio/WaveAudio/WaveAudio/PitchDetection.cs (часть, использующая PitchDetectAlgorithm.Amdf)
Поэтому, чтобы определить класс основного тона, я строю массив с нормализованной корреляцией для частот от C2 до B3 (2 октавы) и выбираю тот, у которого наибольшее значение (сначала выполняется преобразование "1 - корреляция", поэтому поиск не производится). минимум, но максимум)
Я проверил это с сгенерированным аудио (простой синус):
data[i] = (short)(Math.Sin(2 * Math.PI * i/fs * freq) * short.MaxValue);
Но это работает только для входных частот ниже, чем B4. Исследуя сгенерированный массив, я обнаружил, что, начиная с G3, появился еще один взгляд, который в итоге становится больше, чем правильный. и мой B4 определяется как E. Изменение количества анализируемых частот не помогло вообще.
Мой размер буфера составляет 4000 сэмплов, а частота B4 составляет ~493 Гц, поэтому я не могу придумать причину, по которой это терпит неудачу. Есть ли еще какие-либо ограничения на частоты или размеры буфера? Что там происходит не так?
Я знаю, что я мог бы использовать БПФ, как это делает Performous, но использование этого метода выглядело простым и также дает взвешенные частоты, которые можно использовать для отображения визуализаций. Я не хочу выбрасывать это так легко и хотя бы понять, почему это не удается.
Обновление: основная функция используется:
private double _GetAmdf(int tone)
{
int samplesPerPeriod = _SamplesPerPeriodPerTone[tone]; // samples in one period
int accumDist = 0; // accumulated distances
int sampleIndex = 0; // index of sample to analyze
// Start value= index of sample one period ahead
for (int correlatingSampleIndex = sampleIndex + samplesPerPeriod; correlatingSampleIndex < _AnalysisBufLen; correlatingSampleIndex++, sampleIndex++)
{
// calc distance (correlation: 1-dist/IntMax*2) to corresponding sample in next period (0=equal .. IntMax*2=totally different)
int dist = Math.Abs(_AnalysisBuffer[sampleIndex] - _AnalysisBuffer[correlatingSampleIndex]);
accumDist += dist;
}
return 1.0 - (double)accumDist / Int16.MaxValue / sampleIndex;
}
С помощью этой функции высота тона (псевдокод)
tone = Max(_GetAmdf(tone)) <- for tone = C2..
Я также попытался использовать фактическую автокорреляцию с:
double accumDist=0;
//...
double dist = _AnalysisBuffer[sampleIndex] * _AnalysisBuffer[correlatingSampleIndex];
//...
const double scaleValue = (double)Int16.MaxValue * (double)Int16.MaxValue;
return accumDist / (scaleValue * sampleIndex);
но это не удается получить A3 в качестве D в дополнение к B4 в качестве E
Примечание: я делю не по длине буфера, а по количеству фактически сравниваемых выборок. Не уверен, что это правильно, но это кажется логичным.
3 ответа
Это обычная проблема октав с использованием автокорреляции и аналогичных оценок отставания основного тона (AMDF, ASDF и т. Д.).
Частота, которая на одну октаву (или любое другое целочисленное кратное) ниже, также даст хорошее совпадение по схожести смещенной формы волны (например, синусоида, сдвинутая на 2pi, выглядит так же, как сдвинутая на 4pi, что представляет октаву ниже. В зависимости от шума и насколько близок непрерывный пик к выбранному пику, один или другой оценочный пик может быть немного выше без изменения высоты тона.
Таким образом, для удаления пиков нижней октавы (или другой субмножительной частоты) в корреляции формы сигнала или согласовании лагов необходимо использовать какой-то другой тест (например, выглядит ли пик достаточно близко, как один или несколько других пиков, одна или несколько октав или другая частота умножается вверх), так далее.)
Я не знаю C#, но если крошечный объем кода, который вы предоставили, верен и, как и большинство других c-подобных языков, он вводит огромное количество так называемых интермодульных искажений.
В большинстве c-подобных языков (и в большинстве других известных мне языков, таких как java) вывод чего-то вроде Math.sin() будет находиться в диапазоне [-1,1]. При приведении к int, short или long, это изменится на [-1,0]. По сути, вы изменили свою синусоидальную волну на очень искаженную прямоугольную волну со многими обертонами, что может быть тем, что воспринимают эти библиотеки.
Попробуй это:
data[i] = (short)(32,767 * Math.Sin(2 * Math.PI * i/fs * freq));
Помимо всего, о чем говорили @Bjorn и @Hotpaw, в прошлом я обнаружил проблемы, описанные @hotpaw2.
Было непонятно из вашего кода, если вы вычисляете с разницей в один образец (как я когда-либо видел в уравнениях для вычисления AMDF)!
Я сделал в Java, вы можете найти в Tarsos полный исходный код!
Вот эквивалентные шаги из вашего поста в Java:
int maxShift = audioBuffer.length;
for (int i = 0; i < maxShift; i++) {
frames1 = new double[maxShift - i + 1];
frames2 = new double[maxShift - i + 1];
t = 0;
for (int aux1 = 0; aux1 < maxShift - i; aux1++) {
t = t + 1;
frames1[t] = audioBuffer[aux1];
}
t = 0;
for (int aux2 = i; aux2 < maxShift; aux2++) {
t = t + 1;
frames2[t] = audioBuffer[aux2];
}
int frameLength = frames1.length;
calcSub = new double[frameLength];
for (int u = 0; u < frameLength; u++) {
calcSub[u] = frames1[u] - frames2[u];
}
double summation = 0;
for (int l = 0; l < frameLength; l++) {
summation += Math.abs(calcSub[l]);
}
amd[i] = summation;
}