/*
* Copyright (C) 2013-2017 たんらる
*/
package fourthline.mmlTools;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import fourthline.mmlTools.core.MMLTickTable;
public final class MMLTempoEvent extends MMLEvent implements Cloneable {
private static final long serialVersionUID = 8014294359518840951L;
private int tempo;
public static final int META = 0x51; /* MIDI meta: tempo */
public static final int INITIAL_TEMPO = 120;
public MMLTempoEvent(int tempo, int tickOffset) throws IllegalArgumentException {
super(tickOffset);
if (tempo <= 0) {
throw new IllegalArgumentException("tempo "+tempo);
}
this.tempo = tempo;
}
public int getTempo() {
return this.tempo;
}
public void setTempo(int tempo) {
this.tempo = tempo;
}
public byte[] getMetaData() {
ByteBuffer buf = ByteBuffer.allocate(4);
buf.putInt(60000000/tempo);
byte array[] = buf.array();
return Arrays.copyOfRange(array, 1, array.length);
}
@Override
public String toString() {
return getTickOffset()+"T" + tempo;
}
/**
* toString() でつくった文字列からオブジェクトを生成する.
* @param str
*/
public static MMLTempoEvent fromString(String str) {
if ((str == null) || (str.length() == 0)) {
return null;
}
String s[] = str.split("T");
if (s.length != 2) {
return null;
}
return new MMLTempoEvent(Integer.parseInt(s[1]), Integer.parseInt(s[0]));
}
@Override
public String toMMLString() {
return "t" + tempo;
}
/**
* 指定されたListにオブジェクトを追加します.
* @param list
*/
public void appendToListElement(List<MMLTempoEvent> list) {
int index = 0;
int targetOffset = getTickOffset();
for ( ; index < list.size(); index++) {
MMLTempoEvent tempoEvent = list.get(index);
if (tempoEvent.getTickOffset() == targetOffset) {
list.remove(index);
break;
} else if (tempoEvent.getTickOffset() > targetOffset) {
break;
}
}
list.add(index, this);
}
public static List<MMLTempoEvent> mergeTempoList(List<MMLTempoEvent> list1, List<MMLTempoEvent> list2) {
for (MMLTempoEvent tempoEvent : list1) {
tempoEvent.appendToListElement(list2);
}
return list2;
}
public static int searchOnTick(List<MMLTempoEvent> tempoList, long tickOffset) {
int tempo = INITIAL_TEMPO;
for (MMLTempoEvent tempoEvent : tempoList) {
if (tickOffset < tempoEvent.getTickOffset()) {
break;
}
tempo = tempoEvent.getTempo();
}
return tempo;
}
public static boolean searchEqualsTick(List<MMLTempoEvent> tempoList, long tickOffset) {
for (MMLTempoEvent tempo : tempoList) {
if (tempo.getTickOffset() == tickOffset) {
return true;
} else if (tempo.getTickOffset() > tickOffset) {
break;
}
}
return false;
}
/**
* 指定したtickオフセット位置の先頭からの時間を返します.
* @param tempoList
* @param tickOffset
* @return 先頭からの時間(ms)
*/
public static long getTimeOnTickOffset(List<MMLTempoEvent> tempoList, int tickOffset) {
long totalTime = 0L;
int tempo = INITIAL_TEMPO;
int currentTick = 0;
for (MMLTempoEvent tempoEvent : tempoList) {
int currentTempoTick = tempoEvent.getTickOffset();
if (tickOffset < currentTempoTick) {
break;
}
int currentTempo = tempoEvent.getTempo();
if (tempo != currentTempo) {
totalTime += (currentTempoTick - currentTick) * 60 / tempo * 1000;
currentTick = currentTempoTick;
}
tempo = currentTempo;
}
totalTime += (tickOffset - currentTick) * 60 / tempo * 1000;
totalTime /= (double) MMLTickTable.TPQN;
return totalTime;
}
/**
* 指定した時間からtickオフセットを返します.
* @param tempoList
* @param time
* @return 先頭からの時間(ms)
*/
public static long getTickOffsetOnTime(List<MMLTempoEvent> tempoList, long time) {
int tempo = INITIAL_TEMPO;
long pointTime = 0;
long tick = 0;
for (MMLTempoEvent tempoEvent : tempoList) {
long tempoTime = getTimeOnTickOffset(tempoList, tempoEvent.getTickOffset());
if (time <= tempoTime) {
break;
}
pointTime = tempoTime;
tempo = tempoEvent.getTempo();
tick = tempoEvent.getTickOffset();
}
tick += (time - pointTime) * MMLTickTable.TPQN * tempo / 60 / 1000;
return tick;
}
/**
* テンポリスト中の最大テンポ値を取得します.
* @param tempoList
* @return
*/
public static MMLTempoEvent getMaxTempoEvent(List<MMLTempoEvent> tempoList) {
MMLTempoEvent maxEvent = new MMLTempoEvent(INITIAL_TEMPO, 0);
for (MMLTempoEvent tempoEvent : tempoList) {
int currentTempo = tempoEvent.getTempo();
if (maxEvent.getTempo() < currentTempo) {
maxEvent.setTempo(currentTempo);
}
}
return maxEvent;
}
@Override
public MMLTempoEvent clone() throws CloneNotSupportedException {
return (MMLTempoEvent) super.clone();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof MMLTempoEvent)) {
return false;
}
MMLTempoEvent tempoEvent = (MMLTempoEvent) obj;
if ((this.tempo == tempoEvent.tempo) &&
(super.equals(tempoEvent))) {
return true;
}
return false;
}
}