Java教程是为JDK 8编写的。本页中描述的示例和实践不利用后续版本中引入的改进,并可能使用不再可用的技术。
有关Java SE 9及以后版本中更新的语言功能的概述,请参阅Java语言更改。
有关所有JDK版本的新功能、增强功能和已删除或已弃用选项的信息,请参阅JDK发行说明。
到目前为止,我们已经专注于对MIDI数据的简单播放和录制。本节将简要介绍一些通过Sequencer
接口和Sequence
类的方法可以实现的更高级功能。
有两个Sequencer
方法可以获取序列中的当前位置。第一个方法:
long getTickPosition()
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)
这两个方法中的前两个方法将速度设置为每分钟节拍数或每四分音符的微秒数。速度将保持在指定的值,直到再次调用这些方法,或者直到在序列中遇到速度变化事件,此时当前速度将被新指定的速度覆盖。
第三个方法setTempoFactor
的性质不同。它对序列器设置的任何速度进行缩放(无论是通过速度变化事件还是通过前两个方法之一)。默认比例为1.0(无变化)。虽然该方法导致播放或录制比名义速度更快或更慢(除非因子为1.0),但它不会改变名义速度。换句话说,getTempoInBPM
和getTempoInMPQ
返回的速度值不受速度因子的影响,尽管速度因子确实会影响实际的播放或录制速率。此外,如果速度通过速度变化事件或前两个方法之一更改,它仍然会按照最后设置的速度因子进行缩放。但是,如果加载新的序列,则速度因子将重置为1.0。
请注意,当序列的分割类型是SMPTE类型之一而不是PPQ时,所有这些变速指令都无效。
对于序列器的用户来说,关闭某些音轨以更清楚地听到音乐中发生的情况往往是很方便的。功能齐全的序列器程序允许用户在播放过程中选择哪些音轨应该发声(更准确地说,由于序列器本身不会发出声音,用户选择的音轨将对序列器产生的MIDI消息流起作用)。通常,每个音轨上都有两种类型的图形控件:一个静音按钮和一个独奏按钮。如果激活了静音按钮,该音轨在任何情况下都不会发声,直到静音按钮被取消激活。独奏是一个不太常见的功能。它大致与静音相反。如果激活了任何音轨上的独奏按钮,只有激活了独奏按钮的音轨才会发声。这个功能可以让用户在不必将所有其他音轨静音的情况下快速试听少数音轨。静音按钮通常优先于独奏按钮:如果两者都激活,音轨将不会发声。
使用Sequencer
方法,静音或独奏音轨(以及查询音轨的当前静音或独奏状态)非常容易。假设我们已经获取了默认的Sequencer
并将序列数据加载到其中。静音序列中的第五个音轨可以按以下方式完成:
sequencer.setTrackMute(4, true); boolean muted = sequencer.getTrackMute(4); if (!muted) { return; // 静音失败 }
关于上面的代码片段有几点需要注意。首先,序列的音轨从0开始编号,以总音轨数减1结束。此外,setTrackMute
的第二个参数是一个布尔值。如果为true,则请求静音该音轨;否则请求取消静音指定的音轨。最后,为了测试静音是否生效,我们调用Sequencer getTrackMute
方法,将查询的音轨编号传递给它。如果它返回true
,正如我们在这种情况下所期望的那样,静音请求就起效了。如果它返回false
,则表示请求失败。
静音请求可能因为各种原因而失败。例如,setTrackMute
调用中指定的音轨号码可能超过了总音轨数,或者序列器可能不支持静音。通过调用getTrackMute
,我们可以确定我们的请求成功与否。
顺便说一下,getTrackMute
返回的布尔值确实可以告诉我们是否发生了故障,但它无法告诉我们故障的原因。我们可以测试是否将无效的音轨号码传递给setTrackMute
方法来判断故障是否是由此引起的。为此,我们将调用Sequence
的getTracks
方法,该方法返回一个包含序列中所有音轨的数组。如果setTrackMute
调用中指定的音轨号码超过此数组的长度,则说明我们指定了无效的音轨号码。
如果静音请求成功,则在我们的示例中,当序列播放时,第五个音轨将不会有声音,当前静音的任何其他音轨也都不会有声音。
独奏音轨的方法和技巧与静音非常相似。要独奏一个音轨,调用Sequence
的setTrackSolo
方法:
void setTrackSolo(int track, boolean bSolo)
与setTrackMute
一样,第一个参数指定从零开始的音轨号,第二个参数如果为true
,则指定该音轨应处于独奏模式;否则该音轨不应独奏。
Sequencer
有一个名为Sequencer.SyncMode
的内部类。一个SyncMode
对象代表MIDI序列器的时间概念如何与主设备或从设备同步的方式之一。如果序列器被同步到主设备,则在主设备发送某些MIDI消息时,序列器会根据这些消息调整当前时间。如果序列器有从设备,序列器同样会发送MIDI消息来控制从设备的定时。
有三种预定义模式可指定序列器的可能主设备:INTERNAL_CLOCK、MIDI_SYNC和MIDI_TIME_CODE。如果序列器从其他设备接收到MIDI消息,则后两种模式起作用。在这两种模式下,序列器的时间会根据系统实时定时时钟消息或MIDI时间码(MTC)消息进行重置。(有关这些消息类型的更多信息,请参阅MIDI规范。)这两种模式也可以用作从设备模式,此时序列器会向其接收器发送相应类型的MIDI消息。第四种模式NO_SYNC用于指示序列器不应向其接收器发送定时信息。
通过调用setMasterSyncMode
方法并将支持的SyncMode
对象作为参数,可以指定序列器的定时控制方式。同样,setSlaveSyncMode
方法确定序列器将向其接收器发送的定时信息。这些信息控制使用序列器作为主定时源的设备的定时。
序列的每个音轨都可以包含许多不同类型的MidiEvents
。这些事件包括Note On和Note Off消息、程序更改、控制更改和元事件。Java Sound API为后两种事件类型(控制更改事件和元事件)指定了“监听器”接口。您可以使用这些接口在播放序列时收到此类事件发生的通知。
支持ControllerEventListener
接口的对象可以在Sequencer
处理特定的控制变化消息时接收通知。控制变化消息是一种标准的MIDI消息类型,表示MIDI控制器的值发生了变化,比如弯音轮或数据滑块的变化。(有关控制变化消息的完整列表,请参阅MIDI规范。)当在序列的播放过程中处理这样的消息时,该消息指示接收来自序列器的数据的任何设备(通常是合成器)更新某个参数的值。该参数通常控制声音合成的某些方面,比如控制器是弯音轮时,控制当前发声音符的音高。当记录一个序列时,控制变化消息意味着创建该消息的外部物理设备上的控制器已经移动,或者在软件中模拟了这样的移动。
下面是如何使用ControllerEventListener
接口的方法。假设您已经开发了一个实现了ControllerEventListener
接口的类,这意味着您的类包含以下方法:
void controlChange(ShortMessage msg)
还假设您已经创建了一个您的类的实例,并将其分配给一个名为myListener
的变量。如果您在程序的某个位置包含以下语句:
int[] controllersOfInterest = { 1, 2, 4 }; sequencer.addControllerEventListener(myListener, controllersOfInterest);
那么每当Sequencer
处理MIDI控制器号码为1、2或4的控制变化消息时,将调用您的类的controlChange
方法。换句话说,当Sequencer
处理请求设置任何已注册控制器的值时,Sequencer
将调用您的类的controlChange
方法。(请注意,将MIDI控制器号码分配给特定的控制设备的详细信息在MIDI 1.0规范中有详细说明。)
controlChange
方法接收到一个包含受影响的控制器号码和控制器设置的ShortMessage
。您可以使用ShortMessage.getData1
方法获取控制器号码,并使用ShortMessage.getData2
方法获取控制器值的新设置。
另一种特殊事件监听器由MetaEventListener
接口定义。根据标准MIDI文件1.0规范,元消息是不在MIDI传输协议中存在的消息,但可以嵌入到MIDI文件中。它们对于合成器来说没有意义,但可以被序列器解释。元消息包括指令(比如变速命令)、歌词或其他文本以及其他指示符(比如轨道结束)。
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规范定义的消息类型。