/***************************************************************************
                          ESAC.cpp  -  description
                             -------------------
    begin                : Sam Nov 22 2003
    copyright            : (C) 2003 by klaus frieler
    email                : kgf@omniversum.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
#include "ESAC.h"
#include "Pitch.h"
#include "Segmenter.h"
//#include "Segmenter2.h"
#include "Evaluation.h"

string EsACDecoder::getVal(const char* key)
{
   string tmpKey = string(key) + string("[");
   string sRet("");
   if(m_sData.length()==0) return sRet;
   int pos = (int)m_sData.find(tmpKey);
   if(pos == -1) return sRet;
   sRet = m_sData.substr(pos+tmpKey.length());
   pos = (int)sRet.find("]");
   if(pos == -1) return sRet;
   return sRet.substr(0,pos);
}

string EsACDecoder::token(const string& str, uint num, const char delim)
{
   string sTok("");
   uint count = 0;
   uint start = 0, end = 0;

   for(uint i = 0; i<str.length(); ++i){      
      if( str[i] == delim){
         end = i;
         count++;
         if(count == num){
            sTok = str.substr(start, end-start);
            break;
         }
         while(str[++i] == delim);
         start = i;
      }
   }
   if(count == num-1){
      sTok = str.substr(start);
   }
   else if (num == 1 && sTok.length() == 0) sTok  = str;
   
   //cerr<<"num = "<<num<<" tok = "<<sTok<<endl;
   return sTok;
}

void EsACDecoder::createCommonSignature()
{
   Signature sig;
   char buf[16] = "";
   double sum = 0.0;
   uint min = 0;

   if(m_signatures.size()==1) return;

   for (uint i = 0;i<m_signatures.size(); ++i){
      sig = getSignature(i);
      sig.setFraction();
      sum += sig.fraction();
      if(sig.denom()>min) min = sig.denom();
   }

   sum *=min;
   sprintf(buf, "%d/%d", (uint)floor(sum), min);
   m_signatures.clear();
   m_signatures.push_back(string(buf));
}
string EsACDecoder::trimLeft(const string& str)const
{
   string ret(str);
   size_t pos = str.find_first_not_of(" \t\n\r");
   if(pos != string::npos){
      ret = str.substr(pos);
   }
   return ret;
}

char EsACDecoder::getNextNonWS(const string& str, uint idx)const
{
   if(idx>str.length()-1) return 0;
   size_t i = idx;
   for(; i<str.length();++i){
      if(!isWS(str[i])) return  str[i];
   }
   return 0;
}

string EsACDecoder::trimRight(const string& str)const
{
   string ret(str);
   size_t pos = str.find_last_not_of(" \t\n\r");
   if(pos != string::npos){
      ret = str.substr(0,pos);
   }
   return ret;
}

bool EsACDecoder::parse()
{
   if(m_sData.length() == 0) return false;
   int pos = (int)m_sData.find('\n');
   uint beatCount = 0;
   m_sCollection = m_sData.substr(0, pos);
   m_sTitle    = getVal("CUT");
   m_sRegion   = getVal("REG");
   m_sSource   = getVal("TRD");
   m_sKeyData  = getVal("KEY");
   m_sMel      = trimLeft(getVal("MEL"));
   m_sFunction = getVal("FKT");
   m_sID       = token(m_sKeyData, 1, ' ');
   m_nUnit     = atoi(token(m_sKeyData, 2, ' ').c_str());
   if(m_nUnit == 0 || (m_nUnit %2 != 0) ){
      setErrText("Basic unit invalid.");
      return false;      
   }
   if(hasTriplets(m_sMel)){
      m_nUnit *= 3;  
      m_iLengthBase = 3;
   }      
   if(hasQuintuplets(m_sMel)){
      m_nUnit *= 5;  
      m_iLengthBase *= 5;
   }      
   if(hasSeptlets(m_sMel)){
      m_nUnit *= 7;  
      m_iLengthBase *= 7;
   }      
   m_sKey   = token(m_sKeyData, 3, ' ');
   for(uint i = 0;true; ++i){
      string sig = token(m_sKeyData, 4 + i, ' ');
      beatCount += atoi(sig.c_str());
      if(sig.length() == 0) break;
      m_signatures.push_back(sig);
   }
   //m_nUnit = (4*getSignature(0).m_nTicks)/m_nUnit;

   if(m_signatures.size()>1){
      //createCommonSignature();
      setErrText("Multiple Signatures found.");
      return false;
   }
   if(m_signatures.size()<1){
      //createCommonSignature();
      setErrText("No Signatures found.");
      return false;
   }
   return true;
}

bool EsACDecoder::parseMelody()
{
   if(m_sMel.length()== 0 ) return false;

   bool     bError         = false;
   uint     tupletMark     = 0;
   int      octave         = 0;
   uint     digit          = 0;
   int      sign           = 0;
   uint     length         = m_iLengthBase;
   uint     step           = m_iLengthBase;
   uint     saveStep       = m_iLengthBase;
   uint     caretlength    = 0;
   uint     prollength     = 0;
   uint    *pLength        = &length;
   ToneMark mark           = OUT;
   uint     totalLength    = 0;
   uint     phraseIdx      = 1;
   double   fTotalLength   = 0.0;
   bool     firstBar       = false;
   bool     useUpbeat      = true;
   double   phraseMark     = 0.0;
   string   errType = "";
   uint     errTokNum = 0;

   m_tempo = guessTempo(m_tempo);
   double start = 0;
   m_melody = new Melody;
   if(!m_melody) return false;

   for(uint i = 0; i<m_sMel.length(); ++i){
      char  c = m_sMel[i];
      if(i == 0 && c=='0'){
         useUpbeat = false;
      }
      //end
      if(m_cut>0.0 && fTotalLength>=m_cut) break;
      if(isEnd(c)){
         if(mark == DIG || mark == PRO || mark == ACC || mark == CARET){
            length += caretlength;
            m_melody->push_back(createTone( digit, sign, octave,length));
            m_segments.push_back(1.0);
            fTotalLength +=getLength(length);
            totalLength += length;
         }

         break;
      }
      //CR? find phrase number, set segmentation mark and skip to next non CR
      if(isCR(c)){
         phraseMark = 1.0;
         phraseIdx++;
         if(i < m_sMel.length()-1){
            if(isWS(m_sMel[i+1])) i++;
            if(i != m_sMel.length()-1)
               ++i;
            c = m_sMel[i];
         }
      }
      //right phrase? phraseIdx = 0 means all.
      if(m_iPhraseIdx>0){
         if(phraseIdx<m_iPhraseIdx) continue;
         if(phraseIdx>m_iPhraseIdx) break;
      }
      //whitespace
      if(/*isWS(c) || */isBarSep(c)){
         char lookUp = isBarSep(c) ? getNextNonWS(m_sMel,i+1) : getNextNonWS(m_sMel,i);
         if(mark != OUT && mark != TUPEND){
            if(!isCaret(lookUp) /*&& !isTupletStart(lookUp)*/){
               length      += caretlength;
               totalLength += length;
               m_melody->push_back(createTone( digit, sign, octave,length));
               m_segments.push_back(phraseMark);
               phraseMark = 0.0;
               fTotalLength +=getLength(length);
               octave = sign = 0;
               caretlength = 0;
               prollength  = 0;
               length      = step;
               pLength     = &length;
            }
            if(!firstBar){
               firstBar = true;
               if(totalLength<getBarLength() && useUpbeat){
                  m_melody->setFirstOneIdx(m_melody->size()+1);
                  uint virtualLength = lookUp != '^' ? totalLength: totalLength+length+caretlength;
                  start = getLength(getBarLength()-virtualLength);
               }
               else{
                  m_melody->setFirstOneIdx(0);
               }
            }
         }
         mark = OUT;
         continue;
      }
      if(isTupletStart(c)){
         if(tupletMark>1){
            errTokNum = i;
            errType = "No nested tuplets allowed";
            bError = true;
            break;
         }
         switch (c){
            case '(': 
               tupletMark = 3;
               step = 2* m_iLengthBase /3;
               break;
            case '<':
               tupletMark = 5;
               step = 4* m_iLengthBase /5;
               break;
            case '{':
               tupletMark = 7;
               step = 4 * m_iLengthBase/7;
               break;
         }
         saveStep = step;
         char lookUp = getNextNonWS(m_sMel,i+1);
         if(!isCaret(lookUp)){
            if(mark == DIG || mark == ACC ||mark == PRO || mark == CARET){
               length += caretlength;
               m_melody->push_back(createTone( digit, sign, octave,length));
               m_segments.push_back(phraseMark);
               phraseMark = 0.0;
               totalLength  += length;
               fTotalLength += getLength(length);
               octave = sign = 0;
               step = saveStep;
               length = step;
               caretlength = 0;
               prollength = 0;
               pLength = &length;
            }
            step = saveStep;
            length = step;
         }
         mark = TUPSTART;
         continue;
      }
      if(isTupletEnd(c)){
         if((isTripletEnd(c) && tupletMark != 3) || 
            (isQuintupletEnd(c) && tupletMark != 5) ||
            (isSepletEnd(c) && tupletMark != 7)){
            errTokNum = i;
            errType = "Tuplet signs don't match";
            bError = true;
            break;
         }
         char lookUp = getNextNonWS(m_sMel,i+1);
         if(tupletMark>0 && (mark == DIG || mark == ACC ||mark == PRO || mark == CARET) ){
            tupletMark = 0;
            step = m_iLengthBase;
            saveStep = step;
            if(!isCaret(lookUp)){
               length += caretlength;
               m_melody->push_back(createTone( digit, sign, octave,length));
               m_segments.push_back(phraseMark);
               phraseMark = 0.0;
               totalLength += length;
               fTotalLength +=getLength(length);
               caretlength = 0;
               octave = sign = 0;
               length = step;
               prollength = 0;
               pLength = &length;
            }
         }
         else{
            errTokNum = i;
            errType = "Misplaced triplet sign";
            bError = true;
            break;
         }
         mark = TUPEND;
         continue;
      }
      //octaver
      if(isOctaver(c)){
         if(mark == PRO || mark == DIG || mark == ACC || mark == CARET){
            length += caretlength;
            m_melody->push_back(createTone( digit, sign, octave,length));
            m_segments.push_back(phraseMark);
            phraseMark = 0.0;
            totalLength += length;
            fTotalLength +=getLength(length);
            octave = sign = 0;
            length = step;
            caretlength = 0;
            prollength = 0;
            pLength = &length;
         }
         if(c == '-'){
            if((mark == OCT && octave>0)){
                errTokNum = i;
                errType = "Wrong octavation";
                bError = true;
                break;
            }
            octave--;
         }
         else if(c == '+'){
            if((mark == OCT && octave<0)){
               errTokNum = i;
               errType = "Wrong octavation";
               bError = true;
               break;
            }
            octave++;
         }
         mark = OCT;
         continue;
      }
      //digit  
      if(isDigit(c)){
         if((mark == DIG || mark == ACC) ||mark == PRO || mark == CARET ){
            length += caretlength;
            m_melody->push_back(createTone( digit, sign, octave,length));
            m_segments.push_back(phraseMark);
            phraseMark = 0.0;
            fTotalLength +=getLength(length);
            totalLength += length;
            octave = sign = 0;
            length = step;
            caretlength = 0;
            prollength = 0;
            pLength = &length;
         }
         if (isUndefined(c)){
            digit = 128;
         }
         else
            digit = (uint)(c - '0');
         mark = DIG;
         continue; 
      }
      //caret
      if(isCaret(c)){
         if(pLength == &caretlength){
            length += caretlength; 
            caretlength  = 0;
         }
         pLength = &caretlength;
         mark = CARET;
         caretlength += step;
         prollength = 0;
         continue;
      }

      //sign
      if(isAccidential(c)){
         if(mark != DIG){
            errTokNum = i;
            errType = "Invalid accidential.";
            bError = true;
            break;
         }
         mark = ACC;
         sign = c == 'b' ? -1 :1;
         continue;
      }
      //prolongation
      if(isProlongation(c)){
         if(mark!= ACC && mark != DIG && mark != PRO && mark != CARET){
            bError = true;
            errTokNum = i;
            errType = "Misplaced prolongation.";
            break;
         }
         if(c == '_'){
            *pLength *=2;
         }
         else{
            prollength = prollength == 0 ? (*pLength)/2 : prollength/2;
            if(prollength == 0){
               errTokNum = i;
               errType = "Misplaced prolongation.";
               bError = true;
               break;
            }
            else {
               *pLength += prollength;
            }
         }
         mark = PRO;
         continue;
      }
   }
   if(!bError){
      //check for very short melodies (<1 bar)
      if(totalLength<getBarLength()){
         m_melody->setFirstOneIdx(0);
         start = getLength(0);
      }

      m_melody->setSignature(getSignature(0));
      m_melody->setKey(Pitch::getPitchNumber(m_sKey.c_str()));
      m_melody->setTempo(m_tempo);
      m_melody->setTicks(getSignature(0).ticks());

      m_melody->createOnsets(start);
      m_melody->createIOIs();
      m_nUnit = (4*getSignature(0).ticks())/m_nUnit;
      m_melody->setIOIUnit(m_nUnit);
      m_melody->setDurationUnit(m_nUnit);
      m_melody->check();
      //dumpMelody();
      m_melody->calcBarPositions(0);

      //dumpMelody();
      m_segments.pop_back();
      //m_segments.dump("Parsed segments:");
      m_melody->setSegments(m_segments);
   }
   else{
      char buf[128] = "";
      uint minIdx = errTokNum -5;
      uint maxIdx = errTokNum +5;
      if(minIdx<=0) minIdx = 0;
      if(maxIdx>=m_sMel.length()) maxIdx =  m_sMel.length()-1;
      string blanks = "         ";
      blanks = blanks.substr(0,errTokNum-minIdx);
      string errContext = m_sMel.substr(minIdx, errTokNum-minIdx-1)+ string("\n")+ blanks+m_sMel.substr(errTokNum, maxIdx-errTokNum-2);
      sprintf(buf, "\"%s\"\nError while parsing melody string at token %d. %s", errContext.c_str(), errTokNum, errType.c_str());
      setErrText(buf);
   }
   return !bError;
}

