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 theSequence
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)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: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.public void setTempoInBPM(float bpm) public void setTempoInMPQ(float mpq) public void setTempoFactor(float factor)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 bygetTempoInBPM
andgetTempoInMPQ
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 defaultSequencer
and that we've loaded sequence data into it. Muting the fifth track in the sequence would be accomplished as follows: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 tosequencer.setTrackMute(4, true); boolean muted = sequencer.getTrackMute(4); if (!muted) { return; // muting failed }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 theSequencer getTrackMute
method, passing it the track number we're querying. If it returnstrue
, as we'd expect in this case, then the mute request worked. If it returnsfalse
, 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 callinggetTrackMute
, 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 thesetTrackMute
method. To do this, we would call thegetTracks
method ofSequence
, which returns an array containing all of the tracks in the sequence. If the track number specified in thesetTrackMute
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 ofSequence:
As invoid setTrackSolo(int track, boolean bSolo)setTrackMute
, the first argument specifies the zero-based track number, and the second argument, iftrue
, 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 calledSequencer.SyncMode
. ASyncMode
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
, andMIDI_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 supportedSyncMode
object as the argument, you can specify how the sequencer's timing is controlled. Likewise, thesetSlaveSyncMode
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 aSequencer
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 theControllerEventListener
interface, meaning that your class contains the following method:Let's also assume that you've created an instance of your class and assigned it to a variable calledvoid controlChange(ShortMessage msg)myListener
. If you include the following statements somewhere within your program:then your class'sint[] controllersOfInterest = { 1, 2, 4 }; sequencer.addControllerEventListener(myListener, controllersOfInterest);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 theSequencer
processes a request to set the value of any of the registered controllers, theSequencer
will invoke your class'scontrolChange
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 aShortMessage
containing the controller number affected, and the new value to which the controller was set. You can obtain the controller number using theShortMessage.getData1
method, and the new setting of the controller's value using theShortMessage.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 toControllerEventListener
. Implement theMetaEventListener
interface in any class whose instances need to be notified when aMetaMessage
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:This is slightly different from the approach taken by theboolean b = sequencer.addMetaEventListener (myMetaListener);ControllerEventListener
interface, because you have to register to receive allMetaMessages,
not just selected ones of interest. If the sequencer encounters aMetaMessage
in its sequence, it will invokemyMetaListener.meta
, passing it theMetaMessage
encountered. Themeta
method can invokegetType
on itsMetaMessage
argument to obtain an integer from 0 to 127 that indicates the message type, as defined by the Standard MIDI Files 1.0 specification.