第 1 章「Java Sound API の紹介」では、JavaTM Sound API の MIDI 機能の概要を説明しました。この章は、読者が第 1 章を読み終えていることを前提としています。ここでは、javax.sound.midi
パッケージを介してアクセスする Java Sound API のMIDI アーキテクチャについてより詳しく説明します。復習または導入として、MIDI 自体の基本機能について説明してから、本題の Java Sound API の MIDI 機能について説明します。その後、Java Sound API が MIDI を処理する方法について説明し、続く章で扱うプログラミング作業に備えます。この章では、MIDI API について、主にデータとデバイスの 2 つの面から考察します。
MIDI (Musical Instrument Digital Interface) は、電子キーボードなどの電子楽器とパーソナルコンピュータとの通信プロトコルを定義する標準規格です。MIDI データは、実演中は、特殊ケーブルを介して送信されます。また、標準形式のファイルに格納して、あとで再生や編集を行うこともできます。
ここでは、Java Sound API から離れて、MIDI の基本事項について復習します。MIDI についての知識がある読者は復習として、MIDI の知識がない読者は導入として活用し、後述する Java Sound API の MIDI パッケージの説明に備えてください。MIDI を十分理解している読者は、この節を読まずに次の節に進んでも構いません。本格的な MIDI アプリケーションを作成する場合、MIDI に精通していないプログラマは、このドキュメントに記載されている説明よりもさらに詳細な MIDI に関する説明が必要になります。その場合は、http://www.midi.org からハードコピーでのみ入手可能な『Complete MIDI 1.0 Detailed Specification』を参照してください (Web から入手できるのは簡約版のみ)。
MIDI は、ハードウェア仕様であり、かつソフトウェア仕様でもあります。MIDI の設計を理解するには、その歴史を理解するのが早道です。MIDI は、本来、シンセサイザなどの電子キーボード楽器間での音楽的イベント (キーの押下など) の引き渡しを目的として設計されました。第 1 章で説明したとおり、MIDI データは、主にミュージシャンのジェスチャを伝達する制御イベントで構成されています。MIDI データには、イベントから生じるオーディオは含まれません。シーケンサと呼ばれるハードウェア機器にはシンセサイザを制御する音符のシーケンスが記憶されており、楽器演奏の記録および再生を可能にします。その後、ハードウェアインタフェースの発展により、MIDI 機器をコンピュータのシリアルポートに接続して、シーケンサをソフトウェア上に実装することが可能になりました。さらに最近では、コンピュータのサウンドカードが MIDI I/O と楽器音の合成のためのハードウェアを組み込んでいます。現在では、多くの MIDI ユーザは、外部の MIDI 機器に接続せずにサウンドカードだけで、MIDI を楽しんでいます。CPU の速度も向上したため、シンセサイザをソフトウェアとして実装することも可能になりました。サウンドカードは、通常オーディオ I/O 用にのみ必要ですが、アプリケーションによっては、外部 MIDI デバイスとの通信に必要な場合もあります。
MIDI 仕様の中でハードウェアに関する部分は少なく、ここには MIDI ケーブルのピン数およびケーブルを差し込むジャックについて規定されています。ハードウェアに関する部分は、プログラミングの際に考慮に入れる必要はありません。シーケンサやシンセサイザなどの本来必須ハードウェアだった機器類が、現在ではソフトウェアで実装されているため、MIDI ハードウェアデバイスに関して大半のプログラマが理解する必要があるとすれば、MIDI におけるメタファを理解するためだけです。ただし、いくつかの重要な音楽用アプリケーションでは、外部 MIDI ハードウェアデバイスが依然として重要な役割を果たしているため、Java Sound API は MIDI データの入力および出力をサポートしています。
MIDI 仕様のソフトウェアに関する部分には、幅広い内容が含まれています。ソフトウェアに関する部分では、MIDI データの構造、およびシンセサイザなどのデバイスが MIDI データに応答する方法が記述されています。MIDI データでは、「ストリーミング」または「シーケンシング」が可能であることを理解することが重要です。この双対関係は、『Complete MIDI 1.0 Detailed Specification』の次の 2 つの部分を反映しています。
これから、MIDI 仕様の 2 つの部分の目的を考察することにより、ストリーミングとシーケンシングの意味を学びます。MIDI 仕様の 2 つの部分のうち、最初の部分には、非公式のいわゆる「MIDI ワイヤプロトコル」について記述されています。MIDI ワイヤプロトコルは、当初から存在する MIDI プロトコルで、MIDI データが MIDI ケーブル (ワイヤ) 経由で送信されているという前提に基づいています。ケーブルは、ある MIDI デバイスから別のデバイスにデジタルデータを送信します。各 MIDI デバイスは、楽器またはそれに類似した機器の場合もあれば、MIDI 対応のサウンドカードや MIDI への接続用シリアルポートインタフェースを備えた汎用的なコンピュータの場合もあります。
MIDI ワイヤプロトコルの定義に従って、MIDI データはメッセージに編成されます。種類の異なるメッセージは、「ステータスバイト」と呼ばれるメッセージの最初のバイトで識別されます (最上位ビットが 1 に設定されるバイトはステータスバイトのみ)。メッセージ内でステータスバイトに続くバイトは、「データバイト」と呼ばれます。「チャネル」メッセージと呼ばれる MIDI メッセージが保持するステータスバイトでは、そのうちの 4 バイトでチャネルメッセージの種類を指定し、残りの 4 バイトでチャネル番号を指定します。このため、16 の MIDI チャネルが存在することになります。これらの仮想チャネルのすべてまたは 1 つでチャネルメッセージに応答するように、MIDI メッセージを受信するデバイスを設定できます。MIDI チャネルをオーディオチャネルと混同しないようにしてください。各 MIDI チャネルは、異なる楽器音の送信に使用されます。たとえば、一般的なチャネルメッセージであるノートオンとノートオフを考えてみましょう。これらはそれぞれ、音を鳴らす動作を開始および停止する機能です。これら 2 つのメッセージは、それぞれ 2 つのデータバイトを保持します。最初のデータバイトは音符のピッチを、2 番目のデータバイトは「ベロシティ」(キーボード楽器の場合、キーを打鍵する速さ) を指定します。
MIDI ワイヤプロトコルは、MIDI データ用のストリーミングモデルを定義します。このプロトコルの主な機能は、MIDI データのバイトをリアルタイムに渡すこと、つまりストリーム配信を行うことです。データ自体にはタイミング情報は含まれません。各イベントの着信時間は正確であると見なされて、着信時に処理されます。このモデルは、ミュージシャンが生成する楽譜の場合には問題ありませんが、あとで再生するためにそれらの音符を記憶したり、リアルタイム以外で曲を構成する場合には不十分です。本来、MIDI は、多くのミュージシャンがコンピュータを使用するようになる前の時代に、楽器演奏用 (キーボード奏者が複数のシンセサイザを制御する場合など) として設計されたことを考えると、この制約の理由を理解できます。MIDI 仕様の最初のバージョンは、1984 年に発表されました。
MIDI 仕様内の標準 MIDI ファイルを扱った部分に、MIDI ワイヤプロトコルのタイミング制限に関する記述があります。標準 MIDI ファイルは、MIDI 「イベント」を含むデジタルファイルです。イベントは、MIDI ワイヤプロトコルで定義された単なる MIDI メッセージに、イベントのタイミングを指定する情報が追加されたものです。ただし、次の節で説明するように、MIDI ワイヤプロトコルメッセージに対応しないイベントも存在します。追加のタイミング情報は、メッセージに記述された操作をいつ実行するかを示すバイト群です。つまり、標準 MIDI ファイルは、再生する音だけではなく、それぞれを正確にいつ再生するかを指定します。このため、標準 MIDI ファイルは楽譜にいくらか似ています。
標準 MIDI ファイル内の情報は、「シーケンス」と呼ばれます。標準 MIDI ファイルには、1 つまたは複数の「トラック」が含まれます。ライブ演奏の場合、各トラックには、1 つの楽器で演奏される音が含まれます。シーケンサは、シーケンスを読み取り、読み取った MIDI メッセージを適切なタイミングで配信できるソフトウェアまたはハードウェアデバイスです。シーケンサは、オーケストラの指揮者に似た働きをします。つまり、すべての音符に関する情報 (タイミングも含む) を保持し、楽譜をいつ演奏すべきかをほかの実体に通知します。
ここまでで、MIDI 仕様がストリームおよびシーケンス対象の音楽データを処理する方法を概観しました。これから、Java Sound API が音楽データを表現する方法について考察します。
MidiMessage
は、「raw」MIDI メッセージを表現する抽象クラスです。通常、「raw」MIDI メッセージは MIDI ワイヤプロトコルにより定義されたメッセージです。MIDI メッセージを標準 MIDI ファイル仕様により定義されたイベントとすることもできますが、その場合イベントのタイミング情報は含まれません。raw MIDI メッセージには、3 つのカテゴリが存在します。各カテゴリは、次の 3 つの MidiMessage
サブクラスにより Java Sound API で表現されます。
ShortEvents
はもっとも一般的なメッセージで、ステータスバイトのあとに最大 2 データバイトを持つ。ノートオンやノートオフなどのチャネルメッセージ、およびほかのいくつかのメッセージは、すべてショートメッセージ
SysexMessages
には、「システム専用」の MIDI メッセージが含まれる。これらは多くのバイトを持つことが可能で、一般に、メーカ固有の指示が含まれる
MetaMessages
は MIDI ファイル内で発生しますが、MIDI ワイヤプロトコル内では発生しない。メタメッセージには、シーケンサには有用でも通常シンセサイザには無意味な歌詞やテンポなどの設定データが含まれる
すでに説明したように、標準 MIDI ファイルには、「raw」 MIDI メッセージとタイミング情報のラッパーとして機能するイベントが含まれます。Java Sound API の MidiEvent
クラスのインスタンスは、標準 MIDI ファイルに格納予定のイベントなどを表します。
MidiEvent
の API には、イベントのタイミング値を設定および取得するメソッドが含まれます。MidiMessage
のサブクラスのインスタンスである、埋め込み raw MIDI メッセージを取得するメソッドも存在します。このメソッドについては、後述します。埋め込み raw MIDI メッセージは、MidiEvent
の構成時にのみ設定可能です。
すでに説明したように、標準 MIDI ファイルは、トラック内に配置するイベントを格納します。通常、ファイルは 1 つの音楽構成を表し、各トラックは 1 つの楽器の演奏パートを表します。楽器演奏者の演奏する各音は、少なくとも 2 つのイベント、音の開始を示す Note On および音の終了を示す Note Off によって表現されます。トラックには、上述のメタイベントなどの、音に関連しないイベントも含まれます。
Java Sound API は、次の 3 つの階層で MIDI データを編成します。
Track
は MidiEvents
のコレクション、Sequence
は Tracks
のコレクションです。この階層は、標準 MIDI ファイル仕様のファイル、トラック、およびイベントを反映しています。なお、これは、包含関係および所有権の階層であり、継承を示すクラス階層ではありません。これら 3 つのクラスはそれぞれ、java.lang.Object
を直接継承します。
Sequence
は、MIDI ファイルから読み込むか、ゼロから作成することが可能で、Sequence
に Track
を追加 (または削除) することによって編集できます。同様に MidiEvent
は、シーケンス内のトラックに追加することも、トラックから削除することもできます。
前の節では、Java Sound API で MIDI メッセージを表現する方法を説明しました。ただし、MIDI メッセージは、真空中に存在しているわけではありません。一般に、MIDI メッセージはあるデバイスから別のデバイスに送信されます。Java Sound API を使用するプログラムは、MIDI メッセージを新たに作成できますが、シーケンサなどのソフトウエアデバイスによりメッセージを作成したり、MIDI 入力ポート経由でコンピュータの外部からメッセージを受信するほうがより一般的です。このようなデバイスは、通常、シンセサイザや MIDI 出力ポートなどの別のデバイスにメッセージを送信します。
外部 MIDI ハードウェアデバイスの中で多くのデバイスが、MIDI メッセージをほかのデバイスに送信したり、ほかのデバイスからメッセージを受信したりできます。同様に、Java Sound API では、MidiDevice
インタフェースを実装するソフトウェアオブジェクトがメッセージの送信と受信を実行できます。このようなオブジェクトは、純粋にソフトウェアで実装することも、サウンドカードの MIDI 機能などのハードウェアへのインタフェースとすることも可能です。基本的な MidiDevice
インタフェースは、一般に MIDI 入力ポートまたは出力ポートが必要とする機能のすべてを提供します。ただし、シンセサイザおよびシーケンサはそれぞれ、MidiDevice
のサブインタフェースであるSynthesizer
と Sequencer
をそれぞれ実装します。
MidiDevice
インタフェースには、デバイスをオープンおよびクローズするための API が含まれます。また、デバイスのテキスト表現 (名前、ベンダー、バージョンなど) を提供する MidiDevice.Info
と呼ばれる内部クラスも含まれます。すでにこのドキュメントのサンプリングオーディオに関する記述に目を通している読者は、この API に見覚えがあると感じるはずです。それは、この API の設計が、 javax.sampled.Mixer
インタフェースの設計と類似しているためです。 javax.sampled.Mixer
インタフェースは、オーディオデバイスを表し、類似した内部クラス Mixer.Info
を保持します。
大半の MIDI デバイスは、MidiMessages
の送信、受信、またはその両方を実行できます。デバイスは、所有する 1 つまたは複数のトランスミッタオブジェクトを介してデータを送信します。同様の方法で、デバイスは 1 つまたは複数のレシーバオブジェクトを介してデータを受信します。トランスミッタオブジェクトは Transmitter
インタフェースを実装し、レシーバは Receiver
インタフェースを実装します。
各トランスミッタは一度に 1 つのレシーバにだけ接続できます。その逆も同様です。MIDI メッセージを複数のデバイスに同時に送信するデバイスは、それぞれが異なるデバイスに接続された複数のトランスミッタを保持することにより、これを可能にしています。同様に、一度に複数のソースから MIDI メッセージを受信可能なデバイスは、複数のレシーバを保持しています。
シーケンサは、MIDI イベントのシーケンスの取り込みおよび再生を行うデバイスです。シーケンサは、トランスミッタを保持します。これは、一般にシーケンサは、シーケンスに格納された MIDI メッセージをシンセサイザや MIDI 出力ポートなどの別のデバイスに送信するためです。シーケンサは、レシーバも保持します。これは、MIDI メッセージの取り込みおよびシーケンスへの格納を実行するためです。MidiDevice
、Sequencer
は、このスーパーインタフェースに対して、基本的な MIDI シーケンス操作用のメソッドを追加します。シーケンサでは、MIDI ファイルからシーケンスをロードし、シーケンスのテンポの問い合わせと設定を行い、ほかのデバイスをそのシーケンスに同期させることができます。アプリケーションプログラムは、オブジェクトを登録しておいて、シーケンサが特定の種類のイベントを処理する際に通知を受けるようにすることができます。
Synthesizer
は、サウンドを生成するデバイスです。これは、オーディオデータを生成する javax.sound.midi
パッケージ内の唯一のオブジェクトです。シンセサイザデバイスは、MIDI チャネルオブジェクトのセットを制御します。通常、MIDI 仕様では 16 の MIDI チャネルが要求されているため、このセットは、16 個の MIDI チャネルオブジェクトで構成されます。これらの MIDI チャネルオブジェクトは、MidiChannel
インタフェースを実装するクラスのインスタンスであり、そのメソッドは、MIDI 仕様の「チャネル音声メッセージ」および「チャネルモードメッセージ」を表します。
アプリケーションプログラムは、シンセサイザの MIDI チャネルオブジェクトのメソッドを直接呼び出すことにより、サウンドを生成できます。ただし、シンセサイザが、1 つまたは複数のレシーバに送信されたメッセージへの応答としてサウンドを生成する場合の方が一般的です。たとえば、シーケンサまたは MIDI 入力ポートからこれらのメッセージを送信できます。シンセサイザは、レシーバが取得した各メッセージを解析し、通常はイベント内に指定された MIDI チャネル番号に応じて、対応するコマンド (noteOn
や controlChange
など) を MidiChannel
オブジェクトの 1 つにディスパッチします。
MidiChannel
は、これらのメッセージ内の音情報を使って音楽を構成します。たとえば、noteOn
メッセージは、ノートのピッチおよび「ベロシティ」(ボリューム) を指定します。ただし、この音情報は十分ではありません。シンセサイザには、各音に対してオーディオ信号を作成する方法についての正確な指示も必要です。これらの指示は、Instrument
によって表されます。通常、各 Instrument
は、実際にある楽器またはサウンドエフェクトをエミュレートします。Instruments
は、シンセサイザであらかじめ設定されているようにすることも、サウンドバンクファイルからロードするようにすることも可能です。シンセサイザでは、Instruments
は、バンク番号 (行) およびプログラム番号 (列) で配置されます。
この章では、MIDI データを理解する上で役立つ基礎的な情報を提供し、また Java Sound API 内の MIDI に関連した重要なインタフェースおよびクラスを紹介しました。次章からは、これらのオブジェクトをアプリケーションプログラムからアクセスして使用する方法について説明します。