KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > sound > midi > Track


1 /*
2  * @(#)Track.java 1.22 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package javax.sound.midi;
9
10 import java.util.Vector JavaDoc;
11 import java.util.ArrayList JavaDoc;
12 import java.util.HashSet JavaDoc;
13 import com.sun.media.sound.MidiUtils;
14
15 /**
16  * A MIDI track is an independent stream of MIDI events (time-stamped MIDI
17  * data) that can be stored along with other tracks in a standard MIDI file.
18  * The MIDI specification allows only 16 channels of MIDI data, but tracks
19  * are a way to get around this limitation. A MIDI file can contain any number
20  * of tracks, each containing its own stream of up to 16 channels of MIDI data.
21  * <p>
22  * A <code>Track</code> occupies a middle level in the hierarchy of data played
23  * by a <code>{@link Sequencer}</code>: sequencers play sequences, which contain tracks,
24  * which contain MIDI events. A sequencer may provide controls that mute
25  * or solo individual tracks.
26  * <p>
27  * The timing information and resolution for a track is controlled by and stored
28  * in the sequence containing the track. A given <code>Track</code>
29  * is considered to belong to the particular <code>{@link Sequence}</code> that
30  * maintains its timing. For this reason, a new (empty) track is created by calling the
31  * <code>{@link Sequence#createTrack}</code> method, rather than by directly invoking a
32  * <code>Track</code> constructor.
33  * <p>
34  * The <code>Track</code> class provides methods to edit the track by adding
35  * or removing <code>MidiEvent</code> objects from it. These operations keep
36  * the event list in the correct time order. Methods are also
37  * included to obtain the track's size, in terms of either the number of events
38  * it contains or its duration in ticks.
39  *
40  * @see Sequencer#setTrackMute
41  * @see Sequencer#setTrackSolo
42  *
43  * @version 1.22, 03/12/19
44  * @author Kara Kytle
45  * @author Florian Bomers
46  */