Signature EsACDecoder::getSignature(uint idx )
{
   Signature   sig;
   string szSig = m_signatures[idx];
   string sCount = token(szSig, 1, '/');
   string sDenom = token(szSig, 2, '/');
   if(atoi(sCount.c_str()) == 0){
      return sig;
   }
   sig = Signature(atoi(sCount.c_str()), atoi(sDenom.c_str()), 480);
   return sig;
}

uint  EsACDecoder::getTicks(uint length)
{
   Signature   sig = getSignature(0);
   return length * (sig.ticks()/m_nUnit) * sig.denom();     
}

double EsACDecoder::getLength(uint length)
{
   Signature   sig = getSignature(0);
   return length * m_tempo * (sig.denom()+0.0)/m_nUnit;     
}

uint EsACDecoder::getMIDIPitch(uint digit, uint sign, int octave )
{
   if(digit == 0 || digit == 128 ) return digit;
   return Pitch::getMIDIPitchByKey(digit, m_sKey.c_str(), sign, octave);  
}

Tone EsACDecoder::createTone(uint digit, int sign, int octave, uint length)
{
   return Tone(getTicks(length), getLength(length), getMIDIPitch(digit, sign, octave), 0.0, invalidBarPosition);   
}


void EsACDecoder::dumpMelody()
{
   for(uint i = 0; i<m_melody->size(); ++i){
      m_melody->at(i).dump();
   }
}

