//********************************************************
// Windows SAPI コールライブラリ
//------------------------------------------------------------------------
// CopyRight : MSLABO  Ver1.0
//********************************************************
// 大人の約束
// 本プログラムとドキュメント（以下、本APと呼称）の著作権はMSLABOにあります。
//
// 本APは、以下のクリエィティブ・コモンズライセンスに従い提供されています。
// https://creativecommons.org/licenses/by-sa/4.0/
//
// 利用にあたっては、必ず自己責任でお願い致します。
//
// 本APの不具合については極力善処を行う予定ですが、これを保証するものではありません。
// また本APに関する情報はすべてMSLABOのHPから一方的に公開するものとし、原則として
// 個別の問い合わせや要望は受け付けません。
//
// 上記事項に同意頂ける方のみ、利用可能です。
//
// 情報掲載先：
// URL : http://mslabo.sakura.ne.jp/WordPress/
//
// 本プログラムが、みなさまのPROCESSING利用の一助になれば幸いです。
//
//***********************************************************

#pragma once
#include <msclr/marshal.h>
#include <msclr/marshal_cppstd.h>
#include "stdafx.h"
#include <Windows.h>
#include <conio.h>

using namespace System;
using namespace msclr::interop;
using namespace System::Reflection;
using namespace System::Speech::Synthesis;
using namespace System::Threading;
using namespace System::Reflection;


namespace ClassLibrary1 {
	//DLL静的変数
	public ref class Class1 {
		public: static SpeechSynthesizer ^_synte;	//シンセサイザー
		public: static String  ^_culture;			//言語ローカル
	};
}

//*******************************************************************
//シンセサイザー生成
//-------------------------------------------------------------------
// IN : wchar_t *fileName	WAVファイルパス
//*******************************************************************
extern "C" __declspec(dllexport) int createSynthesizer(wchar_t *fileName)
{
	ClassLibrary1::Class1 work;

	work._synte = nullptr;
	work._synte = gcnew SpeechSynthesizer();
	if ( work._synte == nullptr ) {
		//生成失敗
		System::Console::Error->WriteLine("createSynthesizer:SpeechSynthesizer create error.\n");
		return(-99);
	}

	//printf("createSynthesizer:SpeechSynthesizer create start.\n");
	if (fileName == NULL || wcslen(fileName) < 1) {
		//標準デバイスに出力
		work._synte->SetOutputToDefaultAudioDevice();
	}
	else {
		//WAVファイルに出力
		System::String^ s1 = marshal_as<System::String^>(fileName);
		work._synte->SetOutputToWaveFile(s1);
	}

	work._culture = work._synte->Voice->Culture->Name;
	
	//std::string s_cpp = marshal_as<std::string>(work._synte->Voice->Culture->Name);
	//printf("%s\n", s_cpp.c_str());
	//printf("createSynthesizer:SpeechSynthesizer create done.\n");
	return(0);
}

//*******************************************************************
//読み上げ速度設定
//-------------------------------------------------------------------
// IN : int speed	速度(-10 〜 10)
//*******************************************************************
extern "C" __declspec(dllexport) void setSpeed(int speed)
{
	ClassLibrary1::Class1 work;
	if (speed < -10 || speed > 10) {
		//範囲外エラー
		System::Console::Error->WriteLine("setSpeed:Irregular Argument.\n");
		return;
	}

	if (work._synte == nullptr) {
		//シンセサイザー未生成
		System::Console::Error->WriteLine("setSpeed:SpeechSynthesizer not created.\n");
		return;
	}

	work._synte->Rate = speed;
	return;
}

//*******************************************************************
//音量設定
//-------------------------------------------------------------------
// IN : int vol	音量(0 〜 100)
//*******************************************************************
extern "C" __declspec(dllexport) void setVolume(int vol)
{
	ClassLibrary1::Class1 work;
	if (vol < 0 || vol > 100) {
		//範囲外エラー
		System::Console::Error->WriteLine("setVolume:Irregular Argument.\n");
		return;
	}

	if (work._synte == nullptr) {
		//シンセサイザー未生成
		System::Console::Error->WriteLine("setVolume:SpeechSynthesizer not created.\n");
		return;
	}

	work._synte->Volume = vol;
	return;
}

//*******************************************************************
// String^ -> char* 変換
//*******************************************************************
char * change(String ^ss) {
	pin_ptr<const wchar_t> wch = PtrToStringChars(ss);

	size_t convertedChars = 0;
	size_t  sizeInBytes = ((ss->Length + 1) * 2);
	errno_t err = 0;
	char    *ch = (char *)calloc(1, sizeInBytes);

	err = wcstombs_s(&convertedChars,
		ch, sizeInBytes,
		wch, sizeInBytes);
	if (err != 0)
		System::Console::Error->WriteLine("change:wcstombs_s  failed!\n");

	return(ch);
}

