paint-brush
Toucher une corde sensible : l'art d'animer la musique avec du senspar@charnog
322 lectures
322 lectures

Toucher une corde sensible : l'art d'animer la musique avec du sens

par Denis Gonchar10m2023/10/18
Read on Terminal Reader

Trop long; Pour lire

Combinant une passion pour la musique avec une expertise technologique, cette pièce guide les lecteurs à travers la création d'un site Web d'album visuel distinct. Les défis, les raccourcis de codage et le rôle du protocole Open Graph sont mis en évidence, tout en mettant l'accent sur l'équilibre entre perfectionnisme et exécution rapide.
featured image - Toucher une corde sensible : l'art d'animer la musique avec du sens
Denis Gonchar HackerNoon profile picture
0-item

Couverture de l'album de DALL·E 3


Dans cet article, je vais partager comment j'ai réalisé un projet pendant un week-end pour sortir mon album ( https://evhaevla.netlify.app/ ). Je ne suis ni musicien ni compositeur de formation, mais parfois, des airs me viennent à l'esprit. Je les note, puis je laisse l'ordinateur les lire.


En 2021, j'ai lancé mon album intitulé "Tout le monde est heureux, tout le monde rit". C'est un simple album d'un « compositeur » inconnu – c'est moi.


Je n'aime pas seulement la musique ; Je suis également développeur, me concentrant principalement sur le travail frontend récemment. Je me suis dit : pourquoi ne pas combiner ces deux amours ? J'ai donc décidé de concevoir un site Web pour présenter visuellement mon album.


Cet article n’entrera pas dans tous les détails techniques – ce serait trop long et pourrait ne pas plaire à tout le monde. Au lieu de cela, je mettrai en évidence les concepts fondamentaux et les obstacles que j'ai rencontrés. Pour ceux que cela intéresse, tout le code se trouve sur GitHub .

Visuel

Mon album est composé pour piano, ce qui rend la décision simple. Imaginez des rectangles descendant sur les touches du piano. Toute personne ayant un penchant musical a probablement rencontré de nombreuses vidéos sur YouTube illustrant des notes de cette manière. Un rectangle touche une touche, l'éclaire, indiquant le moment précis pour frapper la note.


Je ne suis pas sûr de l'origine de ce style visuel, mais une recherche rapide sur Google donne principalement des captures d'écran de Synthesia.

Interface utilisateur de synthèse

Sur YouTube, certains créateurs parviennent à produire des effets visuellement époustouflants. Regarder de telles vidéos est un régal, tant du point de vue esthétique que musical. Regardez ceci ou cela .


Que devrons-nous mettre en œuvre ?

  1. Clés
  2. Rectangulaires
  3. l'audio
  4. Animation


Abordons chaque point et mettons tout cela en action.

Clés

Au départ, je pensais que la mise en œuvre des clés constituerait le plus grand défi. Cependant, une recherche rapide en ligne a révélé une multitude d’exemples et de guides expliquant comment procéder. Visant un design avec une touche d'élégance, j'ai opté pour un exemple créé par Philip Zastrow .


Il ne me restait plus qu'à reproduire les touches plusieurs fois et à établir une grille sur laquelle les notes pouvaient glisser. J'ai utilisé Vue.js comme framework frontend, et ci-dessous se trouve le code du composant.

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

Je voudrais mentionner que j'ai ajouté un attribut id à chaque clé, qui sera essentiel lors du lancement de leurs animations.

Rectangulaires

Bien que ce segment puisse sembler le plus simple, quelques défis sont cachés à la vue de tous.


  1. Comment obtenir l’effet des notes descendantes ?


  2. Est-il nécessaire de maintenir une structure qui peut être interrogée pour récupérer les notes actuelles ?


  3. Quelle est la meilleure approche pour restituer les résultats de telles requêtes ?


Chaque question présente un obstacle à surmonter pour obtenir l’effet souhaité de manière transparente.


Je ne m'attarderai pas sur chaque question mais j'irai plutôt droit au but. Compte tenu de la myriade de défis associés à l’approche dynamique, il est sage de tenir compte du rasoir d’Occam et d’opter pour une solution statique.


Voici comment j'ai abordé le problème : j'ai rendu les 6 215 notes simultanément sur une seule grande toile. Ce canevas est logé dans un conteneur portant la propriété overflow: hidden . Pour obtenir l'effet de notes tombantes, il suffit alors d'animer le scrollTop de ce conteneur.


