
#include "stdafx.h"

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


namespace GuruGuruSmf { namespace Core {
namespace WindowsMultimedia {

	// コンストラクタ
	Device::Device()
	{
		isOpen = false;
	}

	// デストラクタ
	Device::~Device()
	{
		Close();
	}

	// デバイスを開く
	GGSERROR Device::Open(int deviceId)
	{
		Close();
		
		if (midiOutOpen(&midiOutHandle, deviceId, 0, 0, 0) != 0)
			return GgsError::CannotOpenDevice;
		isOpen = true;
		return GgsError::NoError;
	}
		
	// デバイスを閉じる
	void Device::Close()
	{
		if (isOpen){
			midiOutClose(midiOutHandle);
			isOpen = false;
		}
	}

	// 長いMIDIメッセージを送信
	GGSERROR Device::SendLongMessage(const byte* data, int length)
	{
		if (!isOpen) return GgsError::NotReady;
		
		MIDIHDR midiHeader;
		int headerSize = sizeof(MIDIHDR);
		ZeroMemory(&midiHeader, headerSize);

		midiHeader.lpData = (LPSTR)data;
		midiHeader.dwBufferLength = length;
		midiHeader.dwFlags = 0;
		midiOutPrepareHeader(midiOutHandle, &midiHeader, headerSize);

		while ((midiHeader.dwFlags & MHDR_PREPARED) != MHDR_PREPARED){
			Sleep(0);
		}

		// MHDR_DONEが立たないことがあるため3回だけ待つ
		if(midiOutLongMsg(midiOutHandle, &midiHeader, headerSize) == MMSYSERR_NOERROR){
			if ((midiHeader.dwFlags & MHDR_DONE) != MHDR_DONE){
				Sleep(1);
				if ((midiHeader.dwFlags & MHDR_DONE) != MHDR_DONE){
					Sleep(5);
					if ((midiHeader.dwFlags & MHDR_DONE) != MHDR_DONE){
						Sleep(5);
					}
				}
			}
		}
		
		midiOutUnprepareHeader(midiOutHandle, &midiHeader, headerSize);
		GGS_LOGGING_LONGMSG(GGS_LOGGING_GETTIME(), length);
		return GgsError::NoError;
	}
		
		
	// 短いMIDIメッセージを送信
	GGSERROR Device::SendShortMessage(int status, int data1, int data2)
	{
		ShortMessage msg;
		msg.Status = (byte)status;
		msg.Data1 = (byte)data1;
		msg.Data2 = (byte)data2;
		msg.Reserve = 0;
		midiOutShortMsg(midiOutHandle, msg.Value);
		GGS_LOGGING_SHORTMSG(GGS_LOGGING_GETTIME(), status, data1, data2);
		return GgsError::NoError;
	}

}


namespace DirectMusicPort {

	// コンストラクタ
	Device::Device(IDirectMusicTimer* timer, GuruGuruSmf::Dxmus::Controller *pctrl)
	{
		times = timer;
		isOpen = false;
		//by Kobarin
		ctrl = pctrl; 
		for (int i = 0; i < MAX_PORT; i++){
			for(int j = 0; j < 16; j++) {
				ch_vols[i][j] = 0xff;
			}
		}
		master_vol = 127;
		num_port = MAX_PORT;
		//ここまで
	}

	// デストラクタ
	Device::~Device()
	{
		Close();
	}

	// デバイスを開く
	GGSERROR Device::Open(int deviceId, HWND handle)
	{
		Close();
		
		GGSERROR err = ctrl/*GuruGuruSmf::Dxmus::Controller::GetInstance()*/->PortOpen(handle);
		if (err != 0) return err;
		isOpen = true;

		//by Kobarin
		for (int i = 0; i < MAX_PORT; i++) {
			for (int j = 0; j < 16; j++) {
				ch_vols[i][j] = 0xff;
			}
		}
		master_vol = 127;
		//ここまで

		return GgsError::NoError;
	}
		
	// デバイスを閉じる
	void Device::Close()
	{
		if (isOpen){
			ctrl/*GuruGuruSmf::Dxmus::Controller::GetInstance()*/->PortClose();
			isOpen = false;
		}
	}
	