//*******************************************************************
//音声データ指定
//-------------------------------------------------------------------
// IN : char *voice	 音声データ名
//*******************************************************************
extern "C" __declspec(dllexport) int setVoice(wchar_t *voice)
{
	ClassLibrary1::Class1 work;

	if (work._synte == nullptr) {
		//シンセサイザー未生成
		System::Console::Error->WriteLine("setVoice:SpeechSynthesizer not created.\n");
		return(-99);
	}

	//指定された音声名がOSに登録済みか検査する
	System::Collections::ObjectModel::ReadOnlyCollection<InstalledVoice^> ^voices;
	voices = work._synte->GetInstalledVoices();

	bool found = false;
	int  index = 0;

	System::String^ sv = marshal_as<System::String^>(voice);

	for (index = 0; index < voices->Count; index++) {

		String ^gv = voices[index]->VoiceInfo->Name;

		//printf("[%s] [%s]\n", change(gv), change(sv));

		if(gv->IndexOf(sv) > -1 ){
			//見つけた
			found = true;
			break;
		}
	}

	if (found == false) {
		//OS未登録
		System::Console::Error->WriteLine("setVoice:voice name is not found\n");
		return (-99);
	}

	work._synte->SelectVoice(voices[index]->VoiceInfo->Name);
	work._culture = voices[index]->VoiceInfo->Culture->Name;

	//std::string s_cpp = marshal_as<std::string>(work._synte->Voice->Culture->Name);
	//printf("%s\n", s_cpp.c_str());
	return(0);
}

//*******************************************************************
//読み上げ速度取得
//-------------------------------------------------------------------
// return : int		速度
//*******************************************************************
extern "C" __declspec(dllexport) int getSpeed()
{
	ClassLibrary1::Class1 work;

	if (work._synte == nullptr) {
		//シンセサイザー未生成
		System::Console::Error->WriteLine("getSpeed:SpeechSynthesizer not created.\n");
		return(-99);
	}
	
	int speed = work._synte->Rate;
	return(speed);
}

//*******************************************************************
//音量度取得
//-------------------------------------------------------------------
// return : int		音量
//*******************************************************************
extern "C" __declspec(dllexport) int getVolume()
{
	ClassLibrary1::Class1 work;

	if (work._synte == nullptr) {
		//シンセサイザー未生成
		System::Console::Error->WriteLine("getVolume:SpeechSynthesizer not created.\n");
		return(-99);
	}

	int volume = work._synte->Volume;
	return(volume);
}

//*******************************************************************
//音声データ取得
//-------------------------------------------------------------------
// return : char  音声データ名
//*******************************************************************
extern "C" __declspec(dllexport) char* getVoice()
{
	ClassLibrary1::Class1 work;
	if (work._synte == nullptr) {
		//シンセサイザー未生成
		System::Console::Error->WriteLine("getVoice:SpeechSynthesizer not created.\n");
		return(nullptr);
	}

	//char pStr[128];
	//memset( pStr, '\0', 128 );

	//カレント音声データを取得し、String^ -> char*に変換する
	VoiceInfo ^v;
	v = work._synte->Voice;

	char    *ch = change(v->Name );

	//pin_ptr<const wchar_t> wch = PtrToStringChars(v->Name);

	//size_t convertedChars = 0;
	//size_t  sizeInBytes = ((v->Name->Length + 1) * 2);
	//errno_t err = 0;
	//char    *ch = (char *)calloc(1,sizeInBytes);

	//err = wcstombs_s(&convertedChars,
	//	ch, sizeInBytes,
	//	wch, sizeInBytes);
	//if (err != 0)
	//	System::Console::Error->WriteLine("getVoice:wcstombs_s  failed!\n");

	//printf_s("[%s]\n", ch);
	return(ch);
}

//*******************************************************************
//状態取得
//-------------------------------------------------------------------
// return : int  状態
//*******************************************************************
extern "C" __declspec(dllexport) int getStat()
{
	ClassLibrary1::Class1 work;
	if (work._synte == nullptr) {
		//シンセサイザー未生成
		System::Console::Error->WriteLine("getStat:SpeechSynthesizer not created.\n");
		return(-99);
	}

	return(0);
}

//*******************************************************************
//読み上げ共通部
//-------------------------------------------------------------------
// IN : String^    読み上げる文字列
//    : mode       同期、非同期
//*******************************************************************
void speakMsgCommon( String ^msg, bool mode ) {
	ClassLibrary1::Class1 work;

	try {
		if (mode == true) {
			//同期
			work._synte->Speak(msg);
		}
		else {
			//非同期
			work._synte->SpeakAsync(msg);
		}
		
	}
	catch (...) {
		System::Console::Error->WriteLine("speakMsgCommon:speak command error.\n");
	}
}