double  EsACDecoder::guessTempo(double timebase)
{
   Signature sig = getSignature(0);
   sig.setFraction();
   double   factor = sig.denom()/4.0;
   return timebase/factor;
   
}

uint EsACDecoder::getBarLength()
{
   Signature sig = getSignature(0);
   return m_nUnit * sig.count() / sig.denom();
}

string EsACEncoder::begin           = "MEL[";
string EsACEncoder::end             = "//]\n";
string EsACEncoder::rest            = "0";
string EsACEncoder::flat            = "b";
string EsACEncoder::sharp           = "#";
string EsACEncoder::prolong_under   = "_";
string EsACEncoder::prolong_dot     = ".";
string EsACEncoder::caret           = "^";
string EsACEncoder::undefined       = "?";
string EsACEncoder::space           = " ";
string EsACEncoder::nl              = "\n";
string EsACEncoder::octup           = "+";
string EsACEncoder::octdown         = "-";

string EsACEncoder::header          = "CUT[]\nREG[]\nTRD[]\n";
string EsACEncoder::footer          = "FKT[]\nTXT[]\n";

const char *EsACEncoder::heptatonic[12] = { "1", "2b", "2", "3b", "3", "4", "4#", "5", "6b", "6", "7b", "7" };

string EsACEncoder::getEsACToneByMIDIPitch(const int pitch, const int key) const
{
   //special cases
   if(pitch == 0 || pitch == 150)  return EsACEncoder::rest;
   if(pitch ==128) return EsACEncoder::caret;
   if(pitch>128)  return EsACEncoder::undefined;
      
   uint octave    = pitch >=key ? (uint)(pitch-key)/12 : (uint)(key-pitch)/12 +1;
   int interval   = pitch >=key ? (pitch-key) % 12 : 12 + (pitch-key) % 12;
   string tone("");
      
   string modifier = pitch>key ?EsACEncoder::octup : EsACEncoder::octdown;
   for(uint i= 0; i< octave; ++i){
      tone += modifier;
   }
   //if(interval<0) interval = 12 + interval;
   tone+= EsACEncoder::heptatonic[interval%12];
   return tone;
}

