paint-brush
Tocar um acorde: a arte de animar música com significadopor@charnog
273 leituras

Tocar um acorde: a arte de animar música com significado

por Denis Gonchar10m2023/10/18
Read on Terminal Reader

Muito longo; Para ler

Combinando a paixão pela música com o conhecimento técnico, esta peça orienta os leitores na criação de um site de álbum visual distinto. Desafios, atalhos de codificação e o papel do protocolo Open Graph são destacados, ao mesmo tempo em que enfatiza o equilíbrio entre perfeccionismo e execução rápida.
featured image - Tocar um acorde: a arte de animar música com significado
Denis Gonchar HackerNoon profile picture
0-item

Capa do álbum de DALL·E 3


Neste artigo, vou compartilhar como fiz um projeto durante um fim de semana para lançar meu álbum ( https://evhaevla.netlify.app/ ). Não sou um músico ou compositor treinado, mas às vezes surgem músicas na minha mente. Eu os anoto e deixo o computador reproduzi-los.


Em 2021, lancei meu álbum intitulado “Todo mundo está feliz, todo mundo está rindo”. É um álbum simples de um “compositor” desconhecido – sou eu.


Não gosto apenas de música; Também sou desenvolvedor, focando principalmente no trabalho de frontend recentemente. Pensei, por que não combinar esses dois amores? Então, decidi criar um site para apresentar visualmente meu álbum.


Este artigo não irá se aprofundar em todos os detalhes técnicos – isso seria muito longo e pode não agradar a todos. Em vez disso, destacarei os conceitos básicos e os obstáculos que encontrei. Para os interessados, todo o código pode ser encontrado no GitHub .

Visual

Meu álbum é composto para piano, tornando a decisão simples. Imagine retângulos descendo sobre as teclas do piano. Qualquer pessoa com inclinação musical provavelmente já encontrou vários vídeos no YouTube retratando notas dessa maneira. Um retângulo toca uma tecla, iluminando-a e indicando o momento preciso para tocar a nota.


Não tenho certeza da origem desse estilo visual, mas uma rápida pesquisa no Google produz predominantemente capturas de tela do Synthesia.

IU de síntese

No YouTube, existem criadores que conseguem produzir efeitos visualmente deslumbrantes. Assistir a esses vídeos é uma delícia, tanto do ponto de vista estético quanto musical. Assista isso ou aquilo .


O que precisaremos implementar?

  1. Chaves
  2. Retângulos
  3. Áudio
  4. Animação


Vamos abordar cada ponto e colocar tudo isso em ação.

Chaves

Inicialmente, presumi que a implementação das chaves representaria o maior desafio. No entanto, uma rápida pesquisa online revelou uma infinidade de exemplos e guias sobre como fazer exatamente isso. Visando um design com um toque de elegância, optei por um exemplar criado por Philip Zastrow .


Tudo o que me restou foi replicar as teclas várias vezes e estabelecer uma grade para as notas deslizarem. Empreguei Vue.js como estrutura de frontend e abaixo está o código do componente.

 <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>

Gostaria de mencionar que acrescentei um atributo id a cada chave, que será essencial ao iniciar suas animações.

Retângulos

Embora este possa parecer o segmento mais simples, alguns desafios estão ocultos à vista de todos.


  1. Como pode ser conseguido o efeito das notas descendentes?


  2. Há necessidade de manter uma estrutura que possa ser consultada para recuperar as notas atuais?


  3. Qual é a melhor abordagem para renderizar os resultados de tais consultas?


Cada pergunta apresenta um obstáculo a ser navegado para alcançar o efeito desejado de maneira integrada.


Não vou me deter em cada questão, mas vou direto ao assunto. Dados os inúmeros desafios associados à abordagem dinâmica, é aconselhável prestar atenção à Navalha de Occam e optar por uma solução estática.


Veja como resolvi isso: renderizei todas as 6.215 notas simultaneamente em uma única tela expansiva. Esta tela está alojada em um contêiner estilizado com a propriedade overflow: hidden . Conseguir o efeito de notas cadentes é simplesmente uma questão de animar o scrollTop deste contêiner.


No entanto, permanece uma questão persistente: como obtenho as coordenadas de cada nota?


Felizmente, tenho um arquivo MIDI onde todas essas notas estão arquivadas, uma comodidade proporcionada por ser o compositor do álbum. Tudo se resume a renderizar as notas utilizando os dados extraídos do arquivo MIDI.


Dado que o arquivo MIDI está em formato binário e eu não tinha intenção de analisá-lo sozinho, procurei a ajuda da biblioteca de arquivos midi .


A biblioteca midi-file é eficiente na extração de dados brutos de arquivos MIDI, mas para minhas necessidades isso não é suficiente. Meu objetivo é transformar esses dados em um formato mais acessível e fácil de usar para facilitar a renderização contínua dentro do aplicativo.


Em um arquivo MIDI, não estou lidando com notas no sentido usual, mas com eventos. Há uma grande variedade desses eventos, mas estou focado principalmente em dois tipos: 'noteOn', que é acionado quando uma tecla é pressionada, e 'noteOff', quando a tecla é liberada.


Os eventos 'noteOn' e 'noteOff' especificam o número da nota específica que foi pressionada ou liberada, respectivamente. O tempo, no sentido convencional, está ausente no MIDI. Em vez disso, temos “tiques”. O número de ticks por batida está detalhado no cabeçalho do arquivo MIDI.

Azul - batidas (a duração padrão é 96 ticks), Vermelho - eventos de notas


Na verdade, há mais a considerar. Uma faixa de andamento também está presente, contendo eventos 'setTempo' que são essenciais ao processo, considerando que o andamento pode mudar durante a reprodução. Minha abordagem inicial envolveu ajustar a velocidade da animação da propriedade scrollTop do contêiner para alinhá-la com o andamento.


No entanto, logo percebi que isso não produziria o resultado esperado devido ao acúmulo excessivo de erros. Um intervalo de tempo 'linear' provou ser mais eficaz para animar o scrollTop .


Mesmo com o aspecto da animação resolvido, o andamento ainda exigia incorporação. Resolvi isso ajustando os comprimentos dos próprios retângulos das notas. Embora não seja a solução ideal (manipular a velocidade seria o ideal), este método garantiu uma operação mais suave.


Esta solução não é perfeita, principalmente porque associo um evento de tempo a um evento de nota dependendo se eles têm o mesmo tempo ou menos. Isso significa que se outro evento de andamento ocorrer enquanto uma nota ainda estiver sendo tocada, ele será simplesmente desconsiderado.


Isso poderia potencialmente introduzir um erro, especialmente se uma nota for muito longa e houver uma mudança dramática de andamento durante a reprodução. É uma troca. Aceitei essa pequena falha porque estou focado no desenvolvimento rápido.


Há casos em que a velocidade tem precedência e é mais pragmático não se envolver em cada detalhe.

Apenas o primeiro evento de andamento está associado à nota


Portanto, estamos equipados com as seguintes informações:


  1. O número da chave específico
  2. O momento exato em que a tecla é pressionada
  3. A hora exata em que a chave é liberada
  4. A intensidade ou "velocidade" na qual a tecla é pressionada


Com esses detalhes em mãos, posso identificar as coordenadas exatas na tela para cada nota. O número da tecla determina o eixo X, enquanto o início do pressionamento da tecla é o eixo Y. O comprimento da prensa determina a altura do retângulo.


Usando um elemento div padrão e definindo sua posição como 'absoluta', consegui o efeito desejado.

Os retângulos que você observa aqui são apenas elementos `<div>` simples com estilos aplicados. Eles são posicionados absolutamente em uma tela alongada, que é então rolada


Áudio

Não pretendia criar um sintetizador para piano, pois isso levaria muito tempo. Em vez disso, usei um arquivo OGG existente que já havia sido "renderizado" e selecionei The Grandeur da Native Instruments para a biblioteca de sons.


Pessoalmente, acredito que é o melhor instrumento VST de piano disponível.


Incorporei o arquivo OGG resultante em um elemento de áudio padrão. Minha principal tarefa era sincronizar o áudio com a animação scrollTop da minha tela de notas.

Animação

Antes que eu pudesse abordar a sincronização, a animação teve que ser estabelecida primeiro. A animação da tela é bastante simples – eu animo o scrollTop de um valor infinito até zero, usando interpolação linear. A duração desta animação corresponde à duração do álbum.


Quando uma nota desce sobre uma tonalidade, essa tonalidade se ilumina. Isso significa que para a descida de cada nota, preciso “ativar” a tecla correspondente, e assim que a nota completar seu curso, desativá-la.


Com um total de 6.215 notas, isso equivale a impressionantes 12.430 animações de ativação e desativação de notas.


Além disso, meu objetivo era fornecer aos usuários a capacidade de retroceder o áudio, permitindo-lhes navegar em qualquer lugar do álbum. Para implementar tal recurso, uma solução robusta é essencial.


E quando me deparo com a necessidade de uma solução confiável que “simplesmente funcione”, minha escolha é sempre a Plataforma de Animação GreenSock .


Veja a quantidade de código necessária para criar todas as animações para cada uma das chaves. Usar id para animar componentes não é a prática recomendada para aplicativos de página única. No entanto, este método economiza muito tempo. Lembra do id que mencionei para cada chave? É aqui que eles entram em jogo.


 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) })


