diff options
Diffstat (limited to 'Plugins/eSpeak/eSpeak.cpp')
-rw-r--r-- | Plugins/eSpeak/eSpeak.cpp | 1348 |
1 files changed, 1348 insertions, 0 deletions
diff --git a/Plugins/eSpeak/eSpeak.cpp b/Plugins/eSpeak/eSpeak.cpp new file mode 100644 index 0000000..609c485 --- /dev/null +++ b/Plugins/eSpeak/eSpeak.cpp @@ -0,0 +1,1348 @@ +/*
+Copyright (C) 2006 Ricardo Pescuma Domenecci
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "commons.h"
+#include "comutil.h"
+
+
+// Prototypes ///////////////////////////////////////////////////////////////////////////
+
+
+PLUGININFOEX pluginInfo={
+ sizeof(PLUGININFOEX),
+#ifdef UNICODE
+ "meSpeak (Unicode)",
+#else
+ "meSpeak",
+#endif
+ PLUGIN_MAKE_VERSION(0,2,0,0),
+ "Speaker plugin based on eSpeak engine (%s)",
+ "Ricardo Pescuma Domenecci",
+ "",
+ "© 2007-2009 Ricardo Pescuma Domenecci",
+ "http://pescuma.org/miranda/meSpeak",
+ UNICODE_AWARE,
+ 0, //doesn't replace anything built-in
+#ifdef UNICODE
+ { 0x9c1fad62, 0x8f94, 0x484b, { 0x8e, 0x96, 0xff, 0x6d, 0xa4, 0xcd, 0x98, 0x95 } } // {9C1FAD62-8F94-484b-8E96-FF6DA4CD9895}
+#else
+ { 0xb8b98db2, 0xa8e5, 0x4501, { 0x91, 0xf5, 0x6f, 0x3b, 0x44, 0x34, 0x81, 0xa8 } } // {B8B98DB2-A8E5-4501-91F5-6F3B443481A8}
+#endif
+};
+
+
+HINSTANCE hInst;
+PLUGINLINK *pluginLink;
+LIST_INTERFACE li;
+struct MM_INTERFACE mmi;
+struct UTF8_INTERFACE utfi;
+
+static std::vector<HANDLE> hHooks;
+static std::vector<HANDLE> hServices;
+
+LIST<Language> languages(20);
+LIST<Variant> variants(20);
+ContactAsyncQueue *queue;
+
+HANDLE hDictionariesFolder = NULL;
+TCHAR dictionariesFolder[1024];
+
+HANDLE hFlagsDllFolder = NULL;
+TCHAR flagsDllFolder[1024];
+
+HBITMAP hCheckedBmp;
+BITMAP bmpChecked;
+
+char *metacontacts_proto = NULL;
+BOOL loaded = FALSE;
+
+int ModulesLoaded(WPARAM wParam, LPARAM lParam);
+int PreShutdown(WPARAM wParam, LPARAM lParam);
+
+int SpeakAService(WPARAM wParam, LPARAM lParam);
+int SpeakWService(WPARAM wParam, LPARAM lParam);
+TCHAR * VariablesSpeak(ARGUMENTSINFO *ai);
+
+LRESULT CALLBACK MenuWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+void LoadVoices();
+
+Language *GetClosestLanguage(TCHAR *lang_name);
+
+void Speak(HANDLE hContact, void *param);
+
+TCHAR *aditionalLanguages[] = {
+ _T("en_R"), _T("English (Rhotic)"),
+ _T("en_SC"), _T("English (Scottish)"),
+ _T("en_UK"), _T("English - UK"),
+ _T("en_UK_NORTH"), _T("English - UK (Northern)"),
+ _T("en_UK_RP"), _T("English - UK (Received Pronunciation)"),
+ _T("en_UK_WMIDS"), _T("English - UK (West Midlands)"),
+ _T("en_WI"), _T("English - Westindies"),
+ _T("es_LA"), _T("Spanish - Latin American"),
+ _T("eo"), _T("Esperanto"),
+ _T("la"), _T("Latin"),
+ _T("no"), _T("Norwegian"),
+ _T("jbo"), _T("Lojban"),
+ _T("sr"), _T("Serbian"),
+ _T("grc"), _T("Ancient Greek"),
+ _T("yue"), _T("Cantonese"),
+ _T("ku"), _T("Kurdish"),
+ _T("hbs"), _T("Serbo-Croatian"),
+ _T("zh_YUE"), _T("Chinese - Cantonese"),
+};
+
+
+BOOL shutDown = FALSE;
+
+
+// Functions ////////////////////////////////////////////////////////////////////////////
+
+
+extern "C" BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
+{
+ hInst = hinstDLL;
+ return TRUE;
+}
+
+static void FixPluginDescription()
+{
+ static char description[128];
+ _snprintf(description, MAX_REGS(description), "Speaker plugin based on eSpeak engine (%s)", espeak_Info(NULL));
+ description[MAX_REGS(description)-1] = '\0';
+ pluginInfo.description = description;
+}
+
+extern "C" __declspec(dllexport) PLUGININFO* MirandaPluginInfo(DWORD mirandaVersion)
+{
+ pluginInfo.cbSize = sizeof(PLUGININFO);
+ FixPluginDescription();
+ return (PLUGININFO*) &pluginInfo;
+}
+
+
+extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD mirandaVersion)
+{
+ pluginInfo.cbSize = sizeof(PLUGININFOEX);
+ FixPluginDescription();
+ return &pluginInfo;
+}
+
+
+static const MUUID interfaces[] = { MIID_SPEAK, MIID_LAST };
+extern "C" __declspec(dllexport) const MUUID* MirandaPluginInterfaces(void)
+{
+ return interfaces;
+}
+
+
+extern "C" int __declspec(dllexport) Load(PLUGINLINK *link)
+{
+ pluginLink = link;
+
+ CHECK_VERSION( MODULE_NAME )
+
+ mir_getMMI(&mmi);
+ mir_getUTFI(&utfi);
+ mir_getLI(&li);
+
+ // hooks
+ hHooks.push_back( HookEvent(ME_SYSTEM_MODULESLOADED, ModulesLoaded) );
+ hHooks.push_back( HookEvent(ME_SYSTEM_PRESHUTDOWN, PreShutdown) );
+
+ hCheckedBmp = LoadBitmap(NULL, MAKEINTRESOURCE(OBM_CHECK));
+ if (GetObject(hCheckedBmp, sizeof(bmpChecked), &bmpChecked) == 0)
+ bmpChecked.bmHeight = bmpChecked.bmWidth = 10;
+
+ InitTypes();
+
+ TCHAR mirandaFolder[1024];
+ GetModuleFileName(GetModuleHandle(NULL), mirandaFolder, MAX_REGS(mirandaFolder));
+ TCHAR *p = _tcsrchr(mirandaFolder, _T('\\'));
+ if (p != NULL)
+ *p = _T('\0');
+
+ // Folders plugin support
+ if (ServiceExists(MS_FOLDERS_REGISTER_PATH))
+ {
+ hDictionariesFolder = (HANDLE) FoldersRegisterCustomPathT(Translate("meSpeak"),
+ Translate("Languages"),
+ _T(MIRANDA_PATH) _T("\\Dictionaries\\Voice"));
+
+ FoldersGetCustomPathT(hDictionariesFolder, dictionariesFolder, MAX_REGS(dictionariesFolder), _T("."));
+
+ hFlagsDllFolder = (HANDLE) FoldersRegisterCustomPathT(Translate("meSpeak"),
+ Translate("Flags DLL"),
+ _T(MIRANDA_PATH) _T("\\Icons"));
+
+ FoldersGetCustomPathT(hFlagsDllFolder, flagsDllFolder, MAX_REGS(flagsDllFolder), _T("."));
+ }
+ else
+ {
+ mir_sntprintf(dictionariesFolder, MAX_REGS(dictionariesFolder), _T("%s\\Dictionaries\\Voice"), mirandaFolder);
+
+ mir_sntprintf(flagsDllFolder, MAX_REGS(flagsDllFolder), _T("%s\\Icons"), mirandaFolder);
+ }
+
+ LoadVoices();
+
+ hServices.push_back( CreateServiceFunction(MS_SPEAK_SAY_A, SpeakAService) );
+ hServices.push_back( CreateServiceFunction(MS_SPEAK_SAY_W, SpeakWService) );
+
+ queue = new ContactAsyncQueue(&Speak);
+
+ return 0;
+}
+
+extern "C" int __declspec(dllexport) Unload(void)
+{
+ FreeTypes();
+
+ DeleteObject(hCheckedBmp);
+
+ return 0;
+}
+
+
+HICON LoadIconEx(Language *lang, BOOL copy)
+{
+#ifdef UNICODE
+ char tmp[NAME_SIZE];
+ WideCharToMultiByte(CP_ACP, 0, lang->language, -1, tmp, MAX_REGS(tmp), NULL, NULL);
+ return LoadIconEx(tmp, copy);
+#else
+ return LoadIconEx(lang->language, copy);
+#endif
+}
+
+
+// Called when all the modules are loaded
+int ModulesLoaded(WPARAM wParam, LPARAM lParam)
+{
+ if (ServiceExists(MS_MC_GETPROTOCOLNAME))
+ metacontacts_proto = (char *) CallService(MS_MC_GETPROTOCOLNAME, 0, 0);
+
+ // add our modules to the KnownModules list
+ CallService("DBEditorpp/RegisterSingleModule", (WPARAM) MODULE_NAME, 0);
+
+ // updater plugin support
+ if(ServiceExists(MS_UPDATE_REGISTER))
+ {
+ Update upd = {0};
+ char szCurrentVersion[30];
+
+ upd.cbSize = sizeof(upd);
+ upd.szComponentName = pluginInfo.shortName;
+
+ upd.szUpdateURL = UPDATER_AUTOREGISTER;
+
+ upd.szBetaVersionURL = "http://pescuma.org/miranda/meSpeak_version.txt";
+ upd.szBetaChangelogURL = "http://pescuma.org/miranda/mespeak#Changelog";
+ upd.pbBetaVersionPrefix = (BYTE *)"meSpeak ";
+ upd.cpbBetaVersionPrefix = strlen((char *)upd.pbBetaVersionPrefix);
+#ifdef UNICODE
+ upd.szBetaUpdateURL = "http://pescuma.org/miranda/meSpeakW.zip";
+#else
+ upd.szBetaUpdateURL = "http://pescuma.org/miranda/meSpeak.zip";
+#endif
+
+ upd.pbVersion = (BYTE *)CreateVersionStringPlugin((PLUGININFO*) &pluginInfo, szCurrentVersion);
+ upd.cpbVersion = strlen((char *)upd.pbVersion);
+
+ CallService(MS_UPDATE_REGISTER, 0, (LPARAM)&upd);
+ }
+
+ InitOptions();
+
+ if (opts.use_flags)
+ {
+ // Load flags dll
+ TCHAR flag_file[1024];
+ _sntprintf(flag_file, MAX_REGS(flag_file), _T("%s\\flags.dll"), flagsDllFolder);
+ flag_file[1023] = 0;
+ HMODULE hFlagsDll = LoadLibrary(flag_file);
+
+ char path[1024];
+ GetModuleFileNameA(hInst, path, MAX_REGS(path));
+
+ SKINICONDESC sid = {0};
+ sid.cbSize = sizeof(SKINICONDESC);
+ sid.pszDefaultFile = path;
+ sid.flags = SIDF_TCHAR | SIDF_SORTED;
+ sid.ptszSection = TranslateT("Languages/Flags");
+
+ // Get language flags
+ for (int i = 0; i < languages.getCount(); i++)
+ {
+ sid.ptszDescription = languages[i]->full_name;
+#ifdef UNICODE
+ char lang[32];
+ mir_snprintf(lang, MAX_REGS(lang), "%S", languages[i]->language);
+ sid.pszName = lang;
+#else
+ sid.pszName = languages[i]->language;
+#endif
+
+ HICON hFlag = LoadIconEx(sid.pszName);
+ if (hFlag != NULL)
+ {
+ // Already registered
+ ReleaseIconEx(hFlag);
+ continue;
+ }
+
+ if (hFlagsDll != NULL)
+ {
+ hFlag = (HICON) LoadImage(hFlagsDll, languages[i]->language, IMAGE_ICON, 16, 16, 0);
+
+ if (hFlag == NULL)
+ {
+ TCHAR tmp[NAME_SIZE];
+ lstrcpyn(tmp, languages[i]->language, MAX_REGS(tmp));
+ do
+ {
+ TCHAR *p = _tcsrchr(tmp, _T('_'));
+ if (p == NULL)
+ break;
+
+ *p = _T('\0');
+ hFlag = (HICON) LoadImage(hFlagsDll, tmp, IMAGE_ICON, 16, 16, 0);
+ }
+ while(hFlag == NULL);
+ }
+ }
+ else
+ hFlag = NULL;
+
+ if (hFlag != NULL)
+ {
+ sid.hDefaultIcon = hFlag;
+ sid.pszDefaultFile = NULL;
+ sid.iDefaultIndex = 0;
+ }
+ else
+ {
+ sid.hDefaultIcon = NULL;
+ sid.pszDefaultFile = path;
+ sid.iDefaultIndex = - IDI_UNKNOWN_FLAG;
+ }
+
+ // Oki, lets add to IcoLib, then
+ CallService(MS_SKIN2_ADDICON, 0, (LPARAM)&sid);
+
+ if (hFlag != NULL)
+ DestroyIcon(hFlag);
+ }
+ FreeLibrary(hFlagsDll);
+ }
+
+ // Variables support
+ if (ServiceExists(MS_VARS_REGISTERTOKEN))
+ {
+ TOKENREGISTER tr = {0};
+ tr.cbSize = sizeof(TOKENREGISTER);
+ tr.memType = TR_MEM_MIRANDA;
+ tr.flags = TRF_FREEMEM | TRF_PARSEFUNC | TRF_FUNCTION | TRF_TCHAR;
+
+ tr.tszTokenString = _T("speak");
+ tr.parseFunctionT = VariablesSpeak;
+ tr.szHelpText = "Speak\t(x,[y])\tSpeak the text x using the y contact voice (y is optional)";
+ CallService(MS_VARS_REGISTERTOKEN, 0, (LPARAM) &tr);
+ }
+
+ loaded = TRUE;
+
+ return 0;
+}
+
+
+Language *GetLanguage(TCHAR *language, BOOL create)
+{
+ for (int i = 0; i < languages.getCount(); i++)
+ {
+ Language *lang = languages[i];
+ if (lstrcmpi(lang->language, language) == 0)
+ return lang;
+ }
+
+ if (create)
+ {
+ Language *lang = new Language(language);
+ languages.insert(lang);
+ return lang;
+ }
+
+ return NULL;
+}
+
+
+int SortVoices(const Voice *voice1, const Voice *voice2)
+{
+ return (int) voice1->prio - (int) voice2->prio;
+}
+
+
+// To get the names of the languages
+BOOL CALLBACK EnumLocalesProc(LPTSTR lpLocaleString)
+{
+ TCHAR *stopped = NULL;
+ USHORT langID = (USHORT) _tcstol(lpLocaleString, &stopped, 16);
+
+ TCHAR ini[32];
+ TCHAR end[32];
+ GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SISO639LANGNAME, ini, MAX_REGS(ini));
+ GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SISO3166CTRYNAME, end, MAX_REGS(end));
+
+ TCHAR name[64];
+ mir_sntprintf(name, MAX_REGS(name), _T("%s_%s"), ini, end);
+/*
+OutputDebugString(name);
+OutputDebugStringA(" : ");
+TCHAR tmp[128];
+GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SENGLANGUAGE, tmp, MAX_REGS(tmp));
+OutputDebugString(tmp);
+OutputDebugStringA(" | ");
+GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SLANGUAGE, tmp, MAX_REGS(tmp));
+OutputDebugString(tmp);
+OutputDebugStringA(" | ");
+GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SNATIVELANGNAME , tmp, MAX_REGS(tmp));
+OutputDebugString(tmp);
+OutputDebugStringA("\n");
+*/
+ for (int i = 0; i < languages.getCount(); i++)
+ {
+ size_t len = lstrlen(languages[i]->language);
+ if (len > 2 && lstrcmpi(languages[i]->language, name) == 0)
+ {
+ GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SLANGUAGE,
+ languages[i]->localized_name, MAX_REGS(languages[i]->localized_name));
+ GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SENGLANGUAGE,
+ languages[i]->english_name, MAX_REGS(languages[i]->english_name));
+
+ if (languages[i]->localized_name[0] != _T('\0'))
+ {
+ mir_sntprintf(languages[i]->full_name, MAX_REGS(languages[i]->full_name),
+ _T("%s [%s]"), TranslateTS(languages[i]->localized_name), languages[i]->language);
+ }
+ }
+ else if (len == 2 && lstrcmpi(languages[i]->language, ini) == 0 && lstrcmpi(languages[i]->language, end) == 0)
+ {
+ GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SENGLANGUAGE,
+ languages[i]->localized_name, MAX_REGS(languages[i]->localized_name));
+ GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SENGLANGUAGE,
+ languages[i]->english_name, MAX_REGS(languages[i]->english_name));
+
+ if (languages[i]->localized_name[0] != _T('\0'))
+ {
+ mir_sntprintf(languages[i]->full_name, MAX_REGS(languages[i]->full_name),
+ _T("%s [%s]"), TranslateTS(languages[i]->localized_name), languages[i]->language);
+ }
+ }
+ else if (len == 2 && lstrcmpi(languages[i]->language, ini) == 0 && languages[i]->localized_name[0] == _T('\0'))
+ {
+ GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SENGLANGUAGE,
+ languages[i]->localized_name, MAX_REGS(languages[i]->localized_name));
+ GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SENGLANGUAGE,
+ languages[i]->english_name, MAX_REGS(languages[i]->english_name));
+
+ if (languages[i]->localized_name[0] != _T('\0'))
+ {
+ mir_sntprintf(languages[i]->full_name, MAX_REGS(languages[i]->full_name),
+ _T("%s [%s]"), TranslateTS(languages[i]->localized_name), languages[i]->language);
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+
+int SynthCallback(short*, int, espeak_EVENT*)
+{
+ return shutDown;
+}
+
+
+void LoadESpeak()
+{
+ char *tmp = mir_t2a(dictionariesFolder);
+
+ if (espeak_Initialize(AUDIO_OUTPUT_SYNCH_PLAYBACK, 0, tmp, 0) == EE_INTERNAL_ERROR)
+ {
+ MessageBox(NULL, _T("Error initializing eSpeak engine"), _T("meSpeak"), MB_OK | MB_ICONERROR);
+ mir_free(tmp);
+ return;
+ }
+
+ espeak_SetSynthCallback(SynthCallback);
+
+ mir_free(tmp);
+
+ const espeak_VOICE **voices = espeak_ListVoices(NULL);
+ const espeak_VOICE *voice;
+ int i;
+ for (i = 0; (voice = voices[i]) != NULL; i++)
+ {
+ Utf8ToTchar name = voice->name;
+ Utf8ToTchar id = voice->identifier;
+
+ const char *p = voice->languages;
+ while(*p != '\0')
+ {
+ size_t len = strlen(p+1);
+ Utf8ToTchar language = p + 1;
+
+ TCHAR *tmp = language;
+ while((tmp = _tcschr(tmp, _T('-'))) != NULL)
+ {
+ *tmp = _T('_');
+ CharUpper(tmp);
+ }
+
+ Language *lang = GetLanguage(language, TRUE);
+ lang->voices.insert(new Voice(ENGINE_ESPEAK, name, *p, voice->gender, _T(""), id));
+
+ p += len+2;
+ }
+ }
+
+ if (languages.getCount() <= 0)
+ return;
+
+ espeak_VOICE voice_select;
+ voice_select.languages = "variant";
+ voice_select.age = 0;
+ voice_select.gender = 0;
+ voice_select.name = NULL;
+
+ voices = espeak_ListVoices(&voice_select);
+ for (i = 0; (voice = voices[i]) != NULL; i++)
+ {
+ variants.insert(new Variant(Utf8ToTchar(voice->name), voice->gender, Utf8ToTchar(&voice->identifier[3])));
+ }
+}
+
+void LoadSAPI()
+{
+#define CHECK( _X_ ) hr = _X_; if(!SUCCEEDED(hr)) goto err
+
+ HRESULT hr = 0;
+
+ ISpeechVoice *voice = NULL;
+ ISpeechObjectTokens *voices = NULL;
+ ISpeechObjectToken *v = NULL;
+
+ long count = 0;
+ long i;
+
+ if (FAILED(CoInitialize(NULL)))
+ {
+ MessageBox(NULL, _T("Error to intiliaze COM"), _T(MODULE_NAME), MB_OK | MB_ICONERROR);
+ return;
+ }
+
+ CHECK( CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpeechVoice, (void **)&voice) );
+
+ CHECK( voice->GetVoices(NULL, NULL, &voices) );
+
+ CHECK( voices->get_Count(&count) );
+
+ for(i = 0; i < count; i++)
+ {
+ CHECK( voices->Item(i, &v) );
+ if (v == NULL)
+ continue;
+
+ BstrToTchar name;
+ CHECK( v->GetDescription(0, name) );
+
+ BstrToTchar id;
+ CHECK( v->get_Id(id) );
+
+ BstrToTchar languageId;
+ hr = v->GetAttribute(BstrToTchar(L"Language"), languageId);
+ if (hr != S_OK)
+ {
+ RELEASE(v);
+ continue;
+ }
+
+ TCHAR *stopped = NULL;
+ USHORT langID = (USHORT) _tcstol(languageId, &stopped, 16);
+
+ TCHAR ini[32];
+ TCHAR end[32];
+ GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SISO639LANGNAME, ini, MAX_REGS(ini));
+ GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SISO3166CTRYNAME, end, MAX_REGS(end));
+ CharUpper(end);
+
+ TCHAR language[64];
+ mir_sntprintf(language, MAX_REGS(language), _T("%s_%s"), ini, end);
+
+ BstrToTchar genderName;
+ hr = v->GetAttribute(BstrToTchar(L"Gender"), genderName);
+ int gender;
+ if (hr != S_OK)
+ gender = GENDER_UNKNOWN;
+ else if (lstrcmpi(_T("Male"), genderName) == 0)
+ gender = GENDER_MALE;
+ else if (lstrcmpi(_T("Female"), genderName) == 0)
+ gender = GENDER_FEMALE;
+ else
+ gender = GENDER_UNKNOWN;
+
+ BstrToTchar age;
+ hr = v->GetAttribute(BstrToTchar(L"Age"), age);
+ if (hr != S_OK)
+ age = L"";
+
+ Language *lang = GetLanguage(language, TRUE);
+ lang->voices.insert(new Voice(ENGINE_SAPI, name, 6, gender, age, id));
+
+ RELEASE( v );
+ }
+
+ goto cleanup;
+
+err:
+ MessageBox(NULL, _T("Error initializing SAPI"), _T(MODULE_NAME), MB_OK | MB_ICONERROR);
+
+cleanup:
+ RELEASE( v );
+ RELEASE( voices );
+ RELEASE( voice );
+ CoUninitialize();
+}
+
+void LoadVoices()
+{
+ LoadESpeak();
+ LoadSAPI();
+
+ if (languages.getCount() <= 0)
+ return;
+
+ EnumSystemLocales(EnumLocalesProc, LCID_SUPPORTED);
+
+ // Try to get name from DB
+ for(int i = 0; i < languages.getCount(); i++)
+ {
+ Language *lang = languages[i];
+ if (lang->full_name[0] == _T('\0'))
+ {
+ DBVARIANT dbv;
+#ifdef UNICODE
+ char tmp[NAME_SIZE];
+ WideCharToMultiByte(CP_ACP, 0, lang->language, -1, tmp, MAX_REGS(tmp), NULL, NULL);
+ if (!DBGetContactSettingTString(NULL, MODULE_NAME, tmp, &dbv))
+#else
+ if (!DBGetContactSettingTString(NULL, MODULE_NAME, lang->language, &dbv))
+#endif
+ {
+ lstrcpyn(lang->localized_name, dbv.ptszVal, MAX_REGS(lang->localized_name));
+ DBFreeVariant(&dbv);
+ }
+
+ if (lang->localized_name[0] == _T('\0'))
+ {
+ for(size_t j = 0; j < MAX_REGS(aditionalLanguages); j+=2)
+ {
+ if (lstrcmp(aditionalLanguages[j], lang->language) == 0)
+ {
+ lstrcpyn(lang->localized_name, aditionalLanguages[j+1], MAX_REGS(lang->localized_name));
+ break;
+ }
+ }
+ }
+
+ if (lang->localized_name[0] != _T('\0'))
+ {
+ mir_sntprintf(lang->full_name, MAX_REGS(lang->full_name),
+ _T("%s [%s]"), TranslateTS(lang->localized_name), lang->language);
+ }
+ else
+ {
+ lstrcpyn(lang->full_name, TranslateTS(lang->language), MAX_REGS(lang->full_name));
+ }
+ }
+ }
+/*
+ for (i = 0; i < languages.getCount(); i++)
+ {
+ Language *lang = languages[i];
+ OutputDebugString(lang->language);
+ OutputDebugStringA(" (");
+ OutputDebugString(lang->full_name);
+ OutputDebugStringA(") ");
+ OutputDebugStringA(":\n");
+ for (int j = 0; j < lang->voices.getCount(); j++)
+ {
+ char tmp[128];
+ Voice *voice = lang->voices[j];
+
+ OutputDebugStringA(" - ");
+ OutputDebugStringA(itoa(voice->prio, tmp, 10));
+ OutputDebugStringA(" : ");
+ OutputDebugStringA(voice->name);
+ OutputDebugStringA(" - ");
+ tmp[0] = voice->gender;
+ tmp[1] = 0;
+ OutputDebugStringA(tmp);
+ OutputDebugStringA("\n");
+ }
+ }
+*/
+}
+
+
+int PreShutdown(WPARAM wParam, LPARAM lParam)
+{
+ shutDown = TRUE;
+
+ delete queue;
+
+ DeInitOptions();
+
+ if (ServiceExists(MS_MSG_REMOVEICON))
+ {
+ StatusIconData sid = {0};
+ sid.cbSize = sizeof(sid);
+ sid.szModule = MODULE_NAME;
+ CallService(MS_MSG_REMOVEICON, 0, (LPARAM) &sid);
+ }
+
+ unsigned i;
+ for(i = 0; i < hHooks.size(); ++i)
+ UnhookEvent(hHooks[i]);
+
+ for(i = 0; i < hServices.size(); ++i)
+ DestroyServiceFunction(hServices[i]);
+
+
+ return 0;
+}
+
+
+void ToLocaleID(TCHAR *szKLName, size_t size)
+{
+ TCHAR *stopped = NULL;
+ USHORT langID = (USHORT) _tcstol(szKLName, &stopped, 16);
+
+ TCHAR ini[32];
+ TCHAR end[32];
+ GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SISO639LANGNAME, ini, MAX_REGS(ini));
+ GetLocaleInfo(MAKELCID(langID, 0), LOCALE_SISO3166CTRYNAME, end, MAX_REGS(end));
+
+ mir_sntprintf(szKLName, size, _T("%s_%s"), ini, end);
+}
+
+
+Language * GetClosestLanguage(TCHAR *lang_name)
+{
+ // Search the language by name
+ for (int i = 0; i < languages.getCount(); i++)
+ {
+ if (lstrcmpi(languages[i]->language, lang_name) == 0)
+ {
+ return languages[i];
+ }
+ }
+
+ // Try searching by the prefix only
+ TCHAR *p = _tcschr(lang_name, _T('_'));
+ if (p != NULL)
+ {
+ *p = _T('\0');
+ for (int i = 0; i < languages.getCount(); i++)
+ {
+ if (lstrcmpi(languages[i]->language, lang_name) == 0)
+ {
+ *p = '_';
+ return languages[i];
+ }
+ }
+ *p = _T('_');
+ }
+
+ // Try any suffix, if one not provided
+ if (p == NULL)
+ {
+ size_t len = lstrlen(lang_name);
+ for (int i = 0; i < languages.getCount(); i++)
+ {
+ if (_tcsnicmp(languages[i]->language, lang_name, len) == 0
+ && languages[i]->language[len] == _T('_'))
+ {
+ return languages[i];
+ }
+ }
+ }
+
+ return NULL;
+}
+
+void GetUserProtoLanguageSetting(TCHAR *lang_name, HANDLE hContact, char *proto, char *setting)
+{
+ DBVARIANT dbv = {0};
+
+ if (!DBGetContactSettingTString(hContact, proto, setting, &dbv))
+ {
+ for (int i = 0; i < languages.getCount(); i++)
+ {
+ if (lstrcmpi(languages[i]->localized_name, dbv.ptszVal) == 0)
+ {
+ lstrcpyn(lang_name, languages[i]->language, NAME_SIZE);
+ break;
+ }
+ if (lstrcmpi(languages[i]->english_name, dbv.ptszVal) == 0)
+ {
+ lstrcpyn(lang_name, languages[i]->language, NAME_SIZE);
+ break;
+ }
+ }
+ DBFreeVariant(&dbv);
+ }
+}
+
+void GetUserLanguageSetting(TCHAR *lang_name, HANDLE hContact, char *setting)
+{
+ DBVARIANT dbv = {0};
+
+ char *proto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (proto == NULL)
+ return;
+
+ GetUserProtoLanguageSetting(lang_name, hContact, proto, setting);
+
+ // If not found and is inside meta, try to get from the meta
+ if (lang_name[0] != _T('\0'))
+ return;
+
+ // Is a subcontact?
+ if (!ServiceExists(MS_MC_GETMETACONTACT))
+ return;
+
+ HANDLE hMetaContact = (HANDLE) CallService(MS_MC_GETMETACONTACT, (WPARAM) hContact, 0);
+ if (hMetaContact == NULL)
+ return;
+
+ GetUserProtoLanguageSetting(lang_name, hMetaContact, metacontacts_proto, setting);
+}
+
+
+void GetLangPackLanguage(TCHAR *name, size_t len)
+{
+ LCID localeID = CallService(MS_LANGPACK_GETLOCALE, 0, 0);
+ TCHAR ini[32];
+ TCHAR end[32];
+ GetLocaleInfo(localeID, LOCALE_SISO639LANGNAME, ini, MAX_REGS(ini));
+ GetLocaleInfo(localeID, LOCALE_SISO3166CTRYNAME, end, MAX_REGS(end));
+
+ mir_sntprintf(name, len, _T("%s_%s"), ini, end);
+}
+
+
+Language *GetContactLanguage(HANDLE hContact)
+{
+ if (hContact == NULL)
+ {
+ // System language
+
+ // First try the db setting
+ TCHAR lang_name[NAME_SIZE] = _T("");
+ DBVARIANT dbv;
+ if (!DBGetContactSettingTString(NULL, MODULE_NAME, "TalkLanguage", &dbv))
+ {
+ lstrcpyn(lang_name, dbv.ptszVal, MAX_REGS(lang_name));
+ DBFreeVariant(&dbv);
+ }
+
+ // Then the langpack language
+ if (lang_name[0] == _T('\0'))
+ GetLangPackLanguage(lang_name, MAX_REGS(lang_name));
+
+ Language *lang = GetLanguage(lang_name);
+ if (lang != NULL)
+ return lang;
+
+ // Then english
+ lang = GetLanguage(_T("en"));
+ if (lang != NULL)
+ return lang;
+
+ // Last shot: first avaiable language
+ return languages[0];
+ }
+ else
+ {
+ // Contact language
+ TCHAR lang_name[NAME_SIZE] = _T("");
+
+ DBVARIANT dbv = {0};
+ if (!DBGetContactSettingTString(hContact, MODULE_NAME, "TalkLanguage", &dbv))
+ {
+ lstrcpyn(lang_name, dbv.ptszVal, NAME_SIZE);
+ DBFreeVariant(&dbv);
+ }
+
+ if (lang_name[0] == _T('\0') && !DBGetContactSettingTString(hContact, "SpellChecker", "TalkLanguage", &dbv))
+ {
+ lstrcpyn(lang_name, dbv.ptszVal, NAME_SIZE);
+ DBFreeVariant(&dbv);
+ }
+
+ // Try from metacontact
+ if (lang_name[0] == _T('\0') && ServiceExists(MS_MC_GETMETACONTACT))
+ {
+ HANDLE hMetaContact = (HANDLE) CallService(MS_MC_GETMETACONTACT, (WPARAM) hContact, 0);
+ if (hMetaContact != NULL)
+ {
+ if (!DBGetContactSettingTString(hMetaContact, MODULE_NAME, "TalkLanguage", &dbv))
+ {
+ lstrcpyn(lang_name, dbv.ptszVal, NAME_SIZE);
+ DBFreeVariant(&dbv);
+ }
+
+ if (lang_name[0] == _T('\0') && !DBGetContactSettingTString(hMetaContact, "SpellChecker", "TalkLanguage", &dbv))
+ {
+ lstrcpyn(lang_name, dbv.ptszVal, NAME_SIZE);
+ DBFreeVariant(&dbv);
+ }
+ }
+ }
+
+ // Try to get from Language info
+ if (lang_name[0] == _T('\0'))
+ GetUserLanguageSetting(lang_name, hContact, "Language");
+ if (lang_name[0] == _T('\0'))
+ GetUserLanguageSetting(lang_name, hContact, "Language1");
+ if (lang_name[0] == _T('\0'))
+ GetUserLanguageSetting(lang_name, hContact, "Language2");
+ if (lang_name[0] == _T('\0'))
+ GetUserLanguageSetting(lang_name, hContact, "Language3");
+
+ if (lang_name[0] == _T('\0'))
+ // Use default lang
+ return opts.default_language;
+
+ Language *ret = GetClosestLanguage(lang_name);
+ if(ret == NULL)
+ // Lost a lang?
+ return opts.default_language;
+
+ return ret;
+ }
+}
+
+
+Voice *GetContactVoice(HANDLE hContact, Language *lang)
+{
+ int i;
+ DBVARIANT dbv;
+ if (DBGetContactSettingTString(hContact, MODULE_NAME, "Voice", &dbv))
+ goto DEFAULT;
+
+ TCHAR name[NAME_SIZE];
+ lstrcpyn(name, dbv.ptszVal, MAX_REGS(name));
+ DBFreeVariant(&dbv);
+
+ if (name[0] == _T('\0'))
+ goto DEFAULT;
+
+ for (i = 0; i < lang->voices.getCount(); i++)
+ if (lstrcmpi(name, lang->voices[i]->name) == 0)
+ return lang->voices[i];
+
+DEFAULT:
+ if (lang == opts.default_language && opts.default_voice != NULL)
+ return opts.default_voice;
+ else
+ return lang->voices[0];
+}
+
+
+Variant *GetVariant(TCHAR *name)
+{
+ for (int i = 0; i < variants.getCount(); i++)
+ if (lstrcmpi(name, variants[i]->name) == 0)
+ return variants[i];
+ return NULL;
+}
+
+
+Variant *GetContactVariant(HANDLE hContact)
+{
+ Variant *ret;
+
+ DBVARIANT dbv;
+ if (DBGetContactSettingTString(hContact, MODULE_NAME, "Variant", &dbv))
+ goto DEFAULT;
+
+ TCHAR name[NAME_SIZE];
+ lstrcpyn(name, dbv.ptszVal, MAX_REGS(name));
+ DBFreeVariant(&dbv);
+
+ if (name[0] == _T('\0'))
+ goto DEFAULT;
+
+ ret = GetVariant(name);
+ if (ret != NULL)
+ return ret;
+
+DEFAULT:
+
+ if (hContact != NULL && opts.select_variant_per_genre)
+ {
+ CONTACTINFO ci = {0};
+ ci.cbSize = sizeof(ci);
+ ci.hContact = hContact;
+ ci.dwFlag = CNF_GENDER;
+ CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM) &ci);
+
+ if (ci.bVal == 'M')
+ {
+ ret = GetVariant(_T("male1"));
+ if (ret != NULL)
+ return ret;
+ }
+ else if (ci.bVal == 'F')
+ {
+ ret = GetVariant(_T("female1"));
+ if (ret != NULL)
+ return ret;
+ }
+ }
+
+ return opts.default_variant;
+}
+
+
+int GetContactParam(HANDLE hContact, int param)
+{
+ Voice *voice = GetContactVoice(hContact, GetContactLanguage(hContact));
+
+ int def;
+ if (voice->engine == ENGINE_SAPI && PARAMETERS[param].eparam == espeakRATE)
+ def = SAPI_GetDefaultRateFor(voice->id);
+ else if (voice->engine == ENGINE_SAPI)
+ def = PARAMETERS[param].sapi.def;
+ else
+ def = PARAMETERS[param].espeak.def;
+
+ int ret = DBGetContactSettingDword(NULL, MODULE_NAME, PARAMETERS[param].setting, def);
+ if (hContact != NULL)
+ ret = DBGetContactSettingDword(hContact, MODULE_NAME, PARAMETERS[param].setting, ret);
+
+ return ret;
+}
+
+
+void SetContactParam(HANDLE hContact, int param, int value)
+{
+ DBWriteContactSettingDword(hContact, MODULE_NAME, PARAMETERS[param].setting, value);
+}
+
+
+BOOL StatusEnabled(int status)
+{
+ switch(status) {
+ case ID_STATUS_OFFLINE: return !opts.disable_offline;
+ case ID_STATUS_ONLINE: return !opts.disable_online;
+ case ID_STATUS_AWAY: return !opts.disable_away;
+ case ID_STATUS_DND: return !opts.disable_dnd;
+ case ID_STATUS_NA: return !opts.disable_na;
+ case ID_STATUS_OCCUPIED: return !opts.disable_occupied;
+ case ID_STATUS_FREECHAT: return !opts.disable_freechat;
+ case ID_STATUS_INVISIBLE: return !opts.disable_invisible;
+ case ID_STATUS_ONTHEPHONE: return !opts.disable_onthephone;
+ case ID_STATUS_OUTTOLUNCH: return !opts.disable_outtolunch;
+ }
+
+ return !opts.disable_offline;
+}
+
+
+int SpeakService(HANDLE hContact, TCHAR *text)
+{
+ Language *lang;
+ Voice *voice;
+ SpeakData *data;
+ int status, i;
+
+ // Enabled?
+ if (hContact != NULL && !DBGetContactSettingByte(hContact, MODULE_NAME, "Enabled", TRUE))
+ goto RETURN;
+
+ // Check status
+ status = CallService(MS_CLIST_GETSTATUSMODE, 0, 0);
+ if (!StatusEnabled(status))
+ goto RETURN;
+
+ if (opts.enable_only_idle)
+ {
+ MIRANDA_IDLE_INFO idle = {0};
+ CallService(MS_IDLE_GETIDLEINFO, 0, (LPARAM) &idle);
+ if (idle.idleType == 0)
+ goto RETURN;
+ }
+
+ // Check language
+ lang = GetContactLanguage(hContact);
+ if (lang == NULL)
+ goto RETURN;
+
+ voice = GetContactVoice(hContact, lang);
+ if (voice == NULL)
+ goto RETURN;
+
+ data = new SpeakData(lang, voice, GetContactVariant(hContact), text);
+ for (i = 0; i < NUM_PARAMETERS; i++)
+ data->setParameter(i, GetContactParam(hContact, i));
+ queue->Add(0, hContact, data);
+
+ return 0;
+
+RETURN:
+ mir_free(text);
+ return -1;
+}
+
+
+int SpeakAService(WPARAM wParam, LPARAM lParam)
+{
+ char *text = (char *) lParam;
+ if (text == NULL)
+ return -1;
+
+ return SpeakService((HANDLE) wParam, mir_a2t(text));
+}
+
+
+int SpeakWService(WPARAM wParam, LPARAM lParam)
+{
+ WCHAR *text = (WCHAR *) lParam;
+ if (text == NULL)
+ return -1;
+
+ return SpeakService((HANDLE) wParam, mir_u2t(text));
+}
+
+
+void Speak(HANDLE hContact, void *param)
+{
+ int status;
+
+ SpeakData *data = (SpeakData *) param;
+ if (data == NULL)
+ return;
+
+ if (languages.getCount() < 1)
+ goto RETURN;
+
+ if (hContact != (HANDLE) -1)
+ {
+ if (opts.respect_sndvol_mute && !DBGetContactSettingByte(NULL, "Skin", "UseSound", 1))
+ goto RETURN;
+
+ if (hContact != NULL && !DBGetContactSettingByte(hContact, MODULE_NAME, "Enabled", TRUE))
+ goto RETURN;
+
+ status = CallService(MS_CLIST_GETSTATUSMODE, 0, 0);
+ if (!StatusEnabled(status))
+ goto RETURN;
+
+ if (opts.enable_only_idle)
+ {
+ MIRANDA_IDLE_INFO idle = {0};
+ CallService(MS_IDLE_GETIDLEINFO, 0, (LPARAM) &idle);
+ if (idle.idleType == 0)
+ goto RETURN;
+ }
+ }
+
+ Speak(data);
+
+RETURN:
+ mir_free(data->text);
+ delete data;
+}
+
+
+ISpeechObjectToken * SAPI_FindVoice(ISpeechVoice *voice, TCHAR *id)
+{
+ HRESULT hr = 0;
+ ISpeechObjectTokens *voices = NULL;
+ ISpeechObjectToken *v = NULL;
+ long count = 0;
+ long i;
+
+ CHECK( voice->GetVoices(NULL, NULL, &voices) );
+
+ CHECK( voices->get_Count(&count) );
+
+ for(i = 0; i < count; i++)
+ {
+ CHECK( voices->Item(i, &v) );
+ if (v == NULL)
+ continue;
+
+ BstrToTchar id2;
+ CHECK( v->get_Id(id2) );
+
+ if (lstrcmpi(id2, id) == 0)
+ break;
+
+ RELEASE(v);
+ }
+
+ RELEASE( voices );
+ return v;
+
+err:
+ RELEASE( voices );
+ RELEASE( v );
+ return NULL;
+}
+
+
+void Speak(SpeakData *data)
+{
+ size_t len = lstrlen(data->text);
+
+ if (opts.truncate && len > opts.truncate_len)
+ data->text[opts.truncate_len] = 0;
+
+ if (data->voice->engine == ENGINE_ESPEAK)
+ {
+ if (data->variant != NULL)
+ {
+ TCHAR name[NAME_SIZE];
+ mir_sntprintf(name, MAX_REGS(name), _T("%s+%s"), data->voice->id, data->variant->id);
+ espeak_SetVoiceByName(TcharToUtf8(name));
+ }
+ else
+ espeak_SetVoiceByName(TcharToUtf8(data->voice->id));
+
+ for (int i = 0; i < NUM_PARAMETERS; i++)
+ espeak_SetParameter(PARAMETERS[i].eparam, data->getParameter(i), 0);
+
+ espeak_Synth(data->text, (len + 1) * sizeof(TCHAR), 0, POS_CHARACTER,
+ 0, espeakCHARS_TCHAR, NULL, NULL);
+ }
+ else if (data->voice->engine == ENGINE_SAPI)
+ {
+ HRESULT hr = 0;
+ ISpeechVoice *voice = NULL;
+ ISpeechObjectToken *v = NULL;
+
+ if (FAILED(CoInitialize(NULL)))
+ return;
+
+ CHECK( CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpeechVoice, (void **)&voice) );
+
+ v = SAPI_FindVoice(voice, data->voice->id);
+ if (v == NULL) goto err;
+
+ CHECK( voice->putref_Voice(v) );
+ CHECK( voice->put_Rate(data->getParameter(0)) );
+ CHECK( voice->put_Volume(data->getParameter(1)) );
+
+ CHECK( voice->Speak(BstrToTchar(data->text), SVSFDefault, NULL) );
+
+err:
+ RELEASE( v );
+ RELEASE( voice );
+
+ CoUninitialize();
+ }
+}
+
+
+int SAPI_GetDefaultRateFor(TCHAR *id)
+{
+ int ret = 0;
+
+ HRESULT hr = 0;
+ ISpeechVoice *voice = NULL;
+ ISpeechObjectToken *v = NULL;
+
+ if (FAILED(CoInitialize(NULL)))
+ return 0;
+
+ CHECK( CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpeechVoice, (void **)&voice) );
+
+ v = SAPI_FindVoice(voice, id);
+ if (v == NULL) goto err;
+
+ CHECK( voice->putref_Voice(v) );
+
+ {
+ long rate;
+ CHECK( voice->get_Rate(&rate) );
+ ret = (int) rate;
+ }
+
+err:
+ RELEASE( v );
+ RELEASE( voice );
+
+ CoUninitialize();
+
+ return ret;
+}
+
+
+TCHAR * VariablesSpeak(ARGUMENTSINFO *ai)
+{
+ if (ai->cbSize < sizeof(ARGUMENTSINFO))
+ return NULL;
+
+ ai->flags = AIF_FALSE;
+ if (ai->argc < 2 || ai->argc > 3)
+ return NULL;
+
+ TCHAR *text = ai->targv[1];
+ if (text == NULL)
+ return NULL;
+
+ HANDLE hContact = NULL;
+ if (ai->argc >= 3)
+ {
+ CONTACTSINFO ci = {0};
+ ci.cbSize = sizeof(ci);
+ ci.tszContact = ai->targv[2];
+ ci.flags = 0xFF | CI_UNICODE;
+ int count = CallService(MS_VARS_GETCONTACTFROMSTRING, (WPARAM)&ci, 0);
+ if (count == 1 && ci.hContacts != NULL)
+ {
+ hContact = ci.hContacts[0];
+ }
+ else
+ {
+ if (ci.hContacts != NULL)
+ CallService(MS_VARS_FREEMEMORY, (WPARAM) ci.hContacts, 0);
+
+ return NULL;
+ }
+ }
+
+ SpeakService(hContact, mir_tstrdup(text));
+ ai->flags = 0;
+ return mir_tstrdup(_T(""));
+}
|