string EsACEncoder::getProlongation(const uint unitDuration) const
{
   string ret(""), tmp("");
   if(unitDuration == 0 || unitDuration == 1) return ret;
   uint length = 1, us = 0, dots = 0;
   while (length<=unitDuration){
      length *=2;
      us++;      
   }
   length /=2;
   if(us) us--;
   uint diff = unitDuration-length;
   if(diff>0){
      uint l2 = length/2;
      while(length<unitDuration){
         length += l2;
         l2 /= 2;
         dots++;
      }
      if(length>unitDuration){
         while(dots){
            l2 *=2;
            length -= l2;
            dots--;
         }
         diff = unitDuration-length;
         tmp = EsACEncoder::caret + getProlongation(diff);
      }
   }
   for(uint i= 0; i<us; ++i){
      ret +=EsACEncoder::prolong_under;
   }
   for(uint i= 0; i<dots; ++i){
      ret +=EsACEncoder::prolong_dot;
   }
   ret += tmp;
   return ret;
}

string  EsACEncoder::getExtendedProlongation(const uint duration, const uint start, const uint unitsPerBar, uint &barCount) const
{
   string prolong(""), leftOver("");
   uint leftThisBar     = unitsPerBar - start;
   uint thisBar         = duration;
   uint leftOverUnits   = 0;
   //cout<<"dur ="<<duration<<" start = "<<start<<" Units ="<<unitsPerBar<<endl;
   if(duration>leftThisBar){
      thisBar = leftThisBar;
      leftOverUnits = duration - leftThisBar;
   }
   
   prolong = getProlongation(thisBar);
   while(leftOverUnits>unitsPerBar){
      leftOver += string(" ")+getEsACToneByMIDIPitch(128, 0);
      leftOver += getProlongation(unitsPerBar);
      leftOver +" ";
      leftOverUnits -= unitsPerBar;
      barCount++;
   }
   if(leftOverUnits){
      leftOver += string(" ") +  getEsACToneByMIDIPitch(128, 0);
      leftOver += getProlongation(leftOverUnits);
      barCount++;
   }
   prolong = prolong + leftOver;
   return prolong;
}