O código de sincronização estabelece essencialmente uma conexão através de eventos entre o áudio e a linha do tempo global do 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() })


Legendas

Justamente quando tive vontade de encerrar, uma ideia intrigante me veio à mente. E se eu adicionasse um toque único ao álbum? Originalmente não estava na minha lista de tarefas, mas senti que o projeto não brilharia de verdade sem esse recurso. Então, optei por incorporá-lo também.


Cada vez que mergulho em uma faixa, reflito sobre seus significados mais profundos. Que mensagem o compositor estava tentando transmitir? Considere, por exemplo, um segmento de “Nightbook” de Ludovico Einaudi. O piano ressoa no ouvido esquerdo, enquanto as cordas ecoam no direito.


Ele cria um ambiente de diálogo que se desenrola entre os dois. É como se as teclas do piano estivessem sondando: “Você concorda?” As cordas respondem afirmativamente. "Essa é a pergunta?" As cordas ecoam sua afirmação. A sequência culmina com a convergência de ambos os instrumentos, simbolizando uma realização de unidade e harmonia. Não é uma experiência fascinante?


É imperativo mencionar que esta é puramente minha interpretação pessoal. Uma vez tive a oportunidade de assistir a um concerto do Ludovico em Milão. Após a apresentação, abordei-o e perguntei se ele realmente pretendia incorporar a noção de diálogo naquele segmento específico.


