以前、M2 MacBook Air 24GB 上の独自エージェンティックAI「mazzaim2air 」に「歌詞同期ビジュアライザー」を生やした話を書きました。今回はその発展形です。
前回のビジュアライザーは、WAV/MP3 を読み込んで、WebAudio の AnalyserNode で帯域を分けて、歌詞の内容からシーンを自動分類して、2D Canvas 上でパーティクルと波形を踊らせる、というやつ。
あれはあれで気に入っていて、Xに上げた動画もそれなりに反応があったのですが、もっと面白い、見たことのないようなものにしたいという欲求がありました。波形だけって、よくあるパターンじゃないですか。
そのきっかけを作ってくれたのが、清水亮さんです。4月15日のデイリーAIニュースで「これすごいよ」と紹介していたのがSparkJSという技術。紹介してから数時間後には本人がsparkjs-skill — SparkJS という API リファレンスを Claude Code 用スキル形式に整理したものを公開してくれていて、実例として示されていた使い方が面白かったので、「じゃあ自分のビジュアライザーで試してみよう」となったわけです。


Gaussian Splat とポイントクラウドの話
本題に入る前に、Gaussian Splatting という技術が何なのかを、雑に説明しておきます(Claudeが)。
昔から 3D の世界を表現する方法として「ポイントクラウド」というものがあります。点群、です。空間の中に無数の点を打って、それぞれに座標と色を持たせる。LiDARで部屋をスキャンするとポイントクラウドが出てくる、というのはもうおなじみの光景です。ただし、点は点なので、隙間から向こう側が透けて見えたり、近づくとスカスカだったりします。
これを「点ではなく、ちっちゃな楕円体(ガウシアン)で置いてしまえ」としたのが 3D Gaussian Splatting です。2023年の SIGGRAPH で鮮烈に登場して以来、3D再構成の世界を一気に塗り替えた手法で、要するに、
空間の中に何百万個の「ぼやけた楕円の絵の具スタンプ」を配置する
それぞれに位置・向き・大きさ・色・透明度・視点による色変化(球面調和関数)を持たせる
それをリアルタイムに GPU で重ね合わせて描く
という乱暴だけど驚くほどフォトリアルな手法です。NeRF みたいに推論にGPUをゴリゴリ使う必要もなく、データさえあればiPhoneやAndroidのブラウザでもヌルヌル動く。最近 iPhone でスキャンした部屋が3D空間として AirDrop できたりするのはこの系統の技術のおかげです。
ポイントクラウドが「点描」だとしたら、Gaussian Splat は「水彩画」です。点が輪郭を持たずにふわっと広がるから、間が自然に補間される。
SparkJS とは何か
で、その 3D Gaussian Splat を「普通のウェブ開発者がブラウザで扱えるようにする」のが SparkJS です。World Labs が作っているオープンソースライブラリで、Three.js の上に乗って動きます。
なぜこれが重要なのか。Gaussian Splat は論文と研究コードの段階ではすごく性能が出るけど、実用アプリケーションに組み込もうとすると、CUDA必須だったり、独自ビューワが必要だったり、ファイル形式が乱立していたり、とにかく「研究者のおもちゃ」止まりになりやすかった。
SparkJS はそこを一撃で解決しています。

