Алгоритм резонанса для обнаружения высоты тона

Я рассматривал различные методы определения высоты тона, воспроизводимого в микрофон.

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

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

Я хотел бы иметь возможность смоделировать это поведение. Но как мне выполнить задание? Может ли кто-нибудь помочь мне продвинуть это вперед?


person P i    schedule 01.11.2010    source источник
comment
Чтобы изолировать входные частоты, вам понадобится разложение Фурье. Вы знаете, как это сделать? Просто не хочу постить что-то очевидное для вас!   -  person Dr. belisarius    schedule 01.11.2010
comment
Ничто не было бы слишком очевидным для меня на данный момент, поэтому, пожалуйста, увольте!   -  person P i    schedule 01.11.2010
comment
stackoverflow.com/questions/3438429/ эта ветка ссылается на статью, содержащую работающий исходный код, прекрасно решающий проблему!   -  person P i    schedule 19.11.2010


Ответы (6)


Взгляните на функцию автокорреляции.

person midtiby    schedule 01.11.2010

Одно интересное решение, которое я нашел, заключается в том, чтобы просто подавать входной сигнал микрофона в алгоритм Karplus Strong.

Таким образом, Karplus Strong имитирует щипковую струну:

  • создание кольцевого буфера (если мы семплируем на частоте 44,1 кГц и хотим имитировать средний A, т.е. A4, который составляет 440 Гц, тогда размер нашего буфера будет ~ 101 элемент)
  • заполнение его статическим от -1 до 1
  • ходьба по кругу, каждый раз устанавливая текущее значение на среднее из двух предыдущих (и излучая текущее значение в динамик)
  • можно добавить константу затухания

Теперь, если мы добавим поток микрофона в этот процесс, то:

x = ( ringBuf[prev] + ring theBuf[prev2] ) * 0.5 * 0.998;
micOut[moPtr++] = x;
ringBuf[curr] = x + micIn[miPtr++];

Он на самом деле очень точно имитирует пение под гитару. если вы получите свой тон, он действительно вопит.

Но у этого подхода есть серьезная проблема: рассмотрим высоту тона, сгенерированную буфером из 100 элементов, и высоту, сгенерированную буфером из 101 элемента. между этими двумя значениями невозможно создать какой-либо шаг. мы ограничены дискретным рабочим набором шагов. в то время как это будет довольно точным для низких нот (A2 будет иметь длину буфера ~ 400), чем выше мы идем, тем больше ошибка: A7 будет иметь длину буфера ~ 12,5. Эта ошибка, вероятно, больше полутона.

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

person P i    schedule 17.11.2010

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

Немного продвинутый алгоритм может сделать что-то вроде этого:

  1. Примерно определить частоту основного тона (можно сделать с помощью ДПФ).
  2. Полосовой сигнал для фильтрации частоты основного тона.
  3. Подсчитайте количество выборок между двумя пиками в отфильтрованных сигналах.

Подсчитав количество сэмплов, вы получите разрешение основного тона, соответствующее частоте сэмплов. Если вам нужно еще более высокое разрешение, чем частота дискретизации, вы можете подогнать функцию, такую ​​как полином, к сэмплам вокруг точки пика. Поскольку вы подавили другие частоты, вы сможете это сделать.

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

person Holstebroe    schedule 03.11.2010
comment
Не могли бы вы объяснить шаг 2? как реализовать полосовой фильтр? - person P i; 04.11.2010

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

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

person CodesInChaos    schedule 03.11.2010

Один из подходов, который я считаю полезным, состоит в том, чтобы сгенерировать две эталонные волны, отстоящие друг от друга на 90 градусов (я называю их «синус» и «косинус»), и взять скалярное произведение входной формы волны с этими эталонными волнами за некоторое довольно короткое время (скажем, 1 /60 секунд) отрезки ввода. Это даст вам несколько зашумленный индикатор того, какая часть вашей входной частоты находится в фазе или не в фазе по отношению к вашим опорным волнам (квадратный корень из суммы квадратов значений, сгенерированных с использованием двух опорных волн, будет быть амплитудой). При небольшом размере окна вы заметите, что вывод довольно зашумлен, но если вы отфильтруете вывод чем-то вроде простого КИХ- или БИХ-фильтра, вы, вероятно, должны получить что-то довольно разумное.

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

Оба измерения амплитуды будут испытывать одинаковую задержку, но первое будет намного более избирательным, чем второе; таким образом, вы можете очень четко сказать, является ли частота «правильной» или немного отстает. Используя этот подход, можно быстро обнаруживать DTMF-тоны, отбрасывая тоны, которые отличаются даже на несколько Гц (тоны с отклонением от основного тона будут улавливаться гораздо сильнее на «свободном» детекторе, чем на узком).

Образец кода:

