18+
реклама
18+
Бургер менюБургер меню

Александр Иванов – Цифровая обработка сигналов на Python. От инженера к разработчику. (страница 7)

18

Ответ на этот вопрос даёт преобразование Фурье. Это математический инструмент, который берёт сигнал — любой сигнал, каким бы сложным он ни был — и раскладывает его на составные части. На элементарные кирпичики, из которых построен звук. И эти кирпичики — синусоиды. Чистые, гладкие, математически идеальные волны. Преобразование Фурье говорит нам: «Вот из каких частот состоит твой звук и насколько громка каждая из них». Это как если бы вы взяли оркестровую запись и волшебным образом разделили её на отдельные инструменты — скрипки, виолончели, флейты, барабаны, — показав, кто и когда играл.

В этой главе мы начнём с самого простого: поймём, что такое синусоида и почему именно она является универсальным строительным блоком для любого звука. Затем мы познакомимся с идеей разложения сигнала на частоты — спектральным анализом. Мы узнаем, что такое комплексные числа, зачем они нужны в обработке звука, и поймём их через простую метафору с вращающейся стрелкой. После этого мы напишем наше первое дискретное преобразование Фурье — DFT — с нуля, на чистом Python. Оно будет работать правильно, но очень медленно. Мы поймём, почему медленно, и это понимание подготовит нас к следующей главе, где мы ускорим алгоритм в сотни раз.

Синусоида: простейший звук во вселенной

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

Почему именно синус? Потому что синус описывает идеальное колебание. Представьте себе грузик на пружине. Если оттянуть его вниз и отпустить, он начнёт колебаться вверх-вниз. График его движения — это синусоида. Представьте себе точку на ободе вращающегося колеса. Если смотреть на неё сбоку, её движение вверх-вниз — тоже синусоида. Представьте себе маятник старинных часов. Его отклонение от центра — синусоида. Колебания пронизывают природу, и все они, от вибрации атомов до орбит планет, описываются синусоидами или их комбинациями.

У синусоиды есть три главных параметра. Первый — частота. Это количество полных колебаний в секунду, измеряется в герцах. Частота определяет, насколько высоким или низким мы слышим звук. Синусоида с частотой 440 Гц — это нота «ля» первой октавы, стандартный камертон. Синусоида с частотой 220 Гц — «ля» малой октавы, вдвое ниже. Синусоида с частотой 880 Гц — «ля» второй октавы, вдвое выше. Зависимость между частотой и воспринимаемой высотой звука логарифмическая: увеличение частоты вдвое всегда воспринимается как повышение на одну октаву, независимо от того, с какой частоты мы начали.

Второй параметр — амплитуда. Это размах колебаний, максимальное отклонение от нуля. Амплитуда определяет громкость звука. В цифровом аудио амплитуда измеряется в условных единицах от -1 до 1, где -1 и 1 — это максимально возможная громкость без искажений. Когда мы делаем звук громче в коде, мы просто умножаем все числа в массиве на коэффициент больше единицы — увеличиваем амплитуду. Когда делаем тише — умножаем на коэффициент меньше единицы.

Третий параметр — фаза. Это начальное положение колебания в момент времени ноль. Две синусоиды одинаковой частоты и амплитуды, но с разными фазами, будут сдвинуты друг относительно друга. Если сдвинуть синусоиду ровно на половину периода, она превратится в косинусоиду — ту же форму волны, но начинающуюся не с нуля, а с максимума. Фаза важна, когда несколько звуков складываются вместе: две синусоиды в фазе усиливают друг друга, в противофазе — гасят.

Давайте создадим синусоиду в коде и посмотрим на неё. Заодно послушаем, как звучит чистый тон.

python

import numpy as np

import soundfile as sf

# Параметры

duration = 2.0 # две секунды

sample_rate = 44100 # CD-качество

freq = 440.0 # нота "ля"

# Создаём массив моментов времени

t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)

# Создаём синусоиду

y = np.sin(2 * np.pi * freq * t)

# Сохраняем в файл

sf.write('sine_440.wav', y, sample_rate)

print(f"Создана синусоида: частота {freq} Гц, длительность {duration} сек")

print(f"Количество отсчётов: {len(y)}")

print(f"Диапазон значений: от {np.min(y):.4f} до {np.max(y):.4f}")

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

Сложение синусоид: как рождается сложный звук

В реальном мире чистые синусоиды встречаются редко. Разве что камертон или генератор сигналов издают что-то близкое к чистому тону. Большинство звуков гораздо сложнее. Но вот ключевая идея: любой сложный звук можно представить как сумму простых синусоид. Эту идею впервые высказал французский математик Жозеф Фурье в начале XIX века, и она произвела революцию в науке.

