# 使用 Sequencer 方法

> 原文： [https://docs.oracle.com/javase/tutorial/sound/MIDI-seq-methods.html](https://docs.oracle.com/javase/tutorial/sound/MIDI-seq-methods.html)

[`Sequencer`](https://docs.oracle.com/javase/8/docs/api/javax/sound/midi/Sequencer.html) 接口提供以下几种方法：

*   从 MIDI 文件或`Sequence`对象加载序列数据，并将当前加载的序列数据保存到 MIDI 文件的方法。
*   类似于录音机的传输功能的方法，用于停止和开始播放和录制，启用和禁用特定轨道上的录制，以及在`Sequence`中穿梭当前播放或录制位置。
*   用于查询和设置对象的同步和定时参数的高级方法。 `Sequencer`可以以不同的速度播放，其中一些`Tracks`静音，并且与其他对象处于各种同步状态。
*   用于注册“侦听器”对象的高级方法，这些对象在`Sequencer`处理某些类型的 MIDI 事件时得到通知。

无论您调用哪种`Sequencer`方法，第一步是从系统中获取`Sequencer`设备并保留它以供程序使用。

## 获取序列发生器

应用程序不实例化`Sequencer`;毕竟，`Sequencer`只是一个界面。相反，与 Java Sound API 的 MIDI 包中的所有设备一样，通过静态`MidiSystem`对象访问`Sequencer`。如前面[访问 MIDI 系统资源](accessing-MIDI.html)中所述，以下`MidiSystem`方法可用于获取默认`Sequencer`：

```
static Sequencer getSequencer()

```

以下代码片段获取默认`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`的调用保留了音序器设备供程序使用。想象共享一个音序器没有多大意义，因为它一次只能播放一个序列。使用完顺控程序后，可以通过调用`close`将其提供给其他程序。

可以按照[访问 MIDI 系统资源](accessing-MIDI.html)中的描述获得非默认音序器。

## 加载序列

从系统中获取了一个音序器并保留了它，然后你需要加载音序器应该播放的数据。有三种典型的方法来实现这一目标：

*   从 MIDI 文件中读取序列数据
*   通过接收来自其他设备（如 MIDI 输入端口）的 MIDI 信息实时录制
*   通过向空序列添加轨道并将`MidiEvent`对象添加到这些轨道，以“从头开始”以编程方式构建它

我们现在看一下获取序列数据的第一种方法。 （另外两种方式分别在[记录和保存序列](#124654)和[编辑序列](#124674)中描述。）第一种方法实际上包含两种稍微不同的方法。一种方法是将 MIDI 文件数据输入`InputStream`，然后通过`Sequencer.setSequence(InputStream)`将其直接读取到顺控程序。使用此方法，您不会显式创建`Sequence`对象。事实上，`Sequencer`实现甚至可能不会在幕后创建`Sequence`，因为一些序列发生器具有直接从文件处理数据的内置机制。

另一种方法是明确地创建`Sequence`。如果您要在播放之前编辑序列数据，则需要使用此方法。使用此方法，您可以调用`MidiSystem's`重载方法`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's` `getSequence`方法类似，`setSequence`可能会抛出`InvalidMidiDataException` - 在`InputStream`变体的情况下，`IOException` - 如果它遇到任何麻烦。

## 播放序列

使用以下方法完成`Sequencer`的启动和停止：

```
    void start()

```

和

```
    void stop()

```

`Sequencer.start`方法开始播放序列。请注意，播放从序列中的当前位置开始。使用上述`setSequence`方法加载现有序列，将序列发生器的当前位置初始化为序列的最开头。 `stop`方法停止音序器，但它不会自动倒回当前`Sequence`。在不重置位置的情况下启动已停止的`Sequence`只会从当前位置恢复序列的播放。在这种情况下，`stop`方法用作暂停操作。但是，在开始播放之前，有各种`Sequencer`方法用于将当前序列位置设置为任意值。 （我们将在下面讨论这些方法。）

如前所述，`Sequencer`通常有一个或多个`Transmitter`对象，通过它们将`MidiMessages`发送到`Receiver`。正是通过这些`Transmitters`，`Sequencer`通过适当地发射对应于当前`Sequence`中包含的`MidiEvents`的定时`MidiMessages`来播放`Sequence`。因此，播放`Sequence`的部分设置过程是调用`Sequencer's` `Transmitter`对象上的`setReceiver`方法，实际上将其输出连接到将使用播放数据的设备。有关`Transmitters`和`Receivers`的更多详情，请参阅[发送和接收 MIDI 信息](MIDI-messages.html)。

## 录制和保存序列

要将 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`参数指定 resolution 参数的单位。在这种情况下，我们已经指定我们创建的`Sequence`的定时分辨率将是每季度 10 个脉冲。 `Sequence`构造函数的另一个可选参数是多个轨道参数，这将导致初始序列以指定数量的（最初为空）`Tracks`开始。否则将创建`Sequence`而没有初始`Tracks`;可以根据需要稍后添加它们。
4.  使用`Sequence.createTrack`在`Sequence`中创建一个空`Track`。如果使用初始`Tracks`创建`Sequence`，则不需要此步骤。
5.  使用`Sequencer.setSequence`，选择我们的新`Sequence`接收录音。 `setSequence`方法将现有的`Sequence`与`Sequencer`连接在一起，这有点类似于将磁带装入磁带录音机。
6.  为每个`Track`调用`Sequencer.recordEnable`进行记录。如有必要，通过调用`Sequence.getTracks`获取`Sequence`中可用`Tracks`的参考。
7.  调用`Sequencer`上的`startRecording`。
8.  完成录制后，调用`Sequencer.stop`或`Sequencer.stopRecording`。
9.  用`MidiSystem.write`将录制的`Sequence`保存到 MIDI 文件。 `MidiSystem`的`write`方法将`Sequence`作为其参数之一，并将`Sequence`写入流或文件。

## 编辑序列

许多应用程序允许通过从文件加载序列来创建序列，还有一些应用程序允许通过从实时 MIDI 输入（即录制）捕获序列来创建序列。但是，某些程序需要从头开始创建 MIDI 序列，无论是以编程方式还是响应用户输入。功能齐全的音序器程序允许用户手动构建新序列，以及编辑现有序列。

这些数据编辑操作是在 Java Sound API 中实现的，而不是通过`Sequencer`方法实现的，而是通过数据对象本身的方法实现的：`Sequence`，`Track`和`MidiEvent`。您可以使用`Sequence`构造函数之一创建一个空序列，然后通过调用以下`Sequence`方法向其中添加轨道：

```
    Track createTrack() 

```

如果您的程序允许用户编辑序列，则需要使用此`Sequence`方法删除轨道：

```
    boolean deleteTrack(Track track) 

```

一旦序列包含轨道，您可以通过调用`Track`类的方法来修改轨道的内容。 `Track`中包含的`MidiEvents`作为`Track`对象存储在`Track`中，`Track`提供了一组访问，添加和删除列表中事件的方法。方法`add`和`remove`相当不言自明，在`Track`中添加或删除指定的`MidiEvent`。提供了`get`方法，该方法将索引存入`Track's`事件列表并返回存储在那里的`MidiEvent`。此外，还有`size`和`tick`方法，它们分别返回轨道中`MidiEvents`的数量和轨道的持续时间，表示为`Ticks`的总数。

要在将新事件添加到轨道之前创建新事件，您当然会使用`MidiEvent`构造函数。要指定或修改事件中嵌入的 MIDI 消息，可以调用相应`MidiMessage`子类（`ShortMessage`，`SysexMessage`或`MetaMessage`）的`setMessage`方法。要修改事件发生的时间，请调用`MidiEvent.setTick`。

结合使用，这些低级方法为全功能音序器程序所需的编辑功能提供了基础。