string  EsACEncoder::createTriplet(const uint pos, const iData& pitches, const uint pitchOffset, const iData &iois, const uint unitsPerBar, uint& counter, uint &barCount)
{
   string ret("("), triplet("");

   uint count = 0;
   uint tripleUnitsPerBar = unitsPerBar *3;
   uint tripleCount = counter * 3;
   uint effPitch = 0;
   for(uint i = pos; i<pos+3; ++i){
      if(i>iois.size()-1) return string("");
      uint ioi = m_mel.divRound(iois[i], m_mel.getIOIUnit());
      tripleCount += ioi;
      count += ioi/2;
      string prolong  = getProlongation(ioi/2);
      effPitch = pitches[i];
      if(pitches[i]> 0 && pitches[i] < 128){
         effPitch +=  pitchOffset;
      }
      triplet    += getEsACToneByMIDIPitch(effPitch, m_mel.getKey()+C0);
      triplet    += prolong;
      if(tripleCount>tripleUnitsPerBar){
         triplet += string(" ");
         barCount++;
      }
   }
   //counter += (count-1) % unitsPerBar;  
   counter = (tripleCount/3) ;
   ret += triplet + (")");
   return ret;
}

string EsACEncoder::getKeyEntry()const
{
   char buf[1024] = "";

   string sKey = Pitch::getToneName(m_mel.getKey());

   uint num    = m_mel.getSignature().count();
   uint denom  = m_mel.getSignature().denom();
   uint unit   = 4 * m_mel.getTicks()/m_mel.getDurationUnit();

   if(unit % 3 ==0)
      unit = (unit/3);

   sprintf(buf, "KEY[%s %d %s %d/%d]\n", m_sName.c_str(), unit, sKey.c_str(), num, denom);
   return string(buf);
}

