javascriptの勉強をするにあたって、Electronというものを使っている。
ネット接続せずにスタンドアローンで動く何かを作りながらがいいだろうと思って、jsだけで簡単なゲームを作ろうと思った。色々書いているうちに、ゲームにはサウンドが必要だろう、ということで極めて短いコードでサウンド再生の知見を得ようとした。
1.Electronを立ち上げると、猫画像が表示され、neko.mp3が流れる。
2.エンターを押すと、犬画像に切り替わり、inu.mp3が流れる。
極めてシンプルな仕様だが、これで発狂した。
調べるとjsにはaudioというオブジェクトでローカルファイルを再生できる。
サンプルなんかを見ると簡単に見えた。
function soundPlay (path){
let music = new Audio(path);
music.play(); // 再生}
画像切り替えをトリガーにして、ローカルファイルのパスを渡して再生するという形だ。これをそのまま実装するとどうなるか。猫画像から犬画像に切り替えたときに、二重に音楽がなるのだ。neko.mp3とinu.mp3が同時に鳴る。
そりゃそうか、Audioオブジェクトを新しく作っているんだからそうなる、と思って、じゃあ音楽を止めればいいじゃないか、などと安直にobject生成の前に、music.stop()とか書くと、Electronの立ち上げのときに構文チェックに引っかかる。
obuject生成前にobject操作してませんか?というわけ。
そりゃあそうだ、鳴っていない音楽を止めるなんて不可能だ。
だけどじゃあ音楽をどうやって止めるわけ?
ここらでちょっとジョジョっぽい顔になっていたと思う。
「object操作はできない"はず"ッ!!なら、どうして音楽が"鳴って"いるんだァァァアァァ!」
そもそも僕が実装してきたのは、企業内のインフラ的なソフトウェア製造であって画像取扱もサウンド取扱もろくにしたことがない。objectの扱いもサーバーサイドで処理したものを画面に表示し、画面で操作された値をサーバーサイドに持っていくということの繰り返しだった。
「objectってなんだ??!???!?」
jsのレンダリングとobjectの誕生からその死にいたるまでを勉強すればいいのか?
jsは処理が終わればすべてのobjectって破棄されるんだっけ?
例外的に残るobjectとかあるっけ?
objectの操作状態の保存方法とは?
音楽を再生させるjsの仕様と、objectの保存は関係ないということなのか?
少なくともobjectが破棄されたあとも音楽は鳴り続けるというならば、audio.play()は音楽ファイルを再生するだけのボタンみたいなもので、その後ろで音楽を鳴らし続けるという処理はjsとは無関係の、chromiumの仕様が効いているという感じだろうか。
色々と調べると、バイナリとかバッファとかいう言葉がでてくる。
音楽ファイルをArrayBuffer型に変換して、さらにBASE64形式にすればいいんだよ、とか簡単に言ってくれるな。それをしてLocalStorageに保存して、必要なときに取り出して再変換すればいいだってさ。音楽を鳴らしたと同時に、その情報をバイナリとして保存。犬画像に切り替えたら情報を取り出して止める。
理屈はわかる。コンピューター上のすべての電子データはつきつめれば01の2進数(バイナリ)だ。音楽だろう画像だろうとバイナリファイルにしてしまえば、一時保存領域においておくことができる。
BASE64って??
なんで音楽データの場合はその形式が必要なの??
ArrayBufferって??
一発でバイナリに変換できないの??
調べると、出るわ出るわコンピューターサイエンスの基礎領域。
僕がPHPerとして見て見ぬ振りをした領域がドンドン掘られてくる。
ああ、そうだ。
意識高い気分になったとき、プログラミングを深く勉強しようと思ったとき、たまに見ることがあった深淵の入り口だ!
深淵の入り口に入ることをやめたぼくは更に検索を続ける。
async/await処理内で使えるarrayBuffer関数とdecodeAudioData関数を使うことで、再生されている音楽を止めることができた。ここでもなんで通常のfunction内だと使えねんだよ、ということは無視した。async/awaitの仕様と密接なのは想像できる。通常の処理終了後に破棄されるobjectとは違うのだろう。
通常のfunction内にasync/awaitを書くと、音楽objectが持続して使えるようなことになっている。これも知見をquiitaから引っ張ってきて改造したものだ。理解していない。
問題なのは画像切り替えだけでなく、複数の操作をしたときだった。例えば音楽Muteボタンをつけて、neko.mp3が鳴っている最中にMuteしたあと、犬画像に切り替えるとどうなるか、とか。neko画像を複数枚用意して、猫画像の間は音楽を切り替えないとか。エンターボタン長押しで画像を高速スキップした場合、などなど。
3日ぐらいかけたが、ゲームに普通にある仕様を追加するたびに壁にあたった。
jsの処理時間のせいか、エンター長押しで画像高速スキップすると、鳴るはずのない音楽が2重に鳴ったり、muteボタンが効かなくなったり、何度押しても音楽が鳴らなくなったりする不具合がでた。それにループ再生も実装していないし。
これだけ時間かけても自由自在にサウンドを取り扱えていることもない。ES6の範囲で実装するという点だけは満たしている気がする。プラグイン型のライブラリを探してインストールしないだけ進歩と言えるか?
promiseで処理順を決めても、async/await内のサウンド再生あたりでコケているっぽくて、サウンド再生後に画像切り替えという手順も作れていない。ここでpromiseというものの仕様や、避けているasync/awaitがどんなものなのかを理解すべきなのだろうが脳が拒否する。
何も作ってないし、何も知見を得ていないように思える。何かを作る目的があれば、手段を選ばずに実装するんだけど、ここで適当なライブラリで解決するというのは負けた気がする。やりたいことはそうじゃないしね。
複雑な環境汚染を引き起こしながらプログラミングをするのではなく、なるべく言語仕様一本で実装してコーディングの確信を得ることが目的だからね。どんな状況になってもその言語の標準仕様だけで、目的を達成できたならそれが完全な自由だ。
そうするとコンピューターサイエンスの深淵からマッドハンドがニョキニョキする。
深淵をのぞく時、やっぱり深淵をのぞきたくないのだ。