
#include "stdafx.h"

#include <dxmus.core/Controller.h>
#include "DmPortPlayer.h"



namespace GuruGuruSmf { namespace Core { namespace DirectMusicPort {

	// スレッド呼び出し用
	static DWORD WINAPI PlayerWorker(LPVOID object)
	{
		CoInitializeEx(NULL, COINIT_MULTITHREADED);
		//CoInitialize(NULL);
		((Player*)object)->OnWorker();
		CoUninitialize();
		return 0;
	}

	// コンストラクタ
	Player::Player(GuruGuruSmf::Dxmus::Controller* pctrl)
	{
		ctrl = pctrl;
		times = new PlayerTimes(pctrl);
		device = new Device(times, pctrl);
		volume = new Volume(device);

		workerThread = new Thread();

		sentReset = false;
		isWorkerOn = false;
		doMasterPitch = false;
		loopCount = 0;
		state = PlayerState::Stop;
		seek_tick = 0;//by Kobarin
		first_note_tick = 0;//by Kobarin
		max_volume = 0;//by Kobarin
		has_reset = 0;//by Kobarin
	}

	// デストラクタ
	Player::~Player()
	{
		TRACE(L"●DmPort.Player:デストラクタ:CloseDevice\n");
		CloseDevice();

		TRACE(L": volume\n");
		delete volume;
		TRACE(L": device\n");
		delete device;
		TRACE(L": times\n");
		delete times;

		TRACE(L": thread\n");
		delete workerThread;
	}

	// デバイスを開く
	GGSERROR Player::OpenDevice(int deviceId, HWND handle)
	{
		CloseDevice();

		GGS_LOGGING_INITIALIZE();
		return device->Open(deviceId, handle);
	}

	// デバイスを閉じる
	void Player::CloseDevice()
	{
		if (!device->GetIsOpen()) return;
		
		TRACE(L"DmPort.Player: CloseDevice : Stop\n");
		Stop(0);
		TRACE(L"DmPort.Player: CloseDevice : Sleep\n");
		//Sleep(150);	// FinalizeMusicでリセットを送信しているため
		//↑ del by Kobarin
		//直ぐにデバイスを閉じてしまうので仮に取りこぼしがあっても（あるのか？）音が止まるだけで待ちは不要と判断
		TRACE(L"DmPort.Player: CloseDevice : device->Close()\n");
		device->Close();
	}