string EsACEncoder::getMelEntry()
{
   string ret("");
   iData durs           = m_mel.getDurationsInUnits();
   iData iois           = m_mel.getIOITicks();
   iData pitches        = m_mel.getPitches();
   uint  key            = m_mel.getKey();
   uint  unitsPerBar    = m_mel.getUnitsPerBar();
   uint  counter        = m_mel.getStartUnits();
   uint  barCount       = 0;
   int   diff           = 0;
   bool  bHasTriplets   = false;
   bool  bStartTriplet  = false;
   uint  effPitch       = 0;
   int   modusPitch     = (int)m_mel.getMedianPitch();
   int   keyPitch       = C0+(key%12);
   int   pitchOffset    = abs((int)keyPitch-modusPitch);
   
   int sgn =  modusPitch> keyPitch ? -1 : +1;
   for(uint i = 0;; ++i){
      int tmp    = modusPitch+ sgn * 12 * i;
      pitchOffset = sgn * 12 * i;
      if(tmp>=keyPitch&& tmp<= keyPitch+12) break;
   }
   
   if(unitsPerBar%3 == 0 && m_mel.getMinIOIUnits() != 1){
      cout<<"Melody has triplets!"<<endl;
      bHasTriplets    = true;
      unitsPerBar    /= 3;
      //unitsPerBar    *= 2; 
      counter        /= 3;
   }
   
   for(uint i = 0; i<pitches.size(); ++i){
      string tone(""), prolong("");
      uint ioi = 0, effDur = durs[i];
      
      if(i == pitches.size()-1){
         //ioi = unitsPerBar - counter;
         ioi = m_mel.divRound(iois[i], m_mel.getIOIUnit());
         //if(bHasTriplets) ioi *=3;
      }
      else{
         ioi = m_mel.divRound(iois[i], m_mel.getIOIUnit());
         bStartTriplet = false;
         if(ioi%3 && bHasTriplets){
            bStartTriplet = true;
         }
      }
      if(bStartTriplet){
         if(i>pitches.size()-3){
            m_sErrText = "Incomplete triplet at end.";
            return string("");
         }
         cout<<"Create triplet, counter="<<counter<<endl;
         tone = createTriplet(i, pitches, pitchOffset, iois,  unitsPerBar, counter, barCount);
         cout<<"Created triplet : counter="<<counter<<endl;
         i += 2;
         goto cont;
      }
      if(bHasTriplets){
         effDur = durs[i]/3;
         if(effDur<1) effDur = 1;
         ioi   /= 3;
      }
      //need to insert a rest?
      diff = ioi - effDur;
      if(diff>0){
         if(diff<ioi/2.3){
            effDur = ioi;
            diff = 0;
         }
      }
      prolong  = getExtendedProlongation(effDur, counter, unitsPerBar, barCount);

      effPitch = pitches[i];
      if(pitches[i]> 0 && pitches[i] < 128){
         effPitch +=  pitchOffset;
      }

      tone     = getEsACToneByMIDIPitch(effPitch, key%12+C0);
      tone    += prolong;
      counter += effDur % unitsPerBar;

      //cout<<i<<" ioi = "<<ioi<<" counter = "<<counter<<" durs[i] = "<<effDur<<" diff = "<<diff<<" prolog = '"<<prolong.c_str()<<"'"<<endl;

      if(diff<0 && i!= pitches.size()-1){
         m_sErrText = string("Invalid melody. Probably not monophonic.");
         return string("");
      }
      if(diff>0){
         prolong   = getExtendedProlongation(diff, counter, unitsPerBar, barCount);
         tone     += getEsACToneByMIDIPitch(0, key+C0);
         tone     += prolong;
         counter  += diff % unitsPerBar;
         
      }
cont:
      bool bPhraseEnd = false;
      if(!m_boundaries.empty() && m_boundaries[i] == 1.0){
         bPhraseEnd = true;
      }

      if(counter >= unitsPerBar){
         barCount++;
         counter = counter % unitsPerBar;
         if(counter == 0) tone += string(" ");
         if(m_boundaries.empty()){
            if(barCount % 2 == 0 && counter == 0){
               bPhraseEnd = true;
            }
         }
      }
      if(bPhraseEnd){
         tone += EsACEncoder::nl;
      }

      ret += tone;
   }
   return ret;
}

string EsACEncoder::encode()
{
   string mel(""), tmp("");
   tmp = EsACEncoder::header;
   //testSegmenter();
   m_mel.removeNoteOverlaps();
   TemperleySegmenter segmenter(&m_mel);
   Vector segments = segmenter.analyze();
   setBoundaries(segments);

   tmp += getKeyEntry();
   mel  = getMelEntry();
   if(mel.length() == 0) return string("");
   tmp += EsACEncoder::begin + mel + EsACEncoder::end;
   tmp += EsACEncoder::footer;
   return tmp;
}

