Java教程是针对JDK 8编写的。本页面中描述的示例和实践不利用后续版本引入的改进,并可能使用已不再可用的技术。
有关Java SE 9和后续版本中更新的语言功能的摘要,请参阅Java语言更改。
有关所有JDK版本的新功能、增强功能和已删除或弃用选项的信息,请参阅JDK发行说明。
Sequencer
接口提供了几个类别的方法:
Sequence
对象中加载序列数据的方法,以及将当前加载的序列数据保存到MIDI文件中的方法。Sequence
中移动当前播放或录制位置。Sequencer
可以以不同的速度播放,一些Tracks
被静音,并与其他对象处于不同的同步状态。Sequencer
处理某些类型的MIDI事件时通知它们。无论您调用哪些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
。该方法能够从InputStream
、File
或URL
中获取序列。该方法返回一个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) { // 处理错误和/或返回 }
与MidiSystem
的getSequence
方法一样,setSequence
可能会在遇到任何问题时抛出InvalidMidiDataException
,并且在InputStream
变体的情况下,还会抛出IOException
。
void start()
void stop()
Sequencer.start
方法开始播放序列。请注意,播放从序列中的当前位置开始。使用上面描述的 setSequence
方法加载现有序列会将序列的当前位置初始化为序列的开头。 stop
方法停止播放器,但不会自动倒回当前的 Sequence
。在不重置位置的情况下启动已停止的 Sequence
只会从当前位置继续播放序列。在这种情况下,stop
方法充当了暂停操作。然而,在开始播放之前,Sequencer
提供了各种方法来将当前序列位置设置为任意值。(我们将在下面讨论这些方法。)
如前所述,Sequencer
通常具有一个或多个 Transmitter
对象,通过这些对象将 MidiMessages
发送到 Receiver
。通过这些 Transmitter
,Sequencer
播放 Sequence
,通过发出与当前 Sequence
中包含的 MidiEvents
相对应的适时 MidiMessages
。因此,播放 Sequence
的设置过程的一部分是在 Sequencer
的 Transmitter
对象上调用 setReceiver
方法,实际上将其输出连接到将使用播放的数据的设备。有关 Transmitters
和 Receivers
的更多详细信息,请参阅传输和接收MIDI消息。
要将 MIDI 数据捕获到 Sequence
,然后保存到文件,您需要执行一些额外的步骤。以下概述显示了设置录制到 Sequence
中的 Track
所需的步骤:
MidiSystem.getSequencer
来获取一个新的序列器用于录音,如上所述。setReceiver
方法进行配置,将数据发送到与录制Sequencer
相关联的Receiver
。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
。Sequence
中创建一个空的Track
,使用Sequence.createTrack
方法。如果Sequence
是带有初始Tracks
创建的,则此步骤是不必要的。Sequencer.setSequence
,选择我们的新Sequence
来接收录制的数据。setSequence
方法将现有的Sequence
与Sequencer
关联起来,类似于将磁带加载到磁带录音机上。Track
,调用Sequencer.recordEnable
。如果需要,通过调用Sequence.getTracks
获取Sequence
中可用的Tracks
的引用。Sequencer
上调用startRecording
。Sequencer.stop
或Sequencer.stopRecording
。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
中包含的MidiEvent
以java.util.Vector
的形式存储在Track
对象中,Track
提供了一组方法用于访问、添加和删除列表中的事件。方法add
和remove
相当明显,它们添加或从Track
中删除指定的MidiEvent
。还提供了一个get
方法,该方法接受一个索引,返回存储在该位置的MidiEvent
。此外,还有size
和tick
方法,分别返回轨道中的MidiEvents
数量和轨道的持续时间,以Ticks
的总数表示。
要在添加到轨道之前创建一个新的事件,您当然会使用MidiEvent
构造函数。要指定或修改嵌入在事件中的MIDI消息,您可以调用适当的MidiMessage
子类(ShortMessage
、SysexMessage
或MetaMessage
)的setMessage
方法。要修改事件的发生时间,调用MidiEvent.setTick
。