提供 MIDI 服务

服务提供商interface简介解释说javax.sound.sampled.spijavax.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类相似。两者都包含两个重载方法,每个方法都可以采用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方法执行解析 MIDIImporting 文件中的字节并构造相应的Sequence对象的实际工作。这实际上是MidiFileWriter.write使用的过程的逆过程。因为 MIDI 规范定义的 MIDI 文件的内容和 Java Sound API 定义的Sequence对象之间存在一一对应的关系,所以解析的细节非常简单。如果传递给getSequence的文件包含文件读取器无法解析的数据(例如,由于文件已损坏或不符合 MIDI 规范),则应抛出InvalidMidiDataException

提供特定的 MIDI 设备

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

与其他javax.sound.midi.spi服务一样,应用程序开发人员可以通过调用MidiSystem方法(在本例中为MidiSystem.getMidiDeviceMidiSystem.getMidiDeviceInfo)来间接访问MidiDeviceProvider服务。对MidiDeviceProvider进行子类化的 Object 是提供一种新型的设备,因此服务开发人员还必须为要返回的设备创建一个伴随类-就像我们在javax.sound.sampled.spi包中使用MixerProvider所看到的一样。在那里,返回的设备的类实现了javax.sound.sampled.Mixerinterface;在这里它实现了javax.sound.midi.MidiDeviceinterface。它还可能实现MidiDevice的子interface,例如SynthesizerSequencer

由于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的具体子类将与提供程序定义的SoundBankInstrumentSynthesizer的特定提供者协同工作,以允许系统将SoundBank从文件加载到特定Synthesizer类的实例中。 Synthesizer到另一Synthesizer的合成技术可能大相径庭,因此,存储在InstrumentSoundBank中的数据为Synthesizer的合成过程提供控制或规范数据,可以采用多种形式。一种综合技术可能只需要几个字节的参数数据。另一个可能基于大量的声音 samples。 SoundBank中存在的资源将取决于将它们加载到其中的Synthesizer的性质,因此SoundbankReader子类的getSoundbank方法的实现可以访问特定种类的SoundBank的知识。此外,SoundbankReader的特定子类了解用于存储SoundBank数据的特定文件格式。该文件格式可以是特定于供应商的专有文件。

SoundBank只是一个interface,对SoundBank对象的内容只有很小的约束。对象必须支持的用于实现此interface的方法(getResourcesgetInstrumentsgetVendorgetName等)对对象包含的数据提出了宽松的要求。例如,getResourcesgetInstruments可以返回空数组。子类别SoundBank对象的实际内容,特别是其工具及其非仪器资源,由服务提供商定义。因此,解析音库文件的机制完全取决于该特定种类的音库文件的规范。

Soundbank 文件是在 Java Sound API 外部创建的,通常是由可以加载这种 Soundbank 的合成器供应商提供的。一些供应商可能会提供用于创建此类文件的final用户工具。