	// 演奏！
	GGSERROR Player::Play(ISmfController* smfCtrl, int option, int seek, int fadeInTime, int newKeyShift)
	{
		if (!device->GetIsOpen()) return GgsError::NotReady;
		Stop(0);

		Smf* newSmf = smfCtrl->GetSmf();

		//by Kobarin 使用ポート数を設定しておくことで、使わないポートに送信しないようにする
		//フェードアウト時のデータ送信量をなるべく減らす(モタりの原因だと思ってたが関係なかった模様。せっかくなので残しておく)
		device->SetNumPort(newSmf->GetNumPort());

		if (!newSmf->GetIsReady()) return GgsError::NotReady;
		if (newSmf->GetData()->FirstNoteTick == -1) return GgsError::NoError;	// 無音データはスルー

		GGSERROR err = smfCtrl->Open();
		if(err != GgsError::NoError) return err;
		smfController = smfCtrl;

		Initialize(newSmf);

		VoiceList* voiceList = newSmf->GetData()->VoiceList;
		ctrl/*GuruGuruSmf::Dxmus::Controller::GetInstance()*/->PortSetVoiceList((DWORD*)voiceList->GetItems(), voiceList->GetCount());
		ParsePlayOption(option);
		SetKeyShift(newKeyShift);
		
		// 直前に何か送信していたら1ミリ秒のウェイトをかける
		err = times->ReadMasterClock();
		if(err != GgsError::NoError) return err;
		times->SetMarkerDmTimeByQueueDmTime(10000);
	
		device->SetPort(-1);//by Kobarin 初期化メッセージは全てのポートに送信
		// Stopなどでリセットを送ってなかった場合、GSリセットを送っておく
		if (!sentReset){
			err = device->SendLongMessage(Exclusive::GSReset, Exclusive::GSResetLength);
			if(err != GgsError::NoError) return err;
			times->Wait(10000);		// 不要なようですが一応1ミリ秒ウェイト
		}
		sentReset = false;
		
		// RPN0番にはリセットが効かないようなので送信しておく
		InitializePitchBendSensitivity(device);

		volume->SetFadeIn(fadeInTime);
		volume->InitializeModule(GetDoNormalize(), newSmf->GetData()->MaxVolume);
		//by Kobarin
		max_volume = newSmf->GetData()->MaxVolume;
		//times->Wait(10000);		// 処理順が狂うことがあるようなのでウェイト
		device->SetPort(0);//現在のポートを0に設定しておく
		seek_tick = seek;
		first_note_tick = newSmf->GetData()->FirstNoteTick;
		has_reset = newSmf->GetData()->HasReset;
		if (GetDoSkipBeginning() && seek == 0) {//SkipBeginning は呼ばず、代わりに FirstNoteTick までシーク
			seek_tick = first_note_tick;
		}
		times->Rewind();//位置を0に戻す
		times->CalcNowTime();//位置を0に戻す
		state = PlayerState::Play;
		StartWorker();
		return GgsError::NoError;
		/* del by Kobarin(ワーカースレッド内で処理)
		if (seek > 0){
			Seek(seek);
		}else{
			SkipBeginning();
		}

		times->UpdateQueueDmTime();

		if (TimeEventStep()) return GgsError::NoError;
		state = PlayerState::Play;
		StartWorker();
		return GgsError::NoError;
		*/
		
	}

	// 停止
	void Player::Stop(int fadeOutTime)
	{
		if (!device->GetIsOpen() || state == PlayerState::Stop) return;
		
		// 一時停止状態の時はただちに停止
		if (state != PlayerState::Pause && fadeOutTime > 0){	// フェードアウト処理
			volume->SetFadeOut(fadeOutTime);
			return;
		}
		
		StopWorker();
		FinalizeMusic(false, true);
	}

	// 一時停止
	void Player::Pause()
	{
		if (state == PlayerState::Play){
			isPause = true;
			doRestart = false;
			Sleep(Constant::TimeInterval);	// 保険
		}
	}

	// 一時停止解除
	void Player::Restart()
	{
		if (state == PlayerState::Pause){
			doRestart = true;
			Sleep(Constant::TimeInterval);	// 保険
		}
	}

	// 演奏情報を取得
	void Player::GetStatus(PlayerStatus* status)
	{
		status->State = state;
		status->LoopCount = loopCount;
		status->Tick = times->GetNowTick();
		status->Time = (int)times->GetNowTime();
		status->Tempo = times->GetMusicTempo();
	}

	// マスターテンポ
	void Player::SetMasterTempo(float masterTempo)
	{
		times->SetMasterTempo(masterTempo);
	}

	// マスターボリューム
	void Player::SetMasterVolume(int masterVolume)
	{
		volume->SetMasterVolume(masterVolume);
	}

	// マスターピッチ
	void Player::SetMasterPitch(int pitch)
	{
		SetMasterPitchBase(pitch);
		doMasterPitch = true;
	}
		
	// マーカーTickに加算処理
	void Player::AddMarkerTick(int deltaTime)
	{
		times->UpdateMarkerTick(deltaTime);
		
		// ループ位置を設定
		if (loopPosition == -1 && times->PassedLoopTick()){
			loopPosition = GetCode()->GetPosition();
		}
	}
		
	// 終点処理
	void Player::MusicEnd()
	{
		isMusicEnd = true;
	}

	// テンポをセット
	void Player::SetTempo(int tempo)
	{
		times->SetTempo(tempo);
	}

	// ボリュームメッセージを送信
	void Player::SendVolume(int channel, int vol)
	{
		volume->SetVolume(device->GetPort(), channel, vol);//by Kobarin
	}
		
