/***************************************************************************
 * tabtimesplayer.cpp: implementation of TabTimesPlayer class
 *
 * This file is part of KTabEdit, a KDE tabulature editor
 *
 * copyright (C) 2003-2006 the KTabEdit development team <mrgwen@gmail.com>
 ***************************************************************************/

/***************************************************************************
 * 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.
 *
 * See the file COPYING for more information.
 ***************************************************************************/

#include "tabtimesplayer.h"
#include "util/assert.h"

#include <tse3/Error.h>
#include <tse3/listen/Transport.h>
#include <tse3/Metronome.h>
#include <tse3/Part.h>
#include <tse3/PhraseEdit.h>
#include <tse3/Song.h>
#include <tse3/TempoTrack.h>
#include <tse3/Track.h>
#include <tse3/Transport.h>
#include <tse3/util/MidiScheduler.h>

#include "settings.h"
#include "tablature/tabnote.h"
#include "tablature/tabtimes.h"
#include "tablature/tabbar.h"
#include "tablature/tabtrack.h"

TabTimesPlayer::TabTimesPlayer(const TabTime* time, uint i)
: QThread(),
  mTabTime(0x0),
  scheduler(0),
  stop(false),
  channel(0),
  tempo(i)
{
	PRECONDITION( time != 0x0 );
	
	mTabTime = new TabTime(0);
	mTabTime->clone(time);
}

TabTimesPlayer::~TabTimesPlayer()
{
	delete mTabTime;
}

void TabTimesPlayer::setScheduler(TSE3::MidiScheduler* midiScheduler)
{
	scheduler = midiScheduler;
}

void TabTimesPlayer::setTrack(const TabTrack* currentTrack)
{
	track = currentTrack;
}

void TabTimesPlayer::stopPlaying()
{
	stop = true;
}

void TabTimesPlayer::drum()
{
	channel = 9;
}

void TabTimesPlayer::run()
{
	if (!scheduler || !track)
		return ;
	
	Settings* settings = Settings::getInstance();
	TSE3::PhraseEdit phraseEdit;
	TSE3::Clock time = 0, nextTime = 0;
	uint i;
	int note;
	
	// Select the instrument
	//
	phraseEdit.insert(TSE3::MidiEvent(
				TSE3::MidiCommand(
					TSE3::MidiCommand_ProgramChange, 
					channel, settings->midiPortNumber(), track->patch())
				, 0)
			);
	
	switch (mTabTime->duration()) {
	case NoneDuration:
		break;
	case SixtyFourth:
		nextTime = (TSE3::Clock::PPQN / 32);
		break;
	case HundredTwentyEighth:
		nextTime = (TSE3::Clock::PPQN / 16);
		break;
	case ThirthSecond:  // 1/32, triple croche
		nextTime = (TSE3::Clock::PPQN / 8);
		break;
	case Sixteenth:  // 1/16, double croche
		nextTime = (TSE3::Clock::PPQN / 4);
		break;
	case Eighth:  // 1/8, croche
		nextTime = (TSE3::Clock::PPQN / 2);
		break;
	case Quarter:  // 1/4 - a long vertical line, so we need to find the highest note, noire
		nextTime = TSE3::Clock::PPQN;
		break;
	case Half: // 1/2, blanche
		nextTime = (2 * TSE3::Clock::PPQN);
		break;
	case Whole: // whole, ronde
		nextTime = (4 * TSE3::Clock::PPQN);
		break;
	}
	
	if (mTabTime->isDotted())
		nextTime = nextTime + nextTime / 2;
	
	if (mTabTime->nTuplet())
		nextTime*=2;
	
	nextTime = nextTime + nextTime / ((int)mTabTime->nTuplet() + 1);
	
	// Add the notes to be played
	//
	for (i = 0; i < track->nbStrings(); i++) {
		if (!mTabTime->isNoteEmpty(i) && !mTabTime->isDeadNote(i)) {
			note = track->tune(track->nbStrings() - i - 1) + mTabTime->fret(i);
			
			phraseEdit.insert(TSE3::MidiEvent(
				TSE3::MidiCommand(
					TSE3::MidiCommand_NoteOn, 
					channel, settings->midiPortNumber(), note, 0x60)
				, time, 0x60, nextTime)
			);
		}
	}
	
	phraseEdit.insert(TSE3::MidiEvent(
		TSE3::MidiCommand(
			TSE3::MidiCommand_NoteOn, 
			channel, settings->midiPortNumber(), 0, 0)
		, nextTime, 0, nextTime + TSE3::Clock::PPQN)
	);
	
	// Assemble the song
	//
	TSE3::Song    song(1);
	
	song.tempoTrack()->insert(TSE3::Event<TSE3::Tempo>(TSE3::Tempo(tempo), 0));
	
	TSE3::Phrase *phrase = phraseEdit.createPhrase(song.phraseList());
	TSE3::Part   *part   = new TSE3::Part(0, phraseEdit.lastClock());
	part->setPhrase(phrase);
	song[0]->insert(part);
	
	TSE3::Metronome metronome;
	TSE3::Transport transport(&metronome, scheduler);
	
	// PLay the song
	//
	transport.play(&song, 0);
	do {
		transport.poll();
		
		if (stop)
			transport.stop();
	} while(transport.status() != TSE3::Transport::Resting && !stop);
	
	if (stop)
		transport.stop();
}
