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. package javax.sound.midi;
  8. import java.util.Vector;
  9. import java.util.ArrayList;
  10. import java.util.HashSet;
  11. import com.sun.media.sound.MidiUtils;
  12. /**
  13. * A MIDI track is an independent stream of MIDI events (time-stamped MIDI
  14. * data) that can be stored along with other tracks in a standard MIDI file.
  15. * The MIDI specification allows only 16 channels of MIDI data, but tracks
  16. * are a way to get around this limitation. A MIDI file can contain any number
  17. * of tracks, each containing its own stream of up to 16 channels of MIDI data.
  18. * <p>
  19. * A <code>Track</code> occupies a middle level in the hierarchy of data played
  20. * by a <code>{@link Sequencer}</code>: sequencers play sequences, which contain tracks,
  21. * which contain MIDI events. A sequencer may provide controls that mute
  22. * or solo individual tracks.
  23. * <p>
  24. * The timing information and resolution for a track is controlled by and stored
  25. * in the sequence containing the track. A given <code>Track</code>
  26. * is considered to belong to the particular <code>{@link Sequence}</code> that
  27. * maintains its timing. For this reason, a new (empty) track is created by calling the
  28. * <code>{@link Sequence#createTrack}</code> method, rather than by directly invoking a
  29. * <code>Track</code> constructor.
  30. * <p>
  31. * The <code>Track</code> class provides methods to edit the track by adding
  32. * or removing <code>MidiEvent</code> objects from it. These operations keep
  33. * the event list in the correct time order. Methods are also
  34. * included to obtain the track's size, in terms of either the number of events
  35. * it contains or its duration in ticks.
  36. *
  37. * @see Sequencer#setTrackMute
  38. * @see Sequencer#setTrackSolo
  39. *
  40. * @version 1.22, 03/12/19
  41. * @author Kara Kytle
  42. * @author Florian Bomers
  43. */
  44. public class Track {
  45. // TODO: use arrays for faster access
  46. // the list containing the events
  47. private ArrayList eventsList = new ArrayList();
  48. // use a hashset to detect duplicate events in add(MidiEvent)
  49. private HashSet set = new HashSet();
  50. private MidiEvent eotEvent;
  51. /**
  52. * Package-private constructor. Constructs a new, empty Track object,
  53. * which initially contains one event, the meta-event End of Track.
  54. */
  55. Track() {
  56. // start with the end of track event
  57. MetaMessage eot = new ImmutableEndOfTrack();
  58. eotEvent = new MidiEvent(eot, 0);
  59. eventsList.add(eotEvent);
  60. set.add(eotEvent);
  61. }
  62. /**
  63. * Adds a new event to the track. However, if the event is already
  64. * contained in the track, it is not added again. The list of events
  65. * is kept in time order, meaning that this event inserted at the
  66. * appropriate place in the list, not necessarily at the end.
  67. *
  68. * @param event the event to add
  69. * @return <code>true</code> if the event did not already exist in the
  70. * track and was added, otherwise <code>false</code>
  71. */
  72. public boolean add(MidiEvent event) {
  73. if (event == null) {
  74. return false;
  75. }
  76. synchronized(eventsList) {
  77. if (!set.contains(event)) {
  78. int eventsCount = eventsList.size();
  79. // get the last event
  80. MidiEvent lastEvent = null;
  81. if (eventsCount > 0) {
  82. lastEvent = (MidiEvent) eventsList.get(eventsCount - 1);
  83. }
  84. // sanity check that we have a correct end-of-track
  85. if (lastEvent != eotEvent) {
  86. // if there is no eot event, add our immutable instance again
  87. if (lastEvent != null) {
  88. // set eotEvent's tick to the last tick of the track
  89. eotEvent.setTick(lastEvent.getTick());
  90. } else {
  91. // if the events list is empty, just set the tick to 0
  92. eotEvent.setTick(0);
  93. }
  94. // we needn't check for a duplicate of eotEvent in "eventsList",
  95. // since then it would appear in the set.
  96. eventsList.add(eotEvent);
  97. set.add(eotEvent);
  98. }
  99. // first see if we are trying to add
  100. // and endoftrack event.
  101. if (MidiUtils.isMetaEndOfTrack(event.getMessage())) {
  102. // since end of track event is useful
  103. // for delays at the end of a track, we want to keep
  104. // the tick value requested here if it is greater
  105. // than the one on the eot we are maintaining.
  106. // Otherwise, we only want a single eot event, so ignore.
  107. if (event.getTick() > eotEvent.getTick()) {
  108. eotEvent.setTick(event.getTick());
  109. }
  110. return true;
  111. }
  112. // prevent duplicates
  113. set.add(event);
  114. // insert event such that events is sorted in increasing
  115. // tick order
  116. int i = eventsCount;
  117. for ( ; i > 0; i--) {
  118. if (event.getTick() >= ((MidiEvent)eventsList.get(i-1)).getTick()) {
  119. break;
  120. }
  121. }
  122. if (i == eventsCount) {
  123. // we're adding an event after the
  124. // tick value of our eot, so push the eot out.
  125. // Always add at the end for better performance:
  126. // this saves all the checks and arraycopy when inserting
  127. // overwrite eot with new event
  128. eventsList.set(eventsCount - 1, event);
  129. // set new time of eot, if necessary
  130. if (eotEvent.getTick() < event.getTick()) {
  131. eotEvent.setTick(event.getTick());
  132. }
  133. // add eot again at the end
  134. eventsList.add(eotEvent);
  135. } else {
  136. eventsList.add(i, event);
  137. }
  138. return true;
  139. }
  140. }
  141. return false;
  142. }
  143. /**
  144. * Removes the specified event from the track.
  145. * @param event the event to remove
  146. * @return <code>true</code> if the event existed in the track and was removed,
  147. * otherwise <code>false</code>
  148. */
  149. public boolean remove(MidiEvent event) {
  150. // this implementation allows removing the EOT event.
  151. // pretty bad, but would probably be too risky to
  152. // change behavior now, in case someone does tricks like:
  153. //
  154. // while (track.size() > 0) track.remove(track.get(track.size() - 1));
  155. // also, would it make sense to adjust the EOT's time
  156. // to the last event, if the last non-EOT event is removed?
  157. // Or: document that the ticks() length will not be reduced
  158. // by deleting events (unless the EOT event is removed)
  159. synchronized(eventsList) {
  160. if (set.remove(event)) {
  161. int i = eventsList.indexOf(event);
  162. if (i >= 0) {
  163. eventsList.remove(i);
  164. return true;
  165. }
  166. }
  167. }
  168. return false;
  169. }
  170. /**
  171. * Obtains the event at the specified index.
  172. * @param index the location of the desired event in the event vector
  173. * @throws <code>ArrayIndexOutOfBoundsException</code> if the
  174. * specified index is negative or not less than the current size of
  175. * this track.
  176. * @see #size
  177. */
  178. public MidiEvent get(int index) throws ArrayIndexOutOfBoundsException {
  179. try {
  180. synchronized(eventsList) {
  181. return (MidiEvent)eventsList.get(index);
  182. }
  183. } catch (IndexOutOfBoundsException ioobe) {
  184. throw new ArrayIndexOutOfBoundsException(ioobe.getMessage());
  185. }
  186. }
  187. /**
  188. * Obtains the number of events in this track.
  189. * @return the size of the track's event vector
  190. */
  191. public int size() {
  192. synchronized(eventsList) {
  193. return eventsList.size();
  194. }
  195. }
  196. /**
  197. * Obtains the length of the track, expressed in MIDI ticks. (The
  198. * duration of a tick in seconds is determined by the timing resolution
  199. * of the <code>Sequence</code> containing this track, and also by
  200. * the tempo of the music as set by the sequencer.)
  201. * @return the duration, in ticks
  202. * @see Sequence#Sequence(float, int)
  203. * @see Sequencer#setTempoInBPM(float)
  204. * @see Sequencer#getTickPosition()
  205. */
  206. public long ticks() {
  207. long ret = 0;
  208. synchronized (eventsList) {
  209. if (eventsList.size() > 0) {
  210. ret = ((MidiEvent)eventsList.get(eventsList.size() - 1)).getTick();
  211. }
  212. }
  213. return ret;
  214. }
  215. private static class ImmutableEndOfTrack extends MetaMessage {
  216. private ImmutableEndOfTrack() {
  217. super(new byte[3]);
  218. data[0] = (byte) META;
  219. data[1] = MidiUtils.META_END_OF_TRACK_TYPE;
  220. data[2] = 0;
  221. }
  222. public void setMessage(int type, byte[] data, int length) throws InvalidMidiDataException {
  223. throw new InvalidMidiDataException("cannot modify end of track message");
  224. }
  225. }
  226. }