Как реализовать интерполяционную линию задержки и весь проходной фильтр с помощью сильного алгоритма karplus? - PullRequest
3 голосов
/ 13 июля 2011

Хорошо, я реализовал сильный алгоритм karplus в C. Это простой алгоритм для имитации звука оторванной струны. Вы начинаете с кольцевого буфера длиной n (n = частота дискретизации / частота, которую вы хотите), проходите через простой двухточечный средний фильтр y [n] = (x [n] + x [n-1]) / 2, выведите его, а затем верните обратно в линию задержки. Промыть и повторить. Это сглаживает шум с течением времени, создавая естественный звук струны.

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

http://quod.lib.umich.edu/cgi/p/pod/dod-idx?c=icmc;idno=bbp2372.1997.068
http://www.jaffe.com/Jaffe-Smith-Extensions-CMJ-1983.pdf
http://www.music.mcgill.ca/~gary/courses/projects/618_2009/NickDonaldson/index.html

Ранее я реализовал интерполированные линии задержки, но только для таблиц сигналов, где буфер сигнала не изменяется. Я просто шаг за шагом по разным ставкам. Но что меня смущает, так это то, что когда речь заходит об алгоритме KS, статьи, похоже, говорят о том, чтобы реально изменить длину задержки, а не просто скорость, которую я прохожу. Алгоритм ks усложняет ситуацию, потому что я должен постоянно передавать значения обратно в линию задержки.

Так, как бы я пошёл на реализацию этого? Вернуть ли интерполированное значение обратно или как? Полностью ли я избавился от двухточечного усредняющего фильтра низких частот?

А как будет работать весь проходной фильтр? Должен ли я заменить 2-точечный усредняющий фильтр на фильтр всех проходов? Как бы я скользил между отдаленными высотами с глиссандо, используя метод линейной интерполяции или метод фильтрации всех проходов?

Ответы [ 2 ]

2 голосов
/ 27 июля 2011

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

Вот блок-схема Karplus Strong.

Wikipedia Karplus Strong block diagram

Дляблок задержки, вам нужно реализовать дробную линию задержки.Это будет включать в себя собственный фильтр нижних частот, но это деталь того, как реализована линия задержки.Для эффекта Karplus Strong также требуется фильтр нижних частот.Характеристики этих фильтров будут разными.Не пытайтесь комбинировать.Кстати, выбранный вами усредняющий фильтр нижних частот имеет плохую частотную характеристику, которая вводит эффект «гребенчатого фильтра».Возможно, вы захотите разработать более сложный FIR или IIR фильтр.

Итак, как мне поступить в реализации этого?Вернуть ли интерполированное значение обратно или как?Полностью ли я избавился от двухточечного усредняющего фильтра нижних частот?

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

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

Я считаю, что наиболее удобно рассматривать чтение и запись как свободные счетчики, которые никогда не оборачиваются или не заканчиваются, потому что тогда

current_delay_length = (write - read) % total_delay_length
current_read_sample = delay_line[read % total_delay_length]

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

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

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

1 голос
/ 21 октября 2012

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

В общем, я делаю это так, как описывает jbarlow. Я использую кольцевой буфер длиной 2 ^ x, где x «достаточно большой», например 12, это будет означать максимальную длину задержки 2 ^ 12 = 4096 выборок, это ~ 12 Гц как самая низкая базовая частота при рендеринге при 48 кГц. Причиной степени двойки является то, что по модулю можно выполнять побитовое И, что намного дешевле, чем фактическое по модулю.

// init
int writepointer = 0;

// loop:
writepointer = (writepointer+1) & 0xFFF;

Точка записи остается простой и начинается, например, с 0 и всегда увеличивается на 1 для каждой выходной выборки.

Указатель чтения начинается с дельты относительно указателя записи, рассчитывается заново каждый раз, когда частота должна измениться.

// init
float delta = samplingrate/frequency;
int readpointer = (writepointer-(int)delta)-1) & 0xFFF;
float frac = delta-(int)delta;
weight_a = frac;
weight_b = (1.0-frac);

// loop:
readpointer = (readpointer + 1) & 0xFFF;

Он также увеличивается на 1, но обычно находится более или менее между двумя целыми позициями. Мы используем округленную вниз позицию для хранения в целочисленном readpointer. Вес между этим и следующими образцами составляет weight_a и _b.

Вариант № 1 : Игнорировать дробную часть и указывать (целочисленный) указатель чтения как есть.

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

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

Вариант № 2 : Линейная интерполяция между образцом readpointer и следующим образцом. Означает, что я на самом деле считываю два последовательных сэмпла из кольцевого буфера и суммирую их, взвешенные на weight_a и weight_b соответственно.

Плюсы: идеальная базовая частота, без артефактов

Минусы: линейная интерполяция вводит фильтр нижних частот, который может быть нежелателен. Еще хуже, низкие частоты варьируются в зависимости от поля. Если дробная часть оказывается близкой к 0 или 1, происходит только небольшое количество фильтрации нижних частот, в то время как дробная часть, составляющая около 0,5, выполняет тяжелую фильтрацию нижних частот. Это делает некоторые ноты инструмента более яркими, чем другие, и никогда не может быть ярче, чем позволяет этот низкочастотный диапазон. (плохо для стальной гитары или клавесина)

Вариант № 3 : Вид дрожания. Я всегда читаю задержку из целочисленной позиции, но отслеживаю ошибку, которую я делаю, это означает, что есть переменная, которая суммирует дробную часть. Когда оно превышает 1, я вычитаю 1.0 из ошибки и считываю задержку из второй позиции.

Плюсы: безупречная базовая частота, неявный низкочастотный диапазон

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

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

Я читал в литературе, что фильтр с проходами должен делать это лучше, но разве линия задержки уже не является проходом? Какая будет разница в реализации?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...