JSSの内部(1/2)
JavaScriptについて
JSSはJavaScriptで記述されていますが、基本的には機能ごとに変数を作り、その中でメソッドやプロパティをカンマで列挙した作りになっています。このような記述スタイルは、JSON(JavaScript Object Notation)と呼ばれ、可読性がすぐれ、名前空間が切れる表記法としてお勧めです。次は、JSSのソースの一部ですが、
var SakuraSMFFormatter = { big32: function(value){ var d = []; d[3] = value % 256; value = parseInt(value / 256); d[2] = value % 256; value = parseInt(value / 256); d[1] = value % 256; value = parseInt(value / 256); d[0] = value % 256; return d; }, (…略…) setup: function(midis){ this.midis = midis; this.track_chunks = []; } };
このとき、SakuraSMFFormatter.big32
やSakuraSMFFormatter.setup
といった関数が使えるようになります。また、SakuraSMFFormatter
内部ではthis.big32
やthis.setup
という書式で使うことができます。
内部処理ブロック
JSSでは次の順序でMMLを処理しています。
- 字句解析(マクロ展開)
- 構文解析
- 意味解析
- SMF変換
ブロック名 | 意味 |
字句解析 | MMLをトークンとして切り出す |
構文解析 | トークンを意味のある塊にまとめる |
意味解析 | 構文解析された情報を元にMIDIを生成する |
SMF変換 | 生成されたMIDIをSMFに変換する |
次項でそれぞれのブロックについて説明します。
字句解析
JSSは字句解析を変数SakuraLexer
の中で実装しています。この機能ブロックは、「マクロ登録/展開」および、「トークン切り出し」をしています。
マクロ登録/展開
ここで言うマクロとは、意味や構文解析の前にトークン(MMLとして意味を持つ最小の文字列)を置換する機能です。マクロの目的はMMLでよく使う長い記述を短く書けるようにしたり、日本語で書けるようにすることです。JSSは、日本語マクロとリズムマクロの2種類のマクロ機能を持っています。
日本語マクロ
日本語マクロは日本語をMMLに置き換える機能です。MML中で次のように定義します。
~{テンポ}={Tempo}
これで「テンポ」→「Tempo」という変換が行われることになります。従って「テンポ」を定義した後は、
テンポ 60
と書くと
Tempo 60
と同じ意味になります。
JSSでは~{XXX}={YYY}
を見つけると、文字列変換ハッシュ(SakuraLexer.jmacro
)に、「XXX→YYY」という対応を作ります。そして、トークン切り出し前には必ず文字列変換ハッシュに従って文字列変換を行うようにしています。
日本語マクロに関してはデフォルトで定義用MMLを持ち、変数JapaneseDefaultMacro
に定義しています。ユーザーから渡されるMMLの前に本MMLを評価し、日本語マクロを登録しています。そのため、日本語マクロ定義を意識することなくMMLを日本語で記述できます。
リズムマクロ
リズムマクロは主としてリズムノートを簡易的に記述する目的で実装しています。一文字のアルファベットを任意の文字列に置き換えることができます。MML中で次のように定義します。
$b{n36,} $s{n38,}
リズムマクロを使う場合はRhythm
と波括弧で囲まれた部分でのみ使用できます。
Rhythm{ bsbs }
上記はb
にバスドラム、s
にスネアドラムを割り当てて、リズムマクロで再生する例です。基本的には日本語マクロと処理方式は同じであり、$x{y}
を見つけると文字列変換ハッシュ(SakuraLexer.rmacro
)に「x→y」という対応表を作ります。
トークン切り出し
トークンとは、MMLとして意味を持つ最小の文字列のことです。字句解析での主な処理は、与えられた文字列からトークンを切り出すことです。例えば、
rcdefagb→r c d e f g a b
のように一つ一つ切り出していきます。あとの処理のため、このときにどんな種類のトークンなのかを記録しておきます。JSSでは大きく次のようにトークンを分類しています。
項目 | 例 | 説明 |
文字列リテラル | {"~"} | 文字列リテラルです |
アルファベット | Abc | 変数定義用のアルファベット文字列です |
10進数値 | 10 | 数値です |
16進数値 | $a7 | 16進数値です |
ノート | a | 音符です。音符は特別なフォーマットを持っているので分類しています |
無視 | | | MIDI化に必要のないものはここに分類します。これは小節区切り文字になります |
パラメータ (単体) | > | 1トークンで意味が確定するものはここに分類します。これは「オクターブを上げる」というMMLになります |
パラメータ (引数つき) | @ | トークンの後に10進数値を複数必要とするものはここに分類します。これは「楽器を変更する」というMMLで、楽器番号を必要とします |
パラメータ (16進数引数つき) | SysEx | トークンの後に16進数値を複数必要とするものはここに分類します。これは「システムエクスクルーシブを発行する」というMMLで、システムエクスクルーシブバイト列を必要とします |
再帰 | Sub | 構文解析中に再帰的に処理する必要のあるものはここに分類します。これは「時間を進めずにMMLを記述する」というMMLで、内部(引数)としてMMLを必要とします |
その他 | 上記に当てはまらないもの |
例えば、次のようなMML文字列は、
Track(10) @33 l8c4<ba
字句解析の結果としては次のようになります。
No | トークン | 種類 |
1 | Track | パラメータ(引数つき) |
2 | ( | その他 |
3 | 10 | 10進数値 |
4 | ) | その他 |
5 | @ | パラメータ(引数つき) |
6 | 33 | 10進数値 |
7 | l | パラメータ(引数つき) |
8 | 8 | 10進数値 |
9 | c | ノート |
10 | 4 | 10進数値 |
11 | < | パラメータ(単体) |
12 | b | ノート |
13 | a | ノート |
構文解析
JSSは、構文解析を変数SakuraParser
の中で実装しています。ここでは、バラバラになっているトークンを理解可能な最小の塊にまとめて次の処理ブロックへ渡すのが目的です。字句解析のときに種類の分類をしましたが、種類に従って構文解析を行います。
ここではメインの機能であるノートとパラメータ(引数つき)について説明します。
ケース1:ノートの場合
ノートは次のような書式になっています。
[音符名][臨時記号*][長さ][,ゲート][,ベロシティ][,タイミング]
項目 | 例 | 意味 |
音符名 | a/b/c/d/e/f/g | 音符を表す一文字のアルファベット。a=ラです |
臨時記号 | +/-/* | 臨時記号を表す一文字記号。+は#、-は♭、*はナチュラルを表します。省略可能 |
長さ | 4. | n分音符のn。4.だと、付点4分音符を意味します。省略可能 |
ゲート | 95 | 実際の発音時間をパーセントで示したもの。省略可能 |
ベロシティ | 75 | 打鍵(キーボードを弾くこと)の強さを0~127で示したもの。0だとノートオフ。省略可能 |
タイミング | -3 | 発音タイミングのズレをステップ数(後述)で示したもの。省略可能 |
音符名以外はすべて省略可能です。字句解析中でノートだと判定されたトークンを見つけるとノート書式の解析に入ります。例えば与えられたトークン列が次のようなものだとすると、
c 8 , , , 5 d . , 20 e - , , 90
構文解析の結果は、次のようになります。
No | 構文解析結果 |
1 | ノートc、8分音符、タイミング5 |
2 | ノートd、付点音符、ゲート20 |
3 | ノートe、臨時記号-、ベロシティ90 |
ケース2:パラメータの場合
パラメータは次のような書式になっています。
[パラメータ名] [引数1][,引数2]..[,引数n] または、 [パラメータ名] = [引数1][,引数2]..[,引数n] または、 [パラメータ名] ( [引数1][,引数2]..[,引数n] )
項目 | 例 | 意味 |
パラメータ名 | @ | パラメータを表す文字列。@は音色変更を意味する |
引数1 | 51 | パラメータに必要な数値1。@の第1引数は楽器番号 |
引数2 | 16 | パラメータに必要な数値2。@の第2引数はバンクセレクト(MSB) |
引数n | 0 | パラメータに必要な数値n。@の第3引数はバンクセレクト(LSB) |
引数は無限に取ることができます。構文上、引数はすべて省略可能です。パラメータによっては省略不可能な場合や、一つしかパラメータを取らないものもありますが、構文解析上では同じ処理としています。上記表ではパラメータの引数の意味を書いていますが、ここでは意味については意識せず、とにかく構文どおりにトークンをまとめて行きます。
字句解析で「パラメータ(引数つき)」としてトークンと認識されたものが、パラメータとして構文解析されます。次のトークンが該当します。
'TR','Track', 'CH', 'Channel', 'Port', 'REV', 'Reverb', 'CHO', 'Chorus', 'Volume', 'V', 'P','Panpot', 'EP', 'Expression', 'M', 'Modulation', 'BR', 'BendRange', 'Tempo', 'TimeKey', 'TimeKeyFlag', 'onNote', 'Repeat', 'onTime', 'Random', 'TimeBase', 'Time', 'TrackSync', 'v_', 'q_', 'p%', 't_', 'v', 'o', 'q', 'p', 't', 'y','@'
上記トークンを見つけるとパラメータ書式の解析に入ります。例えば与えられたトークン列が次のようなものだとすると、
Tempo = 40 CHO ( 50 ) @ 1 , , 1
構文解析の結果は、次のようになります。
No | 構文解析結果 |
1 | パラメータ Tempo、 引数1 40 |
2 | パラメータ CHO、 引数1 50 |
3 | パラメータ @、引数1=1、引数3=1 |