Как стать автором
Обновить
827.8
Яндекс
Как мы делаем Яндекс

Как научиться чувствовать треки? Визуализация музыкальных частот в Моей волне

Время на прочтение6 мин
Количество просмотров16K
Разработчики всего мира потратили миллионы часов на создание визуализаций музыки в приложениях и плеерах. Наверняка многие из вас помнят анимации в старом-добром Winamp. Или разные скины JetAudio. Олды вообще скажут: «Погоди-ка, ты забыл про Atari Video Music, всего-то 1976-й был!» — и будут правы.

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



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

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

Лично для меня в этом проекте звёзды сошлись — я в институте активно изучал цифровой анализ сигнала. Нам его объясняли как какого-то сферического коня в вакууме: вроде бы теоретические знания есть, понимание предмета есть, а вот как и где применять всё это в реальной жизни и задачах — вопрос.

Чего мы хотели достичь


Нашей главной задачей было красиво визуализировать музыкальный поток, причём сделать это максимально энергоэффективно для устройства пользователя. Поэтому мы решили, что в основу визуализации Моей волны ляжет преобразование Фурье: тогда выйдет, что бо́льшая часть энергии будет тратиться на чистую математику и перегон PCM-байтов. Обе этих операции не представляют для современных гаджетов какой-то серьёзной нагрузки.

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

Для подобных проектов в Android уже существует своя реализация анализа звука и преобразование Фурье, но библиотека Google требует разрешения на запись звука. Нам кажется, что запрашивать для такого разрешение — перебор. К тому же, этот способ зависит от громкости звука.

Нам это не подходило, поэтому мы решили написать своё решение, которому не нужен будет доступ к микрофону.

Как всё работает


В стандартном приложении музыки для Android есть плеер. В нём мы можем подключиться к аудиопотоку до его выхода в ЦАП и получить аудиоданные в виде PCM-байтов (формат определяет плановую характеристику звука, комплексное число). Так мы можем проанализировать эти байты ещё до выхода сигнала на воспроизведение. За анализ отвечает быстрое преобразование Фурье, раскладывающее звуковую волну на нужные нам частоты, а также их амплитуды.

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

Итак, мы начинаем анализировать конкретную песню. Нам надо накопить 100 миллисекунд звука и запустить преобразование Фурье, после чего мы сможем точно сказать — да, на этом отрезке звучали ударные (скрипки, гитарный запил). Мы можем распознать конкретный инструмент, так как понимаем, какая частота звучит в выбранный нами момент.

К сожалению, метод не идеален, потому что существуют треки с очень большой скоростью. Например, творчество группы Dragonforce, где гитарный запил может попасть между окнами в 100 мс. Волна может начаться на 90 мс и закончиться на 120 мс — так что мы ёё пропустим и не учтём. А в тяжёлом роке множество инструментов, вот и получается, что все частоты заполнены и перепадов почти нет.

class PCMAudioStreamAudioProcessor : BaseAudioProcessor() {

    // properties

    override fun onConfigure(inputAudioFormat: AudioProcessor.AudioFormat): AudioProcessor.AudioFormat {
        // check format, save channels count, smaple rate and other data
        return inputAudioFormat
    }

    override fun queueInput(inputBuffeer: ByteBuffer) {
        // sending pcm bytes to analyze
    }
}

Как я писал выше, мы получаем данные из AudioProcessor ExoPlayer. На самом деле, он может использоваться для изменения звука, но мы просто копируем PCM-байты и отдаём их дальше нетронутыми.

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

Также стоит помнить про работу с форматами, например, PCM_16_BIT линейный и с ним не требуется делать каких-то специфических манипуляций. Грубо говоря, каждый Short, который достали из буфера, — это дискретное значение амплитуды оцифрованного аналогового сигнала в данный момент времени (вот статья с подробным описанием, как работать с чистыми данными в SilenceSkippingAudioProcessor).

Остаётся вопрос — считать среднюю амплитуду по всем каналам или же по одному? Возможно, проще брать канал с максимальным значением и отдавать в функцию быстрого преобразования Фурье?

