MIDI の世界では、「シーケンサ」とは、タイムスタンプ付きの MIDI メッセージの「シーケンス」を正確に再生または記録できる任意のハードウェアまたはソフトウェアデバイスのことです。 同様に、JavaTM Sound API では、Sequencer
抽象インタフェースは、MidiEvent
オブジェクトの Sequences
の再生および記録が可能なオブジェクトのプロパティを定義します。一般に、Sequencer
はこれらの MidiEvent
シーケンスを、標準 MIDI ファイルからロードするか、または標準 MIDI ファイルに保存します。シーケンスは、編集も可能です。この章では、Sequencer
オブジェクトの使用方法、および作業をする上で必要な関連クラスおよびインタフェースについて説明します。
Sequencer
とは何かを直感的に理解するために、テープレコーダにたとえて考えてみましょう。シーケンサとテープレコーダは、多くの点で類似しています。テープレコーダがオーディオを再生するのに対し、シーケンサは MIDI データを再生します。シーケンスとは、MIDI 音楽データをマルチトラック、リニア、時間順に記録したもので、シーケンサはその音楽データに対して、さまざまな速度での再生、巻き戻し、特定区間の往復、ファイルへの記録または保存目的でのファイルへのコピー等の処理を行います。
第 10 章「MIDI メッセージの送信および受信」では、デバイスが一般に、Receiver
オブジェクトと Transmitter
オブジェクトのいずれかまたは両方を保持することを説明しました。音楽を「再生」する場合、デバイスは通常 Receiver
経由で MidiMessages
を受信します。Receiver
は、Sequencer
に属する Transmitter
から受信した MidiMessages
を送ります。この Receiver
を保持するデバイスとして、Synthesizer
があります。Synthesizer
はオーディオを直接生成するか、MIDI の出力ポートとして機能します。出力ポートとしての場合、MIDI データを物理ケーブル経由で何らかの外部機器に送信します。同様に、音楽を「録音」する場合、一般に、タイムスタンプ付きの一連の MidiMessages
が Sequencer
の所有する Receiver
に送信されます。Sequencer
は、MidiMessages
を Sequence
オブジェクト内に配置します。一般に、メッセージを送信するオブジェクトは、ハードウェア入力ポートに関連付けられた Transmitter
で、入力ポートは外部の楽器から取得した MIDI データを中継します。ただし、メッセージの送信を担当するデバイスが、他の何らかの Sequencer
であるか、または Transmitter
を所有する他のデバイスであることもあります。また、第 10 章で説明したように、Transmitter
をまったく使用せずにプログラムからメッセージを送信することも可能です。
Sequencer
は、それ自体で Receiver
と Transmitter
の両方を保持しています。事実、Sequencer
は、記録時に Receiver
経由で MidiMessages
を取得します。再生時には、Transmitter
を使って、記録 (またはファイルからロード) した Sequence
に格納された MidiMessages
を送信しています。
Java Sound API 内での Sequencer
の役割を理解する 1 つの方法は、MidiMessages
の集合体/非集合体ととらえることです。独立した一連の分離 MidiMessages
は、音楽イベントのタイミングのマークとなる独自のタイムスタンプとともに Sequencer
に送信されます。これらの MidiMessages
は、MidiEvent
オブジェクト内でカプセル化され、Sequencer.record
メソッドのアクションを介して Sequence
オブジェクト内に収集されます。Sequence
は、MidiEvent
の集合を含むデータ構造で、通常、一連の音符、歌や構成全体を表すこともあります。再生時に、Sequencer
は、Sequence
内の MidiEvent
オブジェクトから MidiMessages
を再度抽出し、1 つまたは複数のデバイスに送信します。デバイスは、MidiMessages
のサウンド化、保存、変更、または他のデバイスへの引き渡しを行います。
シーケンサの中には、トランスミッタもレシーバも保持しないものも存在します。たとえば、この種のシーケンサは、Receiver
経由で MidiMessages
を受信する代わりに、キーボードイベントまたはマウスイベントの結果としてはじめから MidiEvents
を作成します。同様に、MidiMessages
を別個のオブジェクトに関連付けられた 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
はアンサンブル中のひとつの音声または演奏者に対応していました。この様式では、特定の 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 では、MidiMessages
をタイミング値と関連付けるための 2 つの異なる方法が用意されています。その 1 つは、「シーケンサをいつ使用するか」で説明した方法です。この方法の詳細については、第 10 章「MIDI メッセージの送信および受信」の「トランスミッタを使用せずにメッセージをレシーバに送信する方法」および「タイムスタンプの理解」を参照してください。Receiver
の send
メソッドが MidiMessage
引数およびタイムスタンプ引数をとることが説明されています。そのようなタイムスタンプは、マイクロ秒単位のみで表現されます。
MidiMessage
が指定されたタイミングを保持する別の方法は、MidiMessage
を MidiEvent
内でカプセル化する方法です。この場合、タイミングは「ティック」と呼ばれる、より抽象的な単位で表現されます。
ティックのデュレーション。1 ティックの持続時間は、シーケンスによって異なります。1 つのシーケンス内で異なることはありません。値は、標準 MIDI ファイルのヘッダに格納されます。ティックのサイズは、次の 2 種類の単位のいずれかを使って指定されます。
一方 SMPTE の場合、単位は絶対時間に基づくため、テンポという概念を適用することはできません。4 種類の SMPTE 規約が存在し、各規約は秒単位での動画フレーム数を示しています。秒単位のフレーム数として、24、25、29.97、30 のいずれかを指定できます。SMPTE タイムコードと共に使用することで、ティックのサイズをフレームの何分の一かで表現できます。
Java Sound API では、特定のシーケンスでどの種類の単位 (つまり PPQ か、またはいずれかの SMPTE 単位か) が使用されているかを、Sequence.getDivisionType
を呼び出すことで判断できます。その後、Sequence.getResolution
を呼び出してティックのサイズを計算できます。除算形式が PPQ の場合、Sequence.getResolution
は、4 分音符ごとのティック数を返し、除算形式が SMPTE 規則のうちのいずれかである場合、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 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
オブジェクトを保持します。MidiMessages
は、このオブジェクトを介して Receiver
に送信されます。Sequencer
は、これらの Transmitter
を介して Sequence
を再生します。再生は、具体的には、現在の Sequence
に含まれる MidiEvent
に対応する、適切にタイミングをとった MidiMessages
を発行することにより実行されます。このため、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 仕様の定義に準拠) を取得します。