🔊️

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

2022/02/05に公開

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

GitHubで編集を提案

Discussion