Save This Page
Home » openjdk-7 » com.sun.media » sound » [javadoc | source]
    1   /*
    2    * Copyright (c) 2003, 2007, 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 com.sun.media.sound;
   27   
   28   import javax.sound.midi;
   29   import 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    */
   39   public 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   }

Save This Page
Home » openjdk-7 » com.sun.media » sound » [javadoc | source]