blob: a18be514bd5bc2d87c2dad01a15fb5b5b2938c57 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2003-2007 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package com.sun.media.sound;
27
28import javax.sound.midi.*;
29import java.util.ArrayList;
30
31// TODO:
32// - define and use a global symbolic constant for 60000000 (see convertTempo)
33
34/**
35 * Some utilities for MIDI (some stuff is used from javax.sound.midi)
36 *
37 * @author Florian Bomers
38 */
39public class MidiUtils {
40
41 public final static int DEFAULT_TEMPO_MPQ = 500000; // 120bpm
42 public final static int META_END_OF_TRACK_TYPE = 0x2F;
43 public final static int META_TEMPO_TYPE = 0x51;
44
45
46 /** return true if the passed message is Meta End Of Track */
47 public static boolean isMetaEndOfTrack(MidiMessage midiMsg) {
48 // first check if it is a META message at all
49 if (midiMsg.getLength() != 3
50 || midiMsg.getStatus() != MetaMessage.META) {
51 return false;
52 }
53 // now get message and check for end of track
54 byte[] msg = midiMsg.getMessage();
55 return ((msg[1] & 0xFF) == META_END_OF_TRACK_TYPE) && (msg[2] == 0);
56 }
57
58
59 /** return if the given message is a meta tempo message */
60 public static boolean isMetaTempo(MidiMessage midiMsg) {
61 // first check if it is a META message at all
62 if (midiMsg.getLength() != 6
63 || midiMsg.getStatus() != MetaMessage.META) {
64 return false;
65 }
66 // now get message and check for tempo
67 byte[] msg = midiMsg.getMessage();
68 // meta type must be 0x51, and data length must be 3
69 return ((msg[1] & 0xFF) == META_TEMPO_TYPE) && (msg[2] == 3);
70 }
71
72
73 /** parses this message for a META tempo message and returns
74 * the tempo in MPQ, or -1 if this isn't a tempo message
75 */
76 public static int getTempoMPQ(MidiMessage midiMsg) {
77 // first check if it is a META message at all
78 if (midiMsg.getLength() != 6
79 || midiMsg.getStatus() != MetaMessage.META) {
80 return -1;
81 }
82 byte[] msg = midiMsg.getMessage();
83 if (((msg[1] & 0xFF) != META_TEMPO_TYPE) || (msg[2] != 3)) {
84 return -1;
85 }
86 int tempo = (msg[5] & 0xFF)
87 | ((msg[4] & 0xFF) << 8)
88 | ((msg[3] & 0xFF) << 16);
89 return tempo;
90 }
91
92
93 /**
94 * converts<br>
95 * 1 - MPQ-Tempo to BPM tempo<br>
96 * 2 - BPM tempo to MPQ tempo<br>
97 */
98 public static double convertTempo(double tempo) {
99 if (tempo <= 0) {
100 tempo = 1;
101 }
102 return ((double) 60000000l) / tempo;
103 }
104
105
106 /**
107 * convert tick to microsecond with given tempo.
108 * Does not take tempo changes into account.
109 * Does not work for SMPTE timing!
110 */
111 public static long ticks2microsec(long tick, double tempoMPQ, int resolution) {
112 return (long) (((double) tick) * tempoMPQ / resolution);
113 }
114
115 /**
116 * convert tempo to microsecond with given tempo
117 * Does not take tempo changes into account.
118 * Does not work for SMPTE timing!
119 */
120 public static long microsec2ticks(long us, double tempoMPQ, int resolution) {
121 // do not round to nearest tick
122 //return (long) Math.round((((double)us) * resolution) / tempoMPQ);
123 return (long) ((((double)us) * resolution) / tempoMPQ);
124 }
125
126
127 /**
128 * Given a tick, convert to microsecond
129 * @param cache tempo info and current tempo
130 */
131 public static long tick2microsecond(Sequence seq, long tick, TempoCache cache) {
132 if (seq.getDivisionType() != Sequence.PPQ ) {
133 double seconds = ((double)tick / (double)(seq.getDivisionType() * seq.getResolution()));
134 return (long) (1000000 * seconds);
135 }
136
137 if (cache == null) {
138 cache = new TempoCache(seq);
139 }
140
141 int resolution = seq.getResolution();
142
143 long[] ticks = cache.ticks;
144 int[] tempos = cache.tempos; // in MPQ
145 int cacheCount = tempos.length;
146
147 // optimization to not always go through entire list of tempo events
148 int snapshotIndex = cache.snapshotIndex;
149 int snapshotMicro = cache.snapshotMicro;
150
151 // walk through all tempo changes and add time for the respective blocks
152 long us = 0; // microsecond
153
154 if (snapshotIndex <= 0
155 || snapshotIndex >= cacheCount
156 || ticks[snapshotIndex] > tick) {
157 snapshotMicro = 0;
158 snapshotIndex = 0;
159 }
160 if (cacheCount > 0) {
161 // this implementation needs a tempo event at tick 0!
162 int i = snapshotIndex + 1;
163 while (i < cacheCount && ticks[i] <= tick) {
164 snapshotMicro += ticks2microsec(ticks[i] - ticks[i - 1], tempos[i - 1], resolution);
165 snapshotIndex = i;
166 i++;
167 }
168 us = snapshotMicro
169 + ticks2microsec(tick - ticks[snapshotIndex],
170 tempos[snapshotIndex],
171 resolution);
172 }
173 cache.snapshotIndex = snapshotIndex;
174 cache.snapshotMicro = snapshotMicro;
175 return us;
176 }
177
178 /**
179 * Given a microsecond time, convert to tick.
180 * returns tempo at the given time in cache.getCurrTempoMPQ
181 */
182 public static long microsecond2tick(Sequence seq, long micros, TempoCache cache) {
183 if (seq.getDivisionType() != Sequence.PPQ ) {
184 double dTick = ( ((double) micros)
185 * ((double) seq.getDivisionType())
186 * ((double) seq.getResolution()))
187 / ((double) 1000000);
188 long tick = (long) dTick;
189 if (cache != null) {
190 cache.currTempo = (int) cache.getTempoMPQAt(tick);
191 }
192 return tick;
193 }
194
195 if (cache == null) {
196 cache = new TempoCache(seq);
197 }
198 long[] ticks = cache.ticks;
199 int[] tempos = cache.tempos; // in MPQ
200 int cacheCount = tempos.length;
201
202 int resolution = seq.getResolution();
203
204 long us = 0; long tick = 0; int newReadPos = 0; int i = 1;
205
206 // walk through all tempo changes and add time for the respective blocks
207 // to find the right tick
208 if (micros > 0 && cacheCount > 0) {
209 // this loop requires that the first tempo Event is at time 0
210 while (i < cacheCount) {
211 long nextTime = us + ticks2microsec(ticks[i] - ticks[i - 1],
212 tempos[i - 1], resolution);
213 if (nextTime > micros) {
214 break;
215 }
216 us = nextTime;
217 i++;
218 }
219 tick = ticks[i - 1] + microsec2ticks(micros - us, tempos[i - 1], resolution);
220 if (Printer.debug) Printer.debug("microsecond2tick(" + (micros / 1000)+") = "+tick+" ticks.");
221 //if (Printer.debug) Printer.debug(" -> convert back = " + (tick2microsecond(seq, tick, null) / 1000)+" microseconds");
222 }
223 cache.currTempo = tempos[i - 1];
224 return tick;
225 }
226
227
228 /**
229 * Binary search for the event indexes of the track
230 *
231 * @param tick - tick number of index to be found in array
232 * @return index in track which is on or after "tick".
233 * if no entries are found that follow after tick, track.size() is returned
234 */
235 public static int tick2index(Track track, long tick) {
236 int ret = 0;
237 if (tick > 0) {
238 int low = 0;
239 int high = track.size() - 1;
240 while (low < high) {
241 // take the middle event as estimate
242 ret = (low + high) >> 1;
243 // tick of estimate
244 long t = track.get(ret).getTick();
245 if (t == tick) {
246 break;
247 } else if (t < tick) {
248 // estimate too low
249 if (low == high - 1) {
250 // "or after tick"
251 ret++;
252 break;
253 }
254 low = ret;
255 } else { // if (t>tick)
256 // estimate too high
257 high = ret;
258 }
259 }
260 }
261 return ret;
262 }
263
264
265 public static class TempoCache {
266 long[] ticks;
267 int[] tempos; // in MPQ
268 // index in ticks/tempos at the snapshot
269 int snapshotIndex = 0;
270 // microsecond at the snapshot
271 int snapshotMicro = 0;
272
273 int currTempo; // MPQ, used as return value for microsecond2tick
274
275 private boolean firstTempoIsFake = false;
276
277 public TempoCache() {
278 // just some defaults, to prevents weird stuff
279 ticks = new long[1];
280 tempos = new int[1];
281 tempos[0] = DEFAULT_TEMPO_MPQ;
282 snapshotIndex = 0;
283 snapshotMicro = 0;
284 }
285
286 public TempoCache(Sequence seq) {
287 this();
288 refresh(seq);
289 }
290
291
292 public synchronized void refresh(Sequence seq) {
293 ArrayList list = new ArrayList();
294 Track[] tracks = seq.getTracks();
295 if (tracks.length > 0) {
296 // tempo events only occur in track 0
297 Track track = tracks[0];
298 int c = track.size();
299 for (int i = 0; i < c; i++) {
300 MidiEvent ev = track.get(i);
301 MidiMessage msg = ev.getMessage();
302 if (isMetaTempo(msg)) {
303 // found a tempo event. Add it to the list
304 list.add(ev);
305 }
306 }
307 }
308 int size = list.size() + 1;
309 firstTempoIsFake = true;
310 if ((size > 1)
311 && (((MidiEvent) list.get(0)).getTick() == 0)) {
312 // do not need to add an initial tempo event at the beginning
313 size--;
314 firstTempoIsFake = false;
315 }
316 ticks = new long[size];
317 tempos = new int[size];
318 int e = 0;
319 if (firstTempoIsFake) {
320 // add tempo 120 at beginning
321 ticks[0] = 0;
322 tempos[0] = DEFAULT_TEMPO_MPQ;
323 e++;
324 }
325 for (int i = 0; i < list.size(); i++, e++) {
326 MidiEvent evt = (MidiEvent) list.get(i);
327 ticks[e] = evt.getTick();
328 tempos[e] = getTempoMPQ(evt.getMessage());
329 }
330 snapshotIndex = 0;
331 snapshotMicro = 0;
332 }
333
334 public int getCurrTempoMPQ() {
335 return currTempo;
336 }
337
338 float getTempoMPQAt(long tick) {
339 return getTempoMPQAt(tick, -1.0f);
340 }
341
342 synchronized float getTempoMPQAt(long tick, float startTempoMPQ) {
343 for (int i = 0; i < ticks.length; i++) {
344 if (ticks[i] > tick) {
345 if (i > 0) i--;
346 if (startTempoMPQ > 0 && i == 0 && firstTempoIsFake) {
347 return startTempoMPQ;
348 }
349 return (float) tempos[i];
350 }
351 }
352 return tempos[tempos.length - 1];
353 }
354
355 }
356}