/* * This file is modified by Ivan Maidanski <ivmai@ivmaisoft.com> * Project name: JCGO-SUNAWT (http://www.ivmaisoft.com/jcgo/) ** * Comment: not really modified but is a part of JSound "front-end". */ /* * @(#)Track.java 1.19 03/01/23 * * Copyright 2003 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */ package javax.sound.midi; import java.util.Vector; /** * A MIDI track is an independent stream of MIDI events (time-stamped MIDI * data) that can be stored along with other tracks in a standard MIDI file. * The MIDI specification allows only 16 channels of MIDI data, but tracks * are a way to get around this limitation. A MIDI file can contain any number * of tracks, each containing its own stream of up to 16 channels of MIDI data. * <p> * A <code>Track</code> occupies a middle level in the hierarchy of data played * by a <code>{@link Sequencer}</code>: sequencers play sequences, which contain tracks, * which contain MIDI events. A sequencer may provide controls that mute * or solo individual tracks. * <p> * The timing information and resolution for a track is controlled by and stored * in the sequence containing the track. A given <code>Track</code> * is considered to belong to the particular <code>{@link Sequence}</code> that * maintains its timing. For this reason, a new (empty) track is created by calling the * <code>{@link Sequence#createTrack}</code> method, rather than by directly invoking a * <code>Track</code> constructor. * <p> * The <code>Track</code> class provides methods to edit the track by adding * or removing <code>MidiEvent</code> objects from it. These operations keep * the event list in the correct time order. Methods are also * included to obtain the track's size, in terms of either the number of events * it contains or its duration in ticks. * * @see Sequencer#setTrackMute * @see Sequencer#setTrackSolo * * @version 1.19, 03/01/23 * @author Kara Kytle */ public class Track { /** * The list of <code>MidiEvents</code> contained in this track. */ protected Vector events = new Vector(); // $$fb 2002-07-17: use a hashset to detect same events in add(MidiEvent) // this requires at least JDK1.2 private java.util.HashSet set = new java.util.HashSet(); /** * Package-private constructor. Constructs a new, empty Track object, * which initially contains one event, the meta-event End of Track. */ Track() { // $$jb: 10.18.99: start with the end of track event MetaMessage eot = new MetaMessage(); try { eot.setMessage( 0x2F, new byte[0], 0 ); } catch (InvalidMidiDataException e) { // this should never happen. } MidiEvent eotevent = new MidiEvent( eot, 0 ); events.addElement( eotevent ); set.add(eotevent); } /** * Adds a new event to the track. However, if the event is already * contained in the track, it is not added again. The list of events * is kept in time order, meaning that this event inserted at the * appropriate place in the list, not necessarily at the end. * * @param event the event to add * @return <code>true</code> if the event did not already exist in the * track and was added, otherwise <code>false</code> */ public boolean add(MidiEvent event) { // $$kk: 01.21.99: i guess i will refuse to add the event if // it already exists in the event vector. otherwise people will // do event.setData(...) and add(event) and create a disaster. int i; synchronized(events) { if ( !set.contains(event) ) { // $$jb: 10.18.99: first see if we are trying to add // and endoftrack event. since this event is useful // for delays at the end of a track, we want to keep // the tick value requested here if it is greater // than the one on the eot we are maintaining. otherwise, // we only want a single eot event, so ignore. if( event.getMessage().getStatus() == 0xff ) { MetaMessage mm = (MetaMessage)(event.getMessage()); if (mm.getType() == 0x2f) { MidiEvent eot = (MidiEvent) events.elementAt( events.size()-1 ); if( event.getTick() > eot.getTick() ) { eot.setTick( event.getTick() ); } return true; } } // insert event such that events is sorted in increasing // tick order set.add(event); if(events.size()==0) { events.addElement(event); return true; } else { for( i=events.size(); i > 0; i-- ) { if( event.getTick() >= ((MidiEvent)events.elementAt(i-1)).getTick() ) { break; } } if( i==events.size() ) { // $$jb: 10.18.99: we're adding an event after the // tick value of our eot, so push the eot out ((MidiEvent)events.elementAt(i-1)).setTick( event.getTick() ); events.insertElementAt(event, (i-1) ); } else { events.insertElementAt(event,i); } return true; } } } return false; } /** * Removes the specified event from the track. * @param event the event to remove * @return <code>true</code> if the event existed in the track and was removed, * otherwise <code>false</code> */ public boolean remove(MidiEvent event) { synchronized(events) { set.remove(event); return events.removeElement(event); } } /** * Obtains the event at the specified index. * @param index the location of the desired event in the event vector * @throws <code>ArrayIndexOutOfBoundsException</code> if the * specified index is negative or not less than the current size of * this track. * @see #size */ public MidiEvent get(int index) throws ArrayIndexOutOfBoundsException { return (MidiEvent)events.elementAt(index); // can throw ArrayIndexOutOfBoundsException } /** * Obtains the number of events in this track. * @return the size of the track's event vector */ public int size() { return events.size(); } /** * Obtains the length of the track, expressed in MIDI ticks. (The * duration of a tick in seconds is determined by the timing resolution * of the <code>Sequence</code> containing this track, and also by * the tempo of the music as set by the sequencer.) * @return the duration, in ticks * @see Sequence#Sequence(float, int) * @see Sequencer#setTempoInBPM(float) * @see Sequencer#getTickPosition() */ public long ticks() { return ((MidiEvent)events.lastElement()).getTick(); } }