[Top] [Prev] [Next] [Bottom]

# Chapter 11

# Playing, Recording, and Editing MIDI Sequences

In the world of MIDI, a sequencer is any hardware or software device that can precisely play or record a sequence of time-stamped MIDI messages. Similarly, in the JavaTM Sound API, the `Sequencer` abstract interface defines the properties of an object that can play and record `Sequences` of `MidiEvent` objects. A `Sequencer` typically loads these `MidiEvent` sequences from a standard MIDI file or saves them to such a file. Sequences can also be edited. This chapter explains how to use `Sequencer` objects, along with related classes and interfaces, to accomplish such tasks.

### 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 a `Synthesizer`, which will generate audio directly, or it might be a MIDI output port, which transmits MIDI data through a physical cable to some external piece of equipment. Similarly, to record music, a series of time-stamped `MidiMessages` are generally sent to a `Receiver` owned by a `Sequencer`, which places them in a `Sequence` object. Typically the object sending the messages is a `Transmitter` associated with a hardware input port, and the port relays MIDI data that it gets from an external instrument. However, the device responsible for sending the messages might instead be some other `Sequencer`, or any other device that has a `Transmitter`. Furthermore, as mentioned in Chapter 10, a program can send messages without using any `Transmitter` at all.

A `Sequencer` itself has both `Receivers` and `Transmitters`. When it's recording, it actually obtains `MidiMessages` via its `Receivers`. During playback, it uses its `Transmitters` to send `MidiMessages` that are stored in the `Sequence` that it has recorded (or loaded from a file).

One way to think of the role of a `Sequencer` in the Java Sound API is as an aggregator and "de-aggregator" of `MidiMessages`. A series of separate `MidiMessages`, each of which is independent, is sent to the `Sequencer` along with its own time stamp that marks the timing of a musical event. These `MidiMessages` are encapsulated in `MidiEvent` objects and collected in `Sequence` objects through the action of the `Sequencer.record` method. A `Sequence` is a data structure containing aggregates of `MidiEvents`, and it usually represents a series of musical notes, often an entire song or composition. On playback, the `Sequencer` again extracts the `MidiMessages` from the `MidiEvent` objects in the `Sequence` and then transmits them to one or more devices that will either render them into sound, save them, modify them, or pass them on to some other device.

Some sequencers might have neither transmitters nor receivers. For example, they might create `MidiEvents` from scratch as a result of keyboard or mouse events, instead of receiving `MidiMessages` through `Receivers`. Similarly, they might play music by communicating directly with an internal synthesizer (which could actually the same object as the sequencer) instead of sending `MidiMessages` to a `Receiver` associated with a separate object. However, the rest of this chapter assumes the normal case of a sequencer that uses `Receivers` and `Transmitters`.

#### When to Use a Sequencer

It's possible for an application program to send MIDI messages directly to a device, without using a sequencer, as was described in Chapter 10, "Transmitting and Receiving MIDI Messages." The program simply invokes the `Receiver.send` method each time it wants to send a message. This is a straightforward approach that's useful when the program itself creates the messages in real time. For example, consider a program that lets the user play notes by clicking on an onscreen piano keyboard. When the program gets a mouse-down event, it immediately sends the appropriate Note On message to the synthesizer.

As mentioned in Chapter 10, the program can include a time stamp with each MIDI message it sends to the device's receiver. However, such time stamps are used only for fine-tuning the timing, to correct for processing latency. The caller can't generally set arbitrary time stamps; the time value passed to `Receiver.send` must be close to the present time, or the receiving device might not be able to schedule the message correctly. This means that if an application program wanted to create a queue of MIDI messages for an entire piece of music ahead of time (instead of creating each message in response to a real-time event), it would have to be very careful to schedule each invocation of `Receiver.send` for nearly the right time.

Fortunately, most application programs don't have to be concerned with such scheduling. Instead of invoking `Receiver.send` itself, a program can use a `Sequencer` object to manage the queue of MIDI messages for it. The sequencer takes care of scheduling and sending the messages-in other words, playing the music with the correct timing. Generally, it's advantageous to use a sequencer whenever you need to convert a non-real-time series of MIDI messages to a real-time series (as in playback), or vice versa (as in recording). Sequencers are most commonly used for playing data from MIDI files and for recording data from a MIDI input port.

### Understanding Sequence Data

Before examining the `Sequencer` API, it helps to understand the kind of data that's stored in a sequence.

#### Sequences and Tracks