void EsACEncoder::testSegmenter()
{
   //testTemperley();
   //testPlainLBDM();
   //testRefinedLBDM();
   //testHybrid();
}
#if 0
void  EsACEncoder::testHybrid()
{
   Evaluation eval(m_mel.getSegments());
   Segmenter::LinearBoundaryCluster   linCluster(&m_mel);   
   Segmenter::LinearBoundaryCluster   gapScore(&m_mel);   
   Segmenter::TemperleyGapScore       gapScore1(&m_mel);
   Segmenter::TemperleyLengthBoundary tmpLenBound(&m_mel);
   Segmenter::MetricParallelBoundary  metricBound(&m_mel, 0.3, 0.15);
   //new section
   Transformations::IOI   ioi(&m_mel);
   Transformations::OOI   ooi(&m_mel);
   Transformations::AbsPitchIntervals absInt(&m_mel);
   Segmenter::PlainLBDM    ref1(&m_mel, &ioi, 2, 1, 1);
   Segmenter::PlainLBDM    ref2(&m_mel, &ooi, 0, 1, 1);
   Segmenter::PlainLBDM    ref3(&m_mel, &absInt, 0, 1, 1 );
   gapScore.add(&ref1);
   gapScore.add(&ref2);
   gapScore.add(&ref3);

   gapScore.setFactor(0.1);
   gapScore1.setFactor(0.5);

   Vector bounds;
   gapScore.getBoundaryStrengths(bounds);
   //bounds.dump("RefLBDM");
   gapScore1.getBoundaryStrengths(bounds);
   //bounds.dump("Temperley");

   tmpLenBound.setFactor(-0.6);
   metricBound.setFactor(-1.0);

   linCluster.add(&gapScore1);
   linCluster.add(&tmpLenBound);   
   linCluster.add(&metricBound);

   Vector                              segments;
   Segmenter::TemperleyDPA             grouper(&linCluster, 20, true);
   double max = 0;
   for(int i = 0; i<10;++i){
     // gapScore1.setFactor(0.1*(i-5)+0.5);
      MRESULT mr = grouper.analyze(segments);

      if(FAIL(mr)){
         cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;
      }
      //m_mel.getSegments().dump("GT");
      mr = eval.evaluate(segments);
      if(FAIL(mr)){
         cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;      
      }
      //segments.dump("Hybrid\n");
      //eval.dump();
      double score = eval.getSensitivity() + eval.getSpecificity();
      if(score>max){
         cout<<"***Factor = "<<0.1*(i-5)+0.5<<endl;
         cout<<"Max = "<<max<<" score = "<<score<<endl;
         max = score;
      }
   }
}
void  EsACEncoder::testTemperley()
{
   Evaluation eval(m_mel.getSegments());
   Segmenter::LinearBoundaryCluster   linCluster(&m_mel);   
   Segmenter::TemperleyLengthBoundary tmpLenBound(&m_mel);
   Segmenter::MetricParallelBoundary  metricBound(&m_mel, 0.3, 0.15);
   Segmenter::TemperleyGapScore       gapScore1(&m_mel);

   gapScore1.setFactor(0.5);
   tmpLenBound.setFactor(-0.6);
   metricBound.setFactor(-1.0);

   linCluster.add(&gapScore1);
   linCluster.add(&tmpLenBound);   
   linCluster.add(&metricBound);

   Vector                              segments;
   Segmenter::TemperleyDPA             grouper(&linCluster, 20, true);

   MRESULT mr = grouper.analyze(segments);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;
   }
   cout<<endl<<"*****************************"<<endl;
   cout<<"Temperley boundaries (2)."<<endl;

   mr = eval.evaluate(segments);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;      
   }
   eval.dump();
   TemperleySegmenter segmenter(&m_mel);
   Vector   segments2 = segmenter.analyze();
   cout<<endl<<"*****************************"<<endl;
   cout<<"Temperley boundaries (1)."<<endl;
   //segments1.dump();
   eval.evaluate(segments2);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;      
   }
   eval.dump();

   eval.setGroundTruth(segments);
   eval.evaluate(segments2);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;      
   }
   eval.dump();
   cout<<"+++++++"<<endl;
   segments.dump();
   segments2.dump();
     
}

