マルチトラックに対応する
ここまでで、MMLを使って演奏するプログラムができました。単音だと少し物足りないので、サンプル3を改造して二重和音に対応します(二重以上でもFirefoxとマシンパワーが許す限り多重演奏できます)。
サンプル3との違いは、トラックごとにオブジェクトを生成し、オブジェクト内で処理する点です。playTrackというオブジェクトを用意し、ここまでに作成したプログラムを格納します。ptrやtempoなどの変数はオブジェクトのプロパティとして定義します。たとえば、
this.ptr this.tempo
のように変更します。また、setTimeout()の仕様上、2回目の呼び出しはthisがWindowオブジェクトを指し示してしまうので、ローカル変数objにthisのデータを入れておきます。
var obj = this;
実際のプログラムはサンプル4です。2つのテキストエリアに入力されたMMLが同時に処理され演奏されます。ちなみに、サンプル4を以下のように変更すると正弦波ではなく矩形波が出力されます。8ビットパソコンの雰囲気を味わいたい方はお試しください。
data[i] = Math.sin(k * i);
↓
data[i] = Math.sin(k * i); if (data[i] > 0) data[i] = 1; else data[i] = -1;
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>MMLを使って演奏する(二重和音)</title> </head> <body> <h1>MMLを使って演奏する(二重和音)</h1> <form> <input type="button" value="演奏" onclick="initMML()" /><br /> <textarea cols="20" rows="5" id="track1">O4 C4 D4 E2 F2</textarea> <textarea cols="20" rows="5" id="track2">O2 C4 D4 E2</textarea> </form> <script type="text/javascript"> var freq = []; // 音階に応じた周波数を入れる配列 freq["R"] = 1; freq["C"] = 261; freq["D"] = 293; freq["E"] = 329; freq["F"] = 349; freq["G"] = 392; freq["A"] = 440; freq["B"] = 493; freq["#C"] = 277; freq["#D"] = 311; freq["#F"] = 370; freq["#G"] = 415; freq["#A"] = 466; for(i in freq) { freq[i] = freq[i] * 4; } // トラック1、2を再生する function initMML(){ track1 = new playTrack("track1"); track1.playMML(); track2 = new playTrack("track2"); track2.playMML(); } // 指定されたトラックIDを再生するためのクラス function playTrack(trackID){ var obj = this; this.octave = 4; // オクターブ設定 this.tempo = 60; // テンポ設定 this.ptr = 0; // 読み出し位置を初期化 this.track = trackID; // トラックID this.playMML = function(){ // console.log(obj.track+" = "+obj.ptr); // 再生位置を調べる場合はこの注釈を消去 var data = document.getElementById(obj.track).value; var MML = data.split(" "); // 空白 while(true){ if (obj.ptr >= MML.length) return; // 最後まで演奏が終わっていたら以後の処理はしない var sdata = MML[obj.ptr]; var onkai = sdata.match(/[a-z]+/i)[0].toUpperCase(); if (sdata.charAt(0) == "#") { onkai = "#"+onkai; } var num = sdata.match(/\d+/); if (onkai == "O") { // オクターブ指定 obj.octave = num; obj.ptr++; continue; } if (onkai == "T") { // テンポ指定 obj.tempo = num; obj.ptr++; continue; } break; } var sec = (60 / obj.tempo) / num; // 音長を計算 var fr = freq[onkai]/obj.octave; var sampleRate = 44100; // 44.1kHz // 指定された周波数の音を指定時間出力 var audio = new Audio(); audio.mozSetup(1, sampleRate); // 1ch, 44kHz var bufferSize = Math.ceil(sampleRate * sec); // 再生秒数 var data = new Float32Array(bufferSize); var k = 2* Math.PI * fr / sampleRate; for(var i=0; i<data.length; i++){ data[i] = Math.sin(k * i); // if (data[i] > 0) data[i] = 1; else data[i] = -1; // PSG(矩形波)にしたい場合はこの注釈を消去 } audio.mozWriteAudio(data); obj.ptr++; setTimeout(obj.playMML, sec*1000); audio.play(); } } </script> </body> </html>
HTML5のFile APIと組み合わせるとテキストエリアに入力されたMMLではなく、ローカルにあるMMLファイルも演奏できます。実際のプログラムはサンプル5です。File APIについては以下の記事を参考にしてください。
- ・File APIでブラウザーからローカルファイルを操作
- http://ascii.jp/elem/000/000/559/559105/
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>ローカルファイルのMMLを演奏</title> </head> <body> <h1>ローカルファイルのMMLを演奏</h1> <form> <input type="file" id="myFile"> <input type="button" value="演奏" onclick="initMML()" /><br /> </form> <script type="text/javascript"> var freq = []; // 音階に応じた周波数を入れる配列 freq["R"] = 1; freq["C"] = 261; freq["D"] = 293; freq["E"] = 329; freq["F"] = 349; freq["G"] = 392; freq["A"] = 440; freq["B"] = 493; freq["#C"] = 277; freq["#D"] = 311; freq["#F"] = 370; freq["#G"] = 415; freq["#A"] = 466; for(i in freq) { freq[i] = freq[i] * 4; } // トラック1、2を再生する function initMML(){ var filename = document.getElementById("myFile").files[0]; var reader = new FileReader(); reader.onload = function(evt){ track1 = new playTrack(evt.target.result); track1.playMML(); } reader.readAsText(filename, "utf-8"); // 文字コードをUTF-8として読み込む } // 指定されたトラックIDを再生するためのクラス function playTrack(MMLdata){ var obj = this; this.octave = 4; // オクターブ設定 this.tempo = 60; // テンポ設定 this.ptr = 0; // 読み出し位置を初期化 this.data = MMLdata; // MMLデータ this.playMML = function(){ // console.log(obj.track+" = "+obj.ptr); // 再生位置を調べる場合はこの注釈を消去 var MML = obj.data.split(" "); // 空白 while(true){ if (obj.ptr >= MML.length) return; // 最後まで演奏が終わっていたら以後の処理はしない var sdata = MML[obj.ptr]; var onkai = sdata.match(/[a-z]+/i)[0].toUpperCase(); if (sdata.charAt(0) == "#") { onkai = "#"+onkai; } var num = sdata.match(/\d+/); if (onkai == "O") { // オクターブ指定 obj.octave = num; obj.ptr++; continue; } if (onkai == "T") { // テンポ指定 obj.tempo = num; obj.ptr++; continue; } break; } var sec = (60 / obj.tempo) / num; // 音長を計算 var fr = freq[onkai]/obj.octave; var sampleRate = 44100; // 44.1kHz // 指定された周波数の音を指定時間出力 var audio = new Audio(); audio.mozSetup(1, sampleRate); // 1ch, 44kHz var bufferSize = Math.ceil(sampleRate * sec); // 再生秒数 var data = new Float32Array(bufferSize); var k = 2* Math.PI * fr / sampleRate; for(var i=0; i<data.length; i++){ data[i] = Math.sin(k * i); // if (data[i] > 0) data[i] = 1; else data[i] = -1; // PSG(矩形波)にしたい場合はこの注釈を消去 } audio.mozWriteAudio(data); obj.ptr++; setTimeout(obj.playMML, sec*1000); audio.play(); } } </script> </body> </html>