Про быстрое преобразование Фурье сложно сказать что-то новое, есть множество хороших статей на эту тему, кое-что мы уже описывали на Хабре. Нам не требовалось получить абсолютно точную частоту — 200 мгц, 1 кгц и так далее. Мы рисовали средние и низкие, то есть просто проверяли, входит ли частота в нужный нам диапазон или нет, а затем суммировали амплитуды.


Но мало на первых этапах написать всё это — надо проверить, чтобы всё работало так, как задумано.

Здесь дело обстоит так же, как и с реальной музыкой: допустим, с настройкой гитары. Есть хорошие гитаристы, которые прекрасно играют, но гитару предпочитают настраивать по тюнеру, а не на слух. Кто-то просто так привык, а кто-то именно не может нормально это сделать сам, потому что слух хорош, но недостаточно хорош. Если бы я был гитаристом, я был бы из вторых: на слух частоты различаю не настолько хорошо, чтобы понять, справляется мой код с задачей или нет.

Поэтому я просто нашёл нужные мне сэмплы, выбрал конкретный тон на 1 килогерц, после чего загрузил его в анализатор. На выходе показало, что это на самом деле 1 килогерц, и я понял — всё работает как надо.

Визуализация



(Глубина заполнения цветом — нижние частоты, изгибание волны — средние.)

На примере схемы ниже разберём вкратце, как это работает.



PcmAudioProcessor просто накапливает в течение 100 мс и отдаёт байты PCM-формата.

AudoVisualizationCenter, получая эти байты, преобразовывает их в правильный формат и проводит быстрое преобразование Фурье, а дальше мы в комплексном массиве ищем нужные нам диапазоны и получаем количество вхождений и амплитуды частот.

Затем мы отправляем данные в Flow и на каждый эмит в WaveAnimation посылаем новое значение.

VaweAnimationRenderer на каждый запрос фрейма анимации волны анимирует предыдущее значение AudioData к новому, исходя из прошедшего времени с учётом окна данных в 100 мс. Мы отказались от ValueAnimator’а и сами рассчитываем новые значения анимации на каждый фрейм, так оказалось гораздо эффективнее, потому что ValueAnimator приходится создавать на каждую новую анимированную величину. А так как величина обновляется каждые 100 мс, нам пришлось плодить аниматор каждые 100 мс, пока идут данные.

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

Зато отлично себя показали кантри и рэп. В кантри вообще всё прекрасно, чёткие отдельные ударные, яркие гитары, банджо — очень красиво раскладывается и, как следствие, так же красиво визуализируется. Рэп для меня в этом плане стал откровением — совсем не мой стиль музыки, но визуализируется он очень красиво, просвечивается некая ритмичная точность того, как речитатив на самом деле попадает в ритм и низкие частоты (сама речь человека идет на средних).

Классическая музыка, к сожалению, тоже пока не так заметна — мало привычных нам низких частот, много средних и высоких. Так как высокие мы пока не выводим в визуализацию (ещё не решили, как это сделать в плане дизайна), то остаются только средние. С современной классикой в плане визуализации дела обстоят гораздо лучше.

Размышления о будущем


Я и сам раньше постоянно ходил в любимые треки, но теперь слушаю музыку именно через Мою волну. Сейчас её хотя бы раз в неделю включают 70% всех слушателей Музыки. Живая анимация в тандеме с алгоритмами Моей волны даёт занятный пользовательский опыт: ты не только слушаешь то, что тебе нравится, но и можешь увидеть это. Хотя бы в разрезе частот.

Лично у меня есть пара хотелок, которые я надеюсь реализовать.

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

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

Если у вас тоже есть хотелки и пожелания к работе Моей волны в частности и Музыки в целом — пишите мне в комментарии или и личку, буду рад ответить.
Теги:
Хабы:
Всего голосов 39: ↑37 и ↓2+35
Комментарии48

Публикации

Информация

Сайт
www.ya.ru
Дата регистрации
Дата основания
Численность
свыше 10 000 человек
Местоположение
Россия