//*******************************************************************
//読み上げ共通部(SSML)
//-------------------------------------------------------------------
// IN : String^    読み上げる文字列
//    : mode       同期、非同期
//*******************************************************************
void speakMsgSsmlCommon(String ^msg, bool mode) {
	ClassLibrary1::Class1 work;

	//選択中の音声国種別を取得する
	String ^cu = work._culture;

	boolean unexpectedSsml = false;
	//speakという文字が無いので、正しいSSML形式ではないとみなす
	
	if (msg->IndexOf("<speak", System::StringComparison::CurrentCultureIgnoreCase) < 0) {
		//先頭が <speak で　始まっていないのでSSML形式ではないと見なす
		//System::Console::Out->WriteLine("Because it is illegal SSML, we add tag automatically.\n");
		unexpectedSsml = true;
	}

	if (unexpectedSsml) {
		//必要な書式を強制付加する
		String ^head1 = gcnew String("<speak xml:lang=\"");
		String ^head2 = gcnew String("\" version=\"1.0\">");
		String ^tail = gcnew String("</speak>");
		msg = head1 + cu + head2 + msg + tail;
	}

	try {
		if (mode == true) {
			//同期
			work._synte->SpeakSsml(msg);
		}
		else {
			//非同期
			work._synte->SpeakSsmlAsync(msg);
		}

	}
	catch (...) {
		System::Console::Error->WriteLine("speakMsgSsmlCommon:speak command error.\n");
	}
}

//*******************************************************************
//同期読み上げ
//-------------------------------------------------------------------
// IN : wchar_t*   読み上げる文字列
//*******************************************************************
extern  "C" __declspec(dllexport) void speakMsg(wchar_t *msg) {
	ClassLibrary1::Class1 work;
	if (work._synte == nullptr) {
		//シンセサイザー未生成
		System::Console::Error->WriteLine("speakMsg:SpeechSynthesizer not created.\n");
		return;
	}


	//printf("%x%x%x%x\n", msg[0], msg[1], msg[2], msg[3]);

	System::String^ s1 = marshal_as<System::String^>(msg);
	speakMsgCommon(s1, true);

	return;
}

//*******************************************************************
//非同期読み上げ
//-------------------------------------------------------------------
// IN : wchar_t*   読み上げる文字列
//*******************************************************************
extern  "C" __declspec(dllexport) void speakAsyMsg(wchar_t *msg) {
	ClassLibrary1::Class1 work;
	if (work._synte == nullptr) {
		//シンセサイザー未生成
		System::Console::Error->WriteLine("speakAsyMsg:SpeechSynthesizer not created.\n");
		return;
	}

	System::String^ s1 = marshal_as<System::String^>(msg);
	speakMsgCommon(s1, false);

	return;
}

//*******************************************************************
//同期読み上げ(SSML)
//-------------------------------------------------------------------
// IN : wchar_t*   読み上げる文字列
//*******************************************************************
extern "C" __declspec(dllexport) void speakSsmlMsg(wchar_t *msg)
{
	ClassLibrary1::Class1 work;
	if (work._synte == nullptr) {
		//シンセサイザー未生成
		System::Console::Error->WriteLine("speakSsmlMsg:SpeechSynthesizer not created.\n");
		return;
	}

	System::String^ pmsg = marshal_as<System::String^>(msg);
	speakMsgSsmlCommon(pmsg, true);
	
	return;
}

//*******************************************************************
//非同期読み上げ(SSML)
//-------------------------------------------------------------------
// IN : wchar_t*   読み上げる文字列
//*******************************************************************
extern "C" __declspec(dllexport) void speakAsySsmlMsg(wchar_t *msg)
{
	ClassLibrary1::Class1 work;
	if (work._synte == nullptr) {
		//シンセサイザー未生成
		System::Console::Error->WriteLine("speakAsySsmlMsg:SpeechSynthesizer not created.\n");
		return;
	}

	System::String^ pmsg = marshal_as<System::String^>(msg);
	speakMsgSsmlCommon(pmsg, false);

	return;
}

//*******************************************************************
//開放
//*******************************************************************
extern "C" __declspec(dllexport) void sapiDispose()
{
	ClassLibrary1::Class1 work;
	if (work._synte != nullptr) {
		try{
			work._synte->SpeakAsyncCancelAll();	
			work._synte->SetOutputToNull();
		}
		catch (...) {
			//System::Console::Error->WriteLine("SpeakAsyncCancelAll error");
		}		
		work._synte = nullptr;
	}
	return;
}
