Unity является мощным и универсальным движком. Используя его стредства мы можем создать удобную архитектуру, которая поможет нам анализировать звук и использовать полученные данные, например, для создания визуализаций.
В этом уроке речь пойдет о создании архитектуры анализа звука. Начнем мы с небольшого блока теории, затем перейдем к реализации получения данных и к их обработке.
Анализируя аудиофайл, можно получить самые разые данные. Но для начала сконцетрироваться стоит на спектре звука и амплитуде (громкости). Это самые частоиспользуемые типы данных о звуке.
Спектр звука по сути можно представить в виде массива, который содержит данные о громкости звука на разных частотах (наверняка все знакомы с эквалайзером. Он как раз таки наглядно показывает стоение звукового спектра).
Амплитуда же является громкостью аудиофайла в текущий момент. То есть средним арифметическим среди значений спектра.
Теперь, когда мы узнали основные понятия, можно приступить к написанию кода
В Unity есть встроенная функция GetSpectrumData(), которая выдает нам звуковой спектр в данный момент времени. Она принимает три параметра:
samples — массив типа float, в который будут занесены значения звукового спектра. Длина массива обязательно должна быть степенью двойки (64 — минимум, 8192 — максимум)
channel — номер канала, который мы хотим анализировать. Обычно выставляется просто 0
FFTWindow — можно назвать типом спектрального анализа. Разные типы отличаются по сложности своего алгоритма. Более сложные (Blackman) точнее, но могут замедлить работу вашей программы. Более простые (Triangle) менее точны, но работают быстрее
Для начала создадим массив длиной 512, и передадим его в кач-ве аргумента. Также выставим канал на 0, а FFTWindow — FFTWindow.Blackman
Тогда мы сможем создать 512 кубов, выстроить их в линию и заставить их размер измениться соответственно значением спектра.
Обратите внимание на использование переменной maxScale, При умножении значение спектра на неё мы получаем высоту куба. Это связано с тем, что получаемые значения очень малы, и без данной операции эффект будет практически незаметен.
Мы видем, что низкие частоты принимают гараздо большие значение, чем средние и высокие. Это нормально, но с этим не удобно работать. Из 512 значений большинство не пригодны к использованию, т.к. практически не изменяются. Те же, что изменяются принимают слишком большие значение. Удачным решением, на мой взгляд, будет перераспределение значений на более мелкий массив, с их усреднением. (Что я имею ввиду станет понятнее ниже)
Мы напишем вот такую функцию:
Сначала она берет значение первых двух элементов спектра, усредняет их и присваивает элементу нового массива. Затем то же самое происходит с последующими четырьмя, восьмб, шестнадцатью и тд. элементами. В итоге мы получаем новый массив из 8 элементов (я выбрал 8, потому что это этим кол-вом элементов не трудно манипулировать, а так же по техническим причинам, которые объясню далее), которые не имеют такого сильного разброса по значениям.
Мы получаем среднее значение из 2 ^ i+1 элементов массива. Т.К. у нас 512 элементов, в итоге мы используем их все (обратите внимание на поправку на 40-41 линиях. Без неё мы бы использовали лишь 510 элементов). А так как кол-во усредняемых элементов возрастает, значения низких частот становятся не такими отличными от более высоких.
Но мы увидим, что они очень резко дергаются. Это связано с быстрым изменением значений (ведь функция update вызывается каждый кадр!). Мы можем исправить это, создав сглаживающую функцию.
Здесь мы используем еще два массива. Один из них хранит «сглаженные» значения. А другой значения, которые мы будет из него вычитать. Когда значение элемента обычного массива больше, чем у сглаженного, последний принимает значение первого. В ином случае, сглаженный элемент уменьшается. Манипуляции с вычитаемым помогают нам получить мягкий и сглаженный результат.
Последним шагом в обработке полученных значений спектра будет изменение диапозона значений. А именно — от ноля до единицы. С такими значениями будет гараздо легче работать.
Для этого нам нужно поделить значение элемента на максимольно возможное (т.к. дробь максимальна равна нулю). Но т.к. максимальное значение нам не известно, мы будем обновлять его динамически. В результате выйдет следующая функция
Теперь у нас есть очень удобные для работы значения, которые мы можем использовать как координату, размер, цветовое значение и вообще любое числовое значение типа float (можете дать волю воображению и сделать с этим что-то креативное!)
А чтобы получить амплитуду, нам достаточно сложить все 8 значений и разделить их на 8. Т.е. найти среднее арифмитическое. Ничего сложного!
В среду в 19:00 будут проходить прямые трансляции по моему обучающему проекту «Введение в визуализацию звука на Unity» где мы создадим более сложные и интересные визуализации используя объекты, проект в свободном доступе.
В этом уроке речь пойдет о создании архитектуры анализа звука. Начнем мы с небольшого блока теории, затем перейдем к реализации получения данных и к их обработке.
Типы данных:
Анализируя аудиофайл, можно получить самые разые данные. Но для начала сконцетрироваться стоит на спектре звука и амплитуде (громкости). Это самые частоиспользуемые типы данных о звуке.
Спектр звука по сути можно представить в виде массива, который содержит данные о громкости звука на разных частотах (наверняка все знакомы с эквалайзером. Он как раз таки наглядно показывает стоение звукового спектра).
Амплитуда же является громкостью аудиофайла в текущий момент. То есть средним арифметическим среди значений спектра.
Теперь, когда мы узнали основные понятия, можно приступить к написанию кода
Получение звукового спектра. Функция GetSpectrumData()
В Unity есть встроенная функция GetSpectrumData(), которая выдает нам звуковой спектр в данный момент времени. Она принимает три параметра:
AudioSource.GetSpectrumData(float samples, int channel, FFTWindow window)
samples — массив типа float, в который будут занесены значения звукового спектра. Длина массива обязательно должна быть степенью двойки (64 — минимум, 8192 — максимум)
channel — номер канала, который мы хотим анализировать. Обычно выставляется просто 0
FFTWindow — можно назвать типом спектрального анализа. Разные типы отличаются по сложности своего алгоритма. Более сложные (Blackman) точнее, но могут замедлить работу вашей программы. Более простые (Triangle) менее точны, но работают быстрее
Для начала создадим массив длиной 512, и передадим его в кач-ве аргумента. Также выставим канал на 0, а FFTWindow — FFTWindow.Blackman
Тогда мы сможем создать 512 кубов, выстроить их в линию и заставить их размер измениться соответственно значением спектра.
source.GetSpectrumData(spectrum, 0, FFTWindow.Blackman);
Обратите внимание на использование переменной maxScale, При умножении значение спектра на неё мы получаем высоту куба. Это связано с тем, что получаемые значения очень малы, и без данной операции эффект будет практически незаметен.
Мы видем, что низкие частоты принимают гараздо большие значение, чем средние и высокие. Это нормально, но с этим не удобно работать. Из 512 значений большинство не пригодны к использованию, т.к. практически не изменяются. Те же, что изменяются принимают слишком большие значение. Удачным решением, на мой взгляд, будет перераспределение значений на более мелкий массив, с их усреднением. (Что я имею ввиду станет понятнее ниже)
Мы напишем вот такую функцию:
private void makeFreqBands()
{
int count = 0;
for (int i = 1; i <= 8; i++)
{
float avg = 0;
int sampleCount = (int)Mathf.Pow(2, i);
if (i == 8)
sampleCount += 2;
for(int j = 0; j < sampleCount; j++)
{
avg += spectrum;
count++;
}
avg /= count;
bands = avg * 10;
}
}
Сначала она берет значение первых двух элементов спектра, усредняет их и присваивает элементу нового массива. Затем то же самое происходит с последующими четырьмя, восьмб, шестнадцатью и тд. элементами. В итоге мы получаем новый массив из 8 элементов (я выбрал 8, потому что это этим кол-вом элементов не трудно манипулировать, а так же по техническим причинам, которые объясню далее), которые не имеют такого сильного разброса по значениям.
Мы получаем среднее значение из 2 ^ i+1 элементов массива. Т.К. у нас 512 элементов, в итоге мы используем их все (обратите внимание на поправку на 40-41 линиях. Без неё мы бы использовали лишь 510 элементов). А так как кол-во усредняемых элементов возрастает, значения низких частот становятся не такими отличными от более высоких.
Теперь мы можем визуализировать скрипт 8-ю кубами
Но мы увидим, что они очень резко дергаются. Это связано с быстрым изменением значений (ведь функция update вызывается каждый кадр!). Мы можем исправить это, создав сглаживающую функцию.
void ProcBandBuffer()
{
for(int i = 0; i < 8; i++)
{
if(bands > bufferBands)
{
bufferBands = bands;
bufferDecrease = 0.00005f;
}
if (bands < bufferBands)
{
bufferBands -= bufferDecrease;
bufferDecrease = 1.2f;
}
}
}
Здесь мы используем еще два массива. Один из них хранит «сглаженные» значения. А другой значения, которые мы будет из него вычитать. Когда значение элемента обычного массива больше, чем у сглаженного, последний принимает значение первого. В ином случае, сглаженный элемент уменьшается. Манипуляции с вычитаемым помогают нам получить мягкий и сглаженный результат.
Последним шагом в обработке полученных значений спектра будет изменение диапозона значений. А именно — от ноля до единицы. С такими значениями будет гараздо легче работать.
Для этого нам нужно поделить значение элемента на максимольно возможное (т.к. дробь максимальна равна нулю). Но т.к. максимальное значение нам не известно, мы будем обновлять его динамически. В результате выйдет следующая функция
void mapBufferedBands()
{
for (int i = 0; i < 8; i++)
{
if (bufferBands[i] > heighesBufferBand[i])
heighesBufferBand[i] = bufferBands[i];
rangedBufferBand[i] = bufferBands[i] / heighesBufferBand[i];
}
}
Теперь у нас есть очень удобные для работы значения, которые мы можем использовать как координату, размер, цветовое значение и вообще любое числовое значение типа float (можете дать волю воображению и сделать с этим что-то креативное!)
А чтобы получить амплитуду, нам достаточно сложить все 8 значений и разделить их на 8. Т.е. найти среднее арифмитическое. Ничего сложного!
В среду в 19:00 будут проходить прямые трансляции по моему обучающему проекту «Введение в визуализацию звука на Unity» где мы создадим более сложные и интересные визуализации используя объекты, проект в свободном доступе.