文档

Java™ 教程
隐藏目录
使用序列器方法
路径:声音

使用Sequencer方法

Sequencer接口提供了几个类别的方法:

无论您调用哪些Sequencer方法,第一步都是从系统中获取一个Sequencer设备并为您的程序保留它。

获取Sequencer

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

static Sequencer getSequencer()

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

Sequencer sequencer;
// 获取默认的sequencer。
sequencer = MidiSystem.getSequencer(); 
if (sequencer == null) {
    // 错误--不支持sequencer设备。
    // 通知用户并返回...
} else {
    // 获取资源并使其可操作。
    sequencer.open();
}

调用open会为您的程序保留sequencer设备的使用权。想象共享一个sequencer并没有太多意义,因为它一次只能播放一个序列。当您使用完sequencer后,可以通过调用close将其提供给其他程序使用。

可以按照访问MIDI系统资源中描述的方法获取非默认的sequencer。

加载序列

在从系统中获取并预留了一个序列器之后,您需要加载序列器应该播放的数据。有三种典型的方法可以实现这一点:

现在我们来看一下获取序列数据的第一种方式。(其他两种方式分别在录制和保存序列编辑序列下进行描述。)这种第一种方式实际上包含了两种稍微不同的方法。一种方法是将MIDI文件数据提供给一个InputStream,然后通过Sequencer.setSequence(InputStream)直接将其读取到序列器中。使用这种方法,您不需要显式创建Sequence对象。实际上,Sequencer实现可能甚至不会在幕后创建一个Sequence,因为一些序列器具有用于直接处理来自文件的数据的内置机制。

另一种方法是显式创建一个Sequence。如果要在播放之前编辑序列数据,您需要使用此方法。使用此方法,您调用MidiSystem的重载方法getSequence。该方法能够从InputStreamFileURL中获取序列。该方法返回一个Sequence对象,然后可以将其加载到Sequencer中进行播放。在上面的代码摘录的基础上,下面是一个从File中获取Sequence对象并加载到我们的sequencer中的示例:

try {
    File myMidiFile = new File("seq1.mid");
    // 构造一个Sequence对象,并
    // 加载到我的sequencer中。
    Sequence mySeq = MidiSystem.getSequence(myMidiFile);
    sequencer.setSequence(mySeq);
} catch (Exception e) {
   // 处理错误和/或返回
}

MidiSystemgetSequence方法一样,setSequence可能会在遇到任何问题时抛出InvalidMidiDataException,并且在InputStream变体的情况下,还会抛出IOException

播放序列

使用以下方法可以开始和停止Sequencer

    void start()

    void stop()

Sequencer.start 方法开始播放序列。请注意,播放从序列中的当前位置开始。使用上面描述的 setSequence 方法加载现有序列会将序列的当前位置初始化为序列的开头。 stop 方法停止播放器,但不会自动倒回当前的 Sequence。在不重置位置的情况下启动已停止的 Sequence 只会从当前位置继续播放序列。在这种情况下,stop 方法充当了暂停操作。然而,在开始播放之前,Sequencer 提供了各种方法来将当前序列位置设置为任意值。(我们将在下面讨论这些方法。)

如前所述,Sequencer 通常具有一个或多个 Transmitter 对象,通过这些对象将 MidiMessages 发送到 Receiver。通过这些 TransmitterSequencer 播放 Sequence,通过发出与当前 Sequence 中包含的 MidiEvents 相对应的适时 MidiMessages。因此,播放 Sequence 的设置过程的一部分是在 SequencerTransmitter 对象上调用 setReceiver 方法,实际上将其输出连接到将使用播放的数据的设备。有关 TransmittersReceivers 的更多详细信息,请参阅传输和接收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的时间分辨率为每四分音符10个脉冲。Sequence构造方法的另一个可选参数是轨道数量的参数,它可以使初始序列以指定数量的(最初为空)Tracks开始。否则,Sequence将被创建为没有初始Tracks;后续可以根据需要添加Tracks
  4. Sequence中创建一个空的Track,使用Sequence.createTrack方法。如果Sequence是带有初始Tracks创建的,则此步骤是不必要的。
  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写入流或文件。

编辑一个Sequence

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

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

    Track createTrack() 

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

    boolean deleteTrack(Track track) 

一旦序列包含轨道,您可以通过调用Track类的方法来修改轨道的内容。Track中包含的MidiEventjava.util.Vector的形式存储在Track对象中,Track提供了一组方法用于访问、添加和删除列表中的事件。方法addremove相当明显,它们添加或从Track中删除指定的MidiEvent。还提供了一个get方法,该方法接受一个索引,返回存储在该位置的MidiEvent。此外,还有sizetick方法,分别返回轨道中的MidiEvents数量和轨道的持续时间,以Ticks的总数表示。

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

综合来看,这些低级方法为全功能的序列程序所需的编辑功能提供了基础。


上一页: Sequencers简介
下一页: 使用高级Sequencer功能