Cependant, une question subsiste : comment obtenir les coordonnées de chaque note ?


Heureusement, je dispose d'un fichier MIDI dans lequel toutes ces notes sont archivées, une commodité offerte par le fait d'être le compositeur de l'album. Cela revient à restituer les notes en utilisant les données extraites du fichier MIDI.


Étant donné que le fichier MIDI est au format binaire et que je n'avais pas l'intention de l'analyser moi-même, j'ai fait appel à la bibliothèque de fichiers midi .


La bibliothèque midi-file est efficace pour extraire des données brutes à partir de fichiers MIDI, mais pour mes besoins, ce n'est pas suffisant. Mon objectif est de transformer ces données dans un format plus accessible et plus convivial pour faciliter un rendu transparent au sein de l'application.


Dans un fichier MIDI, ce ne sont pas des notes au sens habituel du terme dont je parle, mais des événements. Il existe toute une gamme de ces événements, mais je me concentre principalement sur deux types : « noteOn », qui est déclenché lorsqu'une touche est enfoncée, et « noteOff », lorsque la touche est relâchée.


Les événements « noteOn » et « noteOff » spécifient respectivement le numéro de note particulier qui a été enfoncé ou relâché. Le temps, au sens conventionnel du terme, est absent du MIDI. Au lieu de cela, nous avons des « tiques ». Le nombre de ticks par battement est détaillé dans l'en-tête du fichier MIDI.

Bleu - battements (la longueur par défaut est de 96 ticks), Rouge - noter les événements


En effet, il y a plus à considérer. Une piste de tempo est également présente, contenant les événements « setTempo » qui font partie intégrante du processus, étant donné que le tempo peut changer pendant la lecture. Mon approche initiale consistait à ajuster la vitesse d'animation de la propriété scrollTop du conteneur pour l'aligner sur le tempo.


Cependant, j'ai vite réalisé que cela ne donnerait pas le résultat escompté en raison d'une accumulation excessive d'erreurs. Un étirement temporel « linéaire » s'est avéré plus efficace pour animer le scrollTop .


Même avec l’aspect animation trié, le tempo devait encore être incorporé. J'ai résolu ce problème en ajustant la longueur des rectangles des notes eux-mêmes. Bien qu’elle ne soit pas la solution optimale (la vitesse de manipulation serait idéale), cette méthode garantissait un fonctionnement plus fluide.


Cette solution n'est pas parfaite, principalement parce que j'associe un événement de tempo à un événement de note selon qu'ils ont la même durée ou moins. Cela signifie que si un autre événement de tempo se produit alors qu'une note est encore en cours de lecture, il sera simplement ignoré.


Cela pourrait potentiellement introduire une erreur, en particulier si une note est très longue et qu'un changement radical de tempo se produit pendant sa lecture. C'est un compromis. J'ai accepté ce défaut mineur car je me concentre sur un développement rapide.


Il y a des cas où la rapidité prime, et il est plus pragmatique de ne pas s'embrouiller dans les moindres détails.

Seul le premier événement de tempo est associé à la note


Nous disposons donc des informations suivantes :


  1. Le numéro de clé spécifique
  2. Le moment précis où la touche est enfoncée
  3. L'heure exacte à laquelle la clé est libérée
  4. L'intensité ou la "vitesse" à laquelle la touche est enfoncée


Avec ces détails à portée de main, je peux identifier les coordonnées exactes sur la toile pour chaque note. Le numéro de touche détermine l'axe X, tandis que le début de la pression sur la touche est l'axe Y. La longueur de la presse dicte la hauteur du rectangle.


En utilisant un élément div standard et en définissant sa position sur « absolue », j'ai réussi à obtenir l'effet souhaité.

Les rectangles que vous observez ici sont simplement de simples éléments `<div>` avec des styles appliqués. Ils sont positionnés de manière absolue sur une toile allongée, qui défile ensuite


l'audio

Je n'avais pas l'intention de créer un synthétiseur pour piano, car cela aurait pris beaucoup de temps. Au lieu de cela, j'ai utilisé un fichier OGG existant qui avait déjà été "rendu" et j'ai sélectionné The Grandeur de Native Instruments pour la bibliothèque sonore.


Personnellement, je pense que c'est le meilleur instrument VST pour piano disponible.


J'ai intégré le fichier OGG résultant dans un élément audio standard. Ma tâche principale était alors de synchroniser l'audio avec l'animation scrollTop de mon canevas de notes.

Animation