Sua resposta foi esclarecedora: “Nunca pensei dessa forma, mas você certamente possui uma imaginação vívida”.


A partir dessa experiência, pensei: e se eu integrasse legendas na partitura? À medida que segmentos específicos são reproduzidos, comentários podem se materializar na tela, fornecendo insights ou interpretações sobre a intenção do compositor.


Esse recurso poderia oferecer aos ouvintes uma compreensão mais profunda ou uma nova perspectiva sobre "o que o autor realmente quis dizer?"


Foi uma sorte ter escolhido o GSAP como minha ferramenta de animação. Isso me permitiu integrar sem esforço outra linha do tempo, especificamente encarregada de animar o comentário. Essa adição agilizou o processo e tornou a implementação da minha ideia muito mais tranquila.


Tive vontade de apresentar os comentários por meio de marcação HTML. Para conseguir isso, criei um componente que apresenta a animação durante o evento 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>


O uso deste componente seria o seguinte.

 <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>

Apoteose

Com todos os elementos montados, o próximo passo foi hospedar o site. Optei pelo Netlify. Agora, convido você a experimentar o álbum e ver a apresentação final.


https://evhaevla.netlify.app


Eu realmente espero que existam outros desenvolvedores amantes de piano por aí, ansiosos para apresentar seus álbuns de uma maneira tão única. Se você é um deles, não hesite em fazer um fork do projeto.