D言語 Advent Calendar 17日目の記事です。
#MMLを鳴らしたい
前回 PortAudio で波形を鳴らす を書きましたが、
単純な波形をただ鳴らすのもつまらないので、今回はMMLの再生に挑戦してみました。
##MML is 何?
MML は Music Macro Language の略で、曲の楽譜情報をテキストで記述できる言語です。
MIDIなどが登場する以前の大昔のパソコンで楽曲を演奏するのに使われていたようです。
Music Macro Language - Wikipedia
T120 L4
CDEF EDCR EFGA GFER CRCR CRCR C8C8D8D8E8E8F8F8 EDCR
上記はカエルの歌をMMLで記述したものです。
C D E F G A B がドレミファソラシの音符に対応し、Rは休符です。
T120はテンポ120(bpm)。L4は音符のデフォルト長を4分音符にすることを表しています。
何も付いていないCDEFGABはデフォルト長(4分音符)。8が付いている音符は8分音符です。
とりあえずMMLを鳴らせるエンジンを作ってみようってことで(先行ないよね?)、色々調べ始めた。
##規格がバラバラ
MMLは大昔に使われていた言語なので実装によって規格がバラバラだった。
< > でオクターブシフトだが、実装によって上げ下げがあべこべだったり…。
色々探しているうちに、FlushでMMLを鳴らすFlMMLというライブラリがあるらしく、
ニコニコ動画のピコカキコもこの規格で実装されているらしい。ということが分かった。
##実装がんばるぞい!
を見ながらコマンドを一つ一つ実装していく。
CDEFGAB が来たら波形ジェネレータの周波数をいじる。
frequency = referenceFrequency * pow(2, scale / 12);
referenceFrequencyは基準周波数。ここでは220Hzに設定。
int noteToScale(int note, int acci) {
int scale = 0;
switch (note) {
case 'c': scale = 3; break;
case 'd': scale = 5; break;
case 'e': scale = 7; break;
case 'f': scale = 8; break;
case 'g': scale = 10; break;
case 'a': scale = 12; break;
case 'b': scale = 14; break;
default: return 0;
}
scale += this.noteShift;
return scale + (this.octave - 4) * 12 + acci;
}
音符と音階の関係はこんな感じです。
他にも音量(V)、定位(P)、エンベロープ(E1)、音色(@)など様々な要素をいじって複雑な発音を制御します。
##とりあえず再生できるところまで
Git Repository : MMLPlayer-D
下記コマンドで再生できます。
(音しか出力されないので、ここに掲載できないのが残念…)
MMLPlayer [MMLファイル]
MMLデータはここにあるものをいくつか試してます。
ニコニコ大百科:ピコカキコ曲ジャンル一覧
※未対応機能がある(#WAV等)ので、残念ながら再生できないものが結構あります。
#ライブラリとして使う
// 初期化周り
auto player = new MMLPlayer(44100);
player.setMML(mmlText);
player.play();
// 波形を生成する
float sample = player.synth();
使い方の詳細はmain.dを見てください。
#最後に
FlMMLが思ってたより仕様多くて大変でした。
ただ、コマンドを実装すればするほど再生できる曲が増えていくのでモチベ維持は容易でしたね。
正直、波形生成周りはD言語じゃなくてもいいかな?
と一瞬思ったりもしたものの、パーサーなど文字列操作周りはD言語の強力さを思い知った感じです。
信号処理プログラミングはC/C++の十八番って感じですが、
高速なD言語でもできるよ!ってことを知ってもらえれば幸いです。
トライアンドエラーが多い信号処理では、コンパイルが高速なDの利点が活きてくるのではないかと。
頑張ればVSTプラグインもD言語で実装できるかもしれません。
皆さんも興味があれば音プログラミングに挑戦してみてください。
それでは。