Avant de pouvoir aborder la synchronisation, il fallait d'abord établir l'animation. L'animation du canevas est assez simple : j'anime le scrollTop d'une valeur infinie jusqu'à zéro, en utilisant une interpolation linéaire. La durée de cette animation correspond à la durée de l'album.


Lorsqu'une note descend sur une touche, cette touche s'allume. Cela signifie qu'à chaque descente de note, je dois "activer" la touche correspondante, et une fois la note terminée sa course, la désactiver.


Avec un total de 6 215 notes, cela équivaut à 12 430 animations d'activation et de désactivation de notes.


De plus, mon objectif était de fournir aux utilisateurs la possibilité de rembobiner l'audio, leur permettant ainsi de naviguer n'importe où dans l'album. Pour mettre en œuvre une telle fonctionnalité, une solution robuste est essentielle.


Et lorsque je suis confronté au besoin d'une solution fiable qui « fonctionne », ma préférence est toujours la plateforme d'animation GreenSock .


Regardez la quantité de code nécessaire pour créer toutes les animations pour chacune des touches. Utiliser id pour animer des composants n’est pas la meilleure pratique pour les applications à page unique. Cependant, cette méthode constitue un véritable gain de temps. Vous souvenez-vous de l' id que j'ai mentionné pour chaque clé ? C'est là qu'ils entrent en jeu.


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


Le code de synchronisation établit essentiellement une connexion via des événements entre l'audio et la chronologie globale 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() })


Légendes

Juste au moment où j’avais envie de conclure, une idée intrigante m’est venue à l’esprit. Et si j’ajoutais une touche unique à l’album ? À l'origine, cela ne figurait pas sur ma liste de choses à faire, mais je pensais que le projet ne brillerait pas vraiment sans cette fonctionnalité. J’ai donc choisi de l’intégrer également.


Chaque fois que je me plonge dans un morceau, je me retrouve à réfléchir à ses significations plus profondes. Quel message le compositeur essayait-il de transmettre ? Prenons, par exemple, un segment du "Nightbook" de Ludovico Einaudi. Le piano résonne dans l’oreille gauche, tandis que les cordes résonnent dans l’oreille droite.


Il crée une ambiance de dialogue qui se déroule entre les deux. C'est comme si les touches du piano sondaient : « Êtes-vous d'accord ? Les cordes répondent affirmativement. "Est-ce que c'est ça la question ?" Les cordes font écho à leur affirmation. La séquence culmine avec la convergence des deux instruments, symbolisant une réalisation d'unité et d'harmonie. N'est-ce pas une expérience fascinante ?


Il est impératif de mentionner qu'il s'agit uniquement de mon interprétation personnelle. Une fois, j'ai eu l'occasion d'assister à un concert de Ludovico à Milan. Après la représentation, je l'ai approché et lui ai demandé s'il avait effectivement eu l'intention d'intégrer la notion de dialogue dans ce segment particulier.


Sa réponse a été éclairante : « Je n’ai jamais réfléchi de cette façon, mais vous possédez certainement une imagination débordante. »


Fort de cette expérience, je me suis demandé : et si j’intégrais des sous-titres dans la partition ? Au fur et à mesure de la lecture de segments spécifiques, des commentaires pourraient se matérialiser à l'écran, fournissant un aperçu ou des interprétations de l'intention du compositeur.


Cette fonctionnalité pourrait offrir aux auditeurs une compréhension plus approfondie ou une nouvelle perspective sur « que voulait vraiment dire l’auteur ? »


J'ai eu la chance d'avoir choisi GSAP comme outil d'animation. Cela m'a permis d'intégrer sans effort une autre timeline, spécifiquement chargée d'animer le commentaire. Cet ajout a rationalisé le processus et rendu la mise en œuvre de mon idée beaucoup plus fluide.


J'avais envie d'introduire les commentaires via un balisage HTML. Pour y parvenir, j'ai créé un composant qui introduit l'animation lors de l'événement 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>


L'utilisation de ce composant serait la suivante.

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

Apothéose

Une fois tous les éléments en place, l’étape suivante consistait à héberger le site. J'ai opté pour Netlify. Maintenant, je vous invite à découvrir l'album et à visionner la présentation finale.


https://evhaevla.netlify.app


J'espère sincèrement qu'il existe d'autres développeurs passionnés de piano, désireux de présenter leurs albums d'une manière aussi unique. Si vous en faites partie, n'hésitez pas à lancer le projet.