
Когда передо мной встала задача написания программы анализа передаточных функций звуковых систем в реальном времени, я, как и все, сперва обратился к быстрому преобразованию. Всё было хорошо, но при больших размерах временного окна загрузка CPU становилась неприлично большой и с этим надо было что-то делать. Было решено сделать паузу и изучить преобразование еще раз, а заодно поискать способы решения проблемы. Вернемся к оригинальному преобразованию Жозефа Фурье2:
Посмотрим внимательно, что же тут происходит. Каждое выходное значение в частотной области
Избавляемся от n
Напомню, что изначально была задача анализа звуковых данных в реальном времени. Для этого выбранное временное окно (по сути буфер) размером N заполняется данными с частотой fd соответствующей частоте дискретизации. С периодом Т происходят преобразования входных данных из временного окна в частотное. Если посмотреть на реальные числа, то N варьируется от 214 (16 384) до 216 (65 536) сэмплов (значения остались в наследство от БПФ, где размер окна обязан быть степенью двойки). Время T = 80мс (12,5 раз в секунду), что позволяет видеть достаточно удобно изменения и не слишком нагружать CPU и GPU. Частота дискретизации fd стандартная и составляет 48кГц. Давайте посчитаем, сколько же данных во временном окне изменяется между измерениями. За время Т в буфер поступает
Внимательный читатель в этот момент поднимет руку и скажет: «постойте, а как же коэффициент
Для наглядности, можно записать в табличном виде как будет изменяться буфер:
| t=0 | f(0) | f(1) | f(2) | f(3) | f(4) | f(5) | f(6) | f(7) | f(8) | f(9) |
| t=1 | f(10) | f(1) | f(2) | f(3) | f(4) | f(5) | f(6) | f(7) | f(8) | f(9) |
| t=2 | f(10) | f(11) | f(2) | f(3) | f(4) | f(5) | f(6) | f(7) | f(8) | f(9) |
| t=3 | f(10) | f(11) | f(12) | f(3) | f(4) | f(5) | f(6) | f(7) | f(8) | f(9) |
| t=4 | f(10) | f(11) | f(12) | f(13) | f(4) | f(5) | f(6) | f(7) | f(8) | f(9) |
Можно записать как изменяется преобразование во времени от t1 до t2:
Значение
Для полноты картины осталось лишь обозначить начальное состояние, но тут всё просто:
*- конечно, конечная сложность всего преобразования так и останется
А что, если копнуть глубже. Или избавляемся от второго n
Сразу хочу оговориться, что дальнейшие шаги применимы только в том случае, если вы не планируете для результата производить обратное преобразование (с целью коррекции сигнала или получения импульсной характеристики). Для начала, хочу напомнить, что в результате преобразования мы получаем симметричный массив данных, что сходу позволяет уменьшить количество преобразований в два раза.
А теперь давайте проанализируем результирующий набор данных, учитывая условия задачи. Мы имеем набор комплексных чисел, каждое из которых описывает амплитуду и фазу колебаний на определенной частоте. Частоту можно определить по формуле:
Опытным путем можно выяснить, что вполне достаточно иметь лишь 48 точек на октаву, а чтобы иметь данные чуть более сглаженными и усредненными, предлагаю остановиться на значении 96. В звуковом диапазоне частот от 20Гц до 20кГц нетрудно насчитать всего 10 октав: 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240, 20480, каждую из которых можно разделить на заданное количество поддиапазонов (не забывайте, что разбиение следует производить геометрически, а не арифметически), следовательно, более чем достаточно произвести преобразование только для 960 частот, чтобы получить результат, что в 16...65 раз меньше изначального варианта.
Таким образом, комбинируя оба подхода, получаем константную сложность выполнения алгоритма обновления данных
Мёд в квадрате и ложка дёгтя
Вот теперь можно смело заявить, что от сложности
- проанализировав задачу, заметили, что данные добавляются постепенно, а период полного обновления временного окна значительно выше периода преобразований и перешли к вычислению разницы преобразования Фурье.
- ушли от арифметического шага в частотном окне к ограниченному лишь заданными значениями, что позволяет разительно снизить количество преобразований.
Но, конечно, жизнь была бы действительно сказкой, если бы не одно но. Применение этих двух подходов позволило действительно разгрузить CPU так, что догадаться о том, что он рассчитывает преобразование Фурье и выводит результаты на экран даже при
От второго шага я тоже в итоге отказался и вернулся обратно к БПФ, т.к. выигрыш в данной задаче уже был невелик.
В заключение
Первый подход можно применять, если ваши данные имеют явно выраженный периодический характер и их надо анализировать на протяжении времени с использованием большого временного окна, которое, напоминаю, не обязано быть степенью 2, т.е. любое натуральное число.
Второй подход применим (даже с учетом оконных функций), если в данных анализируются только определенный, небольшой набор частот.
Увы, для меня в данной задаче, это осталось лишь небольшим математическим развлечением, но я надеюсь, что оно вдохновит вас заняться на праздниках исследованием других алгоритмов с точки зрения изменений входных данных во времени :)
Литература
Изображение взято из манги Митио Сибуя «ЗАНИМАТЕЛЬНАЯ МАТЕМАТИКА. АНАЛИЗ ФУРЬЕ»

