
#include "stdafx.h"

#include "Volume.h"

namespace GuruGuruSmf { namespace Core {

	// コンストラクタ
	Volume::Volume(IDevice* dev)
	{
		device = dev;

		InitializeCriticalSection(&lockObject);
		//volumes = new int[16];
		masterVolume = 0;
		//by Kobarin 変数初期化
		ZeroMemory(volumes, sizeof(volumes));//by Kobarin(実際は後で100に設定されるが念の為)
		//fadingCount = 0;
		//fadingGain = 0;
		//fadingTime = 0;
		state = VolumeState::Normal;
		normalizedGain = 0;
		oldFadingGain = 0;
		updated = false;
		fadeInStartTime = 0;
		fadeInEndTime = 0;  
		fadeOutStartTime = 0;
		fadeOutEndTime = 0;
		fadePauseTime = 0;
	}

	// デストラクタ
	Volume::~Volume()
	{
		//delete[] volumes;

		DeleteCriticalSection(&lockObject);
	}

	// 初期化
	void Volume::InitializeModule(bool normalize, int maxVolume)
	{
		if (normalize){
			normalizedGain = 127 - maxVolume;
		}else{
			normalizedGain = 0;
		}
		
		/*for (int i=0; i<16; i++){
			// ボリュームメッセージを送ってしまいます
			SetVolume(i, 100);
		}*/
		//by Kobarin 全ポートに送信(ここでは実際には送信せず、発音前に送信する)
		//
		for(int i = 0; i < MAX_PORT; i++){
			for(int j = 0; j < 16; j++){
				//SetVolume(i, j, 100);//ここでは実際には送信せず、発音前に送信する
				volumes[i][j] = 100; //テーブルだけ初期化
			}
		}
		fadeOutStartTime = 0;
		fadeOutEndTime = 0;
		fadePauseTime = 0;
		//ここまで
		updated = false;
	}

	// trueだと音量を強制的に0にする、falseだと解除(同期)
	void Volume::Mute(bool isMute)
	{
		if (isMute){
			//for (int i=0; i<16; i++){
			//	device->SendShortMessage(0xb0+i, 7, 0);
			//}
			//by Kobarin
			int cur_port = device->GetPort();
			for (int i = 0; i < MAX_PORT; i++) {
				device->SetPort(i);
				for (int j = 0; j < 16; j++) {
					device->SendShortMessage(0xb0+j, 7, 0);
				}
			}
			device->SetPort(cur_port);
		}else{
			//for (int i=0; i<16; i++){
			//	SetVolume(i, volumes[i]);
			//}
			//by Kobarin
			for(int i = 0; i < MAX_PORT; i++){
				for(int j = 0; j < 16; j++){
					SetVolume(i, j, volumes[i][j]);
				}
			}
		}
	}

	// チャンネルごとのボリューム設定(同期)
	void Volume::SetVolume(int port, int channel, int vol)
	{//by Kobarin ポートごとにボリューム設定
		port %= MAX_PORT;
		if(port < 0){
			for(int i = 0; i < MAX_PORT; i++){
				volumes[i][channel] = vol;
			}
		}
		else{
			volumes[port][channel] = vol;
		}
		vol += normalizedGain + masterVolume + fadingGain;
		
		if (vol > 127){
			vol = 127;
		}else if (vol < 0){
			vol = 0;
		}
		int old_port = device->GetPort();
		device->SetPort(port);
		device->SendShortMessage(0xb0+channel, 7, vol);
		device->SetPort(old_port);
	}

	// マスターボリュームを設定(非同期)
	void Volume::SetMasterVolume(int vol)
	{
		masterVolume = vol;
		updated = true;
	}

	// フェードインを設定(非同期) InitializeModuleの前に呼ぶ
	void Volume::SetFadeIn(int time)
	{
		if (time > 0){
			//fadingTime = time;
			//fadingCount = 0;
			fadingGain = -127;

			state = VolumeState::FadeIn;
			fadeInStartTime = timeGetTime();	   //by Kobarin
			fadeInEndTime = fadeInStartTime + time;//by Kobarin
		}else{
			fadingGain = 0;
			state = VolumeState::Normal;
			fadeInStartTime = fadeInEndTime = 0;
		}
	}

	// フェードアウトを設定(非同期)
	void Volume::SetFadeOut(int time)
	{
		EnterCriticalSection(&lockObject);

		/*if (state == VolumeState::Normal){
			fadingCount = time;
		}else{
			fadingCount = fadingCount * time / fadingTime;
		}*/
		if (fadeOutStartTime == 0) {//フェードアウト中にフェードアウトが呼ばれた場合は何もしない
			fadeOutStartTime = timeGetTime();	   
			fadeOutEndTime = fadeOutStartTime + time;
		}
		//fadingTime = time;
		state = VolumeState::FadeOut;
		
		LeaveCriticalSection(&lockObject);
	}

