SHOEISHA iD

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

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

特集記事

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

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

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

JSSの内部(2/2)

意味解析

 JSSは意味解析を変数SakuraMidiContextの中で実装しています。

 意味解析では、構文解析された情報を元にMIDIデータ化を行います。MIDIデータは、メッセージの種類ごとに書式が決められており、1メッセージあたり2バイト以上のバイト列になっています。

意味解析
バイト列意味
8n kk vvノートオフ。鍵盤から指を離すことに相当します。kk=ノート番号。vv=打鍵の強さ。JSSではノートオフ打鍵の強さは0にしています
9n kk vvノートオン。鍵盤を指で押すことに相当します。kk=ノート番号。vv=打鍵の強さ
An kk vvポリフォニックキープレッシャー。kk=ノート番号
Bn kk mmコントロールチェンジ。kk=コントローラー番号。mm=パラメータ値
Cn ppプログラムチェンジ。pp=プログラム番号(音色番号)
Dn vvチャンネルプレッシャー。vv=チャンネルプレッシャー値
En ll mmピッチベンドチェンジ。ll=下位ピッチベンド値、mm=上位ピッチベンド値
Fx xx ..エクスクルーシブメッセージ/メタイベント他

 nはチャンネル番号[0~F]です。MIDIでは最大16種類の楽器を取り扱うことができ、その区別にチャンネル番号を使用します。

 MMLでは音符を一文字で表現していましたが、MIDIではノートオンとノートオフの合計2メッセージになることが分かると思います。MIDIメッセージ自体に時間情報は含まれていませんので、MIDIファイル化を前提にした場合、時間管理が必要となります。すなわち、意味解析では、「時間とMIDIメッセージバイト列」の配列を結果として生成します。

 なお、JSSでは4分音符の長さを120としてMML開始からの時間を管理しています。この時間数値のことをMIDIではステップと言います。

 また、JavaScriptではバイト列を取り扱えないので、整数(0~255)の配列としています。ここではいくつかの例を元に、意味解析がどんな処理になっているのかを解説します。

ケース1:ノート

 該当MIDIメッセージは、8n(ノートオフ)および、9n(ノートオン)になります。これらメッセージには「ノート番号」と「ベロシティ」が必要です。最初にノートオンメッセージを作り、その後適切な時間後にノートオフメッセージを作ることになります。

 ノート番号とは音符に割り当てられた番号であり、ノート番号60がピアノで言うところの中央の「ド」になります。そこから半音移動するたびに1が増減されます。例えば、ド#は61、レは62となります。JSSでは、中央のドをオクターブ5と定義しているのでオクターブ4(1オクターブ下)のドだとノート番号は48になります。

 ベロシティは鍵盤をたたく強さです。0~127で表します。0のときはノートオフです。

 例えば次のようなノートだと、

c+4,75,90,5

 意味解析の結果は、次のようになります。

意味解析結果
パラメータ意味解析結果
ノートc
臨時記号+
長さ4
ゲート75
ベロシティ90
タイミング5

 この場合、MIDIメッセージに必要なパラメータは次のようになります。

MIDIメッセージに必要なパラメータ
パラメータ名実際の例
ノート番号オクターブを5としてcはノート番号60なので、これに臨時記号のシャープ分を追加して、合計61(16進数だと3D)となります
ノートオンまでの時間現在時刻からタイミング分進めた時間になります。現在をTcとするとTc+5となります
ノートオフまでの時間4分音符のステップ数は120です。ゲートが75なので、実際に鳴っている時間は120ステップの75%である90ステップになります。タイミングを考慮して、ノートオフ時刻は、現在時刻をTcとしてTc+95になります
ベロシティ90(16進数だと5A)です

 ここから、出来上がるMIDIメッセージは、現在時刻をTcとすると、次のようになります。

出来上がるMIDIメッセージ
時刻MIDIメッセージ
Tc+590 3D 5A
Tc+9580 3D 00

 この音符は4分音符ですので、現在時刻は120ステップ進みます。この処理は、JSSではSakuraMidiContext.event_noteで実装しています。

ケース2:コントロールチェンジの場合

 MIDIにはコントローラーと呼ばれる一連の音源操作機能があります。コントロールチェンジメッセージを使うことで、それぞれの機能を操作できます。例えば、次のようなコントローラーをMMLから指定できるようになっています(一例)。

コントローラー(一例)
バイト列意味MMLでの表記
Bn 01 vvモジュレーション。音の揺れ具合を指定M
Bn 07 vvボリューム。音の大きさを指定V
Bn 5B vvエフェクト1(リバーブ)。残響効果の強さを指定REV
Bn 5D vvエフェクト3(コーラス)。コーラスの強さを指定CHO

 それぞれの値は、すべて0~127の範囲で指定します。

 例えば次のようなパラメータだと、

CHO(100)

 構文解析の結果は、次のようになります。

