/**************************************************************************

    track.cpp  - class track, which has a midi file track and its events
    This file is part of LibKMid 0.9.5
    Copyright (C) 1997,98,99,2000  Antonio Larrosa Jimenez
    LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libtdemid.html

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.
 
    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.
 
    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.

    Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org>

***************************************************************************/ 

#include "track.h"
#include <stdlib.h>
#include "sndcard.h"
#include "midispec.h"
#include "midfile.h"

#define T2MS(ticks) (((double)ticks)*(double)60000L)/((double)tempoToMetronomeTempo(tempo)*(double)tPCN)

#define MS2T(ms) (((ms)*(double)tempoToMetronomeTempo(tempo)*(double)tPCN)/((double)60000L))

#define PEDANTIC_TRACK
#define CHANGETEMPO_ONLY_IN_TRACK0
//#define TRACKDEBUG
//#define TRACKDEBUG2

MidiTrack::MidiTrack(FILE *file,int tpcn,int Id)
{
  id=Id;
  tPCN=tpcn;
  currentpos=0;
  size=0;
  data=0L;
  tempo=1000000;
  if (feof(file)) 
  {
    clear();
    return; 
  };
  size=readLong(file);
#ifdef TRACKDEBUG
  printf("Track %d : Size %ld\n",id,size);
#endif
  data=new uchar[size];
  if (data==NULL)
  {
    perror("track: Not enough memory ?");
    exit(-1);
  }
  ulong rsize=0;
  if ((rsize=fread(data,1,size,file))!=size)
  {
    fprintf(stderr,"track (%d): File is corrupt : Couldn't load track (%ld!=%ld) !!\n", id, rsize, size);
    size=rsize;
  };
  /*
     ptrdata=data;
     current_ticks=0;
     delta_ticks=readVariableLengthValue();
     wait_ticks=delta_ticks;
     endoftrack=0;
   */
  init();
}

MidiTrack::~MidiTrack()
{
  delete data;
  endoftrack=1;
  currentpos=0;
  size=0;
}

int MidiTrack::power2to(int i)
{
  return 1<<i;
}

ulong MidiTrack::readVariableLengthValue(void)
{
  ulong dticks=0;

  while ((*ptrdata) & 0x80)
  {
#ifdef PEDANTIC_TRACK
    if (currentpos>=size)
    {
      endoftrack=1;
      fprintf(stderr, "track (%d) : EndofTrack found by accident !\n",id);
      delta_ticks = wait_ticks = ~0;	
      time_at_next_event=10000 * 60000L;
      return 0;
    }
    else
#endif
    {
      dticks=(dticks << 7) | (*ptrdata) & 0x7F;
      ptrdata++;currentpos++;
    }

  }
  dticks=((dticks << 7) | (*ptrdata) & 0x7F);
  ptrdata++;currentpos++;

#ifdef PEDANTIC_TRACK

  if (currentpos>=size)
  {
    endoftrack=1;
    fprintf(stderr,"track (%d): EndofTrack found by accident 2 !\n",id);
    dticks=0;
    delta_ticks = wait_ticks = ~0;	
    time_at_next_event=10000 * 60000L;
    return 0;
  }
#endif
#ifdef TRACKDEBUG
  printfdebug("track(%d): DTICKS : %ld\n",id,dticks);
  usleep(10);
#endif
  return dticks;
}

int MidiTrack::ticksPassed (ulong ticks)
{
  if (endoftrack==1) return 0;
  if (ticks>wait_ticks) 
  {
    printfdebug("track (%d): ERROR : TICKS PASSED > WAIT TICKS\n", id);
    return 1;
  }
  wait_ticks-=ticks;
  return 0;
}

int MidiTrack::msPassed (ulong ms)
{
  if (endoftrack==1) return 0;
  current_time+=ms;
  //fprintf(stderr, "old + %ld = CURR %g  ", ms,current_time);
  if ( current_time>time_at_next_event )
  {
    fprintf(stderr, "track (%d): ERROR : MS PASSED > WAIT MS\n", id);
    return 1;
  }
#ifdef TRACKDEBUG
  if (current_time==time_at_next_event) printfdebug("track(%d): _OK_",id);
#endif
  return 0;
}

