< 目次

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

 

注:

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


MIDIの世界では、シーケンサとは、タイムスタンプ付きのMIDIメッセージのシーケンスを正確に再生または記録できるハードウェアまたはソフトウェア・デバイスのことです。同様に、Java Sound APIでは、Sequencer抽象インタフェースは、MidiEventオブジェクトのSequencesの再生および記録が可能なオブジェクトのプロパティを定義します。一般に、SequencerはこれらのMidiEventシーケンスを、標準MIDIファイルからロードするか、または標準MIDIファイルに保存します。シーケンスは、編集も可能です。この章では、Sequencerオブジェクトの使用方法、およびタスクを実行する上で必要な関連クラスおよびインタフェースについて説明します。

シーケンサの紹介

Sequencerは、テープ・レコーダに例えてみると、その本質を捉えることができます。シーケンサとテープ・レコーダは、多くの点で類似しています。テープ・レコーダがオーディオを再生するのに対し、シーケンサはMIDIデータを再生します。シーケンスとは、MIDI音楽データをマルチトラック、リニア、時間順で記録したものです。シーケンサはその音楽データに対して、様々な速度での再生、巻き戻し、特定区間の往復、ファイルへの記録または保存目的でのファイルへのコピーなどの処理を行います。

第10章「MIDIメッセージの送信および受信」では、デバイスが一般に、ReceiverオブジェクトとTransmitterオブジェクトのいずれかまたは両方を保持することを説明しました。音楽を再生する場合、デバイスは通常Receiver経由でMidiMessagesを受信します。Receiverは、通常Sequencerに属するTransmitterからMidiMessageを受信しています。このReceiverを保持するデバイスとして、Synthesizerがあります。これはオーディオを直接生成するか、またはMIDIの出力ポートとして機能します。出力ポートとして機能する場合は、MIDIデータを物理ケーブル経由でなんらかの外部機器に送信します。同様に、音楽を録音する場合、一般に、タイムスタンプ付きの一連のMidiMessagesSequencerの所有するReceiverに送信されます。Sequencerは、MidiMessagesをSequenceオブジェクト内に配置します。一般に、メッセージを送信するオブジェクトは、ハードウェア入力ポートに関連付けられたTransmitterで、入力ポートは外部の楽器から取得したMIDIデータを中継します。ただし、メッセージの送信を行うデバイスが、他のSequencerであるか、またはTransmitterを所有する他のデバイスであることもあります。また、第10章で説明したように、Transmitterをまったく使わないでプログラムからメッセージを送信することも可能です。

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

Java Sound API内でのSequencerの役割を理解する1つの方法は、MidiMessagesの集合体/非集合体と捉えることです。独立した、一連の別個のMidiMessagesは、音楽イベントのタイミングのマークとなる独自のタイムスタンプとともにSequencerに送信されます。MidiMessagesMidiEventオブジェクトにカプセル化され、Sequencer.recordメソッドのアクションによってSequenceオブジェクトに収集されます。SequenceMidiEventsの集合体を含むデータ構造で、通常は一連の音符(多くの場合は楽曲全体)を表しています。再生時に、Sequencerは、Sequence内のMidiEventオブジェクトからMidiMessagesを再度抽出し、1つまたは複数のデバイスに送信します。デバイスは、それらのサウンド化、保存、変更、または他のデバイスへの引渡しを行います。

シーケンサの中には、トランスミッタもレシーバも保持しないものもあります。たとえば、この種のシーケンサは、Receivers経由でMidiMessagesを受信する代わりに、キーボード・イベントまたはマウス・イベントの結果としてMidiEventsをゼロから作成します。同様に、MidiMessagesを別個のオブジェクトに関連付けられたReceiverに送信する代わりに、内部のシンセサイザ(実際にはシーケンサと同じオブジェクトの場合もある)と直接通信することにより、音楽を再生します。ただし、この章の後半では、ReceiversおよびTransmittersを使用する一般的なシーケンサを前提に説明します。

シーケンサをいつ使用するか

