文档

Java™教程
隐藏目录
提供MIDI服务
路径:声音

提供MIDI服务

Service Provider Interfaces简介解释了javax.sound.sampled.spijavax.sound.midi.spi包定义了开发声音服务的抽象类。通过实现这些抽象类的子类,服务提供商可以创建一个扩展运行时系统功能的新服务。前一节介绍了如何使用javax.sound.sampled.spi包。本节讨论如何使用javax.sound.midi.spi包提供处理MIDI设备和文件的新服务。

javax.sound.midi.spi包中有四个抽象类,代表了可以为MIDI系统提供的四种不同类型的服务:

应用程序不会直接创建服务对象的实例,无论是提供者对象,如MidiDeviceProvider,还是由提供者对象提供的对象,如Synthesizer。程序也不会直接引用SPI类。相反,应用程序通过javax.sound.midi包中的MidiSystem对象发出请求,MidiSystem再使用javax.sound.midi.spi类的具体子类来处理这些请求。

提供MIDI文件写入服务

有三种标准的MIDI文件格式,Java Sound API的实现可以支持:Type 0、Type 1和Type 2。这些文件格式在文件中的MIDI序列数据的内部表示上有所不同,并且适用于不同类型的序列。如果一个实现本身不支持所有三种类型,服务提供商可以提供对未实现类型的支持。还有一些标准MIDI文件格式的变种,其中一些是专有的,同样可以由第三方供应商提供支持。

编写MIDI文件的能力由MidiFileWriter的具体子类提供。这个抽象类与javax.sampled.spi.AudioFileWriter直接对应。同样,这些方法被分组为查询方法和实际写入文件的方法。与AudioFileWriter一样,其中两个查询方法是具体的:

boolean isFileTypeSupported(int fileType)
boolean isFileTypeSupported(int fileType, Sequence sequence) 

第一个方法提供了关于文件写入程序是否可以写入指定类型的MIDI文件类型的一般信息。第二个方法更具体:它询问是否可以将特定的Sequence写入指定类型的MIDI文件。通常情况下,您不需要覆盖这两个具体方法中的任何一个。在默认实现中,每个方法调用了两个其他相应的查询方法之一,并迭代返回的结果。作为抽象方法,这另外两个查询方法需要在子类中实现:

abstract int[] getMidiFileTypes() 
abstract int[] getMidiFileTypes(Sequence sequence) 

第一个方法返回支持的所有文件类型的数组。典型的实现可能会在文件写入程序的构造函数中初始化数组,并从该方法返回数组。从文件类型集合中,第二个方法找到文件写入程序可以将给定的Sequence写入的子集。根据MIDI规范,不是所有类型的序列都可以写入所有类型的MIDI文件。

MidiFileWriter子类的write方法将给定的Sequence中的数据编码为请求的MIDI文件类型的正确数据格式,并将编码的流写入文件或输出流:

abstract int write(Sequence in, int fileType, 
                   java.io.File out) 
abstract int write(Sequence in, int fileType, 
                   java.io.OutputStream out) 

为了做到这一点,write方法必须通过迭代其轨道来解析Sequence,构建适当的文件头,并将文件头和轨道写入输出。当然,MIDI文件的头格式是由MIDI规范定义的。它包括一些信息,如一个标识此文件为MIDI文件的"魔数",头的长度,轨道数和序列的定时信息(分割类型和分辨率)。MIDI文件的其余部分由MIDI规范定义的格式的轨道数据组成。

让我们简要地看一下应用程序、MIDI系统和服务提供商在编写MIDI文件时如何协作。在典型情况下,应用程序有一个特定的MIDI Sequence要保存到文件中。程序在尝试写入文件之前,会查询MidiSystem对象,查看是否支持特定Sequence的任何MIDI文件格式。 MidiSystem.getMidiFileTypes(Sequence)方法返回一个数组,该数组包含系统可以写入特定序列的所有MIDI文件类型。它通过调用每个已安装的MidiFileWriter服务的相应getMidiFileTypes方法来实现,然后收集并返回一个整数数组,可以将其视为与给定Sequence兼容的所有文件类型的主列表。当要将Sequence写入文件时,调用MidiSystem.write时传递一个表示文件类型的整数,以及要写入和输出文件的SequenceMidiSystem使用提供的类型来决定哪个已安装的MidiFileWriter应处理写入请求,并将相应的write分派给适当的MidiFileWriter

提供MIDI文件读取服务

MidiFileReader抽象类与javax.sampled.spi.AudioFileReader类直接对应。两者都包含两个重载方法,每个方法都可以接受FileURLInputStream参数。重载方法中的第一个方法返回指定文件的文件格式。对于MidiFileReader,API如下:

abstract MidiFileFormat getMidiFileFormat(java.io.File file) 
abstract MidiFileFormat getMidiFileFormat(
    java.io.InputStream stream) 
abstract MidiFileFormat getMidiFileFormat(java.net.URL url) 

具体的子类必须实现这些方法,以返回一个填充完整的MidiFileFormat对象,描述指定的MIDI文件(或流或URL)的格式,假设该文件是文件读取器支持的类型,并且包含有效的头信息。否则,应抛出InvalidMidiDataException异常。

