paint-brush
コードを叩く: 意味のある音楽にアニメーションを与える芸術@charnog
331 測定値
331 測定値

コードを叩く: 意味のある音楽にアニメーションを与える芸術

Denis Gonchar10m2023/10/18
Read on Terminal Reader

長すぎる; 読むには

音楽への情熱とテクノロジーの専門知識を組み合わせたこの記事では、読者に独特のビジュアル アルバム Web サイトの作成方法を説明します。課題、コーディングのショートカット、Open Graph プロトコルの役割が強調され、完璧主義と迅速な実行のバランスが強調されます。
featured image - コードを叩く: 意味のある音楽にアニメーションを与える芸術
Denis Gonchar HackerNoon profile picture
0-item

DALL・E 3 のアルバムのジャケット


この記事では、アルバム ( https://evhaevla.netlify.app/ ) をリリースするために週末をかけてプロジェクトを作成した方法を共有します。私は訓練されたミュージシャンや作曲家ではありませんが、時々、曲が頭の中に浮かびます。それらを書き留めて、コンピューターに再生させました。


2021年に『みんなが幸せ、みんなが笑ってる』というアルバムを発売しました。それは、見知らぬ「作曲家」、つまり私によるシンプルなアルバムです。


私は音楽だけが好きなわけではありません。最近はフロントエンドの仕事を中心に開発者もやってます。この 2 つの愛を組み合わせてみてはいかがでしょうか?そこで、アルバムを視覚的に紹介するウェブサイトをデザインすることにしました。


この記事では、技術的な詳細をすべて掘り下げるつもりはありません。長くなりすぎて、すべての人に魅力を感じていない可能性があります。代わりに、中心となる概念と私が遭遇したハードルを強調します。興味のある方は、すべてのコードをGitHubで見つけることができます。

ビジュアル

私のアルバムはピアノのために作曲されているので、決断は簡単です。長方形がピアノの鍵盤の上に降りてくると想像してください。音楽に興味のある人なら誰でも、このように音符を描いた YouTube のビデオを数多く目にしたことがあるでしょう。長方形がキーに触れると点灯し、音を打つ正確な瞬間を示します。


このビジュアル スタイルの由来はわかりませんが、Google で簡単に検索すると、主に Synthesia のスクリーンショットが見つかります。

シンセシアUI

YouTube には、視覚的に素晴らしい効果を生み出すクリエイターがいます。このようなビデオを見るのは、美的観点と音楽的観点の両方から見ても楽しいものです。これまたはこれを見てください。


何を実装する必要があるでしょうか?

  1. キー
  2. 長方形
  3. オーディオ
  4. アニメーション


それぞれのポイントに取り組み、これらすべてを実行に移してみましょう。

キー

当初、私はキーの実装が最大の課題となるだろうと考えていました。ただし、オンラインで簡単に検索すると、その方法に関する例とガイドが大量に見つかりました。エレガントなタッチのデザインを目指して、 Philip Zastrowが作成したを選択しました。


私に残された作業は、キーを数回複製し、ノートが滑るように配置されるグリッドを確立することだけでした。フロントエンド フレームワークとして 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属性を追加したことに言及したいと思います。これはアニメーションを開始するときに不可欠です。

長方形

これは最も単純なセグメントに見えるかもしれませんが、目に見えるところにいくつかの課題が隠されています。


  1. 下降音符の効果はどのようにして実現できるのでしょうか?


  2. 現在のメモを取得するためにクエリできる構造を維持する必要がありますか?


  3. このようなクエリの結果を表示するための最良のアプローチは何でしょうか?


各質問には、目的の効果をシームレスに達成するためにナビゲートすべき障害が存在します。


それぞれの質問に長々と答えるつもりはなく、むしろ本題に入りたいと思います。動的アプローチに関連する無数の課題を考慮すると、オッカムの剃刀に注意を払い、静的ソリューションを選択するのが賢明です。


これが私がそれに対処した方法です。私は 6215 個のノートすべてを 1 つの広大なキャンバス上に同時にレンダリングしました。このキャンバスは、 overflow: hiddenプロパティでスタイル設定されたコンテナ内に格納されます。落ちる音符の効果を実現するには、このコンテナのscrollTopをアニメーション化するだけです。


ただし、各音符の座標をどのように取得するかという疑問が残ります。


幸いなことに、私はこれらすべての音符をアーカイブした MIDI ファイルを持っています。これはアルバムの作曲者であるため便利です。つまり、MIDI ファイルから抽出されたデータを利用してノートをレンダリングすることになります。


MIDI ファイルはバイナリ形式であり、自分で解析するつもりはなかったので、 MIDI ファイルライブラリの助けを借りました。


midi-fileライブラリは、MIDI ファイルから生データを効率的に抽出できますが、私のニーズには十分ではありません。このデータをよりアクセスしやすく、アプリケーションに適した形式に変換して、アプリ内でのシームレスなレンダリングを容易にすることを目指しています。


MIDI ファイルで扱うのは通常の意味での音符ではなく、イベントです。これらのイベントは多数ありますが、私は主に 2 つのタイプに注目します。1 つはキーが押されたときにトリガーされる「noteOn」、もう 1 つはキーが放されたときにトリガーされる「noteOff」です。


「noteOn」イベントと「noteOff」イベントは両方とも、それぞれ押されたまたは放された特定のノート番号を指定します。従来の意味での時間は MIDI には存在しません。代わりに「ダニ」があります。ビートあたりのティック数は、MIDI ファイルのヘッダーに詳細に記載されています。

青 - ビート (デフォルトの長さは 96 ティック)、赤 - ノート イベント


実際、考慮すべきことはまだあります。再生中にテンポが変化する可能性があることを考慮して、プロセスに不可欠な「setTempo」イベントを含むテンポ トラックも存在します。私の最初のアプローチは、コンテナのscrollTopプロパティのアニメーション速度をテンポに合わせて調整することでした。


しかし、過剰なエラーの蓄積により、これでは期待した結果が得られないことがすぐにわかりました。 「線形」タイムストレッチは、 scrollTopアニメーション化する場合により効果的であることが判明しました。


アニメーションの側面が整理されたとしても、テンポを組み込む必要がありました。私はノートの長方形自体の長さを調整することでこの問題に対処しました。最適な解決策ではありませんが (操作速度が理想的です)、この方法によりスムーズな操作が保証されました。


この解決策は完璧ではありません。主な理由は、テンポ イベントとノート イベントの時間が同じか短いかに基づいて関連付けているからです。これは、ノートがまだ再生状態にある間に別のテンポ イベントが発生した場合、それは単に無視されることを意味します。


これにより、特にノートが非常に長く、再生中に劇的なテンポの変化が発生した場合、エラーが発生する可能性があります。それはトレードオフです。私は迅速な開発に重点を置いているため、この小さな欠陥を受け入れました。


スピードが優先される場合もあり、細部にまでこだわらないほうが現実的です。

最初のテンポイベントのみがノートに関連付けられます


したがって、次の情報が用意されています。


  1. 特定のキー番号
  2. キーが押された正確な瞬間
  3. キーが放された正確な時刻
  4. キーを押す強さまたは「速度」


これらの詳細が手元にあれば、各音符のキャンバス上の正確な座標を正確に特定できます。キー番号によって X 軸が決まり、キーの押し始めが Y 軸になります。プレスの長さによって長方形の高さが決まります。


標準の div 要素を使用し、その位置を「絶対」に設定することで、目的の効果を達成することができました。

ここで観察する四角形は、スタイルが適用された単なる単純な `<div>` 要素です。それらは細長いキャンバス上に絶対的に配置され、スクロールされます。


オーディオ

ピアノ用のシンセサイザーを作成するつもりはありませんでした。それは非常に時間がかかるからです。代わりに、すでに「レンダリング」されている既存の OGG ファイルを使用し、サウンド ライブラリとして Native Instruments のThe Grandeurを選択しました。


個人的には、これは入手可能なピアノ VST インストゥルメントの中で最高のものだと信じています。


結果の OGG ファイルを標準オーディオ要素に埋め込みました。私の主なタスクは、オーディオをノート キャンバスのscrollTopアニメーションと同期させることでした。

アニメーション

同期に取り組む前に、まずアニメーションを確立する必要がありました。キャンバスのアニメーションは非常に単純です。線形補間を使用して、 scrollTop無限値からゼロまでアニメーション化します。このアニメーションの長さはアルバムの長さと一致します。


音が鍵盤に降下すると、その鍵盤が点灯します。これは、各ノートが下降するたびに、対応するキーを「アクティブ化」する必要があり、ノートがその過程を完了したら、そのキーを非アクティブ化する必要があることを意味します。


合計 6,215 個のノートがあるため、これはなんと 12,430 個のノートのアクティブ化および非アクティブ化アニメーションに相当します。


さらに、オーディオを巻き戻す機能をユーザーに提供して、アルバム内のどこにでも移動できるようにすることを目指しました。このような機能を実装するには、堅牢なソリューションが不可欠です。


そして、「きちんと機能する」信頼できるソリューションの必要性に直面したとき、私が頼るのは常にGreenSock アニメーション プラットフォームです。


各キーのすべてのアニメーションを作成するのに必要なコードの量を見てください。 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を選択しました。ぜひアルバムを体験し、最終プレゼンテーションをご覧ください。


https://evhaevla.netlify.app


このようなユニークな方法でアルバムを紹介したいと熱望する、ピアノを愛する開発者が他にもいることを心から願っています。あなたもその一人なら、ためらわずにプロジェクトをフォークしてください。