void  EsACEncoder::testPlainLBDM()
{
   Segmenter::LinearBoundaryCluster   linCluster(&m_mel);   
   Evaluation eval(m_mel.getSegments());
   MRESULT mr = SUCC_OK;
   Vector segments1, segments2, bounds, bounds2;
   PlainLBDM  segmenter(&m_mel, LBDMBase::iois, 2.0, 1.0, 1.0, true);
   segments1 =  segmenter.analyze();


   PlainLBDM::Parameters params(2.0, 1.0, 1.0, false);
   segmenter.setIntervalType(LBDMBase::pitches);
   segmenter.setParameters(params);
   segments2 =  segmenter.analyze();
   segments1 += segments2;

   segmenter.setIntervalType(LBDMBase::rests);
   segmenter.setParameters(params);
   segments2 =  segmenter.analyze();
   segments1 += segments2;
   segments1.dump("TOTAL\n");
   bounds = segmenter.getBoundaries(segments1);

   cout<<endl<<"*****************************"<<endl;
   cout<<"First order local boundaries (plain LDBM, iois, pitches, rests)"<<endl;  
   eval.evaluate(bounds);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;      
   }
   eval.dump();


   bounds2 = segmenter.getSecondBoundaries(segments1);
   cout<<endl<<"*****************************"<<endl;
   cout<<"Second order local boundaries (plain  LDBM, iois, pitches, rests)"<<endl;  
   eval.evaluate(bounds2);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;      
   }
   eval.dump();

   //new section
   Transformations::IOI   ioi(&m_mel);
   Transformations::OOI   ooi(&m_mel);
   Transformations::AbsPitchIntervals absInt(&m_mel);
   Segmenter::PlainLBDM    plain1(&m_mel, &ioi,    2.0, 1.0, 1.0);
   Segmenter::PlainLBDM    plain2(&m_mel, &ooi,    0.0, 1.0, 1.0);
   Segmenter::PlainLBDM    plain3(&m_mel, &absInt, 0.0, 1.0, 1.0);
   linCluster.add(&plain1);
   linCluster.add(&plain2);
   linCluster.add(&plain3);

   Segmenter::LocalMaxima  grouper(&linCluster);
   mr = grouper.analyze(segments1);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;
   }
   cout<<endl<<"*****************************"<<endl;
   cout<<"First order local boundaries (plain LDBM, iois, pitches, rests) (2)"<<endl;  

   mr = eval.evaluate(segments1);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;
   }
   eval.dump();   
   Segmenter::LocalMaximaMaxima  grouper2(&linCluster);
   mr = grouper2.analyze(segments1);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;
   }
   cout<<endl<<"*****************************"<<endl;
   cout<<"Second order local boundaries (plain  LDBM, iois, pitches, rests) (2)"<<endl;  

   mr = eval.evaluate(segments1);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;
   }
   eval.dump();   

}

void  EsACEncoder::testRefinedLBDM()
{
   Segmenter::LinearBoundaryCluster   linCluster(&m_mel);   

   Evaluation eval(m_mel.getSegments());
   MRESULT mr = SUCC_OK;
   Vector segments1, segments2, bounds, bounds2;

   RefinedLBDM   segmenter(&m_mel, LBDMBase::iois);
   segments1 = segmenter.analyze();

   segmenter.setIntervalType(LBDMBase::pitches);
   segments2 =  segmenter.analyze();
   segments1 += segments2;

   segmenter.setIntervalType(LBDMBase::rests);
   segments2 =  segmenter.analyze();
   segments1 += segments2;
   segments1.dump("total\n");

   bounds = segmenter.getBoundaries(segments1);
   cout<<endl<<"*****************************"<<endl;
   cout<<"First order local boundaries (refined LDBM, iois, pitches, rests)"<<endl;  
   eval.evaluate(bounds);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;      
   }
   eval.dump();

   bounds2 = segmenter.getSecondBoundaries(segments1);
   cout<<endl<<"*****************************"<<endl;
   cout<<"Second order local boundaries (refined LDBM, iois, pitches, rests)"<<endl;  
   eval.evaluate(bounds2);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;      
   }
   eval.dump();

   Transformations::IOI   ioi(&m_mel);
   Transformations::OOI   ooi(&m_mel);
   Transformations::AbsPitchIntervals absInt(&m_mel);
   Segmenter::RefinedLBDM    ref1(&m_mel, &ioi);
   Segmenter::RefinedLBDM    ref2(&m_mel, &ooi);
   Segmenter::RefinedLBDM    ref3(&m_mel, &absInt );
   linCluster.add(&ref1);
   linCluster.add(&ref2);
   linCluster.add(&ref3);

   Segmenter::LocalMaxima  grouper(&linCluster);
   mr = grouper.analyze(segments1);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;
   }
   cout<<endl<<"*****************************"<<endl;
   cout<<"First order local boundaries (refined LDBM, iois, pitches, rests) (2)"<<endl;  
   segments1.dump("ioi\n");

   mr = eval.evaluate(segments1);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;
   }
   eval.dump();   
   Segmenter::LocalMaximaMaxima  grouper2(&linCluster);
   mr = grouper2.analyze(segments1);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;
   }
   cout<<endl<<"*****************************"<<endl;
   cout<<"Second order local boundaries (refined  LDBM, iois, pitches, rests) (2)"<<endl;  

   mr = eval.evaluate(segments1);
   if(FAIL(mr)){
      cout<<"Error: "<<MelError::getErrTextFromCode(mr).c_str()<<endl;
   }
   eval.dump();   

}
#endif