使用 Sequencer 方法

原文: https://docs.oracle.com/javase/tutorial/sound/MIDI-seq-methods.html

Sequencer 接口提供以下几种方法:

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

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

获取序列发生器

应用程序不实例化Sequencer;毕竟,Sequencer只是一个接口。相反,与 Java Sound API 的 MIDI 包中的所有设备一样,通过静态MidiSystem对象访问Sequencer。如前面访问 MIDI 系统资源中所述,以下MidiSystem方法可用于获取默认Sequencer

  1. static Sequencer getSequencer()

以下代码片段获取默认Sequencer,获取所需的任何系统资源,并使其运行:

  1. Sequencer sequencer;
  2. // Get default sequencer.
  3. sequencer = MidiSystem.getSequencer();
  4. if (sequencer == null) {
  5. // Error -- sequencer device is not supported.
  6. // Inform user and return...
  7. } else {
  8. // Acquire resources and make operational.
  9. sequencer.open();
  10. }

open的调用保留了音序器设备供程序使用。想象共享一个音序器没有多大意义,因为它一次只能播放一个序列。使用完顺控程序后,可以通过调用close将其提供给其他程序。

可以按照访问 MIDI 系统资源中的描述获得非默认音序器。

加载序列

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

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

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

另一种方法是明确地创建Sequence。如果您要在播放之前编辑序列数据,则需要使用此方法。使用此方法,您可以调用MidiSystem's重载方法getSequence。该方法能够从InputStreamFileURL获得序列。该方法返回一个Sequence对象,然后可以将其加载到Sequencer中进行回放。扩展前面的代码摘录,这是从File获取Sequence对象并将其加载到sequencer中的示例:

  1. try {
  2. File myMidiFile = new File("seq1.mid");
  3. // Construct a Sequence object, and
  4. // load it into my sequencer.
  5. Sequence mySeq = MidiSystem.getSequence(myMidiFile);
  6. sequencer.setSequence(mySeq);
  7. } catch (Exception e) {
  8. // Handle error and/or return
  9. }

MidiSystem's getSequence方法类似,setSequence可能会抛出InvalidMidiDataException - 在InputStream变体的情况下,IOException - 如果它遇到任何麻烦。

播放序列

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

  1. void start()

  1. void stop()

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

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

录制和保存序列

要将 MIDI 数据捕获到Sequence,然后再捕获到文件,您需要执行除上述步骤之外的其他一些步骤。以下概述显示了设置录制到SequenceTrack所需的步骤:

  1. 如上所述,使用MidiSystem.getSequencer获取用于录制的新音序器。
  2. 设置 MIDI 连接的“接线”。发送要记录的 MIDI 数据的对象应通过其setReceiver方法配置为将数据发送到与记录Sequencer相关联的Receiver
  3. 创建一个新的Sequence对象,它将存储记录的数据。创建Sequence对象时,必须指定序列的全局计时信息。例如:

    1. Sequence mySeq;
    2. try{
    3. mySeq = new Sequence(Sequence.PPQ, 10);
    4. } catch (Exception ex) {
    5. ex.printStackTrace();
    6. }

    Sequence的构造器将divisionType和时序分辨率作为参数。 divisionType参数指定 resolution 参数的单位。在这种情况下,我们已经指定我们创建的Sequence的定时分辨率将是每季度 10 个脉冲。 Sequence构造器的另一个可选参数是多个轨道参数,这将导致初始序列以指定数量的(最初为空)Tracks开始。否则将创建Sequence而没有初始Tracks;可以根据需要稍后添加它们。

  4. 使用Sequence.createTrackSequence中创建一个空Track。如果使用初始Tracks创建Sequence,则不需要此步骤。
  5. 使用Sequencer.setSequence,选择我们的新Sequence接收录音。 setSequence方法将现有的SequenceSequencer连接在一起,这有点类似于将磁带装入磁带录音机。
  6. 为每个Track调用Sequencer.recordEnable进行记录。如有必要,通过调用Sequence.getTracks获取Sequence中可用Tracks的参考。
  7. 调用Sequencer上的startRecording
  8. 完成录制后,调用Sequencer.stopSequencer.stopRecording
  9. MidiSystem.write将录制的Sequence保存到 MIDI 文件。 MidiSystemwrite方法将Sequence作为其参数之一,并将Sequence写入流或文件。

编辑序列

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

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

  1. Track createTrack()

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

  1. boolean deleteTrack(Track track)

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

要在将新事件添加到轨道之前创建新事件,您当然会使用MidiEvent构造器。要指定或修改事件中嵌入的 MIDI 消息,可以调用相应MidiMessage子类(ShortMessageSysexMessageMetaMessage)的setMessage方法。要修改事件发生的时间,请调用MidiEvent.setTick

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