Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/javax/sound/midi/Track.java
38830 views
/*1* Copyright (c) 1999, 2013, 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.Vector;28import java.util.ArrayList;29import java.util.HashSet;30import com.sun.media.sound.MidiUtils;3132/**33* A MIDI track is an independent stream of MIDI events (time-stamped MIDI34* data) that can be stored along with other tracks in a standard MIDI file.35* The MIDI specification allows only 16 channels of MIDI data, but tracks36* are a way to get around this limitation. A MIDI file can contain any number37* of tracks, each containing its own stream of up to 16 channels of MIDI data.38* <p>39* A <code>Track</code> occupies a middle level in the hierarchy of data played40* by a <code>{@link Sequencer}</code>: sequencers play sequences, which contain tracks,41* which contain MIDI events. A sequencer may provide controls that mute42* or solo 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</code>46* is considered to belong to the particular <code>{@link Sequence}</code> that47* maintains its timing. For this reason, a new (empty) track is created by calling the48* <code>{@link Sequence#createTrack}</code> method, rather than by directly invoking a49* <code>Track</code> constructor.50* <p>51* The <code>Track</code> class provides methods to edit the track by adding52* or removing <code>MidiEvent</code> objects from it. These operations keep53* the event list in the correct time order. Methods are also54* included to obtain the track's size, in terms of either the number of events55* it contains or its duration in ticks.56*57* @see Sequencer#setTrackMute58* @see Sequencer#setTrackSolo59*60* @author Kara Kytle61* @author Florian Bomers62*/63public class Track {6465// TODO: use arrays for faster access6667// the list containing the events68private ArrayList eventsList = new ArrayList();6970// use a hashset to detect duplicate events in add(MidiEvent)71private HashSet set = new HashSet();7273private MidiEvent eotEvent;747576/**77* Package-private constructor. Constructs a new, empty Track object,78* which initially contains one event, the meta-event End of Track.79*/80Track() {81// start with the end of track event82MetaMessage eot = new ImmutableEndOfTrack();83eotEvent = new MidiEvent(eot, 0);84eventsList.add(eotEvent);85set.add(eotEvent);86}8788/**89* Adds a new event to the track. However, if the event is already90* contained in the track, it is not added again. The list of events91* is kept in time order, meaning that this event inserted at the92* appropriate place in the list, not necessarily at the end.93*94* @param event the event to add95* @return <code>true</code> if the event did not already exist in the96* track and was added, otherwise <code>false</code>97*/98public boolean add(MidiEvent event) {99if (event == null) {100return false;101}102synchronized(eventsList) {103104if (!set.contains(event)) {105int eventsCount = eventsList.size();106107// get the last event108MidiEvent lastEvent = null;109if (eventsCount > 0) {110lastEvent = (MidiEvent) eventsList.get(eventsCount - 1);111}112// sanity check that we have a correct end-of-track113if (lastEvent != eotEvent) {114// if there is no eot event, add our immutable instance again115if (lastEvent != null) {116// set eotEvent's tick to the last tick of the track117eotEvent.setTick(lastEvent.getTick());118} else {119// if the events list is empty, just set the tick to 0120eotEvent.setTick(0);121}122// we needn't check for a duplicate of eotEvent in "eventsList",123// since then it would appear in the set.124eventsList.add(eotEvent);125set.add(eotEvent);126eventsCount = eventsList.size();127}128129// first see if we are trying to add130// and endoftrack event.131if (MidiUtils.isMetaEndOfTrack(event.getMessage())) {132// since end of track event is useful133// for delays at the end of a track, we want to keep134// the tick value requested here if it is greater135// than the one on the eot we are maintaining.136// Otherwise, we only want a single eot event, so ignore.137if (event.getTick() > eotEvent.getTick()) {138eotEvent.setTick(event.getTick());139}140return true;141}142143// prevent duplicates144set.add(event);145146// insert event such that events is sorted in increasing147// tick order148int i = eventsCount;149for ( ; i > 0; i--) {150if (event.getTick() >= ((MidiEvent)eventsList.get(i-1)).getTick()) {151break;152}153}154if (i == eventsCount) {155// we're adding an event after the156// tick value of our eot, so push the eot out.157// Always add at the end for better performance:158// this saves all the checks and arraycopy when inserting159160// overwrite eot with new event161eventsList.set(eventsCount - 1, event);162// set new time of eot, if necessary163if (eotEvent.getTick() < event.getTick()) {164eotEvent.setTick(event.getTick());165}166// add eot again at the end167eventsList.add(eotEvent);168} else {169eventsList.add(i, event);170}171return true;172}173}174175return false;176}177178179/**180* Removes the specified event from the track.181* @param event the event to remove182* @return <code>true</code> if the event existed in the track and was removed,183* otherwise <code>false</code>184*/185public boolean remove(MidiEvent event) {186187// this implementation allows removing the EOT event.188// pretty bad, but would probably be too risky to189// change behavior now, in case someone does tricks like:190//191// while (track.size() > 0) track.remove(track.get(track.size() - 1));192193// also, would it make sense to adjust the EOT's time194// to the last event, if the last non-EOT event is removed?195// Or: document that the ticks() length will not be reduced196// by deleting events (unless the EOT event is removed)197synchronized(eventsList) {198if (set.remove(event)) {199int i = eventsList.indexOf(event);200if (i >= 0) {201eventsList.remove(i);202return true;203}204}205}206return false;207}208209210/**211* Obtains the event at the specified index.212* @param index the location of the desired event in the event vector213* @throws ArrayIndexOutOfBoundsException if the214* specified index is negative or not less than the current size of215* this track.216* @see #size217* @return the event at the specified index218*/219public MidiEvent get(int index) throws ArrayIndexOutOfBoundsException {220try {221synchronized(eventsList) {222return (MidiEvent)eventsList.get(index);223}224} catch (IndexOutOfBoundsException ioobe) {225throw new ArrayIndexOutOfBoundsException(ioobe.getMessage());226}227}228229230/**231* Obtains the number of events in this track.232* @return the size of the track's event vector233*/234public int size() {235synchronized(eventsList) {236return eventsList.size();237}238}239240241/**242* Obtains the length of the track, expressed in MIDI ticks. (The243* duration of a tick in seconds is determined by the timing resolution244* of the <code>Sequence</code> containing this track, and also by245* the tempo of the music as set by the sequencer.)246* @return the duration, in ticks247* @see Sequence#Sequence(float, int)248* @see Sequencer#setTempoInBPM(float)249* @see Sequencer#getTickPosition()250*/251public long ticks() {252long ret = 0;253synchronized (eventsList) {254if (eventsList.size() > 0) {255ret = ((MidiEvent)eventsList.get(eventsList.size() - 1)).getTick();256}257}258return ret;259}260261private static class ImmutableEndOfTrack extends MetaMessage {262private ImmutableEndOfTrack() {263super(new byte[3]);264data[0] = (byte) META;265data[1] = MidiUtils.META_END_OF_TRACK_TYPE;266data[2] = 0;267}268269public void setMessage(int type, byte[] data, int length) throws InvalidMidiDataException {270throw new InvalidMidiDataException("cannot modify end of track message");271}272}273274}275276277