/*
* Copyright (C) 2013-2014 たんらる
*/
package fourthline.mmlTools.core;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
/**
* MML解析
* @author たんらる
*/
public final class MelodyParser {
private String mml_src;
private String mml_L;
private int mml_length = -1; // for tick
private int mml_oct = 4;
private int tempo;
private final Map<Integer, Integer> tempoList = new TreeMap<>(); // <tick, tempo>
// for check tempo backward
private char playingNote = ' ';
private final List<Integer> warnIndex = new ArrayList<>();
private final static int R_NOTE = Integer.MIN_VALUE;
private int minNote = R_NOTE;
private int maxNote = R_NOTE;
private int noteNumber = R_NOTE;
private String gt;
public MelodyParser(String mml) {
this(mml, "4", 120);
}
public MelodyParser(String mml, String mml_L, int tempo) {
if (mml == null)
mml_src = "";
else
mml_src = mml;
this.mml_L = mml_L;
this.tempo = tempo;
tempoList.put(0, tempo); // initial tempo
}
public int getTempo() throws UndefinedTickException {
if (mml_length < 0)
cals_length();
return tempo;
}
public String getMmlL() throws UndefinedTickException {
if (mml_length < 0)
cals_length();
return mml_L;
}
public List<Integer> getWarnIndex() {
if (mml_length < 0)
throw new NullPointerException(" non cals. ");
return warnIndex;
}
public int getLength() throws UndefinedTickException {
if (mml_length < 0)
cals_length();
return mml_length;
}
public int getMinNote() {
return minNote;
}
public int getMaxNote() {
return maxNote;
}
public int getNoteNumber() {
return noteNumber;
}
public String getGt() {
return gt;
}
public boolean checkPitch(int min, int max) {
if ( minNote == R_NOTE && maxNote == R_NOTE ) {
return true;
}
if ( (minNote >= min ) && (maxNote <= max) ) {
return true;
} else {
return false;
}
}
public void mergeParser(MelodyParser srcParser) throws UndefinedTickException {
int len1 = this.getLength();
int len2 = srcParser.getLength();
if (this.tempoList.get(0) == srcParser.tempoList.get(0)) // non tempo mml
srcParser.tempoList.remove(0);
if (len1 <= len2) { // length merge
this.tempoList.remove(len1);
this.mml_length = len2;
} else {
srcParser.tempoList.remove(len2);
}
tempoList.putAll(srcParser.tempoList);
}
public Map<Integer, Integer> getTempoList() throws UndefinedTickException {
if (tempoList.size() < 2)
cals_length();
return tempoList;
}
public double getPlayLengthByTempoList() throws UndefinedTickException {
double length_total = 0.0;
if (tempoList.size() < 2)
cals_length();
Iterator<Map.Entry<Integer, Integer>> iter = tempoList.entrySet().iterator();
Map.Entry<Integer, Integer> pre_elem = iter.next();
while ( iter.hasNext() ) {
Map.Entry<Integer, Integer> now_elem = iter.next();
if ( now_elem.getValue().equals(pre_elem.getValue()) )
continue; // now tempo == pre tempo
length_total += (now_elem.getKey() - pre_elem.getKey())*60 / pre_elem.getValue();
pre_elem = now_elem;
}
length_total /= (double) MMLTickTable.TPQN;
return length_total;
}
private int mmlGT(String gt) throws UndefinedTickException {
int tick = MMLTicks.getTick(gt);
return tick;
}
/**
* ノートのmin, maxを記録する
* @param note
*/
private void noteMinMax(int note) {
if (note == Integer.MIN_VALUE) {
return;
}
if ( (minNote == R_NOTE) || (maxNote == R_NOTE) ) {
minNote = maxNote = note;
return;
}
if (note < minNote) {
minNote = note;
}
if (note > maxNote) {
maxNote = note;
}
}
private int noteIndex(char n1, char n2) {
int result = -1;
if (n1 < 'a') {
n1 += 'a' - 'A';
}
switch (n1) {
case 'c': result = 0; break;
case 'd': result = 2; break;
case 'e': result = 4; break;
case 'f': result = 5; break;
case 'g': result = 7; break;
case 'a': result = 9; break;
case 'b': result = 11; break;
default : result = -1; break;
}
switch (n2) {
case '+':
case '#': result++; break;
case '-': result--; break;
default : break;
}
return result;
}
private void mmlOperation(String note) throws ParserWarn3ML {
try {
switch(note.charAt(0)) {
case 'l':
case 'L':
mml_L = note.substring(1);
break;
case 't':
case 'T':
int temp = Integer.parseInt( note.substring(1) );
tempoList.put(mml_length, temp);
if ( (temp <= 255) && (temp >= 32) ) {
this.tempo = temp;
}
if ( (this.playingNote == 'r') || (this.playingNote == 'R') ) {
throw new ParserWarn3ML();
}
break;
case 'o':
case 'O':
mml_oct = Integer.parseInt( note.substring(1) );
break;
case '<':
if (mml_oct > 0) {
mml_oct--;
}
break;
case '>':
if (mml_oct < 8) {
mml_oct++;
}
break;
default:
break;
}
} catch (NumberFormatException e) {
System.err.println("skip: "+note);
}
}
public int noteGT(String note) throws UndefinedTickException, ParserWarn3ML {
if (!MMLTokenizer.isNote(note.charAt(0))) {
mmlOperation(note);
return 0;
}
this.playingNote = note.charAt(0);
if ( (this.playingNote == 'n') || (this.playingNote == 'N') ) {
try {
noteNumber = Integer.parseInt(note.substring(1));
} catch (NumberFormatException e) {
throw new UndefinedTickException(note);
}
noteMinMax( noteNumber );
gt = "";
return mmlGT(mml_L);
}
char note1 = note.charAt(0);
char note2 = ' ';
gt = mml_L;
if (note.length() > 1) {
int startIndex = 1;
note2 = note.charAt(1);
if ( (note2 == '+') || (note2 == '-') || (note2 == '#') )
startIndex++;
if (startIndex < note.length()) {
String s = note.substring(startIndex);
if ( (s.length() > 0) && (s.charAt(0) == '.' || Character.isDigit(s.charAt(0))) ) {
gt = s;
}
if (gt.startsWith(".")) {
gt = mml_L+".";
}
}
}
int noteIndex = noteIndex(note1, note2);
noteNumber = mml_oct * 12 + noteIndex;
if ( (note1 != 'r' ) && (note1 != 'R') ) {
noteMinMax( noteNumber );
} else {
noteNumber = R_NOTE;
}
return mmlGT(gt);
}
protected void reset() {
mml_length = 0;
warnIndex.removeAll(warnIndex);
}
/**
* cals mml length
*/
private int cals_length() throws UndefinedTickException {
MMLTokenizer mt = new MMLTokenizer(mml_src);
reset();
while (mt.hasNext()) {
int parseIndex[] = mt.getIndex();
String item = mt.next();
try {
mml_length += noteGT(item);
} catch (ParserWarn3ML warn) {
System.err.println(warn.getMessage()+parseIndex[1]);
warnIndex.add(parseIndex[1]);
}
}
try {
mmlOperation("T0");
} catch (ParserWarn3ML warn) {}
return mml_length;
}
}