Сам Фурье занимался проблемой распространения тепла, а не звука. Он показал, что любое периодическое колебание — неважно, насколько сложное, — можно разложить на сумму синусоид с частотами, кратными основной частоте. Основная частота определяет высоту звука, а дополнительные частоты — обертоны — определяют тембр. Благодаря обертонам мы отличаем скрипку от флейты, даже когда они играют одну и ту же ноту. У скрипки один набор обертонов, у флейты — другой.

Давайте проверим эту идею на практике. Создадим несколько синусоид с кратными частотами и сложим их. Посмотрим, что получится.

python

import numpy as np

import soundfile as sf

duration = 2.0

sample_rate = 44100

t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False)

# Основная частота — 220 Гц (нота "ля" малой октавы)

f0 = 220.0

# Создаём основную синусоиду и обертоны

fundamental = np.sin(2 * np.pi * f0 * t) # основная частота

harmonic_2 = 0.5 * np.sin(2 * np.pi * f0 * 2 * t) # второй обертон (вдвое выше)

harmonic_3 = 0.33 * np.sin(2 * np.pi * f0 * 3 * t) # третий обертон (втрое выше)

harmonic_4 = 0.25 * np.sin(2 * np.pi * f0 * 4 * t) # четвёртый обертон

harmonic_5 = 0.2 * np.sin(2 * np.pi * f0 * 5 * t) # пятый обертон

# Складываем все синусоиды

y_complex = fundamental + harmonic_2 + harmonic_3 + harmonic_4 + harmonic_5

# Сохраняем основной тон и сложный звук для сравнения

sf.write('fundamental_220.wav', fundamental, sample_rate)

sf.write('complex_tone_220.wav', y_complex, sample_rate)

print("Созданы файлы: fundamental_220.wav и complex_tone_220.wav")

print("Прослушайте оба. Основной тон звучит ровно и скучно.")

print("Сложный тон — богаче, с характером, похож на простой музыкальный инструмент.")

Прослушайте оба файла. Основной тон — чистый, гладкий, sterile. Сложный тон звучит иначе — он теплее, богаче, более музыкально. Похоже на простой синтезатор или орган. Но обратите внимание: высота звука та же самая. И в том, и в другом случае это нота «ля». Потому что высоту определяет основная частота, а тембр — набор и соотношение обертонов.

Спектр: портрет звука в частотной области

Когда мы смотрим на аудиосигнал как на массив чисел — то есть во временной области, — мы видим, как громкость меняется со временем. Это полезное представление, но оно не говорит нам, из каких частот состоит звук. Глядя на волновую форму сложного тона, вы не сможете сказать, сколько в нём обертонов и какой они амплитуды. Для этого нужно другое представление — частотное.

Спектр — это график, который показывает, какая энергия приходится на каждую частоту. По горизонтальной оси откладывается частота в герцах, по вертикальной — амплитуда или энергия. Спектр чистого тона 440 Гц — это один пик на частоте 440 Гц и больше ничего. Спектр сложного тона, который мы только что создали, — это несколько пиков на частотах 220, 440, 660, 880 и 1100 Гц, убывающих по амплитуде.

Спектр — это не просто красивая картинка. Это мощнейший диагностический инструмент. Глядя на спектр записи, вы можете увидеть фоновый гул на 50 Гц (наводка от электросети), шипение на высоких частотах (шум микрофона), резонансы помещения (усиленные частоты, на которых комната «гудит»). Вы можете понять, почему голос звучит глухо — не хватает высоких частот — или резко — слишком много верхней середины. Спектр показывает то, что ухо слышит, но не может выразить в числах.

Как же получить спектр из временного сигнала? Для этого и нужно преобразование Фурье. Оно переводит сигнал из временной области в частотную.

Дискретное преобразование Фурье: основная идея

Дискретное преобразование Фурье, или DFT, — это алгоритм, который берёт массив из N чисел, представляющих сигнал во времени, и выдаёт массив из N комплексных чисел, представляющих сигнал в частоте. Каждое число в выходном массиве соответствует определённой частоте и говорит нам: «На этой частоте сигнал имеет такую-то амплитуду и такую-то фазу».

Как DFT это делает? Идея проста и гениальна одновременно. Представьте, что вы хотите узнать, есть ли в сигнале частота 100 Гц. Вы берёте синусоиду с частотой 100 Гц, умножаете её на ваш сигнал и суммируете результат. Если в сигнале есть 100 Гц, синусоида будет с ним совпадать, и после умножения и суммирования получится большое число. Если 100 Гц нет — синусоида будет не в такт с сигналом, умножение даст то положительные, то отрицательные значения, и в сумме они погасят друг друга. Повторите эту процедуру для всех частот, которые вас интересуют — и вы получите спектр.