Разработчики всего мира потратили миллионы часов на создание визуализаций музыки в приложениях и плеерах. Наверняка многие из вас помнят анимации в старом-добром 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% всех слушателей Музыки. Живая анимация в тандеме с алгоритмами Моей волны даёт занятный пользовательский опыт: ты не только слушаешь то, что тебе нравится, но и можешь увидеть это. Хотя бы в разрезе частот.

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

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

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

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

Комментарии (32)


  1. 4reddy
    22.07.2022 11:25
    +1

    Тест на синестета :)
    Интересная статья.


  1. papafucker
    22.07.2022 11:32
    +2

    Можете добавить какие-нибудь капли или звёзды на высоких частотах, вверху облака. Что-то резкое, «охлаждающее»


  1. 13werwolf13
    22.07.2022 11:46
    +10

    красивая анимация, мне очень понравилось, секунд на пять.. больше я её не видел, да и наверное никто не видел дольше.. в 90% случаев приложение свёрнуто (вкладка браузера вообще чаще на другом раб столе вместе с прочим ПО не требующем внимания) а управление воспроизведением из апплета(десктоп)/уведомления(ведроид).

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


    1. xxx52rus
      22.07.2022 14:00
      +1

      Человеку нравится делать такие штуки, как по мне он подошол к вопросу с энтузиазмом, в нашем мире есть огромная куча более бесполезных вещей, на которые потратили гораздо больше времени и денег. А некоторые из них не только бесполезные, так еще и вредные. Лично мне нравится, да она почти бесполезная и долго на нее не смотришь, но иногда приятно включить вечерком какую-нибудь красивую музыку, и ее визуализировать, а если есть проектор, так вообще красота. Вобщем способствует поддержанию нужного настроения, а это тоже важно!


    1. Zy2ba
      22.07.2022 14:34
      +2

      Ну у меня веб версия частенько открыта на пол-экрана на третьем мониторе, так что где-то в периферии болтается. Другой вопрос, что сейчас в вебе, насколько могу судить, крутилка на музыку ни как не реагирует.


    1. pavel_kudinov
      22.07.2022 17:46

      у таких штук есть применение в качестве Mind machine. Правда, тут WinAMP Milkdrop визуализатор наверное наиболее продвинутый с практической точки зрения, учитывая сложность шейдеров и количество пресетов от сообщества авторов (там же есть интерактивный редактор для создания новых визуализаций, впечатляет насколько разные по смыслу они могут быть собраны на одном и том же конструкторе)


  1. FlyHighOnTheSky
    22.07.2022 11:46
    +1

    В веб-версии планируется ее анимировать? А то просто статично крутится и отвлекает, может лучше ее на обложку большого размера текущего трека заменить?


    1. andruy94 Автор
      22.07.2022 20:47
      +1

      В ближайших планах такого нет. Мы всегда ждём во фронте таких же героев, какие есть в андроиде :) https://yandex.ru/jobs/vacancies/фронтенд-разработчик-в-музыку-1466


  1. myxo
    22.07.2022 11:52
    +9

    Это все очень здорово, но я хотел бы узнать как вы проводите исследования на тему «оно вообще пользователям нужно?».
    Мой опыт (и небольшой нерепрезентативной группы моих знакомых) показал, что после 1 минуты вау эффекта, хочется просто слушать музыку, без всего этого. Мало того, что отвлекает, так ещё и cpu жрет довольно неплохо. Так что я всегда включаю мою волну и переключаюсь на другую вкладку внутри плеера. Мягко говоря неудобно.

    Может быть у других другой опыт, но мне бы очень хотелось галочку «выключить всю анимацию».


    1. shashurup
      22.07.2022 12:50
      +1

      Вот мне тоже очень интересно. Я буквально позавчера удалял из яндекс музыки 1.5к неудачно загруженных треков - выяснилось что google takeout потерял теги у половины треков. И вот на фоне того, что мне пришлось удалять эти треки по одному (!!!) никак не получается воспринимать анимацию как какую-то полезную, или пусть, хотя бы, прикольную фичу :(
      Кстати, при загрузке около 100 треков тупо зависли и мне пришлось их "руками" догружать, тоже по одному фактически.
      На фоне таких недочетов грустно читать уже вторую статью о том как разработчики занимаются второстепенными с точки зрения пользователя вещами.


      1. SergeyT-hh
        22.07.2022 17:19
        +1

        Периодически в командах бывает так, что все бэкэндеры заняты или в отпусках и полезную фичу не взять в работу, а фронтэндеру в это время заняться нечем) вот он и делает то что "всегда хотелось сделать" потому что прикольно)))


        1. shashurup
          22.07.2022 17:36

          Да, ситуация знакомая :)
          Только вот запрос на фичу удаления группой на их сайте хотелок висит уже 2 года без движения.


  1. kray74
    22.07.2022 14:30
    +5

    Не подскажите, с нормализацией громкости есть какие то подвижки?


  1. Azazellol
    22.07.2022 15:15
    +1

    Статья интересная и мне как юзверю, было интересно узнать, как это работает и пронаблюдать. Вообще считаю, в отличие от граждан выше, что это важный элемент дизайна, который придает изюминку плееру и ты ни с чем другим его не спутаешь, это очень круто и нужно продолжать работу с ним. Кстати, Jules Gaia отлично проходит визуализацию и наблюдать за ней весьма... одухотворяюще! Однако есть и вопрос. Почему берется статичный срез в 100 мс? Почему нельзя использовать динамический срез основаный на... ну допустим такт, брать размерность исходя из частоты перепадов?


    1. andruy94 Автор
      22.07.2022 20:49
      +1

      Очень интересный вопрос, срез в 100 мс берется из-за того, что это оптимальное значение по памяти/процессора к визуальным эффектам. Когда есть статический размер, можно и величины "анимировать" предсказуемо, и backpressure учесть. И на самом деле даже динамический срез не так подходит, как плавающее окно, чтобы не пропускать частоты на стыке "срезов", но тут создается дополнительная нагрузка по памяти и на процессор. Все-таки, так как это делается на мобильном устройстве, ресурсы тут ограничены, особенно батарейкой пользователя. Алгоритм FFT по сложности хоть O(Nlog(N)), но нагрузку создает также ещё копирование и преобразование байтов из AudioProcessor'а.


  1. E_BEREZIN
    22.07.2022 15:40
    +2

    Всегда думал, что на экране обычная гифка или рандомная анимация.

    Зачем для прослушивания музыки смотреть на экран смартфона.

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


    1. abbath0767
      22.07.2022 19:07
      +2

      потому что разработчики не пользуются им по прямому назначению, а заняты визуализацией музыкальных частот

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


      1. E_BEREZIN
        22.07.2022 20:35
        +1

        С одной стороны, программисту важно выполнять разработку в соответствии с ТЗ.

        С другой стороны, известно, что музыку на смартфоне слушают в 99% случаев в наушниках, не глядя на экран, если это не клип.

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

        Наиболее энергоэффективное решение для смартфона - выключить экран.


  1. ldirko
    22.07.2022 16:10
    +2

    все-таки для абстрактной визуализации FFT, имхо, избыточно, достаточно пик детектора. Все равно это все воспринимается на уровне ритма песни, пауз и прочего музыкального рисунка. Сейчас делаю визуализации, правда на eps и адресных диодах, звук беру с микрофона. Дальше перлин с UV маппингами всякими табличными, получается красиво. У меня правда девайс в отличие от приложения стоит на столе все время, такой always on top )) вот так выглядел прототип, сейчас сделал новый и тестовую партию 15 шт планирую, раздам друзьям, наверное https://www.youtube.com/watch?v=g7F77bmlhUU&t=1s видел анимации на новой колонке в обзорах, вот чего-то им не хватает на мой взгляд. А сюда зашел в надежде набраться опыта, хотел посмотреть как рендерится это все дело, а тут фурье и больше ни слова.


    1. andruy94 Автор
      22.07.2022 20:50
      +2

      Хороший вопрос, сначала казалось, что пик детектор хорошее решение, но стоит включить трек полный басов и средних с перепадами, становится понятно почему именно через FFT. В целом нагрузка по большей части заключается в копировании и преобразование байтов PCM, FFT не так сильно загружает процессор. Но думаю в случае решения на "железе", разница между пик детектором по цене/сложности и FFT будет значительной, лично мне прототип кажется весьма интересным, удачи вам с ним.


      1. ldirko
        23.07.2022 11:01

        на самом деле под любое решение можно найти трек, который не будет «играть». Здесь дело не в фурье и прочем, просто музыка очень разнообразна. Поэтому я и зашел посмотреть как вы дальше используете преобразованные данные по частотам в вашем рендере. Еще я не понял в чем разница решения по цене/сложности между peak detection и FFT, это же просто алгоритмы.


  1. gazzz
    22.07.2022 18:36

    Есть же детекторы транзиентов позволяющие определить ритмическую составляющую, даже определить ритм и темп, для анимации по ним.


  1. Qvxb
    22.07.2022 19:11

    Вообще, по идее бассы должны быть видны отчетливее, если например поставить окно 8192 в спектраграмме foobar2k, то в принципе почти все басы видно.


  1. Maksim-Burtsev
    22.07.2022 23:46
    +2

    У меня есть вопрос касательно веб-версии ЯМ - почему анимация "Моей волны" частично перекрывается этими блоками? Так и задумано или это исключительно у меня? (p.s. такой вид при любом масштабе, на скрине дефолтный 100%)

    Анимация действительно выглядит круто, единственное непонятно кто на неё смотрит дольше подбора подходящего трека.


  1. Akr0n
    23.07.2022 05:58
    +4

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


    1. ganzmavag
      23.07.2022 08:48

      Кстати интересно почему. Очень полезная штука же была и работала.

      Тоже присоединюсь к тому же вопросу. С одной стороны интересный пост и красиво получилось. С другой - удивительно, что функции, связанные со звуком, по-прежнему не реализованы никак. Нет эквалайзера, нет нормализации громкости, нет кроссфейда. Но теперь есть анимация. На смартфоне. Который у большинства через 30 секунд гасит экран. И да, кнопки не гасить экран тоже нет.


      1. Akr0n
        23.07.2022 16:18

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


        1. ganzmavag
          23.07.2022 19:39

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


  1. Akr0n
    23.07.2022 06:01

    Как пожелание: можно ли, чтоб эта анимация хотя бы воспроизводилась на ТВ при трансляции через Хромкаст? Это был бы реально полезный юзкейс.


  1. sysor
    23.07.2022 10:50
    +2

    Лучше б сделали возможность ставить треки в очередь произвольно


  1. Torvald3d
    23.07.2022 10:56
    +5

    В приложении огромное количество багов, сам функционал критически беден, а вы красивые анимашки делаете.

    Десктопное приложение вообще не обновлялось миллион лет, либо обновления не приносили ничего нового:

    • Нормализация громкости. Пожалуй, самая главная фича которой не хватает

    • Навигация по трекам потока. В мобильном приложении можно просмотреть историю треков, в версии для ПК - нет. Так же не хватает просмотра следующих треков. Поддержка говорит, что следующие треки невозможно показать, тк они меняются из-за действий пользователя (лайки/дизлайки). Но спотифаю же как-то удалось? Уверен такая возможность есть, например, если пользователь что-то лайкает, то просто изменять список следующих треков.

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

    • Полоса перемотки трека имеет высоту пикселей пять - попасть по ней бывает сложновато. Визуально она может быть сколь угодно тонкой, но сам хитбокс желательно увеличить раза в три. Берите пример с порнхаба =D

    • Трек в очередь. Проигрывание следующего трека без прерывания текущего.

    • Баг с мультимедийными клавишами. Если в приложении стоит галочка автозапуска, то после включения ПК и запуска приложения в нём не работают мультимедийные клавиши клавиатуры. Чтобы они заработали нужно мышкой нажать кнопку плей.

    • Баг - после автозагрузки приложение запускается свёрнутым, хотя выключалось развёрнутым

    • Баг - иногда сразу после запуска приложения, после включения радио не проигрывается следующий трек. Помогает включение другого радио и обратно.

    Обо всех багах сообщал поддержке, сначала она отвечала, потом стала игнорировать.


  1. net_men
    25.07.2022 08:53

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

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

    Но я не скажу, что это бесполезная штуковина: лучше когда она есть, чем если бы её не было :)