提供 MIDI 服务
服务提供商interface简介解释说javax.sound.sampled.spi
和javax.sound.midi.spi
包定义了声音服务开发人员要使用的抽象类。通过实现这些抽象类之一的子类,服务提供者可以创建一个扩展了运行时系统功能的新服务。上一节介绍了javax.sound.sampled.spi
包的用法。本节讨论如何使用javax.sound.midi.spi
包提供用于处理 MIDI 设备和文件的新服务。
javax.sound.midi.spi
包中有四个抽象类,它们代表可以为 MIDI 系统提供的四种不同类型的服务:
-
MidiFileWriter提供 MIDI 文件写入服务。这些服务使应用程序可以将已生成或处理的 MIDI
Sequence
保存到 MIDI 文件。 -
MidiFileReader提供了文件读取服务,该服务从 MIDI 文件返回 MIDI
Sequence
以便在应用程序中使用。 -
MidiDeviceProvider提供一种或多种特定类型的 MIDI 设备(可能包括硬件设备)的实例。
-
SoundbankReader提供声音库文件读取服务。
SoundbankReader
的具体子类解析给定的音库文件,生成Soundbank
对象,可以将其加载到Synthesizer
中。
应用程序不会直接创建服务对象的实例,无论是提供者对象提供的对象(例如MidiDeviceProvider
还是Synthesizer
这样的对象)。该程序也不会直接引用 SPI 类。而是,应用程序向javax.sound.midi
包中的MidiSystem
对象发出请求,并且MidiSystem
依次使用javax.sound.midi.spi
类的具体子类来处理这些请求。
提供 MIDI 文件写入服务
共有三种标准 MIDI 文件格式,Java Sound API 的实现可以支持所有三种格式:Type 0,Type 1 和 Type2.这些文件格式在文件中 MIDI 序列数据的内部表示形式上有所不同,并且适用于不同种类的序列。如果实现本身不支持所有三种类型,则服务提供者可以为未实现的类型提供支持。标准 MIDI 文件格式也有一些变体,其中一些是专有的,第三方供应商也可以提供类似的支持。
的具体子类MidiFileWriter
提供了写入 MIDI 文件的功能。这个抽象类直接类似于javax.sampled.spi.AudioFileWriter
。同样,这些方法被分为查询方法和用于实际写入文件的方法,查询方法用于学习可以写入哪些类型的文件。与AudioFileWriter
一样,以下两种查询方法是具体的:
boolean isFileTypeSupported(int fileType)
boolean isFileTypeSupported(int fileType, Sequence sequence)
其中的第一个提供有关文件编写器是否可以写入指定类型的 MIDI 文件类型的常规信息。第二种方法更具体:询问是否可以将特定音序写入指定类型的 MIDI 文件。通常,您不需要覆盖这两个具体方法中的任何一个。在默认实现中,每个调用另一个两个相应查询方法之一,并迭代返回的结果。作为抽象,其他两个查询方法需要在子类中实现:
abstract int[] getMidiFileTypes()
abstract int[] getMidiFileTypes(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 文件,标题的 Long 度,音轨数以及序列的定时信息(分割类型和分辨率)。 MIDI 文件的其余部分由音轨数据组成,格式由 MIDI 规范定义。
我们简要介绍一下应用程序,MIDI 系统和服务提供商在编写 MIDI 文件时如何合作。在典型情况下,应用程序具有特定的 MIDI Sequence
以保存到文件中。程序在try写入文件之前,查询MidiSystem
对象以查看手头特定Sequence
支持哪些 MIDI 文件格式(如果有)。 MidiSystem.getMidiFileTypes(Sequence)
方法返回系统可以向其写入特定序列的所有 MIDI 文件类型的数组。为此,它为每个已安装的MidiFileWriter
服务调用相应的getMidiFileTypes
方法,并以整数数组形式收集和返回结果,这些整数数组可以视为与给定Sequence
兼容的所有文件类型的主列表。在将Sequence
写入文件时,对MidiSystem.write
的调用将传递一个表示文件类型的整数,以及要写入的Sequence
和输出文件。 MidiSystem
使用提供的类型来决定应由哪个已安装的MidiFileWriter
处理写请求,并将相应的write
分派给适当的MidiFileWriter
。
提供 MIDI 文件读取服务
MidiFileReader
抽象类直接与javax.sampled.spi.AudioFileReader
类相似。两者都包含两个重载方法,每个方法都可以采用File
,URL
或InputStream
参数。第一个重载方法返回指定文件的文件格式。在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
方法执行解析 MIDIImporting 文件中的字节并构造相应的Sequence
对象的实际工作。这实际上是MidiFileWriter.write
使用的过程的逆过程。因为 MIDI 规范定义的 MIDI 文件的内容和 Java Sound API 定义的Sequence
对象之间存在一一对应的关系,所以解析的细节非常简单。如果传递给getSequence
的文件包含文件读取器无法解析的数据(例如,由于文件已损坏或不符合 MIDI 规范),则应抛出InvalidMidiDataException
。
提供特定的 MIDI 设备
MidiDeviceProvider
可被视为提供一种或多种特定类型的 MIDI 设备的工厂。该类包括一个返回 MIDI 设备实例的方法,以及查询该提供程序可以提供哪些设备的查询方法。
与其他javax.sound.midi.spi
服务一样,应用程序开发人员可以通过调用MidiSystem
方法(在本例中为MidiSystem.getMidiDevice
和MidiSystem.getMidiDeviceInfo
)来间接访问MidiDeviceProvider
服务。对MidiDeviceProvider
进行子类化的 Object 是提供一种新型的设备,因此服务开发人员还必须为要返回的设备创建一个伴随类-就像我们在javax.sound.sampled.spi
包中使用MixerProvider
所看到的一样。在那里,返回的设备的类实现了javax.sound.sampled.Mixer
interface;在这里它实现了javax.sound.midi.MidiDevice
interface。它还可能实现MidiDevice
的子interface,例如Synthesizer
或Sequencer
。
由于MidiDeviceProvider
的一个子类可以提供MidiDevice
的一种以上类型,因此该类的getDeviceInfo
方法返回MidiDevice.Info
对象的数组,该对象枚举了可用的不同MidiDevices
:
abstract MidiDevice.Info[] getDeviceInfo()
当然,返回的数组可以包含一个元素。提供程序的典型实现可能会在其构造函数中初始化一个数组,然后在此处将其返回。这允许MidiSystem
遍历所有已安装的MidiDeviceProviders
来构造所有已安装设备的列表。 MidiSystem
然后可以将此列表(MidiDevice.Info[]
数组)返回给应用程序。
MidiDeviceProvider
还包括一种具体的查询方法:
boolean isDeviceSupported(MidiDevice.Info info)
此方法允许系统向提供商查询特定类型的设备。通常,您不需要重写此便捷方法。默认实现对 getDeviceInfo 返回的数组进行迭代,并将参数与每个元素进行比较。
第三个也是最后一个MidiDeviceProvider
方法返回所请求的设备:
abstract MidiDevice getDevice(MidiDevice.Info info)
此方法应首先测试该参数,以确保它描述了此提供程序可以提供的设备。如果不是,则应抛出IllegalArgumentException
。否则,它将返回设备。
提供 Soundbank 文件读取服务
SoundBank
是一组Instruments
,可以加载到Synthesizer
中。 _5 是声音合成算法的一种实现,该算法可产生特定种类的声音,并包括伴随的名称和信息字符串。 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
的具体子类将与提供程序定义的SoundBank
,Instrument
和Synthesizer
的特定提供者协同工作,以允许系统将SoundBank
从文件加载到特定Synthesizer
类的实例中。 Synthesizer
到另一Synthesizer
的合成技术可能大相径庭,因此,存储在Instrument
或SoundBank
中的数据为Synthesizer
的合成过程提供控制或规范数据,可以采用多种形式。一种综合技术可能只需要几个字节的参数数据。另一个可能基于大量的声音 samples。 SoundBank
中存在的资源将取决于将它们加载到其中的Synthesizer
的性质,因此SoundbankReader
子类的getSoundbank
方法的实现可以访问特定种类的SoundBank
的知识。此外,SoundbankReader
的特定子类了解用于存储SoundBank
数据的特定文件格式。该文件格式可以是特定于供应商的专有文件。
SoundBank
只是一个interface,对SoundBank
对象的内容只有很小的约束。对象必须支持的用于实现此interface的方法(getResources
,getInstruments
,getVendor
,getName
等)对对象包含的数据提出了宽松的要求。例如,getResources
和getInstruments
可以返回空数组。子类别SoundBank
对象的实际内容,特别是其工具及其非仪器资源,由服务提供商定义。因此,解析音库文件的机制完全取决于该特定种类的音库文件的规范。
Soundbank 文件是在 Java Sound API 外部创建的,通常是由可以加载这种 Soundbank 的合成器供应商提供的。一些供应商可能会提供用于创建此类文件的final用户工具。