第 11 章: MIDI シーケンスの再生、記録、および編集


注:

5.0 のリリースには、すべての MIDI デバイスで機能する新しいリアルタイムの Sequencer (実装) が備わっており、その getTransmitter() メソッドを介して、トランスミッタを無制限に入手することができます。このリリース以前は、TransmitterSequencer から取得することはできませんでした。


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 を送ります。このレシーバを保持するデバイスとして、シンセサイザがあります。シンセサイザはオーディオを直接生成するか、または MIDI の出力ポートとして機能します。出力ポートとして機能する場合は、MIDI データを物理ケーブル経由で何らかの外部機器に送信します。同様に、音楽を「録音」する場合、一般に、タイムスタンプ付きの一連の MidiMessagesSequencer の所有する Receiver に送信されます。Sequencer は、MidiMessagesSequence オブジェクト内に配置します。一般に、メッセージを送信するオブジェクトは、ハードウェア入力ポートに関連付けられた Transmitter で、入力ポートは外部の楽器から取得した MIDI データを中継します。ただし、メッセージの送信を行うデバイスが、ほかのシーケンサであるか、またはトランスミッタを所有するほかのデバイスであることもあります。また、第 10 章で説明したように、Transmitter をまったく使わないでプログラムからメッセージを送信することも可能です。

Sequencer は、それ自体で ReceiverTransmitter の両方を保持しています。事実、Sequencer は、記録時に Receiver 経由で MidiMessage を取得します。再生時には、Transmitter を使って、記録 (またはファイルからロード) した Sequence に格納された MidiMessage を送信します。

Java Sound API 内での Sequencer の役割を理解する 1 つの方法は、MidiMessage の集合体/非集合体と捉えることです。独立した一連の分離 MidiMessage は、音楽イベントのタイミングのマークとなる独自のタイムスタンプとともに Sequencer に送信されます。MidiMessageMidiEvent オブジェクトにカプセル化され、Sequencer.record メソッドのアクションによって Sequence オブジェクトに収集されます。SequenceMidiEvent の集合体を含むデータ構造で、通常は一連の音符 (多くの場合は楽曲全体) を表しています。再生時に、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 には、総合的なタイミング情報のほかに Tracks の集まりが含まれます。MidiEvent データを保持するのは、この Tracks です。このため、シーケンサにより再生されるデータは、SequencerTrack、および MidiEvent という 3 階層のオブジェクトで構成されています。

これらのオブジェクトの従来の使用法は、Sequence が曲全体または曲の 1 楽節を表し、各 トラックはアンサンブル中の 1 つの音声または演奏者に対応していました。この様式では、特定のトラックのデータはすべて、その音声または演奏者用に予約された特定の MIDI チャネルに符号化されます。

このデータ編成方法は、シーケンスを編集する場合には有用ですが、Track を使用する従来のやり方の 1 つにすぎないことに留意してください。Track クラス自体の定義には、異なる MIDI チャネルの混合 MidiEvent を含まないという規定はありません。たとえば、完全にマルチチャネル化された MIDI 構成をミックスして 1 つの Track に記録することができます。また、標準 MIDI ファイルの Type 0 には、Type 1 や Type 2 とは異なり、定義上 1 トラックしか含まれません。このため、このようなファイルから読み取った Sequence は単一の Track オブジェクトしか保持しません。

MidiEvent とティック

第 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 メッセージの送信および受信」「トランスミッタを使わずにメッセージをレシーバに送信する方法」および「タイムスタンプの理解」を参照してください。Receiversend メソッドが MidiMessage 引数およびタイムスタンプ引数をとることが説明されています。そのようなタイムスタンプは、マイクロ秒単位でのみ表現されます。

MidiMessage が指定されたタイミングを保持する 2 つ目の方法は、MidiMessageMidiEvent 内でカプセル化する方法です。この場合、タイミングは「ティック」と呼ばれる、より抽象的な単位で表現されます。

1 ティックのデュレーションは、シーケンスによって異なります。ただし、1 つのシーケンス内で異なることはありません。値は、標準 MIDI ファイルのヘッダに格納されます。ティックのサイズは、次の 2 種類の単位のどちらかを使って指定されます。

