提供 MIDI 服务
原文: https://docs.oracle.com/javase/tutorial/sound/SPI-providing-MIDI.html
服务提供者接口简介解释了javax.sound.sampled.spi
和javax.sound.midi.spi
包定义了声音服务开发人员使用的抽象类。通过实现这些抽象类之一的子类,服务提供者可以创建扩展运行时系统功能的新服务。上一节介绍了javax.sound.sampled.spi
包的使用。本节讨论如何使用javax.sound.midi.spi
包提供处理 MIDI 设备和文件的新服务。
javax.sound.midi.spi
包中有四个抽象类,它们代表了您可以为 MIDI 系统提供的四种不同类型的服务:
MidiFileWriter
提供 MIDI 文件写入服务。这些服务使应用程序可以将已生成或处理的 MIDISequence
保存到 MIDI 文件。MidiFileReader
提供文件读取服务,从 MIDI 文件返回 MIDISequence
,以便在应用程序中使用。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
一样,两种查询方法是具体的:
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 文件的“幻数”,标题长度,轨道数以及序列的定时信息(分割类型和分辨率)等信息。 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
类。两者都包含两个重载方法,每个方法都可以采用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
方法执行解析 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.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
迭代所有已安装的MidiDeviceProviders
以构建所有已安装设备的列表。然后,MidiSystem
可以将此列表(MidiDevice.Info[]
数组)返回给应用程序。
MidiDeviceProvider
还包括一个具体的查询方法:
boolean isDeviceSupported(MidiDevice.Info info)
该方法允许系统向提供者查询特定类型的设备。通常,您不需要覆盖此便捷方法。默认实现迭代 getDeviceInfo 返回的数组,并将参数与每个元素进行比较。
第三个也是最后一个MidiDeviceProvider
方法返回请求的设备:
abstract MidiDevice getDevice(MidiDevice.Info info)
此方法应首先测试参数,以确保它描述此供应器可以提供的设备。如果没有,则应该抛出IllegalArgumentException
。否则,它返回设备。
提供 Soundbank 文件阅读服务
A SoundBank
是一组Instruments
,可以加载到Synthesizer
中。 Instrument
是声音合成算法的实现,其产生特定类型的声音,并且包括伴随的名称和信息字符串。 SoundBank
大致对应于 MIDI 规范中的一个库,但它是一个更广泛和可寻址的集合;它可能更好地被认为是 MIDI 银行的集合。
SoundbankReader
由单个重载方法组成,系统调用该方法从 soundbank 文件中读取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
相差甚远,因此,存储在Instrument
或SoundBank
中的数据可为Synthesizer
的合成过程提供控制或规格数据。形式。一种合成技术可能只需要几个字节的参数数据;另一种可能基于广泛的声音样例。 SoundBank
中存在的资源将取决于它们加载到的Synthesizer
的性质,因此SoundbankReader
子类的getSoundbank
方法的实现可以获得特定类型的getSoundbank
方法的知识。 COD14]。此外,SoundbankReader
的特定子类理解用于存储SoundBank
数据的特定文件格式。该文件格式可能是供应商特定的和专有的。
SoundBank
只是一个接口,对SoundBank
对象的内容只有很弱的约束。对象必须支持实现此接口的方法(getResources
,getInstruments
,getVendor
,getName
等)对对象包含的数据施加了宽松的要求。例如,getResources
和getInstruments
可以返回空数组。子类SoundBank
对象的实际内容,特别是其仪器及其非仪器资源,由服务提供商定义。因此,解析音库文件的机制完全取决于特定类型的音库文件的规范。
Soundbank 文件是在 Java Sound API 之外创建的,通常由可以加载那种音库的合成器的供应商创建。某些供应商可能会提供最终用户工具来创建此类文件。