注:5.0 のリリースには、すべての MIDI デバイスで機能する新しいリアルタイムの |
MIDI の世界では、「シーケンサ」とは、タイムスタンプ付きの MIDI メッセージの「シーケンス」を正確に再生または記録できる任意のハードウェアまたはソフトウェアデバイスのことです。同様に、JavaTM Sound API では、Sequencer
抽象インタフェースは、MidiEvent
オブジェクトの Sequence
の再生および記録が可能なオブジェクトのプロパティーを定義します。一般に、Sequencer
はこれらの MidiEvent
シーケンスを、標準 MIDI ファイルからロードするか、または標準 MIDI ファイルに保存します。シーケンスは、編集も可能です。この章では、Sequencer
オブジェクトの使用方法、および作業をする上で必要な関連クラスおよびインタフェースについて説明します。
Sequencer
は、テープレコーダに例えてみると、その本質を捉えることができます。シーケンサとテープレコーダは、多くの点で類似しています。テープレコーダがオーディオを再生するのに対し、シーケンサは MIDI データを再生します。シーケンスとは、MIDI 音楽データをマルチトラック、リニア、時間順で記録したものです。シーケンサはその音楽データに対して、さまざまな速度での再生、巻き戻し、特定区間の往復、ファイルへの記録または保存目的でのファイルへのコピーなどの処理を行います。
第 10 章「MIDI メッセージの送信および受信」では、デバイスが一般に、Receiver
オブジェクトと Transmitter
オブジェクトのいずれかまたは両方を保持することを説明しました。音楽を「再生」する場合、デバイスは通常 Receiver
経由で MidiMessage
を受信します。Receiver
は、Sequencer
に属する Transmitter
から受信した MidiMessage
を送ります。この Receiver
を保持するデバイスとして、Synthesizer
があります。Synthesizer
はオーディオを直接生成するか、または MIDI の出力ポートとして機能します。出力ポートとして機能する場合は、MIDI データを物理ケーブル経由でなんらかの外部機器に送信します。同様に、音楽を「録音」する場合、一般に、タイムスタンプ付きの一連の MidiMessages
が Sequencer
の所有する Receiver
に送信されます。Sequencer
は、MidiMessages
を Sequence
オブジェクト内に配置します。一般に、メッセージを送信するオブジェクトは、ハードウェア入力ポートに関連付けられた Transmitter
で、入力ポートは外部の楽器から取得した MIDI データを中継します。ただし、メッセージの送信を行うデバイスが、ほかの Sequencer
であるか、または Transmitter
を所有するほかのデバイスであることもあります。また、第 10 章で説明したように、Transmitter
をまったく使わないでプログラムからメッセージを送信することも可能です。
Sequencer
は、それ自体で Receiver
と Transmitter
の両方を保持しています。事実、Sequencer
は、記録時に Receiver
経由で MidiMessage
を取得します。再生時には、Transmitter
を使って、記録 (またはファイルからロード) した Sequence
に格納された MidiMessage
を送信します。
Java Sound API 内での Sequencer
の役割を理解する 1 つの方法は、MidiMessage
の集合体/非集合体と捉えることです。独立した一連の分離 MidiMessage
は、音楽イベントのタイミングのマークとなる独自のタイムスタンプとともに Sequencer
に送信されます。MidiMessage
は MidiEvent
オブジェクトにカプセル化され、Sequencer.record
メソッドのアクションによって Sequence
オブジェクトに収集されます。Sequence
は MidiEvent
の集合体を含むデータ構造で、通常は一連の音符 (多くの場合は楽曲全体) を表しています。再生時に、Sequencer
は、Sequence
内の MidiEvent
オブジェクトから MidiMessage
を再度抽出し、1 つまたは複数のデバイスに送信します。デバイスは、MidiMessage
のサウンド化、保存、変更、またはほかのデバイスへの引き渡しを行います。
シーケンサの中には、トランスミッタもレシーバも保持しないものもあります。たとえば、この種のシーケンサは、Receiver
経由で MidiMessages
を受信する代わりに、キーボードイベントまたはマウスイベントの結果として MidiEvent
をゼロから作成します。同様に、MidiMessage
を別個のオブジェクトに関連付けられた Receiver
に送信する代わりに、内部のシンセサイザ (実際にはシーケンサと同じオブジェクトの場合もある) と直接通信することにより、音楽を再生します。ただし、この章の後半では、Receiver
および Transmitter
を使用する一般的なシーケンサを前提に説明します。
第 10 章「MIDI メッセージの送信および受信」で説明したように、シーケンサを使用しなくても、アプリケーションプログラムから直接 MIDI メッセージをデバイスに送信できます。プログラムは、メッセージを送付するたびに Receiver.send
メソッドを呼び出せばいいのです。これは直接的な方法で、プログラム自体がリアルタイムにメッセージを作成する場合に有用です。たとえば、画面に表示されたピアノの鍵盤をユーザーがクリックして音を再生するプログラムについて考えましょう。プログラムは、マウスダウンイベントをキャッチするとすぐに適切なノートオンメッセージをシンセサイザに送信します。
第 10 章の説明にあるように、プログラムで、デバイスの受信側に送信する MIDI メッセージにタイムスタンプを含めることができます。ただし、このようなタイムスタンプは、タイミングを微調整して待ち時間処理を訂正する場合だけに使用されます。呼び出し側は、任意のタイムスタンプを設定することは通常できません。Receiver.send
に渡される時刻の値は、現在の時刻に近い値でなければなりません。さもないと、受信デバイスがメッセージを正確にスケジュールできなくなります。これは、アプリケーションプログラムで、リアルタイムのイベントに応答して各メッセージを生成する代わりに、曲全体の MIDI メッセージのキューを前もって作成する場合、Receiver.send
の各呼び出しがほぼ正確な時間に行われるようにするためには、スケジュールの際に細心の注意を払う必要があることを意味します。
幸い、大半のアプリケーションプログラムでは、このようなスケジューリングの問題を気にする必要はありません。プログラムは、Receiver.send
自体を呼び出す代わりに、Sequencer
オブジェクトを使用して、MIDI メッセージのキューを管理できます。シーケンサは、メッセージのスケジューリングおよび送信を行います。 つまり、正確なタイミングで音楽を再生します。シーケンサを利用する方法は、一般に、リアルタイムではない一連の MIDI メッセージをリアルタイムの一連のメッセージに変換する場合 (再生時など) や、その逆を実行する場合 (記録時など) に便利です。シーケンサは、MIDI ファイルからのデータ再生、および MIDI 入力ポートからのデータ記録でよく使用されています。
Sequencer
API についての詳しい考察に入る前に、シーケンスに格納されるデータの種類を説明します。
Java Sound API では、シーケンサは記録済み MIDI データの編成方法を定めた、標準 MIDI ファイル仕様に厳密に準拠しています。すでに説明したように、Sequence
は、的確なタイミングで編成される MidiEvent
の集合です。ただし、Sequence
には、リニアな一連の MidiEvent
より上の構造が含まれます。Sequence
には、総合的なタイミング情報のほかに Track
の集まりが含まれます。MidiEvent
データを保持するのは、この Track
です。このため、シーケンサにより再生されるデータは、Sequencer
、Track
、および MidiEvent
という 3 階層のオブジェクトで構成されています。
これらのオブジェクトの従来の使用法は、Sequence
が曲全体または曲の 1 楽節を表し、各 Track
はアンサンブル中の 1 つの音声または演奏者に対応していました。この様式では、特定の Track
のデータはすべて、その音声または演奏者用に予約された特定の MIDI チャネルに符号化されます。
このデータ編成方法は、シーケンスを編集する場合には有用ですが、Track
を使用する従来のやり方の 1 つにすぎないことに留意してください。Track
クラス自体の定義には、異なる MIDI チャネルの混合 MidiEvent
を含まないという規定はありません。たとえば、完全にマルチチャネル化された MIDI 構成をミックスして 1 つの Track
に記録することができます。また、標準 MIDI ファイルの Type 0 には、Type 1 や Type 2 とは異なり、定義上 1 トラックしか含まれません。このため、このようなファイルから読み取った Sequence
は単一の Track
オブジェクトしか保持しません。
第 8 章「MIDI パッケージの概要」の説明にあるように、Java Sound API には、大半の標準 MIDI メッセージの基になっている 2 または 3 バイトの raw シーケンスに対応する MidiMessage
オブジェクトが含まれています。MidiEvent
は、MidiMessage
をイベントの発生時を指定するタイミング値とともにパッケージ化したものです。シーケンスとは、実際には、3 階層ではなく 4 または 5 階層のデータで構成されているといえます。見かけ上の最下層である MidiEvent
には、より低レベルの MidiMessage
が含まれ、また MidiMessage
オブジェクトには標準 MIDI メッセージを構成するバイト配列が含まれるためです。
Java Sound API では、MidiMessage
をタイミング値と関連付けるための 2 種類の方法が用意されています。1 つ目は、「シーケンサをいつ使用するか」で説明した方法です。この方法の詳細については、第 10 章「MIDI メッセージの送信および受信」の「トランスミッタを使わずにメッセージをレシーバに送信する方法」および「タイムスタンプの理解」を参照してください。Receiver
の send
メソッドが MidiMessage
引数およびタイムスタンプ引数を取ることが説明されています。そのようなタイムスタンプは、マイクロ秒単位でのみ表現されます。
MidiMessage
が指定されたタイミングを保持する 2 つ目の方法は、MidiMessage
を MidiEvent
内でカプセル化する方法です。この場合、タイミングは「ティック」と呼ばれる、より抽象的な単位で表現されます。
1 ティックのデュレーションは、シーケンスによって異なります。ただし、1 つのシーケンス内で異なることはありません。値は、標準 MIDI ファイルのヘッダーに格納されます。ティックのサイズは、次の 2 種類の単位のどちらかを使って指定されます。
一方 SMPTE の場合、単位は絶対時間に基づくため、テンポという概念を適用することはできません。4 種類の SMPTE 規約が存在し、各規約は秒単位での動画フレーム数を示しています。秒単位のフレーム数として、24、25、29.97、30 のどれかを指定できます。SMPTE タイムコードとともに使用することで、ティックのサイズをフレームの何分の一かで表現できます。
Java Sound API では、Sequence.getDivisionType
を呼び出すことで、特定のシーケンスで使われている単位 (PPQ、または SMPTE 単位のどれか) を判断できます。単位がわかったら、Sequence.getResolution
を呼び出してティックのサイズを計算できます。2 つ目の方法では、除算形式が PPQ の場合は、4 分音符ごとのティック数が返され、除算形式が SMPTE 規格の 1 つである場合は、SMPTE フレームごとのティック数が返されます。PPQ の場合は、次の式を使ってティックのサイズを取得できます。
ticksPerSecond = resolution * (currentTempoInBeatsPerMinute / 60.0); tickSize = 1.0 / ticksPerSecond;
framesPerSecond = (divisionType == Sequence.SMPTE_24 ? 24 : (divisionType == Sequence.SMPTE_25 ? 25 : (divisionType == Sequence.SMPTE_30 ? 30 : (divisionType == Sequence.SMPTE_30DROP ?
29.97)))); ticksPerSecond = resolution * framesPerSecond; tickSize = 1.0 / ticksPerSecond;
Java Sound API でのシーケンス内のタイミング定義は、標準 MIDI ファイル仕様の定義を忠実に反映したものです。ただし、重要な相違点が 1 つあります。MidiEvent
に含まれるティック値は、「増分 (デルタ)」時間ではなく、「累積」時間を示すものです。標準 MIDI ファイルでは、各イベントのタイミング情報は、シーケンス内での、前のイベントの開始以降の経過時間を表します。これは増分 (デルタ) 時間と呼ばれます。一方、Java Sound API では、ティックは増分値ではなく、前のイベントの時間値に増分 (デルタ) 値を加えた値になります。つまり、Java Sound API では、各イベントのタイミング値は、シーケンス内の前のイベントのタイミング値よりも常に大きくなります。イベントが同時に発生することになっている場合は、タイミング値は等しくなります。各イベントのタイミング値は、シーケンスの開始を起点とする経過時間を示します。
要約すると、Java Sound API は、タイミング情報を、MIDI ティック単位またはマイクロ秒単位で表現します。MidiEvent
は、タイミング情報を MIDI ティックに換算して格納します。ティックのデュレーションは、Sequence
の総合的なタイミング情報に基づいて算出されます。シーケンスがテンポに基づくタイミングを使用する場合、現時点の音楽上のテンポが使用されます。一方、Receiver
に送信される MidiMessage
に関連付けられたタイムスタンプは、常にマイクロ秒単位で表現されます。
この設計の目標の 1 つは、時間に関する概念の矛盾を避けることです。PPQ 単位を使用する MidiEvent
内での時間の単位を解釈するのは、Sequencer
の役割です。Sequencer
は現在のテンポを考慮しつつこれを解釈してマイクロ秒単位の絶対時間に変換します。さらに、シーケンサは、メッセージを受信するデバイスのオープン時からの相対時間もマイクロ秒単位で表現できなければなりません。シーケンサは複数のトランスミッタを保持することができます。各トランスミッタは、種類のまったく異なるデバイスへの関連付けが可能な異種のレシーバにメッセージを送ります。このため、シーケンサには、複数の変換を同時に実行して、各デバイスがその時間の概念に沿ったタイムスタンプを確実に受信するための機能が求められます。
問題をさらに複雑にする要素として、複数の異なるデバイスで時間の概念を更新するときに、複数の異なるソース (オペレーティングシステムのクロックや、サウンドカードにより維持されるクロックなど) から実行する場合があることです。これは、タイミングがシーケンサのタイミングに相関して変化する可能性があることを意味します。シーケンサとの同期を維持するために、シーケンサの時間の概念に「スレーブ」として自身を位置付けるデバイスもあります。マスター/スレーブの設定については、「シーケンサの高度な機能」で説明します。
Sequencer
インタフェースが提供するメソッド群は、次のようなカテゴリに分類されます。
Sequence
オブジェクトからロードしたり、現在ロードされているシーケンスデータを MIDI ファイルに保存するメソッド群
Sequence
内での現時点の再生/録音位置へのシャトルなどの機能を実現する
Sequencer
では、再生時に、テンポを変更すること、特定の Track
の音を消すこと (ミュート)、およびほかのオブジェクトとの同期状態を変更することが可能
Sequencer
がある種の MIDI イベントを処理する際に通知を受ける「リスナー」オブジェクトを登録する高度なメソッド群
Sequencer
メソッドを呼び出す場合でも、まず、システムから Sequencer
デバイスを取得して、プログラムで使用できるように予約する必要があります。
アプリケーションプログラムは、Sequencer
をインスタンス化しません。Sequencer
インタフェースそのものであると考えられます。ただし、Java Sound API の MIDI パッケージのすべてのデバイスと同様、Sequencer
へは static MidiSystem
オブジェクトを介してアクセスできます。第 9 章「MIDI システムリソースへのアクセス」で説明したように、デフォルトの Sequencer
を取得するには、次の MidiSystem
メソッドを使用します。
static Sequencer getSequencer()
次のコードは、デフォルトの Sequencer
を取得し、必要なすべてのシステムリソースを獲得して、Sequencer
を操作可能にします。
Sequencer sequencer; // Get default sequencer. sequencer = MidiSystem.getSequencer(); if (sequencer == null) { // Error -- sequencer device is not supported. // Inform user and return... } else { // Acquire resources and make operational. sequencer.open(); }
open
の呼び出しにより、シーケンサデバイスがこのプログラム用に予約されます。一度に再生できるシーケンスは 1 つだけであるため、シーケンサを共有してもあまり意味はありません。シーケンサの使用を完了したら、close
を呼び出して、シーケンサをほかのプログラムから利用可能にします。
デフォルトでないシーケンサは、第 9 章「MIDI システムリソースへのアクセス」で説明したような方法で取得できます。
シーケンサをシステムから取得して予約したら、シーケンサが再生するデータをロードする必要があります。通常、これを実行するには、次の 3 つの方法のいずれかが使用されます。
MidiEvent
オブジェクトをこれらのトラックに追加することにより行う
InputStream
に送り、その後、Sequencer.setSequence(InputStream)
を使ってシーケンサに直接読み込む方法です。この方法では、Sequence
オブジェクトを明示的に作成しません。実際は、内部で Sequence
を作成することさえない Sequencer
実装もあります。これは、シーケンサによっては、データをファイルから直接処理する組み込み機構を持っているためです。
もう 1 つは、Sequence
を明示的に作成する方法です。データを再生する前にシーケンスを編集する場合は、この方法を使用する必要があります。この方法では、MidiSystem
のオーバーロードされたメソッドである getSequence
を呼び出します。このメソッドは、InputStream
、File
、または URL
からシーケンスを取得できます。このメソッドが返す Sequence
オブジェクトを Sequencer
にロードすることにより再生が実行されます。File
から Sequence
オブジェクトを取得して sequencer
にロードするコード例 (前述のコードを拡張したもの) を次に示します。
try { File myMidiFile = new File("seq1.mid"); // Construct a Sequence object, and // load it into my sequencer. Sequence mySeq = MidiSystem.getSequence(myMidiFile); sequencer.setSequence(mySeq); } catch (Exception e) { // Handle error and/or return }
MidiSystem
の getSequence
メソッドと同様、問題が発生すると setSequence
は InvalidMidiDataException
、InputStream
形式の場合には IOException
をスローします。
Sequencer
の開始と停止は、次のメソッドを使って行われます。
void start()
void stop()
Sequencer.start
メソッドは、シーケンスの再生を開始します。再生は、シーケンス内の現在位置から始まることに留意してください。すでに説明したように、setSequence
メソッドを使って既存のシーケンスをロードすると、シーケンサの現在位置が初期化されてシーケンスの冒頭に位置が設定されます。stop
メソッドはシーケンサを停止しますが、現在の Sequence
を自動的に巻き戻すことはしません。位置を再設定しないで、停止した Sequence
を開始すると、シーケンスの再生が現在位置から再開されます。この場合、stop
メソッドは、一時停止の機能を果たします。ただし、再生を開始する前に現在のシーケンス位置を任意の値に設定するためのさまざまな Sequencer
メソッドがあります。これらのメソッドについては、あとで説明します。
すでに説明したように、Sequencer
は一般に、1 つまたは複数の Transmitter
オブジェクトを保持します。MidiMessage
は、このオブジェクトを介して Receiver
に送信されます。Sequencer
は、これらの Transmitter
を介して Sequence
を再生します。再生は、具体的には、現在の Sequence
に含まれる MidiEvent
に対応する、適切にタイミングをとった MidiMessage
を発行することにより実行されます。このため、Sequence
再生の設定手順には、Sequencer
の Transmitter
オブジェクトに対して setReceiver
メソッドを呼び出すこと、つまり再生されたデータを利用するデバイスへの出力の配線が含まれます。Transmitter
と Receiver
の詳細は、第 10 章「MIDI メッセージの送信および受信」を参照してください。
MIDI データを、まず Sequence
に取りこみ、次にファイルに取り込むには、前述の手順に加えていくつかの追加手順を実行する必要があります。Sequence
内の Track
への記録を実行するために必要な手順を次に示します。
MidiSystem.getSequencer
を使って、記録に使用する新たなシーケンサを取得します。
setReceiver
メソッドを使って、記録用の Sequencer
に関連付けられた Receiver
にデータを送信します。
Sequence
オブジェクトを作成します。このオブジェクトに記録されたデータを格納します。Sequence
オブジェクトの作成時に、シーケンスの総合的なタイミング情報を指定する必要があります。例を示します。
Sequence mySeq; try{ mySeq = new Sequence(Sequence.PPQ, 10); } catch (Exception ex) { ex.printStackTrace(); }
Sequence
のコンストラクタは、引数として divisionType
とタイミング分割を取ります。divisionType
引数には、タイミング分割引数の単位を指定します。この場合、作成する Sequence
のタイミング分割に、4 分音符につき 10 パルスを指定します。Sequence
コンストラクタのオプション引数として、トラック数を指定します。これを指定することにより、初期シーケンスが指定数 (何も指定しない場合は空) の Track
で始まります。指定しない場合、Sequence
は初期 Track
なしで作成されるため、必要に応じてあとで追加します。
Sequence.createTrack
を使って、Sequence
内に空の Track
を作成します。Sequence
が初期 Track
を使って作成された場合は、この手順は不要です。
Sequencer.setSequence
を使って、記録を受信する新規 Sequence
を選択します。setSequence
メソッドは、既存の Sequence
を Sequencer
に結合します。これは、テープレコーダにテープをロードする動作に類似しています。
Track
ごとに Sequencer.recordEnable
を呼び出します。必要に応じて、Sequence.getTracks
を呼び出して、Sequence
内の利用可能な Track
への参照を取得します。
Sequencer
に対して startRecording
を呼び出します。
Sequencer.stop
または Sequencer.stopRecording
を呼び出します。
MidiSystem.write
を使って、記録された Sequence
を MIDI ファイルに保存します。MidiSystem
の write
メソッドは、引数の 1 つに Sequence
を取り、その Sequence
をストリームまたはファイルに書き込みます。
多くのアプリケーションプログラムでは、シーケンスをファイルからロードして作成できます。また、かなり多くのアプリケーションプログラムでは、シーケンスをライブの MIDI 入力 (録音) から取り込んで作成することもできます。ただし、プログラム的に、またはユーザーからの入力に応じて、はじめから MIDI シーケンスを作成する必要のあるプログラムもあります。フル装備のシーケンサプログラムは、既存シーケンスの編集機能に加え、ユーザーが手動で新規シーケンスを生成するための機能も備えています。
これらのデータ編集操作は、Sequencer
のメソッドではなく、データオブジェクト自体のメソッドであるSequence
、Track
、および MidiEvent
を使って Java Sound API 内で実現されています。Sequence
コンストラクタの 1 つを使って空のシーケンスを作成し、次の Sequence
メソッドを呼び出すことによってそのシーケンスにトラックを追加できます。
ユーザーによるシーケンスの編集が可能なプログラムの場合は、トラックを削除する際に次のTrack createTrack()
Sequence
メソッドが必要になります。
boolean deleteTrack(Track track)
いったんシーケンスにトラックが格納されたら、Track
クラスのメソッドを呼び出してトラックの内容を変更できます。Track
に含まれる MidiEvent
は、Track
オブジェクト内に java.util.Vector
として格納されます。Track
は、リスト内のイベントへのアクセス、およびリストへのイベントの追加や削除を実行するためのメソッドセットを提供します。add
および remove
メソッドは、その名前からわかるように、指定された MidiEvent
を Track
に対して追加または削除します。get
メソッドも提供されています。このメソッドは Track
のイベントリストにインデックスをとり、そこに格納された MidiEvent
を返します。また、size
および tick
メソッドもあります。これらは、それぞれトラック内の MidiEvent
数、および Tick
の総数で表現されたトラックのデュレーションを返します。
トラックに追加する前にイベントを新規作成する場は、MidiEvent
コンストラクタを使用します。イベントに埋め込まれた MIDI メッセージを指定または変更する場合は、適切な MidiMessage
サブクラス (ShortMessage
、SysexMessage
、または MetaMessage
) の setMessage
メソッドを呼び出すことができます。イベントを発生させる時間を変更する場合は、MidiEvent.setTick
を呼び出します。
これらの低レベルメソッドを組み合わせることにより、フル装備のシーケンサプログラムで必要とされる基本編集機能が提供されます。
この章では、これまで MIDI データの単純な再生と記録を中心に説明してきました。この節では、Sequencer
インタフェースおよび Sequence
クラスのメソッドを介して利用できる、いくつかの高度な機能について説明します。
シーケンス内でのシーケンサの現在位置を取得する 2 つの Sequencer
メソッドが存在します。最初のメソッドを次に示します。
long getTickPosition()
これは、シーケンスの先頭からの位置を MIDI ティックで返します。2 番目のメソッドを次に示します。
long getMicrosecondPosition()
これは、現在位置をマイクロ秒で返します。このメソッドは、シーケンスが、MIDI ファイルまたは Sequence
内への格納時のデフォルトのレートで再生されていることを前提にしています。以下で説明するように、再生スピードを変更した場合、返される値に変更はありません。
同様に、ある単位に従ってシーケンサの現在位置を設定できます。
void setTickPosition(long tick)
void setMicrosecondPosition(long microsecond)
すでに説明したように、シーケンスのスピードはテンポで表されます。テンポはシーケンスの途中で変更することができます。シーケンスには、標準 MIDI のテンポチェンジメッセージをカプセル化するイベントを含めることができます。このようなイベントを処理する場合、シーケンサは指示されたテンポに合わせて再生スピードを変更します。また、次の Sequencer
メソッドのいずれかを呼び出すことにより、テンポをプログラム上で変更することもできます。
最初の 2 つのメソッドはそれぞれ、テンポを 1 秒あたりのビート数、4 分音符あたりのマイクロ秒数で設定します。これらのメソッドのどちらかが再度呼び出されるまで、またはシーケンス内でテンポチェンジイベントが見つかるまで、テンポは指定された値のままです。いずれかの時点で、現在のテンポは新たに指定されたテンポにオーバーライドされます。public void setTempoInBPM(float bpm) public void setTempoInMPQ(float mpq) public void setTempoFactor(float factor)
3 番目のメソッドである setTempoFactor
は、上の 2 つとは本質的に性質が異なります。このメソッドは、テンポチェンジイベントまたは上記の最初の 2 つのメソッドのどちらかによってシーケンサに設定されたすべてのテンポを基準化 (増減) します。デフォルトのスカラは 1.0 (変更なし) です。このメソッドにより再生または記録の速度が公称テンポより速くなっても、遅くなっても (係数が 1.0 以外の場合)、公称テンポは変更されません。つまり、実際の再生または記録スピードにテンポ係数が影響しても、getTempoInBPM
および getTempoInMPQ
が返すテンポ値は、テンポ係数による影響を受けません。また、テンポチェンジイベントまたは最初の 2 つのメソッドのどちらかによってテンポが変更された場合でも、テンポは最後に設定されたテンポ係数によって基準化されます。ただし、新たなシーケンスをロードすると、テンポ係数は 1.0 に再設定されます。
シーケンスの除算形式が PPQ ではなく、SMPTE タイプのいずれかである場合、これらのテンポチェンジ指示はすべて無効になることに留意してください。
シーケンサを使用する立場からは、特定のトラックを消して、音そのものを集中的に聞きとることができれば、好都合です。フル装備のシーケンサプログラムでは、ユーザーが再生するときに音を出すトラックを選択できます。厳密には、シーケンサはサウンド自体を作成するものではないため、シーケンサが作成する MIDI メッセージストリームに加えるトラックをユーザーが選択します。通常、各トラックには、「ミュート」ボタンと「ソロ」ボタンの 2 種類のグラフィカルコントロールがあります。ミュートボタンをアクティブにすると、選択が解除されるまで、トラックからはどのような状況でも音は出ません。ソロ機能は、あまり知られていません。簡単に言うと、ソロ機能はミュート機能と逆の機能です。任意のトラックのソロボタンをアクティブにすると、そのソロボタンがアクティブになっているトラックのサウンドだけが出力されます。ソロ機能により、少数のトラックを試演する場合に、ほかのトラックをすべてミュートしなくても、ただちに試演を開始できます。通常、ミュートボタンはソロボタンよりも優先されます。つまり、両方のボタンがアクティブな場合、そのトラックの音は出力されません。
Sequencer
メソッドを使うと、トラックのミュートやソロ機能およびトラックの現時点のミュートやソロの状態の問い合わせを簡単に実行できます。たとえば、デフォルトの Sequencer
を取得済みで、シーケンスデータをロードしたとします。シーケンスの 5 番目のトラックをミュートするには、次のように記述します。
上記のコードには、注意すべき点がいくつかあります。まず、シーケンスのトラック番号は、0 から始まり、総トラック数 - 1 で終わります。また、sequencer.setTrackMute(4, true); boolean muted = sequencer.getTrackMute(4); if (!muted) { return; // muting failed }
setTrackMute
への 2 番目の引数は、boolean 型です。true の場合、要求はトラックのミュートです。true でない場合、要求は指定されたトラックのミュート解除です。最後に、ミュート機能が有効かどうかをテストするため、Sequencer getTrackMute
を呼び出して問い合わせるトラック番号を渡します。この例で予想したとおりに true
が返されれば、ミュート要求が受け入れられています。false
が返された場合、ミュート要求が失敗したことを意味します。
ミュート要求の失敗には、さまざまな原因が考えられます。たとえば、setTrackMute
呼び出しで指定されたトラック番号が総トラック数を超過している場合や、シーケンサがミュート機能をサポートしていない場合などがあります。getTrackMute
を呼び出すと、要求が成功したか失敗したかを判断できます。
getTrackMute
によって返される boolean 型の値は、障害が発生したことは示しますが、その理由は示しません。無効なトラック番号が setTrackMute
メソッドに渡されたことが原因で障害が発生したのかを調べることができます。これを行うには、Sequence
の getTracks
メソッドを呼び出します。その結果、シーケンス内の全トラックを含む配列が返されます。setTrackMute
呼び出しで指定されたトラック番号がこの配列の長さを超過している場合、指定したトラック番号が無効であったことがわかります。
この例では、ミュート要求が成功すると、シーケンスの再生時に 5 番目のトラックからも、現在ミュートが設定されているほかのすべてのトラックからもサウンドは出力されません。
トラックのソロ機能に使用するメソッドや技術は、ミュート機能の場合に類似しています。あるトラックのソロ機能を有効にするには、Sequence
の setTrackSolo
メソッドを呼び出します。
void setTrackSolo(int track, boolean bSolo)
setTrackMute
の場合と同様、最初の引数にはゼロから始まるトラック番号を指定します。2 番目の引数が true
の場合、そのトラックはソロモードに設定されます。true
でない場合は、ソロモードには設定されません。
デフォルトでは、トラックにはミュート機能もソロ機能も設定されません。
Sequencer
には、Sequencer.SyncMode
と呼ばれる内部クラスがあります。SyncMode
オブジェクトは、MIDI シーケンサの時間の概念をマスタまたはスレーブデバイスと同期させる方法の 1 つを表します。シーケンサをマスタと同期させる場合、シーケンサはマスタから送信される特定の MIDI メッセージに応答して現在の時刻を調整します。シーケンサがスレーブを保持する場合も同様に、シーケンサは、MIDI メッセージを送信してスレーブのタイミングを制御します。
シーケンサが利用できるマスタを指定するための 3 つの定義済みモード、INTERNAL_CLOCK
、MIDI_SYNC
、および MIDI_TIME_CODE
があります。後ろの 2 つは、シーケンサが別のデバイスから MIDI メッセージを受信する場合に機能します。この 2 つのモードは、それぞれシステムのリアルタイムクロックメッセージまたは MIDI タイムコード (MTC) メッセージに基づいてシーケンサの時間を再設定します。これらのメッセージタイプの詳細については、MIDI 仕様を参照してください。これら 2 つのモードは、スレーブモードとして使用することも可能です。その場合、シーケンサは対応する MIDI メッセージタイプをレシーバに送信します。4 番目のモードである NO_SYNC
は、シーケンサからレシーバにタイミング情報を送信しない場合に使用します。
引数に、サポートされている SyncMode
オブジェクトを指定して setMasterSyncMode
メソッドを呼び出すことにより、シーケンサのタイミングを制御する方法を指定できます。同様に、setSlaveSyncMode
メソッドを使って、シーケンサがレシーバに送信するタイミング情報が設定されます。この情報は、シーケンサをマスタタイミングソースとして使用するデバイスのタイミングを制御します。
シーケンスの各トラックには、複数種類の MidiEvent
を含めることができます。含めることのできるイベントには、ノートオンメッセージとノートオフメッセージ、プログラムチェンジイベント、コントロールチェンジイベント、メタイベントなどがあります。Java Sound API は、最後の 2 つのイベントタイプ (コントロールチェンジイベントとメタイベント) 用の「リスナー」インタフェースを指定します。これらのインタフェースは、シーケンスの再生中にこのようなイベントが発生した場合に通知を受け取るために使用します。
ControllerEventListener
インタフェースをサポートするオブジェクトは、Sequencer
が特定のコントロールチェンジメッセージを処理する際、通知を受け取ることができます。コントロールチェンジメッセージは、標準タイプの MIDI メッセージであり、ピッチベンドのホイールやデータスライダなどの MIDI コントローラの値が変わったことを表します。コントロールチェンジメッセージについての詳細は、MIDI 仕様を参照してください。シーケンスの処理中にこのようなメッセージが処理されると、シーケンサからデータを受信中の任意のデバイス (シンセサイザのことが多い) に対し、パラメータ値をいくつか更新するよう指示が出されます。パラメータは、通常、サウンド合成の状況を制御します。たとえば、コントローラがピッチベンドのホイールである場合は、現在出力中の音のピッチがパラメータによって制御されます。シーケンスの記録中のコントロールチェンジメッセージは、メッセージを作成した外部の物理デバイスのコントローラが回されたか、またはそのような動作がソフトウェアでシミュレートされたことを意味します。
ControllerEventListener
インタフェースの使用方法を次に示します。ControllerEventListener
インタフェースを実装するクラスを開発したとします。この場合、作成したクラスには次のメソッドが含まれます。
さらに、このクラスのインスタンスを作成して、それにvoid controlChange(ShortMessage msg)
myListener
という変数を割り当てたとします。そして、次の文をプログラム内に含めます。
この場合、シーケンサが MIDI コントローラ番号 1、2、または 4 のコントロールチェンジメッセージを処理するたびに、このクラスのint[] controllersOfInterest = { 1, 2, 4 }; sequencer.addControllerEventListener(myListener, controllersOfInterest);
controlChange
メソッドが呼び出されます。つまり、Sequencer
が要求を処理して任意の登録済みコントローラの値を設定する際、Sequencer
は作成したクラスの controlChange
メソッドを呼び出します。指定された制御デバイスへの MIDI コントローラ番号の割り当てに関する詳細は、MIDI 1.0 仕様を参照してください。
controlChange
メソッドに渡される ShortMessage
には、影響を受けるコントローラ番号、およびコントローラに設定される新規の値が含まれます。コントローラ番号は、ShortMessage.getData1
メソッドを使って取得できます。また、コントローラの新規の設定値は、ShortMessage.getData2
メソッドを使って取得できます。
その他の特殊な種類のイベントリスナーは、MetaEventListener
インタフェースにより定義されます。標準 MIDI ファイル 1.0 仕様によると、メタメッセージは、MIDI ワイヤプロトコルの対象外のメッセージで、MIDI ファイルに埋め込むことができるメッセージです。これは、シンセサイザにとって意味のないメッセージですが、シーケンサはこのメッセージを解釈できます。メタメッセージには、指示 (テンポチェンジコマンドなど)、歌詞などのテキスト、およびほかのインジケータ (end-of-track など) が含まれます。
MetaEventListener
の機構は、ControllerEventListener
の機構に類似しています。シーケンサによる MetaMessage
の処理時にインスタンスが通知を必要とする任意クラス内に MetaEventListener
インタフェースを実装します。この場合、クラスに次のメソッドを追加します。
void meta(MetaMessage msg)
このクラスのインスタンス登録は、次のように、Sequencer
の addMetaEventListener
メソッドへの引数としてインスタンスを渡すことにより実行します。
これは、boolean b = sequencer.addMetaEventListener (myMetaListener);
ControllerEventListener
インタフェースが採用する方法とは若干異なります。登録する理由が、選択した MetaMessages
だけを受信するためではなく、すべての MetaMessages
を受信するためだからです。シーケンサは、シーケンス内で MetaMessage
を見つけると、myMetaListener.meta
を呼び出して見つけた MetaMessage
を渡します。meta
メソッドは、MetaMessage
引数に対して getType
を呼び出して、メッセージタイプを示す 0 ~ 127 の整数 (標準 MIDI ファイル 1.0 仕様の定義に準拠) を取得します。