単位が PPQ に基づく場合、ティックのサイズは 4 分音符の何分の一というように表現されます。これは相対時間値であり、絶対時間値ではありません。4 分音符は、音楽の 1 ビートに対応するデュレーションを表す値です (4/4 拍子の 1/4)。4 分音符のデュレーションは、テンポにより異なります。このため、シーケンスにテンポチェンジイベントが含まれている場合、演奏の途中で 4 分音符のデュレーションが変化する場合があります。このため、シーケンスのタイミング増分 (ティック) が、たとえば 4 分音符につき 96 回発生すると、各イベントのタイミング値は、絶対的な時間値としてではなく、音楽的表現でのイベントの位置を示します。

一方 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;

SMPTE の場合、この式は、次のようになります。

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 インタフェースが提供するメソッド群は、次のようなカテゴリに分類されます。

上記のどの 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 つの方法のいずれかが使用されます。

次に、シーケンスデータを取得するための上記 3 つのうちの最初の方法について説明します。ほかの 2 つの方法については、あとの「シーケンスの記録および保存」および「シーケンスの編集」で説明します。1 つ目の方法は、実際はさらに 2 つの方法に分かれています。その 1 つは、MIDI ファイルデータを InputStream に送り、その後、Sequencer.setSequence(InputStream) を使ってシーケンサに直接読み込む方法です。この方法では、Sequence オブジェクトを明示的に作成しません。実際は、内部で Sequence を作成することさえない Sequencer 実装もあります。これは、シーケンサによっては、データをファイルから直接処理する組み込み機構を持っているためです。

もう 1 つは、Sequence を明示的に作成する方法です。データを再生する前にシーケンスを編集する場合は、この方法を使用する必要があります。この方法では、MidiSystem のオーバーロードされたメソッドである getSequence を呼び出します。このメソッドは、InputStreamFile、または 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
    }
	

MidiSystemgetSequence メソッドと同様、問題が発生すると setSequenceInvalidMidiDataExceptionInputStream 形式の場合には 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 再生の設定手順には、SequencerTransmitter オブジェクトに対して setReceiver メソッドを呼び出すこと、つまり再生されたデータを利用するデバイスへの出力の配線が含まれます。TransmitterReceiver の詳細は、第 10 章「MIDI メッセージの送信および受信」を参照してください。

シーケンスの記録および保存

MIDI データを、まず Sequence に取り込み、次にファイルに取り込むには、前述の手順に加えていくつかの追加手順を実行する必要があります。Sequence 内の Track への記録を実行するために必要な手順を次に示します。

  1. すでに説明したように、MidiSystem.getSequencer を使って、記録に使用する新たなシーケンサを取得します。

  2. MIDI 接続の「配線」を設定します。記録対象の MIDI データを送信するオブジェクトを構成する必要があります。このオブジェクトの setReceiver メソッドを使って、記録用の Sequencer に関連付けられた Receiver にデータを送信します。

  3. 新規 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 なしで作成されるため、必要に応じてあとで追加します。

  4. Sequence.createTrack を使って、Sequence 内に空の Track を作成します。Sequence が初期 Track を使って作成された場合は、この手順は不要です。

  5. Sequencer.setSequence を使って、記録を受信する新規 Sequence を選択します。setSequence メソッドは、既存の SequenceSequencer に結合します。これは、テープレコーダにテープをロードする動作に類似しています。

  6. 記録する Track ごとに Sequencer.recordEnable を呼び出します。必要に応じて、Sequence.getTracks を呼び出して、Sequence 内の利用可能な Track への参照を取得します。

  7. Sequencer に対して startRecording を呼び出します。

  8. 記録が完了したら、Sequencer.stop または Sequencer.stopRecording を呼び出します。

  9. MidiSystem.write を使って、記録された Sequence を MIDI ファイルに保存します。MidiSystemwrite メソッドは、引数の 1 つに Sequence を取り、その Sequence をストリームまたはファイルに書き込みます。

シーケンスの編集

多くのアプリケーションプログラムでは、シーケンスをファイルからロードして作成できます。また、かなり多くのアプリケーションプログラムでは、シーケンスをライブの MIDI 入力 (録音) から取り込んで作成することもできます。ただし、プログラム的に、またはユーザからの入力に応じて、はじめから MIDI シーケンスを作成する必要のあるプログラムもあります。フル装備のシーケンサプログラムは、既存シーケンスの編集機能に加え、ユーザが手動で新規シーケンスを生成するための機能も備えています。

