まえがき
シリーズ化するつもりで始めた 薄毛のエンジニアが〜 なのですが、最初の1本記事を書いただけで3ヶ月ほど間が空いてしまいました。いかんせん連休にならないと実装が進まないうえ、途中で迷いが生じて育毛しかけたりしていたのが原因です。
さて、そんなわけで今回も連休ということで前回と同様、またニッチな方面のソフトウェアを作ってみました。
- Emscripten を使って、ブラウザ内で MSX の音をレンダリング再生
って意味が分からないですよね。
いや、おっさんプログラマの同志は分かる...?
MSX というのは今から30年ぐらい前のコンシューマ向けパソコンです。こいつと出会わなかったら私は理系になることもエンジニアになることも薄毛になることもハゲになることもなく、普通にリア充として一生を終えたのではないかと思います。そんなパソコンです。要するに MSX については Wikipedia に詳しく書いてあります。
さて、この MSX には 矩形波で硬派な PSG音源 AY-3-8910 や YAMAHAのFM音源史上最弱の YM2413 など、制御が簡単なかわりにお値段が安く、ショッキングなほどチープな音が出る音源LSIが搭載されていました。
YouTube を検索したところ YM2413 の こんな動画 が出てきました。お聞きいだければ分かるように、SIN波に毛が生えた程度のしょっぱい音です。しかし油断はできません。何度も聴いていると癖になり、徐々にこのわびさび感のある音が良くなってくるのです。騙されたと思って3回聞いてみてください。数年後、ふとした拍子にもう一度聞きたくなります。中毒性があります。遅効性です。
ほにゃららミュージックとかいうモダンなサービスが跋扈し、音楽ストリームしまくりの現在からは信じられないかもしれませんが、80年代はこんな音でもなんだか驚くほど未来感があったのです。だって当時は8bit時代。メインメモリが64KBしかなかったのです。KBですよ。44100KHz 16bitのwavファイルだとせいぜい0.3秒分。そんなパソコンで音楽が演奏できる、さらには音色をエディットできるってだけでそれはもうワクワクしたものなのです。
作ったもの
さて、前置きが長くなりましたが、今回はそんな骨董パソコンの中毒者愛好者や、当時興味があったのに波に乗り遅れて触らずじまいで今さら後悔している方や、上司が物理モデルベースのVSTシンセ音源について語ってくるのがチャラくて嫌です、2OP FM音源の教養を身につけてこらしめてやりたいと思っています。といった人々(以上すべて妄想)のために、MSXの音源をブラウザでリアルタイムに鳴らす MSXplay.js というページを作ってみました。
閲覧はある程度 CPUパワーがある PC か Mac で Chrome か FireFox を推奨します。 Safariも可。Edgeはたまにすごい重くなることがあるけどなんとかセーフ。 IEにはまったく対応していません(対応できません)。 スマホだと Nexus 5X の Chrome では動作しましたが iPhone はノーチェックです→動きました。
実際の音楽プレイヤー画面は下のような感じです。サイトにはサンプル曲を置いてありますので再生ボタンを押せばすぐ80年代な音が鳴ります。
もし昔の MSX用 音楽データを持っている人がいれば、直接 ファイルをドロップすれば演奏できます。ブラウザ内で完結しているので、サーバーにファイルを送信することはありません。スピーカーの音量にさえ注意すれば、アニメの曲でもなんでも安心してドロップしてください。家族や親戚やGoogleやAmazonやFacebookの中の人には分かりません。
余談ですが、私は薄毛であることがAmazonにもGoogleにもFacebookにも完全にバレているようで、ネットサーフィン中は常に育毛や増毛の広告に追いかけられるというハラスメントを受け続けています。ひどい時代です。
ところで、音楽を再生するだけだと mp3 をちんたらストリーミング再生しているのと何も体験が変わりません。せっかく音楽をリアルタイムにブラウザで合成している恩恵がまったく感じられないのです。ということで、テキストで書かれた演奏データ(MML)をコンパイルして → MSXの音で再生する、というページも作ってみました。
細かい使い方は割愛しますが、エディタに MML を入力して(サンプル曲の MML を選択もできます)コンパイルボタンを押すと MSX な音楽が流れて幸せになることができます。
なお MMLのコンパイラ部分は MSX-DOS 用のコンパイラ コマンド MGSC.COM を JavaScript で動かしています。つまり MSX-DOS の エミュレーションをやってます。このへんのソースは mgsc.js の GitHub にあります。
使った技術と、分かったこと
Emscripten
Emscripten は C言語のソースコードをコンパイルして JavaScript を吐くコンパイラです。出力形式が JavaScript なのが変わっていますが、その他はおもいっきり普通にCコンパイラです。 emcmake
ツールを使えばCMakeでビルドができるので、10年前ぐらいにC言語から離れてしまったような人など(私がそう)はビルドの簡単さに唖然とすると思います。
過去にC言語で作った MSX の音源エミュレーションライブラリ libkss があり、今回はこれを JavaScript化するために Emscripten を使いました。
Emscripten でコンパイルしたコードの動作速度については、今回作ったサイトが良いベンチマークになる気がしています。ざっくりですが、 FireFox の asm.js 対応は確かに高速で Chrome の倍ぐらいのパフォーマンスが出ているようです。
Web Audio API
Web Audio API はブラウザで音声信号を合成・出力するためのAPIです。
JavaScript で計算した波形を再生するには Web Audio API の ScriptProcessorNode を使います。 ScriptProcessorNode は定期的に audioprocess イベントを発行するので、このタイミングに合わせて JavaScriptで一定時間分の波形をレンダリングすれば、後はブラウザが勝手に音声として再生してくれます。
しかし、実際に audioprocess イベントのハンドラ関数を実装してみたところ、 Chrome ではハンドラ内で重い処理を実行すると、 ScriptProcessorNode が無応答(audioprocess イベントが永久に発行されなくなる)になる現象が頻繁に観測されました。バグのような気もしますが、ブラウザ側の意図的なハングアップ回避ロジックかもしれません。正確な原因は不明です。
いかんせんこのままではまともな再生ができないので、 MSXplay.js では audioprocess ハンドラ内で波形を合成することを諦めました。代わりに、setInterval で定期的に音声レンダリング処理を呼び出し、音声をバッファに貯めておきます。audioprocess ハンドラではバッファの読み出しのみおこなっています。
ところで setInterval はブラウザがバックグラウンドに回ると呼び出しが低速化します(1秒に1回ぐらい)。そのあたりを考慮して今回の実装では一回の setInterval 呼び出しで計算する波形の量を動的に増減させています。
以上のとおりだいぶワークアラウンドっぽい実装にはなりましたが、いちおう Chrome, Firefox, Safari, Edge すべてで動いているようです。
なお ScriptProcessorNode は 仕様上は既に deprecated であり、 今後は Audio Worker (マルチスレッドで音声レンダリング) という仕組みに変わっていくようです。 Audio Worker が実装されれば、上記のようなややこしい実装は不要になると思われます。
ところで、 Web Audio API の実装はブラウザごとにかなり差があるようです。今回実装中に気がついたことを列挙してみると、
- Chrome は BiquadFilterNode の入力に undefined 値が入った Float32Array を突っ込むと BiquadFilterNode がハングアップ(それ以降音がまったくでなくなる)する。
- Chrome や FireFox は GainNode の出力にリミッター(コンプレッサー?)が入っているが、Safari は入っていない。音量ゲインが大きすぎると普通に音がオーバーフローしてボロボロに。
- BiquadFilterNode のフィルタはパラメータが同じでもブラウザごとに音がぜんぜん違う。
などなど。特にレンダリングされる音がブラウザによって違ってくるのは、根本的な部分で互換性に問題がある印象です。
おわりに
ネットで検索してもなかなか情報が少なく、敷居が高い雰囲気の Emscripten ですが、実際つかってみると思ったより簡単でした。また、最新のブラウザと組み合わせると、なかなかどうして速度も出るので気持ちが良いです。みなさんも機会があればぜひ。