Обложка альбома DALL·E 3
В этой статье я расскажу, как за выходные я разработал проект по выпуску своего альбома ( https://evhaevla.netlify.app/ ). Я не профессиональный музыкант и не композитор, но иногда в моей голове всплывают мелодии. Я записываю их, а затем позволяю компьютеру воспроизвести.
В 2021 году я выпустил свой альбом под названием «Все счастливы, все смеются». Это простой альбом незнакомого «композитора» – это я.
Я не просто увлекаюсь музыкой; Я также разработчик, в последнее время занимаюсь в основном фронтенд-работой. Я подумал, а почему бы не совместить эти две любви? Итак, я решил разработать веб-сайт, чтобы визуально представить мой альбом.
В этой статье не будут углубляться во все технические детали — она была бы слишком длинной и могла бы понравиться не всем. Вместо этого я выделю основные концепции и препятствия, с которыми я столкнулся. Для интересующихся весь код можно найти на GitHub .
Мой альбом написан для фортепиано, что делает решение простым. Представьте себе прямоугольники, нисходящие на клавиши фортепиано. Любой, у кого есть музыкальные наклонности, наверняка встречал на YouTube множество видеороликов, изображающих ноты в такой манере. Прямоугольник касается клавиши, подсвечивая ее и указывая точный момент удара по ноте.
Я не уверен в происхождении этого визуального стиля, но быстрый поиск в Google в основном выдаёт скриншоты Synthesia.
На YouTube есть авторы, которым удается создавать визуально ошеломляющие эффекты. Просмотр таких видео – удовольствие как с эстетической, так и с музыкальной точки зрения. Посмотрите то или это .
Что нам нужно будет реализовать?
Давайте разберемся с каждым пунктом и приведем все это в действие.
Первоначально я предполагал, что реализация ключей будет представлять собой самую большую проблему. Однако быстрый поиск в Интернете выявил множество примеров и руководств о том, как это сделать. Стремясь к дизайну с оттенком элегантности, я остановил свой выбор на примере , созданном Филипом Застроу .
Все, что мне оставалось сделать, это несколько раз воспроизвести клавиши и установить сетку, по которой будут скользить ноты. В качестве внешней среды я использовал Vue.js, а ниже приведен код компонента.
<template> <ul style="transform: translate3d(0, 0, 0)"> <li :id="`key_${OFFSET - 1}`" style="display: none"></li> <template v-for="key in keys" :key="key.number"> <li :class="`${key.color} ${key.name}`" :id="`key_${key.number}`"></li> </template> </ul> </template> <script setup lang="ts"> import { ref } from 'vue' import type { Key } from '@/components/types' const OFFSET = 24 const template = [ { color: 'white', name: 'c' // Do }, { color: 'black', name: 'cs' // Do-diez }, { color: 'white', name: 'd' // Re }, /* ... */ ] const keys = ref<Key[]>([]) for (let i = 0; i < 72; i++) { keys.value.push({ ...template[i % 12], number: i + OFFSET }) } </script>
Я хотел бы отметить, что я добавил атрибут id
к каждой клавише, который будет необходим при запуске анимации.
Хотя это может показаться самым простым сегментом, несколько проблем скрыты на виду.
Как можно добиться эффекта нисходящих нот?
Есть ли необходимость поддерживать структуру, к которой можно обращаться для получения текущих заметок?
Каков наилучший подход к отображению результатов таких запросов?
Каждый вопрос представляет собой препятствие для беспрепятственного достижения желаемого эффекта.
Я не буду задерживаться на каждом вопросе, а сразу перейду к делу. Учитывая множество проблем, связанных с динамическим подходом, разумно прислушаться к бритве Оккама и выбрать статическое решение.
Вот как я решил эту проблему: я отрисовал все 6215 нот одновременно на одном обширном холсте. Этот холст размещен внутри контейнера, стилизованного под свойство overflow: hidden
. Для достижения эффекта падающих нот достаточно просто анимировать scrollTop
этого контейнера.
Однако остается нерешенный вопрос: как мне получить координаты каждой ноты?
К счастью, у меня есть MIDI-файл, в котором заархивированы все эти ноты, — удобство, которое дает мне возможность быть композитором альбома. Все сводится к рендерингу нот с использованием данных, извлеченных из MIDI-файла.
Учитывая, что MIDI-файл имеет двоичный формат и у меня не было намерения анализировать его самостоятельно, я заручился помощью библиотеки MIDI-файлов .
Библиотека midi-file
эффективно извлекает необработанные данные из MIDI-файлов, но для моих нужд этого недостаточно. Я стремлюсь преобразовать эти данные в более доступный и удобный для приложений формат, чтобы обеспечить плавный рендеринг в приложении.
В MIDI-файле речь идет не о нотах в привычном понимании, а о событиях. Существует целый ряд таких событий, но я в первую очередь сосредоточен на двух типах: «noteOn», которое срабатывает при нажатии клавиши, и «noteOff», когда клавиша отпускается.
События noteOn и noteOff указывают конкретный номер ноты, которая была нажата или отпущена соответственно. Время в общепринятом понимании в MIDI отсутствует. Вместо этого у нас есть «галочки». Количество тиков на долю подробно указано в заголовке MIDI-файла.
Действительно, есть еще над чем подумать. Также присутствует дорожка темпа, содержащая события setTempo, которые являются неотъемлемой частью процесса, учитывая, что темп может меняться во время воспроизведения. Мой первоначальный подход заключался в настройке скорости анимации свойства scrollTop
контейнера в соответствии с темпом.
Однако вскоре я понял, что это не даст ожидаемого результата из-за чрезмерного накопления ошибок. «Линейное» растяжение времени оказалось более эффективным для анимации scrollTop
.
Даже после того, как аспект анимации был отсортирован, темп по-прежнему требовал учета. Я решил эту проблему, отрегулировав длину самих прямоугольников нот. Хотя этот метод и не был оптимальным решением (идеальным было бы управление скоростью), этот метод обеспечивал более плавную работу.
Это решение не идеально, главным образом потому, что я связываю событие темпа с событием ноты в зависимости от того, имеют ли они одинаковое или меньшее время. Это означает, что если произойдет другое событие темпа, пока нота все еще находится в состоянии воспроизведения, оно будет просто проигнорировано.
Это потенциально может привести к ошибке, особенно если нота очень длинная и во время ее воспроизведения происходит резкое изменение темпа. Это компромисс. Я принял этот незначительный недостаток, поскольку сосредоточен на быстром развитии.
Бывают случаи, когда скорость имеет приоритет, и прагматичнее не запутываться в каждой детали.
Итак, мы располагаем следующей информацией:
Имея под рукой эти детали, я могу определить точные координаты каждой ноты на холсте. Номер клавиши определяет ось X, а начало нажатия клавиши — ось Y. Длина пресса определяет высоту прямоугольника.
Используя стандартный элемент div и установив для него «абсолютное» положение, я успешно добился желаемого эффекта.
Я не собирался создавать синтезатор для фортепиано, так как это заняло бы много времени. Вместо этого я использовал существующий файл OGG, который уже был «рендерен», и выбрал The Grandeur от Native Instruments в качестве звуковой библиотеки.
Лично я считаю, что это лучший фортепианный VST-инструмент, доступный на рынке.
Я встроил полученный файл OGG в стандартный аудиоэлемент. Моей основной задачей тогда была синхронизация звука с анимацией scrollTop
моего холста заметок.
Прежде чем я смог заняться синхронизацией, сначала нужно было установить анимацию. Анимация холста довольно проста — я анимирую scrollTop
от бесконечного значения до нуля, используя линейную интерполяцию. Продолжительность этой анимации соответствует длине альбома.
Когда нота попадает на клавишу, эта клавиша подсвечивается. Это означает, что для спуска каждой ноты мне нужно «активировать» соответствующую клавишу, а как только нота завершит свой ход, деактивировать ее.
Всего 6215 заметок, что соответствует колоссальным 12 430 анимациям активации и деактивации заметок.
Кроме того, я стремился предоставить пользователям возможность перематывать аудио, позволяя им перемещаться в любом месте альбома. Для реализации такой функции необходимо надежное решение.
И когда я сталкиваюсь с необходимостью надежного решения, которое «просто работает», я всегда обращаюсь к платформе GreenSock Animation Platform .
Посмотрите, сколько кода требуется для создания анимации для каждой клавиши. Использование id
для анимации компонентов — не лучшая практика для одностраничных приложений. Однако этот метод реально экономит время. Помните id
, который я упоминал для каждого ключа? Вот тут-то они и вступают в игру.
const keysTl = gsap.timeline() notes.value.forEach((note) => { const keySelector = `#note_${note.noteNumber}` keysTl .set(keySelector, KEY_ACTIVE_STATE, note.positionSeconds) .set(keySelector, KEY_INACTIVE_STATE, note.positionSeconds + note.durationSeconds - 0.02) })
Код синхронизации по существу устанавливает связь через события между звуком и глобальной временной шкалой GSAP.
audioRef.value?.addEventListener('timeupdate', () => { const time = audioRef.value?.currentTime ?? 0 globalTl.time(time) }) audioRef.value?.addEventListener('play', () => { globalTl.play() }) audioRef.value?.addEventListener('playing', () => { globalTl.play() }) audioRef.value?.addEventListener('waiting', () => { globalTl.pause() }) audioRef.value?.addEventListener('pause', () => { globalTl.pause() })
Когда мне захотелось подвести итоги, мне в голову пришла интригующая идея. Что, если я добавлю в альбом уникальную особенность? Изначально этого не было в моем списке дел, но я чувствовал, что без этой функции проект не будет по-настоящему блестящим. Поэтому я решил включить и его.
Каждый раз, когда я погружаюсь в трек, я размышляю о его более глубоком значении. Какое послание пытался передать композитор? Возьмем, к примеру, отрывок из «Ночной тетради» Людовико Эйнауди. Фортепиано резонирует в левом ухе, а струны — в правом.
Это создает атмосферу диалога, разворачивающегося между ними. Такое ощущение, будто клавиши пианино щупают: «Вы согласны?» Струны отвечают утвердительно. «Это вопрос?» Струны повторяют их подтверждение. Кульминацией композиции является сближение обоих инструментов, символизирующее реализацию единства и гармонии. Разве это не завораживающий опыт?
Необходимо отметить, что это сугубо моя личная интерпретация. Однажды мне довелось побывать на концерте Людовико в Милане. После спектакля я подошел к нему и поинтересовался, действительно ли он намеревался внедрить идею диалога в этот конкретный фрагмент.
Его ответ был поучительным: «Я никогда не думал об этом таким образом, но у вас определенно яркое воображение».
Опираясь на этот опыт, я задумался: а что, если интегрировать субтитры в ноты? По мере воспроизведения определенных фрагментов на экране могут материализоваться комментарии, дающие понимание или интерпретацию замысла композитора.
Эта функция может предложить слушателям более глубокое понимание или свежий взгляд на то, «что на самом деле имел в виду автор?»
Мне повезло, что я выбрал GSAP в качестве инструмента для анимации. Это позволило мне без особых усилий интегрировать еще одну временную шкалу, специально предназначенную для анимации комментариев. Это дополнение упростило процесс и сделало реализацию моей идеи намного более гладкой.
У меня была склонность вводить комментарии с помощью HTML-разметки. Для этого я создал компонент, который представляет анимацию во время события onMounted
.
<template> <div :class="$style.comment" ref="commentRef"> <slot></slot> </div> </template> <script setup lang="ts"> /* ... */ onMounted(() => { if (!commentRef.value) return props.timeline .fromTo( commentRef.value, { autoAlpha: 0 }, { autoAlpha: 1, duration: 0.5 }, props.time ? parseTime(props.time) : props.delay ? `+=${props.delay}` : '+=1' ) .to( commentRef.value, { autoAlpha: 0, duration: 0.5 }, props.time ? parseTime(props.time) + props.duration : `+=${props.duration}` ) }) </script>
Использование этого компонента будет следующим.
<template> <div> <Comment time="0:01" :duration="5" :timeline="commentsTl"> <h1>A title for a track</h1> </Comment> <Comment :delay="1" :duration="13" :timeline="commentsTl"> I would like to say... </Comment>
Когда все элементы были готовы, следующим шагом было размещение сайта на хостинге. Я выбрал Netlify. А теперь я приглашаю вас познакомиться с альбомом и посмотреть финальную презентацию.
Я искренне надеюсь, что найдутся и другие разработчики, любящие фортепиано, которые хотят продемонстрировать свои альбомы в такой уникальной манере. Если вы один из них, не стесняйтесь создавать форк проекта.