ブラウザでMMLを演奏するChrome拡張作った

ブラウザでMML(Music Macro Language)の文字列を選択してCtrl-Cを押すと音楽が流れるChrome拡張を作りました。
Googleウェブストアからインストールできます。
https://goo.gl/0UJQcT
ソースはこちら
https://github.com/aike/TSSCCjs
実行例
http://youtube.com/watch?v=o-IzrXaqIbM


今から10年くらい前、ネットの掲示板上でMMLによる音楽が盛り上がったことがありました。添付ファイルなどではなく、掲示板のテキストとして直接貼られた10行程度のMMLでも驚くほどリッチな楽曲が再現できるため、いかに短いMMLでリアルな音楽を表現するか、技術の競い合いのような文化が生まれていました。

 

当時、再生ツールとして使われていたのが、keimさんが作られたTSS Clipboard Player(TSSCPまたはTCP)というWindowsMMLプレイヤーです。これは、とよしまさん制作のT'SoundSystem(TSS)という、PSG、ファミコンMSXFM音源などの音を再現できるサウンドドライバを利用したものでした。Ctrl-CでクリップボードMMLをコピーするだけですぐに音楽が再生されるTSSCPのあまりの使いやすさと、TSSの音の良さに支えられて、掲示板上のMMLは大変盛り上がったようです。(自分は後から知った口なので、当事者ではないんですが)

 

その後、別の方による偽TCPというツールも出たりしましたが、基本的にTSSCPMMLデファクトスタンダードとなって、その上に拡張機能を追加したものになっています。

 

月日は流れ2016年、Web Musicハッカソン#5で、いまやWeb Audio/Web MIDIのキーマンである、とよしまさんにお会いすることができたので、
「TSSのWeb Audio版ってないの?」
と聞いてみたら、
あるよ
とのこと。つまり、OSにかかわらずブラウザのJavaScriptMMLを再生できる!

 

「じゃあ、クリップボードAPIMML演奏するツールもあるの?」
MMLの方言がいろいろ違うし、それはまだなくて……」
え、そうなの? MMLの方言くらいなんとかなるでしょ、ってことで作りはじめたのがこのツールです。つまりTSSCPのWeb版クローンです。

 

実装してみたら、意外と大変で。ブラウザのクリップボードAPIはセキュリティのために、コピー操作時点でのクリップボードの内容が取得できないことが判明。ぐぬぬ
まあ、キー操作と選択文字は取れるので、Ctrl-Cを押されたときに選択されている文字を取得すれば同じことはできるか。*1

 

MMLも難関です。
やるべきことはTSSCP形式のMMLをTSS形式のMMLに変換してTSSに渡すだけだし、どうせMMLの方言といっても、不等号の向きとかそんな程度だろうと思ってたら全然違いました。TSSCPMMLは圧縮効率を重視したさまざまな拡張がされていて、行の終端は改行ではなくセミコロン、#A〜#Zまでマクロ定義、#FMマクロでFMアルゴリズムの柔軟な定義、といったTSSにない機能を持っています。

 

たとえばTSSCPMMLは以下のようなものです。

#TITLE <sample>;t150;#A=%3v15o5cdefg;#FMB(A);A;A;

これを、TSSで再生できるようにするには以下のようなMMLに変換する必要があります。#FMマクロは、MMLの中でもさらに別のシンタックスを持った言語と考えられ、専用のパーサを別途用意する必要がありました。

#TITLE <sample>
#CHANNEL 3
#A t150
#B %3 @i0,0@o1,0v15o5cdefg
#C %3 @i3,0@o0,0v15o5cdefg

 

また、通常のマクロも、A(7)とすると、5度(7セミトーン)上に移調してマクロ展開するなど、高度な機能があり、これらを忠実に実装しなくては当時のMMLを同じ音で再生することができません。
さらに、偽TCPの拡張命令も難題です、これはTSSでは再生不可能な機能を実装していたりするので拡張コマンドを無視するしかないのですが、MMLは命令同士がセパレータで分割されておらず、また命令の文字数も1〜数文字とまちまちであるため、無視するにしても、ここからここまでが拡張コマンド、というように認識する必要があります。

 

