🔊

音楽に合わせて動くかっこいい映像を簡単に作れるサービスを公開しました

2022/02/05に公開約4,600字

https://reaktr.vercel.app

https://www.youtube.com/watch?v=MSgEgdC3OdU

どんなサービス?

デモ動画を見てもらうのが一番わかりやすいと思いますが、「音楽に合わせてウネウネ動く映像」を簡単に作成できます。

作った理由

僕はプログラミング以外に音楽製作もやっていて、SoundCloudなどにアップロードして公開したりしています。

活動していて感じたのが、いまどきは音楽だけで公開しても目立つことができないということです。Web上のコンテンツはどんどんリッチになっているので、音声だけでは、例えばTwitterのタイムライン上で見過ごされてしまいがちです。

かといって、(僕のように)本気で音楽をやっているわけでもない人が、MV製作までするのはかなり負担が大きいです。自分で作るにしても依頼するにしても、音楽制作とは全く関係のないスキルとコストが必要です。

一言でいうと、「作った音楽に、あまり労力をかけずに、それっぽい映像をつけてくれるサービスがあったらいいな」と思ったのがこのサービスを作った理由です。

類似サービス・ツール

もちろんこの問題を解決してくれるサービスがすでにあるなら新しく作る必要はないですが、価格やOS対応、個人的な好みなど、僕の用途に合うものは見つかりませんでした。

技術スタック

  • React (Next.js)
  • Chakra UI
  • three.js
  • react-three-fiber
  • zustand
  • Web Audio API

音声の視覚化

Web Audio APIのAnalyzerNodeでオーディオデータの解析をリアルタイムで行い、その結果をthree.jsのオブジェクトの形状に反映させるのがメインの処理です。

基本的にはMDNのチュートリアルでやっていることと同じですが、波形の動きを「それっぽく」するのはちょっと手間取りました。

以下のように Box コンポーネントを円形に配置して周波数ごとに割りあて、音の大きさに応じてサイズを変更しています。

<group ref={group}>
  {Array.from(Array(BAR_COUNT)).map((_, i) => {
    const degree = (360 / BAR_COUNT) * i + 90;
    const x = INNER_RADIUS * Math.cos(degree * (Math.PI / 180));
    const y = INNER_RADIUS * Math.sin(degree * (Math.PI / 180));
    return (
      <Box
        key={i}
        args={[0.5, 0.4, 0.1]}
        position={[x, y, 0]}
        rotation={[0, 0, (Math.PI / (360 / degree)) * 2]}
      >
        <meshStandardMaterial attach="material" color={getColor(i)} />
      </Box>
    );
  })}
</group>
useFrame(() => {
  const dataArray = audio.getByteFrequencyData();
  const avgFreqData = new Uint8Array(BAR_COUNT);
  for (let i = 0; i < BAR_COUNT; i++) {
    let sum = 0;
    const offset = FREQ_OFFSET + i * FREQ_STEP;
    for (let j = offset; j < offset + FREQ_STEP; j++) {
      sum += dataArray[j];
    }
    avgFreqData[i] = sum / FREQ_STEP;
  }
  group.current.children.forEach((mesh, i) => {
    if (!(mesh instanceof THREE.Mesh)) {
      return;
    }
    const data = avgFreqData[i];
    const scale = Math.max(1, toScale(data));
    mesh.scale.setX(scale);
  });
});

const toScale = (num: number) => num ** 4 * 0.000000004;

(toScale() 内の数字は完全なマジックナンバーで、映像を見ながら設定しました。)

ちなみに、現状は2Dの映像に見えますが、three.jsなので3D空間に存在しています。後述するように、はじめは背景も含めてより三次元っぽい映像を作ろうとしていましたが、現状はあまり3Dを活かせていません・・・。

斜めから

背景

音楽に反応して動くオブジェクトは作成できましたが、それだけではかっこいい映像にはなりません。

もともとは背景もthree.jsで自作するつもりでしたが、難しかったので一旦諦めました。映像や3Dの素人ががんばって作っても、課題の解決につながらなければ意味がないので無理はしません。また、どう考えてもデザインの好みやニーズは千差万別すぎるので、大量に提案できない限りはこちらで提供するべきではないと判断しました。

動画製作に疎いので知らなかったのですが、フリーの動画素材もそれなりに存在していることがわかったので、ユーザーの選択した動画/静止画を背景に使えるようにしました。実装コストを下げつつ、ユーザーの好みの映像を作ることができるようになったと思います。

ビデオ出力

現状は、MediaRecorderでリアルタイムで録画してwebm形式でダウンロードできるようしていますが、技術的な課題は多いです。

const chunks: Blob[] = [];
const canvasStream = canvas.captureStream();
const outputStream = new MediaStream();
[audio.streamDestination.stream, canvasStream].forEach((stream) => {
  stream.getTracks().forEach((track) => {
    outputStream.addTrack(track);
  });
});
recorder = new MediaRecorder(outputStream, options);
recorder.ondataavailable = (e) => chunks.push(e.data);

リアルタイムでの録画は端末の性能に左右されすぎる

映像のプレビューだけならそれほどハイスペックな端末でなくてもスムーズに動くのですが、録画はかなりカクつきやすいです。

手持ちのMacBook Air (M1, 16GB RAM)ならFull HD, 8 MBpsでカクつかずに録画できますが、なんだかフレームレートが下がったように収録されます。Windowsはスペックを発揮しづらいのか、i9, 32GB RAMのマシンでも映像が飛びがちです。GPUの性能の方が重要なのかもしれません。

また、モバイル端末だとメモリに格納できるサイズの限界値が小さいらしく、3分の収録もできませんでした。

対策としては、CCaptureのように、(リアルタイムではなく)フレームごとに撮影して動画化することになると思います。このサービスでCCaptureをすぐに採用できない理由は、再生中の音声をリアルタイムで解析して、それを映像に反映させているからです。フレームごとの撮影を実現するには、先に音声の解析を済ませて保存しておき、各フレームでその時間に対応するデータを参照するようにコードを書き換える必要があります。

出力形式がwebm

できれば一般的なmp4形式で出力したいですが、MediaRecorderは対応していないようです(少なくともChromeでは)。

ffmpeg.wasmを使うことで、webmからmp4への変換をブラウザ上で行うことは実現できました。ただ、録画自体が不安定なこともあり、とりあえずこちらは保留にしました。

まとめ

興味の湧いた方はぜひ使ってみてください!

https://reaktr.vercel.app

Product Huntにも投稿しました。

https://www.producthunt.com/posts/reaktr

追記

#2 Product of the Dayを獲得しました!

top-post-badge

Discussion

ログインするとコメントできます