	// 長いMIDIメッセージを送信
	GGSERROR Device::SendLongMessage(const byte* data, int length)
	{
	//by Kobarin
	//マスターボリューム系 SysEx をチャンネルボリュームに置き換えて送信
		if (cur_port > num_port - 1) {
			return GgsError::NoError;//使わないポートに対しては何もしない
		}
		static const byte MasterVolGM[] = {0xf0,0x7f,0x7f,0x04,0x01};
		static const byte MasterVolGS[] = {0xf0,0x41,0xff,0x42,0x12,0x40,0x00,0x04};//3バイト目(dev id)は見ない
		static const byte MasterVolXG[] = {0xf0,0x43,0xff,0x4c,0x00,0x00,0x04};	    //
		//Compiler.cpp::ExclusiveEventExecutor::Execute() の実装により、GS Reset 以外は来ない
		//static const byte ResetGM[] = {0xf0,0x7e,0x7f,0x09,0x01,0xf7};//GM System On
		static const byte ResetGS[] = {0xf0,0x41,0x10,0x42,0x12,0x40,0x00,0x7f,0x00,0x41,0xf7};//GS Reset
		//static const byte ResetGS2[] ={0xf0,0x41,0x10,0x42,0x12,0x00,0x00,0x7f,0x00,0x01,0xf7};//System Mode Set
		//static const byte ResetXG[] = {0xf0,0x43,0x10,0x4c,0x00,0x00,0x7e,0x00,0xf7};//XG System On
		bool is_mastervol = false;
		bool is_reset = false;
		if(length == 8 && memcmp(data, MasterVolGM, 5) == 0){//GM MasterVolume
			is_mastervol = true;
			master_vol = data[6];
		}
		else if(length == 11 && memcmp(data, MasterVolGS, 2) == 0 && memcmp(data+3, MasterVolGS+3, 5) == 0){
			is_mastervol = true;
			master_vol = data[8];
		}
		else if (length == 9 && memcmp(data, MasterVolXG, 2) == 0 && memcmp(data+3, MasterVolXG+3, 4) == 0) {
			is_mastervol = true;
			master_vol = data[7];
		}
		//else if(length == 6 && memcmp(data, ResetGM, 6) == 0){//GM System On
		//	is_reset = true;
		//}
		else if(length == 11 && memcmp(data, ResetGS, 11) == 0){//GS Reset
			is_reset = true;
		}
		//else if(length == 11 && memcmp(data, ResetGS2, 11) == 0) {//System Mode Set
		//	is_reset = true;
		//}
		//else if(length == 9 && memcmp(data, ResetXG, 9) == 0){//XG System On
		//	is_reset = true;
		//}
		if(is_mastervol){//マスターボリューム
		//SysEx は送信せず、全ポート(実際は SetNumPort で設定されたポートまで)、全チャンネルにチャンネルボリュームを送信
			int saved_port = cur_port;
			for(int i = 0; i < num_port/*MAX_PORT*/; i++){
				cur_port = i;
				for (int j = 0; j < 16; j++) {
					if(ch_vols[i][j] == 0xff){//未初期化
						ch_vols[i][j] = 100;//チャンネルボリュームの初期値は100(Volume.cpp でもそうしている)
					}
					SendShortMessage(0xb0 + j, 7, ch_vols[i][j]);//ch_vols[i][j] はマスターボリューム適用前の現在の値
				}
			}
			cur_port = saved_port;
			return GgsError::NoError;
		}
		else if(is_reset){//リセット
		//ボリューム値を初期値に戻しておく
			for (int i = 0; i < MAX_PORT; i++) {
				for (int j = 0; j < 16; j++) {
					ch_vols[i][j] = 0xff;
				}
			}
			master_vol = 127;
		}
//ここまで
		TRACE4(L"/SendLongMessage(%d): %02x %02x %02x ", length, data[0], data[1], data[2]);
		for(int i = 3; i < length; i++){
			TRACE1(L"%02x ", data[i]);
		}
		TRACE(L"\n");
		//TRACE3(L"%x %x %x\n", data[3], data[4], data[5]);

		__int64 t = times->GetMarkerDmTime();
		GGS_LOGGING_LONGMSG(t, length);
		//GGSERROR err = ctrl/*GuruGuruSmf::Dxmus::Controller::GetInstance()*/->PortSendLongMessage(t, (LPBYTE)data, length);
		GGSERROR err = ctrl->PortSendLongMessage(cur_port, t, (LPBYTE)data, length);
		if(err != GgsError::NoError) return err;
		times->IncrementMarkerDmTime();
		return GgsError::NoError;
	}
	
	
	// 短いMIDIメッセージを送信
	GGSERROR Device::SendShortMessage(int status, int data1, int data2)
	{
	//by Kobarin
		if (cur_port > num_port-1){
			return GgsError::NoError;//使わないポートに対しては何もしない
		}
		if(data1 == 7 && (status&0xf0)==0xb0){//チャンネルボリューム
		//マスターボリューム系 SysEx の音量指定も反映させる
			int ch = status & 0x0f;
			int port = cur_port % MAX_PORT;
			if(port >= 0){
				ch_vols[port][ch] = data2;//マスターボリューム適用前の値を保持しておく(マスターボリューム系SysExが呼ばれたときに全チャンネルに反映させる為)
			}
			else{//全てのポート
				for(int i = 0 ; i < MAX_PORT; i++){
					ch_vols[i][ch] = data2;//全ポートのマスターボリューム適用前の値を保持しておく(マスターボリューム系SysExが呼ばれたときに全チャンネルに反映させる為)
				}
			}
			//int vol = data2 * master_vol / 127;
			int vol = data2 + (master_vol-127);
			if(vol < 0)vol = 0;
			data2 = vol;
		}
	//ここまで
		ShortMessage msg;
		msg.Status = (byte)status;
		msg.Data1 = (byte)data1;
		msg.Data2 = (byte)data2;
		msg.Reserve = 0;

		__int64 t = times->GetMarkerDmTime();
		GGS_LOGGING_SHORTMSG(t, status, data1, data2);
		
		//GGSERROR err = ctrl/*GuruGuruSmf::Dxmus::Controller::GetInstance()*/->PortSendShortMessage(t, msg.Value);
		GGSERROR err = ctrl->PortSendShortMessage(cur_port, t, msg.Value);
		if(err != GgsError::NoError) return err;
		times->IncrementMarkerDmTime();
		return GgsError::NoError;
	}
	

}

}}
