Download the JDK
Search the Tutorials
Hide the TOC
Trail: Sound
Overview of the Sampled Package
Accessing Audio System Resources
Playing Back Audio
Capturing Audio
Processing Audio with Controls
Using Files and Format Converters
Overview of the MIDI Package
Accessing MIDI System Resources
Transmitting and Receiving MIDI Messages
Introduction to Sequencers
Using Sequencer Methods
Using Advanced Sequencer Features
Synthesizing Sound
Introduction to the Service Provider Interfaces
Providing Sampled-Audio Services
Providing MIDI Services
Home Page > Sound
« Previous • Trail • Next »
Using Advanced Sequencer Features

> So far, we've 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.

« Previous • Trail • Next »
Previous page: Using Sequencer Methods
Next page: Synthesizing Sound