MMLトークン解析技法
便利なのでMMLを使うことが多くて、そのたびにMMLシーケンサーを書いている。
おおまかな流れとして
- MML文字列をトークン配列化する
- 再生している音が終ったら次のコマンドを参照して実行
- 終るまで
のような感じなんだけど、良い書き方が分からない。たとえば c&d のようなポルタメントの処理 (ドの音からなめらかにレに遷移する) だと、c のコマンドの時点で & と d が続くことを知っていないといけないとかあって、そのあたりを毎回試行錯誤して書き直している。良い方法あるのだろうけど..
それはそうと、MMLのコマンド切り出しに関しては洗練されてきた感があって、以下のようなコードを使っている。優先順に正規表現とオブジェクト化する定義を書くだけで新しいコマンドを追加できるのでかなり楽。
compile = (mml, MMLCommands)-> commands = [] checked = new Array(mml.length) for def in MMLCommands re = def.re while m = re.exec mml if not checked[m.index] for i in [0...m[0].length] checked[m.index+i] = true cmd = def.func m cmd.index = m.index cmd.origin = m[0] commands.push cmd while re.lastIndex < mml.length if not checked[re.lastIndex] break re.lastIndex++ commands.sort (a, b)-> a.index - b.index commands MMLCommands = [ { re:/@w(\d*)/g, func:(m)-> name:'@w', val:m[1]|0 } { re:/@n(\d*)/g, func:(m)-> name:'@n', val:m[1]|0 } { re:/@(\d*)/g, func:(m)-> name:'@', val:m[1]|0 } { re:/t(\d*)/g, func:(m)-> name:'t', val:m[1]|0 } { re:/l(\d*)/g, func:(m)-> name:'l', val:m[1]|0 } { re:/v(\d*)/g, func:(m)-> name:'v', val:m[1]|0 } { re:/o(\d*)/g, func:(m)-> name:'o', val:m[1]|0 } { re:/[<>]/g, func:(m)-> name:m[0] } { re:/([cdefgab])([-+]?)(\d*)(\.*)/g, func:(m)-> name:'note', len:m[3]|0, dot:m[4].length, tone:{c:0,d:2,e:4,f:5,g:7,a:9,b:11}[m[1]] + ({'-':-1,'+':+1}[m[2]]|0) } { re:/r(\d*)(\.*)/g, func:(m)-> name:'rest', len:m[1]|0, dot:m[2].length } ]
# これが "t200l8 @6 @w60 v15o4 efga4.>a4 <c+dee4ede" # こんな感じになる (実際は文字列じゃなくてオブジェクト) [ 't200', 'l8', '@6', '@w60', 'v15', 'o4', 'e', 'f', 'g', 'a4.', '>', 'a4', '<', 'c+', 'd', 'e', 'e4', 'e', 'd', 'e' ]