47 public class Track {
48
49     // TODO: use arrays for faster access
50

51     // the list containing the events
52
private ArrayList JavaDoc eventsList = new ArrayList JavaDoc();
53
54     // use a hashset to detect duplicate events in add(MidiEvent)
55
private HashSet JavaDoc set = new HashSet JavaDoc();
56
57     private MidiEvent JavaDoc eotEvent;
58
59
60     /**
61      * Package-private constructor. Constructs a new, empty Track object,
62      * which initially contains one event, the meta-event End of Track.
63      */

64     Track() {
65     // start with the end of track event
66
MetaMessage JavaDoc eot = new ImmutableEndOfTrack();
67     eotEvent = new MidiEvent JavaDoc(eot, 0);
68     eventsList.add(eotEvent);
69     set.add(eotEvent);
70     }
71
72     /**
73      * Adds a new event to the track. However, if the event is already
74      * contained in the track, it is not added again. The list of events
75      * is kept in time order, meaning that this event inserted at the
76      * appropriate place in the list, not necessarily at the end.
77      *
78      * @param event the event to add
79      * @return <code>true</code> if the event did not already exist in the
80      * track and was added, otherwise <code>false</code>
81      */

82     public boolean add(MidiEvent JavaDoc event) {
83         if (event == null) {
84         return false;
85     }
86     synchronized(eventsList) {
87
88         if (!set.contains(event)) {
89         int eventsCount = eventsList.size();
90
91         // get the last event
92
MidiEvent JavaDoc lastEvent = null;
93         if (eventsCount > 0) {
94             lastEvent = (MidiEvent JavaDoc) eventsList.get(eventsCount - 1);
95         }
96         // sanity check that we have a correct end-of-track
97
if (lastEvent != eotEvent) {
98             // if there is no eot event, add our immutable instance again
99
if (lastEvent != null) {
100             // set eotEvent's tick to the last tick of the track
101
eotEvent.setTick(lastEvent.getTick());
102             } else {
103             // if the events list is empty, just set the tick to 0
104
eotEvent.setTick(0);
105             }
106             // we needn't check for a duplicate of eotEvent in "eventsList",
107
// since then it would appear in the set.
108
eventsList.add(eotEvent);
109             set.add(eotEvent);
110         }
111
112         // first see if we are trying to add
113
// and endoftrack event.
114
if (MidiUtils.isMetaEndOfTrack(event.getMessage())) {
115             // since end of track event is useful
116
// for delays at the end of a track, we want to keep
117
// the tick value requested here if it is greater
118
// than the one on the eot we are maintaining.
119
// Otherwise, we only want a single eot event, so ignore.
120
if (event.getTick() > eotEvent.getTick()) {
121             eotEvent.setTick(event.getTick());
122             }
123             return true;
124         }
125
126             // prevent duplicates
127
set.add(event);
128
129         // insert event such that events is sorted in increasing
130
// tick order
131
int i = eventsCount;
132         for ( ; i > 0; i--) {
133             if (event.getTick() >= ((MidiEvent JavaDoc)eventsList.get(i-1)).getTick()) {
134             break;
135             }
136         }
137         if (i == eventsCount) {
138             // we're adding an event after the
139
// tick value of our eot, so push the eot out.
140
// Always add at the end for better performance:
141
// this saves all the checks and arraycopy when inserting
142

143             // overwrite eot with new event
144
eventsList.set(eventsCount - 1, event);
145             // set new time of eot, if necessary
146
if (eotEvent.getTick() < event.getTick()) {
147             eotEvent.setTick(event.getTick());
148             }
149             // add eot again at the end
150
eventsList.add(eotEvent);
151         } else {
152             eventsList.add(i, event);
153         }
154         return true;
155         }
156     }
157
158     return false;
159     }
160
161
162     /**
163      * Removes the specified event from the track.
164      * @param event the event to remove
165      * @return <code>true</code> if the event existed in the track and was removed,
166      * otherwise <code>false</code>
167      */

168     public boolean remove(MidiEvent JavaDoc event) {
169
170         // this implementation allows removing the EOT event.
171
// pretty bad, but would probably be too risky to
172
// change behavior now, in case someone does tricks like:
173
//
174
// while (track.size() > 0) track.remove(track.get(track.size() - 1));
175

176     // also, would it make sense to adjust the EOT's time
177
// to the last event, if the last non-EOT event is removed?
178
// Or: document that the ticks() length will not be reduced
179
// by deleting events (unless the EOT event is removed)
180
synchronized(eventsList) {
181         if (set.remove(event)) {
182         int i = eventsList.indexOf(event);
183         if (i >= 0) {
184             eventsList.remove(i);
185             return true;
186         }
187         }
188     }
189     return false;
190     }
191
192
193     /**
194      * Obtains the event at the specified index.
195      * @param index the location of the desired event in the event vector
196      * @throws <code>ArrayIndexOutOfBoundsException</code> if the
197      * specified index is negative or not less than the current size of
198      * this track.
199      * @see #size
200      */

201     public MidiEvent JavaDoc get(int index) throws ArrayIndexOutOfBoundsException JavaDoc {
202         try {
203             synchronized(eventsList) {
204         return (MidiEvent JavaDoc)eventsList.get(index);
205         }
206     } catch (IndexOutOfBoundsException JavaDoc ioobe) {
207         throw new ArrayIndexOutOfBoundsException JavaDoc(ioobe.getMessage());
208     }
209     }
210
211
212     /**
213      * Obtains the number of events in this track.
214      * @return the size of the track's event vector
215      */

216     public int size() {
217         synchronized(eventsList) {
218         return eventsList.size();
219     }
220     }
221
222
223     /**
224      * Obtains the length of the track, expressed in MIDI ticks. (The
225      * duration of a tick in seconds is determined by the timing resolution
226      * of the <code>Sequence</code> containing this track, and also by
227      * the tempo of the music as set by the sequencer.)
228      * @return the duration, in ticks
229      * @see Sequence#Sequence(float, int)
230      * @see Sequencer#setTempoInBPM(float)
231      * @see Sequencer#getTickPosition()
232      */

233     public long ticks() {
234     long ret = 0;
235     synchronized (eventsList) {
236         if (eventsList.size() > 0) {
237         ret = ((MidiEvent JavaDoc)eventsList.get(eventsList.size() - 1)).getTick();
238         }
239     }
240     return ret;
241     }
242
243     private static class ImmutableEndOfTrack extends MetaMessage JavaDoc {
244         private ImmutableEndOfTrack() {
245         super(new byte[3]);
246         data[0] = (byte) META;
247         data[1] = MidiUtils.META_END_OF_TRACK_TYPE;
248         data[2] = 0;
249     }
250
251     public void setMessage(int type, byte[] data, int length) throws InvalidMidiDataException JavaDoc {
252         throw new InvalidMidiDataException JavaDoc("cannot modify end of track message");
253     }
254     }
255
256 }
257
Popular Tags