結局、簡易的な文字列処理ではなく、しっかりパースして抽象構文木を生成してから変換するちょっとしたトランスパイラのプログラムになりました。

 

だいたいここまでで、当時のMMLは再生できるようになりましたが、まだ同じ音色になりません。よく調べてみると、JS版TSSのバグで発音数制限があったり、JS版ではmlコマンドがサポートされていなかったり、ということあったので、JS版TSSのソースに手を入れて調整した結果、ほぼ同じ音が出るようになりました。*2

 

非互換部分は以下のとおりです。
・偽TCPの拡張コマンドは非対応
・JS版TSSでは固定波形テーブル音源が未実装のため、%5の音源は無音になる
・oコマンドやlコマンドを未指定時の値が違うっぽい
・Panが左右逆かも
・文法エラーのあるMMLの解釈

 

再生確認用のサンプルMMLも書いてみたので、Chrome拡張をインストールして聞いてみてください。

#TITLE <TSSCCjs's Theme by aike CC-BY>;
t165;#A=l4dr8d16e16f2.r8gf8e8f8e.d.ec.d.efr8f16g16a2.r8b-a8g8d8fr8fr8fgr8gr8
;#B=d.a2r8g.c+2r8c+.d.ef2.rd.a2r8g.c2r8c+.g.df2.r;#C=l4ar8a16g16a2.r8ga<d8c.
>a.<c>a.a+.<c>a.b-16<c16d2.r8c.>a+ar8ar8a;#D=a.d8>a8.<d8.f8er8e8e8ef8g.a8a+8
ga+8;#E=dgfdgdfd>a<fd>a<f>a<d>a<cgecgcec>a<ec>a<e>a<c>a<dgfdgdfdcgecgcec>b-<
fd>b-<f>b-<d>b-;#F=fgfde.f8g.r8efg8aa+8ac+defgfde.f8g.r8efg8aa+8d2.r;#G=l4d.
>a.<d>b-r8b-.<dc.>g.<c>ar8ar8a<d.>a.<dcr8cr8c>b-r8b-r8b-a16r16a16r16a16r16a1
6r16<e>a<;#H=l16[dagfdfga][c+agfc+fga][eagfefga][dagfdfga];#I=[4arararar]<[d
rdrdrdr]frfrfrfrgrgrgrgr;#J=AeAa+BB;#K=C<cr8cr8c>C<c+r8c+r8c+Da2.rDa.d2r8Da2
.rDa.d2r8;#L=l8Ea<ec>a<e>a<c>a<Ea<ec+>a<e>a<c+>al4<FF>;#M=GGHHHH;#N=l8[8r1]I
[16r1];#O=%3v10ml3q6s5;#P=%3v10mp1,4,1,2,0k1q6s5;#Q=%3q10s3;#FMC3(B2(A));Oo6
$J;Po6$J;Qp1o6v6$J;#FMC3(B2(A));Oo5p2$K;Po5p2$K;Qp2o5v6p2$K;#FMC3(B2(A));Oo5
$L;Po5$L;Qo5v4$L;#FMC4(B2(A));%3o4l4v15$q0s1o3M;%3o4l4v13$q0s3o3M;%3o4l4v4$q
13s1o3M;o6v4q4s4$N;o6v4q4s4k5$N;o6v2q4s4r8.$N;o6v2q4s4k5r8.$N;o8%2q0s50l16v8
$[16arararaaarararaa][16araaaraaaraaaraa];#R=[7rara]ral16s20;%2q0l4o3s12,18v
8$Rarrraaaal4s12Raaaaaaaal4s12Rarrraaaal4s12Raaaaaaaal4s12;%3o4v15s1,-15q1l1
6$[16crrrrrcrcrrrrrcr][16crccrcrccrccrcrc];

*1:そういうわけでClipboard Playerとは名づけてません

*2:いずれも本家にプルリクエストして反映してもらいました