double sine_phase,sine_freq;
void process_some_waves(double *input, int16 len, 
  double *sine_phase, double sine_freq, 
  double *sine_result, double *cosine_result)
{
  int i;
  double phase, sin_tot,cos_tot;
  phase = *sine_phase;
  sin_tot = cos_tot = 0;
  for (i=0; len > i; i++)
  {
    sin_tot += input[i] * sin(phase);
    cos_tot += input[i] * cos(phase);
    phase += sine_freq;
  }
  *sine_result = sin_tot;
  *cosine_result = cos_tot;
  *sine_phase = phase;
}

/* Takes first element in buffer and 'smears' it through buffer with simple Gaussian resp. */
void simple_fir_filter(double *buff, int buffsize)
{
  int i;

  for (i=buffsize-1; i>=2; i--)
    buff[i] = (buff[i-1] + buff[i-2])/2;
}

#define FILTER_SIZE1 10
#define FILTER_SIZE2 8
#define SECTION_LENGTH 128
#define FREQ whatever

double sine_buff1[FILTER_SIZE1], sine_buff2[FILTER_SIZE2];
double cos_buff1[FILTER_SIZE1], cos_buff2[FILTER_SIZE2];
double combined_buff[FILTER_SIZE2];
double tight_amplitude, loose_amplitude;
double ref_phase;

void handle_some_data(double *input)
{
  /* Put results in first element of filter buffers */
  process_some_waves(input, SECTION_LENGTH, &ref_phase, FREQ, sine_buff1, cos_buff1);

  /* Run first stage of filtering */

  simple_fir_filter(sine_buff1, FILTER_SIZE1); 
  simple_fir_filter(cosine_buff1, FILTER_SIZE1);

  /* Last element of each array will hold results of filtering. */
  /* Now do second stage */

  sine_buff2[0] = sine_buff1[FILTER_SIZE1-1];
  cosine_buff2[0] = cosine_buff1[FILTER_SIZE1-1];
  combined_buff[0] = sine_buff2[0]*sine_buff2[0] + cosine_buff2[0]*cosine_buff2[0];
  simple_fir_filter(sine_buff2, FILTER_SIZE2); 
  simple_fir_filter(cosine_buff2, FILTER_SIZE2); 
  simple_fir_filter(combined_buff, FILTER_SIZE2); 

  tight_amplitude = sine_buff2[FILTER_SIZE2-1]*sine_buff2[FILTER_SIZE2-1] + 
                    cosine_buff2[FILTER_SIZE2-1]*cosine_buff2[FILTER_SIZE2-1];
  loose_amplitude = combined_buff2[FILTER_SIZE2-1];
}

Код здесь использует «двойной» для всех математических операций, кроме индексации массива. На практике почти наверняка было бы быстрее заменить некоторые математические операции целочисленными. Я ожидаю, что на машинах с плавающей запятой лучшим подходом будет сохранение фазы в виде 32-битного целого числа и использование таблицы ~ 4096 «одиночных» синусоидальных значений (чем меньше размер таблицы в ОЗУ, тем лучше когерентность кеша). представление). Я с большим успехом использовал код, очень похожий на приведенный выше, на ЦСП с фиксированной точкой (целочисленный); вычисления синуса и косинуса в process_some_waves выполнялись в отдельных «циклах», при этом каждый «цикл» реализовывался как отдельная инструкция с префиксом «повторить».

person supercat    schedule 17.11.2010

Я читал об анализе Фурье.

По сути, если вы хотите извлечь частоту f из сигнала, вы просто добавляете частоту синусоиды f, умножаете ее на исходный сигнал и интегрируете

Если исходный сигнал не содержал ничего с частотой f, вы должны получить практически нуль. если да, то вы получите меру того, сколько энергии в сигнале на этой частоте.

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

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

Конечно, это не лишено недостатков: если вы удерживаете ноту C1 (на этот раз без педали) и споёте/играете ноту C2, нота C1 будет резонировать с удвоенной основной частотой, производя звук C2.

Точно так же игра G2 заставит его резонировать в три раза больше его основной частоты и т. д.

person P i    schedule 03.11.2010
comment
Основная проблема с преобразованием Фурье заключается в том, что оно происходит на фиксированных блоках. И поддерживает только частоты, которые являются целым кратным базовой частоты. Будет ли это нормально, зависит от того, что именно вам нужно. - person CodesInChaos; 03.11.2010
comment
Я заметил это. Поэтому, если я установлю базовую частоту своего преобразования Фурье на A0, оно обнаружит все A, а также несколько других нот, таких как E2, B4, которые являются гармониками A0. (хотя в современной системе настройки это будет только прибивать октавы ля, остальные будут немного отставать). Какую бы базовую частоту я ни установил, я всегда буду рисковать тем, что конкретная нота лежит прямо на границе между двумя бинами. Что происходит в этом случае? Находится ли половина энергии банкнот в каждой ячейке? Или этот сигнал полностью потерян? - person P i; 04.11.2010