Introduction to the Service Provider Interfaces explained that the
javax.sound.sampled.spi
andjavax.sound.midi.spi
packages define abstract classes to be used by developers of sound services. By implementing a subclass of one of these abstract classes, a service provider can create a new service that extends the functionality of the runtime system. The previous section covered the use of thejavax.sound.sampled.spi
package. This section discusses how to use thejavax.sound.midi.spi
package to provide new services for handling MIDI devices and files.There are four abstract classes in the
javax.sound.midi.spi
package, which represent four different types of services that you can provide for the MIDI system:
MidiFileWriter
provides MIDI file-writing services. These services make it possible for an application program to save, to a MIDI file, a MIDISequence
that it has generated or processed.
MidiFileReader
provides file-reading services that return a MIDISequence
from a MIDI file for use in an application program.
MidiDeviceProvider
supplies instances of one or more specific types of MIDI device, possibly including hardware devices.
SoundbankReader
supplies soundbank file-reading services. Concrete subclasses ofSoundbankReader
parse a given soundbank file, producing aSoundbank
object that can be loaded into aSynthesizer
.An application program will not directly create an instance of a service objectwhether a provider object, such as a
MidiDeviceProvider
, or an object, such as aSynthesizer
, that is supplied by the provider object. Nor will the program directly refer to the SPI classes. Instead, the application program makes requests to theMidiSystem
object in thejavax.sound.midi
package, andMidiSystem
in turn uses concrete subclasses of thejavax.sound.midi.spi
classes to process these requests.Providing MIDI File-Writing Services
There are three standard MIDI file formats, all of which an implementation of the Java Sound API can support: Type 0, Type 1, and Type 2. These file formats differ in their internal representation of the MIDI sequence data in the file, and are appropriate for different kinds of sequences. If an implementation doesn't itself support all three types, a service provider can supply the support for the unimplemented ones. There are also variants of the standard MIDI file formats, some of them proprietary, which similarly could be supported by a third-party vendor.
The ability to write MIDI files is provided by concrete subclasses of
MidiFileWriter
. This abstract class is directly analogous tojavax.sampled.spi.AudioFileWriter
. Again, the methods are grouped into query methods for learning what types of files can be written, and methods for actually writing a file. As withAudioFileWriter
, two of the query methods are concrete:The first of these provides general information about whether the file writer can ever write the specified type of MIDI file type. The second method is more specific: it asks whether a particular Sequence can be written to the specified type of MIDI file. Generally, you don't need to override either of these two concrete methods. In the default implementation, each invokes one of two other corresponding query methods and iterates over the results returned. Being abstract, these other two query methods need to be implemented in the subclass:boolean isFileTypeSupported(int fileType) boolean isFileTypeSupported(int fileType, Sequence sequence)The first of these returns an array of all the file types that are supported in general. A typical implementation might initialize the array in the file writer's constructor and return the array from this method. From that set of file types, the second method finds the subset to which the file writer can write the given Sequence. In accordance with the MIDI specification, not all types of sequences can be written to all types of MIDI files.abstract int[] getMidiFileTypes() abstract int[] getMidiFileTypes(Sequence sequence)The
write
methods of aMidiFileWriter
subclass perform the encoding of the data in a givenSequence
into the correct data format for the requested type of MIDI file, writing the coded stream to either a file or an output stream:To do this, theabstract int write(Sequence in, int fileType, java.io.File out) abstract int write(Sequence in, int fileType, java.io.OutputStream out)write
method must parse theSequence
by iterating over its tracks, construct an appropriate file header, and write the header and tracks to the output. The MIDI file's header format is, of course, defined by the MIDI specification. It includes such information as a "magic number" identifying this as a MIDI file, the header's length, the number of tracks, and the sequence's timing information (division type and resolution). The rest of the MIDI file consists of the track data, in the format defined by the MIDI specification.Let's briefly look at how the application program, MIDI system, and service provider cooperate in writing a MIDI file. In a typical situation, an application program has a particular MIDI
Sequence
to save to a file. The program queries theMidiSystem
object to see what MIDI file formats, if any, are supported for the particularSequence
at hand, before attempting to write the file. TheMidiSystem.getMidiFileTypes(Sequence)
method returns an array of all the MIDI file types to which the system can write a particular sequence. It does this by invoking the correspondinggetMidiFileTypes
method for each of the installedMidiFileWriter
services, and collecting and returning the results in an array of integers that can be thought of as a master list of all file types compatible with the givenSequence
. When it comes to writing theSequence
to a file, the call toMidiSystem.write
is passed an integer representing a file type, along with theSequence
to be written and the output file;MidiSystem
uses the supplied type to decide which installedMidiFileWriter
should handle the write request, and dispatches a correspondingwrite
to the appropriateMidiFileWriter
.Providing MIDI File-Reading Services
The
MidiFileReader
abstract class is directly analogous tojavax.sampled.spi.AudioFileReader
class. Both consist of two overloaded methods, each of which can take aFile
,URL
, orInputStream
argument. The first of the overloaded methods returns the file format of a specified file. In the case ofMidiFileReader
, the API is:Concrete subclasses must implement these methods to return a filled-outabstract MidiFileFormat getMidiFileFormat(java.io.File file) abstract MidiFileFormat getMidiFileFormat( java.io.InputStream stream) abstract MidiFileFormat getMidiFileFormat(java.net.URL url)MidiFileFormat
object describing the format of the specified MIDI file (or stream or URL), assuming that the file is of a type supported by the file reader and that it contains valid header information. Otherwise, anInvalidMidiDataException
should be thrown.The other overloaded method returns a MIDI
Sequence
from a given file, stream, or URL :Theabstract Sequence getSequence(java.io.File file) abstract Sequence getSequence(java.io.InputStream stream) abstract Sequence getSequence(java.net.URL url)getSequence
method performs the actual work of parsing the bytes in the MIDI input file and constructing a correspondingSequence
object. This is essentially the inverse of the process used byMidiFileWriter.write
. Because there is a one-to-one correspondence between the contents of a MIDI file as defined by the MIDI specification and aSequence
object as defined by the Java Sound API, the details of the parsing are straightforward. If the file passed togetSequence
contains data that the file reader can't parse (for example, because the file has been corrupted or doesn't conform to the MIDI specification), anInvalidMidiDataException
should be thrown.Providing Particular MIDI Devices
A
MidiDeviceProvider
can be considered a factory that supplies one or more particular types of MIDI device. The class consists of a method that returns an instance of a MIDI device, as well as query methods to learn what kinds of devices this provider can supply.As with the other
javax.sound.midi.spi
services, application developers get indirect access to aMidiDeviceProvider
service through a call toMidiSystem
methods, in this caseMidiSystem.getMidiDevice
andMidiSystem.getMidiDeviceInfo
. The purpose of subclassingMidiDeviceProvider
is to supply a new kind of device, so the service developer must also create an accompanying class for the device being returnedjust as we saw withMixerProvider
in thejavax.sound.sampled.spi
package. There, the returned device's class implemented thejavax.sound.sampled.Mixer
interface; here it implements thejavax.sound.midi.MidiDevice
interface. It might also implement a subinterface ofMidiDevice
, such asSynthesizer
orSequencer
.Because a single subclass of
MidiDeviceProvider
can provide more than one type ofMidiDevice
, thegetDeviceInfo
method of the class returns an array ofMidiDevice.Info
objects enumerating the differentMidiDevices
available:abstract MidiDevice.Info[] getDeviceInfo()The returned array can contain a single element, of course. A typical implementation of the provider might initialize an array in its constructor and return it here. This allows
MidiSystem
to iterate over all installedMidiDeviceProviders
to construct a list of all installed devices.MidiSystem
can then return this list (MidiDevice.Info[]
array) to an application program.
MidiDeviceProvider
also includes a concrete query method:This method permits the system to query the provider about a specific kind of device. Generally, you don't need to override this convenience method. The default implementation iterates over the array returned by getDeviceInfo and compares the argument to each element.boolean isDeviceSupported(MidiDevice.Info info)The third and final
MidiDeviceProvider
method returns the requested device:This method should first test the argument to make sure it describes a device that this provider can supply. If it doesn't, it should throw anabstract MidiDevice getDevice(MidiDevice.Info info)IllegalArgumentException
. Otherwise, it returns the device.Providing Soundbank File-Reading Services
A
SoundBank
is a set ofInstruments
that can be loaded into aSynthesizer
. AnInstrument
is an implementation of a sound-synthesis algorithm that produces a particular sort of sound, and includes accompanying name and information strings. ASoundBank
roughly corresponds to a bank in the MIDI specification, but it's a more extensive and addressable collection; it can perhaps better be thought of as a collection of MIDI banks.
SoundbankReader
consists of a single overloaded method, which the system invokes to read aSoundbank
object from a soundbank file:abstract Soundbank getSoundbank(java.io.File file) abstract Soundbank getSoundbank(java.io.InputStream stream) abstract Soundbank getSoundbank(java.net.URL url)Concrete subclasses of
SoundbankReader
will work in tandem with particular provider-defined implementations ofSoundBank
,Instrument
, andSynthesizer
to allow the system to load aSoundBank
from a file into an instance of a particularSynthesizer
class. Synthesis techniques may differ wildly from oneSynthesizer
to another, and, as a consequence, the data stored in anInstrument
orSoundBank
providing control or specification data for the synthesis process of aSynthesizer
can take a variety of forms. One synthesis technique may require only a few bytes of parameter data; another may be based on extensive sound samples. The resources present in aSoundBank
will depend upon the nature of theSynthesizer
into which they get loaded, and therefore the implementation of thegetSoundbank
method of aSoundbankReader
subclass has access to knowledge of a particular kind ofSoundBank
. In addition, a particular subclass ofSoundbankReader
understands a particular file format for storing theSoundBank
data. That file format may be vendor-specific and proprietary.
SoundBank
is just an interface, with only weak constraints on the contents of aSoundBank
object. The methods an object must support to implement this interface (getResources
,getInstruments
,getVendor
,getName
, etc.) impose loose requirements on the data that the object contains. For example,getResources
andgetInstruments
can return empty arrays. The actual contents of a subclassedSoundBank
object, in particular its instruments and its non-instrument resources, are defined by the service provider. Thus, the mechanism of parsing a soundbank file depends entirely on the specification of that particular kind of soundbank file.Soundbank files are created outside the Java Sound API, typically by the vendor of the synthesizer that can load that kind of soundbank. Some vendors might supply end-user tools for creating such files.