The
Sequencer
interface provides methods in several categories:
Sequence
object, and to save the currently loaded sequence data to a MIDI file.Sequence
.Sequencer
may play at different tempos, with some Tracks
muted, and in various synchronization states with other objects.Sequencer
processes certain kinds of MIDI events. Regardless of which Sequencer
methods you'll invoke, the first step is to obtain a Sequencer
device from the system and reserve it for your program's use.
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 MidiSystem
object. As previously mentioned in
Accessing MIDI System Resources, the following MidiSystem
method can be used to obtain the default Sequencer
:
static Sequencer getSequencer()
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
.
Non-default sequencers can be obtained as described in Accessing MIDI System Resources.
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:
MidiEvent
objects to those tracks We'll now look at the first of these ways of getting sequence data. (The other two ways are described below 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 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 }
Like MidiSystem's
getSequence
method, setSequence
may throw an InvalidMidiDataException
???and, in the case of the InputStream
variant, an IOException
???if it runs into any trouble.
Starting and stopping a Sequencer
is accomplished using the following methods:
void start()
void stop()
The Sequencer.start
method begins playback of the sequence. Note that playback starts at the current position in a sequence. Loading an existing sequence using the setSequence
method, described above, initializes the sequencer's current position to the very beginning of the sequence. The stop
method stops the sequencer, but it does not automatically rewind the current Sequence
. Starting a stopped Sequence
without resetting the position simply resumes playback of the sequence from the current position. In this case, the stop
method has served as a pause operation. However, there are various Sequencer
methods for setting the current sequence position to an arbitrary value before playback is started. (We'll discuss these methods below.)
As mentioned earlier, a Sequencer
typically has one or more Transmitter
objects, through which it sends MidiMessages
to a Receiver
. It is through these Transmitters
that a Sequencer
plays the Sequence
, by emitting appropriately timed MidiMessages
that correspond to the MidiEvents
contained in the current Sequence
. Therefore, part of the setup procedure for playing back a Sequence
is to invoke the setReceiver
method on the Sequencer's
Transmitter
object, in effect wiring its output to the device that will make use of the played-back data. For more details on Transmitters
and Receivers
, refer back to
Transmitting and Receiving MIDI Messages.
To capture MIDI data to a Sequence
, and subsequently to a file, you need to perform some additional steps beyond those described above. The following outline shows the steps necessary to set up for recording to a Track
in a Sequence
:
MidiSystem.getSequencer
to get a new sequencer to use for recording, as above.setReceiver
method, to send data to a Receiver
associated with the recording Sequencer
.Sequence
object, which will store the recorded data. When you create the Sequence
object, you must specify the global timing information for the sequence. For example:
Sequence mySeq; try{ mySeq = new Sequence(Sequence.PPQ, 10); } catch (Exception ex) { ex.printStackTrace(); }
Sequence
takes as arguments a divisionType
and a timing resolution. The divisionType
argument specifies the units of the resolution argument. In this case, we've specified that the timing resolution of the Sequence
we're creating will be 10 pulses per quarter note. An additional optional argument to the Sequence
constructor is a number of tracks argument, which would cause the initial sequence to begin with the specified number of (initially empty) Tracks
. Otherwise the Sequence
will be created with no initial Tracks
; they can be added later as needed.Track
in the Sequence
, with Sequence.createTrack
. This step is unnecessary if the Sequence
was created with initial Tracks
.Sequencer.setSequence
, select our new Sequence
to receive the recording. The setSequence
method ties together an existing Sequence
with the Sequencer
, which is somewhat analogous to loading a tape onto a tape recorder.Sequencer.recordEnable
for each Track
to be recorded. If necessary, get a reference to the available Tracks
in the Sequence
by invoking Sequence.getTracks
.startRecording
on the Sequencer
.Sequencer.stop
or Sequencer.stopRecording
.Sequence
to a MIDI file with MidiSystem.write
. The write
method of MidiSystem
takes a Sequence
as one of its arguments, and will write that Sequence
to a stream or file.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 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.