int MidiTrack::currentMs(double ms)
{
  if (endoftrack==1) return 0;
  current_time=ms;
  //printfdebug("CURR %g",current_time);
#ifdef PEDANTIC_TRACK
  if (current_time>time_at_next_event) 
  {
    fprintf(stderr,"track(%d): ERROR : MS PASSED > WAIT MS\n", id);
    exit(-1);
    return 1;
  }
#endif
  return 0;
}

void MidiTrack::readEvent(MidiEvent *ev)
{
  int i,j;
  if (endoftrack==1) 
  {
    ev->command=0;
    return;
  }
  /*
     printfdebug("...... %d\n",id);
     printfdebug("current : %g , tane : %g\n",current_time,time_at_next_event);
     printfdebug("......\n");
   */
  int skip_event=0;
  current_time=time_at_next_event;
  if (((*ptrdata)&0x80)!=0)
  {
    ev->command=(*ptrdata);
    ptrdata++;currentpos++;
    lastcommand=ev->command;
  }
  else
  {
    ev->command=lastcommand;
  }

#ifdef PEDANTIC_TRACK
  if (currentpos>=size)
  {
    endoftrack=1;
    delta_ticks = wait_ticks = ~0;	
    time_at_next_event=10000 * 60000L;
    ev->command=MIDI_SYSTEM_PREFIX;
    ev->chn=0xF;
    ev->d1=ME_END_OF_TRACK;
    fprintf(stderr, "track (%d): EndofTrack found by accident 3\n",id);
    return;
  }
#endif

  ev->chn=ev->command & 0xF;
  ev->command=ev->command & 0xF0;
  switch (ev->command)
  {
    case (MIDI_NOTEON) :
      ev->note = *ptrdata;ptrdata++;currentpos++;
      ev->vel  = *ptrdata;ptrdata++;currentpos++;
      if (ev->vel==0)
	note[ev->chn][ev->note]=false;
      else
	note[ev->chn][ev->note]=true;

#ifdef TRACKDEBUG2
      if (ev->chn==6) {
	if (ev->vel==0) printfdebug("Note Onf\n");
	else printfdebug("Note On\n");
      };
#endif
      break;
    case (MIDI_NOTEOFF) :
#ifdef TRACKDEBUG2
      if (ev->chn==6) printfdebug("Note Off\n");
#endif
      ev->note = *ptrdata;ptrdata++;currentpos++; 
      ev->vel  = *ptrdata;ptrdata++;currentpos++;
      note[ev->chn][ev->note]=false;

      break;
    case (MIDI_KEY_PRESSURE) :
#ifdef TRACKDEBUG2
      if (ev->chn==6) printfdebug ("Key press\n");
#endif
      ev->note = *ptrdata;ptrdata++;currentpos++; 
      ev->vel  = *ptrdata;ptrdata++;currentpos++;
      break;
    case (MIDI_PGM_CHANGE) :
#ifdef TRACKDEBUG2
      if (ev->chn==6) printfdebug ("Pgm\n");
#endif
      ev->patch = *ptrdata;ptrdata++;currentpos++; 
      break;
    case (MIDI_CHN_PRESSURE) :
#ifdef TRACKDEBUG2
      if (ev->chn==6) printfdebug ("Chn press\n");
#endif
      ev->vel  = *ptrdata;ptrdata++;currentpos++;
      break;
    case (MIDI_PITCH_BEND) :
#ifdef TRACKDEBUG2
      if (ev->chn==6) printfdebug ("Pitch\n");
#endif
      ev->d1 = *ptrdata;ptrdata++;currentpos++; 
      ev->d2 = *ptrdata;ptrdata++;currentpos++;
      break;
    case (MIDI_CTL_CHANGE) :
#ifdef TRACKDEBUG2
      if (ev->chn==6) printfdebug (stderr, "Ctl\n");
#endif
      ev->ctl = *ptrdata;ptrdata++; currentpos++;
      ev->d1  = *ptrdata;ptrdata++;currentpos++;
      /*
	 switch (ev->ctl)
	 {
	 case (96) : printfdebug("RPN Increment\n");break;
	 case (97) : printfdebug("RPN Decrement\n");break;
	 case (98) : printfdebug("nRPN 98 %d\n",ev->d1);break;
	 case (99) : printfdebug("nRPN 99 %d\n",ev->d1);break;
	 case (100) : printfdebug("RPN 100 %d\n",ev->d1);break;
	 case (101) : printfdebug("RPN 101 %d\n",ev->d1);break;
	 };
       */
      break;

    case (MIDI_SYSTEM_PREFIX) :
#ifdef TRACKDEBUG2
      if (ev->chn==6) printfdebug ("Sys Prefix\n");
#endif
      switch ((ev->command|ev->chn))
      {
	case (0xF0) : 
	case (0xF7) :
	  ev->length=readVariableLengthValue();
#ifdef PEDANTIC_TRACK
	  if (endoftrack) 
	  {
	    ev->command=MIDI_SYSTEM_PREFIX;
	    ev->chn=0xF;
	    ev->d1=ME_END_OF_TRACK;
	  }
	  else
#endif
	  {
	    ev->data=ptrdata;
	    ptrdata+=ev->length;currentpos+=ev->length;
	  }
	  break;
	case (0xFE):
	case (0xF8):
	  //		printfdebug("Active sensing\n");
	  break;
	case (META_EVENT) : 
	  ev->d1=*ptrdata;ptrdata++;currentpos++;
	  switch (ev->d1)
	  {
	    case (ME_END_OF_TRACK) :
	      i=0;
	      j=0;
	      while ((i<16)&&(note[i][j]==false))
	      {
		j++;
		if (j==128) { j=0; i++; };
	      }
	      if (i<16) // that is, if there is any key still pressed
	      {
		ptrdata--;currentpos--;
		ev->chn=i;
		ev->command=MIDI_NOTEOFF;
		ev->note = j;
		ev->vel  = 0;
		note[ev->chn][ev->note]=false;
		fprintf(stderr,"Note Off(simulated)\n");
		return;
	      }
	      else
	      {
		endoftrack=1;
		delta_ticks = wait_ticks = ~0;
		time_at_next_event=10000 * 60000L;
#ifdef TRACKDEBUG
		printfdebug("EndofTrack %d event\n",id);
#endif
	      }
	      break;
	    case (ME_SET_TEMPO):
	      ev->length=readVariableLengthValue();
#ifdef PEDANTIC_TRACK
	      if (endoftrack) 
	      {
		ev->command=MIDI_SYSTEM_PREFIX;
		ev->chn=0xF;
		ev->d1=ME_END_OF_TRACK;
	      }
	      else
#endif
	      {
		ev->data=ptrdata;
		ptrdata+=ev->length;currentpos+=ev->length;
		//  tempo=((ev->data[0]<<16)|(ev->data[1]<<8)|(ev->data[2]));
		//  ticks_from_previous_tempochange=0;
		// 	time_at_previous_tempochange=current_time;
#ifdef TRACKDEBUG
		printfdebug("Track %d : Set Tempo : %ld\n",id,tempo);
#endif
#ifdef CHANGETEMPO_ONLY_IN_TRACK0
		if (id!=0) skip_event=1;
#endif
	      }
	      break;
	    case (ME_TIME_SIGNATURE) :
	      ev->length=*ptrdata;ptrdata++;currentpos++;
	      ev->d2=*ptrdata;ptrdata++;currentpos++;
	      ev->d3=power2to(*ptrdata);ptrdata++;currentpos++;
	      ev->d4=*ptrdata;ptrdata++;currentpos++;
	      ev->d5=*ptrdata;ptrdata++;currentpos++;
#ifdef TRACKDEBUG
	      printfdebug("TIME SIGNATURE :\n");	
	      printfdebug("%d\n",ev->d2);
	      printfdebug("----  %d metronome , %d number of 32nd notes per quarter note\n",ev->d4,ev->d5);
	      printfdebug("%d\n",ev->d3);
#endif
	      break;
	    case (ME_TRACK_SEQ_NUMBER) :
	    case (ME_TEXT) :
	    case (ME_COPYRIGHT) :
	    case (ME_SEQ_OR_TRACK_NAME) :
	    case (ME_TRACK_INSTR_NAME) :
	    case (ME_LYRIC) :
	    case (ME_MARKER) :
	    case (ME_CUE_POINT) :
	    case (ME_CHANNEL_PREFIX) :
	    case (ME_MIDI_PORT) :
	    case (ME_SMPTE_OFFSET) :
	    case (ME_KEY_SIGNATURE) :
	      ev->length=readVariableLengthValue();
#ifdef PEDANTIC_TRACK
	      if (endoftrack) 
	      {
		ev->command=MIDI_SYSTEM_PREFIX;
		ev->chn=0xF;
		ev->d1=ME_END_OF_TRACK;
	      }
	      else
#endif
	      {
		ev->data=ptrdata;
		ptrdata+=ev->length;currentpos+=ev->length;
	      }
	      break;
	    default: 
#ifdef GENERAL_DEBUG_MESSAGES
	      fprintf(stderr,"track (%d) : Default handler for meta event " \
		  "0x%x\n", id, ev->d1);
#endif
	      ev->length=readVariableLengthValue();
#ifdef PEDANTIC_TRACK
	      if (endoftrack) 
	      {
		ev->command=MIDI_SYSTEM_PREFIX;
		ev->chn=0xF;
		ev->d1=ME_END_OF_TRACK;
	      }
	      else
#endif
	      {
		ev->data=ptrdata;
		ptrdata+=ev->length;currentpos+=ev->length;
	      }
	      break;
	  }
	  break;
	default : 
	  fprintf(stderr,"track (%d): Default handler for system event 0x%x\n",
	      id, (ev->command|ev->chn));
	  break;
      }
      break;
    default : 
      fprintf(stderr,"track (%d): Default handler for event 0x%x\n",
	  id, (ev->command|ev->chn));
      break;
  }
#ifdef PEDANTIC_TRACK
  if (currentpos>=size)
  {
    endoftrack=1;
    delta_ticks = wait_ticks = ~0;	
    time_at_next_event=10000 * 60000L;
    printfdebug("track (%d): EndofTrack reached\n",id);
  }
#endif
  if (endoftrack==0)
  {
    current_ticks+=delta_ticks;
    delta_ticks=readVariableLengthValue();
#ifdef PEDANTIC_TRACK
    if (endoftrack) 
    {
      ev->command=MIDI_SYSTEM_PREFIX;
      ev->chn=0xF;
      ev->d1=ME_END_OF_TRACK;
      return;
    }
#endif
    ticks_from_previous_tempochange+=delta_ticks;

    time_at_next_event=T2MS(ticks_from_previous_tempochange)+time_at_previous_tempochange;
    /*
       printf("tane2 : %g, ticks : %g, delta_ticks %ld, tempo : %ld\n",
       time_at_next_event,ticks_from_previous_tempochange,delta_ticks,tempo);
       printf("timeatprevtc %g , curr %g\n",time_at_previous_tempochange,current_time);
     */
    wait_ticks=delta_ticks;

  }
  if (skip_event) readEvent(ev);
}