.ply, .spz, .splat, .ksplat, .sog, .rad と主要フォーマットを全部サポート
Three.js のシーングラフにそのまま SplatMesh として追加できる
手続き的に splat を生成したり(PackedSplats)、SDF(符号付き距離関数)で splat を削ったり照らしたりできる
Level-of-Detail ストリーミング、VR、Dyno という独自シェーダーグラフシステム
要するに「Three.js を書ける人なら誰でも Gaussian Splat をいじれる世界」を提供しているわけで、2010年代の WebGL がやったことに近いもの。研究室から街角へ、という橋渡しです。
そして、このライブラリを使うとき Claude Code に毎回ドキュメント URL を食わせるのが面倒。それを解決するためにと清水さんが作ってくれたのが sparkjs-skill です。~/.claude/skills/sparkjs/ に置いておけば、Claude Code が SparkJS の話題を察知したときに勝手に API リファレンスを参照してくれる。これがないと、途中でAPI迷子になってしまいます(今回も経験済み)。
既存ビジュアライザーに 3D モードを生やす
さて本題。Claude Code を起動して、sparkjs-skill を入れて、こう頼みました。
「既存の2Dビジュアライザーと共存する形で、SparkJS を使った3Dビジュアライザーを足したい」
最初のラウンドで出てきた設計は筋が良かった。要点は以下:
音声グラフを 2D と 3D で共有する(createMediaElementSource は <audio> 1個につき1回しか呼べないので)
window.mazzaiViz というフックを 2D 側に生やして、analyser や現在の歌詞インデックスを 3D 側に渡す
ツールバーに「3D」ボタンを追加、押すとキャンバスが切り替わる
3D 側は PackedSplats で手続き的にスプラット雲を作り、音量で回転・スケール・色を動かす
ここまでは良かった。動かしてみると、たしかに 3D 雲が回る。「おお、Gaussian Splat が自分の曲に合わせて踊ってる!」と一瞬テンションが上がって、次の瞬間、冷静になります。
地味すぎる。
地味問題を潰していく
Bass 値で 1.0~1.25 倍にスケール、rotation.y = t * 0.12、という穏当な設定にしていたのが敗因でした。落ち着いた知的な動きを目指してはいけない、ビジュアライザーはパーティーです。
Claude Code に「もっと効かせて」と言ったら、こうなりました:
帯域値をスムージングして envelope 化、ガタつきを除去
ベースで カメラ半径が縮む(曲の低音で雲に突っ込む感じ)
ボーカルで FOV がパンプ
雲の scale が X/Y/Z で異方性になり、ベースで横に広がり縦に潰れる
回転が3軸になり、それぞれ違う周波数で揺れる
これで一気に「酔うレベル」になりました。でもまだ何か足りない。
最初、Claude Code は textSplats という SparkJS の便利関数で歌詞を 3D 空間に浮かせていたのですが、日本語フォントが解決できずに try/catch で握りつぶされていました。要するに何も出ていなかった。
これは DOM オーバーレイに逃がす判断を下しました。後述する動画書き出しのときに問題になるのですが、とりあえず DOM で表示。
楽器ごとに独立した色が欲しい
ここで一歩踏み込むアイデアが出ました。「スプラット雲を1つではなく、帯域ごとに3つ用意したらどうだ」と。
Bass用クラウド:内側の扁平ディスク、温色(赤~橙)
Vocal用クラウド:中間の球殻、緑~シアン
Treble用クラウド:外殻のキラキラ、マゼンタ~紫
それぞれが独立に recolor と scale を持ち、対応する帯域で光って膨らむ。これが効きました。画面の中に「音が見えてる」感覚が強烈に出るんです。ベースドラムがドンと来たら内側の赤い円盤がフラッシュ、ボーカルの伸びる音で緑のシェルが広がり、ハイハットのシャリシャリで外殻の紫が震える。
これはもう、音楽を3Dで観ているのと言って差し支えない状態でした。
でも色が固定だと寂しい
ここまで作って曲を最後まで通したら、色構成に飽きてきます。「赤・緑・紫」の役割分担は強いけど、それが全曲通して同じだと単調。
というわけで次の指示。「色割り当ては固定せず、曲の進行で変化させたい」
これは気持ちの良い実装になりました。
hueBase というグローバルな基準色相を毎フレームじわっとドリフトさせる
3バンドはそれぞれ hueBase + 固定オフセット(0 / 0.42 / 0.78) で HSL を計算
歌詞行が切り替わる瞬間に hueTarget を 0.11~0.18 ジャンプさせて hueBase を追従させる
これで、曲の進行(歌詞の進行)とともにパレット全体が色相環を回っていく。しかも Bass/Vocal/Treble の相対的な色差は保たれるので、楽器ごとの役割分担は見失わない。静的な色ではなく、「カメラを固定した時計の針が回るように色が動く」感覚です。



3Dモードで動画書き出しが効かない
2D版は MediaRecorder で canvas の captureStream を録っていました。3D 切り替え時もボタンは残っているのに、動画化すると 2D の黒キャンバスが映っている。そりゃそうです、3D キャンバスは別の <canvas> だから。
修正は2点。
visualizer3d.js に beginExportResize(w,h) を追加:WebGLRenderer を 1280×720 に固定し、カメラの aspect も同期させる。復帰関数を返しておいて終了時に自動復元
visualizer.js の exportToMp4 が #viz-stage.mode-3d を検出したら 3D キャンバス側を録る
それと、DOMオーバーレイで出している歌詞は canvas.captureStream で拾えない という罠がここで顕在化します。動画に歌詞が映らない。
なので歌詞表示を THREE.Sprite + CanvasTexture に移しました。canvas 2D API でテキストを描いてテクスチャにして、Three.js のスプライトとして 3D シーンに置く。これなら Gaussian Splat と一緒に録画されます。
結果:3Dモードのまま再生→「MP4で書き出し」を押すと、歌詞付きの3D Gaussian Splat ビジュアライザー動画が降ってくる。
動作確認のためにサンプル曲「雨のように消えていく」を再生して書き出したら、なんとも言えない良さになりました。赤い核、緑のシェル、紫のキラキラが、歌詞の進行に合わせて色を変えながら回転している。Gaussian Splat のふわっとした輪郭が、2Dのパーティクルにはない湿度を画面に与えている。
実装ログから見えたこと
全部で 500~600 行の追加で、このレベルの 3D ビジュアライザーが組めてしまう。これは SparkJS が良くできているからでもあるし、Claude Code に「意図」を伝えれば勝手に API を引いてきてくれるからでもあります。
なお、今回の実験環境は M2 MacBook Air の 24GB モデル。「24GBあるならそりゃ快適でしょ」という話ではなく、ブラウザ上で Three.js と SparkJS を走らせる分には 8GB でもたぶん変わらないよ、というのが本音ではあります。ただ、Claude Code が並行してモデルを走らせたり、複数ファイルのコンテキストを抱えたりする作業では、やはり余裕があるに越したことはなかった。
そして今回、sparkjs-skill を使った効果は明確でした。スキルなしだと textSplats のシグネチャを毎回確認させられるし、SplatMesh の recolor と opacity の関係も忘れがちです。スキルを刺しておくと、会話の最初の数ターンで「SparkJS 文脈の頭」に切り替わっていて、レビューやリファクタの精度が明らかに違う。
これは、LLM時代の「ライブラリのエコシステム」の新しい形だと思います。ドキュメントは人間が読むためのもの、スキルは LLM が読むためのもの、ソースは機械が実行するためのもの、という三層構造。SparkJS のような「橋渡し型ライブラリ」にこそ、対応するスキルの存在が効いてくる。清水さんがデイリーAIニュースで紹介してその日のうちにスキルまで公開してくれたおかげで、こういう実験がすぐできてしまう。ありがたい話です。
雨の中の涙のように
前回作った2Dビジュアライザーに込めた感傷 — 雨の中の涙のように消えていく表現の道具たちへの追悼 — は、実は 3D化した瞬間、少し違うものに変わりました。