第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は、的確なタイミングで編成されるMidiEventsの集合です。ただし、Sequenceにはリニアな一連のMidiEventsより大きな構造が含まれます。Sequenceには、総合的なタイミング情報の他にTracksの集まりが含まれます。MidiEventデータを保持するのは、このTracksです。このため、シーケンサにより再生されるデータは、SequencerTrack、およびMidiEventという3階層のオブジェクトで構成されています。

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

このデータ編成方法は、シーケンスを編集する場合には有用ですが、Tracksを使用する従来のやり方の1つにすぎないことに留意してください。Trackクラス自体の定義には、異なるMIDIチャネルの混合MidiEventsを含まないという規定はありません。たとえば、マルチチャネルの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では、MidiMessagesをタイミング値と関連付けるための2種類の方法が用意されています。1つ目は、「シーケンサをいつ使用するか」で説明した方法です。この方法の詳細については、第10章「MIDIメッセージの送信および受信」「トランスミッタを使わずにメッセージをレシーバに送信する方法」および「タイムスタンプの理解」を参照してください。ReceiversendメソッドがMidiMessage引数およびタイムスタンプ引数を取ることが説明されています。そのようなタイムスタンプは、マイクロ秒単位でのみ表現されます。

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

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つあります。MidiEventsに含まれるティック値は、増分(デルタ)時間ではなく、累積時間を示すものです。標準MIDIファイルでは、各イベントのタイミング情報は、シーケンス内での、前のイベントの開始以降の経過時間を表します。これは増分(デルタ)時間と呼ばれます。一方、Java Sound APIでは、ティックは増分値ではなく、前のイベントの時間値に増分(デルタ)値を加えた値になります。つまり、Java Sound APIでは、各イベントのタイミング値は、シーケンス内の前のイベントのタイミング値よりも常に大きくなります。イベントが同時に発生することになっている場合は、タイミング値は等しくなります。各イベントのタイミング値は、シーケンスの開始を起点とする経過時間を示します。

要約すると、Java Sound APIは、タイミング情報を、MIDIティック単位またはマイクロ秒単位で表現します。MidiEventsは、タイミング情報をMIDIティックに換算して格納します。ティックのデュレーションは、Sequenceの総合的なタイミング情報に基づいて算出されます。シーケンスがテンポに基づくタイミングを使用する場合、現時点の音楽上のテンポが使用されます。一方、Receiverに送信されるMidiMessageに関連付けられたタイムスタンプは、常にマイクロ秒単位で表現されます。

この設計の目標の1つは、時間に関する概念の矛盾を避けることです。PPQ単位を使用するMidiEvents内での時間の単位を解釈するのは、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
    }
        

MidiSystemの getSequenceメソッドと同様、問題が発生すると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再生の設定手順には、Sequencerの Transmitterオブジェクトに対して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分音符1つにつき10パルスを指定します。Sequenceコンストラクタのオプション引数として、トラック引数の数を指定します。これを指定することにより、初期シーケンスが指定数(何も指定しない場合は空)のTrackで始まります。指定しない場合、Sequenceは初期Trackなしで作成されるため、必要に応じて後で追加します。
  4. Sequence.createTrackを使って、Sequence内に空のTrackを作成します。Sequenceが初期Tracksを使って作成された場合は、この手順は不要です。
  5. Sequencer.setSequenceを使って、記録を受信する新規Sequenceを選択します。setSequenceメソッドは、既存のSequenceSequencerに結合します。これは、テープ・レコーダにテープをセットする動作に類似しています。
  6. 記録するTrackごとにSequencer.recordEnableを呼び出します。必要に応じて、Sequence.getTracksを呼び出して、Sequence内の利用可能なTracksへの参照を取得します。
  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の場合、そのトラックはソロ・モードに設定されます。そうでない場合は、ソロ・モードには設定されません。

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

ほかの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メソッドを使って、シーケンサがレシーバに送信するタイミング情報が設定されます。この情報は、シーケンサをマスター・タイミング・ソースとして使用するデバイスのタイミングを制御します。

スペシャル・イベント・リスナーの指定

シーケンスの各トラックには、複数種類のMidiEventsを含めることができます。含めることのできるイベントには、ノート・オン・メッセージとノート・オフ・メッセージ、プログラム・チェンジ・イベント、コントロール・チェンジ・イベント、メタイベントなどがあります。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)

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

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

 


Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.