另一个重载方法从给定的文件、流或URL返回一个MIDI Sequence

abstract Sequence getSequence(java.io.File file) 
abstract Sequence getSequence(java.io.InputStream stream) 
abstract Sequence getSequence(java.net.URL url) 

getSequence方法执行实际的工作,解析MIDI输入文件中的字节并构建相应的Sequence对象。这基本上是MidiFileWriter.write使用的过程的逆过程。由于根据MIDI规范定义的MIDI文件的内容与Java Sound API定义的Sequence对象之间存在一对一的对应关系,解析的细节是直接的。如果传递给getSequence的文件包含文件读取器无法解析的数据(例如,因为文件已损坏或不符合MIDI规范),应抛出InvalidMidiDataException异常。

提供特定的MIDI设备

一个MidiDeviceProvider可以被视为提供一个或多个特定类型的MIDI设备的工厂。该类由一个方法组成,该方法返回一个MIDI设备的实例,以及查询方法来了解该提供程序可以提供哪些类型的设备。

与其他javax.sound.midi.spi服务一样,应用程序开发人员通过调用MidiSystem方法间接访问MidiDeviceProvider服务,在这种情况下是MidiSystem.getMidiDevice和MidiSystem.getMidiDeviceInfo。子类化MidiDeviceProvider的目的是提供一种新的设备,因此服务开发人员还必须为返回的设备创建一个相应的类,就像我们在javax.sound.sampled.spi包中看到的MixerProvider一样。在那里,返回的设备类实现了javax.sound.sampled.Mixer接口;在这里,它实现了javax.sound.midi.MidiDevice接口。它还可以实现MidiDevice的子接口,如Synthesizer或Sequencer。

因为MidiDeviceProvider的单个子类可以提供多种类型的MidiDevice,所以类的getDeviceInfo方法返回一个枚举不同MidiDevices的MidiDevice.Info对象数组:

abstract MidiDevice.Info[] getDeviceInfo() 

返回的数组当然可以包含一个元素。提供程序的典型实现可能会在其构造函数中初始化一个数组,并在这里返回它。这样,MidiSystem可以迭代所有已安装的MidiDeviceProvider来构建所有已安装设备的列表。然后,MidiSystem可以将此列表(MidiDevice.Info[]数组)返回给应用程序。

MidiDeviceProvider还包括一个具体的查询方法:

boolean isDeviceSupported(MidiDevice.Info info) 

此方法允许系统查询提供程序关于特定类型的设备的信息。通常情况下,您不需要重写此便利方法。默认实现会遍历由getDeviceInfo返回的数组,并将参数与每个元素进行比较。

第三个也是最后一个MidiDeviceProvider方法返回请求的设备:

abstract MidiDevice getDevice(MidiDevice.Info info) 

此方法应首先测试参数,确保它描述的是此提供程序可以提供的设备。如果不是,它应该抛出一个IllegalArgumentException异常。否则,它将返回该设备。

提供音频文件读取服务

SoundBank 是一组可以加载到 Synthesizer 中的 InstrumentsInstrument 是一个声音合成算法的实现,用于产生特定类型的声音,并包含相应的名称和信息字符串。 SoundBank 大致对应于MIDI规范中的银行,但它是一个更广泛和可寻址的集合;可以将其视为MIDI银行的集合。

SoundbankReader 包含一个重载的方法,系统调用该方法从音频文件中读取 Soundbank 对象:

abstract Soundbank getSoundbank(java.io.File file) 
abstract Soundbank getSoundbank(java.io.InputStream stream) 
abstract Soundbank getSoundbank(java.net.URL url) 

SoundbankReader 的具体子类将与特定提供者定义的 SoundBankInstrumentSynthesizer 实现配合工作,以允许系统从文件中加载 SoundBank 到特定 Synthesizer 类的实例中。合成技术可能因一个 Synthesizer 而异,因此存储在 InstrumentSoundBank 中的控制或规范数据的形式可以各不相同。一种合成技术可能只需要几个字节的参数数据,而另一种则基于大量的声音样本。一个 SoundBank 中的资源将取决于它们加载到的 Synthesizer 的性质,因此 SoundbankReader 子类的 getSoundbank 方法的实现可以访问特定类型的 SoundBank 的相关信息。此外,SoundbankReader 的特定子类了解存储 SoundBank 数据的特定文件格式。该文件格式可能是特定供应商的专有格式。

SoundBank 只是一个接口,对 SoundBank 对象的内容只有弱约束。要实现此接口,对象必须支持的方法(getResourcesgetInstrumentsgetVendorgetName 等)对对象包含的数据有宽松的要求。例如,getResourcesgetInstruments 可以返回空数组。子类化的 SoundBank 对象的实际内容,特别是其乐器和非乐器资源,由服务提供商定义。因此,解析音频文件的机制完全取决于该特定类型的音频文件的规范。

声音库文件是在Java Sound API之外创建的,通常由能够加载此类声音库的合成器供应商创建。一些供应商可能会提供用于创建这些文件的最终用户工具。

 


上一页:提供采样音频服务
下一页:行程结束