2Dの平面で流れていく歌詞と波形には「記録」のニュアンスがあったのですが、3D Gaussian Splat の中に文字が浮かび、楽器ごとの色の雲が空間を回る様子は、「記録」というより「再生」に近い。消えたものを悼むというより、消えたものが別の次元で踊り続けている、という感じ。
ここではステム分離を行わず、周波数区分だけで3パートに分けていますが、MacBook Neo版ビジュアライザーではちゃんと4パートのステムにしているので、そちらに実装したらもっと面白くなるかもしれません。ボーカルのピッチ追随もやってるし。
Gaussian Splat が持つ「点ではなく、にじみのある存在」という質感が、たまたまこのモチーフに合っていたのかもしれません。技術というのはしばしば、作る前に想像していた意味の外側に連れていってくれるものです。
自宅に戻って、今度はMacBook Neoにこのプログラムを組み込んでみました。MacBook Airで仕様書を作って、それをNeoのmazzaineoで読み込ませ再実装。同じような間違いを踏みながらも動作まで漕ぎ着けました。こちらの方が、もともとステム分離やLRC歌詞アライン機能を実装しているので、より高度なことができるんですよね。
8GBというメモリだけが不安材料でしたが、何の問題もなく動作しました。

そして、もっとド派手なものにしたかったので、Claude Codeに追加でいろいろと注文。テキストの重なり、色や形状の変化などを改善しました。
Claude Codeによれば、実装したのはこんなものらしい。
ビジュアライザー 3D (SparkJS / 新機能)
基盤
- Three.js 0.180 + SparkJS 2.0 のimportmap統合
- visualizer3d.js ES module — window.mazzaiViz3D API公開
- window.mazzaiViz 共有インターフェース(2D→3D音声解析ブリッジ)
- 3Dトグルボタン、mode-3d CSS切替、録画時の3Dキャンバスソース対応
3帯域 Gaussian Splatクラウド
- 各帯域を3サブクラウドに分割、6種の分布形状(sphere/disk/torus/filament/spiral/nebula)
- サブクラウド間の位相オフセットで合成形状が常に変化
- 非一様スケールウォブル(X/Y/Z独立に異なる周期で伸縮)
SDF彫刻
- 各帯域に3つのSplatEdit SDFオービター(sphere/ellipsoid/box)
- クラウド内を周回し穴・えぐれ・裂け目をリアルタイム生成
- carver半径がオーディオで脈動
派手なカラーリング
- HDRオーバードライブ(recolor値>1.0で白熱発光)
- ACES Filmicトーンマッピング + 動的露出(ビートでフレア)
- 全帯域彩度MAX(hueSat: 1.0)
- 色変化3倍速(driftPerFrame/lyricJumpBase増)
- トレブルシマー強化(18Hz/振幅0.4)
- ドラムonset全体フレア(1.8倍)
- バースト600粒、HDR 2.5倍フラッシュ、パレットhue連動
- スプラット個体のhue/sat/lightランダム変動
歌詞スプライト
- CanvasTexture → THREE.Sprite(DOM不使用 → MP4書き出し対応)
- renderOrder: 9999で常にスプラット手前
- 行ごと色分け(8色hue巡回)、色付きグロー + 白い芯
- ±5°傾き + 時間揺れ
- 横断・Y揺れ・呼吸スケール・三角エンベロープフェード
全パラメータ リアルタイム調整可能
- mazzaiViz3D.params.* でDevToolsから即時変更


このSparkJS、清水さんはシューティングゲームの面白いのを作ってますが、まだまだアイデア次第でできそうです。
※この記事は、Claude Codeでの記録、Claudeでのリライトを経て、筆者による加筆・修正を行なっています。