	// ピッチベンドメッセージを送信
	void Player::SendPitchBend(int channel, int pitch)
	{
		SendPitchBendBase(device, channel, pitch);
	}

	// エクスクルーシブを送信
	void Player::SendExclusive(CodeEvent* codeEvent)
	{
		SendExclusiveBase(device, codeEvent);
	}
		
	// Seek用にイベントを登録
	void Player::RegisterEvent(CODE_EVENTTYPE type, int channel, int data1, CodeEvent* codeEvent)
	{
		//int key = (byte)type << 16 | channel << 8 | data1;
		//by Kobarin
		//複数ポート対応
		int port = device->GetPort();
		int key = (byte)port << 24 | (byte)type << 16 | channel << 8 | data1;
		seekingCodeEventTable[key] = *codeEvent;
	}

	// シフトされたノート番号を取得
	int Player::GetShiftedNote(int note, int channel)
	{
		//return GetShiftedNoteBase(note, channel);//del by Kobarin
		return GetShiftedNoteBase(device, note, channel);//by Kobarin
	}
	
	static int CheckWaitEvent(CodeEvent &ev)
	{//byKobarin
	 //送信前後に待たないと正常に演奏出来なくなるMIDIイベントの場合に待機時間を返す
	 //送信前に待つ必要がある場合は負の値、送信後に待つ必要がある場合は正の値、待つ必要がない場合は 0 を返す
		//return 0;
		if (ev.Type == CodeEventType::Exclusive) {//システムエクスクルーシブ
			return 0;//シーク後等に正常再生出来なくなるデータで試す限り、SysEx で待機しても効果はなさそう
		}
		#if 1
		else if (ev.Type == CodeEventType::UseRhythmPart){//リズムパートのSysEx
		//送信後ではなく、送信前に待たないと正常に再生されないデータがある(kiyoka88.mid, tm_kataf.mid, ln2mat.mid)
		//送信後に待機して状況が改善するデータは見つからない
		//送信前に待つだけでは解決出来ないデータがあり、振る舞いが謎
			return -100;//80ms 以上は待つ必要がある
		}
		#endif
		else if (ev.Type == CodeEventType::PitchBend){//ピッチベンド(シーク時に全てのピッチベンドを送信すると正常に再生されなくなるのを確認出来る)(ffnak88.mid, ds1.mid)
			return 20;//発音前にピッチベンドが詰まってると演奏がおかしくなる可能性がある
		}
		#if 1
		else if (ev.Type == CodeEventType::Volume) {//チャンネルボリューム
		//リズムパートの SysEx を使う曲データでチャンネルボリュームが利かないことがある(scheme.mid)
		//発音直前にチャンネルボリュームを送信すれば回避出来るが待機しないとシーク時に稀に演奏がおかしくなるデータがある(futari55.mid)
			return 20;
		}
		#endif
		#if 0
		else if (ev.Type == CodeEventType::ProgramChange) {//プログラムチェンジ
			return 20;//関係なさそう
		}
		#endif
		else if (ev.Type == CodeEventType::ControlChangeA) {
			if(ev.ByteData1 == 11){//エクスプレッション(シーク時に全てのエクスプレッションを送信すると正常に再生されなくなるのを確認出来る)(hsline89.mid)
				return 20;//どのデータでも頻繁に呼ばれるので直接の原因は他の MIDI イベントにあるのかもしれないが、マスターボリュームが確実におかしいのを考えると音量系が怪しい気がする
			}
			//if (ev.ByteData1 == 10) {//パン
			//	return 20;//関係なさそう
			//}
		}
		#if 0
		else if (ev.Type == CodeEventType::ControlChangeB) {
			if (ev.ByteData1 == 0) {//バンクセレクトMSB
				return 20;//関係なさそう
			}
			if (ev.ByteData1 == 32) {//バンクセレクトLSB
				return 20;//関係なさそう
			}
		}
		#endif
		#if 0
		else if (ev.Type == CodeEventType::ControlChangeC) {
			return 0;//関係なさそう
		}
		#endif
		return 0;
	}
	// 現在時間+PrepareTimeまでのイベントを処理
	bool Player::TimeEventStep()
	{
		bool first_note_on = false;
		while(!times->PassedPrepareTime()){
			CodeEvent ev;
			GetCode()->ReadEvent(&ev);
			int wait = CheckWaitEvent(ev);
			if(wait < 0){//送信前に待つ必要がある MIDI イベント(リズムパート)
				int wait_time = -wait;
				Sleep(wait_time);
				times->FixQueueDmTime(wait_time);
				times->Wait(wait_time * 10000);
			}
			PlayingEventExecute(&ev, this);
			if(!times->PassedFirstNote()){//発音前
				first_note_on = true;
				if (ev.Type != CodeEventType::DeltaTime && //ウェイトをかけない
					ev.Type != CodeEventType::Tempo &&     //ウェイトをかけない
					ev.Type != CodeEventType::Port         //ウェイトをかけない
					) {
					if(wait > 0){
						Sleep(wait);
						times->FixQueueDmTime(wait);
						times->Wait((UINT64)wait * 10000);
					}
				}
			}
			else if(first_note_on){
				volume->Update();//発音前に音量設定(リズムパートのSysExを使うとチャンネルボリュームが利かなくなることがあることへの対処)(futari55.mid/akira.mid/scheme.mid/ln2mat.mid)
			}
			// 末尾の空白飛ばし
			if(GetDoSkipEnding()){
				if(times->PassedLastNote()){
					times->RewindQueueTime();	// 処理済み時間まで戻しておく
					isMusicEnd = true;
				}
			}
			
			// ループか終了か
			if(isMusicEnd){
				++loopCount;
				if(GetDoLoop()){
					switch(GetSmf()->GetData()->LoopType){
					case LoopType::Beginning:
						Rewind();
						SkipBeginning();
						break;
					case LoopType::LoopPointAtBeginning:
						Rewind();
						break;
					case LoopType::LoopPoint:
						GetCode()->SetPosition(loopPosition);
						times->GotoLoopPoint();
						isMusicEnd = false;
						break;
					case LoopType::End:
						return true;
					}
				}else{
					return true;	// 自然終了
				}
			}
		}
		
		// 現在のTickと演奏時間を計算
		times->CalcNowTime();
		return false;
	}
		
