小学校とか中学校の頃、音楽の授業で、何か楽器を習わされませんでしたか?作者の場合、小学校では縦笛、中学校ではハモニカだった記憶。学校によっては、鍵盤ハモニカを使うところもあるようです。今回のテーマは、鍵盤ハモニカで簡単な曲を自動演奏(?)しているような動画をレイトレーシングで生成すること、です。
まず、演奏できる音は同時に1音とします。後でもう少し考察を進めますが、同時に2音、3音演奏できるようにすると、レイトレーシングすべきフレーム数が爆発的に増加してしまうためです。
次に、鍵盤の数を決めます。「まるで自動演奏しているかのごとき」動画にしたときに、鍵盤が3オクターブ分も4オクターブ分もあったところで、所詮同時に1音しか奏でないので、一部の狭い範囲で鍵がコソコソ動いているだけの動画になります。最小限の鍵盤数にしましょう。そうすると1オクターブ分、と言うのが最小でしょうが、今度は見た目が……。小ぢんまりし過ぎて、鍵盤ハモニカっぽくない(短すぎっ、ある程度の長さが必要)。と、言うことで、決めました。2オクターブ分、24鍵とします。
まあ、あんまり、「自動演奏できる鍵盤ハモニカ」って、聞かないのですけどね。仮に自動で鍵盤が動いたとしても、息を吹き込まないと鳴らないし……。それとも、吸気も自動?!
いつものように無音の動画にナレーションだけ、と言うわけには行きません。何しろ、今回は、「まるで自動演奏しているかのごとき、鍵盤ハモニカ」なので。動画の演奏に合わせて、音をつけなくては!一旦、レイトレーシングから離れて、演奏する音楽の方に注目してみます。
DTM等が趣味で、曲を入力したり演奏したりできるソフトウェアをお持ちの方なら、そちらを使うのでしょうが、少し困るのは演奏のスピードです。演奏開始から、終了までの間、ずっと同じスピードで演奏しなければならないことはもちろんのこと、演奏のスピードと動画の再生速度をぴったり同期しないと、後ろの方に進むにつれて、音と映像がずれて行ってしまいます。
DTMソフトウェアの方がgivenなら、自由度の利くXi-TRACE側で、演奏に合わせて動画のタイミングを合わせることになるのでしょうが、「不可能ではない」にせよ、骨が折れそうです。
そこで今回は、演奏するWAVファイルもスクリプトで作ってしまいます。そうすることによって、動画と音楽、双方の進行時間を握る(掌握する)ことができます。まず前段として、スクリプトでWAVファイルを作成するサンプルと解説が「Olive+紹介ページ」の「WAVファイルを生成する」にありますので、ご確認ください。「WAVファイルを生成する」では、チューニングなどの基準となる440Hzの「ラ」の音を生成するスクリプトを例に解説しています。
今回は一歩進めて、簡単な「曲」を演奏可能なスクリプトにしてみます。まずは、「曲」を定義するMML(Music Macro Language、音楽を記述するための言語)を定義しましょう。
次の節で演奏動画を生成する時にも、同じデータが使えるように、MMLを定義します。
まずは、「ドレミファソラシド」。最後の「ド」まで到達すると、そこからまた「ドレミファソラシド」が繰り返されます。ただし、1オクターブ上がって(音が高くなる)。同様に、最初の「ド」の前にも、1オクターブ下の「ドレミファソラシド」が続いています。
この「1オクターブ」と言うのは、前・後のオクターブで周波数が倍・半分の関係になっているようです。チューニングなどで基準に使われる「ラ」の音は440Hzですが、1オクターブ高い(周波数が高い)「ラ」の音は、倍の880Hz。逆に、1オクターブ低い(周波数が低い)「ラ」の音は、半分の220Hz、と言う様な関係です。
では、「1オクターブ」の中に、いくつの音(音階)があるか?と言うと、半音上げる(♯、以降本文では便宜上、ナンバ記号(#)を使います。あしからず)音も入れて、「ド/ド#/レ/レ#/ミ/ファ/ファ#/ソ/ソ#/ラ/ラ#/シ」の12音。つまり、12音通過すると、周波数が2のべき乗で+1乗(二倍)または−1乗(半分)になるので、通過した音の数をdと置き、「ラ」の音(440Hz)を基準(d=0)にすると、各音階の周波数fは以下の式で求めることができます。
音階とd、周波数fの関係を表にしておきます。
音階 | オクターブ | d | 周波数f |
ファ/F | 3 | −16 | 174.614 |
ファ#/F# | 3 | −15 | 184.997 |
ソ/G | 3 | −14 | 195.998 |
ソ#/G# | 3 | −13 | 207.652 |
ラ/A | 3 | −12 | 220 |
ラ#/A# | 3 | −11 | 233.082 |
シ/B | 3 | −10 | 246.942 |
ド/C | 4 | −9 | 261.626 |
ド#/C# | 4 | −8 | 277.183 |
レ/D | 4 | −7 | 293.665 |
レ#/D# | 4 | −6 | 311.127 |
ミ/E | 4 | −5 | 329.628 |
ファ/F | 4 | −4 | 349.228 |
ファ#/F# | 4 | −3 | 369.994 |
ソ/G | 4 | −2 | 391.995 |
ソ#/G# | 4 | −1 | 415.305 |
ラ/A | 4 | 0 | 440 |
ラ#/A# | 4 | 1 | 466.164 |
シ/B | 4 | 2 | 493.883 |
ド/C | 5 | 3 | 523.251 |
ド#/C# | 5 | 4 | 554.365 |
レ/D | 5 | 5 | 587.330 |
レ#/D# | 5 | 6 | 622.254 |
ミ/E | 5 | 7 | 659.255 |
dと周波数fに関しては、スクリプトの内部で処理しますので、ここでは音階の記号と、オクターブ番号を覚えておいてください。
後は音の長さですね。楽譜で使う記号と名前を以下に示します。
全音符の長さを基準として、「n分音符」は、全音符の×1/n倍の長さになります。他に、丸い記号の横に点(付点)がついた音符があり、この場合は、×1.5/n倍になります。例えば、付点付き4分音符の場合、長さは4分音符+8分音符に相当します。
そもそも、動画の動きと、曲の進行を同期したいがために、自前でWAVを生成しようとしています。スクリプトの中で、基準となる全音符の長さをlen_zenという変数に、単位[s]で指定するようにしてありますので、曲の進行と動画の動きをしっかり合わせることができるはずです。
ようやくすべての材料が揃いましたので、MMLの定義に移ります。
例えば以下の様な楽譜は、「ド44., F48, ミ42」等と、記述できます(ト音記号がうまく書けなかったのですが、下から2本目がト音のつもり。合っていますかね?)。
もう少し複雑な例で、付点付きの16分音符でオクターブ5の「レ#」の場合は、「レ#516.」または「D#516.」となります。
とりあえず、誰もが知っている「きらきら星」をMMLで起こしてみました。
# きらきら星
ド44,ド44,ソ44,ソ44,ラ44,ラ44,ソ42
ファ44,ファ44,ミ44,ミ44,レ44,レ44,ド42
ソ44,ソ44,ファ44,ファ44,ミ44,ミ44,レ42
ソ44,ソ44,ファ44,ファ44,ミ44,ミ44,レ42
ド44,ド44,ソ44,ソ44,ラ44,ラ44,ソ42
ファ44,ファ44,ミ44,ミ44,レ44,レ44,ド42
「ドドソソララソ〜」って、やつですね。まず、そのまま音階を書き出してから、後はオクターブと音の長さを追記して行きました。全て、オクターブ番号4の音で構成されていて、ほとんどの長さが4分音符のため、大抵は音階の後に「44,」をコピー&ペーストしたら終了でした。2小節毎の最後だけ2分音符があるので、「42」を付加して完成。
完成画像の所で、mml2wav.txtと言うファイルでDLできます。
スクリプトの方は、「WAVファイルを生成する」をベースに、MMLの解析部分を追加しました。MMLの解析がちょっとコテコテしていますが、メインディッシュは「レイトレーシングで動画を作成する」ことなので、解説はパスしますね。順を追ってご覧いただけば、わかると思いますので、ソースリストの中身をご確認ください。
ソースは、完成動画と一緒にリンクしておきます(ファイル名:mml2wav.olvです)。
なお、Olive+のスクリプト(.olv)になっていますが、中身はXi-TRACEのデータ記述と同じスクリプト言語ですので、Xi-TRACEでも実行することはできます。ファイル(.olv)をXi-TRACEのウインドウにドラッグ&ドロップするか、拡張子を(.xitr)に変更して実行してください。
「ラ44, ラ44」と、記述した場合、基本的には、同じ「ラ」の音が4分音符で連続するので、結果、2分音符と同じ長さの「ラ」が演奏されるのでしょうか?しかし、たぶん、そうはなりません。丁度、4分音符の長さだけ演奏した後に、「プツッ」というような音(ノイズ)が入って、次の「ラ」の音が4分音符の長さだけ演奏されます。
これは、新しい音を再生始める時に、常にsin波の位相が0にリセットされるためです、前の音が綺麗にsin波になっていたところに、次の音の始点(前の音の終点)が来て、途中まで振幅していたsin波が突然位相0に戻ってしまうため、ここで急な波形の変化が起こり、高周波数イズとなって「プツッ」音が聞こえてしまいます。
このようなノイズを回避する基本的な方法は、音の始点と終点で、sin波の振幅を0に絞ることです。そもそも、「ラ44, ラ44」と書いた時点で、「ラ」の音を短く2回鳴らしたいのであって、それが長めに1回になってしまったら、意図に反するはずです。音の始点と終点で振幅を0に絞り、音と音の間を区切ることは理にかなっています。
そこで、スクリプトでは、以下の様な画像データを使って、1音の中の振幅波形を規定しています。
画像は白と黒の二値画像としてください。横幅一杯で、1音の長さを示しています。左端が開始時間、右端が終了時間です。高さ方向はsin波の振幅に相当し、黒い部分に振幅するようになります。下端が振幅0、上端が最大振幅となります。黒い部分は、画像の下端に接している必要があります。
上図の波形で作成したWAVファイルを、WAV編集ツールで見てみると、以下の様になります。
sin波の振幅が、+側と−側に現れますので、白黒の波形が上下に鏡像になったように見えます。青い部分は、時間軸を拡大していくと、音階の表で示した周波数で振動するsin波になっています。
白黒の波形ファイルは、mml2wav.xpicです。こちらも完成画像の所でDLできるようになっています。グラフィックエディタ(Olive+等)で波形ファイルを修正すると、その波形でWAVファイルを生成するようになります。この程度の波形制御では、音色までは変えられないと思いますが……、ある程度波形を制御することが可能な仕様になっています。
生成するWAVファイルのサンプリングレートは、44.1kHzの1/4に設定してあります。レートを上げると、生成するデータ量が増え、処理に時間がかかるようになります。スクリプトなので、基本、遅いです。いつも、レイトレーシングの動画で、20時間とか30時間とか掛けて動画を生成しているくせに、何をいまさら……、と言う感じですが(1^^;)。短い「きらきら星」一曲の生成に数10分かかると、耐えがたい。中途半端な時間だからですかね??
ということで、サンプリングレートを下げて、時間の節約をしています。今回のレイトレーシングデータでは、2オクターブ分、一番高い音で「ミ3」の660Hzまでしか使いませんので、標本化定理に従って、×2倍の1.32kHzくらいまで下げても、演奏可能なはずです。44.1kHz÷4=11.025kHzなので、まだ1桁近い余裕があります。
もし、もっと高い音まで演奏させたい場合は、スクリプトの中のnSamplesPerSecという変数に適切な値をセットしてください。もし、nSamplesPerSec=44,100(44.1kHz)とすると、処理時間が現行設定の4倍かかると推定されます。
WAVを生成するスクリプト(mml2wav.olv)では、全音符の長さをlen_zen=2s(♩(4分音符)=120に相当)としてあります。従って、もし、32分音符が指定された場合、その演奏時間は、2/32[s]となります。30FPSの動画に当てはめると、1.875フレーム→整数になりません。さて、どうするか?音符の長さが2冪数になっているので、綺麗にシンクロさせるためには、動画のフレームレートも2冪数にした方が都合が良いのです。若しくは、len_zen/32が、30FPSの整数倍になるように、例えば、len_zen=2×32/30=2.1333に設定するとか?32/30は無限小数なので、丸めた分の誤差は蓄積されて、長い曲の時に最後の方でずれが生じる可能性があります(本末転倒)。
結論として、今回は動画を16FPSで生成します。その場合、32分音符は16FPSの1フレーム分ですので、鍵盤が徐々に下がっていくアニメーションを生成したところで、所詮、それが使用されることは無く、意味がありません。0/1で、押し下げるか/押さないか、の2通りの画像があれば十分、という事です。
必要なフレームは、以下の通りとなります。
フレーム番号 | d | 状態 |
0 | × | 全ての鍵盤が押されていない状態のフレーム |
1 | −16 | 1つだけ、「ファ3」の鍵盤が押し下げられたフレーム |
2 | −15 | 1つだけ、「ファ#3」の鍵盤が押し下げられたフレーム |
3 | −14 | 1つだけ、「ソ3」の鍵盤が押し下げられたフレーム |
: | : | : |
23 | 6 | 1つだけ、「レ#5」の鍵盤が押し下げられたフレーム |
24 | 7 | 1つだけ、「ミ5」の鍵盤が押し下げられたフレーム |
鍵盤の数は2オクターブ分、24鍵と決めたので、合計25フレームを計算したら終了です。いつも、10〜20[s]で30FPS(=300〜600フレーム)のレイトレーシングを実行していることに比べたら、実に楽。
そうは言っても、もし、2音同時に押し下げることを許容するとなると、24鍵の組み合わせの数はC(24, 2)=276フレームとなり、いつものフレーム数に近づいてきます。3音同時なら、C(24, 3)=2024フレームが追加……。やはり、「同時に1音」に制限しておいて正解。
先ほどWAVファイルを作成した時に使用したMMLファイルを読み込みながら、レイトレーシングした25フレームから適切なフレームを選んで動画に起こしていくスクリプトを作ります。
まず、MMLからWAVを出力するスクリプト(mml2wav.olv)をコピーして持って来て、修正していくのが楽そうです。WAVファイルに出力している辺りのコードは必要ないので、マルっと削除してしまいます。MMLを解析して、音階の表に示したdの値を計算している所がありますので、この辺りはそのまま使用可能です。モデリングした24鍵の鍵盤ハモニカで、一番左の鍵は「ファ3」の音で、d=-16でした。前節で示したように「ファ3」を押し下げたフレームは、フレーム番号1となっています。レイトレーシングした動画には、音階の表の上から順に1鍵づつ押し下げた画像が順に記録されているので、dに17を加えたフレーム番号を表示すれば、対応する鍵が押された画像が選ばれることがわかります。
音符の長さも計算されているので、選んだフレームを、計算した時間出力し続けたらOKです。
スクリプトを実行すると、最初に画像を.aviファイルに記録しようとした時に、パラメータを問いあわせるウインドウ「AVIフォーマット設定」が開きますので、そこで「フレームレート」に16を指定してください。
完成したデータと動画です。
1) | Xi-TRACEデータ | kb_harmonica.xitr |
2) | 曲データ | mml2wav.txt |
3) | Olive+スクリプト | mml2wav.olv |
3’) | 波形データ | mml2wav.xpic |
4) | 動画生成スクリプト | mml2avi.olv |
1)で動画の元となる画像を生成(レイトレーシング)。2)のMMLを使って、3)で演奏WAVデータを生成、4)で演奏動画を生成。
2)のMMLファイルをエディットすると、3)4)を使って、動画と音声を生成可能ですが、鍵盤の外にはみ出すようなMMLの場合、画像の方は追従してくれませんのでご注意ください。
残念ながら、Xi-TRACEやOlive+には、生成した動画(.avi)と音声データ(.wav)を合成する機能はありません。別途、Microsoft MovieMakerなどを使用して、動画と音声を合成してください。本ページで解説したように、時間軸を合わせて作っているので、動画と音声を同時に再生開始すれば、ぴったり同期するはずです。
本当はもう少し、動画にして面白い(いろいろな鍵を押している、テンポの速い部分がある、等)曲にしたかったところですが、うかつな曲を選んで著作権を侵害してもいけないので、無難なところにしておきました。面白そうな曲の楽譜をお持ちでしたら、MMLに起こして動画にしてみてください。