浅草寺サウンドスケーププレイヤーの開発
GPXデータから歩行ルートを地図上に描画し、録音した環境音をリアルタイムで解析・可視化するプレイヤーを作った。UIはMGSのソリトンレーダーをモチーフにしたミリタリーテック調。JSXデモから始めて、最終的にNuxt 3のVueコンポーネントとしてデプロイするところまで進めた。
JSXデモからHTMLデモへの変換
出発点はReact/JSXで書かれたプロトタイプだった。GPXデータのパース、Leaflet地図上へのルート描画、音声再生の3つを統合する必要があり、まずは依存を減らすためにプレーンなHTMLデモに変換した。
Leafletの地図タイルはCartoDB Dark Matterを継続使用。GPXデータからポリラインを描画し、再生位置に連動してマーカーが移動する仕組みを組んだ。
Python音声解析パイプラインの構築
録音データの特徴量を事前に抽出するパイプラインをPythonで構築した。
librosaによる基本解析
- dB計算: RMS値からデシベル値を算出し、時系列データとしてJSON出力
- スペクトログラム生成: mel spectrogramを画像として書き出し
PANNsによる音分類
大規模事前学習済みの音響イベント検出モデル(PANNs)を導入した。AudioSetの527カテゴリで音を分類できる。人声、車両、鳥、風など環境音の構成要素を時間軸に沿って可視化するのが目的。
モデルファイルは312MBあり、ダウンロードで一手間かかった。Windows環境にはwgetがなく、urllibで手動ダウンロードして対処した。
# PANNs推論の核心部分
# 527カテゴリの確率を時間窓ごとに出力
framewise_output = model(waveform)['framewise_output']
解析結果はすべてJSONファイルとして書き出し、フロントエンドで読み込む構成にした。リアルタイム解析ではなく事前計算方式を選んだのは、ブラウザ側の負荷を最小限にしたかったため。
MGSソリトンレーダー風UI
UIのコンセプトはMetal Gear Solidのソリトンレーダー。回転するスキャンライン、同心円のソナーリング、PANNsで検出した人声に連動するアラート表示を組み合わせた。
コックピットレイアウト
画面を左右に分割し、地図が2/3、計器パネルが1/3のコックピットレイアウトにした。計器パネル側にレーダー、dBゲージ、スペクトログラム、タイムラインを縦に配置する。
フロー型情報(リアルタイムに変化するもの)とストック型情報(全体の概要)を意識的に分けて配置した。
- フロー型: dBゲージ、レーダー(現在の音環境)
- ストック型: スペクトログラム全体像、サウンドレイヤータイムライン
dBアークゲージ
当初はバー型のdBメーターだったが、コックピット感を出すためにSVGアークゲージに変更した。stroke-dasharrayとstroke-dashoffsetで弧の長さを制御する方式。
スペクトログラムの表示方式
最初はリアルタイムに描画していたが、全体のプリレンダリング画像 + 白線カーソルの方式に変更した。音声全体の構造が一目で分かるほうが、散歩の記録として見返すときに有用だと判断した。
サウンドレイヤータイムライン
PANNsの分類結果を横軸に時間、縦軸にカテゴリで並べたタイムライン。2つの表示モードを実装した。
- 全体表示: 録音全体の音構成を俯瞰(白線カーソルが現在位置を示す)
- コンポジション: 現在位置周辺のリアルタイムバー表示
アニメーションの調整
レーダーのスキャンラインやdBゲージの針など、生データをそのまま反映すると動きがカクつく問題があった。lerp(線形補間)で値をスムージングし、CSS transitionと組み合わせることで滑らかな動きにした。
// lerp補間でスムージング
const smoothed = prev + (target - prev) * 0.15;
音声シーク問題の解決
開発中にシーク(再生位置の変更)が効かない問題にはまった。原因はサーバーがHTTP Range Requestsに対応していなかったこと。音声ファイルをfetchしてBlob URLに変換する方式で解決した。
const response = await fetch(audioUrl);
const blob = await response.blob();
audio.src = URL.createObjectURL(blob);
全体をメモリに読み込むので大きなファイルには向かないが、数分程度の散歩録音なら問題ない。
MGS風HUDオーバーレイ
画面四隅にMGS風のHUD要素を追加した。
- SIGINT: 音声信号の検出状態
- NAV: GPSナビゲーション情報(座標、速度)
- CONTACT: PANNsで検出した音源のコンタクト情報
ステータス表示は「トランスポート状態」(再生/停止/シーク)と「音環境状態」(dB、検出カテゴリ)を分離した。再生操作と環境情報が混在すると視認性が下がるため。
配色の統一
全体をMGS風のミリタリーテック色調に統一した。暗い背景にグリーン系のアクセント、走査線風のオーバーレイ。Leafletの地図タイルもDark Matterなので全体のトーンが揃う。
Nuxt 3 Vueコンポーネントへの移植
HTMLデモで固まった実装を Player.client.vue としてNuxt 3に移植した。.clientサフィックスを付けることでSSR時のLeaflet/Audio APIエラーを回避している。
移植時のポイントは、解析済みJSONファイルをR2(Cloudflare R2)に配置し、プレイヤーが非同期で読み込む構成にしたこと。音声ファイルもR2にアップロードした。
conceptサイトへのデプロイ
R2への音声ファイル・解析JSONのアップロードを行い、conceptサイトに統合してデプロイした。浅草寺の散歩録音をサンプルデータとして使用。
ふりかえり
事前計算方式にしたことで、フロントエンドはJSONの読み込みと描画に集中でき、実装がシンプルになった。PANNsの527カテゴリは粒度が細かすぎるので、表示時にグルーピングする処理が今後必要になりそう。
MGS風UIは遊びで始めたが、環境音の監視・解析という文脈に意外とマッチした。ソリトンレーダーの「周囲を探知する」というメタファーが、環境音プレイヤーの目的と重なる。
Blob URL方式でのシーク対応は、Range Requests非対応環境でのワークアラウンドとして覚えておきたい。
次のステップ
- PANNsカテゴリのグルーピング(527 → 10~15の大分類)
- 複数スポットの切り替え対応
- モバイル表示の最適化(コックピットレイアウトの縦積み化)
- 歩行速度と音環境の相関分析