Эта статья о том, как череда не связанных между собой событий привела меня от разработки программы цветомузыки на Arduino к созданию функционального онлайн-плеера, который не только закрыл мои музыкальные потребности, но и заменил мне и моим друзьям ушедшие зарубежные стриминговые площадки.
Всем привет. Меня зовут Владислав. Я работаю в компании NTechLab фронтенд-разработчиком и уже более 10 лет пишу на JavaScript и TypeScript. В своей жизни я часто использую эти навыки для решения различных бытовых задач. Как и в этой истории, например.
Проект, который я хотел бы представить, начался довольно скромно, но со временем перерос в нечто гораздо большее. Все началось на новогодних праздниках, когда я разработал простое приложение на Qt, которое отправляло сигналы на Arduino для управления автомобильными лампами 12 V, мигающими в такт музыке. Работа с музыкой происходила через библиотеку BASS, которая умеет и воспроизводить, и считать значения спектра. Лампочки подключались через транзисторы на ШИМ-выводы. Результат оказался довольно забавным: плавные переходы цветов и мерцание ламп создавали необычную, праздничную атмосферу. Приложение выглядело довольно просто, но было очень эффектным.
Остатки от старой версии
Старый софт
На следующий Новый год были уже лампочки накаливания на 220 V. Они управлялись от той же Arduino с использованием модуля тиристоров. Программное обеспечение сильно не изменилось, а необходимость в блоке питания на 12 V отпала. Лампы обеспечивали значительную яркость и могли осветить небольшой зал. Для дома этого хватало с головой.
Слово за слово, и мы взяли их на день рождения друга, который проходил на репетиционной базе. Вместо старой программы были разработаны наброски нового ПО, соединяющего лампы с MIDI-клавиатурой. Все было написано на JavaScript с Vue.JS в виде веб-приложения, ведь JavaScript умеет работать как с MIDI-устройствами, так и с COM-портами. Таким образом, когда звучала музыка, каждый мог подойти и поиграть светом. Эта тема оказалась очень занимательной, и с тех пор мы стали брать лампочки на каждые посиделки.
Постепенно захотелось большего. Одним из минусов ламп было то, что они сосредоточены в одной точке на большой деревянной рейке, и возникло желание разнести их по углам помещения. Кроме того, цапонлак на лампах не позволял достигать сочных цветов, как это делают светодиоды. В итоге был разработан переходник с USB на DMX512 с использованием той же Arduino и модуля на микросхеме MAX485. Это позволило подключить свет к готовым DMX-светильникам, что значительно расширило возможности управления.
Новая площадка с экранами
У нас появилась новая площадка, где мы могли протестировать обновленное оборудование с использованием DMX512. Всяческий свет, лазерный проектор, дым-машина. В дополнение на этой площадке висело около 20 мониторов, подключенных к одному хабу. Захотелось не просто отображать на них случайные изображения, а создать что-то более интересное и связанное с освещением. Эта идея вдохновила меня на разработку видеоклипов, которые синхронизировались бы со звуком и светом, создавая гармоничное аудиовизуальное пространство для всех участников мероприятий.
youtu.be/lyGvBiTKeXk?si=TEdvui6LIWfolDvs
Самое первое, с чем я столкнулся, — это необходимость отладки. В приложение добавились воспроизведение видео с кошкодевочками, специальный таймлайн для разметки аудиофайла. Можно было как сделать разметку файла вручную, так и разметить пачку файлов через программу Native Instruments Traktor. Далее сетка (BeatGrid) парсилась из «Трактора» и подгружалась в мое приложение.
Для синхронизации музыки и видео я разработал код, который вычислял скорость и фазу видео в зависимости от аудиотрека. Это оказалось относительно просто, но потребовало внимательности в обработке таких моментов, как изменения позиции в треке и паузы. Формула, по которой я делал синхронизацию, была достаточно прямолинейной:
В самом начале видео по мигающим ушам кошкодевочек было сразу видно, что темп видео совпал с темпом аудио и мы молодцы. А кадр из видео так и стал основным логотипом плеера.
После внесения этих улучшений я немного причесал интерфейс и решил выложить как есть на «Пикабу». Почему бы не поделиться с людьми и не позволить им тоже позалипать в этом эксперименте? В своем посте я попросил помощи у кого-то, кто разбирается в дизайне, так как тогда я был больше погружен в код и математику.
Когда я выложил приложение на «Пикабу», отклик был удивительно положительным. Люди писали комменты, ностальгировали по разным старым программам с похожим функционалом, предлагали разные варианты развития. Это вдохновило меня улучшить интерфейс. Дизайнер Борис Маслов помог мне задать вектор для редизайна: он предложил добавить обложки к трекам, папки для загрузки музыки, улучшить вид плейлиста и в целом сделать интерфейс более удобным и красивым. Это был важный шаг, ведь до этого интерфейс выглядел довольно сыро.
Однако у меня не было обложек для треков, БД, учетных записей. Казалось, что, чтобы все это реализовать, нужно практически написать с нуля. Я почти забросил проект, наслаждаясь музыкой в Spotify. Но неожиданно, когда сервис ушел из страны, я столкнулся с проблемой потери плейлистов. Я кое-как переехал на «Яндекс Музыку». Через пару месяцев большая часть моих треков там просто пропала. Это стало толчком для того, чтобы вернуться к проекту и продолжить его развитие. Ведь теперь у меня появилась возможность создать собственный музыкальный сервис, который не зависел бы от внешних факторов.
Я был, мягко говоря, расстроен, когда потерял доступ к своим любимым плейлистам. Однако, вместо того чтобы сдаваться, я решил упорядочить оставшуюся у меня музыку. Какую-то часть добавили друзья. Полученную коллекцию я обернул через NodeJS чексуммами для отслеживания поврежденных файлов и использовал PostgreSQL для хранения метаданных, таких как год выхода, артист, альбом и лейбл. С появлением БД появились учетные записи, а с ними и простановка лайков трекам. Для быстрого поиска я интегрировал Elasticsearch, что оказалось настоящим прорывом. Теперь, когда я вводил запрос, результаты появлялись за доли секунды. Таким образом, проект получил новый импульс.
Мой ЦОД из ноутбука и док-станции с жесткими дисками
Все части были раскиданы по докер-контейнерам и проброшены порты. Получился сервис, который позволял слушать музыку и дома, и в машине, и на работе, и на даче. Баги иногда стреляли, но они не блокировали работу и не вызывали сильных неудобств. В таком виде система стала функционировать, долгое время закрывая мои потребности по прослушиванию музыки.
Старый дизайн плеера
Люди добавляли новую музыку, количество пользователей увеличивалось. В свободное время я продолжал не торопясь исправлять ошибки и дорабатывать интерфейс. Стала появляться даже музыка друзей друзей. Нагрузка на систему росла в геометрической прогрессии. Вскоре я понял, что «Трактор» (Native Instruments Traktor) уже не мог справляться с такими объемами треков. К тому же работать с ним становилось все менее удобно, а качество данных, которые он выдавал, уже не впечатляло. Поэтому я принял решение начать вычислять сетку темпа для трека самому.
Первое, что пришлось улучшить, — это средство для отладки результатов. Таймлайн с сеткой был оптимизирован для быстрого обновления сетки, созданной моим алгоритмом. Прошло еще какое-то время, и мои размышления о создании собственного генератора сетки темпа начали постепенно приносить плоды. Я начал экспериментировать, и стало получаться что-то интересное.
Программа для разметки сетки треков
Однако самое неприятное в этом процессе заключалось в том, что алгоритм для разметки треков был сделан на С++ c kissfft и ffmpeg и мог работать в течение нескольких недель, а затем внезапно упасть с сегфолтом на каком-то конкретном треке. Бывали ситуации, когда я даже не знал, на каком именно треке произошел сбой, а иногда, наоборот, знал и сталкивался с падением в 1 из 10 случаев. Или просто память текла. Или просто проц ревел, что пашет за десятерых, а процесс не шел.
Как говорится, вода камень точит. Постепенно я начал прояснять загадки тех магических багов, которые вызывали сбои в разметке треков.
Самым интересным, на мой взгляд, был цикл на флоатах, в котором прибавлялся настолько маленький флоат, что он его просто игнорировал и зацикливался. Поэтому всегда используйте циклы с целыми числами.
Процесс исправления ошибок был небыстрым: я правил всего пару строчек кода, затем запускал алгоритм и уходил на неделю или даже месяц, оставляя его работать в фоновом режиме. Это требовало терпения, но каждый новый успех вдохновлял меня продолжать работу над проектом.
Сам веб-интерфейс моего музыкального плеера также постепенно становился все более стабильным. Я добавлял различные полезные функции и улучшения в пользовательский опыт (UX). Плеер активно использовал не только я, но и мои друзья, и это придавало дополнительную мотивацию для дальнейшей работы.
Я использовал алгоритм квантизации цветов обложки трека, чтобы извлекать основные цвета и перекрашивать интерфейс плеера в соответствии с этими цветами, что добавляло визуальную привлекательность.
Цветовая схема использует цветовую палитру из обложки трека
Кроме того, Elasticsearch значительно ускорил процесс поиска и добавления пачки треков. Я мог легко импортировать музыку из «ВКонтакте» и «Яндекса» по названиям. Достаточно было просто скопировать список треков из любого источника и выполнить поиск, чтобы добавить их в новый плейлист. Это значительно облегчило взаимодействие с плеером и сделало процесс импорта треков максимально простым и быстрым.
Поиск альбомов и треков
В выходных данных разметчика теперь были аккорды, частоты трека на таймлайне и изображение спектра. Это стало настоящим помощником, когда я хотел подыграть на пианино под текущий играющий трек, поскольку теперь я мог сразу видеть аккорд, который звучит в данный момент.
Отображение спектрограммы, сетки и аккордов.
Если затронуть мобильное приложение, я сразу закладывал максимальную адаптивность в верстке, так как делать отдельное приложение под все устройства не было ни сил, ни времени, ни возможности, ни знаний.
Вместе с одной знакомой, которая учится на дизайнера, мы посмотрели разные готовые приложения и сделали наброски мобильного варианта. За что ей тоже большая благодарность, если она это читает. Пришлось немного переделать десктопную версию, чтобы она могла перетекать в мобильную. На телефоне в Chrome можно было установить сайт как PWA-приложение, тогда плеер устанавливается как приложение с отдельным ярлыком. На десктопе в адресной строке Chrome есть стрелочка с подобным функционалом.
Я бы поспорил с тем, что если бы вначале была мобильная версия, то десктопную сделать проще. Может быть, и проще, если задача — просто отобразить хоть как-то, но если хочется сделать красиво и гармонично, придется продумать этот переход.
Такая мобильная версия получилась.
Интересной особенностью стал вебсокет-чат, который позволял управлять плеером на одном устройстве из другого. Это делало совместное прослушивание музыки очень удобным. Или можно было лежа на диване подключиться к плееру на компе и сменить трек. Я внедрил несколько аудиокомпонентов на странице, что обеспечивало плавный переход от одного трека к другому, создавая непрерывный поток музыки.
Дальше мне захотелось большего, и я решил использовать все накопленные побочные данные аудиоразметчика для обучения нейросети. При создании сетки темпа я в ходе экспериментов собрал множество побочных характеристик трека — ритм, частоты, ноты и прочие параметры. В итоге получилось около 400 различных числовых характеристик, которые позднее переросли в 4000. Возникла идея использовать эти данные для нейросети.
Я использовал PyTorch и четыре полносвязанных слоя. Первый слой принимал числовые характеристики трека. Второй слой был скрытым, чтобы сделать модель более сильной. Третий слой был векторным и предоставлял непосредственно векторы для векторизации треков. Ну и последний слой определял плейлист, которому принадлежит трек. Таким образом, нейросеть училась размещать в векторном пространстве треки тем ближе, чем ближе в плейлисте они расположены.
Я пробовал разные подходы к нормализации данных. Сложно выделить, какой лучше, а какой хуже. Очень часто в результате при выборе топ-10 треков несколько могли попасть прямо совсем мимо, а несколько хоть и звучали по-другому, но передавали тот же вайб.
Обучение нейросети
В процессе работы я столкнулся с некоторыми проблемами, которые мешали мне добиться желаемого результата. К счастью, мне на помощь пришел Михаил Антонович Горохов, который делал видео на YouTube по нейросетям. Его объяснения были очень доступными и понятными, и я был благодарен за помощь, потому что он показывал все без вырезок и монтажа.
Еще возникла проблема, когда объем данных для обучения перестал помещаться в оперативку, как бы я ни старался, пришлось написать свой датасет на базе SQLite. Теперь данные не выдавались из памяти, а считывались с диска. За счет памяти SQLite поддерживала кеш данных, что, как мне кажется, снижало нагрузку на диск. Обучение проходило намного медленнее, но проходило.
Одним из больших плюсов стало то, что я использовал Elasticsearch, который изначально поддерживал поиск по векторам. Это избавило меня от необходимости изобретать велосипед — достаточно было просто добавить векторы эмбеддингов из нейросети в поисковый индекс. Я выбрал 64-мерное пространство для представления векторов. В качестве функции сравнения я использовал CosineSimilarity. Эта функция сравнивает векторы как угол между ними в многомерном пространстве. Также хочу попробовать l1_norm и l2_norm, которые считают среднее и среднеквадратичное расстояние между координатами векторов.
В итоге получившаяся система оказалась довольно прикольной: когда я начинал искать похожие треки, она генерировала бесконечный плейлист с композициями того же жанра. Например, можно было легко запустить бесконечное радио в жанре chillhop (где девочка на YouTube учит уроки) или, наоборот, что-то танцевальное.
пример 1:
исходный: SwuM, Chief. — Show Me How
первый найденный: G Mills, HM Surf — Mmmm
пример 2:
исходный: Edward Maya & Vika Jigulina — Stereo Love
первый найденный: Violet Light — Love Story (Original Version)
пример 3:
исходный: Simple Plan, Julian Emery — Astronaut
первый найденный не ремикс оригинала: Rush Of Fools — Undo
далее: Josh Ross — Ain't Doin' Jack
Интересно, что нейросеть сама по себе не знает, что такое жанр и каковы его особенности, но тем не менее создает музыкальные последовательности, которые приятно слушать. Это не окончательная версия алгоритма для векторизации и рекомендаций треков. Разметка треков еще не завершена, что позволит увеличить количество треков для обучения и, соответственно, улучшить качество результатов. Современные браузеры поддерживают OpenGL, что позволяет визуализировать треки в 2D и 3D пространстве на основе векторов. Это открывает новые горизонты для музыкального эксперимента и поиска звучания!
В последний год я не занимался разработкой новых сложных функций, а в основном сосредоточился на исправлении мелких багов, на которые натыкался в процессе использования плеера. Теперь я чувствую, что проект уже достаточно зрел, чтобы смело показать его людям на «Хабре».
Что касается приложения на Android, то оно почти готово. Я использовал WebView для интерфейса и создал отдельную прослойку, которая воспроизводит аудио нативно через Media3 (в этом сильно помог @AlexLong4). Это значит, что интерфейс для формирования плейлистов остается полностью готовым, а воспроизведение производится с использованием нативных возможностей Android. Однако мне пока не удалось заставить Media3 кэшировать треки в папку на телефоне. Если кто-то сможет помочь решить эту задачу, это будет замечательно, и мы сможем завершить разработку приложения. Я просто не являюсь Android-разработчиком, поэтому здесь мне нужна поддержка.
В будущем мне бы хотелось создать распространяемый бэкенд, чтобы каждый мог его запустить на своем компе или в Docker на сервере. Бэкенд мог бы хостить локальную музыку или использовать торрент-клиент с Web API (например, transmission-daemon отлично живет в докере) для управления загрузками, следить за статусом загрузки и отдавать треки по HTTP. Плюс было бы удобно поддерживать раздачи тех альбомов, которые находятся в «избранном» плеера и уже лежат на диске. Это позволило бы развивать плеер как статическое веб-приложение, которое только управляет загрузками, раздачей, воспроизведением, рекомендациями и лайками. Можно было бы сделать базу с DHT-хешами, проиндексировать музыку, которая уже находится в торрент-сети. Это все откроет новые возможности для пользователей и сделает приложение еще более функциональным!
Мой проект стал чем-то большим, чем просто плеер. Он обрел массу интересных фич, таких как интеграция с нейросетями, синхронизация видео и аудио, а также возможность искать похожие треки и формировать уникальные плейлисты. На данный момент приложение стабильно работает, и я горжусь его прогрессом. В будущем я надеюсь продолжить развивать его, добавляя новые возможности и улучшая производительность.
Если кто-то захочет помочь с доработкой Android-версии или с другими аспектами проекта, буду рад!
Сам плеер тут:
someradio.github.io
web.valse.me
Обсуждение и доки тут:
t.me/valse_me
vk.com/valse_me
Не законченное приложение под Android тут:
github.com/vartemkin/valse_app
Дисклеймер: Все упомянутые в настоящей статье объекты интеллектуальной собственности принадлежат соответствующим правообладателям и используются исключительно в информационных, научных, учебных или культурных целях в качестве иллюстраций тезисов автора статьи в объеме, оправданном поставленной целью.
Всем привет. Меня зовут Владислав. Я работаю в компании NTechLab фронтенд-разработчиком и уже более 10 лет пишу на JavaScript и TypeScript. В своей жизни я часто использую эти навыки для решения различных бытовых задач. Как и в этой истории, например.
История моего проекта
Проект, который я хотел бы представить, начался довольно скромно, но со временем перерос в нечто гораздо большее. Все началось на новогодних праздниках, когда я разработал простое приложение на Qt, которое отправляло сигналы на Arduino для управления автомобильными лампами 12 V, мигающими в такт музыке. Работа с музыкой происходила через библиотеку BASS, которая умеет и воспроизводить, и считать значения спектра. Лампочки подключались через транзисторы на ШИМ-выводы. Результат оказался довольно забавным: плавные переходы цветов и мерцание ламп создавали необычную, праздничную атмосферу. Приложение выглядело довольно просто, но было очень эффектным.
Остатки от старой версии
Старый софт
От ламп накаливания до взаимодействия с MIDI
На следующий Новый год были уже лампочки накаливания на 220 V. Они управлялись от той же Arduino с использованием модуля тиристоров. Программное обеспечение сильно не изменилось, а необходимость в блоке питания на 12 V отпала. Лампы обеспечивали значительную яркость и могли осветить небольшой зал. Для дома этого хватало с головой.
Слово за слово, и мы взяли их на день рождения друга, который проходил на репетиционной базе. Вместо старой программы были разработаны наброски нового ПО, соединяющего лампы с MIDI-клавиатурой. Все было написано на JavaScript с Vue.JS в виде веб-приложения, ведь JavaScript умеет работать как с MIDI-устройствами, так и с COM-портами. Таким образом, когда звучала музыка, каждый мог подойти и поиграть светом. Эта тема оказалась очень занимательной, и с тех пор мы стали брать лампочки на каждые посиделки.
Новые горизонты: от ламп к DMX-системе и визуализации
Постепенно захотелось большего. Одним из минусов ламп было то, что они сосредоточены в одной точке на большой деревянной рейке, и возникло желание разнести их по углам помещения. Кроме того, цапонлак на лампах не позволял достигать сочных цветов, как это делают светодиоды. В итоге был разработан переходник с USB на DMX512 с использованием той же Arduino и модуля на микросхеме MAX485. Это позволило подключить свет к готовым DMX-светильникам, что значительно расширило возможности управления.
Новая площадка с экранами
У нас появилась новая площадка, где мы могли протестировать обновленное оборудование с использованием DMX512. Всяческий свет, лазерный проектор, дым-машина. В дополнение на этой площадке висело около 20 мониторов, подключенных к одному хабу. Захотелось не просто отображать на них случайные изображения, а создать что-то более интересное и связанное с освещением. Эта идея вдохновила меня на разработку видеоклипов, которые синхронизировались бы со звуком и светом, создавая гармоничное аудиовизуальное пространство для всех участников мероприятий.
Синхронизация музыки и видео
youtu.be/lyGvBiTKeXk?si=TEdvui6LIWfolDvs
Самое первое, с чем я столкнулся, — это необходимость отладки. В приложение добавились воспроизведение видео с кошкодевочками, специальный таймлайн для разметки аудиофайла. Можно было как сделать разметку файла вручную, так и разметить пачку файлов через программу Native Instruments Traktor. Далее сетка (BeatGrid) парсилась из «Трактора» и подгружалась в мое приложение.
Для синхронизации музыки и видео я разработал код, который вычислял скорость и фазу видео в зависимости от аудиотрека. Это оказалось относительно просто, но потребовало внимательности в обработке таких моментов, как изменения позиции в треке и паузы. Формула, по которой я делал синхронизацию, была достаточно прямолинейной:
- скорость видео = BPM видео / BPM аудиотрека
- фаза видео = фаза аудиотрека
const audioPosition = audioPlayerState.position;
const audioBeat = (audioPosition - audioOffset) / audioPeriod;
const audioPhase = audioBeat % 1;
const videoPosition = this.video.currentTime;
const videoBeat = (videoPosition - videoOffset) / videoPeriod;
const newTimes = [
videoOffset + videoPeriod * (Math.floor(videoBeat - 1) + audioPhase),
videoOffset + videoPeriod * (Math.floor(videoBeat) + audioPhase),
videoOffset + videoPeriod * (Math.ceil(videoBeat) + audioPhase)
]
.filter((item: number) => item >= 0)
.sort((a, b) => {
return Math.abs(a - videoPosition) - Math.abs(b - videoPosition);
});
if (this.video) {
this.video.playbackRate = videoRate;
this.video.currentTime = newTimes[0];
}
В самом начале видео по мигающим ушам кошкодевочек было сразу видно, что темп видео совпал с темпом аудио и мы молодцы. А кадр из видео так и стал основным логотипом плеера.
После внесения этих улучшений я немного причесал интерфейс и решил выложить как есть на «Пикабу». Почему бы не поделиться с людьми и не позволить им тоже позалипать в этом эксперименте? В своем посте я попросил помощи у кого-то, кто разбирается в дизайне, так как тогда я был больше погружен в код и математику.
Когда я выложил приложение на «Пикабу», отклик был удивительно положительным. Люди писали комменты, ностальгировали по разным старым программам с похожим функционалом, предлагали разные варианты развития. Это вдохновило меня улучшить интерфейс. Дизайнер Борис Маслов помог мне задать вектор для редизайна: он предложил добавить обложки к трекам, папки для загрузки музыки, улучшить вид плейлиста и в целом сделать интерфейс более удобным и красивым. Это был важный шаг, ведь до этого интерфейс выглядел довольно сыро.
Однако у меня не было обложек для треков, БД, учетных записей. Казалось, что, чтобы все это реализовать, нужно практически написать с нуля. Я почти забросил проект, наслаждаясь музыкой в Spotify. Но неожиданно, когда сервис ушел из страны, я столкнулся с проблемой потери плейлистов. Я кое-как переехал на «Яндекс Музыку». Через пару месяцев большая часть моих треков там просто пропала. Это стало толчком для того, чтобы вернуться к проекту и продолжить его развитие. Ведь теперь у меня появилась возможность создать собственный музыкальный сервис, который не зависел бы от внешних факторов.
Новая жизнь проекта: восстановление и оптимизация
Я был, мягко говоря, расстроен, когда потерял доступ к своим любимым плейлистам. Однако, вместо того чтобы сдаваться, я решил упорядочить оставшуюся у меня музыку. Какую-то часть добавили друзья. Полученную коллекцию я обернул через NodeJS чексуммами для отслеживания поврежденных файлов и использовал PostgreSQL для хранения метаданных, таких как год выхода, артист, альбом и лейбл. С появлением БД появились учетные записи, а с ними и простановка лайков трекам. Для быстрого поиска я интегрировал Elasticsearch, что оказалось настоящим прорывом. Теперь, когда я вводил запрос, результаты появлялись за доли секунды. Таким образом, проект получил новый импульс.
Мой ЦОД из ноутбука и док-станции с жесткими дисками
Все части были раскиданы по докер-контейнерам и проброшены порты. Получился сервис, который позволял слушать музыку и дома, и в машине, и на работе, и на даче. Баги иногда стреляли, но они не блокировали работу и не вызывали сильных неудобств. В таком виде система стала функционировать, долгое время закрывая мои потребности по прослушиванию музыки.
Старый дизайн плеера
Люди добавляли новую музыку, количество пользователей увеличивалось. В свободное время я продолжал не торопясь исправлять ошибки и дорабатывать интерфейс. Стала появляться даже музыка друзей друзей. Нагрузка на систему росла в геометрической прогрессии. Вскоре я понял, что «Трактор» (Native Instruments Traktor) уже не мог справляться с такими объемами треков. К тому же работать с ним становилось все менее удобно, а качество данных, которые он выдавал, уже не впечатляло. Поэтому я принял решение начать вычислять сетку темпа для трека самому.
Разработка собственного генератора сетки темпа
Первое, что пришлось улучшить, — это средство для отладки результатов. Таймлайн с сеткой был оптимизирован для быстрого обновления сетки, созданной моим алгоритмом. Прошло еще какое-то время, и мои размышления о создании собственного генератора сетки темпа начали постепенно приносить плоды. Я начал экспериментировать, и стало получаться что-то интересное.
Программа для разметки сетки треков
Однако самое неприятное в этом процессе заключалось в том, что алгоритм для разметки треков был сделан на С++ c kissfft и ffmpeg и мог работать в течение нескольких недель, а затем внезапно упасть с сегфолтом на каком-то конкретном треке. Бывали ситуации, когда я даже не знал, на каком именно треке произошел сбой, а иногда, наоборот, знал и сталкивался с падением в 1 из 10 случаев. Или просто память текла. Или просто проц ревел, что пашет за десятерых, а процесс не шел.
Как говорится, вода камень точит. Постепенно я начал прояснять загадки тех магических багов, которые вызывали сбои в разметке треков.
Самым интересным, на мой взгляд, был цикл на флоатах, в котором прибавлялся настолько маленький флоат, что он его просто игнорировал и зацикливался. Поэтому всегда используйте циклы с целыми числами.
Процесс исправления ошибок был небыстрым: я правил всего пару строчек кода, затем запускал алгоритм и уходил на неделю или даже месяц, оставляя его работать в фоновом режиме. Это требовало терпения, но каждый новый успех вдохновлял меня продолжать работу над проектом.
Эволюция веб-интерфейса плеера
Сам веб-интерфейс моего музыкального плеера также постепенно становился все более стабильным. Я добавлял различные полезные функции и улучшения в пользовательский опыт (UX). Плеер активно использовал не только я, но и мои друзья, и это придавало дополнительную мотивацию для дальнейшей работы.
Я использовал алгоритм квантизации цветов обложки трека, чтобы извлекать основные цвета и перекрашивать интерфейс плеера в соответствии с этими цветами, что добавляло визуальную привлекательность.
Цветовая схема использует цветовую палитру из обложки трека
Кроме того, Elasticsearch значительно ускорил процесс поиска и добавления пачки треков. Я мог легко импортировать музыку из «ВКонтакте» и «Яндекса» по названиям. Достаточно было просто скопировать список треков из любого источника и выполнить поиск, чтобы добавить их в новый плейлист. Это значительно облегчило взаимодействие с плеером и сделало процесс импорта треков максимально простым и быстрым.
Поиск альбомов и треков
В выходных данных разметчика теперь были аккорды, частоты трека на таймлайне и изображение спектра. Это стало настоящим помощником, когда я хотел подыграть на пианино под текущий играющий трек, поскольку теперь я мог сразу видеть аккорд, который звучит в данный момент.
Отображение спектрограммы, сетки и аккордов.
Если затронуть мобильное приложение, я сразу закладывал максимальную адаптивность в верстке, так как делать отдельное приложение под все устройства не было ни сил, ни времени, ни возможности, ни знаний.
Вместе с одной знакомой, которая учится на дизайнера, мы посмотрели разные готовые приложения и сделали наброски мобильного варианта. За что ей тоже большая благодарность, если она это читает. Пришлось немного переделать десктопную версию, чтобы она могла перетекать в мобильную. На телефоне в Chrome можно было установить сайт как PWA-приложение, тогда плеер устанавливается как приложение с отдельным ярлыком. На десктопе в адресной строке Chrome есть стрелочка с подобным функционалом.
Я бы поспорил с тем, что если бы вначале была мобильная версия, то десктопную сделать проще. Может быть, и проще, если задача — просто отобразить хоть как-то, но если хочется сделать красиво и гармонично, придется продумать этот переход.
Такая мобильная версия получилась.
Интересной особенностью стал вебсокет-чат, который позволял управлять плеером на одном устройстве из другого. Это делало совместное прослушивание музыки очень удобным. Или можно было лежа на диване подключиться к плееру на компе и сменить трек. Я внедрил несколько аудиокомпонентов на странице, что обеспечивало плавный переход от одного трека к другому, создавая непрерывный поток музыки.
Обучение нейросети для генерации плейлистов
Дальше мне захотелось большего, и я решил использовать все накопленные побочные данные аудиоразметчика для обучения нейросети. При создании сетки темпа я в ходе экспериментов собрал множество побочных характеристик трека — ритм, частоты, ноты и прочие параметры. В итоге получилось около 400 различных числовых характеристик, которые позднее переросли в 4000. Возникла идея использовать эти данные для нейросети.
Я использовал PyTorch и четыре полносвязанных слоя. Первый слой принимал числовые характеристики трека. Второй слой был скрытым, чтобы сделать модель более сильной. Третий слой был векторным и предоставлял непосредственно векторы для векторизации треков. Ну и последний слой определял плейлист, которому принадлежит трек. Таким образом, нейросеть училась размещать в векторном пространстве треки тем ближе, чем ближе в плейлисте они расположены.
Я пробовал разные подходы к нормализации данных. Сложно выделить, какой лучше, а какой хуже. Очень часто в результате при выборе топ-10 треков несколько могли попасть прямо совсем мимо, а несколько хоть и звучали по-другому, но передавали тот же вайб.
Обучение нейросети
В процессе работы я столкнулся с некоторыми проблемами, которые мешали мне добиться желаемого результата. К счастью, мне на помощь пришел Михаил Антонович Горохов, который делал видео на YouTube по нейросетям. Его объяснения были очень доступными и понятными, и я был благодарен за помощь, потому что он показывал все без вырезок и монтажа.
Еще возникла проблема, когда объем данных для обучения перестал помещаться в оперативку, как бы я ни старался, пришлось написать свой датасет на базе SQLite. Теперь данные не выдавались из памяти, а считывались с диска. За счет памяти SQLite поддерживала кеш данных, что, как мне кажется, снижало нагрузку на диск. Обучение проходило намного медленнее, но проходило.
class MySqliteDataset(Dataset):
def __init__(self, file_path:str):
super().__init__()
self.sqlite_conn = sqlite3.connect(file_path)
self.sqlite_cursor = self.sqlite_conn.cursor()
self.sqlite_cursor.execute('PRAGMA page_size = 4096')
self.sqlite_cursor.execute('PRAGMA cache_size = 7388608')
def __len__(self):
self.sqlite_cursor.execute("SELECT MAX(ROWID) FROM train_data")
item = self.sqlite_cursor.fetchone()
return item[0]
def __getitem__(self, idx):
self.sqlite_cursor.execute("SELECT json_data, category_index FROM train_data WHERE ROWID = ?", [idx + 1])
item = self.sqlite_cursor.fetchone()
x = torch.from_numpy(np.asarray(json.loads(item[0])))
y = torch.tensor(item[1])
return (x, y)
Одним из больших плюсов стало то, что я использовал Elasticsearch, который изначально поддерживал поиск по векторам. Это избавило меня от необходимости изобретать велосипед — достаточно было просто добавить векторы эмбеддингов из нейросети в поисковый индекс. Я выбрал 64-мерное пространство для представления векторов. В качестве функции сравнения я использовал CosineSimilarity. Эта функция сравнивает векторы как угол между ними в многомерном пространстве. Также хочу попробовать l1_norm и l2_norm, которые считают среднее и среднеквадратичное расстояние между координатами векторов.
Результаты работы нейросети
В итоге получившаяся система оказалась довольно прикольной: когда я начинал искать похожие треки, она генерировала бесконечный плейлист с композициями того же жанра. Например, можно было легко запустить бесконечное радио в жанре chillhop (где девочка на YouTube учит уроки) или, наоборот, что-то танцевальное.
пример 1:
исходный: SwuM, Chief. — Show Me How
первый найденный: G Mills, HM Surf — Mmmm
пример 2:
исходный: Edward Maya & Vika Jigulina — Stereo Love
первый найденный: Violet Light — Love Story (Original Version)
пример 3:
исходный: Simple Plan, Julian Emery — Astronaut
первый найденный не ремикс оригинала: Rush Of Fools — Undo
далее: Josh Ross — Ain't Doin' Jack
Интересно, что нейросеть сама по себе не знает, что такое жанр и каковы его особенности, но тем не менее создает музыкальные последовательности, которые приятно слушать. Это не окончательная версия алгоритма для векторизации и рекомендаций треков. Разметка треков еще не завершена, что позволит увеличить количество треков для обучения и, соответственно, улучшить качество результатов. Современные браузеры поддерживают OpenGL, что позволяет визуализировать треки в 2D и 3D пространстве на основе векторов. Это открывает новые горизонты для музыкального эксперимента и поиска звучания!
Доработка проекта и планы на будущее
В последний год я не занимался разработкой новых сложных функций, а в основном сосредоточился на исправлении мелких багов, на которые натыкался в процессе использования плеера. Теперь я чувствую, что проект уже достаточно зрел, чтобы смело показать его людям на «Хабре».
Что касается приложения на Android, то оно почти готово. Я использовал WebView для интерфейса и создал отдельную прослойку, которая воспроизводит аудио нативно через Media3 (в этом сильно помог @AlexLong4). Это значит, что интерфейс для формирования плейлистов остается полностью готовым, а воспроизведение производится с использованием нативных возможностей Android. Однако мне пока не удалось заставить Media3 кэшировать треки в папку на телефоне. Если кто-то сможет помочь решить эту задачу, это будет замечательно, и мы сможем завершить разработку приложения. Я просто не являюсь Android-разработчиком, поэтому здесь мне нужна поддержка.
В будущем мне бы хотелось создать распространяемый бэкенд, чтобы каждый мог его запустить на своем компе или в Docker на сервере. Бэкенд мог бы хостить локальную музыку или использовать торрент-клиент с Web API (например, transmission-daemon отлично живет в докере) для управления загрузками, следить за статусом загрузки и отдавать треки по HTTP. Плюс было бы удобно поддерживать раздачи тех альбомов, которые находятся в «избранном» плеера и уже лежат на диске. Это позволило бы развивать плеер как статическое веб-приложение, которое только управляет загрузками, раздачей, воспроизведением, рекомендациями и лайками. Можно было бы сделать базу с DHT-хешами, проиндексировать музыку, которая уже находится в торрент-сети. Это все откроет новые возможности для пользователей и сделает приложение еще более функциональным!
Заключение
Мой проект стал чем-то большим, чем просто плеер. Он обрел массу интересных фич, таких как интеграция с нейросетями, синхронизация видео и аудио, а также возможность искать похожие треки и формировать уникальные плейлисты. На данный момент приложение стабильно работает, и я горжусь его прогрессом. В будущем я надеюсь продолжить развивать его, добавляя новые возможности и улучшая производительность.
Если кто-то захочет помочь с доработкой Android-версии или с другими аспектами проекта, буду рад!
Сам плеер тут:
someradio.github.io
web.valse.me
Обсуждение и доки тут:
t.me/valse_me
vk.com/valse_me
Не законченное приложение под Android тут:
github.com/vartemkin/valse_app
Дисклеймер: Все упомянутые в настоящей статье объекты интеллектуальной собственности принадлежат соответствующим правообладателям и используются исключительно в информационных, научных, учебных или культурных целях в качестве иллюстраций тезисов автора статьи в объеме, оправданном поставленной целью.
zabanen2
"маленькое приложение на qt" политое с любовью