/***************************************************************************
                          MIDIMelodyAdapter.cpp  -  description
                             -------------------
    begin                : Don Feb 3 2005
    copyright            : (C) 2005 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 "MIDIMelodyAdapter.h"
#include "ZN.h"
#include "BarTime.h"

void MIDIMelodyAdapter::mf_note_on( const MIDITimedMessage &msg )
{
   if(m_nCurTrk != m_nTrkNum ) return;

   if(m_dCut>0 && m_dTotalLen>m_dCut) return;
   uint vel = msg.GetVelocity();
   if(vel == 0){
      mf_note_off(msg);
      return;
   }
   checkScheduledTempo();   
   //cout<<"NoteOn: GetTime() returned "<<msg.GetTime()<<endl;
   //m_iLastOnset = m_dLastOnset*(m_nDivision * m_dTempo);
   //cout<<"m_iLastOnset "<<m_iLastOnset<<endl;
   m_msgQueue.push(msg);
   //cout<<"m_msgQueue.size() = "<<m_msgQueue.size()<<endl;
   int actualTime = calcTime(msg.GetTime());
   double onset = (actualTime+0.0)/(m_nDivision/m_dTempo);
   uint ioi = actualTime - m_iLastOnset;
   if(m_nQuantLevel == 0){
      if(ioi && ioi<m_nMinIOITicks && m_nDivision>ioi){
         m_nMinIOITicks = ioi;
         uint min = m_nDivision/(uint)floor(0.5+(m_nDivision/ioi));
         //cout<<"New minIOITicks = "<<ioi<<" "<<min<<endl;
         m_pMel->setIOIUnit(min);
         m_pMel->setDurationUnit(min);
      }
   }

   if(m_bFirstOnset){
      m_bFirstOnset = false;
      if(m_curBarPos.isSet()){
         m_curBarPos.addTicks(ioi);
      }
   }
   else{
      m_dTotalLen += onset - m_dLastOnset;
      if(m_curBarPos.isSet()){
         m_curBarPos.addTicks(ioi);
      }
   }
   m_iLastOnset = actualTime;
   m_dLastOnset = onset;
}

void MIDIMelodyAdapter::mf_note_off( const  MIDITimedMessage &msg )
{
   if(m_nCurTrk != m_nTrkNum ) return;
   if(m_dCut>0 && m_dTotalLen>m_dCut) return;
   //cout<<"NoteOff: GetTime() returned "<<msg.GetTime()<<endl;
   MIDIChanMsg tmp(m_msgQueue.front());
   //cout<<"(Before pop)m_msgQueue.size() = "<<m_msgQueue.size()<<endl;
   m_msgQueue.pop();
   //cout<<"(After pop)m_msgQueue.size() = "<<m_msgQueue.size()<<endl;

   //tmp.dump();
   int actualTime = calcTime(msg.GetTime());
   int tmpActualTime = calcTime(tmp.getTime());
   double onset   = (tmpActualTime+0.0)/(m_nDivision /m_dTempo);
   double offset  = (actualTime+0.0)/(m_nDivision /m_dTempo);
   //double dur     = (actualTime - tmpActualTime)/(0.0+m_nDivision)*m_dTempo;
   double dur     = (msg.GetTime() - tmp.getTime())/(0.0+m_nDivision)*m_dTempo;
   
   Tone tone(tmpActualTime, onset, tmp.getPitch(), dur, m_curBarPos);
   //tone.dump();
   m_pMel->push_back(tone);
   
}

void MIDIMelodyAdapter::mf_timesig(
      MIDIClockTime time,
      int num,
      int denom_power,
      int midi_clocks_per_metronome,
      int notated_32nds_per_midi_quarter_note)
{
   if(m_nCurTrk != m_nTrkNum && m_nCurTrk != m_nSigTrkNum ) return;
   Signature sig(num, 1<<denom_power, m_nDivision);
   m_pMel->setSignature(sig);
   m_curBarPos.setSignature(sig);
   m_curBarPos.set(1,1,0);
}

void MIDIMelodyAdapter::mf_tempo( MIDIClockTime time, unsigned long tempo )
{
   static bool bFirstTime = true;
   if(m_nCurTrk != m_nTrkNum && m_nCurTrk != m_nSigTrkNum ) return;

   if(bFirstTime){
      //if(m_dTempo != 0.0) return;
      //cout<<"Tempo (au)="<<tempo<<endl;
      m_pMel->clearTempi();
      scheduleNewTempo((tempo+.0)/1000);
      //cout<<"Tempo (us)="<<m_dTempo<<endl;
      bFirstTime = false;
   }
   else{
      if(!m_bIgnoreTempoChanges){
         scheduleNewTempo((tempo+.0)/1000);
      }
   }
}


void MIDIMelodyAdapter::mf_keysig(MIDIClockTime time, int sf, int mi )
{
   if(m_nCurTrk != m_nTrkNum && m_nCurTrk != m_nSigTrkNum ) return;
   Chroma   ch(7*sf-3*mi);    
   uint key = ch.get()+12*mi;
   m_pMel->setKey(key);
}

void MIDIMelodyAdapter::mf_starttrack( int trk )
{
   m_nCurTrk = trk;
}

void MIDIMelodyAdapter::mf_endtrack( int trk )
{
   
}

void MIDIMelodyAdapter::mf_header( int format, int nTrks, int division)
{
   format =  format;
   nTrks  = nTrks;

   if(m_nQuantLevel){
      if(m_nQuantLevel<(uint)division && (uint)division % m_nQuantLevel){
         uint kgv = kgV(division, m_nQuantLevel);
         m_nTickScaling = kgv / division;
         division = kgv;
      }
      else{
         if(m_nQuantLevel>=(uint)division){
            cerr<<"WARNING: "<<division<<" tick per beats is smaller than quantisation level "<<m_nQuantLevel<<". Adjusting."<<endl;
            m_nQuantLevel = division;
         }
      }
      m_pMel->setDurationUnit(division/m_nQuantLevel);
      m_pMel->setIOIUnit(division/m_nQuantLevel);
   }

   m_nDivision = division;
   if(division<0x8000 ){
      m_pMel->setTicks(division);
   }
   
}
uint MIDIMelodyAdapter::calcTime(const uint rawTime) const
{
   uint time = rawTime* m_nTickScaling;
   if(m_nQuantLevel){
      time  = BarPosition::getNearestTick(time, m_nDivision, m_nQuantLevel);
   }
   return time;
}

void MIDIMelodyAdapter::scheduleNewTempo(double tempo)
{
   m_dNewTempo = tempo;
}

void MIDIMelodyAdapter::checkScheduledTempo()
{
   if(m_dNewTempo>0){
      m_dTempo = m_dNewTempo;
      m_pMel->addTempo(m_dNewTempo);      
      m_dNewTempo = -1;
   }   
}
