Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.desktop/share/classes/javax/sound/midi/Track.java
41159 views
1
/*
2
* Copyright (c) 1999, 2017, 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
}
277
278