手持ちのゲーム音楽ファイルをひたすらループさせるWebアプリを作った。
以下はなんで作りたかったのか、 どんな技術を用いたり勉強したかったのか、 今後どういう風に作り込んでいくか等をつらつらと書き並べていく。
そもそも何故作りたかったのか
しかし、それでも集中を阻むもの。 それは「普通のサウンドトラックの曲データは、ゲーム内BGMのように、ループ再生ができない」ということ。 いくら戦闘曲とはいえ1ループはそれほど長くなく、だいたい1分ちょっとで終わってしまいます。 サウンドトラックのデータは、大体2~3ループぶん収録されてますが、それでも2,3分で終わり。 この曲の切れ目の無音期間と、再度イントロから始まってしまう感覚が、どうしても集中力を削いでしまいます。
まさにこれ。 一回フェードアウトして音楽が途切れ、イントロが大音量で流れ始めるあれで集中力の8割は持って行かれるのだ。 職場のメンバーにいくら力説しても全く理解が得られなかったが、探せば同じ事考えてる人間は結構居るものだ。
一度HTML5のAudioのcurrentTimeをいじるという手法で作っていたのだが、 退職と同時にクリーンインストールしたことでそのHTMLファイルやスクリプトが全部削除。 まぁいいや、もう一回作り直そうとなった次第。
どんな技術を採用したり、習得したかったのか
既に一度作っているというのもあり、 LiveScript様のパワーを借りれば1日程度でこしらえる事は可能なのだが、折角なので勉強になる事がしたかった。 従って、要件は以下の3つ
- AltJS、静的サイトジェネレータのベストプラクティスの模索
- 波形データを可視化してループ時間を設定したかった
- React でページ構築をしたかった
AltJS、静的サイトジェネレータのベストプラクティスの模索
今時素のHTML、CSS、JSを駆使してWebサイトを作るなんてありえない。
前職はLiveScriptを用いて開発していたのだが、
Gulpにどっぷり浸かる生活、あの毎回gulp watch
のコンパイルに成功したのを見届けないとブラウザーのF5を叩けないとかありえんだろ。
というで、職場全体では新プロジェクトは色々改良を重ねていたが、自分が先陣でプロジェクトを立ち上げるという訳でもなかった為、他人の作ったシステムを文句を言いながら利用する立場であった為、使い勝手や設計にいまいちしっくり来ていなかった。
- HTML: Pug (いつもの)
- JavaScript: LiveScript (いつもの)
- CSS: Stylus (いつもの)
どうしたかに関してはloop-bgm/bin/serve.lsを参照。
要するにExpressでローカル用のWebサーバーを構築して、
localhost:3000/index.html
にアクセスが飛んできたら、
assets/templates/index.pug
を探しに行って、ファイルが存在したら都度コンパイルして返すような仕組みを作った。
ちょっとでもファイルがなかったりするとエラーが出まくるが気にしない。 ローカル開発環境でしか使わないからね。 前の職場では本番環境用のスクリプトにこのような構造があり、環境変数やif文でゴリ押しする仕組みだったのが気に入らなかった。
波形データを可視化してループ時間を設定したかった
Web Audio APIにおいて, サウンドの視覚化のために定義されているのが, AnalyserNodeクラスです. そして, インスタンスの生成には, AudioContextインスタンスのcreateAnalyerメソッドを利用します.
上記の通り、音楽ファイルの波形データはJavaScriptで取得可能。 時間が出来たら作って盛り込みたいと思っていたので、今回の必須要件に追加。
React でページ構築をしたかった
職場ではAngularJS(1系)とPolymerを触っていたが、イマイチしっくり来ていなかった。 うーん、乗り換えるならVueかReactかな。 個人的には流行りモノに流されるようでReactは乗り気ではなかったのだが、 React Nativeを発見。 覚えておけばちょっとスマホ用であんなアプリ作りたいという欲求が生まれた時に対処しやすくなる。
作業ログ
真っ先に作ったのはloop-bgm/bin/serve.lsとassetsディレクトリ 起動しておけば一々眠くなるHTMLやJSを書かずに済む。
AnalyserNode - WEB SOUNDERのページ下部にあるデモ13をそっくりそのまま写してしてLiveScriptに手動変換。 ロジックの解析をしつつ波形データを加工する方法を勉強。 因みにSVGを採用にしたのはCanvasと違って再利用が簡単な事が分かっていた為。
さて、今度はReactの導入 心の師匠raccy氏が作った記事React.jsチュートリアルをLiveScriptで書いてみる - Qiitaをまずは模写。 なるほど、JSX臭がかなり緩和されて読みやすく書きやすい。 これを読み解いた後、ループBGMのページを構築していたのだが、ここで大問題発生。
Reactは親子関係でしかデータを持ち運べないので、 レンダリングした波形データのすぐ側を再生バーが通りかかろうとした時に何度もSVGを更新しまくってブラウザがフリーズ。 値が変わるとレンダリングし直しだからね、シングルトンで値を管理出来れば親子関係に縛られずに済んでパフォーマンスの改善が簡単になるのだが…というわけでFluxの必要性に気が付き座学再開。
暫くFluxを調べていたが、どうも記述がバラバラでしっくり来ない。 どうやらFluxとは概念的な部分が強く、それを簡略化して具体的にしたReduxという状態の管理手法がReactでは一般的になってるのだとか。 Reduxのチュートリアルを一通り眺めつつ写経して完成。
なんでReactは関数型プログラミングって点をごりプッシュしてるのにクラスが山ほど出るんだよ。 React v0.14以降はStateless functional componentsという書き方が出来るようになり、推奨されているのだとか…こっちで統一するか。
という流れを経て第一号が完成した。
TODO
今後やりたい事は以下の2項目
- パフォーマンス向上
- 自動ループ検索
- OGGファイルの書き出し
パフォーマンス
まずSVGのレンダリングが超絶重いのでなんとかしたい。 20msで1pxを無理やり縮小してるんだよなぁ…ここのレンダリングをもう少し賢く改造するだけで全然違うと思う。 でもまぁ、優先順位は低め。
優先順位が高いのはAudioBufferSourceNodeを使う案。 今はAudioタグの一定時間がすぎるか否かを虎視眈々とイベントループで待ち構えている。 従って、ブラウザのフォーカスを外す等してCPU優先順位が下がると途端にループをサボり始める。 無理に見張っても重い原因にしかならないので、Audioタグの廃止を試してみて簡単にループが出来る事が分かればそちらを採用する。
自動ループ検索
自分で波形データを見ながらループ位置を探す行為はかなり捗る。 しかし、折角数値で見れるのであれば、JavaScriptに勝手に探してもらって候補を持ち帰って来ればいいじゃん。
既にプロトタイプの検索機能は実装済みだが動作未検証。 あまり重くならないように注意しつつ実装出来ればと思っている。
OGGファイルの書き出し
OGGのそもそもの仕様にはないらしい(未確認)のだが、 拡張タグでLOOPSTARTやLOOPLENGTHをサンプル数の数値で指定すると、ループの設定が行われるらしい。 RPGツクールVXやFalcomのゲームで採用されており、検索するとそれ絡みで結構な記事がヒットする。
しかし、普通のプレーヤーには実装されていない。 ゲームミュージックに関心のある一部のエンジニアが不便に思って自作しているようだ。
Macのプレーヤーが見当たらないわけだが、OGGのライブラリは無償公開されているのでCLIのプレーヤーでもこしらえればいいか。 その場合C言語を触ることになりそうなのでかなりハマりそうだが…
そのOGGの音楽ファイルをどうやって用意するんだとか、タグの設定どうすんだという問題が残る。 AndroidはデフォルトのライブラリでOGGが再生出来るが、iTunes等はAACを採用している関係でOGGはノータッチ。 Windowsも再生するには専用のコーデックを落としてくる必要ありと普及はまだまだ。 要するに、CDからリッピングしたりiTunesからDLする等で用意した音楽データはMP3やAACが大多数なわけだ。
結局波形データで編集するアプリ立ち上げて自作かよ、しかもOGG使うならこのWebアプリ意味ね~じゃんという話になる。 ところが、JavaScriptはHTML5になってバイナリデータをいじれる様になり、C言語からトランスパイルすることが可能だ(らしい)。
従って、こんな風に純JavaScriptでOGGファイルをエンコードするアプリも存在する。 higuma/ogg-vorbis-encoder-js - GitHub
まとめ
この流れが実現出来れば、後からゲーム音楽に興味を持った人にとってかなりの足しになるんじゃないかな これをゴールに頑張ろう。
- MP3やAACの音楽ファイルをこのWebページで開く
- ループ箇所をサーチ
- 128kbps程度のOGGフォーマットに変換