In the Java Sound API, sequencers closely follow the Standard MIDI Files specification in the way that they organize recorded MIDI data. As mentioned above, a `Sequence` is an aggregation of `MidiEvents`, organized in time. But there is more structure to a `Sequence` than just a linear series of `MidiEvents`: a `Sequence` actually contains global timing information plus a collection of `Tracks`, and it is the `Tracks` themselves that hold the `MidiEvent` data. So the data played by a sequencer consists of a three-level hierarchy of objects: `Sequencer`, `Track`, and `MidiEvent`.

In the conventional use of these objects, the `Sequence` represents a complete musical composition or section of a composition, with each `Track` corresponding to a voice or player in the ensemble. In this model, all the data on a particular `Track` would also therefore be encoded into a particular MIDI channel reserved for that voice or player.

This way of organizing data is convenient for purposes of editing sequences, but note that this is just a conventional way to use `Tracks`. There is nothing in the definition of the `Track` class per se that keeps it from containing a mix of `MidiEvents` on different MIDI channels. For example, an entire multi-channel MIDI composition can be mixed and recorded onto one `Track`. Also, standard MIDI files of Type 0 (as opposed to Type 1 and Type 2) contain only one track, by definition, so a `Sequence` that's read from such a file will necessarily have a single `Track` object.

#### MidiEvents and Ticks

As discussed in Chapter 8, "Overview of the MIDI Package," the Java Sound API includes `MidiMessage` objects that correspond to the raw two- or three-byte sequences that make up most standard MIDI messages. A `MidiEvent` is simply a packaging of a `MidiMessage` along with an accompanying timing value that specifies when the event occurs. (We might then say that a sequence really consists of a four- or five-level hierarchy of data, rather than three-level, because the ostensible lowest level, `MidiEvent`, actually contains a lower-level `MidiMessage`, and likewise the `MidiMessage` object contains an array of bytes that comprises a standard MIDI message.)

In the Java Sound API, there are two different ways in which `MidiMessages` can be associated with timing values. One is the way mentioned above under "When to Use a Sequencer." This technique is described in detail under "Sending a Message to a Receiver without Using a Transmitter" and "Understanding Time Stamps" in Chapter 10, "Transmitting and Receiving MIDI Messages." There, we saw that the `send` method of `Receiver` takes a `MidiMessage` argument and a time-stamp argument. That kind of time stamp can only be expressed in microseconds.

The other way in which a `MidiMessage` can have its timing specified is by being encapsulated in a `MidiEvent`. In this case, the timing is expressed in slightly more abstract units called ticks.

What is the duration of a tick? It can vary between sequences (but not within a sequence), and its value is stored in the header of a standard MIDI file. The size of a tick is given in one of two types of unit:

- Pulses (ticks) per quarter note, abbreviated as PPQ
- Ticks per frame, also known as SMPTE time code (a standard adopted by the Society of Motion Picture and Television Engineers)

If the unit is PPQ, the size of a tick is expressed as a fraction of a quarter note, which is a relative, not absolute, time value. A quarter note is a musical duration value that often corresponds to one beat of the music (a quarter of a measure in 4/4 time). The duration of a quarter note is dependent on the tempo, which can vary during the course of the music if the sequence contains tempo-change events. So if the sequence's timing increments (ticks) occur, say 96 times per quarter note, each event's timing value measures that event's position in musical terms, not as an absolute time value.

On the other hand, in the case of SMPTE, the units measure absolute time, and the notion of tempo is inapplicable. There are actually four different SMPTE conventions available, which refer to the number of motion-picture frames per second. The number of frames per second can be 24, 25, 29.97, or 30. With SMPTE time code, the size of a tick is expressed as a fraction of a frame.

In the Java Sound API, you can learn which type of unit-namely, PPQ or one of the SMPTE units-is used in a particular sequence by invoking `Sequence.getDivisionType`. You can then calculate the size of a tick after invoking `Sequence.getResolution`. The latter method returns the number of ticks per quarter note if the division type is PPQ, or per SMPTE 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;

and this formula in the case of SMPTE:

>     framesPerSecond =
>       (divisionType == Sequence.SMPTE_24 ? 24
>         : (divisionType == Sequence.SMPTE_25 ? 25
>           : (divisionType == Sequence.SMPTE_30 ? 30
>             : (divisionType == Sequence.SMPTE_30DROP ?

>                 29.97))));
>     ticksPerSecond = resolution * framesPerSecond;
>     tickSize = 1.0 / ticksPerSecond;

The Java Sound API's definition of timing in a sequence mirrors that of the Standard MIDI Files specification. However, there's one important difference. The tick values contained in `MidiEvents` measure cumulative time, rather than delta time. In a standard MIDI file, each event's timing information measures the amount of time elapsed since the onset of the previous event in the sequence. This is called delta time. But in the Java Sound API, the ticks aren't delta values; they're the previous event's time value plus the delta value. In other words, in the Java Sound API the timing value for each event is always greater than that of the previous event in the sequence (or equal, if the events are supposed to be simultaneous). Each event's timing value measures the time elapsed since the beginning of the sequence.