	// 冒頭の空白をスキップ
	//by Kobarin
	//playopt でループ有効時、CC111 を使わないタイプの曲で先頭に戻るときだけ呼ばれる
	void Player::SkipBeginning() 
	{//by Kobarin
		if(has_reset){//リセットを含む曲データは先頭に戻ったらリセットする
			device->SetPort(-1);//初期化メッセージは全てのポートに送信
			device->SendLongMessage(Exclusive::GSReset, Exclusive::GSResetLength);
			InitializePitchBendSensitivity(device);
			volume->InitializeModule(GetDoNormalize(), max_volume);
			if (GetCanDoMasterPitch()) {
				SendMasterPitch();
			}
		}
		device->SetPort(0);//現在のポートを0に設定しておく
		if(GetDoSkipBeginning()){
			/*times->SetSkipping(true);
			while(!times->PassedFirstNote()){
				CodeEvent ev;
				GetCode()->ReadEvent(&ev);
				PlayingEventExecute(&ev, this);
			}
			times->SetSkipping(false);
			*/
			DWORD time = timeGetTime();
			Seek(first_note_tick);
			DWORD elapsed = timeGetTime() - time;
			times->Wait((UINT64)elapsed * 10000);
			times->UpdateQueueDmTime();
		}
	}
	// シーク処理
	void Player::Seek(int tick)
	{
		seekingCodeEventTable.clear();
		times->SetSkipping(true);
		while(!times->PassedTick(tick)){
			CodeEvent ev;
			GetCode()->ReadEvent(&ev);
			#if 0
			if (ev.Type == CodeEventType::UseRhythmPart) {//送信前に80ms以上待たないと演奏がおかしくなることが多い
				Sleep(100);
			}
			SeekingEventExecute(&ev, this);//del by Kobarin
			times->CalcNowTime();
			volume->FadePause();//シーク中のフェードアウト(シークが終わってから指定時間経過後にフェードが終わるようにする)
			if (isPause) {//シーク中の一時停止(内部的にはシーク処理を続ける)
				WorkerPause();
			}
			if (doRestart) {
				WorkerRestart();
			}
			#else
			//by Kobarin
			//送信前後にメッセージに応じてウェイトする
			if (!isWorkerOn)break;//シーク中の演奏停止
			if (ev.Type != CodeEventType::NoteOn &&
				ev.Type != CodeEventType::NoteOff){//NoteOn/Off 以外
				int wait = CheckWaitEvent(ev);
				//wait = 0;
				if(wait < 0){//送信前に待つ(リズムパート)
					Sleep(-wait);
				}
				if(!times->PassedFirstNote()) {//発音前は全て送信してウェイトをかける
					PlayingEventExecute(&ev, this);
					if (ev.Type != CodeEventType::DeltaTime && //発音前でも待たない(DeltaTime/Tempo/Port/MusicEnd)
						ev.Type != CodeEventType::Tempo &&
						ev.Type != CodeEventType::Port &&
						ev.Type != CodeEventType::MusicEnd &&
						wait == 0){
						wait = 0;//発音前は1ms以上待つ
					}
				}
				#if 1 //チャンネルボリュームは送信後待たないと演奏がおかしくなる
				else if (ev.Type == CodeEventType::Volume) {//チャンネルボリューム(最後に送信)
					SeekingEventExecute(&ev, this);
					wait = 0;
				}
				#endif
				#if 1 //ピッチベンドは送信後待たないと演奏がおかしくなる
				else if (ev.Type == CodeEventType::PitchBend) {//ピッチベンド(最後に送信)
					SeekingEventExecute(&ev, this);
					wait = 0;
				}
				#endif
				#if 1 //エクスプレッションを送信後に待つとシークに物凄く時間がかかるデータがある(待たないと演奏がおかしくなる)
				else if(ev.Type == CodeEventType::ControlChangeA && ev.ByteData1 == 11){//エクスプレッション(最後に送信)
					SeekingEventExecute(&ev, this);
					wait = 0;
				}
				#endif
				else{
					PlayingEventExecute(&ev, this);
				}
				if(wait > 0){
					Sleep(wait);
				}
			}
			times->CalcNowTime();
			volume->FadePause();//シーク中のフェードアウト(シークが終わってから指定時間経過後にフェードが終わるようにする)
			if (isPause) {//シーク中の一時停止(内部的にはシーク処理を続ける)
				WorkerPause();
			}
			if (doRestart){
				WorkerRestart();
			}
			//ここまで
			#endif
			if (isMusicEnd) break;
		}
		// 保存しておいたデータを送信
		int cur_port = device->GetPort();
		std::map<int, CodeEvent>::iterator it = seekingCodeEventTable.begin();
		while(it != seekingCodeEventTable.end()){
			int port = (it->first) >> 24;
			int wait = CheckWaitEvent(it->second);
			if(wait < 0)Sleep(-wait);//送信前に待つ
			device->SetPort(port);
			PlayingEventExecute(&it->second, this);
			if(wait > 0)Sleep(wait);//送信後に待つ
			++it;
		}
		seekingCodeEventTable.clear();
		times->SetSkipping(false);
		device->SetPort(cur_port);
		volume->EndFadePause();//シーク中のフェードアウトに対応
		volume->Update();//発音前に音量設定(リズムパートのSysExを使うとチャンネルボリュームが利かなくなることがある)(futari55.mid/akira.mid/scheme.mid/ln2mat.mid)
	}
	
