/*
* Copyright (C) 2013-2017 たんらる
*/
package fourthline.mmlTools;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import fourthline.mmlTools.core.MMLText;
import fourthline.mmlTools.core.MMLTicks;
import fourthline.mmlTools.core.UndefinedTickException;
import fourthline.mmlTools.optimizer.MMLStringOptimizer;
public final class MMLTrack implements Serializable {
private static final long serialVersionUID = 2006880378975808647L;
private static final int PART_COUNT = 4;
private List<MMLEventList> mmlParts = new ArrayList<>();
private List<MMLTempoEvent> globalTempoList = new ArrayList<>();
private boolean generated = false;
private int program = 0;
private String trackName;
private int panpot = 64;
// for MML input
private MMLText originalMML = new MMLText();
// for MML output
private MMLText mabiMML = new MMLText();
// コーラスオプション (楽器+歌)
private int songProgram = -1; // コーラスを使用しない.
public MMLTrack() {
mmlParse();
generated = true;
}
public MMLTrack setMML(String mml) {
if (mml.indexOf('\n') >= 0) {
// ゲーム内からコピーされたフォーマットを読む.
String parts[] = mml.split("\n", 8);
boolean invalidFormat = false;
if (parts.length == 7) {
for (int i = 2; i < parts.length-1; i++) {
int startIndex = parts[i].indexOf(':');
if (startIndex < 0) {
invalidFormat = true;
break;
}
parts[i] = parts[i].substring(startIndex+1).trim();
}
if (!invalidFormat) {
return setMML(parts[2], parts[3], parts[4], parts[5]);
}
}
}
originalMML.setMMLText(mml);
mabiMML.setMMLText(mml);
mmlParse();
return this;
}
public MMLTrack setMML(String mml1, String mml2, String mml3, String mml4) {
originalMML.setMMLText(mml1, mml2, mml3, mml4);
mabiMML.setMMLText(mml1, mml2, mml3, mml4);
mmlParse();
return this;
}
private void mmlParse() {
mmlParts.clear();
generated = false;
for (int i = 0; i < PART_COUNT; i++) {
String s = originalMML.getText(i);
mmlParts.add( new MMLEventList(s, globalTempoList) );
}
}
public boolean isEmpty() {
return originalMML.isEmpty();
}
public String getOriginalMML() {
return originalMML.getMML();
}
public String getMabiMML() {
return mabiMML.getMML();
}
public ComposeRank mmlRank() {
return mabiMML.mmlRank();
}
/**
* 出力用のMMLランク
* @return フォーマット済みRank文字列
*/
public String mmlRankFormat() {
return (generated ? "" : "*") + mabiMML.mmlRankFormat();
}
/**
* 出力用のMMLを取得する.
* @return 各パートのMML文字列
*/
public String[] getMabiMMLArray() {
String mml[] = new String[ PART_COUNT ];
for (int i = 0; i < mml.length; i++) {
mml[i] = mabiMML.getText(i);
}
return mml;
}
public void setGlobalTempoList(List<MMLTempoEvent> globalTempoList) {
this.globalTempoList = globalTempoList;
for (MMLEventList eventList : mmlParts) {
eventList.setGlobalTempoList(globalTempoList);
}
}
public List<MMLTempoEvent> getGlobalTempoList() {
return this.globalTempoList;
}
public MMLTrack setProgram(int program) {
this.program = program;
return this;
}
public int getProgram() {
return this.program;
}
public void setSongProgram(int songProgram) {
this.songProgram = songProgram;
}
public int getSongProgram() {
return this.songProgram;
}
public void setTrackName(String name) {
this.trackName = name;
}
public String getTrackName() {
return this.trackName;
}
public void setPanpot(int panpot) {
if (panpot > 127) {
panpot = 127;
} else if (panpot < 0) {
panpot = 0;
}
this.panpot = panpot;
}
public int getPanpot() {
return this.panpot;
}
public MMLEventList getMMLEventAtIndex(int index) {
return mmlParts.get(index);
}
public List<MMLEventList> getMMLEventList() {
return mmlParts;
}
public long getMaxTickLength() {
long max = 0;
for (MMLEventList eventList : mmlParts) {
long tick = eventList.getTickLength();
if (max < tick) {
max = tick;
}
}
return max;
}
public MMLTrack generate() throws UndefinedTickException {
String mml1 = getOriginalMML();
originalMML.setMMLText(getMMLStrings(false, false));
if (!this.equals(new MMLTrack().setMML(getOriginalMML()))) {
System.err.println("Verify error.");
System.err.println(mml1);
System.err.println(getOriginalMML());
throw new UndefinedTickException("Verify error.");
}
/*
* tailFixはMusicQアップデートで不要になりました. 2017/01/07
*/
mabiMML.setMMLText(getMMLStrings(false, true));
generated = true;
return this;
}
private String[] getMMLStrings(boolean tailFix, boolean mabiTempo) throws UndefinedTickException {
int count = mmlParts.size();
String mml[] = new String[count];
int totalTick = (int)this.getMaxTickLength();
for (int i = 0; i < count; i++) {
// メロディパートのMML更新(テンポ, tickLengthにあわせる.
MMLEventList eventList = mmlParts.get(i);
boolean isPrimaryTempoPart = (i == 0) || (i == 3);
if ( isPrimaryTempoPart ) {
mml[i] = eventList.toMMLString(true, totalTick, mabiTempo);
} else {
mml[i] = eventList.toMMLString();
}
}
if (tailFix) { // 終端補正
mml[0] = tailFix(mml[0], mml[1], mml[2]);
}
// for mabi MML, メロディ~和音2 までがカラの時にはメロディパートもカラにする.
if ( mabiTempo && mmlParts.get(0).getMMLNoteEventList().isEmpty() && mml[1].equals("") && mml[2].equals("") ) {
mml[0] = "";
}
for (int i = 0; i < count; i++) {
mml[i] = new MMLStringOptimizer(mml[i]).toString();
}
if ((mmlParts.get(3).getTickLength() == 0)) {
mml[3] = "";
}
return mml;
}
private String tailFix(String melody, String chord1, String chord2) throws UndefinedTickException {
String s = melody;
MMLTrack partTrack = new MMLTrack().setMML(melody, chord1, chord2, "");
long totalTick = partTrack.getMaxTickLength();
double playTime = partTrack.getPlayTime();
double mmlTime = partTrack.getMabinogiTime();
int tick = (int)(totalTick - new MMLEventList(s).getTickLength());
if (playTime > mmlTime) {
// スキルが演奏の途中で止まるのを防ぎます.
s += new MMLTicks("r", tick, false).toMMLText();
} else if (playTime < mmlTime) {
// 演奏が終ってスキルが止まらないのを防ぎます.
if (tick > 0) {
s += new MMLTicks("r", tick, false).toMMLText() + "v0c64";
}
s += MMLTempoEvent.getMaxTempoEvent(globalTempoList).toMMLString();
}
return s;
}
/**
* MMLの演奏時間を取得する.
* @return 時間(秒)
*/
public double getPlayTime() {
int totalTick = (int)getMaxTickLength();
long playTime = MMLTempoEvent.getTimeOnTickOffset(globalTempoList, totalTick);
return playTime/1000.0;
}
/**
* マビノギでの演奏スキル時間を取得する.
* <p>演奏時間 - 0.6秒 < スキル時間 であれば、切れずに演奏される</p>
* TODO: 歌パートの扱いは調べる必要があります・・・.
* @return 時間(秒)
*/
public double getMabinogiTime() {
long partTime[] = new long[mmlParts.size()];
int melodyTick = (int)mmlParts.get(0).getTickLength();
partTime[0] = MMLTempoEvent.getTimeOnTickOffset(globalTempoList, melodyTick);
ArrayList<MMLTempoEvent> globalTailTempo = new ArrayList<>();
MMLTempoEvent lastTempoEvent = new MMLTempoEvent(120, 0);
if (globalTempoList.size() > 0) {
lastTempoEvent.setTempo(globalTempoList.get(globalTempoList.size()-1).getTempo());
}
globalTailTempo.add(new MMLTempoEvent(lastTempoEvent.getTempo(), 0));
for (int i = 1; i < partTime.length; i++) {
int tick = (int)mmlParts.get(i).getTickLength();
partTime[i] = MMLTempoEvent.getTimeOnTickOffset(globalTailTempo, tick);
}
long maxTime = 0;
for (long time : partTime) {
if (maxTime < time) {
maxTime = time;
}
}
return maxTime/1000.0;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof MMLTrack)) {
return false;
}
MMLTrack mmlTrack = (MMLTrack) obj;
if (this.mmlParts.size() != mmlTrack.mmlParts.size()) {
return false;
}
if (Arrays.equals(this.mmlParts.toArray(), mmlTrack.mmlParts.toArray())) {
return true;
}
return false;
}
}