Java Sound
Java Sound
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
Who this Guide Is For . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
What this Guide Describes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
For More Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xii
Chapter 1 Introduction to the Java Sound API . . . . . . . . . . . . . . . . 1
Design Goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Who is the Java Sound API For? . . . . . . . . . . . . . . . . . . . . 1
How Does the Java Sound API Relate
to Other Interfaces? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Sampled Audio. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
What Is Sampled Audio? . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Audio Configurations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
MIDI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
What Is MIDI? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
MIDI Configurations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Service Provider Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Part I: Sampled Audio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Chapter 2 Overview of the Sampled Package . . . . . . . . . . . . . . . . 11
Design Goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
A Focus on Data Transport . . . . . . . . . . . . . . . . . . . . . . . 11
Buffered and Unbuffered Handling of Audio. . . . . . . . 12
v
vi Java Sound API Programmer’s Guide
xi
xii Java Sound API Programmer’s Guide
Design Goals
The Java™ Sound API is a low-level API for effecting and controlling the
input and output of sound media, including both audio and Musical
Instrument Digital Interface (MIDI) data. The Java Sound API provides
explicit control over the capabilities normally required for sound input
and output, in a framework that promotes extensibility and flexibility.
1
2 Java Sound API Programmer’s Guide
Packages
The Java Sound API includes support for both digital audio and MIDI
data. These two major modules of functionality are provided in separate
packages:
• javax.sound.sampled
This package specifies interfaces for capture, mixing, and playback of
digital (sampled) audio.
• javax.sound.midi
This package provides interfaces for MIDI synthesis, sequencing, and
event transport.
Chapter 1 Introduction to the Java Sound API 3
The rest of this chapter briefly discusses the sampled-audio system, the
MIDI system, and the SPI packages. Each of these is then discussed in
detail in a subsequent part of the guide.
Sampled Audio
What Is Sampled Audio?
The javax.sound.sampled package handles digital audio data, which
the Java Sound API refers to as sampled audio. Samples are successive
snapshots of a signal. In the case of audio, the signal is a sound wave. A
microphone converts the acoustic signal into a corresponding analog
electrical signal, and an analog-to-digital converter transforms that analog
signal into a sampled digital form. The following figure shows a brief
moment in a sound recording.
This graph plots sound pressure (amplitude) on the vertical axis, and time
on the horizontal axis. The amplitude of the analog sound wave is
measured periodically at a certain rate, resulting in the discrete samples
(the red data points in the figure) that comprise the digital audio signal.
The center horizontal line indicates zero amplitude; points above the line
4 Java Sound API Programmer’s Guide
are positive-valued samples, and points below are negative. The accuracy
of the digital approximation of the analog signal depends on its resolution
in time (the sampling rate) and its quantization, or resolution in amplitude
(the number of bits used to represent each sample). As a point of
reference, the audio recorded for storage on compact discs is sampled
44,100 times per second and represented with 16 bits per sample.
The term “sampled audio” is used here slightly loosely. A sound wave
could be sampled at discrete intervals while being left in an analog form.
For purposes of the Java Sound API, however, “sampled audio” is
equivalent to “digital audio.”
Typically, sampled audio on a computer comes from a sound recording,
but the sound could instead be synthetically generated (for example, to
create the sounds of a touch-tone telephone). The term “sampled audio”
refers to the type of data, not its origin.
Further information about the structure of digital audio data is given
under “What Is Formatted Audio Data?” in Chapter 2, “Overview of the
Sampled Package.”
Audio Configurations
The Java Sound API does not assume a specific audio hardware
configuration; it is designed to allow different sorts of audio components
to be installed on a system and accessed by the API. The Java Sound API
supports common functionality such as input and output from a sound
card (for example, for recording and playback of sound files) as well as
mixing of multiple streams of audio. Here is one example of a typical
audio architecture:
In this example, a device such as a sound card has various input and
output ports, and mixing is provided in the software. The mixer might
receive data that has been read from a file, streamed from a network,
generated on the fly by an application program, or produced by a MIDI
synthesizer. (The javax.sound.midi package, discussed next, supplies a
Java language interface for synthesizers.) The mixer combines all its audio
inputs into a single stream, which can be sent to an output device for
rendering.
MIDI
The javax.sound.midi package contains APIs for transporting and
sequencing MIDI events, and for synthesizing sound from those events.
What Is MIDI?
Whereas sampled audio is a direct representation of a sound itself, MIDI
data can be thought of as a recipe for creating a sound, especially a
musical sound. MIDI data, unlike audio data, does not describe sound
directly. Instead, it describes events that affect the sound a synthesizer is
making. MIDI data is analogous to a graphical user interface’s keyboard
and mouse events. In the case of MIDI, the events can be thought of as
actions upon a musical keyboard, along with actions on various pedals,
sliders, switches, and knobs on that musical instrument. These events
need not actually originate with a hardware musical instrument; they can
be simulated in software, and they can be stored in MIDI files. A program
that can create, edit, and perform these files is called a sequencer. Many
computer sound cards include MIDI-controllable music synthesizer chips
to which sequencers can send their MIDI events. Synthesizers can also be
implemented entirely in software. The synthesizers interpret the MIDI
events that they receive and produce audio output. Usually the sound
synthesized from MIDI data is musical sound (as opposed to speech, for
example). MIDI synthesizers are also capable of generating various kinds
of sound effects.
Some sound cards include MIDI input and output ports to which external
MIDI hardware devices (such as keyboard synthesizers or other
instruments) can be connected. From a MIDI input port, an application
program can receive events generated by an external MIDI-equipped
musical instrument. The program might play the musical performance
using the computer’s internal synthesizer, save it to disk as a MIDI file, or
render it into musical notation. A program might use a MIDI output port
6 Java Sound API Programmer’s Guide
MIDI Configurations
The diagram below illustrates the functional relationships between the
major components in a possible MIDI configuration based on the Java
Sound API. (As with audio, the Java Sound API permits a variety of MIDI
software devices to be installed and interconnected. The system shown
here is one potential scenario.) The flow of data between components is
indicated by arrows. The data can be in a standard file format, or (as
indicated by the key in the lower right corner of the diagram), it can be
audio, raw MIDI bytes, or time-tagged MIDI messages.
• An audio mixer
• A MIDI synthesizer
• A file parser that can read or write a new type of audio or MIDI file
• A converter that translates between different sound data formats
In some cases, services are software interfaces to the capabilities of
hardware devices, such as sound cards, and the service provider might be
the same as the vendor of the hardware. In other cases, the services exist
purely in software. For example, a synthesizer or a mixer could be an
interface to a chip on a sound card, or it could be implemented without
any hardware support at all.
8 Java Sound API Programmer’s Guide
Design Goals
Before examining the elements of the Java Sound API, it helps to
understand the orientation of the javax.sound.sampled package.
11
12 Java Sound API Programmer’s Guide
handled when the user requests that the flow of sound be started, paused,
resumed, or stopped.
To support this focus on basic audio input and output, the Java Sound API
provides methods for converting between various audio data formats,
and for reading and writing common types of sound files. However, it
does not attempt to be a comprehensive sound-file toolkit. A particular
implementation of the Java Sound API need not support an extensive set
of file types or data format conversions. Third-party service providers can
supply modules that “plug in” to an existing implementation to support
additional file types and conversions.
Data Formats
A data format tells you how to interpret a series of bytes of “raw”
sampled audio data, such as samples that have already been read from a
sound file, or samples that have been captured from the microphone
input. You might need to know, for example, how many bits constitute
one sample (the representation of the shortest instant of sound), and
similarly you might need to know the sound’s sample rate (how fast the
samples are supposed to follow one another). When setting up for
playback or capture, you specify the data format of the sound you are
capturing or playing.
In the Java Sound API, a data format is represented by an AudioFormat
object, which includes the following attributes:
File Formats
A file format specifies the structure of a sound file, including not only the
format of the raw audio data in the file, but also other information that can
be stored in the file. Sound files come in various standard varieties, such
as WAVE (also known as WAV, and often associated with PCs), AIFF
(often associated with Macintoshes), and AU (often associated with UNIX
systems). The different types of sound file have different structures. For
example, they might have a different arrangement of data in the file’s
“header.” A header contains descriptive information that typically
precedes the file’s actual audio samples, although some file formats allow
successive “chunks” of descriptive and audio data. The header includes a
specification of the data format that was used for storing the audio in the
sound file. Any of these types of sound file can contain various data
formats (although usually there is only one data format within a given
file), and the same data format can be used in files that have different file
formats.
In the Java Sound API, a file format is represented by an
AudioFileFormat object, which contains:
What Is a Mixer?
Many application programming interfaces (APIs) for sound make use of
the notion of an audio device. A device is often a software interface to a
physical input/output device. For example, a sound-input device might
represent the input capabilities of a sound card, including a microphone
input, a line-level analog input, and perhaps a digital audio input.
In the Java Sound API, devices are represented by Mixer objects. The
purpose of a mixer is to handle one or more streams of audio input and
one or more streams of audio output. In the typical case, it actually mixes
together multiple incoming streams into one outgoing stream. A Mixer
object can represent the sound-mixing capabilities of a physical device
such as a sound card, which might need to mix the sound coming in to the
computer from various inputs, or the sound coming from application
programs and going to outputs.
Alternatively, a Mixer object can represent sound-mixing capabilities that
are implemented entirely in software, without any inherent interface to
physical devices.
In the Java Sound API, a component such as the microphone input on a
sound card is not itself considered a device—that is, a mixer—but rather a
port into or out of the mixer. A port typically provides a single stream of
audio into or out of the mixer (although the stream can be multichannel,
such as stereo). The mixer might have several such ports. For example, a
mixer representing a sound card's output capabilities might mix several
streams of audio together, and then send the mixed signal to any or all of
various output ports connected to the mixer. These output ports could be
(for example) a headphone jack, a built-in speaker, or a line-level output.
To understand the notion of a mixer in the Java Sound API, it helps to
visualize a physical mixing console, such as those used in live concerts
and recording studios. (See illustration that follows.)
Chapter 2 Overview of the Sampled Package 17
channels are perhaps also sent via an amplifier to speakers in the hall,
depending on the type of music and the size of the hall.
Now imagine a recording studio, in which each instrument or singer is
recorded to a separate track of a multitrack tape recorder. After the
instruments and singers have all been recorded, the recording engineer
performs a “mixdown” to combine all the taped tracks into a two-channel
(stereo) recording that can be distributed on compact discs. In this case,
the input to each of the mixer’s strips is not a microphone, but one track of
the multitrack recording. Once again, the engineer can use controls on the
strips to decide each track’s volume, pan, and reverb amount. The mixer’s
outputs go once again to a stereo recorder and to stereo speakers, as in the
example of the live concert.
These two examples illustrate two different uses of a mixer: to capture
multiple input channels, combine them into fewer tracks, and save the
mixture, or to play back multiple tracks while mixing them down to fewer
tracks.
In the Java Sound API, a mixer can similarly be used for input (capturing
audio) or output (playing back audio). In the case of input, the source from
which the mixer gets audio for mixing is one or more input ports. The
mixer sends the captured and mixed audio streams to its target, which is
an object with a buffer from which an application program can retrieve
this mixed audio data. In the case of audio output, the situation is
reversed. The mixer’s source for audio is one or more objects containing
buffers into which one or more application programs write their sound
data; and the mixer’s target is one or more output ports.
What Is a Line?
The metaphor of a physical mixing console is also useful for
understanding the Java Sound API's concept of a line.
A line is an element of the digital audio “pipeline”—that is, a path for
moving audio into or out of the system. Usually the line is a path into or
out of a mixer (although technically the mixer itself is also a kind of line).
Audio input and output ports are lines. These are analogous to the
microphones and speakers connected to a physical mixing console.
Another kind of line is a data path through which an application program
can get input audio from, or send output audio to, a mixer. These data
paths are analogous to the tracks of the multitrack recorder connected to
the physical mixing console.
Chapter 2 Overview of the Sampled Package 19
One difference between lines in the Java Sound API and those of a
physical mixer is that the audio data flowing through a line in the Java
Sound API can be mono or multichannel (for example, stereo). By
contrast, each of a physical mixer’s inputs and outputs is typically a single
channel of sound. To get two or more channels of output from the physical
mixer, two or more physical outputs are normally used (at least in the case
of analog sound; a digital output jack is often multichannel). In the Java
Sound API, the number of channels in a line is specified by the
AudioFormat of the data that is currently flowing through the line.
Note that this is just one example of a possible mixer that is supported by
the API. Not all audio configurations will have all the features illustrated.
An individual source data line might not support panning, a mixer might
not implement reverb, and so on.
Here, data flows into the mixer from one or more input ports, commonly
the microphone or the line-in jack. Gain and pan are applied, and the
mixer delivers the captured data to an application program via the mixer's
target data line. A target data line is a mixer output, containing the
mixture of the streamed input sounds. The simplest mixer has just one
target data line, but some mixers can deliver captured data to multiple
target data lines simultaneously.
• Controls
Data lines and ports often have a set of controls that affect the audio
signal passing through the line. The Java Sound API specifies control
classes that can be used to manipulate aspects of sound such as: gain
(which affects the signal’s volume in decibels), pan (which affects the
sound's right-left positioning, reverb (which adds reverberation to
the sound to emulate different kinds of room acoustics), and sample
rate (which affects the rate of playback as well as the sound’s pitch).
• Open or closed status
Successful opening of a line guarantees that resources have been
allocated to the line. A mixer has a finite number of lines, so at some
point multiple application programs (or the same one) might vie for
usage of the mixer’s lines. Closing a line indicates that any resources
used by the line may now be released.
• Events
A line generates events when it opens or closes. Subinterfaces of Line
can introduce other types of events. When a line generates an event,
the event is sent to all objects that have registered to “listen” for
events on that line. An application program can create these objects,
register them to listen for line events, and react to the events as
desired.
We’ll now examine the subinterfaces of the Line interface.
Ports are simple lines for input or output of audio to or from audio
devices. As mentioned earlier, some common types of ports are the
22 Java Sound API Programmer’s Guide
• Audio format
Each data line has an audio format associated with its data stream.
• Media position
A data line can report its current position in the media, expressed in
sample frames. This represents the number of sample frames
captured by or rendered from the data line since it was opened.
• Buffer size
This is the size of the data line’s internal buffer in bytes. For a source
data line, the internal buffer is one to which data can be written, and
Chapter 2 Overview of the Sampled Package 23
for a target data line it’s one from which data can be read.
• Level (the current amplitude of the audio signal)
• Start and stop playback or capture
• Pause and resume playback or capture
• Flush (discard unprocessed data from the queue)
• Drain (block until all unprocessed data has been drained from the
queue, and the data line's buffer has become empty)
• Active status
A data line is considered active if it is engaged in active presentation
or capture of audio data to or from a mixer.
• Events
START and STOP events are produced when active presentation or
capture of data from or to the data line starts or stops.
A TargetDataLine receives audio data from a mixer. Commonly, the
mixer has captured audio data from a port such as a microphone; it might
process or mix this captured audio before placing the data in the target
data line's buffer. The TargetDataLine interface provides methods for
reading the data from the target data line’s buffer and for determining
how much data is currently available for reading.
A SourceDataLine receives audio data for playback. It provides
methods for writing data to the source data line’s buffer for playback, and
for determining how much data the line is prepared to receive without
blocking.
A Clip is a data line into which audio data can be loaded prior to
playback. Because the data is pre-loaded rather than streamed, the clip’s
duration is known before playback, and you can choose any starting
position in the media. Clips can be looped, meaning that upon playback,
all the data between two specified loop points will repeat a specified
number of times, or indefinitely.
This chapter has introduced most of the important interfaces and classes
of the sampled-audio API. Subsequent chapters show how you can access
and use these objects in your application program.
24 Java Sound API Programmer’s Guide
Chapter 3
Accessing Audio System
Resources
25
26 Java Sound API Programmer’s Guide
• Mixers
A system typically has multiple mixers installed. There is usually at
least one for audio input and one for audio output. There might also
be mixers that don’t have I/O ports but instead accept audio from an
application program and deliver the mixed audio back to the
program. The AudioSystem class provides a list of all of the installed
mixers.
• Lines
Even though every line is associated with a mixer, an application
program can get a line directly from the AudioSystem, without
dealing explicitly with mixers.
• Format conversions
An application program can use format conversions to translate
audio data from one format to another. Conversions are described in
Chapter 7, “Using Files and Format Converters.”
• Files and streams
The AudioSystem class provides methods for translating between
audio files and audio streams. It can also report the file format of a
sound file and can write files in different formats. These facilities are
discussed in Chapter 7, “Using Files and Format Converters.”
Information Objects
Several classes in the Java Sound API provide useful information about
associated interfaces. For example, Mixer.Info provides details about an
installed mixer, such as the mixer's vendor, name, description, and
version. Line.Info obtains the class of a specific line. Subclasses of
Line.Info include Port.Info and DataLine.Info, which obtain details
relevant to a specific port and data line, respectively. Each of these classes
is described further in the appropriate section below. It’s important not to
confuse the Info object with the mixer or line object that it describes.
Chapter 3 Accessing Audio System Resources 27
Getting a Mixer
Usually, one of the first things a program that uses the Java Sound API
needs to do is to obtain a mixer, or at least one line of a mixer, so that you
can get sound into or out of the computer. Your program might need a
specific kind of mixer, or you might want to display a list of all the
available mixers so that the user can select one. In either case, you need to
learn what kinds of mixers are installed. AudioSystem provides the
following method:
static Mixer.Info[] getMixerInfo()
• Name
• Version
• Vendor
• Description
These are arbitrary strings, so an application program that needs a specific
mixer must know what to expect and what to compare the strings to. The
company that provides the mixer should include this information in its
documentation. Alternatively, and perhaps more typically, the application
program will display all the Mixer.Info objects’ strings to the user and
let the user choose the corresponding mixer.
Once an appropriate mixer is found, the application program invokes the
following AudioSystem method to obtain the desired mixer:
static Mixer getMixer(Mixer.Info info)
What if your program needs a mixer that has certain capabilities, but it
doesn’t need a specific mixer made by a specific vendor? And what if you
can’t depend on the user’s knowing which mixer should be chosen? In
that case, the information in the Mixer.Info objects won’t be of much
use. Instead, you can iterate over all the Mixer.Info objects returned by
getMixerInfo, get a mixer for each by invoking getMixer, and query each
mixer for its capabilities. For example, you might need a mixer that can
28 Java Sound API Programmer’s Guide
write its mixed audio data to a certain number of target data lines
simultaneously. In that case, you would query each mixer using this Mixer
method:
int getMaxLines(Line.Info info)
try {
line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format);
} catch (LineUnavailableException ex) {
// Handle the error.
//...
}
Note the use of the method isLineSupported to see whether the mixer
even has a line of the desired type.
Recall that a source line is an input to a mixer—namely, a Port object if
the mixer represents an audio-input device, and a SourceDataLine or
Clip object if the mixer represents an audio-output device. Similarly, a
target line is an output of the mixer: a Port object for an audio-output
mixer, and a TargetDataLine object for an audio-input mixer. What if a
mixer doesn’t connect to any external hardware device at all? For
example, consider an internal or software-only mixer that gets audio from
an application program and delivers its mixed audio back to the program.
This kind of mixer has SourceDataLine or Clip objects for its input lines
and TargetDataLine objects for its output lines.
You can also use the following AudioSystem methods to learn more about
source and target lines of a specified type that are supported by any
installed mixer:
static Line.Info[] getSourceLineInfo(Line.Info info)
static Line.Info[] getTargetLineInfo(Line.Info info)
30 Java Sound API Programmer’s Guide
Note that the array returned by each of these methods indicates unique
types of lines, not necessarily all the lines. For example, if two of a mixer’s
lines, or two lines of different mixers, have identical Line.Info objects,
the two lines will represented by only one Line.Info in the returned
array.
These methods return arrays of all the Line.Info objects for the
particular mixer. Once you’ve obtained the arrays, you can iterate over
them, calling Mixer’s getLine method to obtain each line, followed by
Line’s open method to reserve use of each line for your program.
user might have the speaker port turned off so as not to disturb her co-
workers. She would be rather upset if your program suddenly overrode
her wishes and started blaring music. As another example, a user might
want to be assured that his computer’s microphone is never turned on
without his knowledge, to avoid eavesdropping. In general, it is
recommended not to open or close ports unless your program is
responding to the user’s intentions, as expressed through the user
interface. Instead, respect the settings that the user or the operating
system has already selected.
It isn’t necessary to open or close a port before the mixer it’s attached to
will function correctly. For example, you can start playing back sound into
an audio-output mixer, even though all its output ports are closed. The
data still flows into the mixer; the playback isn’t blocked. The user just
won’t hear anything. As soon as the user opens an output port, the sound
will be audible through that port, starting at whatever point in the media
the playback has already reached.
Also, you don’t need to access the ports to learn whether the mixer has
certain ports. To learn whether a mixer is actually an audio-output mixer,
for example, you can invoke getTargetLineInfo to see whether it has
output ports. There’s no reason to access the ports themselves unless you
want to change their settings (such as their open-or-closed state, or the
settings of any controls they might have).
• An applet running with the applet security manager can play, but not
record, audio.
• An application running with no security manager can both play and
record audio.
• An application running with the default security manager can play,
but not record, audio.
32 Java Sound API Programmer’s Guide
In general, applets are run under the scrutiny of a security manager and
aren’t permitted to record sound. Applications, on the other hand, don’t
automatically install a security manager, and are able to record sound.
(However, if the default security manager is invoked explicitly for an
application, the application isn’t permitted to record sound.)
Both applets and applications can record sound even when running with
a security manager if they have been granted explicit permission to do so.
If your program doesn’t have permission to record (or play) sound, an
exception will be thrown when it attempts to open a line. There is nothing
you can do about this in your program, other than to catch the exception
and report the problem to the user, because permissions can’t be changed
through the API. (If they could, they would be pointless, because nothing
would be secure!) Generally, permissions are set in one or more policy
configuration files, which a user or system administrator can edit using a
text editor or the Policy Tool program.
For more information on security and permissions, see “Security
Architecture” and “Policy Permissions” at:
http://java.sun.com/products/jdk/1.3/docs/security
and the specialized trail on security in the Java Tutorial at
http://java.sun.com/docs/books/tutorial/
Chapter 4
Playing Back Audio
• Use a Clip when you have non-real-time sound data that can be
preloaded into memory.
33
34 Java Sound API Programmer’s Guide
For example, you might read a short sound file into a clip. If you want
the sound to play back more than once, a Clip is more convenient
than a SourceDataLine, especially if you want the playback to loop
(cycle repeatedly through all or part of the sound). If you need to start
the playback at an arbitrary position in the sound, the Clip interface
provides a method to do that easily. Finally, playback from a Clip
generally has less latency than buffered playback from a
SourceDataLine. In other words, because the sound is preloaded
into a clip, playback can start immediately instead of having to wait
for the buffer to be filled.
• Use a SourceDataLine for streaming data, such as a long sound file
that won’t all fit in memory at once, or a sound whose data can't be
known in advance of playback.
As an example of the latter case, suppose you’re monitoring sound
input—that is, playing sound back as it’s being captured. If you don’t
have a mixer that can send input audio right back out an output port,
your application program will have to take the captured data and
send it to an audio-output mixer. In this case, a SourceDataLine is
more appropriate than a Clip. Another example of sound that can’t
be known in advance occurs when you synthesize or manipulate the
sound data interactively in response to the user’s input. For example,
imagine a game that gives aural feedback by “morphing” from one
sound to another as the user moves the mouse. The dynamic nature
of the sound transformation requires the application program to
update the sound data continuously during playback, instead of
supplying it all before playback starts.
Using a Clip
You obtain a Clip as described earlier under “Getting a Line of a Desired
Type” in Chapter 3, “Accessing Audio System Resources“: Construct a
DataLine.Info object with Clip.class for the first argument, and pass
this DataLine.Info as an argument to the getLine method of
AudioSystem or Mixer.
Chapter 4 Playing Back Audio 35
Using a SourceDataLine
Obtaining a SourceDataLine is similar to obtaining a Clip. See “Getting
a Line of a Desired Type” in Chapter 3, “Accessing Audio System
Resources.“
Notice that when you open a SourceDataLine, you don’t associate any
sound data with the line yet, unlike opening a Clip. Instead, you just
specify the format of the audio data you want to play. The system chooses
a default buffer length.
You can also stipulate a certain buffer length in bytes, using this variant:
void open(AudioFormat format, int bufferSize)
The offset into the array is expressed in bytes, as is the array’s length.
The line begins sending data as soon as possible to its mixer. When the
mixer itself delivers the data to its target, the SourceDataLine generates a
START event. (In a typical implementation of the Java Sound API, the
delay between the moment that the source line delivers data to the mixer
and the moment that the mixer delivers the data to its target is
negligible—that is, much less than the time of one sample.) This START
38 Java Sound API Programmer’s Guide
• The method returns as soon as the data has been written to the buffer.
It doesn’t wait until all the data in the buffer has finished playing. (If
it did, you might not have time to write the next buffer without
creating a discontinuity in the audio.)
• It’s all right to try to write more data than the buffer will hold. In this
case, the method blocks (doesn’t return) until all the data you
requested has actually been placed in the buffer. In other words, one
buffer’s worth of your data at a time will be written to the buffer and
played, until the remaining data all fits in the buffer, at which point
the method returns. Whether or not the method blocks, it returns as
soon as the last buffer’s worth of data from this invocation can be
written. Again, this means that your code will in all likelihood regain
control before playback of the last buffer’s worth of data has finished.
• While in many contexts it is fine to write more data than the buffer will
hold, if you want to be certain that the next write issued does not
block, you can limit the number of bytes you write to the number that
DataLine's available method returns.
Here’s an example of iterating through chunks of data that are read from a
stream, writing one chunk at a time to the SourceDataLine for playback:
// read chunks from a stream and write them to a source data
line
line.start();
while (total < totalToRead && !stopped)}
numBytesRead = stream.read(myData, 0, numBytesToRead);
if (numBytesRead == -1) break;
total += numBytesRead;
line.write(myData, 0, numBytesRead);
}
Chapter 4 Playing Back Audio 39
If you don’t want the write method to block, you can first invoke the
available method (inside the loop) to find out how many bytes can be
written without blocking, and then limit the numBytesToRead variable to
this number, before reading from the stream. In the example given,
though, blocking won’t matter much, since the write method is invoked
inside a loop that won’t complete until the last buffer is written in the final
loop iteration. Whether or not you use the blocking technique, you’ll
probably want to invoke this playback loop in a separate thread from the
rest of the application program, so that your program doesn’t appear to
freeze when playing a long sound. On each iteration of the loop, you can
test whether the user has requested playback to stop. Such a request needs
to set the stopped boolean, used in the code above, to true.
Since write returns before all the data has finished playing, how do you
learn when the playback has actually completed? One way is to invoke
the drain method of DataLine after writing the last buffer’s worth of
data. This method blocks until all the data has been played. When control
returns to your program, you can free up the line, if desired, without fear
of prematurely cutting off the playback of any audio samples:
line.write(b, offset, numBytesToWrite);
//this is the final invocation of write
line.drain();
line.stop();
line.close();
line = null;
the isRunning method will return true, even if a STOP event is generated,
and it will begin to return false only once the stop method is invoked.)
It’s important to realize that START and STOP events correspond to
isActive, not to isRunning.
doesn’t ever generate an OPEN event. (See “Selecting Input and Output
Ports” in Chapter 3, “Accessing Audio System Resources.”)
The first parameter specifies a group of specific data lines, and the second
parameter indicates the accuracy with which synchronization must be
maintained. If the second parameter is true, the query is asking whether
the mixer is capable of maintaining sample-accurate precision in
controlling the specified lines at all times; otherwise, precise
synchronization is required only during start and stop operations, not
throughout playback.
Commonly, only one input port can be open at a time, but an audio-input
mixer that mixes audio from multiple ports is also possible. Another
scenario consists of a mixer that has no ports but instead gets its audio
input over a network.
43
44 Java Sound API Programmer’s Guide
Setting Up a TargetDataLine
The process of obtaining a target data line was described in Chapter 3,
“Accessing Audio System Resources,” but we repeat it here for
convenience:
TargetDataLine line;
DataLine.Info info = new DataLine.Info(TargetDataLine.class,
format); // format is an AudioFormat object
if (!AudioSystem.isLineSupported(info)) {
// Handle the error ...
}
// Obtain and open the line.
try {
line = (TargetDataLine) AudioSystem.getLine(info);
line.open(format);
} catch (LineUnavailableException ex) {
// Handle the error ...
}
As shown in this example, once you’ve obtained a target data line, you
reserve it for your application's use by invoking the DataLine method
open, exactly as was described in the case of a source data line in Chapter
Chapter 5 Capturing Audio 45
When choosing a buffer size, keep in mind the tradeoff between the
delays incurred by long buffers, on the one hand, and the risk of
discontinuities in the audio if the buffers are so short that you can’t
retrieve the data fast enough, on the other hand. When capturing audio,
you risk data overflow if you don’t pull data from the filled buffers fast
enough. If overflow occurs, some of the captured data will be discarded,
which will probably cause audible clicks and skips in the sound. This is
the opposite situation from playback, where you risk data underflow,
which can result in gaps in the sound. (See Chapter 4, “Playing Back
Audio,” for more on choosing buffer sizes.)
This method attempts to read length bytes of data into the array b,
starting at the byte position offset in the array. The method returns the
number of bytes actually read.
As with SourceDataLine’s write method, you can request more data
than actually fits in the buffer, because the method blocks until the
requested amount of data has been delivered, even if you request many
buffers’ worth of data.
46 Java Sound API Programmer’s Guide
To avoid having your application hang during recording, you can invoke
the read method within a loop, until you’ve retrieved all the audio input,
as in this example:
// Assume that the TargetDataLine, line, has already
// been obtained and opened.
ByteArrayOutputStream out = new ByteArrayOutputStream();
int numBytesRead;
byte[] data = new byte[line.getBufferSize() / 5];
Notice that in this example, the size of the byte array into which the data
is read is set to be one-fifth the size of the line’s buffer. If you instead make
it as big as the line’s buffer and try to read the entire buffer, you need to be
very exact in your timing, because data will be dumped if the mixer needs
to deliver data to the line while you are reading from it. By using some
fraction of the line’s buffer size, as shown here, your application will be
more successful in sharing access to the line’s buffer with the mixer.
The read method of TargetDataLine takes three arguments: a byte array,
an offset into the array, and the number of bytes of input data that you
would like to read. In this example, the third argument is simply the
length of your byte array. The read method returns the number of bytes
that were actually read into your array.
Chapter 5 Capturing Audio 47
Typically, you read data from the line in a loop, as in this example. Within
the while loop, each chunk of retrieved data is processed in whatever
way is appropriate for the application—here, it’s written to a
ByteArrayOutputStream. Not shown here is the use of a separate thread
to set the boolean stopped, which terminates the loop. This boolean’s
value might be set to true when the user clicks a Stop button, and also
when a listener receives a CLOSE or STOP event from the line. The listener
is necessary for CLOSE events and recommended for STOP events.
Otherwise, if the line gets stopped somehow without stopped being set to
true, the while loop will capture zero bytes on each iteration, running
fast and wasting CPU cycles. A more thorough code example would show
the loop being re-entered if capture becomes active again.
As with a source data line, it’s possible to drain or flush a target data line.
For example, if you’re recording the input to a file, you’ll probably want to
invoke the drain method when the user clicks a Stop button. The drain
method will cause the mixer’s remaining data to get delivered to the
target data line’s buffer. If you don’t drain the data, the captured sound
might seem to be truncated prematurely at the end.
There might be some cases where you instead want to flush the data. In
any case, if you neither flush nor drain the data, it will be left in the mixer.
This means that when capture recommences, there will be some leftover
sound at the beginning of the new recording, which might be undesirable.
It can be useful, then, to flush the target data line before restarting the
capture.
• You can use any processing supported by the mixer or its component
lines, by querying for Control objects and then setting the controls as
the user desires. Typical controls supported by mixers and lines
include gain, pan, and reverberation controls.
• If the kind of processing you need isn’t provided by the mixer or its
lines, your program can operate directly on the audio bytes,
manipulating them as desired.
This chapter discusses the first technique in greater detail, because there is
no special API for the second technique.
49
50 Java Sound API Programmer’s Guide
Introduction to Controls
A mixer can have various sorts of signal-processing controls on some or
all of its lines. For example, a mixer used for audio capture might have an
input port with a gain control, and target data lines with gain and pan
controls. A mixer used for audio playback might have sample-rate
controls on its source data lines. In each case, the controls are all accessed
through methods of the Line interface.
Because the Mixer interface extends Line, the mixer itself can have its
own set of controls. These might serve as master controls affecting all the
mixer’s source or target lines. For example, the mixer might have a master
gain control whose value in decibels is added to the values of individual
gain controls on its target lines.
Others of the mixer's own controls might affect a special line, neither a
source nor a target, that the mixer uses internally for its processing. For
example, a global reverb control might choose the sort of reverberation to
apply to a mixture of the input signals, and this “wet” (reverberated)
signal would get mixed back into the “dry” signal before delivery to the
mixer's target lines.
If the mixer or any of its lines have controls, you might wish to expose the
controls via graphical objects in your program's user interface, so that the
user can adjust the audio characteristics as desired. The controls are not
themselves graphical; they just allow you to retrieve and change their
settings. It’s up to you to decide what sort of graphical representations
(sliders, buttons, etc.), if any, to use in your program.
All controls are implemented as concrete subclasses of the abstract class
Control. Many typical audio-processing controls can be described by
abstract subclasses of Control based on a data type (such as boolean,
enumerated, or float). Boolean controls, for example, represent binary-
state controls, such as on/off controls for mute or reverb. Float controls,
on the other hand, are well suited to represent continuously variable
controls, such as pan, balance, or volume.
Chapter 6 Processing Audio with Controls 51
An implementation of the Java Sound API can provide any or all of these
control types on its mixers and lines. It can also supply additional control
types not defined in the Java Sound API. Such control types could be
implemented via concrete subclasses of any of these four abstract
subclasses, or via additional Control subclasses that don’t inherit from
any of these four abstract subclasses. An application program can query
each line to find what controls it supports.
Chapter 6 Processing Audio with Controls 53
For example, suppose your program plays back sound. You’re using a
SourceDataLine, which you’ve obtained as described under “Getting a
Line of a Desired Type” in Chapter 3, “Accessing Audio System
Resources.” You can access the line’s controls by invoking the Line
method:
Control[] getControls()
Then, for each of the controls in this returned array, you then use the
following Control method to get the control’s type:
Control.Type getType()
Detecting that the user moved the slider, the program gets the slider's
current value and passes it, as the parameter newValue, to the method
above. This changes the volume of the signal flowing though the line that
“owns” the control.
For example, if a program only wants a single reverb setting that sounds
like a cavern, it can iterate over the ReverbType objects until it finds one
for which getDecayTime returns a value greater than 2000. For a thorough
explanation of these methods, including a table of representative return
values, see the API reference documentation for
javax.sound.sampled.ReverbType.
Most application programs that deal with sound need to read sound files
or audio streams. This is common functionality, regardless of what the
program may subsequently do with the data it reads (such as play, mix, or
process it). Similarly, many programs need to write sound files (or
streams). In some cases, the data that has been read (or that will be
written) needs to be converted to a different format.
As was briefly mentioned in Chapter 3, “Accessing Audio System
Resources,” the Java™ Sound API provides application developers with
various facilities for file input/output and format translations.
Application programs can read, write, and translate between a variety of
sound file formats and audio data formats.
Chapter 2, “Overview of the Sampled Package,” introduced the main
classes related to sound files and audio data formats. As a review:
59
60 Java Sound API Programmer’s Guide
• Information about the format of the audio data stored in the sound file
• A stream of formatted audio data that can be read from the sound file
Chapter 7 Using Files and Format Converters 61
These methods give you an object (an AudioInputStream) that lets you
read the file’s audio data, using one of the read methods of
AudioInputStream. We’ll see an example momentarily.
3. Repeatedly read bytes from the audio input stream into the array. On
each iteration, do something useful with the bytes in the array (for
example, you might play them, filter them, analyze them, display
them, or write them to another file).
Let’s take a look at what’s happening in the above code sample. First, the
outer try clause instantiates an AudioInputStream object through the call
to the AudioSystem.getAudioInputStream(File) method. This method
Chapter 7 Using Files and Format Converters 63
Note that the second argument must be one of the file types supported by
the system (for example, AU, AIFF, or WAV), otherwise the write method
will throw an IllegalArgumentException. To avoid this, you can test
whether or not a particular AudioInputStream may be written to a
particular type of file, by invoking this AudioSystem method:
static boolean isFileTypeSupported
(AudioFileFormat.Type fileType, AudioInputStream stream)
The first of these returns all the types of file that the system can write, and
the second returns only those that the system can write from the given
audio input stream.
The following excerpt demonstrates one technique for creating an output
file from an AudioInputStream using the write method mentioned
above.
File fileOut = new File(someNewPathName);
AudioFileFormat.Type fileType = fileFormat.getType();
if (AudioSystem.isFileTypeSupported(fileType,
audioInputStream)) {
AudioSystem.write(audioInputStream, fileType, fileOut);
}
The first statement above, creates a new File object, fileOut, with a user-
or program-specified pathname. The second statement gets a file type
from a pre-existing AudioFileFormat object called fileFormat, which
might have been obtained from another sound file, such as the one that
was read in the “Reading Sound Files” section of this chapter. (You could
instead supply whatever supported file type you want, instead of getting
Chapter 7 Using Files and Format Converters 65
the file type from elsewhere. For example, you might delete the second
statement and replace the other two occurrences of fileType in the code
above with AudioFileFormat.Type.WAVE.)
The third statement tests whether a file of the designated type can be
written from a desired AudioInputStream. Like the file format, this
stream might have been derived from the sound file previously read. (If
so, presumably you’ve processed or altered its data in some way, because
otherwise there are easier ways to simply copy a file.) Or perhaps the
stream contains bytes that have been freshly captured from the
microphone input.
Finally, the stream, file type, and output file are passed to the
AudioSystem.write method, to accomplish the goal of writing the file.
problem. On the other hand, if the input sound file is an already an AIFF
file, the program notifies the user that there is no need to convert it.
The following function implements the logic just described:
public void ConvertFileToAIFF(String inputPath,
String outputPath) {
AudioFileFormat inFileFormat;
File inFile;
File outFile;
try {
inFile = new File(inputPath);
outFile = new File(outputPath);
} catch (NullPointerException ex) {
System.out.println(“Error: one of the
ConvertFileToAIFF" +" parameters is null!”);
return;
}
try {
// query file type
inFileFormat = AudioSystem.getAudioFileFormat(inFile);
if (inFileFormat.getType() != AudioFileFormat.Type.AIFF)
{
// inFile is not AIFF, so let's try to convert it.
AudioInputStream inFileAIS =
AudioSystem.getAudioInputStream(inFile);
inFileAIS.reset(); // rewind
if (AudioSystem.isFileTypeSupported(
AudioFileFormat.Type.AIFF, inFileAIS)) {
// inFileAIS can be converted to AIFF.
// so write the AudioInputStream to the
// output file.
AudioSystem.write(inFileAIS,
AudioFileFormat.Type.AIFF, outFile);
System.out.println(“Successfully made AIFF file, “
+ outFile.getPath() + “, from “
+ inFileFormat.getType() + " file, " +
inFile.getPath() + ".");
inFileAIS.close();
return; // All done now
} else
System.out.println("Warning: AIFF conversion of "
Chapter 7 Using Files and Format Converters 67
+ inFile.getPath()
+ " is not currently supported by AudioSystem.");
} else
System.out.println("Input file " + inFile.getPath() +
" is AIFF." + " Conversion is unnecessary.");
} catch (UnsupportedAudioFileException e) {
System.out.println("Error: " + inFile.getPath()
+ " is not a supported audio file type!");
return;
} catch (IOException e) {
System.out.println("Error: failure attempting to read "
+ inFile.getPath() + "!");
return;
}
}
which also constructs an AudioFormat, but lets you specify the encoding,
frame size, and frame rate, in addition to the other parameters.
Now, armed with the methods above, let’s see how we might extend our
ConvertFileToAIFF function to perform the desired “low-res” audio
data format conversion. First, we would construct an AudioFormat object
describing the desired output audio data format. The following statement
would suffice and could be inserted near the top of the function:
AudioFormat outDataFormat = new AudioFormat((float) 8000.0,
(int) 8, (int) 1, true, false);
inFileAIS.getFormat())) {
lowResAIS = AudioSystem.getAudioInputStream
(outDataFormat, inFileAIS);
}
It wouldn't matter too much where we inserted this code, as long as it was
after the construction of inFileAIS. Without the
isConversionSupported test, the call would fail and throw an
IllegalArgumentException if the particular conversion being requested
was unsupported. (In this case, control would transfer to the appropriate
catch clause in our function.)
75
76 Java Sound API Programmer’s Guide
• MIDI 1.0
• Standard MIDI Files
We’ll explain what’s meant by streaming and sequencing by examining
the purpose of each of these two parts of the MIDI specification.
musician to control more than one synthesizer, back in the days before
many musicians used computers. (The first version of the specification
was released in 1984.)
MIDI Messages
MidiMessage is an abstract class that represents a “raw” MIDI message. A
“raw” MIDI message is usually a message defined by the MIDI wire
protocol. It can also be one of the events defined by the Standard MIDI
Files specification, but without the event’s timing information. There are
Chapter 8 Overview of the MIDI Package 79
three categories of raw MIDI message, represented in the Java Sound API
by these three respective MidiMessage subclasses:
• ShortMessages are the most common messages and have at most two
data bytes following the status byte. The channel messages, such as
Note On and Note Off, are all short messages, as are some other
messages.
• SysexMessages contain system-exclusive MIDI messages. They may
have many bytes, and generally contain manufacturer-specific
instructions.
• MetaMessages occur in MIDI files, but not in MIDI wire protocol.
Meta messages contain data, such as lyrics or tempo settings, that
might be useful to sequencers but that are usually meaningless for
synthesizers.
MIDI Events
As we’ve seen, standard MIDI files contain events that are wrappers for
“raw” MIDI messages along with timing information. An instance of the
Java Sound API’s MidiEvent class represents an event such as might be
stored in a standard MIDI file.
The API for MidiEvent includes methods to set and get the event’s timing
value. There’s also a method to retrieve its embedded raw MIDI message,
which is an instance of a subclass of MidiMessage, discussed next. (The
embedded raw MIDI message can be set only when constructing the
MidiEvent.)
The MidiDevice interface includes API for opening and closing a device.
It also includes an inner class called MidiDevice.Info that provides
Chapter 8 Overview of the MIDI Package 81
Sequencers
A sequencer is a device for capturing and playing back sequences of MIDI
events. It has transmitters, because it typically sends the MIDI messages
stored in the sequence to another device, such as a synthesizer or MIDI
output port. It also has receivers, because it can capture MIDI messages
and store them in a sequence. To its superinterface, MidiDevice,
Sequencer adds methods for basic MIDI sequencing operations. A
sequencer can load a sequence from a MIDI file, query and set the
sequence’s tempo, and synchronize other devices to it. An application
program can register an object to be notified when the sequencer
processes certain kinds of events.
Synthesizers
A Synthesizer is a device for generating sound. It’s the only object in the
javax.sound.midi package that produces audio data. A synthesizer
device controls a set of MIDI channel objects—typically 16 of them,
because the MIDI specification calls for 16 MIDI channels. These MIDI
channel objects are instances of a class that implements the MidiChannel
82 Java Sound API Programmer’s Guide
The Java™ Sound API offers a flexible model for MIDI system
configuration, just as it does for configuration of the sampled-audio
system. An implementation of the Java Sound API can itself provide
different sorts of MIDI devices, and additional ones can be supplied by
service providers and installed by users. You can write your program in
such a way that it makes few assumptions about which specific MIDI
devices are installed on the computer. Instead, the program can take
advantage of the MIDI system’s defaults, or it can allow the user to select
from whatever devices happen to be available.
This section shows how your program can learn what MIDI resources
have been installed, and how to get access to the desired ones. After
you’ve accessed and opened the devices, you can connect them to each
other, as discussed in the next chapter, “Transmitting and Receiving MIDI
Messages.”
83
84 Java Sound API Programmer’s Guide
You can query the MidiSystem to learn what sorts of devices are installed,
and then you can iterate over the available devices and obtain access to
the desired ones. For example, an application program might start out by
asking the MidiSystem what synthesizers are available, and then display
a list of them, from which the user can select one. A simpler application
program might just use the system’s default synthesizer.
The MidiSystem class also provides methods for translating between
MIDI files and Sequences. It can report the file format of a MIDI file and
can write files of different types.
An application program can obtain the following resources from the
MidiSystem:
• Sequencers
• Synthesizers
• Transmitters (such as those associated with MIDI input ports)
• Receivers (such as those associated with MIDI output ports)
• Data from standard MIDI files
• Data from soundbank files
This chapter focuses on the first four of these types of resource. The
MidiSystem class’s file-handling facilities are discussed in Chapter 11,
“Playing, Recording, and Editing MIDI Sequences,“ and Chapter 12,
“Synthesizing Sound.” To understand how the MIDI system itself gets
access to all these resources, see Part III of this guide, “Service Provider
Interfaces.”
The first two of these methods obtain the system’s default sequencing and
synthesis resources, which either represent physical devices or are
implemented wholly in software. The getReceiver method obtains a
Receiver object that takes MIDI messages sent to it and relays them to the
default receiving device. Similarly, the getTransmitter method obtains
a Transmitter object that can send MIDI messages to some receiver on
behalf of the default transmitting device.
• Name
• Version number
• Vendor (the company that created the device)
• A description of the device
You can display these strings in your user interface to let the user select
from the list of devices.
However, to use the strings programmatically to select a device (as
opposed to displaying the strings to the user), you need to know in
advance what they might be. The company that provides each device
should include this information in its documentation. An application
program that requires or prefers a particular device can use this
information to locate that device. This approach has the drawback of
limiting the program to device implementations that it knows about in
advance.
Another, more general, approach is to go ahead and iterate over the
MidiDevice.Info objects, obtaining each corresponding device, and
determining programmatically whether it’s suitable to use (or at least
suitable to include in a list from which the user can choose). The next
section describes how to do this.
You can use this method if you’ve already found the info object describing
the device you need. However, if you can’t interpret the info objects
returned by getMidiDeviceInfo to determine which device you need,
and if you don’t want to display information about all the devices to the
user, you might be able to do the following instead: Iterate over all the
MidiDevice.Info objects returned by getMidiDeviceInfo, get the
corresponding devices using the method above, and test each device to
see whether it’s suitable. In other words, you can query each device for its
Chapter 9 Accessing MIDI System Resources 87
class and its capabilities before including it in the list that you display to
the user, or as a way to decide upon a device programmatically without
involving the user. For example, if your program needs a synthesizer, you
can obtain each of the installed devices, see which are instances of classes
that implement the Synthesizer interface, and then display them in a list
from which the user can choose one, as follows:
// Obtain information about all the installed synthesizers.
Vector synthInfos;
MidiDevice device;
MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
for (int i = 0; i < infos.length; i++) {
try {
device = MidiSystem.getMidiDevice(info);
} catch (MidiUnavailableException e) {
// Handle or throw exception...
}
if (device instanceof Synthesizer) {
synthInfos.add(info);
}
}
// Now, display strings from synthInfos list in GUI.
Opening Devices
The previous section showed how to get an installed device. However, a
device might be installed but unavailable. For example, another
application program might have exclusive use of it. To actually reserve a
device for your program, you need to use the MidiDevice method open:
88 Java Sound API Programmer’s Guide
if (!(device.isOpen())) {
try {
device.open();
} catch (MidiUnavailableException e) {
// Handle or throw exception...
}
}
89
90 Java Sound API Programmer’s Guide
Once you have a message ready to send, you can send it to a Receiver
object, using this Receiver method:
void send(MidiMessage message, long timeStamp)
latter part of the specification, each event stored in a standard MIDI file is
tagged with a timing value that indicates when that event should be
played. By contrast, messages in MIDI wire protocol are always supposed
to be processed immediately, as soon as they’re received by a device, so
they have no accompanying timing values.
The Java Sound API adds an additional twist. It comes as no surprise that
timing values are present in the MidiEvent objects that are stored in
sequences (as might be read from a MIDI file), just as in the Standard
MIDI Files specification. But in the Java Sound API, even the messages
sent between devices—in other words, the messages that correspond to
MIDI wire protocol—can be given timing values, known as time stamps. It
is these time stamps that concern us here. (The timing values in
MidiEvent objects are discussed in detail in Chapter 11, “Playing,
Recording, and Editing MIDI Sequences.”)
whose time stamp is very far in the future and have the device handle it as
you intended, and you certainly can’t expect a device to correctly
schedule a message whose time stamp is in the past! It’s up to the device
to decide how to handle time stamps that are too far off in the future or
the past. The sender doesn’t know what the device considers to be too far
off, or whether the device had any problem with the time stamp. This
ignorance mimics the behavior of external MIDI hardware devices, which
send messages without ever knowing whether they were received
correctly. (MIDI wire protocol is unidirectional.)
Some devices send time-stamped messages (via a Transmitter). For
example, the messages sent by a MIDI input port might be stamped with
the time the incoming message arrived at the port. On some systems, the
event-handling mechanisms cause a certain amount of timing precision to
be lost during subsequent processing of the message. The message’s time
stamp allows the original timing information to be preserved.
To learn whether a device supports time stamps, invoke the following
method of MidiDevice:
long getMicrosecondPosition()
Closing Connections
Once you’re done with a connection, you can free up its resources by
invoking the close method for each transmitter and receiver that you’ve
obtained. The Transmitter and Receiver interfaces each have a close
method. Note that invoking Transmitter.setReceiver doesn’t close
the transmitter’s current receiver. The receiver is left open, and it can still
receive messages from any other transmitter that’s connected to it.
If you’re also done with the devices, you can similarly make them
available to other application programs by invoking
MidiDevice.close(). Closing a device automatically closes all its
transmitters and receivers.
Chapter 11
Playing, Recording, and
Editing MIDI Sequences
Introduction to Sequencers
To develop an intuitive understanding of what a Sequencer is, think of it
by analogy with a tape recorder, which a sequencer resembles in many
respects. Whereas a tape recorder plays audio, a sequencer plays MIDI
data. A sequence is a multi-track, linear, time-ordered recording of MIDI
musical data, which a sequencer can play at various speeds, rewind,
shuttle to particular points, record into, or copy to a file for storage.
Chapter 10, “Transmitting and Receiving MIDI Messages,” explained that
devices typically have Receiver objects, Transmitter objects, or both. To
play music, a device generally receives MidiMessages through a
Receiver, which in turn has usually received them from a Transmitter
that belongs to a Sequencer. The device that owns this Receiver might be
97
98 Java Sound API Programmer’s Guide
frame if the division type is one of the SMPTE conventions. You can get
the size of a tick using this formula in the case of PPQ:
ticksPerSecond =
resolution * (currentTempoInBeatsPerMinute / 60.0);
tickSize = 1.0 / ticksPerSecond;
Obtaining a Sequencer
An application program doesn’t instantiate a Sequencer; after all,
Sequencer is just an interface. Instead, like all devices in the Java Sound
API’s MIDI package, a Sequencer is accessed through the static
104 Java Sound API Programmer’s Guide
The following code fragment obtains the default Sequencer, acquires any
system resources it needs, and makes it operational:
Sequencer sequencer;
// Get default sequencer.
sequencer = MidiSystem.getSequencer();
if (sequencer == null) {
// Error -- sequencer device is not supported.
// Inform user and return...
} else {
// Acquire resources and make operational.
sequencer.open();
}
The invocation of open reserves the sequencer device for your program’s
use. It doesn’t make much sense to imagine sharing a sequencer, because
it can play only one sequence at a time. When you’re done using the
sequencer, you can make it available to other programs by invoking
close.
Loading a Sequence
Having obtained a sequencer from the system and reserved it, you then
need load the data that the sequencer should play. There are three typical
ways of accomplishing this:
We’ll now look at the first of these ways of getting sequence data. (The
other two ways are described later under “Recording and Saving
Sequences” and “Editing a Sequence,” respectively.) This first way
actually encompasses two slightly different approaches. One approach is
to feed MIDI file data to an InputStream that you then read directly to the
sequencer by means of Sequencer.setSequence(InputStream). With
this approach, you don’t explicitly create a Sequence object. In fact, the
Sequencer implementation might not even create a Sequence behind the
scenes, because some sequencers have a built-in mechanism for handling
data directly from a file.
The other approach is to create a Sequence explicitly. You’ll need to use
this approach if you’re going to edit the sequence data before playing it.
With this approach, you invoke MidiSystem’s overloaded method
getSequence. The method is able to get the sequence from an
InputStream, a File, or a URL. The method returns a Sequence object
that can be then be loaded into a Sequencer for playback. Expanding on
the previous code excerpt, here’s an example of obtaining a Sequence
object from a File and loading it into our sequencer:
try {
File myMidiFile = new File("seq1.mid");
// Construct a Sequence object, and
// load it into my sequencer.
Sequence mySeq = MidiSystem.getSequence(myMidiFile);
sequencer.setSequence(mySeq);
} catch (Exception e) {
// Handle error and/or return
}
Playing a Sequence
Starting and stopping a Sequencer is accomplished using the following
methods:
void start()
and
void stop()
Editing a Sequence
Many application programs allow a sequence to be created by loading it
from a file, and quite a few also allow a sequence to be created by
capturing it from live MIDI input (that is, recording). Some programs,
however, will need to create MIDI sequences from scratch, whether
programmatically or in response to user input. Full-featured sequencer
programs permit the user to manually construct new sequences, as well as
to edit existing ones.
These data-editing operations are achieved in the Java Sound API not by
Sequencer methods, but by methods of the data objects themselves:
Sequence, Track, and MidiEvent. You can create an empty sequence
using one of the Sequence constructors, and then add tracks to it by
invoking the following Sequence method:
Track createTrack()
If your program allows the user to edit sequences, you’ll need this
Sequence method to remove tracks:
boolean deleteTrack(Track track)
Once the sequence contains tracks, you can modify the contents of the
tracks by invoking methods of the Track class. The MidiEvents
contained in the Track are stored as a java.util.Vector in the Track
object, and Track provides a set of methods for accessing, adding, and
removing the events in the list. The methods add and remove are fairly
self-explanatory, adding or removing a specified MidiEvent from a
Track. A get method is provided, which takes an index into the Track’s
event list and returns the MidiEvent stored there. In addition, there are
size and tick methods, which respectively return the number of
MidiEvents in the track, and the track’s duration, expressed as a total
number of Ticks.
To create a new event before adding it to the track, you’ll of course use the
MidiEvent constructor. To specify or modify the MIDI message
Chapter 11 Playing, Recording, and Editing MIDI Sequences 109
embedded in the event, you can invoke the setMessage method of the
appropriate MidiMessage subclass (ShortMessage, SysexMessage, or
MetaMessage). To modify the time that the event should occur, invoke
MidiEvent.setTick.
In combination, these low-level methods provide the basis for the editing
functionality needed by a full-featured sequencer program.
returns the position measured in MIDI ticks from the beginning of the
sequence. The second method:
long getMicrosecondPosition()
or
void setMicrosecondPosition(long microsecond)
The first two of these methods set the tempo in beats per minute or
microseconds per quarter note, respectively. The tempo will stay at the
specified value until one of these methods is invoked again, or until a
tempo-change event is encountered in the sequence, at which point the
current tempo is overridden by the newly specified one.
The third method, setTempoFactor, is different in nature. It scales
whatever tempo is set for the sequencer (whether by tempo-change events
or by one of the first two methods above). The default scalar is 1.0 (no
change). Although this method causes the playback or recording to be
faster or slower than the nominal tempo (unless the factor is 1.0), it
doesn’t alter the nominal tempo. In other words, the tempo values
returned by getTempoInBPM and getTempoInMPQ are unaffected by the
tempo factor, even though the tempo factor does affect the actual rate of
playback or recording. Also, if the tempo is changed by a tempo-change
event or by one of the first two methods, it still gets scaled by whatever
tempo factor was last set. If you load a new sequence, however, the tempo
factor is reset to 1.0.
Note that all these tempo-change directives are ineffectual when the
sequence’s division type is one of the SMPTE types, instead of PPQ.
are activated will sound. This feature lets the user quickly audition a small
number of tracks without having to mute all the other tracks. The mute
button typically takes priority over the solo button: if both are activated,
the track doesn’t sound.
Using Sequencer methods, muting or soloing tracks (as well as querying
a track’s current mute or solo state) is easily accomplished. Let’s assume
we have obtained the default Sequencer and that we’ve loaded sequence
data into it. Muting the fifth track in the sequence would be accomplished
as follows:
sequencer.setTrackMute(4, true);
boolean muted = sequencer.getTrackMute(4);
if (!muted) {
return;// muting failed
}
There are a couple of things to note about the above code snippet. First,
tracks of a sequence are numbered starting with 0 and ending with the
total number of tracks minus 1. Also, the second argument to
setTrackMute is a boolean. If it’s true, the request is to mute the track;
otherwise the request is to unmute the specified track. Lastly, in order to
test that the muting took effect, we invoke the Sequencer getTrackMute
method, passing it the track number we’re querying. If it returns true, as
we’d expect in this case, then the mute request worked. If it returns false,
then it failed.
Mute requests may fail for various reasons. For example, the track
number specified in the setTrackMute call might exceed the total number
of tracks, or the sequencer might not support muting. By calling
getTrackMute, we can determine if our request succeeded or failed.
The method and techniques for soloing a track are very similar to those
for muting. To solo a track, invoke the setTrackSolo method of
Sequence:
void setTrackSolo(int track, boolean bSolo)
control changes, and meta events. The Java Sound API specifies “listener”
interfaces for the last two of these event types (control change events and
meta events). You can use these interfaces to receive notifications when
such events occur during playback of a sequence.
Objects that support the ControllerEventListener interface can receive
notification when a Sequencer processes particular control-change
messages. A control-change message is a standard type of MIDI message
that represents a change in the value of a MIDI controller, such as a pitch-
bend wheel or a data slider. (See the MIDI specification for the complete
list of control-change messages.) When such a message is processed
during playback of a sequence, the message instructs any device
(probably a synthesizer) that’s receiving the data from the sequencer to
update the value of some parameter. The parameter usually controls some
aspect of sound synthesis, such as the pitch of the currently sounding
notes if the controller was the pitch-bend wheel. When a sequence is being
recorded, the control-change message means that a controller on the
external physical device that created the message has been moved, or that
such a move has been simulated in software.
Here’s how the ControllerEventListener interface is used. Let’s
assume that you’ve developed a class that implements the
ControllerEventListener interface, meaning that your class contains
the following method:
void controlChange(ShortMessage msg)
Let’s also assume that you’ve created an instance of your class and
assigned it to a variable called myListener. If you include the following
statements somewhere within your program:
int[] controllersOfInterest = { 1, 2, 4 };
sequencer.addControllerEventListener(myListener,
controllersOfInterest);
then your class’s controlChange method will be invoked every time the
sequencer processes a control-change message for MIDI controller
numbers 1, 2, or 4. In other words, when the Sequencer processes a
request to set the value of any of the registered controllers, the Sequencer
will invoke your class’s controlChange method. (Note that the
assignments of MIDI controller numbers to specific control devices is
detailed in the MIDI 1.0 Specification.)
The controlChange method is passed a ShortMessage containing the
controller number affected, and the new value to which the controller was
114 Java Sound API Programmer’s Guide
Most programs that avail themselves of the Java™ Sound API’s MIDI
package do so to synthesize sound. The entire apparatus of MIDI files,
events, sequences, and sequencers, which was discussed in other chapters,
nearly always has the goal of eventually sending musical data to a
synthesizer to convert into audio. (Possible exceptions include programs
that convert MIDI into musical notation that can be read by a musician,
and programs that send messages to external MIDI-controlled devices
such as mixing consoles.)
The Synthesizer interface is therefore fundamental to the MIDI package.
This chapter shows how to manipulate a synthesizer to play sound. Many
programs will simply use a sequencer to send MIDI file data to the
synthesizer, and won’t need to invoke many Synthesizer methods
directly. However, it’s possible to control a synthesizer directly, without
using sequencers or even MidiMessage objects, as explained near the end
of this chapter.
The synthesis architecture might seem complex for readers who are
unfamiliar with MIDI. Its API includes three interfaces:
• Synthesizer
• MidiChannel
• Soundbank
115
116 Java Sound API Programmer’s Guide
• SoundbankResource
• VoiceStatus
As orientation for all this API, the next section explains some of the basics
of MIDI synthesis and how they’re reflected in the Java Sound API. (Also
see the brief section “Synthesizers” under “The Java Sound API’s
Representation of MIDI Devices” in Chapter 8, “Overview of the MIDI
Package.”) Subsequent sections give a more detailed look at the API.
Instruments
What all synthesis techniques have in common is the ability to create
many sorts of sounds. Different algorithms, or different settings of
parameters within the same algorithm, create different-sounding results.
An instrument is a specification for synthesizing a certain type of sound.
That sound may emulate a traditional musical instrument (such as a piano
or violin) or some other kind of sound source (for example, a telephone or
helicopter)—or it may emulate no “real-world” sound at all. A
specification called General MIDI defines a standard list of 128
instruments, but most synthesizers allow other instruments as well.
Many synthesizers provide a collection of built-in instruments that are
always available for use; some synthesizers also support mechanisms for
loading additional instruments
Chapter 12 Synthesizing Sound 117
Channels
Many synthesizers are multimbral (sometimes called polytimbral), meaning
that they can play the notes of different instruments simultaneously.
(Timbre is the characteristic sound quality that enables a listener to
distinguish one kind of musical instrument from other kinds.) Multimbral
synthesizers can emulate an entire ensemble of real-world instruments,
instead of only one instrument at a time. MIDI synthesizers normally
implement this feature by taking advantage of the different MIDI
channels on which the MIDI specification allows data to be transmitted. In
this case, the synthesizer is actually a collection of sound-generating units,
each emulating a different instrument and responding independently to
messages that are received on a different MIDI channel. Since the MIDI
specification provides only 16 channels, a typical MIDI synthesizer can
play up to 16 different instruments at once. The synthesizer receives a
stream of MIDI commands, many of which are channel commands.
(Channel commands are targeted to a particular MIDI channel; for more
information, see the MIDI specification.) If the synthesizer is multitimbral,
it routes each channel command to the correct sound-generating unit,
according to the channel number indicated in the command.
In the Java Sound API, these sound-generating units are instances of
classes that implement the MidiChannel interface. A synthesizer object
has at least one MidiChannel object. If the synthesizer is multimbral, it
has more than one, normally 16. Each MidiChannel represents an
independent sound-generating unit.
118 Java Sound API Programmer’s Guide
Voices
It’s important to distinguish between the number of timbres a synthesizer
can play simultaneously and the number of notes it can play
simultaneously. The former was described above under “Channels.”The
ability to play multiple notes at once is referred to as polyphony. Even a
synthesizer that isn’t multitimbral can generally play more than one note
at a time (all having the same timbre, but different pitches). For example,
playing any chord, such as a G major triad or a B minor seventh chord,
requires polyphony. Any synthesizer that generates sound in real time has
a limitation on the number of notes it can synthesize at once. In the Java
Sound API, the synthesizer reports this limitation through the
getMaxPolyphony method.
With this background, let’s examine the specifics of the Java Sound API
for synthesis.
and iterate over the returned array to see exactly which instruments are
currently loaded. Most likely, you would display the instruments’ names
in the user interface (using the getName method of Instrument), and let
the user decide whether to use those instruments or load others. The
Instrument API includes a method that reports which soundbank the
instrument belongs to. The soundbank’s name might help your program
or the user ascertain exactly what the instrument is.
This Synthesizer method:
Soundbank getDefaultSoundbank()
gives you the default soundbank. The Soundbank API includes methods
to retrieve the soundbank’s name, vendor, and version number, by which
the program or the user can verify the bank’s identity. However, you can’t
assume when you first get a synthesizer that the instruments from the
default soundbank have been loaded into the synthesizer. For example, a
synthesizer might have a large assortment of built-in instruments
available for use, but because of its limited memory it might not load
them automatically.
The instrument gets loaded into the synthesizer in the location specified
by the instrument’s Patch object (which can be retrieved using the
getPatch method of Instrument).
can then invoke one of these methods to load instruments from the
soundbank:
boolean loadAllInstruments(Soundbank soundbank)
boolean loadInstruments(Soundbank soundbank,
Patch[] patchList)
As the names suggest, the first of these loads the entire set of instruments
from a given soundbank, and the second loads selected instruments from
the soundbank. You could also use Soundbank’s getInstruments
method to access all the instruments, then iterate over them and load
selected instruments one at a time using loadInstrument.
It’s not necessary for all the instruments you load to be from the same
soundbank. You could use loadInstrument or loadInstruments to load
certain instruments from one soundbank, another set from a different
soundbank, and so on.
Each instrument has its own Patch object that specifies the location on the
synthesizer where the instrument should be loaded. The location is
defined by a bank number and a program number. There’s no API to
change the location by changing the patch’s bank or program number.
However, it is possible to load an instrument into a location other than the
one specified by its patch, using the following method of Synthesizer:
boolean remapInstrument(Instrument from, Instrument to)
This method unloads its first argument from the synthesizer, and places
its second argument in whatever synthesizer patch location had been
occupied by the first argument.
Unloading Instruments
Loading an instrument into a program location automatically unloads
whatever instrument was already at that location, if any. You can also
explicitly unload instruments without necessarily replacing them with
new ones. Synthesizer includes three unloading methods that
correspond to the three loading methods. If the synthesizer receives a
program-change message that selects a program location where no
instrument is currently loaded, there won’t be any sound from the MIDI
channel on which the program-change message was sent.
Chapter 12 Synthesizing Sound 123
The latency measures the worst-case delay between the time a MIDI
message is delivered to the synthesizer and the time that the synthesizer
actually produces the corresponding result. For example, it might take a
synthesizer a few milliseconds to begin generating audio after receiving a
note-on event.
The getMaxPolyphony method indicates how many notes the synthesizer
can sound simultaneously, as discussed under “Voices” in the section
“Understanding MIDI Synthesis” earlier in this chapter. As mentioned in
the same discussion, a synthesizer can provide information about its
voices. This is accomplished through the following method:
public VoiceStatus[] getVoiceStatus()
Each VoiceStatus in the returned array reports the voice’s current active
or inactive status, MIDI channel, bank and program number, MIDI note
number, and MIDI volume. The array’s length should normally be the
same number returned by getMaxPolyphony. If the synthesizer isn’t
playing, all its VoiceStatus objects have their active field set to false.
124 Java Sound API Programmer’s Guide
Using Channels
Sometimes it’s useful or necessary to access a synthesizer’s MidiChannel
objects directly. This section discusses such situations.
The second way is to bypass the message-passing layer (that is, the
MidiMessage and Receiver API) altogether, and interact with the
synthesizer’s MidiChannel objects directly. You first need to retrieve the
synthesizer’s MidiChannel objects, using the following Synthesizer
method:
public MidiChannel[] getChannels()
after which you can invoke the desired MidiChannel methods directly,.
This is a more immediate route than sending the corresponding
MidiMessages to the synthesizer’s Receiver and letting the synthesizer
handle the communication with its own MidiChannels. For example, the
code corresponding to the preceding example would be:
Synthesizer synth = MidiSystem.getSynthesizer();
MidiChannel chan[] = synth.getChannels();
// Check for null; maybe not all 16 channels exist.
if (chan[4] != null) {
chan[4].noteOn(60, 93);
}
These methods might be useful for displaying channel state to the user, or
for deciding what values to send subsequently to the channel.
• Readers for various types of soundbank files (which are often specific
to particular synthesizers)
• MIDI-controlled sound synthesizers, sequencers, and I/O ports,
whether implemented purely in software, or in hardware with a
software interface
Behind the scenes, the AudioSystem determines what installed service can
read the file, and asks it to supply the audio data as an
AudioInputStream object. The developer might not know or even care
that the input audio file is in some new file format (such as Acme’s),
supported by installed third-party services. The program’s first contact
with the stream is through the AudioSystem object, and all its subsequent
access to the stream and its properties are through the methods of
AudioInputStream. Both of these are standard objects in the
132 Java Sound API Programmer’s Guide
javax.sound.sampled API; the special handling that the new file format
may require is completely hidden.
In addition, for each new SPI class being subclassed, we create a mapping
file in a specially named directory META-INF/Services. The name of the
Chapter 13 Introduction to the Service Provider Interfaces 133
file is the name of the SPI class being subclassed, and the file contains the
names of the new subclasses of that SPI abstract class.
We create the file
META-INF/Services/javax.sound.sampled.spi.AudioFileReader,
which consists of:
# Providers of sound file-reading services
# (a comment line begins with a pound sign)
com.acme.AcmeAudioFileReader
which contains:
# Providers of sound file-writing services
com.acme.AcmeAudioFileWriter
Now, we run jar from any directory with the command line:
jar cvf acme.jar -C /devel .
The file Manifest.mf, which is generated by the jar utility itself, is a list
of all the files contained in the archive.
It’s not an error to install more than one provider for the same service. For
example, two different service providers might supply support for
reading the same type of sound file. In such a case, the system arbitrarily
chooses one of the providers. Users who care which provider is chosen
should install only the desired one.
Chapter 14
Providing Sampled-Audio
Services
Introduction
There are four abstract classes in the javax.sound.sampled.spi package,
representing four different types of services that you can provide for the
sampled-audio system:
135
136 Java Sound API Programmer’s Guide
The first of these methods informs the caller whether this file writer can
write sound files of the specified type. This method is a general inquiry, it
will return true if the file writer can write that kind of file, assuming the
file writer is handed appropriate audio data. However, the ability to write
a file can depend on the format of the specific audio data that’s handed to
the file writer. A file writer might not support every audio data format, or
the constraint might be imposed by the file format itself. (Not all kinds of
audio data can be written to all kinds of sound files.) The second method
is more specific, then, asking whether a particular AudioInputStream can
be written to a particular type of file.
Generally, you won’t need to override these two concrete methods. Each is
simply a wrapper that invokes one of two other query methods and
iterates over the results returned. These other two query methods are
abstract and therefore need to be implemented in the subclass:
abstract AudioFileFormat.Type[] getAudioFileTypes()
abstract AudioFileFormat.Type[]
getAudioFileTypes(AudioInputStream stream)
These methods write a stream of bytes representing the audio data to the
stream or file specified by the third argument. The details of how this is
done depend on the structure of the specified type of file. The write
method must write the file’s header and the audio data in the manner
prescribed for sound files of this format (whether it’s a standard type of
sound file or a new, possibly proprietary one).
The actual format conversion takes place in new read methods of the
returned AcmeCodecStream, a subclass of AudioInputStream. Again,
application programs that access this returned AcmeCodecStream simply
operate on it as an AudioInputStream, and don’t need to know the
details of its implementation.
Chapter 14 Providing Sampled-Audio Services 141
Introduction
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:
143
144 Java Sound API Programmer’s Guide
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
Chapter 15 Providing MIDI Services 145
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.
The write methods of a MidiFileWriter subclass perform the encoding
of the data in a given Sequence into the correct data format for the
requested type of MIDI file, writing the coded stream to either a file or an
output stream:
abstract int write(Sequence in, int fileType,
java.io.File out)
abstract int write(Sequence in, int fileType,
java.io.OutputStream out)
To do this, the write method must parse the Sequence 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 the MidiSystem object to see what MIDI file formats, if
any, are supported for the particular Sequence at hand, before attempting
to write the file. The MidiSystem.getMidiFileTypes(Sequence)
method returns an array of all the MIDI file types to which the system can
write a particular sequence. A typical implementation does this by
invoking the corresponding getMidiFileTypes method for each of the
installed MidiFileWriter services, and collecting and returning the
results in an array of integers that can be thought of as a master list of all
146 Java Sound API Programmer’s Guide
file types compatible with the given Sequence. When it comes to writing
the Sequence to a file, the call to MidiSystem.write is passed an integer
representing a file type, along with the Sequence to be written and the
output file; MidiSystem uses the supplied type to decide which installed
MidiFileWriter should handle the write request, and dispatches a
corresponding write to the appropriate MidiFileWriter.
The other overloaded method returns a MIDI Sequence from a given file,
stream, or URL :
abstract Sequence getSequence(java.io.File file)
abstract Sequence getSequence(java.io.InputStream stream)
abstract Sequence getSequence(java.net.URL url)
The getSequence method performs the actual work of parsing the bytes
in the MIDI input file and constructing a corresponding Sequence object.
This is essentially the inverse of the process used by
MidiFileWriter.write. Because there is a one-to-one correspondence
between the contents of a MIDI file as defined by the MIDI specification
and a Sequence object as defined by the Java Sound API, the details of the
parsing are straightforward. If the file passed to getSequence 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), an
InvalidMidiDataException should be thrown.
Chapter 15 Providing MIDI Services 147
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.
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 an
IllegalArgumentException. Otherwise, it returns the device.
151
152 Java Sound API Programmer’s Guide
T
TargetDataLine 23
read method 46
retrieving data from the buffer 45
timbre 117
timeStamp 93
timing values 92
Track 80
Transmitter 90
U
unbuffered handling of audio 12
understanding time stamps 91
V
voice, MIDI, definition 119
volume, changing 55
W
writing sound files 63–65