	// スレッド開始
	void Player::StartWorker()
	{
		workerThread->Join();	// スレッドが完全に終了してないケースがあるので待つ

		isWorkerOn = true;
		workerThread->Start(this, PlayerWorker, THREAD_PRIORITY_TIME_CRITICAL);
	}

	// スレッド停止
	void Player::StopWorker()
	{
		if (isWorkerOn){
			isWorkerOn = false;
			workerThread->Join();
		}
	}
		
	// 一時停止
	void Player::WorkerPause()
	{
		if (state == PlayerState::Play){
			// 音量を0にする
			times->EnterRealtimeMode();
			volume->Mute(true);
			times->LeaveRealtimeMode();
			times->Pause();
			
			state = PlayerState::Pause;
		}
	}
		
	// 一時停止解除
	void Player::WorkerRestart()
	{
		times->Restart();
		
		times->EnterRealtimeMode();
		volume->Mute(false);
		times->LeaveRealtimeMode();
		
		state = PlayerState::Play;
		isPause = false;
		doRestart = false;
	}
		
	// スレッド実行部
	void Player::OnWorker()
	{	//by Kobarin(シークと冒頭の空白スキップをスレッド内で実行)
		DWORD time = timeGetTime();
		if (seek_tick > 0) {
			Seek(seek_tick);
			seek_tick = 0;
		}
		//else {
		//	SkipBeginning();
		//}
		//times->UpdateQueueDmTime();
		DWORD elapsed = timeGetTime() - time;
		times->Wait((UINT64)elapsed * 10000);
		times->UpdateQueueDmTime();
		//ここまで
		while(isWorkerOn){
			times->ReadMasterClock();
			
			if (doRestart) WorkerRestart();
			
			if (isPause){
				WorkerPause();
				volume->FadePause();
			}else{
				volume->EndFadePause();
				times->EnterRealtimeMode();
				bool terminated = volume->Worker();
				times->LeaveRealtimeMode();
				if (terminated){
					// オールノートオフ
					device->SetPort(-1);//by Kobarin 全ポートに対して送信(直ぐに終了するのでポート番号を元に戻す処理は省略)
					for (int i=0; i<16; i++){
						device->SendShortMessage(0xb0+i, 123, 0);
					}
					times->UpdateQueueDmTime();
					FinalizeMusic(true, true);
					return;
				}
				
				if (GetCanDoMasterPitch() && doMasterPitch){
					SendMasterPitch();
					doMasterPitch = false;
				}
				
				if (TimeEventStep()){
					FinalizeMusic(true, false);
					return;
				}
			}
			//Sleep(Constant::TimeInterval);
			//times->FixQueueDmTime();
			//by Kobarin
			if(volume->IsFading()){
				Sleep(1);//フェード中の Sleep は 1ms だけ(モタリ対策)
				times->FixQueueDmTime(1);
			}
			else{
				Sleep(Constant::TimeInterval);
				times->FixQueueDmTime();
			}
		}
	}
	
