SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

JavaScriptとFlashによるWebオルゴールの製作

JavaScript MIDIライブラリ「JavaScriptSakura」を使う

  • X ポスト
  • このエントリーをはてなブックマークに追加

Flashでオルゴール本体を作る

 オルゴールの本体は、Flash 8で作っています。Flashを利用することで、JavaScriptよりも見た目が派手なインターフェイスを手軽に作ることができます。ここでは、MIDIファイルを再生するまでの流れを確認し、その後、オルゴールデータの管理方法などを見ていきます。

再生までの流れ

 前述のJavaScriptSakuraを利用することにより、オルゴール本体では、簡単な音程を表すMMLデータを作るだけで、MIDIファイルを生成できます。再生までの流れは次のようになります。

  1. オルゴール本体で簡単なMMLデータを作る
  2. Flash側でJavaScriptSakuraの関数にMMLデータを渡して呼び出す
  3. JavaScriptSakuraが保存用CGIにMIDIファイルを送信する
  4. QuickTimeでMIDIファイルが再生される

 Flash側で実行することは、画面にグリッドを表示して、ユーザーにピンを立ててもらい、再生ボタンが押されたら、JavaScriptの関数を呼び出すことです。

FlashからJavaScriptの関数を呼ぶ方法

 JavaScriptSakuraを呼ぶために、Flash 8から追加されたExternal APIを利用しています。この機能を使うと、FlashからJavaScriptの関数を読んだり、その逆に、JavaScriptからFlashの関数を呼ぶことができます。

 External APIを使うためには、はじめに、ExternalInterfaceを使うことを宣言します。JavaScriptの関数を呼ぶ場合は、「ExternalInterface.call(関数名, 引数...)」のように記述します。

JavaScriptの関数を呼び出す
// 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のオブジェクトを取得してから、そのオブジェクトのメソッドを呼び出すという手順になります。

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");
 *1 -- HTMLにFlashを表示している部分です。後述しますが、SWFObjectを利用して、SWFファイルを表示します。embedタグのIDをmboxにしています。
 *2 -- ブラウザごとに、Flashのオブジェクトを取得する方法が違うので、オブジェクトの取得を関数にしています。
 *3 -- IDがmboxである、Flashのオブジェクトのメソッド「showMBox(...)」を呼び出します。

 このように、Flash側の宣言は単純ですが、JavaScript側はブラウザごとの差異を気にしないといけないので少し処理が面倒です。

オルゴールのピンについて

 オルゴールでは、ユーザーがピンを立てたところで音が鳴るようになっています。そこで、オン/オフの状態を持つピンを作成し、管理する必要があります。

 はじめに、ピンのオン/オフを表すムービークリップを作りました。これは、タイムライン上に、ピンが立っていない状態を表す「off」と、ピンを立てた状態を表す「on」です。

ピンのムービークリップ(オフ)
ピンのムービークリップ(オフ)
ピンのムービークリップ(オン)
ピンのムービークリップ(オン)
フィルタの利用
 ピンの絵ですが、ただの円を描いたムービークリップを作り、これに、Flash 8から使えるフィルタの「べベル」を適用して凹凸の立体感を出しました。プログラマーがFlashで何か作る場合、絵が描けなくて困ることがありますが、フィルタを利用することで絵心をカバーすることができます。

 この画像をActionScriptからより手軽に使えるように、シンボルのリンケージで、AS2.0クラスでPinBoxクラスに設定します。AS2.0クラスでは、「クラス名.as」という名前のクラスファイルを作り、その中に、ムービーの動作を定義することができます。

PinBoxクラスの定義
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をアクセサメソッド(gettersetter) にして、定義しています。これで、「pinbox.status = true;」のように書くとピンがある状態のグラフィックが表示され、「pinbox.status = false;」でピンがない状態になります。

 オルゴールのメインクラスでは、このPinBoxクラスをattachMovie()で複数作成し、碁盤のように等間隔に配置しています。

PinBoxクラスを並べる
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つ目をピンの番号と管理しています。

MMLデータの作成
// ノートデータ
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」というローカル変数を用意しているのです。

次のページ
Flash SWFファイルを簡単に貼り付ける「SWFObject」

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

クジラ飛行机(クジラヒコウヅクエ)

ソフト企画「くじらはんど」にて、多数のフリーソフトを公開しています。日本語プログラミング言語「なでしこ」、テキスト音楽「サクラ」、日本語Wiki記法が特徴の「KonaWiki」などを公開しています。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

adas(adas)

趣味プログラマー。10年以上前にゲーム作りでプログラミングに目覚めて以来、ソフトウェアで実現できるものは何でも興味の対象に。住処はhttp://www.0and1.org/

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/703 2006/11/24 00:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング