Cover für das Album von DALL·E 3
In diesem Artikel erzähle ich, wie ich an einem Wochenende ein Projekt zur Veröffentlichung meines Albums erstellt habe ( https://evhaevla.netlify.app/ ). Ich bin kein ausgebildeter Musiker oder Komponist, aber manchmal kommen mir Melodien in den Sinn. Ich schreibe sie auf und lasse sie dann vom Computer abspielen.
Im Jahr 2021 brachte ich mein Album mit dem Titel „Everyone is Happy, Everyone is Laughing“ heraus. Es ist ein einfaches Album eines unbekannten „Komponisten“ – das bin ich.
Ich interessiere mich nicht nur für Musik; Ich bin auch Entwickler und konzentriere mich in letzter Zeit hauptsächlich auf Frontend-Arbeit. Ich dachte, warum nicht diese beiden Lieben kombinieren? Also machte ich mich daran, eine Website zu entwerfen, um mein Album visuell zu präsentieren.
In diesem Artikel wird nicht auf jedes technische Detail eingegangen – das würde zu langwierig sein und möglicherweise nicht jeden ansprechen. Stattdessen werde ich die Kernkonzepte und die Hürden hervorheben, auf die ich gestoßen bin. Für Interessierte ist der gesamte Code auf GitHub zu finden.
Da mein Album für Klavier komponiert ist, fällt mir die Entscheidung leicht. Stellen Sie sich Rechtecke vor, die auf die Klaviertasten herabsinken. Jeder mit einer musikalischen Neigung ist auf YouTube wahrscheinlich auf zahlreiche Videos gestoßen, in denen Noten auf diese Weise dargestellt werden. Ein Rechteck berührt eine Taste, beleuchtet sie und zeigt den genauen Moment an, in dem die Note angeschlagen werden muss.
Ich bin mir nicht sicher, woher dieser visuelle Stil stammt, aber eine schnelle Google-Suche liefert überwiegend Screenshots von Synthesia.
Auf YouTube gibt es YouTuber, die es schaffen, visuell beeindruckende Effekte zu erzielen. Das Anschauen solcher Videos ist sowohl aus ästhetischer als auch aus musikalischer Sicht ein Genuss. Sehen Sie sich dies oder das an.
Was müssen wir umsetzen?
Lassen Sie uns jeden Punkt angehen und alles in die Tat umsetzen.
Zunächst ging ich davon aus, dass die Implementierung der Schlüssel die größte Herausforderung darstellen würde. Eine schnelle Online-Suche ergab jedoch eine Fülle von Beispielen und Anleitungen, wie man genau das macht. Da ich ein Design mit einem Hauch von Eleganz anstreben wollte, habe ich mich für ein Beispiel von Philip Zastrow entschieden.
Mir blieb nur noch, die Tasten mehrmals nachzubilden und ein Raster zu erstellen, über das die Noten gleiten konnten. Ich habe Vue.js als Frontend-Framework verwendet und unten ist der Komponentencode.
<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>
Ich möchte erwähnen, dass ich jeder Taste ein id
Attribut angehängt habe, das beim Starten ihrer Animationen unerlässlich ist.
Obwohl dies das einfachste Segment zu sein scheint, sind einige Herausforderungen auf den ersten Blick verborgen.
Wie kann der Effekt absteigender Noten erzielt werden?
Muss eine Struktur gepflegt werden, die abgefragt werden kann, um die aktuellen Notizen abzurufen?
Was ist der beste Ansatz, um die Ergebnisse solcher Abfragen darzustellen?
Jede Frage stellt ein Hindernis dar, das es zu bewältigen gilt, um nahtlos den gewünschten Effekt zu erzielen.
Ich werde nicht bei jeder Frage verweilen, sondern komme gleich zur Sache. Angesichts der unzähligen Herausforderungen, die mit dem dynamischen Ansatz verbunden sind, ist es ratsam, Occams Razor zu beherzigen und sich für eine statische Lösung zu entscheiden.
So habe ich es angegangen: Ich habe alle 6215 Notizen gleichzeitig auf einer einzigen großen Leinwand gerendert. Diese Leinwand befindet sich in einem Container, der mit der Eigenschaft overflow: hidden
gestaltet ist. Um den Effekt fallender Noten zu erzielen, müssen Sie lediglich den scrollTop
dieses Containers animieren.
Es bleibt jedoch eine Frage offen: Wie erhalte ich die Koordinaten für jede Notiz?
Glücklicherweise habe ich eine MIDI-Datei, in der alle diese Notizen archiviert sind, ein Vorteil, der mir dadurch entsteht, dass ich der Komponist des Albums bin. Es läuft darauf hinaus, die Noten mithilfe der aus der MIDI-Datei extrahierten Daten zu rendern.
Da die MIDI-Datei im Binärformat vorliegt und ich nicht die Absicht hatte, sie selbst zu analysieren, habe ich die Hilfe der Midi-File- Bibliothek in Anspruch genommen.
Die midi-file
Bibliothek ist effizient beim Extrahieren von Rohdaten aus MIDI-Dateien, aber für meine Bedürfnisse reicht das nicht aus. Mein Ziel ist es, diese Daten in ein zugänglicheres und anwendungsfreundlicheres Format umzuwandeln, um eine nahtlose Darstellung innerhalb der App zu ermöglichen.
In einer MIDI-Datei handelt es sich nicht um Noten im üblichen Sinne, sondern um Ereignisse. Es gibt eine ganze Reihe dieser Ereignisse, aber ich konzentriere mich hauptsächlich auf zwei Arten: „noteOn“, das ausgelöst wird, wenn eine Taste gedrückt wird, und „noteOff“, wenn die Taste losgelassen wird.
Sowohl die Ereignisse „noteOn“ als auch „noteOff“ geben die jeweilige Notennummer an, die gedrückt bzw. losgelassen wurde. Zeit im herkömmlichen Sinne fehlt in MIDI. Stattdessen haben wir „Zecken“. Die Anzahl der Ticks pro Schlag wird im Header der MIDI-Datei angegeben.
Tatsächlich gibt es noch mehr zu bedenken. Es ist auch eine Tempospur vorhanden, die „setTempo“-Ereignisse enthält, die für den Prozess von wesentlicher Bedeutung sind, da sich das Tempo während der Wiedergabe ändern kann. Mein erster Ansatz bestand darin, die Animationsgeschwindigkeit der scrollTop
Eigenschaft des Containers an das Tempo anzupassen.
Allerdings wurde mir bald klar, dass dies aufgrund der übermäßigen Fehlerhäufigkeit nicht zum erwarteten Ergebnis führen würde. Eine „lineare“ Zeitdehnung erwies sich für die Animation des scrollTop
als effektiver.
Auch wenn der Animationsaspekt geklärt war, musste das Tempo noch berücksichtigt werden. Ich habe dieses Problem gelöst, indem ich die Länge der Rechtecke der Noten selbst angepasst habe. Diese Methode war zwar nicht die optimale Lösung (ideal wäre eine Geschwindigkeitsbeeinflussung), sorgte aber für einen reibungsloseren Betrieb.
Diese Lösung ist nicht perfekt, vor allem weil ich ein Tempo-Ereignis einem Noten-Ereignis zuordne, basierend darauf, ob sie die gleiche oder eine kürzere Zeit haben. Das bedeutet, dass ein anderes Tempoereignis, während eine Note noch gespielt wird, einfach ignoriert wird.
Dies könnte möglicherweise zu einem Fehler führen, insbesondere wenn eine Note sehr lang ist und während ihrer Spielzeit ein dramatischer Tempowechsel auftritt. Es ist ein Kompromiss. Ich habe diesen kleinen Fehler akzeptiert, da ich mich auf eine schnelle Entwicklung konzentriere.
Es gibt Fälle, in denen Geschwindigkeit Vorrang hat und es pragmatischer ist, sich nicht in jedes Detail zu verstricken.
Wir verfügen also über folgende Informationen:
Mit diesen Details kann ich für jede Notiz die genauen Koordinaten auf der Leinwand bestimmen. Die Tastennummer bestimmt die X-Achse, während der Beginn des Tastendrucks die Y-Achse ist. Die Länge der Presse bestimmt die Höhe des Rechtecks.
Durch die Verwendung eines Standard-Div-Elements und das Festlegen seiner Position auf „absolut“ habe ich erfolgreich den gewünschten Effekt erzielt.
Ich hatte nicht vor, einen Synthesizer für das Klavier zu entwickeln, da das viel Zeit gekostet hätte. Stattdessen habe ich eine vorhandene OGG-Datei verwendet, die bereits „gerendert“ wurde, und für die Soundbibliothek „The Grandeur“ von Native Instruments ausgewählt.
Persönlich glaube ich, dass es das beste Klavier-VST-Instrument ist, das es gibt.
Die resultierende OGG-Datei habe ich in ein Standard-Audioelement eingebettet. Meine Hauptaufgabe bestand dann darin, den Ton mit der scrollTop
Animation meiner Notizleinwand zu synchronisieren.
Bevor ich die Synchronisation in Angriff nehmen konnte, musste zunächst die Animation erstellt werden. Die Canvas-Animation ist ziemlich einfach – ich animiere scrollTop
von einem unendlichen Wert bis hinunter zu Null, indem ich lineare Interpolation verwende. Die Dauer dieser Animation entspricht der Länge des Albums.
Wenn eine Note auf eine Taste fällt, leuchtet diese Taste auf. Das bedeutet, dass ich für den Abstieg jeder Note die entsprechende Taste „aktivieren“ und sie deaktivieren muss, sobald die Note ihren Lauf beendet hat.
Bei insgesamt 6215 Noten entspricht das satten 12.430 Notenaktivierungs- und -deaktivierungsanimationen.
Darüber hinaus wollte ich den Benutzern die Möglichkeit geben, den Ton zurückzuspulen, damit sie überall im Album navigieren können. Um eine solche Funktion zu implementieren, ist eine robuste Lösung unerlässlich.
Und wenn ich eine zuverlässige Lösung brauche, die „einfach funktioniert“, ist meine erste Wahl immer die GreenSock Animation Platform .
Sehen Sie sich an, wie viel Code erforderlich ist, um alle Animationen für jede der Tasten zu erstellen. Die Verwendung id
zum Animieren von Komponenten ist nicht die beste Vorgehensweise für Einzelseitenanwendungen. Allerdings ist diese Methode eine echte Zeitersparnis. Erinnern Sie sich an die id
, die ich für jeden Schlüssel angegeben habe? Hier kommen sie ins Spiel.
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) })
Der Synchronisationscode stellt im Wesentlichen eine Verbindung durch Ereignisse zwischen dem Audio und der globalen GSAP-Zeitleiste her.
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() })
Gerade als ich Schluss machen wollte, kam mir eine faszinierende Idee. Was wäre, wenn ich dem Album eine einzigartige Wendung hinzufügen würde? Es stand ursprünglich nicht auf meiner To-Do-Liste, aber ich hatte das Gefühl, dass das Projekt ohne diese Funktion nicht wirklich glänzen würde. Also habe ich beschlossen, es auch zu integrieren.
Jedes Mal, wenn ich mich in einen Titel vertiefe, denke ich über seine tiefere Bedeutung nach. Welche Botschaft wollte der Komponist vermitteln? Betrachten Sie zum Beispiel einen Abschnitt in „Nightbook“ von Ludovico Einaudi. Das Klavier schwingt im linken Ohr mit, während die Saiten im rechten Ohr widerhallen.
Es schafft die Atmosphäre eines Dialogs, der sich zwischen den beiden entfaltet. Es fühlt sich an, als würden die Klaviertasten prüfen: „Stimmen Sie zu?“ Die Saiten antworten positiv. „Ist das die Frage?“ Die Streicher geben ihre Bestätigung wieder. Die Sequenz gipfelt in der Konvergenz beider Instrumente und symbolisiert die Verwirklichung von Einheit und Harmonie. Ist das nicht ein faszinierendes Erlebnis?
Es muss unbedingt erwähnt werden, dass dies rein meine persönliche Interpretation ist. Einmal hatte ich die Gelegenheit, ein Ludovico-Konzert in Mailand zu besuchen. Nach der Aufführung ging ich auf ihn zu und fragte ihn, ob er tatsächlich vorgehabt habe, die Idee eines Dialogs in diesem bestimmten Abschnitt zu verankern.
Seine Antwort war aufschlussreich: „Ich habe nie so darüber nachgedacht, aber Sie besitzen auf jeden Fall eine lebhafte Vorstellungskraft.“
Aufgrund dieser Erfahrung überlegte ich: Was wäre, wenn ich Untertitel in die Noten integrieren würde? Während bestimmte Segmente abgespielt werden, könnten auf dem Bildschirm Kommentare erscheinen, die Einblicke oder Interpretationen in die Absicht des Komponisten geben.
Diese Funktion könnte den Zuhörern ein tieferes Verständnis oder eine neue Perspektive auf die Frage „Was meinte der Autor wirklich?“ bieten.
Zum Glück habe ich mich für GSAP als Animationstool entschieden. Dadurch konnte ich mühelos eine weitere Zeitleiste integrieren, die speziell für die Animation des Kommentars zuständig war. Dieser Zusatz hat den Prozess rationalisiert und die Umsetzung meiner Idee viel reibungsloser gestaltet.
Ich hatte eine Neigung, die Kommentare per HTML-Markup einzuführen. Um dies zu erreichen, habe ich eine Komponente erstellt, die die Animation während des onMounted
Ereignisses einleitet.
<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>
Die Verwendung dieser Komponente wäre wie folgt.
<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>
Nachdem alle Elemente vorhanden waren, bestand der nächste Schritt darin, die Website zu hosten. Ich habe mich für Netlify entschieden. Jetzt lade ich Sie ein, das Album zu erleben und die Abschlusspräsentation anzusehen.
Ich hoffe wirklich, dass es noch andere klavierbegeisterte Entwickler gibt, die ihre Alben gerne auf so einzigartige Weise präsentieren möchten. Wenn Sie einer von ihnen sind, zögern Sie nicht, das Projekt zu teilen.