1 /* 2 * Copyright (c) 1999, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.sound.midi; 27 28 import java.util.ArrayList; 29 import java.util.HashSet; 30 31 import com.sun.media.sound.MidiUtils; 32 33 /** 34 * A MIDI track is an independent stream of MIDI events (time-stamped MIDI data) 35 * that can be stored along with other tracks in a standard MIDI file. The MIDI 36 * specification allows only 16 channels of MIDI data, but tracks are a way to 37 * get around this limitation. A MIDI file can contain any number of tracks, 38 * each containing its own stream of up to 16 channels of MIDI data. 39 * <p> 40 * A {@code Track} occupies a middle level in the hierarchy of data played by a 41 * {@link Sequencer}: sequencers play sequences, which contain tracks, which 42 * contain MIDI events. A sequencer may provide controls that mute or solo 43 * individual tracks. 44 * <p> 45 * The timing information and resolution for a track is controlled by and stored 46 * in the sequence containing the track. A given {@code Track} is considered to 47 * belong to the particular {@link Sequence} that maintains its timing. For this 48 * reason, a new (empty) track is created by calling the 49 * {@link Sequence#createTrack} method, rather than by directly invoking a 50 * {@code Track} constructor. 51 * <p> 52 * The {@code Track} class provides methods to edit the track by adding or 53 * removing {@code MidiEvent} objects from it. These operations keep the event 54 * list in the correct time order. Methods are also included to obtain the 55 * track's size, in terms of either the number of events it contains or its 56 * duration in ticks. 57 * 58 * @author Kara Kytle 59 * @author Florian Bomers 60 * @see Sequencer#setTrackMute 61 * @see Sequencer#setTrackSolo 62 */ 63 public class Track { 64 65 // TODO: use arrays for faster access 66 67 /** 68 * The list containing the events. 69 */ 70 private final ArrayList<MidiEvent> eventsList = new ArrayList<>(); 71 72 /** 73 * Use a hashset to detect duplicate events in add(MidiEvent). 74 */ 75 private final HashSet<MidiEvent> set = new HashSet<>(); 76 77 private final MidiEvent eotEvent; 78 79 /** 80 * Package-private constructor. Constructs a new, empty Track object, which 81 * initially contains one event, the meta-event End of Track. 82 */ 83 Track() { 84 // start with the end of track event 85 MetaMessage eot = new ImmutableEndOfTrack(); 86 eotEvent = new MidiEvent(eot, 0); 87 eventsList.add(eotEvent); 88 set.add(eotEvent); 89 } 90 91 /** 92 * Adds a new event to the track. However, if the event is already contained 93 * in the track, it is not added again. The list of events is kept in time 94 * order, meaning that this event inserted at the appropriate place in the 95 * list, not necessarily at the end. 96 * 97 * @param event the event to add 98 * @return {@code true} if the event did not already exist in the track and 99 * was added, otherwise {@code false} 100 */ 101 public boolean add(MidiEvent event) { 102 if (event == null) { 103 return false; 104 } 105 synchronized(eventsList) { 106 107 if (!set.contains(event)) { 108 int eventsCount = eventsList.size(); 109 110 // get the last event 111 MidiEvent lastEvent = null; 112 if (eventsCount > 0) { 113 lastEvent = eventsList.get(eventsCount - 1); 114 } 115 // sanity check that we have a correct end-of-track 116 if (lastEvent != eotEvent) { 117 // if there is no eot event, add our immutable instance again 118 if (lastEvent != null) { 119 // set eotEvent's tick to the last tick of the track 120 eotEvent.setTick(lastEvent.getTick()); 121 } else { 122 // if the events list is empty, just set the tick to 0 123 eotEvent.setTick(0); 124 } 125 // we needn't check for a duplicate of eotEvent in "eventsList", 126 // since then it would appear in the set. 127 eventsList.add(eotEvent); 128 set.add(eotEvent); 129 eventsCount = eventsList.size(); 130 } 131 132 // first see if we are trying to add 133 // and endoftrack event. 134 if (MidiUtils.isMetaEndOfTrack(event.getMessage())) { 135 // since end of track event is useful 136 // for delays at the end of a track, we want to keep 137 // the tick value requested here if it is greater 138 // than the one on the eot we are maintaining. 139 // Otherwise, we only want a single eot event, so ignore. 140 if (event.getTick() > eotEvent.getTick()) { 141 eotEvent.setTick(event.getTick()); 142 } 143 return true; 144 } 145 146 // prevent duplicates 147 set.add(event); 148 149 // insert event such that events is sorted in increasing 150 // tick order 151 int i = eventsCount; 152 for ( ; i > 0; i--) { 153 if (event.getTick() >= (eventsList.get(i-1)).getTick()) { 154 break; 155 } 156 } 157 if (i == eventsCount) { 158 // we're adding an event after the 159 // tick value of our eot, so push the eot out. 160 // Always add at the end for better performance: 161 // this saves all the checks and arraycopy when inserting 162 163 // overwrite eot with new event 164 eventsList.set(eventsCount - 1, event); 165 // set new time of eot, if necessary 166 if (eotEvent.getTick() < event.getTick()) { 167 eotEvent.setTick(event.getTick()); 168 } 169 // add eot again at the end 170 eventsList.add(eotEvent); 171 } else { 172 eventsList.add(i, event); 173 } 174 return true; 175 } 176 } 177 178 return false; 179 } 180 181 /** 182 * Removes the specified event from the track. 183 * 184 * @param event the event to remove 185 * @return {@code true} if the event existed in the track and was removed, 186 * otherwise {@code false} 187 */ 188 public boolean remove(MidiEvent event) { 189 190 // this implementation allows removing the EOT event. 191 // pretty bad, but would probably be too risky to 192 // change behavior now, in case someone does tricks like: 193 // 194 // while (track.size() > 0) track.remove(track.get(track.size() - 1)); 195 196 // also, would it make sense to adjust the EOT's time 197 // to the last event, if the last non-EOT event is removed? 198 // Or: document that the ticks() length will not be reduced 199 // by deleting events (unless the EOT event is removed) 200 synchronized(eventsList) { 201 if (set.remove(event)) { 202 int i = eventsList.indexOf(event); 203 if (i >= 0) { 204 eventsList.remove(i); 205 return true; 206 } 207 } 208 } 209 return false; 210 } 211 212 /** 213 * Obtains the event at the specified index. 214 * 215 * @param index the location of the desired event in the event vector 216 * @return the event at the specified index 217 * @throws ArrayIndexOutOfBoundsException if the specified index is negative 218 * or not less than the current size of this track 219 * @see #size 220 */ 221 public MidiEvent get(int index) throws ArrayIndexOutOfBoundsException { 222 try { 223 synchronized(eventsList) { 224 return eventsList.get(index); 225 } 226 } catch (IndexOutOfBoundsException ioobe) { 227 throw new ArrayIndexOutOfBoundsException(ioobe.getMessage()); 228 } 229 } 230 231 /** 232 * Obtains the number of events in this track. 233 * 234 * @return the size of the track's event vector 235 */ 236 public int size() { 237 synchronized(eventsList) { 238 return eventsList.size(); 239 } 240 } 241 242 /** 243 * Obtains the length of the track, expressed in MIDI ticks. (The duration 244 * of a tick in seconds is determined by the timing resolution of the 245 * {@code Sequence} containing this track, and also by the tempo of the 246 * music as set by the sequencer.) 247 * 248 * @return the duration, in ticks 249 * @see Sequence#Sequence(float, int) 250 * @see Sequencer#setTempoInBPM(float) 251 * @see Sequencer#getTickPosition() 252 */ 253 public long ticks() { 254 long ret = 0; 255 synchronized (eventsList) { 256 if (eventsList.size() > 0) { 257 ret = (eventsList.get(eventsList.size() - 1)).getTick(); 258 } 259 } 260 return ret; 261 } 262 263 private static class ImmutableEndOfTrack extends MetaMessage { 264 private ImmutableEndOfTrack() { 265 super(new byte[3]); 266 data[0] = (byte) META; 267 data[1] = MidiUtils.META_END_OF_TRACK_TYPE; 268 data[2] = 0; 269 } 270 271 @Override 272 public void setMessage(int type, byte[] data, int length) throws InvalidMidiDataException { 273 throw new InvalidMidiDataException("cannot modify end of track message"); 274 } 275 } 276 }