構文解析結果
パラメータ意味解析結果
パラメータCHO
引数100(16進数だと64)

 ここから、現在時刻をTcとすると、出来上がるMIDIメッセージは、次のとおりです。

MIDIメッセージ
時刻MIDIメッセージ
TcB0 5D 64

 この処理は、JSSではSakuraMidiContext.event_control_changeで実装しています。

ケース3:コンテキストの場合

 MIDIメッセージには変換されませんが、コンテキストとして保持しなければならないパラメータがあります。一例を挙げると、オクターブ番号はoで指定しますが、この値は保持しなければなりません。例えば、

o5 c o4 c

 というMMLの場合、最初のノートcはオクターブ5なのでノート番号60ですが、あとのほうのノートcはオクターブ4なのでノート番号は48になります。また、

o5 c < c

 というMMLの場合も「<」はオクターブを一つ下げるという意味なので先ほどのMMLとまったく同じ処理になります。

 このように、ノートにおいて省略できるパラメータである長さ、ゲート、ベロシティ、タイミングすべては、コンテキストとして保持する必要があります。JSSでは、コンテキストをSakuraMidiContext.tracksに保持しています。

SMF変換

 SMF変換では、意味解析によりMIDIメッセージ化されたデータをSMFに変換します。JSSはSakuraSMFFormatterの中で実装しています。

 SMFはヘッダチャンクおよび複数のトラックチャンクから成っています。トラックチャンクは複数のMIDIメッセージを時間順に含みます。

SMF概略図・再掲
SMF概略図・再掲

 SMFはフォーマット0、1、2の3種類が規定されていますが、ここではFormat1専用としています。

 ヘッダチャンクは14バイトで固定です。4バイトの固定文字列MThd、32bitビッグエンディアンのデータ長、6バイトのデータにより構成されます。最初の10バイトは次のようになっています。

header_chunk = [0x4d, 0x54, 0x68, 0x64, // Magic, MThd
        0x00, 0x00, 0x00, 0x06, // length
        0x00, 0x01 // format 1
];

 この後にトラック数とステップ数が、16bitビッグエンディアンで格納されます。

 トラックチャンクのヘッダは8バイトです。4バイトの固定文字列MTrk、32bitビッグエンディアンのデータ長により構成されます。その後、MIDIメッセージの数だけチャンクが伸びていきます。

var chunk = [0x4d, 0x54, 0x72, 0x6b, // Magic, MTrk
         0x00, 0x00, 0x00, 0x00 // length
];

 意味解析の段階で既に時刻つきのMIDIメッセージ配列を作っています。SMF変換では、SakuraSMFFormatter.midis.tracks[i].Events(iはトラック番号)にトラック単位で格納されています。本稿では示していませんが、JSSのMMLでは休符にマイナス長を指定して時刻を逆戻りできる仕様になっているので、MIDIメッセージの時刻並びは前後することがあります。

 例えば、次のようなメッセージの並びもあり得ます。

メッセージ例
時間バイト列
590 3D 5A
120B0 5D 64
9580 3D 00

 そこで、最初に時刻をキーにソートします。JavaScriptでは配列にソート用関数があるので、比較関数を与えてやればどんなデータ型でもソートが可能です。該当のトラック情報をtrとして、次のようなコードでソートできます。

tr.Events = tr.Events.sort(function(a, b){
  return (a.time > b.time ? 1 : -1);
});

 結果として次のようなMIDIメッセージ順になります。

メッセージ結果
時間バイト列
590 3D 5A
9580 3D 00
120B0 5D 64

 SMFでは、時刻を相対時間で表すことになっているので、前後のMIDIメッセージ間で時刻を減算し、メッセージ間の相対時間を求めます。結果として、次のようになります。

メッセージ間の早退時間
時間バイト列
590 3D 5A
9080 3D 00
25B0 5D 64

 これをトラックチャンクに並べてデータ長を調整すればSMFの出来上がりです。

CGIへの渡し方

 JSSでできることは、MMLをSMFのバイト列へ変換することです。実際にSMFのバイナリファイルを生成することはできないので、CGIへ渡すようにしています。バイト列は、そのままでは整数の配列にすぎないので、Hexbinary化したあとxmlhttprequestを使ってCGIへ渡しています。

 Hexbinary化は変数Hexbinaryに、xmlhttprequestによるHTTP通信については変数Ajaxに実装しています。実際のコード例は次のようなものです。

var result = "type=hex&midi=" +
  Hexbinary.encode(SakuraSMFFormatter.results); 
    // SMFバイト列をHexbinary化
var ajax = new Ajax.Updater(this.target_div, this.url,{ 
    // xmlhttprequestを使ってPOST
  method: 'POST', parameters: result
});

 CGI側では渡されたHexbinaryをバイナリファイルとして保存し、そのURLを返せばよいことになります。

次のページ
Flashでオルゴール本体を作る

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

  • 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」など、さまざまなカンファレンスを企画・運営しています。

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

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

メールバックナンバー

アクセスランキング

アクセスランキング