これらのデータ編集操作は、Sequencer のメソッドではなく、データオブジェクト自体のメソッドであるSequenceTrack、および MidiEvent を使って Java Sound API 内で実現されています。Sequence コンストラクタの 1 つを使って空のシーケンスを作成し、次の Sequence メソッドを呼び出すことによってそのシーケンスにトラックを追加できます。

    Track createTrack() 

ユーザによるシーケンスの編集が可能なプログラムの場合は、トラックを削除する際に次の Sequence メソッドが必要になります。
    boolean deleteTrack(Track track) 

いったんシーケンスにトラックが格納されたら、Track クラスのメソッドを呼び出してトラックの内容を変更できます。Track に含まれる MidiEvent は、Track オブジェクト内に java.util.Vector として格納されます。Track は、リスト内のイベントへのアクセス、およびリストへのイベントの追加や削除を実行するためのメソッドセットを提供します。add および remove メソッドは、その名前からわかるように、指定された MidiEventTrack に対して追加または削除します。get メソッドも提供されています。このメソッドは Track のイベントリストにインデックスをとり、そこに格納された MidiEvent を返します。また、size および tick メソッドもあります。これらは、それぞれトラック内の MidiEvent 数、および Tick の総数で表現されたトラックのデュレーションを返します。

トラックに追加する前にイベントを新規作成する場は、MidiEvent コンストラクタを使用します。イベントに埋め込まれた MIDI メッセージを指定または変更する場合は、適切な MidiMessage サブクラス (ShortMessageSysexMessage、または 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 メソッドのいずれかを呼び出すことにより、テンポをプログラム上で変更することもできます。

    public void setTempoInBPM(float bpm)
public void setTempoInMPQ(float mpq)
public void setTempoFactor(float factor)
最初の 2 つのメソッドはそれぞれ、テンポを 1 秒あたりのビート数、4 分音符あたりのマイクロ秒数で設定します。これらのメソッドのどちらかが再度呼び出されるまで、またはシーケンス内でテンポチェンジイベントが見つかるまで、テンポは指定された値のままです。いずれかの時点で、現在のテンポは新たに指定されたテンポにオーバーライドされます。

3 番目のメソッドである setTempoFactor は、上の 2 つとは本質的に性質が異なります。このメソッドは、テンポチェンジイベントまたは上記の最初の 2 つのメソッドのどちらかによってシーケンサに設定されたすべてのテンポを基準化 (増減) します。デフォルトのスカラは 1.0 (変更なし) です。このメソッドにより再生または記録の速度が公称テンポより速くなっても、遅くなっても (係数が 1.0 以外の場合)、公称テンポは変更されません。つまり、実際の再生または記録スピードにテンポ係数が影響しても、getTempoInBPM および getTempoInMPQ が返すテンポ値は、テンポ係数による影響を受けません。また、テンポチェンジイベントまたは最初の 2 つのメソッドのどちらかによってテンポが変更された場合でも、テンポは最後に設定されたテンポ係数によって基準化されます。ただし、新たなシーケンスをロードすると、テンポ係数は 1.0 に再設定されます。

シーケンスの除算形式が PPQ ではなく、SMPTE タイプのいずれかである場合、これらのテンポチェンジ指示はすべて無効になることに留意してください。

シーケンス内の個別のトラックのミュートまたはソロ機能

シーケンサを使用する立場からは、特定のトラックを消して、音そのものを集中的に聞き取ることができれば、好都合です。フル装備のシーケンサプログラムでは、ユーザが再生するときに音を出すトラックを選択できます。厳密には、シーケンサはサウンド自体を作成するものではないため、シーケンサが作成する MIDI メッセージストリームに加えるトラックをユーザが選択します。通常、各トラックには、「ミュート」ボタンと「ソロ」ボタンの 2 種類のグラフィカルコントロールがあります。ミュートボタンをアクティブにすると、選択が解除されるまで、トラックからはどのような状況でも音は出ません。ソロ機能は、あまり知られていません。簡単に言うと、ソロ機能はミュート機能と逆の機能です。任意のトラックのソロボタンをアクティブにすると、そのソロボタンがアクティブになっているトラックのサウンドだけが出力されます。ソロ機能により、少数のトラックを試演する場合に、ほかのトラックをすべてミュートしなくても、ただちに試演を開始できます。通常、ミュートボタンはソロボタンよりも優先されます。つまり、両方のボタンがアクティブな場合、そのトラックの音は出力されません。

Sequencer メソッドを使うと、トラックのミュートやソロ機能およびトラックの現時点のミュートやソロの状態の問い合わせを簡単に実行できます。たとえば、デフォルトの Sequencer を取得済みで、シーケンスデータをロードしたとします。シーケンスの 5 番目のトラックをミュートするには、次のように記述します。

    sequencer.setTrackMute(4, true);
boolean muted = sequencer.getTrackMute(4);
if (!muted) {
return;		// muting failed
    }
上記のコードには、注意すべき点がいくつかあります。まず、シーケンスのトラック番号は、0 から始まり、総トラック数 - 1 で終わります。また、setTrackMute への 2 番目の引数は、boolean 型です。true の場合、要求はトラックのミュートです。true でない場合、要求は指定されたトラックのミュート解除です。最後に、ミュート機能が有効かどうかをテストするため、Sequencer getTrackMute を呼び出して問い合わせるトラック番号を渡します。この例で予想したとおりに true が返されれば、ミュート要求が受け入れられています。false が返された場合、ミュート要求が失敗したことを意味します。

ミュート要求の失敗には、さまざまな原因が考えられます。たとえば、setTrackMute 呼び出しで指定されたトラック番号が総トラック数を超過している場合や、シーケンサがミュート機能をサポートしていない場合などがあります。getTrackMute を呼び出すと、要求が成功したか失敗したかを判断できます。

getTrackMute によって返される boolean 型の値は、障害が発生したことは示しますが、その理由は示しません。無効なトラック番号が setTrackMute メソッドに渡されたことが原因で障害が発生したのかを調べることができます。これを行うには、SequencegetTracks メソッドを呼び出します。その結果、シーケンス内の全トラックを含む配列が返されます。setTrackMute 呼び出しで指定されたトラック番号がこの配列の長さを超過している場合、指定したトラック番号が無効であったことがわかります。

この例では、ミュート要求が成功すると、シーケンスの再生時に 5 番目のトラックからも、現在ミュートが設定されているほかのすべてのトラックからもサウンドは出力されません。

トラックのソロ機能に使用するメソッドや技術は、ミュート機能の場合に類似しています。あるトラックのソロ機能を有効にするには、SequencesetTrackSolo メソッドを呼び出します。

void setTrackSolo(int track, boolean bSolo)
setTrackMute の場合と同様、最初の引数にはゼロから始まるトラック番号を指定します。2 番目の引数が true の場合、そのトラックはソロモードに設定されます。true でない場合は、ソロモードには設定されません。

デフォルトでは、トラックにはミュート機能もソロ機能も設定されません。

ほかの MIDI デバイスとの同期

Sequencer には、Sequencer.SyncMode と呼ばれる内部クラスがあります。SyncMode オブジェクトは、MIDI シーケンサの時間の概念をマスタまたはスレーブデバイスと同期させる方法の 1 つを表します。シーケンサをマスタと同期させる場合、シーケンサはマスタから送信される特定の MIDI メッセージに応答して現在の時刻を調整します。シーケンサがスレーブを保持する場合も同様に、シーケンサは、MIDI メッセージを送信してスレーブのタイミングを制御します。

シーケンサが利用できるマスタを指定するための 3 つの定義済みモード、INTERNAL_CLOCKMIDI_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 という変数を割り当てたとします。そして、次の文をプログラム内に含めます。
int[] controllersOfInterest = { 1, 2, 4 };
sequencer.addControllerEventListener(myListener,
controllersOfInterest);
この場合、シーケンサが MIDI コントローラ番号 1、2、または 4 のコントロールチェンジメッセージを処理するたびに、このクラスの 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)

このクラスのインスタンス登録は、次のように、SequenceraddMetaEventListener メソッドへの引数としてインスタンスを渡すことにより実行します。

boolean b = sequencer.addMetaEventListener
(myMetaListener);
これは、ControllerEventListener インタフェースが採用する方法とは若干異なります。登録する理由が、選択した MetaMessages だけを受信するためではなく、すべての MetaMessages を受信するためだからです。シーケンサは、シーケンス内で MetaMessage を見つけると、myMetaListener.meta を呼び出して見つけた MetaMessage を渡します。meta メソッドは、MetaMessage 引数に対して getType を呼び出して、メッセージタイプを示す 0 〜 127 の整数 (標準 MIDI ファイル 1.0 仕様の定義に準拠) を取得します。