Flashでオルゴール本体を作る
オルゴールの本体は、Flash 8で作っています。Flashを利用することで、JavaScriptよりも見た目が派手なインターフェイスを手軽に作ることができます。ここでは、MIDIファイルを再生するまでの流れを確認し、その後、オルゴールデータの管理方法などを見ていきます。
再生までの流れ
前述のJavaScriptSakuraを利用することにより、オルゴール本体では、簡単な音程を表すMMLデータを作るだけで、MIDIファイルを生成できます。再生までの流れは次のようになります。
- オルゴール本体で簡単なMMLデータを作る
- Flash側でJavaScriptSakuraの関数にMMLデータを渡して呼び出す
- JavaScriptSakuraが保存用CGIにMIDIファイルを送信する
- QuickTimeでMIDIファイルが再生される
Flash側で実行することは、画面にグリッドを表示して、ユーザーにピンを立ててもらい、再生ボタンが押されたら、JavaScriptの関数を呼び出すことです。
FlashからJavaScriptの関数を呼ぶ方法
JavaScriptSakuraを呼ぶために、Flash 8から追加されたExternal APIを利用しています。この機能を使うと、FlashからJavaScriptの関数を読んだり、その逆に、JavaScriptからFlashの関数を呼ぶことができます。
External APIを使うためには、はじめに、ExternalInterface
を使うことを宣言します。JavaScriptの関数を呼ぶ場合は、「ExternalInterface.call(関数名, 引数...)
」のように記述します。
// ExternalInterface利用の宣言 import flash.external.ExternalInterface; ... // JavaScriptの関数を呼び出す ExternalInterface.call("mmlPlay", "cdefgfed");
JavaScriptからFlashのメソッドを呼び出す
また、JavaScriptからFlashの関数を呼び出すためには、まず、Flash側でJavaScriptから呼び出すことができるように、メソッドを登録しておく必要があります。登録するには、「ExternalInterface.addCallback(メソッドの名前, インスタンス, 実際のメソッド)
」のように書きます。
今回作成したオルゴールでも、掲示板に登録した曲データをFlash側のオルゴール本体にロードさせるために、showMBox
というメソッドを登録して、JavaScriptから利用しています。
import flash.external.ExternalInterface; class MusicBoxMain { function MusicBoxMain(root:MovieClip) // コンストラクタ { ... // JavaScriptから呼べるように登録 ExternalInterface.addCallback("showMBox", this, showMBox); } // JavaScriptから呼ばれるメソッド function showMBox(id:Number, name:String, title:String, comment:String, mb:String):Void { ...
次に、JavaScript側の記述方法を示します。まず、Flashのオブジェクトを取得してから、そのオブジェクトのメソッドを呼び出すという手順になります。
function init() // HTMLにFlashを表示---(*1) { var so = new SWFObject("MusicBox.swf", "mbox", "450", "450", "8", "#CCCCCC"); so.write("flashcontent"); } function thisMovie(movieName) { // オブジェクトを取得する関数---(*2) var isIE = navigator.appName.indexOf("Microsoft") != -1; return (isIE) ? window[movieName] : document[movieName]; } // Flashのメソッドを呼び出す ---(*3) thisMovie("mbox").showMBox(100,"name","title","comment","mmldata");
SWFObject
を利用して、SWFファイルを表示します。embed
タグのIDをmbox
にしています。*2 -- ブラウザごとに、Flashのオブジェクトを取得する方法が違うので、オブジェクトの取得を関数にしています。
*3 -- IDが
mbox
である、Flashのオブジェクトのメソッド「showMBox(...)
」を呼び出します。このように、Flash側の宣言は単純ですが、JavaScript側はブラウザごとの差異を気にしないといけないので少し処理が面倒です。
オルゴールのピンについて
オルゴールでは、ユーザーがピンを立てたところで音が鳴るようになっています。そこで、オン/オフの状態を持つピンを作成し、管理する必要があります。
はじめに、ピンのオン/オフを表すムービークリップを作りました。これは、タイムライン上に、ピンが立っていない状態を表す「off」と、ピンを立てた状態を表す「on」です。
この画像をActionScriptからより手軽に使えるように、シンボルのリンケージで、AS2.0
クラスでPinBox
クラスに設定します。AS2.0
クラスでは、「クラス名.as」という名前のクラスファイルを作り、その中に、ムービーの動作を定義することができます。
class PinBox extends MovieClip { var _status:Boolean = false; function get status():Boolean { return _status; } function set status(v:Boolean) { _status = v; if (v) { gotoAndStop("on"); } else { gotoAndStop("off"); } } }
より手軽に状態を切り替えられるように、status
をアクセサメソッド(getter
と setter
) にして、定義しています。これで、「pinbox.status = true;
」のように書くとピンがある状態のグラフィックが表示され、「pinbox.status = false;
」でピンがない状態になります。
オルゴールのメインクラスでは、このPinBox
クラスをattachMovie()
で複数作成し、碁盤のように等間隔に配置しています。
function createPin() { pins = new Array(); for (var y = 0; y < ROW; y++) { for (var x = 0; x < COL; x++) { var no = y * COL + x; var name = "pin" + no; var m = root.attachMovie("PinBox", name, root.getNextHighestDepth()); pins[no] = m; mes[0][no] = false; m._x = x * PIN_W + border_mc._x + 1; m._y = y * PIN_W + border_mc._y + 1; m.status = false; setPinEvent(m); } } }
MMLデータの作成
JavaScriptSakuraに渡すデータとなる、オルゴール本体で作成するデータは単純なMMLです。それでは、MMLの作成メソッドを見てみましょう。
配列変数mes
にピンの状態が記録されています。この「mes
」は二次元配列変数で、「mes[小節番号][ピン番号]
」のように、配列の1つ目を何小節目のデータであるか、2つ目をピンの番号と管理しています。
// ノートデータ var note:Array = ['o5c','o5d','o5e','o5f','o5g','o5a','o5b', 'o6c','o6d','o6e','o6f','o6g','o6a','o6b']; // MMLを作成しているところ function getMML():String { measure_change(); var res:String = ""; for (var m = 0; m < mes.length; m++) { for (var x = 0; x < COL; x++) { var ary:Array = new Array(); for (var y = 0; y < ROW; y++) { var no = y * COL + x; if (mes[m][no]) { ary.push(note[y]); // ---(*1) } } if (ary.length == 0) { res += "r"; // ---(*2) } else { res += ary.join("0"); // ---(*3) } } } return "Tempo=" + tempo_num.value + "\n@11o5l8r16\n" + res; } function play_btn_click() // ---(*4)再生ボタンが押された時 { var mml = getMML(); // MMLを生成する ExternalInterface.call("mmlPlay", mml); // JavaScriptの関数を呼ぶ }
このオルゴールでは、行(X軸)方向が時間を表し、列(Y軸)方向が音程を表しています。そこで、列(Y軸)ごとに、ピンがあるか調べ、もしあれば、音程に対応するMMLを追加し(*1)、ピンがなければ、休符「r」を出力する(*2)ようにしています。
ちなみに、列上に複数のピンが置かれることがあります。この時は、和音を出力する必要があります。JavaScriptSakuraで和音を表すには、音の長さ「0」でつなぐことになっているので、各音符に「0」をつけてつなげています(*3)。
オルゴールの再生ボタンが押されたときは(*4)、上記の手順でMMLを作成し、FlashのExternalInterface.call()
を利用して、JavaScriptの関数を呼び出します。
掲示板への保存と読み込み
上記で見たとおり、再生時に作成するのは、単純なMMLです。そこで、掲示板に保存するデータもMMLにしたいところですが、MMLは解析が若干大変なので、保存したデータを再現するのが簡単な形式を作ってこれを掲示板に保存することにしました。ここでは、データの保存、読み込みの方法を見てみようと思います。
次のソースはオルゴールのデータを保存用データに変換している部分です。
function dataEncode() { var opt:String = ""; opt += "Tempo=" + tempo_num.value; var res:String = ""; for (var m = 0; m < mes.length; m++) { for (var x = 0; x < COL; x++) { for (var y = 0; y < ROW; y++) { var no = y * COL + x; res += (mes[m][no]) ? "1" : "0"; } if (x != (COL-1)) res += ","; } if (m != (mes.length-1)) res += ":"; } return opt +":"+res; }
このデータ形式は、読み込み、保存が簡単になるように考えた形式で、各小節を「:」で区切り、各列(Y軸)方向を「,」で区切ったものです。行(X軸)は、区切りなし、オンを「1」オフを「0」としてつなげました。そして、1小節目の前に、テンポなどの情報をヘッダとして持たせました。
これによって変換したデータは、次のように簡単な手順で復元できます。
function dataDecode(data:String) { mes = new Array(); var opt:Object = new Object(); var ma:Array = data.split(":"); var opts = ma[0]; ... // data for (var m = 0; m < ma.length; m++) { mes[m] = new Array(); var cols = ma[m].split(","); for (var x = 0; x < cols.length; x++) { var rows = cols[x].split(""); // split to byte for (var y = 0; y < rows.length; y++) { var no = y * COL + x; mes[m][no] = (rows[y] == "1"); } } } ...
こうして作成したデータは、ユーザーのコンピューターに保存されるのではなく、サーバー側に保存されます。サーバーのCGIへデータを投稿するプログラムは次のようになります。
function postForm() { var self:MusicBoxMain = this; // フォームの値を取り出す var name = form_mc.name_txt.text; var title = form_mc.title_txt.text; var comment = form_mc.comment_txt.text; var info_txt:TextField = form_mc.info_txt; var password = form_mc.password_txt.text; // 正当性のチェック (*1) if (name == "" || title == "" || comment == "") { info_txt.text = "項目を記入してください。"; return; } // 投稿データの作成 (*2) var send_lv:LoadVars = new LoadVars(); var recv_lv:LoadVars = new LoadVars(); send_lv.name = name; send_lv.title = title; send_lv.comment = comment; if (mbox_id >= 0) { send_lv.mode = "edit"; send_lv.id = mbox_id; } else { send_lv.mode = "new"; } send_lv.password = password; send_lv.mb = dataEncode(); trace(send_lv.mb); // 投稿完了チェックイベント (*3) recv_lv.onLoad = function (ok) { if(!ok) { info_txt.text = "保存失敗"; return; } if (recv_lv.result == "ng") { info_txt.text = "失敗:" + recv_lv.reason; } else { info_txt.text = "保存しました。"; self.mbox_id = recv_lv.id; var d:Date = new Date(); getURL(self.cgi + "?time=" + d.getTime()); } } // データの投稿 if (!send_lv.sendAndLoad(self.cgi, recv_lv, "POST")) { info_txt.text = "失敗です。"; } }
はじめに、フォームに入力された名前や曲名、コメントなどのデータを取得して、入力チェックを行います。ここでは行っていませんが、本格的な機能を実装する場合には、入力チェック(*1)では、いたずら防止のために、最大入力文字数のチェックなども行いたいところです。
投稿データの作成(*2)では、送信用と受信用で2つのLoadVars
のインスタンスを生成しています。送信用のLoadVars
インスタンスには、名前や曲名、曲データをセットします。
受信用のLoadVars
インスタンスには、投稿完了時に行われるチェック用のイベントを設定しておきます(*3)。このイベントでは、投稿が失敗した場合は、その理由を画面に表示し、投稿が成功したら、掲示板のデータを更新するために、getURL()
メソッドで、ページをリロードします。このとき、ページが常に最新の状態で表示されるように、現在の時刻をページの引数へ与えています。これにより、ページがキャッシュされるのを防いでいます。
また、受信イベント内で、このメソッドが定義されているメインクラスMusicBoxMain
クラス自身を「self
」という名前で参照しています。これは、受信用に作成したLoadVars
のインスタンスrecv_lv
の、onLoad
メソッドの実行が失敗したときのためのローカル変数となります。失敗時にはonLoad
メソッドのクラス自身を表す、this
が「recv_lv
」となってしまい、投稿が失敗したことを表示するためにMusicBoxMain
クラスを参照したくても、間接的に参照できなくなってしまうので、別途、「self
」というローカル変数を用意しているのです。