Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/com/sun/media/sound/MidiUtils.java
38924 views
/*1* Copyright (c) 2003, 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 com.sun.media.sound;2627import javax.sound.midi.*;28import java.util.ArrayList;2930// TODO:31// - define and use a global symbolic constant for 60000000 (see convertTempo)3233/**34* Some utilities for MIDI (some stuff is used from javax.sound.midi)35*36* @author Florian Bomers37*/38public final class MidiUtils {3940public final static int DEFAULT_TEMPO_MPQ = 500000; // 120bpm41public final static int META_END_OF_TRACK_TYPE = 0x2F;42public final static int META_TEMPO_TYPE = 0x51;4344/**45* Suppresses default constructor, ensuring non-instantiability.46*/47private MidiUtils() {48}4950/** return true if the passed message is Meta End Of Track */51public static boolean isMetaEndOfTrack(MidiMessage midiMsg) {52// first check if it is a META message at all53if (midiMsg.getLength() != 354|| midiMsg.getStatus() != MetaMessage.META) {55return false;56}57// now get message and check for end of track58byte[] msg = midiMsg.getMessage();59return ((msg[1] & 0xFF) == META_END_OF_TRACK_TYPE) && (msg[2] == 0);60}616263/** return if the given message is a meta tempo message */64public static boolean isMetaTempo(MidiMessage midiMsg) {65// first check if it is a META message at all66if (midiMsg.getLength() != 667|| midiMsg.getStatus() != MetaMessage.META) {68return false;69}70// now get message and check for tempo71byte[] msg = midiMsg.getMessage();72// meta type must be 0x51, and data length must be 373return ((msg[1] & 0xFF) == META_TEMPO_TYPE) && (msg[2] == 3);74}757677/** parses this message for a META tempo message and returns78* the tempo in MPQ, or -1 if this isn't a tempo message79*/80public static int getTempoMPQ(MidiMessage midiMsg) {81// first check if it is a META message at all82if (midiMsg.getLength() != 683|| midiMsg.getStatus() != MetaMessage.META) {84return -1;85}86byte[] msg = midiMsg.getMessage();87if (((msg[1] & 0xFF) != META_TEMPO_TYPE) || (msg[2] != 3)) {88return -1;89}90int tempo = (msg[5] & 0xFF)91| ((msg[4] & 0xFF) << 8)92| ((msg[3] & 0xFF) << 16);93return tempo;94}959697/**98* converts<br>99* 1 - MPQ-Tempo to BPM tempo<br>100* 2 - BPM tempo to MPQ tempo<br>101*/102public static double convertTempo(double tempo) {103if (tempo <= 0) {104tempo = 1;105}106return ((double) 60000000l) / tempo;107}108109110/**111* convert tick to microsecond with given tempo.112* Does not take tempo changes into account.113* Does not work for SMPTE timing!114*/115public static long ticks2microsec(long tick, double tempoMPQ, int resolution) {116return (long) (((double) tick) * tempoMPQ / resolution);117}118119/**120* convert tempo to microsecond with given tempo121* Does not take tempo changes into account.122* Does not work for SMPTE timing!123*/124public static long microsec2ticks(long us, double tempoMPQ, int resolution) {125// do not round to nearest tick126//return (long) Math.round((((double)us) * resolution) / tempoMPQ);127return (long) ((((double)us) * resolution) / tempoMPQ);128}129130131/**132* Given a tick, convert to microsecond133* @param cache tempo info and current tempo134*/135public static long tick2microsecond(Sequence seq, long tick, TempoCache cache) {136if (seq.getDivisionType() != Sequence.PPQ ) {137double seconds = ((double)tick / (double)(seq.getDivisionType() * seq.getResolution()));138return (long) (1000000 * seconds);139}140141if (cache == null) {142cache = new TempoCache(seq);143}144145int resolution = seq.getResolution();146147long[] ticks = cache.ticks;148int[] tempos = cache.tempos; // in MPQ149int cacheCount = tempos.length;150151// optimization to not always go through entire list of tempo events152int snapshotIndex = cache.snapshotIndex;153int snapshotMicro = cache.snapshotMicro;154155// walk through all tempo changes and add time for the respective blocks156long us = 0; // microsecond157158if (snapshotIndex <= 0159|| snapshotIndex >= cacheCount160|| ticks[snapshotIndex] > tick) {161snapshotMicro = 0;162snapshotIndex = 0;163}164if (cacheCount > 0) {165// this implementation needs a tempo event at tick 0!166int i = snapshotIndex + 1;167while (i < cacheCount && ticks[i] <= tick) {168snapshotMicro += ticks2microsec(ticks[i] - ticks[i - 1], tempos[i - 1], resolution);169snapshotIndex = i;170i++;171}172us = snapshotMicro173+ ticks2microsec(tick - ticks[snapshotIndex],174tempos[snapshotIndex],175resolution);176}177cache.snapshotIndex = snapshotIndex;178cache.snapshotMicro = snapshotMicro;179return us;180}181182/**183* Given a microsecond time, convert to tick.184* returns tempo at the given time in cache.getCurrTempoMPQ185*/186public static long microsecond2tick(Sequence seq, long micros, TempoCache cache) {187if (seq.getDivisionType() != Sequence.PPQ ) {188double dTick = ( ((double) micros)189* ((double) seq.getDivisionType())190* ((double) seq.getResolution()))191/ ((double) 1000000);192long tick = (long) dTick;193if (cache != null) {194cache.currTempo = (int) cache.getTempoMPQAt(tick);195}196return tick;197}198199if (cache == null) {200cache = new TempoCache(seq);201}202long[] ticks = cache.ticks;203int[] tempos = cache.tempos; // in MPQ204int cacheCount = tempos.length;205206int resolution = seq.getResolution();207208long us = 0; long tick = 0; int newReadPos = 0; int i = 1;209210// walk through all tempo changes and add time for the respective blocks211// to find the right tick212if (micros > 0 && cacheCount > 0) {213// this loop requires that the first tempo Event is at time 0214while (i < cacheCount) {215long nextTime = us + ticks2microsec(ticks[i] - ticks[i - 1],216tempos[i - 1], resolution);217if (nextTime > micros) {218break;219}220us = nextTime;221i++;222}223tick = ticks[i - 1] + microsec2ticks(micros - us, tempos[i - 1], resolution);224if (Printer.debug) Printer.debug("microsecond2tick(" + (micros / 1000)+") = "+tick+" ticks.");225//if (Printer.debug) Printer.debug(" -> convert back = " + (tick2microsecond(seq, tick, null) / 1000)+" microseconds");226}227cache.currTempo = tempos[i - 1];228return tick;229}230231232/**233* Binary search for the event indexes of the track234*235* @param tick - tick number of index to be found in array236* @return index in track which is on or after "tick".237* if no entries are found that follow after tick, track.size() is returned238*/239public static int tick2index(Track track, long tick) {240int ret = 0;241if (tick > 0) {242int low = 0;243int high = track.size() - 1;244while (low < high) {245// take the middle event as estimate246ret = (low + high) >> 1;247// tick of estimate248long t = track.get(ret).getTick();249if (t == tick) {250break;251} else if (t < tick) {252// estimate too low253if (low == high - 1) {254// "or after tick"255ret++;256break;257}258low = ret;259} else { // if (t>tick)260// estimate too high261high = ret;262}263}264}265return ret;266}267268269public static final class TempoCache {270long[] ticks;271int[] tempos; // in MPQ272// index in ticks/tempos at the snapshot273int snapshotIndex = 0;274// microsecond at the snapshot275int snapshotMicro = 0;276277int currTempo; // MPQ, used as return value for microsecond2tick278279private boolean firstTempoIsFake = false;280281public TempoCache() {282// just some defaults, to prevents weird stuff283ticks = new long[1];284tempos = new int[1];285tempos[0] = DEFAULT_TEMPO_MPQ;286snapshotIndex = 0;287snapshotMicro = 0;288}289290public TempoCache(Sequence seq) {291this();292refresh(seq);293}294295296public synchronized void refresh(Sequence seq) {297ArrayList list = new ArrayList();298Track[] tracks = seq.getTracks();299if (tracks.length > 0) {300// tempo events only occur in track 0301Track track = tracks[0];302int c = track.size();303for (int i = 0; i < c; i++) {304MidiEvent ev = track.get(i);305MidiMessage msg = ev.getMessage();306if (isMetaTempo(msg)) {307// found a tempo event. Add it to the list308list.add(ev);309}310}311}312int size = list.size() + 1;313firstTempoIsFake = true;314if ((size > 1)315&& (((MidiEvent) list.get(0)).getTick() == 0)) {316// do not need to add an initial tempo event at the beginning317size--;318firstTempoIsFake = false;319}320ticks = new long[size];321tempos = new int[size];322int e = 0;323if (firstTempoIsFake) {324// add tempo 120 at beginning325ticks[0] = 0;326tempos[0] = DEFAULT_TEMPO_MPQ;327e++;328}329for (int i = 0; i < list.size(); i++, e++) {330MidiEvent evt = (MidiEvent) list.get(i);331ticks[e] = evt.getTick();332tempos[e] = getTempoMPQ(evt.getMessage());333}334snapshotIndex = 0;335snapshotMicro = 0;336}337338public int getCurrTempoMPQ() {339return currTempo;340}341342float getTempoMPQAt(long tick) {343return getTempoMPQAt(tick, -1.0f);344}345346synchronized float getTempoMPQAt(long tick, float startTempoMPQ) {347for (int i = 0; i < ticks.length; i++) {348if (ticks[i] > tick) {349if (i > 0) i--;350if (startTempoMPQ > 0 && i == 0 && firstTempoIsFake) {351return startTempoMPQ;352}353return (float) tempos[i];354}355}356return tempos[tempos.length - 1];357}358359}360}361362363