void MidiTrack::clear(void)
{
  endoftrack=1;
  ptrdata=data;
  current_ticks=0;
  currentpos=0;

  for (int i=0;i<16;i++)
    for (int j=0;j<128;j++)
      note[i][j]=false;

  delta_ticks = wait_ticks = ~0;	
  time_at_previous_tempochange=0;
  current_time=0;
  ticks_from_previous_tempochange=0;
  tempo=1000000;
  time_at_next_event=10000 * 60000L;

}


void MidiTrack::init(void)
{
  if (data==0L) { clear(); return; };
  endoftrack=0;
  ptrdata=data;
  current_ticks=0;
  currentpos=0;

  for (int i=0;i<16;i++)
    for (int j=0;j<128;j++)
      note[i][j]=false;

  delta_ticks=readVariableLengthValue();
  if (endoftrack) return;
  wait_ticks=delta_ticks;


  time_at_previous_tempochange=0;
  current_time=0;
  ticks_from_previous_tempochange=wait_ticks;
  tempo=1000000;
  time_at_next_event=T2MS(delta_ticks);
  //printf("tane1 : %g\n",time_at_next_event);
}

void MidiTrack::changeTempo(ulong t)
{
  if (endoftrack==1) return;
  if (tempo==t) return;
  double ticks;
  time_at_previous_tempochange=current_time;
  ticks=MS2T(time_at_next_event-current_time);
  tempo=t;
  time_at_next_event=T2MS(ticks)+current_time;
  ticks_from_previous_tempochange=ticks;

}

/*
double MidiTrack::absMsOfNextEvent (void) 
{
  //printf("%d : %g\n",id,time_at_next_event);
  return time_at_next_event;
}
*/

#undef T2MS
#undef MS2T
