Построение аудио спектра в реальном времени
Всем привет. Мне нужен анализатор спектра в реальном времени как в звуковых плеерах. На языке python. Код для чтение wav файла и fft готов, но не понимаю как строить "столбики звука". Пробовал через matplotlib. Нечего не вышло. Заранее спасибо.
import numpy
import numpy as np
import matplotlib.pyplot as plt
from scipy.io import wavfile
from scipy.fftpack import fft
import time
# Указываем путь до аудио файла
myAudio = "2.wav"
# Чтение файла и получение частоты дискретизации и звукового объект
samplingFreq, mySound = wavfile.read(myAudio)
# Проверяем является ли wave-файл 16-битным или 32-битным. (24 бит не поддерживается)
mySoundDataType = mySound.dtype
# Мы можем преобразовать наш звуковой массив в значения с плавающей запятой в диапазоне от -1 до 1 следующим образом
mySound = mySound / (2. ** 15)
# Проверим точки отсчета и звуковой канал для двух каналов или для моноканала
mySoundShape = mySound.shape
samplePoints = float(mySound.shape[0])
# Если два канала, то выберем только один кана
mySoundOneChannel = mySound[0:]
# График частотного содержания
# Мы можем получить частоту из амплитуды и времени с помощью БПФ, быстрого алгоритма преобразования Фурье
# Получим длину моего массива звуковых объектов
mySoundLength = len(mySound)
# Возьмем преобразование Фурье для данной точки отсчета
fftArray = fft(mySoundOneChannel)
numUniquePoints = int(numpy.ceil((mySoundLength + 1) / 2.0))
fftArray = fftArray[0:numUniquePoints]
# БПФ содержит как величину, так и фазу и задается комплексными числами в формате действительных + мнимых частей (a + ib).
# Принимая абсолютное значение, мы получаем только действительную часть.
fftArray = abs(fftArray)
# Масштабируйте массив fft по длине точек выборки, чтобы величина не зависела отдлина сигнала или его частота дискретизации
fftArray = fftArray / float(mySoundLength)
# БПФ имеет как положительную, так и отрицательную информацию. Площадь возводим в квадрат, чтобы получить только положительные значения
fftArray = fftArray ** 2
if mySoundLength % 2 > 0: # у нас есть нечетное число точек в FFT
fftArray[1:len(fftArray)] = fftArray[1:len(fftArray)] * 2
else: # У нас есть чётное количество точек в БПФ
fftArray[1:len(fftArray) - 1] = fftArray[1:len(fftArray) - 1] * 2
freqArray = numpy.arange(0, numUniquePoints, 1.0) * (samplingFreq / mySoundLength)
plt...........
plt.xlabel('Частота (Khz)')
plt.ylabel('Мощность (dB)')
plt.show()
Ответы (1 шт):
Самая сложная часть задачи, ИМХО, - понять, что именно отрисовывают полосовые анализаторы. Как они агрегируют амплитуды отдельных частот в полосу?
Если с этим получится справиться, то дальше всё просто. Разложить очередную секунду сигнала по частотам, агрегировать, отрисовать столбики.
Я не нашел, как агрегируют частоты в полоски анализаторы, поэтому своим произволом постановил считать, что там рисуют удельную энергию, приходящуюся на диапазон. То есть сумма квадратов амплитуд делённая на ширину диапазона.
def bandpower(samples, sampling_rate, freq_ranges):
"""Функция возвращаем массив, каждый элемент которого есть удельная энергия, приходящаяся на заданный интервал частот.
samples - массив амплитуд сигнала (например, из wav файла).
sampling_rate - частота дискретизации.
freq_ranges - списко частот, ограничивающих интервалы.
"""
result = []
# Преобразование Фурье, оптимизированная функция для вещественных значений
fft_data = np.abs(np.fft.rfft(samples))
fft_data_sq = fft_data*fft_data
# У этой функции нет отрицательных значений частот
fft_freqs = np.fft.rfftfreq(len(samples), d=1.0/sampling_rate)
for f1, f2 in zip(freq_ranges[:-1], freq_ranges[1:]):
mask = np.logical_and(f1 <= fft_freqs, fft_freqs < f2)
# Интегрирование квадрата амплитуды по каждому из диапазонов
# Энергия приводится к ширине диапазона
power = np.trapz(y=fft_data_sq[mask], x=fft_freqs[mask])/(f2-f1)
result.append(power)
return np.array(result)
Я рекомендую использовать для разложения Фурье функцию np.fft.rfft. Эта функция оптимизирована для разложения вещественнозначных волн, и парная ей функция np.fft.rfftfreq не содержит отрицательных частот, поэтому не требуются трюки с отрезанием лишних значений.
Если в функции bandpower не делить на ширину диапазона, то получится перекос в сторону 4-й и 5-й октав. Просто потому, что они самые широкие, по ширине как все предыдущие вместе взятые.
Код для отрисовки полос средствами анимации matplotlib я загрузил в блокнот Jupyter. Github не умеет показывать анимацию поэтому в блокноте есть код, который отрисовывает спектры для первых 10 секундных отрывков Имперского марша в статике.
Наверное, для полного соответствия обычным анализаторам нужно логарифмировать полученные значения и рисовать в децибелах, но ... Для этого нужно выбрать какой-то единичный уровень, а у меня нет никаких идей, как именно звуковики это делают.
UPDATE
Я вспомнил, откуда упёр идею bandpower - с какого-то форума по Matlab. Там как раз спрашивали, как сделать band analyzer. Сейчас ссылку найти не могу. Но мысль про среднюю энергию я почерпнул от них, совершенно точно.