	// 音楽停止時の最終処理
	void Player::FinalizeMusic(bool isWorker, bool doReset)
	{
		if (isWorker){
			isWorkerOn = false;
		}
		state = PlayerState::Stop;
		smfController->Close();
	
		if (doReset){
			device->SetPort(-1);//by Kobarin 全てのポートに送信
			// 
			// GSリセットを送る
			times->ReadMasterClock();
			times->SetMarkerDmTimeByQueueDmTime(10000);
			device->SendLongMessage(Exclusive::GSReset, Exclusive::GSResetLength);
			sentReset = true;
			times->UpdateQueueDmTime();
		}
	}
	
	// マスターピッチを送信
	void Player::SendMasterPitch()
	{
		times->EnterRealtimeMode();
		SendMasterPitchBase(device);
		times->LeaveRealtimeMode();
	}
		
	// SMFで初期化
	void Player::Initialize(Smf* newSmf)
	{
		PlayerBase::Initialize(newSmf);
		
		times->Initialize(newSmf);
		loopPosition = -1;
	
		loopCount = 0;
		isPause = false;
		doRestart = false;
	
		Rewind();
	}

	// 冒頭に巻き戻し
	void Player::Rewind()
	{
		PlayerBase::Rewind();
		isMusicEnd = false;

		times->Rewind();
		GetCode()->SetPosition(0);
	}



}}}

