Path: blob/master/src/java.desktop/share/classes/javax/sound/midi/Track.java
41159 views
/*1* Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package javax.sound.midi;2627import java.util.ArrayList;28import java.util.HashSet;2930import com.sun.media.sound.MidiUtils;3132/**33* A MIDI track is an independent stream of MIDI events (time-stamped MIDI data)34* that can be stored along with other tracks in a standard MIDI file. The MIDI35* specification allows only 16 channels of MIDI data, but tracks are a way to36* get around this limitation. A MIDI file can contain any number of tracks,37* each containing its own stream of up to 16 channels of MIDI data.38* <p>39* A {@code Track} occupies a middle level in the hierarchy of data played by a40* {@link Sequencer}: sequencers play sequences, which contain tracks, which41* contain MIDI events. A sequencer may provide controls that mute or solo42* individual tracks.43* <p>44* The timing information and resolution for a track is controlled by and stored45* in the sequence containing the track. A given {@code Track} is considered to46* belong to the particular {@link Sequence} that maintains its timing. For this47* reason, a new (empty) track is created by calling the48* {@link Sequence#createTrack} method, rather than by directly invoking a49* {@code Track} constructor.50* <p>51* The {@code Track} class provides methods to edit the track by adding or52* removing {@code MidiEvent} objects from it. These operations keep the event53* list in the correct time order. Methods are also included to obtain the54* track's size, in terms of either the number of events it contains or its55* duration in ticks.56*57* @author Kara Kytle58* @author Florian Bomers59* @see Sequencer#setTrackMute60* @see Sequencer#setTrackSolo61*/62public class Track {6364// TODO: use arrays for faster access6566/**67* The list containing the events.68*/69private final ArrayList<MidiEvent> eventsList = new ArrayList<>();7071/**72* Use a hashset to detect duplicate events in add(MidiEvent).73*/74private final HashSet<MidiEvent> set = new HashSet<>();7576private final MidiEvent eotEvent;7778/**79* Package-private constructor. Constructs a new, empty Track object, which80* initially contains one event, the meta-event End of Track.81*/82Track() {83// start with the end of track event84MetaMessage eot = new ImmutableEndOfTrack();85eotEvent = new MidiEvent(eot, 0);86eventsList.add(eotEvent);87set.add(eotEvent);88}8990/**91* Adds a new event to the track. However, if the event is already contained92* in the track, it is not added again. The list of events is kept in time93* order, meaning that this event inserted at the appropriate place in the94* list, not necessarily at the end.95*96* @param event the event to add97* @return {@code true} if the event did not already exist in the track and98* was added, otherwise {@code false}99*/100public boolean add(MidiEvent event) {101if (event == null) {102return false;103}104synchronized(eventsList) {105106if (!set.contains(event)) {107int eventsCount = eventsList.size();108109// get the last event110MidiEvent lastEvent = null;111if (eventsCount > 0) {112lastEvent = eventsList.get(eventsCount - 1);113}114// sanity check that we have a correct end-of-track115if (lastEvent != eotEvent) {116// if there is no eot event, add our immutable instance again117if (lastEvent != null) {118// set eotEvent's tick to the last tick of the track119eotEvent.setTick(lastEvent.getTick());120} else {121// if the events list is empty, just set the tick to 0122eotEvent.setTick(0);123}124// we needn't check for a duplicate of eotEvent in "eventsList",125// since then it would appear in the set.126eventsList.add(eotEvent);127set.add(eotEvent);128eventsCount = eventsList.size();129}130131// first see if we are trying to add132// and endoftrack event.133if (MidiUtils.isMetaEndOfTrack(event.getMessage())) {134// since end of track event is useful135// for delays at the end of a track, we want to keep136// the tick value requested here if it is greater137// than the one on the eot we are maintaining.138// Otherwise, we only want a single eot event, so ignore.139if (event.getTick() > eotEvent.getTick()) {140eotEvent.setTick(event.getTick());141}142return true;143}144145// prevent duplicates146set.add(event);147148// insert event such that events is sorted in increasing149// tick order150int i = eventsCount;151for ( ; i > 0; i--) {152if (event.getTick() >= (eventsList.get(i-1)).getTick()) {153break;154}155}156if (i == eventsCount) {157// we're adding an event after the158// tick value of our eot, so push the eot out.159// Always add at the end for better performance:160// this saves all the checks and arraycopy when inserting161162// overwrite eot with new event163eventsList.set(eventsCount - 1, event);164// set new time of eot, if necessary165if (eotEvent.getTick() < event.getTick()) {166eotEvent.setTick(event.getTick());167}168// add eot again at the end169eventsList.add(eotEvent);170} else {171eventsList.add(i, event);172}173return true;174}175}176177return false;178}179180/**181* Removes the specified event from the track.182*183* @param event the event to remove184* @return {@code true} if the event existed in the track and was removed,185* otherwise {@code false}186*/187public boolean remove(MidiEvent event) {188189// this implementation allows removing the EOT event.190// pretty bad, but would probably be too risky to191// change behavior now, in case someone does tricks like:192//193// while (track.size() > 0) track.remove(track.get(track.size() - 1));194195// also, would it make sense to adjust the EOT's time196// to the last event, if the last non-EOT event is removed?197// Or: document that the ticks() length will not be reduced198// by deleting events (unless the EOT event is removed)199synchronized(eventsList) {200if (set.remove(event)) {201int i = eventsList.indexOf(event);202if (i >= 0) {203eventsList.remove(i);204return true;205}206}207}208return false;209}210211/**212* Obtains the event at the specified index.213*214* @param index the location of the desired event in the event vector215* @return the event at the specified index216* @throws ArrayIndexOutOfBoundsException if the specified index is negative217* or not less than the current size of this track218* @see #size219*/220public MidiEvent get(int index) throws ArrayIndexOutOfBoundsException {221try {222synchronized(eventsList) {223return eventsList.get(index);224}225} catch (IndexOutOfBoundsException ioobe) {226throw new ArrayIndexOutOfBoundsException(ioobe.getMessage());227}228}229230/**231* Obtains the number of events in this track.232*233* @return the size of the track's event vector234*/235public int size() {236synchronized(eventsList) {237return eventsList.size();238}239}240241/**242* Obtains the length of the track, expressed in MIDI ticks. (The duration243* of a tick in seconds is determined by the timing resolution of the244* {@code Sequence} containing this track, and also by the tempo of the245* music as set by the sequencer.)246*247* @return the duration, in ticks248* @see Sequence#Sequence(float, int)249* @see Sequencer#setTempoInBPM(float)250* @see Sequencer#getTickPosition()251*/252public long ticks() {253long ret = 0;254synchronized (eventsList) {255if (eventsList.size() > 0) {256ret = (eventsList.get(eventsList.size() - 1)).getTick();257}258}259return ret;260}261262private static class ImmutableEndOfTrack extends MetaMessage {263private ImmutableEndOfTrack() {264super(new byte[3]);265data[0] = (byte) META;266data[1] = MidiUtils.META_END_OF_TRACK_TYPE;267data[2] = 0;268}269270@Override271public void setMessage(int type, byte[] data, int length) throws InvalidMidiDataException {272throw new InvalidMidiDataException("cannot modify end of track message");273}274}275}276277278