To summarize, the Java Sound API expresses timing information in either MIDI ticks or microseconds. `MidiEvents` store timing information in terms of MIDI ticks. The duration of a tick can be calculated from the `Sequence's` global timing information and, if the sequence uses tempo-based timing, the current musical tempo. The time stamp associated with a `MidiMessage` sent to a `Receiver`, on the other hand, is always expressed in microseconds.

One goal of this design is to avoid conflicting notions of time. It's the job of a `Sequencer` to interpret the time units in its `MidiEvents`, which might have PPQ units, and translate these into absolute time in microseconds, taking the current tempo into account. The sequencer must also express the microseconds relative to the time when the device receiving the message was opened. Note that a sequencer can have multiple transmitters, each delivering messages to a different receiver that might be associated with a completely different device. You can see, then, that the sequencer has to be able to perform multiple translations at the same time, making sure that each device receives time stamps appropriate for its notion of time.

To make matters more complicated, different devices might update their notions of time based on different sources (such as the operating system's clock, or a clock maintained by a sound card). This means that their timings can drift relative to the sequencer's. To keep in synchronization with the sequencer, some devices permit themselves to be "slaves" to the sequencer's notion of time. Setting masters and slaves is discussed later under "Using Advanced Sequencer Features."

### Overview of Sequencer Methods

The `Sequencer` interface provides methods in several categories:

- Methods to load sequence data from a MIDI file or a `Sequence` object, and to save the currently loaded sequence data to a MIDI file.
- Methods analogous to the transport functions of a tape recorder, for stopping and starting playback and recording, enabling and disabling recording on specific tracks, and shuttling the current playback or recording position in a `Sequence`.
- Advanced methods for querying and setting the synchronization and timing parameters of the object. A `Sequencer` may play at different tempos, with some `Tracks` muted, and in various synchronization states with other objects.
- Advanced methods for registering "listener" objects that are notified when the `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.

### 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 `MidiSystem` object. As mentioned in Chapter 9, "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 Chapter 9, "Accessing MIDI System Resources."

### 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:

- Reading the sequence data from a MIDI file
- Recording it in real time by receiving MIDI messages from another device, such as a MIDI input port
- Building it programmatically "from scratch" by adding tracks to an empty sequence and adding `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 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
>         }

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.

### Playing a Sequence

Starting and stopping a `Sequencer` is accomplished using the following methods:

>         void start()

and

>         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`, see Chapter 10, "Transmitting and Receiving MIDI Messages."

### Recording and Saving Sequences

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`:

1. Use `MidiSystem.getSequencer` to get a new sequencer to use for recording, as above.

2. Set up the "wiring" of the MIDI connections. The object that is transmitting the MIDI data to be recorded should be configured, through its `setReceiver` method, to send data to a `Receiver` associated with the recording `Sequencer`.

3. Create a new `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();
   >           }

   The constructor for `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.

4. Create an empty `Track` in the `Sequence`, with `Sequence.createTrack`. This step is unnecessary if the `Sequence` was created with initial `Tracks`.

5. Using `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.

6. Invoke `Sequencer.recordEnable` for each `Track` to be recorded. If necessary, get a reference to the available `Tracks` in the `Sequence` by invoking `Sequence.getTracks`.

7. Invoke `startRecording` on the `Sequencer`.

8. When done recording, invoke `Sequencer.stop` or `Sequencer.stopRecording`.

9. Save the recorded `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.

### 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 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.

### Using Advanced Sequencer Features

So far, this chapter has focused on simple playback and recording of MIDI data. This section will briefly describe some of the more advanced features available through methods of the `Sequencer` interface and the `Sequence` class.

#### Moving to an Arbitrary Position in the Sequence

There are two `Sequencer` methods that obtain the sequencer's current position in the sequence. The first of these:

>         long getTickPosition()

returns the position measured in MIDI ticks from the beginning of the sequence. The second method:

>     long getMicrosecondPosition()

returns the current position in microseconds. This method assumes that the sequence is being played at the default rate as stored in the MIDI file or in the `Sequence`. It does not return a different value if you've changed the playback speed as described below.

You can similarly set the sequencer's current position according to one unit or the other:

>     void setTickPosition(long tick)

or

>     void setMicrosecondPosition(long microsecond)

#### Changing the Playback Speed

As indicated earlier, a sequence's speed is indicated by its tempo, which can vary over the course of the sequence. A sequence can contain events that encapsulate standard MIDI tempo-change messages. When the sequencer processes such an event, it changes the speed of playback to reflect the indicated tempo. In addition, you can programmatically change the tempo by invoking any of these `Sequencer` methods:

>         public void setTempoInBPM(float bpm)
>         public void setTempoInMPQ(float mpq)
>         public void setTempoFactor(float factor)

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.

#### Muting or Soloing Individual Tracks in the Sequence

It's often convenient for users of sequencers to be able to turn off certain tracks, to hear more clearly exactly what is happening in the music. A full-featured sequencer program lets the user choose which tracks should sound during playback. (Speaking more precisely, since sequencers don't actually create sound themselves, the user chooses which tracks will contribute to the stream of MIDI messages that the sequencer produces.) Typically, there are two types of graphical controls on each track: a mute button and a solo button. If the mute button is activated, that track will not sound under any circumstances, until the mute button is deactivated. Soloing is a less well-known feature. It's roughly the opposite of muting. If the solo button on any track is activated, only tracks whose solo buttons 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.

As an aside, the boolean that's returned by `getTrackMute` can, indeed, tell us if a failure occurred, but it can't tell us why it occurred. We could test to see if a failure was caused by passing an invalid track number to the `setTrackMute` method. To do this, we would call the `getTracks` method of `Sequence`, which returns an array containing all of the tracks in the sequence. If the track number specified in the `setTrackMute` call exceeds the length of this array, then we know we specified an invalid track number.

If the mute request succeeded, then in our example, the fifth track will not sound when the sequence is playing, nor will any other tracks that are currently muted.

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)

As in `setTrackMute`, the first argument specifies the zero-based track number, and the second argument, if `true`, specifies that the track should be in solo mode; otherwise the track should not be soloed.

By default, a track is neither muted nor soloed.

#### Synchronizing with Other MIDI Devices

`Sequencer` has an inner class called `Sequencer.SyncMode`. A `SyncMode` object represents one of the ways in which a MIDI sequencer's notion of time can be synchronized with a master or slave device. If the sequencer is being synchronized to a master, the sequencer revises its current time in response to certain MIDI messages from the master. If the sequencer has a slave, the sequencer similarly sends MIDI messages to control the slave's timing.

There are three predefined modes that specify possible masters for a sequencer: `INTERNAL_CLOCK`, `MIDI_SYNC`, and `MIDI_TIME_CODE`. The latter two work if the sequencer receives MIDI messages from another device. In these two modes, the sequencer's time gets reset based on system real-time timing clock messages or MIDI time code (MTC) messages, respectively. (See the MIDI specification for more information about these types of message.) These two modes can also be used as slave modes, in which case the sequencer sends the corresponding types of MIDI messages to its receiver. A fourth mode, `NO_SYNC`, is used to indicate that the sequencer should not send timing information to its receivers.

By calling the `setMasterSyncMode` method with a supported `SyncMode` object as the argument, you can specify how the sequencer's timing is controlled. Likewise, the `setSlaveSyncMode` method determines what timing information the sequencer will send to its receivers. This information controls the timing of devices that use the sequencer as a master timing source.

#### Specifying Special Event Listeners

Each track of a sequence can contain many different kinds of `MidiEvents`. Such events include Note On and Note Off messages, program changes, 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 set. You can obtain the controller number using the `ShortMessage.getData1` method, and the new setting of the controller's value using the `ShortMessage.getData2` method.

The other kind of special event listener is defined by the `MetaEventListener` interface. Meta messages, according to the Standard MIDI Files 1.0 specification, are messages that are not present in MIDI wire protocol but that can be embedded in a MIDI file. They are not meaningful to a synthesizer, but can be interpreted by a sequencer. Meta messages include instructions (such as tempo change commands), lyrics or other text, and other indicators (such as end-of-track).

The `MetaEventListener` mechanism is analogous to `ControllerEventListener`. Implement the `MetaEventListener` interface in any class whose instances need to be notified when a `MetaMessage` is processed by the sequencer. This involves adding the following method to the class:

>     void meta(MetaMessage msg)

You register an instance of this class by passing it as the argument to the `Sequencer addMetaEventListener` method:

>     boolean b = sequencer.addMetaEventListener
>             (myMetaListener);

This is slightly different from the approach taken by the `ControllerEventListener` interface, because you have to register to receive all `MetaMessages,` not just selected ones of interest. If the sequencer encounters a `MetaMessage` in its sequence, it will invoke `myMetaListener.meta`, passing it the `MetaMessage` encountered. The `meta` method can invoke `getType` on its `MetaMessage` argument to obtain an integer from 0 to 127 that indicates the message type, as defined by the Standard MIDI Files 1.0 specification.

[Top] [Prev] [Next] [Bottom]

Copyright © 2000, Sun Microsystems Inc. All rights reserved.