提供 MIDI 服务

原文: https://docs.oracle.com/javase/tutorial/sound/SPI-providing-MIDI.html

服务提供者接口简介解释了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的具体子类解析给定的音库文件,生成可以加载到Synthesizer中的Soundbank对象。

应用程序不会直接创建服务对象的实例 - 无论是提供者对象(如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一样,两种查询方法是具体的:

  1. boolean isFileTypeSupported(int fileType)
  2. boolean isFileTypeSupported(int fileType, Sequence sequence)

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

  1. abstract int[] getMidiFileTypes()
  2. abstract int[] getMidiFileTypes(Sequence sequence)

第一个返回一般支持的所有文件类型的数组。典型的实现可能会在文件编写器的构造器中初始化数组,并从此方法返回数组。从该组文件类型中,第二种方法查找文件编写者可以写入给定序列的子集。根据 MIDI 规范,并非所有类型的序列都可以写入所有类型的 MIDI 文件。

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

  1. abstract int write(Sequence in, int fileType,
  2. java.io.File out)
  3. abstract int write(Sequence in, int fileType,
  4. java.io.OutputStream out)

为此,write方法必须通过遍历其轨道来解析Sequence,构造适当的文件头,并将标题和轨道写入输出。 MIDI 文件的标题格式当然是由 MIDI 规范定义的。它包括诸如将其识别为 MIDI 文件的“幻数”,标题长度,轨道数以及序列的定时信息(分割类型和分辨率)等信息。 MIDI 文件的其余部分由轨道数据组成,采用 MIDI 规范定义的格式。

让我们简要介绍一下应用程序,MIDI 系统和服务提供商如何配合写入 MIDI 文件。在典型情况下,应用程序具有特定的 MIDI Sequence以保存到文件。在尝试写入文件之前,程序查询MidiSystem对象以查看特定Sequence支持的 MIDI 文件格式(如果有)。 MidiSystem.getMidiFileTypes(Sequence)方法返回系统可以写入特定序列的所有 MIDI 文件类型的数组。它通过为每个已安装的MidiFileWriter服务调用相应的getMidiFileTypes方法,并在一个整数数组中收集和返回结果,这可以被认为是与给定[兼容的]所有文件类型的主列表。 COD6]。当将Sequence写入文件时,对MidiSystem.write的调用将传递一个表示文件类型的整数,以及要写入的Sequence和输出文件; MidiSystem使用提供的类型来决定安装的MidiFileWriter应该处理写请求,并将相应的write发送到适当的MidiFileWriter

提供 MIDI 文件阅读服务

MidiFileReader抽象类直接类似于javax.sampled.spi.AudioFileReader类。两者都包含两个重载方法,每个方法都可以采用FileURLInputStream参数。第一个重载方法返回指定文件的文件格式。在MidiFileReader的情况下,API 是:

  1. abstract MidiFileFormat getMidiFileFormat(java.io.File file)
  2. abstract MidiFileFormat getMidiFileFormat(
  3. java.io.InputStream stream)
  4. abstract MidiFileFormat getMidiFileFormat(java.net.URL url)

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

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

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

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

提供特定的 MIDI 设备

A MidiDeviceProvider可以被视为提供一种或多种特定类型 MIDI 设备的工厂。该类包含一个返回 MIDI 设备实例的方法,以及用于了解此供应器可以提供哪种设备的查询方法。

与其他javax.sound.midi.spi服务一样,应用程序开发人员通过调用MidiSystem方法间接访问MidiDeviceProvider服务,在本例中为MidiSystem.getMidiDeviceMidiSystem.getMidiDeviceInfo。子类化MidiDeviceProvider的目的是提供一种新类型的设备,因此服务开发人员还必须为返回的设备创建一个附带的类 - 正如我们在javax.sound.sampled.spi包中使用MixerProvider所看到的那样。在那里,返回的设备的类实现了javax.sound.sampled.Mixer接口;这里它实现了javax.sound.midi.MidiDevice接口。它也可能实现MidiDevice的子接口,例如SynthesizerSequencer

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

  1. abstract MidiDevice.Info[] getDeviceInfo()

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

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

  1. boolean isDeviceSupported(MidiDevice.Info info)

该方法允许系统向提供者查询特定类型的设备。通常,您不需要覆盖此便捷方法。默认实现迭代 getDeviceInfo 返回的数组,并将参数与每个元素进行比较。

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

  1. abstract MidiDevice getDevice(MidiDevice.Info info)

此方法应首先测试参数,以确保它描述此供应器可以提供的设备。如果没有,则应该抛出IllegalArgumentException。否则,它返回设备。

提供 Soundbank 文件阅读服务

A SoundBank是一组Instruments,可以加载到Synthesizer中。 Instrument是声音合成算法的实现,其产生特定类型的声音,并且包括伴随的名称和信息字符串。 SoundBank大致对应于 MIDI 规范中的一个库,但它是一个更广泛和可寻址的集合;它可能更好地被认为是 MIDI 银行的集合。

SoundbankReader由单个重载方法组成,系统调用该方法从 soundbank 文件中读取Soundbank对象:

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

SoundbankReader的具体子类将与特定提供者定义的SoundBankInstrumentSynthesizer实现协同工作,以允许系统将SoundBank从文件加载到特定实例Synthesizer ]班。合成技术可能与Synthesizer相差甚远,因此,存储在InstrumentSoundBank中的数据可为Synthesizer的合成过程提供控制或规格数据。形式。一种合成技术可能只需要几个字节的参数数据;另一种可能基于广泛的声音样例。 SoundBank中存在的资源将取决于它们加载到的Synthesizer的性质,因此SoundbankReader子类的getSoundbank方法的实现可以获得特定类型的getSoundbank方法的知识。 COD14]。此外,SoundbankReader的特定子类理解用于存储SoundBank数据的特定文件格式。该文件格式可能是供应商特定的和专有的。

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

Soundbank 文件是在 Java Sound API 之外创建的,通常由可以加载那种音库的合成器的供应商创建。某些供应商可能会提供最终用户工具来创建此类文件。