	// フェード音量を計算
	void Volume::CalcFadingGain()
	{
		//fadingGain = fadingCount * 127 / fadingTime - 127;//del by Kobarin
		//by Kobarin
		DWORD fadein_time = fadeInEndTime - fadeInStartTime;
		DWORD fadeout_time = fadeOutEndTime - fadeOutStartTime;
		int fadingGain_fadein = 0;
		int fadingGain_fadeout = 0;
		DWORD cur_time = timeGetTime();
		if(fadein_time != 0){//フェードイン中
			DWORD cur_time_fadein = cur_time;
			//if (fadeout_time) {//フェードイン中のフェードアウト
			//フェードアウトが呼ばれた時点よりフェードインを進行させない(なんかしっくり来ないので不採用)
			//	cur_time_fadein = fadeOutStartTime;
			//}
			//(フェードイン中にフェードアウトが呼ばれたら同時進行させる方を採用)
			if (cur_time_fadein > fadeInEndTime)cur_time_fadein = fadeInEndTime;
			fadingGain_fadein = (cur_time_fadein - fadeInStartTime) * 127 / fadein_time - 127;
		}
		if(fadeout_time != 0){//フェードアウト中
			DWORD cur_time_fadeout = cur_time;
			if (cur_time_fadeout > fadeOutEndTime)cur_time_fadeout = fadeOutEndTime;
			fadingGain_fadeout = (fadeOutEndTime - cur_time_fadeout) * 127 / fadeout_time - 127;
		}
		fadingGain = fadingGain_fadein + fadingGain_fadeout;
		//ここまで
		
		if (fadingGain > 0){
			fadingGain = 0;
		}
		if (fadingGain != oldFadingGain){
			updated = true;
			oldFadingGain = fadingGain;
		}
	}

	// 非同期処理用のエントリーポイント tureの場合フェードアウト終了
	bool Volume::Worker()
	{
		bool terminated = false;
		
		EnterCriticalSection(&lockObject);
		/* del by Kobarin
		if (state == VolumeState::FadeIn){
			fadingCount += Constant::TimeInterval;
			CalcFadingGain();
			if (fadingCount >= fadingTime){
				state = VolumeState::Normal;
			}
		}else if (state == VolumeState::FadeOut){
			fadingCount -= Constant::TimeInterval;
			CalcFadingGain();
			if (fadingCount < 0){
				state = VolumeState::Normal;
				terminated = true;
			}
		}
		*/
		//by Kobarin
		//音量と終了判定を実際に経過した時間で行うように修正
		//途中で一時停止した場合は再開時までの経過時間分だけフェードが進行する
		if (state == VolumeState::FadeIn) {
			CalcFadingGain();
			if(timeGetTime() >= fadeInEndTime){
				state = VolumeState::Normal;
				fadeInStartTime = fadeInEndTime = 0;
			}
		}
		else if (state == VolumeState::FadeOut) {
			CalcFadingGain();
			if (timeGetTime() >= fadeOutEndTime) {
				state = VolumeState::Normal;
				fadeInStartTime = fadeInEndTime = 0;
				fadeOutStartTime = fadeOutEndTime = 0;
				terminated = true;
			}
		}
		//ここまで
		LeaveCriticalSection(&lockObject);
		
		
		if (updated){
			//for (int i=0; i<16; i++){
			//	SetVolume(i, volumes[i]);
			//}
			//by Kobarin
			//全ポートに送信
			for(int i = 0; i < MAX_PORT; i++){
				for(int j = 0; j < 16; j++){
					SetVolume(i, j, volumes[i][j]);
				}
			}
			updated = false;
		}
		return terminated;
	}

	//フェードイン/フェードアウト中の一時停止
	void Volume::FadePause()
	{//by Kobarin
		if(!IsFading())return;
		//fadePauseTime はワーカースレッド以外のスレッドからはアクセスされないのでクリティカルセクションで保護する必要はない
		if(fadePauseTime == 0){
			fadePauseTime = timeGetTime();
			return;
		}
		//フェードが進まないように時間調整
		DWORD elapsed = timeGetTime() - fadePauseTime;
		fadePauseTime += elapsed;
		EnterCriticalSection(&lockObject);
		if(fadeInStartTime){
			fadeInStartTime += elapsed;
			fadeInEndTime += elapsed;
		}
		if (fadeOutStartTime) {
			fadeOutStartTime += elapsed;
			fadeOutEndTime += elapsed;
		}
		LeaveCriticalSection(&lockObject);
	}



}}

