diff options
author | pescuma <pescuma@c086bb3d-8645-0410-b8da-73a8550f86e7> | 2011-10-10 01:39:18 +0000 |
---|---|---|
committer | pescuma <pescuma@c086bb3d-8645-0410-b8da-73a8550f86e7> | 2011-10-10 01:39:18 +0000 |
commit | e9bf8a6e2d782dc480fb97cb59928c8cfe1dd777 (patch) | |
tree | 12bb8ebaab13ef1476ce9343482796c901ee0130 /Plugins/eSpeak | |
parent | f574681d9b6fee0d05319af1831620e48cc8492f (diff) |
Moved files from BerliOS
git-svn-id: http://pescuma.googlecode.com/svn/trunk/Miranda@229 c086bb3d-8645-0410-b8da-73a8550f86e7
Diffstat (limited to 'Plugins/eSpeak')
232 files changed, 37264 insertions, 0 deletions
diff --git a/Plugins/eSpeak/Docs/langpack_meSpeak.txt b/Plugins/eSpeak/Docs/langpack_meSpeak.txt new file mode 100644 index 0000000..ffca544 --- /dev/null +++ b/Plugins/eSpeak/Docs/langpack_meSpeak.txt @@ -0,0 +1,2 @@ +; meSpeak
+; Author: Pescuma
diff --git a/Plugins/eSpeak/Docs/meSpeak.png b/Plugins/eSpeak/Docs/meSpeak.png Binary files differnew file mode 100644 index 0000000..dc0451e --- /dev/null +++ b/Plugins/eSpeak/Docs/meSpeak.png diff --git a/Plugins/eSpeak/Docs/meSpeak_changelog.txt b/Plugins/eSpeak/Docs/meSpeak_changelog.txt new file mode 100644 index 0000000..2a05bb3 --- /dev/null +++ b/Plugins/eSpeak/Docs/meSpeak_changelog.txt @@ -0,0 +1,57 @@ +meSpeak
+
+Changelog:
+
+. 0.2.0.0
+ + Added SAPI voices
+ + Added option to truncate text by size
+
+. 0.0.0.12
+ * Another fix for stop speaking bug
+
+. 0.0.0.11
+ * Fix for stop speaking bug
+
+. 0.0.0.10
+ * Fix for overlaping options
+ + Option to disable selection of variant based on genre
+
+. 0.0.0.9
+ + Updated to eSpeak 1.39
+ + Option to only talk when idle
+ + Now the types are disabled by default
+ + If contact has gender, select a variant based on it (male1 or female1)
+
+. 0.0.0.8
+ * Fix for test button in contact options to use punctuation data
+ - Removed old mlog function
+
+. 0.0.0.7
+ * Fix to make contacts use system voice and variant if none is set for them
+
+. 0.0.0.6
+ + Updated to eSpeak 1.31
+ + Added punctuation to system settings
+ + Added option to respect SndVol mute (enabled by default)
+ * Fix for variants
+
+. 0.0.0.5
+ * Changed to create services in Load method
+ * Language names should be translatable now
+
+. 0.0.0.4
+ + Added function to Variables plugin to allow reading text
+
+. 0.0.0.3
+ + Option to prepend name of contact for event types without template
+ * Fix for no voices found
+ * Fix to translate dialog
+
+. 0.0.0.2
+ * Fix for test speak (while other speak happening at the same time)
+ * Fix for spelling words with accents
+ + Added options for volume, rate, pitch and range
+ + Renamed plugin to meSpeak
+
+. 0.0.0.1
+ + Initial version
\ No newline at end of file diff --git a/Plugins/eSpeak/Docs/meSpeak_readme.txt b/Plugins/eSpeak/Docs/meSpeak_readme.txt new file mode 100644 index 0000000..8ad24ea --- /dev/null +++ b/Plugins/eSpeak/Docs/meSpeak_readme.txt @@ -0,0 +1,30 @@ +meSpeak plugin
+--------------
+
+CAUTION: THIS IS A BETA STAGE PLUGIN. IT CAN DO VERY BAD THINGS. USE AT YOUR OWN RISK.
+
+
+This is a service plugin that read text aloud. It is based on eSpeak engine (http://espeak.sourceforge.net/) and also support SAPI voices (ver 5.1).
+
+It provides the following functionality:
+- System voice
+- Per contact voice
+- Options to disable by global status
+- Packed with voices for a lot of languages
+- "fake" support for plugins that support Variables
+
+This is a service plugin, so it does not read any text by itself. Other plugins need to request it!
+
+It also has an iconpack of flags with it. It is built using famfamfam's icons. If you want other set of flags or to download then separated, they can be found here:
+- famfamfam's icons as .ico: http://pescuma.mirandaim.ru/miranda/flags-famfamfam.zip (note that there are a lot of files inside this zip with wrong names. It happens because I don't know which languages they represent - and if they represent a language or not. So, if you think some file name must change, please tell me)
+- famfamfam's icons as .dll: http://pescuma.mirandaim.ru/miranda/flags-dll-famfamfam.zip
+- Angeli-Ka's icons as .ico: http://pescuma.mirandaim.ru/miranda/flags-angelika.zip
+- Angeli-Ka's icons as .ico with language names: http://pescuma.mirandaim.ru/miranda/flags-angelika-name.zip
+- Angeli-Ka's icons as .dll: http://pescuma.mirandaim.ru/miranda/flags-dll-angelika.zip
+
+Additional dictionary data is available for languages: +Russian, Cantonese at http://espeak.sourceforge.net/data/index.html
+
+Many thanks to the eSpeak team for their engine and to the famfamfam.com site for the icons.
+
+To report bugs/make suggestions, go to the forum thread: http://forums.miranda-im.org/showthread.php?t=16188
\ No newline at end of file diff --git a/Plugins/eSpeak/Docs/meSpeak_version.txt b/Plugins/eSpeak/Docs/meSpeak_version.txt new file mode 100644 index 0000000..84eff82 --- /dev/null +++ b/Plugins/eSpeak/Docs/meSpeak_version.txt @@ -0,0 +1 @@ +meSpeak 0.2.0.0
\ No newline at end of file diff --git a/Plugins/eSpeak/ZIP/doit.bat b/Plugins/eSpeak/ZIP/doit.bat new file mode 100644 index 0000000..17b501a --- /dev/null +++ b/Plugins/eSpeak/ZIP/doit.bat @@ -0,0 +1,132 @@ +rem @echo off
+
+rem Batch file to build and upload files
+rem
+rem TODO: Integration with FL
+
+set name=meSpeak
+
+rem To upload, this var must be set here or in other batch
+rem set ftp=ftp://<user>:<password>@<ftp>/<path>
+
+echo Building %name% ...
+
+msdev ..\eSpeak.dsp /MAKE "eSpeak - Win32 Release" /REBUILD
+msdev ..\eSpeak.dsp /MAKE "eSpeak - Win32 Unicode Release" /REBUILD
+
+echo Generating files for %name% ...
+
+del *.zip
+del *.dll
+copy ..\Docs\%name%_changelog.txt
+copy ..\Docs\%name%_version.txt
+copy ..\Docs\%name%_readme.txt
+mkdir Plugins
+cd Plugins
+del /Q *.*
+copy ..\..\..\..\bin\release\Plugins\%name%.dll
+cd ..
+mkdir Docs
+cd Docs
+del /Q *.*
+copy ..\..\Docs\%name%_readme.txt
+rem copy ..\..\Docs\langpack_%name%.txt
+rem copy ..\..\Docs\helppack_%name%.txt
+copy ..\..\m_speak.h
+cd ..
+mkdir Dictionaries
+cd Dictionaries
+mkdir Voice
+cd Voice
+xcopy ..\..\..\espeak-data\*.* /S
+cd ..
+cd ..
+mkdir Icons
+cd Icons
+copy ..\..\..\spellchecker\Flags\flags.dll
+cd ..
+mkdir src
+cd src
+del /Q *.*
+copy ..\..\*.h
+copy ..\..\*.c*
+copy ..\..\*.
+copy ..\..\*.rc
+copy ..\..\*.dsp
+copy ..\..\*.dsw
+mkdir Docs
+cd Docs
+del /Q *.*
+copy ..\..\..\Docs\*.*
+cd ..
+mkdir sdk
+cd sdk
+del /Q *.*
+copy ..\..\..\sdk\*.*
+cd ..
+mkdir lib
+cd lib
+del /Q *.*
+copy ..\..\..\lib\*.*
+cd ..
+mkdir res
+cd res
+del /Q *.*
+copy ..\..\..\res\*.*
+cd ..
+mkdir eSpeak
+cd eSpeak
+del /Q *.*
+copy ..\..\..\eSpeak\*.*
+cd ..
+cd ..
+copy ..\Release\%name%.pdb
+copy ..\Unicode_Release\%name%W.pdb
+
+
+"C:\Program Files\Filzip\Filzip.exe" -a -rp %name%.zip Plugins Docs Icons Dictionaries
+
+cd Plugins
+del /Q *.*
+copy ..\..\..\..\bin\release\Plugins\%name%W.dll
+cd ..
+
+"C:\Program Files\Filzip\Filzip.exe" -a -rp %name%W.zip Plugins Docs Icons Dictionaries
+
+"C:\Program Files\Filzip\Filzip.exe" -a -rp %name%_src.zip src\*.*
+"C:\Program Files\Filzip\Filzip.exe" -a -rp %name%.pdb.zip %name%.pdb
+"C:\Program Files\Filzip\Filzip.exe" -a -rp %name%W.pdb.zip %name%W.pdb
+
+del *.pdb
+rd /S /Q Plugins
+rd /S /Q Docs
+rd /S /Q Dictionaries
+rd /S /Q src
+rd /S /Q Icons
+
+if "%ftp%"=="" GOTO END
+
+echo Going to upload files...
+pause
+
+"C:\Program Files\FileZilla\FileZilla.exe" -u .\%name%.zip %ftp% -overwrite -close
+"C:\Program Files\FileZilla\FileZilla.exe" -u .\%name%W.zip %ftp% -overwrite -close
+"C:\Program Files\FileZilla\FileZilla.exe" -u .\%name%.pdb.zip %ftp% -overwrite -close
+"C:\Program Files\FileZilla\FileZilla.exe" -u .\%name%W.pdb.zip %ftp% -overwrite -close
+"C:\Program Files\FileZilla\FileZilla.exe" -u .\%name%_changelog.txt %ftp% -overwrite -close
+"C:\Program Files\FileZilla\FileZilla.exe" -u .\%name%_version.txt %ftp% -overwrite -close
+"C:\Program Files\FileZilla\FileZilla.exe" -u .\%name%_readme.txt %ftp% -overwrite -close
+
+if "%ftp2%"=="" GOTO END
+
+"C:\Program Files\FileZilla\FileZilla.exe" -u .\%name%.zip %ftp2% -overwrite -close
+"C:\Program Files\FileZilla\FileZilla.exe" -u .\%name%W.zip %ftp2% -overwrite -close
+"C:\Program Files\FileZilla\FileZilla.exe" -u .\%name%.pdb.zip %ftp2% -overwrite -close
+"C:\Program Files\FileZilla\FileZilla.exe" -u .\%name%W.pdb.zip %ftp2% -overwrite -close
+"C:\Program Files\FileZilla\FileZilla.exe" -u .\%name%_changelog.txt %ftp2% -overwrite -close
+"C:\Program Files\FileZilla\FileZilla.exe" -u .\%name%_version.txt %ftp2% -overwrite -close
+"C:\Program Files\FileZilla\FileZilla.exe" -u .\%name%_readme.txt %ftp2% -overwrite -close
+
+:END
+
+echo Done.
diff --git a/Plugins/eSpeak/commons.h b/Plugins/eSpeak/commons.h new file mode 100644 index 0000000..c288320 --- /dev/null +++ b/Plugins/eSpeak/commons.h @@ -0,0 +1,243 @@ +/*
+Copyright (C) 2007 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.
+*/
+
+
+#ifndef __COMMONS_H__
+# define __COMMONS_H__
+
+
+#define OEMRESOURCE
+#include <windows.h>
+#include <tchar.h>
+#include <stdio.h>
+#include <time.h>
+#include <commctrl.h>
+#include <sapi.h>
+#include <vector>
+
+
+// Miranda headers
+#define MIRANDA_VER 0x0700
+#include <newpluginapi.h>
+#include <m_system.h>
+#include <m_system_cpp.h>
+#include <m_protocols.h>
+#include <m_protosvc.h>
+#include <m_clist.h>
+#include <m_contacts.h>
+#include <m_langpack.h>
+#include <m_database.h>
+#include <m_options.h>
+#include <m_utils.h>
+#include <m_updater.h>
+#include <m_metacontacts.h>
+#include <m_popup.h>
+#include <m_history.h>
+#include <m_message.h>
+#include <m_folders.h>
+#include <m_icolib.h>
+#include <m_userinfo.h>
+#include <m_idle.h>
+
+#include "../utils/mir_memory.h"
+#include "../utils/mir_options.h"
+#include "../utils/mir_icons.h"
+#include "../utils/mir_buffer.h"
+#include "../utils/ContactAsyncQueue.h"
+#include "../utils/utf8_helpers.h"
+
+#include "resource.h"
+#include "m_speak.h"
+#include "options.h"
+#include "types.h"
+
+#include "eSpeak/speak_lib.h"
+
+
+#define MODULE_NAME "meSpeak"
+
+
+// Global Variables
+extern HINSTANCE hInst;
+extern PLUGINLINK *pluginLink;
+
+
+#define MAX_REGS(_A_) ( sizeof(_A_) / sizeof(_A_[0]) )
+#define RELEASE(_A_) if (_A_ != NULL) { _A_->Release(); _A_ = NULL; }
+
+
+#define ICON_SIZE 16
+#define NAME_SIZE 128
+
+#define GENDER_UNKNOWN 0
+#define GENDER_MALE 1
+#define GENDER_FEMALE 2
+
+int SortVoices(const Voice *voice1, const Voice *voice2);
+
+enum Engine
+{
+ ENGINE_ESPEAK,
+ ENGINE_SAPI
+};
+
+class Variant
+{
+public:
+ TCHAR name[NAME_SIZE];
+ int gender;
+ TCHAR id[NAME_SIZE];
+
+ Variant(const TCHAR *aName, int aGender, const TCHAR *anId)
+ {
+ lstrcpyn(name, aName, MAX_REGS(name));
+ gender = aGender;
+ lstrcpyn(id, anId, MAX_REGS(id));
+ }
+};
+
+
+class Voice
+{
+public:
+ Engine engine;
+ TCHAR name[NAME_SIZE];
+ int prio;
+ int gender;
+ TCHAR age[NAME_SIZE];
+ TCHAR id[NAME_SIZE];
+
+ Voice(Engine anEngine, const TCHAR *aName, int aPrio, int aGender, const TCHAR *anAge, const TCHAR *anId)
+ {
+ engine = anEngine;
+ lstrcpyn(name, aName, MAX_REGS(name));
+ prio = aPrio;
+ gender = aGender;
+ lstrcpyn(age, anAge, MAX_REGS(age));
+ lstrcpyn(id, anId, MAX_REGS(id));
+ }
+};
+
+
+class Language
+{
+public:
+ TCHAR language[NAME_SIZE];
+ TCHAR localized_name[NAME_SIZE];
+ TCHAR english_name[NAME_SIZE];
+ TCHAR full_name[NAME_SIZE];
+
+ LIST<Voice> voices;
+
+ Language(TCHAR *aLanguage)
+ : voices(5, SortVoices)
+ {
+ lstrcpyn(language, aLanguage, MAX_REGS(language));
+ localized_name[0] = _T('\0');
+ english_name[0] = _T('\0');
+ full_name[0] = _T('\0');
+ }
+};
+
+
+#define SCROLL 0
+#define COMBO 1
+
+struct RANGE
+{
+ int min;
+ int max;
+ int def;
+};
+
+static struct {
+ espeak_PARAMETER eparam;
+ RANGE espeak;
+ RANGE sapi;
+ char *setting;
+ int ctrl;
+ int label;
+ int type;
+} PARAMETERS[] = {
+ { espeakRATE, { 80, 389, 165 }, { -10, 10, 0 }, "Rate", IDC_RATE, IDC_RATE_L, SCROLL },
+ { espeakVOLUME, { 10, 190, 100 }, { 0, 100, 100 }, "Volume", IDC_VOLUME, IDC_VOLUME_L, SCROLL },
+ { espeakPITCH, { 0, 99, 50 }, { 0, -1, 0 }, "Pitch", IDC_PITCH, IDC_PITCH_L, SCROLL },
+ { espeakRANGE, { -100, 99, 50 }, { 0, -1, 0 }, "Range", IDC_RANGE, IDC_RANGE_L, SCROLL },
+ { espeakPUNCTUATION, { espeakPUNCT_NONE, espeakPUNCT_SOME, espeakPUNCT_SOME }, { 0, -1, 0 }, "Punctuation", IDC_PUNCT, IDC_PUNCT_L, COMBO }
+};
+
+#define NUM_PARAMETERS MAX_REGS(PARAMETERS)
+
+class SpeakData
+{
+public:
+ Language *lang;
+ Voice *voice;
+ Variant *variant;
+ TCHAR *text;
+ int parameters[NUM_PARAMETERS];
+
+ SpeakData(Language *aLang, Voice *aVoice, Variant *aVariant, TCHAR *aText)
+ {
+ lang = aLang;
+ voice = aVoice;
+ variant = aVariant;
+ text = aText;
+ }
+
+ void setParameter(int param, int value)
+ {
+ parameters[param] = value;
+ }
+
+ int getParameter(int param)
+ {
+ return parameters[param];
+ }
+};
+
+
+extern LIST<Language> languages;
+extern LIST<Variant> variants;
+extern ContactAsyncQueue *queue;
+
+
+int SpeakService(HANDLE hContact, TCHAR *text);
+void Speak(SpeakData *data);
+
+Language *GetLanguage(TCHAR *language, BOOL create = FALSE);
+
+Language *GetContactLanguage(HANDLE hContact);
+Voice *GetContactVoice(HANDLE hContact, Language *lang);
+Variant *GetContactVariant(HANDLE hContact);
+int GetContactParam(HANDLE hContact, int param);
+void SetContactParam(HANDLE hContact, int param, int value);
+
+void GetLangPackLanguage(TCHAR *name, size_t len);
+
+int SAPI_GetDefaultRateFor(TCHAR *id);
+
+HICON LoadIconEx(Language *lang, BOOL copy = FALSE);
+
+#define SPEAK_NAME "SpeakName"
+#define TEMPLATE_ENABLED "Enabled"
+#define TEMPLATE_TEXT "Text"
+
+
+#endif // __COMMONS_H__
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(""));
+}
diff --git a/Plugins/eSpeak/eSpeak.dsp b/Plugins/eSpeak/eSpeak.dsp new file mode 100644 index 0000000..c6c7958 --- /dev/null +++ b/Plugins/eSpeak/eSpeak.dsp @@ -0,0 +1,383 @@ +# Microsoft Developer Studio Project File - Name="eSpeak" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=eSpeak - Win32 Release
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "eSpeak.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "eSpeak.mak" CFG="eSpeak - Win32 Release"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "eSpeak - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "eSpeak - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "eSpeak - Win32 Unicode Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "eSpeak - Win32 Unicode Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "eSpeak - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MD /W3 /GX /O1 /YX /FD /c
+# SUBTRACT BASE CPP /Fr
+# ADD CPP /nologo /G4 /MT /W3 /GX /O2 /Ob0 /I "../../include" /I "sdk" /D "WIN32" /D "W32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /Fr /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x417 /d "NDEBUG"
+# ADD RSC /l 0x417 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 user32.lib shell32.lib wininet.lib gdi32.lib /nologo /base:"0x67100000" /dll /machine:I386 /filealign:0x200
+# SUBTRACT BASE LINK32 /pdb:none /map
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winmm.lib advapi32.lib PAStaticWMME.lib ole32.lib oleaut32.lib /nologo /base:"0x3EC20000" /dll /map /debug /debugtype:both /machine:I386 /nodefaultlib:"LIBC" /out:"..\..\bin\release\Plugins\meSpeak.dll" /pdbtype:sept /libpath:"lib" /filealign:0x200 /ALIGN:4096 /ignore:4108
+# SUBTRACT LINK32 /profile /pdb:none
+
+!ELSEIF "$(CFG)" == "eSpeak - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Ignore_Export_Lib 0
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /G4 /MT /W3 /GX /O2 /Ob0 /I "../../include" /FR /YX /FD /c
+# ADD CPP /nologo /G4 /MTd /W3 /GX /ZI /Od /I "../../include" /I "sdk" /D "WIN32" /D "W32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /FR /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x417 /d "NDEBUG"
+# ADD RSC /l 0x417 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 comctl32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /out:"..\..bin\release\Plugins\eSpeak.dll" /filealign:0x200 /ALIGN:4096 /ignore:4108
+# SUBTRACT BASE LINK32 /profile /pdb:none
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winmm.lib advapi32.lib PAStaticWMME.lib ole32.lib oleaut32.lib /nologo /base:"0x3EC20000" /dll /incremental:yes /debug /machine:I386 /nodefaultlib:"LIBC" /out:"..\..\bin\debug\Plugins\meSpeak.dll" /libpath:"lib" /filealign:0x200 /ALIGN:4096 /ignore:4108
+# SUBTRACT LINK32 /profile /pdb:none
+
+!ELSEIF "$(CFG)" == "eSpeak - Win32 Unicode Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "eSpeak___Win32_Unicode_Debug"
+# PROP BASE Intermediate_Dir "eSpeak___Win32_Unicode_Debug"
+# PROP BASE Ignore_Export_Lib 0
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Unicode_Debug"
+# PROP Intermediate_Dir "Unicode_Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /G4 /MTd /W3 /GX /ZI /Od /I "../../include" /FR /YX /FD /c
+# ADD CPP /nologo /G4 /MTd /W3 /GX /ZI /Od /I "../../include" /I "sdk" /D "WIN32" /D "W32" /D "_DEBUG" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /D "_USRDLL" /FR /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x417 /d "NDEBUG"
+# ADD RSC /l 0x417 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 comctl32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /base:"0x32100000" /dll /incremental:yes /debug /machine:I386 /out:"..\..\bin\debug\Plugins\eSpeak.dll" /filealign:0x200 /ALIGN:4096 /ignore:4108
+# SUBTRACT BASE LINK32 /profile /pdb:none
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winmm.lib advapi32.lib PAStaticWMME.lib ole32.lib oleaut32.lib /nologo /base:"0x3EC20000" /dll /incremental:yes /debug /machine:I386 /nodefaultlib:"LIBC" /out:"..\..\bin\debug unicode\Plugins\meSpeakW.dll" /libpath:"lib" /filealign:0x200 /ALIGN:4096 /ignore:4108
+# SUBTRACT LINK32 /profile /pdb:none
+
+!ELSEIF "$(CFG)" == "eSpeak - Win32 Unicode Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "eSpeak___Win32_Unicode_Release"
+# PROP BASE Intermediate_Dir "eSpeak___Win32_Unicode_Release"
+# PROP BASE Ignore_Export_Lib 0
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Unicode_Release"
+# PROP Intermediate_Dir "Unicode_Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /G4 /MT /W3 /GX /O2 /Ob0 /I "../../include" /Fr /YX /FD /c
+# ADD CPP /nologo /G4 /MT /W3 /GX /O2 /Ob0 /I "../../include" /I "sdk" /D "WIN32" /D "W32" /D "NDEBUG" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /D "_USRDLL" /Fr /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x417 /d "NDEBUG"
+# ADD RSC /l 0x417 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 comctl32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /base:"0x32100000" /dll /map /machine:I386 /out:"..\..\bin\release\Plugins\eSpeak.dll" /filealign:0x200 /ALIGN:4096 /ignore:4108
+# SUBTRACT BASE LINK32 /profile /pdb:none
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winmm.lib advapi32.lib PAStaticWMME.lib ole32.lib oleaut32.lib /nologo /base:"0x3EC20000" /dll /map /debug /debugtype:both /machine:I386 /nodefaultlib:"LIBC" /out:"..\..\bin\release\Plugins\meSpeakW.dll" /pdbtype:sept /libpath:"lib" /filealign:0x200 /ALIGN:4096 /ignore:4108
+# SUBTRACT LINK32 /profile /pdb:none
+
+!ENDIF
+
+# Begin Target
+
+# Name "eSpeak - Win32 Release"
+# Name "eSpeak - Win32 Debug"
+# Name "eSpeak - Win32 Unicode Debug"
+# Name "eSpeak - Win32 Unicode Release"
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=.\commons.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\utils\ContactAsyncQueue.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\m_speak.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\utils\mir_icons.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\utils\mir_memory.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\utils\mir_options.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\options.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\types.h
+# End Source File
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# Begin Source File
+
+SOURCE=.\resource.rc
+# End Source File
+# Begin Source File
+
+SOURCE=.\res\unknown.ico
+# End Source File
+# End Group
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=..\utils\ContactAsyncQueue.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=..\utils\mir_icons.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=..\utils\mir_options.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\options.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\types.cpp
+# End Source File
+# End Group
+# Begin Group "Docs"
+
+# PROP Default_Filter ""
+# Begin Source File
+
+SOURCE=.\Docs\langpack_meSpeak.txt
+# End Source File
+# Begin Source File
+
+SOURCE=.\Docs\meSpeak_changelog.txt
+# End Source File
+# Begin Source File
+
+SOURCE=.\Docs\meSpeak_readme.txt
+# End Source File
+# Begin Source File
+
+SOURCE=.\Docs\meSpeak_version.txt
+# End Source File
+# End Group
+# Begin Group "eSpeak"
+
+# PROP Default_Filter ""
+# Begin Source File
+
+SOURCE=.\eSpeak\compiledict.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\dictionary.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\event.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\fifo.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\intonation.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\klatt.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\klatt.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\numbers.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\phoneme.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\phonemelist.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\readclause.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\setlengths.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\sintab.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\speak_lib.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\speak_lib.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\speech.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\StdAfx.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\synth_mbrola.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\synthdata.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\synthesize.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\synthesize.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\tr_languages.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\tr_languages.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\translate.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\translate.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\voice.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\voices.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\wave.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\wave_pulse.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\wave_sada.cpp
+# End Source File
+# Begin Source File
+
+SOURCE=.\eSpeak\wavegen.cpp
+# End Source File
+# End Group
+# End Target
+# End Project
diff --git a/Plugins/eSpeak/eSpeak.dsw b/Plugins/eSpeak/eSpeak.dsw new file mode 100644 index 0000000..ac273da --- /dev/null +++ b/Plugins/eSpeak/eSpeak.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00
+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
+
+###############################################################################
+
+Project: "eSpeak"=.\eSpeak.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
diff --git a/Plugins/eSpeak/eSpeak.sln b/Plugins/eSpeak/eSpeak.sln new file mode 100644 index 0000000..a490d6f --- /dev/null +++ b/Plugins/eSpeak/eSpeak.sln @@ -0,0 +1,26 @@ +
+Microsoft Visual Studio Solution File, Format Version 10.00
+# Visual Studio 2008
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "eSpeak", "eSpeak.vcproj", "{AC88F3D2-D114-4174-8F0E-209921DAB63A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Release|Win32 = Release|Win32
+ Unicode Debug|Win32 = Unicode Debug|Win32
+ Unicode Release|Win32 = Unicode Release|Win32
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {AC88F3D2-D114-4174-8F0E-209921DAB63A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {AC88F3D2-D114-4174-8F0E-209921DAB63A}.Debug|Win32.Build.0 = Debug|Win32
+ {AC88F3D2-D114-4174-8F0E-209921DAB63A}.Release|Win32.ActiveCfg = Release|Win32
+ {AC88F3D2-D114-4174-8F0E-209921DAB63A}.Release|Win32.Build.0 = Release|Win32
+ {AC88F3D2-D114-4174-8F0E-209921DAB63A}.Unicode Debug|Win32.ActiveCfg = Unicode Debug|Win32
+ {AC88F3D2-D114-4174-8F0E-209921DAB63A}.Unicode Debug|Win32.Build.0 = Unicode Debug|Win32
+ {AC88F3D2-D114-4174-8F0E-209921DAB63A}.Unicode Release|Win32.ActiveCfg = Unicode Release|Win32
+ {AC88F3D2-D114-4174-8F0E-209921DAB63A}.Unicode Release|Win32.Build.0 = Unicode Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Plugins/eSpeak/eSpeak.vcproj b/Plugins/eSpeak/eSpeak.vcproj new file mode 100644 index 0000000..f73319c --- /dev/null +++ b/Plugins/eSpeak/eSpeak.vcproj @@ -0,0 +1,1402 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9,00"
+ Name="eSpeak"
+ ProjectGUID="{AC88F3D2-D114-4174-8F0E-209921DAB63A}"
+ RootNamespace="eSpeak"
+ TargetFrameworkVersion="131072"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Unicode Debug|Win32"
+ OutputDirectory=".\Unicode_Debug"
+ IntermediateDirectory=".\Unicode_Debug"
+ ConfigurationType="2"
+ InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops"
+ UseOfMFC="0"
+ ATLMinimizesCRunTimeLibraryUsage="false"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ PreprocessorDefinitions="NDEBUG"
+ MkTypLibCompatible="true"
+ SuppressStartupBanner="true"
+ TargetEnvironment="1"
+ TypeLibraryName=".\Unicode_Debug/eSpeak.tlb"
+ HeaderFileName=""
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories="../../include,sdk"
+ PreprocessorDefinitions="WIN32;W32;_DEBUG;_WINDOWS;UNICODE;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE"
+ RuntimeLibrary="1"
+ PrecompiledHeaderFile=".\Unicode_Debug/eSpeak.pch"
+ AssemblerListingLocation=".\Unicode_Debug/"
+ ObjectFile=".\Unicode_Debug/"
+ ProgramDataBaseFileName=".\Unicode_Debug/"
+ BrowseInformation="1"
+ WarningLevel="3"
+ SuppressStartupBanner="true"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ PreprocessorDefinitions="NDEBUG"
+ Culture="1047"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalOptions="/ALIGN:4096 /filealign:0x200 /ignore:4108 "
+ AdditionalDependencies="winmm.lib PAStaticWMME.lib comsuppw.lib"
+ OutputFile="..\..\bin\debug unicode\Plugins\meSpeakW.dll"
+ LinkIncremental="2"
+ SuppressStartupBanner="true"
+ AdditionalLibraryDirectories="lib"
+ IgnoreDefaultLibraryNames="LIBC"
+ GenerateDebugInformation="true"
+ ProgramDatabaseFile=".\Unicode_Debug/eSpeakW.pdb"
+ BaseAddress="0x3EC20000"
+ RandomizedBaseAddress="1"
+ DataExecutionPrevention="0"
+ ImportLibrary=".\Unicode_Debug/eSpeakW.lib"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ SuppressStartupBanner="true"
+ OutputFile=".\Unicode_Debug/eSpeak.bsc"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Unicode Release|Win32"
+ OutputDirectory=".\Unicode_Release"
+ IntermediateDirectory=".\Unicode_Release"
+ ConfigurationType="2"
+ InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops"
+ UseOfMFC="0"
+ ATLMinimizesCRunTimeLibraryUsage="false"
+ CharacterSet="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ PreprocessorDefinitions="NDEBUG"
+ MkTypLibCompatible="true"
+ SuppressStartupBanner="true"
+ TargetEnvironment="1"
+ TypeLibraryName=".\Unicode_Release/eSpeak.tlb"
+ HeaderFileName=""
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ InlineFunctionExpansion="0"
+ AdditionalIncludeDirectories="../../include,sdk"
+ PreprocessorDefinitions="WIN32;W32;NDEBUG;_WINDOWS;UNICODE;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE"
+ StringPooling="true"
+ RuntimeLibrary="0"
+ EnableFunctionLevelLinking="true"
+ PrecompiledHeaderFile=".\Unicode_Release/eSpeak.pch"
+ AssemblerListingLocation=".\Unicode_Release/"
+ ObjectFile=".\Unicode_Release/"
+ ProgramDataBaseFileName=".\Unicode_Release/"
+ BrowseInformation="2"
+ BrowseInformationFile=".\Unicode_Release/"
+ WarningLevel="3"
+ SuppressStartupBanner="true"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ PreprocessorDefinitions="NDEBUG"
+ Culture="1047"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalOptions="/ALIGN:4096 /filealign:0x200 /ignore:4108 "
+ AdditionalDependencies="winmm.lib PAStaticWMME.lib sapi.lib"
+ OutputFile="..\..\bin\release\Plugins\meSpeakW.dll"
+ LinkIncremental="1"
+ SuppressStartupBanner="true"
+ AdditionalLibraryDirectories="lib"
+ IgnoreDefaultLibraryNames="LIBC"
+ GenerateDebugInformation="true"
+ ProgramDatabaseFile=".\Unicode_Release/eSpeakW.pdb"
+ GenerateMapFile="true"
+ MapFileName=".\Unicode_Release/eSpeakW.map"
+ BaseAddress="0x3EC20000"
+ RandomizedBaseAddress="1"
+ DataExecutionPrevention="0"
+ ImportLibrary=".\Unicode_Release/eSpeakW.lib"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ SuppressStartupBanner="true"
+ OutputFile=".\Unicode_Release/eSpeak.bsc"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory=".\Release"
+ IntermediateDirectory=".\Release"
+ ConfigurationType="2"
+ InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops"
+ UseOfMFC="0"
+ ATLMinimizesCRunTimeLibraryUsage="false"
+ CharacterSet="2"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ PreprocessorDefinitions="NDEBUG"
+ MkTypLibCompatible="true"
+ SuppressStartupBanner="true"
+ TargetEnvironment="1"
+ TypeLibraryName=".\Release/eSpeak.tlb"
+ HeaderFileName=""
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ InlineFunctionExpansion="0"
+ AdditionalIncludeDirectories="../../include,sdk"
+ PreprocessorDefinitions="WIN32;W32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE"
+ StringPooling="true"
+ RuntimeLibrary="0"
+ EnableFunctionLevelLinking="true"
+ PrecompiledHeaderFile=".\Release/eSpeak.pch"
+ AssemblerListingLocation=".\Release/"
+ ObjectFile=".\Release/"
+ ProgramDataBaseFileName=".\Release/"
+ BrowseInformation="2"
+ BrowseInformationFile=".\Release/"
+ WarningLevel="3"
+ SuppressStartupBanner="true"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ PreprocessorDefinitions="NDEBUG"
+ Culture="1047"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalOptions="/ALIGN:4096 /filealign:0x200 /ignore:4108 "
+ AdditionalDependencies="winmm.lib PAStaticWMME.lib sapi.lib"
+ OutputFile="..\..\bin\release\Plugins\meSpeak.dll"
+ LinkIncremental="1"
+ SuppressStartupBanner="true"
+ AdditionalLibraryDirectories="lib"
+ IgnoreDefaultLibraryNames="LIBC"
+ GenerateDebugInformation="true"
+ ProgramDatabaseFile=".\Release/eSpeak.pdb"
+ GenerateMapFile="true"
+ MapFileName=".\Release/eSpeak.map"
+ BaseAddress="0x3EC20000"
+ RandomizedBaseAddress="1"
+ DataExecutionPrevention="0"
+ ImportLibrary=".\Release/eSpeak.lib"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ SuppressStartupBanner="true"
+ OutputFile=".\Release/eSpeak.bsc"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory=".\Debug"
+ IntermediateDirectory=".\Debug"
+ ConfigurationType="2"
+ InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC60.vsprops"
+ UseOfMFC="0"
+ ATLMinimizesCRunTimeLibraryUsage="false"
+ CharacterSet="2"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ PreprocessorDefinitions="NDEBUG"
+ MkTypLibCompatible="true"
+ SuppressStartupBanner="true"
+ TargetEnvironment="1"
+ TypeLibraryName=".\Debug/eSpeak.tlb"
+ HeaderFileName=""
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories="../../include,sdk"
+ PreprocessorDefinitions="WIN32;W32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE"
+ RuntimeLibrary="1"
+ PrecompiledHeaderFile=".\Debug/eSpeak.pch"
+ AssemblerListingLocation=".\Debug/"
+ ObjectFile=".\Debug/"
+ ProgramDataBaseFileName=".\Debug/"
+ BrowseInformation="1"
+ WarningLevel="3"
+ SuppressStartupBanner="true"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ PreprocessorDefinitions="NDEBUG"
+ Culture="1047"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ AdditionalOptions="/ALIGN:4096 /filealign:0x200 /ignore:4108 "
+ AdditionalDependencies="winmm.lib PAStaticWMME.lib sapi.lib"
+ OutputFile="..\..\bin\debug\Plugins\meSpeak.dll"
+ LinkIncremental="2"
+ SuppressStartupBanner="true"
+ AdditionalLibraryDirectories="lib"
+ IgnoreDefaultLibraryNames="LIBC"
+ GenerateDebugInformation="true"
+ ProgramDatabaseFile=".\Debug/eSpeak.pdb"
+ BaseAddress="0x3EC20000"
+ RandomizedBaseAddress="1"
+ DataExecutionPrevention="0"
+ ImportLibrary=".\Debug/eSpeak.lib"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ SuppressStartupBanner="true"
+ OutputFile=".\Debug/eSpeak.bsc"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl"
+ >
+ <File
+ RelativePath="commons.h"
+ >
+ </File>
+ <File
+ RelativePath="..\utils\ContactAsyncQueue.h"
+ >
+ </File>
+ <File
+ RelativePath="m_speak.h"
+ >
+ </File>
+ <File
+ RelativePath="..\utils\mir_buffer.h"
+ >
+ </File>
+ <File
+ RelativePath="..\utils\mir_icons.h"
+ >
+ </File>
+ <File
+ RelativePath="..\utils\mir_memory.h"
+ >
+ </File>
+ <File
+ RelativePath="..\utils\mir_options.h"
+ >
+ </File>
+ <File
+ RelativePath="options.h"
+ >
+ </File>
+ <File
+ RelativePath=".\resource.h"
+ >
+ </File>
+ <File
+ RelativePath=".\types.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Resource Files"
+ Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+ >
+ <File
+ RelativePath="resource.rc"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCResourceCompilerTool"
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCResourceCompilerTool"
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCResourceCompilerTool"
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCResourceCompilerTool"
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="res\unknown.ico"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+ >
+ <File
+ RelativePath="..\utils\ContactAsyncQueue.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="eSpeak.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\utils\mir_icons.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="..\utils\mir_options.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="options.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\types.cpp"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Docs"
+ >
+ <File
+ RelativePath=".\Docs\langpack_meSpeak.txt"
+ >
+ </File>
+ <File
+ RelativePath=".\Docs\meSpeak_changelog.txt"
+ >
+ </File>
+ <File
+ RelativePath=".\Docs\meSpeak_readme.txt"
+ >
+ </File>
+ <File
+ RelativePath=".\Docs\meSpeak_version.txt"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="eSpeak"
+ >
+ <File
+ RelativePath="eSpeak\compiledict.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="eSpeak\dictionary.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\eSpeak\event.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\eSpeak\fifo.cpp"
+ >
+ </File>
+ <File
+ RelativePath="eSpeak\intonation.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="eSpeak\numbers.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="eSpeak\phoneme.h"
+ >
+ </File>
+ <File
+ RelativePath="eSpeak\phonemelist.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="eSpeak\readclause.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="eSpeak\setlengths.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="eSpeak\sintab.h"
+ >
+ </File>
+ <File
+ RelativePath="eSpeak\speak_lib.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="eSpeak\speak_lib.h"
+ >
+ </File>
+ <File
+ RelativePath="eSpeak\speech.h"
+ >
+ </File>
+ <File
+ RelativePath="eSpeak\StdAfx.h"
+ >
+ </File>
+ <File
+ RelativePath="eSpeak\synth_mbrola.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="eSpeak\synthdata.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="eSpeak\synthesize.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="eSpeak\synthesize.h"
+ >
+ </File>
+ <File
+ RelativePath="eSpeak\tr_languages.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="eSpeak\translate.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath="eSpeak\translate.h"
+ >
+ </File>
+ <File
+ RelativePath="eSpeak\voice.h"
+ >
+ </File>
+ <File
+ RelativePath="eSpeak\voices.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\eSpeak\wave.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\eSpeak\wave_pulse.cpp"
+ >
+ </File>
+ <File
+ RelativePath=".\eSpeak\wave_sada.cpp"
+ >
+ </File>
+ <File
+ RelativePath="eSpeak\wavegen.cpp"
+ >
+ <FileConfiguration
+ Name="Unicode Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Unicode Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ PreprocessorDefinitions=""
+ />
+ </FileConfiguration>
+ </File>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/Plugins/eSpeak/eSpeak/StdAfx.h b/Plugins/eSpeak/eSpeak/StdAfx.h new file mode 100644 index 0000000..c9a526f --- /dev/null +++ b/Plugins/eSpeak/eSpeak/StdAfx.h @@ -0,0 +1,3 @@ +// This is a dummy file. +// A file of this name is needed on Windows + diff --git a/Plugins/eSpeak/eSpeak/compiledict.cpp b/Plugins/eSpeak/eSpeak/compiledict.cpp new file mode 100644 index 0000000..9fcaa9a --- /dev/null +++ b/Plugins/eSpeak/eSpeak/compiledict.cpp @@ -0,0 +1,1649 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "StdAfx.h" + +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <wctype.h> + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "translate.h" + +//#define OPT_FORMAT // format the text and write formatted copy to Log file +//#define OUTPUT_FORMAT + +extern void Write4Bytes(FILE *f, int value); +int HashDictionary(const char *string); + +static FILE *f_log = NULL; +extern char *dir_dictionary; + +static int linenum; +static int error_count; +static int transpose_offset; // transpose character range for LookupDictList() +static int transpose_min; +static int transpose_max; +static int text_mode = 0; +static int debug_flag = 0; + +static int hash_counts[N_HASH_DICT]; +static char *hash_chains[N_HASH_DICT]; +static char letterGroupsDefined[N_LETTER_GROUPS]; + +MNEM_TAB mnem_flags[] = { + // these in the first group put a value in bits0-3 of dictionary_flags + {"$1", 0x41}, // stress on 1st syllable + {"$2", 0x42}, // stress on 2nd syllable + {"$3", 0x43}, + {"$4", 0x44}, + {"$5", 0x45}, + {"$6", 0x46}, + {"$7", 0x47}, + {"$u", 0x48}, // reduce to unstressed + {"$u1", 0x49}, + {"$u2", 0x4a}, + {"$u3", 0x4b}, + {"$u+", 0x4c}, // reduce to unstressed, but stress at end of clause + {"$u1+", 0x4d}, + {"$u2+", 0x4e}, + {"$u3+", 0x4f}, + + + // these set the corresponding numbered bit if dictionary_flags + {"$pause", 8}, /* ensure pause before this word */ + {"$only", 9}, /* only match on this word without suffix */ + {"$onlys", 10}, /* only match with none, or with 's' suffix */ + {"$strend", 11}, /* full stress if at end of clause */ + {"$strend2", 12}, /* full stress if at end of clause, or only followed by unstressed */ + {"$unstressend",13}, /* reduce stress at end of clause */ + {"$atend", 14}, /* use this pronunciation if at end of clause */ + + {"$dot", 16}, /* ignore '.' after this word (abbreviation) */ + {"$abbrev", 17}, /* use this pronuciation rather than split into letters */ + {"$stem", 18}, // must have a suffix + +// language specific + {"$double", 19}, // IT double the initial consonant of next word + {"$alt", 20}, // use alternative pronunciation + {"$alt2", 21}, + + + {"$max3", 27}, // limit to 3 repetitions + {"$brk", 28}, // a shorter $pause + {"$text", 29}, // word translates to replcement text, not phonemes + +// flags in dictionary word 2 + {"$verbf", 0x20}, /* verb follows */ + {"$verbsf", 0x21}, /* verb follows, allow -s suffix */ + {"$nounf", 0x22}, /* noun follows */ + {"$pastf", 0x23}, /* past tense follows */ + {"$verb", 0x24}, /* use this pronunciation when its a verb */ + {"$noun", 0x25}, /* use this pronunciation when its a noun */ + {"$past", 0x26}, /* use this pronunciation when its past tense */ + {"$verbextend",0x28}, /* extend influence of 'verb follows' */ + {"$capital", 0x29}, /* use this pronunciation if initial letter is upper case */ + {"$allcaps", 0x2a}, /* use this pronunciation if initial letter is upper case */ + {"$accent", 0x2b}, // character name is base-character name + accent name + + // doesn't set dictionary_flags + {"$?", 100}, // conditional rule, followed by byte giving the condition number + + {"$textmode", 200}, + {"$phonememode", 201}, + {NULL, -1} +}; + + +#define LEN_GROUP_NAME 12 + +typedef struct { + char name[LEN_GROUP_NAME+1]; + unsigned int start; + unsigned int length; +} RGROUP; + + +int isspace2(unsigned int c) +{//========================= +// can't use isspace() because on Windows, isspace(0xe1) gives TRUE ! + int c2; + + if(((c2 = (c & 0xff)) == 0) || (c > ' ')) + return(0); + return(1); +} + + +static const char *LookupMnem2(MNEM_TAB *table, int value) +{//======================================================= + while(table->mnem != NULL) + { + if(table->value == value) + return(table->mnem); + table++; + } + return(""); +} + + +char *print_dictionary_flags(unsigned int *flags) +{//============================================== + static char buf[20]; + + sprintf(buf,"%s 0x%x/%x",LookupMnem2(mnem_flags,(flags[0] & 0xf)+0x40), flags[0], flags[1]); + return(buf); +} + + + +static FILE *fopen_log(const char *fname,const char *access) +{//================================================== +// performs fopen, but produces error message to f_log if it fails + FILE *f; + + if((f = fopen(fname,access)) == NULL) + { + if(f_log != NULL) + fprintf(f_log,"Can't access (%s) file '%s'\n",access,fname); + } + return(f); +} + + +#ifdef OPT_FORMAT +static const char *lookup_mnem(MNEM_TAB *table, int value) +//======================================================== +/* Lookup a mnemonic string in a table, return its name */ +{ + while(table->mnem != NULL) + { + if(table->value==value) + return(table->mnem); + table++; + } + return("??"); /* not found */ +} /* end of mnem */ +#endif + + + + +static int compile_line(char *linebuf, char *dict_line, int *hash) +{//=============================================================== +// Compile a line in the language_list file + unsigned char c; + char *p; + char *word; + char *phonetic; + unsigned int ix; + int step; + unsigned int n_flag_codes = 0; + int flag_offset; + int length; + int multiple_words = 0; + char *multiple_string = NULL; + char *multiple_string_end = NULL; + + int len_word; + int len_phonetic; + int text_not_phonemes; // this word specifies replacement text, not phonemes + unsigned int wc; + int all_upper_case; + + char *mnemptr; + char *comment; + unsigned char flag_codes[100]; + char encoded_ph[200]; + unsigned char bad_phoneme[4]; +static char nullstring[] = {0}; + + comment = NULL; + text_not_phonemes = 0; + phonetic = word = nullstring; + +if(memcmp(linebuf,"_-",2)==0) +{ +step=1; // TEST +} + p = linebuf; +// while(isspace2(*p)) p++; + +#ifdef deleted + if(*p == '$') + { + if(memcmp(p,"$textmode",9) == 0) + { + text_mode = 1; + return(0); + } + if(memcmp(p,"$phonememode",12) == 0) + { + text_mode = 0; + return(0); + } + } +#endif + + step = 0; + + c = 0; + while(c != '\n') + { + c = *p; + + if((c == '?') && (step==0)) + { + // conditional rule, allow only if the numbered condition is set for the voice + flag_offset = 100; + + p++; + if(*p == '!') + { + // allow only if the numbered condition is NOT set + flag_offset = 132; + p++; + } + + ix = 0; + if(isdigit(*p)) + { + ix += (*p-'0'); + p++; + } + if(isdigit(*p)) + { + ix = ix*10 + (*p-'0'); + p++; + } + flag_codes[n_flag_codes++] = ix + flag_offset; + c = *p; + } + + if((c == '$') && isalnum(p[1])) + { + /* read keyword parameter */ + mnemptr = p; + while(!isspace2(c = *p)) p++; + *p = 0; + + ix = LookupMnem(mnem_flags,mnemptr); + if(ix > 0) + { + if(ix == 200) + { + text_mode = 1; + } + else + if(ix == 201) + { + text_mode = 0; + } + else + if(ix == BITNUM_FLAG_TEXTMODE) + { + text_not_phonemes = 1; + } + else + { + flag_codes[n_flag_codes++] = ix; + } + } + else + { + fprintf(f_log,"%5d: Unknown keyword: %s\n",linenum,mnemptr); + error_count++; + } + } + + if((c == '/') && (p[1] == '/') && (multiple_words==0)) + { + c = '\n'; /* "//" treat comment as end of line */ + comment = p; + } + + switch(step) + { + case 0: + if(c == '(') + { + multiple_words = 1; + word = p+1; + step = 1; + } + else + if(!isspace2(c)) + { + word = p; + step = 1; + } + break; + + case 1: + if((c == '-') && (word[0] != '_')) + { + flag_codes[n_flag_codes++] = BITNUM_FLAG_HYPHENATED; + c = ' '; + } + if(isspace2(c)) + { + p[0] = 0; /* terminate english word */ + + if(multiple_words) + { + multiple_string = multiple_string_end = p+1; + step = 2; + } + else + { + step = 3; + } + } + else + if((c == ')') && multiple_words) + { + p[0] = 0; + step = 3; + multiple_words = 0; + } + break; + + case 2: + if(isspace2(c)) + { + multiple_words++; + } + else + if(c == ')') + { + p[0] = ' '; // terminate extra string + multiple_string_end = p+1; + step = 3; + } + break; + + case 3: + if(!isspace2(c)) + { + phonetic = p; + step = 4; + } + break; + + case 4: + if(isspace2(c)) + { + p[0] = 0; /* terminate phonetic */ + step = 5; + } + break; + + case 5: + break; + } + p++; + } + + if(word[0] == 0) + { +#ifdef OPT_FORMAT + if(comment != NULL) + fprintf(f_log,"%s",comment); + else + fputc('\n',f_log); +#endif + return(0); /* blank line */ + } + + if(text_mode) + text_not_phonemes = 1; + + if(text_not_phonemes != translator->langopts.textmode) + { + flag_codes[n_flag_codes++] = BITNUM_FLAG_TEXTMODE; + } + + if(text_not_phonemes) + { + // this is replacement text, so don't encode as phonemes. Restrict the length of the replacement word + strncpy0(encoded_ph,phonetic,N_WORD_BYTES-4); + } + else + { + EncodePhonemes(phonetic,encoded_ph,bad_phoneme); + if(strchr(encoded_ph,phonSWITCH) != 0) + { + flag_codes[n_flag_codes++] = BITNUM_FLAG_ONLY_S; // don't match on suffixes (except 's') when switching languages + } + + // check for errors in the phonemes codes + for(ix=0; ix<sizeof(encoded_ph); ix++) + { + c = encoded_ph[ix]; + if(c == 0) break; + + if(c == 255) + { + /* unrecognised phoneme, report error */ + fprintf(f_log,"%5d: Bad phoneme [%c] (0x%x) in: %s %s\n",linenum,bad_phoneme[0],bad_phoneme[0],word,phonetic); + error_count++; + } + } + } + + if(sscanf(word,"U+%x",&wc) == 1) + { + // Character code + ix = utf8_out(wc, word); + word[ix] = 0; + } + else + if(word[0] != '_') + { + // convert to lower case, and note if the word is all-capitals + int c2; + + all_upper_case = 1; + p = word; + for(p=word;;) + { + // this assumes that the lower case char is the same length as the upper case char + // OK, except for Turkish "I", but use towlower() rather than towlower2() + ix = utf8_in(&c2,p); + if(c2 == 0) + break; + if(iswupper(c2)) + { + utf8_out(towlower(c2),p); + } + else + { + all_upper_case = 0; + } + p += ix; + } + if(all_upper_case) + { + flag_codes[n_flag_codes++] = BITNUM_FLAG_ALLCAPS; + } + } + + len_word = strlen(word); + + if(transpose_offset > 0) + { + len_word = TransposeAlphabet(word, transpose_offset, transpose_min, transpose_max); + } + + *hash = HashDictionary(word); + len_phonetic = strlen(encoded_ph); + + dict_line[1] = len_word; // bit 6 indicates whether the word has been compressed + len_word &= 0x3f; + + memcpy(&dict_line[2],word,len_word); + + if(len_phonetic == 0) + { + // no phonemes specified. set bit 7 + dict_line[1] |= 0x80; + length = len_word + 2; + } + else + { + length = len_word + len_phonetic + 3; + strcpy(&dict_line[(len_word)+2],encoded_ph); + } + + for(ix=0; ix<n_flag_codes; ix++) + { + dict_line[ix+length] = flag_codes[ix]; + } + length += n_flag_codes; + + if((multiple_string != NULL) && (multiple_words > 0)) + { + if(multiple_words > 10) + { + fprintf(f_log,"%5d: Two many parts in a multi-word entry: %d\n",linenum,multiple_words); + } + else + { + dict_line[length++] = 80 + multiple_words; + ix = multiple_string_end - multiple_string; + memcpy(&dict_line[length],multiple_string,ix); + length += ix; + } + } + dict_line[0] = length; + +#ifdef OPT_FORMAT + spaces = 16; + for(ix=0; ix<n_flag_codes; ix++) + { + if(flag_codes[ix] >= 100) + { + fprintf(f_log,"?%d ",flag_codes[ix]-100); + spaces -= 3; + } + } + + fprintf(f_log,"%s",word); + spaces -= strlen(word); + DecodePhonemes(encoded_ph,decoded_ph); + while(spaces-- > 0) fputc(' ',f_log); + spaces += (14 - strlen(decoded_ph)); + + fprintf(f_log," %s",decoded_ph); + while(spaces-- > 0) fputc(' ',f_log); + for(ix=0; ix<n_flag_codes; ix++) + { + if(flag_codes[ix] < 100) + fprintf(f_log," %s",lookup_mnem(mnem_flags,flag_codes[ix])); + } + if(comment != NULL) + fprintf(f_log," %s",comment); + else + fputc('\n',f_log); +#endif + + return(length); +} /* end of compile_line */ + + + +static void compile_dictlist_start(void) +{//===================================== +// initialise dictionary list + int ix; + char *p; + char *p2; + + for(ix=0; ix<N_HASH_DICT; ix++) + { + p = hash_chains[ix]; + while(p != NULL) + { + memcpy(&p2,p,sizeof(char *)); + free(p); + p = p2; + } + hash_chains[ix] = NULL; + hash_counts[ix]=0; + } +} + + +static void compile_dictlist_end(FILE *f_out) +{//========================================== +// Write out the compiled dictionary list + int hash; + int length; + char *p; + + if(f_log != NULL) + { +#ifdef OUTPUT_FORMAT + for(hash=0; hash<N_HASH_DICT; hash++) + { + fprintf(f_log,"%8d",hash_counts[hash]); + if((hash & 7) == 7) + fputc('\n',f_log); + } + fflush(f_log); +#endif + } + + for(hash=0; hash<N_HASH_DICT; hash++) + { + p = hash_chains[hash]; + hash_counts[hash] = (int)ftell(f_out); + + while(p != NULL) + { + length = *(p+sizeof(char *)); + fwrite(p+sizeof(char *),length,1,f_out); + memcpy(&p,p,sizeof(char *)); + } + fputc(0,f_out); + } +} + + + +static int compile_dictlist_file(const char *path, const char* filename) +{//===================================================================== + int length; + int hash; + char *p; + int count=0; + FILE *f_in; + char buf[200]; + char fname[sizeof(path_home)+45]; + char dict_line[128]; + + text_mode = 0; + + sprintf(fname,"%s%s",path,filename); + if((f_in = fopen(fname,"r")) == NULL) + return(-1); + + fprintf(f_log,"Compiling: '%s'\n",fname); + + linenum=0; + + while(fgets(buf,sizeof(buf),f_in) != NULL) + { + linenum++; + + length = compile_line(buf,dict_line,&hash); + if(length == 0) continue; /* blank line */ + + hash_counts[hash]++; + + p = (char *)malloc(length+sizeof(char *)); + if(p == NULL) + { + if(f_log != NULL) + { + fprintf(f_log,"Can't allocate memory\n"); + error_count++; + } + break; + } + + memcpy(p,&hash_chains[hash],sizeof(char *)); + hash_chains[hash] = p; + memcpy(p+sizeof(char *),dict_line,length); + count++; + } + + fprintf(f_log,"\t%d entries\n",count); + fclose(f_in); + return(0); +} /* end of compile_dictlist_file */ + + + +static char rule_cond[80]; +static char rule_pre[80]; +static char rule_post[80]; +static char rule_match[80]; +static char rule_phonemes[80]; +static char group_name[LEN_GROUP_NAME+1]; + +#define N_RULES 2000 // max rules for each group + + + +static void copy_rule_string(char *string, int &state) +{//=================================================== +// state 0: conditional, 1=pre, 2=match, 3=post, 4=phonemes + static char *outbuf[5] = {rule_cond, rule_pre, rule_match, rule_post, rule_phonemes}; + static int next_state[5] = {2,2,4,4,4}; + char *output; + char *p; + int ix; + int len; + char c; + int sxflags; + int value; + int literal; + + if(string[0] == 0) return; + + output = outbuf[state]; + if(state==4) + { + // append to any previous phoneme string, i.e. allow spaces in the phoneme string + len = strlen(rule_phonemes); + if(len > 0) + rule_phonemes[len++] = ' '; + output = &rule_phonemes[len]; + } + sxflags = 0x808000; // to ensure non-zero bytes + + for(p=string,ix=0;;) + { + literal = 0; + c = *p++; + if(c == '\\') + { + c = *p++; // treat next character literally + if((c >= '0') && (c <= '3') && (p[0] >= '0') && (p[0] <= '7') && (p[1] >= '0') && (p[1] <= '7')) + { + // character code given by 3 digit octal value; + c = (c-'0')*64 + (p[0]-'0')*8 + (p[1]-'0'); + p += 2; + } + literal = 1; + } + + if((state==1) || (state==3)) + { + // replace special characters (note: 'E' is reserved for a replaced silent 'e') + if(literal == 0) + { + static const char lettergp_letters[9] = {LETTERGP_A,LETTERGP_B,LETTERGP_C,0,0,LETTERGP_F,LETTERGP_G,LETTERGP_H,LETTERGP_Y}; + switch(c) + { + case '_': + c = RULE_SPACE; + break; + + case 'Y': + c = 'I'; // drop through to next case + case 'A': // vowel + case 'B': + case 'C': + case 'H': + case 'F': + case 'G': + if(state == 1) + { + // pre-rule, put the number before the RULE_LETTERGP; + output[ix++] = lettergp_letters[c-'A'] + 'A'; + c = RULE_LETTERGP; + } + else + { + output[ix++] = RULE_LETTERGP; + c = lettergp_letters[c-'A'] + 'A'; + } + break; + case 'D': + c = RULE_DIGIT; + break; + case 'K': + c = RULE_NOTVOWEL; + break; + case 'N': + c = RULE_NO_SUFFIX; + break; + case 'V': + c = RULE_IFVERB; + break; + case 'Z': + c = RULE_NONALPHA; + break; + case '+': + c = RULE_INC_SCORE; + break; + case '@': + c = RULE_SYLLABLE; + break; + case '&': + c = RULE_STRESSED; + break; + case '%': + c = RULE_DOUBLE; + break; + case '#': + c = RULE_DEL_FWD; + break; + case '!': + c = RULE_CAPITAL; + break; + case 'T': + c = RULE_ALT1; + break; + case 'W': + c = RULE_SPELLING; + break; + case 'X': + c = RULE_NOVOWELS; + break; + case 'L': + // expect two digits + c = *p++ - '0'; + value = *p++ - '0'; + c = c * 10 + value; + if((value < 0) || (value > 9)) + { + c = 0; + fprintf(f_log,"%5d: Expected 2 digits after 'L'\n",linenum); + error_count++; + } + else + if((c <= 0) || (c >= N_LETTER_GROUPS) || (letterGroupsDefined[(int)c] == 0)) + { + fprintf(f_log,"%5d: Letter group L%.2d not defined\n",linenum,c); + error_count++; + } + c += 'A'; + if(state == 1) + { + // pre-rule, put the group number before the RULE_LETTERGP command + output[ix++] = c; + c = RULE_LETTERGP2; + } + else + { + output[ix++] = RULE_LETTERGP2; + } + break; + + case '$': // obsolete, replaced by S + fprintf(f_log,"%5d: $ now not allowed, use S for suffix",linenum); + error_count++; + break; + case 'P': + sxflags |= SUFX_P; // Prefix, now drop through to Suffix + case 'S': + output[ix++] = RULE_ENDING; + value = 0; + while(!isspace2(c = *p++) && (c != 0)) + { + switch(c) + { + case 'e': + sxflags |= SUFX_E; + break; + case 'i': + sxflags |= SUFX_I; + break; + case 'p': // obsolete, replaced by 'P' above + sxflags |= SUFX_P; + break; + case 'v': + sxflags |= SUFX_V; + break; + case 'd': + sxflags |= SUFX_D; + break; + case 'f': + sxflags |= SUFX_F; + break; + case 'q': + sxflags |= SUFX_Q; + break; + case 't': + sxflags |= SUFX_T; + break; + case 'b': + sxflags |= SUFX_B; + break; + default: + if(isdigit(c)) + value = (value*10) + (c - '0'); + break; + } + } + p--; + output[ix++] = sxflags >> 16; + output[ix++] = sxflags >> 8; + c = value | 0x80; + break; + } + } + } + output[ix++] = c; + if(c == 0) break; + } + + state = next_state[state]; +} // end of copy_rule_string + + + +static char *compile_rule(char *input) +{//=================================== + int ix; + unsigned char c; + int wc; + char *p; + char *prule; + int len; + int len_name; + int state=2; + int finish=0; + int pre_bracket=0; + char buf[80]; + char output[150]; + unsigned char bad_phoneme[4]; + + buf[0]=0; + rule_cond[0]=0; + rule_pre[0]=0; + rule_post[0]=0; + rule_match[0]=0; + rule_phonemes[0]=0; + + p = buf; + + for(ix=0; finish==0; ix++) + { + c = input[ix]; + + switch(c = input[ix]) + { + case ')': // end of prefix section + *p = 0; + state = 1; + pre_bracket = 1; + copy_rule_string(buf,state); + p = buf; + break; + + case '(': // start of suffix section + *p = 0; + state = 2; + copy_rule_string(buf,state); + state = 3; + p = buf; + break; + + case '\n': // end of line + case '\r': + case 0: // end of line + *p = 0; + copy_rule_string(buf,state); + finish=1; + break; + + case '\t': // end of section section + case ' ': + *p = 0; + copy_rule_string(buf,state); + p = buf; + break; + + case '?': + if(state==2) + state=0; + else + *p++ = c; + break; + + default: + *p++ = c; + break; + } + } + + if(strcmp(rule_match,"$group")==0) + strcpy(rule_match,group_name); + + if(rule_match[0]==0) + return(NULL); + + EncodePhonemes(rule_phonemes,buf,bad_phoneme); + for(ix=0;; ix++) + { + if((c = buf[ix])==0) break; + if(c==255) + { + fprintf(f_log,"%5d: Bad phoneme [%c] in %s",linenum,bad_phoneme[0],input); + error_count++; + break; + } + } + strcpy(output,buf); + len = strlen(buf)+1; + + len_name = strlen(group_name); + if((len_name > 0) && (memcmp(rule_match,group_name,len_name) != 0)) + { + utf8_in(&wc,rule_match); + if((group_name[0] == '9') && IsDigit(wc)) + { + // numeric group, rule_match starts with a digit, so OK + } + else + { + fprintf(f_log,"%5d: Wrong initial letters '%s' for group '%s'\n",linenum,rule_match,group_name); + error_count++; + } + } + strcpy(&output[len],rule_match); + len += strlen(rule_match); + + if(debug_flag) + { + output[len] = RULE_LINENUM; + output[len+1] = (linenum % 255) + 1; + output[len+2] = (linenum / 255) + 1; + len+=3; + } + + if(rule_cond[0] != 0) + { + ix = -1; + if(rule_cond[0] == '!') + { + // allow the rule only if the condition number is NOT set for the voice + ix = atoi(&rule_cond[1]) + 32; + } + else + { + // allow the rule only if the condition number is set for the voice + ix = atoi(rule_cond); + } + + if((ix > 0) && (ix < 255)) + { + output[len++] = RULE_CONDITION; + output[len++] = ix; + } + else + { + fprintf(f_log,"%5d: bad condition number ?%d\n",linenum,ix); + error_count++; + } + } + if(rule_pre[0] != 0) + { + output[len++] = RULE_PRE; + // output PRE string in reverse order + for(ix = strlen(rule_pre)-1; ix>=0; ix--) + output[len++] = rule_pre[ix]; + } + + if(rule_post[0] != 0) + { + sprintf(&output[len],"%c%s",RULE_POST,rule_post); + len += (strlen(rule_post)+1); + } + output[len++]=0; + prule = (char *)malloc(len); + memcpy(prule,output,len); + return(prule); +} // end of compile_rule + + +static int __cdecl string_sorter(char **a, char **b) +{//================================================= + char *pa, *pb; + int ix; + + if((ix = strcmp(pa = *a,pb = *b)) != 0) + return(ix); + pa += (strlen(pa)+1); + pb += (strlen(pb)+1); + return(strcmp(pa,pb)); +} /* end of string_sorter */ + + +static int __cdecl rgroup_sorter(RGROUP *a, RGROUP *b) +{//=================================================== + int ix; + ix = strcmp(a->name,b->name); + if(ix != 0) return(ix); + return(a->start-b->start); +} + + +#ifdef OUTPUT_FORMAT +static void print_rule_group(FILE *f_out, int n_rules, char **rules, char *name) +{//============================================================================= + int rule; + int ix; + unsigned char c; + int len1; + int len2; + int spaces; + char *p; + char *pout; + int condition; + char buf[80]; + char suffix[12]; + + static unsigned char symbols[] = {'@','&','%','+','#','$','D','Z','A','B','C','F'}; + + fprintf(f_out,"\n$group %s\n",name); + + for(rule=0; rule<n_rules; rule++) + { + p = rules[rule]; + len1 = strlen(p) + 1; + p = &p[len1]; + len2 = strlen(p); + + rule_match[0]=0; + rule_pre[0]=0; + rule_post[0]=0; + condition = 0; + + pout = rule_match; + for(ix=0; ix<len2; ix++) + { + switch(c = p[ix]) + { + case RULE_PRE: + *pout = 0; + pout = rule_pre; + break; + case RULE_POST: + *pout = 0; + pout = rule_post; + break; + case RULE_CONDITION: + condition = p[++ix]; + break; + case RULE_ENDING: + sprintf(suffix,"$%d[%x]",(p[ix+2]),p[ix+1] & 0x7f); + ix += 2; + strcpy(pout,suffix); + pout += strlen(suffix); + break; + default: + if(c <= RULE_LETTER7) + c = symbols[c-RULE_SYLLABLE]; + if(c == ' ') + c = '_'; + *pout++ = c; + break; + } + } + *pout = 0; + + spaces = 12; + if(condition > 0) + { + sprintf(buf,"?%d ",condition); + spaces -= strlen(buf); + fprintf(f_out,"%s",buf); + } + + if(rule_pre[0] != 0) + { + p = buf; + for(ix=strlen(rule_pre)-1;ix>=0;ix--) + *p++ = rule_pre[ix]; + sprintf(p,") "); + spaces -= strlen(buf); + for(ix=0; ix<spaces; ix++) + fputc(' ',f_out); + fprintf(f_out,"%s",buf); + spaces = 0; + } + + for(ix=0; ix<spaces; ix++) + fputc(' ',f_out); + + spaces = 14; + sprintf(buf," %s ",rule_match); + if(rule_post[0] != 0) + { + p = &buf[strlen(buf)]; + sprintf(p,"(%s ",rule_post); + } + fprintf(f_out,"%s",buf); + spaces -= strlen(buf); + + for(ix=0; ix<spaces; ix++) + fputc(' ',f_out); + DecodePhonemes(rules[rule],buf); + fprintf(f_out,"%s\n",buf); // phonemes + } +} +#endif + + +//#define LIST_GROUP_INFO +static void output_rule_group(FILE *f_out, int n_rules, char **rules, char *name) +{//============================================================================== + int ix; + int len1; + int len2; + int len_name; + char *p; + char *p2, *p3; + const char *common; + + short nextchar_count[256]; + memset(nextchar_count,0,sizeof(nextchar_count)); + + len_name = strlen(name); + +#ifdef OUTPUT_FORMAT + print_rule_group(f_log,n_rules,rules,name); +#endif + + // sort the rules in this group by their phoneme string + common = ""; + qsort((void *)rules,n_rules,sizeof(char *),(int (__cdecl *)(const void *,const void *))string_sorter); + + if(strcmp(name,"9")==0) + len_name = 0; // don't remove characters from numeric match strings + + for(ix=0; ix<n_rules; ix++) + { + p = rules[ix]; + len1 = strlen(p) + 1; // phoneme string + p3 = &p[len1]; + p2 = p3 + len_name; // remove group name from start of match string + len2 = strlen(p2); + + nextchar_count[(unsigned char)(p2[0])]++; // the next byte after the group name + + if((common[0] != 0) && (strcmp(p,common)==0)) + { + fwrite(p2,len2,1,f_out); + fputc(0,f_out); // no phoneme string, it's the same as previous rule + } + else + { + if((ix < n_rules-1) && (strcmp(p,rules[ix+1])==0)) + { + common = rules[ix]; // phoneme string is same as next, set as common + fputc(RULE_PH_COMMON,f_out); + } + + fwrite(p2,len2,1,f_out); + fputc(RULE_PHONEMES,f_out); + fwrite(p,len1,1,f_out); + } + } + +#ifdef LIST_GROUP_INFO + for(ix=32; ix<256; ix++) + { + if(nextchar_count[ix] > 30) + printf("Group %s %c %d\n",name,ix,nextchar_count[ix]); + } +#endif +} // end of output_rule_group + + + +static int compile_lettergroup(char *input, FILE *f_out) +{//===================================================== + char *p; + char *p_start; + int group; + int ix; + int n_items; + int length; + int max_length = 0; + + #define N_LETTERGP_ITEMS 200 + char *items[N_LETTERGP_ITEMS]; + char item_length[N_LETTERGP_ITEMS]; + + p = input; + if(!isdigit(p[0]) || !isdigit(p[1])) + { + fprintf(f_log,"%5d: Expected 2 digits after '.L'\n",linenum); + error_count++; + return(1); + } + + group = atoi(&p[0]); + if(group >= N_LETTER_GROUPS) + { + fprintf(f_log,"%5d: lettergroup out of range (01-%.2d)\n",linenum,N_LETTER_GROUPS-1); + error_count++; + return(1); + } + + while(!isspace2(*p)) p++; + + fputc(RULE_GROUP_START,f_out); + fputc(RULE_LETTERGP2,f_out); + fputc(group + 'A', f_out); + letterGroupsDefined[group] = 1; + + n_items = 0; + while(n_items < N_LETTERGP_ITEMS) + { + while(isspace2(*p)) p++; + if(*p == 0) + break; + + items[n_items] = p_start = p; + while((*p & 0xff) > ' ') + { + p++; + } + *p++ = 0; + length = p - p_start; + if(length > max_length) + max_length = length; + item_length[n_items++] = length; + } + + // write out the items, longest first + while(max_length > 1) + { + for(ix=0; ix < n_items; ix++) + { + if(item_length[ix] == max_length) + { + fwrite(items[ix],1,max_length,f_out); + } + } + max_length--; + } + + fputc(RULE_GROUP_END,f_out); + + return(0); +} + + +static int compile_dictrules(FILE *f_in, FILE *f_out, char *fname_temp) +{//==================================================================== + char *prule; + unsigned char *p; + int ix; + int c; + int gp; + FILE *f_temp; + int n_rules=0; + int count=0; + int different; + const char *prev_rgroup_name; + unsigned int char_code; + int compile_mode=0; + char *buf; + char buf1[200]; + char *rules[N_RULES]; + + int n_rgroups = 0; + RGROUP rgroup[N_RULE_GROUP2]; + + linenum = 0; + group_name[0] = 0; + + if((f_temp = fopen_log(fname_temp,"wb")) == NULL) + return(1); + + for(;;) + { + linenum++; + buf = fgets(buf1,sizeof(buf1),f_in); + if(buf != NULL) + { + if((p = (unsigned char *)strstr(buf,"//")) != NULL) + *p = 0; + + if(buf[0] == '\r') buf++; // ignore extra \r in \r\n + } + + if((buf == NULL) || (buf[0] == '.')) + { + // next .group or end of file, write out the previous group + + if(n_rules > 0) + { + strcpy(rgroup[n_rgroups].name,group_name); + rgroup[n_rgroups].start = ftell(f_temp); + output_rule_group(f_temp,n_rules,rules,group_name); + rgroup[n_rgroups].length = ftell(f_temp) - rgroup[n_rgroups].start; + n_rgroups++; + + count += n_rules; + } + n_rules = 0; + + if(compile_mode == 2) + { + // end of the character replacements section + fwrite(&n_rules,1,4,f_out); // write a zero word to terminate the replacemenmt list + compile_mode = 0; + } + + if(buf == NULL) break; // end of file + + if(memcmp(buf,".L",2)==0) + { + compile_lettergroup(&buf[2], f_out); + continue; + } + + if(memcmp(buf,".replace",8)==0) + { + compile_mode = 2; + fputc(RULE_GROUP_START,f_out); + fputc(RULE_REPLACEMENTS,f_out); + + // advance to next word boundary + while((ftell(f_out) & 3) != 0) + fputc(0,f_out); + } + + if(memcmp(buf,".group",6)==0) + { + compile_mode = 1; + + p = (unsigned char *)&buf[6]; + while((p[0]==' ') || (p[0]=='\t')) p++; // Note: Windows isspace(0xe1) gives TRUE ! + ix = 0; + while((*p > ' ') && (ix < LEN_GROUP_NAME)) + group_name[ix++] = *p++; + group_name[ix]=0; + + if(sscanf(group_name,"0x%x",&char_code)==1) + { + // group character is given as a character code (max 16 bits) + p = (unsigned char *)group_name; + + if(char_code > 0x100) + { + *p++ = (char_code >> 8); + } + *p++ = char_code; + *p = 0; + } + + if(strlen(group_name) > 2) + { + if(utf8_in(&c,group_name) < 2) + { + fprintf(f_log,"%5d: Group name longer than 2 bytes (UTF8)",linenum); + error_count++; + } + + group_name[2] = 0; + } + } + + continue; + } + + switch(compile_mode) + { + case 1: // .group + prule = compile_rule(buf); + if((prule != NULL) && (n_rules < N_RULES)) + { + rules[n_rules++] = prule; + } + break; + + case 2: // .replace + { + int replace1; + int replace2; + char *p; + + p = buf; + replace1 = 0; + replace2 = 0; + while(isspace2(*p)) p++; + ix = 0; + while((unsigned char)(*p) > 0x20) // not space or zero-byte + { + p += utf8_in(&c,p); + replace1 += (c << ix); + ix += 16; + } + while(isspace2(*p)) p++; + ix = 0; + while((unsigned char)(*p) > 0x20) + { + p += utf8_in(&c,p); + replace2 += (c << ix); + ix += 16; + } + if(replace1 != 0) + { + Write4Bytes(f_out,replace1); // write as little-endian + Write4Bytes(f_out,replace2); // if big-endian, reverse the bytes in LoadDictionary() + } + } + break; + } + } + fclose(f_temp); + + qsort((void *)rgroup,n_rgroups,sizeof(rgroup[0]),(int (__cdecl *)(const void *,const void *))rgroup_sorter); + + if((f_temp = fopen(fname_temp,"rb"))==NULL) + return(2); + + prev_rgroup_name = "\n"; + + for(gp = 0; gp < n_rgroups; gp++) + { + fseek(f_temp,rgroup[gp].start,SEEK_SET); + + if((different = strcmp(rgroup[gp].name, prev_rgroup_name)) != 0) + { + // not the same as the previous group + if(gp > 0) + fputc(RULE_GROUP_END,f_out); + fputc(RULE_GROUP_START,f_out); + fprintf(f_out, prev_rgroup_name = rgroup[gp].name); + fputc(0,f_out); + } + + for(ix=rgroup[gp].length; ix>0; ix--) + { + c = fgetc(f_temp); + fputc(c,f_out); + } + + if(different) + { + } + } + fputc(RULE_GROUP_END,f_out); + fputc(0,f_out); + + fclose(f_temp); + remove(fname_temp); + + fprintf(f_log,"\t%d rules, %d groups\n\n",count,n_rgroups); + return(0); +} // end of compile_dictrules + + + + +int CompileDictionary(const char *dsource, const char *dict_name, FILE *log, char *fname_err, int flags) +{//===================================================================================================== +// fname: space to write the filename in case of error +// flags: bit 0: include source line number information, for debug purposes. + + FILE *f_in; + FILE *f_out; + int offset_rules=0; + int value; + char fname_in[sizeof(path_home)+45]; + char fname_out[sizeof(path_home)+15]; + char fname_temp[sizeof(path_home)+15]; + char path[sizeof(path_home)+40]; // path_dsource+20 + + error_count = 0; + memset(letterGroupsDefined,0,sizeof(letterGroupsDefined)); + + debug_flag = flags & 1; + + if(dsource == NULL) + dsource = ""; + + f_log = log; +//f_log = fopen("log2.txt","w"); + if(f_log == NULL) + f_log = stderr; + + sprintf(path,"%s%s_",dsource,dict_name); + sprintf(fname_in,"%srules",path); + f_in = fopen_log(fname_in,"r"); + if(f_in == NULL) + { + if(fname_err) + strcpy(fname_err,fname_in); + return(-1); + } + + sprintf(fname_out,"%s%c%s_dict",path_home,PATHSEP,dict_name); + if((f_out = fopen_log(fname_out,"wb+")) == NULL) + { + if(fname_err) + strcpy(fname_err,fname_in); + return(-1); + } + sprintf(fname_temp,"%s%ctemp",path_home,PATHSEP); + + transpose_offset = 0; + + if(strcmp(dict_name,"ru") == 0) + { + // transpose cyrillic alphabet from unicode to iso8859-5 +// transpose_offset = 0x430-0xd0; + transpose_offset = 0x42f; // range 0x01 to 0x22 + transpose_min = 0x430; + transpose_max = 0x451; + } + + value = N_HASH_DICT; + Write4Bytes(f_out,value); + Write4Bytes(f_out,offset_rules); + + compile_dictlist_start(); + + fprintf(f_log,"Using phonemetable: '%s'\n",phoneme_tab_list[phoneme_tab_number].name); + compile_dictlist_file(path,"roots"); + if(translator->langopts.listx) + { + compile_dictlist_file(path,"list"); + compile_dictlist_file(path,"listx"); + } + else + { + compile_dictlist_file(path,"listx"); + compile_dictlist_file(path,"list"); + } + compile_dictlist_file(path,"extra"); + + compile_dictlist_end(f_out); + offset_rules = ftell(f_out); + + fprintf(f_log,"Compiling: '%s'\n",fname_in); + + compile_dictrules(f_in,f_out,fname_temp); + fclose(f_in); + + fseek(f_out,4,SEEK_SET); + Write4Bytes(f_out,offset_rules); + fclose(f_out); + + LoadDictionary(translator, dict_name, 0); + + return(error_count); +} // end of compile_dictionary + diff --git a/Plugins/eSpeak/eSpeak/debug.cpp b/Plugins/eSpeak/eSpeak/debug.cpp new file mode 100644 index 0000000..38ea57c --- /dev/null +++ b/Plugins/eSpeak/eSpeak/debug.cpp @@ -0,0 +1,74 @@ +#include <stdio.h> +#include <stdarg.h> +#include "speech.h" +#include "debug.h" + +#ifdef DEBUG_ENABLED +#include <sys/time.h> +#include <unistd.h> + +static FILE* fd_log=NULL; +static const char* FILENAME="/tmp/espeak.log"; + +void debug_init() +{ + if((fd_log = fopen(FILENAME,"a")) != NULL) + setvbuf(fd_log, NULL, _IONBF, 0); +} + +void debug_enter(const char* text) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + // fd_log = fopen(FILENAME,"a"); + if (!fd_log) + { + debug_init(); + } + + if (fd_log) + { + fprintf(fd_log, "%03d.%03dms > ENTER %s\n",(int)(tv.tv_sec%1000), (int)(tv.tv_usec/1000), text); + // fclose(fd_log); + } +} + + +void debug_show(const char *format, ...) +{ + va_list args; + va_start(args, format); + // fd_log = fopen(FILENAME,"a"); + if (!fd_log) + { + debug_init(); + } + if (fd_log) + { + vfprintf(fd_log, format, args); + // fclose(fd_log); + } + va_end(args); +} + +void debug_time(const char* text) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + // fd_log = fopen(FILENAME,"a"); + if (!fd_log) + { + debug_init(); + } + if (fd_log) + { + fprintf(fd_log, "%03d.%03dms > %s\n",(int)(tv.tv_sec%1000), (int)(tv.tv_usec/1000), text); + // fclose(fd_log); + } +} + +#endif diff --git a/Plugins/eSpeak/eSpeak/debug.h b/Plugins/eSpeak/eSpeak/debug.h new file mode 100644 index 0000000..c3fb9c9 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/debug.h @@ -0,0 +1,26 @@ +#ifndef DEBUG_H +#define DEBUG_H + +//#define DEBUG_ENABLED + +#ifdef DEBUG_ENABLED +#define ENTER(text) debug_enter(text) +#define SHOW(format,...) debug_show(format,__VA_ARGS__); +#define SHOW_TIME(text) debug_time(text); +extern void debug_enter(const char* text); +extern void debug_show(const char* format,...); +extern void debug_time(const char* text); + +#else + +#ifdef PLATFORM_WINDOWS +#define SHOW(format) // VC6 doesn't allow "..." +#else +#define SHOW(format,...) +#endif +#define SHOW_TIME(text) +#define ENTER(text) +#endif + + +#endif diff --git a/Plugins/eSpeak/eSpeak/dictionary.cpp b/Plugins/eSpeak/eSpeak/dictionary.cpp new file mode 100644 index 0000000..f19fd85 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/dictionary.cpp @@ -0,0 +1,3433 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "StdAfx.h" + +#define LOG_TRANSLATE + +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include <wctype.h> +#include <wchar.h> + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "translate.h" + + +int dictionary_skipwords; +char dictionary_name[40]; + +extern char *print_dictionary_flags(unsigned int *flags); + +// accented characters which indicate (in some languages) the start of a separate syllable +//static const unsigned short diereses_list[7] = {L'ä',L'ë',L'ï',L'ö',L'ü',L'ÿ',0}; +static const unsigned short diereses_list[7] = {0xe4,0xeb,0xef,0xf6,0xfc,0xff,0}; + +// convert characters to an approximate 7 bit ascii equivalent +// used for checking for vowels +static unsigned char remove_accent[] = { +'a','a','a','a','a','a','a','c','e','e','e','e','i','i','i','i', // 0c0 +'d','n','o','o','o','o','o', 0, 'o','u','u','u','u','y','t','s', // 0d0 +'a','a','a','a','a','a','a','c','e','e','e','e','i','i','i','i', // 0e0 +'d','n','o','o','o','o','o', 0 ,'o','u','u','u','u','y','t','y', // 0f0 + +'a','a','a','a','a','a','c','c','c','c','c','c','c','c','d','d', // 100 +'d','d','e','e','e','e','e','e','e','e','e','e','g','g','g','g', // 110 +'g','g','g','g','h','h','h','h','i','i','i','i','i','i','i','i', // 120 +'i','i','i','i','j','j','k','k','k','l','l','l','l','l','l','l', // 130 +'l','l','l','n','n','n','n','n','n','n','n','n','o','o','o','o', // 140 +'o','o','o','o','r','r','r','r','r','r','s','s','s','s','s','s', // 150 +'s','s','t','t','t','t','t','t','u','u','u','u','u','u','u','u', // 160 +'u','u','u','u','w','w','y','y','y','z','z','z','z','z','z','s', // 170 +'b','b','b','b', 0, 0, 'o','c','c','d','d','d','d','d','e','e', // 180 +'e','f','f','g','g','h','i','i','k','k','l','l','m','n','n','o', // 190 +'o','o','o','o','p','p','y', 0, 0, 's','s','t','t','t','t','u', // 1a0 +'u','u','v','y','y','z','z','z','z','z','z','z', 0, 0, 0, 'w', // 1b0 +'t','t','t','k','d','d','d','l','l','l','n','n','n','a','a','i', // 1c0 +'i','o','o','u','u','u','u','u','u','u','u','u','u','e','a','a', // 1d0 +'a','a','a','a','g','g','g','g','k','k','o','o','o','o','z','z', // 1e0 +'j','d','d','d','g','g','w','w','n','n','a','a','a','a','o','o', // 1f0 + +'a','a','a','a','e','e','e','e','i','i','i','i','o','o','o','o', // 200 +'r','r','r','r','u','u','u','u','s','s','t','t','y','y','h','h', // 210 +'n','d','o','o','z','z','a','a','e','e','o','o','o','o','o','o', // 220 +'o','o','y','y','l','n','t','j','d','q','a','c','c','l','t','s', // 230 +'z', 0 }; + + + + +void strncpy0(char *to,const char *from, int size) +{//=============================================== + // strcpy with limit, ensures a zero terminator + strncpy(to,from,size); + to[size-1] = 0; +} + + +static int reverse_word_bytes(int word) +{//============================= + // reverse the order of bytes from little-endian to big-endian +#ifdef ARCH_BIG + int ix; + int word2 = 0; + + for(ix=0; ix<=24; ix+=8) + { + word2 = word2 << 8; + word2 |= (word >> ix) & 0xff; + } + return(word2); +#else + return(word); +#endif +} + + +int LookupMnem(MNEM_TAB *table, char *string) +{//========================================== + while(table->mnem != NULL) + { + if(strcmp(string,table->mnem)==0) + return(table->value); + table++; + } + return(table->value); +} + + + +//============================================================================================= +// Read pronunciation rules and pronunciation lookup dictionary +// +//============================================================================================= + + +static void InitGroups(Translator *tr) +{//=================================== +/* Called after dictionary 1 is loaded, to set up table of entry points for translation rule chains + for single-letters and two-letter combinations +*/ + + int ix; + char *p; + char *p_name; + unsigned int *pw; + unsigned char c, c2; + int len; + + tr->n_groups2 = 0; + for(ix=0; ix<256; ix++) + { + tr->groups1[ix]=NULL; + tr->groups2_count[ix]=0; + tr->groups2_start[ix]=255; // indicates "not set" + } + memset(tr->letterGroups,0,sizeof(tr->letterGroups)); + + p = tr->data_dictrules; + while(*p != 0) + { + if(*p != RULE_GROUP_START) + { + fprintf(stderr,"Bad rules data in '%s_dict' at 0x%x\n",dictionary_name,(unsigned int)(p - tr->data_dictrules)); + break; + } + p++; + + if(p[0] == RULE_REPLACEMENTS) + { + pw = (unsigned int *)(((long)p+4) & ~3); // advance to next word boundary + tr->langopts.replace_chars = pw; + while(pw[0] != 0) + { + pw += 2; // find the end of the replacement list, each entry is 2 words. + } + p = (char *)(pw+1); + +#ifdef ARCH_BIG + pw = (unsigned int *)(tr->langopts.replace_chars); + while(*pw != 0) + { + *pw = reverse_word_bytes(*pw); + pw++; + *pw = reverse_word_bytes(*pw); + pw++; + } +#endif + continue; + } + + if(p[0] == RULE_LETTERGP2) + { + ix = p[1] - 'A'; + p += 2; + if((ix >= 0) && (ix < N_LETTER_GROUPS)) + { + tr->letterGroups[ix] = p; + } + } + else + { + len = strlen(p); + p_name = p; + c = p_name[0]; + + p += (len+1); + if(len == 1) + { + tr->groups1[c] = p; + } + else + if(len == 0) + { + tr->groups1[0] = p; + } + else + { + if(tr->groups2_start[c] == 255) + tr->groups2_start[c] = tr->n_groups2; + + tr->groups2_count[c]++; + tr->groups2[tr->n_groups2] = p; + c2 = p_name[1]; + tr->groups2_name[tr->n_groups2++] = (c + (c2 << 8)); + } + } + + // skip over all the rules in this group + while(*p != RULE_GROUP_END) + { + p += (strlen(p) + 1); + } + p++; + } + +} // end of InitGroups + + + +int LoadDictionary(Translator *tr, const char *name, int no_error) +{//=============================================================== + int hash; + char *p; + int *pw; + int length; + FILE *f; + unsigned int size; + char fname[sizeof(path_home)+20]; + + strcpy(dictionary_name,name); // currently loaded dictionary name + + if(no_error) // don't load dictionary, just set the dictionary_name + return(1); + + // Load a pronunciation data file into memory + // bytes 0-3: offset to rules data + // bytes 4-7: number of hash table entries + sprintf(fname,"%s%c%s_dict",path_home,PATHSEP,name); + size = GetFileLength(fname); + + f = fopen(fname,"rb"); + if((f == NULL) || (size <= 0)) + { + if(no_error == 0) + { + fprintf(stderr,"Can't read dictionary file: '%s'\n",fname); + } + return(1); + } + + if(tr->data_dictlist != NULL) + Free(tr->data_dictlist); + + tr->data_dictlist = Alloc(size); + fread(tr->data_dictlist,size,1,f); + fclose(f); + + + pw = (int *)(tr->data_dictlist); + length = reverse_word_bytes(pw[1]); + + if(size <= (N_HASH_DICT + sizeof(int)*2)) + { + fprintf(stderr,"Empty _dict file: '%s\n",fname); + return(2); + } + + if((reverse_word_bytes(pw[0]) != N_HASH_DICT) || + (length <= 0) || (length > 0x8000000)) + { + fprintf(stderr,"Bad data: '%s' (%x length=%x)\n",fname,reverse_word_bytes(pw[0]),length); + return(2); + } + tr->data_dictrules = &(tr->data_dictlist[length]); + + // set up indices into data_dictrules + InitGroups(tr); + if(tr->groups1[0] == NULL) + { + fprintf(stderr,"Error in %s_rules, no default rule group\n",name); + } + + // set up hash table for data_dictlist + p = &(tr->data_dictlist[8]); + + for(hash=0; hash<N_HASH_DICT; hash++) + { + tr->dict_hashtab[hash] = p; + while((length = *p) != 0) + { + p += length; + } + p++; // skip over the zero which terminates the list for this hash value + } + + return(0); +} // end of LoadDictionary + + +int HashDictionary(const char *string) +//==================================== +/* Generate a hash code from the specified string + This is used to access the dictionary_2 word-lookup dictionary +*/ +{ + int c; + int chars=0; + int hash=0; + + while((c = (*string++ & 0xff)) != 0) + { + hash = hash * 8 + c; + hash = (hash & 0x3ff) ^ (hash >> 8); /* exclusive or */ + chars++; + } + + return((hash+chars) & 0x3ff); // a 10 bit hash code +} // end of HashDictionary + + + +//============================================================================================= +// Translate between internal representation of phonemes and a mnemonic form for display +// +//============================================================================================= + + + +char *EncodePhonemes(char *p, char *outptr, unsigned char *bad_phoneme) +/*********************************************************************/ +/* Translate a phoneme string from ascii mnemonics to internal phoneme numbers, + from 'p' up to next blank . + Returns advanced 'p' + outptr contains encoded phonemes, unrecognised phonemes are encoded as 255 + bad_phoneme must point to char array of length 2 of more +*/ +{ + int ix; + unsigned char c; + int count; /* num. of matching characters */ + int max; /* highest num. of matching found so far */ + int max_ph; /* corresponding phoneme with highest matching */ + int consumed; + unsigned int mnemonic_word; + + bad_phoneme[0] = 0; + + // skip initial blanks + while(isspace(*p)) + { + p++; + } + + while(((c = *p) != 0) && !isspace(c)) + { + consumed = 0; + + switch(c) + { + case '|': + // used to separate phoneme mnemonics if needed, to prevent characters being treated + // as a multi-letter mnemonic + + if((c = p[1]) == '|') + { + // treat double || as a word-break symbol, drop through + // to the default case with c = '|' + } + else + { + p++; + break; + } + + default: + // lookup the phoneme mnemonic, find the phoneme with the highest number of + // matching characters + max= -1; + max_ph= 0; + + for(ix=1; ix<n_phoneme_tab; ix++) + { + if(phoneme_tab[ix] == NULL) + continue; + if(phoneme_tab[ix]->type == phINVALID) + continue; // this phoneme is not defined for this language + + count = 0; + mnemonic_word = phoneme_tab[ix]->mnemonic; + + while(((c = p[count]) > ' ') && (count < 4) && + (c == ((mnemonic_word >> (count*8)) & 0xff))) + count++; + + if((count > max) && + ((count == 4) || (((mnemonic_word >> (count*8)) & 0xff)==0))) + { + max = count; + max_ph = phoneme_tab[ix]->code; + } + } + + if(max_ph == 0) + { + max_ph = 255; /* not recognised */ + bad_phoneme[0] = *p; + bad_phoneme[1] = 0; + } + + if(max <= 0) + max = 1; + p += (consumed + max); + *outptr++ = (char)(max_ph); + + if(max_ph == phonSWITCH) + { + // Switch Language: this phoneme is followed by a text string + char *p_lang = outptr; + while(!isspace(c = *p) && (c != 0)) + { + p++; + *outptr++ = tolower(c); + } + *outptr = 0; + if(c == 0) + { + if(strcmp(p_lang,"en")==0) + { + *p_lang = 0; // don't need "en", it's assumed by default + return(p); + } + } + else + { + *outptr++ = '|'; // more phonemes follow, terminate language string with separator + } + } + break; + } + } + /* terminate the encoded string */ + *outptr = 0; + return(p); +} // end of EncodePhonemes + + + +void DecodePhonemes(const char *inptr, char *outptr) +//================================================== +// Translate from internal phoneme codes into phoneme mnemonics +{ + unsigned char phcode; + unsigned char c; + unsigned int mnem; + PHONEME_TAB *ph; + static const char *stress_chars = "==,,'* "; + + while((phcode = *inptr++) > 0) + { + if(phcode == 255) + continue; /* indicates unrecognised phoneme */ + if((ph = phoneme_tab[phcode]) == NULL) + continue; + + if((ph->type == phSTRESS) && (ph->std_length <= 4) && (ph->spect == 0)) + { + if(ph->std_length > 1) + *outptr++ = stress_chars[ph->std_length]; + } + else + { + mnem = ph->mnemonic; + + while((c = (mnem & 0xff)) != 0) + { + *outptr++ = c; + mnem = mnem >> 8; + } + if(phcode == phonSWITCH) + { + while(isalpha(*inptr)) + { + *outptr++ = *inptr++; + } + } + } + } + *outptr = 0; /* string terminator */ +} // end of DecodePhonemes + + + +static void WriteMnemonic(char *phon_out, int *ix, int mnem) +{//========================================================= + unsigned char c; + + while((c = mnem & 0xff) != 0) + { + if((c == '/') && (option_phoneme_variants==0)) + break; // discard phoneme variant indicator + phon_out[(*ix)++]= c; + // phon_out[phon_out_ix++]= ipa1[c]; + mnem = mnem >> 8; + } +} + + + +void GetTranslatedPhonemeString(char *phon_out, int n_phon_out) +{//============================================================ +/* Can be called after a clause has been translated into phonemes, in order + to display the clause in phoneme mnemonic form. +*/ + + int ix; + int phon_out_ix=0; + int stress; + char *p; + PHONEME_LIST *plist; + + static const char *stress_chars = "==,,''"; + + if(phon_out != NULL) + { + for(ix=1; ix<(n_phoneme_list-2) && (phon_out_ix < (n_phon_out - 6)); ix++) + { + plist = &phoneme_list[ix]; + if(plist->newword) + phon_out[phon_out_ix++] = ' '; + + if(plist->synthflags & SFLAG_SYLLABLE) + { + if((stress = plist->tone) > 1) + { + if(stress > 5) stress = 5; + phon_out[phon_out_ix++] = stress_chars[stress]; + } + } + WriteMnemonic(phon_out, &phon_out_ix, plist->ph->mnemonic); + + if(plist->synthflags & SFLAG_LENGTHEN) + { + WriteMnemonic(phon_out, &phon_out_ix, phoneme_tab[phonLENGTHEN]->mnemonic); + } + if((plist->synthflags & SFLAG_SYLLABLE) && (plist->type != phVOWEL)) + { + // syllablic consonant + WriteMnemonic(phon_out, &phon_out_ix, phoneme_tab[phonSYLLABIC]->mnemonic); + } + if(plist->ph->code == phonSWITCH) + { + // the tone_ph field contains a phoneme table number + p = phoneme_tab_list[plist->tone_ph].name; + while(*p != 0) + { + phon_out[phon_out_ix++] = *p++; + } + phon_out[phon_out_ix++] = ' '; + } + else + if(plist->tone_ph > 0) + { + WriteMnemonic(phon_out, &phon_out_ix, phoneme_tab[plist->tone_ph]->mnemonic); + } + } + + if(phon_out_ix >= n_phon_out) + phon_out_ix = n_phon_out - 1; + phon_out[phon_out_ix] = 0; + } +} // end of GetTranslatedPhonemeString + + + +//============================================================================================= +// Is a word Unpronouncable - and so should be spoken as individual letters +// +//============================================================================================= + + + +static int IsLetterGroup(Translator *tr, char *word, int group, int pre) +{//===================================================================== + // match the word against a list of utf-8 strings + char *p; + char *w; + int len=0; + + p = tr->letterGroups[group]; + if(p == NULL) + return(0); + + while(*p != RULE_GROUP_END) + { + if(pre) + { + len = strlen(p); + w = word - len + 1; + } + else + { + w = word; + } + while(*p == *w) + { + w++; + p++; + } + if(*p == 0) + { + if(pre) + return(len); + return(w-word); // matched a complete string + } + + while(*p++ != 0); // skip to end of string + } + return(0); +} + + +static int IsLetter(Translator *tr, int letter, int group) +{//======================================================= + int letter2; + + if(tr->letter_groups[group] != NULL) + { + if(wcschr(tr->letter_groups[group],letter)) + return(1); + return(0); + } + + if(group > 7) + return(0); + + if(tr->letter_bits_offset > 0) + { + if(((letter2 = (letter - tr->letter_bits_offset)) > 0) && (letter2 < 0x80)) + letter = letter2; + else + return(0); + } + else + { + if((letter >= 0xc0) && (letter <= 0x241)) + return(tr->letter_bits[remove_accent[letter-0xc0]] & (1L << group)); + } + + if((letter >= 0) && (letter < 0x80)) + return(tr->letter_bits[letter] & (1L << group)); + + return(0); +} + + +static int IsVowel(Translator *tr, int letter) +{//=========================================== + return(IsLetter(tr, letter, 0)); +} + + + + +static int Unpronouncable_en(Translator *tr, char *word) +{//===================================================== +/* Determines whether a word in 'unpronouncable', i.e. whether it should + be spoken as individual letters. + + This function is language specific. +*/ + + int c; + int vowel_posn=9; + int index; + int count; + int ix; + int apostrophe=0; + + static unsigned char initials_bitmap[86] = { + 0x00, 0x00, 0x00, 0x00, 0x22, 0x08, 0x00, 0x88, // 0 + 0x20, 0x24, 0x20, 0x80, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x28, 0x08, 0x00, 0x88, 0x22, 0x04, 0x00, // 16 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x88, 0x22, 0x04, 0x00, 0x02, 0x00, 0x04, // 32 + 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x28, 0x8a, 0x03, 0x00, 0x00, 0x40, 0x00, // 48 + 0x02, 0x00, 0x41, 0xca, 0x9b, 0x06, 0x20, 0x80, + 0x91, 0x00, 0x00, 0x00, 0x00, 0x20, 0x08, 0x00, // 64 + 0x08, 0x20, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x22, 0x00, 0x01, 0x00, }; + + + // words which we pass through to the dictionary, even though they look unpronouncable + static const char *exceptions[] = { + "'s ", "st ","nd ","rd ","th ",NULL }; + + if((*word == ' ') || (*word == 0)) + return(0); + + for(ix=0; exceptions[ix] != NULL; ix++) + { + // Seemingly uncpronouncable words, but to be looked in the dictionary rules instead + if(memcmp(word,exceptions[ix],3)==0) + return(0); + } + + index=0; + count=0; + for(;;) + { + index += utf8_in(&c,&word[index]); + count++; + + if((c==0) || (c==' ')) + break; + + if(IsVowel(tr, c) || (c == 'y')) + { + vowel_posn = count; + break; + } + + if(c == '\'') + apostrophe = 1; + else + if(!IsAlpha(c)) + return(0); // letter (not vowel) outside Latin character range or apostrophe, abort test + } + if((vowel_posn > 5) || ((word[0]!='s') && (vowel_posn > 4))) + return(1); // no vowel, or no vowel in first four letters + + /* there is at least one vowel, is the initial letter combination valid ? */ + + if(vowel_posn < 3) + return(0); /* vowel in first two letters, OK */ + + if(apostrophe) + return(0); // first two letters not a-z, abort test + + index = (word[0]-'a') * 26 + (word[1]-'a'); + if(initials_bitmap[index >> 3] & (1L << (index & 7))) + return(0); + else + return(1); /****/ +} /* end of Unpronounceable */ + + + + +int Unpronouncable(Translator *tr, char *word) +{//=========================================== +/* Determines whether a word in 'unpronouncable', i.e. whether it should + be spoken as individual letters. + + This function may be language specific. This is a generic version. +*/ + + int c; + int c1=0; + int vowel_posn=9; + int index; + int count; + int apostrophe=0; + + if(tr->translator_name == L('e','n')) + { + return(Unpronouncable_en(tr,word)); + } + + utf8_in(&c,word); + if((tr->letter_bits_offset > 0) && (c < 0x241)) + { + // Latin characters for a language with a non-latin alphabet + return(0); // so we can re-translate the word as English + } + + if(tr->langopts.param[LOPT_UNPRONOUNCABLE] == 1) + return(0); + + if((*word == ' ') || (*word == 0)) + return(0); + + index = 0; + count = 0; + for(;;) + { + index += utf8_in(&c,&word[index]); + if((c==0) || (c==' ')) + break; + + if(count==0) + c1 = c; + count++; + + if(IsVowel(tr, c)) + { + vowel_posn = count; // position of the first vowel + break; + } + + if(c == '\'') + apostrophe = 1; + else + if(!iswalpha(c)) + return(0); // letter (not vowel) outside a-z range or apostrophe, abort test + } + + if((vowel_posn < 9) && (tr->langopts.param[LOPT_UNPRONOUNCABLE] == 2)) + return(0); // option means allow any word with a vowel + + if(c1 == tr->langopts.param[LOPT_UNPRONOUNCABLE]) + vowel_posn--; // disregard this as the initial letter when counting + + if(vowel_posn > (tr->langopts.max_initial_consonants+1)) + return(1); // no vowel, or no vowel in first four letters + +return(0); + +} /* end of Unpronounceable */ + + + +//============================================================================================= +// Determine the stress pattern of a word +// +//============================================================================================= + + + +static int GetVowelStress(Translator *tr, unsigned char *phonemes, unsigned char *vowel_stress, int &vowel_count, int &stressed_syllable, int control) +{//==================================================================================================================================================== +// control = 1, set stress to 1 for forced unstressed vowels + unsigned char phcode; + PHONEME_TAB *ph; + unsigned char *ph_out = phonemes; + int count = 1; + int max_stress = 0; + int ix; + int j; + int stress = 0; + int primary_posn = 0; + + vowel_stress[0] = 0; + while(((phcode = *phonemes++) != 0) && (count < (N_WORD_PHONEMES/2)-1)) + { + if((ph = phoneme_tab[phcode]) == NULL) + continue; + + if((ph->type == phSTRESS) && (ph->spect == 0)) + { + /* stress marker, use this for the following vowel */ + + if(phcode == phonSTRESS_PREV) + { + /* primary stress on preceeding vowel */ + j = count - 1; + while((j > 0) && (stressed_syllable == 0) && (vowel_stress[j] < 4)) + { + if(vowel_stress[j] != 1) + { + // don't promote a phoneme which must be unstressed + vowel_stress[j] = 4; + + if(max_stress < 4) + { + max_stress = 4; + primary_posn = j; + } + + /* reduce any preceding primary stress markers */ + for(ix=1; ix<j; ix++) + { + if(vowel_stress[ix] == 4) + vowel_stress[ix] = 3; + } + break; + } + j--; + } + } + else + { + if((ph->std_length < 4) || (stressed_syllable == 0)) + { + stress = ph->std_length; + + if(stress > max_stress) + max_stress = stress; + } + } + continue; + } + + if((ph->type == phVOWEL) && !(ph->phflags & phNONSYLLABIC)) + { + vowel_stress[count] = (char)stress; + if((stress >= 4) && (stress >= max_stress)) + { + primary_posn = count; + max_stress = stress; + } + + if((stress == 0) && (control & 1) && (ph->phflags & phUNSTRESSED)) + vowel_stress[count] = 1; /* weak vowel, must be unstressed */ + + count++; + stress = 0; + } + else + if(phcode == phonSYLLABIC) + { + // previous consonant phoneme is syllablic + vowel_stress[count] = (char)stress; + if((stress == 0) && (control & 1)) + vowel_stress[count++] = 1; // syllabic consonant, usually unstressed + } + + *ph_out++ = phcode; + } + vowel_stress[count] = 0; + *ph_out = 0; + + /* has the position of the primary stress been specified by $1, $2, etc? */ + if(stressed_syllable > 0) + { + if(stressed_syllable >= count) + stressed_syllable = count-1; // the final syllable + + vowel_stress[stressed_syllable] = 4; + max_stress = 4; + primary_posn = stressed_syllable; + } + + if(max_stress == 5) + { + // priority stress, replaces any other primary stress marker + for(ix=1; ix<count; ix++) + { + if(vowel_stress[ix] == 4) + { + if(tr->langopts.stress_flags & 0x20000) + vowel_stress[ix] = 0; + else + vowel_stress[ix] = 3; + } + + if(vowel_stress[ix] == 5) + { + vowel_stress[ix] = 4; + primary_posn = ix; + } + } + max_stress = 4; + } + + stressed_syllable = primary_posn; + vowel_count = count; + return(max_stress); +} // end of GetVowelStress + + + +static char stress_phonemes[] = {phonSTRESS_U, phonSTRESS_D, phonSTRESS_2, phonSTRESS_3, + phonSTRESS_P, phonSTRESS_P2, phonSTRESS_TONIC}; + + +void ChangeWordStress(Translator *tr, char *word, int new_stress) +{//============================================================== + int ix; + unsigned char *p; + int max_stress; + int vowel_count; // num of vowels + 1 + int stressed_syllable=0; // position of stressed syllable + unsigned char phonetic[N_WORD_PHONEMES]; + unsigned char vowel_stress[N_WORD_PHONEMES/2]; + + strcpy((char *)phonetic,word); + max_stress = GetVowelStress(tr, phonetic, vowel_stress, vowel_count, stressed_syllable, 0); + + if(new_stress >= 4) + { + // promote to primary stress + for(ix=1; ix<vowel_count; ix++) + { + if(vowel_stress[ix] >= max_stress) + { + vowel_stress[ix] = new_stress; + break; + } + } + } + else + { + // remove primary stress + for(ix=1; ix<vowel_count; ix++) + { + if(vowel_stress[ix] > new_stress) // >= allows for diminished stress (=1) + vowel_stress[ix] = new_stress; + } + } + + // write out phonemes + ix = 1; + p = phonetic; + while(*p != 0) + { + if((phoneme_tab[*p]->type == phVOWEL) && !(phoneme_tab[*p]->phflags & phNONSYLLABIC)) + { + if(vowel_stress[ix] != 0) + *word++ = stress_phonemes[vowel_stress[ix]]; + + ix++; + } + *word++ = *p++; + } + *word = 0; +} // end of ChangeWordStress + + + +void SetWordStress(Translator *tr, char *output, unsigned int dictionary_flags, int tonic, int prev_stress) +{//======================================================================================================== +/* Guess stress pattern of word. This is language specific + + 'dictionary_flags' has bits 0-3 position of stressed vowel (if > 0) + or unstressed (if == 7) or syllables 1 and 2 (if == 6) + bits 8... dictionary flags + + If 'tonic' is set (>= 0), replace highest stress by this value. + + Parameter used for input and output +*/ + + unsigned char phcode; + unsigned char *p; + PHONEME_TAB *ph; + int stress; + int max_stress; + int vowel_count; // num of vowels + 1 + int ix; + int v; + int v_stress; + int stressed_syllable; // position of stressed syllable + int max_stress_posn; + int unstressed_word = 0; + char *max_output; + int final_ph; + int final_ph2; + int mnem; + int mnem2; + int post_tonic; + int opt_length; + int done; + int stressflags; + + unsigned char vowel_stress[N_WORD_PHONEMES/2]; + char syllable_weight[N_WORD_PHONEMES/2]; + char vowel_length[N_WORD_PHONEMES/2]; + unsigned char phonetic[N_WORD_PHONEMES]; + + static char consonant_types[16] = {0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0}; + + + /* stress numbers STRESS_BASE + + 0 diminished, unstressed within a word + 1 unstressed, weak + 2 + 3 secondary stress + 4 main stress */ + + stressflags = tr->langopts.stress_flags; + + /* copy input string into internal buffer */ + for(ix=0; ix<N_WORD_PHONEMES; ix++) + { + phonetic[ix] = output[ix]; + // check for unknown phoneme codes + if(phonetic[ix] >= n_phoneme_tab) + phonetic[ix] = phonSCHWA; + if(phonetic[ix] == 0) + break; + } + if(ix == 0) return; + final_ph = phonetic[ix-1]; + final_ph2 = phonetic[ix-2]; + + max_output = output + (N_WORD_PHONEMES-3); /* check for overrun */ + + // any stress position marked in the xx_list dictionary ? + stressed_syllable = dictionary_flags & 0x7; + if(dictionary_flags & 0x8) + { + // this indicates a word without a primary stress + stressed_syllable = dictionary_flags & 0x3; + unstressed_word = 1; + } + + max_stress = GetVowelStress(tr, phonetic, vowel_stress, vowel_count, stressed_syllable, 1); + + // heavy or light syllables + ix = 1; + for(p = phonetic; *p != 0; p++) + { + if((phoneme_tab[p[0]]->type == phVOWEL) && !(phoneme_tab[p[0]]->phflags & phNONSYLLABIC)) + { + int weight = 0; + int lengthened = 0; + + if(phoneme_tab[p[1]]->code == phonLENGTHEN) + lengthened = 1; + + if(lengthened || (phoneme_tab[p[0]]->phflags & phLONG)) + { + // long vowel, increase syllable weight + weight++; + } + vowel_length[ix] = weight; + + if(lengthened) p++; // advance over phonLENGTHEN + + if(consonant_types[phoneme_tab[p[1]]->type] && ((phoneme_tab[p[2]]->type != phVOWEL) || (phoneme_tab[p[1]]->phflags & phLONG))) + { + // followed by two consonants, a long consonant, or consonant and end-of-word + weight++; + } + syllable_weight[ix] = weight; + ix++; + } + } + + switch(tr->langopts.stress_rule) + { + case 8: + // stress on first syllable, unless it is a light syllable + if(syllable_weight[1] > 0) + break; + // else drop through to case 1 + case 1: + // stress on second syllable + if((stressed_syllable == 0) && (vowel_count > 2)) + { + stressed_syllable = 2; + if(max_stress == 0) + { + vowel_stress[stressed_syllable] = 4; + } + max_stress = 4; + } + break; + + case 2: + // a language with stress on penultimate vowel + + if(stressed_syllable == 0) + { + /* no explicit stress - stress the penultimate vowel */ + max_stress = 4; + + if(vowel_count > 2) + { + stressed_syllable = vowel_count - 2; + + if(stressflags & 0x300) + { + // LANG=Spanish, stress on last vowel if the word ends in a consonant other than 'n' or 's' + if(phoneme_tab[final_ph]->type != phVOWEL) + { + if(stressflags & 0x100) + { + stressed_syllable = vowel_count - 1; + } + else + { + mnem = phoneme_tab[final_ph]->mnemonic; + mnem2 = phoneme_tab[final_ph2]->mnemonic; + + if((mnem == 's') && (mnem2 == 'n')) + { + // -ns stress remains on penultimate syllable + } + else + if(((mnem != 'n') && (mnem != 's')) || (phoneme_tab[final_ph2]->type != phVOWEL)) + { + stressed_syllable = vowel_count - 1; + } + } + } + } + if(stressflags & 0x80000) + { + // stress on last syllable if it has a long vowel, but previous syllable has a short vowel + if(vowel_length[vowel_count - 1] > vowel_length[vowel_count - 2]) + { + stressed_syllable = vowel_count - 1; + } + } + + if(vowel_stress[stressed_syllable] == 1) + { + // but this vowel is explicitly marked as unstressed + if(stressed_syllable > 1) + stressed_syllable--; + else + stressed_syllable++; + } + } + else + { + stressed_syllable = 1; + if(stressflags & 0x1) + max_stress = 3; // don't give full stress to monosyllables + } + + // only set the stress if it's not already marked explicitly + if(vowel_stress[stressed_syllable] == 0) + { + // don't stress if next and prev syllables are stressed + if((vowel_stress[stressed_syllable-1] < 4) || (vowel_stress[stressed_syllable+1] < 4)) + vowel_stress[stressed_syllable] = max_stress; + } + } + break; + + case 3: + // stress on last vowel + if(stressed_syllable == 0) + { + /* no explicit stress - stress the final vowel */ + stressed_syllable = vowel_count - 1; + if(max_stress == 0) + { + while(stressed_syllable > 0) + { + if(vowel_stress[stressed_syllable] == 0) + { + vowel_stress[stressed_syllable] = 4; + break; + } + else + stressed_syllable--; + } + } + max_stress = 4; + } + break; + + case 4: // stress on antipenultimate vowel + if(stressed_syllable == 0) + { + stressed_syllable = vowel_count - 3; + if(stressed_syllable < 1) + stressed_syllable = 1; + + if(max_stress == 0) + { + vowel_stress[stressed_syllable] = 4; + } + max_stress = 4; + } + break; + + case 5: + // LANG=Russian + if(stressed_syllable == 0) + { + /* no explicit stress - guess the stress from the number of syllables */ + static char guess_ru[16] = {0,0,1,1,2,3,3,4,5,6,7,7,8,9,10,11}; + static char guess_ru_v[16] = {0,0,1,1,2,2,3,3,4,5,6,7,7,8,9,10}; // for final phoneme is a vowel + static char guess_ru_t[16] = {0,0,1,2,3,3,3,4,5,6,7,7,7,8,9,10}; // for final phoneme is an unvoiced stop + + stressed_syllable = vowel_count - 3; + if(vowel_count < 16) + { + if(phoneme_tab[final_ph]->type == phVOWEL) + stressed_syllable = guess_ru_v[vowel_count]; + else + if(phoneme_tab[final_ph]->type == phSTOP) + stressed_syllable = guess_ru_t[vowel_count]; + else + stressed_syllable = guess_ru[vowel_count]; + } + vowel_stress[stressed_syllable] = 4; + max_stress = 4; + } + break; + + case 6: // LANG=hi stress on the last heaviest syllable + if(stressed_syllable == 0) + { + int wt; + int max_weight = -1; + int prev_stressed; + + // find the heaviest syllable, excluding the final syllable + for(ix = 1; ix < (vowel_count-1); ix++) + { + if(vowel_stress[ix] == 0) + { + if((wt = syllable_weight[ix]) >= max_weight) + { + max_weight = wt; + prev_stressed = stressed_syllable; + stressed_syllable = ix; + } + } + } + + if((syllable_weight[vowel_count-1] == 2) && (max_weight< 2)) + { + // the only double=heavy syllable is the final syllable, so stress this + stressed_syllable = vowel_count-1; + } + else + if(max_weight <= 0) + { + // all syllables, exclusing the last, are light. Stress the first syllable + stressed_syllable = 1; + } + + vowel_stress[stressed_syllable] = 4; + max_stress = 4; + } + break; + + case 7: // LANG=tr, the last syllable for any vowel markes explicitly as unstressed + if(stressed_syllable == 0) + { + stressed_syllable = vowel_count - 1; + for(ix=1; ix < vowel_count; ix++) + { + if(vowel_stress[ix] == 1) + { + stressed_syllable = ix-1; + break; + } + } + vowel_stress[stressed_syllable] = 4; + max_stress = 4; + } + break; + + case 9: // mark all as stressed + for(ix=1; ix<vowel_count; ix++) + { + if(vowel_stress[ix] == 0) + vowel_stress[ix] = 4; + } + break; + } + + /* now guess the complete stress pattern */ + if(max_stress < 4) + stress = 4; /* no primary stress marked, use for 1st syllable */ + else + stress = 3; + + + if((stressflags & 0x1000) && (vowel_count == 2)) + { + // Two syllable word, if one syllable has primary stress, then give the other secondary stress + if(vowel_stress[1] == 4) + vowel_stress[2] = 3; + if(vowel_stress[2] == 4) + vowel_stress[1] = 3; + } +#if deleted + if((stressflags & 0x2000) && (vowel_stress[1] == 0)) + { + // If there is only one syllable before the primary stress, give it a secondary stress + if((vowel_count > 2) && (vowel_stress[2] >= 4)) + { + vowel_stress[1] = 3; + } + } +#endif + + done = 0; + for(v=1; v<vowel_count; v++) + { + if(vowel_stress[v] == 0) + { + if((stressflags & 0x10) && (stress < 4) && (v == vowel_count-1)) + { + // flag: don't give secondary stress to final vowel + } + else + if((stressflags & 0x8000) && (done == 0)) + { + vowel_stress[v] = (char)stress; + done =1; + stress = 3; /* use secondary stress for remaining syllables */ + } + else + if((vowel_stress[v-1] <= 1) && (vowel_stress[v+1] <= 1)) + { + /* trochaic: give stress to vowel surrounded by unstressed vowels */ + + if((stress == 3) && (stressflags & 0x20)) + continue; // don't use secondary stress + + if((v > 1) && (stressflags & 0x40) && (syllable_weight[v]==0) && (syllable_weight[v+1]>0)) + { + // don't put secondary stress on a light syllable which is followed by a heavy syllable + continue; + } + +// should start with secondary stress on the first syllable, or should it count back from +// the primary stress and put secondary stress on alternate syllables? + vowel_stress[v] = (char)stress; + done =1; + stress = 3; /* use secondary stress for remaining syllables */ + } + } + } + + if((unstressed_word) && (tonic < 0)) + { + if(vowel_count <= 2) + tonic = tr->langopts.unstressed_wd1; /* monosyllable - unstressed */ + else + tonic = tr->langopts.unstressed_wd2; /* more than one syllable, used secondary stress as the main stress */ + } + + max_stress = 0; + max_stress_posn = 0; + for(v=1; v<vowel_count; v++) + { + if(vowel_stress[v] >= max_stress) + { + max_stress = vowel_stress[v]; + max_stress_posn = v; + } + } + + if(tonic >= 0) + { + /* find position of highest stress, and replace it by 'tonic' */ + + /* don't disturb an explicitly set stress by 'unstress-at-end' flag */ + if((tonic > max_stress) || (max_stress <= 4)) + vowel_stress[max_stress_posn] = (char)tonic; + max_stress = tonic; + } + + + /* produce output phoneme string */ + p = phonetic; + v = 1; + + if((ph = phoneme_tab[*p]) != NULL) + { + + if(ph->type == phSTRESS) + ph = phoneme_tab[p[1]]; + +#ifdef deleted + int gap = tr->langopts.word_gap & 0x700; + if((gap) && (vowel_stress[1] >= 4) && (prev_stress >= 4)) + { + /* two primary stresses together, insert a short pause */ + *output++ = pause_phonemes[gap >> 8]; + } + else +#endif + if((tr->langopts.vowel_pause & 0x30) && (ph->type == phVOWEL)) + { + // word starts with a vowel + + if((tr->langopts.vowel_pause & 0x20) && (vowel_stress[1] >= 4)) + { + *output++ = phonPAUSE_NOLINK; // not to be replaced by link + } + else + { + *output++ = phonPAUSE_VSHORT; // break, but no pause + } + } + } + + p = phonetic; + post_tonic = 0; + while(((phcode = *p++) != 0) && (output < max_output)) + { + if((ph = phoneme_tab[phcode]) == NULL) + continue; + +// if(ph->type == phSTRESS) +// continue; + + if(ph->type == phPAUSE) + { + tr->prev_last_stress = 0; + } + else + if(((ph->type == phVOWEL) && !(ph->phflags & phNONSYLLABIC)) || (*p == phonSYLLABIC)) + { + // a vowel, or a consonant followed by a syllabic consonant marker + + v_stress = vowel_stress[v]; + tr->prev_last_stress = v_stress; + + if(vowel_stress[v-1] >= max_stress) + post_tonic = 1; + + if(v_stress <= 1) + { + if((v > 1) && (max_stress >= 4) && (stressflags & 4) && (v == (vowel_count-1))) + { + // option: mark unstressed final syllable as diminished + v_stress = 1; + } + else + if((stressflags & 2) || (v == 1) || (v == (vowel_count-1))) + { + // first or last syllable, or option 'don't set diminished stress' + v_stress = 0; + } + else + if((v == (vowel_count-2)) && (vowel_stress[vowel_count-1] <= 1)) + { + // penultimate syllable, followed by an unstressed final syllable + v_stress = 0; + } + else + { + // unstressed syllable within a word + if((vowel_stress[v-1] != 1) || ((stressflags & 0x10000) == 0)) + { + v_stress = 1; /* change from 0 (unstressed) to 1 (diminished stress) */ + vowel_stress[v] = v_stress; + } + } + } + + if(v_stress > 0) + *output++ = stress_phonemes[v_stress]; // mark stress of all vowels except 0 (unstressed) + + + if(vowel_stress[v] > max_stress) + { + max_stress = vowel_stress[v]; + } + + if((*p == phonLENGTHEN) && ((opt_length = tr->langopts.param[LOPT_IT_LENGTHEN]) != 0)) + { + // remove lengthen indicator from non-stressed syllables + int shorten=0; + + if(opt_length & 0x10) + { + // only allow lengthen indicator on the highest stress syllable in the word + if(v != max_stress_posn) + shorten = 1; + } + else + if(v_stress < 4) + { + // only allow lengthen indicator if stress >= 4. + shorten = 1; + } + + if(((opt_length & 0xf)==2) && (v != (vowel_count - 2))) + shorten = 1; // LANG=Italian, remove lengthen indicator from non-penultimate syllables + + if(shorten) + p++; + } + + v++; + } + + if(phcode != 1) + *output++ = phcode; + } + *output++ = 0; + +} /* end of SetWordStress */ + + + + +//============================================================================================= +// Look up a word in the pronunciation rules +// +//============================================================================================= + + +#ifdef LOG_TRANSLATE +static char *DecodeRule(const char *group, char *rule) +{//================================================== +/* Convert compiled match template to ascii */ + + unsigned char rb; + unsigned char c; + char *p; + int ix; + int match_type; + int finished=0; + int value; + int linenum=0; + int flags; + int suffix_char; + int condition_num=0; + char buf[60]; + char buf_pre[60]; + char suffix[20]; + static char output[60]; + + static char symbols[] = {' ',' ',' ',' ',' ',' ',' ',' ',' ', + '@','&','%','+','#','S','D','Z','A','L',' ',' ',' ',' ',' ','N','K','V',' ','T','X','?','W'}; + + static char symbols_lg[] = {'A','B','C','H','F','G','Y'}; + + match_type = 0; + buf_pre[0] = 0; + strcpy(buf,group); + p = &buf[strlen(buf)]; + while(!finished) + { + rb = *rule++; + + if(rb <= RULE_LINENUM) + { + switch(rb) + { + case 0: + case RULE_PHONEMES: + finished=1; + break; + case RULE_PRE: + match_type = RULE_PRE; + *p = 0; + p = buf_pre; + break; + case RULE_POST: + match_type = RULE_POST; + *p = 0; + strcat(buf," ("); + p = &buf[strlen(buf)]; + break; + case RULE_PH_COMMON: + break; + case RULE_CONDITION: + /* conditional rule, next byte gives condition number */ + condition_num = *rule++; + break; + case RULE_LINENUM: + value = (rule[1] & 0xff) - 1; + linenum = (rule[0] & 0xff) - 1 + (value * 255); + rule+=2; + break; + } + continue; + } + + if(rb == RULE_ENDING) + { + static const char *flag_chars = "ei vtfq t"; + flags = ((rule[0] & 0x7f)<< 8) + (rule[1] & 0x7f); + suffix_char = 'S'; + if(flags & (SUFX_P >> 8)) + suffix_char = 'P'; + sprintf(suffix,"%c%d",suffix_char,rule[2] & 0x7f); + rule += 3; + for(ix=0;ix<9;ix++) + { + if(flags & 1) + sprintf(&suffix[strlen(suffix)],"%c",flag_chars[ix]); + flags = (flags >> 1); + } + strcpy(p,suffix); + p += strlen(suffix); + c = ' '; + } + else + if(rb == RULE_LETTERGP) + { + c = symbols_lg[*rule++ - 'A']; + } + else + if(rb == RULE_LETTERGP2) + { + value = *rule++ - 'A'; + p[0] = 'L'; + p[1] = (value / 10) + '0'; + c = (value % 10) + '0'; + + if(match_type == RULE_PRE) + { + p[0] = c; + c = 'L'; + } + p+=2; + } + else + if(rb <= RULE_LAST_RULE) + c = symbols[rb]; + else + if(rb == RULE_SPACE) + c = '_'; + else + c = rb; + *p++ = c; + } + *p = 0; + + p = output; + if(linenum > 0) + { + sprintf(p,"%5d:\t",linenum); + p += 7; + } + if(condition_num > 0) + { + sprintf(p,"?%d ",condition_num); + p = &p[strlen(p)]; + } + if((ix = strlen(buf_pre)) > 0) + { + while(--ix >= 0) + *p++ = buf_pre[ix]; + *p++ = ')'; + *p++ = ' '; + } + *p = 0; + strcat(p,buf); + ix = strlen(output); + while(ix < 8) + output[ix++]=' '; + output[ix]=0; + return(output); +} /* end of decode_match */ +#endif + + + +void AppendPhonemes(Translator *tr, char *string, int size, const char *ph) +{//======================================================================== +/* Add new phoneme string "ph" to "string" + Keeps count of the number of vowel phonemes in the word, and whether these + can be stressed syllables. These values can be used in translation rules +*/ + const char *p; + unsigned char c; + int unstress_mark; + int length; + + length = strlen(ph) + strlen(string); + if(length >= size) + { + return; + } + + /* any stressable vowel ? */ + unstress_mark = 0; + p = ph; + while((c = *p++) != 0) + { + if(c >= n_phoneme_tab) continue; + + if(phoneme_tab[c]->type == phSTRESS) + { + if(phoneme_tab[c]->std_length < 4) + unstress_mark = 1; + } + else + { + if(phoneme_tab[c]->type == phVOWEL) + { + if(((phoneme_tab[c]->phflags & phUNSTRESSED) == 0) && + (unstress_mark == 0)) + { + tr->word_stressed_count++; + } + unstress_mark = 0; + tr->word_vowel_count++; + } + } + } + + if(string != NULL) + strcat(string,ph); +} /* end of AppendPhonemes */ + + + +static void MatchRule(Translator *tr, char *word[], const char *group, char *rule, MatchRecord *match_out, int word_flags, int dict_flags) +{//======================================================================================================================================= +/* Checks a specified word against dictionary rules. + Returns with phoneme code string, or NULL if no match found. + + word (indirect) points to current character group within the input word + This is advanced by this procedure as characters are consumed + + group: the initial characters used to choose the rules group + + rule: address of dictionary rule data for this character group + + match_out: returns best points score + + word_flags: indicates whether this is a retranslation after a suffix has been removed +*/ + + unsigned char rb; // current instuction from rule + unsigned char letter; // current letter from input word, single byte + int letter_w; // current letter, wide character + int letter_xbytes; // number of extra bytes of multibyte character (num bytes - 1) + unsigned char last_letter; + + char *pre_ptr; + char *post_ptr; /* pointer to first character after group */ + + char *rule_start; /* start of current match template */ + char *p; + + int match_type; /* left, right, or consume */ + int failed; + int consumed; /* number of letters consumed from input */ + int count; /* count through rules in the group */ + int syllable_count; + int vowel; + int letter_group; + int distance_right; + int distance_left; + int lg_pts; + int n_bytes; + int add_points; + + MatchRecord match; + static MatchRecord best; + + int total_consumed; /* letters consumed for best match */ + int group_length; + + unsigned char condition_num; + char *common_phonemes; /* common to a group of entries */ + + + + if(rule == NULL) + { + match_out->points = 0; + (*word)++; + return; + } + + + total_consumed = 0; + count = 0; + common_phonemes = NULL; + match_type = 0; + + best.points = 0; + best.phonemes = ""; + best.end_type = 0; + best.del_fwd = NULL; + + group_length = strlen(group); + + /* search through dictionary rules */ + while(rule[0] != RULE_GROUP_END) + { + match_type=0; + consumed = 0; + letter = 0; + distance_right= -6; /* used to reduce points for matches further away the current letter */ + distance_left= -2; + count++; + + match.points = 1; + match.end_type = 0; + match.del_fwd = NULL; + + pre_ptr = *word; + post_ptr = *word + group_length; + + /* work through next rule until end, or until no-match proved */ + rule_start = rule; + failed = 0; + while(!failed) + { + rb = *rule++; + + if(rb <= RULE_LINENUM) + { + switch(rb) + { + case 0: // no phoneme string for this rule, use previous common rule + if(common_phonemes != NULL) + { + match.phonemes = common_phonemes; + while(((rb = *match.phonemes++) != 0) && (rb != RULE_PHONEMES)) + { + if(rb == RULE_CONDITION) + match.phonemes++; // skip over condition number + } + } + else + { + match.phonemes = ""; + } + rule--; // so we are still pointing at the 0 + failed=2; // matched OK + break; + case RULE_PRE: + match_type = RULE_PRE; + break; + case RULE_POST: + match_type = RULE_POST; + break; + case RULE_PHONEMES: + match.phonemes = rule; + failed=2; // matched OK + break; + case RULE_PH_COMMON: + common_phonemes = rule; + break; + case RULE_CONDITION: + /* conditional rule, next byte gives condition number */ + condition_num = *rule++; + + if(condition_num >= 32) + { + // allow the rule only if the condition number is NOT set + if((tr->dict_condition & (1L << (condition_num-32))) != 0) + failed = 1; + } + else + { + // allow the rule only if the condition number is set + if((tr->dict_condition & (1L << condition_num)) == 0) + failed = 1; + } + + if(!failed) + match.points++; // add one point for a matched conditional rule + break; + case RULE_LINENUM: + rule+=2; + break; + } + continue; + } + + add_points = 0; + + switch(match_type) + { + case 0: + /* match and consume this letter */ + last_letter = letter; + letter = *post_ptr++; + + if((letter == rb) || ((letter==(unsigned char)REPLACED_E) && (rb=='e'))) + { + add_points = 21; + consumed++; + } + else + failed = 1; + break; + + + case RULE_POST: + /* continue moving fowards */ + distance_right += 6; + if(distance_right > 18) + distance_right = 19; + last_letter = letter; + letter_xbytes = utf8_in(&letter_w,post_ptr)-1; + letter = *post_ptr++; + + switch(rb) + { + case RULE_LETTERGP: + letter_group = *rule++ - 'A'; + if(IsLetter(tr, letter_w, letter_group)) + { + lg_pts = 20; + if(letter_group==2) + lg_pts = 19; // fewer points for C, general consonant + add_points = (lg_pts-distance_right); + post_ptr += letter_xbytes; + } + else + failed = 1; + break; + + case RULE_LETTERGP2: // match against a list of utf-8 strings + letter_group = *rule++ - 'A'; + if((n_bytes = IsLetterGroup(tr, post_ptr-1,letter_group,0)) >0) + { + add_points = (20-distance_right); + post_ptr += (n_bytes-1); + } + else + failed =1; + break; + + case RULE_NOTVOWEL: + if(!IsLetter(tr, letter_w,0)) + { + add_points = (20-distance_right); + post_ptr += letter_xbytes; + } + else + failed = 1; + break; + + case RULE_DIGIT: + if(IsDigit(letter_w)) + { + add_points = (20-distance_right); + post_ptr += letter_xbytes; + } + else + if(tr->langopts.tone_numbers) + { + // also match if there is no digit + add_points = (20-distance_right); + post_ptr--; + } + else + failed = 1; + break; + + case RULE_NONALPHA: + if(!iswalpha(letter_w)) + { + add_points = (21-distance_right); + post_ptr += letter_xbytes; + } + else + failed = 1; + break; + + case RULE_DOUBLE: + if(letter == last_letter) + add_points = (21-distance_right); + else + failed = 1; + break; + + case RULE_ALT1: + if(dict_flags & FLAG_ALT_TRANS) + add_points = 1; + else + failed = 1; + break; + + case '-': + if((letter == '-') || ((letter == ' ') && (word_flags & FLAG_HYPHEN_AFTER))) + { + add_points = (22-distance_right); // one point more than match against space + } + else + failed = 1; + break; + + case RULE_SYLLABLE: + { + /* more than specified number of vowel letters to the right */ + char *p = post_ptr + letter_xbytes; + + syllable_count = 1; + while(*rule == RULE_SYLLABLE) + { + rule++; + syllable_count+=1; /* number of syllables to match */ + } + vowel = 0; + while(letter_w != RULE_SPACE) + { + if((vowel==0) && IsLetter(tr, letter_w,LETTERGP_VOWEL2)) + { + // this is counting vowels which are separated by non-vowels + syllable_count--; + } + vowel = IsLetter(tr, letter_w,LETTERGP_VOWEL2); + p += utf8_in(&letter_w,p); + } + if(syllable_count <= 0) + add_points = (19-distance_right); + else + failed = 1; + } + break; + + case RULE_NOVOWELS: + { + char *p = post_ptr + letter_xbytes; + while(letter_w != RULE_SPACE) + { + if(IsLetter(tr, letter_w,LETTERGP_VOWEL2)) + { + failed = 1; + break; + } + p += utf8_in(&letter_w,p); + } + if(!failed) + add_points = (19-distance_right); + } + break; + + case RULE_INC_SCORE: + add_points = 20; // force an increase in points + break; + + case RULE_DEL_FWD: + // find the next 'e' in the word and replace by '' + for(p = *word + group_length; *p != ' '; p++) + { + if(*p == 'e') + { + match.del_fwd = p; + break; + } + } + break; + + case RULE_ENDING: + // next 3 bytes are a (non-zero) ending type. 2 bytes of flags + suffix length + match.end_type = (rule[0] << 16) + ((rule[1] & 0x7f) << 8) + (rule[2] & 0x7f); + rule += 3; + break; + + case RULE_NO_SUFFIX: + if(word_flags & FLAG_SUFFIX_REMOVED) + failed = 1; // a suffix has been removed + else + add_points = 1; + break; + + default: + if(letter == rb) + { + if(letter == RULE_SPACE) + add_points = (21-distance_right); + else + add_points = (21-distance_right); + } + else + failed = 1; + break; + } + break; + + + case RULE_PRE: + /* match backwards from start of current group */ + distance_left += 2; + if(distance_left > 18) + distance_left = 19; + + last_letter = *pre_ptr; + pre_ptr--; + letter_xbytes = utf8_in2(&letter_w,pre_ptr,1)-1; + letter = *pre_ptr; + + switch(rb) + { + case RULE_LETTERGP: + letter_group = *rule++ - 'A'; + if(IsLetter(tr, letter_w,letter_group)) + { + lg_pts = 20; + if(letter_group==2) + lg_pts = 19; // fewer points for C, general consonant + add_points = (lg_pts-distance_left); + pre_ptr -= letter_xbytes; + } + else + failed = 1; + break; + + case RULE_LETTERGP2: // match against a list of utf-8 strings + letter_group = *rule++ - 'A'; + if((n_bytes = IsLetterGroup(tr, pre_ptr-letter_xbytes,letter_group,1)) >0) + { + add_points = (20-distance_right); + pre_ptr -= (n_bytes-1); + } + else + failed =1; + break; + + case RULE_NOTVOWEL: + if(!IsLetter(tr, letter_w,0)) + { + add_points = (20-distance_left); + pre_ptr -= letter_xbytes; + } + else + failed = 1; + break; + + case RULE_DOUBLE: + if(letter == last_letter) + add_points = (21-distance_left); + else + failed = 1; + break; + + case RULE_DIGIT: + if(IsDigit(letter_w)) + { + add_points = (21-distance_left); + pre_ptr -= letter_xbytes; + } + else + failed = 1; + break; + + case RULE_NONALPHA: + if(!iswalpha(letter_w)) + { + add_points = (21-distance_right); + pre_ptr -= letter_xbytes; + } + else + failed = 1; + break; + + case RULE_SYLLABLE: + /* more than specified number of vowels to the left */ + syllable_count = 1; + while(*rule == RULE_SYLLABLE) + { + rule++; + syllable_count++; /* number of syllables to match */ + } + if(syllable_count <= tr->word_vowel_count) + add_points = (19-distance_left); + else + failed = 1; + break; + + case RULE_STRESSED: + if(tr->word_stressed_count > 0) + add_points = 19; + else + failed = 1; + break; + + case RULE_NOVOWELS: + { + char *p = pre_ptr - letter_xbytes - 1; + while(letter_w != RULE_SPACE) + { + if(IsLetter(tr, letter_w,LETTERGP_VOWEL2)) + { + failed = 1; + break; + } + p -= utf8_in2(&letter_w,p,1); + } + if(!failed) + add_points = 3; + } + break; + + case RULE_IFVERB: + if(tr->expect_verb) + add_points = 1; + else + failed = 1; + break; + + case RULE_CAPITAL: + if(word_flags & FLAG_FIRST_UPPER) + add_points = 1; + else + failed = 1; + break; + + case '.': + // dot in pre- section, match on any dot before this point in the word + for(p=pre_ptr; *p != ' '; p--) + { + if(*p == '.') + { + add_points = 50; + break; + } + } + if(*p == ' ') + failed = 1; + break; + + case '-': + if((letter == '-') || ((letter == ' ') && (word_flags & FLAG_HYPHEN))) + { + add_points = (22-distance_right); // one point more than match against space + } + else + failed = 1; + break; + + default: + if(letter == rb) + { + if(letter == RULE_SPACE) + add_points = 4; + else + add_points = (21-distance_left); + } + else + failed = 1; + break; + } + break; + } + + if(failed == 0) + match.points += add_points; + } + + if(failed == 2) + { + /* matched OK, is this better than the last best match ? */ + if(match.points >= best.points) + { + memcpy(&best,&match,sizeof(match)); + total_consumed = consumed; + } + +#ifdef LOG_TRANSLATE + if((option_phonemes == 2) && (match.points > 0) && ((word_flags & FLAG_NO_TRACE) == 0)) + { + // show each rule that matches, and it's points score + int pts; + char decoded_phonemes[80]; + + // note: 'count' contains the rule number, if we want to include it + pts = match.points; + if(group_length > 1) + pts += 35; // to account for an extra letter matching + DecodePhonemes(match.phonemes,decoded_phonemes); + fprintf(f_trans,"%3d\t%s [%s]\n",pts,DecodeRule(group,rule_start),decoded_phonemes); + } +#endif + + } + + /* skip phoneme string to reach start of next template */ + while(*rule++ != 0); + } + +#ifdef LOG_TRANSLATE + if((option_phonemes == 2) && ((word_flags & FLAG_NO_TRACE)==0)) + { + if(group_length <= 1) + fprintf(f_trans,"\n"); + } +#endif + + /* advance input data pointer */ + total_consumed += group_length; + if(total_consumed == 0) + total_consumed = 1; /* always advance over 1st letter */ + + *word += total_consumed; + + if(best.points == 0) + best.phonemes = ""; + memcpy(match_out,&best,sizeof(MatchRecord)); +} /* end of MatchRule */ + + + + +int TranslateRules(Translator *tr, char *p_start, char *phonemes, int ph_size, char *end_phonemes, int word_flags, unsigned int *dict_flags) +{//===================================================================================================================================== +/* Translate a word bounded by space characters + Append the result to 'phonemes' and any standard prefix/suffix in 'end_phonemes' */ + + unsigned char c, c2; + unsigned int c12; + int wc=0; + int wc_prev; + int wc_bytes; + char *p2; /* copy of p for use in double letter chain match */ + int found; + int g; /* group chain number */ + int g1; /* first group for this letter */ + int n; + int letter; + int any_alpha=0; + int ix; + unsigned int digit_count=0; + char *p; + int dict_flags0=0; + MatchRecord match1; + MatchRecord match2; + char ph_buf[40]; + char word_copy[N_WORD_BYTES]; + static const char str_pause[2] = {phonPAUSE_NOLINK,0}; + + char group_name[4]; + + if(tr->data_dictrules == NULL) + return(0); + + if(dict_flags != NULL) + dict_flags0 = dict_flags[0]; + + for(ix=0; ix<(N_WORD_BYTES-1);) + { + c = p_start[ix]; + word_copy[ix++] = c; + if(c == 0) + break; + } + word_copy[ix] = 0; + + +#ifdef LOG_TRANSLATE + if((option_phonemes == 2) && ((word_flags & FLAG_NO_TRACE)==0)) + { + char wordbuf[120]; + int ix; + + for(ix=0; ((c = p_start[ix]) != ' ') && (c != 0); ix++) + { + wordbuf[ix] = c; + } + wordbuf[ix] = 0; + fprintf(f_trans,"Translate '%s'\n",wordbuf); + } +#endif + + p = p_start; + tr->word_vowel_count = 0; + tr->word_stressed_count = 0; + + if(end_phonemes != NULL) + end_phonemes[0] = 0; + + while(((c = *p) != ' ') && (c != 0)) + { + wc_prev = wc; + wc_bytes = utf8_in(&wc,p); + if(IsAlpha(wc)) + any_alpha++; + + n = tr->groups2_count[c]; + if(IsDigit(wc) && ((tr->langopts.tone_numbers == 0) || !any_alpha)) + { + // lookup the number in *_list not *_rules + char string[8]; + char buf[40]; + string[0] = '_'; + memcpy(&string[1],p,wc_bytes); + string[1+wc_bytes] = 0; + Lookup(tr, string,buf); + if(++digit_count >= 2) + { + strcat(buf,str_pause); + digit_count=0; + } + AppendPhonemes(tr,phonemes,ph_size,buf); + p += wc_bytes; + continue; + } + else + { + digit_count = 0; + found = 0; + + if(n > 0) + { + /* there are some 2 byte chains for this initial letter */ + c2 = p[1]; + c12 = c + (c2 << 8); /* 2 characters */ + + g1 = tr->groups2_start[c]; + for(g=g1; g < (g1+n); g++) + { + if(tr->groups2_name[g] == c12) + { + found = 1; + + group_name[0] = c; + group_name[1] = c2; + group_name[2] = 0; + p2 = p; + MatchRule(tr, &p2, group_name, tr->groups2[g], &match2, word_flags, dict_flags0); + if(match2.points > 0) + match2.points += 35; /* to acount for 2 letters matching */ + + /* now see whether single letter chain gives a better match ? */ + group_name[1] = 0; + MatchRule(tr, &p, group_name, tr->groups1[c], &match1, word_flags, dict_flags0); + + if(match2.points >= match1.points) + { + // use match from the 2-letter group + memcpy(&match1,&match2,sizeof(MatchRecord)); + p = p2; + } + } + } + } + + if(!found) + { + /* alphabetic, single letter chain */ + group_name[0] = c; + group_name[1] = 0; + + if(tr->groups1[c] != NULL) + MatchRule(tr, &p, group_name, tr->groups1[c], &match1, word_flags, dict_flags0); + else + { + // no group for this letter, use default group + MatchRule(tr, &p, "", tr->groups1[0], &match1, word_flags, dict_flags0); + + if((match1.points == 0) && ((option_sayas & 0x10) == 0)) + { + n = utf8_in(&letter,p-1)-1; + + if(tr->letter_bits_offset > 0) + { + // not a Latin alphabet, switch to the default Latin alphabet language + if((letter <= 0x241) && iswalpha(letter)) + { + sprintf(phonemes,"%c%s",phonSWITCH,tr->langopts.ascii_language); + return(0); + } + } +#ifdef deleted +// can't switch to a tone language, because the tone-phoneme numbers are not valid for the original language + if((letter >= 0x4e00) && (letter < 0xa000) && (tr->langopts.ideographs != 1)) + { + // Chinese ideogram + sprintf(phonemes,"%czh",phonSWITCH); + return(0); + } +#endif + // no match, try removing the accent and re-translating the word + if((letter >= 0xc0) && (letter <= 0x241) && ((ix = remove_accent[letter-0xc0]) != 0)) + { + // within range of the remove_accent table + if((p[-2] != ' ') || (p[n] != ' ')) + { + // not the only letter in the word + p2 = p-1; + p[-1] = ix; + while((p[0] = p[n]) != ' ') p++; + while(n-- > 0) *p++ = ' '; // replacement character must be no longer than original + + if(tr->langopts.param[LOPT_DIERESES] && (lookupwchar(diereses_list,letter) > 0)) + { + // vowel with dieresis, replace and continue from this point + p = p2; + continue; + } + + phonemes[0] = 0; // delete any phonemes which have been produced so far + p = p_start; + tr->word_vowel_count = 0; + tr->word_stressed_count = 0; + continue; // start again at the beginning of the word + } + } + else + if((letter >= 0x3200) && (letter < 0xa700) && (end_phonemes != NULL)) + { + // ideograms + // outside the range of the accent table, speak the unknown symbol sound + Lookup(tr, "_??", ph_buf); + match1.phonemes = ph_buf; + match1.points = 1; + p += (wc_bytes-1); + } + } + } + + if(match1.points == 0) + { + if((wc >= 0x300) && (wc <= 0x36f)) + { + // combining accent inside a word, ignore + } + else + if(IsAlpha(wc)) + { + if((any_alpha > 1) || (p[wc_bytes-1] > ' ')) + { + // an unrecognised character in a word, abort and then spell the word + phonemes[0] = 0; + if(dict_flags != NULL) + dict_flags[0] |= FLAG_SPELLWORD; + break; + } + } + else + { + LookupLetter(tr, wc, -1, ph_buf); + if(ph_buf[0]) + { + match1.phonemes = ph_buf; + match1.points = 1; + } + } + p += (wc_bytes-1); + } + else + { + tr->phonemes_repeat_count = 0; + } + } + } + + if(match1.phonemes == NULL) + match1.phonemes = ""; + + if(match1.points > 0) + { + if((match1.phonemes[0] == phonSWITCH) && ((word_flags & FLAG_DONT_SWITCH_TRANSLATOR)==0)) + { + // an instruction to switch language, return immediately so we can re-translate + strcpy(phonemes,match1.phonemes); + return(0); + } + + if((match1.end_type != 0) && (end_phonemes != NULL)) + { + /* a standard ending has been found, re-translate the word without it */ + if((match1.end_type & SUFX_P) && (word_flags & FLAG_NO_PREFIX)) + { + // ignore the match on a prefix + } + else + { + if((match1.end_type & SUFX_P) && ((match1.end_type & 0x7f) == 0)) + { + // no prefix length specified + match1.end_type |= p - p_start; + } + strcpy(end_phonemes,match1.phonemes); + memcpy(p_start,word_copy,strlen(word_copy)); + return(match1.end_type); + } + } + if(match1.del_fwd != NULL) + *match1.del_fwd = REPLACED_E; + AppendPhonemes(tr,phonemes,ph_size,match1.phonemes); + } + } + + // any language specific changes ? + ApplySpecialAttribute(tr,phonemes,dict_flags0); + memcpy(p_start,word_copy,strlen(word_copy)); + + return(0); +} /* end of TranslateRules */ + + + +void ApplySpecialAttribute(Translator *tr, char *phonemes, int dict_flags) +{//======================================================================= +// Amend the translated phonemes according to an attribute which is specific for the language. + int len; + int ix; + char *p_end; + int phoneme_1; + + if((dict_flags & (FLAG_ALT_TRANS | FLAG_ALT2_TRANS)) == 0) + return; + + len = strlen(phonemes); + p_end = &phonemes[len-1]; + + switch(tr->translator_name) + { + case L('d','e'): + if(p_end[0] == PhonemeCode2('i',':')) + { + // words ends in ['i:], change to [=I@] + p_end[-1] = phonSTRESS_PREV; + p_end[0] = PhonemeCode('I'); + p_end[1] = phonSCHWA; + p_end[2] = 0; + } + break; + + case L('p','t'): + phoneme_1 = PhonemeCode('o'); + for(ix=0; ix<(len-1); ix++) + { + if(phonemes[ix] == phoneme_1) + { + phonemes[ix] = PhonemeCode('O'); + break; + } + } + break; + + case L('r','o'): + if(p_end[0] == PhonemeCode('j')) + { + // word end in [j], change to ['i] + p_end[0] = phonSTRESS_P; + p_end[1] = PhonemeCode('i'); + p_end[2] = 0; + } + break; + } +} // end of ApplySpecialAttribute + + + +//============================================================================================= +// Look up a word in the pronunciation dictionary list +// - exceptions which override the usual pronunciation rules, or which give a word +// special properties, such as pronounce as unstressed +//============================================================================================= + +// common letter pairs, encode these as a single byte +static const short pairs_ru[] = { +0x010c, // ла 21052 0x23 +0x010e, // на 18400 +0x0113, // та 14254 +0x0301, // ав 31083 +0x030f, // ов 13420 +0x060e, // не 21798 +0x0611, // ре 19458 +0x0903, // ви 16226 +0x0b01, // ак 14456 +0x0b0f, // ок 17836 +0x0c01, // ал 13324 +0x0c09, // ил 16877 +0x0e01, // ан 15359 +0x0e06, // ен 13543 0x30 +0x0e09, // ин 17168 +0x0e0e, // нн 15973 +0x0e0f, // он 22373 +0x0e1c, // ын 15052 +0x0f03, // во 24947 +0x0f11, // ро 13552 +0x0f12, // Ñо 16368 +0x100f, // оп 19054 +0x1011, // рп 17067 +0x1101, // ар 23967 +0x1106, // ер 18795 +0x1109, // ир 13797 +0x110f, // ор 21737 +0x1213, // Ñ‚Ñ 25076 +0x1220, // ÑÑ 14310 +0x7fff}; +//0x040f ог 12976 +//0x1306 ет 12826 +//0x0f0d мо 12688 + + +int TransposeAlphabet(char *text, int offset, int min, int max) +{//============================================================ +// transpose cyrillic alphabet (for example) into ascii (single byte) character codes +// return: number of bytes, bit 6: 1=used compression + int c; + int c2; + int ix; + char *p = text; + char *p2 = text; + int all_alpha=1; + int bits; + int acc; + + do { + p += utf8_in(&c,p); + if((c >= min) && (c <= max)) + { + *p2++ = c - offset; + } + else + if(c != 0) + { + p2 += utf8_out(c,p2); + all_alpha=0; + } + } while (c != 0); + *p2 = 0; + + if(all_alpha) + { + // compress to 6 bits per character + acc=0; + bits=0; + + p = text; + p2 = text; + while((c = *p++) != 0) + { + c2 = c + (*p << 8); + for(ix=0; c2 >= pairs_ru[ix]; ix++) + { + if(c2 == pairs_ru[ix]) + { + // found an encoding for a 2-character pair + c = ix + 0x23; // 2-character codes start at 0x23 + p++; + break; + } + } + acc = (acc << 6) + (c & 0x3f); + bits += 6; + + if(bits >= 8) + { + bits -= 8; + *p2++ = (acc >> bits); + } + } + if(bits > 0) + { + *p2++ = (acc << (8-bits)); + } + *p2 = 0; + return((p2 - text) | 0x40); // bit 6 indicates compressed characters + } + return(p2 - text); +} // end of TransposeAlphabet + + + + +static const char *LookupDict2(Translator *tr, const char *word, const char *word2, + char *phonetic, unsigned int *flags, int end_flags, WORD_TAB *wtab) +//===================================================================================== +/* Find an entry in the word_dict file for a specified word. + Returns NULL if no match, else returns 'word_end' + + word zero terminated word to match + word2 pointer to next word(s) in the input text (terminated by space) + + flags: returns dictionary flags which are associated with a matched word + + end_flags: indicates whether this is a retranslation after removing a suffix +*/ +{ + char *p; + char *next; + int hash; + int phoneme_len; + int wlen; + unsigned char flag; + unsigned int dictionary_flags; + unsigned int dictionary_flags2; + int condition_failed=0; + int n_chars; + int no_phonemes; + int skipwords; + int ix; + const char *word_end; + const char *word1; + int wflags = 0; + char word_buf[N_WORD_BYTES]; + + if(wtab != NULL) + { + wflags = wtab->flags; + } + + word1 = word; + if(tr->transpose_offset > 0) + { + strcpy(word_buf,word); + wlen = TransposeAlphabet(word_buf, tr->transpose_offset, tr->transpose_min, tr->transpose_max); + word = word_buf; + } + else + { + wlen = strlen(word); + } + + hash = HashDictionary(word); + p = tr->dict_hashtab[hash]; + + if(p == NULL) + { + if(flags != NULL) + *flags = 0; + return(0); + } + + // Find the first entry in the list for this hash value which matches. + // This corresponds to the last matching entry in the *_list file. + + while(*p != 0) + { + next = p + p[0]; + + if(((p[1] & 0x7f) != wlen) || (memcmp(word,&p[2],wlen & 0x3f) != 0)) + { + // bit 6 of wlen indicates whether the word has been compressed; so we need to match on this also. + p = next; + continue; + } + + /* found matching entry. Decode the phonetic string */ + word_end = word2; + + dictionary_flags = 0; + dictionary_flags2 = 0; + no_phonemes = p[1] & 0x80; + + p += ((p[1] & 0x3f) + 2); + + if(no_phonemes) + { + phonetic[0] = 0; + phoneme_len = 0; + } + else + { + strcpy(phonetic,p); + phoneme_len = strlen(p); + p += (phoneme_len + 1); + } + + while(p < next) + { + // examine the flags which follow the phoneme string + + flag = *p++; + if(flag >= 100) + { + // conditional rule + if(flag >= 132) + { + // fail if this condition is set + if((tr->dict_condition & (1 << (flag-132))) != 0) + condition_failed = 1; + } + else + { + // allow only if this condition is set + if((tr->dict_condition & (1 << (flag-100))) == 0) + condition_failed = 1; + } + } + else + if(flag > 80) + { + // flags 81 to 90 match more than one word + // This comes after the other flags + n_chars = next - p; + skipwords = flag - 80; + + // don't use the contraction if any of the words are emphasized + for(ix=0; ix <= skipwords; ix++) + { + if(wflags & FLAG_EMPHASIZED2) + { + condition_failed = 1; + } + } + + if(memcmp(word2,p,n_chars) != 0) + condition_failed = 1; + + if(condition_failed) + { + p = next; + break; + } + + dictionary_flags |= FLAG_SKIPWORDS; + dictionary_skipwords = skipwords; + p = next; + word_end = word2 + n_chars; + } + else + if(flag > 64) + { + // stressed syllable information, put in bits 0-3 + dictionary_flags = (dictionary_flags & ~0xf) | (flag & 0xf); + if((flag & 0xc) == 0xc) + dictionary_flags |= FLAG_STRESS_END; + } + else + if(flag >= 32) + { + dictionary_flags2 |= (1L << (flag-32)); + } + else + { + dictionary_flags |= (1L << flag); + } + } + + if(condition_failed) + { + condition_failed=0; + continue; + } + + if((end_flags & FLAG_SUFX)==0) + { + // no suffix has been removed + if(dictionary_flags & FLAG_STEM) + continue; // this word must have a suffix + } + + if((end_flags & SUFX_P) && (dictionary_flags & (FLAG_ONLY | FLAG_ONLY_S))) + continue; // $only or $onlys, don't match if a prefix has been removed + + if(end_flags & FLAG_SUFX) + { + // a suffix was removed from the word + if(dictionary_flags & FLAG_ONLY) + continue; // no match if any suffix + + if((dictionary_flags & FLAG_ONLY_S) && ((end_flags & FLAG_SUFX_S)==0)) + { + // only a 's' suffix allowed, but the suffix wasn't 's' + continue; + } + } + + if(dictionary_flags2 & FLAG_HYPHENATED) + { + if(!(wflags & FLAG_HYPHEN_AFTER)) + { + continue; + } + } + if(dictionary_flags2 & FLAG_CAPITAL) + { + if(!(wflags & FLAG_FIRST_UPPER)) + { + continue; + } + } + if(dictionary_flags2 & FLAG_ALLCAPS) + { + if(!(wflags & FLAG_ALL_UPPER)) + { + continue; + } + } + + if((dictionary_flags & FLAG_ATEND) && (word_end < tr->clause_end)) + { + // only use this pronunciation if it's the last word of the clause + continue; + } + + if(dictionary_flags2 & FLAG_VERB) + { + // this is a verb-form pronunciation + + if(tr->expect_verb || (tr->expect_verb_s && (end_flags & FLAG_SUFX_S))) + { + // OK, we are expecting a verb + } + else + { + /* don't use the 'verb' pronunciation unless we are + expecting a verb */ + continue; + } + } + if(dictionary_flags2 & FLAG_PAST) + { + if(!tr->expect_past) + { + /* don't use the 'past' pronunciation unless we are + expecting past tense */ + continue; + } + } + if(dictionary_flags2 & FLAG_NOUN) + { + if(!tr->expect_noun) + { + /* don't use the 'noun' pronunciation unless we are + expecting a noun */ + continue; + } + } + + if(flags != NULL) + { + flags[0] = dictionary_flags | FLAG_FOUND_ATTRIBUTES; + flags[1] = dictionary_flags2; + } + + if(phoneme_len == 0) + { + if(option_phonemes == 2) + { + fprintf(f_trans,"Flags: %s %s\n",word1,print_dictionary_flags(flags)); + } + return(0); // no phoneme translation found here, only flags. So use rules + } + + if(flags != NULL) + flags[0] |= FLAG_FOUND; // this flag indicates word was found in dictionary + + if(option_phonemes == 2) + { + unsigned int flags1 = 0; + char ph_decoded[N_WORD_PHONEMES]; + int textmode; + + DecodePhonemes(phonetic,ph_decoded); + if(flags != NULL) + flags1 = flags[0]; + + if((dictionary_flags & FLAG_TEXTMODE) == 0) + textmode = 0; + else + textmode = 1; + + if(textmode == translator->langopts.textmode) + { + // only show this line if the word translates to phonemes, not replacement text + fprintf(f_trans,"Found: %s [%s] %s\n",word1,ph_decoded,print_dictionary_flags(flags)); + } + } + return(word_end); + + } + return(0); +} // end of LookupDict2 + + + +int LookupDictList(Translator *tr, char **wordptr, char *ph_out, unsigned int *flags, int end_flags, WORD_TAB *wtab) +//================================================================================================================== +/* Lookup a specified word in the word dictionary. + Returns phonetic data in 'phonetic' and bits in 'flags' + + end_flags: indicates if a suffix has been removed +*/ +{ + int length; + const char *found; + const char *word1; + const char *word2; + unsigned char c; + int nbytes; + int len; + char word[N_WORD_BYTES]; + static char word_replacement[N_WORD_BYTES]; + + length = 0; + word2 = word1 = *wordptr; + + while((word2[nbytes = utf8_nbytes(word2)]==' ') && (word2[nbytes+1]=='.')) + { + // look for an abbreviation of the form a.b.c + // try removing the spaces between the dots and looking for a match + memcpy(&word[length],word2,nbytes); + length += nbytes; + word[length++] = '.'; + word2 += nbytes+3; + } + if(length > 0) + { + // found an abbreviation containing dots + nbytes = 0; + while(((c = word2[nbytes]) != 0) && (c != ' ')) + { + nbytes++; + } + memcpy(&word[length],word2,nbytes); + word[length+nbytes] = 0; + found = LookupDict2(tr, word, word2, ph_out, flags, end_flags, wtab); + if(found) + { + // set the skip words flag + flags[0] |= FLAG_SKIPWORDS; + dictionary_skipwords = length; + return(1); + } + } + + for(length=0; length<N_WORD_BYTES; length++) + { + if(((c = *word1++)==0) || (c == ' ')) + break; + word[length] = c; + } + word[length] = 0; + + found = LookupDict2(tr, word, word1, ph_out, flags, end_flags, wtab); + + if(flags[0] & FLAG_MAX3) + { + if(strcmp(ph_out, tr->phonemes_repeat) == 0) + { + tr->phonemes_repeat_count++; + if(tr->phonemes_repeat_count > 3) + { + ph_out[0] = 0; + } + } + else + { + strncpy0(tr->phonemes_repeat, ph_out, sizeof(tr->phonemes_repeat)); + tr->phonemes_repeat_count = 1; + } + } + else + { + tr->phonemes_repeat_count = 0; + } + + + if((found == 0) && (flags[1] & FLAG_ACCENT)) + { + int letter; + word2 = word; + if(*word2 == '_') word2++; + len = utf8_in(&letter, word2); + LookupAccentedLetter(tr,letter, ph_out); + found = word2 + len; + } + + if(found == 0) + { + ph_out[0] = 0; + + // try modifications to find a recognised word + + if((end_flags & FLAG_SUFX_E_ADDED) && (word[length-1] == 'e')) + { + // try removing an 'e' which has been added by RemoveEnding + word[length-1] = 0; + found = LookupDict2(tr, word, word1, ph_out, flags, end_flags, wtab); + } + else + if((end_flags & SUFX_D) && (word[length-1] == word[length-2])) + { + // try removing a double letter + word[length-1] = 0; + found = LookupDict2(tr, word, word1, ph_out, flags, end_flags, wtab); + } + } + + if(found) + { + // if textmode is the default, then words which have phonemes are marked. + if(tr->langopts.textmode) + *flags ^= FLAG_TEXTMODE; + + if(*flags & FLAG_TEXTMODE) + { + // the word translates to replacement text, not to phonemes + + if(end_flags & FLAG_ALLOW_TEXTMODE) + { + // only use replacement text if this is the original word, not if a prefix or suffix has been removed + word_replacement[0] = 0; + word_replacement[1] = ' '; + sprintf(&word_replacement[2],"%s ",ph_out); // replacement word, preceded by zerochar and space + + word1 = *wordptr; + *wordptr = &word_replacement[2]; + + if(option_phonemes == 2) + { + len = found - word1; + memcpy(word,word1,len); // include multiple matching words + word[len] = 0; + fprintf(f_trans,"Replace: %s %s\n",word,*wordptr); + } + } + + ph_out[0] = 0; + return(0); + } + + return(1); + } + + ph_out[0] = 0; + return(0); +} // end of LookupDictList + + + +int Lookup(Translator *tr, const char *word, char *ph_out) +{//=================================================== + unsigned int flags[2]; + flags[0] = flags[1] = 0; + char *word1 = (char *)word; + return(LookupDictList(tr, &word1, ph_out, flags, 0, NULL)); +} + + + +int RemoveEnding(Translator *tr, char *word, int end_type, char *word_copy) +{//======================================================================== +/* Removes a standard suffix from a word, once it has been indicated by the dictionary rules. + end_type: bits 0-6 number of letters + bits 8-14 suffix flags + + word_copy: make a copy of the original word + This routine is language specific. In English it deals with reversing y->i and e-dropping + that were done when the suffix was added to the original word. +*/ + + int i; + char *word_end; + int len_ending; + int end_flags; + const char *p; + int len; + static char ending[12]; + + // these lists are language specific, but are only relevent if the 'e' suffix flag is used + static const char *add_e_exceptions[] = { + "ion", NULL }; + + static const char *add_e_additions[] = { + "c", "rs", "ir", "ur", "ath", "ns", "lu", NULL }; + + for(word_end = word; *word_end != ' '; word_end++) + { + /* replace discarded 'e's */ + if(*word_end == REPLACED_E) + *word_end = 'e'; + } + i = word_end - word; + memcpy(word_copy,word,i); + word_copy[i] = 0; + + // look for multibyte characters to increase the number of bytes to remove + for(len_ending = i = (end_type & 0x3f); i>0 ;i--) // num.of characters of the suffix + { + word_end--; + while((*word_end & 0xc0) == 0x80) + { + word_end--; // for multibyte characters + len_ending++; + } + } + + // remove bytes from the end of the word and replace them by spaces + for(i=0; i<len_ending; i++) + { + ending[i] = word_end[i]; + word_end[i] = ' '; + } + ending[i] = 0; + word_end--; /* now pointing at last character of stem */ + + end_flags = (end_type & 0xfff0) | FLAG_SUFX; + + /* add an 'e' to the stem if appropriate, + if stem ends in vowel+consonant + or stem ends in 'c' (add 'e' to soften it) */ + + if(end_type & SUFX_I) + { + if(word_end[0] == 'i') + word_end[0] = 'y'; + } + + if(end_type & SUFX_E) + { + // add 'e' to end of stem + if(IsLetter(tr, word_end[-1],LETTERGP_VOWEL2) && IsLetter(tr, word_end[0],1)) + { + // vowel(incl.'y') + hard.consonant + + for(i=0; (p = add_e_exceptions[i]) != NULL; i++) + { + len = strlen(p); + if(memcmp(p,&word_end[1-len],len)==0) + { + break; + } + } + if(p == NULL) + end_flags |= FLAG_SUFX_E_ADDED; // no exception found + } + else + { + for(i=0; (p = add_e_additions[i]) != NULL; i++) + { + len = strlen(p); + if(memcmp(p,&word_end[1-len],len)==0) + { + end_flags |= FLAG_SUFX_E_ADDED; + break; + } + } + } + + if(end_flags & FLAG_SUFX_E_ADDED) + { + word_end[1] = 'e'; +#ifdef LOG_TRANSLATE +if(option_phonemes == 2) +{ + fprintf(f_trans,"add e\n"); +} +#endif + } + } + + if((end_type & SUFX_V) && (tr->expect_verb==0)) + tr->expect_verb = 1; // this suffix indicates the verb pronunciation + + + if((strcmp(ending,"s")==0) || (strcmp(ending,"es")==0)) + end_flags |= FLAG_SUFX_S; + + if(strcmp(ending,"'s")==0) + end_flags &= ~FLAG_SUFX; // don't consider 's as an added suffix + + return(end_flags); +} /* end of RemoveEnding */ + + diff --git a/Plugins/eSpeak/eSpeak/espeak_command.h b/Plugins/eSpeak/eSpeak/espeak_command.h new file mode 100644 index 0000000..c3bf35d --- /dev/null +++ b/Plugins/eSpeak/eSpeak/espeak_command.h @@ -0,0 +1,129 @@ +#ifndef ESPEAK_COMMAND_H +#define ESPEAK_COMMAND_H + +#ifndef PLATFORM_WINDOWS +#include <unistd.h> +#endif +#include "speak_lib.h" + +enum t_espeak_type + { + ET_TEXT, + ET_MARK, + ET_KEY, + ET_CHAR, + ET_PARAMETER, + ET_PUNCTUATION_LIST, + ET_VOICE_NAME, + ET_VOICE_SPEC, + ET_TERMINATED_MSG + }; + +typedef struct +{ + unsigned int unique_identifier; + void* text; + size_t size; + unsigned int position; + espeak_POSITION_TYPE position_type; + unsigned int end_position; + unsigned int flags; + void* user_data; +} t_espeak_text; + +typedef struct +{ + unsigned int unique_identifier; + void* text; + size_t size; + const char* index_mark; + unsigned int end_position; + unsigned int flags; + void* user_data; +} t_espeak_mark; + +typedef struct +{ + unsigned int unique_identifier; + void* user_data; +} t_espeak_terminated_msg; + +typedef struct +{ + espeak_PARAMETER parameter; + int value; + int relative; +} t_espeak_parameter; + +enum t_command_state +{ + CS_UNDEFINED, // The command has just been created + CS_PENDING, // stored in the fifo + CS_PROCESSED // processed +}; + +typedef struct +{ + enum t_espeak_type type; + t_command_state state; + + union command + { + t_espeak_text my_text; + t_espeak_mark my_mark; + const char* my_key; + wchar_t my_char; + t_espeak_parameter my_param; + const wchar_t* my_punctuation_list; + const char *my_voice_name; + espeak_VOICE my_voice_spec; + t_espeak_terminated_msg my_terminated_msg; + } u; +} t_espeak_command; + + +t_espeak_command* create_espeak_text(const void *text, size_t size, unsigned int position, espeak_POSITION_TYPE position_type, unsigned int end_position, unsigned int flags, void* user_data); + +t_espeak_command* create_espeak_mark(const void *text, size_t size, const char *index_mark, unsigned int end_position, unsigned int flags, void* user_data); + +t_espeak_command* create_espeak_terminated_msg(unsigned int unique_identifier, void* user_data); + +t_espeak_command* create_espeak_key(const char *key_name); + +t_espeak_command* create_espeak_char(wchar_t character); + +t_espeak_command* create_espeak_parameter(espeak_PARAMETER parameter, int value, int relative); + +t_espeak_command* create_espeak_punctuation_list(const wchar_t *punctlist); + +t_espeak_command* create_espeak_voice_name(const char *name); + +t_espeak_command* create_espeak_voice_spec(espeak_VOICE *voice_spec); + +void process_espeak_command( t_espeak_command* the_command); + +int delete_espeak_command( t_espeak_command* the_command); + +void display_espeak_command(t_espeak_command* the_command); + + +espeak_ERROR sync_espeak_Synth(unsigned int unique_identifier, const void *text, size_t size, + unsigned int position, espeak_POSITION_TYPE position_type, + unsigned int end_position, unsigned int flags, void* user_data); +espeak_ERROR sync_espeak_Synth_Mark(unsigned int unique_identifier, const void *text, size_t size, + const char *index_mark, unsigned int end_position, + unsigned int flags, void* user_data); +void sync_espeak_Key(const char *key); +void sync_espeak_Char(wchar_t character); +void sync_espeak_SetPunctuationList(const wchar_t *punctlist); +void sync_espeak_SetParameter(espeak_PARAMETER parameter, int value, int relative); +int sync_espeak_SetVoiceByName(const char *name); +int sync_espeak_SetVoiceByProperties(espeak_VOICE *voice_selector); +espeak_ERROR SetVoiceByName(const char *name); +espeak_ERROR SetVoiceByProperties(espeak_VOICE *voice_selector); +void SetParameter(int parameter, int value, int relative); + +int sync_espeak_terminated_msg(unsigned int unique_identifier, void* user_data); + +//> +#endif diff --git a/Plugins/eSpeak/eSpeak/event.h b/Plugins/eSpeak/eSpeak/event.h new file mode 100644 index 0000000..c9ee482 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/event.h @@ -0,0 +1,51 @@ +#ifndef EVENT_H +#define EVENT_H + +/* +Manage events (sentence, word, mark, end,...), is responsible of calling the external +callback as soon as the relevant audio sample is played. + + +The audio stream is composed of samples from synthetised messages or audio icons. +Each event is associated to a sample. + +Scenario: + +- event_declare is called for each expected event. + +- A timeout is started for the first pending event. + +- When the timeout happens, the synth_callback is called. + +Note: the timeout is checked against the real progress of the audio stream, which depends on pauses or underruns. If the real progress is lower than the expected one, a new timeout starts. + +*/ + +#include "speak_lib.h" + +// Initialize the event component. +// First function to be called. +// the callback will be called when the event actually occurs. +// The callback is detailled in speak_lib.h . +void event_init(void); +void event_set_callback(t_espeak_callback* cb); + +// Clear any pending event. +// +// Return: EE_OK: operation achieved +// EE_INTERNAL_ERROR. +espeak_ERROR event_clear_all (); + +// Declare a future event +// +// Return: EE_OK: operation achieved +// EE_BUFFER_FULL: the event can not be buffered; +// you may try after a while to call the function again. +// EE_INTERNAL_ERROR. +espeak_ERROR event_declare (espeak_EVENT* event); + +// Terminate the event component. +// Last function to be called. +void event_terminate(); + +#endif diff --git a/Plugins/eSpeak/eSpeak/fifo.h b/Plugins/eSpeak/eSpeak/fifo.h new file mode 100644 index 0000000..b3699ff --- /dev/null +++ b/Plugins/eSpeak/eSpeak/fifo.h @@ -0,0 +1,58 @@ +#ifndef FIFO_H +#define FIFO_H + +// Helps to add espeak commands in a first-in first-out queue +// and run them asynchronously. + +#include "espeak_command.h" +#include "speak_lib.h" + +// Initialize the fifo component. +// First function to be called. +void fifo_init(); + +// Add an espeak command. +// +// Note: this function fails if too many commands are already buffered. +// In such a case, the calling function could wait and then add again its command. +// +// Return: EE_OK: operation achieved +// EE_BUFFER_FULL: the command can not be buffered; +// you may try after a while to call the function again. +// EE_INTERNAL_ERROR. +espeak_ERROR fifo_add_command (t_espeak_command* c); + +// Add two espeak commands in a single transaction. +// +// Note: this function fails if too many commands are already buffered. +// In such a case, the calling function could wait and then add again these commands. +// +// Return: EE_OK: operation achieved +// EE_BUFFER_FULL: at least one command can not be buffered; +// you may try after a while to call the function again. +// EE_INTERNAL_ERROR. +espeak_ERROR fifo_add_commands (t_espeak_command* c1, t_espeak_command* c2); + +// The current running command must be stopped and the awaiting commands are cleared. +// Return: EE_OK: operation achieved +// EE_INTERNAL_ERROR. +espeak_ERROR fifo_stop (); + +// Is there a running command? +// Returns 1 if yes; 0 otherwise. +int fifo_is_busy (); + +// Terminate the fifo component. +// Last function to be called. +void fifo_terminate(); + +// Indicates if the running command is still enabled. +// +// Note: this function is mainly called by the SynthCallback (speak_lib.cpp) +// It indicates if the actual wave sample can still be played. It is helpful for +// stopping speech as soon as a cancel command is applied. +// +// Returns 1 if yes, or 0 otherwise. +int fifo_is_command_enabled(); + +#endif diff --git a/Plugins/eSpeak/eSpeak/intonation.cpp b/Plugins/eSpeak/eSpeak/intonation.cpp new file mode 100644 index 0000000..6cc4f91 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/intonation.cpp @@ -0,0 +1,1104 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "StdAfx.h" + +#include <stdio.h> +#include <string.h> +#include <wctype.h> + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "voice.h" +#include "translate.h" + + +/* Note this module is mostly old code that needs to be rewritten to + provide a more flexible intonation system. +*/ + +// bits in SYLLABLE.flags +#define SYL_RISE 1 +#define SYL_EMPHASIS 2 +#define SYL_END_CLAUSE 4 + +typedef struct { + char stress; + char env; + char flags; //bit 0=pitch rising, bit1=emnphasized, bit2=end of clause + char nextph_type; + short pitch1; + short pitch2; +} SYLLABLE; + +static SYLLABLE *syllable_tab; + + +static int tone_pitch_env; /* used to return pitch envelope */ + + + +/* Pitch data for tone types */ +/*****************************/ + + +#define PITCHfall 0 +#define PITCHrise 1 +#define PITCHfrise 2 // and 3 must be for the varient preceded by 'r' +#define PITCHfrise2 4 // and 5 must be the 'r' variant +#define PITCHrisefall 6 + +/* 0 fall */ +unsigned char env_fall[128] = { + 0xff, 0xfd, 0xfa, 0xf8, 0xf6, 0xf4, 0xf2, 0xf0, 0xee, 0xec, 0xea, 0xe8, 0xe6, 0xe4, 0xe2, 0xe0, + 0xde, 0xdc, 0xda, 0xd8, 0xd6, 0xd4, 0xd2, 0xd0, 0xce, 0xcc, 0xca, 0xc8, 0xc6, 0xc4, 0xc2, 0xc0, + 0xbe, 0xbc, 0xba, 0xb8, 0xb6, 0xb4, 0xb2, 0xb0, 0xae, 0xac, 0xaa, 0xa8, 0xa6, 0xa4, 0xa2, 0xa0, + 0x9e, 0x9c, 0x9a, 0x98, 0x96, 0x94, 0x92, 0x90, 0x8e, 0x8c, 0x8a, 0x88, 0x86, 0x84, 0x82, 0x80, + 0x7e, 0x7c, 0x7a, 0x78, 0x76, 0x74, 0x72, 0x70, 0x6e, 0x6c, 0x6a, 0x68, 0x66, 0x64, 0x62, 0x60, + 0x5e, 0x5c, 0x5a, 0x58, 0x56, 0x54, 0x52, 0x50, 0x4e, 0x4c, 0x4a, 0x48, 0x46, 0x44, 0x42, 0x40, + 0x3e, 0x3c, 0x3a, 0x38, 0x36, 0x34, 0x32, 0x30, 0x2e, 0x2c, 0x2a, 0x28, 0x26, 0x24, 0x22, 0x20, + 0x1e, 0x1c, 0x1a, 0x18, 0x16, 0x14, 0x12, 0x10, 0x0e, 0x0c, 0x0a, 0x08, 0x06, 0x04, 0x02, 0x00 }; + +/* 1 rise */ +unsigned char env_rise[128] = { + 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, + 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, + 0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, + 0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, + 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, + 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, + 0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, + 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfd, 0xff }; + +unsigned char env_frise[128] = { + 0xff, 0xf4, 0xea, 0xe0, 0xd6, 0xcc, 0xc3, 0xba, 0xb1, 0xa8, 0x9f, 0x97, 0x8f, 0x87, 0x7f, 0x78, + 0x71, 0x6a, 0x63, 0x5c, 0x56, 0x50, 0x4a, 0x44, 0x3f, 0x39, 0x34, 0x2f, 0x2b, 0x26, 0x22, 0x1e, + 0x1a, 0x17, 0x13, 0x10, 0x0d, 0x0b, 0x08, 0x06, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x13, 0x15, 0x17, + 0x1a, 0x1d, 0x1f, 0x22, 0x25, 0x28, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x39, 0x3b, 0x3d, 0x40, + 0x42, 0x45, 0x47, 0x4a, 0x4c, 0x4f, 0x51, 0x54, 0x57, 0x5a, 0x5d, 0x5f, 0x62, 0x65, 0x68, 0x6b, + 0x6e, 0x71, 0x74, 0x78, 0x7b, 0x7e, 0x81, 0x85, 0x88, 0x8b, 0x8f, 0x92, 0x96, 0x99, 0x9d, 0xa0, + 0xa4, 0xa8, 0xac, 0xaf, 0xb3, 0xb7, 0xbb, 0xbf, 0xc3, 0xc7, 0xcb, 0xcf, 0xd3, 0xd7, 0xdb, 0xe0 }; + +static unsigned char env_r_frise[128] = { + 0xcf, 0xcc, 0xc9, 0xc6, 0xc3, 0xc0, 0xbd, 0xb9, 0xb4, 0xb0, 0xab, 0xa7, 0xa2, 0x9c, 0x97, 0x92, + 0x8c, 0x86, 0x81, 0x7b, 0x75, 0x6f, 0x69, 0x63, 0x5d, 0x57, 0x50, 0x4a, 0x44, 0x3e, 0x38, 0x33, + 0x2d, 0x27, 0x22, 0x1c, 0x17, 0x12, 0x0d, 0x08, 0x04, 0x02, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x0a, 0x0c, 0x0d, 0x0f, 0x12, 0x14, 0x16, + 0x19, 0x1b, 0x1e, 0x21, 0x24, 0x27, 0x2a, 0x2d, 0x30, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3f, 0x41, + 0x43, 0x46, 0x48, 0x4b, 0x4d, 0x50, 0x52, 0x55, 0x58, 0x5a, 0x5d, 0x60, 0x63, 0x66, 0x69, 0x6c, + 0x6f, 0x72, 0x75, 0x78, 0x7b, 0x7e, 0x81, 0x85, 0x88, 0x8b, 0x8f, 0x92, 0x96, 0x99, 0x9d, 0xa0, + 0xa4, 0xa8, 0xac, 0xaf, 0xb3, 0xb7, 0xbb, 0xbf, 0xc3, 0xc7, 0xcb, 0xcf, 0xd3, 0xd7, 0xdb, 0xe0 }; + +static unsigned char env_frise2[128] = { + 0xff, 0xf9, 0xf4, 0xee, 0xe9, 0xe4, 0xdf, 0xda, 0xd5, 0xd0, 0xcb, 0xc6, 0xc1, 0xbd, 0xb8, 0xb3, + 0xaf, 0xaa, 0xa6, 0xa1, 0x9d, 0x99, 0x95, 0x90, 0x8c, 0x88, 0x84, 0x80, 0x7d, 0x79, 0x75, 0x71, + 0x6e, 0x6a, 0x67, 0x63, 0x60, 0x5d, 0x59, 0x56, 0x53, 0x50, 0x4d, 0x4a, 0x47, 0x44, 0x41, 0x3e, + 0x3c, 0x39, 0x37, 0x34, 0x32, 0x2f, 0x2d, 0x2b, 0x28, 0x26, 0x24, 0x22, 0x20, 0x1e, 0x1c, 0x1a, + 0x19, 0x17, 0x15, 0x14, 0x12, 0x11, 0x0f, 0x0e, 0x0d, 0x0c, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, + 0x05, 0x04, 0x03, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x04, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0e, 0x0f, 0x10, 0x12, 0x13, 0x15, 0x17, 0x18, 0x1a, 0x1c, 0x1e, 0x20 }; + +static unsigned char env_r_frise2[128] = { + 0xd0, 0xce, 0xcd, 0xcc, 0xca, 0xc8, 0xc7, 0xc5, 0xc3, 0xc1, 0xc0, 0xbd, 0xbb, 0xb8, 0xb5, 0xb3, + 0xb0, 0xad, 0xaa, 0xa7, 0xa3, 0xa0, 0x9d, 0x99, 0x96, 0x92, 0x8f, 0x8b, 0x87, 0x84, 0x80, 0x7c, + 0x78, 0x74, 0x70, 0x6d, 0x69, 0x65, 0x61, 0x5d, 0x59, 0x55, 0x51, 0x4d, 0x4a, 0x46, 0x42, 0x3e, + 0x3b, 0x37, 0x34, 0x31, 0x2f, 0x2d, 0x2a, 0x28, 0x26, 0x24, 0x22, 0x20, 0x1e, 0x1c, 0x1a, 0x19, + 0x17, 0x15, 0x14, 0x12, 0x11, 0x0f, 0x0e, 0x0d, 0x0c, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x05, + 0x04, 0x03, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x04, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0e, 0x0f, 0x10, 0x12, 0x13, 0x15, 0x17, 0x18, 0x1a, 0x1c, 0x1e, 0x20 }; + +static unsigned char env_risefall[128] = { + 0x98, 0x99, 0x99, 0x9a, 0x9c, 0x9d, 0x9f, 0xa1, 0xa4, 0xa7, 0xa9, 0xac, 0xb0, 0xb3, 0xb6, 0xba, + 0xbe, 0xc1, 0xc5, 0xc9, 0xcd, 0xd1, 0xd4, 0xd8, 0xdc, 0xdf, 0xe3, 0xe6, 0xea, 0xed, 0xf0, 0xf2, + 0xf5, 0xf7, 0xf9, 0xfb, 0xfc, 0xfd, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfd, + 0xfb, 0xfa, 0xf8, 0xf6, 0xf3, 0xf1, 0xee, 0xec, 0xe9, 0xe6, 0xe4, 0xe0, 0xdd, 0xda, 0xd7, 0xd3, + 0xd0, 0xcc, 0xc8, 0xc4, 0xc0, 0xbc, 0xb8, 0xb4, 0xb0, 0xac, 0xa7, 0xa3, 0x9f, 0x9a, 0x96, 0x91, + 0x8d, 0x88, 0x84, 0x7f, 0x7b, 0x76, 0x72, 0x6d, 0x69, 0x65, 0x60, 0x5c, 0x58, 0x54, 0x50, 0x4c, + 0x48, 0x44, 0x40, 0x3c, 0x39, 0x35, 0x32, 0x2f, 0x2b, 0x28, 0x26, 0x23, 0x20, 0x1d, 0x1a, 0x17, + 0x15, 0x12, 0x0f, 0x0d, 0x0a, 0x08, 0x07, 0x05, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static unsigned char env_rise2[128] = { + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x06, 0x06, + 0x07, 0x08, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x16, 0x17, 0x18, 0x19, 0x1b, 0x1c, 0x1d, 0x1f, 0x20, 0x22, 0x23, 0x25, 0x26, 0x28, 0x29, 0x2b, + 0x2d, 0x2f, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, 0x40, 0x42, 0x44, 0x47, 0x49, 0x4b, + 0x4e, 0x50, 0x52, 0x55, 0x57, 0x5a, 0x5d, 0x5f, 0x62, 0x65, 0x67, 0x6a, 0x6d, 0x70, 0x73, 0x76, + 0x79, 0x7c, 0x7f, 0x82, 0x86, 0x89, 0x8c, 0x90, 0x93, 0x96, 0x9a, 0x9d, 0xa0, 0xa3, 0xa6, 0xa9, + 0xac, 0xaf, 0xb2, 0xb5, 0xb8, 0xbb, 0xbe, 0xc1, 0xc4, 0xc7, 0xca, 0xcd, 0xd0, 0xd3, 0xd6, 0xd9, + 0xdc, 0xdf, 0xe2, 0xe4, 0xe7, 0xe9, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfb, 0xfd }; + +static unsigned char env_fall2[128] = { + 0xfe, 0xfe, 0xfd, 0xfd, 0xfc, 0xfb, 0xfb, 0xfa, 0xfa, 0xf9, 0xf8, 0xf8, 0xf7, 0xf7, 0xf6, 0xf6, + 0xf5, 0xf4, 0xf4, 0xf3, 0xf3, 0xf2, 0xf2, 0xf1, 0xf0, 0xf0, 0xef, 0xee, 0xee, 0xed, 0xec, 0xeb, + 0xea, 0xea, 0xe9, 0xe8, 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, 0xde, 0xdd, 0xdc, 0xdb, + 0xd9, 0xd8, 0xd6, 0xd5, 0xd3, 0xd2, 0xd0, 0xce, 0xcc, 0xcb, 0xc9, 0xc7, 0xc5, 0xc3, 0xc0, 0xbe, + 0xbc, 0xb9, 0xb7, 0xb5, 0xb2, 0xaf, 0xad, 0xaa, 0xa7, 0xa4, 0xa1, 0x9e, 0x9a, 0x97, 0x94, 0x90, + 0x8d, 0x89, 0x85, 0x81, 0x7d, 0x79, 0x75, 0x71, 0x6d, 0x68, 0x64, 0x61, 0x5e, 0x5b, 0x57, 0x54, + 0x51, 0x4d, 0x4a, 0x46, 0x43, 0x40, 0x3c, 0x39, 0x35, 0x32, 0x2e, 0x2a, 0x27, 0x23, 0x1f, 0x1c, + 0x18, 0x14, 0x11, 0x0d, 0x0b, 0x09, 0x07, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00 }; + +static unsigned char env_fallrise3[128] = { + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfd, 0xfc, 0xfa, 0xf8, 0xf6, 0xf4, 0xf1, 0xee, 0xeb, + 0xe8, 0xe5, 0xe1, 0xde, 0xda, 0xd6, 0xd2, 0xcd, 0xc9, 0xc4, 0xbf, 0xba, 0xb6, 0xb0, 0xab, 0xa6, + 0xa1, 0x9c, 0x96, 0x91, 0x8b, 0x86, 0x80, 0x7b, 0x75, 0x6f, 0x6a, 0x64, 0x5f, 0x59, 0x54, 0x4f, + 0x49, 0x44, 0x3f, 0x3a, 0x35, 0x30, 0x2b, 0x26, 0x22, 0x1d, 0x19, 0x15, 0x11, 0x0d, 0x0a, 0x07, + 0x04, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x04, 0x05, + 0x07, 0x09, 0x0b, 0x0d, 0x10, 0x12, 0x15, 0x18, 0x1b, 0x1e, 0x22, 0x25, 0x29, 0x2d, 0x31, 0x35, + 0x3a, 0x3e, 0x43, 0x48, 0x4c, 0x51, 0x57, 0x5b, 0x5e, 0x62, 0x65, 0x68, 0x6b, 0x6e, 0x71, 0x74, + 0x76, 0x78, 0x7b, 0x7c, 0x7e, 0x80, 0x81, 0x82, 0x83, 0x83, 0x84, 0x84, 0x83, 0x83, 0x82, 0x81 }; + +static unsigned char env_fallrise4[128] = { + 0x72, 0x72, 0x71, 0x71, 0x70, 0x6f, 0x6d, 0x6c, 0x6a, 0x68, 0x66, 0x64, 0x61, 0x5f, 0x5c, 0x5a, + 0x57, 0x54, 0x51, 0x4e, 0x4b, 0x48, 0x45, 0x42, 0x3f, 0x3b, 0x38, 0x35, 0x32, 0x2f, 0x2c, 0x29, + 0x26, 0x23, 0x20, 0x1d, 0x1b, 0x18, 0x16, 0x14, 0x12, 0x10, 0x0e, 0x0c, 0x0b, 0x0a, 0x09, 0x08, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, + 0x07, 0x07, 0x08, 0x09, 0x0a, 0x0c, 0x0d, 0x0f, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1b, 0x1d, 0x20, + 0x23, 0x26, 0x29, 0x2c, 0x2f, 0x33, 0x37, 0x3b, 0x3f, 0x43, 0x47, 0x4c, 0x51, 0x56, 0x5b, 0x60, + 0x65, 0x6a, 0x6f, 0x74, 0x79, 0x7f, 0x84, 0x89, 0x8f, 0x95, 0x9b, 0xa1, 0xa7, 0xad, 0xb3, 0xba, + 0xc0, 0xc7, 0xce, 0xd5, 0xdc, 0xe3, 0xea, 0xf1, 0xf5, 0xf7, 0xfa, 0xfc, 0xfd, 0xfe, 0xff, 0xff }; + +static unsigned char env_risefallrise[128] = { + 0x7f, 0x7f, 0x7f, 0x80, 0x81, 0x83, 0x84, 0x87, 0x89, 0x8c, 0x8f, 0x92, 0x96, 0x99, 0x9d, 0xa1, + 0xa5, 0xaa, 0xae, 0xb2, 0xb7, 0xbb, 0xc0, 0xc5, 0xc9, 0xcd, 0xd2, 0xd6, 0xda, 0xde, 0xe2, 0xe6, + 0xea, 0xed, 0xf0, 0xf3, 0xf5, 0xf8, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xfe, 0xfd, 0xfc, 0xfb, 0xf9, + 0xf7, 0xf4, 0xf0, 0xec, 0xe7, 0xe2, 0xdc, 0xd5, 0xce, 0xc6, 0xbd, 0xb4, 0xa9, 0x9e, 0x92, 0x88, + 0x82, 0x7d, 0x77, 0x72, 0x6c, 0x66, 0x60, 0x5a, 0x54, 0x4e, 0x49, 0x42, 0x3c, 0x37, 0x32, 0x2d, + 0x28, 0x24, 0x1f, 0x1b, 0x18, 0x14, 0x11, 0x0e, 0x0c, 0x09, 0x07, 0x06, 0x05, 0x04, 0x04, 0x04, + 0x04, 0x05, 0x06, 0x08, 0x0a, 0x0d, 0x10, 0x14, 0x18, 0x1d, 0x23, 0x29, 0x2f, 0x37, 0x3e, 0x47, + 0x50, 0x5a, 0x64, 0x70, 0x7c, 0x83, 0x85, 0x88, 0x8a, 0x8c, 0x8e, 0x8f, 0x91, 0x92, 0x93, 0x93 }; + + + + +unsigned char *envelope_data[18] = { + env_fall, + env_rise, + env_frise, env_r_frise, + env_frise2, env_r_frise2, + env_risefall, env_risefall, + + env_fallrise3, env_fallrise3, + env_fallrise4, env_fallrise4, + env_fall2, env_fall2, + env_rise2, env_rise2, + env_risefallrise, env_risefallrise + }; + + +/* all pitches given in Hz above pitch_base */ + +// pitch change during the main part of the clause +static int drops_0[8] = {0x400,0x400,0x700,0x700,0x700,0xa00,0x1800,0x0e00}; +//static int drops_1[8] = {0x400,0x400,0x600,0x600,0xc00,0xc00,0x0e00,0x0e00}; +//static int drops_2[8] = {0x400,0x400,0x600,0x600,-0x800,0xc00,0x0e00,0x0e00}; + +static short oflow[] = {0, 20, 12, 4, 0}; +static short oflow_emf[] = {5, 24, 15, 10, 5}; +static short oflow_less[] = {3, 19, 12, 7, 2}; +// static short oflow_test2[] = {20, 0, 20, 0, 20}; +// static short back_emf[] = {35, 32, 0}; + + +#define N_TONE_HEAD_TABLE 13 +#define N_TONE_NUCLEUS_TABLE 13 + + +typedef struct { + unsigned char pre_start; + unsigned char pre_end; + + unsigned char body_start; + unsigned char body_end; + + int *body_drops; + unsigned char body_max_steps; + char body_lower_u; + + char n_overflow; + short *overflow; +} TONE_HEAD; + + +typedef struct { + unsigned char pitch_env0; /* pitch envelope, tonic syllable at end */ + unsigned char tonic_max0; + unsigned char tonic_min0; + + unsigned char pitch_env1; /* followed by unstressed */ + unsigned char tonic_max1; + unsigned char tonic_min1; + + short *backwards; + + unsigned char tail_start; + unsigned char tail_end; + unsigned char flags; +} TONE_NUCLEUS; + +#define T_EMPH 1 + +static TONE_HEAD tone_head_table[N_TONE_HEAD_TABLE] = { + {20, 25, 34, 22, drops_0, 3, 3, 5, oflow}, // 0 statement + {20, 25, 34, 20, drops_0, 3, 3, 5, oflow}, // 1 comma + {20, 25, 34, 20, drops_0, 3, 3, 5, oflow}, // 2 question + {20, 25, 36, 22, drops_0, 3, 4, 5, oflow_emf}, // 3 exclamation + {20, 25, 34, 22, drops_0, 3, 3, 5, oflow}, // 4 statement, emphatic + {20, 25, 32, 24, drops_0, 4, 3, 5, oflow_less}, // 5 statement, less intonation + {20, 25, 32, 24, drops_0, 4, 3, 5, oflow_less}, // 6 comma, less intonation + {20, 25, 32, 24, drops_0, 4, 3, 5, oflow_less}, // 7 comma, less intonation, less rise + {20, 25, 34, 22, drops_0, 3, 3, 5, oflow}, // 8 pitch raises at end of sentence + {20, 25, 34, 20, drops_0, 3, 3, 5, oflow}, // 9 comma + {20, 25, 34, 22, drops_0, 3, 3, 5, oflow}, // 10 question + {15, 18, 18, 14, drops_0, 3, 3, 5, oflow_less}, // 11 test + {20, 25, 24, 22, drops_0, 3, 3, 5, oflow_less}, // 12 test +}; + +static TONE_NUCLEUS tone_nucleus_table[N_TONE_NUCLEUS_TABLE] = { + {PITCHfall, 30, 5, PITCHfall, 32, 9, NULL, 12, 7, 0}, // 0 statement + {PITCHfrise, 35, 8, PITCHfrise2, 35,10, NULL, 15, 23, 0}, // 1 comma + {PITCHfrise, 39,10, PITCHfrise2, 36,10, NULL, 15, 28, 0}, // 2 question +// {PITCHfall, 41, 4, PITCHfall, 41,27, NULL, 16, 4, T_EMPH}, // 3 exclamation + {PITCHfall, 41, 4, PITCHfall, 41,35, NULL, 35, 4, T_EMPH}, // 3 exclamation + {PITCHfall, 38, 2, PITCHfall, 42,30, NULL, 15, 5, 0}, // 4 statement, emphatic + {PITCHfall, 28, 5, PITCHfall, 28, 9, NULL, 12, 7, 0}, // 5 statement, less intonation + {PITCHfrise, 30, 8, PITCHfrise2, 30,10, NULL, 13, 20, 0}, // 6 comma, less intonation + {PITCHfrise2, 28, 7, PITCHfall, 29,14, NULL, 14, 8, 0}, // 7 comma, less intonation, less rise + {PITCHrise, 30,20, PITCHfall, 19,14, NULL, 20, 26, 0}, // 8 pitch raises at end of sentence + {PITCHfrise, 35,11, PITCHfrise2, 32,10, NULL, 19, 24, 0}, // 9 comma + {PITCHfrise, 39,15, PITCHfall, 28,14, NULL, 20, 36, 0}, // 10 question + {PITCHfall, 28, 6, PITCHfall, 28,10, NULL, 12, 6, 0}, // 11 test + {PITCHfall, 35, 9, PITCHfall, 35,12, NULL, 16, 10, 0}, // 12 test +}; + + + +/* index by 0=. 1=, 2=?, 3=! 4=none, 5=emphasized */ +unsigned char punctuation_to_tone[INTONATION_TYPES][PUNCT_INTONATIONS] = { + {0,1,2,3,0,4}, + {0,1,2,3,0,4}, + {5,6,2,3,0,4}, + {5,7,1,3,0,4}, + {8,9,10,3,0,0}, + {8,8,10,3,0,0}, + {11,11,11,11,0,0}, // 6 test + {12,12,12,12,0,0} +}; + + + +/* indexed by stress */ +static int min_drop[] = {0x300,0x300,0x400,0x400,0x900,0x900,0x900,0xb00}; + + + + +#define SECONDARY 3 +#define PRIMARY 4 +#define PRIMARY_STRESSED 6 +#define PRIMARY_LAST 7 + + +static int number_pre; +static int number_body; +static int number_tail; +static int last_primary; +static int tone_posn; +static int tone_posn2; +static int no_tonic; + + +static void count_pitch_vowels(int start, int end, int clause_end) +/****************************************************************/ +{ + int ix; + int stress; + int max_stress = 0; + int max_stress_posn = 0; // last syllable ot the highest stress + int max_stress_posn2 = 0; // penuntimate syllable of the highest stress + + number_pre = -1; /* number of vowels before 1st primary stress */ + number_body = 0; + number_tail = 0; /* number between tonic syllable and next primary */ + last_primary = -1; + + for(ix=start; ix<end; ix++) + { + stress = syllable_tab[ix].stress; /* marked stress level */ + + if(stress >= max_stress) + { + if(stress > max_stress) + { + max_stress_posn2 = ix; + } + else + { + max_stress_posn2 = max_stress_posn; + } + max_stress_posn = ix; + max_stress = stress; + } + if(stress >= PRIMARY) + { + if(number_pre < 0) + number_pre = ix - start; + + last_primary = ix; + } + + } + + if(number_pre < 0) + number_pre = end; + + number_tail = end - max_stress_posn - 1; + tone_posn = max_stress_posn; + tone_posn2 = max_stress_posn2; + + if(no_tonic) + { + tone_posn = tone_posn2 = end; // next position after the end of the truncated clause + } + else + if(last_primary >= 0) + { + if(end == clause_end) + { + syllable_tab[last_primary].stress = PRIMARY_LAST; + } + } + else + { + // no primary stress. Use the highest stress + syllable_tab[tone_posn].stress = PRIMARY_LAST; + } +} /* end of count_pitch_vowels */ + + + + +static int count_increments(int ix, int end_ix, int min_stress) +/*************************************************************/ +/* Count number of primary stresses up to tonic syllable or body_reset */ +{ + int count = 0; + int stress; + + while(ix < end_ix) + { + stress = syllable_tab[ix++].stress; + if(stress >= PRIMARY_LAST) + break; + + if(stress >= min_stress) + count++; + } + return(count); +} /* end of count_increments */ + + + +static void set_pitch(SYLLABLE *syl, int base, int drop) +/******************************************************/ +// Set the pitch of a vowel in syllable_tab. Base & drop are Hz * 256 +{ + int pitch1, pitch2; + int flags = 0; + + /* adjust experimentally */ + int pitch_range2 = 148; + int pitch_base2 = 72; + + if(base < 0) base = 0; + + pitch2 = ((base * pitch_range2 ) >> 15) + pitch_base2; + + if(drop < 0) + { + flags = SYL_RISE; + drop = -drop; + } + + pitch1 = pitch2 + ((drop * pitch_range2) >> 15); + + if(pitch1 > 511) pitch1 = 511; + if(pitch2 > 511) pitch2 = 511; + + syl->pitch1 = pitch1; + syl->pitch2 = pitch2; + syl->flags |= flags; +} /* end of set_pitch */ + + + +static int calc_pitch_segment(int ix, int end_ix, TONE_HEAD *th, TONE_NUCLEUS *tn, int min_stress, int continuing) +/**********************************************************************************************/ +/* Calculate pitches until next RESET or tonic syllable, or end. + Increment pitch if stress is >= min_stress. + Used for tonic segment */ +{ + int stress; + int pitch=0; + int increment=0; + int n_primary=0; + int n_steps=0; + int initial; + int overflow=0; + int n_overflow; + int *drops; + short *overflow_tab; + SYLLABLE *syl; + + static short continue_tab[5] = {-13, 16, 10, 4, 0}; + + drops = th->body_drops; + + if(continuing) + { + initial =0; + overflow = 0; + n_overflow = 5; + overflow_tab = continue_tab; + increment = (th->body_end - th->body_start) << 8; + increment = increment / (th->body_max_steps -1); + } + else + { + n_overflow = th->n_overflow; + overflow_tab = th->overflow; + initial = 1; + } + + while(ix < end_ix) + { + syl = &syllable_tab[ix]; + stress = syl->stress; + +// if(stress == PRIMARY_MARKED) +// initial = 1; // reset the intonation pattern + + if(initial || (stress >= min_stress)) + { + // a primary stress + + if((initial) || (stress == 5)) + { + initial = 0; + overflow = 0; + n_steps = n_primary = count_increments(ix,end_ix,min_stress); + + if(n_steps > th->body_max_steps) + n_steps = th->body_max_steps; + + if(n_steps > 1) + { + increment = (th->body_end - th->body_start) << 8; + increment = increment / (n_steps -1); + } + else + increment = 0; + + pitch = th->body_start << 8; + } + else + { + if(n_steps > 0) + pitch += increment; + else + { + pitch = (th->body_end << 8) - (increment * overflow_tab[overflow++])/16; + if(overflow >= n_overflow) + { + overflow = 0; + overflow_tab = th->overflow; + } + } + } + + n_steps--; + + n_primary--; + if((tn->backwards) && (n_primary < 2)) + { + pitch = tn->backwards[n_primary] << 8; + } + } + + if(stress >= PRIMARY) + { + syl->stress = PRIMARY_STRESSED; + set_pitch(syl,pitch,drops[stress]); + } + else + if(stress >= SECONDARY) + { + set_pitch(syl,pitch,drops[stress]); + } + else + { + /* unstressed, drop pitch if preceded by PRIMARY */ + if((syllable_tab[ix-1].stress & 0x3f) >= SECONDARY) + set_pitch(syl,pitch - (th->body_lower_u << 8), drops[stress]); + else + set_pitch(syl,pitch,drops[stress]); + } + + ix++; + } + return(ix); +} /* end of calc_pitch_segment */ + + + + + +static int calc_pitch_segment2(int ix, int end_ix, int start_p, int end_p, int min_stress) +/****************************************************************************************/ +/* Linear pitch rise/fall, change pitch at min_stress or stronger + Used for pre-head and tail */ +{ + int stress; + int pitch; + int increment; + int n_increments; + int drop; + SYLLABLE *syl; + + if(ix >= end_ix) + return(ix); + + n_increments = count_increments(ix,end_ix,min_stress); + increment = (end_p - start_p) << 8; + + if(n_increments > 1) + { + increment = increment / n_increments; + } + + + pitch = start_p << 8; + while(ix < end_ix) + { + syl = &syllable_tab[ix]; + stress = syl->stress; + + if(increment > 0) + { + set_pitch(syl,pitch,-increment); + pitch += increment; + } + else + { + drop = -increment; + if(drop < min_drop[stress]) + drop = min_drop[stress]; + + pitch += increment; + + if(drop > 0x900) + drop = 0x900; + set_pitch(syl, pitch, drop); + } + + ix++; + } + return(ix); +} /* end of calc_pitch_segment2 */ + + + + + + +static int calc_pitches(int start, int end, int head_tone, int nucleus_tone) +//=========================================================================== +// Calculate pitch values for the vowels in this tone group +{ + int ix; + TONE_HEAD *th; + TONE_NUCLEUS *tn; + int drop; + int continuing = 0; + + if(start > 0) + continuing = 1; + + th = &tone_head_table[head_tone]; + tn = &tone_nucleus_table[nucleus_tone]; + ix = start; + + /* vowels before the first primary stress */ + /******************************************/ + + if(number_pre > 0) + { + ix = calc_pitch_segment2(ix, ix+number_pre, th->pre_start, th->pre_end, 0); + } + + /* body of tonic segment */ + /*************************/ + + if(option_tone_flags & OPTION_EMPHASIZE_PENULTIMATE) + { + tone_posn = tone_posn2; // put tone on the penultimate stressed word + } + ix = calc_pitch_segment(ix,tone_posn, th, tn, PRIMARY, continuing); + + if(no_tonic) + return(0); + + /* tonic syllable */ + /******************/ + + if(tn->flags & T_EMPH) + { + syllable_tab[ix].flags |= SYL_EMPHASIS; + } + + if(number_tail == 0) + { + tone_pitch_env = tn->pitch_env0; + drop = tn->tonic_max0 - tn->tonic_min0; + set_pitch(&syllable_tab[ix++],tn->tonic_min0 << 8,drop << 8); + } + else + { + tone_pitch_env = tn->pitch_env1; + drop = tn->tonic_max1 - tn->tonic_min1; + set_pitch(&syllable_tab[ix++],tn->tonic_min1 << 8,drop << 8); + } + + syllable_tab[tone_posn].env = tone_pitch_env; + if(syllable_tab[tone_posn].stress == PRIMARY) + syllable_tab[tone_posn].stress = PRIMARY_STRESSED; + + /* tail, after the tonic syllable */ + /**********************************/ + + calc_pitch_segment2(ix, end, tn->tail_start, tn->tail_end, 0); + + return(tone_pitch_env); +} /* end of calc_pitches */ + + + + + + +static void CalcPitches_Tone(Translator *tr, int clause_tone) +{//========================================================== +// clause_tone: 0=. 1=, 2=?, 3=! 4=none + PHONEME_LIST *p; + int ix; + int count_stressed=0; + int final_stressed=0; + + int tone_ph; + int pause; + int tone_promoted; + PHONEME_TAB *tph; + PHONEME_TAB *prev_tph; // forget across word boundary + PHONEME_TAB *prevw_tph; // remember across word boundary + PHONEME_TAB *prev2_tph; // 2 tones previous + PHONEME_LIST *prev_p; + + int pitch_adjust = 0; // pitch gradient through the clause - inital value + int pitch_decrement = 0; // decrease by this for each stressed syllable + int pitch_low = 0; // until it drops to this + int pitch_high = 0; // then reset to this + + p = &phoneme_list[0]; + + // count number of stressed syllables + p = &phoneme_list[0]; + for(ix=0; ix<n_phoneme_list; ix++, p++) + { + if((p->type == phVOWEL) && (p->tone >= 4)) + { + if(count_stressed == 0) + final_stressed = ix; + + if(p->tone >= 4) + { + final_stressed = ix; + count_stressed++; + } + } + } + + phoneme_list[final_stressed].tone = 7; + + // language specific, changes to tones + if(tr->translator_name == L('v','i')) + { + // LANG=vi + p = &phoneme_list[final_stressed]; + if(p->tone_ph == 0) + p->tone_ph = PhonemeCode('7'); // change default tone (tone 1) to falling tone at end of clause + } + + + pause = 1; + tone_promoted = 0; + + prev_p = p = &phoneme_list[0]; + prev_tph = prevw_tph = phoneme_tab[phonPAUSE]; + + // perform tone sandhi + for(ix=0; ix<n_phoneme_list; ix++, p++) + { + if((p->type == phPAUSE) && (p->ph->std_length > 50)) + { + pause = 1; // there is a pause since the previous vowel + prevw_tph = phoneme_tab[phonPAUSE]; // forget previous tone + } + + if(p->newword) + { + prev_tph = phoneme_tab[phonPAUSE]; // forget across word boundaries + } + + if(p->synthflags & SFLAG_SYLLABLE) + { + tone_ph = p->tone_ph; + tph = phoneme_tab[tone_ph]; + + // Mandarin + if(tr->translator_name == L('z','h')) + { + if(tone_ph == 0) + { + if(pause || tone_promoted) + { + tone_ph = PhonemeCode2('5','5'); // no previous vowel, use tone 1 + tone_promoted = 1; + } + else + { + tone_ph = PhonemeCode2('1','1'); // default tone 5 + } + + p->tone_ph = tone_ph; + tph = phoneme_tab[tone_ph]; + + } + else + { + tone_promoted = 0; + } + + if(ix == final_stressed) + { + if((tph->mnemonic == 0x3535 ) || (tph->mnemonic == 0x3135)) + { + // change sentence final tone 1 or 4 to stress 6, not 7 + phoneme_list[final_stressed].tone = 6; + } + } + + if(prevw_tph->mnemonic == 0x343132) // [214] + { + if(tph->mnemonic == 0x343132) // [214] + prev_p->tone_ph = PhonemeCode2('3','5'); + else + prev_p->tone_ph = PhonemeCode2('2','1'); + } + if((prev_tph->mnemonic == 0x3135) && (tph->mnemonic == 0x3135)) // [51] + [51] + { + prev_p->tone_ph = PhonemeCode2('5','3'); + } + + if(tph->mnemonic == 0x3131) // [11] Tone 5 + { + // tone 5, change its level depending on the previous tone (across word boundaries) + if(prevw_tph->mnemonic == 0x3535) + p->tone_ph = PhonemeCode2('2','2'); + if(prevw_tph->mnemonic == 0x3533) + p->tone_ph = PhonemeCode2('3','3'); + if(prevw_tph->mnemonic == 0x343132) + p->tone_ph = PhonemeCode2('4','4'); + + // tone 5 is unstressed (shorter) + p->tone = 1; // diminished stress + } + } + + prev_p = p; + prev2_tph = prevw_tph; + prevw_tph = prev_tph = tph; + pause = 0; + } + } + + // convert tone numbers to pitch + p = &phoneme_list[0]; + for(ix=0; ix<n_phoneme_list; ix++, p++) + { + if(p->synthflags & SFLAG_SYLLABLE) + { + tone_ph = p->tone_ph; + + if(p->tone != 1) // TEST, consider all syllables as stressed + { + if(ix == final_stressed) + { + // the last stressed syllable + pitch_adjust = pitch_low; + } + else + { + pitch_adjust -= pitch_decrement; + if(pitch_adjust <= pitch_low) + pitch_adjust = pitch_high; + } + } + + if(tone_ph ==0) + { + tone_ph = phonDEFAULTTONE; // no tone specified, use default tone 1 + p->tone_ph = tone_ph; + } + p->pitch1 = pitch_adjust + phoneme_tab[tone_ph]->start_type; + p->pitch2 = pitch_adjust + phoneme_tab[tone_ph]->end_type; + } + } + + +} // end of Translator::CalcPitches_Tone + + + +void CalcPitches(Translator *tr, int clause_type) +{//============================================== +// clause_type: 0=. 1=, 2=?, 3=! 4=none + PHONEME_LIST *p; + SYLLABLE *syl; + int ix; + int x; + int st_ix; + int n_st; + int option; + int group_tone; + int group_tone_emph; + int group_tone_comma; + int ph_start=0; + int st_start; + int st_clause_end; + int count; + int n_primary; + int count_primary; + PHONEME_TAB *ph; + int ph_end=n_phoneme_list; + + SYLLABLE syllable_tab2[N_PHONEME_LIST]; + + syllable_tab = syllable_tab2; // don't use permanent storage. it's only needed during the call of CalcPitches() + n_st = 0; + n_primary = 0; + for(ix=0; ix<(n_phoneme_list-1); ix++) + { + p = &phoneme_list[ix]; + if(p->synthflags & SFLAG_SYLLABLE) + { + syllable_tab[n_st].flags = 0; + syllable_tab[n_st].env = PITCHfall; + syllable_tab[n_st].nextph_type = phoneme_list[ix+1].type; + syllable_tab[n_st++].stress = p->tone; // stress level + + if(p->tone >= 4) + n_primary++; + } + else + if((p->ph->code == phonPAUSE_CLAUSE) && (n_st > 0)) + { + syllable_tab[n_st-1].flags |= SYL_END_CLAUSE; + } + } + syllable_tab[n_st].stress = 0; // extra 0 entry at the end + + if(n_st == 0) + return; // nothing to do + + + + if(tr->langopts.tone_language == 1) + { + CalcPitches_Tone(tr,clause_type); + return; + } + + + option = tr->langopts.intonation_group; + if(option >= INTONATION_TYPES) + option = 0; + + group_tone = tr->punct_to_tone[option][clause_type]; + group_tone_emph = tr->punct_to_tone[option][5]; // emphatic form of statement + group_tone_comma = tr->punct_to_tone[option][1]; // emphatic form of statement + + if(clause_type == 4) + no_tonic = 1; /* incomplete clause, used for abbreviations such as Mr. Dr. Mrs. */ + else + no_tonic = 0; + + st_start = 0; + count_primary=0; + for(st_ix=0; st_ix<n_st; st_ix++) + { + syl = &syllable_tab[st_ix]; + + if(syl->stress >= 4) + count_primary++; + + if(syl->stress == 6) + { + // reduce the stress of the previous stressed syllable (review only the previous few syllables) + for(ix=st_ix-1; ix>=st_start && ix>=(st_ix-3); ix--) + { + if(syllable_tab[ix].stress == 6) + break; + if(syllable_tab[ix].stress == 4) + { + syllable_tab[ix].stress = 3; + break; + } + } + + // are the next primary syllables also emphasized ? + for(ix=st_ix+1; ix<n_st; ix++) + { + if(syllable_tab[ix].stress == 4) + break; + if(syllable_tab[ix].stress == 6) + { + // emphasize this syllable, but don't end the current tone group + syllable_tab[st_ix].flags = SYL_EMPHASIS; + syl->stress = 5; + break; + } + } + } + + if(syl->stress == 6) + { + // an emphasized syllable, end the tone group after the next primary stress + syllable_tab[st_ix].flags = SYL_EMPHASIS; + + count = 0; + if((n_primary - count_primary) > 1) + count =1; + + for(ix=st_ix+1; ix<n_st; ix++) + { + if(syllable_tab[ix].stress > 4) + break; + if(syllable_tab[ix].stress == 4) + { + count++; + if(count > 1) + break; + } + } + + count_pitch_vowels(st_start, ix, n_st); + if((ix < n_st) || (clause_type == 0)) + calc_pitches(st_start, ix, group_tone_emph, group_tone_emph); // split into > 1 tone groups, use emphatic tone + else + calc_pitches(st_start, ix, group_tone, group_tone); + + st_start = ix; + } + if((st_start < st_ix) && (syl->flags & SYL_END_CLAUSE)) + { + // end of clause after this syllable, indicated by a phonPAUSE_CLAUSE phoneme + st_clause_end = st_ix+1; + count_pitch_vowels(st_start, st_clause_end, st_clause_end); + calc_pitches(st_start, st_clause_end, group_tone_comma, group_tone_comma); + st_start = st_clause_end; + } + } + + if(st_start < st_ix) + { + count_pitch_vowels(st_start, st_ix, n_st); + calc_pitches(st_start, st_ix, group_tone, group_tone); + } + + + // unpack pitch data + st_ix=0; + for(ix=ph_start; ix < ph_end; ix++) + { + p = &phoneme_list[ix]; + p->tone = syllable_tab[st_ix].stress; + + if(p->synthflags & SFLAG_SYLLABLE) + { + syl = &syllable_tab[st_ix]; + + x = syl->pitch1 - 72; + if(x < 0) x = 0; + p->pitch1 = x; + + x = syl->pitch2 - 72; + if(x < 0) x = 0; + p->pitch2 = x; + + p->env = PITCHfall; + if(syl->flags & SYL_RISE) + { + p->env = PITCHrise; + } + else + if(p->tone > 5) + p->env = syl->env; + + if(p->pitch1 > p->pitch2) + { + // swap so that pitch2 is the higher + x = p->pitch1; + p->pitch1 = p->pitch2; + p->pitch2 = x; + } + +if(p->tone_ph) +{ + ph = phoneme_tab[p->tone_ph]; + x = (p->pitch1 + p->pitch2)/2; + p->pitch2 = x + ph->end_type; + p->pitch1 = x + ph->start_type; +} + + if(syl->flags & SYL_EMPHASIS) + { + p->tone |= 8; // emphasized + } + + st_ix++; + } + } + +} // end of Translator::CalcPitches + + diff --git a/Plugins/eSpeak/eSpeak/klatt.cpp b/Plugins/eSpeak/eSpeak/klatt.cpp new file mode 100644 index 0000000..da0baaa --- /dev/null +++ b/Plugins/eSpeak/eSpeak/klatt.cpp @@ -0,0 +1,1282 @@ + +/*************************************************************************** + * Copyright (C) 2008 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * Based on a re-implementation by: * + * (c) 1993,94 Jon Iles and Nick Ing-Simmons * + * of the Klatt cascade-parallel formant synthesizer * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +// See URL: ftp://svr-ftp.eng.cam.ac.uk/pub/comp.speech/synthesis/klatt.3.04.tar.gz + +#include "StdAfx.h" + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <string.h> +#include "klatt.h" + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "voice.h" + +#ifdef INCLUDE_KLATT // conditional compilation for the whole file + +extern unsigned char *out_ptr; // **JSD +extern unsigned char *out_start; +extern unsigned char *out_end; +extern WGEN_DATA wdata; +static int nsamples; +static int sample_count; + + +#ifdef _MSC_VER +#define getrandom(min,max) ((rand()%(int)(((max)+1)-(min)))+(min)) +#else +#define getrandom(min,max) ((rand()%(long)(((max)+1)-(min)))+(min)) +#endif + + +/* function prototypes for functions private to this file */ + +static void flutter(klatt_frame_ptr); +static double sampled_source (void); +static double impulsive_source (void); +static double natural_source (void); +static void pitch_synch_par_reset (klatt_frame_ptr); +static double gen_noise (double); +static double DBtoLIN (long); +static void frame_init (klatt_frame_ptr); +static void setabc (long,long,resonator_ptr); +static void setzeroabc (long,long,resonator_ptr); + +static klatt_frame_t kt_frame; +static klatt_global_t kt_globals; + +/* +function RESONATOR + +This is a generic resonator function. Internal memory for the resonator +is stored in the globals structure. +*/ + +static double resonator(resonator_ptr r, double input) +{ + double x; + + x = (double) ((double)r->a * (double)input + (double)r->b * (double)r->p1 + (double)r->c * (double)r->p2); + r->p2 = (double)r->p1; + r->p1 = (double)x; + + return (double)x; +} + +static double resonator2(resonator_ptr r, double input) +{ + double x; + + x = (double) ((double)r->a * (double)input + (double)r->b * (double)r->p1 + (double)r->c * (double)r->p2); + r->p2 = (double)r->p1; + r->p1 = (double)x; + + r->a += r->a_inc; + r->b += r->b_inc; + r->c += r->c_inc; + return (double)x; +} + + + +/* +function ANTIRESONATOR + +This is a generic anti-resonator function. The code is the same as resonator +except that a,b,c need to be set with setzeroabc() and we save inputs in +p1/p2 rather than outputs. There is currently only one of these - "rnz" +Output = (rnz.a * input) + (rnz.b * oldin1) + (rnz.c * oldin2) +*/ + +#ifdef deleted +static double antiresonator(resonator_ptr r, double input) +{ + register double x = (double)r->a * (double)input + (double)r->b * (double)r->p1 + (double)r->c * (double)r->p2; + r->p2 = (double)r->p1; + r->p1 = (double)input; + return (double)x; +} +#endif + +static double antiresonator2(resonator_ptr r, double input) +{ + register double x = (double)r->a * (double)input + (double)r->b * (double)r->p1 + (double)r->c * (double)r->p2; + r->p2 = (double)r->p1; + r->p1 = (double)input; + + r->a += r->a_inc; + r->b += r->b_inc; + r->c += r->c_inc; + return (double)x; +} + + + +/* +function FLUTTER + +This function adds F0 flutter, as specified in: + +"Analysis, synthesis and perception of voice quality variations among +female and male talkers" D.H. Klatt and L.C. Klatt JASA 87(2) February 1990. + +Flutter is added by applying a quasi-random element constructed from three +slowly varying sine waves. +*/ + +static void flutter(klatt_frame_ptr frame) +{ + static int time_count; + double delta_f0; + double fla,flb,flc,fld,fle; + + fla = (double) kt_globals.f0_flutter / 50; + flb = (double) kt_globals.original_f0 / 100; +// flc = sin(2*PI*12.7*time_count); +// fld = sin(2*PI*7.1*time_count); +// fle = sin(2*PI*4.7*time_count); + flc = sin(PI*12.7*time_count); // because we are calling flutter() more frequently, every 2.9mS + fld = sin(PI*7.1*time_count); + fle = sin(PI*4.7*time_count); + delta_f0 = fla * flb * (flc + fld + fle) * 10; + frame->F0hz10 = frame->F0hz10 + (long) delta_f0; + time_count++; +} + + + +/* +function SAMPLED_SOURCE + +Allows the use of a glottal excitation waveform sampled from a real +voice. +*/ + +static double sampled_source() +{ + int itemp; + double ftemp; + double result; + double diff_value; + int current_value; + int next_value; + double temp_diff; + + if(kt_globals.T0!=0) + { + ftemp = (double) kt_globals.nper; + ftemp = ftemp / kt_globals.T0; + ftemp = ftemp * kt_globals.num_samples; + itemp = (int) ftemp; + + temp_diff = ftemp - (double) itemp; + + current_value = kt_globals.natural_samples[itemp]; + next_value = kt_globals.natural_samples[itemp+1]; + + diff_value = (double) next_value - (double) current_value; + diff_value = diff_value * temp_diff; + + result = kt_globals.natural_samples[itemp] + diff_value; + result = result * kt_globals.sample_factor; + } + else + { + result = 0; + } + return(result); +} + + + + +/* +function PARWAVE + +Converts synthesis parameters to a waveform. +*/ + + +static int parwave(klatt_frame_ptr frame) +{ + double temp; + double outbypas; + double out; + long n4; + double frics; + double glotout; + double aspiration; + double casc_next_in; + double par_glotout; + static double noise; + static double voice; + static double vlast; + static double glotlast; + static double sourc; + int ix; + + frame_init(frame); /* get parameters for next frame of speech */ + + flutter(frame); /* add f0 flutter */ + +#ifdef deleted +{ + FILE *f; + f=fopen("klatt_log","a"); + fprintf(f,"%4dhz %2dAV %4d %3d, %4d %3d, %4d %3d, %4d %3d, %4d, %3d, %4d %3d TLT=%2d\n",frame->F0hz10,frame->AVdb, + frame->F1hz,frame->B1hz,frame->F2hz,frame->B2hz,frame->F3hz,frame->B3hz,frame->F4hz,frame->B4hz,frame->F5hz,frame->B5hz,frame->F6hz,frame->B6hz,frame->TLTdb); + fclose(f); +} +#endif + + /* MAIN LOOP, for each output sample of current frame: */ + + for (kt_globals.ns=0; kt_globals.ns<kt_globals.nspfr; kt_globals.ns++) + { + /* Get low-passed random number for aspiration and frication noise */ + noise = gen_noise(noise); + + /* + Amplitude modulate noise (reduce noise amplitude during + second half of glottal period) if voicing simultaneously present. + */ + + if (kt_globals.nper > kt_globals.nmod) + { + noise *= (double) 0.5; + } + + /* Compute frication noise */ + frics = kt_globals.amp_frica * noise; + + /* + Compute voicing waveform. Run glottal source simulation at 4 + times normal sample rate to minimize quantization noise in + period of female voice. + */ + + for (n4=0; n4<4; n4++) + { + switch(kt_globals.glsource) + { + case IMPULSIVE: + voice = impulsive_source(); + break; + case NATURAL: + voice = natural_source(); + break; + case SAMPLED: + voice = sampled_source(); + break; + } + + /* Reset period when counter 'nper' reaches T0 */ + if (kt_globals.nper >= kt_globals.T0) + { + kt_globals.nper = 0; + pitch_synch_par_reset(frame); + } + + /* + Low-pass filter voicing waveform before downsampling from 4*samrate + to samrate samples/sec. Resonator f=.09*samrate, bw=.06*samrate + */ + + voice = resonator(&(kt_globals.rsn[RLP]),voice); + + /* Increment counter that keeps track of 4*samrate samples per sec */ + kt_globals.nper++; + } + + /* + Tilt spectrum of voicing source down by soft low-pass filtering, amount + of tilt determined by TLTdb + */ + + voice = (voice * kt_globals.onemd) + (vlast * kt_globals.decay); + vlast = voice; + + /* + Add breathiness during glottal open phase. Amount of breathiness + determined by parameter Aturb Use nrand rather than noise because + noise is low-passed. + */ + + + if (kt_globals.nper < kt_globals.nopen) + { + voice += kt_globals.amp_breth * kt_globals.nrand; + } + + /* Set amplitude of voicing */ + glotout = kt_globals.amp_voice * voice; + par_glotout = kt_globals.par_amp_voice * voice; + + /* Compute aspiration amplitude and add to voicing source */ + aspiration = kt_globals.amp_aspir * noise; + glotout += aspiration; + + par_glotout += aspiration; + + /* + Cascade vocal tract, excited by laryngeal sources. + Nasal antiresonator, then formants FNP, F5, F4, F3, F2, F1 + */ + + out=0; + if(kt_globals.synthesis_model != ALL_PARALLEL) + { + casc_next_in = antiresonator2(&(kt_globals.rsn[Rnz]),glotout); + casc_next_in = resonator(&(kt_globals.rsn[Rnpc]),casc_next_in); + casc_next_in = resonator(&(kt_globals.rsn[R8c]),casc_next_in); + casc_next_in = resonator(&(kt_globals.rsn[R7c]),casc_next_in); + casc_next_in = resonator(&(kt_globals.rsn[R6c]),casc_next_in); + casc_next_in = resonator2(&(kt_globals.rsn[R5c]),casc_next_in); + casc_next_in = resonator2(&(kt_globals.rsn[R4c]),casc_next_in); + casc_next_in = resonator2(&(kt_globals.rsn[R3c]),casc_next_in); + casc_next_in = resonator2(&(kt_globals.rsn[R2c]),casc_next_in); + out = resonator2(&(kt_globals.rsn[R1c]),casc_next_in); + } + + /* Excite parallel F1 and FNP by voicing waveform */ + sourc = par_glotout; /* Source is voicing plus aspiration */ + + /* + Standard parallel vocal tract Formants F6,F5,F4,F3,F2, + outputs added with alternating sign. Sound source for other + parallel resonators is frication plus first difference of + voicing waveform. + */ + + out += resonator(&(kt_globals.rsn[R1p]),sourc); + out += resonator(&(kt_globals.rsn[Rnpp]),sourc); + + sourc = frics + par_glotout - glotlast; + glotlast = par_glotout; + + for(ix=R2p; ix<=R6p; ix++) + { + out = resonator(&(kt_globals.rsn[ix]),sourc) - out; + } + + outbypas = kt_globals.amp_bypas * sourc; + + out = outbypas - out; + +#ifdef deleted +// for testing + if (kt_globals.outsl != 0) + { + switch(kt_globals.outsl) + { + case 1: + out = voice; + break; + case 2: + out = aspiration; + break; + case 3: + out = frics; + break; + case 4: + out = glotout; + break; + case 5: + out = par_glotout; + break; + case 6: + out = outbypas; + break; + case 7: + out = sourc; + break; + } + } +#endif + + out = resonator(&(kt_globals.rsn[Rout]),out); + temp = (out * wdata.amplitude * kt_globals.amp_gain0) ; /* Convert back to integer */ + + + // mix with a recorded WAV if required for this phoneme + { + int z2; + signed char c; + int sample; + + z2 = 0; + if(wdata.mix_wavefile_ix < wdata.n_mix_wavefile) + { + if(wdata.mix_wave_scale == 0) + { + // a 16 bit sample + c = wdata.mix_wavefile[wdata.mix_wavefile_ix+1]; + sample = wdata.mix_wavefile[wdata.mix_wavefile_ix] + (c * 256); + wdata.mix_wavefile_ix += 2; + } + else + { + // a 8 bit sample, scaled + sample = (signed char)wdata.mix_wavefile[wdata.mix_wavefile_ix++] * wdata.mix_wave_scale; + } + z2 = sample * wdata.amplitude_v / 1024; + z2 = (z2 * wdata.mix_wave_amp)/40; + temp += z2; + } + } + + // if fadeout is set, fade to zero over 64 samples, to avoid clicks at end of synthesis + if(kt_globals.fadeout > 0) + { + kt_globals.fadeout--; + temp = (temp * kt_globals.fadeout) / 64; + } + + if (temp < -32768.0) + { + temp = -32768.0; + } + + if (temp > 32767.0) + { + temp = 32767.0; + } + + *out_ptr++ = int(temp); // **JSD + *out_ptr++ = int(temp) >> 8; + sample_count++; + if(out_ptr >= out_end) + { + return(1); + } + } + return(0); +} // end of parwave + + + + +/* +function PARWAVE_INIT + +Initialises all parameters used in parwave, sets resonator internal memory +to zero. +*/ + +static void reset_resonators() +{ + int r_ix; + + for(r_ix=0; r_ix < N_RSN; r_ix++) + { + kt_globals.rsn[r_ix].p1 = 0; + kt_globals.rsn[r_ix].p2 = 0; + } +} + +static void parwave_init() +{ + kt_globals.FLPhz = (950 * kt_globals.samrate) / 10000; + kt_globals.BLPhz = (630 * kt_globals.samrate) / 10000; + kt_globals.minus_pi_t = -PI / kt_globals.samrate; + kt_globals.two_pi_t = -2.0 * kt_globals.minus_pi_t; + setabc(kt_globals.FLPhz,kt_globals.BLPhz,&(kt_globals.rsn[RLP])); + kt_globals.nper = 0; + kt_globals.T0 = 0; + kt_globals.nopen = 0; + kt_globals.nmod = 0; + + reset_resonators(); +} + + +/* +function FRAME_INIT + +Use parameters from the input frame to set up resonator coefficients. +*/ + +static void frame_init(klatt_frame_ptr frame) +{ + double amp_par[7]; + static double amp_par_factor[7] = {0.6, 0.4, 0.15, 0.06, 0.04, 0.022, 0.03}; + long Gain0_tmp; + int ix; + + kt_globals.original_f0 = frame->F0hz10 / 10; + + frame->AVdb_tmp = frame->AVdb - 7; + if (frame->AVdb_tmp < 0) + { + frame->AVdb_tmp = 0; + } + + kt_globals.amp_aspir = DBtoLIN(frame->ASP) * 0.05; + kt_globals.amp_frica = DBtoLIN(frame->AF) * 0.25; + kt_globals.par_amp_voice = DBtoLIN(frame->AVpdb); + kt_globals.amp_bypas = DBtoLIN(frame->AB) * 0.05; + + for(ix=0; ix <= 6; ix++) + { + // parallel amplitudes F1 to F6, and parallel nasal pole + amp_par[ix] = DBtoLIN(frame->Ap[ix]) * amp_par_factor[ix]; + } + + Gain0_tmp = frame->Gain0 - 3; + if (Gain0_tmp <= 0) + { + Gain0_tmp = 57; + } + kt_globals.amp_gain0 = DBtoLIN(Gain0_tmp) / kt_globals.scale_wav; + + /* Set coefficients of variable cascade resonators */ + for(ix=0; ix<=8; ix++) + { + // formants 1 to 8, plus nasal pole + setabc(frame->Fhz[ix],frame->Bhz[ix],&(kt_globals.rsn[ix])); + + if(ix <= 5) + { + setabc(frame->Fhz_next[ix],frame->Bhz_next[ix],&(kt_globals.rsn_next[ix])); + + kt_globals.rsn[ix].a_inc = (kt_globals.rsn_next[ix].a - kt_globals.rsn[ix].a) / 64.0; + kt_globals.rsn[ix].b_inc = (kt_globals.rsn_next[ix].b - kt_globals.rsn[ix].b) / 64.0; + kt_globals.rsn[ix].c_inc = (kt_globals.rsn_next[ix].c - kt_globals.rsn[ix].c) / 64.0; + } + } + + // nasal zero anti-resonator + setzeroabc(frame->Fhz[F_NZ],frame->Bhz[F_NZ],&(kt_globals.rsn[Rnz])); + setzeroabc(frame->Fhz_next[F_NZ],frame->Bhz_next[F_NZ],&(kt_globals.rsn_next[Rnz])); + kt_globals.rsn[F_NZ].a_inc = (kt_globals.rsn_next[F_NZ].a - kt_globals.rsn[F_NZ].a) / 64.0; + kt_globals.rsn[F_NZ].b_inc = (kt_globals.rsn_next[F_NZ].b - kt_globals.rsn[F_NZ].b) / 64.0; + kt_globals.rsn[F_NZ].c_inc = (kt_globals.rsn_next[F_NZ].c - kt_globals.rsn[F_NZ].c) / 64.0; + + + /* Set coefficients of parallel resonators, and amplitude of outputs */ + + for(ix=0; ix<=6; ix++) + { + setabc(frame->Fhz[ix],frame->Bphz[ix],&(kt_globals.rsn[Rparallel+ix])); + kt_globals.rsn[Rparallel+ix].a *= amp_par[ix]; + } + + /* output low-pass filter */ + + setabc((long)0.0,(long)(kt_globals.samrate/2),&(kt_globals.rsn[Rout])); + +} + + + +/* +function IMPULSIVE_SOURCE + +Generate a low pass filtered train of impulses as an approximation of +a natural excitation waveform. Low-pass filter the differentiated impulse +with a critically-damped second-order filter, time constant proportional +to Kopen. +*/ + + +static double impulsive_source() +{ + static double doublet[] = {0.0,13000000.0,-13000000.0}; + static double vwave; + + if (kt_globals.nper < 3) + { + vwave = doublet[kt_globals.nper]; + } + else + { + vwave = 0.0; + } + + return(resonator(&(kt_globals.rsn[RGL]),vwave)); +} + + + +/* +function NATURAL_SOURCE + +Vwave is the differentiated glottal flow waveform, there is a weak +spectral zero around 800 Hz, magic constants a,b reset pitch synchronously. +*/ + +static double natural_source() +{ + double lgtemp; + static double vwave; + + if (kt_globals.nper < kt_globals.nopen) + { + kt_globals.pulse_shape_a -= kt_globals.pulse_shape_b; + vwave += kt_globals.pulse_shape_a; + lgtemp=vwave * 0.028; + + return(lgtemp); + } + else + { + vwave = 0.0; + return(0.0); + } +} + + + + + +/* +function PITCH_SYNC_PAR_RESET + +Reset selected parameters pitch-synchronously. + + +Constant B0 controls shape of glottal pulse as a function +of desired duration of open phase N0 +(Note that N0 is specified in terms of 40,000 samples/sec of speech) + +Assume voicing waveform V(t) has form: k1 t**2 - k2 t**3 + + If the radiation characterivative, a temporal derivative + is folded in, and we go from continuous time to discrete + integers n: dV/dt = vwave[n] + = sum over i=1,2,...,n of { a - (i * b) } + = a n - b/2 n**2 + + where the constants a and b control the detailed shape + and amplitude of the voicing waveform over the open + potion of the voicing cycle "nopen". + + Let integral of dV/dt have no net dc flow --> a = (b * nopen) / 3 + + Let maximum of dUg(n)/dn be constant --> b = gain / (nopen * nopen) + meaning as nopen gets bigger, V has bigger peak proportional to n + + Thus, to generate the table below for 40 <= nopen <= 263: + + B0[nopen - 40] = 1920000 / (nopen * nopen) +*/ + +static void pitch_synch_par_reset(klatt_frame_ptr frame) +{ + long temp; + double temp1; + static long skew; + static short B0[224] = + { + 1200,1142,1088,1038, 991, 948, 907, 869, 833, 799, 768, 738, 710, 683, 658, + 634, 612, 590, 570, 551, 533, 515, 499, 483, 468, 454, 440, 427, 415, 403, + 391, 380, 370, 360, 350, 341, 332, 323, 315, 307, 300, 292, 285, 278, 272, + 265, 259, 253, 247, 242, 237, 231, 226, 221, 217, 212, 208, 204, 199, 195, + 192, 188, 184, 180, 177, 174, 170, 167, 164, 161, 158, 155, 153, 150, 147, + 145, 142, 140, 137, 135, 133, 131, 128, 126, 124, 122, 120, 119, 117, 115, + 113,111, 110, 108, 106, 105, 103, 102, 100, 99, 97, 96, 95, 93, 92, 91, 90, + 88, 87, 86, 85, 84, 83, 82, 80, 79, 78, 77, 76, 75, 75, 74, 73, 72, 71, + 70, 69, 68, 68, 67, 66, 65, 64, 64, 63, 62, 61, 61, 60, 59, 59, 58, 57, + 57, 56, 56, 55, 55, 54, 54, 53, 53, 52, 52, 51, 51, 50, 50, 49, 49, 48, 48, + 47, 47, 46, 46, 45, 45, 44, 44, 43, 43, 42, 42, 41, 41, 41, 41, 40, 40, + 39, 39, 38, 38, 38, 38, 37, 37, 36, 36, 36, 36, 35, 35, 35, 35, 34, 34,33, + 33, 33, 33, 32, 32, 32, 32, 31, 31, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, + 28, 28, 28, 28, 27, 27 + }; + + if (frame->F0hz10 > 0) + { + /* T0 is 4* the number of samples in one pitch period */ + + kt_globals.T0 = (40 * kt_globals.samrate) / frame->F0hz10; + + + kt_globals.amp_voice = DBtoLIN(frame->AVdb_tmp); + + /* Duration of period before amplitude modulation */ + + kt_globals.nmod = kt_globals.T0; + if (frame->AVdb_tmp > 0) + { + kt_globals.nmod >>= 1; + } + + /* Breathiness of voicing waveform */ + + kt_globals.amp_breth = DBtoLIN(frame->Aturb) * 0.1; + + /* Set open phase of glottal period where 40 <= open phase <= 263 */ + + kt_globals.nopen = 4 * frame->Kopen; + + if ((kt_globals.glsource == IMPULSIVE) && (kt_globals.nopen > 263)) + { + kt_globals.nopen = 263; + } + + if (kt_globals.nopen >= (kt_globals.T0-1)) + { +// printf("Warning: glottal open period cannot exceed T0, truncated\n"); + kt_globals.nopen = kt_globals.T0 - 2; + } + + if (kt_globals.nopen < 40) + { + /* F0 max = 1000 Hz */ +// printf("Warning: minimum glottal open period is 10 samples.\n"); +// printf("truncated, nopen = %d\n",kt_globals.nopen); + kt_globals.nopen = 40; + } + + + /* Reset a & b, which determine shape of "natural" glottal waveform */ + + kt_globals.pulse_shape_b = B0[kt_globals.nopen-40]; + kt_globals.pulse_shape_a = (kt_globals.pulse_shape_b * kt_globals.nopen) * 0.333; + + /* Reset width of "impulsive" glottal pulse */ + + temp = kt_globals.samrate / kt_globals.nopen; + + setabc((long)0,temp,&(kt_globals.rsn[RGL])); + + /* Make gain at F1 about constant */ + + temp1 = kt_globals.nopen *.00833; + kt_globals.rsn[RGL].a *= temp1 * temp1; + + /* + Truncate skewness so as not to exceed duration of closed phase + of glottal period. + */ + + + temp = kt_globals.T0 - kt_globals.nopen; + if (frame->Kskew > temp) + { +// printf("Kskew duration=%d > glottal closed period=%d, truncate\n", frame->Kskew, kt_globals.T0 - kt_globals.nopen); + frame->Kskew = temp; + } + if (skew >= 0) + { + skew = frame->Kskew; + } + else + { + skew = - frame->Kskew; + } + + /* Add skewness to closed portion of voicing period */ + kt_globals.T0 = kt_globals.T0 + skew; + skew = - skew; + } + else + { + kt_globals.T0 = 4; /* Default for f0 undefined */ + kt_globals.amp_voice = 0.0; + kt_globals.nmod = kt_globals.T0; + kt_globals.amp_breth = 0.0; + kt_globals.pulse_shape_a = 0.0; + kt_globals.pulse_shape_b = 0.0; + } + + /* Reset these pars pitch synchronously or at update rate if f0=0 */ + + if ((kt_globals.T0 != 4) || (kt_globals.ns == 0)) + { + /* Set one-pole low-pass filter that tilts glottal source */ + + kt_globals.decay = (0.033 * frame->TLTdb); + + if (kt_globals.decay > 0.0) + { + kt_globals.onemd = 1.0 - kt_globals.decay; + } + else + { + kt_globals.onemd = 1.0; + } + } +} + + + +/* +function SETABC + +Convert formant freqencies and bandwidth into resonator difference +equation constants. +*/ + + +static void setabc(long int f, long int bw, resonator_ptr rp) +{ + double r; + double arg; + + /* Let r = exp(-pi bw t) */ + arg = kt_globals.minus_pi_t * bw; + r = exp(arg); + + /* Let c = -r**2 */ + rp->c = -(r * r); + + /* Let b = r * 2*cos(2 pi f t) */ + arg = kt_globals.two_pi_t * f; + rp->b = r * cos(arg) * 2.0; + + /* Let a = 1.0 - b - c */ + rp->a = 1.0 - rp->b - rp->c; +} + + +/* +function SETZEROABC + +Convert formant freqencies and bandwidth into anti-resonator difference +equation constants. +*/ + +static void setzeroabc(long int f, long int bw, resonator_ptr rp) +{ + double r; + double arg; + + f = -f; + + if(f>=0) + { + f = -1; + } + + /* First compute ordinary resonator coefficients */ + /* Let r = exp(-pi bw t) */ + arg = kt_globals.minus_pi_t * bw; + r = exp(arg); + + /* Let c = -r**2 */ + rp->c = -(r * r); + + /* Let b = r * 2*cos(2 pi f t) */ + arg = kt_globals.two_pi_t * f; + rp->b = r * cos(arg) * 2.; + + /* Let a = 1.0 - b - c */ + rp->a = 1.0 - rp->b - rp->c; + + /* Now convert to antiresonator coefficients (a'=1/a, b'=b/a, c'=c/a) */ + rp->a = 1.0 / rp->a; + rp->c *= -rp->a; + rp->b *= -rp->a; +} + + +/* +function GEN_NOISE + +Random number generator (return a number between -8191 and +8191) +Noise spectrum is tilted down by soft low-pass filter having a pole near +the origin in the z-plane, i.e. output = input + (0.75 * lastoutput) +*/ + + +static double gen_noise(double noise) +{ + long temp; + static double nlast; + + temp = (long) getrandom(-8191,8191); + kt_globals.nrand = (long) temp; + + noise = kt_globals.nrand + (0.75 * nlast); + nlast = noise; + + return(noise); +} + + +/* +function DBTOLIN + +Convert from decibels to a linear scale factor + + +Conversion table, db to linear, 87 dB --> 32767 + 86 dB --> 29491 (1 dB down = 0.5**1/6) + ... + 81 dB --> 16384 (6 dB down = 0.5) + ... + 0 dB --> 0 + +The just noticeable difference for a change in intensity of a vowel +is approximately 1 dB. Thus all amplitudes are quantized to 1 dB +steps. +*/ + + +static double DBtoLIN(long dB) +{ + static short amptable[88] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, + 8, 9, 10, 11, 13, 14, 16, 18, 20, 22, 25, 28, 32, + 35, 40, 45, 51, 57, 64, 71, 80, 90, 101, 114, 128, + 142, 159, 179, 202, 227, 256, 284, 318, 359, 405, + 455, 512, 568, 638, 719, 881, 911, 1024, 1137, 1276, + 1438, 1622, 1823, 2048, 2273, 2552, 2875, 3244, 3645, + 4096, 4547, 5104, 5751, 6488, 7291, 8192, 9093, 10207, + 11502, 12976, 14582, 16384, 18350, 20644, 23429, + 26214, 29491, 32767 }; + + if ((dB < 0) || (dB > 87)) + { + return(0); + } + + return(double(amptable[dB]) * 0.001); +} + + + + + +extern voice_t *wvoice; +static wavegen_peaks_t peaks[N_PEAKS]; +static int end_wave; +static int klattp[N_KLATTP]; +static double klattp1[N_KLATTP]; +static double klattp_inc[N_KLATTP]; + +static int scale_wav_tab[] = {45,38,45,45}; // scale output from different voicing sources + + + +int Wavegen_Klatt(int resume) +{//========================== + int pk; + int x; + int ix; + + if(resume==0) + { + sample_count = 0; + } + + while(sample_count < nsamples) + { + kt_frame.F0hz10 = (wdata.pitch * 10) / 4096; + + kt_frame.Fhz[F_NP] = peaks[0].freq; + kt_frame.Fhz[F_NZ] = peaks[0].left; + + // formants F6,F7,F8 are fixed values for cascade resonators, set in KlattInit() + // but F6 is used for parallel resonator + for(ix=1; ix<=6; ix++) + { + if(ix < 6) + { + kt_frame.Fhz[ix] = peaks[ix].freq; + kt_frame.Bhz[ix] = peaks[ix].height; + } + kt_frame.Bphz[ix] = peaks[ix].left; + kt_frame.Ap[ix] = peaks[ix].right; + } + + kt_frame.AVdb = klattp[KLATT_AV]; + kt_frame.AVpdb = klattp[KLATT_AVp]; + kt_frame.AF = klattp[KLATT_Fric]; + kt_frame.AB = klattp[KLATT_FricBP]; + kt_frame.ASP = klattp[KLATT_Aspr]; + kt_frame.Aturb = klattp[KLATT_Turb]; + kt_frame.Kskew = klattp[KLATT_Skew]; + kt_frame.TLTdb = klattp[KLATT_Tilt]; + kt_frame.Kopen = klattp[KLATT_Kopen]; + + // advance formants + for(pk=0; pk<N_PEAKS; pk++) + { + peaks[pk].freq1 += peaks[pk].freq_inc; + peaks[pk].freq = (int)peaks[pk].freq1; + peaks[pk].height1 += peaks[pk].height_inc; + peaks[pk].height = (int)peaks[pk].height1; + peaks[pk].left1 += peaks[pk].left_inc; + peaks[pk].left = (int)peaks[pk].left1; + peaks[pk].right1 += peaks[pk].right_inc; + peaks[pk].right = (int)peaks[pk].right1; + } + + for(ix=1; ix<=6; ix++) + { + kt_frame.Fhz_next[ix] = peaks[ix].freq; + kt_frame.Bhz_next[ix] = peaks[ix].height; + } + kt_frame.Fhz_next[F_NZ] = peaks[0].left; + + for(ix=0; ix < N_KLATTP; ix++) + { + klattp1[ix] += klattp_inc[ix]; + klattp[ix] = int(klattp1[ix]); + } + + // advance the pitch + wdata.pitch_ix += wdata.pitch_inc; + if((ix = wdata.pitch_ix>>8) > 127) ix = 127; + x = wdata.pitch_env[ix] * wdata.pitch_range; + wdata.pitch = (x>>8) + wdata.pitch_base; + + kt_globals.nspfr = (nsamples - sample_count); + if(kt_globals.nspfr > STEPSIZE) + kt_globals.nspfr = STEPSIZE; + + if(parwave(&kt_frame) == 1) + { + return(1); + } + } + + if(end_wave == 1) + { + // fade out to avoid a click + kt_globals.fadeout = 64; + end_wave = 0; + sample_count -= 64; + kt_globals.nspfr = 64; + if(parwave(&kt_frame) == 1) + { + return(1); + } + } + + return(0); +} + + +void SetSynth_Klatt(int length, int modn, frame_t *fr1, frame_t *fr2, voice_t *v, int control) +{//=========================================================================================== + int ix; + DOUBLEX next; + int qix; + int cmd; + static frame_t prev_fr; + + if(wvoice != NULL) + { + if((wvoice->klatt[0] > 0) && (wvoice->klatt[0] <=3 )) + { + kt_globals.glsource = wvoice->klatt[0]; + kt_globals.scale_wav = scale_wav_tab[kt_globals.glsource]; + } + kt_globals.f0_flutter = wvoice->flutter/32; + } + + end_wave = 0; + if(control & 2) + { + end_wave = 1; // fadeout at the end + } + if(control & 1) + { + end_wave = 1; + for(qix=wcmdq_head+1;;qix++) + { + if(qix >= N_WCMDQ) qix = 0; + if(qix == wcmdq_tail) break; + + cmd = wcmdq[qix][0]; + if(cmd==WCMD_KLATT) + { + end_wave = 0; // next wave generation is from another spectrum + break; + } + if((cmd==WCMD_WAVE) || (cmd==WCMD_PAUSE)) + break; // next is not from spectrum, so continue until end of wave cycle + } + } + +{ +//FILE *f; +//f=fopen("klatt_log","a"); +//fprintf(f,"len %4d (%3d %4d %4d) (%3d %4d %4d)\n",length,fr1->ffreq[1],fr1->ffreq[2],fr1->ffreq[3],fr2->ffreq[1],fr2->ffreq[2],fr2->ffreq[3]); +//fclose(f); +} + + if(control & 1) + { + if(wdata.prev_was_synth == 0) + { + // A break, not following on from another synthesized sound. + // Reset the synthesizer + //reset_resonators(&kt_globals); + parwave_init(); + } + else + { + if((prev_fr.ffreq[1] != fr1->ffreq[1]) || (prev_fr.ffreq[2] != fr1->ffreq[2])) + { + + // fade out to avoid a click, but only up to the end of output buffer + ix = (out_end - out_ptr)/2; + if(ix > 64) + ix = 64; + kt_globals.fadeout = ix; + kt_globals.nspfr = ix; + parwave(&kt_frame); + + //reset_resonators(&kt_globals); + parwave_init(); + } + } + wdata.prev_was_synth = 1; + memcpy(&prev_fr,fr2,sizeof(prev_fr)); + } + if(fr2->frflags & FRFLAG_BREAK) + { +// fr2 = fr1; +// reset_resonators(&kt_globals); + } + + for(ix=0; ix<N_KLATTP; ix++) + { + klattp1[ix] = klattp[ix] = fr1->klattp[ix]; + klattp_inc[ix] = double((fr2->klattp[ix] - klattp[ix]) * STEPSIZE)/length; + + if((ix>0) && (ix < KLATT_AVp)) + klattp1[ix] = klattp[ix] = (klattp[ix] + wvoice->klatt[ix]); + } + + nsamples = length; + + for(ix=0; ix<N_PEAKS; ix++) + { + peaks[ix].freq1 = (fr1->ffreq[ix] * v->freq[ix] / 256.0) + v->freqadd[ix]; + peaks[ix].freq = int(peaks[ix].freq1); + next = (fr2->ffreq[ix] * v->freq[ix] / 256.0) + v->freqadd[ix]; + peaks[ix].freq_inc = ((next - peaks[ix].freq1) * STEPSIZE) / length; // lower headroom for fixed point math + + peaks[ix].height1 = fr1->fheight[ix] * 2; + peaks[ix].height = int(peaks[ix].height1); + next = fr2->fheight[ix] * 2; + peaks[ix].height_inc = ((next - peaks[ix].height1) * STEPSIZE) / length; + + if(ix < 6) + { + peaks[ix].left1 = fr1->fwidth[ix] * 2; + peaks[ix].left = int(peaks[ix].left1); + next = fr2->fwidth[ix] * 2; + peaks[ix].left_inc = ((next - peaks[ix].left1) * STEPSIZE) / length; + + peaks[ix].right1 = fr1->fright[ix]; + peaks[ix].right = int(peaks[ix].right1); + next = fr2->fright[ix]; + peaks[ix].right_inc = ((next - peaks[ix].right1) * STEPSIZE) / length; + } + peaks[6].left1 = fr1->fwidth6 * 2; + peaks[6].left = int(peaks[6].left1); + next = fr2->fwidth6 * 2; + peaks[6].left_inc = ((next - peaks[6].left1) * STEPSIZE) / length; + + peaks[6].right1 = fr1->fright6; + peaks[6].right = int(peaks[6].right1); + next = fr2->fright6; + peaks[6].right_inc = ((next - peaks[6].right1) * STEPSIZE) / length; + } +} // end of SetSynth_Klatt + + +int Wavegen_Klatt2(int length, int modulation, int resume, frame_t *fr1, frame_t *fr2) +{//=================================================================================== + if(resume==0) + SetSynth_Klatt(length, modulation, fr1, fr2, wvoice, 1); + + return(Wavegen_Klatt(resume)); +} + + + +void KlattInit() +{ +#define NUMBER_OF_SAMPLES 100 + + static short natural_samples[NUMBER_OF_SAMPLES]= + { + -310,-400,530,356,224,89,23,-10,-58,-16,461,599,536,701,770, + 605,497,461,560,404,110,224,131,104,-97,155,278,-154,-1165, + -598,737,125,-592,41,11,-247,-10,65,92,80,-304,71,167,-1,122, + 233,161,-43,278,479,485,407,266,650,134,80,236,68,260,269,179, + 53,140,275,293,296,104,257,152,311,182,263,245,125,314,140,44, + 203,230,-235,-286,23,107,92,-91,38,464,443,176,98,-784,-2449, + -1891,-1045,-1600,-1462,-1384,-1261,-949,-730 + }; + static short formant_hz[10] = {280,688,1064,2806,3260,3700,6500,7000,8000,280}; + static short bandwidth[10] = {89,160,70,160,200,200,500,500,500,89}; + static short parallel_amp[10] = { 0,59,59,59,59,59,59,0,0,0}; + static short parallel_bw[10] = {59,59,89,149,200,200,500,0,0,0}; + + int ix; + + sample_count=0; + + kt_globals.synthesis_model = CASCADE_PARALLEL; + kt_globals.samrate = 22050; + + kt_globals.glsource = IMPULSIVE; // IMPULSIVE, NATURAL, SAMPLED + kt_globals.scale_wav = scale_wav_tab[kt_globals.glsource]; + kt_globals.natural_samples = natural_samples; + kt_globals.num_samples = NUMBER_OF_SAMPLES; + kt_globals.sample_factor = 3.0; + kt_globals.nspfr = (kt_globals.samrate * 10) / 1000; + kt_globals.outsl = 0; + kt_globals.f0_flutter = 20; + + parwave_init(); + + // set default values for frame parameters + for(ix=0; ix<=9; ix++) + { + kt_frame.Fhz[ix] = formant_hz[ix]; + kt_frame.Bhz[ix] = bandwidth[ix]; + kt_frame.Ap[ix] = parallel_amp[ix]; + kt_frame.Bphz[ix] = parallel_bw[ix]; + } + kt_frame.Bhz_next[F_NZ] = bandwidth[F_NZ]; + + kt_frame.F0hz10 = 1000; + kt_frame.AVdb = 59; // 59 + kt_frame.ASP = 0; + kt_frame.Kopen = 40; // 40 + kt_frame.Aturb = 0; + kt_frame.TLTdb = 0; + kt_frame.AF =50; + kt_frame.Kskew = 0; + kt_frame.AB = 0; + kt_frame.AVpdb = 0; + kt_frame.Gain0 = 62; +} // end of KlattInit + +#endif // INCLUDE_KLATT diff --git a/Plugins/eSpeak/eSpeak/klatt.h b/Plugins/eSpeak/eSpeak/klatt.h new file mode 100644 index 0000000..812385e --- /dev/null +++ b/Plugins/eSpeak/eSpeak/klatt.h @@ -0,0 +1,138 @@ + + +#define CASCADE_PARALLEL 1 /* Type of synthesis model */ +#define ALL_PARALLEL 2 + +#define IMPULSIVE 1 /* Type of voicing source */ +#define NATURAL 2 +#define SAMPLED 3 + +#define PI 3.1415927 + + +/* typedef's that need to be exported */ + +typedef long flag; + +/* Resonator Structure */ + +typedef struct +{ + double a; + double b; + double c; + double p1; + double p2; + double a_inc; + double b_inc; + double c_inc; +} resonator_t, *resonator_ptr; + +/* Structure for Klatt Globals */ + +typedef struct +{ + flag synthesis_model; /* cascade-parallel or all-parallel */ + flag outsl; /* Output waveform selector */ + long samrate; /* Number of output samples per second */ + long FLPhz ; /* Frequeny of glottal downsample low-pass filter */ + long BLPhz ; /* Bandwidth of glottal downsample low-pass filter */ + flag glsource; /* Type of glottal source */ + int f0_flutter; /* Percentage of f0 flutter 0-100 */ + long nspfr; /* number of samples per frame */ + long nper; /* Counter for number of samples in a pitch period */ + long ns; + long T0; /* Fundamental period in output samples times 4 */ + long nopen; /* Number of samples in open phase of period */ + long nmod; /* Position in period to begin noise amp. modul */ + long nrand; /* Varible used by random number generator */ + double pulse_shape_a; /* Makes waveshape of glottal pulse when open */ + double pulse_shape_b; /* Makes waveshape of glottal pulse when open */ + double minus_pi_t; + double two_pi_t; + double onemd; + double decay; + double amp_bypas; /* AB converted to linear gain */ + double amp_voice; /* AVdb converted to linear gain */ + double par_amp_voice; /* AVpdb converted to linear gain */ + double amp_aspir; /* AP converted to linear gain */ + double amp_frica; /* AF converted to linear gain */ + double amp_breth; /* ATURB converted to linear gain */ + double amp_gain0; /* G0 converted to linear gain */ + int num_samples; /* number of glottal samples */ + double sample_factor; /* multiplication factor for glottal samples */ + short *natural_samples; /* pointer to an array of glottal samples */ + long original_f0; /* original value of f0 not modified by flutter */ + + int fadeout; // set to 64 to cause fadeout over 64 samples + int scale_wav; // depends on the voicing source + +#define N_RSN 20 +#define Rnpc 0 +#define R1c 1 +#define R2c 2 +#define R3c 3 +#define R4c 4 +#define R5c 5 +#define R6c 6 +#define R7c 7 +#define R8c 8 +#define Rnz 9 + +#define Rparallel 10 +#define Rnpp 10 +#define R1p 11 +#define R2p 12 +#define R3p 13 +#define R4p 14 +#define R5p 15 +#define R6p 16 + +#define RGL 17 +#define RLP 18 +#define Rout 19 + + resonator_t rsn[N_RSN]; // internal storage for resonators + resonator_t rsn_next[N_RSN]; + +} klatt_global_t, *klatt_global_ptr; + +/* Structure for Klatt Parameters */ + +#define F_NP 0 // nasal zero formant +#define F1 1 +#define F2 2 +#define F3 3 +#define F4 4 +#define F5 5 +#define F6 6 +#define F_NZ 9 // nasal pole formant + + +typedef struct +{ + long F0hz10; /* Voicing fund freq in Hz */ + long AVdb; /* Amp of voicing in dB, 0 to 70 */ + int Fhz[10]; // formant Hz, F_NZ to F6 to F_NP + int Bhz[10]; + int Ap[10]; /* Amp of parallel formants in dB, 0 to 80 */ + int Bphz[10]; /* Parallel formants bw in Hz, 40 to 1000 */ + + long ASP; /* Amp of aspiration in dB, 0 to 70 */ + long Kopen; /* # of samples in open period, 10 to 65 */ + long Aturb; /* Breathiness in voicing, 0 to 80 */ + long TLTdb; /* Voicing spectral tilt in dB, 0 to 24 */ + long AF; /* Amp of frication in dB, 0 to 80 */ + long Kskew; /* Skewness of alternate periods, 0 to 40 in sample#/2 */ + + long AB; /* Amp of bypass fric. in dB, 0 to 80 */ + long AVpdb; /* Amp of voicing, par in dB, 0 to 70 */ + long Gain0; /* Overall gain, 60 dB is unity, 0 to 60 */ + + long AVdb_tmp; //copy of AVdb, which is changed within parwave() + int Fhz_next[10]; // Fhz for the next chunk, so we can do interpolation of resonator (a,b,c) parameters + int Bhz_next[10]; + } klatt_frame_t, *klatt_frame_ptr; + + + diff --git a/Plugins/eSpeak/eSpeak/mbrolib.h b/Plugins/eSpeak/eSpeak/mbrolib.h new file mode 100644 index 0000000..0616b46 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/mbrolib.h @@ -0,0 +1,205 @@ +#ifndef MBROLIB_H +#define MBROLIB_H + +/* + * mbrolib: mbrola wrapper. + * + * Copyright (C) 2007 Gilles Casse <gcasse@oralux.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* < types */ + +/** Parameters */ + +typedef struct { + int ignore_error; /* 1=Ignore any fatal error or unknown diphone */ + char comment_char; /* Comment character */ + float volume_ratio; /* Volume ratio */ + float frequency_ratio; /* Applied to pitch points */ + float time_ratio; /* Applied to phone durations */ +} mbrolib_parameter; + + +/** Returned errors */ + +typedef enum { + MBROLIB_OK=0, + MBROLIB_DATABASE_NOT_INSTALLED, + MBROLIB_INVAL, + MBROLIB_OUT_OF_MEMORY, + MBROLIB_OUT_OF_RANGE, + MBROLIB_READ_ERROR, + MBROLIB_WRITE_ERROR +} MBROLIB_ERROR; + + + +/** Gender */ + +typedef enum { + MBROLIB_FEMALE, + MBROLIB_MALE +} MBROLIB_GENDER; + + + +/** Voice descriptor */ + +typedef struct { + char *name; /* name (for example: "en1") */ + char *filename; /* database pathname (for example: "/usr/share/mbrola/voices/en1) */ + int rate; /* database sample rate */ + MBROLIB_GENDER gender; + const char *language; /* Language and optional dialect qualifier in ascii (e.g. en, fr_ca). */ +} mbrolib_voice; + +/* > */ + + +/** Initialization, returns a new handle. + First function. + + @param the_sample_rate: output rate in Hz (for example 22050). If 0, keep the original database rate. + + @return handle (or NULL if error). +*/ +void* mbrolib_init( int sample_rate); +typedef void* (t_mbrolib_init)(int); + + +/** Returns the list of the installed mbrola databases. + The databases are searched according to the MBROLA_PATH environment variable if set, + or under a default path otherwise (see MBROLA_PATH in mbrolib.c). + + An array of voices is returned. The last item is set to NULL. + The caller must not free the returned items or the array. + + @param the_handle previously given by mbrolib_init. + + @return An array of voices. +*/ +const mbrolib_voice ** mbrolib_list_voices( void* the_handle); +typedef const mbrolib_voice ** (t_mbrolib_list_voices)(void*); + + + +/** Set voice + + @param the_handle. + + @param the_database (for example, "en1"). + + @return error code (MBROLIB_OK, MBROLIB_DATABASE_NOT_INSTALLED, MBROLIB_INVAL). + +*/ +MBROLIB_ERROR mbrolib_set_voice( void* the_handle, const char* the_name); +typedef MBROLIB_ERROR (t_mbrolib_set_voice)( void*, const char*); + + + +/** Get the current database parameters. + The caller supplies a pointer to an already allocated structure. + + @param the_handle previously given by mbrolib_init. + + @param the_parameters: pointer to the structure. + + @return error code (MBROLIB_OK, MBROLIB_INVAL). +*/ +MBROLIB_ERROR mbrolib_get_parameter(void* the_handle, mbrolib_parameter* the_parameter); +typedef MBROLIB_ERROR (t_mbrolib_get_parameter)(void*, mbrolib_parameter*); + + + +/** Set the database parameters using the supplied data. + + @param the_handle previously given by mbrolib_init. + + @param the_parameters: pointer to the wished parameters. + + @return error code (MBROLIB_OK, MBROLIB_INVAL). +*/ +MBROLIB_ERROR mbrolib_set_parameter(void* the_handle, const mbrolib_parameter* the_parameter); +typedef MBROLIB_ERROR (t_mbrolib_set_parameter)(void*, const mbrolib_parameter*); + + + +/** Write the mbrola phonemes in the internal buffer. + + @param the_handle. + + @param the_mbrola_phonemes. + + @param the_size in bytes. + + @return error code (MBROLIB_OK, MBROLIB_INVAL, MBROLIB_WRITE_ERROR, MBROLIB_READ_ERROR). +*/ +MBROLIB_ERROR mbrolib_write(void* the_handle, const char* the_mbrola_phonemes, size_t the_size); +typedef MBROLIB_ERROR (t_mbrolib_write)(void*, const char*, size_t); + + + +/** Read n bytes of the output samples. + + @param the_handle. + + @param the_samples (raw audio data, 16bits, mono). + + @param the_size max number of int16 to read. + + @param the_size number of int16 read. + + @return error code (MBROLIB_OK, MBROLIB_INVAL, MBROLIB_READ_ERROR). + +*/ +MBROLIB_ERROR mbrolib_read(void* the_handle, short* the_samples, int the_max_size, int* the_read_size); +typedef MBROLIB_ERROR (t_mbrolib_read)(void*, short*, int, int*); + + + +/** Flush + + @param the_handle. + +*/ +void mbrolib_flush(void* the_handle); +typedef void (t_mbrolib_flush)(void*); + + + +/** Release the handle + + @param the_handle. + + @return error code (MBROLIB_OK, MBROLIB_INVAL). + +*/ +MBROLIB_ERROR mbrolib_terminate(void* the_handle); +typedef MBROLIB_ERROR (t_mbrolib_terminate)(void*); + + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Plugins/eSpeak/eSpeak/numbers.cpp b/Plugins/eSpeak/eSpeak/numbers.cpp new file mode 100644 index 0000000..2b0cfd6 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/numbers.cpp @@ -0,0 +1,1401 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "StdAfx.h" + +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include <wctype.h> +#include <wchar.h> + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "voice.h" +#include "translate.h" + + + +#define M_NAME 0 +#define M_SMALLCAP 1 +#define M_TURNED 2 +#define M_REVERSED 3 +#define M_CURL 4 + +#define M_ACUTE 5 +#define M_BREVE 6 +#define M_CARON 7 +#define M_CEDILLA 8 +#define M_CIRCUMFLEX 9 +#define M_DIAERESIS 10 +#define M_DOUBLE_ACUTE 11 +#define M_DOT_ABOVE 12 +#define M_GRAVE 13 +#define M_MACRON 14 +#define M_OGONEK 15 +#define M_RING 16 +#define M_STROKE 17 +#define M_TILDE 18 + +#define M_BAR 19 +#define M_RETROFLEX 20 +#define M_HOOK 21 + + +#define M_MIDDLE_DOT M_DOT_ABOVE // duplicate of M_DOT_ABOVE +#define M_IMPLOSIVE M_HOOK + +typedef struct { +const char *name; +int flags; +} ACCENTS; + +// these are tokens to look up in the *_list file. +static ACCENTS accents_tab[] = { +{"_lig", 1}, +{"_smc", 1}, // smallcap +{"_tur", 1}, // turned +{"_rev", 1}, // reversed +{"_crl", 0}, // curl + +{"_acu", 0}, // acute +{"_brv", 0}, // breve +{"_hac", 0}, // caron/hacek +{"_ced", 0}, // cedilla +{"_cir", 0}, // circumflex +{"_dia", 0}, // diaeresis +{"_ac2", 0}, // double acute +{"_dot", 0}, // dot +{"_grv", 0}, // grave +{"_mcn", 0}, // macron +{"_ogo", 0}, // ogonek +{"_rng", 0}, // ring +{"_stk", 0}, // stroke +{"_tld", 0}, // tilde + +{"_bar", 0}, // bar +{"_rfx", 0}, // retroflex +{"_hok", 0}, // hook +}; + + +#define CAPITAL 0 +#define LETTER(ch,mod1,mod2) (ch-59)+(mod1 << 6)+(mod2 << 11) +#define LIGATURE(ch1,ch2,mod1) (ch1-59)+((ch2-59) << 6)+(mod1 << 12)+0x8000 + + +#define L_ALPHA 60 // U+3B1 +#define L_SCHWA 61 // U+259 +#define L_OPEN_E 62 // U+25B +#define L_GAMMA 63 // U+3B3 +#define L_IOTA 64 // U+3B9 +#define L_OE 65 // U+153 +#define L_OMEGA 66 // U+3C9 + +#define L_PHI 67 // U+3C6 +#define L_ESH 68 // U+283 +#define L_UPSILON 69 // U+3C5 +#define L_EZH 70 // U+292 +#define L_GLOTTAL 71 // U+294 +#define L_RTAP 72 // U+27E + + +static const short non_ascii_tab[] = { + 0, 0x3b1, 0x259, 0x25b, 0x3b3, 0x3b9, 0x153, 0x3c9, +0x3c6, 0x283, 0x3c5, 0x292, 0x294, 0x27e }; + + +// characters U+00e0 to U+017f +static const unsigned short letter_accents_0e0[] = { +LETTER('a',M_GRAVE,0), // U+00e0 +LETTER('a',M_ACUTE,0), +LETTER('a',M_CIRCUMFLEX,0), +LETTER('a',M_TILDE,0), +LETTER('a',M_DIAERESIS,0), +LETTER('a',M_RING,0), +LIGATURE('a','e',0), +LETTER('c',M_CEDILLA,0), +LETTER('e',M_GRAVE,0), +LETTER('e',M_ACUTE,0), +LETTER('e',M_CIRCUMFLEX,0), +LETTER('e',M_DIAERESIS,0), +LETTER('i',M_GRAVE,0), +LETTER('i',M_ACUTE,0), +LETTER('i',M_CIRCUMFLEX,0), +LETTER('i',M_DIAERESIS,0), +LETTER('d',M_NAME,0), // eth // U+00f0 +LETTER('n',M_TILDE,0), +LETTER('o',M_GRAVE,0), +LETTER('o',M_ACUTE,0), +LETTER('o',M_CIRCUMFLEX,0), +LETTER('o',M_TILDE,0), +LETTER('o',M_DIAERESIS,0), +0, // division sign +LETTER('o',M_STROKE,0), +LETTER('u',M_GRAVE,0), +LETTER('u',M_ACUTE,0), +LETTER('u',M_CIRCUMFLEX,0), +LETTER('u',M_DIAERESIS,0), +LETTER('y',M_ACUTE,0), +LETTER('t',M_NAME,0), // thorn +LETTER('y',M_DIAERESIS,0), +CAPITAL, // U+0100 +LETTER('a',M_MACRON,0), +CAPITAL, +LETTER('a',M_BREVE,0), +CAPITAL, +LETTER('a',M_OGONEK,0), +CAPITAL, +LETTER('c',M_ACUTE,0), +CAPITAL, +LETTER('c',M_CIRCUMFLEX,0), +CAPITAL, +LETTER('c',M_DOT_ABOVE,0), +CAPITAL, +LETTER('c',M_CARON,0), +CAPITAL, +LETTER('d',M_CARON,0), +CAPITAL, // U+0110 +LETTER('d',M_STROKE,0), +CAPITAL, +LETTER('e',M_MACRON,0), +CAPITAL, +LETTER('e',M_BREVE,0), +CAPITAL, +LETTER('e',M_DOT_ABOVE,0), +CAPITAL, +LETTER('e',M_OGONEK,0), +CAPITAL, +LETTER('e',M_CARON,0), +CAPITAL, +LETTER('g',M_CIRCUMFLEX,0), +CAPITAL, +LETTER('g',M_BREVE,0), +CAPITAL, // U+0120 +LETTER('g',M_DOT_ABOVE,0), +CAPITAL, +LETTER('g',M_CEDILLA,0), +CAPITAL, +LETTER('h',M_CIRCUMFLEX,0), +CAPITAL, +LETTER('h',M_STROKE,0), +CAPITAL, +LETTER('i',M_TILDE,0), +CAPITAL, +LETTER('i',M_MACRON,0), +CAPITAL, +LETTER('i',M_BREVE,0), +CAPITAL, +LETTER('i',M_OGONEK,0), +CAPITAL, // U+0130 +LETTER('i',M_NAME,0), // dotless i +CAPITAL, +LIGATURE('i','j',0), +CAPITAL, +LETTER('j',M_CIRCUMFLEX,0), +CAPITAL, +LETTER('k',M_CEDILLA,0), +LETTER('k',M_NAME,0), // kra +CAPITAL, +LETTER('l',M_ACUTE,0), +CAPITAL, +LETTER('l',M_CEDILLA,0), +CAPITAL, +LETTER('l',M_CARON,0), +CAPITAL, +LETTER('l',M_MIDDLE_DOT,0), // U+0140 +CAPITAL, +LETTER('l',M_STROKE,0), +CAPITAL, +LETTER('n',M_ACUTE,0), +CAPITAL, +LETTER('n',M_CEDILLA,0), +CAPITAL, +LETTER('n',M_CARON,0), +LETTER('n',M_NAME,0), // apostrophe n +CAPITAL, +LETTER('n',M_NAME,0), // eng +CAPITAL, +LETTER('o',M_MACRON,0), +CAPITAL, +LETTER('o',M_BREVE,0), +CAPITAL, // U+0150 +LETTER('o',M_DOUBLE_ACUTE,0), +CAPITAL, +LIGATURE('o','e',0), +CAPITAL, +LETTER('r',M_ACUTE,0), +CAPITAL, +LETTER('r',M_CEDILLA,0), +CAPITAL, +LETTER('r',M_CARON,0), +CAPITAL, +LETTER('s',M_ACUTE,0), +CAPITAL, +LETTER('s',M_CIRCUMFLEX,0), +CAPITAL, +LETTER('s',M_CEDILLA,0), +CAPITAL, // U+0160 +LETTER('s',M_CARON,0), +CAPITAL, +LETTER('t',M_CEDILLA,0), +CAPITAL, +LETTER('t',M_CARON,0), +CAPITAL, +LETTER('t',M_STROKE,0), +CAPITAL, +LETTER('u',M_TILDE,0), +CAPITAL, +LETTER('u',M_MACRON,0), +CAPITAL, +LETTER('u',M_BREVE,0), +CAPITAL, +LETTER('u',M_RING,0), +CAPITAL, // U+0170 +LETTER('u',M_DOUBLE_ACUTE,0), +CAPITAL, +LETTER('u',M_OGONEK,0), +CAPITAL, +LETTER('w',M_CIRCUMFLEX,0), +CAPITAL, +LETTER('y',M_CIRCUMFLEX,0), +CAPITAL, // Y-DIAERESIS +CAPITAL, +LETTER('z',M_ACUTE,0), +CAPITAL, +LETTER('z',M_DOT_ABOVE,0), +CAPITAL, +LETTER('z',M_CARON,0), +LETTER('s',M_NAME,0), // long-s // U+17f +}; + + +// characters U+0250 to U+029F +static const unsigned short letter_accents_250[] = { +LETTER('a',M_TURNED,0), // U+250 +LETTER(L_ALPHA,0,0), +LETTER(L_ALPHA,M_TURNED,0), +LETTER('b',M_IMPLOSIVE,0), +0, // open-o +LETTER('c',M_CURL,0), +LETTER('d',M_RETROFLEX,0), +LETTER('d',M_IMPLOSIVE,0), +LETTER('e',M_REVERSED,0), // U+258 +0, // schwa +LETTER(L_SCHWA,M_HOOK,0), +0, // open-e +LETTER(L_OPEN_E,M_REVERSED,0), +LETTER(L_OPEN_E,M_HOOK,M_REVERSED), +0,//LETTER(L_OPEN_E,M_CLOSED,M_REVERSED), +LETTER('j',M_BAR,0), +LETTER('g',M_IMPLOSIVE,0), // U+260 +LETTER('g',0,0), +LETTER('g',M_SMALLCAP,0), +LETTER(L_GAMMA,0,0), +0, // ramshorn +LETTER('h',M_TURNED,0), +LETTER('h',M_HOOK,0), +0,//LETTER(L_HENG,M_HOOK,0), +LETTER('i',M_BAR,0), // U+268 +LETTER(L_IOTA,0,0), +LETTER('i',M_SMALLCAP,0), +LETTER('l',M_TILDE,0), +LETTER('l',M_BAR,0), +LETTER('l',M_RETROFLEX,0), +LIGATURE('l','z',0), +LETTER('m',M_TURNED,0), +0,//LETTER('m',M_TURNED,M_LEG), // U+270 +LETTER('m',M_HOOK,0), +0,//LETTER('n',M_LEFTHOOK,0), +LETTER('n',M_RETROFLEX,0), +LETTER('n',M_SMALLCAP,0), +LETTER('o',M_BAR,0), +LIGATURE('o','e',M_SMALLCAP), +0,//LETTER(L_OMEGA,M_CLOSED,0), +LETTER(L_PHI,0,0), // U+278 +LETTER('r',M_TURNED,0), +0,//LETTER('r',M_TURNED,M_LEG), +LETTER('r',M_RETROFLEX,M_TURNED), +0,//LETTER('r',M_LEG,0), +LETTER('r',M_RETROFLEX,0), +0, // r-tap +LETTER(L_RTAP,M_REVERSED,0), +LETTER('r',M_SMALLCAP,0), // U+280 +LETTER('r',M_TURNED,M_SMALLCAP), +LETTER('s',M_RETROFLEX,0), +0, // esh +0,//LETTER('j',M_BAR,L_IMPLOSIVE), +LETTER(L_ESH,M_REVERSED,0), +LETTER(L_ESH,M_CURL,0), +LETTER('t',M_TURNED,0), +LETTER('t',M_RETROFLEX,0), // U+288 +LETTER('u',M_BAR,0), +LETTER(L_UPSILON,0,0), +LETTER('v',M_HOOK,0), +LETTER('v',M_TURNED,0), +LETTER('w',M_TURNED,0), +LETTER('y',M_TURNED,0), +LETTER('y',M_SMALLCAP,0), +LETTER('z',M_RETROFLEX,0), // U+290 +LETTER('z',M_CURL,0), +0, // ezh +LETTER(L_EZH,M_CURL,0), +0, // glottal stop +LETTER(L_GLOTTAL,M_REVERSED,0), +LETTER(L_GLOTTAL,M_TURNED,0), +0,//LETTER('c',M_LONG,0), +0, // bilabial click // U+298 +LETTER('b',M_SMALLCAP,0), +0,//LETTER(L_OPEN_E,M_CLOSED,0), +LETTER('g',M_IMPLOSIVE,M_SMALLCAP), +LETTER('h',M_SMALLCAP,0), +LETTER('j',M_CURL,0), +LETTER('k',M_TURNED,0), +LETTER('l',M_SMALLCAP,0), +LETTER('q',M_HOOK,0), // U+2a0 +LETTER(L_GLOTTAL,M_STROKE,0), +LETTER(L_GLOTTAL,M_STROKE,M_REVERSED), +LIGATURE('d','z',0), +0, // dezh +LIGATURE('d','z',M_CURL), +LIGATURE('t','s',0), +0, // tesh +LIGATURE('t','s',M_CURL), +}; + +static int LookupLetter2(Translator *tr, unsigned int letter, char *ph_buf) +{//======================================================================== + int len; + char single_letter[10]; + + single_letter[0] = 0; + single_letter[1] = '_'; + len = utf8_out(letter, &single_letter[2]); + single_letter[len+2] = ' '; + single_letter[len+3] = 0; + + if(Lookup(tr, &single_letter[1], ph_buf) == 0) + { + single_letter[1] = ' '; + if(Lookup(tr, &single_letter[2], ph_buf) == 0) + { + TranslateRules(tr, &single_letter[2], ph_buf, 20, NULL,0,NULL); + } + } + return(ph_buf[0]); +} + + +void LookupAccentedLetter(Translator *tr, unsigned int letter, char *ph_buf) +{//========================================================================= + // lookup the character in the accents table + int accent_data = 0; + int accent1 = 0; + int accent2 = 0; + int basic_letter; + int letter2=0; + char ph_letter1[30]; + char ph_letter2[30]; + char ph_accent1[30]; + char ph_accent2[30]; + + ph_accent2[0] = 0; + + if((letter >= 0xe0) && (letter < 0x17f)) + { + accent_data = letter_accents_0e0[letter - 0xe0]; + } + else + if((letter >= 0x250) && (letter <= 0x2a8)) + { + accent_data = letter_accents_250[letter - 0x250]; + } + + if(accent_data != 0) + { + basic_letter = (accent_data & 0x3f) + 59; + if(basic_letter < 'a') + basic_letter = non_ascii_tab[basic_letter-59]; + + if(accent_data & 0x8000) + { + letter2 = (accent_data >> 6) & 0x3f; + letter2 += 59; + accent2 = (accent_data >> 12) & 0x7; + } + else + { + accent1 = (accent_data >> 6) & 0x1f; + accent2 = (accent_data >> 11) & 0xf; + } + + + if(Lookup(tr, accents_tab[accent1].name, ph_accent1) != 0) + { + + if(LookupLetter2(tr, basic_letter, ph_letter1) != 0) + { + if(accent2 != 0) + { + if(Lookup(tr, accents_tab[accent2].name, ph_accent2) == 0) + { +// break; + } + + if(accents_tab[accent2].flags & 1) + { + strcpy(ph_buf,ph_accent2); + ph_buf += strlen(ph_buf); + ph_accent2[0] = 0; + } + } + if(letter2 != 0) + { + //ligature + LookupLetter2(tr, letter2, ph_letter2); + sprintf(ph_buf,"%s%c%s%c%s%s",ph_accent1, phonPAUSE_VSHORT, ph_letter1, phonSTRESS_P, ph_letter2, ph_accent2); + } + else + { + if(accent1 == 0) + strcpy(ph_buf, ph_letter1); + else + if((tr->langopts.accents & 1) || (accents_tab[accent1].flags & 1)) + sprintf(ph_buf,"%s%c%c%s", ph_accent1, phonPAUSE_VSHORT, phonSTRESS_P, ph_letter1); + else + sprintf(ph_buf,"%c%s%c%s%c", phonSTRESS_2, ph_letter1, phonPAUSE_VSHORT, ph_accent1, phonPAUSE_VSHORT); + } + } + } + } +} // end of LookupAccentedLetter + + + +void LookupLetter(Translator *tr, unsigned int letter, int next_byte, char *ph_buf1) +{//================================================================================= + int len; + unsigned char *p; + static char single_letter[10] = {0,0}; + char ph_stress[2]; + unsigned int dict_flags[2]; + char ph_buf3[40]; + char *ptr; + + ph_buf1[0] = 0; + len = utf8_out(letter,&single_letter[2]); + single_letter[len+2] = ' '; + + if(next_byte == -1) + { + // speaking normal text, not individual characters + if(Lookup(tr, &single_letter[2], ph_buf1) != 0) + return; + + single_letter[1] = '_'; + if(Lookup(tr, &single_letter[1], ph_buf3) != 0) + return; // the character is specified as _* so ignore it when speaking normal text + + // check whether this character is specified for English + if(tr->translator_name == L('e','n')) + return; // we are already using English + + SetTranslator2("en"); + if(Lookup(translator2, &single_letter[2], ph_buf3) != 0) + { + // yes, switch to English and re-translate the word + sprintf(ph_buf1,"%c",phonSWITCH); + } + SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table + return; + } + + if((letter <= 32) || iswspace(letter)) + { + // lookup space as _&32 etc. + sprintf(&single_letter[1],"_#%d ",letter); + Lookup(tr, &single_letter[1], ph_buf1); + return; + } + + if(next_byte != ' ') + next_byte = RULE_SPELLING; + single_letter[3+len] = next_byte; // follow by space-space if the end of the word, or space-0x31 + + single_letter[1] = '_'; + + // if the $accent flag is set for this letter, use the accents table (below) + dict_flags[1] = 0; + ptr = &single_letter[1]; + + if(Lookup(tr, &single_letter[1], ph_buf3) == 0) + { + single_letter[1] = ' '; + if(Lookup(tr, &single_letter[2], ph_buf3) == 0) + { + TranslateRules(tr, &single_letter[2], ph_buf3, sizeof(ph_buf3), NULL,FLAG_NO_TRACE,NULL); + } + } + + if(ph_buf3[0] == 0) + { + LookupAccentedLetter(tr, letter, ph_buf3); + } + + if(ph_buf3[0] == 0) + { + ph_buf1[0] = 0; + return; + } + if(ph_buf3[0] == phonSWITCH) + { + strcpy(ph_buf1,ph_buf3); + return; + } + // at a stress marker at the start of the letter name, unless one is already marked + ph_stress[0] = phonSTRESS_P; + ph_stress[1] = 0; + + for(p=(unsigned char *)ph_buf3; *p != 0; p++) + { + if(phoneme_tab[*p]->type == phSTRESS) + ph_stress[0] = 0; // stress is already marked + } + sprintf(ph_buf1,"%s%s",ph_stress,ph_buf3); +} + + + +int TranslateLetter(Translator *tr, char *word, char *phonemes, int control, int word_length) +{//====================================================================================== +// get pronunciation for an isolated letter +// return number of bytes used by the letter +// control 2=say-as glyphs, 3-say-as chars + int n_bytes; + int letter; + int len; + int save_option_phonemes; + char *p2; + char *pbuf; + char capital[20]; + char ph_buf[60]; + char ph_buf2[60]; + char hexbuf[6]; + + ph_buf[0] = 0; + capital[0] = 0; + + n_bytes = utf8_in(&letter,word); + + if((letter & 0xfff00) == 0x0e000) + { + letter &= 0xff; // uncode private usage area + } + + if(control > 2) + { + // include CAPITAL information + if(iswupper(letter)) + { + Lookup(tr, "_cap", capital); + } + } + letter = towlower2(letter); + + LookupLetter(tr, letter, word[n_bytes], ph_buf); + + if(ph_buf[0] == phonSWITCH) + { + strcpy(phonemes,ph_buf); + return(0); + } + + if((ph_buf[0] == 0) && (tr->translator_name != L('e','n'))) + { + // speak as English, check whether there is a translation for this character + SetTranslator2("en"); + save_option_phonemes = option_phonemes; + option_phonemes = 0; + LookupLetter(translator2, letter, word[n_bytes], ph_buf); + SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table + option_phonemes = save_option_phonemes; + + if(ph_buf[0] != 0) + { + sprintf(phonemes,"%cen",phonSWITCH); + return(0); + } + } + + if(ph_buf[0] == 0) + { + // character name not found + if(iswalpha(letter)) + Lookup(tr, "_?A", ph_buf); + + if((ph_buf[0]==0) && !iswspace(letter)) + Lookup(tr, "_??", ph_buf); + + if(ph_buf[0] != 0) + { + // speak the hexadecimal number of the character code + sprintf(hexbuf,"%x",letter); + pbuf = ph_buf; + for(p2 = hexbuf; *p2 != 0; p2++) + { + pbuf += strlen(pbuf); + *pbuf++ = phonPAUSE_VSHORT; + LookupLetter(tr, *p2, 0, pbuf); + } + } + } + + len = strlen(phonemes); + if(tr->langopts.accents & 2) + sprintf(ph_buf2,"%c%s%s",0xff,ph_buf,capital); + else + sprintf(ph_buf2,"%c%s%s",0xff,capital,ph_buf); // the 0xff marker will be removed or replaced in SetSpellingStress() + if((len + strlen(ph_buf2)) < N_WORD_PHONEMES) + { + strcpy(&phonemes[len],ph_buf2); + } + return(n_bytes); +} // end of TranslateLetter + + + +void SetSpellingStress(Translator *tr, char *phonemes, int control, int n_chars) +{//============================================================================= +// Individual letter names, reduce the stress of some. + int ix; + unsigned int c; + int n_stress=0; + int count; + unsigned char buf[N_WORD_PHONEMES]; + + for(ix=0; (c = phonemes[ix]) != 0; ix++) + { + if(c == phonSTRESS_P) + { + n_stress++; + } + buf[ix] = c; + } + buf[ix] = 0; + + count = 0; + for(ix=0; (c = buf[ix]) != 0; ix++) + { + if((c == phonSTRESS_P) && (n_chars > 1)) + { + count++; + + if(tr->langopts.spelling_stress == 1) + { + // stress on initial letter when spelling + if(count > 1) + c = phonSTRESS_3; + } + else + { + if(count != n_stress) + { + if(((count % 3) != 0) || (count == n_stress-1)) + c = phonSTRESS_3; // reduce to secondary stress + } + } + } + else + if(c == 0xff) + { + if((control < 2) || (ix==0)) + continue; // don't insert pauses + + if(control == 4) + c = phonPAUSE; // pause after each character + if(((count % 3) == 0) || (control > 2)) + c = phonPAUSE_SHORT; // pause following a primary stress + else + continue; // remove marker + } + *phonemes++ = c; + } + if(control >= 2) + *phonemes++ = phonPAUSE_NOLINK; + *phonemes = 0; +} // end of SetSpellingStress + + + + +int TranslateRoman(Translator *tr, char *word, char *ph_out) +{//===================================================== + int c; + char *p; + const char *p2; + int acc; + int prev; + int value; + int subtract; + int repeat = 0; + unsigned int flags; + char ph_roman[30]; + char number_chars[N_WORD_BYTES]; + + static const char *roman_numbers = "ixcmvld"; + static int roman_values[] = {1,10,100,1000,5,50,500}; + + acc = 0; + prev = 0; + subtract = 0x7fff; + + while((c = *word++) != ' ') + { + if((p2 = strchr(roman_numbers,c)) == NULL) + return(0); + + value = roman_values[p2 - roman_numbers]; + if(value == prev) + { + repeat++; + if(repeat >= 3) + return(0); + } + else + repeat = 0; + + if((prev > 1) && (prev != 10) && (prev != 100)) + { + if(value >= prev) + return(0); + } + if((prev != 0) && (prev < value)) + { + if(((acc % 10) != 0) || ((prev*10) < value)) + return(0); + subtract = prev; + value -= subtract; + } + else + if(value >= subtract) + return(0); + else + acc += prev; + prev = value; + } + acc += prev; + if(acc < 2) + return(0); + + if(acc > tr->langopts.max_roman) + return(0); + + Lookup(tr, "_roman",ph_roman); // precede by "roman" if _rom is defined in *_list + p = &ph_out[0]; + + if((tr->langopts.numbers & NUM_ROMAN_AFTER) == 0) + { + strcpy(ph_out,ph_roman); + p = &ph_out[strlen(ph_out)]; + } + + sprintf(number_chars," %d ",acc); + TranslateNumber(tr, &number_chars[1], p, &flags, 0); + + if(tr->langopts.numbers & NUM_ROMAN_AFTER) + strcat(ph_out,ph_roman); + return(1); +} // end of TranslateRoman + + +static const char *M_Variant(int value) +{//==================================== + // returns M, or perhaps MA for some cases + + if((translator->langopts.numbers2 & 0x100) && (value >= 2) && (value <= 4)) + return("0MA"); // Czech, Slovak + else + if(((value % 100) < 10) || ((value % 100) > 20)) // but not teens, 10 to 19 + { + if ((translator->langopts.numbers2 & 0x40) && + ((value % 10)>=2) && + ((value % 10)<=4)) + { + // for Polish language - two forms of plural! + return("0MA"); + } + + if((translator->langopts.numbers2 & 0x80) && + ((value % 10)==1)) + { + return("1MA"); + } + + } + return("0M"); +} + + +static int LookupThousands(Translator *tr, int value, int thousandplex, char *ph_out) +{//================================================================================== + int found; + char string[12]; + char ph_of[12]; + char ph_thousands[40]; + + ph_of[0] = 0; + + // first look fora match with the exact value of thousands + sprintf(string,"_%dM%d",value,thousandplex); + + if((found = Lookup(tr, string, ph_thousands)) == 0) + { + if((value % 100) >= 20) + { + Lookup(tr, "_0of", ph_of); + } + + sprintf(string,"_%s%d",M_Variant(value),thousandplex); + + if(Lookup(tr, string, ph_thousands) == 0) + { + // repeat "thousand" if higher order names are not available + sprintf(string,"_%dM1",value); + if((found = Lookup(tr, string, ph_thousands)) == 0) + Lookup(tr, "_0M1", ph_thousands); + } + } + sprintf(ph_out,"%s%s",ph_of,ph_thousands); + return(found); +} + + +static int LookupNum2(Translator *tr, int value, int control, char *ph_out) +{//======================================================================== +// Lookup a 2 digit number +// control bit 0: use special form of '1' +// control bit 2: use feminine form of '2' + + int found; + int ix; + int units; + int used_and=0; + int next_phtype; + char string[12]; // for looking up entries in de_list + char ph_tens[50]; + char ph_digits[50]; + char ph_and[12]; + + if((value == 1) && (control & 1)) + { + if(Lookup(tr, "_1a", ph_out) != 0) + return(0); + } + // is there a special pronunciation for this 2-digit number + found = 0; + if(control & 4) + { + sprintf(string,"_%df",value); + found = Lookup(tr, string, ph_digits); + } + if(found == 0) + { + sprintf(string,"_%d",value); + found = Lookup(tr, string, ph_digits); + } + + // no, speak as tens+units + if((control & 2) && (value < 10)) + { + // speak leading zero + Lookup(tr, "_0", ph_tens); + } + else + { + if(found) + { + strcpy(ph_out,ph_digits); + return(0); + } + + if((value % 10) == 0) + { + sprintf(string,"_%d0",value / 10); + found = Lookup(tr, string, ph_tens); + } + if(!found) + { + sprintf(string,"_%dX",value / 10); + Lookup(tr, string, ph_tens); + } + + if((value % 10) == 0) + { + strcpy(ph_out,ph_tens); + return(0); + } + + found = 0; + units = (value % 10); + if(control & 4) + { + // is there a variant form of this number? + sprintf(string,"_%df",units); + found = Lookup(tr, string, ph_digits); + } + if(found == 0) + { + sprintf(string,"_%d",units); + Lookup(tr, string, ph_digits); + } + } + + if(tr->langopts.numbers & 0x30) + { + Lookup(tr, "_0and", ph_and); + if(tr->langopts.numbers & 0x10) + sprintf(ph_out,"%s%s%s",ph_digits,ph_and,ph_tens); + else + sprintf(ph_out,"%s%s%s",ph_tens,ph_and,ph_digits); + used_and = 1; + } + else + { + if(tr->langopts.numbers & 0x200) + { + // remove vowel from the end of tens if units starts with a vowel (LANG=Italian) + if((ix = strlen(ph_tens)-1) >= 0) + { + if((next_phtype = phoneme_tab[(unsigned int)(ph_digits[0])]->type) == phSTRESS) + next_phtype = phoneme_tab[(unsigned int)(ph_digits[1])]->type; + + if((phoneme_tab[(unsigned int)(ph_tens[ix])]->type == phVOWEL) && (next_phtype == phVOWEL)) + ph_tens[ix] = 0; + } + } + sprintf(ph_out,"%s%s",ph_tens,ph_digits); + } + + if(tr->langopts.numbers & 0x100) + { + // only one primary stress + found = 0; + for(ix=strlen(ph_out)-1; ix>=0; ix--) + { + if(ph_out[ix] == phonSTRESS_P) + { + if(found) + ph_out[ix] = phonSTRESS_3; + else + found = 1; + } + } + } + return(used_and); +} // end of LookupNum2 + + +static int LookupNum3(Translator *tr, int value, char *ph_out, int suppress_null, int thousandplex, int prev_thousands) +{//==================================================================================================================== +// Translate a 3 digit number + int found; + int hundreds; + int x; + char string[12]; // for looking up entries in **_list + char buf1[100]; + char buf2[100]; + char ph_100[20]; + char ph_10T[20]; + char ph_digits[50]; + char ph_thousands[50]; + char ph_hundred_and[12]; + char ph_thousand_and[12]; + + hundreds = value / 100; + buf1[0] = 0; + + if(hundreds > 0) + { + ph_thousands[0] = 0; + ph_thousand_and[0] = 0; + + Lookup(tr, "_0C", ph_100); + + if(((tr->langopts.numbers & 0x0800) != 0) && (hundreds == 19)) + { + // speak numbers such as 1984 as years: nineteen-eighty-four +// ph_100[0] = 0; // don't say "hundred", we also need to surpess "and" + } + else + if(hundreds >= 10) + { + ph_digits[0] = 0; + + if(LookupThousands(tr, hundreds / 10, thousandplex+1, ph_10T) == 0) + { + x = 0; + if(tr->langopts.numbers2 & (1 << (thousandplex+1))) + x = 4; + LookupNum2(tr, hundreds/10, x, ph_digits); + } + + if(tr->langopts.numbers2 & 0x200) + sprintf(ph_thousands,"%s%s%c",ph_10T,ph_digits,phonPAUSE_NOLINK); // say "thousands" before its number, not after + else + sprintf(ph_thousands,"%s%s%c",ph_digits,ph_10T,phonPAUSE_NOLINK); + + hundreds %= 10; + if(hundreds == 0) + ph_100[0] = 0; + suppress_null = 1; + } + + ph_digits[0] = 0; + if(hundreds > 0) + { + if((tr->langopts.numbers & 0x100000) && (prev_thousands || (ph_thousands[0] != 0))) + { + Lookup(tr, "_0and", ph_thousand_and); + } + + suppress_null = 1; + + found = 0; + if((value % 1000) == 100) + { + // is there a special pronunciation for exactly 100 ? + found = Lookup(tr, "_1C0", ph_digits); + } + if(!found) + { + sprintf(string,"_%dC",hundreds); + found = Lookup(tr, string, ph_digits); // is there a specific pronunciation for n-hundred ? + } + + if(found) + { + ph_100[0] = 0; + } + else + { + if((hundreds > 1) || ((tr->langopts.numbers & 0x400) == 0)) + { + LookupNum2(tr, hundreds, 0, ph_digits); + } + } + } + + sprintf(buf1,"%s%s%s%s",ph_thousands,ph_thousand_and,ph_digits,ph_100); + } + + ph_hundred_and[0] = 0; + if((tr->langopts.numbers & 0x40) && ((value % 100) != 0)) + { + if((value > 100) || (prev_thousands && (thousandplex==0))) + { + Lookup(tr, "_0and", ph_hundred_and); + } + } + + + buf2[0] = 0; + value = value % 100; + + if(value == 0) + { + if(suppress_null == 0) + Lookup(tr, "_0", buf2); + } + else + { + x = 0; + if(thousandplex==0) + x = 1; // allow "eins" for 1 rather than "ein" + else + { + if(tr->langopts.numbers2 & (1 << thousandplex)) + x = 4; // use variant (feminine) for before thousands and millions + } + + if(LookupNum2(tr, value, x, buf2) != 0) + { + if(tr->langopts.numbers & 0x80) + ph_hundred_and[0] = 0; // don't put 'and' after 'hundred' if there's 'and' between tens and units + } + } + + sprintf(ph_out,"%s%s%s",buf1,ph_hundred_and,buf2); + + return(0); +} // end of LookupNum3 + + + +static int TranslateNumber_1(Translator *tr, char *word, char *ph_out, unsigned int *flags, int wflags) +{//==================================================================================================== +// Number translation with various options +// the "word" may be up to 4 digits +// "words" of 3 digits may be preceded by another number "word" for thousands or millions + + int n_digits; + int value; + int ix; + unsigned char c; + int suppress_null = 0; + int decimal_point = 0; + int thousandplex = 0; + int thousands_inc = 0; + int prev_thousands = 0; + int this_value; + static int prev_value; + int decimal_count; + int max_decimal_count; + char string[12]; // for looking up entries in de_list + char buf1[100]; + char ph_append[50]; + char ph_buf[200]; + char ph_buf2[50]; + + static const char str_pause[2] = {phonPAUSE_NOLINK,0}; + + for(ix=0; isdigit(word[ix]); ix++) ; + n_digits = ix; + value = this_value = atoi(word); + + ph_append[0] = 0; + ph_buf2[0] = 0; + + // is there a previous thousands part (as a previous "word") ? + if((n_digits == 3) && (word[-2] == tr->langopts.thousands_sep) && isdigit(word[-3])) + { + prev_thousands = 1; + } + else + if((tr->langopts.thousands_sep == ' ') || (tr->langopts.numbers & 0x1000)) + { + // thousands groups can be separated by spaces + if((n_digits == 3) && isdigit(word[-2])) + { + prev_thousands = 1; + } + } + + if((word[0] == '0') && (prev_thousands == 0) && (word[1] != tr->langopts.decimal_sep)) + { + if((n_digits == 2) && (word[3] == ':') && isdigit(word[5]) && isspace(word[7])) + { + // looks like a time 02:30, omit the leading zero + } + else + { + return(0); // number string with leading zero, speak as individual digits + } + } + + if((tr->langopts.numbers & 0x1000) && (word[n_digits] == ' ')) + thousands_inc = 1; + else + if(word[n_digits] == tr->langopts.thousands_sep) + thousands_inc = 2; + + if(thousands_inc > 0) + { + // if the following "words" are three-digit groups, count them and add + // a "thousand"/"million" suffix to this one + + ix = n_digits + thousands_inc; + while(isdigit(word[ix]) && isdigit(word[ix+1]) && isdigit(word[ix+2])) + { + thousandplex++; + if(word[ix+3] == tr->langopts.thousands_sep) + ix += (3 + thousands_inc); + else + break; + } + } + + if((value == 0) && prev_thousands) + { + suppress_null = 1; + } + + if((word[n_digits] == tr->langopts.decimal_sep) && isdigit(word[n_digits+1])) + { + // this "word" ends with a decimal point + Lookup(tr, "_dpt", ph_append); + decimal_point = 1; + } + else + if(suppress_null == 0) + { + if(thousands_inc > 0) + { + if((thousandplex > 0) && (value < 1000)) + { + if((suppress_null == 0) && (LookupThousands(tr,value,thousandplex,ph_append))) + { + // found an exact match for N thousand + value = 0; + suppress_null = 1; + } + } + } + } + else + if((thousandplex > 1) && prev_thousands && (prev_value > 0)) + { + sprintf(string,"_%s%d",M_Variant(value),thousandplex+1); + if(Lookup(tr, string, buf1)==0) + { + // speak this thousandplex if there was no word for the previous thousandplex + sprintf(string,"_0M%d",thousandplex); + Lookup(tr, string, ph_append); + } + } + + if((ph_append[0] == 0) && (word[n_digits] == '.') && (thousandplex == 0)) + { + Lookup(tr, "_.", ph_append); + } + + LookupNum3(tr, value, ph_buf, suppress_null, thousandplex, prev_thousands); + if((thousandplex > 0) && (tr->langopts.numbers2 & 0x200)) + sprintf(ph_out,"%s%s%s",ph_append,ph_buf2,ph_buf); // say "thousands" before its number + else + sprintf(ph_out,"%s%s%s",ph_buf2,ph_buf,ph_append); + + + while(decimal_point) + { + n_digits++; + + decimal_count = 0; + while(isdigit(word[n_digits+decimal_count])) + decimal_count++; + + if(decimal_count > 1) + { + max_decimal_count = 2; + switch(tr->langopts.numbers & 0xe000) + { + case 0x8000: + max_decimal_count = 5; + case 0x4000: + // French/Polish decimal fraction + while(word[n_digits] == '0') + { + Lookup(tr, "_0", buf1); + strcat(ph_out,buf1); + decimal_count--; + n_digits++; + } + if((decimal_count <= max_decimal_count) && isdigit(word[n_digits])) + { + LookupNum3(tr, atoi(&word[n_digits]), buf1, 0,0,0); + strcat(ph_out,buf1); + n_digits += decimal_count; + } + break; + + case 0x2000: + // Italian decimal fractions + if((decimal_count < 4) || ((decimal_count==4) && (word[n_digits] != '0'))) + { + LookupNum3(tr, atoi(&word[n_digits]), buf1, 0,0,0); + strcat(ph_out,buf1); + if(word[n_digits]=='0') + { + // decimal part has leading zeros, so add a "hundredths" or "thousandths" suffix + sprintf(string,"_0Z%d",decimal_count); + Lookup(tr, string, buf1); + strcat(ph_out,buf1); + } + n_digits += decimal_count; + } + break; + + case 0x6000: + // Romanian decimal fractions + if((decimal_count <= 4) && (word[n_digits] != '0')) + { + LookupNum3(tr, atoi(&word[n_digits]), buf1, 0,0,0); + strcat(ph_out,buf1); + n_digits += decimal_count; + } + break; + } + } + + while(isdigit(c = word[n_digits]) && (strlen(ph_out) < (N_WORD_PHONEMES - 10))) + { + value = word[n_digits++] - '0'; + LookupNum2(tr, value, 1, buf1); + strcat(ph_out,buf1); + } + + // something after the decimal part ? + if(Lookup(tr, "_dpt2", buf1)) + strcat(ph_out,buf1); + + if((c == tr->langopts.decimal_sep) && isdigit(word[n_digits+1])) + { + Lookup(tr, "_dpt", buf1); + strcat(ph_out,buf1); + } + else + { + decimal_point = 0; + } + } + if((ph_out[0] != 0) && (ph_out[0] != phonSWITCH)) + { + int next_char; + char *p; + p = &word[n_digits+1]; + + p += utf8_in(&next_char,p); + if((tr->langopts.numbers & NUM_NOPAUSE) && (next_char == ' ')) + utf8_in(&next_char,p); + + if(!iswalpha(next_char)) + strcat(ph_out,str_pause); // don't add pause for 100s, 6th, etc. + } + + *flags = FLAG_FOUND; + prev_value = this_value; + return(1); +} // end of TranslateNumber_1 + + + +int TranslateNumber(Translator *tr, char *word1, char *ph_out, unsigned int *flags, int wflags) +{//============================================================================================ + if(option_sayas == SAYAS_DIGITS1) + return(0); // speak digits individually + + if((tr->langopts.numbers & 0x3) == 1) + return(TranslateNumber_1(tr, word1, ph_out, flags, wflags)); + + return(0); +} // end of TranslateNumber + diff --git a/Plugins/eSpeak/eSpeak/phoneme.h b/Plugins/eSpeak/eSpeak/phoneme.h new file mode 100644 index 0000000..596f457 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/phoneme.h @@ -0,0 +1,168 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + + + +// phoneme types +#define phPAUSE 0 +#define phSTRESS 1 +#define phVOWEL 2 +#define phLIQUID 3 +#define phSTOP 4 +#define phVSTOP 5 +#define phFRICATIVE 6 +#define phVFRICATIVE 7 +#define phNASAL 8 +#define phVIRTUAL 9 +#define phDELETED 14 +#define phINVALID 15 + + +// phoneme properties +// bits 16-19 give place of articulation (not currently used) +#define phWAVE 0x01 +#define phUNSTRESSED 0x02 +#define phFORTIS 0x08 +#define phVOICED 0x10 +#define phSIBILANT 0x20 +#define phNOLINK 0x40 +#define phTRILL 0x80 +#define phVOWEL2 0x100 // liquid that is considered a vowel +#define phPALATAL 0x200 +#define phAPPENDPH 0x2000 // always insert another phoneme (link_out) after this one +#define phBRKAFTER 0x4000 // [*] add a post-pause +#define phBEFOREPAUSE 0x8000 // replace with the link_out phoneme if the next phoneme is a pause + +#define phALTERNATIVE 0x1c00 // bits 10,11,12 specifying use of alternative_ph +#define phBEFOREVOWEL 0x0000 +#define phBEFOREVOWELPAUSE 0x0400 +#define phBEFORENOTVOWEL 0x0c00 +#define phBEFORENOTVOWEL2 0x1000 +#define phSWITCHVOICING 0x0800 +#define phBEFORE_R 0x1400 + +#define phNONSYLLABIC 0x100000 // don't count this vowel as a syllable when finding the stress position +#define phLONG 0x200000 +#define phLENGTHENSTOP 0x400000 // make the pre-pause slightly longer +#define phRHOTIC 0x800000 + +// fixed phoneme code numbers, these can be used from the program code +#define phonCONTROL 1 +#define phonSTRESS_U 2 +#define phonSTRESS_D 3 +#define phonSTRESS_2 4 +#define phonSTRESS_3 5 +#define phonSTRESS_P 6 +#define phonSTRESS_P2 7 // priority stress within a word +#define phonSTRESS_PREV 8 +#define phonPAUSE 9 +#define phonPAUSE_SHORT 10 +#define phonPAUSE_NOLINK 11 +#define phonLENGTHEN 12 +#define phonSCHWA 13 +#define phonSCHWA_SHORT 14 +#define phonEND_WORD 15 +#define phonSONORANT 16 +#define phonDEFAULTTONE 17 +#define phonCAPITAL 18 +#define phonGLOTTALSTOP 19 +#define phonSYLLABIC 20 +#define phonSWITCH 21 +#define phonX1 22 // a language specific action +#define phonPAUSE_VSHORT 23 +#define phonPAUSE_LONG 24 +#define phonT_REDUCED 25 +#define phonSTRESS_TONIC 26 +#define phonPAUSE_CLAUSE 27 + +extern const unsigned char pause_phonemes[8]; // 0, vshort, short, pause, long, glottalstop + +// place of articulation +#define phPLACE 0xf0000 +#define phPLACE_pla 0x60000 + +#define N_PHONEME_TABS 100 // number of phoneme tables +#define N_PHONEME_TAB 256 // max phonemes in a phoneme table +#define N_PHONEME_TAB_NAME 32 // must be multiple of 4 + +// main table of phonemes, index by phoneme number (1-254) +typedef struct { + unsigned int mnemonic; // 1st char is in the l.s.byte + unsigned int phflags; // bits 28-30 reduce_to level, bits 16-19 place of articulation + // bits 10-11 alternative ph control + + unsigned short std_length; // for vowels, in mS; for phSTRESS, the stress/tone type + unsigned short spect; + unsigned short before; + unsigned short after; + + unsigned char code; // the phoneme number + unsigned char type; // phVOWEL, phPAUSE, phSTOP etc + unsigned char start_type; + unsigned char end_type; + + unsigned char length_mod; // a length_mod group number, used to access length_mod_tab + unsigned char reduce_to; // change to this phoneme if unstressed + unsigned char alternative_ph; // change to this phoneme if a vowel follows/doesn't follow + unsigned char link_out; // insert linking phoneme if a vowel follows + +} PHONEME_TAB; + + +// Several phoneme tables may be loaded into memory. phoneme_tab points to +// one for the current voice +extern int n_phoneme_tab; +extern int current_phoneme_table; +extern PHONEME_TAB *phoneme_tab[N_PHONEME_TAB]; +extern unsigned char phoneme_tab_flags[N_PHONEME_TAB]; // bit 0: not inherited + +typedef struct { + char name[N_PHONEME_TAB_NAME]; + PHONEME_TAB *phoneme_tab_ptr; + int n_phonemes; + int includes; // also include the phonemes from this other phoneme table +} PHONEME_TAB_LIST; + + + +// table of phonemes to be replaced with different phonemes, for the current voice +#define N_REPLACE_PHONEMES 60 +typedef struct { + unsigned char old_ph; + unsigned char new_ph; + char type; // 0=always replace, 1=only at end of word +} REPLACE_PHONEMES; + +extern int n_replace_phonemes; +extern REPLACE_PHONEMES replace_phonemes[N_REPLACE_PHONEMES]; + + +#define PH(c1,c2) (c2<<8)+c1 // combine two characters into an integer for phoneme name +#define PH3(c1,c2,c3) (c3<<16)+(c2<<8)+c1 +#define PhonemeCode2(c1,c2) PhonemeCode((c2<<8)+c1) +int LookupPhonemeString(const char *string); +int PhonemeCode(unsigned int mnem); + +char *EncodePhonemes(char *p, char *outptr, unsigned char *bad_phoneme); +void DecodePhonemes(const char *inptr, char *outptr); + +extern const char *WordToString(unsigned int word); + +extern PHONEME_TAB_LIST phoneme_tab_list[N_PHONEME_TABS]; +extern int phoneme_tab_number; diff --git a/Plugins/eSpeak/eSpeak/phonemelist.cpp b/Plugins/eSpeak/eSpeak/phonemelist.cpp new file mode 100644 index 0000000..7f1c65e --- /dev/null +++ b/Plugins/eSpeak/eSpeak/phonemelist.cpp @@ -0,0 +1,687 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "StdAfx.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "translate.h" + + +const unsigned char pause_phonemes[8] = {0, phonPAUSE_VSHORT, phonPAUSE_SHORT, phonPAUSE, phonPAUSE_LONG, phonGLOTTALSTOP, phonPAUSE_LONG, phonPAUSE_LONG}; + + +extern int n_ph_list2; +extern PHONEME_LIST2 ph_list2[N_PHONEME_LIST]; // first stage of text->phonemes + + + +static int ChangePhonemes(Translator *tr, PHONEME_LIST2 *phlist, int n_ph, int index, PHONEME_TAB *ph, CHANGEPH *ch) +{//================================================================================================================= +// Called for each phoneme in the phoneme list, to allow a language to make changes +// ph The current phoneme + + if(tr->translator_name == L('r','u')) + return(ChangePhonemes_ru(tr, phlist, n_ph, index, ph, ch)); + + return(0); +} + + +static int SubstitutePhonemes(Translator *tr, PHONEME_LIST2 *plist_out) +{//==================================================================== +// Copy the phonemes list and perform any substitutions that are required for the +// current voice + int ix; + int k; + int replace_flags; + int n_plist_out = 0; + int word_end; + int max_stress = -1; + int switched_language = 0; + int max_stress_posn=0; + int n_syllables = 0; + int syllable = 0; + int syllable_stressed = 0; + PHONEME_LIST2 *plist2; + PHONEME_LIST2 *pl; + PHONEME_TAB *next=NULL; + + for(ix=0; (ix < n_ph_list2) && (n_plist_out < N_PHONEME_LIST); ix++) + { + plist2 = &ph_list2[ix]; + + if(plist2->phcode == phonSWITCH) + switched_language ^= 1; + + // don't do any substitution if the language has been temporarily changed + if(switched_language == 0) + { + if(ix < (n_ph_list2 -1)) + next = phoneme_tab[ph_list2[ix+1].phcode]; + + word_end = 0; + if((plist2+1)->sourceix || ((next != 0) && (next->type == phPAUSE))) + word_end = 1; // this phoneme is the end of a word + + if(tr->langopts.phoneme_change != 0) + { + // this language does changes to phonemes after translation + + if(plist2->sourceix) + { + // start of a word, find the stressed vowel + syllable = 0; + syllable_stressed = 0; + n_syllables = 0; + + max_stress = -1; + max_stress_posn = ix; + for(k=ix; k < n_ph_list2; k++) + { + if(((pl = &ph_list2[k])->sourceix != 0) && (k > ix)) + break; + + pl->stress &= 0xf; + + if(phoneme_tab[pl->phcode]->type == phVOWEL) + { + n_syllables++; + + if(pl->stress > max_stress) + { + syllable_stressed = n_syllables; + max_stress = pl->stress; + max_stress_posn = k; + } + } + } + } + + if(phoneme_tab[plist2->phcode]->type == phVOWEL) + { + syllable++; + } + + // make any language specific changes + int flags; + CHANGEPH ch; + flags = 0; + if(ix == max_stress_posn) + flags |= 2; + if(ix > max_stress_posn) + flags |= 4; + if(ph_list2[ix].synthflags & SFLAG_DICTIONARY) + flags |= 8; + ch.flags = flags | word_end; + + ch.stress = plist2->stress; + ch.stress_highest = max_stress; + ch.n_vowels = n_syllables; + ch.vowel_this = syllable; + ch.vowel_stressed = syllable_stressed; + + ChangePhonemes(tr, ph_list2, n_ph_list2, ix, phoneme_tab[ph_list2[ix].phcode], &ch); + } + + // check whether a Voice has specified that we should replace this phoneme + for(k=0; k<n_replace_phonemes; k++) + { + if(plist2->phcode == replace_phonemes[k].old_ph) + { + replace_flags = replace_phonemes[k].type; + + if((replace_flags & 1) && (word_end == 0)) + continue; // this replacement only occurs at the end of a word + + if((replace_flags & 2) && ((plist2->stress & 0x7) > 3)) + continue; // this replacement doesn't occur in stressed syllables + + // substitute the replacement phoneme + plist2->phcode = replace_phonemes[k].new_ph; + if((plist2->stress > 1) && (phoneme_tab[plist2->phcode]->phflags & phUNSTRESSED)) + plist2->stress = 0; // the replacement must be unstressed + break; + } + } + + if(plist2->phcode == 0) + { + continue; // phoneme has been replaced by NULL, so don't copy it + } + } + + // copy phoneme into the output list + memcpy(&plist_out[n_plist_out++],plist2,sizeof(PHONEME_LIST2)); + } + return(n_plist_out); +} // end of SubstitutePhonemes + + + +void MakePhonemeList(Translator *tr, int post_pause, int start_sentence) +{//===================================================================== + + int ix=0; + int j; + int insert_ph = 0; + PHONEME_LIST *phlist; + PHONEME_TAB *ph; + PHONEME_TAB *prev, *next, *next2; + int unstress_count = 0; + int word_stress = 0; + int switched_language = 0; + int max_stress; + int voicing; + int regression; + int end_sourceix; + int alternative; + int first_vowel=0; // first vowel in a word + PHONEME_LIST2 ph_list3[N_PHONEME_LIST]; + + static PHONEME_LIST2 ph_list2_null = {0,0,0,0,0}; + PHONEME_LIST2 *plist2 = &ph_list2_null; + PHONEME_LIST2 *plist2_inserted = NULL; + + plist2 = ph_list2; + phlist = phoneme_list; + end_sourceix = plist2[n_ph_list2-1].sourceix; + + // is the last word of the clause unstressed ? + max_stress = 0; + for(j = n_ph_list2-3; j>=0; j--) + { + // start with the last phoneme (before the terminating pauses) and move forwards + if((plist2[j].stress & 0x7f) > max_stress) + max_stress = plist2[j].stress & 0x7f; + if(plist2[j].sourceix != 0) + break; + } + if(max_stress < 4) + { + // the last word is unstressed, look for a previous word that can be stressed + while(--j >= 0) + { + if(plist2[j].synthflags & SFLAG_PROMOTE_STRESS) // dictionary flags indicated that this stress can be promoted + { + plist2[j].stress = 4; // promote to stressed + break; + } + if(plist2[j].stress >= 4) + { + // found a stressed syllable, so stop looking + break; + } + } + } + + if((regression = tr->langopts.param[LOPT_REGRESSIVE_VOICING]) != 0) + { + // set consonant clusters to all voiced or all unvoiced + // Regressive + int type; + voicing = 0; + + for(j=n_ph_list2-1; j>=0; j--) + { + ph = phoneme_tab[plist2[j].phcode]; + if(ph == NULL) + continue; + + if(ph->code == phonSWITCH) + switched_language ^= 1; + if(switched_language) + continue; + + type = ph->type; + + if(regression & 0x2) + { + // LANG=Russian, [v] amd [v;] don't cause regression, or [R^] + if((ph->mnemonic == 'v') || (ph->mnemonic == ((';'<<8)+'v')) || ((ph->mnemonic & 0xff)== 'R')) + type = phLIQUID; + } + + if((type==phSTOP) || type==(phFRICATIVE)) + { + if(voicing==0) + { + voicing = 1; + } + else + if((voicing==2) && ((ph->phflags & phALTERNATIVE)==phSWITCHVOICING)) + { + plist2[j].phcode = ph->alternative_ph; // change to voiced equivalent + } + } + else + if((type==phVSTOP) || type==(phVFRICATIVE)) + { + if(voicing==0) + { + voicing = 2; + } + else + if((voicing==1) && ((ph->phflags & phALTERNATIVE)==phSWITCHVOICING)) + { + plist2[j].phcode = ph->alternative_ph; // change to unvoiced equivalent + } + } + else + { + if(regression & 0x8) + { + // LANG=Polish, propagate through liquids and nasals + if((type == phPAUSE) || (type == phVOWEL)) + voicing = 0; + } + else + { + voicing = 0; + } + } + if((regression & 0x4) && (plist2[j].sourceix)) + { + // stop propagation at a word boundary + voicing = 0; + } + } + } + + n_ph_list2 = SubstitutePhonemes(tr,ph_list3) - 2; + + // transfer all the phonemes of the clause into phoneme_list + ph = phoneme_tab[phonPAUSE]; + switched_language = 0; + + for(j=0; insert_ph || ((j < n_ph_list2) && (ix < N_PHONEME_LIST-3)); j++) + { + prev = ph; + + plist2 = &ph_list3[j]; + + if(insert_ph != 0) + { + // we have a (linking) phoneme which we need to insert here + next = phoneme_tab[plist2->phcode]; // this phoneme, i.e. after the insert + + // re-use the previous entry for the inserted phoneme. + // That's OK because we don't look backwards from plist2 + j--; + plist2 = plist2_inserted = &ph_list3[j]; + memset(plist2, 0, sizeof(*plist2)); + plist2->phcode = insert_ph; + ph = phoneme_tab[insert_ph]; + insert_ph = 0; + } + else + { + // otherwise get the next phoneme from the list + ph = phoneme_tab[plist2->phcode]; + + if(plist2->phcode == phonSWITCH) + { + // change phoneme table + SelectPhonemeTable(plist2->tone_number); + switched_language ^= SFLAG_SWITCHED_LANG; + } + next = phoneme_tab[(plist2+1)->phcode]; // the phoneme after this one + } + + if(plist2->sourceix) + { + // start of a word + int k; + word_stress = 0; + first_vowel = 1; + + // find the highest stress level in this word + for(k=j+1; k < n_ph_list2; k++) + { + if(ph_list3[k].sourceix) + break; // start of the next word + + if(ph_list3[k].stress > word_stress) + word_stress = ph_list3[k].stress; + } + } + + if(ph == NULL) continue; + + if(ph->type == phVOWEL) + { + // check for consecutive unstressed syllables + if(plist2->stress == 0) + { + // an unstressed vowel + unstress_count++; + if((unstress_count > 1) && ((unstress_count & 1)==0)) + { + // in a sequence of unstressed syllables, reduce alternate syllables to 'diminished' + // stress. But not for the last phoneme of a stressed word + if((tr->langopts.stress_flags & 0x2) || ((word_stress > 3) && ((plist2+1)->sourceix!=0))) + { + // An unstressed final vowel of a stressed word + unstress_count=1; // try again for next syllable + } + else + { + plist2->stress = 1; // change stress to 'diminished' + } + } + } + else + { + unstress_count = 0; + } + } + + alternative = 0; + + if(ph->alternative_ph > 0) + { + switch(ph->phflags & phALTERNATIVE) + { + // This phoneme changes if vowel follows, or doesn't follow, depending on its phNOTFOLLOWS flag + case phBEFORENOTVOWEL: + if(next->type != phVOWEL) + alternative = ph->alternative_ph; + break; + + case phBEFORENOTVOWEL2: // LANG=tr + if(((plist2+1)->sourceix != 0) || + ((next->type != phVOWEL) && ((phoneme_tab[(plist2+2)->phcode]->type != phVOWEL) || ((plist2+2)->sourceix != 0)))) + { + alternative = ph->alternative_ph; + } + break; + + case phBEFOREVOWELPAUSE: + if((next->type == phVOWEL) || (next->type == phPAUSE)) + alternative = ph->alternative_ph; + break; + + case phBEFOREVOWEL: + if(next->type == phVOWEL) + alternative = ph->alternative_ph; + break; + + case phBEFORE_R: + if(next->phflags & phRHOTIC) + { + alternative = ph->alternative_ph; + } + break; + } + } + if(ph->phflags & phBEFOREPAUSE) + { + if(next->type == phPAUSE) + alternative = ph->link_out; // replace with the link_out phoneme + } + + if(alternative == 1) + continue; // NULL phoneme, discard + + if(alternative > 1) + { + PHONEME_TAB *ph2; + ph2 = ph; + ph = phoneme_tab[alternative]; + + if(ph->type == phVOWEL) + { + plist2->synthflags |= SFLAG_SYLLABLE; + if(ph2->type != phVOWEL) + plist2->stress = 0; // change from non-vowel to vowel, make sure it's unstressed + } + else + plist2->synthflags &= ~SFLAG_SYLLABLE; + } + + if(tr->langopts.param[LOPT_REDUCE_T]) + { + if((ph->mnemonic == 't') && (plist2->sourceix == 0) && ((prev->type == phVOWEL) || (prev->mnemonic == 'n'))) + { + if(((plist2+1)->sourceix == 0) && ((plist2+1)->stress < 3) && (next->type == phVOWEL)) + { + ph = phoneme_tab[phonT_REDUCED]; + } + } + } + + + while((ph->reduce_to != 0) && (!(plist2->synthflags & SFLAG_DICTIONARY) || (tr->langopts.param[LOPT_REDUCE] & 1))) + { + int reduce_level; + int stress_level; + int reduce = 0; + + reduce_level = (ph->phflags >> 28) & 7; + + if(ph->type == phVOWEL) + { + stress_level = plist2->stress; + } + else + { + // consonant, get stress from the following vowel + if(next->type == phVOWEL) + stress_level = (plist2+1)->stress; + else + break; + } + + if((stress_level == 1) && (first_vowel)) + stress_level = 0; // ignore 'dimished' stress on first syllable + + if(stress_level == 1) + reduce = 1; // stress = 'reduced' + + if(stress_level < reduce_level) + reduce =1; + + if((word_stress < 4) && (tr->langopts.param[LOPT_REDUCE] & 0x2) && (stress_level >= word_stress)) + { + // don't reduce the most stressed syllable in an unstressed word + reduce = 0; + } + + if(reduce) + ph = phoneme_tab[ph->reduce_to]; + else + break; + } + + if(ph->type == phVOWEL) + first_vowel = 0; + + if((plist2+1)->synthflags & SFLAG_LENGTHEN) + { + static char types_double[] = {phFRICATIVE,phVFRICATIVE,phNASAL,phLIQUID,0}; + if(strchr(types_double,next->type)) + { + // lengthen this consonant by doubling it + insert_ph = next->code; + (plist2+1)->synthflags ^= SFLAG_LENGTHEN; + } + } + + if((plist2+1)->sourceix != 0) + { + int x; + + if(tr->langopts.vowel_pause && (ph->type != phPAUSE)) + { + + if((ph->type != phVOWEL) && (tr->langopts.vowel_pause & 0x200)) + { + // add a pause after a word which ends in a consonant + insert_ph = phonPAUSE_NOLINK; + } + + if(next->type == phVOWEL) + { + if((x = tr->langopts.vowel_pause & 0x0c) != 0) + { + // break before a word which starts with a vowel + if(x == 0xc) + insert_ph = phonPAUSE_NOLINK; + else + insert_ph = phonPAUSE_VSHORT; + } + + if((ph->type == phVOWEL) && ((x = tr->langopts.vowel_pause & 0x03) != 0)) + { + // adjacent vowels over a word boundary + if(x == 2) + insert_ph = phonPAUSE_SHORT; + else + insert_ph = phonPAUSE_VSHORT; + } + + if(((plist2+1)->stress >= 4) && (tr->langopts.vowel_pause & 0x100)) + { + // pause before a words which starts with a stressed vowel + insert_ph = phonPAUSE_SHORT; + } + } + } + + if(plist2 != plist2_inserted) + { + if((x = (tr->langopts.word_gap & 0x7)) != 0) + { + if((x > 1) || ((insert_ph != phonPAUSE_SHORT) && (insert_ph != phonPAUSE_NOLINK))) + { + // don't reduce the pause + insert_ph = pause_phonemes[x]; + } + } + if(option_wordgap > 0) + { + insert_ph = phonPAUSE_LONG; + } + } + } + + next2 = phoneme_tab[(plist2+2)->phcode]; + + if((insert_ph == 0) && (ph->link_out != 0) && !(ph->phflags & phBEFOREPAUSE) && (((plist2+1)->synthflags & SFLAG_EMBEDDED)==0)) + { + if(ph->phflags & phAPPENDPH) + { + // always append the specified phoneme, unless it already is the next phoneme + if((ph->link_out != (plist2+1)->phcode) && (next->type == phVOWEL)) +// if(ph->link_out != (plist2+1)->phcode) + { + insert_ph = ph->link_out; + } + } + else + if(((tr->langopts.word_gap & 8)==0) || ((plist2+1)->sourceix == 0)) + { + // This phoneme can be linked to a following vowel by inserting a linking phoneme + if(next->type == phVOWEL) + insert_ph = ph->link_out; + else + if(next->code == phonPAUSE_SHORT) + { + // Pause followed by Vowel, replace the Short Pause with the linking phoneme, + if(next2->type == phVOWEL) + (plist2+1)->phcode = ph->link_out; // replace pause by linking phoneme + } + } + } + + if(ph->phflags & phVOICED) + { + // check that a voiced consonant is preceded or followed by a vowel or liquid + // and if not, add a short schwa + + // not yet implemented + } + + phlist[ix].ph = ph; + phlist[ix].type = ph->type; + phlist[ix].env = PITCHfall; // default, can be changed in the "intonation" module + phlist[ix].synthflags = plist2->synthflags | switched_language; + phlist[ix].tone = plist2->stress & 0xf; + phlist[ix].tone_ph = plist2->tone_number; + phlist[ix].sourceix = 0; + + if(plist2->sourceix != 0) + { + phlist[ix].sourceix = plist2->sourceix; + phlist[ix].newword = 1; // this phoneme is the start of a word + + if(start_sentence) + { + phlist[ix].newword = 5; // start of sentence + start of word + start_sentence = 0; + } + } + else + { + phlist[ix].newword = 0; + } + + phlist[ix].length = ph->std_length; + if((ph->code == phonPAUSE_LONG) && (option_wordgap > 0)) + { + phlist[ix].ph = phoneme_tab[phonPAUSE_SHORT]; + phlist[ix].length = option_wordgap*14; // 10mS per unit at the default speed + } + + if(ph->type==phVOWEL || ph->type==phLIQUID || ph->type==phNASAL || ph->type==phVSTOP || ph->type==phVFRICATIVE) + { + phlist[ix].length = 128; // length_mod + phlist[ix].env = PITCHfall; + } + + phlist[ix].prepause = 0; + phlist[ix].amp = 20; // default, will be changed later + phlist[ix].pitch1 = 0x400; + phlist[ix].pitch2 = 0x400; + ix++; + } + phlist[ix].newword = 2; // end of clause + + phlist[ix].type = phPAUSE; // terminate with 2 Pause phonemes + phlist[ix].length = post_pause; // length of the pause, depends on the punctuation + phlist[ix].sourceix = end_sourceix; + phlist[ix].synthflags = 0; + + phlist[ix++].ph = phoneme_tab[phonPAUSE]; + phlist[ix].type = phPAUSE; + phlist[ix].length = 0; + phlist[ix].sourceix=0; + phlist[ix].synthflags = 0; + phlist[ix++].ph = phoneme_tab[phonPAUSE_SHORT]; + + n_phoneme_list = ix; +} // end of MakePhonemeList + + diff --git a/Plugins/eSpeak/eSpeak/portaudio.h b/Plugins/eSpeak/eSpeak/portaudio.h new file mode 100644 index 0000000..2ab8e02 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/portaudio.h @@ -0,0 +1,466 @@ +// NOTE: Copy this file to portaudio.h in order to compile with V18 portaudio + + +#ifndef PORT_AUDIO_H +#define PORT_AUDIO_H + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * $Id: portaudio.h,v 1.5 2002/03/26 18:04:22 philburk Exp $ + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.audiomulch.com/portaudio/ + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +typedef int PaError; +typedef enum { + paNoError = 0, + + paHostError = -10000, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDeviceId, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError, + paDeviceUnavailable +} PaErrorNum; + +/* + Pa_Initialize() is the library initialisation function - call this before + using the library. + +*/ + +PaError Pa_Initialize( void ); + +/* + Pa_Terminate() is the library termination function - call this after + using the library. + +*/ + +PaError Pa_Terminate( void ); + +/* + Pa_GetHostError() returns a host specific error code. + This can be called after receiving a PortAudio error code of paHostError. + +*/ + +long Pa_GetHostError( void ); + +/* + Pa_GetErrorText() translates the supplied PortAudio error number + into a human readable message. + +*/ + +const char *Pa_GetErrorText( PaError errnum ); + +/* + Sample formats + + These are formats used to pass sound data between the callback and the + stream. Each device has a "native" format which may be used when optimum + efficiency or control over conversion is required. + + Formats marked "always available" are supported (emulated) by all + PortAudio implementations. + + The floating point representation (paFloat32) uses +1.0 and -1.0 as the + maximum and minimum respectively. + + paUInt8 is an unsigned 8 bit format where 128 is considered "ground" + +*/ + +typedef unsigned long PaSampleFormat; +#define paFloat32 ((PaSampleFormat) (1<<0)) /*always available*/ +#define paInt16 ((PaSampleFormat) (1<<1)) /*always available*/ +#define paInt32 ((PaSampleFormat) (1<<2)) /*always available*/ +#define paInt24 ((PaSampleFormat) (1<<3)) +#define paPackedInt24 ((PaSampleFormat) (1<<4)) +#define paInt8 ((PaSampleFormat) (1<<5)) +#define paUInt8 ((PaSampleFormat) (1<<6)) +#define paCustomFormat ((PaSampleFormat) (1<<16)) + +/* + Device enumeration mechanism. + + Device ids range from 0 to Pa_CountDevices()-1. + + Devices may support input, output or both. + +*/ + +typedef int PaDeviceID; +#define paNoDevice -1 + +int Pa_CountDevices( void ); + +typedef struct +{ + int structVersion; + const char *name; + int maxInputChannels; + int maxOutputChannels; + /* Number of discrete rates, or -1 if range supported. */ + int numSampleRates; + /* Array of supported sample rates, or {min,max} if range supported. */ + const double *sampleRates; + PaSampleFormat nativeSampleFormats; +} +PaDeviceInfo; + +/* + Pa_GetDefaultInputDeviceID(), Pa_GetDefaultOutputDeviceID() return the + default device ids for input and output respectively, or paNoDevice if + no device is available. + The result can be passed to Pa_OpenStream(). + + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. + + set PA_RECOMMENDED_OUTPUT_DEVICE=1 + + The user should first determine the available device ids by using + the supplied application "pa_devs". + +*/ + +PaDeviceID Pa_GetDefaultInputDeviceID( void ); +PaDeviceID Pa_GetDefaultOutputDeviceID( void ); + + + +/* + Pa_GetDeviceInfo() returns a pointer to an immutable PaDeviceInfo structure + for the device specified. + If the device parameter is out of range the function returns NULL. + + PortAudio manages the memory referenced by the returned pointer, the client + must not manipulate or free the memory. The pointer is only guaranteed to be + valid between calls to Pa_Initialize() and Pa_Terminate(). + +*/ + +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID device ); + +/* + PaTimestamp is used to represent a continuous sample clock with arbitrary + start time that can be used for syncronization. The type is used for the + outTime argument to the PortAudioCallback and as the result of Pa_StreamTime() + +*/ + +typedef double PaTimestamp; + +/* + PortAudioCallback is implemented by PortAudio clients. + + inputBuffer and outputBuffer are arrays of interleaved samples, + the format, packing and number of channels used by the buffers are + determined by parameters to Pa_OpenStream() (see below). + + framesPerBuffer is the number of sample frames to be processed by the callback. + + outTime is the time in samples when the buffer(s) processed by + this callback will begin being played at the audio output. + See also Pa_StreamTime() + + userData is the value of a user supplied pointer passed to Pa_OpenStream() + intended for storing synthesis data etc. + + return value: + The callback can return a non-zero value to stop the stream. This may be + useful in applications such as soundfile players where a specific duration + of output is required. However, it is not necessary to utilise this mechanism + as StopStream() will also terminate the stream. A callback returning a + non-zero value must fill the entire outputBuffer. + + NOTE: None of the other stream functions may be called from within the + callback function except for Pa_GetCPULoad(). + +*/ + +typedef int (PortAudioCallback)( + void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); + + +/* + Stream flags + + These flags may be supplied (ored together) in the streamFlags argument to + the Pa_OpenStream() function. + +*/ + +#define paNoFlag (0) +#define paClipOff (1<<0) /* disable default clipping of out of range samples */ +#define paDitherOff (1<<1) /* disable default dithering */ +#define paPlatformSpecificFlags (0x00010000) +typedef unsigned long PaStreamFlags; + +/* + A single PortAudioStream provides multiple channels of real-time + input and output audio streaming to a client application. + Pointers to PortAudioStream objects are passed between PortAudio functions. +*/ + +typedef void PortAudioStream; +#define PaStream PortAudioStream + +/* + Pa_OpenStream() opens a stream for either input, output or both. + + stream is the address of a PortAudioStream pointer which will receive + a pointer to the newly opened stream. + + inputDevice is the id of the device used for input (see PaDeviceID above.) + inputDevice may be paNoDevice to indicate that an input device is not required. + + numInputChannels is the number of channels of sound to be delivered to the + callback. It can range from 1 to the value of maxInputChannels in the + PaDeviceInfo record for the device specified by the inputDevice parameter. + If inputDevice is paNoDevice numInputChannels is ignored. + + inputSampleFormat is the sample format of inputBuffer provided to the callback + function. inputSampleFormat may be any of the formats described by the + PaSampleFormat enumeration (see above). PortAudio guarantees support for + the device's native formats (nativeSampleFormats in the device info record) + and additionally 16 and 32 bit integer and 32 bit floating point formats. + Support for other formats is implementation defined. + + inputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or stream processing. + inputDriverInfo is never required for correct operation. If not used + inputDriverInfo should be NULL. + + outputDevice is the id of the device used for output (see PaDeviceID above.) + outputDevice may be paNoDevice to indicate that an output device is not required. + + numOutputChannels is the number of channels of sound to be supplied by the + callback. See the definition of numInputChannels above for more details. + + outputSampleFormat is the sample format of the outputBuffer filled by the + callback function. See the definition of inputSampleFormat above for more + details. + + outputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or stream processing. + outputDriverInfo is never required for correct operation. If not used + outputDriverInfo should be NULL. + + sampleRate is the desired sampleRate. For full-duplex streams it is the + sample rate for both input and output + + framesPerBuffer is the length in sample frames of all internal sample buffers + used for communication with platform specific audio routines. Wherever + possible this corresponds to the framesPerBuffer parameter passed to the + callback function. + + numberOfBuffers is the number of buffers used for multibuffered communication + with the platform specific audio routines. If you pass zero, then an optimum + value will be chosen for you internally. This parameter is provided only + as a guide - and does not imply that an implementation must use multibuffered + i/o when reliable double buffering is available (such as SndPlayDoubleBuffer() + on the Macintosh.) + + streamFlags may contain a combination of flags ORed together. + These flags modify the behaviour of the streaming process. Some flags may only + be relevant to certain buffer formats. + + callback is a pointer to a client supplied function that is responsible + for processing and filling input and output buffers (see above for details.) + + userData is a client supplied pointer which is passed to the callback + function. It could for example, contain a pointer to instance data necessary + for processing the audio buffers. + + return value: + Upon success Pa_OpenStream() returns PaNoError and places a pointer to a + valid PortAudioStream in the stream argument. The stream is inactive (stopped). + If a call to Pa_OpenStream() fails a non-zero error code is returned (see + PaError above) and the value of stream is invalid. + +*/ + +PaError Pa_OpenStream( PortAudioStream** stream, + PaDeviceID inputDevice, + int numInputChannels, + PaSampleFormat inputSampleFormat, + void *inputDriverInfo, + PaDeviceID outputDevice, + int numOutputChannels, + PaSampleFormat outputSampleFormat, + void *outputDriverInfo, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PaStreamFlags streamFlags, + PortAudioCallback *callback, + void *userData ); + + +/* + Pa_OpenDefaultStream() is a simplified version of Pa_OpenStream() that opens + the default input and/or output devices. Most parameters have identical meaning + to their Pa_OpenStream() counterparts, with the following exceptions: + + If either numInputChannels or numOutputChannels is 0 the respective device + is not opened. This has the same effect as passing paNoDevice in the device + arguments to Pa_OpenStream(). + + sampleFormat applies to both the input and output buffers. + +*/ + +PaError Pa_OpenDefaultStream( PortAudioStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ); + +/* + Pa_CloseStream() closes an audio stream, flushing any pending buffers. + +*/ + +PaError Pa_CloseStream( PortAudioStream* ); + +/* + Pa_StartStream() and Pa_StopStream() begin and terminate audio processing. + Pa_StopStream() waits until all pending audio buffers have been played. + Pa_AbortStream() stops playing immediately without waiting for pending + buffers to complete. + +*/ + +PaError Pa_StartStream( PortAudioStream *stream ); + +PaError Pa_StopStream( PortAudioStream *stream ); + +PaError Pa_AbortStream( PortAudioStream *stream ); + +/* + Pa_StreamActive() returns one (1) when the stream is active (ie playing + or recording audio), zero (0) when not playing, or a negative error number + if the stream is invalid. + The stream is active between calls to Pa_StartStream() and Pa_StopStream(), + but may also become inactive if the callback returns a non-zero value. + In the latter case, the stream is considered inactive after the last + buffer has finished playing. + +*/ + +PaError Pa_StreamActive( PortAudioStream *stream ); + +/* + Pa_StreamTime() returns the current output time in samples for the stream. + This time may be used as a time reference (for example synchronizing audio to + MIDI). + +*/ + +PaTimestamp Pa_StreamTime( PortAudioStream *stream ); + +/* + Pa_GetCPULoad() returns the CPU Load for the stream. + The "CPU Load" is a fraction of total CPU time consumed by the stream's + audio processing routines including, but not limited to the client supplied + callback. + A value of 0.5 would imply that PortAudio and the sound generating + callback was consuming roughly 50% of the available CPU time. + This function may be called from the callback function or the application. + +*/ + +double Pa_GetCPULoad( PortAudioStream* stream ); + +/* + Pa_GetMinNumBuffers() returns the minimum number of buffers required by + the current host based on minimum latency. + On the PC, for the DirectSound implementation, latency can be optionally set + by user by setting an environment variable. + For example, to set latency to 200 msec, put: + + set PA_MIN_LATENCY_MSEC=200 + + in the AUTOEXEC.BAT file and reboot. + If the environment variable is not set, then the latency will be determined + based on the OS. Windows NT has higher latency than Win95. + +*/ + +int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate ); + +/* + Pa_Sleep() puts the caller to sleep for at least 'msec' milliseconds. + You may sleep longer than the requested time so don't rely on this for + accurate musical timing. + + Pa_Sleep() is provided as a convenience for authors of portable code (such as + the tests and examples in the PortAudio distribution.) + +*/ + +void Pa_Sleep( long msec ); + +/* + Pa_GetSampleSize() returns the size in bytes of a single sample in the + supplied PaSampleFormat, or paSampleFormatNotSupported if the format is + no supported. + +*/ + +PaError Pa_GetSampleSize( PaSampleFormat format ); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORT_AUDIO_H */ diff --git a/Plugins/eSpeak/eSpeak/portaudio18.h b/Plugins/eSpeak/eSpeak/portaudio18.h new file mode 100644 index 0000000..2ab8e02 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/portaudio18.h @@ -0,0 +1,466 @@ +// NOTE: Copy this file to portaudio.h in order to compile with V18 portaudio + + +#ifndef PORT_AUDIO_H +#define PORT_AUDIO_H + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* + * $Id: portaudio.h,v 1.5 2002/03/26 18:04:22 philburk Exp $ + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.audiomulch.com/portaudio/ + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +typedef int PaError; +typedef enum { + paNoError = 0, + + paHostError = -10000, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDeviceId, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError, + paDeviceUnavailable +} PaErrorNum; + +/* + Pa_Initialize() is the library initialisation function - call this before + using the library. + +*/ + +PaError Pa_Initialize( void ); + +/* + Pa_Terminate() is the library termination function - call this after + using the library. + +*/ + +PaError Pa_Terminate( void ); + +/* + Pa_GetHostError() returns a host specific error code. + This can be called after receiving a PortAudio error code of paHostError. + +*/ + +long Pa_GetHostError( void ); + +/* + Pa_GetErrorText() translates the supplied PortAudio error number + into a human readable message. + +*/ + +const char *Pa_GetErrorText( PaError errnum ); + +/* + Sample formats + + These are formats used to pass sound data between the callback and the + stream. Each device has a "native" format which may be used when optimum + efficiency or control over conversion is required. + + Formats marked "always available" are supported (emulated) by all + PortAudio implementations. + + The floating point representation (paFloat32) uses +1.0 and -1.0 as the + maximum and minimum respectively. + + paUInt8 is an unsigned 8 bit format where 128 is considered "ground" + +*/ + +typedef unsigned long PaSampleFormat; +#define paFloat32 ((PaSampleFormat) (1<<0)) /*always available*/ +#define paInt16 ((PaSampleFormat) (1<<1)) /*always available*/ +#define paInt32 ((PaSampleFormat) (1<<2)) /*always available*/ +#define paInt24 ((PaSampleFormat) (1<<3)) +#define paPackedInt24 ((PaSampleFormat) (1<<4)) +#define paInt8 ((PaSampleFormat) (1<<5)) +#define paUInt8 ((PaSampleFormat) (1<<6)) +#define paCustomFormat ((PaSampleFormat) (1<<16)) + +/* + Device enumeration mechanism. + + Device ids range from 0 to Pa_CountDevices()-1. + + Devices may support input, output or both. + +*/ + +typedef int PaDeviceID; +#define paNoDevice -1 + +int Pa_CountDevices( void ); + +typedef struct +{ + int structVersion; + const char *name; + int maxInputChannels; + int maxOutputChannels; + /* Number of discrete rates, or -1 if range supported. */ + int numSampleRates; + /* Array of supported sample rates, or {min,max} if range supported. */ + const double *sampleRates; + PaSampleFormat nativeSampleFormats; +} +PaDeviceInfo; + +/* + Pa_GetDefaultInputDeviceID(), Pa_GetDefaultOutputDeviceID() return the + default device ids for input and output respectively, or paNoDevice if + no device is available. + The result can be passed to Pa_OpenStream(). + + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. + + set PA_RECOMMENDED_OUTPUT_DEVICE=1 + + The user should first determine the available device ids by using + the supplied application "pa_devs". + +*/ + +PaDeviceID Pa_GetDefaultInputDeviceID( void ); +PaDeviceID Pa_GetDefaultOutputDeviceID( void ); + + + +/* + Pa_GetDeviceInfo() returns a pointer to an immutable PaDeviceInfo structure + for the device specified. + If the device parameter is out of range the function returns NULL. + + PortAudio manages the memory referenced by the returned pointer, the client + must not manipulate or free the memory. The pointer is only guaranteed to be + valid between calls to Pa_Initialize() and Pa_Terminate(). + +*/ + +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID device ); + +/* + PaTimestamp is used to represent a continuous sample clock with arbitrary + start time that can be used for syncronization. The type is used for the + outTime argument to the PortAudioCallback and as the result of Pa_StreamTime() + +*/ + +typedef double PaTimestamp; + +/* + PortAudioCallback is implemented by PortAudio clients. + + inputBuffer and outputBuffer are arrays of interleaved samples, + the format, packing and number of channels used by the buffers are + determined by parameters to Pa_OpenStream() (see below). + + framesPerBuffer is the number of sample frames to be processed by the callback. + + outTime is the time in samples when the buffer(s) processed by + this callback will begin being played at the audio output. + See also Pa_StreamTime() + + userData is the value of a user supplied pointer passed to Pa_OpenStream() + intended for storing synthesis data etc. + + return value: + The callback can return a non-zero value to stop the stream. This may be + useful in applications such as soundfile players where a specific duration + of output is required. However, it is not necessary to utilise this mechanism + as StopStream() will also terminate the stream. A callback returning a + non-zero value must fill the entire outputBuffer. + + NOTE: None of the other stream functions may be called from within the + callback function except for Pa_GetCPULoad(). + +*/ + +typedef int (PortAudioCallback)( + void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + PaTimestamp outTime, void *userData ); + + +/* + Stream flags + + These flags may be supplied (ored together) in the streamFlags argument to + the Pa_OpenStream() function. + +*/ + +#define paNoFlag (0) +#define paClipOff (1<<0) /* disable default clipping of out of range samples */ +#define paDitherOff (1<<1) /* disable default dithering */ +#define paPlatformSpecificFlags (0x00010000) +typedef unsigned long PaStreamFlags; + +/* + A single PortAudioStream provides multiple channels of real-time + input and output audio streaming to a client application. + Pointers to PortAudioStream objects are passed between PortAudio functions. +*/ + +typedef void PortAudioStream; +#define PaStream PortAudioStream + +/* + Pa_OpenStream() opens a stream for either input, output or both. + + stream is the address of a PortAudioStream pointer which will receive + a pointer to the newly opened stream. + + inputDevice is the id of the device used for input (see PaDeviceID above.) + inputDevice may be paNoDevice to indicate that an input device is not required. + + numInputChannels is the number of channels of sound to be delivered to the + callback. It can range from 1 to the value of maxInputChannels in the + PaDeviceInfo record for the device specified by the inputDevice parameter. + If inputDevice is paNoDevice numInputChannels is ignored. + + inputSampleFormat is the sample format of inputBuffer provided to the callback + function. inputSampleFormat may be any of the formats described by the + PaSampleFormat enumeration (see above). PortAudio guarantees support for + the device's native formats (nativeSampleFormats in the device info record) + and additionally 16 and 32 bit integer and 32 bit floating point formats. + Support for other formats is implementation defined. + + inputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or stream processing. + inputDriverInfo is never required for correct operation. If not used + inputDriverInfo should be NULL. + + outputDevice is the id of the device used for output (see PaDeviceID above.) + outputDevice may be paNoDevice to indicate that an output device is not required. + + numOutputChannels is the number of channels of sound to be supplied by the + callback. See the definition of numInputChannels above for more details. + + outputSampleFormat is the sample format of the outputBuffer filled by the + callback function. See the definition of inputSampleFormat above for more + details. + + outputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or stream processing. + outputDriverInfo is never required for correct operation. If not used + outputDriverInfo should be NULL. + + sampleRate is the desired sampleRate. For full-duplex streams it is the + sample rate for both input and output + + framesPerBuffer is the length in sample frames of all internal sample buffers + used for communication with platform specific audio routines. Wherever + possible this corresponds to the framesPerBuffer parameter passed to the + callback function. + + numberOfBuffers is the number of buffers used for multibuffered communication + with the platform specific audio routines. If you pass zero, then an optimum + value will be chosen for you internally. This parameter is provided only + as a guide - and does not imply that an implementation must use multibuffered + i/o when reliable double buffering is available (such as SndPlayDoubleBuffer() + on the Macintosh.) + + streamFlags may contain a combination of flags ORed together. + These flags modify the behaviour of the streaming process. Some flags may only + be relevant to certain buffer formats. + + callback is a pointer to a client supplied function that is responsible + for processing and filling input and output buffers (see above for details.) + + userData is a client supplied pointer which is passed to the callback + function. It could for example, contain a pointer to instance data necessary + for processing the audio buffers. + + return value: + Upon success Pa_OpenStream() returns PaNoError and places a pointer to a + valid PortAudioStream in the stream argument. The stream is inactive (stopped). + If a call to Pa_OpenStream() fails a non-zero error code is returned (see + PaError above) and the value of stream is invalid. + +*/ + +PaError Pa_OpenStream( PortAudioStream** stream, + PaDeviceID inputDevice, + int numInputChannels, + PaSampleFormat inputSampleFormat, + void *inputDriverInfo, + PaDeviceID outputDevice, + int numOutputChannels, + PaSampleFormat outputSampleFormat, + void *outputDriverInfo, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PaStreamFlags streamFlags, + PortAudioCallback *callback, + void *userData ); + + +/* + Pa_OpenDefaultStream() is a simplified version of Pa_OpenStream() that opens + the default input and/or output devices. Most parameters have identical meaning + to their Pa_OpenStream() counterparts, with the following exceptions: + + If either numInputChannels or numOutputChannels is 0 the respective device + is not opened. This has the same effect as passing paNoDevice in the device + arguments to Pa_OpenStream(). + + sampleFormat applies to both the input and output buffers. + +*/ + +PaError Pa_OpenDefaultStream( PortAudioStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + unsigned long numberOfBuffers, + PortAudioCallback *callback, + void *userData ); + +/* + Pa_CloseStream() closes an audio stream, flushing any pending buffers. + +*/ + +PaError Pa_CloseStream( PortAudioStream* ); + +/* + Pa_StartStream() and Pa_StopStream() begin and terminate audio processing. + Pa_StopStream() waits until all pending audio buffers have been played. + Pa_AbortStream() stops playing immediately without waiting for pending + buffers to complete. + +*/ + +PaError Pa_StartStream( PortAudioStream *stream ); + +PaError Pa_StopStream( PortAudioStream *stream ); + +PaError Pa_AbortStream( PortAudioStream *stream ); + +/* + Pa_StreamActive() returns one (1) when the stream is active (ie playing + or recording audio), zero (0) when not playing, or a negative error number + if the stream is invalid. + The stream is active between calls to Pa_StartStream() and Pa_StopStream(), + but may also become inactive if the callback returns a non-zero value. + In the latter case, the stream is considered inactive after the last + buffer has finished playing. + +*/ + +PaError Pa_StreamActive( PortAudioStream *stream ); + +/* + Pa_StreamTime() returns the current output time in samples for the stream. + This time may be used as a time reference (for example synchronizing audio to + MIDI). + +*/ + +PaTimestamp Pa_StreamTime( PortAudioStream *stream ); + +/* + Pa_GetCPULoad() returns the CPU Load for the stream. + The "CPU Load" is a fraction of total CPU time consumed by the stream's + audio processing routines including, but not limited to the client supplied + callback. + A value of 0.5 would imply that PortAudio and the sound generating + callback was consuming roughly 50% of the available CPU time. + This function may be called from the callback function or the application. + +*/ + +double Pa_GetCPULoad( PortAudioStream* stream ); + +/* + Pa_GetMinNumBuffers() returns the minimum number of buffers required by + the current host based on minimum latency. + On the PC, for the DirectSound implementation, latency can be optionally set + by user by setting an environment variable. + For example, to set latency to 200 msec, put: + + set PA_MIN_LATENCY_MSEC=200 + + in the AUTOEXEC.BAT file and reboot. + If the environment variable is not set, then the latency will be determined + based on the OS. Windows NT has higher latency than Win95. + +*/ + +int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate ); + +/* + Pa_Sleep() puts the caller to sleep for at least 'msec' milliseconds. + You may sleep longer than the requested time so don't rely on this for + accurate musical timing. + + Pa_Sleep() is provided as a convenience for authors of portable code (such as + the tests and examples in the PortAudio distribution.) + +*/ + +void Pa_Sleep( long msec ); + +/* + Pa_GetSampleSize() returns the size in bytes of a single sample in the + supplied PaSampleFormat, or paSampleFormatNotSupported if the format is + no supported. + +*/ + +PaError Pa_GetSampleSize( PaSampleFormat format ); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORT_AUDIO_H */ diff --git a/Plugins/eSpeak/eSpeak/portaudio19.h b/Plugins/eSpeak/eSpeak/portaudio19.h new file mode 100644 index 0000000..5c060b7 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/portaudio19.h @@ -0,0 +1,1127 @@ +// NOTE: Copy this file to portaudio.h in order to compile with V19 portaudio + +#ifndef PORTAUDIO_H +#define PORTAUDIO_H +/* + * $Id: portaudio.h 1061 2006-06-19 22:46:41Z lschwardt $ + * PortAudio Portable Real-Time Audio Library + * PortAudio API Header File + * Latest version available at: http://www.portaudio.com/ + * + * Copyright (c) 1999-2002 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** @file + @brief The PortAudio API. +*/ + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +/** Retrieve the release number of the currently running PortAudio build, + eg 1900. +*/ +int Pa_GetVersion( void ); + + +/** Retrieve a textual description of the current PortAudio build, + eg "PortAudio V19-devel 13 October 2002". +*/ +const char* Pa_GetVersionText( void ); + + +/** Error codes returned by PortAudio functions. + Note that with the exception of paNoError, all PaErrorCodes are negative. +*/ + +typedef int PaError; +typedef enum PaErrorCode +{ + paNoError = 0, + + paNotInitialized = -10000, + paUnanticipatedHostError, + paInvalidChannelCount, + paInvalidSampleRate, + paInvalidDevice, + paInvalidFlag, + paSampleFormatNotSupported, + paBadIODeviceCombination, + paInsufficientMemory, + paBufferTooBig, + paBufferTooSmall, + paNullCallback, + paBadStreamPtr, + paTimedOut, + paInternalError, + paDeviceUnavailable, + paIncompatibleHostApiSpecificStreamInfo, + paStreamIsStopped, + paStreamIsNotStopped, + paInputOverflowed, + paOutputUnderflowed, + paHostApiNotFound, + paInvalidHostApi, + paCanNotReadFromACallbackStream, /**< @todo review error code name */ + paCanNotWriteToACallbackStream, /**< @todo review error code name */ + paCanNotReadFromAnOutputOnlyStream, /**< @todo review error code name */ + paCanNotWriteToAnInputOnlyStream, /**< @todo review error code name */ + paIncompatibleStreamHostApi, + paBadBufferPtr +} PaErrorCode; + + +/** Translate the supplied PortAudio error code into a human readable + message. +*/ +const char *Pa_GetErrorText( PaError errorCode ); + + +/** Library initialization function - call this before using PortAudio. + This function initialises internal data structures and prepares underlying + host APIs for use. This function MUST be called before using any other + PortAudio API functions. + + If Pa_Initialize() is called multiple times, each successful + call must be matched with a corresponding call to Pa_Terminate(). + Pairs of calls to Pa_Initialize()/Pa_Terminate() may overlap, and are not + required to be fully nested. + + Note that if Pa_Initialize() returns an error code, Pa_Terminate() should + NOT be called. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Terminate +*/ +PaError Pa_Initialize( void ); + + +/** Library termination function - call this when finished using PortAudio. + This function deallocates all resources allocated by PortAudio since it was + initializied by a call to Pa_Initialize(). In cases where Pa_Initialise() has + been called multiple times, each call must be matched with a corresponding call + to Pa_Terminate(). The final matching call to Pa_Terminate() will automatically + close any PortAudio streams that are still open. + + Pa_Terminate() MUST be called before exiting a program which uses PortAudio. + Failure to do so may result in serious resource leaks, such as audio devices + not being available until the next reboot. + + @return paNoError if successful, otherwise an error code indicating the cause + of failure. + + @see Pa_Initialize +*/ +PaError Pa_Terminate( void ); + + + +/** The type used to refer to audio devices. Values of this type usually + range from 0 to (Pa_DeviceCount-1), and may also take on the PaNoDevice + and paUseHostApiSpecificDeviceSpecification values. + + @see Pa_DeviceCount, paNoDevice, paUseHostApiSpecificDeviceSpecification +*/ +typedef int PaDeviceIndex; + + +/** A special PaDeviceIndex value indicating that no device is available, + or should be used. + + @see PaDeviceIndex +*/ +#define paNoDevice ((PaDeviceIndex)-1) + + +/** A special PaDeviceIndex value indicating that the device(s) to be used + are specified in the host api specific stream info structure. + + @see PaDeviceIndex +*/ +#define paUseHostApiSpecificDeviceSpecification ((PaDeviceIndex)-2) + + +/* Host API enumeration mechanism */ + +/** The type used to enumerate to host APIs at runtime. Values of this type + range from 0 to (Pa_GetHostApiCount()-1). + + @see Pa_GetHostApiCount +*/ +typedef int PaHostApiIndex; + + +/** Retrieve the number of available host APIs. Even if a host API is + available it may have no devices available. + + @return A non-negative value indicating the number of available host APIs + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + @see PaHostApiIndex +*/ +PaHostApiIndex Pa_GetHostApiCount( void ); + + +/** Retrieve the index of the default host API. The default host API will be + the lowest common denominator host API on the current platform and is + unlikely to provide the best performance. + + @return A non-negative value ranging from 0 to (Pa_GetHostApiCount()-1) + indicating the default host API index or, a PaErrorCode (which are always + negative) if PortAudio is not initialized or an error is encountered. +*/ +PaHostApiIndex Pa_GetDefaultHostApi( void ); + + +/** Unchanging unique identifiers for each supported host API. This type + is used in the PaHostApiInfo structure. The values are guaranteed to be + unique and to never change, thus allowing code to be written that + conditionally uses host API specific extensions. + + New type ids will be allocated when support for a host API reaches + "public alpha" status, prior to that developers should use the + paInDevelopment type id. + + @see PaHostApiInfo +*/ +typedef enum PaHostApiTypeId +{ + paInDevelopment=0, /* use while developing support for a new host API */ + paDirectSound=1, + paMME=2, + paASIO=3, + paSoundManager=4, + paCoreAudio=5, + paOSS=7, + paALSA=8, + paAL=9, + paBeOS=10, + paWDMKS=11, + paJACK=12, + paWASAPI=13, + paAudioScienceHPI=14 +} PaHostApiTypeId; + + +/** A structure containing information about a particular host API. */ + +typedef struct PaHostApiInfo +{ + /** this is struct version 1 */ + int structVersion; + /** The well known unique identifier of this host API @see PaHostApiTypeId */ + PaHostApiTypeId type; + /** A textual description of the host API for display on user interfaces. */ + const char *name; + + /** The number of devices belonging to this host API. This field may be + used in conjunction with Pa_HostApiDeviceIndexToDeviceIndex() to enumerate + all devices for this host API. + @see Pa_HostApiDeviceIndexToDeviceIndex + */ + int deviceCount; + + /** The default input device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default input device is available. + */ + PaDeviceIndex defaultInputDevice; + + /** The default output device for this host API. The value will be a + device index ranging from 0 to (Pa_GetDeviceCount()-1), or paNoDevice + if no default output device is available. + */ + PaDeviceIndex defaultOutputDevice; + +} PaHostApiInfo; + + +/** Retrieve a pointer to a structure containing information about a specific + host Api. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @return A pointer to an immutable PaHostApiInfo structure describing + a specific host API. If the hostApi parameter is out of range or an error + is encountered, the function returns NULL. + + The returned structure is owned by the PortAudio implementation and must not + be manipulated or freed. The pointer is only guaranteed to be valid between + calls to Pa_Initialize() and Pa_Terminate(). +*/ +const PaHostApiInfo * Pa_GetHostApiInfo( PaHostApiIndex hostApi ); + + +/** Convert a static host API unique identifier, into a runtime + host API index. + + @param type A unique host API identifier belonging to the PaHostApiTypeId + enumeration. + + @return A valid PaHostApiIndex ranging from 0 to (Pa_GetHostApiCount()-1) or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + The paHostApiNotFound error code indicates that the host API specified by the + type parameter is not available. + + @see PaHostApiTypeId +*/ +PaHostApiIndex Pa_HostApiTypeIdToHostApiIndex( PaHostApiTypeId type ); + + +/** Convert a host-API-specific device index to standard PortAudio device index. + This function may be used in conjunction with the deviceCount field of + PaHostApiInfo to enumerate all devices for the specified host API. + + @param hostApi A valid host API index ranging from 0 to (Pa_GetHostApiCount()-1) + + @param hostApiDeviceIndex A valid per-host device index in the range + 0 to (Pa_GetHostApiInfo(hostApi)->deviceCount-1) + + @return A non-negative PaDeviceIndex ranging from 0 to (Pa_GetDeviceCount()-1) + or, a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. + + A paInvalidHostApi error code indicates that the host API index specified by + the hostApi parameter is out of range. + + A paInvalidDevice error code indicates that the hostApiDeviceIndex parameter + is out of range. + + @see PaHostApiInfo +*/ +PaDeviceIndex Pa_HostApiDeviceIndexToDeviceIndex( PaHostApiIndex hostApi, + int hostApiDeviceIndex ); + + + +/** Structure used to return information about a host error condition. +*/ +typedef struct PaHostErrorInfo{ + PaHostApiTypeId hostApiType; /**< the host API which returned the error code */ + long errorCode; /**< the error code returned */ + const char *errorText; /**< a textual description of the error if available, otherwise a zero-length string */ +}PaHostErrorInfo; + + +/** Return information about the last host error encountered. The error + information returned by Pa_GetLastHostErrorInfo() will never be modified + asyncronously by errors occurring in other PortAudio owned threads + (such as the thread that manages the stream callback.) + + This function is provided as a last resort, primarily to enhance debugging + by providing clients with access to all available error information. + + @return A pointer to an immutable structure constaining information about + the host error. The values in this structure will only be valid if a + PortAudio function has previously returned the paUnanticipatedHostError + error code. +*/ +const PaHostErrorInfo* Pa_GetLastHostErrorInfo( void ); + + + +/* Device enumeration and capabilities */ + +/** Retrieve the number of available devices. The number of available devices + may be zero. + + @return A non-negative value indicating the number of available devices or, + a PaErrorCode (which are always negative) if PortAudio is not initialized + or an error is encountered. +*/ +PaDeviceIndex Pa_GetDeviceCount( void ); + + +/** Retrieve the index of the default input device. The result can be + used in the inputDevice parameter to Pa_OpenStream(). + + @return The default input device index for the default host API, or paNoDevice + if no default input device is available or an error was encountered. +*/ +PaDeviceIndex Pa_GetDefaultInputDevice( void ); + + +/** Retrieve the index of the default output device. The result can be + used in the outputDevice parameter to Pa_OpenStream(). + + @return The default output device index for the defualt host API, or paNoDevice + if no default output device is available or an error was encountered. + + @note + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. +<pre> + set PA_RECOMMENDED_OUTPUT_DEVICE=1 +</pre> + The user should first determine the available device ids by using + the supplied application "pa_devs". +*/ +PaDeviceIndex Pa_GetDefaultOutputDevice( void ); + + +/** The type used to represent monotonic time in seconds that can be used + for syncronisation. The type is used for the outTime argument to the + PaStreamCallback and as the result of Pa_GetStreamTime(). + + @see PaStreamCallback, Pa_GetStreamTime +*/ +typedef double PaTime; + + +/** A type used to specify one or more sample formats. Each value indicates + a possible format for sound data passed to and from the stream callback, + Pa_ReadStream and Pa_WriteStream. + + The standard formats paFloat32, paInt16, paInt32, paInt24, paInt8 + and aUInt8 are usually implemented by all implementations. + + The floating point representation (paFloat32) uses +1.0 and -1.0 as the + maximum and minimum respectively. + + paUInt8 is an unsigned 8 bit format where 128 is considered "ground" + + The paNonInterleaved flag indicates that a multichannel buffer is passed + as a set of non-interleaved pointers. + + @see Pa_OpenStream, Pa_OpenDefaultStream, PaDeviceInfo + @see paFloat32, paInt16, paInt32, paInt24, paInt8 + @see paUInt8, paCustomFormat, paNonInterleaved +*/ +typedef unsigned long PaSampleFormat; + + +#define paFloat32 ((PaSampleFormat) 0x00000001) /**< @see PaSampleFormat */ +#define paInt32 ((PaSampleFormat) 0x00000002) /**< @see PaSampleFormat */ +#define paInt24 ((PaSampleFormat) 0x00000004) /**< Packed 24 bit format. @see PaSampleFormat */ +#define paInt16 ((PaSampleFormat) 0x00000008) /**< @see PaSampleFormat */ +#define paInt8 ((PaSampleFormat) 0x00000010) /**< @see PaSampleFormat */ +#define paUInt8 ((PaSampleFormat) 0x00000020) /**< @see PaSampleFormat */ +#define paCustomFormat ((PaSampleFormat) 0x00010000)/**< @see PaSampleFormat */ + +#define paNonInterleaved ((PaSampleFormat) 0x80000000) + +/** A structure providing information and capabilities of PortAudio devices. + Devices may support input, output or both input and output. +*/ +typedef struct PaDeviceInfo +{ + int structVersion; /* this is struct version 2 */ + const char *name; + PaHostApiIndex hostApi; /* note this is a host API index, not a type id*/ + + int maxInputChannels; + int maxOutputChannels; + + /* Default latency values for interactive performance. */ + PaTime defaultLowInputLatency; + PaTime defaultLowOutputLatency; + /* Default latency values for robust non-interactive applications (eg. playing sound files). */ + PaTime defaultHighInputLatency; + PaTime defaultHighOutputLatency; + + double defaultSampleRate; +} PaDeviceInfo; + + +/** Retrieve a pointer to a PaDeviceInfo structure containing information + about the specified device. + @return A pointer to an immutable PaDeviceInfo structure. If the device + parameter is out of range the function returns NULL. + + @param device A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid between calls to Pa_Initialize() and Pa_Terminate(). + + @see PaDeviceInfo, PaDeviceIndex +*/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceIndex device ); + + +/** Parameters for one direction (input or output) of a stream. +*/ +typedef struct PaStreamParameters +{ + /** A valid device index in the range 0 to (Pa_GetDeviceCount()-1) + specifying the device to be used or the special constant + paUseHostApiSpecificDeviceSpecification which indicates that the actual + device(s) to use are specified in hostApiSpecificStreamInfo. + This field must not be set to paNoDevice. + */ + PaDeviceIndex device; + + /** The number of channels of sound to be delivered to the + stream callback or accessed by Pa_ReadStream() or Pa_WriteStream(). + It can range from 1 to the value of maxInputChannels in the + PaDeviceInfo record for the device specified by the device parameter. + */ + int channelCount; + + /** The sample format of the buffer provided to the stream callback, + a_ReadStream() or Pa_WriteStream(). It may be any of the formats described + by the PaSampleFormat enumeration. + */ + PaSampleFormat sampleFormat; + + /** The desired latency in seconds. Where practical, implementations should + configure their latency based on these parameters, otherwise they may + choose the closest viable latency instead. Unless the suggested latency + is greater than the absolute upper limit for the device implementations + should round the suggestedLatency up to the next practial value - ie to + provide an equal or higher latency than suggestedLatency wherever possibe. + Actual latency values for an open stream may be retrieved using the + inputLatency and outputLatency fields of the PaStreamInfo structure + returned by Pa_GetStreamInfo(). + @see default*Latency in PaDeviceInfo, *Latency in PaStreamInfo + */ + PaTime suggestedLatency; + + /** An optional pointer to a host api specific data structure + containing additional information for device setup and/or stream processing. + hostApiSpecificStreamInfo is never required for correct operation, + if not used it should be set to NULL. + */ + void *hostApiSpecificStreamInfo; + +} PaStreamParameters; + + +/** Return code for Pa_IsFormatSupported indicating success. */ +#define paFormatIsSupported (0) + +/** Determine whether it would be possible to open a stream with the specified + parameters. + + @param inputParameters A structure that describes the input parameters used to + open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. inputParameters must be NULL for + output-only streams. + + @param outputParameters A structure that describes the output parameters used + to open a stream. The suggestedLatency field is ignored. See PaStreamParameters + for a description of these parameters. outputParameters must be NULL for + input-only streams. + + @param sampleRate The required sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @return Returns 0 if the format is supported, and an error code indicating why + the format is not supported otherwise. The constant paFormatIsSupported is + provided to compare with the return value for success. + + @see paFormatIsSupported, PaStreamParameters +*/ +PaError Pa_IsFormatSupported( const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); + + + +/* Streaming types and functions */ + + +/** + A single PaStream can provide multiple channels of real-time + streaming audio input and output to a client application. A stream + provides access to audio hardware represented by one or more + PaDevices. Depending on the underlying Host API, it may be possible + to open multiple streams using the same device, however this behavior + is implementation defined. Portable applications should assume that + a PaDevice may be simultaneously used by at most one PaStream. + + Pointers to PaStream objects are passed between PortAudio functions that + operate on streams. + + @see Pa_OpenStream, Pa_OpenDefaultStream, Pa_OpenDefaultStream, Pa_CloseStream, + Pa_StartStream, Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive, + Pa_GetStreamTime, Pa_GetStreamCpuLoad + +*/ +typedef void PaStream; + + +/** Can be passed as the framesPerBuffer parameter to Pa_OpenStream() + or Pa_OpenDefaultStream() to indicate that the stream callback will + accept buffers of any size. +*/ +#define paFramesPerBufferUnspecified (0) + + +/** Flags used to control the behavior of a stream. They are passed as + parameters to Pa_OpenStream or Pa_OpenDefaultStream. Multiple flags may be + ORed together. + + @see Pa_OpenStream, Pa_OpenDefaultStream + @see paNoFlag, paClipOff, paDitherOff, paNeverDropInput, + paPrimeOutputBuffersUsingStreamCallback, paPlatformSpecificFlags +*/ +typedef unsigned long PaStreamFlags; + +/** @see PaStreamFlags */ +#define paNoFlag ((PaStreamFlags) 0) + +/** Disable default clipping of out of range samples. + @see PaStreamFlags +*/ +#define paClipOff ((PaStreamFlags) 0x00000001) + +/** Disable default dithering. + @see PaStreamFlags +*/ +#define paDitherOff ((PaStreamFlags) 0x00000002) + +/** Flag requests that where possible a full duplex stream will not discard + overflowed input samples without calling the stream callback. This flag is + only valid for full duplex callback streams and only when used in combination + with the paFramesPerBufferUnspecified (0) framesPerBuffer parameter. Using + this flag incorrectly results in a paInvalidFlag error being returned from + Pa_OpenStream and Pa_OpenDefaultStream. + + @see PaStreamFlags, paFramesPerBufferUnspecified +*/ +#define paNeverDropInput ((PaStreamFlags) 0x00000004) + +/** Call the stream callback to fill initial output buffers, rather than the + default behavior of priming the buffers with zeros (silence). This flag has + no effect for input-only and blocking read/write streams. + + @see PaStreamFlags +*/ +#define paPrimeOutputBuffersUsingStreamCallback ((PaStreamFlags) 0x00000008) + +/** A mask specifying the platform specific bits. + @see PaStreamFlags +*/ +#define paPlatformSpecificFlags ((PaStreamFlags)0xFFFF0000) + +/** + Timing information for the buffers passed to the stream callback. +*/ +typedef struct PaStreamCallbackTimeInfo{ + PaTime inputBufferAdcTime; + PaTime currentTime; + PaTime outputBufferDacTime; +} PaStreamCallbackTimeInfo; + + +/** + Flag bit constants for the statusFlags to PaStreamCallback. + + @see paInputUnderflow, paInputOverflow, paOutputUnderflow, paOutputOverflow, + paPrimingOutput +*/ +typedef unsigned long PaStreamCallbackFlags; + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that + input data is all silence (zeros) because no real data is available. In a + stream opened without paFramesPerBufferUnspecified, it indicates that one or + more zero samples have been inserted into the input buffer to compensate + for an input underflow. + @see PaStreamCallbackFlags +*/ +#define paInputUnderflow ((PaStreamCallbackFlags) 0x00000001) + +/** In a stream opened with paFramesPerBufferUnspecified, indicates that data + prior to the first sample of the input buffer was discarded due to an + overflow, possibly because the stream callback is using too much CPU time. + Otherwise indicates that data prior to one or more samples in the + input buffer was discarded. + @see PaStreamCallbackFlags +*/ +#define paInputOverflow ((PaStreamCallbackFlags) 0x00000002) + +/** Indicates that output data (or a gap) was inserted, possibly because the + stream callback is using too much CPU time. + @see PaStreamCallbackFlags +*/ +#define paOutputUnderflow ((PaStreamCallbackFlags) 0x00000004) + +/** Indicates that output data will be discarded because no room is available. + @see PaStreamCallbackFlags +*/ +#define paOutputOverflow ((PaStreamCallbackFlags) 0x00000008) + +/** Some of all of the output data will be used to prime the stream, input + data may be zero. + @see PaStreamCallbackFlags +*/ +#define paPrimingOutput ((PaStreamCallbackFlags) 0x00000010) + +/** + Allowable return values for the PaStreamCallback. + @see PaStreamCallback +*/ +typedef enum PaStreamCallbackResult +{ + paContinue=0, + paComplete=1, + paAbort=2 +} PaStreamCallbackResult; + + +/** + Functions of type PaStreamCallback are implemented by PortAudio clients. + They consume, process or generate audio in response to requests from an + active PortAudio stream. + + @param input and @param output are arrays of interleaved samples, + the format, packing and number of channels used by the buffers are + determined by parameters to Pa_OpenStream(). + + @param frameCount The number of sample frames to be processed by + the stream callback. + + @param timeInfo The time in seconds when the first sample of the input + buffer was received at the audio input, the time in seconds when the first + sample of the output buffer will begin being played at the audio output, and + the time in seconds when the stream callback was called. + See also Pa_GetStreamTime() + + @param statusFlags Flags indicating whether input and/or output buffers + have been inserted or will be dropped to overcome underflow or overflow + conditions. + + @param userData The value of a user supplied pointer passed to + Pa_OpenStream() intended for storing synthesis data etc. + + @return + The stream callback should return one of the values in the + PaStreamCallbackResult enumeration. To ensure that the callback continues + to be called, it should return paContinue (0). Either paComplete or paAbort + can be returned to finish stream processing, after either of these values is + returned the callback will not be called again. If paAbort is returned the + stream will finish as soon as possible. If paComplete is returned, the stream + will continue until all buffers generated by the callback have been played. + This may be useful in applications such as soundfile players where a specific + duration of output is required. However, it is not necessary to utilise this + mechanism as Pa_StopStream(), Pa_AbortStream() or Pa_CloseStream() can also + be used to stop the stream. The callback must always fill the entire output + buffer irrespective of its return value. + + @see Pa_OpenStream, Pa_OpenDefaultStream + + @note With the exception of Pa_GetStreamCpuLoad() it is not permissable to call + PortAudio API functions from within the stream callback. +*/ +typedef int PaStreamCallback( + const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + + +/** Opens a stream for either input, output or both. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param inputParameters A structure that describes the input parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + inputParameters must be NULL for output-only streams. + + @param outputParameters A structure that describes the output parameters used by + the opened stream. See PaStreamParameters for a description of these parameters. + outputParameters must be NULL for input-only streams. + + @param sampleRate The desired sampleRate. For full-duplex streams it is the + sample rate for both input and output + + @param framesPerBuffer The number of frames passed to the stream callback + function, or the preferred block granularity for a blocking read/write stream. + The special value paFramesPerBufferUnspecified (0) may be used to request that + the stream callback will recieve an optimal (and possibly varying) number of + frames based on host requirements and the requested latency settings. + Note: With some host APIs, the use of non-zero framesPerBuffer for a callback + stream may introduce an additional layer of buffering which could introduce + additional latency. PortAudio guarantees that the additional latency + will be kept to the theoretical minimum however, it is strongly recommended + that a non-zero framesPerBuffer value only be used when your algorithm + requires a fixed number of frames per stream callback. + + @param streamFlags Flags which modify the behaviour of the streaming process. + This parameter may contain a combination of flags ORed together. Some flags may + only be relevant to certain buffer formats. + + @param streamCallback A pointer to a client supplied function that is responsible + for processing and filling input and output buffers. If this parameter is NULL + the stream will be opened in 'blocking read/write' mode. In blocking mode, + the client can receive sample data using Pa_ReadStream and write sample data + using Pa_WriteStream, the number of samples that may be read or written + without blocking is returned by Pa_GetStreamReadAvailable and + Pa_GetStreamWriteAvailable respectively. + + @param userData A client supplied pointer which is passed to the stream callback + function. It could for example, contain a pointer to instance data necessary + for processing the audio buffers. This parameter is ignored if streamCallback + is NULL. + + @return + Upon success Pa_OpenStream() returns paNoError and places a pointer to a + valid PaStream in the stream argument. The stream is inactive (stopped). + If a call to Pa_OpenStream() fails, a non-zero error code is returned (see + PaError for possible error codes) and the value of stream is invalid. + + @see PaStreamParameters, PaStreamCallback, Pa_ReadStream, Pa_WriteStream, + Pa_GetStreamReadAvailable, Pa_GetStreamWriteAvailable +*/ +PaError Pa_OpenStream( PaStream** stream, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ); + + +/** A simplified version of Pa_OpenStream() that opens the default input + and/or output devices. + + @param stream The address of a PaStream pointer which will receive + a pointer to the newly opened stream. + + @param numInputChannels The number of channels of sound that will be supplied + to the stream callback or returned by Pa_ReadStream. It can range from 1 to + the value of maxInputChannels in the PaDeviceInfo record for the default input + device. If 0 the stream is opened as an output-only stream. + + @param numOutputChannels The number of channels of sound to be delivered to the + stream callback or passed to Pa_WriteStream. It can range from 1 to the value + of maxOutputChannels in the PaDeviceInfo record for the default output dvice. + If 0 the stream is opened as an output-only stream. + + @param sampleFormat The sample format of both the input and output buffers + provided to the callback or passed to and from Pa_ReadStream and Pa_WriteStream. + sampleFormat may be any of the formats described by the PaSampleFormat + enumeration. + + @param sampleRate Same as Pa_OpenStream parameter of the same name. + @param framesPerBuffer Same as Pa_OpenStream parameter of the same name. + @param streamCallback Same as Pa_OpenStream parameter of the same name. + @param userData Same as Pa_OpenStream parameter of the same name. + + @return As for Pa_OpenStream + + @see Pa_OpenStream, PaStreamCallback +*/ +PaError Pa_OpenDefaultStream( PaStream** stream, + int numInputChannels, + int numOutputChannels, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamCallback *streamCallback, + void *userData ); + + +/** Closes an audio stream. If the audio stream is active it + discards any pending buffers as if Pa_AbortStream() had been called. +*/ +PaError Pa_CloseStream( PaStream *stream ); + + +/** Functions of type PaStreamFinishedCallback are implemented by PortAudio + clients. They can be registered with a stream using the Pa_SetStreamFinishedCallback + function. Once registered they are called when the stream becomes inactive + (ie once a call to Pa_StopStream() will not block). + A stream will become inactive after the stream callback returns non-zero, + or when Pa_StopStream or Pa_AbortStream is called. For a stream providing audio + output, if the stream callback returns paComplete, or Pa_StopStream is called, + the stream finished callback will not be called until all generated sample data + has been played. + + @param userData The userData parameter supplied to Pa_OpenStream() + + @see Pa_SetStreamFinishedCallback +*/ +typedef void PaStreamFinishedCallback( void *userData ); + + +/** Register a stream finished callback function which will be called when the + stream becomes inactive. See the description of PaStreamFinishedCallback for + further details about when the callback will be called. + + @param stream a pointer to a PaStream that is in the stopped state - if the + stream is not stopped, the stream's finished callback will remain unchanged + and an error code will be returned. + + @param streamFinishedCallback a pointer to a function with the same signature + as PaStreamFinishedCallback, that will be called when the stream becomes + inactive. Passing NULL for this parameter will un-register a previously + registered stream finished callback function. + + @return on success returns paNoError, otherwise an error code indicating the cause + of the error. + + @see PaStreamFinishedCallback +*/ +PaError Pa_SetStreamFinishedCallback( PaStream *stream, PaStreamFinishedCallback* streamFinishedCallback ); + + +/** Commences audio processing. +*/ +PaError Pa_StartStream( PaStream *stream ); + + +/** Terminates audio processing. It waits until all pending + audio buffers have been played before it returns. +*/ +PaError Pa_StopStream( PaStream *stream ); + + +/** Terminates audio processing immediately without waiting for pending + buffers to complete. +*/ +PaError Pa_AbortStream( PaStream *stream ); + + +/** Determine whether the stream is stopped. + A stream is considered to be stopped prior to a successful call to + Pa_StartStream and after a successful call to Pa_StopStream or Pa_AbortStream. + If a stream callback returns a value other than paContinue the stream is NOT + considered to be stopped. + + @return Returns one (1) when the stream is stopped, zero (0) when + the stream is running or, a PaErrorCode (which are always negative) if + PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamActive +*/ +PaError Pa_IsStreamStopped( PaStream *stream ); + + +/** Determine whether the stream is active. + A stream is active after a successful call to Pa_StartStream(), until it + becomes inactive either as a result of a call to Pa_StopStream() or + Pa_AbortStream(), or as a result of a return value other than paContinue from + the stream callback. In the latter case, the stream is considered inactive + after the last buffer has finished playing. + + @return Returns one (1) when the stream is active (ie playing or recording + audio), zero (0) when not playing or, a PaErrorCode (which are always negative) + if PortAudio is not initialized or an error is encountered. + + @see Pa_StopStream, Pa_AbortStream, Pa_IsStreamStopped +*/ +PaError Pa_IsStreamActive( PaStream *stream ); + + + +/** A structure containing unchanging information about an open stream. + @see Pa_GetStreamInfo +*/ + +typedef struct PaStreamInfo +{ + /** this is struct version 1 */ + int structVersion; + + /** The input latency of the stream in seconds. This value provides the most + accurate estimate of input latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for output-only streams. + @see PaTime + */ + PaTime inputLatency; + + /** The output latency of the stream in seconds. This value provides the most + accurate estimate of output latency available to the implementation. It may + differ significantly from the suggestedLatency value passed to Pa_OpenStream(). + The value of this field will be zero (0.) for input-only streams. + @see PaTime + */ + PaTime outputLatency; + + /** The sample rate of the stream in Hertz (samples per second). In cases + where the hardware sample rate is inaccurate and PortAudio is aware of it, + the value of this field may be different from the sampleRate parameter + passed to Pa_OpenStream(). If information about the actual hardware sample + rate is not available, this field will have the same value as the sampleRate + parameter passed to Pa_OpenStream(). + */ + double sampleRate; + +} PaStreamInfo; + + +/** Retrieve a pointer to a PaStreamInfo structure containing information + about the specified stream. + @return A pointer to an immutable PaStreamInfo structure. If the stream + parameter invalid, or an error is encountered, the function returns NULL. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @note PortAudio manages the memory referenced by the returned pointer, + the client must not manipulate or free the memory. The pointer is only + guaranteed to be valid until the specified stream is closed. + + @see PaStreamInfo +*/ +const PaStreamInfo* Pa_GetStreamInfo( PaStream *stream ); + + +/** Determine the current time for the stream according to the same clock used + to generate buffer timestamps. This time may be used for syncronising other + events to the audio stream, for example synchronizing audio to MIDI. + + @return The stream's current time in seconds, or 0 if an error occurred. + + @see PaTime, PaStreamCallback +*/ +PaTime Pa_GetStreamTime( PaStream *stream ); + + +/** Retrieve CPU usage information for the specified stream. + The "CPU Load" is a fraction of total CPU time consumed by a callback stream's + audio processing routines including, but not limited to the client supplied + stream callback. This function does not work with blocking read/write streams. + + This function may be called from the stream callback function or the + application. + + @return + A floating point value, typically between 0.0 and 1.0, where 1.0 indicates + that the stream callback is consuming the maximum number of CPU cycles possible + to maintain real-time operation. A value of 0.5 would imply that PortAudio and + the stream callback was consuming roughly 50% of the available CPU time. The + return value may exceed 1.0. A value of 0.0 will always be returned for a + blocking read/write stream, or if an error occurrs. +*/ +double Pa_GetStreamCpuLoad( PaStream* stream ); + + +/** Read samples from an input stream. The function doesn't return until + the entire buffer has been filled - this may involve waiting for the operating + system to supply the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the inputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + inputParameters->numChannels. If non-interleaved samples were requested, + buffer is a pointer to the first element of an array of non-interleaved + buffer pointers, one for each channel. + + @param frames The number of frames to be read into buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or PaInputOverflowed if input + data was discarded by PortAudio after the previous call and before this call. +*/ +PaError Pa_ReadStream( PaStream* stream, + void *buffer, + unsigned long frames ); + + +/** Write samples to an output stream. This function doesn't return until the + entire buffer has been consumed - this may involve waiting for the operating + system to consume the data. + + @param stream A pointer to an open stream previously created with Pa_OpenStream. + + @param buffer A pointer to a buffer of sample frames. The buffer contains + samples in the format specified by the outputParameters->sampleFormat field + used to open the stream, and the number of channels specified by + outputParameters->numChannels. If non-interleaved samples were requested, + buffer is a pointer to the first element of an array of non-interleaved + buffer pointers, one for each channel. + + @param frames The number of frames to be written from buffer. This parameter + is not constrained to a specific range, however high performance applications + will want to match this parameter to the framesPerBuffer parameter used + when opening the stream. + + @return On success PaNoError will be returned, or paOutputUnderflowed if + additional output data was inserted after the previous call and before this + call. +*/ +PaError Pa_WriteStream( PaStream* stream, + const void *buffer, + unsigned long frames ); + + +/** Retrieve the number of frames that can be read from the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be read from the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamReadAvailable( PaStream* stream ); + + +/** Retrieve the number of frames that can be written to the stream without + waiting. + + @return Returns a non-negative value representing the maximum number of frames + that can be written to the stream without blocking or busy waiting or, a + PaErrorCode (which are always negative) if PortAudio is not initialized or an + error is encountered. +*/ +signed long Pa_GetStreamWriteAvailable( PaStream* stream ); + + +/* Miscellaneous utilities */ + + +/** Retrieve the size of a given sample format in bytes. + + @return The size in bytes of a single sample in the specified format, + or paSampleFormatNotSupported if the format is not supported. +*/ +PaError Pa_GetSampleSize( PaSampleFormat format ); + + +/** Put the caller to sleep for at least 'msec' milliseconds. This function is + provided only as a convenience for authors of portable code (such as the tests + and examples in the PortAudio distribution.) + + The function may sleep longer than requested so don't rely on this for accurate + musical timing. +*/ +void Pa_Sleep( long msec ); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORTAUDIO_H */ diff --git a/Plugins/eSpeak/eSpeak/readclause.cpp b/Plugins/eSpeak/eSpeak/readclause.cpp new file mode 100644 index 0000000..3eca8de --- /dev/null +++ b/Plugins/eSpeak/eSpeak/readclause.cpp @@ -0,0 +1,2402 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "StdAfx.h" + +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <wctype.h> +#include <wchar.h> +#include <math.h> + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "voice.h" +#include "translate.h" + +#ifdef PLATFORM_POSIX +#include <unistd.h> +#endif + +#include <locale.h> +#define N_XML_BUF 256 + + +static const char *xmlbase = ""; // base URL from <speak> + +static int namedata_ix=0; +static int n_namedata = 0; +char *namedata = NULL; + + +static FILE *f_input = NULL; +static int ungot_char2 = 0; +char *p_textinput; +wchar_t *p_wchar_input; +static int ungot_char; +static const char *ungot_word = NULL; +static int end_of_input; + +static int ignore_text=0; // set during <sub> ... </sub> to ignore text which has been replaced by an alias +static int clear_skipping_text = 0; // next clause should clear the skipping_text flag +int count_characters = 0; +static int sayas_mode; +static int ssml_ignore_l_angle = 0; + +static const char *punct_stop = ".:!?"; // pitch fall if followed by space +static const char *punct_close = ")]}>;'\""; // always pitch fall unless followed by alnum + +// alter tone for announce punctuation or capitals +static const char *tone_punct_on = "\0016T"; // add reverberation, lower pitch +static const char *tone_punct_off = "\001T"; + +// punctuations symbols that can end a clause +static const unsigned short punct_chars[] = {',','.','?','!',':',';', + 0x2013, // en-dash + 0x2014, // em-dash + 0x2026, // elipsis + + 0x037e, // Greek question mark (looks like semicolon) + 0x0387, // Greek semicolon, ano teleia + 0x0964, // Devanagari Danda (fullstop) + + 0x0589, // Armenian period + 0x055d, // Armenian comma + 0x055c, // Armenian exclamation + 0x055e, // Armenian question + 0x055b, // Armenian emphasis mark + + 0x1362, // Ethiopic period + 0x1363, + 0x1364, + 0x1365, + 0x1366, + 0x1367, + 0x1368, + + 0x3001, // ideograph comma + 0x3002, // ideograph period + + 0xff01, // fullwidth exclamation + 0xff0c, // fullwidth comma + 0xff0e, // fullwidth period + 0xff1a, // fullwidth colon + 0xff1b, // fullwidth semicolon + 0xff1f, // fullwidth question mark + + 0}; + + +// indexed by (entry num. in punct_chars) + 1 +// bits 0-7 pause x 10mS, bits 12-14 intonation type, bit 15 don't need following space or bracket +static const unsigned int punct_attributes [] = { 0, + CLAUSE_COMMA, CLAUSE_PERIOD, CLAUSE_QUESTION, CLAUSE_EXCLAMATION, CLAUSE_COLON, CLAUSE_SEMICOLON, + CLAUSE_SEMICOLON, // en-dash + CLAUSE_SEMICOLON, // em-dash + CLAUSE_SEMICOLON, // elipsis + + CLAUSE_QUESTION, // Greek question mark + CLAUSE_SEMICOLON, // Greek semicolon + CLAUSE_PERIOD+0x8000, // Devanagari Danda (fullstop) + + CLAUSE_PERIOD+0x8000, // Armenian period + CLAUSE_COMMA, // Armenian comma + CLAUSE_EXCLAMATION + PUNCT_IN_WORD, // Armenian exclamation + CLAUSE_QUESTION + PUNCT_IN_WORD, // Armenian question + CLAUSE_PERIOD + PUNCT_IN_WORD, // Armenian emphasis mark + + CLAUSE_PERIOD, // Ethiopic period + CLAUSE_COMMA, // Ethiopic comma + CLAUSE_SEMICOLON, // Ethiopic semicolon + CLAUSE_COLON, // Ethiopic colon + CLAUSE_COLON, // Ethiopic preface colon + CLAUSE_QUESTION, // Ethiopic question mark + CLAUSE_PERIOD, // Ethiopic paragraph + + CLAUSE_COMMA+0x8000, // ideograph comma + CLAUSE_PERIOD+0x8000, // ideograph period + + CLAUSE_EXCLAMATION+0x8000, // fullwidth + CLAUSE_COMMA+0x8000, + CLAUSE_PERIOD+0x8000, + CLAUSE_COLON+0x8000, + CLAUSE_SEMICOLON+0x8000, + CLAUSE_QUESTION+0x8000, + + CLAUSE_SEMICOLON, // spare + 0 }; + + +// stack for language and voice properties +// frame 0 is for the defaults, before any ssml tags. +typedef struct { + int tag_type; + int voice_variant; + int voice_gender; + int voice_age; + char voice_name[40]; + char language[20]; +} SSML_STACK; + +#define N_SSML_STACK 20 +static int n_ssml_stack; +static SSML_STACK ssml_stack[N_SSML_STACK]; + +static char current_voice_id[40] = {0}; + + +#define N_PARAM_STACK 20 +static int n_param_stack; +PARAM_STACK param_stack[N_PARAM_STACK]; + +static int speech_parameters[N_SPEECH_PARAM]; // current values, from param_stack + +const int param_defaults[N_SPEECH_PARAM] = { + 0, // silence (internal use) + 170, // rate wpm + 100, // volume + 50, // pitch + 50, // range + 0, // punctuation + 0, // capital letters + 0, // wordgap + 0, // options + 0, // intonation + 0, + 0, + 0, // emphasis + 0, // line length + 0, // voice type +}; + + +#ifdef NEED_WCHAR_FUNCTIONS + +// additional Latin characters beyond the Latin1 character set +#define MAX_WALPHA 0x233 +// indexed by character - 0x100 +// 0=not alphabetic, 0xff=lower case, other=value to add to upper case to convert to lower case +static unsigned char walpha_tab[MAX_WALPHA-0xff] = { + 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 100 + 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 110 + 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 120 + 0xff,0xff, 1,0xff, 1,0xff, 1,0xff,0xff, 1,0xff, 1,0xff, 1,0xff, 1, // 130 + 0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff,0xff, 1,0xff, 1,0xff, 1,0xff, // 140 + 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 150 + 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 160 + 1,0xff, 1,0xff, 1,0xff, 1,0xff,0xff, 1,0xff, 1,0xff, 1,0xff,0xff, // 170 + 0xff, 210, 1,0xff, 1,0xff, 206, 1,0xff, 205, 205, 1,0xff,0xff, 79, 202, // 180 + 203, 1,0xff, 205, 207,0xff, 211, 209, 1,0xff,0xff,0xff, 211, 213,0xff, 214, // 190 + 1,0xff, 1,0xff, 1,0xff, 218, 1,0xff, 218,0xff,0xff, 1,0xff, 218, 1, // 1a0 + 0xff, 217, 217, 1,0xff, 1,0xff, 219, 1,0xff,0xff,0xff, 1,0xff,0xff,0xff, // 1b0 + 0xff,0xff,0xff,0xff, 2, 1,0xff, 2, 1,0xff, 2, 1,0xff, 1,0xff, 1, // 1c0 + 0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff,0xff, 1,0xff, // 1d0 + 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 1e0 + 0xff, 2, 1,0xff, 1,0xff,0xff,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 1f0 + 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 200 + 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 210 + 0xff, 0, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 220 + 1,0xff, 1,0xff }; // 230 + +// use ctype.h functions for Latin1 (character < 0x100) +int iswalpha(int c) +{ + if(c < 0x100) + return(isalpha(c)); + if((c > 0x3040) && (c <= 0xa700)) + return(1); // japanese, chinese characters + if(c > MAX_WALPHA) + return(0); + return(walpha_tab[c-0x100]); +} + +int iswdigit(int c) +{ + if(c < 0x100) + return(isdigit(c)); + return(0); +} + +int iswalnum(int c) +{ + if(iswdigit(c)) + return(1); + return(iswalpha(c)); +} + +int towlower(int c) +{ + int x; + if(c < 0x100) + return(tolower(c)); + if((c > MAX_WALPHA) || ((x = walpha_tab[c-0x100])==0xff)) + return(c); // already lower case + return(c + x); // convert to lower case +} + +int towupper(int c) +{ + // check whether the previous character code is the upper-case equivalent of this character + if(tolower(c-1) == c) + return(c-1); // yes, use it + return(c); // no +} + +int iswupper(int c) +{ + int x; + if(c < 0x100) + return(isupper(c)); + if(((c > MAX_WALPHA) || (x = walpha_tab[c-0x100])==0) || (x == 0xff)) + return(0); + return(1); +} + +int iswlower(int c) +{ + if(c < 0x100) + return(islower(c)); + if((c > MAX_WALPHA) || (walpha_tab[c-0x100] != 0xff)) + return(0); + return(1); +} + +int iswspace(int c) +{ + if(c < 0x100) + return(isspace(c)); + return(0); +} + +int iswpunct(int c) +{ + if(c < 0x100) + return(ispunct(c)); + return(0); +} + +const wchar_t *wcschr(const wchar_t *str, int c) +{ + while(*str != 0) + { + if(*str == c) + return(str); + str++; + } + return(NULL); +} + +const int wcslen(const wchar_t *str) +{ + int ix=0; + + while(*str != 0) + { + ix++; + } + return(ix); +} + +float wcstod(const wchar_t *str, wchar_t **tailptr) +{ + int ix; + char buf[80]; + while(isspace(*str)) str++; + for(ix=0; ix<80; ix++) + { + buf[ix] = str[ix]; + if(isspace(buf[ix])) + break; + } + *tailptr = (wchar_t *)&str[ix]; + return(atof(buf)); +} +#endif + +int towlower2(unsigned int c) +{ + // check for non-standard upper to lower case conversions + if(c == 'I') + { + if(translator->translator_name == L('t','r')) + { + c = 0x131; // I -> ı + } + } + return(towlower(c)); +} + +static void GetC_unget(int c) +{//========================== +// This is only called with UTF8 input, not wchar input + if(f_input != NULL) + ungetc(c,f_input); + else + { + p_textinput--; + *p_textinput = c; + end_of_input = 0; + } +} + +int Eof(void) +{//========== + if(ungot_char != 0) + return(0); + + if(f_input != 0) + return(feof(f_input)); + + return(end_of_input); +} + + +static int GetC_get(void) +{//====================== + int c; + + if(f_input != NULL) + { + c = fgetc(f_input); + if(feof(f_input)) c = ' '; + return(c & 0xff); + } + + if(option_multibyte == espeakCHARS_WCHAR) + { + if(*p_wchar_input == 0) + { + end_of_input = 1; + return(0); + } + + if(!end_of_input) + return(*p_wchar_input++); + } + else + { + if(*p_textinput == 0) + { + end_of_input = 1; + return(0); + } + + if(!end_of_input) + return(*p_textinput++ & 0xff); + } + return(0); +} + + +static int GetC(void) +{//================== +// Returns a unicode wide character +// Performs UTF8 checking and conversion + + int c; + int c1; + int c2; + int cbuf[4]; + int ix; + int n_bytes; + unsigned char m; + static int ungot2 = 0; + static const unsigned char mask[4] = {0xff,0x1f,0x0f,0x07}; + static const unsigned char mask2[4] = {0,0x80,0x20,0x30}; + + if((c1 = ungot_char) != 0) + { + ungot_char = 0; + return(c1); + } + + if(ungot2 != 0) + { + c1 = ungot2; + ungot2 = 0; + } + else + { + c1 = GetC_get(); + } + + if(option_multibyte == espeakCHARS_WCHAR) + { + count_characters++; + return(c1); // wchar_t text + } + + if((option_multibyte < 2) && (c1 & 0x80)) + { + // multi-byte utf8 encoding, convert to unicode + n_bytes = 0; + + if(((c1 & 0xe0) == 0xc0) && ((c1 & 0x1e) != 0)) + n_bytes = 1; + else + if((c1 & 0xf0) == 0xe0) + n_bytes = 2; + else + if(((c1 & 0xf8) == 0xf0) && ((c1 & 0x0f) <= 4)) + n_bytes = 3; + + if((ix = n_bytes) > 0) + { + c = c1 & mask[ix]; + m = mask2[ix]; + while(ix > 0) + { + if((c2 = cbuf[ix] = GetC_get()) == 0) + { + if(option_multibyte==espeakCHARS_AUTO) + option_multibyte=espeakCHARS_8BIT; // change "auto" option to "no" + GetC_unget(' '); + break; + } + + if((c2 & 0xc0) != 0x80) + { + // This is not UTF8. Change to 8-bit characterset. + if((n_bytes == 2) && (ix == 1)) + ungot2 = cbuf[2]; + GetC_unget(c2); + break; + } + m = 0x80; + c = (c << 6) + (c2 & 0x3f); + ix--; + } + if(ix == 0) + { + count_characters++; + return(c); + } + } + // top-bit-set character is not utf8, drop through to 8bit charset case + if((option_multibyte==espeakCHARS_AUTO) && !Eof()) + option_multibyte=espeakCHARS_8BIT; // change "auto" option to "no" + } + + // 8 bit character set, convert to unicode if + count_characters++; + if(c1 >= 0xa0) + return(translator->charset_a0[c1-0xa0]); + return(c1); +} // end of GetC + + +static void UngetC(int c) +{//====================== + ungot_char = c; +} + + +static const char *WordToString2(unsigned int word) +{//================================================ +// Convert a language mnemonic word into a string + int ix; + static char buf[5]; + char *p; + + p = buf; + for(ix=3; ix>=0; ix--) + { + if((*p = word >> (ix*8)) != 0) + p++; + } + *p = 0; + return(buf); +} + + +static const char *LookupSpecial(Translator *tr, const char *string, char* text_out) +{//================================================================================= + unsigned int flags[2]; + char phonemes[55]; + char phonemes2[55]; + char *string1 = (char *)string; + + if(LookupDictList(tr,&string1,phonemes,flags,0,NULL)) + { + SetWordStress(tr, phonemes, flags[0], -1, 0); + DecodePhonemes(phonemes,phonemes2); + sprintf(text_out,"[[%s]]",phonemes2); + option_phoneme_input |= 2; + return(text_out); + } + return(NULL); +} + + +static const char *LookupCharName(Translator *tr, int c) +{//===================================================== +// Find the phoneme string (in ascii) to speak the name of character c +// Used for punctuation characters and symbols + + int ix; + unsigned int flags[2]; + char single_letter[24]; + char phonemes[60]; + char phonemes2[60]; + const char *lang_name = NULL; + char *string; + static char buf[60]; + + buf[0] = 0; + flags[0] = 0; + flags[1] = 0; + single_letter[0] = 0; + single_letter[1] = '_'; + ix = utf8_out(c,&single_letter[2]); + single_letter[2+ix]=0; + + string = &single_letter[1]; + if(LookupDictList(tr, &string, phonemes, flags, 0, NULL) == 0) + { + // try _* then * + string = &single_letter[2]; + if(LookupDictList(tr, &string, phonemes, flags, 0, NULL) == 0) + { + // now try the rules + single_letter[1] = ' '; + TranslateRules(tr, &single_letter[2], phonemes, sizeof(phonemes), NULL,0,NULL); + } + } + + if((phonemes[0] == 0) && (tr->translator_name != L('e','n'))) + { + // not found, try English + SetTranslator2("en"); + string = &single_letter[1]; + single_letter[1] = '_'; + if(LookupDictList(translator2, &string, phonemes, flags, 0, NULL) == 0) + { + string = &single_letter[2]; + LookupDictList(translator2, &string, phonemes, flags, 0, NULL); + } + if(phonemes[0]) + { + lang_name = "en"; + } + else + { + SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table + } + } + + if(phonemes[0]) + { + if(lang_name) + { + SetWordStress(translator2, phonemes, flags[0], -1, 0); + DecodePhonemes(phonemes,phonemes2); + sprintf(buf,"[[_^_%s %s _^_%s]]","en",phonemes2,WordToString2(tr->translator_name)); + SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table + } + else + { + SetWordStress(tr, phonemes, flags[0], -1, 0); + DecodePhonemes(phonemes,phonemes2); + sprintf(buf,"[[%s]] ",phonemes2); + } + option_phoneme_input |= 2; + } + else + { + strcpy(buf,"[[(X1)(X1)(X1)]]"); + option_phoneme_input |= 2; + } + + return(buf); +} + +int Read4Bytes(FILE *f) +{//==================== +// Read 4 bytes (least significant first) into a word + int ix; + unsigned char c; + int acc=0; + + for(ix=0; ix<4; ix++) + { + c = fgetc(f) & 0xff; + acc += (c << (ix*8)); + } + return(acc); +} + + +static int LoadSoundFile(const char *fname, int index) +{//=================================================== + FILE *f; + char *p; + int *ip; + int length; + char fname_temp[100]; + char fname2[sizeof(path_home)+13+40]; + + if(fname == NULL) + { + // filename is already in the table + fname = soundicon_tab[index].filename; + } + + if(fname==NULL) + return(1); + + if(fname[0] != '/') + { + // a relative path, look in espeak-data/soundicons + sprintf(fname2,"%s%csoundicons%c%s",path_home,PATHSEP,PATHSEP,fname); + fname = fname2; + } + + f = NULL; +#ifdef PLATFORM_POSIX + if((f = fopen(fname,"rb")) != NULL) + { + int ix; + int fd_temp; + const char *resample; + int header[3]; + char command[sizeof(fname2)+sizeof(fname2)+40]; + + fseek(f,20,SEEK_SET); + for(ix=0; ix<3; ix++) + header[ix] = Read4Bytes(f); + + // if the sound file is not mono, 16 bit signed, at the correct sample rate, then convert it + if((header[0] != 0x10001) || (header[1] != samplerate) || (header[2] != samplerate*2)) + { + fclose(f); + f = NULL; + + if(header[2] == samplerate) + resample = ""; + else + resample = "polyphase"; + + strcpy(fname_temp,"/tmp/espeakXXXXXX"); + if((fd_temp = mkstemp(fname_temp)) >= 0) + { + close(fd_temp); +// sprintf(fname_temp,"%s.wav",tmpnam(NULL)); + sprintf(command,"sox \"%s\" -r %d -w -s -c1 %s %s\n", fname, samplerate, fname_temp, resample); + if(system(command) == 0) + { + fname = fname_temp; + } + } + } + } +#endif + + if(f == NULL) + { + f = fopen(fname,"rb"); + if(f == NULL) + { + fprintf(stderr,"Can't read temp file: %s\n",fname); + return(3); + } + } + + length = GetFileLength(fname); + fseek(f,0,SEEK_SET); + if((p = (char *)realloc(soundicon_tab[index].data, length)) == NULL) + { + fclose(f); + return(4); + } + fread(p,length,1,f); + fclose(f); + remove(fname_temp); + + ip = (int *)(&p[40]); + soundicon_tab[index].length = (*ip) / 2; // length in samples + soundicon_tab[index].data = p; + return(0); +} // end of LoadSoundFile + + +static int LookupSoundicon(int c) +{//============================== +// Find the sound icon number for a punctuation chatacter + int ix; + + for(ix=N_SOUNDICON_SLOTS; ix<n_soundicon_tab; ix++) + { + if(soundicon_tab[ix].name == c) + { + if(soundicon_tab[ix].length == 0) + { + if(LoadSoundFile(NULL,ix)!=0) + return(-1); // sound file is not available + } + return(ix); + } + } + return(-1); +} + + +static int LoadSoundFile2(const char *fname) +{//========================================= +// Load a sound file into one of the reserved slots in the sound icon table +// (if it'snot already loaded) + + int ix; + static int slot = -1; + + for(ix=0; ix<n_soundicon_tab; ix++) + { + if(((soundicon_tab[ix].filename != NULL) && strcmp(fname, soundicon_tab[ix].filename) == 0)) + return(ix); // already loaded + } + + // load the file into the next slot + slot++; + if(slot >= N_SOUNDICON_SLOTS) + slot = 0; + + if(LoadSoundFile(fname, slot) != 0) + return(-1); + + soundicon_tab[slot].filename = (char *)realloc(soundicon_tab[ix].filename, strlen(fname)+1); + strcpy(soundicon_tab[slot].filename, fname); + return(slot); +} + + + +static int AnnouncePunctuation(Translator *tr, int c1, int c2, char *buf, int bufix) +{//================================================================================= + // announce punctuation names + // c1: the punctuation character + // c2: the following character + + int punct_count; + const char *punctname; + int found = 0; + int soundicon; + char *p; + + if((soundicon = LookupSoundicon(c1)) >= 0) + { + // add an embedded command to play the soundicon + sprintf(&buf[bufix],"\001%dI ",soundicon); + UngetC(c2); + found = 1; + } + else + if((punctname = LookupCharName(tr, c1)) != NULL) + { + found = 1; + if(bufix==0) + { + punct_count=1; + while(c2 == c1) + { + punct_count++; + c2 = GetC(); + } + UngetC(c2); + + p = &buf[bufix]; + if(punct_count==1) + { + sprintf(p,"%s %s %s",tone_punct_on,punctname,tone_punct_off); + } + else + if(punct_count < 4) + { + sprintf(p,"\001+10S%s",tone_punct_on); + while(punct_count-- > 0) + sprintf(buf,"%s %s",buf,punctname); + sprintf(p,"%s %s\001-10S",buf,tone_punct_off); + } + else + { + sprintf(p,"%s %s %d %s %s", + tone_punct_on,punctname,punct_count,punctname,tone_punct_off); + return(CLAUSE_COMMA); + } + } + else + { + // end the clause now and pick up the punctuation next time + UngetC(c2); + if(option_ssml) + { + if((c1 == '<') || (c1 == '&')) + ssml_ignore_l_angle = c1; // this was < which was converted to <, don't pick it up again as < + } + ungot_char2 = c1; + buf[bufix] = ' '; + buf[bufix+1] = 0; + } + } + + if(found == 0) + return(-1); + + if(c1 == '-') + return(CLAUSE_NONE); // no pause + if((strchr_w(punct_close,c1) != NULL) && !iswalnum(c2)) + return(CLAUSE_COLON); + if(iswspace(c2) && strchr_w(punct_stop,c1)!=NULL) + return(punct_attributes[lookupwchar(punct_chars,c1)]); + + return(CLAUSE_SHORTCOMMA); +} // end of AnnouncePunctuation + +#define SSML_SPEAK 1 +#define SSML_VOICE 2 +#define SSML_PROSODY 3 +#define SSML_SAYAS 4 +#define SSML_MARK 5 +#define SSML_SENTENCE 6 +#define SSML_PARAGRAPH 7 +#define SSML_PHONEME 8 +#define SSML_SUB 9 +#define SSML_STYLE 10 +#define SSML_AUDIO 11 +#define SSML_EMPHASIS 12 +#define SSML_BREAK 13 +#define SSML_IGNORE_TEXT 14 +#define HTML_BREAK 15 +#define SSML_CLOSE 0x10 // for a closing tag, OR this with the tag type + +// these tags have no effect if they are self-closing, eg. <voice /> +static char ignore_if_self_closing[] = {0,1,1,1,1,0,0,0,0,1,1,0,1,0,1,0,0}; + + +static MNEM_TAB ssmltags[] = { + {"speak", SSML_SPEAK}, + {"voice", SSML_VOICE}, + {"prosody", SSML_PROSODY}, + {"say-as", SSML_SAYAS}, + {"mark", SSML_MARK}, + {"s", SSML_SENTENCE}, + {"p", SSML_PARAGRAPH}, + {"phoneme", SSML_PHONEME}, + {"sub", SSML_SUB}, + {"tts:style", SSML_STYLE}, + {"audio", SSML_AUDIO}, + {"emphasis", SSML_EMPHASIS}, + {"break", SSML_BREAK}, + {"metadata", SSML_IGNORE_TEXT}, + + {"br", HTML_BREAK}, + {"li", HTML_BREAK}, + {"img", HTML_BREAK}, + {"td", HTML_BREAK}, + {"h1", SSML_PARAGRAPH}, + {"h2", SSML_PARAGRAPH}, + {"h3", SSML_PARAGRAPH}, + {"h4", SSML_PARAGRAPH}, + {"hr", SSML_PARAGRAPH}, + {"script", SSML_IGNORE_TEXT}, + {"style", SSML_IGNORE_TEXT}, + {NULL,0}}; + + + + +static const char *VoiceFromStack() +{//================================ +// Use the voice properties from the SSML stack to choose a voice, and switch +// to that voice if it's not the current voice + int ix; + SSML_STACK *sp; + const char *v_id; + int voice_name_specified; + int voice_found; + espeak_VOICE voice_select; + char voice_name[40]; + char language[40]; + + strcpy(voice_name,ssml_stack[0].voice_name); + strcpy(language,ssml_stack[0].language); + voice_select.age = ssml_stack[0].voice_age; + voice_select.gender = ssml_stack[0].voice_gender; + voice_select.variant = ssml_stack[0].voice_variant; + voice_select.identifier = NULL; + + for(ix=0; ix<n_ssml_stack; ix++) + { + sp = &ssml_stack[ix]; + voice_name_specified = 0; + + if((sp->voice_name[0] != 0) && (SelectVoiceByName(NULL,sp->voice_name) != NULL)) + { + voice_name_specified = 1; + strcpy(voice_name, sp->voice_name); + language[0] = 0; + voice_select.gender = 0; + voice_select.age = 0; + voice_select.variant = 0; + } + if(sp->language[0] != 0) + { + strcpy(language, sp->language); + if(voice_name_specified == 0) + voice_name[0] = 0; // forget a previous voice name if a language is specified + } + if(sp->voice_gender != 0) + voice_select.gender = sp->voice_gender; + if(sp->voice_age != 0) + voice_select.age = sp->voice_age; + if(sp->voice_variant != 0) + voice_select.variant = sp->voice_variant; + } + + voice_select.name = voice_name; + voice_select.languages = language; + v_id = SelectVoice(&voice_select, &voice_found); + if(v_id == NULL) + return("default"); + return(v_id); +} // end of VoiceFromStack + + + +static void ProcessParamStack(char *outbuf, int &outix) +{//==================================================== +// Set the speech parameters from the parameter stack + int param; + int ix; + int value; + char buf[20]; + int new_parameters[N_SPEECH_PARAM]; + static char cmd_letter[N_SPEECH_PARAM] = {0, 'S','A','P','R', 0, 0, 0, 0, 0, 0, 0, 'F'}; // embedded command letters + + + for(param=0; param<N_SPEECH_PARAM; param++) + new_parameters[param] = -1; + + for(ix=0; ix<n_param_stack; ix++) + { + for(param=0; param<N_SPEECH_PARAM; param++) + { + if(param_stack[ix].parameter[param] >= 0) + new_parameters[param] = param_stack[ix].parameter[param]; + } + } + + for(param=0; param<N_SPEECH_PARAM; param++) + { + if((value = new_parameters[param]) != speech_parameters[param]) + { + buf[0] = 0; + + switch(param) + { + case espeakPUNCTUATION: + option_punctuation = value-1; + break; + + case espeakCAPITALS: + option_capitals = value; + break; + + case espeakRATE: + case espeakVOLUME: + case espeakPITCH: + case espeakRANGE: + case espeakEMPHASIS: + sprintf(buf,"%c%d%c",CTRL_EMBEDDED,value,cmd_letter[param]); + break; + } + + speech_parameters[param] = new_parameters[param]; + strcpy(&outbuf[outix],buf); + outix += strlen(buf); + } + } +} // end of ProcessParamStack + + +static PARAM_STACK *PushParamStack(int tag_type) +{//============================================= + int ix; + PARAM_STACK *sp; + + sp = ¶m_stack[n_param_stack]; + if(n_param_stack < (N_PARAM_STACK-1)) + n_param_stack++; + + sp->type = tag_type; + for(ix=0; ix<N_SPEECH_PARAM; ix++) + { + sp->parameter[ix] = -1; + } + return(sp); +} // end of PushParamStack + + +static void PopParamStack(int tag_type, char *outbuf, int &outix) +{//============================================================== + // unwind the stack up to and including the previous tag of this type + int ix; + int top = 0; + + if(tag_type >= SSML_CLOSE) + tag_type -= SSML_CLOSE; + + for(ix=0; ix<n_param_stack; ix++) + { + if(param_stack[ix].type == tag_type) + { + top = ix; + } + } + if(top > 0) + { + n_param_stack = top; + } + ProcessParamStack(outbuf, outix); +} // end of PopParamStack + + + +static wchar_t *GetSsmlAttribute(wchar_t *pw, const char *name) +{//============================================================ +// Gets the value string for an attribute. +// Returns NULL if the attribute is not present + int ix; + static wchar_t empty[1] = {0}; + + while(*pw != 0) + { + if(iswspace(pw[-1])) + { + ix = 0; + while(*pw == name[ix]) + { + pw++; + ix++; + } + if(name[ix]==0) + { + // found the attribute, now get the value + while(iswspace(*pw)) pw++; + if(*pw == '=') pw++; + while(iswspace(*pw)) pw++; + if(*pw == '"') + return(pw+1); + else + return(empty); + } + } + pw++; + } + return(NULL); +} // end of GetSsmlAttribute + + +static int attrcmp(const wchar_t *string1, const char *string2) +{//============================================================ + int ix; + + if(string1 == NULL) + return(1); + + for(ix=0; (string1[ix] == string2[ix]) && (string1[ix] != 0); ix++) + { + } + if((string1[ix]=='"') && (string2[ix]==0)) + return(0); + return(1); +} + + +static int attrlookup(const wchar_t *string1, const MNEM_TAB *mtab) +{//================================================================ + int ix; + + for(ix=0; mtab[ix].mnem != NULL; ix++) + { + if(attrcmp(string1,mtab[ix].mnem) == 0) + return(mtab[ix].value); + } + return(mtab[ix].value); +} + + +static int attrnumber(const wchar_t *pw, int default_value, int type) +{//================================================================== + int value = 0; + + if((pw == NULL) || !isdigit(*pw)) + return(default_value); + + while(isdigit(*pw)) + { + value = value*10 + *pw++ - '0'; + } + if((type==1) && (towlower(*pw)=='s')) + { + // time: seconds rather than ms + value *= 1000; + } + return(value); +} // end of attrnumber + + + +static int attrcopy_utf8(char *buf, const wchar_t *pw, int len) +{//============================================================ +// Convert attribute string into utf8, write to buf, and return its utf8 length + unsigned int c; + int ix = 0; + int n; + int prev_c = 0; + + if(pw != NULL) + { + while((ix < (len-4)) && ((c = *pw++) != 0)) + { + if((c=='"') && (prev_c != '\\')) + break; // " indicates end of attribute, unless preceded by backstroke + n = utf8_out(c,&buf[ix]); + ix += n; + prev_c = c; + } + } + buf[ix] = 0; + return(ix); +} // end of attrcopy_utf8 + + + +static int attr_prosody_value(int param_type, const wchar_t *pw, int *value_out) +{//============================================================================= + int sign = 0; + wchar_t *tail; + float value; + + while(iswspace(*pw)) pw++; + if(*pw == '+') + { + pw++; + sign = 1; + } + if(*pw == '-') + { + pw++; + sign = -1; + } + value = (float)wcstod(pw,&tail); + if(tail == pw) + { + // failed to find a number, return 100% + *value_out = 100; + return(2); + } + + if(*tail == '%') + { + if(sign != 0) + value = 100 + (sign * value); + *value_out = (int)value; + return(2); // percentage + } + + if((tail[0]=='s') && (tail[1]=='t')) + { + double x; + // convert from semitones to a frequency percentage + x = pow(double(2.0),double((value*sign)/12)) * 100; + *value_out = (int)x; + return(2); // percentage + } + + if(param_type == espeakRATE) + { + *value_out = (int)(value * 100); + return(2); // percentage + } + + *value_out = (int)value; + return(sign); // -1, 0, or 1 +} // end of attr_prosody_value + + +int AddNameData(const char *name, int wide) +{//======================================== +// Add the name to the namedata and return its position +// (Used by the Windows SAPI wrapper) + int ix; + int len; + void *vp; + + if(wide) + { + len = (wcslen((const wchar_t *)name)+1)*sizeof(wchar_t); + n_namedata = (n_namedata + sizeof(wchar_t) - 1) % sizeof(wchar_t); // round to wchar_t boundary + } + else + { + len = strlen(name)+1; + } + + if(namedata_ix+len >= n_namedata) + { + // allocate more space for marker names + if((vp = realloc(namedata, namedata_ix+len + 300)) == NULL) + return(-1); // failed to allocate, original data is unchanged but ignore this new name + + namedata = (char *)vp; + n_namedata = namedata_ix+len + 300; + } + memcpy(&namedata[ix = namedata_ix],name,len); + namedata_ix += len; + return(ix); +} // end of AddNameData + + +void SetVoiceStack(espeak_VOICE *v) +{//================================ + SSML_STACK *sp; + sp = &ssml_stack[0]; + + if(v == NULL) + { + memset(sp,0,sizeof(ssml_stack[0])); + return; + } + if(v->languages != NULL) + strcpy(sp->language,v->languages); + if(v->name != NULL) + strcpy(sp->voice_name,v->name); + sp->voice_variant = v->variant; + sp->voice_age = v->age; + sp->voice_gender = v->gender; +} + + +static int GetVoiceAttributes(wchar_t *pw, int tag_type) +{//===================================================== +// Determines whether voice attribute are specified in this tag, and if so, whether this means +// a voice change. +// If it's a closing tag, delete the top frame of the stack and determine whether this implies +// a voice change. +// Returns CLAUSE_BIT_VOICE if there is a voice change + + wchar_t *lang; + wchar_t *gender; + wchar_t *name; + wchar_t *age; + wchar_t *variant; + const char *new_voice_id; + SSML_STACK *ssml_sp; + + static const MNEM_TAB mnem_gender[] = { + {"male", 1}, + {"female", 2}, + {"neutral", 3}, + {NULL, 0}}; + + if(tag_type & SSML_CLOSE) + { + // delete a stack frame + if(n_ssml_stack > 1) + { + n_ssml_stack--; + } + } + else + { + // add a stack frame if any voice details are specified + lang = GetSsmlAttribute(pw,"xml:lang"); + + if(tag_type != SSML_VOICE) + { + // only expect an xml:lang attribute + name = NULL; + variant = NULL; + age = NULL; + gender = NULL; + } + else + { + name = GetSsmlAttribute(pw,"name"); + variant = GetSsmlAttribute(pw,"variant"); + age = GetSsmlAttribute(pw,"age"); + gender = GetSsmlAttribute(pw,"gender"); + } + + if((tag_type != SSML_VOICE) && (lang==NULL)) + return(0); // <s> or <p> without language spec, nothing to do + + ssml_sp = &ssml_stack[n_ssml_stack++]; + + attrcopy_utf8(ssml_sp->language,lang,sizeof(ssml_sp->language)); + attrcopy_utf8(ssml_sp->voice_name,name,sizeof(ssml_sp->voice_name)); + ssml_sp->voice_variant = attrnumber(variant,1,0)-1; + ssml_sp->voice_age = attrnumber(age,0,0); + ssml_sp->voice_gender = attrlookup(gender,mnem_gender); + ssml_sp->tag_type = tag_type; + } + + new_voice_id = VoiceFromStack(); + if(strcmp(new_voice_id,current_voice_id) != 0) + { + // add an embedded command to change the voice + strcpy(current_voice_id,new_voice_id); + return(CLAUSE_BIT_VOICE); // change of voice + } + + return(0); +} // end of GetVoiceAttributes + + +static void SetProsodyParameter(int param_type, wchar_t *attr1, PARAM_STACK *sp) +{//============================================================================= + int value; + int sign; + + static const MNEM_TAB mnem_volume[] = { + {"default",100}, + {"silent",0}, + {"x-soft",30}, + {"soft",65}, + {"medium",100}, + {"loud",150}, + {"x-loud",230}, + {NULL, -1}}; + + static const MNEM_TAB mnem_rate[] = { + {"default",100}, + {"x-slow",60}, + {"slow",80}, + {"medium",100}, + {"fast",120}, + {"x-fast",150}, + {NULL, -1}}; + + static const MNEM_TAB mnem_pitch[] = { + {"default",100}, + {"x-low",70}, + {"low",85}, + {"medium",100}, + {"high",110}, + {"x-high",120}, + {NULL, -1}}; + + static const MNEM_TAB mnem_range[] = { + {"default",100}, + {"x-low",20}, + {"low",50}, + {"medium",100}, + {"high",140}, + {"x-high",180}, + {NULL, -1}}; + + static const MNEM_TAB *mnem_tabs[5] = { + NULL, mnem_rate, mnem_volume, mnem_pitch, mnem_range }; + + + if((value = attrlookup(attr1,mnem_tabs[param_type])) >= 0) + { + // mnemonic specifies a value as a percentage of the base pitch/range/rate/volume + sp->parameter[param_type] = (param_stack[0].parameter[param_type] * value)/100; + } + else + { + sign = attr_prosody_value(param_type,attr1,&value); + + if(sign == 0) + sp->parameter[param_type] = value; // absolute value in Hz + else + if(sign == 2) + { + // change specified as percentage or in semitones + sp->parameter[param_type] = (speech_parameters[param_type] * value)/100; + } + else + { + // change specified as plus or minus Hz + sp->parameter[param_type] = speech_parameters[param_type] + (value*sign); + } + } +} // end of SetProsodyParemeter + + + +static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int &outix, int n_outbuf, int self_closing) +{//================================================================================================== +// xml_buf is the tag and attributes with a zero terminator in place of the original '>' +// returns a clause terminator value. + + unsigned int ix; + int index; + int c; + int tag_type; + int value; + int value2; + int value3; + int voice_change_flag; + wchar_t *px; + wchar_t *attr1; + wchar_t *attr2; + wchar_t *attr3; + int terminator; + char *uri; + int param_type; + char tag_name[40]; + char buf[80]; + PARAM_STACK *sp; + SSML_STACK *ssml_sp; + + static const MNEM_TAB mnem_punct[] = { + {"none", 1}, + {"all", 2}, + {"some", 3}, + {NULL, -1}}; + + static const MNEM_TAB mnem_capitals[] = { + {"no", 0}, + {"spelling", 2}, + {"icon", 1}, + {"pitch", 20}, // this is the amount by which to raise the pitch + {NULL, -1}}; + + static const MNEM_TAB mnem_interpret_as[] = { + {"characters",SAYAS_CHARS}, + {"tts:char",SAYAS_SINGLE_CHARS}, + {"tts:key",SAYAS_KEY}, + {"tts:digits",SAYAS_DIGITS}, + {"telephone",SAYAS_DIGITS1}, + {NULL, -1}}; + + static const MNEM_TAB mnem_sayas_format[] = { + {"glyphs",1}, + {NULL, -1}}; + + static const MNEM_TAB mnem_break[] = { + {"none",0}, + {"x-weak",1}, + {"weak",2}, + {"medium",3}, + {"strong",4}, + {"x-strong",5}, + {NULL,-1}}; + + static const MNEM_TAB mnem_emphasis[] = { + {"none",1}, + {"reduced",2}, + {"moderate",3}, + {"strong",4}, + {NULL,-1}}; + + static const char *prosody_attr[5] = { + NULL, "rate", "volume", "pitch", "range" }; + + for(ix=0; ix<(sizeof(tag_name)-1); ix++) + { + if(((c = xml_buf[ix]) == 0) || iswspace(c)) + break; + tag_name[ix] = tolower((char)c); + } + tag_name[ix] = 0; + + px = &xml_buf[ix]; // the tag's attributes + + if(tag_name[0] == '/') + { + tag_type = LookupMnem(ssmltags,&tag_name[1]) + SSML_CLOSE; // closing tag + } + else + { + tag_type = LookupMnem(ssmltags,tag_name); + + if(self_closing && ignore_if_self_closing[tag_type]) + return(0); + } + + voice_change_flag = 0; + terminator = CLAUSE_NONE; + ssml_sp = &ssml_stack[n_ssml_stack-1]; + + switch(tag_type) + { + case SSML_STYLE: + sp = PushParamStack(tag_type); + attr1 = GetSsmlAttribute(px,"field"); + attr2 = GetSsmlAttribute(px,"mode"); + + + if(attrcmp(attr1,"punctuation")==0) + { + value = attrlookup(attr2,mnem_punct); + sp->parameter[espeakPUNCTUATION] = value; + } + else + if(attrcmp(attr1,"capital_letters")==0) + { + value = attrlookup(attr2,mnem_capitals); + sp->parameter[espeakCAPITALS] = value; + } + ProcessParamStack(outbuf, outix); + break; + + case SSML_PROSODY: + sp = PushParamStack(tag_type); + + // look for attributes: rate, volume, pitch, range + for(param_type=espeakRATE; param_type <= espeakRANGE; param_type++) + { + if((attr1 = GetSsmlAttribute(px,prosody_attr[param_type])) != NULL) + { + SetProsodyParameter(param_type, attr1, sp); + } + } + + ProcessParamStack(outbuf, outix); + break; + + case SSML_EMPHASIS: + sp = PushParamStack(tag_type); + value = 3; // default is "moderate" + if((attr1 = GetSsmlAttribute(px,"level")) != NULL) + { + value = attrlookup(attr1,mnem_emphasis); + } + + if(translator->langopts.tone_language == 1) + { + static unsigned char emphasis_to_pitch_range[] = {50,50,40,70,90,90}; + static unsigned char emphasis_to_volume[] = {100,100,70,110,140,140}; + // tone language (eg.Chinese) do emphasis by increasing the pitch range. + sp->parameter[espeakRANGE] = emphasis_to_pitch_range[value]; + sp->parameter[espeakVOLUME] = emphasis_to_volume[value]; + } + else + { + sp->parameter[espeakEMPHASIS] = value; + } + ProcessParamStack(outbuf, outix); + break; + + case SSML_STYLE + SSML_CLOSE: + case SSML_PROSODY + SSML_CLOSE: + case SSML_EMPHASIS + SSML_CLOSE: + PopParamStack(tag_type, outbuf, outix); + break; + + case SSML_SAYAS: + attr1 = GetSsmlAttribute(px,"interpret-as"); + attr2 = GetSsmlAttribute(px,"format"); + attr3 = GetSsmlAttribute(px,"detail"); + value = attrlookup(attr1,mnem_interpret_as); + value2 = attrlookup(attr2,mnem_sayas_format); + if(value2 == 1) + value = SAYAS_GLYPHS; + + value3 = attrnumber(attr3,0,0); + + if(value == SAYAS_DIGITS) + { + if(value3 <= 1) + value = SAYAS_DIGITS1; + else + value = SAYAS_DIGITS + value3; + } + + sprintf(buf,"%c%dY",CTRL_EMBEDDED,value); + strcpy(&outbuf[outix],buf); + outix += strlen(buf); + + sayas_mode = value; // punctuation doesn't end clause during SAY-AS + break; + + case SSML_SAYAS + SSML_CLOSE: + outbuf[outix++] = CTRL_EMBEDDED; + outbuf[outix++] = 'Y'; + sayas_mode = 0; + break; + + case SSML_SUB: + if((attr1 = GetSsmlAttribute(px,"alias")) != NULL) + { + // use the alias rather than the text + ignore_text = 1; + outix += attrcopy_utf8(&outbuf[outix],attr1,n_outbuf-outix); + } + break; + + case SSML_IGNORE_TEXT: + ignore_text = 1; + break; + + case SSML_SUB + SSML_CLOSE: + case SSML_IGNORE_TEXT + SSML_CLOSE: + ignore_text = 0; + break; + + case SSML_MARK: + if((attr1 = GetSsmlAttribute(px,"name")) != NULL) + { + // add name to circular buffer of marker names + attrcopy_utf8(buf,attr1,sizeof(buf)); + + if(strcmp(skip_marker,buf)==0) + { + // This is the marker we are waiting for before starting to speak + clear_skipping_text = 1; + skip_marker[0] = 0; + return(CLAUSE_NONE); + } + + if((index = AddNameData(buf,0)) >= 0) + { + sprintf(buf,"%c%dM",CTRL_EMBEDDED,index); + strcpy(&outbuf[outix],buf); + outix += strlen(buf); + } + } + break; + + case SSML_AUDIO: + sp = PushParamStack(tag_type); + + if((attr1 = GetSsmlAttribute(px,"src")) != NULL) + { + char fname[256]; + attrcopy_utf8(buf,attr1,sizeof(buf)); + + if(uri_callback == NULL) + { + if((xmlbase != NULL) && (buf[0] != '/')) + { + sprintf(fname,"%s/%s",xmlbase,buf); + index = LoadSoundFile2(fname); + } + else + { + index = LoadSoundFile2(buf); + } + if(index >= 0) + { + sprintf(buf,"%c%dI",CTRL_EMBEDDED,index); + strcpy(&outbuf[outix],buf); + outix += strlen(buf); + sp->parameter[espeakSILENCE] = 1; + } + } + else + { + if((index = AddNameData(buf,0)) >= 0) + { + uri = &namedata[index]; + if(uri_callback(1,uri,xmlbase) == 0) + { + sprintf(buf,"%c%dU",CTRL_EMBEDDED,index); + strcpy(&outbuf[outix],buf); + outix += strlen(buf); + sp->parameter[espeakSILENCE] = 1; + } + } + } + } + ProcessParamStack(outbuf, outix); + + if(self_closing) + PopParamStack(tag_type, outbuf, outix); + return(CLAUSE_NONE); + + case SSML_AUDIO + SSML_CLOSE: + PopParamStack(tag_type, outbuf, outix); + return(CLAUSE_NONE); + + case SSML_BREAK: + value = 21; + terminator = CLAUSE_NONE; + + if((attr1 = GetSsmlAttribute(px,"strength")) != NULL) + { + static int break_value[6] = {0,7,14,21,40,80}; // *10mS + value = attrlookup(attr1,mnem_break); + if(value < 3) + { + // adjust prepause on the following word + sprintf(&outbuf[outix],"%c%dB",CTRL_EMBEDDED,value); + outix += 3; + terminator = 0; + } + value = break_value[value]; + } + if((attr2 = GetSsmlAttribute(px,"time")) != NULL) + { + value = (attrnumber(attr2,0,1) * 25) / speed.speed_factor1; // compensate for speaking speed to keep constant pause length + + if(terminator == 0) + terminator = CLAUSE_NONE; + } + if(terminator) + { + if(value > 0xfff) + value = 0xfff; + return(terminator + value); + } + break; + + case SSML_SPEAK: + if((attr1 = GetSsmlAttribute(px,"xml:base")) != NULL) + { + attrcopy_utf8(buf,attr1,sizeof(buf)); + if((index = AddNameData(buf,0)) >= 0) + { + xmlbase = &namedata[index]; + } + } + if(GetVoiceAttributes(px, tag_type) == 0) + return(0); // no voice change + return(CLAUSE_VOICE); + + case SSML_VOICE: + if(GetVoiceAttributes(px, tag_type) == 0) + return(0); // no voice change + return(CLAUSE_VOICE); + + case SSML_SPEAK + SSML_CLOSE: + // unwind stack until the previous <voice> or <speak> tag + while((n_ssml_stack > 1) && (ssml_stack[n_ssml_stack-1].tag_type != SSML_SPEAK)) + { + n_ssml_stack--; + } + return(CLAUSE_PERIOD + GetVoiceAttributes(px, tag_type)); + + case SSML_VOICE + SSML_CLOSE: + // unwind stack until the previous <voice> or <speak> tag + while((n_ssml_stack > 1) && (ssml_stack[n_ssml_stack-1].tag_type != SSML_VOICE)) + { + n_ssml_stack--; + } + +terminator=0; // ?? Sentence intonation, but no pause ?? + return(terminator + GetVoiceAttributes(px, tag_type)); + + case HTML_BREAK: + case HTML_BREAK + SSML_CLOSE: + return(CLAUSE_COLON); + + case SSML_SENTENCE: + if(ssml_sp->tag_type == SSML_SENTENCE) + { + // new sentence implies end-of-sentence + voice_change_flag = GetVoiceAttributes(px, SSML_SENTENCE+SSML_CLOSE); + } + voice_change_flag |= GetVoiceAttributes(px, tag_type); + return(CLAUSE_PARAGRAPH + voice_change_flag); + + + case SSML_PARAGRAPH: + if(ssml_sp->tag_type == SSML_SENTENCE) + { + // new paragraph implies end-of-sentence or end-of-paragraph + voice_change_flag = GetVoiceAttributes(px, SSML_SENTENCE+SSML_CLOSE); + } + if(ssml_sp->tag_type == SSML_PARAGRAPH) + { + // new paragraph implies end-of-sentence or end-of-paragraph + voice_change_flag |= GetVoiceAttributes(px, SSML_PARAGRAPH+SSML_CLOSE); + } + voice_change_flag |= GetVoiceAttributes(px, tag_type); + return(CLAUSE_PARAGRAPH + voice_change_flag); + + + case SSML_SENTENCE + SSML_CLOSE: + if(ssml_sp->tag_type == SSML_SENTENCE) + { + // end of a sentence which specified a language + voice_change_flag = GetVoiceAttributes(px, tag_type); + } + return(CLAUSE_PERIOD + voice_change_flag); + + + case SSML_PARAGRAPH + SSML_CLOSE: + if((ssml_sp->tag_type == SSML_SENTENCE) || (ssml_sp->tag_type == SSML_PARAGRAPH)) + { + // End of a paragraph which specified a language. + // (End-of-paragraph also implies end-of-sentence) + return(GetVoiceAttributes(px, tag_type) + CLAUSE_PARAGRAPH); + } + return(CLAUSE_PARAGRAPH); + } + return(0); +} // end of ProcessSsmlTag + + +static MNEM_TAB xml_char_mnemonics[] = { + {"gt",'>'}, + {"lt",'<'}, + {"amp", '&'}, + {"quot", '"'}, + {"nbsp", ' '}, + {"apos", '\''}, + {NULL,-1}}; + + +int ReadClause(Translator *tr, FILE *f_in, char *buf, short *charix, int n_buf, int *tone_type) +{//============================================================================================ +/* Find the end of the current clause. + Write the clause into buf + + returns: clause type (bits 0-7: pause x10mS, bits 8-11 intonation type) + + Also checks for blank line (paragraph) as end-of-clause indicator. + + Does not end clause for: + punctuation immediately followed by alphanumeric eg. 1.23 !Speak :path + repeated punctuation, eg. ... !!! +*/ + int c1=' '; // current character + int c2; // next character + int cprev=' '; // previous character + int parag; + int ix = 0; + int j; + int nl_count; + int linelength = 0; + int phoneme_mode = 0; + int n_xml_buf; + int terminator; + int punct; + int found; + int any_alnum = 0; + int self_closing; + int punct_data; + int stressed_word = 0; + const char *p; + wchar_t xml_buf[N_XML_BUF+1]; + +#define N_XML_BUF2 20 + char xml_buf2[N_XML_BUF2+2]; // for &<name> and &<number> sequences + static char ungot_string[N_XML_BUF2+4]; + static int ungot_string_ix = -1; + + if(clear_skipping_text) + { + skipping_text = 0; + clear_skipping_text = 0; + } + + tr->clause_upper_count = 0; + tr->clause_lower_count = 0; + end_of_input = 0; + *tone_type = 0; + +f_input = f_in; // for GetC etc + + if(ungot_word != NULL) + { + strcpy(buf,ungot_word); + ix += strlen(ungot_word); + ungot_word = NULL; + } + + if(ungot_char2 != 0) + { + c2 = ungot_char2; + } + else + { + c2 = GetC(); + } + + while(!Eof() || (ungot_char != 0) || (ungot_char2 != 0) || (ungot_string_ix >= 0)) + { + if(!iswalnum(c1)) + { + if((end_character_position > 0) && (count_characters > end_character_position)) + { + end_of_input = 1; + return(CLAUSE_EOF); + } + + if((skip_characters > 0) && (count_characters > skip_characters)) + { + // reached the specified start position + // don't break a word + clear_skipping_text = 1; + skip_characters = 0; + UngetC(c2); + return(CLAUSE_NONE); + } + } + + cprev = c1; + c1 = c2; + + if(ungot_string_ix >= 0) + { + if(ungot_string[ungot_string_ix] == 0) + ungot_string_ix = -1; + } + + if((ungot_string_ix == 0) && (ungot_char2 == 0)) + { + c1 = ungot_string[ungot_string_ix++]; + } + if(ungot_string_ix >= 0) + { + c2 = ungot_string[ungot_string_ix++]; + } + else + { + c2 = GetC(); + + if(Eof()) + { + c2 = ' '; + } + } + ungot_char2 = 0; + + if((option_ssml) && (phoneme_mode==0)) + { + if((ssml_ignore_l_angle != '&') && (c1 == '&') && ((c2=='#') || ((c2 >= 'a') && (c2 <= 'z')))) + { + n_xml_buf = 0; + c1 = c2; + while(!Eof() && (iswalnum(c1) || (c1=='#')) && (n_xml_buf < N_XML_BUF2)) + { + xml_buf2[n_xml_buf++] = c1; + c1 = GetC(); + } + xml_buf2[n_xml_buf] = 0; + c2 = GetC(); + sprintf(ungot_string,"%s%c%c",&xml_buf2[0],c1,c2); + + if(c1 == ';') + { + if(xml_buf2[0] == '#') + { + // character code number + if(xml_buf2[1] == 'x') + found = sscanf(&xml_buf2[2],"%x",(unsigned int *)(&c1)); + else + found = sscanf(&xml_buf2[1],"%d",&c1); + } + else + { + if((found = LookupMnem(xml_char_mnemonics,xml_buf2)) != -1) + { + c1 = found; + if(c2 == 0) + c2 = ' '; + } + } + } + else + { + found = -1; + } + + if(found <= 0) + { + ungot_string_ix = 0; + c1 = '&'; + c2 = ' '; + } + + if((c1 <= 0x20) && ((sayas_mode == SAYAS_SINGLE_CHARS) || (sayas_mode == SAYAS_KEY))) + { + c1 += 0xe000; // move into unicode private usage area + } + } + else + if((c1 == '<') && (ssml_ignore_l_angle != '<')) + { + // SSML Tag + n_xml_buf = 0; + c1 = c2; + while(!Eof() && (c1 != '>') && (n_xml_buf < N_XML_BUF)) + { + xml_buf[n_xml_buf++] = c1; + c1 = GetC(); + } + xml_buf[n_xml_buf] = 0; + c2 = ' '; + + buf[ix++] = ' '; + + self_closing = 0; + if(xml_buf[n_xml_buf-1] == '/') + { + // a self-closing tag + xml_buf[n_xml_buf-1] = ' '; + self_closing = 1; + } + + terminator = ProcessSsmlTag(xml_buf,buf,ix,n_buf,self_closing); + + if(terminator != 0) + { + buf[ix] = ' '; + buf[ix++] = 0; + + if(terminator & CLAUSE_BIT_VOICE) + { + // a change in voice, write the new voice name to the end of the buf + p = current_voice_id; + while((*p != 0) && (ix < (n_buf-1))) + { + buf[ix++] = *p++; + } + buf[ix++] = 0; + } + return(terminator); + } + continue; + } + } + ssml_ignore_l_angle=0; + + if(ignore_text) + continue; + + if((c2=='\n') && (option_linelength == -1)) + { + // single-line mode, return immediately on NL + if((punct = lookupwchar(punct_chars,c1)) == 0) + { + charix[ix] = count_characters - clause_start_char; + ix += utf8_out(c1,&buf[ix]); + terminator = CLAUSE_PERIOD; // line doesn't end in punctuation, assume period + } + else + { + terminator = punct_attributes[punct]; + } + buf[ix] = ' '; + buf[ix+1] = 0; + return(terminator); + } + + if((c1 == CTRL_EMBEDDED) || (c1 == ctrl_embedded)) + { + // an embedded command. If it's a voice change, end the clause + if(c2 == 'V') + { + buf[ix++] = 0; // end the clause at this point + while(!iswspace(c1 = GetC()) && !Eof() && (ix < (n_buf-1))) + buf[ix++] = c1; // add voice name to end of buffer, after the text + buf[ix++] = 0; + return(CLAUSE_VOICE); + } + else + if(c2 == 'B') + { + // set the punctuation option from an embedded command + // B0 B1 B<punct list><space> + strcpy(&buf[ix]," "); + ix += 3; + + if((c2 = GetC()) == '0') + option_punctuation = 0; + else + { + option_punctuation = 1; + option_punctlist[0] = 0; + if(c2 != '1') + { + // a list of punctuation characters to be spoken, terminated by space + j = 0; + while(!iswspace(c2) && !Eof()) + { + option_punctlist[j++] = c2; + c2 = GetC(); + buf[ix++] = ' '; + } + option_punctlist[j] = 0; // terminate punctuation list + option_punctuation = 2; + } + } + c2 = GetC(); + continue; + } + } + + linelength++; + + if(iswalnum(c1)) + any_alnum = 1; + else + { + if(stressed_word) + { + stressed_word = 0; + c1 = CHAR_EMPHASIS; // indicate this word is strtessed + UngetC(c2); + c2 = ' '; + } + + if(iswspace(c1)) + { + char *p_word; + + if(tr->translator_name == 0x6a626f) + { + // language jbo : lojban + // treat "i" or ".i" as end-of-sentence + p_word = &buf[ix-1]; + if(p_word[0] == 'i') + { + if(p_word[-1] == '.') + p_word--; + if(p_word[-1] == ' ') + { + ungot_word = "i "; + UngetC(c2); + p_word[0] = 0; + return(CLAUSE_PERIOD); + } + } + } + } + } + + if(iswupper(c1)) + { + tr->clause_upper_count++; + if((option_capitals == 2) && (sayas_mode == 0) && !iswupper(cprev)) + { + char text_buf[40]; + char text_buf2[30]; + if(LookupSpecial(tr, "_cap", text_buf2) != NULL) + { + sprintf(text_buf,"%s%s%s",tone_punct_on,text_buf2,tone_punct_off); + j = strlen(text_buf); + if((ix + j) < n_buf) + { + strcpy(&buf[ix],text_buf); + ix += j; + } + } + } + } + else + if(iswalpha(c1)) + tr->clause_lower_count++; + + if(option_phoneme_input) + { + if(phoneme_mode > 0) + phoneme_mode--; + else + if((c1 == '[') && (c2 == '[')) + phoneme_mode = -1; // input is phoneme mnemonics, so don't look for punctuation + else + if((c1 == ']') && (c2 == ']')) + phoneme_mode = 2; // set phoneme_mode to zero after the next two characters + } + + if(c1 == '\n') + { + parag = 0; + + // count consecutive newlines, ignoring other spaces + while(!Eof() && iswspace(c2)) + { + if(c2 == '\n') + parag++; + c2 = GetC(); + } + if(parag > 0) + { + // 2nd newline, assume paragraph + UngetC(c2); + + buf[ix] = ' '; + buf[ix+1] = 0; + if(parag > 3) + parag = 3; +if(option_ssml) parag=1; + return((CLAUSE_PARAGRAPH-30) + 30*parag); // several blank lines, longer pause + } + + if(linelength <= option_linelength) + { + // treat lines shorter than a specified length as end-of-clause + UngetC(c2); + buf[ix] = ' '; + buf[ix+1] = 0; + return(CLAUSE_COLON); + } + + linelength = 0; + } + + if(option_punctuation && (phoneme_mode==0) && (sayas_mode==0) && iswpunct(c1)) + { + // option is set to explicitly speak punctuation characters + // if a list of allowed punctuation has been set up, check whether the character is in it + if((option_punctuation == 1) || (wcschr(option_punctlist,c1) != NULL)) + { + if((terminator = AnnouncePunctuation(tr, c1, c2, buf, ix)) >= 0) + return(terminator); + } + } + + if((phoneme_mode==0) && (sayas_mode==0) && ((punct = lookupwchar(punct_chars,c1)) != 0)) + { + punct_data = punct_attributes[punct]; + + if(punct_data & PUNCT_IN_WORD) + { + // Armenian punctuation inside a word + stressed_word = 1; + *tone_type = punct_data >> 12 & 0xf; // override the end-of-sentence type + continue; + } + + if((iswspace(c2) || (punct_data & 0x8000) || IsBracket(c2) || (c2=='?') || (c2=='-') || Eof())) + { + // note: (c2='?') is for when a smart-quote has been replaced by '?' + buf[ix] = ' '; + buf[ix+1] = 0; + + if((c1 == '.') && (cprev == '.')) + { + c1 = 0x2026; + punct = 9; // elipsis + } + + nl_count = 0; + while(!Eof() && iswspace(c2)) + { + if(c2 == '\n') + nl_count++; + c2 = GetC(); // skip past space(s) + } + if(!Eof()) + { + UngetC(c2); + } + + if((nl_count==0) && (c1 == '.')) + { + if(iswdigit(cprev) && (tr->langopts.numbers & 0x10000)) + { + // dot after a number indicates an ordinal number + c2 = ' '; + continue; + } + if(iswlower(c2)) + { + c2 = ' '; + continue; // next word has no capital letter, this dot is probably from an abbreviation + } + if(any_alnum==0) + { + c2 = ' '; // no letters or digits yet, so probably not a sentence terminator + continue; + } + } + + punct_data = punct_attributes[punct]; + if(nl_count > 1) + { + if((punct_data == CLAUSE_QUESTION) || (punct_data == CLAUSE_EXCLAMATION)) + return(punct_data + 35); // with a longer pause + return(CLAUSE_PARAGRAPH); + } + return(punct_data); // only recognise punctuation if followed by a blank or bracket/quote + } + } + + if(speech_parameters[espeakSILENCE]==1) + continue; + + j = ix+1; + ix += utf8_out(c1,&buf[ix]); // buf[ix++] = c1; + if(!iswspace(c1) && !IsBracket(c1)) + { + charix[ix] = count_characters - clause_start_char; + while(j < ix) + charix[j++] = -1; // subsequent bytes of a multibyte character + } + + if(((ix > (n_buf-20)) && !IsAlpha(c1) && !iswdigit(c1)) || (ix >= (n_buf-2))) + { + // clause too long, getting near end of buffer, so break here + // try to break at a word boundary (unless we actually reach the end of buffer). + buf[ix] = ' '; + buf[ix+1] = 0; + UngetC(c2); + return(CLAUSE_NONE); + } + } + + if(stressed_word) + { + ix += utf8_out(CHAR_EMPHASIS, &buf[ix]); + } + buf[ix] = ' '; + buf[ix+1] = 0; + return(CLAUSE_EOF); // end of file +} // end of ReadClause + + +void InitNamedata(void) +{//==================== + namedata_ix = 0; + if(namedata != NULL) + { + free(namedata); + namedata = NULL; + n_namedata = 0; + } +} + + +void InitText2(void) +{//================= + int param; + + ungot_char = 0; + + n_ssml_stack =1; + n_param_stack = 1; + ssml_stack[0].tag_type = 0; + + for(param=0; param<N_SPEECH_PARAM; param++) + speech_parameters[param] = param_stack[0].parameter[param]; // set all speech parameters to defaults + + option_punctuation = speech_parameters[espeakPUNCTUATION]; + option_capitals = speech_parameters[espeakCAPITALS]; + + current_voice_id[0] = 0; + + ignore_text = 0; + clear_skipping_text = 0; + count_characters = -1; + sayas_mode = 0; + + xmlbase = NULL; +} + diff --git a/Plugins/eSpeak/eSpeak/setlengths.cpp b/Plugins/eSpeak/eSpeak/setlengths.cpp new file mode 100644 index 0000000..4937fde --- /dev/null +++ b/Plugins/eSpeak/eSpeak/setlengths.cpp @@ -0,0 +1,673 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "StdAfx.h" + +#include <stdlib.h> +#include <stdio.h> +#include <wctype.h> + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "voice.h" +#include "translate.h" + +extern int GetAmplitude(void); + + +// convert from words-per-minute to internal speed factor +static unsigned char speed_lookup[290] = { + 250, 246, 243, 239, 236, // 80 + 233, 229, 226, 223, 220, // 85 + 217, 214, 211, 208, 205, // 90 + 202, 197, 194, 192, 190, // 95 + 187, 185, 183, 180, 178, // 100 + 176, 174, 172, 170, 168, // 105 + 166, 164, 161, 159, 158, // 110 + 156, 154, 152, 150, 148, // 115 + 146, 145, 143, 141, 137, // 120 + 136, 135, 133, 132, 131, // 125 + 129, 128, 127, 126, 125, // 130 + 124, 122, 121, 120, 119, // 135 + 117, 116, 115, 114, 113, // 140 + 112, 111, 110, 108, 107, // 145 + 106, 105, 104, 103, 102, // 150 + 101, 100, 99, 98, 97, // 155 + 96, 95, 93, 92, 92, // 160 + 91, 90, 89, 89, 88, // 165 + 87, 87, 86, 85, 85, // 170 + 84, 83, 83, 82, 81, // 175 + 80, 80, 79, 78, 78, // 180 + 77, 76, 76, 75, 73, // 185 + 72, 72, 71, 71, 70, // 190 + 70, 69, 69, 68, 67, // 195 + 67, 66, 66, 65, 65, // 200 + 64, 64, 63, 63, 62, // 205 + 62, 61, 60, 60, 59, // 210 + 59, 58, 58, 57, 57, // 215 + 56, 56, 55, 55, 55, // 220 + 54, 54, 53, 53, 52, // 225 + 52, 51, 51, 50, 50, // 230 + 49, 49, 49, 48, 48, // 235 + 47, 47, 46, 46, 46, // 240 + 45, 45, 44, 44, 43, // 245 + 43, 43, 42, 42, 41, // 250 + 41, 41, 40, 40, 39, // 255 + 39, 39, 38, 38, 38, // 260 + 37, 37, 37, 36, 36, // 265 + 35, 35, 35, 34, 34, // 270 + 34, 33, 33, 33, 32, // 275 + 32, 32, 32, 31, 31, // 280 + 31, 30, 30, 30, 29, // 285 + 29, 29, 29, 28, 28, // 290 + 28, 28, 27, 27, 27, // 295 + 26, 26, 26, 26, 25, // 300 + 25, 25, 22, 22, 22, // 305 + 22, 22, 22, 22, 22, // 310 + 21, 21, 21, 21, 21, // 315 + 21, 20, 20, 20, 20, // 320 + 20, 15, 15, 15, 15, // 325 + 15, 15, 15, 15, 16, // 330 + 16, 16, 16, 15, 15, // 335 + 15, 15, 15, 15, 15, // 340 + 15, 17, 17, 16, 16, // 345 + 15, 15, 14, 14, 13, // 350 + 13, 12, 12, 11, 11, // 355 + 10, 10, 9, 8, 8, // 360 + 7, 6, 5, 5, 4, // 365 +}; + +// speed_factor2 adjustments for speeds 370 to 390 +static unsigned char faster[] = { +114,112,110,109,107,105,104,102,100,98, // 370-379 +96,94,92,90,88,85,83,80,78,75,72 }; //380-390 + +static int speed1 = 130; +static int speed2 = 121; +static int speed3 = 118; + + + +void SetSpeed(int control) +{//======================= + int x; + int s1; + int wpm; + int wpm2; + + wpm = embedded_value[EMBED_S]; + if(control == 2) + wpm = embedded_value[EMBED_S2]; + wpm2 = wpm; + + if(wpm > 369) wpm = 369; + if(wpm < 80) wpm = 80; + + x = speed_lookup[wpm-80]; + + if(control & 1) + { + // set speed factors for different syllable positions within a word + // these are used in CalcLengths() + speed1 = (x * voice->speedf1)/256; + speed2 = (x * voice->speedf2)/256; + speed3 = (x * voice->speedf3)/256; + } + + if(control & 2) + { + // these are used in synthesis file + s1 = (x * voice->speedf1)/256; + speed.speed_factor1 = (256 * s1)/115; // full speed adjustment, used for pause length +if(speed.speed_factor1 < 15) + speed.speed_factor1 = 15; + if(wpm >= 170) +// speed_factor2 = 100 + (166*s1)/128; // reduced speed adjustment, used for playing recorded sounds + speed.speed_factor2 = 110 + (150*s1)/128; // reduced speed adjustment, used for playing recorded sounds + else + speed.speed_factor2 = 128 + (128*s1)/130; // = 215 at 170 wpm + + if(wpm2 > 369) + { + if(wpm2 > 390) + wpm2 = 390; + speed.speed_factor2 = faster[wpm2 - 370]; + } + } + + speed.min_sample_len = 450; + speed.speed_factor3 = 110; // controls the effect of FRFLAG_LEN_MOD reduce length change + + if(wpm2 >= 370) + { + // TESTING + // use experimental fast settings if they have been specified in the Voice + if(speed.fast_settings[0] > 0) + speed.speed_factor1 = speed.fast_settings[0]; + if(speed.fast_settings[1] > 0) + speed.speed_factor2 = speed.fast_settings[1]; + if(speed.fast_settings[2] > 0) + speed.speed_factor3 = speed.fast_settings[2]; + } +} // end of SetSpeed + + +#ifdef deleted +void SetAmplitude(int amp) +{//======================= + static unsigned char amplitude_factor[] = {0,5,6,7,9,11,14,17,21,26, 32, 38,44,50,56,63,70,77,84,91,100 }; + + if((amp >= 0) && (amp <= 20)) + { + option_amplitude = (amplitude_factor[amp] * 480)/256; + } +} +#endif + + + +void SetParameter(int parameter, int value, int relative) +{//====================================================== +// parameter: reset-all, amp, pitch, speed, linelength, expression, capitals, number grouping +// relative 0=absolute 1=relative + + int new_value = value; + int default_value; + + if(relative) + { + if(parameter < 5) + { + default_value = param_defaults[parameter]; + new_value = default_value + (default_value * value)/100; + } + } + param_stack[0].parameter[parameter] = new_value; + + switch(parameter) + { + case espeakRATE: + embedded_value[EMBED_S] = new_value; + embedded_value[EMBED_S2] = new_value; + SetSpeed(3); + break; + + case espeakVOLUME: + embedded_value[EMBED_A] = new_value; + GetAmplitude(); + break; + + case espeakPITCH: + if(new_value > 99) new_value = 99; + if(new_value < 0) new_value = 0; + embedded_value[EMBED_P] = new_value; + break; + + case espeakRANGE: + if(new_value > 99) new_value = 99; + embedded_value[EMBED_R] = new_value; + break; + + case espeakLINELENGTH: + option_linelength = new_value; + break; + + case espeakWORDGAP: + option_wordgap = new_value; + break; + + case espeakINTONATION: + if((new_value & 0xff) != 0) + translator->langopts.intonation_group = new_value & 0xff; + option_tone_flags = new_value; + break; + + default: + break; + } +} // end of SetParameter + + + +static void DoEmbedded2(int &embix) +{//================================ + // There were embedded commands in the text at this point + + unsigned int word; + + do { + word = embedded_list[embix++]; + + if((word & 0x1f) == EMBED_S) + { + // speed + SetEmbedded(word & 0x7f, word >> 8); // adjusts embedded_value[EMBED_S] + SetSpeed(1); + } + } while((word & 0x80) == 0); +} + + +void CalcLengths(Translator *tr) +{//============================== + int ix; + int ix2; + PHONEME_LIST *prev; + PHONEME_LIST *next; + PHONEME_LIST *next2; + PHONEME_LIST *next3; + PHONEME_LIST *p; + PHONEME_LIST *p2; + + int stress; + int type; + static int more_syllables=0; + int pre_sonorant=0; + int pre_voiced=0; + int last_pitch = 0; + int pitch_start; + int length_mod; + int len; + int env2; + int end_of_clause; + int embedded_ix = 0; + int min_drop; + int emphasized; + int tone_mod; + unsigned char *pitch_env=NULL; + + for(ix=1; ix<n_phoneme_list; ix++) + { + prev = &phoneme_list[ix-1]; + p = &phoneme_list[ix]; + stress = p->tone & 0x7; + emphasized = p->tone & 0x8; + + next = &phoneme_list[ix+1]; + + if(p->synthflags & SFLAG_EMBEDDED) + { + DoEmbedded2(embedded_ix); + } + + type = p->type; + if(p->synthflags & SFLAG_SYLLABLE) + type = phVOWEL; + + switch(type) + { + case phPAUSE: + last_pitch = 0; + break; + + case phSTOP: + last_pitch = 0; + if(prev->type == phFRICATIVE) + p->prepause = 20; + else + if((more_syllables > 0) || (stress < 4)) + p->prepause = 40; + else + p->prepause = 60; + + if(prev->type == phSTOP) + p->prepause = 60; + + if((tr->langopts.word_gap & 0x10) && (p->newword)) + p->prepause = 60; + + if(p->ph->phflags & phLENGTHENSTOP) + p->prepause += 30; + + if(p->synthflags & SFLAG_LENGTHEN) + p->prepause += tr->langopts.long_stop; + break; + + case phVFRICATIVE: + if(next->type==phVOWEL) + { + pre_voiced = 1; + } // drop through + case phFRICATIVE: + if(p->newword) + p->prepause = 15; + + if(next->type==phPAUSE && prev->type==phNASAL && !(p->ph->phflags&phFORTIS)) + p->prepause = 25; + + if(prev->ph->phflags & phBRKAFTER) + p->prepause = 30; + + if((p->ph->phflags & phSIBILANT) && next->type==phSTOP && !next->newword) + { + if(prev->type == phVOWEL) + p->length = 200; // ?? should do this if it's from a prefix + else + p->length = 150; + } + else + p->length = 256; + + if((tr->langopts.word_gap & 0x10) && (p->newword)) + p->prepause = 30; + + break; + + case phVSTOP: + if(prev->type==phVFRICATIVE || prev->type==phFRICATIVE || (prev->ph->phflags & phSIBILANT) || (prev->type == phLIQUID)) + p->prepause = 30; + + if(next->type==phVOWEL || next->type==phLIQUID) + { + if((next->type==phVOWEL) || !next->newword) + pre_voiced = 1; + + p->prepause = 40; + + if((prev->type == phPAUSE) || (prev->type == phVOWEL)) // || (prev->ph->mnemonic == ('/'*256+'r'))) + p->prepause = 0; + else + if(p->newword==0) + { + if(prev->type==phLIQUID) + p->prepause = 20; + if(prev->type==phNASAL) + p->prepause = 12; + + if(prev->type==phSTOP && !(prev->ph->phflags & phFORTIS)) + p->prepause = 0; + } + } + if((tr->langopts.word_gap & 0x10) && (p->newword) && (p->prepause < 20)) + p->prepause = 20; + + break; + + case phLIQUID: + case phNASAL: + p->amp = tr->stress_amps[1]; // unless changed later + p->length = 256; // TEMPORARY + min_drop = 0; + + if(p->newword) + { + if(prev->type==phLIQUID) + p->prepause = 25; + if(prev->type==phVOWEL) + p->prepause = 12; + } + + if(next->type==phVOWEL) + { + pre_sonorant = 1; + } + else + if((prev->type==phVOWEL) || (prev->type == phLIQUID)) + { + p->length = prev->length; + p->pitch2 = last_pitch; + if(p->pitch2 < 7) + p->pitch2 = 7; + p->pitch1 = p->pitch2 - 8; + p->env = PITCHfall; + pre_voiced = 0; + + if(p->type == phLIQUID) + { + p->length = speed1; +//p->pitch1 = p->pitch2 - 20; // post vocalic [r/] + } + + if(next->type == phVSTOP) + { + p->length = (p->length * 160)/100; + } + if(next->type == phVFRICATIVE) + { + p->length = (p->length * 120)/100; + } + } + else + { + p->pitch2 = last_pitch; + for(ix2=ix; ix2<n_phoneme_list; ix2++) + { + if(phoneme_list[ix2].type == phVOWEL) + { + p->pitch2 = phoneme_list[ix2].pitch2; + break; + } + } + p->pitch1 = p->pitch2-8; + p->env = PITCHfall; + pre_voiced = 0; + } + break; + + case phVOWEL: + min_drop = 0; + next2 = &phoneme_list[ix+2]; + next3 = &phoneme_list[ix+3]; + + if(stress > 7) stress = 7; + + if(pre_sonorant) + p->amp = tr->stress_amps[stress]-1; + else + p->amp = tr->stress_amps[stress]; + + if(emphasized) + p->amp = 25; + + if(ix >= (n_phoneme_list-3)) + { + // last phoneme of a clause, limit its amplitude + if(p->amp > tr->langopts.param[LOPT_MAXAMP_EOC]) + p->amp = tr->langopts.param[LOPT_MAXAMP_EOC]; + } + + // is the last syllable of a word ? + more_syllables=0; + end_of_clause = 0; + for(p2 = p+1; p2->newword== 0; p2++) + { + if((p2->type == phVOWEL) && !(p2->ph->phflags & phNONSYLLABIC)) + more_syllables++; + + if(p2->ph->code == phonPAUSE_CLAUSE) + end_of_clause = 2; + } + if(p2->ph->code == phonPAUSE_CLAUSE) + end_of_clause = 2; + + if((p2->newword & 2) && (more_syllables==0)) + { + end_of_clause = 2; + } + + // calc length modifier + if((next->ph->code == phonPAUSE_VSHORT) && (next2->type == phPAUSE)) + { + // if PAUSE_VSHORT is followed by a pause, then use that + next = next2; + next2 = next3; + next3 = &phoneme_list[ix+4]; + } + + if(more_syllables==0) + { + len = tr->langopts.length_mods0[next2->ph->length_mod *10+ next->ph->length_mod]; + + if((next->newword) && (tr->langopts.word_gap & 0x20)) + { + // consider as a pause + first phoneme of the next word + length_mod = (len + tr->langopts.length_mods0[next->ph->length_mod *10+ 1])/2; + } + else + length_mod = len; + } + else + { + length_mod = tr->langopts.length_mods[next2->ph->length_mod *10+ next->ph->length_mod]; + + if((next->type == phNASAL) && (next2->type == phSTOP || next2->type == phVSTOP) && (next3->ph->phflags & phFORTIS)) + length_mod -= 15; + } + + if(more_syllables==0) + length_mod *= speed1; + else + if(more_syllables==1) + length_mod *= speed2; + else + length_mod *= speed3; + + length_mod = length_mod / 128; + + if(length_mod < 8) + length_mod = 8; // restrict how much lengths can be reduced + + if(stress >= 7) + { + // tonic syllable, include a constant component so it doesn't decrease directly with speed + length_mod += 20; + if(emphasized) + length_mod += 10; + } + else + if(emphasized) + { + length_mod += 20; + } + + if((len = tr->stress_lengths[stress]) == 0) + len = tr->stress_lengths[6]; + + length_mod = (length_mod * len)/128; + + if(p->tone_ph != 0) + { + if((tone_mod = phoneme_tab[p->tone_ph]->std_length) > 0) + { + // a tone phoneme specifies a percentage change to the length + length_mod = (length_mod * tone_mod) / 100; + } + } + + if(end_of_clause == 2) + { + // this is the last syllable in the clause, lengthen it - more for short vowels + len = p->ph->std_length; + if(tr->langopts.stress_flags & 0x40000) + len=200; // don't lengthen short vowels more than long vowels at end-of-clause + length_mod = length_mod * (256 + (280 - len)/3)/256; + } + +if(p->type != phVOWEL) +{ + length_mod = 256; // syllabic consonant + min_drop = 8; +} + p->length = length_mod; + + // pre-vocalic part + // set last-pitch + env2 = p->env; + if(env2 > 1) env2++; // version for use with preceding semi-vowel + + if(p->tone_ph != 0) + { + pitch_env = LookupEnvelope(phoneme_tab[p->tone_ph]->spect); + } + else + { + pitch_env = envelope_data[env2]; + } + + pitch_start = p->pitch1 + ((p->pitch2-p->pitch1)*pitch_env[0])/256; + + if(pre_sonorant || pre_voiced) + { + // set pitch for pre-vocalic part + if(pitch_start == 1024) + last_pitch = pitch_start; // pitch is not set + + if(pitch_start - last_pitch > 8) // was 9 + last_pitch = pitch_start - 8; + + prev->pitch1 = last_pitch; + prev->pitch2 = pitch_start; + if(last_pitch < pitch_start) + { + prev->env = PITCHrise; + p->env = env2; + } + else + { + prev->env = PITCHfall; + } + + prev->length = length_mod; + + prev->amp = p->amp; + if((prev->type != phLIQUID) && (prev->amp > 18)) + prev->amp = 18; + } + + // vowel & post-vocalic part + next->synthflags &= ~SFLAG_SEQCONTINUE; + if(next->type == phNASAL && next2->type != phVOWEL) + next->synthflags |= SFLAG_SEQCONTINUE; + + if(next->type == phLIQUID) + { + next->synthflags |= SFLAG_SEQCONTINUE; + + if(next2->type == phVOWEL) + { + next->synthflags &= ~SFLAG_SEQCONTINUE; + } + + if(next2->type != phVOWEL) + { + if(next->ph->mnemonic == ('/'*256+'r')) + { + next->synthflags &= ~SFLAG_SEQCONTINUE; +// min_drop = 15; + } + } + } + + if((min_drop > 0) && ((p->pitch2 - p->pitch1) < min_drop)) + { + p->pitch1 = p->pitch2 - min_drop; + if(p->pitch1 < 0) + p->pitch1 = 0; + } + + last_pitch = p->pitch1 + ((p->pitch2-p->pitch1)*envelope_data[p->env][127])/256; + pre_sonorant = 0; + pre_voiced = 0; + break; + } + } +} // end of CalcLengths + diff --git a/Plugins/eSpeak/eSpeak/sintab.h b/Plugins/eSpeak/eSpeak/sintab.h new file mode 100644 index 0000000..08fc18f --- /dev/null +++ b/Plugins/eSpeak/eSpeak/sintab.h @@ -0,0 +1,258 @@ +short int sin_tab[2048] = { + 0, -25, -50, -75, -100, -125, -150, -175, + -201, -226, -251, -276, -301, -326, -351, -376, + -401, -427, -452, -477, -502, -527, -552, -577, + -602, -627, -652, -677, -702, -727, -752, -777, + -802, -827, -852, -877, -902, -927, -952, -977, + -1002, -1027, -1052, -1077, -1102, -1127, -1152, -1177, + -1201, -1226, -1251, -1276, -1301, -1326, -1350, -1375, + -1400, -1425, -1449, -1474, -1499, -1523, -1548, -1573, + -1597, -1622, -1647, -1671, -1696, -1721, -1745, -1770, + -1794, -1819, -1843, -1868, -1892, -1917, -1941, -1965, + -1990, -2014, -2038, -2063, -2087, -2111, -2136, -2160, + -2184, -2208, -2233, -2257, -2281, -2305, -2329, -2353, + -2377, -2401, -2425, -2449, -2473, -2497, -2521, -2545, + -2569, -2593, -2617, -2640, -2664, -2688, -2712, -2735, + -2759, -2783, -2806, -2830, -2853, -2877, -2900, -2924, + -2947, -2971, -2994, -3018, -3041, -3064, -3088, -3111, + -3134, -3157, -3180, -3204, -3227, -3250, -3273, -3296, + -3319, -3342, -3365, -3388, -3410, -3433, -3456, -3479, + -3502, -3524, -3547, -3570, -3592, -3615, -3637, -3660, + -3682, -3705, -3727, -3749, -3772, -3794, -3816, -3839, + -3861, -3883, -3905, -3927, -3949, -3971, -3993, -4015, + -4037, -4059, -4080, -4102, -4124, -4146, -4167, -4189, + -4211, -4232, -4254, -4275, -4296, -4318, -4339, -4360, + -4382, -4403, -4424, -4445, -4466, -4487, -4508, -4529, + -4550, -4571, -4592, -4613, -4633, -4654, -4675, -4695, + -4716, -4736, -4757, -4777, -4798, -4818, -4838, -4859, + -4879, -4899, -4919, -4939, -4959, -4979, -4999, -5019, + -5039, -5059, -5078, -5098, -5118, -5137, -5157, -5176, + -5196, -5215, -5235, -5254, -5273, -5292, -5311, -5331, + -5350, -5369, -5388, -5406, -5425, -5444, -5463, -5482, + -5500, -5519, -5537, -5556, -5574, -5593, -5611, -5629, + -5648, -5666, -5684, -5702, -5720, -5738, -5756, -5774, + -5791, -5809, -5827, -5844, -5862, -5880, -5897, -5914, + -5932, -5949, -5966, -5984, -6001, -6018, -6035, -6052, + -6069, -6085, -6102, -6119, -6136, -6152, -6169, -6185, + -6202, -6218, -6235, -6251, -6267, -6283, -6299, -6315, + -6331, -6347, -6363, -6379, -6395, -6410, -6426, -6441, + -6457, -6472, -6488, -6503, -6518, -6533, -6549, -6564, + -6579, -6594, -6608, -6623, -6638, -6653, -6667, -6682, + -6696, -6711, -6725, -6739, -6754, -6768, -6782, -6796, + -6810, -6824, -6838, -6852, -6865, -6879, -6893, -6906, + -6920, -6933, -6946, -6960, -6973, -6986, -6999, -7012, + -7025, -7038, -7051, -7064, -7076, -7089, -7101, -7114, + -7126, -7139, -7151, -7163, -7175, -7187, -7199, -7211, + -7223, -7235, -7247, -7259, -7270, -7282, -7293, -7305, + -7316, -7327, -7338, -7349, -7361, -7372, -7382, -7393, + -7404, -7415, -7425, -7436, -7446, -7457, -7467, -7478, + -7488, -7498, -7508, -7518, -7528, -7538, -7548, -7557, + -7567, -7577, -7586, -7596, -7605, -7614, -7623, -7633, + -7642, -7651, -7660, -7668, -7677, -7686, -7695, -7703, + -7712, -7720, -7728, -7737, -7745, -7753, -7761, -7769, + -7777, -7785, -7793, -7800, -7808, -7816, -7823, -7830, + -7838, -7845, -7852, -7859, -7866, -7873, -7880, -7887, + -7894, -7900, -7907, -7914, -7920, -7926, -7933, -7939, + -7945, -7951, -7957, -7963, -7969, -7975, -7980, -7986, + -7991, -7997, -8002, -8008, -8013, -8018, -8023, -8028, + -8033, -8038, -8043, -8047, -8052, -8057, -8061, -8066, + -8070, -8074, -8078, -8082, -8086, -8090, -8094, -8098, + -8102, -8105, -8109, -8113, -8116, -8119, -8123, -8126, + -8129, -8132, -8135, -8138, -8141, -8143, -8146, -8149, + -8151, -8153, -8156, -8158, -8160, -8162, -8164, -8166, + -8168, -8170, -8172, -8174, -8175, -8177, -8178, -8179, + -8181, -8182, -8183, -8184, -8185, -8186, -8187, -8187, + -8188, -8189, -8189, -8190, -8190, -8190, -8190, -8190, + -8191, -8190, -8190, -8190, -8190, -8190, -8189, -8189, + -8188, -8187, -8187, -8186, -8185, -8184, -8183, -8182, + -8181, -8179, -8178, -8177, -8175, -8174, -8172, -8170, + -8168, -8166, -8164, -8162, -8160, -8158, -8156, -8153, + -8151, -8149, -8146, -8143, -8141, -8138, -8135, -8132, + -8129, -8126, -8123, -8119, -8116, -8113, -8109, -8105, + -8102, -8098, -8094, -8090, -8086, -8082, -8078, -8074, + -8070, -8066, -8061, -8057, -8052, -8047, -8043, -8038, + -8033, -8028, -8023, -8018, -8013, -8008, -8002, -7997, + -7991, -7986, -7980, -7975, -7969, -7963, -7957, -7951, + -7945, -7939, -7933, -7926, -7920, -7914, -7907, -7900, + -7894, -7887, -7880, -7873, -7866, -7859, -7852, -7845, + -7838, -7830, -7823, -7816, -7808, -7800, -7793, -7785, + -7777, -7769, -7761, -7753, -7745, -7737, -7728, -7720, + -7712, -7703, -7695, -7686, -7677, -7668, -7660, -7651, + -7642, -7633, -7623, -7614, -7605, -7596, -7586, -7577, + -7567, -7557, -7548, -7538, -7528, -7518, -7508, -7498, + -7488, -7478, -7467, -7457, -7446, -7436, -7425, -7415, + -7404, -7393, -7382, -7372, -7361, -7349, -7338, -7327, + -7316, -7305, -7293, -7282, -7270, -7259, -7247, -7235, + -7223, -7211, -7199, -7187, -7175, -7163, -7151, -7139, + -7126, -7114, -7101, -7089, -7076, -7064, -7051, -7038, + -7025, -7012, -6999, -6986, -6973, -6960, -6946, -6933, + -6920, -6906, -6893, -6879, -6865, -6852, -6838, -6824, + -6810, -6796, -6782, -6768, -6754, -6739, -6725, -6711, + -6696, -6682, -6667, -6653, -6638, -6623, -6608, -6594, + -6579, -6564, -6549, -6533, -6518, -6503, -6488, -6472, + -6457, -6441, -6426, -6410, -6395, -6379, -6363, -6347, + -6331, -6315, -6299, -6283, -6267, -6251, -6235, -6218, + -6202, -6185, -6169, -6152, -6136, -6119, -6102, -6085, + -6069, -6052, -6035, -6018, -6001, -5984, -5966, -5949, + -5932, -5914, -5897, -5880, -5862, -5844, -5827, -5809, + -5791, -5774, -5756, -5738, -5720, -5702, -5684, -5666, + -5648, -5629, -5611, -5593, -5574, -5556, -5537, -5519, + -5500, -5482, -5463, -5444, -5425, -5406, -5388, -5369, + -5350, -5331, -5311, -5292, -5273, -5254, -5235, -5215, + -5196, -5176, -5157, -5137, -5118, -5098, -5078, -5059, + -5039, -5019, -4999, -4979, -4959, -4939, -4919, -4899, + -4879, -4859, -4838, -4818, -4798, -4777, -4757, -4736, + -4716, -4695, -4675, -4654, -4633, -4613, -4592, -4571, + -4550, -4529, -4508, -4487, -4466, -4445, -4424, -4403, + -4382, -4360, -4339, -4318, -4296, -4275, -4254, -4232, + -4211, -4189, -4167, -4146, -4124, -4102, -4080, -4059, + -4037, -4015, -3993, -3971, -3949, -3927, -3905, -3883, + -3861, -3839, -3816, -3794, -3772, -3749, -3727, -3705, + -3682, -3660, -3637, -3615, -3592, -3570, -3547, -3524, + -3502, -3479, -3456, -3433, -3410, -3388, -3365, -3342, + -3319, -3296, -3273, -3250, -3227, -3204, -3180, -3157, + -3134, -3111, -3088, -3064, -3041, -3018, -2994, -2971, + -2947, -2924, -2900, -2877, -2853, -2830, -2806, -2783, + -2759, -2735, -2712, -2688, -2664, -2640, -2617, -2593, + -2569, -2545, -2521, -2497, -2473, -2449, -2425, -2401, + -2377, -2353, -2329, -2305, -2281, -2257, -2233, -2208, + -2184, -2160, -2136, -2111, -2087, -2063, -2038, -2014, + -1990, -1965, -1941, -1917, -1892, -1868, -1843, -1819, + -1794, -1770, -1745, -1721, -1696, -1671, -1647, -1622, + -1597, -1573, -1548, -1523, -1499, -1474, -1449, -1425, + -1400, -1375, -1350, -1326, -1301, -1276, -1251, -1226, + -1201, -1177, -1152, -1127, -1102, -1077, -1052, -1027, + -1002, -977, -952, -927, -902, -877, -852, -827, + -802, -777, -752, -727, -702, -677, -652, -627, + -602, -577, -552, -527, -502, -477, -452, -427, + -401, -376, -351, -326, -301, -276, -251, -226, + -201, -175, -150, -125, -100, -75, -50, -25, + 0, 25, 50, 75, 100, 125, 150, 175, + 201, 226, 251, 276, 301, 326, 351, 376, + 401, 427, 452, 477, 502, 527, 552, 577, + 602, 627, 652, 677, 702, 727, 752, 777, + 802, 827, 852, 877, 902, 927, 952, 977, + 1002, 1027, 1052, 1077, 1102, 1127, 1152, 1177, + 1201, 1226, 1251, 1276, 1301, 1326, 1350, 1375, + 1400, 1425, 1449, 1474, 1499, 1523, 1548, 1573, + 1597, 1622, 1647, 1671, 1696, 1721, 1745, 1770, + 1794, 1819, 1843, 1868, 1892, 1917, 1941, 1965, + 1990, 2014, 2038, 2063, 2087, 2111, 2136, 2160, + 2184, 2208, 2233, 2257, 2281, 2305, 2329, 2353, + 2377, 2401, 2425, 2449, 2473, 2497, 2521, 2545, + 2569, 2593, 2617, 2640, 2664, 2688, 2712, 2735, + 2759, 2783, 2806, 2830, 2853, 2877, 2900, 2924, + 2947, 2971, 2994, 3018, 3041, 3064, 3088, 3111, + 3134, 3157, 3180, 3204, 3227, 3250, 3273, 3296, + 3319, 3342, 3365, 3388, 3410, 3433, 3456, 3479, + 3502, 3524, 3547, 3570, 3592, 3615, 3637, 3660, + 3682, 3705, 3727, 3749, 3772, 3794, 3816, 3839, + 3861, 3883, 3905, 3927, 3949, 3971, 3993, 4015, + 4037, 4059, 4080, 4102, 4124, 4146, 4167, 4189, + 4211, 4232, 4254, 4275, 4296, 4318, 4339, 4360, + 4382, 4403, 4424, 4445, 4466, 4487, 4508, 4529, + 4550, 4571, 4592, 4613, 4633, 4654, 4675, 4695, + 4716, 4736, 4757, 4777, 4798, 4818, 4838, 4859, + 4879, 4899, 4919, 4939, 4959, 4979, 4999, 5019, + 5039, 5059, 5078, 5098, 5118, 5137, 5157, 5176, + 5196, 5215, 5235, 5254, 5273, 5292, 5311, 5331, + 5350, 5369, 5388, 5406, 5425, 5444, 5463, 5482, + 5500, 5519, 5537, 5556, 5574, 5593, 5611, 5629, + 5648, 5666, 5684, 5702, 5720, 5738, 5756, 5774, + 5791, 5809, 5827, 5844, 5862, 5880, 5897, 5914, + 5932, 5949, 5966, 5984, 6001, 6018, 6035, 6052, + 6069, 6085, 6102, 6119, 6136, 6152, 6169, 6185, + 6202, 6218, 6235, 6251, 6267, 6283, 6299, 6315, + 6331, 6347, 6363, 6379, 6395, 6410, 6426, 6441, + 6457, 6472, 6488, 6503, 6518, 6533, 6549, 6564, + 6579, 6594, 6608, 6623, 6638, 6653, 6667, 6682, + 6696, 6711, 6725, 6739, 6754, 6768, 6782, 6796, + 6810, 6824, 6838, 6852, 6865, 6879, 6893, 6906, + 6920, 6933, 6946, 6960, 6973, 6986, 6999, 7012, + 7025, 7038, 7051, 7064, 7076, 7089, 7101, 7114, + 7126, 7139, 7151, 7163, 7175, 7187, 7199, 7211, + 7223, 7235, 7247, 7259, 7270, 7282, 7293, 7305, + 7316, 7327, 7338, 7349, 7361, 7372, 7382, 7393, + 7404, 7415, 7425, 7436, 7446, 7457, 7467, 7478, + 7488, 7498, 7508, 7518, 7528, 7538, 7548, 7557, + 7567, 7577, 7586, 7596, 7605, 7614, 7623, 7633, + 7642, 7651, 7660, 7668, 7677, 7686, 7695, 7703, + 7712, 7720, 7728, 7737, 7745, 7753, 7761, 7769, + 7777, 7785, 7793, 7800, 7808, 7816, 7823, 7830, + 7838, 7845, 7852, 7859, 7866, 7873, 7880, 7887, + 7894, 7900, 7907, 7914, 7920, 7926, 7933, 7939, + 7945, 7951, 7957, 7963, 7969, 7975, 7980, 7986, + 7991, 7997, 8002, 8008, 8013, 8018, 8023, 8028, + 8033, 8038, 8043, 8047, 8052, 8057, 8061, 8066, + 8070, 8074, 8078, 8082, 8086, 8090, 8094, 8098, + 8102, 8105, 8109, 8113, 8116, 8119, 8123, 8126, + 8129, 8132, 8135, 8138, 8141, 8143, 8146, 8149, + 8151, 8153, 8156, 8158, 8160, 8162, 8164, 8166, + 8168, 8170, 8172, 8174, 8175, 8177, 8178, 8179, + 8181, 8182, 8183, 8184, 8185, 8186, 8187, 8187, + 8188, 8189, 8189, 8190, 8190, 8190, 8190, 8190, + 8191, 8190, 8190, 8190, 8190, 8190, 8189, 8189, + 8188, 8187, 8187, 8186, 8185, 8184, 8183, 8182, + 8181, 8179, 8178, 8177, 8175, 8174, 8172, 8170, + 8168, 8166, 8164, 8162, 8160, 8158, 8156, 8153, + 8151, 8149, 8146, 8143, 8141, 8138, 8135, 8132, + 8129, 8126, 8123, 8119, 8116, 8113, 8109, 8105, + 8102, 8098, 8094, 8090, 8086, 8082, 8078, 8074, + 8070, 8066, 8061, 8057, 8052, 8047, 8043, 8038, + 8033, 8028, 8023, 8018, 8013, 8008, 8002, 7997, + 7991, 7986, 7980, 7975, 7969, 7963, 7957, 7951, + 7945, 7939, 7933, 7926, 7920, 7914, 7907, 7900, + 7894, 7887, 7880, 7873, 7866, 7859, 7852, 7845, + 7838, 7830, 7823, 7816, 7808, 7800, 7793, 7785, + 7777, 7769, 7761, 7753, 7745, 7737, 7728, 7720, + 7712, 7703, 7695, 7686, 7677, 7668, 7660, 7651, + 7642, 7633, 7623, 7614, 7605, 7596, 7586, 7577, + 7567, 7557, 7548, 7538, 7528, 7518, 7508, 7498, + 7488, 7478, 7467, 7457, 7446, 7436, 7425, 7415, + 7404, 7393, 7382, 7372, 7361, 7349, 7338, 7327, + 7316, 7305, 7293, 7282, 7270, 7259, 7247, 7235, + 7223, 7211, 7199, 7187, 7175, 7163, 7151, 7139, + 7126, 7114, 7101, 7089, 7076, 7064, 7051, 7038, + 7025, 7012, 6999, 6986, 6973, 6960, 6946, 6933, + 6920, 6906, 6893, 6879, 6865, 6852, 6838, 6824, + 6810, 6796, 6782, 6768, 6754, 6739, 6725, 6711, + 6696, 6682, 6667, 6653, 6638, 6623, 6608, 6594, + 6579, 6564, 6549, 6533, 6518, 6503, 6488, 6472, + 6457, 6441, 6426, 6410, 6395, 6379, 6363, 6347, + 6331, 6315, 6299, 6283, 6267, 6251, 6235, 6218, + 6202, 6185, 6169, 6152, 6136, 6119, 6102, 6085, + 6069, 6052, 6035, 6018, 6001, 5984, 5966, 5949, + 5932, 5914, 5897, 5880, 5862, 5844, 5827, 5809, + 5791, 5774, 5756, 5738, 5720, 5702, 5684, 5666, + 5648, 5629, 5611, 5593, 5574, 5556, 5537, 5519, + 5500, 5482, 5463, 5444, 5425, 5406, 5388, 5369, + 5350, 5331, 5311, 5292, 5273, 5254, 5235, 5215, + 5196, 5176, 5157, 5137, 5118, 5098, 5078, 5059, + 5039, 5019, 4999, 4979, 4959, 4939, 4919, 4899, + 4879, 4859, 4838, 4818, 4798, 4777, 4757, 4736, + 4716, 4695, 4675, 4654, 4633, 4613, 4592, 4571, + 4550, 4529, 4508, 4487, 4466, 4445, 4424, 4403, + 4382, 4360, 4339, 4318, 4296, 4275, 4254, 4232, + 4211, 4189, 4167, 4146, 4124, 4102, 4080, 4059, + 4037, 4015, 3993, 3971, 3949, 3927, 3905, 3883, + 3861, 3839, 3816, 3794, 3772, 3749, 3727, 3705, + 3682, 3660, 3637, 3615, 3592, 3570, 3547, 3524, + 3502, 3479, 3456, 3433, 3410, 3388, 3365, 3342, + 3319, 3296, 3273, 3250, 3227, 3204, 3180, 3157, + 3134, 3111, 3088, 3064, 3041, 3018, 2994, 2971, + 2947, 2924, 2900, 2877, 2853, 2830, 2806, 2783, + 2759, 2735, 2712, 2688, 2664, 2640, 2617, 2593, + 2569, 2545, 2521, 2497, 2473, 2449, 2425, 2401, + 2377, 2353, 2329, 2305, 2281, 2257, 2233, 2208, + 2184, 2160, 2136, 2111, 2087, 2063, 2038, 2014, + 1990, 1965, 1941, 1917, 1892, 1868, 1843, 1819, + 1794, 1770, 1745, 1721, 1696, 1671, 1647, 1622, + 1597, 1573, 1548, 1523, 1499, 1474, 1449, 1425, + 1400, 1375, 1350, 1326, 1301, 1276, 1251, 1226, + 1201, 1177, 1152, 1127, 1102, 1077, 1052, 1027, + 1002, 977, 952, 927, 902, 877, 852, 827, + 802, 777, 752, 727, 702, 677, 652, 627, + 602, 577, 552, 527, 502, 477, 452, 427, + 401, 376, 351, 326, 301, 276, 251, 226, + 201, 175, 150, 125, 100, 75, 50, 25, + }; diff --git a/Plugins/eSpeak/eSpeak/speak_lib.cpp b/Plugins/eSpeak/eSpeak/speak_lib.cpp new file mode 100644 index 0000000..29c3e84 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/speak_lib.cpp @@ -0,0 +1,1153 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "StdAfx.h" + +#include "stdio.h" +#include "ctype.h" +#include "string.h" +#include "stdlib.h" +#include "wchar.h" +#include "locale.h" +#include <assert.h> +#include <time.h> + +#include "speech.h" + +#include <sys/stat.h> +#ifndef PLATFORM_WINDOWS +#include <unistd.h> +#endif + +#include "speak_lib.h" +#include "phoneme.h" +#include "synthesize.h" +#include "voice.h" +#include "translate.h" +#include "debug.h" + +#include "fifo.h" +#include "event.h" +#include "wave.h" + +unsigned char *outbuf=NULL; +extern espeak_VOICE voice_selected; + +espeak_EVENT *event_list=NULL; +int event_list_ix=0; +int n_event_list; +long count_samples; +void* my_audio=NULL; + +static unsigned int my_unique_identifier=0; +static void* my_user_data=NULL; +static espeak_AUDIO_OUTPUT my_mode=AUDIO_OUTPUT_SYNCHRONOUS; +static int synchronous_mode = 1; +t_espeak_callback* synth_callback = NULL; +int (* uri_callback)(int, const char *, const char *) = NULL; +int (* phoneme_callback)(const char *) = NULL; + +char path_home[N_PATH_HOME]; // this is the espeak-data directory + + +#ifdef USE_ASYNC + +static int dispatch_audio(short* outbuf, int length, espeak_EVENT* event) +{//====================================================================== + ENTER("dispatch_audio"); + + int a_wave_can_be_played = fifo_is_command_enabled(); + +#ifdef DEBUG_ENABLED + SHOW("*** dispatch_audio > uid=%d, [write=%p (%d bytes)], sample=%d, a_wave_can_be_played = %d\n", + (event) ? event->unique_identifier : 0, wave_test_get_write_buffer(), 2*length, + (event) ? event->sample : 0, + a_wave_can_be_played); +#endif + + switch(my_mode) + { + case AUDIO_OUTPUT_PLAYBACK: + { + if (outbuf && length && a_wave_can_be_played) + { + wave_write (my_audio, (char*)outbuf, 2*length); + } + + while(a_wave_can_be_played) { + // TBD: some event are filtered here but some insight might be given + // TBD: in synthesise.cpp for avoiding to create WORDs with size=0. + // TBD: For example sentence "or ALT)." returns three words + // "or", "ALT" and "". + // TBD: the last one has its size=0. + if (event && (event->type == espeakEVENT_WORD) && (event->length==0)) + { + break; + } + espeak_ERROR a_error = event_declare(event); + if (a_error != EE_BUFFER_FULL) + { + break; + } + SHOW_TIME("dispatch_audio > EE_BUFFER_FULL\n"); + usleep(10000); + a_wave_can_be_played = fifo_is_command_enabled(); + } + } + break; + + case AUDIO_OUTPUT_RETRIEVAL: + if (synth_callback) + { + synth_callback(outbuf, length, event); + } + break; + + case AUDIO_OUTPUT_SYNCHRONOUS: + case AUDIO_OUTPUT_SYNCH_PLAYBACK: + break; + } + + if (!a_wave_can_be_played) + { + SHOW_TIME("dispatch_audio > synth must be stopped!\n"); + } + + SHOW_TIME("LEAVE dispatch_audio\n"); + + return (a_wave_can_be_played==0); // 1 = stop synthesis +} + + + +static int create_events(short* outbuf, int length, espeak_EVENT* event, uint32_t the_write_pos) +{//===================================================================== + int finished; + int i=0; + + // The audio data are written to the output device. + // The list of events in event_list (index: event_list_ix) is read: + // Each event is declared to the "event" object which stores them internally. + // The event object is responsible of calling the external callback + // as soon as the relevant audio sample is played. + + do + { // for each event + espeak_EVENT* event; + if (event_list_ix == 0) + { + event = NULL; + } + else + { + event = event_list + i; +#ifdef DEBUG_ENABLED + SHOW("Synthesize: event->sample(%d) + %d = %d\n", event->sample, the_write_pos, event->sample + the_write_pos); +#endif + event->sample += the_write_pos; + } +#ifdef DEBUG_ENABLED + SHOW("*** Synthesize: i=%d (event_list_ix=%d), length=%d\n",i,event_list_ix,length); +#endif + finished = dispatch_audio((short *)outbuf, length, event); + length = 0; // the wave data are played once. + i++; + } while((i < event_list_ix) && !finished); + return finished; +} + + +int sync_espeak_terminated_msg( uint unique_identifier, void* user_data) +{//===================================================================== + ENTER("sync_espeak_terminated_msg"); + + int finished=0; + + memset(event_list, 0, 2*sizeof(espeak_EVENT)); + + event_list[0].type = espeakEVENT_MSG_TERMINATED; + event_list[0].unique_identifier = unique_identifier; + event_list[0].user_data = user_data; + event_list[1].type = espeakEVENT_LIST_TERMINATED; + event_list[1].unique_identifier = unique_identifier; + event_list[1].user_data = user_data; + + if (my_mode==AUDIO_OUTPUT_PLAYBACK) + { + while(1) + { + espeak_ERROR a_error = event_declare(event_list); + if (a_error != EE_BUFFER_FULL) + { + break; + } + SHOW_TIME("sync_espeak_terminated_msg > EE_BUFFER_FULL\n"); + usleep(10000); + } + } + else + { + if (synth_callback) + { + finished=synth_callback(NULL,0,event_list); + } + } + return finished; +} + +#endif + + +static void select_output(espeak_AUDIO_OUTPUT output_type) +{//======================================================= + my_mode = output_type; + my_audio = NULL; + synchronous_mode = 1; + option_waveout = 1; // inhibit portaudio callback from wavegen.cpp + + switch(my_mode) + { + case AUDIO_OUTPUT_PLAYBACK: + synchronous_mode = 0; +#ifdef USE_ASYNC + wave_init(); + wave_set_callback_is_output_enabled( fifo_is_command_enabled); + my_audio = wave_open("alsa"); + event_init(); +#endif + break; + + case AUDIO_OUTPUT_RETRIEVAL: + synchronous_mode = 0; + break; + + case AUDIO_OUTPUT_SYNCHRONOUS: + break; + + case AUDIO_OUTPUT_SYNCH_PLAYBACK: + option_waveout = 0; + WavegenInitSound(); + break; + } +} // end of select_output + + + + +int GetFileLength(const char *filename) +{//==================================== + struct stat statbuf; + + if(stat(filename,&statbuf) != 0) + return(0); + + if((statbuf.st_mode & S_IFMT) == S_IFDIR) + // if(S_ISDIR(statbuf.st_mode)) + return(-2); // a directory + + return(statbuf.st_size); +} // end of GetFileLength + + +char *Alloc(int size) +{//================== + char *p; + if((p = (char *)malloc(size)) == NULL) + fprintf(stderr,"Can't allocate memory\n"); // I was told that size+1 fixes a crash on 64-bit systems + return(p); +} + +void Free(void *ptr) +{//================= + if(ptr != NULL) + free(ptr); +} + + + +static void init_path(const char *path) +{//==================================== +#ifdef PLATFORM_WINDOWS + HKEY RegKey; + unsigned long size; + unsigned long var_type; + char *env; + unsigned char buf[sizeof(path_home)-13]; + + if(path != NULL) + { + strncpy(path_home,path,sizeof(path_home)-1); + path_home[sizeof(path_home)-1]=0; + return; + } + + if((env = getenv("ESPEAK_DATA_PATH")) != NULL) + { + sprintf(path_home,"%s/espeak-data",env); + if(GetFileLength(path_home) == -2) + return; // an espeak-data directory exists + } + + buf[0] = 0; + RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Speech\\Voices\\Tokens\\eSpeak", 0, KEY_READ, &RegKey); + size = sizeof(buf); + var_type = REG_SZ; + RegQueryValueExA(RegKey, "path", 0, &var_type, buf, &size); + + sprintf(path_home,"%s\\espeak-data",buf); + +#else + char *env; + + if(path != NULL) + { + snprintf(path_home,sizeof(path_home),"%s/espeak-data",path); + return; + } + + // check for environment variable + if((env = getenv("ESPEAK_DATA_PATH")) != NULL) + { + snprintf(path_home,sizeof(path_home),"%s/espeak-data",env); + if(GetFileLength(path_home) == -2) + return; // an espeak-data directory exists + } + + snprintf(path_home,sizeof(path_home),"%s/espeak-data",getenv("HOME")); + if(access(path_home,R_OK) != 0) + { + strcpy(path_home,PATH_ESPEAK_DATA); + } +#endif +} + +static int initialise(void) +{//======================== + int param; + int result; + + LoadConfig(); + WavegenInit(22050,0); // 22050 + if((result = LoadPhData()) != 1) + { + if(result == -1) + fprintf(stderr,"Failed to load espeak-data\n"); + else + fprintf(stderr,"Wrong version of espeak-data 0x%x (expects 0x%x) at %s\n",result,version_phdata,path_home); + } + + memset(&voice_selected,0,sizeof(voice_selected)); + SetVoiceStack(NULL); + SynthesizeInit(); + InitNamedata(); + + for(param=0; param<N_SPEECH_PARAM; param++) + param_stack[0].parameter[param] = param_defaults[param]; + + return(0); +} + + +static espeak_ERROR Synthesize(unsigned int unique_identifier, const void *text, int flags) +{//======================================================================================== + // Fill the buffer with output sound + int length; + int finished = 0; + int count_buffers = 0; +#ifdef USE_ASYNC + uint32_t a_write_pos=0; +#endif + +#ifdef DEBUG_ENABLED + ENTER("Synthesize"); + if (text) + { + SHOW("Synthesize > uid=%d, flags=%d, >>>text=%s<<<\n", unique_identifier, flags, text); + } +#endif + + if((outbuf==NULL) || (event_list==NULL)) + return(EE_INTERNAL_ERROR); // espeak_Initialize() has not been called + + option_multibyte = flags & 7; + option_ssml = flags & espeakSSML; + option_phoneme_input = flags & espeakPHONEMES; + option_endpause = flags & espeakENDPAUSE; + + count_samples = 0; + +#ifdef USE_ASYNC + if(my_mode == AUDIO_OUTPUT_PLAYBACK) + { + a_write_pos = wave_get_write_position(my_audio); + } +#endif + + if(translator == NULL) + { + SetVoiceByName("default"); + } + + SpeakNextClause(NULL,text,0); + + if(my_mode == AUDIO_OUTPUT_SYNCH_PLAYBACK) + { + for(;;) + { +#ifdef PLATFORM_WINDOWS + Sleep(300); // 0.3s +#else +#ifdef USE_NANOSLEEP + struct timespec period; + struct timespec remaining; + period.tv_sec = 0; + period.tv_nsec = 300000000; // 0.3 sec + nanosleep(&period,&remaining); +#else + sleep(1); +#endif +#endif + if(SynthOnTimer() != 0) + break; + } + return(EE_OK); + } + + for(;;) + { +#ifdef DEBUG_ENABLED + SHOW("Synthesize > %s\n","for (next)"); +#endif + out_ptr = outbuf; + out_end = &outbuf[outbuf_size]; + event_list_ix = 0; + WavegenFill(0); + + length = (out_ptr - outbuf)/2; + count_samples += length; + event_list[event_list_ix].type = espeakEVENT_LIST_TERMINATED; // indicates end of event list + event_list[event_list_ix].unique_identifier = my_unique_identifier; + event_list[event_list_ix].user_data = my_user_data; + + count_buffers++; + if (my_mode==AUDIO_OUTPUT_PLAYBACK) + { +#ifdef USE_ASYNC + finished = create_events((short *)outbuf, length, event_list, a_write_pos); + length = 0; // the wave data are played once. +#endif + } + else + { + finished = synth_callback((short *)outbuf, length, event_list); + } + if(finished) + { + SpeakNextClause(NULL,0,2); // stop + break; + } + + if(Generate(phoneme_list,&n_phoneme_list,1)==0) + { + if(WcmdqUsed() == 0) + { + // don't process the next clause until the previous clause has finished generating speech. + // This ensures that <audio> tag (which causes end-of-clause) is at a sound buffer boundary + + event_list[0].type = espeakEVENT_LIST_TERMINATED; + event_list[0].unique_identifier = my_unique_identifier; + event_list[0].user_data = my_user_data; + + if(SpeakNextClause(NULL,NULL,1)==0) + { +#ifdef USE_ASYNC + if (my_mode==AUDIO_OUTPUT_PLAYBACK) + { + dispatch_audio(NULL, 0, NULL); // TBD: test case + } + else + { + synth_callback(NULL, 0, event_list); // NULL buffer ptr indicates end of data + } +#else + synth_callback(NULL, 0, event_list); // NULL buffer ptr indicates end of data +#endif + break; + } + } + } + } + + return(EE_OK); +} // end of Synthesize + +#ifdef DEBUG_ENABLED +static const char* label[] = { + "END_OF_EVENT_LIST", + "WORD", + "SENTENCE", + "MARK", + "PLAY", + "END"}; +#endif + + +void MarkerEvent(int type, unsigned int char_position, int value, unsigned char *out_ptr) +{//====================================================================================== + // type: 1=word, 2=sentence, 3=named mark, 4=play audio, 5=end + ENTER("MarkerEvent"); + espeak_EVENT *ep; + double time; + + if((event_list == NULL) || (event_list_ix >= (n_event_list-2))) + return; + + ep = &event_list[event_list_ix++]; + ep->type = (espeak_EVENT_TYPE)type; + ep->unique_identifier = my_unique_identifier; + ep->user_data = my_user_data; + ep->text_position = char_position & 0xffff; + ep->length = char_position >> 24; + + time = (double(count_samples + mbrola_delay + (out_ptr - out_start)/2)*1000.0)/samplerate; + ep->audio_position = int(time); + ep->sample = (count_samples + mbrola_delay + (out_ptr - out_start)/2); + +#ifdef DEBUG_ENABLED + SHOW("MarkerEvent > count_samples=%d, out_ptr=%x, out_start=0x%x\n",count_samples, out_ptr, out_start); + SHOW("*** MarkerEvent > type=%s, uid=%d, text_pos=%d, length=%d, audio_position=%d, sample=%d\n", + label[ep->type], ep->unique_identifier, ep->text_position, ep->length, + ep->audio_position, ep->sample); +#endif + + if((type == espeakEVENT_MARK) || (type == espeakEVENT_PLAY)) + ep->id.name = &namedata[value]; + else + ep->id.number = value; +} // end of MarkerEvent + + + + +espeak_ERROR sync_espeak_Synth(unsigned int unique_identifier, const void *text, size_t size, + unsigned int position, espeak_POSITION_TYPE position_type, + unsigned int end_position, unsigned int flags, void* user_data) +{//=========================================================================== + +#ifdef DEBUG_ENABLED + ENTER("sync_espeak_Synth"); + SHOW("sync_espeak_Synth > position=%d, position_type=%d, end_position=%d, flags=%d, user_data=0x%x, text=%s\n", position, position_type, end_position, flags, user_data, text); +#endif + + espeak_ERROR aStatus; + + InitText(flags); + my_unique_identifier = unique_identifier; + my_user_data = user_data; + + switch(position_type) + { + case POS_CHARACTER: + skip_characters = position; + break; + + case POS_WORD: + skip_words = position; + break; + + case POS_SENTENCE: + skip_sentences = position; + break; + + } + if(skip_characters || skip_words || skip_sentences) + skipping_text = 1; + + end_character_position = end_position; + + aStatus = Synthesize(unique_identifier, text, flags); + #ifdef USE_ASYNC + wave_flush(my_audio); + #endif + + SHOW_TIME("LEAVE sync_espeak_Synth"); + return aStatus; +} // end of sync_espeak_Synth + + + + +espeak_ERROR sync_espeak_Synth_Mark(unsigned int unique_identifier, const void *text, size_t size, + const char *index_mark, unsigned int end_position, + unsigned int flags, void* user_data) +{//========================================================================= + espeak_ERROR aStatus; + + InitText(flags); + + my_unique_identifier = unique_identifier; + my_user_data = user_data; + + if(index_mark != NULL) + { + strncpy0(skip_marker, index_mark, sizeof(skip_marker)); + skipping_text = 1; + } + + end_character_position = end_position; + + + aStatus = Synthesize(unique_identifier, text, flags | espeakSSML); + SHOW_TIME("LEAVE sync_espeak_Synth_Mark"); + + return (aStatus); +} // end of sync_espeak_Synth_Mark + + + +void sync_espeak_Key(const char *key) +{//================================== + // symbolic name, symbolicname_character - is there a system resource of symbolic names per language? + int letter; + int ix; + + ix = utf8_in(&letter,key); + if(key[ix] == 0) + { + // a single character + sync_espeak_Char(letter); + return; + } + + my_unique_identifier = 0; + my_user_data = NULL; + Synthesize(0, key,0); // speak key as a text string +} + + +void sync_espeak_Char(wchar_t character) +{//===================================== + // is there a system resource of character names per language? + char buf[80]; + my_unique_identifier = 0; + my_user_data = NULL; + + sprintf(buf,"<say-as interpret-as=\"tts:char\">&#%d;</say-as>",character); + Synthesize(0, buf,espeakSSML); +} + + + +void sync_espeak_SetPunctuationList(const wchar_t *punctlist) +{//========================================================== + // Set the list of punctuation which are spoken for "some". + my_unique_identifier = 0; + my_user_data = NULL; + + wcsncpy(option_punctlist, punctlist, N_PUNCTLIST); + option_punctlist[N_PUNCTLIST-1] = 0; +} // end of sync_espeak_SetPunctuationList + + + + +//#pragma GCC visibility push(default) + + +ESPEAK_API void espeak_SetSynthCallback(t_espeak_callback* SynthCallback) +{//====================================================================== + ENTER("espeak_SetSynthCallback"); + synth_callback = SynthCallback; +#ifdef USE_ASYNC + event_set_callback(synth_callback); +#endif +} + +ESPEAK_API void espeak_SetUriCallback(int (* UriCallback)(int, const char*, const char *)) +{//======================================================================================= + ENTER("espeak_SetUriCallback"); + uri_callback = UriCallback; +} + + +ESPEAK_API void espeak_SetPhonemeCallback(int (* PhonemeCallback)(const char*)) +{//=========================================================================== + phoneme_callback = PhonemeCallback; +} + +ESPEAK_API int espeak_Initialize(espeak_AUDIO_OUTPUT output_type, int buf_length, const char *path, int options) +{//============================================================================================================= +ENTER("espeak_Initialize"); + int param; + + // It seems that the wctype functions don't work until the locale has been set + // to something other than the default "C". Then, not only Latin1 but also the + // other characters give the correct results with iswalpha() etc. +#ifdef PLATFORM_RISCOS + setlocale(LC_CTYPE,"ISO8859-1"); +#else + if(setlocale(LC_CTYPE,"en_US.UTF-8") == NULL) + { + if(setlocale(LC_CTYPE,"UTF-8") == NULL) + setlocale(LC_CTYPE,""); + } +#endif + + init_path(path); + initialise(); + select_output(output_type); + + // buflength is in mS, allocate 2 bytes per sample + if(buf_length == 0) + buf_length = 200; + outbuf_size = (buf_length * samplerate)/500; + outbuf = (unsigned char*)realloc(outbuf,outbuf_size); + if((out_start = outbuf) == NULL) + return(EE_INTERNAL_ERROR); + + // allocate space for event list. Allow 200 events per second. + // Add a constant to allow for very small buf_length + n_event_list = (buf_length*200)/1000 + 20; + if((event_list = (espeak_EVENT *)realloc(event_list,sizeof(espeak_EVENT) * n_event_list)) == NULL) + return(EE_INTERNAL_ERROR); + + option_phonemes = 0; + option_phoneme_events = (options & 1); + + SetVoiceByName("default"); + + for(param=0; param<N_SPEECH_PARAM; param++) + param_stack[0].parameter[param] = param_defaults[param]; + + SetParameter(espeakRATE,170,0); + SetParameter(espeakVOLUME,100,0); + SetParameter(espeakCAPITALS,option_capitals,0); + SetParameter(espeakPUNCTUATION,option_punctuation,0); + SetParameter(espeakWORDGAP,0,0); + DoVoiceChange(voice); + +#ifdef USE_ASYNC + fifo_init(); +#endif + + return(samplerate); +} + + + +ESPEAK_API espeak_ERROR espeak_Synth(const void *text, size_t size, + unsigned int position, + espeak_POSITION_TYPE position_type, + unsigned int end_position, unsigned int flags, + unsigned int* unique_identifier, void* user_data) +{//===================================================================================== +#ifdef DEBUG_ENABLED + ENTER("espeak_Synth"); + SHOW("espeak_Synth > position=%d, position_type=%d, end_position=%d, flags=%d, user_data=0x%x, text=%s\n", position, position_type, end_position, flags, user_data, text); +#endif + + espeak_ERROR a_error=EE_INTERNAL_ERROR; + static unsigned int temp_identifier; + + if (unique_identifier == NULL) + { + unique_identifier = &temp_identifier; + } + *unique_identifier = 0; + + if(synchronous_mode) + { + return(sync_espeak_Synth(0,text,size,position,position_type,end_position,flags,user_data)); + } + +#ifdef USE_ASYNC + // Create the text command + t_espeak_command* c1 = create_espeak_text(text, size, position, position_type, end_position, flags, user_data); + + // Retrieve the unique identifier + *unique_identifier = c1->u.my_text.unique_identifier; + + // Create the "terminated msg" command (same uid) + t_espeak_command* c2 = create_espeak_terminated_msg(*unique_identifier, user_data); + + // Try to add these 2 commands (single transaction) + if (c1 && c2) + { + a_error = fifo_add_commands(c1, c2); + if (a_error != EE_OK) + { + delete_espeak_command(c1); + delete_espeak_command(c2); + c1=c2=NULL; + } + } + else + { + delete_espeak_command(c1); + delete_espeak_command(c2); + } + +#endif + return a_error; +} // end of espeak_Synth + + + +ESPEAK_API espeak_ERROR espeak_Synth_Mark(const void *text, size_t size, + const char *index_mark, + unsigned int end_position, + unsigned int flags, + unsigned int* unique_identifier, + void* user_data) +{//========================================================================= +#ifdef DEBUG_ENABLED + ENTER("espeak_Synth_Mark"); + SHOW("espeak_Synth_Mark > index_mark=%s, end_position=%d, flags=%d, text=%s\n", index_mark, end_position, flags, text); +#endif + + espeak_ERROR a_error=EE_OK; + static unsigned int temp_identifier; + + if (unique_identifier == NULL) + { + unique_identifier = &temp_identifier; + } + *unique_identifier = 0; + + if(synchronous_mode) + { + return(sync_espeak_Synth_Mark(0,text,size,index_mark,end_position,flags,user_data)); + } + +#ifdef USE_ASYNC + // Create the mark command + t_espeak_command* c1 = create_espeak_mark(text, size, index_mark, end_position, + flags, user_data); + + // Retrieve the unique identifier + *unique_identifier = c1->u.my_mark.unique_identifier; + + // Create the "terminated msg" command (same uid) + t_espeak_command* c2 = create_espeak_terminated_msg(*unique_identifier, user_data); + + // Try to add these 2 commands (single transaction) + if (c1 && c2) + { + a_error = fifo_add_commands(c1, c2); + if (a_error != EE_OK) + { + delete_espeak_command(c1); + delete_espeak_command(c2); + c1=c2=NULL; + } + } + else + { + delete_espeak_command(c1); + delete_espeak_command(c2); + } + +#endif + return a_error; +} // end of espeak_Synth_Mark + + + +ESPEAK_API espeak_ERROR espeak_Key(const char *key) +{//================================================ + ENTER("espeak_Key"); + // symbolic name, symbolicname_character - is there a system resource of symbolicnames per language + + espeak_ERROR a_error = EE_OK; + + if(synchronous_mode) + { + sync_espeak_Key(key); + return(EE_OK); + } + +#ifdef USE_ASYNC + t_espeak_command* c = create_espeak_key( key); + a_error = fifo_add_command(c); + if (a_error != EE_OK) + { + delete_espeak_command(c); + } + +#endif + return a_error; +} + + +ESPEAK_API espeak_ERROR espeak_Char(wchar_t character) +{//=========================================== + ENTER("espeak_Char"); + // is there a system resource of character names per language? + +#ifdef USE_ASYNC + espeak_ERROR a_error; + + if(synchronous_mode) + { + sync_espeak_Char(character); + return(EE_OK); + } + + t_espeak_command* c = create_espeak_char( character); + a_error = fifo_add_command(c); + if (a_error != EE_OK) + { + delete_espeak_command(c); + } + return a_error; +#else + sync_espeak_Char(character); + return(EE_OK); +#endif +} + + +ESPEAK_API espeak_ERROR espeak_SetVoiceByName(const char *name) +{//============================================================ + ENTER("espeak_SetVoiceByName"); + +//#ifdef USE_ASYNC +// I don't think there's a need to queue change voice requests +#ifdef deleted + espeak_ERROR a_error; + + if(synchronous_mode) + { + return(SetVoiceByName(name)); + } + + t_espeak_command* c = create_espeak_voice_name(name); + a_error = fifo_add_command(c); + if (a_error != EE_OK) + { + delete_espeak_command(c); + } + return a_error; +#else + return(SetVoiceByName(name)); +#endif +} // end of espeak_SetVoiceByName + + + +ESPEAK_API espeak_ERROR espeak_SetVoiceByProperties(espeak_VOICE *voice_selector) +{//============================================================================== + ENTER("espeak_SetVoiceByProperties"); + +//#ifdef USE_ASYNC +#ifdef deleted + espeak_ERROR a_error; + + if(synchronous_mode) + { + return(SetVoiceByProperties(voice_selector)); + } + + t_espeak_command* c = create_espeak_voice_spec( voice_selector); + a_error = fifo_add_command(c); + if (a_error != EE_OK) + { + delete_espeak_command(c); + } + return a_error; +#else + return(SetVoiceByProperties(voice_selector)); +#endif +} // end of espeak_SetVoiceByProperties + + +ESPEAK_API int espeak_GetParameter(espeak_PARAMETER parameter, int current) +{//======================================================================== + ENTER("espeak_GetParameter"); + // current: 0=default value, 1=current value + if(current) + { + return(param_stack[0].parameter[parameter]); + } + else + { + return(param_defaults[parameter]); + } +} // end of espeak_GetParameter + + +ESPEAK_API espeak_ERROR espeak_SetParameter(espeak_PARAMETER parameter, int value, int relative) +{//============================================================================================= + ENTER("espeak_SetParameter"); + +#ifdef USE_ASYNC + espeak_ERROR a_error; + + if(synchronous_mode) + { + SetParameter(parameter,value,relative); + return(EE_OK); + } + + t_espeak_command* c = create_espeak_parameter(parameter, value, relative); + + a_error = fifo_add_command(c); + if (a_error != EE_OK) + { + delete_espeak_command(c); + } + return a_error; +#else + SetParameter(parameter,value,relative); + return(EE_OK); +#endif +} + + +ESPEAK_API espeak_ERROR espeak_SetPunctuationList(const wchar_t *punctlist) +{//================================================================ + ENTER("espeak_SetPunctuationList"); + // Set the list of punctuation which are spoken for "some". + +#ifdef USE_ASYNC + espeak_ERROR a_error; + + if(synchronous_mode) + { + sync_espeak_SetPunctuationList(punctlist); + return(EE_OK); + } + + t_espeak_command* c = create_espeak_punctuation_list( punctlist); + a_error = fifo_add_command(c); + if (a_error != EE_OK) + { + delete_espeak_command(c); + } + return a_error; +#else + sync_espeak_SetPunctuationList(punctlist); + return(EE_OK); +#endif +} // end of espeak_SetPunctuationList + + +ESPEAK_API void espeak_SetPhonemeTrace(int value, FILE *stream) +{//============================================================ + ENTER("espeak_SetPhonemes"); + /* Controls the output of phoneme symbols for the text + value=0 No phoneme output (default) + value=1 Output the translated phoneme symbols for the text + value=2 as (1), but also output a trace of how the translation was done (matching rules and list entries) + */ + option_phonemes = value; + f_trans = stream; + if(stream == NULL) + f_trans = stderr; + +} // end of espeak_SetPhonemes + + +ESPEAK_API void espeak_CompileDictionary(const char *path, FILE *log, int flags) +{//============================================================================= + ENTER("espeak_CompileDictionary"); + CompileDictionary(path, dictionary_name, log, NULL, flags); +} // end of espeak_CompileDirectory + + +ESPEAK_API espeak_ERROR espeak_Cancel(void) +{//=============================== +#ifdef USE_ASYNC + ENTER("espeak_Cancel"); + fifo_stop(); + event_clear_all(); + + if(my_mode == AUDIO_OUTPUT_PLAYBACK) + { + wave_close(my_audio); + } + SHOW_TIME("espeak_Cancel > LEAVE"); +#endif + embedded_value[EMBED_T] = 0; // reset echo for pronunciation announcements + return EE_OK; +} // end of espeak_Cancel + + +ESPEAK_API int espeak_IsPlaying(void) +{//================================== +// ENTER("espeak_IsPlaying"); +#ifdef USE_ASYNC + if((my_mode == AUDIO_OUTPUT_PLAYBACK) && wave_is_busy(my_audio)) + return(1); + + return(fifo_is_busy()); +#else + return(0); +#endif +} // end of espeak_IsPlaying + + +ESPEAK_API espeak_ERROR espeak_Synchronize(void) +{//============================================= +#ifdef USE_ASYNC + SHOW_TIME("espeak_Synchronize > ENTER"); + while (espeak_IsPlaying()) + { + usleep(20000); + } +#endif + SHOW_TIME("espeak_Synchronize > LEAVE"); + return EE_OK; +} // end of espeak_Synchronize + + +extern void FreePhData(void); + +ESPEAK_API espeak_ERROR espeak_Terminate(void) +{//=========================================== + ENTER("espeak_Terminate"); +#ifdef USE_ASYNC + fifo_stop(); + fifo_terminate(); + event_terminate(); + + if(my_mode == AUDIO_OUTPUT_PLAYBACK) + { + wave_close(my_audio); + wave_terminate(); + } + +#endif + Free(event_list); + event_list = NULL; + Free(outbuf); + outbuf = NULL; + FreePhData(); + + return EE_OK; +} // end of espeak_Terminate + +ESPEAK_API const char *espeak_Info(void *) +{//======================================= + return(version_string); +} + +//#pragma GCC visibility pop + + diff --git a/Plugins/eSpeak/eSpeak/speak_lib.h b/Plugins/eSpeak/eSpeak/speak_lib.h new file mode 100644 index 0000000..7945fbc --- /dev/null +++ b/Plugins/eSpeak/eSpeak/speak_lib.h @@ -0,0 +1,604 @@ +#ifndef SPEAK_LIB_H +#define SPEAK_LIB_H +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + + +/*************************************************************/ +/* This is the header file for the library version of espeak */ +/* */ +/*************************************************************/ + +#include <stdio.h> + +#define ESPEAK_API_REVISION 3 +/* +Revision 2 + Added parameter "options" to eSpeakInitialize() + +Revision 3 + Added espeakWORDGAP to espeak_PARAMETER + +Revision 4 + Added flags parameter to espeak_CompileDictionary() + +*/ + /********************/ + /* Initialization */ + /********************/ + + +typedef enum { + espeakEVENT_LIST_TERMINATED = 0, // Retrieval mode: terminates the event list. + espeakEVENT_WORD = 1, // Start of word + espeakEVENT_SENTENCE, // Start of sentence + espeakEVENT_MARK, // Mark + espeakEVENT_PLAY, // Audio element + espeakEVENT_END, // End of sentence or clause + espeakEVENT_MSG_TERMINATED, // End of message + espeakEVENT_PHONEME // Phoneme, if enabled in espeak_Initialize() +} espeak_EVENT_TYPE; + + + +typedef struct { + espeak_EVENT_TYPE type; + unsigned int unique_identifier; // message identifier (or 0 for key or character) + int text_position; // the number of characters from the start of the text + int length; // word length, in characters (for espeakEVENT_WORD) + int audio_position; // the time in mS within the generated speech output data + int sample; // sample id (internal use) + void* user_data; // pointer supplied by the calling program + union { + int number; // used for WORD and SENTENCE events. For PHONEME events this is the phoneme mnemonic. + const char *name; // used for MARK and PLAY events. UTF8 string + } id; +} espeak_EVENT; +/* + When a message is supplied to espeak_synth, the request is buffered and espeak_synth returns. When the message is really processed, the callback function will be repetedly called. + + + In RETRIEVAL mode, the callback function supplies to the calling program the audio data and an event list terminated by 0 (LIST_TERMINATED). + + In PLAYBACK mode, the callback function is called as soon as an event happens. + + For example suppose that the following message is supplied to espeak_Synth: + "hello, hello." + + + * Once processed in RETRIEVAL mode, it could lead to 3 calls of the callback function : + + ** Block 1: + <audio data> + + List of events: SENTENCE + WORD + LIST_TERMINATED + + ** Block 2: + <audio data> + + List of events: WORD + END + LIST_TERMINATED + + ** Block 3: + no audio data + List of events: MSG_TERMINATED + LIST_TERMINATED + + + * Once processed in PLAYBACK mode, it could lead to 5 calls of the callback function: + + ** SENTENCE + ** WORD (call when the sounds are actually played) + ** WORD + ** END (call when the end of sentence is actually played.) + ** MSG_TERMINATED + + + The MSG_TERMINATED event is the last event. It can inform the calling program to clear the user data related to the message. + So if the synthesis must be stopped, the callback function is called for each pending message with the MSG_TERMINATED event. + + A MARK event indicates a <mark> element in the text. + A PLAY event indicates an <audio> element in the text, for which the calling program should play the named sound file. +*/ + + + +typedef enum { + POS_CHARACTER = 1, + POS_WORD, + POS_SENTENCE +} espeak_POSITION_TYPE; + + +typedef enum { + /* PLAYBACK mode: plays the audio data, supplies events to the calling program*/ + AUDIO_OUTPUT_PLAYBACK, + + /* RETRIEVAL mode: supplies audio data and events to the calling program */ + AUDIO_OUTPUT_RETRIEVAL, + + /* SYNCHRONOUS mode: as RETRIEVAL but doesn't return until synthesis is completed */ + AUDIO_OUTPUT_SYNCHRONOUS, + + /* Synchronous playback */ + AUDIO_OUTPUT_SYNCH_PLAYBACK + +} espeak_AUDIO_OUTPUT; + + +typedef enum { + EE_OK=0, + EE_INTERNAL_ERROR=-1, + EE_BUFFER_FULL=1, + EE_NOT_FOUND=2 +} espeak_ERROR; + + +#ifdef __cplusplus +extern "C" +#endif +int espeak_Initialize(espeak_AUDIO_OUTPUT output, int buflength, const char *path, int options); +/* Must be called before any synthesis functions are called. + output: the audio data can either be played by eSpeak or passed back by the SynthCallback function. + + buflength: The length in mS of sound buffers passed to the SynthCallback function. + + path: The directory which contains the espeak-data directory, or NULL for the default location. + + options: bit 0: 1=allow espeakEVENT_PHONEME events. + + + Returns: sample rate in Hz, or -1 (EE_INTERNAL_ERROR). +*/ + +typedef int (t_espeak_callback)(short*, int, espeak_EVENT*); + +#ifdef __cplusplus +extern "C" +#endif +void espeak_SetSynthCallback(t_espeak_callback* SynthCallback); +/* Must be called before any synthesis functions are called. + This specifies a function in the calling program which is called when a buffer of + speech sound data has been produced. + + + The callback function is of the form: + +int SynthCallback(short *wav, int numsamples, espeak_EVENT *events); + + wav: is the speech sound data which has been produced. + NULL indicates that the synthesis has been completed. + + numsamples: is the number of entries in wav. This number may vary, may be less than + the value implied by the buflength parameter given in espeak_Initialize, and may + sometimes be zero (which does NOT indicate end of synthesis). + + events: an array of espeak_EVENT items which indicate word and sentence events, and + also the occurance if <mark> and <audio> elements within the text. The list of + events is terminated by an event of type = 0. + + + Callback returns: 0=continue synthesis, 1=abort synthesis. +*/ + +#ifdef __cplusplus +extern "C" +#endif +void espeak_SetUriCallback(int (*UriCallback)(int, const char*, const char*)); +/* This function may be called before synthesis functions are used, in order to deal with + <audio> tags. It specifies a callback function which is called when an <audio> element is + encountered and allows the calling program to indicate whether the sound file which + is specified in the <audio> element is available and is to be played. + + The callback function is of the form: + +int UriCallback(int type, const char *uri, const char *base); + + type: type of callback event. Currently only 1= <audio> element + + uri: the "src" attribute from the <audio> element + + base: the "xml:base" attribute (if any) from the <speak> element + + Return: 1=don't play the sound, but speak the text alternative. + 0=place a PLAY event in the event list at the point where the <audio> element + occurs. The calling program can then play the sound at that point. +*/ + + + /********************/ + /* Synthesis */ + /********************/ + + +#define espeakCHARS_AUTO 0 +#define espeakCHARS_UTF8 1 +#define espeakCHARS_8BIT 2 +#define espeakCHARS_WCHAR 3 + +#ifdef UNICODE +#define espeakCHARS_TCHAR espeakCHARS_WCHAR +#else +#define espeakCHARS_TCHAR espeakCHARS_8BIT +#endif + +#define espeakSSML 0x10 +#define espeakPHONEMES 0x100 +#define espeakENDPAUSE 0x1000 +#define espeakKEEP_NAMEDATA 0x2000 + +#ifdef __cplusplus +extern "C" +#endif +espeak_ERROR espeak_Synth(const void *text, + size_t size, + unsigned int position, + espeak_POSITION_TYPE position_type, + unsigned int end_position, + unsigned int flags, + unsigned int* unique_identifier, + void* user_data); +/* Synthesize speech for the specified text. The speech sound data is passed to the calling + program in buffers by means of the callback function specified by espeak_SetSynthCallback(). The command is asynchronous: it is internally buffered and returns as soon as possible. If espeak_Initialize was previously called with AUDIO_OUTPUT_PLAYBACK as argument, the sound data are played by eSpeak. + + text: The text to be spoken, terminated by a zero character. It may be either 8-bit characters, + wide characters (wchar_t), or UTF8 encoding. Which of these is determined by the "flags" + parameter. + + size: Equal to (or greatrer than) the size of the text data, in bytes. This is used in order + to allocate internal storage space for the text. This value is not used for + AUDIO_OUTPUT_SYNCHRONOUS mode. + + position: The position in the text where speaking starts. Zero indicates speak from the + start of the text. + + position_type: Determines whether "position" is a number of characters, words, or sentences. + Values: + + end_position: If set, this gives a character position at which speaking will stop. A value + of zero indicates no end position. + + flags: These may be OR'd together: + Type of character codes, one of: + espeakCHARS_UTF8 UTF8 encoding + espeakCHARS_8BIT The 8 bit ISO-8859 character set for the particular language. + espeakCHARS_AUTO 8 bit or UTF8 (this is the default) + espeakCHARS_WCHAR Wide characters (wchar_t) + + espeakSSML Elements within < > are treated as SSML elements, or if not recognised are ignored. + + espeakPHONEMES Text within [[ ]] is treated as phonemes codes (in espeak's Hirshenbaum encoding). + + espeakENDPAUSE If set then a sentence pause is added at the end of the text. If not set then + this pause is suppressed. + + unique_identifier: message identifier; helpful for identifying later + data supplied to the callback. + + user_data: pointer which will be passed to the callback function. + + Return: EE_OK: operation achieved + EE_BUFFER_FULL: the command can not be buffered; + you may try after a while to call the function again. + EE_INTERNAL_ERROR. +*/ + +#ifdef __cplusplus +extern "C" +#endif +espeak_ERROR espeak_Synth_Mark(const void *text, + size_t size, + const char *index_mark, + unsigned int end_position, + unsigned int flags, + unsigned int* unique_identifier, + void* user_data); +/* Synthesize speech for the specified text. Similar to espeak_Synth() but the start position is + specified by the name of a <mark> element in the text. + + index_mark: The "name" attribute of a <mark> element within the text which specified the + point at which synthesis starts. UTF8 string. + + For the other parameters, see espeak_Synth() + + Return: EE_OK: operation achieved + EE_BUFFER_FULL: the command can not be buffered; + you may try after a while to call the function again. + EE_INTERNAL_ERROR. +*/ + +#ifdef __cplusplus +extern "C" +#endif +espeak_ERROR espeak_Key(const char *key_name); +/* Speak the name of a keyboard key. + If key_name is a single character, it speaks the name of the character. + Otherwise, it speaks key_name as a text string. + + Return: EE_OK: operation achieved + EE_BUFFER_FULL: the command can not be buffered; + you may try after a while to call the function again. + EE_INTERNAL_ERROR. +*/ + +#ifdef __cplusplus +extern "C" +#endif +espeak_ERROR espeak_Char(wchar_t character); +/* Speak the name of the given character + + Return: EE_OK: operation achieved + EE_BUFFER_FULL: the command can not be buffered; + you may try after a while to call the function again. + EE_INTERNAL_ERROR. +*/ + + + + + /***********************/ + /* Speech Parameters */ + /***********************/ + +typedef enum { + espeakSILENCE=0, /* internal use */ + espeakRATE=1, + espeakVOLUME=2, + espeakPITCH=3, + espeakRANGE=4, + espeakPUNCTUATION=5, + espeakCAPITALS=6, + espeakWORDGAP=7, + espeakOPTIONS=8, // reserved for misc. options. not yet used + espeakINTONATION=9, + + espeakRESERVED1=10, + espeakRESERVED2=11, + espeakEMPHASIS, /* internal use */ + espeakLINELENGTH, /* internal use */ + espeakVOICETYPE, // internal, 1=mbrola + N_SPEECH_PARAM /* last enum */ +} espeak_PARAMETER; + +typedef enum { + espeakPUNCT_NONE=0, + espeakPUNCT_ALL=1, + espeakPUNCT_SOME=2 +} espeak_PUNCT_TYPE; + +#ifdef __cplusplus +extern "C" +#endif +espeak_ERROR espeak_SetParameter(espeak_PARAMETER parameter, int value, int relative); +/* Sets the value of the specified parameter. + relative=0 Sets the absolute value of the parameter. + relative=1 Sets a relative value of the parameter. + + parameter: + espeakRATE: speaking speed in word per minute. + + espeakVOLUME: volume in range 0-100 0=silence + + espeakPITCH: base pitch, range 0-100. 50=normal + + espeakRANGE: pitch range, range 0-100. 0-monotone, 50=normal + + espeakPUNCTUATION: which punctuation characters to announce: + value in espeak_PUNCT_TYPE (none, all, some), + see espeak_GetParameter() to specify which characters are announced. + + espeakCAPITALS: announce capital letters by: + 0=none, + 1=sound icon, + 2=spelling, + 3 or higher, by raising pitch. This values gives the amount in Hz by which the pitch + of a word raised to indicate it has a capital letter. + + espeakWORDGAP: pause between words, units of 10mS (at the default speed) + + Return: EE_OK: operation achieved + EE_BUFFER_FULL: the command can not be buffered; + you may try after a while to call the function again. + EE_INTERNAL_ERROR. +*/ + +#ifdef __cplusplus +extern "C" +#endif +int espeak_GetParameter(espeak_PARAMETER parameter, int current); +/* current=0 Returns the default value of the specified parameter. + current=1 Returns the current value of the specified parameter, as set by SetParameter() +*/ + +#ifdef __cplusplus +extern "C" +#endif +espeak_ERROR espeak_SetPunctuationList(const wchar_t *punctlist); +/* Specified a list of punctuation characters whose names are to be spoken when the + value of the Punctuation parameter is set to "some". + + punctlist: A list of character codes, terminated by a zero character. + + Return: EE_OK: operation achieved + EE_BUFFER_FULL: the command can not be buffered; + you may try after a while to call the function again. + EE_INTERNAL_ERROR. +*/ + +#ifdef __cplusplus +extern "C" +#endif +void espeak_SetPhonemeTrace(int value, FILE *stream); +/* Controls the output of phoneme symbols for the text + value=0 No phoneme output (default) + value=1 Output the translated phoneme symbols for the text + value=2 as (1), but also output a trace of how the translation was done (matching rules and list entries) + + stream output stream for the phoneme symbols (and trace). If stream=NULL then it uses stdout. +*/ + +#ifdef __cplusplus +extern "C" +#endif +void espeak_CompileDictionary(const char *path, FILE *log, int flags); +/* Compile pronunciation dictionary for a language which corresponds to the currently + selected voice. The required voice should be selected before calling this function. + + path: The directory which contains the language's '_rules' and '_list' files. + 'path' should end with a path separator character ('/'). + log: Stream for error reports and statistics information. If log=NULL then stderr will be used. + + flags: Bit 0: include source line information for debug purposes (This is displayed with the + -X command line option). +*/ + /***********************/ + /* Voice Selection */ + /***********************/ + + +// voice table +typedef struct { + const char *name; // a given name for this voice. UTF8 string. + const char *languages; // list of pairs of (byte) priority + (string) language (and dialect qualifier) + const char *identifier; // the filename for this voice within espeak-data/voices + unsigned char gender; // 0=none 1=male, 2=female, + unsigned char age; // 0=not specified, or age in years + unsigned char variant; // only used when passed as a parameter to espeak_SetVoiceByProperties + unsigned char xx1; // for internal use + int score; // for internal use + void *spare; // for internal use +} espeak_VOICE; + +/* Note: The espeak_VOICE structure is used for two purposes: + 1. To return the details of the available voices. + 2. As a parameter to espeak_SetVoiceByProperties() in order to specify selection criteria. + + In (1), the "languages" field consists of a list of (UTF8) language names for which this voice + may be used, each language name in the list is terminated by a zero byte and is also preceded by + a single byte which gives a "priority" number. The list of languages is terminated by an + additional zero byte. + + A language name consists of a language code, optionally followed by one or more qualifier (dialect) + names separated by hyphens (eg. "en-uk"). A voice might, for example, have languages "en-uk" and + "en". Even without "en" listed, voice would still be selected for the "en" language (because + "en-uk" is related) but at a lower priority. + + The priority byte indicates how the voice is preferred for the language. A low number indicates a + more preferred voice, a higher number indicates a less preferred voice. + + In (2), the "languages" field consists simply of a single (UTF8) language name, with no preceding + priority byte. +*/ + +#ifdef __cplusplus +extern "C" +#endif +const espeak_VOICE **espeak_ListVoices(espeak_VOICE *voice_spec); +/* Reads the voice files from espeak-data/voices and creates an array of espeak_VOICE pointers. + The list is terminated by a NULL pointer + + If voice_spec is NULL then all voices are listed. + If voice spec is give, then only the voices which are compatible with the voice_spec + are listed, and they are listed in preference order. +*/ + +#ifdef __cplusplus +extern "C" +#endif +espeak_ERROR espeak_SetVoiceByName(const char *name); +/* Searches for a voice with a matching "name" field. Language is not considered. + "name" is a UTF8 string. + + Return: EE_OK: operation achieved + EE_BUFFER_FULL: the command can not be buffered; + you may try after a while to call the function again. + EE_INTERNAL_ERROR. +*/ + +#ifdef __cplusplus +extern "C" +#endif +espeak_ERROR espeak_SetVoiceByProperties(espeak_VOICE *voice_spec); +/* An espeak_VOICE structure is used to pass criteria to select a voice. Any of the following + fields may be set: + + name NULL, or a voice name + + languages NULL, or a single language string (with optional dialect), eg. "en-uk", or "en" + + gender 0=not specified, 1=male, 2=female + + age 0=not specified, or an age in years + + variant After a list of candidates is produced, scored and sorted, "variant" is used to index + that list and choose a voice. + variant=0 takes the top voice (i.e. best match). variant=1 takes the next voice, etc +*/ + +#ifdef __cplusplus +extern "C" +#endif +espeak_VOICE *espeak_GetCurrentVoice(void); +/* Returns the espeak_VOICE data for the currently selected voice. + This is not affected by temporary voice changes caused by SSML elements such as <voice> and <s> +*/ + +#ifdef __cplusplus +extern "C" +#endif +espeak_ERROR espeak_Cancel(void); +/* Stop immediately synthesis and audio output of the current text. When this + function returns, the audio output is fully stopped and the synthesizer is ready to + synthesize a new message. + + Return: EE_OK: operation achieved + EE_INTERNAL_ERROR. +*/ + + +#ifdef __cplusplus +extern "C" +#endif +int espeak_IsPlaying(void); +/* Returns 1 if audio is played, 0 otherwise. +*/ + +#ifdef __cplusplus +extern "C" +#endif +espeak_ERROR espeak_Synchronize(void); +/* This function returns when all data have been spoken. + Return: EE_OK: operation achieved + EE_INTERNAL_ERROR. +*/ + +#ifdef __cplusplus +extern "C" +#endif +espeak_ERROR espeak_Terminate(void); +/* last function to be called. + Return: EE_OK: operation achieved + EE_INTERNAL_ERROR. +*/ + + +#ifdef __cplusplus +extern "C" +#endif +const char *espeak_Info(void* ptr); +/* Returns the version number string. + The parameter is for future use, and should be set to NULL +*/ +#endif diff --git a/Plugins/eSpeak/eSpeak/speech.h b/Plugins/eSpeak/eSpeak/speech.h new file mode 100644 index 0000000..bf5fbe8 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/speech.h @@ -0,0 +1,84 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + + +#include <sys/types.h> + +// conditional compilation options +//#define INCLUDE_KLATT + +#if defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN +#define ARCH_BIG +#endif + +#define PLATFORM_WINDOWS +#define __WIN32__ +#define NEED_WCSTOF +#define NEED_GETOPT +#define PATHSEP '\\' +// USE_PORTAUDIO or USE_PULSEAUDIO are now defined in the makefile +#define USE_PORTAUDIO +//#define USE_PULSEAUDIO +//#define USE_NANOSLEEP +//#define __cdecl +#define ESPEAK_API extern "C" + +#define USE_MBROLA_LIB +#ifdef LIBRARY +#define USE_ASYNC +//#define USE_MBROLA_LIB +#endif + +#ifdef _ESPEAKEDIT +#define USE_PORTAUDIO +#define USE_ASYNC +#define LOG_FRAMES // write keyframe info to log-espeakedit +#endif + +// will look for espeak_data directory here, and also in user's home directory +#ifndef PATH_ESPEAK_DATA + #define PATH_ESPEAK_DATA "/usr/share/espeak-data" +#endif + +typedef unsigned short USHORT; +typedef unsigned char UCHAR; +typedef double DOUBLEX; + + +typedef struct { + const char *mnem; + int value; +} MNEM_TAB; +int LookupMnem(MNEM_TAB *table, char *string); + + +#ifdef PLATFORM_WINDOWS +#define N_PATH_HOME 220 +#else +#define N_PATH_HOME 150 +#endif + +extern char path_home[N_PATH_HOME]; // this is the espeak-data directory + +extern void strncpy0(char *to,const char *from, int size); +int GetFileLength(const char *filename); +char *Alloc(int size); +void Free(void *ptr); + +#include <windows.h> diff --git a/Plugins/eSpeak/eSpeak/stdint.h b/Plugins/eSpeak/eSpeak/stdint.h new file mode 100644 index 0000000..6b16207 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/stdint.h @@ -0,0 +1,4 @@ +
+// dummy stdint.h file for Windows
+
+typedef unsigned int uint32_t;
diff --git a/Plugins/eSpeak/eSpeak/synth_mbrola.cpp b/Plugins/eSpeak/eSpeak/synth_mbrola.cpp new file mode 100644 index 0000000..1055f54 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/synth_mbrola.cpp @@ -0,0 +1,760 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "StdAfx.h" + +#include <stdio.h> +#include <ctype.h> +#include <wctype.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "translate.h" +#include "voice.h" + +extern int Read4Bytes(FILE *f); +extern void SetPitch2(voice_t *voice, int pitch1, int pitch2, int *pitch_base, int *pitch_range); + +#ifdef USE_MBROLA_LIB + +extern unsigned char *outbuf; + +#ifndef PLATFORM_WINDOWS + +#include "mbrolib.h" +void * mb_handle; + +#else +#include <windows.h> +typedef void (WINAPI *PROCVV)(void); +typedef void (WINAPI *PROCVI)(int); +typedef void (WINAPI *PROCVF)(float); +typedef int (WINAPI *PROCIV)(); +typedef int (WINAPI *PROCIC) (char *); +typedef int (WINAPI *PROCISI)(short *,int); +typedef char* (WINAPI *PROCVCI)(char *,int); + +PROCIC init_MBR; +PROCIC write_MBR; +PROCIV flush_MBR; +PROCISI read_MBR; +PROCVV close_MBR; +PROCVV reset_MBR; +PROCIV lastError_MBR; +PROCVCI lastErrorStr_MBR; +PROCVI setNoError_MBR; +PROCVI setFreq_MBR; +PROCVF setVolumeRatio_MBR; + + + +HINSTANCE hinstDllMBR = NULL; + + +BOOL load_MBR() +{ + if(hinstDllMBR != NULL) + return TRUE; // already loaded + + if (!(hinstDllMBR=LoadLibraryA("mbrola.dll"))) + return FALSE; + init_MBR =(PROCIC) GetProcAddress(hinstDllMBR,"init_MBR"); + write_MBR =(PROCIC) GetProcAddress(hinstDllMBR,"write_MBR"); + flush_MBR =(PROCIV) GetProcAddress(hinstDllMBR,"flush_MBR"); + read_MBR =(PROCISI) GetProcAddress(hinstDllMBR,"read_MBR"); + close_MBR =(PROCVV) GetProcAddress(hinstDllMBR,"close_MBR"); + reset_MBR =(PROCVV) GetProcAddress(hinstDllMBR,"reset_MBR"); + lastError_MBR =(PROCIV) GetProcAddress(hinstDllMBR,"lastError_MBR"); + lastErrorStr_MBR =(PROCVCI) GetProcAddress(hinstDllMBR,"lastErrorStr_MBR"); + setNoError_MBR =(PROCVI) GetProcAddress(hinstDllMBR,"setNoError_MBR"); + setVolumeRatio_MBR =(PROCVF) GetProcAddress(hinstDllMBR,"setVolumeRatio_MBR"); + return TRUE; +} + + +void unload_MBR() +{ + if (hinstDllMBR) + { + FreeLibrary (hinstDllMBR); + hinstDllMBR=NULL; + } +} + +#endif // windows +#endif // USE_MBROLA_LIB + + +static MBROLA_TAB *mbrola_tab = NULL; +static int mbrola_control = 0; + + + + +espeak_ERROR LoadMbrolaTable(const char *mbrola_voice, const char *phtrans, int srate) +{//=================================================================================== +// Load a phoneme name translation table from espeak-data/mbrola + + int size; + int ix; + int *pw; + FILE *f_in; + char path[sizeof(path_home)+15]; + + mbrola_name[0] = 0; + mbrola_delay = 0; + + if(mbrola_voice == NULL) + { + samplerate = samplerate_native; + SetParameter(espeakVOICETYPE,0,0); + return(EE_OK); + } + + sprintf(path,"%s/mbrola/%s",path_home,mbrola_voice); +#ifdef USE_MBROLA_LIB +#ifdef PLATFORM_WINDOWS + if(load_MBR() == FALSE) // load mbrola.dll + return(EE_INTERNAL_ERROR); + + if(init_MBR(path) != 0) // initialise the required mbrola voice + return(EE_NOT_FOUND); + + setNoError_MBR(1); // don't stop on phoneme errors +#else + mb_handle = mbrolib_init(srate); + mbrolib_parameter m_parameters; + + if(mb_handle == NULL) + return(EE_INTERNAL_ERROR); + + MBROLIB_ERROR a_status = mbrolib_set_voice(mb_handle, mbrola_voice); + if(a_status != MBROLIB_OK) + return(EE_NOT_FOUND); +#endif // not windows +#endif // USE_MBROLA_LIB + + // read eSpeak's mbrola phoneme translation data, eg. en1_phtrans + sprintf(path,"%s/mbrola_ph/%s",path_home,phtrans); + size = GetFileLength(path); + if((f_in = fopen(path,"r")) == NULL) + return(EE_NOT_FOUND); + + if((mbrola_tab = (MBROLA_TAB *)realloc(mbrola_tab,size)) == NULL) + { + fclose(f_in); + return(EE_INTERNAL_ERROR); + } + + mbrola_control = Read4Bytes(f_in); + pw = (int *)mbrola_tab; + for(ix=4; ix<size; ix+=4) + { + *pw++ = Read4Bytes(f_in); + } + fread(mbrola_tab,size,1,f_in); + fclose(f_in); + + +#ifdef USE_MBROLA_LIB +#ifdef PLATFORM_WINDOWS + setVolumeRatio_MBR((float)(mbrola_control & 0xff) /16.0f); +#else + mbrolib_get_parameter(mb_handle,&m_parameters); + m_parameters.ignore_error = 1; + m_parameters.volume_ratio = (float)(mbrola_control & 0xff) /16.0; + mbrolib_set_parameter(mb_handle,&m_parameters); +#endif // not windows +#endif // USE_MBROLA_LIB + + option_quiet = 1; + samplerate = srate; + if(srate == 22050) + SetParameter(espeakVOICETYPE,0,0); + else + SetParameter(espeakVOICETYPE,1,0); + strcpy(mbrola_name,mbrola_voice); + mbrola_delay = 3800; // improve synchronization of events + return(EE_OK); +} // end of LoadMbrolaTable + + +static int GetMbrName(PHONEME_LIST *plist, PHONEME_TAB *ph, PHONEME_TAB *ph_prev, PHONEME_TAB *ph_next, int *name2, int *split, int *control) +{//========================================================================================================================================== +// Look up a phoneme in the mbrola phoneme name translation table +// It may give none, 1, or 2 mbrola phonemes + int mnem = ph->mnemonic; + MBROLA_TAB *pr; + PHONEME_TAB *other_ph; + int found = 0; + + // control + // bit 0 skip the next phoneme + // bit 1 match this and Previous phoneme + // bit 2 only at the start of a word + // bit 3 don't match two phonemes across a word boundary + + pr = mbrola_tab; + while(pr->name != 0) + { + if(mnem == pr->name) + { + if(pr->next_phoneme == 0) + found = 1; + else + if((pr->next_phoneme == ':') && (plist->synthflags & SFLAG_LENGTHEN)) + { + found = 1; + } + else + { + if(pr->control & 2) + other_ph = ph_prev; + else + if((pr->control & 8) && ((plist+1)->newword)) + other_ph = phoneme_tab[phPAUSE]; // don't match the next phoneme over a word boundary + else + other_ph = ph_next; + + if((pr->next_phoneme == other_ph->mnemonic) || + ((pr->next_phoneme == 2) && (other_ph->type == phVOWEL)) || + ((pr->next_phoneme == '_') && (other_ph->type == phPAUSE))) + { + found = 1; + } + } + + if((pr->control & 4) && (plist->newword == 0)) // only at start of word + found = 0; + + if(found) + { + *name2 = pr->mbr_name2; + *split = pr->percent; + *control = pr->control; + return(pr->mbr_name); + } + } + + pr++; + } + *name2=0; + *split=0; + *control=0; + return(mnem); +} + + +static char *WritePitch(int env, int pitch1, int pitch2, int split, int final) +{//=========================================================================== +// final=1: only give the final pitch value. + int x; + int ix; + int pitch_base; + int pitch_range; + int p1,p2,p_end; + unsigned char *pitch_env; + int max = -1; + int min = 999; + int y_max=0; + int y_min=0; + int env100 = 80; // apply the pitch change only over this proportion of the mbrola phoneme(s) + int y2; + int y[4]; + int env_split; + char buf[50]; + static char output[50]; + + output[0] = 0; + pitch_env = envelope_data[env]; + + + SetPitch2(voice, pitch1, pitch2, &pitch_base, &pitch_range); + + + env_split = (split * 128)/100; + if(env_split < 0) + env_split = 0-env_split; + + // find max and min in the pitch envelope + for(x=0; x<128; x++) + { + if(pitch_env[x] > max) + { + max = pitch_env[x]; + y_max = x; + } + if(pitch_env[x] < min) + { + min = pitch_env[x]; + y_min = x; + } + } + // set an additional pitch point half way through the phoneme. + // but look for a maximum or a minimum and use that instead + y[2] = 64; + if((y_max > 0) && (y_max < 127)) + { + y[2] = y_max; + } + if((y_min > 0) && (y_min < 127)) + { + y[2] = y_min; + } + y[1] = y[2] / 2; + y[3] = y[2] + (127 - y[2])/2; + + // set initial pitch + p1 = ((pitch_env[0]*pitch_range)>>8) + pitch_base; // Hz << 12 + p_end = ((pitch_env[127]*pitch_range)>>8) + pitch_base; + + + if(split >= 0) + { + sprintf(buf," 0 %d",p1/4096); + strcat(output,buf); + } + + // don't use intermediate pitch points for linear rise and fall + if(env > 1) + { + for(ix=1; ix<4; ix++) + { + p2 = ((pitch_env[y[ix]]*pitch_range)>>8) + pitch_base; + + if(split > 0) + { + y2 = (y[ix] * env100)/env_split; + } + else + if(split < 0) + { + y2 = ((y[ix]-env_split) * env100)/env_split; + } + else + { + y2 = (y[ix] * env100)/128; + } + if((y2 > 0) && (y2 <= env100)) + { + sprintf(buf," %d %d",y2,p2/4096); + strcat(output,buf); + } + } + } + + p_end = p_end/4096; + if(split <= 0) + { + sprintf(buf," %d %d",env100,p_end); + strcat(output,buf); + } + if(env100 < 100) + { + sprintf(buf," %d %d",100,p_end); + strcat(output,buf); + } + strcat(output,"\n"); + + if(final) + sprintf(output,"\t100 %d\n",p_end); + return(output); +} // end of WritePitch + + +#ifdef USE_MBROLA_LIB + +static void MbrolaMarker(int type, int char_posn, int length, int value) +{//===================================================================== + + MarkerEvent(type,(char_posn & 0xffffff) | (length << 24),value,outbuf); + +} + + +static void MbrolaEmbedded(int &embix, int sourceix) +{//================================================= + // There were embedded commands in the text at this point + unsigned int word; // bit 7=last command for this word, bits 5,6 sign, bits 0-4 command + unsigned int value; + int command; + int sign=0; + + do { + word = embedded_list[embix++]; + value = word >> 8; + command = word & 0x1f; + + if((word & 0x60) == 0x60) + sign = -1; + else + if((word & 0x60) == 0x40) + sign = 1; + + if(command < N_EMBEDDED_VALUES) + { + if(sign == 0) + embedded_value[command] = value; + else + embedded_value[command] += (value * sign); + } + + switch(command & 0x1f) + { + case EMBED_M: // named marker + MbrolaMarker(espeakEVENT_MARK, (sourceix & 0x7ff) + clause_start_char, 0, value); + break; + } + } while ((word & 0x80) == 0); +} + + +#ifdef PLATFORM_WINDOWS +static int MbrolaSynth(char *p_mbrola) +{//=================================== +// p_mbrola is a string of mbrola pho lines - Windows + int len; + int finished; + int result=0; + + if(synth_callback == NULL) + return(1); + + if(p_mbrola == NULL) + flush_MBR(); + else + result = write_MBR(p_mbrola); + + + finished = 0; + while(!finished && ((len = read_MBR((short *)outbuf, outbuf_size/2)) > 0)) + { + out_ptr = outbuf + len*2; + + if(event_list) + { + event_list[event_list_ix].type = espeakEVENT_LIST_TERMINATED; // indicates end of event list + event_list[event_list_ix].user_data = 0; + } + count_samples += len; + finished = synth_callback((short *)outbuf, len, event_list); + event_list_ix=0; + } + + if(finished) + { + // cancelled by user, discard any unused mbrola speech + flush_MBR(); + while((len = read_MBR((short *)outbuf, outbuf_size/2)) > 0); + } + return(finished); +} // end of SynthMbrola +#else + +static int MbrolaSynth(char *p_mbrola) +{//=================================== +// p_mbrola is a string of mbrola pho lines - Linux + +// This is wrong +// It must be called from WavegenFill() + + int len; + int finished; + int result=0; + + if(synth_callback == NULL) + return(1); + + if(p_mbrola == NULL) + mbrolib_flush(mb_handle); + else + result = mbrolib_write(mb_handle,p_mbrola,strlen(p_mbrola)); + + + finished = 0; + while(!finished && (mbrolib_read(mb_handle, (short *)out_ptr, (out_end - out_ptr)/2, &len) == MBROLIB_OK)) + { + if(len == 0) + break; + + out_ptr += (len*2); + + if(event_list) + { + event_list[event_list_ix].type = espeakEVENT_LIST_TERMINATED; // indicates end of event list + event_list[event_list_ix].user_data = 0; + } + count_samples += len; + finished = synth_callback((short *)outbuf, len, event_list); + event_list_ix=0; + } + + if(finished) + { + // cancelled by user, discard any unused mbrola speech + mbrolib_flush(mb_handle); + while(mbrolib_read(mb_handle, (short *)outbuf, outbuf_size/2, &len) == MBROLIB_OK) + { + if(len == 0) + break; + } + } + return(finished); +} // end of SynthMbrola +#endif // not windows +#endif // USE_MBROLA_LIB + + + +void MbrolaTranslate(PHONEME_LIST *plist, int n_phonemes, FILE *f_mbrola) +{//====================================================================== +// Generate a mbrola pho file + unsigned int name; + int phix; + int len; + int len1; + PHONEME_TAB *ph; + PHONEME_TAB *ph_next; + PHONEME_TAB *ph_prev; + PHONEME_LIST *p; + PHONEME_LIST *next; + PHONEME_LIST *prev; + int pause = 0; + int released; + int name2; + int control; + int done; + int len_percent; + const char *final_pitch; + char buf[80]; + char mbr_buf[120]; + +#ifdef USE_MBROLA_LIB + int embedded_ix=0; + int word_count=0; + + event_list_ix = 0; + out_ptr = outbuf; +#ifdef PLATFORM_WINDOWS + setNoError_MBR(1); // don't stop on phoneme errors +#endif +#else +// fprintf(f_mbrola,";; v=%.2f\n",(float)(mbrola_control & 0xff)/16.0); // ;; v= has no effect on mbrola +#endif + + for(phix=1; phix < n_phonemes; phix++) + { + mbr_buf[0] = 0; + + p = &plist[phix]; + next = &plist[phix+1]; + prev = &plist[phix-1]; + ph = p->ph; + ph_prev = plist[phix-1].ph; + ph_next = plist[phix+1].ph; + +#ifdef USE_MBROLA_LIB + if(p->synthflags & SFLAG_EMBEDDED) + { + MbrolaEmbedded(embedded_ix, p->sourceix); + } + if(p->newword & 4) + MbrolaMarker(espeakEVENT_SENTENCE, (p->sourceix & 0x7ff) + clause_start_char, 0, count_sentences); + + if(p->newword & 1) + MbrolaMarker(espeakEVENT_WORD, (p->sourceix & 0x7ff) + clause_start_char, p->sourceix >> 11, clause_start_word + word_count++); +#endif + + name = GetMbrName(p,ph,ph_prev,ph_next,&name2,&len_percent,&control); + if(control & 1) + phix++; + + if(name == 0) + continue; // ignore this phoneme + + if((ph->type == phPAUSE) && (name == ph->mnemonic)) + { + // a pause phoneme, which has not been changed by the translation + name = '_'; + len = (p->length * speed.speed_factor1)/256; +// if(len == 0) continue; + if(len == 0) + len = 1; + } + else + len = (80 * speed.speed_factor2)/256; + +#ifdef USE_MBROLA_LIB + MbrolaMarker(espeakEVENT_PHONEME, (p->sourceix & 0x7ff) + clause_start_char, 0, ph->mnemonic); +#endif + + sprintf(buf,"%s\t",WordToString(name)); + strcat(mbr_buf,buf); + + if(name2 == '_') + { + // add a pause after this phoneme + pause = PauseLength(len_percent,0); + name2 = 0; + } + + done = 0; + final_pitch = ""; + + switch(ph->type) + { + case phVOWEL: + len = ph->std_length; + if(p->synthflags & SFLAG_LENGTHEN) + len += phoneme_tab[phonLENGTHEN]->std_length; // phoneme was followed by an extra : symbol + + if(ph_next->type == phPAUSE) + len += 50; // lengthen vowels before a pause + len = (len * p->length)/256; + + if(name2 == 0) + { + sprintf(buf,"%d\t%s", len, WritePitch(p->env,p->pitch1,p->pitch2,0,0)); + strcat(mbr_buf,buf); + } + else + { + len1 = (len * len_percent)/100; + sprintf(buf,"%d\t%s", len1, WritePitch(p->env,p->pitch1,p->pitch2,len_percent,0)); + strcat(mbr_buf,buf); + + sprintf(buf,"%s\t%d\t%s", WordToString(name2), len-len1, WritePitch(p->env,p->pitch1,p->pitch2,-len_percent,0)); + strcat(mbr_buf,buf); + } + done = 1; + break; + + case phSTOP: + released = 0; + if(next->type==phVOWEL) released = 1; + if(next->type==phLIQUID && !next->newword) released = 1; + + if(released) + len = DoSample(p->ph,next->ph,2,0,-1); + else + len = DoSample(p->ph,phoneme_tab[phonPAUSE],2,0,-1); + len = (len * 1000)/samplerate; // convert to mS + len += PauseLength(p->prepause,1); + break; + + case phVSTOP: + len = (80 * speed.speed_factor2)/256; + break; + + case phFRICATIVE: + len = 0; + if(p->synthflags & SFLAG_LENGTHEN) + len = DoSample(ph,ph_next,2,p->length,-1); // play it twice for [s:] etc. + len += DoSample(ph,ph_next,2,p->length,-1); + + len = (len * 1000)/samplerate; // convert to mS + break; + + case phNASAL: + if(next->type != phVOWEL) + { + len = DoSpect(p->ph,prev->ph,phoneme_tab[phonPAUSE],2,p,-1); + len = (len * 1000)/samplerate; + if(next->type == phPAUSE) + len += 50; + final_pitch = WritePitch(p->env,p->pitch1,p->pitch2,0,1); + } + break; + + case phLIQUID: + if(next->type == phPAUSE) + { + len += 50; + final_pitch = WritePitch(p->env,p->pitch1,p->pitch2,0,1); + } + break; + } + + if(!done) + { + if(name2 != 0) + { + len1 = (len * len_percent)/100; + sprintf(buf,"%d\n%s\t",len1,WordToString(name2)); + strcat(mbr_buf,buf); + len -= len1; + } + sprintf(buf,"%d%s\n",len,final_pitch); + strcat(mbr_buf,buf); + } + + if(pause) + { + sprintf(buf,"_ \t%d\n",PauseLength(pause,0)); + strcat(mbr_buf,buf); + pause = 0; + } + + if(f_mbrola) + { + fwrite(mbr_buf,1,strlen(mbr_buf),f_mbrola); // write .pho to a file + } + else + { +#ifdef USE_MBROLA_LIB + if(MbrolaSynth(mbr_buf) != 0) + return; +#endif + } + } + +#ifdef USE_MBROLA_LIB + MbrolaSynth(NULL); +#endif +} // end of MbrolaTranslate + + +#ifdef TEST_MBROLA + +static PHONEME_LIST mbrola_phlist; +static int mbrola_n_ph; +static int mbrola_phix; + + +int MbrolaFill(int fill_zeros) +{//=========================== +} + +int MbrolaGenerate(PHONEME_LIST *phoneme_list, int *n_ph, int resume) +{//================================================================== + if(resume == 0) + { + mbrola_phlist = phoneme_list; + mbrola_n_ph = n_ph; + mbrola_phix = 0; + } + + resume(0); // finished phoneme list +} +#endif diff --git a/Plugins/eSpeak/eSpeak/synthdata.cpp b/Plugins/eSpeak/eSpeak/synthdata.cpp new file mode 100644 index 0000000..9f4013f --- /dev/null +++ b/Plugins/eSpeak/eSpeak/synthdata.cpp @@ -0,0 +1,681 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + + +#include "StdAfx.h" + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <wctype.h> +#include <string.h> + + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "voice.h" +#include "translate.h" +#include "wave.h" + +const char *version_string = "1.40 22.Dec.08"; +const int version_phdata = 0x014000; + +int option_device_number = -1; + +// copy the current phoneme table into here +int n_phoneme_tab; +int current_phoneme_table; +PHONEME_TAB *phoneme_tab[N_PHONEME_TAB]; +unsigned char phoneme_tab_flags[N_PHONEME_TAB]; // bit 0: not inherited + +unsigned int *phoneme_index=NULL; +char *spects_data=NULL; +unsigned char *wavefile_data=NULL; +static unsigned char *phoneme_tab_data = NULL; + +int n_phoneme_tables; +PHONEME_TAB_LIST phoneme_tab_list[N_PHONEME_TABS]; +int phoneme_tab_number = 0; + +int wavefile_ix; // a wavefile to play along with the synthesis +int wavefile_amp; +int wavefile_ix2; +int wavefile_amp2; + +int seq_len_adjust; +int vowel_transition[4]; +int vowel_transition0; +int vowel_transition1; + +int FormantTransition2(frameref_t *seq, int &n_frames, unsigned int data1, unsigned int data2, PHONEME_TAB *other_ph, int which); + + + +static char *ReadPhFile(void *ptr, const char *fname) +{//================================================== + FILE *f_in; + char *p; + unsigned int length; + char buf[sizeof(path_home)+40]; + + sprintf(buf,"%s%c%s",path_home,PATHSEP,fname); + length = GetFileLength(buf); + + if((f_in = fopen(buf,"rb")) == NULL) + { + fprintf(stderr,"Can't read data file: '%s'\n",buf); + return(NULL); + } + + if(ptr != NULL) + Free(ptr); + + if((p = Alloc(length)) == NULL) + { + fclose(f_in); + return(NULL); + } + if(fread(p,1,length,f_in) != length) + { + fclose(f_in); + return(NULL); + } + + fclose(f_in); + return(p); +} // end of ReadPhFile + + +int LoadPhData() +{//============= + int ix; + int n_phonemes; + int version; + int result = 1; + unsigned char *p; + + if((phoneme_tab_data = (unsigned char *)ReadPhFile((void *)(phoneme_tab_data),"phontab")) == NULL) + return(-1); + if((phoneme_index = (unsigned int *)ReadPhFile((void *)(phoneme_index),"phonindex")) == NULL) + return(-1); + if((spects_data = ReadPhFile((void *)(spects_data),"phondata")) == NULL) + return(-1); + wavefile_data = (unsigned char *)spects_data; + + // read the version number from the first 4 bytes of phondata + version = 0; + for(ix=0; ix<4; ix++) + { + version += (wavefile_data[ix] << (ix*8)); + } + + if(version != version_phdata) + { + result = version; + } + + // set up phoneme tables + p = phoneme_tab_data; + n_phoneme_tables = p[0]; + p+=4; + + for(ix=0; ix<n_phoneme_tables; ix++) + { + n_phonemes = p[0]; + phoneme_tab_list[ix].n_phonemes = p[0]; + phoneme_tab_list[ix].includes = p[1]; + p += 4; + memcpy(phoneme_tab_list[ix].name,p,N_PHONEME_TAB_NAME); + p += N_PHONEME_TAB_NAME; + phoneme_tab_list[ix].phoneme_tab_ptr = (PHONEME_TAB *)p; + p += (n_phonemes * sizeof(PHONEME_TAB)); + } + + if(phoneme_tab_number >= n_phoneme_tables) + phoneme_tab_number = 0; + + return(result); +} // end of LoadPhData + + +void FreePhData(void) +{//================== + Free(phoneme_tab_data); + Free(phoneme_index); + Free(spects_data); + phoneme_tab_data=NULL; + phoneme_index=NULL; + spects_data=NULL; +} + + +int PhonemeCode(unsigned int mnem) +{//=============================== + int ix; + + for(ix=0; ix<n_phoneme_tab; ix++) + { + if(phoneme_tab[ix] == NULL) + continue; + if(phoneme_tab[ix]->mnemonic == mnem) + return(phoneme_tab[ix]->code); + } + return(0); +} + + +int LookupPhonemeString(const char *string) +{//======================================== + int ix; + unsigned char c; + unsigned int mnem; + + // Pack up to 4 characters into a word + mnem = 0; + for(ix=0; ix<4; ix++) + { + if(string[ix]==0) break; + c = string[ix]; + mnem |= (c << (ix*8)); + } + + return(PhonemeCode(mnem)); +} + + + +static unsigned int LookupSound2(int index, unsigned int other_phcode, int control) +{//================================================================================ +// control=1 get formant transition data only + + unsigned int code; + unsigned int value, value2; + + while((value = phoneme_index[index++]) != 0) + { + if((code = (value & 0xff)) == other_phcode) + { + while(((value2 = phoneme_index[index]) != 0) && ((value2 & 0xff) < 8)) + { + switch(value2 & 0xff) + { + case 0: + // next entry is a wavefile to be played along with the synthesis + if(control==0) + { + wavefile_ix = value2 >> 8; + } + break; + case 1: + if(control==0) + { + seq_len_adjust = value2 >> 8; + } + break; + case 2: + if(control==0) + { + seq_len_adjust = value2 >> 8; + seq_len_adjust = -seq_len_adjust; + } + break; + case 3: + if(control==0) + { + wavefile_amp = value2 >> 8; + } + break; + case 4: + // formant transition data, 2 words + vowel_transition[0] = value2 >> 8; + vowel_transition[1] = phoneme_index[index++ + 1]; + break; + case 5: + // formant transition data, 2 words + vowel_transition[2] = value2 >> 8; + vowel_transition[3] = phoneme_index[index++ + 1]; + break; + } + index++; + } + return(value >> 8); + } + else + if((code == 4) || (code == 5)) + { + // formant transition data, ignore next word of data + index++; + } + } + return(3); // not found +} // end of LookupSound2 + + +unsigned int LookupSound(PHONEME_TAB *this_ph, PHONEME_TAB *other_ph, int which, int *match_level, int control) +{//============================================================================================================ + // follows, 1 other_ph preceeds this_ph, 2 other_ph follows this_ph + // control: 1= get formant transition data only + int spect_list; + int spect_list2; + int s_list; + unsigned char virtual_ph; + int result; + int level=0; + unsigned int other_code; + unsigned int other_virtual; + + if(control==0) + { + wavefile_ix = 0; + wavefile_amp = 32; + seq_len_adjust = 0; + } + memset(vowel_transition,0,sizeof(vowel_transition)); + + other_code = other_ph->code; + if(phoneme_tab[other_code]->type == phPAUSE) + other_code = phonPAUSE_SHORT; // use this version of Pause for matching + + if(which==1) + { + spect_list = this_ph->after; + virtual_ph = this_ph->start_type; + spect_list2 = phoneme_tab[virtual_ph]->after; + other_virtual = other_ph->end_type; + } + else + { + spect_list = this_ph->before; + virtual_ph = this_ph->end_type; + spect_list2 = phoneme_tab[virtual_ph]->before; + other_virtual = other_ph->start_type; + } + + result = 3; + // look for ph1-ph2 combination + if((s_list = spect_list) != 0) + { + if((result = LookupSound2(s_list,other_code,control)) != 3) + { + level = 2; + } + else + if(other_virtual != 0) + { + if((result = LookupSound2(spect_list,other_virtual,control)) != 3) + { + level = 1; + } + } + } + // not found, look in a virtual phoneme if one is given for this phoneme + if((result==3) && (virtual_ph != 0) && ((s_list = spect_list2) != 0)) + { + if((result = LookupSound2(s_list,other_code,control)) != 3) + { + level = 1; + } + else + if(other_virtual != 0) + { + if((result = LookupSound2(spect_list2,other_virtual,control)) != 3) + { + level = 1; + } + } + } + + if(match_level != NULL) + *match_level = level; + + if(result==0) + return(0); // NULL was given in the phoneme source + + // note: values = 1 indicates use the default for this phoneme, even though we found a match + // which set a secondary reference + if(result >= 4) + { + // values 1-3 can be used for special codes + // 1 = DFT from the phoneme source file + return(result); + } + + // no match found for other_ph, return the default + return(LookupSound2(this_ph->spect,phonPAUSE,control)); + +} // end of LookupSound + + + +frameref_t *LookupSpect(PHONEME_TAB *this_ph, PHONEME_TAB *prev_ph, PHONEME_TAB *next_ph, + int which, int *match_level, int *n_frames, PHONEME_LIST *plist) +{//========================================================================================================= + int ix; + int nf; + int nf1; + int seq_break; + frameref_t *frames; + int length1; + int length_std; + int length_factor; + SPECT_SEQ *seq, *seq2; + SPECT_SEQK *seqk, *seqk2; + PHONEME_TAB *next2_ph; + frame_t *frame; + static frameref_t frames_buf[N_SEQ_FRAMES]; + + PHONEME_TAB *other_ph; + if(which == 1) + other_ph = prev_ph; + else + other_ph = next_ph; + + if((ix = LookupSound(this_ph,other_ph,which,match_level,0)) < 4) + return(NULL); + seq = (SPECT_SEQ *)(&spects_data[ix]); + seqk = (SPECT_SEQK *)seq; + nf = seq->n_frames; + + + if(nf >= N_SEQ_FRAMES) + nf = N_SEQ_FRAMES - 1; + + seq_break = 0; + length1 = 0; + + for(ix=0; ix<nf; ix++) + { + if(seq->frame[0].frflags & FRFLAG_KLATT) + frame = &seqk->frame[ix]; + else + frame = (frame_t *)&seq->frame[ix]; + frames_buf[ix].frame = frame; + frames_buf[ix].frflags = frame->frflags; + frames_buf[ix].length = frame->length; + if(frame->frflags & FRFLAG_VOWEL_CENTRE) + seq_break = ix; + } + + frames = &frames_buf[0]; + if(seq_break > 0) + { + if(which==1) + { + nf = seq_break + 1; + } + else + { + frames = &frames_buf[seq_break]; // body of vowel, skip past initial frames + nf -= seq_break; + } + } + + // do we need to modify a frame for blending with a consonant? + if(this_ph->type == phVOWEL) + { + if((which==2) && ((frames[nf-1].frflags & FRFLAG_BREAK) == 0)) + { + // lookup formant transition for the following phoneme + + if((*match_level == 0) || (next_ph->type == phNASAL)) + { + LookupSound(next_ph,this_ph,1,NULL,1); + seq_len_adjust += FormantTransition2(frames,nf,vowel_transition[2],vowel_transition[3],next_ph,which); + } + else + if(next_ph->phflags == phVOWEL2) + { + // not really a consonant, rather a coloured vowel + if(LookupSound(next_ph,this_ph,1,NULL,1) == 0) + { + next2_ph = plist[2].ph; + LookupSound(next2_ph,next_ph,1,NULL,1); + seq_len_adjust += FormantTransition2(frames,nf,vowel_transition[2],vowel_transition[3],next2_ph,which); + } + } + } + else + { + if(*match_level == 0) + seq_len_adjust = FormantTransition2(frames,nf,vowel_transition0,vowel_transition1,prev_ph,which); + } + } + + nf1 = nf - 1; + for(ix=0; ix<nf1; ix++) + length1 += frames[ix].length; + + + if((wavefile_ix != 0) && ((wavefile_ix & 0x800000)==0)) + { + // a secondary reference has been returned, which is not a wavefile + // add these spectra to the main sequence + seq2 = (SPECT_SEQ *)(&spects_data[wavefile_ix]); + seqk2 = (SPECT_SEQK *)seq2; + + // first frame of the addition just sets the length of the last frame of the main seq + nf--; + for(ix=0; ix<seq2->n_frames; ix++) + { + if(seq2->frame[0].frflags & FRFLAG_KLATT) + frame = &seqk2->frame[ix]; + else + frame = (frame_t *)&seq2->frame[ix]; + + frames[nf].length = frame->length; + if(ix > 0) + { + frames[nf].frame = frame; + frames[nf].frflags = frame->frflags; + } + nf++; + } + wavefile_ix = 0; + } + + if((this_ph->type == phVOWEL) && (length1 > 0)) + { + if(which==2) + { + // adjust the length of the main part to match the standard length specified for the vowel + // less the front part of the vowel and any added suffix + + length_std = this_ph->std_length + seq_len_adjust - 45; + if(length_std < 10) + length_std = 10; + if(plist->synthflags & SFLAG_LENGTHEN) + length_std += phoneme_tab[phonLENGTHEN]->std_length; // phoneme was followed by an extra : symbol + +// can adjust vowel length for stressed syllables here + + + length_factor = (length_std * 256)/ length1; + + for(ix=0; ix<nf1; ix++) + { + frames[ix].length = (frames[ix].length * length_factor)/256; + } + } + else + { + // front of a vowel + if(*match_level == 0) + { + // allow very short vowels to have shorter front parts + if(this_ph->std_length < 130) + frames[0].length = (frames[0].length * this_ph->std_length)/130; + } + + if(seq_len_adjust != 0) + { + length_std = 0; + for(ix=0; ix<nf1; ix++) + { + length_std += frames[ix].length; + } + length_factor = ((length_std + seq_len_adjust) * 256)/length_std; + for(ix=0; ix<nf1; ix++) + { + frames[ix].length = (frames[ix].length * length_factor)/256; + } + } + } + } + + *n_frames = nf; + return(frames); +} // end of LookupSpect + + +unsigned char *LookupEnvelope(int ix) +{//================================ + if(ix==0) + return(NULL); + return((unsigned char *)&spects_data[phoneme_index[ix]]); +} + + +static void SetUpPhonemeTable(int number, int recursing) +{//===================================================== + int ix; + int includes; + int ph_code; + PHONEME_TAB *phtab; + + if(recursing==0) + { + memset(phoneme_tab_flags,0,sizeof(phoneme_tab_flags)); + } + + if((includes = phoneme_tab_list[number].includes) > 0) + { + // recursively include base phoneme tables + SetUpPhonemeTable(includes-1,1); + } + + // now add the phonemes from this table + phtab = phoneme_tab_list[number].phoneme_tab_ptr; + for(ix=0; ix<phoneme_tab_list[number].n_phonemes; ix++) + { + ph_code = phtab[ix].code; + phoneme_tab[ph_code] = &phtab[ix]; + if(ph_code > n_phoneme_tab) + n_phoneme_tab = ph_code; + + if(recursing == 0) + phoneme_tab_flags[ph_code] |= 1; // not inherited + } +} // end of SetUpPhonemeTable + + +void SelectPhonemeTable(int number) +{//================================ + n_phoneme_tab = 0; + SetUpPhonemeTable(number,0); // recursively for included phoneme tables + n_phoneme_tab++; + current_phoneme_table = number; +} // end of SelectPhonemeTable + + +int LookupPhonemeTable(const char *name) +{//===================================== + int ix; + + for(ix=0; ix<n_phoneme_tables; ix++) + { + if(strcmp(name,phoneme_tab_list[ix].name)==0) + { + phoneme_tab_number = ix; + break; + } + } + if(ix == n_phoneme_tables) + return(-1); + + return(ix); +} + + +int SelectPhonemeTableName(const char *name) +{//========================================= +// Look up a phoneme set by name, and select it if it exists +// Returns the phoneme table number + int ix; + + if((ix = LookupPhonemeTable(name)) == -1) + return(-1); + + SelectPhonemeTable(ix); + return(ix); +} // end of DelectPhonemeTableName + + + + +void LoadConfig(void) +{//================== +// Load configuration file, if one exists + char buf[sizeof(path_home)+10]; + FILE *f; + int ix; + char c1; + char *p; + char string[200]; + + for(ix=0; ix<N_SOUNDICON_SLOTS; ix++) + { + soundicon_tab[ix].filename = NULL; + soundicon_tab[ix].data = NULL; + } + + sprintf(buf,"%s%c%s",path_home,PATHSEP,"config"); + if((f = fopen(buf,"r"))==NULL) + { + return; + } + + while(fgets(buf,sizeof(buf),f)!=NULL) + { + if(memcmp(buf,"tone",4)==0) + { + ReadTonePoints(&buf[5],tone_points); + } + else + if(memcmp(buf,"pa_device",9)==0) + { + sscanf(&buf[7],"%d",&option_device_number); + } + else + if(memcmp(buf,"soundicon",9)==0) + { + ix = sscanf(&buf[10],"_%c %s",&c1,string); + if(ix==2) + { + soundicon_tab[n_soundicon_tab].name = c1; + p = Alloc(strlen(string)+1); + strcpy(p,string); + soundicon_tab[n_soundicon_tab].filename = p; + soundicon_tab[n_soundicon_tab++].length = 0; + } + } + } +} // end of LoadConfig + diff --git a/Plugins/eSpeak/eSpeak/synthesize.cpp b/Plugins/eSpeak/eSpeak/synthesize.cpp new file mode 100644 index 0000000..42d4f0b --- /dev/null +++ b/Plugins/eSpeak/eSpeak/synthesize.cpp @@ -0,0 +1,1660 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "StdAfx.h" + +#include <stdio.h> +#include <ctype.h> +#include <wctype.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "voice.h" +#include "translate.h" + + +extern FILE *f_log; +static void SmoothSpect(void); + + +// list of phonemes in a clause +int n_phoneme_list=0; +PHONEME_LIST phoneme_list[N_PHONEME_LIST]; + +int mbrola_delay; +char mbrola_name[20]; + +SPEED_FACTORS speed; + +static int last_pitch_cmd; +static int last_amp_cmd; +static frame_t *last_frame; +static int last_wcmdq; +static int pitch_length; +static int amp_length; +static int modn_flags; + +static int syllable_start; +static int syllable_end; +static int syllable_centre; + +static voice_t *new_voice=NULL; + +int n_soundicon_tab=N_SOUNDICON_SLOTS; +SOUND_ICON soundicon_tab[N_SOUNDICON_TAB]; + +#define RMS_GLOTTAL1 35 // vowel before glottal stop +#define RMS_START 28 // 28 + +#define VOWEL_FRONT_LENGTH 50 + + + +// a dummy phoneme_list entry which looks like a pause +static PHONEME_LIST next_pause; + + +const char *WordToString(unsigned int word) +{//======================================== +// Convert a phoneme mnemonic word into a string + int ix; + static char buf[5]; + + for(ix=0; ix<3; ix++) + buf[ix] = word >> (ix*8); + buf[4] = 0; + return(buf); +} + + + +void SynthesizeInit() +{//================== + last_pitch_cmd = 0; + last_amp_cmd = 0; + last_frame = NULL; + syllable_centre = -1; + + // initialise next_pause, a dummy phoneme_list entry +// next_pause.ph = phoneme_tab[phonPAUSE]; // this must be done after voice selection + next_pause.type = phPAUSE; + next_pause.newword = 0; +} + + + +static void EndAmplitude(void) +{//=========================== + if(amp_length > 0) + { + if(wcmdq[last_amp_cmd][1] == 0) + wcmdq[last_amp_cmd][1] = amp_length; + amp_length = 0; + } +} + + + +static void EndPitch(int voice_break) +{//================================== + // posssible end of pitch envelope, fill in the length + if((pitch_length > 0) && (last_pitch_cmd >= 0)) + { + if(wcmdq[last_pitch_cmd][1] == 0) + wcmdq[last_pitch_cmd][1] = pitch_length; + pitch_length = 0; + } + + if(voice_break) + { + last_wcmdq = -1; + last_frame = NULL; + syllable_end = wcmdq_tail; + SmoothSpect(); + syllable_centre = -1; + memset(vowel_transition,0,sizeof(vowel_transition)); + } +} // end of Synthesize::EndPitch + + + +static void DoAmplitude(int amp, unsigned char *amp_env) +{//===================================================== + long *q; + + last_amp_cmd = wcmdq_tail; + amp_length = 0; // total length of vowel with this amplitude envelope + + q = wcmdq[wcmdq_tail]; + q[0] = WCMD_AMPLITUDE; + q[1] = 0; // fill in later from amp_length + q[2] = (long)amp_env; + q[3] = amp; + WcmdqInc(); +} // end of Synthesize::DoAmplitude + + + +static void DoPitch(unsigned char *env, int pitch1, int pitch2) +{//============================================================ + long *q; + + EndPitch(0); + + if(pitch1 == 1024) + { + // pitch was not set + pitch1 = 24; + pitch2 = 33; + env = envelope_data[PITCHfall]; + } + last_pitch_cmd = wcmdq_tail; + pitch_length = 0; // total length of spect with this pitch envelope + + if(pitch2 < 0) + pitch2 = 0; + + q = wcmdq[wcmdq_tail]; + q[0] = WCMD_PITCH; + q[1] = 0; // length, fill in later from pitch_length + q[2] = (long)env; + q[3] = (pitch1 << 16) + pitch2; + WcmdqInc(); +} // end of Synthesize::DoPitch + + + +int PauseLength(int pause, int control) +{//==================================== + int len; + + if(control == 0) + len = (pause * speed.speed_factor1)/256; + else + len = (pause * speed.speed_factor2)/256; + + if(len < 5) len = 5; // mS, limit the amount to which pauses can be shortened + return(len); +} + + +static void DoPause(int length, int control) +{//========================================= +// control = 1, less shortening at fast speeds + int len; + + len = PauseLength(length, control); + + len = (len * samplerate) / 1000; // convert from mS to number of samples + + EndPitch(1); + wcmdq[wcmdq_tail][0] = WCMD_PAUSE; + wcmdq[wcmdq_tail][1] = len; + WcmdqInc(); + last_frame = NULL; +} // end of Synthesize::DoPause + + +extern int seq_len_adjust; // temporary fix to advance the start point for playing the wav sample + + +static int DoSample2(int index, int which, int length_mod, int amp) +{//================================================================ + int length; + int length1; + int format; + int min_length; + int start=0; + long *q; + unsigned char *p; + + index = index & 0x7fffff; + p = &wavefile_data[index]; + format = p[2]; + length1 = (p[1] * 256); + length1 += p[0]; // length in bytes + + if(seq_len_adjust > 0) + { + start = (seq_len_adjust * samplerate)/1000; + if(format == 0) + start *= 2; + length1 -= start; + index += start; + } + + + if(length_mod > 0) + length = (length1 * length_mod) / 256; + else + length = length1; + + + length = (length * speed.speed_factor2)/256; + min_length = speed.min_sample_len; + if(format==0) + min_length *= 2; + + if(length < min_length) + length = min_length; + + if(length > length1) + length = length1; // don't exceed wavefile length + + if(format==0) + length /= 2; // 2 byte samples + + + index += 4; + + if(amp >= 0) + { + last_wcmdq = wcmdq_tail; + q = wcmdq[wcmdq_tail]; + if(which & 0x100) + q[0] = WCMD_WAVE2; // mix this with synthesised wave + else + q[0] = WCMD_WAVE; + q[1] = length; // length in samples + q[2] = long(&wavefile_data[index]); + q[3] = format + (amp << 8); + WcmdqInc(); + } + return(length); +} // end of Synthesize::DoSample2 + + +int DoSample(PHONEME_TAB *ph1, PHONEME_TAB *ph2, int which, int length_mod, int amp) +{//====================== ========================================================== + int index; + int match_level; + int amp2; + int result; + + EndPitch(1); + index = LookupSound(ph1,ph2,which & 0xff,&match_level,0); + if((index & 0x800000) == 0) + return(0); // not wavefile data + + amp2 = wavefile_amp; + if(amp != 0) + amp2 = (amp * wavefile_amp)/20; + + if(amp == -1) + amp2 = amp; + + result = DoSample2(index,which,length_mod,amp2); + last_frame = NULL; + return(result); +} // end of Synthesize::DoSample + + + + +static frame_t *AllocFrame() +{//========================= + // Allocate a temporary spectrum frame for the wavegen queue. Use a pool which is big + // enough to use a round-robin without checks. + // Only needed for modifying spectra for blending to consonants + +#define N_FRAME_POOL N_WCMDQ + static int ix=0; + static frame_t frame_pool[N_FRAME_POOL]; + + ix++; + if(ix >= N_FRAME_POOL) + ix = 0; + return(&frame_pool[ix]); +} + + +static void set_frame_rms(frame_t *fr, int new_rms) +{//================================================= +// Each frame includes its RMS amplitude value, so to set a new +// RMS just adjust the formant amplitudes by the appropriate ratio + + int x; + int h; + int ix; + + static const short sqrt_tab[200] = { + 0, 64, 90,110,128,143,156,169,181,192,202,212,221,230,239,247, + 256,263,271,278,286,293,300,306,313,320,326,332,338,344,350,356, + 362,367,373,378,384,389,394,399,404,409,414,419,424,429,434,438, + 443,448,452,457,461,465,470,474,478,483,487,491,495,499,503,507, + 512,515,519,523,527,531,535,539,543,546,550,554,557,561,565,568, + 572,576,579,583,586,590,593,596,600,603,607,610,613,617,620,623, + 627,630,633,636,640,643,646,649,652,655,658,662,665,668,671,674, + 677,680,683,686,689,692,695,698,701,704,706,709,712,715,718,721, + 724,726,729,732,735,738,740,743,746,749,751,754,757,759,762,765, + 768,770,773,775,778,781,783,786,789,791,794,796,799,801,804,807, + 809,812,814,817,819,822,824,827,829,832,834,836,839,841,844,846, + 849,851,853,856,858,861,863,865,868,870,872,875,877,879,882,884, + 886,889,891,893,896,898,900,902}; + + if(fr->frflags & FRFLAG_KLATT) + { + if(new_rms == -1) + { + fr->klattp[KLATT_AV] = 50; + } + return; + } + + if(fr->rms == 0) return; // check for divide by zero + x = (new_rms * 64)/fr->rms; + if(x >= 200) x = 199; + + x = sqrt_tab[x]; // sqrt(new_rms/fr->rms)*0x200; + + for(ix=0; ix<N_PEAKS; ix++) + { + h = fr->fheight[ix] * x; + fr->fheight[ix] = h/0x200; + } +} /* end of set_frame_rms */ + + + +static void formants_reduce_hf(frame_t *fr, int level) +{//==================================================== +// change height of peaks 2 to 8, percentage + int ix; + int x; + + if(fr->frflags & FRFLAG_KLATT) + return; + + for(ix=2; ix<N_PEAKS; ix++) + { + x = fr->fheight[ix] * level; + fr->fheight[ix] = x/100; + } +} + + +static frame_t *CopyFrame(frame_t *frame1, int copy) +{//================================================= +// create a copy of the specified frame in temporary buffer + frame_t *frame2; + + if((copy==0) && (frame1->frflags & FRFLAG_COPIED)) + { + // this frame has already been copied in temporary rw memory + return(frame1); + } + + frame2 = AllocFrame(); + if(frame2 != NULL) + { + memcpy(frame2,frame1,sizeof(frame_t)); + frame2->length = 0; + frame2->frflags |= FRFLAG_COPIED; + } + return(frame2); +} + + +static frame_t *DuplicateLastFrame(frameref_t *seq, int n_frames, int length) +{//========================================================================== + frame_t *fr; + + seq[n_frames-1].length = length; + fr = CopyFrame(seq[n_frames-1].frame,1); + seq[n_frames].frame = fr; + seq[n_frames].length = 0; + return fr; +} + + +static void AdjustFormants(frame_t *fr, int target, int min, int max, int f1_adj, int f3_adj, int hf_reduce, int flags) +{//==================================================================================================================== + int x; + +//hf_reduce = 70; // ?? using fixed amount rather than the parameter?? + + target = (target * voice->formant_factor)/256; + + x = (target - fr->ffreq[2]) / 2; + if(x > max) x = max; + if(x < min) x = min; + fr->ffreq[2] += x; + fr->ffreq[3] += f3_adj; + + if(flags & 0x20) + { + f3_adj = -f3_adj; //. reverse direction for f4,f5 change + } + fr->ffreq[4] += f3_adj; + fr->ffreq[5] += f3_adj; + + if(f1_adj==1) + { + x = (235 - fr->ffreq[1]); + if(x < -100) x = -100; + if(x > -60) x = -60; + fr->ffreq[1] += x; + } + if(f1_adj==2) + { + x = (235 - fr->ffreq[1]); + if(x < -300) x = -300; + if(x > -150) x = -150; + fr->ffreq[1] += x; + fr->ffreq[0] += x; + } + if(f1_adj==3) + { + x = (100 - fr->ffreq[1]); + if(x < -400) x = -400; + if(x > -300) x = -400; + fr->ffreq[1] += x; + fr->ffreq[0] += x; + } + formants_reduce_hf(fr,hf_reduce); +} + + +static int VowelCloseness(frame_t *fr) +{//=================================== +// return a value 0-3 depending on the vowel's f1 + int f1; + + if((f1 = fr->ffreq[1]) < 300) + return(3); + if(f1 < 400) + return(2); + if(f1 < 500) + return(1); + return(0); +} + + +int FormantTransition2(frameref_t *seq, int &n_frames, unsigned int data1, unsigned int data2, PHONEME_TAB *other_ph, int which) +{//============================================================================================================================== + int ix; + int formant; + int next_rms; + + int len; + int rms; + int f1; + int f2; + int f2_min; + int f2_max; + int f3_adj; + int f3_amp; + int flags; + int vcolour; + +#define N_VCOLOUR 2 +// percentage change for each formant in 256ths +static short vcolouring[N_VCOLOUR][5] = { + {243,272,256,256,256}, // palatal consonant follows + {256,256,240,240,240}, // retroflex +}; + + frame_t *fr = NULL; + + if(n_frames < 2) + return(0); + + len = (data1 & 0x3f) * 2; + rms = (data1 >> 6) & 0x3f; + flags = (data1 >> 12); + + f2 = (data2 & 0x3f) * 50; + f2_min = (((data2 >> 6) & 0x1f) - 15) * 50; + f2_max = (((data2 >> 11) & 0x1f) - 15) * 50; + f3_adj = (((data2 >> 16) & 0x1f) - 15) * 50; + f3_amp = ((data2 >> 21) & 0x1f) * 8; + f1 = ((data2 >> 26) & 0x7); + vcolour = (data2 >> 29); + +// fprintf(stderr,"FMT%d %3s %3d-%3d f1=%d f2=%4d %4d %4d f3=%4d %3d\n", +// which,WordToString(other_ph->mnemonic),len,rms,f1,f2,f2_min,f2_max,f3_adj,f3_amp); + + if(other_ph->mnemonic == '?') + flags |= 8; + + if(which == 1) + { + /* entry to vowel */ + fr = CopyFrame(seq[0].frame,0); + seq[0].frame = fr; + seq[0].length = VOWEL_FRONT_LENGTH; + if(len > 0) + seq[0].length = len; + seq[0].frflags |= FRFLAG_LEN_MOD; // reduce length modification + fr->frflags |= FRFLAG_LEN_MOD; + + next_rms = seq[1].frame->rms; + +if(fr->frflags & FRFLAG_KLATT) +{ + fr->klattp[KLATT_AV] = 53; // reduce the amplituide of the start of a vowel +} + if(f2 != 0) + { + if(rms & 0x20) + { + set_frame_rms(fr,(next_rms * (rms & 0x1f))/30); + } + AdjustFormants(fr, f2, f2_min, f2_max, f1, f3_adj, f3_amp, flags); + + if((rms & 0x20) == 0) + { + set_frame_rms(fr,rms*2); + } + } + else + { + if(flags & 8) + set_frame_rms(fr,(next_rms*24)/32); + else + set_frame_rms(fr,RMS_START); + } + + if(flags & 8) + { +// set_frame_rms(fr,next_rms - 5); + modn_flags = 0x800 + (VowelCloseness(fr) << 8); + } + } + else + { + // exit from vowel + rms = rms*2; + if((f2 != 0) || (flags != 0)) + { + + if(flags & 8) + { + fr = CopyFrame(seq[n_frames-1].frame,0); + seq[n_frames-1].frame = fr; + rms = RMS_GLOTTAL1; + + // degree of glottal-stop effect depends on closeness of vowel (indicated by f1 freq) + modn_flags = 0x400 + (VowelCloseness(fr) << 8); + } + else + { + fr = DuplicateLastFrame(seq,n_frames++,len); + if(len > 36) + seq_len_adjust += (len - 36); + + if(f2 != 0) + { + AdjustFormants(fr, f2, f2_min, f2_max, f1, f3_adj, f3_amp, flags); + } + } + + set_frame_rms(fr,rms); + + if((vcolour > 0) && (vcolour <= N_VCOLOUR)) + { + for(ix=0; ix<n_frames; ix++) + { + fr = CopyFrame(seq[ix].frame,0); + seq[ix].frame = fr; + + for(formant=1; formant<=5; formant++) + { + int x; + x = fr->ffreq[formant] * vcolouring[vcolour-1][formant-1]; + fr->ffreq[formant] = x / 256; + } + } + } + } + } + + if(fr != NULL) + { + if(flags & 4) + fr->frflags |= FRFLAG_FORMANT_RATE; + if(flags & 2) + fr->frflags |= FRFLAG_BREAK; // don't merge with next frame + } + + if(flags & 0x40) + DoPause(12,0); // add a short pause after the consonant + + if(flags & 16) + return(len); + return(0); +} // end of FormantTransition2 + + + +static void SmoothSpect(void) +{//========================== + // Limit the rate of frequence change of formants, to reduce chirping + + long *q; + frame_t *frame; + frame_t *frame2; + frame_t *frame1; + frame_t *frame_centre; + int ix; + int len; + int pk; + int modified; + int allowed; + int diff; + + if(syllable_start == syllable_end) + return; + + if((syllable_centre < 0) || (syllable_centre == syllable_start)) + { + syllable_start = syllable_end; + return; + } + + q = wcmdq[syllable_centre]; + frame_centre = (frame_t *)q[2]; + +//if(frame_centre->frflags & FRFLAG_KLATT) +// return; // TESTING + + // backwards + ix = syllable_centre -1; + frame = frame2 = frame_centre; + for(;;) + { + if(ix < 0) ix = N_WCMDQ-1; + q = wcmdq[ix]; + + if(q[0] == WCMD_PAUSE || q[0] == WCMD_WAVE) + break; + + if(q[0] <= WCMD_SPECT2) + { + len = q[1] & 0xffff; + + frame1 = (frame_t *)q[3]; + if(frame1 == frame) + { + q[3] = (long)frame2; + frame1 = frame2; + } + else + break; // doesn't follow on from previous frame + + frame = frame2 = (frame_t *)q[2]; + modified = 0; + + if(frame->frflags & FRFLAG_BREAK) + break; + + if(frame->frflags & FRFLAG_FORMANT_RATE) + len = (len * 12)/10; // allow slightly greater rate of change for this frame (was 12/10) + + for(pk=0; pk<6; pk++) + { + int f1, f2; + + if((frame->frflags & FRFLAG_BREAK_LF) && (pk < 3)) + continue; + + f1 = frame1->ffreq[pk]; + f2 = frame->ffreq[pk]; + + // backwards + if((diff = f2 - f1) > 0) + { + allowed = f1*2 + f2; + } + else + { + allowed = f1 + f2*2; + } + + // the allowed change is specified as percentage (%*10) of the frequency + // take "frequency" as 1/3 from the lower freq + allowed = (allowed * formant_rate[pk])/3000; + allowed = (allowed * len)/256; + + if(diff > allowed) + { + if(modified == 0) + { + frame2 = CopyFrame(frame,0); + modified = 1; + } + frame2->ffreq[pk] = frame1->ffreq[pk] + allowed; + q[2] = (long)frame2; + } + else + if(diff < -allowed) + { + if(modified == 0) + { + frame2 = CopyFrame(frame,0); + modified = 1; + } + frame2->ffreq[pk] = frame1->ffreq[pk] - allowed; + q[2] = (long)frame2; + } + } + } + + if(ix == syllable_start) + break; + ix--; + } + + // forwards + ix = syllable_centre; + + frame = NULL; + for(;;) + { + q = wcmdq[ix]; + + if(q[0] == WCMD_PAUSE || q[0] == WCMD_WAVE) + break; + + if(q[0] <= WCMD_SPECT2) + { + + len = q[1] & 0xffff; + + frame1 = (frame_t *)q[2]; + if(frame != NULL) + { + if(frame1 == frame) + { + q[2] = (long)frame2; + frame1 = frame2; + } + else + break; // doesn't follow on from previous frame + } + + frame = frame2 = (frame_t *)q[3]; + modified = 0; + + if(frame1->frflags & FRFLAG_BREAK) + break; + + if(frame1->frflags & FRFLAG_FORMANT_RATE) + len = (len *6)/5; // allow slightly greater rate of change for this frame + + for(pk=0; pk<6; pk++) + { + int f1, f2; + f1 = frame1->ffreq[pk]; + f2 = frame->ffreq[pk]; + + // forwards + if((diff = f2 - f1) > 0) + { + allowed = f1*2 + f2; + } + else + { + allowed = f1 + f2*2; + } + allowed = (allowed * formant_rate[pk])/3000; + allowed = (allowed * len)/256; + + if(diff > allowed) + { + if(modified == 0) + { + frame2 = CopyFrame(frame,0); + modified = 1; + } + frame2->ffreq[pk] = frame1->ffreq[pk] + allowed; + q[3] = (long)frame2; + } + else + if(diff < -allowed) + { + if(modified == 0) + { + frame2 = CopyFrame(frame,0); + modified = 1; + } + frame2->ffreq[pk] = frame1->ffreq[pk] - allowed; + q[3] = (long)frame2; + } + } + } + + ix++; + if(ix >= N_WCMDQ) ix = 0; + if(ix == syllable_end) + break; + } + + syllable_start = syllable_end; +} // end of SmoothSpect + + +static void StartSyllable(void) +{//============================ + // start of syllable, if not already started + if(syllable_end == syllable_start) + syllable_end = wcmdq_tail; +} + + +int DoSpect(PHONEME_TAB *this_ph, PHONEME_TAB *prev_ph, PHONEME_TAB *next_ph, + int which, PHONEME_LIST *plist, int modulation) +{//=================================================================================== + // which 1 start of phoneme, 2 body and end + // length_mod: 256 = 100% + // modulation: -1 = don't write to wcmdq + + int n_frames; + frameref_t *frames; + int frameix; + frame_t *frame1; + frame_t *frame2; + frame_t *fr; + int ix; + long *q; + int len; + int match_level; + int frame_length; + int frame1_length; + int frame2_length; + int length_factor; + int length_mod; + int total_len = 0; + static int wave_flag = 0; + int wcmd_spect = WCMD_SPECT; + + length_mod = plist->length; + if(length_mod==0) length_mod=256; + +if(which==1) +{ + // limit the shortening of sonorants before shortened (eg. unstressed vowels) + if((this_ph->type==phLIQUID) || (prev_ph->type==phLIQUID) || (prev_ph->type==phNASAL)) + { + if(length_mod < (len = translator->langopts.param[LOPT_SONORANT_MIN])) + { + length_mod = len; + } + } +} + + modn_flags = 0; + frames = LookupSpect(this_ph,prev_ph,next_ph,which,&match_level,&n_frames, plist); + if(frames == NULL) + return(0); // not found + + frame1 = frames[0].frame; + frame1_length = frames[0].length; + if(frame1->frflags & FRFLAG_KLATT) + wcmd_spect = WCMD_KLATT; + + if(wavefile_ix == 0) + { + if(wave_flag) + { + // cancel any wavefile that was playing previously + wcmd_spect = WCMD_SPECT2; + if(frame1->frflags & FRFLAG_KLATT) + wcmd_spect = WCMD_KLATT2; + wave_flag = 0; + } + else + { + wcmd_spect = WCMD_SPECT; + if(frame1->frflags & FRFLAG_KLATT) + wcmd_spect = WCMD_KLATT; + } + } + + if(last_frame != NULL) + { + if(((last_frame->length < 2) || (last_frame->frflags & FRFLAG_VOWEL_CENTRE)) + && !(last_frame->frflags & FRFLAG_BREAK)) + { + // last frame of previous sequence was zero-length, replace with first of this sequence + wcmdq[last_wcmdq][3] = (long)frame1; + + if(last_frame->frflags & FRFLAG_BREAK_LF) + { + // but flag indicates keep HF peaks in last segment + fr = CopyFrame(frame1,1); + for(ix=3; ix<N_PEAKS; ix++) + { + fr->ffreq[ix] = last_frame->ffreq[ix]; + fr->fheight[ix] = last_frame->fheight[ix]; + } + wcmdq[last_wcmdq][3] = (long)fr; + } + } + } + + if((this_ph->type == phVOWEL) && (which == 2)) + { + SmoothSpect(); // process previous syllable + + // remember the point in the output queue of the centre of the vowel + syllable_centre = wcmdq_tail; + } + + frame_length = frame1_length; + for(frameix=1; frameix<n_frames; frameix++) + { + frame2 = frames[frameix].frame; + frame2_length = frames[frameix].length; + + if((wavefile_ix != 0) && ((frame1->frflags & FRFLAG_DEFER_WAV)==0)) + { + // there is a wave file to play along with this synthesis + seq_len_adjust = 0; + DoSample2(wavefile_ix,which+0x100,0,wavefile_amp); + wave_flag = 1; + wavefile_ix = 0; + } + + length_factor = length_mod; + if(frame1->frflags & FRFLAG_LEN_MOD) // reduce effect of length mod + { + length_factor = (length_mod*(256-speed.speed_factor3) + 256*speed.speed_factor3)/256; + } + len = (frame_length * samplerate)/1000; + len = (len * length_factor)/256; + + if(modulation >= 0) + { + if(frame1->frflags & FRFLAG_MODULATE) + { + modulation = 6; + } + if((frameix == n_frames-1) && (modn_flags & 0xf00)) + modulation |= modn_flags; // before or after a glottal stop + } + + pitch_length += len; + amp_length += len; + + if(frame_length < 2) + { + last_frame = NULL; + frame_length = frame2_length; + frame1 = frame2; + } + else + { + last_wcmdq = wcmdq_tail; + + if(modulation >= 0) + { + q = wcmdq[wcmdq_tail]; + q[0] = wcmd_spect; + q[1] = len + (modulation << 16); + q[2] = long(frame1); + q[3] = long(frame2); + + WcmdqInc(); + } + last_frame = frame1 = frame2; + frame_length = frame2_length; + total_len += len; + } + } + return(total_len); +} // end of Synthesize::DoSpect + + +static void DoMarker(int type, int char_posn, int length, int value) +{//================================================================= +// This could be used to return an index to the word currently being spoken +// Type 1=word, 2=sentence, 3=named marker, 4=play audio, 5=end + wcmdq[wcmdq_tail][0] = WCMD_MARKER; + wcmdq[wcmdq_tail][1] = type; + wcmdq[wcmdq_tail][2] = (char_posn & 0xffffff) | (length << 24); + wcmdq[wcmdq_tail][3] = value; + WcmdqInc(); + +} // end of Synthesize::DoMarker + + +void DoVoiceChange(voice_t *v) +{//=========================== +// allocate memory for a copy of the voice data, and free it in wavegenfill() + voice_t *v2; + + v2 = (voice_t *)malloc(sizeof(voice_t)); + memcpy(v2,v,sizeof(voice_t)); + wcmdq[wcmdq_tail][0] = WCMD_VOICE; + wcmdq[wcmdq_tail][1] = (long)(v2); + WcmdqInc(); +} + + +static void DoEmbedded(int &embix, int sourceix) +{//============================================= + // There were embedded commands in the text at this point + unsigned int word; // bit 7=last command for this word, bits 5,6 sign, bits 0-4 command + unsigned int value; + int command; + + do { + word = embedded_list[embix++]; + value = word >> 8; + command = word & 0x7f; + + switch(command & 0x1f) + { + case EMBED_S: // speed + SetEmbedded((command & 0x60) + EMBED_S2,value); // adjusts embedded_value[EMBED_S2] + SetSpeed(2); + break; + + case EMBED_I: // play dynamically loaded wav data (sound icon) + if((int)value < n_soundicon_tab) + { + if(soundicon_tab[value].length != 0) + { + DoPause(10,0); // ensure a break in the speech + wcmdq[wcmdq_tail][0] = WCMD_WAVE; + wcmdq[wcmdq_tail][1] = soundicon_tab[value].length; + wcmdq[wcmdq_tail][2] = (long)soundicon_tab[value].data + 44; // skip WAV header + wcmdq[wcmdq_tail][3] = 0x1500; // 16 bit data, amp=21 + WcmdqInc(); + } + } + break; + + case EMBED_M: // named marker + DoMarker(espeakEVENT_MARK, (sourceix & 0x7ff) + clause_start_char, 0, value); + break; + + case EMBED_U: // play sound + DoMarker(espeakEVENT_PLAY, count_characters+1, 0, value); // always occurs at end of clause + break; + + default: + DoPause(10,0); // ensure a break in the speech + wcmdq[wcmdq_tail][0] = WCMD_EMBEDDED; + wcmdq[wcmdq_tail][1] = command; + wcmdq[wcmdq_tail][2] = value; + WcmdqInc(); + break; + } + } while ((word & 0x80) == 0); +} + + + +int Generate(PHONEME_LIST *phoneme_list, int *n_ph, int resume) +{//============================================================ + static int ix; + static int embedded_ix; + static int word_count; + PHONEME_LIST *prev; + PHONEME_LIST *next; + PHONEME_LIST *next2; + PHONEME_LIST *p; + int released; + int stress; + int modulation; + int pre_voiced; + int free_min; + unsigned char *pitch_env=NULL; + unsigned char *amp_env; + PHONEME_TAB *ph; + PHONEME_TAB *prev_ph; + static int sourceix=0; + +#ifdef TEST_MBROLA + if(mbrola_name[0] != 0) + return(MbrolaGenerate(phoneme_list,n_ph,resume)); +#endif + + if(option_quiet) + return(0); + + if(resume == 0) + { + ix = 1; + embedded_ix=0; + word_count = 0; + pitch_length = 0; + amp_length = 0; + last_frame = NULL; + last_wcmdq = -1; + syllable_start = wcmdq_tail; + syllable_end = wcmdq_tail; + syllable_centre = -1; + last_pitch_cmd = -1; + memset(vowel_transition,0,sizeof(vowel_transition)); + } + + while(ix < (*n_ph)) + { + p = &phoneme_list[ix]; + + if(p->type == phPAUSE) + free_min = 5; + else + if(p->type != phVOWEL) + free_min = 10; // we need less Q space for non-vowels, and we need to generate phonemes after a vowel so that the pitch_length is filled in + else + free_min = MIN_WCMDQ; // 22 + + if(WcmdqFree() <= free_min) + return(1); // wait + + prev = &phoneme_list[ix-1]; + next = &phoneme_list[ix+1]; + next2 = &phoneme_list[ix+2]; + + if(p->synthflags & SFLAG_EMBEDDED) + { + DoEmbedded(embedded_ix, p->sourceix); + } + + if(p->newword) + { + if(translator->langopts.param[LOPT_WORD_MERGE] == 0) + last_frame = NULL; + + sourceix = (p->sourceix & 0x7ff) + clause_start_char; + + if(p->newword & 4) + DoMarker(espeakEVENT_SENTENCE, sourceix, 0, count_sentences); // start of sentence + +// if(p->newword & 2) +// DoMarker(espeakEVENT_END, count_characters, 0, count_sentences); // end of clause + + if(p->newword & 1) + DoMarker(espeakEVENT_WORD, sourceix, p->sourceix >> 11, clause_start_word + word_count++); + } + + EndAmplitude(); + + if(p->prepause > 0) + DoPause(p->prepause,1); + + if(option_phoneme_events && (p->type != phVOWEL)) + { + // Note, for vowels, do the phoneme event after the vowel-start + DoMarker(espeakEVENT_PHONEME, sourceix, 0, p->ph->mnemonic); + } + + switch(p->type) + { + case phPAUSE: + DoPause(p->length,0); + break; + + case phSTOP: + released = 0; + if(next->type==phVOWEL) released = 1; + if(next->type==phLIQUID && !next->newword) released = 1; + + if(released) + DoSample(p->ph,next->ph,2,0,0); + else + DoSample(p->ph,phoneme_tab[phonPAUSE],2,0,0); + break; + + case phFRICATIVE: + if(p->synthflags & SFLAG_LENGTHEN) + DoSample(p->ph,next->ph,2,p->length,0); // play it twice for [s:] etc. + DoSample(p->ph,next->ph,2,p->length,0); + break; + + case phVSTOP: + pre_voiced = 0; + if(next->type==phVOWEL) + { + DoAmplitude(p->amp,NULL); + DoPitch(envelope_data[p->env],p->pitch1,p->pitch2); + pre_voiced = 1; + } + else + if((next->type==phLIQUID) && !next->newword) + { + DoAmplitude(next->amp,NULL); + DoPitch(envelope_data[next->env],next->pitch1,next->pitch2); + pre_voiced = 1; + } + else + { + if(last_pitch_cmd < 0) + { + DoAmplitude(next->amp,NULL); + DoPitch(envelope_data[p->env],p->pitch1,p->pitch2); + } + } + + if((prev->type==phVOWEL) || (prev->ph->phflags & phVOWEL2)) + { + // a period of voicing before the release + DoSpect(p->ph,phoneme_tab[phonSCHWA],next->ph,1,p,0); + if(p->synthflags & SFLAG_LENGTHEN) + { + DoPause(20,0); + DoSpect(p->ph,phoneme_tab[phonSCHWA],next->ph,1,p,0); + } + } + else + { + if(p->synthflags & SFLAG_LENGTHEN) + { + DoPause(50,0); + } + } + + if(pre_voiced) + { + // followed by a vowel, or liquid + vowel + StartSyllable(); + DoSpect(p->ph,prev->ph,next->ph,2,p,0); + } + else + { +// if((prev->type != phVOWEL) && ((prev->ph->phflags & phVOICED)==0) && ((next->ph->phflags & phVOICED)==0)) +// DoSpect(p->ph,prev->ph,phoneme_tab[phonPAUSE_SHORT],2,p,0); +// else + DoSpect(p->ph,prev->ph,phoneme_tab[phonPAUSE],2,p,0); +// DoSpect(p->ph,prev->ph,next->ph,2,p,0); + } + break; + + case phVFRICATIVE: + if(next->type==phVOWEL) + { + DoAmplitude(p->amp,NULL); + DoPitch(envelope_data[p->env],p->pitch1,p->pitch2); + } + else + if(next->type==phLIQUID) + { + DoAmplitude(next->amp,NULL); + DoPitch(envelope_data[next->env],next->pitch1,next->pitch2); + } + else + { + if(last_pitch_cmd < 0) + { + DoAmplitude(p->amp,NULL); + DoPitch(envelope_data[p->env],p->pitch1,p->pitch2); + } + } + + if((next->type==phVOWEL) || ((next->type==phLIQUID)) && (next->newword==0)) // ?? test 14.Aug.2007 + { + StartSyllable(); + if(p->synthflags & SFLAG_LENGTHEN) + DoSpect(p->ph,prev->ph,next->ph,2,p,0); + DoSpect(p->ph,prev->ph,next->ph,2,p,0); + } + else + { + if(p->synthflags & SFLAG_LENGTHEN) + DoSpect(p->ph,prev->ph,phoneme_tab[phonPAUSE],2,p,0); + DoSpect(p->ph,prev->ph,phoneme_tab[phonPAUSE],2,p,0); + } + break; + + case phNASAL: + if(!(p->synthflags & SFLAG_SEQCONTINUE)) + { + DoAmplitude(p->amp,NULL); + DoPitch(envelope_data[p->env],p->pitch1,p->pitch2); + } + + if(prev->type==phNASAL) + { + last_frame = NULL; + } + + if(next->type==phVOWEL) + { + StartSyllable(); + DoSpect(p->ph,prev->ph,next->ph,1,p,0); + } + else + if(prev->type==phVOWEL && (p->synthflags & SFLAG_SEQCONTINUE)) + { + DoSpect(p->ph,prev->ph,phoneme_tab[phonPAUSE],2,p,0); + } + else + { + last_frame = NULL; // only for nasal ? + if(next->type == phLIQUID) + DoSpect(p->ph,prev->ph,phoneme_tab[phonSONORANT],2,p,0); + else + DoSpect(p->ph,prev->ph,phoneme_tab[phonPAUSE],2,p,0); + last_frame = NULL; + } + + break; + + case phLIQUID: + modulation = 0; + if(p->ph->phflags & phTRILL) + modulation = 5; + + prev_ph = prev->ph; +// if(p->newword) +// prev_ph = phoneme_tab[phonPAUSE]; // pronounce fully at the start of a word + + if(!(p->synthflags & SFLAG_SEQCONTINUE)) + { + DoAmplitude(p->amp,NULL); + DoPitch(envelope_data[p->env],p->pitch1,p->pitch2); + } + + if(prev->type==phNASAL) + { + last_frame = NULL; + } + + if(next->type==phVOWEL) + { + StartSyllable(); + DoSpect(p->ph,prev_ph,next->ph,1,p,modulation); // (,)r + } + else + if(prev->type==phVOWEL && (p->synthflags & SFLAG_SEQCONTINUE)) + { + DoSpect(p->ph,prev_ph,next->ph,1,p,modulation); + } + else + { + DoSpect(p->ph,prev_ph,next->ph,1,p,modulation); + } + + break; + + case phVOWEL: + ph = p->ph; + stress = p->tone & 0xf; + + // vowel transition from the preceding phoneme + vowel_transition0 = vowel_transition[0]; + vowel_transition1 = vowel_transition[1]; + + pitch_env = envelope_data[p->env]; + amp_env = NULL; + if(p->tone_ph != 0) + { + pitch_env = LookupEnvelope(phoneme_tab[p->tone_ph]->spect); + amp_env = LookupEnvelope(phoneme_tab[p->tone_ph]->after); + } + + StartSyllable(); + + modulation = 2; + if(stress <= 1) + modulation = 1; // 16ths + else + if(stress >= 7) + modulation = 3; + + if(prev->type == phVSTOP || prev->type == phVFRICATIVE) + { + DoAmplitude(p->amp,amp_env); + DoPitch(pitch_env,p->pitch1,p->pitch2); // don't use prevocalic rising tone + DoSpect(ph,prev->ph,next->ph,1,p,modulation); + } + else + if(prev->type==phLIQUID || prev->type==phNASAL) + { + DoAmplitude(p->amp,amp_env); + DoSpect(ph,prev->ph,next->ph,1,p,modulation); // continue with pre-vocalic rising tone + DoPitch(pitch_env,p->pitch1,p->pitch2); + } + else + { + if(!(p->synthflags & SFLAG_SEQCONTINUE)) + { + DoAmplitude(p->amp,amp_env); + DoPitch(pitch_env,p->pitch1,p->pitch2); + } + + DoSpect(ph,prev->ph,next->ph,1,p,modulation); + } + + if(option_phoneme_events) + { + DoMarker(espeakEVENT_PHONEME, sourceix, 0, p->ph->mnemonic); + } + + DoSpect(p->ph,prev->ph,next->ph,2,p,modulation); + + memset(vowel_transition,0,sizeof(vowel_transition)); + break; + } + ix++; + } + EndPitch(1); + if(*n_ph > 0) + { + DoMarker(espeakEVENT_END, count_characters, 0, count_sentences); // end of clause + *n_ph = 0; + } + + return(0); // finished the phoneme list +} // end of Generate + + + + +static int timer_on = 0; +static int paused = 0; + +int SynthOnTimer() +{//=============== + if(!timer_on) + { + return(WavegenCloseSound()); + } + + do { + if(WcmdqUsed() > 0) + WavegenOpenSound(); + + if(Generate(phoneme_list,&n_phoneme_list,1)==0) + { + SpeakNextClause(NULL,NULL,1); + } + } while(skipping_text); + + return(0); +} + + +int SynthStatus() +{//============== + return(timer_on | paused); +} + + + +int SpeakNextClause(FILE *f_in, const void *text_in, int control) +{//============================================================== +// Speak text from file (f_in) or memory (text_in) +// control 0: start +// either f_in or text_in is set, the other must be NULL + +// The other calls have f_in and text_in = NULL +// control 1: speak next text +// 2: stop +// 3: pause (toggle) +// 4: is file being read (0=no, 1=yes) +// 5: interrupt and flush current text. + + int clause_tone; + char *voice_change; + static FILE *f_text=NULL; + static const void *p_text=NULL; + + if(control == 4) + { + if((f_text == NULL) && (p_text == NULL)) + return(0); + else + return(1); + } + + if(control == 2) + { + // stop speaking + timer_on = 0; + p_text = NULL; + if(f_text != NULL) + { + fclose(f_text); + f_text=NULL; + } + n_phoneme_list = 0; + WcmdqStop(); + + embedded_value[EMBED_T] = 0; + return(0); + } + + if(control == 3) + { + // toggle pause + if(paused == 0) + { + timer_on = 0; + paused = 2; + } + else + { + WavegenOpenSound(); + timer_on = 1; + paused = 0; + Generate(phoneme_list,&n_phoneme_list,0); // re-start from beginning of clause + } + return(0); + } + + if(control == 5) + { + // stop speaking, but continue looking for text + n_phoneme_list = 0; + WcmdqStop(); + return(0); + } + + if((f_in != NULL) || (text_in != NULL)) + { + f_text = f_in; + p_text = text_in; + timer_on = 1; + paused = 0; + } + + if((f_text==NULL) && (p_text==NULL)) + { + skipping_text = 0; + timer_on = 0; + return(0); + } + + if((f_text != NULL) && feof(f_text)) + { + timer_on = 0; + fclose(f_text); + f_text=NULL; + return(0); + } + + if(current_phoneme_table != voice->phoneme_tab_ix) + { + SelectPhonemeTable(voice->phoneme_tab_ix); + } + + // read the next clause from the input text file, translate it, and generate + // entries in the wavegen command queue + p_text = TranslateClause(translator, f_text, p_text, &clause_tone, &voice_change); + + CalcPitches(translator, clause_tone); + CalcLengths(translator); + + GetTranslatedPhonemeString(translator->phon_out,sizeof(translator->phon_out)); + if(option_phonemes > 0) + { + fprintf(f_trans,"%s\n",translator->phon_out); + + if(!iswalpha(0x010d)) + { + // check that c-caron is recognized as an alphabetic character + fprintf(stderr,"Warning: Accented letters are not recognized, eg: U+010D\nSet LC_CTYPE to a UTF-8 locale\n"); + } + } + if(phoneme_callback != NULL) + { + phoneme_callback(translator->phon_out); + } + + + if(skipping_text) + { + n_phoneme_list = 0; + return(1); + } + + if(mbrola_name[0] != 0) + { +#ifdef USE_MBROLA_LIB + MbrolaTranslate(phoneme_list,n_phoneme_list,NULL); +#else + { + FILE *f_mbrola; + if((f_mbrola = f_trans) == stderr) + f_mbrola = stdout; + MbrolaTranslate(phoneme_list,n_phoneme_list,f_mbrola); + } +#endif + } + + Generate(phoneme_list,&n_phoneme_list,0); + WavegenOpenSound(); + + if(voice_change != NULL) + { + // voice change at the end of the clause (i.e. clause was terminated by a voice change) + new_voice = LoadVoiceVariant(voice_change,0); // add a Voice instruction to wavegen at the end of the clause + } + + if(new_voice) + { + // finished the current clause, now change the voice if there was an embedded + // change voice command at the end of it (i.e. clause was broken at the change voice command) + DoVoiceChange(voice); + new_voice = NULL; + } + + return(1); +} // end of SpeakNextClause + diff --git a/Plugins/eSpeak/eSpeak/synthesize.h b/Plugins/eSpeak/eSpeak/synthesize.h new file mode 100644 index 0000000..50710f8 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/synthesize.h @@ -0,0 +1,349 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + + +#define N_PHONEME_LIST 1000 // enough for source[N_TR_SOURCE] full of text, else it will truncate + +#define MAX_HARMONIC 400 // 400 * 50Hz = 20 kHz, more than enough +#define N_SEQ_FRAMES 25 // max frames in a spectrum sequence (real max is ablut 8) +#define STEPSIZE 64 // 2.9mS at 22 kHz sample rate + +#define PITCHfall 0 +#define PITCHrise 1 + +// flags set for frames within a spectrum sequence +#define FRFLAG_KLATT 0x01 // this frame includes extra data for Klatt synthesizer +#define FRFLAG_VOWEL_CENTRE 0x02 // centre point of vowel +#define FRFLAG_LEN_MOD 0x04 // reduce effect of length adjustment +#define FRFLAG_BREAK_LF 0x08 // but keep f3 upwards +#define FRFLAG_BREAK 0x10 // don't merge with next frame +#define FRFLAG_BREAK_2 0x18 // FRFLAG_BREAK_LF or FRFLAG_BREAK +#define FRFLAG_FORMANT_RATE 0x20 // Flag5 allow increased rate of change of formant freq +#define FRFLAG_MODULATE 0x40 // Flag6 modulate amplitude of some cycles to give trill +#define FRFLAG_DEFER_WAV 0x80 // Flag7 defer mixing WAV until the next frame +#define FRFLAG_COPIED 0x8000 // This frame has been copied into temporary rw memory + +#define SFLAG_SEQCONTINUE 0x01 // a liquid or nasal after a vowel, but not followed by a vowel +#define SFLAG_EMBEDDED 0x02 // there are embedded commands before this phoneme +#define SFLAG_SYLLABLE 0x04 // vowel or syllabic consonant +#define SFLAG_LENGTHEN 0x08 // lengthen symbol : included after this phoneme +#define SFLAG_DICTIONARY 0x10 // the pronunciation of this word was listed in the xx_list dictionary +#define SFLAG_SWITCHED_LANG 0x20 // this word uses phonemes from a different language +#define SFLAG_PROMOTE_STRESS 0x40 // this unstressed word can be promoted to stressed + +// embedded command numbers +#define EMBED_P 1 // pitch +#define EMBED_S 2 // speed (used in setlengths) +#define EMBED_A 3 // amplitude/volume +#define EMBED_R 4 // pitch range/expression +#define EMBED_H 5 // echo/reverberation +#define EMBED_T 6 // different tone for announcing punctuation +#define EMBED_I 7 // sound icon +#define EMBED_S2 8 // speed (used in synthesize) +#define EMBED_Y 9 // say-as commands +#define EMBED_M 10 // mark name +#define EMBED_U 11 // audio uri +#define EMBED_B 12 // break +#define EMBED_F 13 // emphasis + +#define N_EMBEDDED_VALUES 14 +extern int embedded_value[N_EMBEDDED_VALUES]; +extern int embedded_default[N_EMBEDDED_VALUES]; + + +#define N_PEAKS 9 +#define N_MARKERS 8 + +typedef struct { + short pkfreq; + short pkheight; + short pkwidth; + short pkright; +} peak_t; + +#define N_KLATTP 10 // this affects the phoneme data file format +#define KLATT_AV 0 +#define KLATT_Kopen 1 +#define KLATT_Skew 2 +#define KLATT_Tilt 3 +#define KLATT_Turb 4 +#define KLATT_Aspr 5 +#define KLATT_AVp 6 // this is after the parameters which can be change by the Voice +#define KLATT_Fric 7 +#define KLATT_FricBP 8 +#define KLATT_spare1 9 + +typedef struct { + short frflags; + unsigned char length; + unsigned char rms; + short ffreq[9]; + unsigned char fheight[9]; + unsigned char fwidth[6]; // width/4 + unsigned char fright[6]; // width/4 + unsigned char fwidth6, fright6; + unsigned char klattp[N_KLATTP]; +} frame_t; + +typedef struct { + short frflags; + unsigned char length; + unsigned char rms; + short ffreq[9]; + unsigned char fheight[9]; + unsigned char fwidth[6]; // width/4 + unsigned char fright[6]; // width/4 +} frame_t2; // the original, without Klatt additions, used for file "phondata" + + + +// formant data used by wavegen +typedef struct { + int freq; // Hz<<16 + int height; // height<<15 + int left; // Hz<<16 + int right; // Hz<<16 + DOUBLEX freq1; // floating point versions of the above + DOUBLEX height1; + DOUBLEX left1; + DOUBLEX right1; + DOUBLEX freq_inc; // increment by this every 64 samples + DOUBLEX height_inc; + DOUBLEX left_inc; + DOUBLEX right_inc; +} wavegen_peaks_t; + +typedef struct { +unsigned char *pitch_env; +int pitch; // pitch Hz*256 +int pitch_ix; // index into pitch envelope (*256) +int pitch_inc; // increment to pitch_ix +int pitch_base; // Hz*256 low, before modified by envelope +int pitch_range; // Hz*256 range of envelope + +unsigned char *mix_wavefile; // wave file to be added to synthesis +int n_mix_wavefile; // length in bytes +int mix_wave_scale; // 0=2 byte samples +int mix_wave_amp; +int mix_wavefile_ix; + +int amplitude; +int amplitude_v; +int prev_was_synth; // previous sound was synthesized (not a played wave or pause) +} WGEN_DATA; + + +typedef struct { + double a; + double b; + double c; + double x1; + double x2; +} RESONATOR; + + +typedef struct { + short length_total; // not used + unsigned char n_frames; + unsigned char flags; + frame_t2 frame[N_SEQ_FRAMES]; // max. frames in a spectrum sequence +} SPECT_SEQ; // sequence of espeak formant frames + +typedef struct { + short length_total; // not used + unsigned char n_frames; + unsigned char flags; + frame_t frame[N_SEQ_FRAMES]; // max. frames in a spectrum sequence +} SPECT_SEQK; // sequence of klatt formants frames + + +typedef struct { + short length; + short frflags; + frame_t *frame; +} frameref_t; + + +typedef struct { + PHONEME_TAB *ph; + unsigned char env; // pitch envelope number + unsigned char tone; + unsigned char type; + unsigned char prepause; + unsigned char amp; + unsigned char tone_ph; // tone phoneme to use with this vowel + unsigned char newword; // bit 0=start of word, bit 1=end of clause, bit 2=start of sentence + unsigned char synthflags; + short length; // length_mod + short pitch1; // pitch, 0-4095 within the Voice's pitch range + short pitch2; + unsigned short sourceix; // ix into the original source text string, only set at the start of a word +} PHONEME_LIST; + + +typedef struct { + int name; + int length; + char *data; + char *filename; +} SOUND_ICON; + +typedef struct { + int name; + unsigned int next_phoneme; + int mbr_name; + int mbr_name2; + int percent; // percentage length of first component + int control; +} MBROLA_TAB; + +typedef struct { + int speed_factor1; + int speed_factor2; + int speed_factor3; + int min_sample_len; + int fast_settings[8]; +} SPEED_FACTORS; + + +// phoneme table +extern PHONEME_TAB *phoneme_tab[N_PHONEME_TAB]; + +// list of phonemes in a clause +extern int n_phoneme_list; +extern PHONEME_LIST phoneme_list[N_PHONEME_LIST]; +extern unsigned int embedded_list[]; + +extern unsigned char env_fall[128]; +extern unsigned char env_rise[128]; +extern unsigned char env_frise[128]; + +#define MAX_PITCH_VALUE 101 +extern unsigned char pitch_adjust_tab[MAX_PITCH_VALUE+1]; + +// queue of commands for wavegen +#define WCMD_KLATT 1 +#define WCMD_KLATT2 2 +#define WCMD_SPECT 3 +#define WCMD_SPECT2 4 +#define WCMD_PAUSE 5 +#define WCMD_WAVE 6 +#define WCMD_WAVE2 7 +#define WCMD_AMPLITUDE 8 +#define WCMD_PITCH 9 +#define WCMD_MARKER 10 +#define WCMD_VOICE 11 +#define WCMD_EMBEDDED 12 + + +#define N_WCMDQ 160 +#define MIN_WCMDQ 22 // need this many free entries before adding new phoneme + +extern long wcmdq[N_WCMDQ][4]; +extern int wcmdq_head; +extern int wcmdq_tail; + +// from Wavegen file +int WcmdqFree(); +void WcmdqStop(); +int WcmdqUsed(); +void WcmdqInc(); +int WavegenOpenSound(); +int WavegenCloseSound(); +int WavegenInitSound(); +void WavegenInit(int rate, int wavemult_fact); +float polint(float xa[],float ya[],int n,float x); +int WavegenFill(int fill_zeros); +void MarkerEvent(int type, unsigned int char_position, int value, unsigned char *out_ptr); + + +extern unsigned char *wavefile_data; +extern int samplerate; +extern int samplerate_native; + +extern int wavefile_ix; +extern int wavefile_amp; +extern int wavefile_ix2; +extern int wavefile_amp2; +extern int vowel_transition[4]; +extern int vowel_transition0, vowel_transition1; + +extern int mbrola_delay; +extern char mbrola_name[20]; + +// from synthdata file +unsigned int LookupSound(PHONEME_TAB *ph1, PHONEME_TAB *ph2, int which, int *match_level, int control); +frameref_t *LookupSpect(PHONEME_TAB *ph1, PHONEME_TAB *prev_ph, PHONEME_TAB *next_ph, int which, int *match_level, int *n_frames, PHONEME_LIST *plist); + +unsigned char *LookupEnvelope(int ix); +int LoadPhData(); + +void SynthesizeInit(void); +int Generate(PHONEME_LIST *phoneme_list, int *n_ph, int resume); +void MakeWave2(PHONEME_LIST *p, int n_ph); +int SynthOnTimer(void); +int SpeakNextClause(FILE *f_text, const void *text_in, int control); +int SynthStatus(void); +void SetSpeed(int control); +void SetEmbedded(int control, int value); +void SelectPhonemeTable(int number); +int SelectPhonemeTableName(const char *name); + +void Write4Bytes(FILE *f, int value); +int Read4Bytes(FILE *f); +int CompileDictionary(const char *dsource, const char *dict_name, FILE *log, char *err_name,int flags); + + +extern unsigned char *envelope_data[18]; +extern int formant_rate[]; // max rate of change of each formant +extern SPEED_FACTORS speed; + +extern long count_samples; +extern int outbuf_size; +extern unsigned char *out_ptr; +extern unsigned char *out_start; +extern unsigned char *out_end; +extern int event_list_ix; +extern espeak_EVENT *event_list; +extern t_espeak_callback* synth_callback; +extern int option_log_frames; +extern const char *version_string; +extern const int version_phdata; + +#define N_SOUNDICON_TAB 80 // total entries in soundicon_tab +#define N_SOUNDICON_SLOTS 4 // number of slots reserved for dynamic loading of audio files +extern int n_soundicon_tab; +extern SOUND_ICON soundicon_tab[N_SOUNDICON_TAB]; + +espeak_ERROR SetVoiceByName(const char *name); +espeak_ERROR SetVoiceByProperties(espeak_VOICE *voice_selector); +espeak_ERROR LoadMbrolaTable(const char *mbrola_voice, const char *phtrans, int srate); +void SetParameter(int parameter, int value, int relative); +void MbrolaTranslate(PHONEME_LIST *plist, int n_phonemes, FILE *f_mbrola); +//int MbrolaSynth(char *p_mbrola); +int DoSample(PHONEME_TAB *ph1, PHONEME_TAB *ph2, int which, int length_mod, int amp); +int DoSpect(PHONEME_TAB *this_ph, PHONEME_TAB *prev_ph, PHONEME_TAB *next_ph, + int which, PHONEME_LIST *plist, int modulation); +int PauseLength(int pause, int control); +int LookupPhonemeTable(const char *name); + +void InitBreath(void); + +void KlattInit(); +int Wavegen_Klatt2(int length, int modulation, int resume, frame_t *fr1, frame_t *fr2); diff --git a/Plugins/eSpeak/eSpeak/tr_languages.cpp b/Plugins/eSpeak/eSpeak/tr_languages.cpp new file mode 100644 index 0000000..0d8776a --- /dev/null +++ b/Plugins/eSpeak/eSpeak/tr_languages.cpp @@ -0,0 +1,1310 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "StdAfx.h" + +#include <stdio.h> +#include <ctype.h> +#include <wctype.h> +#include <stdlib.h> +#include <string.h> +#include <locale.h> + +#include <wctype.h> + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "translate.h" + + + +#define L_qa 0x716100 +#define L_grc 0x677263 // grc Ancient Greek +#define L_jbo 0x6a626f // jbo Lojban +#define L_zhy 0x7a6879 // zhy + +// start of unicode pages for character sets +#define OFFSET_GREEK 0x380 +#define OFFSET_CYRILLIC 0x420 +#define OFFSET_ARMENIAN 0x530 +#define OFFSET_DEVANAGARI 0x900 +#define OFFSET_BENGALI 0x980 +#define OFFSET_TAMIL 0xb80 +#define OFFSET_KANNADA 0xc80 +#define OFFSET_MALAYALAM 0xd00 +#define OFFSET_KOREAN 0x1100 + +static void Translator_Russian(Translator *tr); + +static void SetLetterVowel(Translator *tr, int c) +{//============================================== + tr->letter_bits[c] = (tr->letter_bits[c] & 0x40) | 0x81; // keep value for group 6 (front vowels e,i,y) +} + +static void ResetLetterBits(Translator *tr, int groups) +{//==================================================== +// Clear all the specified groups + unsigned int ix; + unsigned int mask; + + mask = ~groups; + + for(ix=0; ix<sizeof(tr->letter_bits); ix++) + { + tr->letter_bits[ix] &= mask; + } +} + +static void SetLetterBits(Translator *tr, int group, const char *string) +{//===================================================================== + int bits; + unsigned char c; + + bits = (1L << group); + while((c = *string++) != 0) + tr->letter_bits[c] |= bits; +} + +static void SetLetterBitsRange(Translator *tr, int group, int first, int last) +{//=========================================================================== + int bits; + int ix; + + bits = (1L << group); + for(ix=first; ix<=last; ix++) + { + tr->letter_bits[ix] |= bits; + } +} + + +static Translator* NewTranslator(void) +{//=================================== + Translator *tr; + int ix; + static const unsigned char stress_amps2[] = {17,17, 20,20, 20,22, 22,20 }; + static const short stress_lengths2[8] = {182,140, 220,220, 220,240, 260,280}; + static const wchar_t empty_wstring[1] = {0}; + static const wchar_t punct_in_word[2] = {'\'', 0}; // allow hyphen within words + + tr = (Translator *)Alloc(sizeof(Translator)); + if(tr == NULL) + return(NULL); + + tr->charset_a0 = charsets[1]; // ISO-8859-1, this is for when the input is not utf8 + dictionary_name[0] = 0; + tr->dict_condition=0; + tr->data_dictrules = NULL; // language_1 translation rules file + tr->data_dictlist = NULL; // language_2 dictionary lookup file + + tr->transpose_offset = 0; + + // only need lower case + tr->letter_bits_offset = 0; + memset(tr->letter_bits,0,sizeof(tr->letter_bits)); + memset(tr->letter_groups,0,sizeof(tr->letter_groups)); + + // 0-5 sets of characters matched by A B C E F G in pronunciation rules + // these may be set differently for different languages + SetLetterBits(tr,0,"aeiou"); // A vowels, except y + SetLetterBits(tr,1,"bcdfgjklmnpqstvxz"); // B hard consonants, excluding h,r,w + SetLetterBits(tr,2,"bcdfghjklmnpqrstvwxz"); // C all consonants + SetLetterBits(tr,3,"hlmnr"); // H 'soft' consonants + SetLetterBits(tr,4,"cfhkpqstx"); // F voiceless consonants + SetLetterBits(tr,5,"bdgjlmnrvwyz"); // G voiced + SetLetterBits(tr,6,"eiy"); // Letter group Y, front vowels + SetLetterBits(tr,7,"aeiouy"); // vowels, including y + + + tr->char_plus_apostrophe = empty_wstring; + tr->punct_within_word = punct_in_word; + + for(ix=0; ix<8; ix++) + { + tr->stress_amps[ix] = stress_amps2[ix]; + tr->stress_amps_r[ix] = stress_amps2[ix] - 1; + tr->stress_lengths[ix] = stress_lengths2[ix]; + } + memset(&(tr->langopts),0,sizeof(tr->langopts)); + + tr->langopts.stress_rule = 2; + tr->langopts.unstressed_wd1 = 1; + tr->langopts.unstressed_wd2 = 3; + tr->langopts.param[LOPT_SONORANT_MIN] = 95; + tr->langopts.param[LOPT_MAXAMP_EOC] = 19; + tr->langopts.param[LOPT_UNPRONOUNCABLE] = 's'; // don't count this character at start of word + tr->langopts.max_initial_consonants = 3; + tr->langopts.replace_chars = NULL; + tr->langopts.ascii_language = ""; // Non-Latin alphabet languages, use this language to speak Latin words, default is English + + SetLengthMods(tr,201); +// tr->langopts.length_mods = length_mods_en; +// tr->langopts.length_mods0 = length_mods_en0; + + tr->langopts.long_stop = 100; + + tr->langopts.max_roman = 49; + tr->langopts.thousands_sep = ','; + tr->langopts.decimal_sep = '.'; + + memcpy(tr->punct_to_tone, punctuation_to_tone, sizeof(tr->punct_to_tone)); + + return(tr); +} + + +static const unsigned int replace_cyrillic_latin[] = + {0x430,'a', + 0x431,'b', + 0x446,'c', + 0x45b,0x107, + 0x447,0x10d, + 0x45f,'d'+(0x17e<<16), + 0x455,'d'+('z'<<16), + 0x434,'d', + 0x452,0x111, + 0x435,'e', + 0x444,'f', + 0x433,'g', + 0x445,'h', + 0x438,'i', + 0x458,'j', + 0x43a,'k', + 0x459,'l'+('j'<<16), + 0x43b,'l', + 0x43c,'m', + 0x45a,'n'+('j'<<16), + 0x43d,'n', + 0x43e,'o', + 0x43f,'p', + 0x440,'r', + 0x441,'s', + 0x448,0x161, + 0x442,'t', + 0x443,'u', + 0x432,'v', + 0x437,'z', + 0x436,0x17e, + 0x453,0x111, + 0x45c,0x107, +0}; // Ñ“ Ñ• Ñœ + + +void SetIndicLetters(Translator *tr) +{//================================= + // Set letter types for Indic scripts, Devanagari, Tamill, etc + static const char dev_consonants2[] = {0x02,0x03,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f}; + + memset(tr->letter_bits,0,sizeof(tr->letter_bits)); + SetLetterBitsRange(tr,LETTERGP_A,0x04,0x14); // vowel letters only + SetLetterBitsRange(tr,LETTERGP_B,0x3e,0x4d); // vowel signs, and virama + + SetLetterBitsRange(tr,LETTERGP_C,0x15,0x39); // the main consonant range + SetLetterBits(tr,LETTERGP_C,dev_consonants2); // + additional consonants + + SetLetterBitsRange(tr,LETTERGP_Y,0x04,0x14); // vowel letters + SetLetterBitsRange(tr,LETTERGP_Y,0x3e,0x4c); // + vowel signs + + tr->langopts.param[LOPT_UNPRONOUNCABLE] = 1; // disable check for unpronouncable words +} + +void SetupTranslator(Translator *tr, const short *lengths, const unsigned char *amps) +{//================================================================================== + if(lengths != NULL) + memcpy(tr->stress_lengths,lengths,sizeof(tr->stress_lengths)); + if(amps != NULL) + memcpy(tr->stress_amps,amps,sizeof(tr->stress_amps)); +} + + +Translator *SelectTranslator(const char *name) +{//=========================================== + int name2 = 0; + Translator *tr; + + static const unsigned char stress_amps_sk[8] = {17,17, 20,20, 20,22, 22,21 }; + static const short stress_lengths_sk[8] = {190,190, 210,210, 0,0, 210,210}; + + // convert name string into a word of up to 4 characters, for the switch() + while(*name != 0) + name2 = (name2 << 8) + *name++; + + tr = NewTranslator(); + + switch(name2) + { + case L('a','f'): + { + static const short stress_lengths_af[8] = {170,140, 220,220, 0, 0, 250,270}; + SetupTranslator(tr,stress_lengths_af,NULL); + + tr->langopts.stress_rule = 0; + tr->langopts.vowel_pause = 0x30; + tr->langopts.param[LOPT_DIERESES] = 1; + tr->langopts.param[LOPT_PREFIXES] = 1; + SetLetterVowel(tr,'y'); // add 'y' to vowels + + tr->langopts.numbers = 0x8d1 + NUM_ROMAN; + tr->langopts.accents = 1; + } + break; + + case L('b','n'): // Bengali + { + static const short stress_lengths_bn[8] = {180, 180, 210, 210, 0, 0, 230, 240}; + static const unsigned char stress_amps_bn[8] = {18,18, 18,18, 20,20, 22,22 }; + + SetupTranslator(tr,stress_lengths_bn,stress_amps_bn); + tr->langopts.length_mods0 = tr->langopts.length_mods; // don't lengthen vowels in the last syllable + + tr->langopts.stress_rule = 0; + tr->langopts.stress_flags = 0x10004; // use 'diminished' for unstressed final syllable + tr->letter_bits_offset = OFFSET_BENGALI; + SetIndicLetters(tr); // call this after setting OFFSET_BENGALI + SetLetterBitsRange(tr,LETTERGP_F,0x3e,0x4c); // vowel signs, but not virama + + tr->langopts.numbers = 0x1; + tr->langopts.numbers2 = NUM2_100000; + } + break; + + case L('c','y'): // Welsh + { + static const short stress_lengths_cy[8] = {170,220, 180,180, 0, 0, 250,270}; + static const unsigned char stress_amps_cy[8] = {17,15, 18,18, 0,0, 22,20 }; // 'diminished' is used to mark a quieter, final unstressed syllable + + SetupTranslator(tr,stress_lengths_cy,stress_amps_cy); + + tr->charset_a0 = charsets[14]; // ISO-8859-14 +// tr->langopts.length_mods0 = tr->langopts.length_mods; // don't lengthen vowels in the last syllable + tr->langopts.stress_rule = 2; +// tr->langopts.intonation_group = 4; + + // 'diminished' is an unstressed final syllable + tr->langopts.stress_flags = 0x6 | 0x10; + tr->langopts.unstressed_wd1 = 0; + tr->langopts.unstressed_wd2 = 2; + tr->langopts.param[LOPT_SONORANT_MIN] = 120; // limit the shortening of sonorants before short vowels + + tr->langopts.numbers = 0x401; + + SetLetterVowel(tr,'w'); // add letter to vowels and remove from consonants + SetLetterVowel(tr,'y'); + } + break; + + case L('d','a'): // Danish + { + static const short stress_lengths_da[8] = {160,140, 200,200, 0,0, 220,210}; + SetupTranslator(tr,stress_lengths_da,NULL); + + tr->langopts.stress_rule = 0; + SetLetterVowel(tr,'y'); +// tr->langopts.numbers = 0x11849; + } + break; + + + case L('d','e'): + { + static const short stress_lengths_de[8] = {150,130, 190,190, 0, 0, 260,275}; + tr->langopts.stress_rule = 0; + tr->langopts.word_gap = 0x8; // don't use linking phonemes + tr->langopts.vowel_pause = 0x30; + tr->langopts.param[LOPT_PREFIXES] = 1; + memcpy(tr->stress_lengths,stress_lengths_de,sizeof(tr->stress_lengths)); + + tr->langopts.numbers = 0x11419 + NUM_ROMAN; + SetLetterVowel(tr,'y'); + } + break; + + case L('e','n'): + { + static const short stress_lengths_en[8] = {182,140, 220,220, 0,0, 248,275}; + SetupTranslator(tr,stress_lengths_en,NULL); + + tr->langopts.stress_rule = 0; + tr->langopts.numbers = 0x841 + NUM_ROMAN; + tr->langopts.param[LOPT_COMBINE_WORDS] = 2; // allow "mc" to cmbine with the following word + } + break; + + case L('e','l'): // Greek + case L_grc: // Ancient Greek + { + static const short stress_lengths_el[8] = {155, 180, 210, 210, 0, 0, 270, 300}; + static const unsigned char stress_amps_el[8] = {15,12, 20,20, 20,22, 22,21 }; // 'diminished' is used to mark a quieter, final unstressed syllable + + // character codes offset by 0x380 + static const char el_vowels[] = {0x10,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x35,0x37,0x39,0x3f,0x45,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,0}; + static const char el_fvowels[] = {0x2d,0x2e,0x2f,0x35,0x37,0x39,0x45,0x4d,0}; // ε η ι Ï… Πή ί Ï + static const char el_voiceless[]= {0x38,0x3a,0x3e,0x40,0x42,0x43,0x44,0x46,0x47,0}; // θ κ ξ Ï€ Ï‚ σ Ï„ φ χ + static const char el_consonants[]={0x32,0x33,0x34,0x36,0x38,0x3a,0x3b,0x3c,0x3d,0x3e,0x40,0x41,0x42,0x43,0x44,0x46,0x47,0x48,0}; + static const wchar_t el_char_apostrophe[] = {0x3c3,0}; // σ + + SetupTranslator(tr,stress_lengths_el,stress_amps_el); + + tr->charset_a0 = charsets[7]; // ISO-8859-7 + tr->char_plus_apostrophe = el_char_apostrophe; + + tr->letter_bits_offset = OFFSET_GREEK; + memset(tr->letter_bits,0,sizeof(tr->letter_bits)); + SetLetterBits(tr,LETTERGP_A,el_vowels); + SetLetterBits(tr,LETTERGP_B,el_voiceless); + SetLetterBits(tr,LETTERGP_C,el_consonants); + SetLetterBits(tr,LETTERGP_Y,el_fvowels); // front vowels: ε η ι Ï… + + tr->langopts.length_mods0 = tr->langopts.length_mods; // don't lengthen vowels in the last syllable + tr->langopts.stress_rule = 2; + tr->langopts.stress_flags = 0x6; // mark unstressed final syllables as diminished + tr->langopts.unstressed_wd1 = 0; + tr->langopts.unstressed_wd2 = 2; + tr->langopts.param[LOPT_SONORANT_MIN] = 130; // limit the shortening of sonorants before short vowels + + tr->langopts.numbers = 0x309; + tr->langopts.numbers2 = 0x2; // variant form of numbers before thousands + + if(name2 == L_grc) + { + // ancient greek + tr->langopts.param[LOPT_UNPRONOUNCABLE] = 1; + } + } + break; + + case L('e','o'): + { + static const short stress_lengths_eo[8] = {145, 145, 230, 170, 0, 0, 360, 370}; + static const unsigned char stress_amps_eo[] = {16,14, 20,20, 20,22, 22,21 }; + static const wchar_t eo_char_apostrophe[2] = {'l',0}; + + SetupTranslator(tr,stress_lengths_eo,stress_amps_eo); + + tr->charset_a0 = charsets[3]; // ISO-8859-3 + tr->char_plus_apostrophe = eo_char_apostrophe; + + tr->langopts.word_gap = 1; + tr->langopts.vowel_pause = 2; + tr->langopts.stress_rule = 2; + tr->langopts.stress_flags = 0x6 | 0x10; + tr->langopts.unstressed_wd1 = 3; + tr->langopts.unstressed_wd2 = 2; + + tr->langopts.numbers = 0x1409 + NUM_ROMAN; + } + break; + + case L('e','s'): // Spanish + case L('c','a'): // Catalan + { + static const short stress_lengths_es[8] = {180, 210, 190, 190, 0, 0, 230, 260}; +// static const short stress_lengths_es[8] = {170, 200, 180, 180, 0, 0, 220, 250}; + static const unsigned char stress_amps_es[8] = {16,12, 18,18, 20,20, 20,20 }; // 'diminished' is used to mark a quieter, final unstressed syllable + static const wchar_t ca_punct_within_word[] = {'\'',0xb7,0}; // ca: allow middle-dot within word + + SetupTranslator(tr,stress_lengths_es,stress_amps_es); + + tr->langopts.length_mods0 = tr->langopts.length_mods; // don't lengthen vowels in the last syllable + tr->langopts.stress_rule = 2; + + // stress last syllable if it doesn't end in vowel or "s" or "n" + // 'diminished' is an unstressed final syllable + tr->langopts.stress_flags = 0x200 | 0x6 | 0x10; + tr->langopts.unstressed_wd1 = 0; + tr->langopts.unstressed_wd2 = 2; + tr->langopts.param[LOPT_SONORANT_MIN] = 120; // limit the shortening of sonorants before short vowels + + tr->langopts.numbers = 0x529 + NUM_ROMAN + NUM_ROMAN_AFTER; + + if(name2 == L('c','a')) + { + tr->punct_within_word = ca_punct_within_word; + tr->langopts.stress_flags = 0x200 | 0x6 | 0x30; // stress last syllable unless word ends with a vowel + } + } + break; + + + case L('f','i'): // Finnish + { + static const unsigned char stress_amps_fi[8] = {18,16, 22,22, 20,22, 22,22 }; + static const short stress_lengths_fi[8] = {150,180, 200,200, 0,0, 210,250}; + + SetupTranslator(tr,stress_lengths_fi,stress_amps_fi); + + tr->langopts.stress_rule = 0; + tr->langopts.stress_flags = 0x56; // move secondary stress from light to a following heavy syllable + tr->langopts.param[LOPT_IT_DOUBLING] = 1; + tr->langopts.long_stop = 130; + + tr->langopts.numbers = 0x1809; + SetLetterVowel(tr,'y'); + tr->langopts.max_initial_consonants = 2; + tr->langopts.spelling_stress = 1; + tr->langopts.intonation_group = 3; // less intonation, don't raise pitch at comma + } + break; + + case L('f','r'): // french + { + static const short stress_lengths_fr[8] = {190, 170, 190, 200, 0, 0, 235, 240}; + static const unsigned char stress_amps_fr[8] = {18,16, 20,20, 20,22, 22,21 }; + + SetupTranslator(tr,stress_lengths_fr,stress_amps_fr); + tr->langopts.stress_rule = 3; // stress on final syllable + tr->langopts.stress_flags = 0x0024; // don't use secondary stress + tr->langopts.param[LOPT_IT_LENGTHEN] = 1; // remove lengthen indicator from unstressed syllables + + tr->langopts.numbers = 0x1509 + 0x8000 + NUM_NOPAUSE | NUM_ROMAN; + SetLetterVowel(tr,'y'); + } + break; + +#ifdef deleted + case L('g','a'): // Irish Gaelic + { + tr->langopts.stress_rule = 1; + } + break; +#endif + + case L('h','i'): // Hindi + case L('n','e'): // Nepali + { + static const short stress_lengths_hi[8] = {190, 190, 210, 210, 0, 0, 230, 250}; + static const unsigned char stress_amps_hi[8] = {17,14, 20,19, 20,22, 22,21 }; + + SetupTranslator(tr,stress_lengths_hi,stress_amps_hi); + tr->charset_a0 = charsets[19]; // ISCII + tr->langopts.length_mods0 = tr->langopts.length_mods; // don't lengthen vowels in the last syllable + + tr->langopts.stress_rule = 6; // stress on last heaviest syllable, excluding final syllable + tr->langopts.stress_flags = 0x10004; // use 'diminished' for unstressed final syllable + tr->langopts.numbers = 0x011; + tr->langopts.numbers2 = NUM2_100000; + tr->letter_bits_offset = OFFSET_DEVANAGARI; + SetIndicLetters(tr); + } + break; + + + case L('h','r'): // Croatian + case L('b','s'): // Bosnian + case L('s','r'): // Serbian + { + static const unsigned char stress_amps_hr[8] = {17,17, 20,20, 20,22, 22,21 }; + static const short stress_lengths_hr[8] = {180,160, 200,200, 0,0, 220,230}; + static const short stress_lengths_sr[8] = {160,150, 200,200, 0,0, 250,260}; + + if(name2 == L('s','r')) + SetupTranslator(tr,stress_lengths_sr,stress_amps_hr); + else + SetupTranslator(tr,stress_lengths_hr,stress_amps_hr); + tr->charset_a0 = charsets[2]; // ISO-8859-2 + + tr->langopts.stress_rule = 0; + tr->langopts.stress_flags = 0x10; + tr->langopts.param[LOPT_REGRESSIVE_VOICING] = 0x3; + tr->langopts.max_initial_consonants = 5; + tr->langopts.spelling_stress = 1; + tr->langopts.accents = 1; + + tr->langopts.numbers = 0x140d + 0x4000 + NUM_ROMAN_UC; + tr->langopts.numbers2 = 0x4a; // variant numbers before thousands,milliards + tr->langopts.replace_chars = replace_cyrillic_latin; + + SetLetterVowel(tr,'y'); + SetLetterVowel(tr,'r'); + } + break; + + + case L('h','u'): // Hungarian + { + static const unsigned char stress_amps_hu[8] = {17,17, 19,19, 20,22, 22,21 }; + static const short stress_lengths_hu[8] = {185,195, 195,190, 0,0, 210,220}; + + SetupTranslator(tr,stress_lengths_hu,stress_amps_hu); + tr->charset_a0 = charsets[2]; // ISO-8859-2 + + tr->langopts.vowel_pause = 0x20; + tr->langopts.stress_rule = 0; + tr->langopts.stress_flags = 0x8036; + tr->langopts.unstressed_wd1 = 2; +// tr->langopts.param[LOPT_REGRESSIVE_VOICING] = 0x4; // don't propagate over word boundaries + tr->langopts.param[LOPT_IT_DOUBLING] = 1; + tr->langopts.param[LOPT_COMBINE_WORDS] = 99; // combine some prepositions with the following word + + tr->langopts.numbers = 0x1009 + NUM_ROMAN; + SetLetterVowel(tr,'y'); + tr->langopts.spelling_stress = 1; +SetLengthMods(tr,3); // all equal + } + break; + + case L('h','y'): // Armenian + { + static const short stress_lengths_hy[8] = {250, 200, 250, 250, 0, 0, 250, 250}; + static const char hy_vowels[] = {0x31, 0x35, 0x37, 0x38, 0x3b, 0x48, 0x55, 0}; + static const char hy_consonants[] = {0x32,0x33,0x34,0x36,0x39,0x3a,0x3c,0x3d,0x3e,0x3f, + 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,0x50,0x51,0x52,0x53,0x54,0x56,0}; + + SetupTranslator(tr,stress_lengths_hy,NULL); + tr->langopts.stress_rule = 3; // default stress on final syllable + + tr->letter_bits_offset = OFFSET_ARMENIAN; + memset(tr->letter_bits,0,sizeof(tr->letter_bits)); + SetLetterBits(tr,LETTERGP_A,hy_vowels); + SetLetterBits(tr,LETTERGP_C,hy_consonants); + tr->langopts.max_initial_consonants = 6; + tr->langopts.numbers = 0x409; +// tr->langopts.param[LOPT_UNPRONOUNCABLE] = 1; // disable check for unpronouncable words + } + break; + + case L('i','d'): // Indonesian + { + static const short stress_lengths_id[8] = {160, 200, 180, 180, 0, 0, 220, 240}; + static const unsigned char stress_amps_id[8] = {16,18, 18,18, 20,22, 22,21 }; + + SetupTranslator(tr,stress_lengths_id,stress_amps_id); + tr->langopts.stress_rule = 2; + tr->langopts.numbers = 0x1009 + NUM_ROMAN; + tr->langopts.stress_flags = 0x6 | 0x10; + tr->langopts.accents = 2; // "capital" after letter name + } + break; + + case L('i','s'): // Icelandic + { + static const short stress_lengths_is[8] = {180,160, 200,200, 0,0, 240,250}; + static const wchar_t is_lettergroup_B[] = {'c','f','h','k','p','t','x',0xfe,0}; // voiceless conants, including 'þ' ?? 's' + + SetupTranslator(tr,stress_lengths_is,NULL); + tr->langopts.stress_rule = 0; + tr->langopts.stress_flags = 0x10; + tr->langopts.param[LOPT_IT_LENGTHEN] = 0x11; // remove lengthen indicator from unstressed vowels + tr->langopts.param[LOPT_REDUCE] = 2; + + ResetLetterBits(tr,0x18); + SetLetterBits(tr,4,"kpst"); // Letter group F + SetLetterBits(tr,3,"jvr"); // Letter group H + tr->letter_groups[1] = is_lettergroup_B; + SetLetterVowel(tr,'y'); + tr->langopts.numbers = 0x8e9; + tr->langopts.numbers2 = 0x2; + } + break; + + case L('i','t'): // Italian + { + static const short stress_lengths_it[8] = {150, 140, 170, 170, 0, 0, 300, 330}; + static const unsigned char stress_amps_it[8] = {15,14, 19,19, 20,22, 22,20 }; + + SetupTranslator(tr,stress_lengths_it,stress_amps_it); + + tr->langopts.length_mods0 = tr->langopts.length_mods; // don't lengthen vowels in the last syllable + tr->langopts.stress_rule = 2; + tr->langopts.vowel_pause = 1; + tr->langopts.unstressed_wd1 = 2; + tr->langopts.unstressed_wd2 = 2; + tr->langopts.param[LOPT_IT_LENGTHEN] = 2; // remove lengthen indicator from unstressed or non-penultimate syllables + tr->langopts.param[LOPT_IT_DOUBLING] = 2; // double the first consonant if the previous word ends in a stressed vowel + tr->langopts.param[LOPT_SONORANT_MIN] = 130; // limit the shortening of sonorants before short vowels + tr->langopts.param[LOPT_REDUCE] = 1; // reduce vowels even if phonemes are specified in it_list + tr->langopts.numbers = 0x2709 + NUM_ROMAN; + tr->langopts.accents = 2; // Say "Capital" after the letter. + } + break; + + case L_jbo: // Lojban + { + static const short stress_lengths_jbo[8] = {145,145, 170,160, 0,0, 330,350}; + static const wchar_t jbo_punct_within_word[] = {'.',',','\'',0x2c8,0}; // allow period and comma within a word, also stress marker (from LOPT_SYLLABLE_CAPS) + + SetupTranslator(tr,stress_lengths_jbo,NULL); + tr->langopts.stress_rule = 2; + tr->langopts.vowel_pause = 0x20c; // pause before a word which starts with a vowel, or after a word which ends in a consonant +// tr->langopts.word_gap = 1; + tr->punct_within_word = jbo_punct_within_word; + tr->langopts.param[LOPT_SYLLABLE_CAPS] = 1; // capitals indicate stressed syllables + SetLetterVowel(tr,'y'); + } + break; + + case L('k','o'): // Korean, TEST + { + static const char ko_ivowels[] = {0x63,0x64,0x67,0x68,0x6d,0x72,0x74,0x75,0}; // y and i vowels + static const unsigned char ko_voiced[] = {0x02,0x05,0x06,0xab,0xaf,0xb7,0xbc,0}; // voiced consonants, l,m,n,N + + tr->letter_bits_offset = OFFSET_KOREAN; + memset(tr->letter_bits,0,sizeof(tr->letter_bits)); + SetLetterBitsRange(tr,LETTERGP_A,0x61,0x75); + SetLetterBits(tr,LETTERGP_Y,ko_ivowels); + SetLetterBits(tr,LETTERGP_G,(const char *)ko_voiced); + + tr->langopts.stress_rule = 8; // ?? 1st syllable if it is heavy, else 2nd syllable + tr->langopts.param[LOPT_UNPRONOUNCABLE] = 1; // disable check for unpronouncable words + tr->langopts.numbers = 0x0401; + } + break; + + case L('k','u'): // Kurdish + { + static const unsigned char stress_amps_ku[8] = {18,18, 20,20, 20,22, 22,21 }; + static const short stress_lengths_ku[8] = {180,180, 190,180, 0,0, 230,240}; + + SetupTranslator(tr,stress_lengths_ku,stress_amps_ku); + tr->charset_a0 = charsets[9]; // ISO-8859-9 - Latin5 + + tr->langopts.stress_rule = 7; // stress on the last syllable, before any explicitly unstressed syllable + + tr->langopts.numbers = 0x100461; + tr->langopts.max_initial_consonants = 2; + } + break; + + case L('l','a'): //Latin + { + tr->charset_a0 = charsets[4]; // ISO-8859-4, includes a,e,i,o,u-macron + tr->langopts.stress_rule = 2; + tr->langopts.stress_flags = 0x20; + tr->langopts.unstressed_wd1 = 0; + tr->langopts.unstressed_wd2 = 2; + tr->langopts.param[LOPT_DIERESES] = 1; + tr->langopts.numbers = 0x1 + NUM_ROMAN; + tr->langopts.max_roman = 5000; + } + break; + + case L('l','v'): // latvian + { + static const unsigned char stress_amps_lv[8] = {17,13, 20,20, 20,22, 22,21 }; + static const short stress_lengths_lv[8] = {180,130, 210,210, 0,0, 210,210}; + + SetupTranslator(tr,stress_lengths_lv,stress_amps_lv); + + tr->langopts.stress_rule = 0; + tr->langopts.spelling_stress = 1; + tr->charset_a0 = charsets[4]; // ISO-8859-4 + tr->langopts.numbers = 0x409 + 0x8000 + 0x10000; + tr->langopts.stress_flags = 0x16 + 0x40000; + } + break; + + case L('m','k'): // Macedonian + { + static wchar_t vowels_cyrillic[] = {0x440, // also include 'Ñ€' [R] + 0x430,0x435,0x438,0x439,0x43e,0x443,0x44b,0x44d,0x44e,0x44f,0x450,0x451,0x456,0x457,0x45d,0x45e,0}; + static const unsigned char stress_amps_mk[8] = {17,17, 20,20, 20,22, 22,21 }; + static const short stress_lengths_mk[8] = {180,160, 200,200, 0,0, 220,230}; + + SetupTranslator(tr,stress_lengths_mk,stress_amps_mk); + tr->charset_a0 = charsets[5]; // ISO-8859-5 + tr->letter_groups[0] = vowels_cyrillic; + + tr->langopts.stress_rule = 4; // antipenultimate + tr->langopts.numbers = 0x0429 + 0x4000; + tr->langopts.numbers2 = 0x8a; // variant numbers before thousands,milliards + } + break; + + + case L('n','l'): // Dutch + { + static const short stress_lengths_nl[8] = {160,135, 210,210, 0, 0, 260,280}; + + tr->langopts.stress_rule = 0; + tr->langopts.vowel_pause = 1; + tr->langopts.param[LOPT_DIERESES] = 1; + tr->langopts.param[LOPT_PREFIXES] = 1; + SetLetterVowel(tr,'y'); + + tr->langopts.numbers = 0x11c19; + memcpy(tr->stress_lengths,stress_lengths_nl,sizeof(tr->stress_lengths)); + } + break; + + case L('n','o'): // Norwegian + { + static const short stress_lengths_no[8] = {160,140, 200,200, 0,0, 220,210}; + + SetupTranslator(tr,stress_lengths_no,NULL); + tr->langopts.stress_rule = 0; + SetLetterVowel(tr,'y'); + tr->langopts.numbers = 0x11849; + } + break; + + case L('o','m'): + { + static const unsigned char stress_amps_om[] = {18,15, 20,20, 20,22, 22,22 }; + static const short stress_lengths_om[8] = {200,200, 200,200, 0,0, 200,200}; + + SetupTranslator(tr,stress_lengths_om,stress_amps_om); + tr->langopts.stress_rule = 2; + tr->langopts.stress_flags = 0x16 + 0x80000; + } + break; + + case L('p','l'): // Polish + { + static const short stress_lengths_pl[8] = {160, 190, 175, 175, 0, 0, 200, 210}; + static const unsigned char stress_amps_pl[8] = {17,13, 19,19, 20,22, 22,21 }; // 'diminished' is used to mark a quieter, final unstressed syllable + + SetupTranslator(tr,stress_lengths_pl,stress_amps_pl); + + tr->charset_a0 = charsets[2]; // ISO-8859-2 + tr->langopts.stress_rule = 2; + tr->langopts.stress_flags = 0x6; // mark unstressed final syllables as diminished + tr->langopts.param[LOPT_REGRESSIVE_VOICING] = 0x8; + tr->langopts.max_initial_consonants = 7; // for example: wchrzczony :) + tr->langopts.numbers=0x1009 + 0x4000; + tr->langopts.numbers2=0x40; + tr->langopts.param[LOPT_COMBINE_WORDS] = 4 + 0x100; // combine 'nie' (marked with $alt2) with some 1-syllable (and 2-syllable) words (marked with $alt) + SetLetterVowel(tr,'y'); + } + break; + + case L('p','t'): // Portuguese + { + static const short stress_lengths_pt[8] = {180, 125, 210, 210, 0, 0, 270, 295}; + static const unsigned char stress_amps_pt[8] = {16,13, 19,19, 20,22, 22,21 }; // 'diminished' is used to mark a quieter, final unstressed syllable + + SetupTranslator(tr,stress_lengths_pt,stress_amps_pt); + tr->langopts.length_mods0 = tr->langopts.length_mods; // don't lengthen vowels in the last syllable + + tr->langopts.stress_rule = 3; // stress on final syllable + tr->langopts.stress_flags = 0x6 | 0x10 | 0x20000; + tr->langopts.numbers = 0x269 + 0x4000 + NUM_ROMAN; + SetLetterVowel(tr,'y'); + ResetLetterBits(tr,0x2); + SetLetterBits(tr,1,"bcdfgjkmnpqstvxz"); // B hard consonants, excluding h,l,r,w,y + } + break; + + case L('r','o'): // Romanian + { + static const short stress_lengths_ro[8] = {170, 170, 180, 180, 0, 0, 240, 260}; + static const unsigned char stress_amps_ro[8] = {15,13, 18,18, 20,22, 22,21 }; + + SetupTranslator(tr,stress_lengths_ro,stress_amps_ro); + + tr->langopts.stress_rule = 2; + tr->langopts.stress_flags = 0x100 + 0x6; + + tr->charset_a0 = charsets[2]; // ISO-8859-2 + tr->langopts.numbers = 0x1029+0x6000 + NUM_ROMAN; + tr->langopts.numbers2 = 0x1e; // variant numbers before all thousandplex + } + break; + + case L('r','u'): // Russian + Translator_Russian(tr); + break; + + case L('r','w'): // Kiryarwanda + { + tr->langopts.stress_rule = 2; + tr->langopts.stress_flags = 0x16; + tr->langopts.length_mods0 = tr->langopts.length_mods; // don't lengthen vowels in the last syllable + + tr->langopts.numbers = 0x61 + 0x100000 + 0x4000; + tr->langopts.numbers2 = 0x200; // say "thousands" before its number + } + break; + + case L('s','k'): // Slovak + case L('c','s'): // Czech + { + static const char *sk_voiced = "bdgjlmnrvwzaeiouy"; + + SetupTranslator(tr,stress_lengths_sk,stress_amps_sk); + tr->charset_a0 = charsets[2]; // ISO-8859-2 + + tr->langopts.stress_rule = 0; + tr->langopts.stress_flags = 0x16; + tr->langopts.param[LOPT_REGRESSIVE_VOICING] = 0x3; + tr->langopts.max_initial_consonants = 5; + tr->langopts.spelling_stress = 1; + tr->langopts.param[LOPT_COMBINE_WORDS] = 4; // combine some prepositions with the following word + + tr->langopts.numbers = 0x0401 + 0x4000 + NUM_ROMAN; + tr->langopts.numbers2 = 0x100; + tr->langopts.thousands_sep = 0; //no thousands separator + tr->langopts.decimal_sep = ','; + + if(name2 == L('c','s')) + { + tr->langopts.numbers2 = 0x108; // variant numbers before milliards + } + + SetLetterVowel(tr,'y'); + SetLetterVowel(tr,'r'); + ResetLetterBits(tr,0x20); + SetLetterBits(tr,5,sk_voiced); + } + break; + + case L('s','q'): // Albanian + { + static const short stress_lengths_sq[8] = {150, 150, 180, 180, 0, 0, 300, 300}; + static const unsigned char stress_amps_sq[8] = {16,12, 16,16, 20,20, 21,19 }; + + SetupTranslator(tr,stress_lengths_sq,stress_amps_sq); + + tr->langopts.stress_rule = 2; + tr->langopts.stress_flags = 0x16 + 0x100; + SetLetterVowel(tr,'y'); + tr->langopts.numbers = 0x69 + 0x8000; + tr->langopts.accents = 2; // "capital" after letter name + } + break; + + + case L('s','v'): // Swedish + { + static const unsigned char stress_amps_sv[] = {16,16, 20,20, 20,22, 22,21 }; + static const short stress_lengths_sv[8] = {160,135, 220,220, 0,0, 250,280}; + SetupTranslator(tr,stress_lengths_sv,stress_amps_sv); + + tr->langopts.stress_rule = 0; + SetLetterVowel(tr,'y'); + tr->langopts.numbers = 0x1909; + tr->langopts.accents = 1; + } + break; + + case L('s','w'): // Swahili + { + static const short stress_lengths_sw[8] = {160, 170, 200, 200, 0, 0, 320, 340}; + static const unsigned char stress_amps_sw[] = {16,12, 19,19, 20,22, 22,21 }; + + SetupTranslator(tr,stress_lengths_sw,stress_amps_sw); + tr->langopts.length_mods0 = tr->langopts.length_mods; // don't lengthen vowels in the last syllable + + tr->langopts.vowel_pause = 1; + tr->langopts.stress_rule = 2; + tr->langopts.stress_flags = 0x6 | 0x10; + + tr->langopts.numbers = 0x4e1; + tr->langopts.numbers2 = NUM2_100000a; + } + break; + + case L('t','a'): // Tamil + case L('m','l'): // Malayalam + case L('k','n'): // Kannada + case L('m','r'): // Marathi + { + static const short stress_lengths_ta[8] = {200, 200, 210, 210, 0, 0, 230, 230}; + static const unsigned char stress_amps_ta[8] = {18,18, 18,18, 20,20, 22,22 }; + + SetupTranslator(tr,stress_lengths_ta,stress_amps_ta); + tr->langopts.length_mods0 = tr->langopts.length_mods; // don't lengthen vowels in the last syllable + + tr->langopts.stress_rule = 0; + tr->langopts.stress_flags = 0x10004; // use 'diminished' for unstressed final syllable + tr->letter_bits_offset = OFFSET_TAMIL; + + if(name2 == L('m','r')) + { + tr->letter_bits_offset = OFFSET_DEVANAGARI; + } + else + if(name2 == L('m','l')) + { + tr->letter_bits_offset = OFFSET_MALAYALAM; + } + else + if(name2 == L('k','n')) + { + tr->letter_bits_offset = OFFSET_KANNADA; + tr->langopts.numbers = 0x1; + tr->langopts.numbers2 = NUM2_100000; + } + tr->langopts.param[LOPT_WORD_MERGE] = 1; // don't break vowels betwen words + SetIndicLetters(tr); // call this after setting OFFSET_ + } + break; + +#ifdef deleted + case L('t','h'): // Thai + { + static const short stress_lengths_th[8] = {230,150, 230,230, 230,0, 230,250}; + static const unsigned char stress_amps_th[] = {22,16, 22,22, 22,22, 22,22 }; + + SetupTranslator(tr,stress_lengths_th,stress_amps_th); + + tr->langopts.stress_rule = 0; // stress on final syllable of a "word" + tr->langopts.stress_flags = 1; // don't automatically set diminished stress (may be set in the intonation module) + tr->langopts.tone_language = 1; // Tone language, use CalcPitches_Tone() rather than CalcPitches() + tr->langopts.length_mods0 = tr->langopts.length_mods; // don't lengthen vowels in the last syllable +// tr->langopts.tone_numbers = 1; // a number after letters indicates a tone number (eg. pinyin or jyutping) + tr->langopts.word_gap = 0x21; // length of a final vowel is less dependent on the next consonant, don't merge consonant with next word + } + break; +#endif + + case L('t','r'): // Turkish + { + static const unsigned char stress_amps_tr[8] = {18,18, 20,20, 20,22, 22,21 }; + static const short stress_lengths_tr[8] = {190,190, 190,190, 0,0, 250,270}; + + SetupTranslator(tr,stress_lengths_tr,stress_amps_tr); + tr->charset_a0 = charsets[9]; // ISO-8859-9 - Latin5 + + tr->langopts.stress_rule = 7; // stress on the last syllable, before any explicitly unstressed syllable + tr->langopts.stress_flags = 0x20; //no automatic secondary stress + + tr->langopts.numbers = 0x1509 + 0x4000; + tr->langopts.max_initial_consonants = 2; + } + break; + + case L('v','i'): // Vietnamese + { + static const short stress_lengths_vi[8] = {150, 150, 180, 180, 210, 230, 230, 240}; + static const unsigned char stress_amps_vi[] = {16,16, 16,16, 22,22, 22,22 }; + static wchar_t vowels_vi[] = { + 0x61, 0xe0, 0xe1, 0x1ea3, 0xe3, 0x1ea1, // a + 0x103, 0x1eb1, 0x1eaf, 0x1eb3, 0x1eb5, 0x1eb7, // ă + 0xe2, 0x1ea7, 0x1ea5, 0x1ea9, 0x1eab, 0x1ead, // â + 0x65, 0xe8, 0xe9, 0x1ebb, 0x1ebd, 0x1eb9, // e + 0xea, 0x1ec1, 0x1ebf, 0x1ec3, 0x1ec5, 0x1ec7, // i + 0x69, 0xec, 0xed, 0x1ec9, 0x129, 0x1ecb, // i + 0x6f, 0xf2, 0xf3, 0x1ecf, 0xf5, 0x1ecd, // o + 0xf4, 0x1ed3, 0x1ed1, 0x1ed5, 0x1ed7, 0x1ed9, // ô + 0x1a1, 0x1edd, 0x1edb, 0x1edf, 0x1ee1, 0x1ee3, // Æ¡ + 0x75, 0xf9, 0xfa, 0x1ee7, 0x169, 0x1ee5, // u + 0x1b0, 0x1eeb, 0x1ee9, 0x1eed, 0x1eef, 0x1ef1, // ư + 0x79, 0x1ef3, 0xfd, 0x1ef7, 0x1ef9, 0x1e, 0 }; // y + + SetupTranslator(tr,stress_lengths_vi,stress_amps_vi); + tr->langopts.length_mods0 = tr->langopts.length_mods; // don't lengthen vowels in the last syllable + + tr->langopts.stress_rule = 0; + tr->langopts.word_gap = 0x21; // length of a final vowel is less dependent on the next consonant, don't merge consonant with next word +// tr->langopts.vowel_pause = 4; + tr->letter_groups[0] = vowels_vi; + tr->langopts.tone_language = 1; // Tone language, use CalcPitches_Tone() rather than CalcPitches() + tr->langopts.unstressed_wd1 = 2; + tr->langopts.numbers = 0x0049 + 0x8000; + + } + break; + + case L('z','h'): + case L_zhy: + { + static const short stress_lengths_zh[8] = {230,150, 230,230, 230,0, 240,250}; // 1=tone5. end-of-sentence, 6=tone 1&4, 7=tone 2&3 + static const unsigned char stress_amps_zh[] = {22,16, 22,22, 22,22, 22,22 }; + + SetupTranslator(tr,stress_lengths_zh,stress_amps_zh); + + tr->langopts.stress_rule = 3; // stress on final syllable of a "word" + tr->langopts.stress_flags = 1; // don't automatically set diminished stress (may be set in the intonation module) + tr->langopts.vowel_pause = 0; + tr->langopts.tone_language = 1; // Tone language, use CalcPitches_Tone() rather than CalcPitches() + tr->langopts.length_mods0 = tr->langopts.length_mods; // don't lengthen vowels in the last syllable + tr->langopts.tone_numbers = 1; // a number after letters indicates a tone number (eg. pinyin or jyutping) + tr->langopts.ideographs = 1; + tr->langopts.word_gap = 0x21; // length of a final vowel is less dependent on the next consonant, don't merge consonant with next word + if(name2 == L('z','h')) + { + tr->langopts.textmode = 1; + tr->langopts.listx = 1; // compile zh_listx after zh_list + } + } + break; + + default: + break; + } + + tr->translator_name = name2; + + if(tr->langopts.numbers & 0x8) + { + // use . and ; for thousands and decimal separators + tr->langopts.thousands_sep = '.'; + tr->langopts.decimal_sep = ','; + } + if(tr->langopts.numbers & 0x4) + { + tr->langopts.thousands_sep = 0; // don't allow thousands separator, except space + } + return(tr); +} // end of SelectTranslator + + + +//********************************************************************************************************** + + + +static void Translator_Russian(Translator *tr) +{//=========================================== + static const unsigned char stress_amps_ru[] = {16,16, 18,18, 20,24, 24,22 }; + static const short stress_lengths_ru[8] = {150,140, 220,220, 0,0, 260,280}; + + + // character codes offset by 0x420 + static const char ru_vowels[] = {0x10,0x15,0x31,0x18,0x1e,0x23,0x2b,0x2d,0x2e,0x2f,0}; + static const char ru_consonants[] = {0x11,0x12,0x13,0x14,0x16,0x17,0x19,0x1a,0x1b,0x1c,0x1d,0x1f,0x20,0x21,0x22,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2c,0}; + static const char ru_soft[] = {0x2c,0x19,0x27,0x29,0}; // letter group B [k ts; s;] + static const char ru_hard[] = {0x2a,0x16,0x26,0x28,0}; // letter group H [S Z ts] + static const char ru_nothard[] = {0x11,0x12,0x13,0x14,0x17,0x19,0x1a,0x1b,0x1c,0x1d,0x1f,0x20,0x21,0x22,0x24,0x25,0x27,0x29,0x2c,0}; + static const char ru_voiced[] = {0x11,0x12,0x13,0x14,0x16,0x17,0}; // letter group G (voiced obstruents) + static const char ru_ivowels[] = {0x2c,0x15,0x31,0x18,0x2e,0x2f,0}; // letter group Y (iotated vowels & soft-sign) + + SetupTranslator(tr,stress_lengths_ru,stress_amps_ru); + + tr->charset_a0 = charsets[18]; // KOI8-R + tr->transpose_offset = 0x42f; // convert cyrillic from unicode into range 0x01 to 0x22 + tr->transpose_min = 0x430; + tr->transpose_max = 0x451; + + tr->letter_bits_offset = OFFSET_CYRILLIC; + memset(tr->letter_bits,0,sizeof(tr->letter_bits)); + SetLetterBits(tr,0,ru_vowels); + SetLetterBits(tr,1,ru_soft); + SetLetterBits(tr,2,ru_consonants); + SetLetterBits(tr,3,ru_hard); + SetLetterBits(tr,4,ru_nothard); + SetLetterBits(tr,5,ru_voiced); + SetLetterBits(tr,6,ru_ivowels); + SetLetterBits(tr,7,ru_vowels); + + tr->langopts.param[LOPT_UNPRONOUNCABLE] = 0x432; // [v] don't count this character at start of word + tr->langopts.param[LOPT_REGRESSIVE_VOICING] = 1; + tr->langopts.param[LOPT_REDUCE] = 2; + tr->langopts.stress_rule = 5; + tr->langopts.stress_flags = 0x0020; // waas 0x1010 + + tr->langopts.numbers = 0x0409; + tr->langopts.numbers2 = 0xc2; // variant numbers before thousands + tr->langopts.phoneme_change = 1; + tr->langopts.testing = 2; + +} // end of Translator_Russian + + + +/* +typedef struct { + int flags; + unsigned char stress; // stress level of this vowel + unsigned char stress_highest; // the highest stress level of a vowel in this word + unsigned char n_vowels; // number of vowels in the word + unsigned char vowel_this; // syllable number of this vowel (counting from 1) + unsigned char vowel_stressed; // syllable number of the highest stressed vowel +} CHANGEPH; +*/ + + +#define RUSSIAN2 +#ifdef RUSSIAN2 + +int ChangePhonemes_ru(Translator *tr, PHONEME_LIST2 *phlist, int n_ph, int index, PHONEME_TAB *ph, CHANGEPH *ch) +{//============================================================================================================= +// Called for each phoneme in the phoneme list, to allow a language to make changes +// ph The current phoneme + + int variant; + int vowelix; + PHONEME_TAB *prev, *next; + + if(ch->flags & 8) + return(0); // full phoneme translation has already been given + // Russian vowel softening and reduction rules + + if(ph->type == phVOWEL) + { + int prestressed = ch->vowel_stressed==ch->vowel_this+1; // the next vowel after this has the main stress + + #define N_VOWELS_RU 11 + static unsigned int vowels_ru[N_VOWELS_RU] = {'a','V','O','I',PH('I','#'),PH('E','#'),PH('E','2'), +PH('V','#'),PH('I','3'),PH('I','2'),PH('E','3')}; + + + static unsigned int vowel_replace[N_VOWELS_RU][6] = { + // stressed, soft, soft-stressed, j+stressed, j+soft, j+soft-stressed + /*0*/ {'A', 'I', PH('j','a'), 'a', 'a', 'a'}, // a Uses 3,4,5 columns. + /*1*/ {'A', 'V', PH('j','a'), 'a', 'V', 'a'}, // V Uses 3,4,5 columns. + /*2*/ {'o', '8', '8', 'o', '8', '8'}, // O + /*3*/ {'i', 'I', 'i', 'a', 'I', 'a'}, // I Uses 3,4,5 columns. + /*4*/ {'i', PH('I','#'), 'i', 'i', PH('I','#'), 'i'}, // I# + /*5*/ {'E', PH('E','#'), 'E', 'e', PH('E','#'), 'e'}, // E# + /*6*/ {'E', PH('E','2'), 'E', 'e', PH('E','2'), 'e'}, // E2 Uses 3,4,5 columns. + /*7*/ {PH('j','a'), 'V', PH('j','a'), 'A', 'V', 'A'}, // V# + /*8*/ {PH('j','a'), 'I', PH('j','a'), 'e', 'I', 'e'}, // I3 Uses 3,4,5 columns. + /*9*/ {'e', 'I', 'e', 'e', 'I', 'e'}, // I2 + /*10*/ {'e', PH('E', '2'), 'e', 'e', PH('E','2'), 'e'} // E3 + }; + + prev = phoneme_tab[phlist[index-1].phcode]; + next = phoneme_tab[phlist[index+1].phcode]; + + // lookup the vowel name to get an index into the vowel_replace[] table + for(vowelix=0; vowelix<N_VOWELS_RU; vowelix++) + { + if(vowels_ru[vowelix] == ph->mnemonic) + break; + } + if(vowelix == N_VOWELS_RU) + return(0); + + if(prestressed) + { + if((vowelix==6)&&(prev->mnemonic=='j')) + vowelix=8; + if(vowelix==1) + vowelix=0; + if(vowelix==4) + vowelix=3; + if(vowelix==6) + vowelix=5; + if(vowelix==7) + vowelix=8; + if(vowelix==10) + vowelix=9; + } + // do we need a variant of this vowel, depending on the stress and adjacent phonemes ? + variant = -1; + int stressed = ch->flags & 2; + int soft=prev->phflags & phPALATAL; + + if (soft && stressed) + variant = 2; else + if (stressed) + variant = 0; else + if (soft) + variant = 1; + if(variant >= 0) + { + if(prev->mnemonic == 'j') + variant += 3; + + phlist[index].phcode = PhonemeCode(vowel_replace[vowelix][variant]); + } + else + { + phlist[index].phcode = PhonemeCode(vowels_ru[vowelix]); + } + } + + return(0); +} +#else + + +int ChangePhonemes_ru(Translator *tr, PHONEME_LIST2 *phlist, int n_ph, int index, PHONEME_TAB *ph, CHANGEPH *ch) +{//============================================================================================================= +// Called for each phoneme in the phoneme list, to allow a language to make changes +// flags: bit 0=1 last phoneme in a word +// bit 1=1 this is the highest stressed vowel in the current word +// bit 2=1 after the highest stressed vowel in the current word +// bit 3=1 the phonemes were specified explicitly, or found from an entry in the xx_list dictionary +// ph The current phoneme + + int variant; + int vowelix; + PHONEME_TAB *prev, *next; + + if(ch->flags & 8) + return(0); // full phoneme translation has already been given + + // Russian vowel softening and reduction rules + if(ph->type == phVOWEL) + { + #define N_VOWELS_RU 7 + static unsigned char vowels_ru[N_VOWELS_RU] = {'a','A','o','E','i','u','y'}; + + // each line gives: soft, reduced, soft-reduced, post-tonic + static unsigned short vowel_replace[N_VOWELS_RU][4] = { + {'&', 'V', 'I', 'V'}, // a + {'&', 'V', 'I', 'V'}, // A + {'8', 'V', 'I', 'V'}, // o + {'e', 'I', 'I', 'I'}, // E + {'i', 'I', 'I', 'I'}, // i + {'u'+('"'<<8), 'U', 'U', 'U'}, // u + {'y', 'Y', 'Y', 'Y'}}; // y + + prev = phoneme_tab[phlist[index-1].phcode]; + next = phoneme_tab[phlist[index+1].phcode]; + +if(prev->mnemonic == 'j') + return(0); + + // lookup the vowel name to get an index into the vowel_replace[] table + for(vowelix=0; vowelix<N_VOWELS_RU; vowelix++) + { + if(vowels_ru[vowelix] == ph->mnemonic) + break; + } + if(vowelix == N_VOWELS_RU) + return(0); + + // do we need a variant of this vowel, depending on the stress and adjacent phonemes ? + variant = -1; + if(ch->flags & 2) + { + // a stressed vowel + if((prev->phflags & phPALATAL) && ((next->phflags & phPALATAL) || phoneme_tab[phlist[index+2].phcode]->mnemonic == ';')) + { + // between two palatal consonants, use the soft variant + variant = 0; + } + } + else + { + // an unstressed vowel + if(prev->phflags & phPALATAL) + { + variant = 2; // unstressed soft + } + else + if((ph->mnemonic == 'o') && ((prev->phflags & phPLACE) == phPLACE_pla)) + { + variant = 2; // unstressed soft ([o] vowel following: ш ж + } + else + if(ch->flags & 4) + { + variant = 3; // post tonic + } + else + { + variant = 1; // unstressed + } + } + if(variant >= 0) + { + phlist[index].phcode = PhonemeCode(vowel_replace[vowelix][variant]); + } + } + + return(0); +} +#endif + diff --git a/Plugins/eSpeak/eSpeak/translate.cpp b/Plugins/eSpeak/eSpeak/translate.cpp new file mode 100644 index 0000000..69259fc --- /dev/null +++ b/Plugins/eSpeak/eSpeak/translate.cpp @@ -0,0 +1,2771 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "StdAfx.h" + +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include <wctype.h> +#include <wchar.h> + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "voice.h" +#include "translate.h" + +#define WORD_STRESS_CHAR '*' + + +Translator *translator = NULL; // the main translator +Translator *translator2 = NULL; // secondary translator for certain words +static char translator2_language[20] = {0}; + +FILE *f_trans = NULL; // phoneme output text +int option_tone2 = 0; +int option_tone_flags = 0; // bit 8=emphasize allcaps, bit 9=emphasize penultimate stress +int option_phonemes = 0; +int option_phoneme_events = 0; +int option_quiet = 0; +int option_endpause = 0; // suppress pause after end of text +int option_capitals = 0; +int option_punctuation = 0; +int option_sayas = 0; +static int option_sayas2 = 0; // used in translate_clause() +static int option_emphasis = 0; // 0=normal, 1=normal, 2=weak, 3=moderate, 4=strong +int option_ssml = 0; +int option_phoneme_input = 0; // allow [[phonemes]] in input +int option_phoneme_variants = 0; // 0= don't display phoneme variant mnemonics +int option_wordgap = 0; + +static int count_sayas_digits; +int skip_sentences; +int skip_words; +int skip_characters; +char skip_marker[N_MARKER_LENGTH]; +int skipping_text; // waiting until word count, sentence count, or named marker is reached +int end_character_position; +int count_sentences; +int count_words; +int clause_start_char; +int clause_start_word; +int new_sentence; +static int word_emphasis = 0; // set if emphasis level 3 or 4 + +static int prev_clause_pause=0; +static int max_clause_pause = 0; + + +// these were previously in translator class +char word_phonemes[N_WORD_PHONEMES]; // a word translated into phoneme codes +int n_ph_list2; +PHONEME_LIST2 ph_list2[N_PHONEME_LIST]; // first stage of text->phonemes + + + +wchar_t option_punctlist[N_PUNCTLIST]={0}; +char ctrl_embedded = '\001'; // to allow an alternative CTRL for embedded commands +int option_multibyte=espeakCHARS_AUTO; // 0=auto, 1=utf8, 2=8bit, 3=wchar + +// these are overridden by defaults set in the "speak" file +int option_linelength = 0; + +#define N_EMBEDDED_LIST 250 +static int embedded_ix; +static int embedded_read; +unsigned int embedded_list[N_EMBEDDED_LIST]; + +// the source text of a single clause (UTF8 bytes) +#define N_TR_SOURCE 700 +static char source[N_TR_SOURCE+40]; // extra space for embedded command & voice change info at end + +int n_replace_phonemes; +REPLACE_PHONEMES replace_phonemes[N_REPLACE_PHONEMES]; + + +// brackets, also 0x2014 to 0x021f which don't need to be in this list +static const unsigned short brackets[] = { +'(',')','[',']','{','}','<','>','"','\'','`', +0xab,0xbb, // double angle brackets +0x300a,0x300b, // double angle brackets (ideograph) +0}; + +// other characters which break a word, but don't produce a pause +static const unsigned short breaks[] = {'_', 0}; + +// treat these characters as spaces, in addition to iswspace() +static const wchar_t chars_space[] = {0x2500,0}; // box drawing horiz + + +// Translate character codes 0xA0 to 0xFF into their unicode values +// ISO_8859_1 is set as default +static const unsigned short ISO_8859_1[0x60] = { + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, // a0 + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, // a8 + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, // b0 + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, // b8 + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, // c0 + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, // c8 + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, // d0 + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, // d8 + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, // e0 + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, // e8 + 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, // f0 + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff, // f8 +}; + +static const unsigned short ISO_8859_2[0x60] = { + 0x00a0, 0x0104, 0x02d8, 0x0141, 0x00a4, 0x013d, 0x015a, 0x00a7, // a0 + 0x00a8, 0x0160, 0x015e, 0x0164, 0x0179, 0x00ad, 0x017d, 0x017b, // a8 + 0x00b0, 0x0105, 0x02db, 0x0142, 0x00b4, 0x013e, 0x015b, 0x02c7, // b0 + 0x00b8, 0x0161, 0x015f, 0x0165, 0x017a, 0x02dd, 0x017e, 0x017c, // b8 + 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7, // c0 + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e, // c8 + 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7, // d0 + 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df, // d8 + 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7, // e0 + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f, // e8 + 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, // f0 + 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9, // f8 +}; + +static const unsigned short ISO_8859_3[0x60] = { + 0x00a0, 0x0126, 0x02d8, 0x00a3, 0x00a4, 0x0000, 0x0124, 0x00a7, // a0 + 0x00a8, 0x0130, 0x015e, 0x011e, 0x0134, 0x00ad, 0x0000, 0x017b, // a8 + 0x00b0, 0x0127, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x0125, 0x00b7, // b0 + 0x00b8, 0x0131, 0x015f, 0x011f, 0x0135, 0x00bd, 0x0000, 0x017c, // b8 + 0x00c0, 0x00c1, 0x00c2, 0x0000, 0x00c4, 0x010a, 0x0108, 0x00c7, // c0 + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, // c8 + 0x0000, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x0120, 0x00d6, 0x00d7, // d0 + 0x011c, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x016c, 0x015c, 0x00df, // d8 + 0x00e0, 0x00e1, 0x00e2, 0x0000, 0x00e4, 0x010b, 0x0109, 0x00e7, // e0 + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, // e8 + 0x0000, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x0121, 0x00f6, 0x00f7, // f0 + 0x011d, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x016d, 0x015d, 0x02d9, // f8 +}; + +static const unsigned short ISO_8859_4[0x60] = { + 0x00a0, 0x0104, 0x0138, 0x0156, 0x00a4, 0x0128, 0x013b, 0x00a7, // a0 + 0x00a8, 0x0160, 0x0112, 0x0122, 0x0166, 0x00ad, 0x017d, 0x00af, // a8 + 0x00b0, 0x0105, 0x02db, 0x0157, 0x00b4, 0x0129, 0x013c, 0x02c7, // b0 + 0x00b8, 0x0161, 0x0113, 0x0123, 0x0167, 0x014a, 0x017e, 0x014b, // b8 + 0x0100, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x012e, // c0 + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x0116, 0x00cd, 0x00ce, 0x012a, // c8 + 0x0110, 0x0145, 0x014c, 0x0136, 0x00d4, 0x00d5, 0x00d6, 0x00d7, // d0 + 0x00d8, 0x0172, 0x00da, 0x00db, 0x00dc, 0x0168, 0x016a, 0x00df, // d8 + 0x0101, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x012f, // e0 + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x0117, 0x00ed, 0x00ee, 0x012b, // e8 + 0x0111, 0x0146, 0x014d, 0x0137, 0x00f4, 0x00f5, 0x00f6, 0x00f7, // f0 + 0x00f8, 0x0173, 0x00fa, 0x00fb, 0x00fc, 0x0169, 0x016b, 0x02d9, // f8 +}; + +static const unsigned short ISO_8859_5[0x60] = { + 0x00a0, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, // a0 Cyrillic + 0x0408, 0x0409, 0x040a, 0x040b, 0x040c, 0x00ad, 0x040e, 0x040f, // a8 + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, // b0 + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, // b8 + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, // c0 + 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, // c8 + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, // d0 + 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, // d8 + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, // e0 + 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, // e8 + 0x2116, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457, // f0 + 0x0458, 0x0459, 0x045a, 0x045b, 0x045c, 0x00a7, 0x045e, 0x045f, // f8 +}; + +static const unsigned short ISO_8859_7[0x60] = { + 0x00a0, 0x2018, 0x2019, 0x00a3, 0x20ac, 0x20af, 0x00a6, 0x00a7, // a0 Greek + 0x00a8, 0x00a9, 0x037a, 0x00ab, 0x00ac, 0x00ad, 0x0000, 0x2015, // a8 + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x0384, 0x0385, 0x0386, 0x00b7, // b0 + 0x0388, 0x0389, 0x038a, 0x00bb, 0x038c, 0x00bd, 0x038e, 0x038f, // b8 + 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, // c0 + 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, // c8 + 0x03a0, 0x03a1, 0x0000, 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, // d0 + 0x03a8, 0x03a9, 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03ae, 0x03af, // d8 + 0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, // e0 + 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, // e8 + 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, // f0 + 0x03c8, 0x03c9, 0x03ca, 0x03cb, 0x03cc, 0x03cd, 0x03ce, 0x0000, // f8 +}; + +static const unsigned short ISO_8859_9[0x60] = { + 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, // a0 + 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, // a8 + 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, // b0 + 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, // b8 + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, // c0 + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, // c8 + 0x011e, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, // d0 + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x0130, 0x015e, 0x00df, // d8 + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, // e0 + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, // e8 + 0x011f, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, // f0 + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x0131, 0x015f, 0x00ff, // f8 +}; + +static const unsigned short ISO_8859_14[0x60] = { + 0x00a0, 0x1e02, 0x1e03, 0x00a3, 0x010a, 0x010b, 0x1e0a, 0x00a7, // a0 Welsh + 0x1e80, 0x00a9, 0x1e82, 0x1e0b, 0x1ef2, 0x00ad, 0x00ae, 0x0178, // a8 + 0x1e1e, 0x1e1f, 0x0120, 0x0121, 0x1e40, 0x1e41, 0x00b6, 0x1e56, // b0 + 0x1e81, 0x1e57, 0x1e83, 0x1e60, 0x1ef3, 0x1e84, 0x1e85, 0x1e61, // b8 + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, // c0 + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, // c8 + 0x0174, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x1e6a, // d0 + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x0176, 0x00df, // d8 + 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, // e0 + 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, // e8 + 0x0175, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x1e6b, // f0 + 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x0177, 0x00ff, // f8 +}; + +static const unsigned short KOI8_R[0x60] = { + 0x2550, 0x2551, 0x2552, 0x0451, 0x2553, 0x2554, 0x2555, 0x2556, // a0 Russian + 0x2557, 0x2558, 0x2559, 0x255a, 0x255b, 0x255c, 0x255d, 0x255e, // a8 + 0x255f, 0x2560, 0x2561, 0x0401, 0x2562, 0x2563, 0x2564, 0x2565, // b0 + 0x2566, 0x2567, 0x2568, 0x2569, 0x256a, 0x256b, 0x256c, 0x00a9, // b8 + 0x044e, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, // c0 + 0x0445, 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, // c8 + 0x043f, 0x044f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, // d0 + 0x044c, 0x044b, 0x0437, 0x0448, 0x044d, 0x0449, 0x0447, 0x044a, // d8 + 0x042e, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, // e0 + 0x0425, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, // e8 + 0x041f, 0x042f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, // f0 + 0x042c, 0x042b, 0x0417, 0x0428, 0x042d, 0x0429, 0x0427, 0x042a, // f8 +}; + +static const unsigned short ISCII[0x60] = { + 0x0020, 0x0901, 0x0902, 0x0903, 0x0905, 0x0906, 0x0907, 0x0908, // a0 + 0x0909, 0x090a, 0x090b, 0x090e, 0x090f, 0x0910, 0x090d, 0x0912, // a8 + 0x0913, 0x0914, 0x0911, 0x0915, 0x0916, 0x0917, 0x0918, 0x0919, // b0 + 0x091a, 0x091b, 0x091c, 0x091d, 0x091e, 0x091f, 0x0920, 0x0921, // b8 + 0x0922, 0x0923, 0x0924, 0x0925, 0x0926, 0x0927, 0x0928, 0x0929, // c0 + 0x092a, 0x092b, 0x092c, 0x092d, 0x092e, 0x092f, 0x095f, 0x0930, // c8 + 0x0931, 0x0932, 0x0933, 0x0934, 0x0935, 0x0936, 0x0937, 0x0938, // d0 + 0x0939, 0x0020, 0x093e, 0x093f, 0x0940, 0x0941, 0x0942, 0x0943, // d8 + 0x0946, 0x0947, 0x0948, 0x0945, 0x094a, 0x094b, 0x094c, 0x0949, // e0 + 0x094d, 0x093c, 0x0964, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, // e8 + 0x0020, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, // f0 + 0x0037, 0x0038, 0x0039, 0x20, 0x20, 0x20, 0x20, 0x20, // f8 +}; + +const unsigned short *charsets[N_CHARSETS] = { + ISO_8859_1, + ISO_8859_1, + ISO_8859_2, + ISO_8859_3, + ISO_8859_4, + ISO_8859_5, + ISO_8859_1, + ISO_8859_7, + ISO_8859_1, + ISO_8859_9, + ISO_8859_1, + ISO_8859_1, + ISO_8859_1, + ISO_8859_1, + ISO_8859_14, + ISO_8859_1, + ISO_8859_1, + ISO_8859_1, + KOI8_R, // 18 + ISCII }; + +// Tables of the relative lengths of vowels, depending on the +// type of the two phonemes that follow +// indexes are the "length_mod" value for the following phonemes + +// use this table if vowel is not the last in the word +static unsigned char length_mods_en[100] = { +/* a , t s n d z r N <- next */ + 100,120,100,105,100,110,110,100, 95, 100, /* a <- next2 */ + 105,120,105,110,125,130,135,115,125, 100, /* , */ + 105,120, 75,100, 75,105,120, 85, 75, 100, /* t */ + 105,120, 85,105, 95,115,120,100, 95, 100, /* s */ + 110,120, 95,105,100,115,120,100,100, 100, /* n */ + 105,120,100,105, 95,115,120,110, 95, 100, /* d */ + 105,120,100,105,105,122,125,110,105, 100, /* z */ + 105,120,100,105,105,122,125,110,105, 100, /* r */ + 105,120, 95,105,100,115,120,110,100, 100, /* N */ + 100,120,100,100,100,100,100,100,100, 100 }; // SPARE + +// as above, but for the last syllable in a word +static unsigned char length_mods_en0[100] = { +/* a , t s n d z r N <- next */ + 100,150,100,105,110,115,110,110,110, 100, /* a <- next2 */ + 105,150,105,110,125,135,140,115,135, 100, /* , */ + 105,150, 90,105, 90,122,135,100, 90, 100, /* t */ + 105,150,100,105,100,122,135,100,100, 100, /* s */ + 105,150,100,105,105,115,135,110,105, 100, /* n */ + 105,150,100,105,105,122,130,120,125, 100, /* d */ + 105,150,100,105,110,122,125,115,110, 100, /* z */ + 105,150,100,105,105,122,135,120,105, 100, /* r */ + 105,150,100,105,105,115,135,110,105, 100, /* N */ + 100,100,100,100,100,100,100,100,100, 100 }; // SPARE + + +static unsigned char length_mods_equal[100] = { +/* a , t s n d z r N <- next */ + 110,110,110,110,110,110,110,110,110, 110, /* a <- next2 */ + 110,110,110,110,110,110,110,110,110, 110, /* , */ + 110,110,110,110,110,110,110,110,110, 110, /* t */ + 110,110,110,110,110,110,110,110,110, 110, /* s */ + 110,110,110,110,110,110,110,110,110, 110, /* n */ + 110,110,110,110,110,110,110,110,110, 110, /* d */ + 110,110,110,110,110,110,110,110,110, 110, /* z */ + 110,110,110,110,110,110,110,110,110, 110, /* r */ + 110,110,110,110,110,110,110,110,110, 110, /* N */ + 110,110,110,110,110,110,110,110,110, 110 }; // SPARE + + +static unsigned char *length_mod_tabs[6] = { + length_mods_en, + length_mods_en, // 1 + length_mods_en0, // 2 + length_mods_equal, // 3 + length_mods_equal, // 4 + length_mods_equal // 5 + }; + + +void SetLengthMods(Translator *tr, int value) +{//========================================== + int value2; + + tr->langopts.length_mods0 = tr->langopts.length_mods = length_mod_tabs[value % 100]; + if((value2 = value / 100) != 0) + { + tr->langopts.length_mods0 = length_mod_tabs[value2]; + } +} + + +int IsAlpha(unsigned int c) +{//======================== +// Replacement for iswalph() which also checks for some in-word symbols + + if(iswalpha(c)) + return(1); + + if((c >= 0x901) && (c <= 0xdf7)) + { + // Indic scripts: Devanagari, Tamil, etc + if((c & 0x7f) < 0x64) + return(1); + return(0); + } + + if((c >= 0x300) && (c <= 0x36f)) + return(1); // combining accents + + if((c >= 0x1100) && (c <= 0x11ff)) + return(1); //Korean jamo + + if((c > 0x3040) && (c <= 0xa700)) + return(1); // Chinese/Japanese. Should never get here, but Mac OS 10.4's iswalpha seems to be broken, so just make sure + + return(0); +} + +static int IsDigit09(unsigned int c) +{//========================= + if((c >= '0') && (c <= '9')) + return(1); + return(0); +} + +int IsDigit(unsigned int c) +{//======================== + if(iswdigit(c)) + return(1); + + if((c >= 0x966) && (c <= 0x96f)) + return(1); + + return(0); +} + +int IsSpace(unsigned int c) +{//======================== + if(c == 0) + return(0); + if(wcschr(chars_space,c)) + return(1); + return(iswspace(c)); +} + + +void DeleteTranslator(Translator *tr) +{//================================== + if(tr->data_dictlist != NULL) + Free(tr->data_dictlist); + Free(tr); +} + + +int lookupwchar(const unsigned short *list,int c) +{//============================================== +// Is the character c in the list ? + int ix; + + for(ix=0; list[ix] != 0; ix++) + { + if(list[ix] == c) + return(ix+1); + } + return(0); +} + +int IsBracket(int c) +{//================= + if((c >= 0x2014) && (c <= 0x201f)) + return(1); + return(lookupwchar(brackets,c)); +} + + +int utf8_out(unsigned int c, char *buf) +{//==================================== +// write a unicode character into a buffer as utf8 +// returns the number of bytes written + int n_bytes; + int j; + int shift; + static char unsigned code[4] = {0,0xc0,0xe0,0xf0}; + + if(c < 0x80) + { + buf[0] = c; + return(1); + } + if(c >= 0x110000) + { + buf[0] = ' '; // out of range character code + return(1); + } + if(c < 0x0800) + n_bytes = 1; + else + if(c < 0x10000) + n_bytes = 2; + else + n_bytes = 3; + + shift = 6*n_bytes; + buf[0] = code[n_bytes] | (c >> shift); + for(j=0; j<n_bytes; j++) + { + shift -= 6; + buf[j+1] = 0x80 + ((c >> shift) & 0x3f); + } + return(n_bytes+1); +} // end of utf8_out + + +int utf8_nbytes(const char *buf) +{//============================= +// Returns the number of bytes for the first UTF-8 character in buf + unsigned char c = (unsigned char)buf[0]; + if(c < 0x80) + return(1); + if(c < 0xe0) + return(2); + if(c < 0xf0) + return(3); + return(4); +} + + +int utf8_in2(int *c, const char *buf, int backwards) +{//================================================= +// Read a unicode characater from a UTF8 string +// Returns the number of UTF8 bytes used. +// backwards: set if we are moving backwards through the UTF8 string + int c1; + int n_bytes; + int ix; + static const unsigned char mask[4] = {0xff,0x1f,0x0f,0x07}; + + // find the start of the next/previous character + while((*buf & 0xc0) == 0x80) + { + // skip over non-initial bytes of a multi-byte utf8 character + if(backwards) + buf--; + else + buf++; + } + + n_bytes = 0; + + if((c1 = *buf++) & 0x80) + { + if((c1 & 0xe0) == 0xc0) + n_bytes = 1; + else + if((c1 & 0xf0) == 0xe0) + n_bytes = 2; + else + if((c1 & 0xf8) == 0xf0) + n_bytes = 3; + + c1 &= mask[n_bytes]; + for(ix=0; ix<n_bytes; ix++) + { + c1 = (c1 << 6) + (*buf++ & 0x3f); + } + } + *c = c1; + return(n_bytes+1); +} + + +int utf8_in(int *c, const char *buf) +{//================================= +// Read a unicode characater from a UTF8 string +// Returns the number of UTF8 bytes used. + return(utf8_in2(c,buf,0)); +} + + +char *strchr_w(const char *s, int c) +{//================================= +// return NULL for any non-ascii character + if(c >= 0x80) + return(NULL); + return(strchr((char *)s,c)); // (char *) is needed for Borland compiler +} + + + + +int TranslateWord(Translator *tr, char *word1, int next_pause, WORD_TAB *wtab) +{//=========================================================================== +// word1 is terminated by space (0x20) character + + int length; + int word_length; + int ix; + int posn; + int pfix; + int n_chars; + unsigned int dictionary_flags[2]; + unsigned int dictionary_flags2[2]; + int end_type=0; + int prefix_type=0; + char *wordx; + char phonemes[N_WORD_PHONEMES]; + char *ph_limit; + char *phonemes_ptr; + char prefix_phonemes[N_WORD_PHONEMES]; + char end_phonemes[N_WORD_PHONEMES]; + char word_copy[N_WORD_BYTES]; + char prefix_chars[N_WORD_BYTES]; + int found=0; + int end_flags; + char c_temp; // save a character byte while we temporarily replace it with space + int first_char; + int last_char = 0; + int unpron_length; + int add_plural_suffix = 0; + int prefix_flags = 0; + int confirm_prefix; + int spell_word; + int stress_bits; + int emphasize_allcaps = 0; + int wflags = wtab->flags; + int wmark = wtab->wmark; + + // translate these to get pronunciations of plural 's' suffix (different forms depending on + // the preceding letter + static char word_zz[4] = {0,'z','z',0}; + static char word_iz[4] = {0,'i','z',0}; + static char word_ss[4] = {0,'s','s',0}; + + dictionary_flags[0] = 0; + dictionary_flags[1] = 0; + dictionary_flags2[0] = 0; + dictionary_flags2[1] = 0; + dictionary_skipwords = 0; + + prefix_phonemes[0] = 0; + end_phonemes[0] = 0; + ph_limit = &phonemes[N_WORD_PHONEMES]; + + // count the length of the word + wordx = word1; + utf8_in(&first_char,wordx); + word_length = 0; + while((*wordx != 0) && (*wordx != ' ')) + { + wordx += utf8_in(&last_char,wordx); + word_length++; + } + + // try an initial lookup in the dictionary list, we may find a pronunciation specified, or + // we may just find some flags + spell_word = 0; + if(option_sayas == SAYAS_KEY) + { + if(word_length == 1) + spell_word = 4; + } + + if(option_sayas & 0x10) + { + // SAYAS_CHAR, SAYAS_GYLPH, or SAYAS_SINGLE_CHAR + spell_word = option_sayas & 0xf; // 2,3,4 + } + else + { + found = LookupDictList(tr, &word1, phonemes, dictionary_flags, FLAG_ALLOW_TEXTMODE, wtab); // the original word + if(dictionary_flags[0] & FLAG_TEXTMODE) + { + stress_bits = dictionary_flags[0] & 0x7f; + found = LookupDictList(tr, &word1, phonemes, dictionary_flags2, 0, wtab); // the text replacement + if(dictionary_flags2[0]!=0) + { + dictionary_flags[0] = dictionary_flags2[0]; + dictionary_flags[1] = dictionary_flags2[1]; + if(stress_bits != 0) + { + // keep any stress information from the original word + dictionary_flags[0] = (dictionary_flags[0] & ~0x7f) | stress_bits; + } + } + } + else + if((found==0) && (dictionary_flags[0] & FLAG_SKIPWORDS)) + { + // grouped words, but no translation. Join the words with hyphens. + wordx = word1; + ix = 0; + while(ix < dictionary_skipwords) + { + if(*wordx == ' ') + { + *wordx = '-'; + ix++; + } + wordx++; + } + } + + // if textmode, LookupDictList() replaces word1 by the new text and returns found=0 + + if(phonemes[0] == phonSWITCH) + { + // change to another language in order to translate this word + strcpy(word_phonemes,phonemes); + return(0); + } + +if((wmark > 0) && (wmark < 8)) +{ + // the stressed syllable has been specified in the text (TESTING) + dictionary_flags[0] = (dictionary_flags[0] & ~0xf) | wmark; +} + + if(!found && (dictionary_flags[0] & FLAG_ABBREV)) + { + // the word has $abbrev flag, but no pronunciation specified. Speak as individual letters + spell_word = 1; + } + + if(!found && iswdigit(first_char)) + { + Lookup(tr,"_0lang",word_phonemes); + if(word_phonemes[0] == phonSWITCH) + return(0); + + found = TranslateNumber(tr,word1,phonemes,dictionary_flags,wflags); + } + + if(!found & ((wflags & FLAG_UPPERS) != FLAG_FIRST_UPPER)) + { + // either all upper or all lower case + + if((tr->langopts.numbers & NUM_ROMAN) || ((tr->langopts.numbers & NUM_ROMAN_UC) && (wflags & FLAG_ALL_UPPER))) + { + if((found = TranslateRoman(tr, word1, phonemes)) != 0) + dictionary_flags[0] |= FLAG_ABBREV; // prevent emphasis if capitals + } + } + + if((wflags & FLAG_ALL_UPPER) && (word_length > 1)&& iswalpha(first_char)) + { + if((option_tone_flags & OPTION_EMPHASIZE_ALLCAPS) && !(dictionary_flags[0] & FLAG_ABBREV)) + { + // emphasize words which are in capitals + emphasize_allcaps = FLAG_EMPHASIZED; + } + else + if(!found && !(dictionary_flags[0] & FLAG_SKIPWORDS) && (word_length<4) && (tr->clause_lower_count > 3) + && (tr->clause_upper_count <= tr->clause_lower_count)) + { + // An upper case word in a lower case clause. This could be an abbreviation. + spell_word = 1; + } + } + } + + if(spell_word > 0) + { + // Speak as individual letters + wordx = word1; + posn = 0; + phonemes[0] = 0; + end_type = 0; + + while(*wordx != ' ') + { + wordx += TranslateLetter(tr,wordx, phonemes,spell_word, word_length); + posn++; + if(phonemes[0] == phonSWITCH) + { + // change to another language in order to translate this word + strcpy(word_phonemes,phonemes); + if(word_length > 1) + return(FLAG_SPELLWORD); // a mixture of languages, retranslate as individual letters, separated by spaces + return(0); + } + } + SetSpellingStress(tr,phonemes,spell_word,posn); + } + else + if(found == 0) + { + // word's pronunciation is not given in the dictionary list, although + // dictionary_flags may have ben set there + + posn = 0; + length = 999; + wordx = word1; + + while(((length < 3) && (length > 0))|| (word_length > 1 && Unpronouncable(tr,wordx))) + { + char *p; + // This word looks "unpronouncable", so speak letters individually until we + // find a remainder that we can pronounce. + emphasize_allcaps = 0; + wordx += TranslateLetter(tr,wordx,phonemes,0, word_length); + posn++; + if(phonemes[0] == phonSWITCH) + { + // change to another language in order to translate this word + strcpy(word_phonemes,phonemes); + if(strcmp(&phonemes[1],"en")==0) + return(FLAG_SPELLWORD); // _^_en must have been set in TranslateLetter(), not *_rules + return(0); + } + + p = &wordx[word_length-3]; // this looks wrong. Doesn't consider multi-byte chars. + if(memcmp(p,"'s ",3) == 0) + { + // remove a 's suffix and pronounce this separately (not as an individual letter) + add_plural_suffix = 1; + p[0] = ' '; + p[1] = ' '; + last_char = p[-1]; + } + + length=0; + while(wordx[length] != ' ') length++; + if(length > 0) + wordx[-1] = ' '; // prevent this affecting the pronunciation of the pronuncable part + } + SetSpellingStress(tr,phonemes,0,posn); + + // anything left ? + if(*wordx != ' ') + { + // Translate the stem + unpron_length = strlen(phonemes); + end_type = TranslateRules(tr, wordx, phonemes, N_WORD_PHONEMES, end_phonemes, wflags, dictionary_flags); + + if(phonemes[0] == phonSWITCH) + { + // change to another language in order to translate this word + strcpy(word_phonemes,phonemes); + return(0); + } + + if((phonemes[0] == 0) && (end_phonemes[0] == 0)) + { + int wc; + // characters not recognised, speak them individually + + utf8_in(&wc, wordx); + if((word_length == 1) && IsAlpha(wc)) + { + posn = 0; + while((*wordx != ' ') && (*wordx != 0)) + { + wordx += TranslateLetter(tr,wordx, phonemes, 4, word_length); + posn++; + if(phonemes[0] == phonSWITCH) + { + // change to another language in order to translate this word + strcpy(word_phonemes,phonemes); + return(0); + } + } + SetSpellingStress(tr,phonemes,spell_word,posn); + } + } + + c_temp = wordx[-1]; + + found = 0; + confirm_prefix = 1; + while(end_type & SUFX_P) + { + // Found a standard prefix, remove it and retranslate + + if(confirm_prefix && !(end_type & SUFX_B)) + { + int end2; + char phonemes2[N_WORD_PHONEMES]; + char end_phonemes2[N_WORD_PHONEMES]; + + // remove any standard suffix and confirm that the prefix is still recognised + phonemes2[0] = 0; + end2 = TranslateRules(tr, wordx, phonemes2, N_WORD_PHONEMES, end_phonemes2, wflags|FLAG_NO_PREFIX|FLAG_NO_TRACE, dictionary_flags); + if(end2) + { + RemoveEnding(tr, wordx, end2, word_copy); + end_type = TranslateRules(tr, wordx, phonemes, N_WORD_PHONEMES, end_phonemes, wflags|FLAG_NO_TRACE, dictionary_flags); + memcpy(wordx,word_copy,strlen(word_copy)); + if((end_type & SUFX_P) == 0) + { + // after removing the suffix, the prefix is no longer recognised. + // Keep the suffix, but don't use the prefix + end_type = end2; + strcpy(phonemes,phonemes2); + strcpy(end_phonemes,end_phonemes2); + if(option_phonemes == 2) + { + DecodePhonemes(end_phonemes,end_phonemes2); + fprintf(f_trans," suffix [%s]\n\n",end_phonemes2); + } + } + confirm_prefix = 0; + continue; + } + } + + prefix_type = end_type; + + if(prefix_type & SUFX_V) + { + tr->expect_verb = 1; // use the verb form of the word + } + + wordx[-1] = c_temp; + + if((prefix_type & SUFX_B) == 0) + { + for(ix=(prefix_type & 0xf); ix>0; ix--) // num. of characters to remove + { + wordx++; + while((*wordx & 0xc0) == 0x80) wordx++; // for multibyte characters + } + } + else + { + pfix = 1; + prefix_chars[0] = 0; + n_chars = prefix_type & 0x3f; + + for(ix=0; ix < n_chars; ix++) // num. of bytes to remove + { + prefix_chars[pfix++] = *wordx++; + + if((prefix_type & SUFX_B) && (ix == (n_chars-1))) + { + prefix_chars[pfix-1] = 0; // discard the last character of the prefix, this is the separator character + } + } + prefix_chars[pfix] = 0; + } + c_temp = wordx[-1]; + wordx[-1] = ' '; + confirm_prefix = 1; + + if(prefix_type & SUFX_B) + { +// SUFX_B is used for Turkish, tr_rules contains "(Pb£ + // retranslate the prefix part + char *wordpf; + char prefix_phonemes2[12]; + + strncpy0(prefix_phonemes2,end_phonemes,sizeof(prefix_phonemes2)); + wordpf = &prefix_chars[1]; + found = LookupDictList(tr, &wordpf, phonemes, dictionary_flags, SUFX_P, wtab); // without prefix + if(found == 0) + { + end_type = TranslateRules(tr, wordpf, phonemes, N_WORD_PHONEMES, end_phonemes, 0, dictionary_flags); + sprintf(prefix_phonemes,"%s%s%s",phonemes,end_phonemes,prefix_phonemes2); + } + prefix_flags = 1; + } + else + { + strcat(prefix_phonemes,end_phonemes); + } + end_phonemes[0] = 0; + + end_type = 0; + found = LookupDictList(tr, &wordx, phonemes, dictionary_flags2, SUFX_P, wtab); // without prefix + if(dictionary_flags[0]==0) + { + dictionary_flags[0] = dictionary_flags2[0]; + dictionary_flags[1] = dictionary_flags2[1]; + } + else + prefix_flags = 1; + if(found == 0) + { + end_type = TranslateRules(tr, wordx, phonemes, N_WORD_PHONEMES, end_phonemes, 0, dictionary_flags); + + if(phonemes[0] == phonSWITCH) + { + // change to another language in order to translate this word + wordx[-1] = c_temp; + strcpy(word_phonemes,phonemes); + return(0); + } + } + } + + if((end_type != 0) && !(end_type & SUFX_P)) + { +char phonemes2[N_WORD_PHONEMES]; +strcpy(phonemes2,phonemes); + + // The word has a standard ending, re-translate without this ending + end_flags = RemoveEnding(tr, wordx, end_type, word_copy); + + phonemes_ptr = &phonemes[unpron_length]; + phonemes_ptr[0] = 0; + + if(prefix_phonemes[0] != 0) + { + // lookup the stem without the prefix removed + wordx[-1] = c_temp; + found = LookupDictList(tr, &word1, phonemes_ptr, dictionary_flags2, end_flags, wtab); // include prefix, but not suffix + wordx[-1] = ' '; + if(dictionary_flags[0]==0) + { + dictionary_flags[0] = dictionary_flags2[0]; + dictionary_flags[1] = dictionary_flags2[1]; + } + if(found) + prefix_phonemes[0] = 0; // matched whole word, don't need prefix now + + if((found==0) && (dictionary_flags2[0] != 0)) + prefix_flags = 1; + } + if(found == 0) + { + found = LookupDictList(tr, &wordx, phonemes_ptr, dictionary_flags2, end_flags, wtab); // without prefix and suffix + if(phonemes_ptr[0] == phonSWITCH) + { + // change to another language in order to translate this word + memcpy(wordx,word_copy,strlen(word_copy)); + strcpy(word_phonemes,phonemes_ptr); + return(0); + } + if(dictionary_flags[0]==0) + { + dictionary_flags[0] = dictionary_flags2[0]; + dictionary_flags[1] = dictionary_flags2[1]; + } + } + if(found == 0) + { + if(end_type & SUFX_Q) + { + // don't retranslate, use the original lookup result + strcpy(phonemes,phonemes2); + + // language specific changes + ApplySpecialAttribute(tr,phonemes,dictionary_flags[0]); + } + else + { + if(end_flags & FLAG_SUFX) + TranslateRules(tr, wordx, phonemes, N_WORD_PHONEMES, NULL,wflags | FLAG_SUFFIX_REMOVED, dictionary_flags); + else + TranslateRules(tr, wordx, phonemes, N_WORD_PHONEMES, NULL,wflags,dictionary_flags); + + if(phonemes[0] == phonSWITCH) + { + // change to another language in order to translate this word + strcpy(word_phonemes,phonemes); + memcpy(wordx,word_copy,strlen(word_copy)); + wordx[-1] = c_temp; + return(0); + } + } + } + + if((end_type & SUFX_T) == 0) + { + // the default is to add the suffix and then determine the word's stress pattern + AppendPhonemes(tr,phonemes, N_WORD_PHONEMES, end_phonemes); + end_phonemes[0] = 0; + } + } + wordx[-1] = c_temp; + } + } + + if((add_plural_suffix) || (wflags & FLAG_HAS_PLURAL)) + { + // s or 's suffix, append [s], [z] or [Iz] depending on previous letter + if(last_char == 'f') + TranslateRules(tr, &word_ss[1], phonemes, N_WORD_PHONEMES, NULL, 0, NULL); + else + if((last_char==0) || (strchr_w("hsx",last_char)==NULL)) + TranslateRules(tr, &word_zz[1], phonemes, N_WORD_PHONEMES, NULL, 0, NULL); + else + TranslateRules(tr, &word_iz[1], phonemes, N_WORD_PHONEMES, NULL, 0, NULL); + } + + wflags |= emphasize_allcaps; + + + /* determine stress pattern for this word */ + /******************************************/ + /* NOTE: this also adds a single PAUSE if the previous word ended + in a primary stress, and this one starts with one */ + if(prefix_flags || (strchr(prefix_phonemes,phonSTRESS_P)!=NULL)) + { + if((tr->langopts.param[LOPT_PREFIXES]) || (prefix_type & SUFX_T)) + { + char *p; + // German, keep a secondary stress on the stem + SetWordStress(tr, phonemes, dictionary_flags[0], 3, 0); + + // reduce all but the first primary stress + ix=0; + for(p=prefix_phonemes; *p != 0; p++) + { + if(*p == phonSTRESS_P) + { + if(ix==0) + ix=1; + else + *p = phonSTRESS_3; + } + } + strcpy(word_phonemes,prefix_phonemes); + strcat(word_phonemes,phonemes); + SetWordStress(tr, word_phonemes, dictionary_flags[0], -1, 0); + } + else + { + // stress position affects the whole word, including prefix + strcpy(word_phonemes,prefix_phonemes); + strcat(word_phonemes,phonemes); + SetWordStress(tr, word_phonemes, dictionary_flags[0], -1, tr->prev_last_stress); + } + } + else + { + if(prefix_phonemes[0] == 0) + SetWordStress(tr, phonemes, dictionary_flags[0], -1, tr->prev_last_stress); + else + SetWordStress(tr, phonemes, dictionary_flags[0], -1, 0); + strcpy(word_phonemes,prefix_phonemes); + strcat(word_phonemes,phonemes); + } + + if(end_phonemes[0] != 0) + { + // a suffix had the SUFX_T option set, add the suffix after the stress pattern has been determined + strcat(word_phonemes,end_phonemes); + } + + if(wflags & FLAG_LAST_WORD) + { + // don't use $brk pause before the last word of a sentence + // (but allow it for emphasis, see below + dictionary_flags[0] &= ~FLAG_PAUSE1; + } + + if(wflags & FLAG_EMPHASIZED2) + { + // A word is indicated in the source text as stressed + // Give it stress level 6 (for the intonation module) + ChangeWordStress(tr,word_phonemes,6); + + if(wflags & FLAG_EMPHASIZED) + dictionary_flags[0] |= FLAG_PAUSE1; // precede by short pause + } + else + if(wtab[dictionary_skipwords].flags & FLAG_LAST_WORD) + { + // the word has attribute to stress or unstress when at end of clause + if(dictionary_flags[0] & (FLAG_STRESS_END | FLAG_STRESS_END2)) + ChangeWordStress(tr,word_phonemes,4); + else + if(dictionary_flags[0] & FLAG_UNSTRESS_END) + ChangeWordStress(tr,word_phonemes,3); + } + + // dictionary flags for this word give a clue about which alternative pronunciations of + // following words to use. + if(end_type & SUFX_F) + { + // expect a verb form, with or without -s suffix + tr->expect_verb = 2; + tr->expect_verb_s = 2; + } + + if(dictionary_flags[1] & FLAG_PASTF) + { + /* expect perfect tense in next two words */ + tr->expect_past = 3; + tr->expect_verb = 0; + tr->expect_noun = 0; + } + else + if(dictionary_flags[1] & FLAG_VERBF) + { + /* expect a verb in the next word */ + tr->expect_verb = 2; + tr->expect_verb_s = 0; /* verb won't have -s suffix */ + tr->expect_noun = 0; + } + else + if(dictionary_flags[1] & FLAG_VERBSF) + { + // expect a verb, must have a -s suffix + tr->expect_verb = 0; + tr->expect_verb_s = 2; + tr->expect_past = 0; + tr->expect_noun = 0; + } + else + if(dictionary_flags[1] & FLAG_NOUNF) + { + /* not expecting a verb next */ + tr->expect_noun = 3; + tr->expect_verb = 0; + tr->expect_verb_s = 0; + tr->expect_past = 0; + } + + if((wordx[0] != 0) && (!(dictionary_flags[1] & FLAG_VERB_EXT))) + { + if(tr->expect_verb > 0) + tr->expect_verb--; + + if(tr->expect_verb_s > 0) + tr->expect_verb_s--; + + if(tr->expect_noun >0) + tr->expect_noun--; + + if(tr->expect_past > 0) + tr->expect_past--; + } + + if((word_length == 1) && iswalpha(first_char) && (first_char != 'i')) + { +// English Specific !!!! + // any single letter before a dot is an abbreviation, except 'I' + dictionary_flags[0] |= FLAG_DOT; + } + + return(dictionary_flags[0]); +} // end of TranslateWord + + + +static void SetPlist2(PHONEME_LIST2 *p, unsigned char phcode) +{//========================================================== + p->phcode = phcode; + p->stress = 0; + p->tone_number = 0; + p->synthflags = 0; + p->sourceix = 0; +} + +static int CountSyllables(unsigned char *phonemes) +{//=============================================== + int count = 0; + int phon; + while((phon = *phonemes++) != 0) + { + if(phoneme_tab[phon]->type == phVOWEL) + count++; + } + return(count); +} + + +int SetTranslator2(const char *new_language) +{//========================================= +// Set translator2 to a second language + int new_phoneme_tab; + + if((new_phoneme_tab = SelectPhonemeTableName(new_language)) >= 0) + { + if((translator2 != NULL) && (strcmp(new_language,translator2_language) != 0)) + { + // we already have an alternative translator, but not for the required language, delete it + DeleteTranslator(translator2); + translator2 = NULL; + } + + if(translator2 == NULL) + { + translator2 = SelectTranslator(new_language); + strcpy(translator2_language,new_language); + + if(LoadDictionary(translator2, new_language, 0) != 0) + { + SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table + new_phoneme_tab = -1; + translator2_language[0] = 0; + } + } + } + return(new_phoneme_tab); +} // end of SetTranslator2 + + + +static int TranslateWord2(Translator *tr, char *word, WORD_TAB *wtab, int pre_pause, int next_pause) +{//================================================================================================= + int flags=0; + int stress; + int next_stress; + int next_tone=0; + unsigned char *p; + int srcix; + int embedded_flag=0; + int embedded_cmd; + int value; + int found_dict_flag; + unsigned char ph_code; + PHONEME_LIST2 *plist2; + PHONEME_TAB *ph; + int max_stress; + int max_stress_ix=0; + int prev_vowel = -1; + int pitch_raised = 0; + int switch_phonemes = -1; + int first_phoneme = 1; + int source_ix; + int len; + int ix; + int sylimit; // max. number of syllables in a word to be combined with a preceding preposition + const char *new_language; + unsigned char bad_phoneme[4]; + int word_flags; + int word_copy_len; + char word_copy[N_WORD_BYTES+1]; + + len = wtab->length; + if(len > 31) len = 31; + source_ix = (wtab->sourceix & 0x7ff) | (len << 11); // bits 0-10 sourceix, bits 11-15 word length + + word_flags = wtab[0].flags; + if(word_flags & FLAG_EMBEDDED) + { + embedded_flag = SFLAG_EMBEDDED; + + do + { + embedded_cmd = embedded_list[embedded_read++]; + value = embedded_cmd >> 8; + + switch(embedded_cmd & 0x1f) + { + case EMBED_Y: + option_sayas = value; + break; + + case EMBED_F: + option_emphasis = value; + break; + + case EMBED_B: + // break command + if(value == 0) + pre_pause = 0; // break=none + else + pre_pause += value; + break; + } + } while((embedded_cmd & 0x80) == 0); + } + + if(word[0] == 0) + { + // nothing to translate + word_phonemes[0] = 0; + return(0); + } + + // after a $pause word attribute, ignore a $pause attribute on the next two words + if(tr->prepause_timeout > 0) + tr->prepause_timeout--; + + if((option_sayas & 0xf0) == 0x10) + { + if(!(word_flags & FLAG_FIRST_WORD)) + { + // SAYAS_CHARS, SAYAS_GLYPHS, or SAYAS_SINGLECHARS. Pause between each word. + pre_pause += 4; + } + } + + if(word_flags & FLAG_FIRST_UPPER) + { + if((option_capitals > 2) && (embedded_ix < N_EMBEDDED_LIST-6)) + { + // indicate capital letter by raising pitch + if(embedded_flag) + embedded_list[embedded_ix-1] &= ~0x80; // already embedded command before this word, remove terminator + if((pitch_raised = option_capitals) == 3) + pitch_raised = 20; // default pitch raise for capitals + embedded_list[embedded_ix++] = EMBED_P+0x40+0x80 + (pitch_raised << 8); // raise pitch + embedded_flag = SFLAG_EMBEDDED; + } + } + + p = (unsigned char *)word_phonemes; + if(word_flags & FLAG_PHONEMES) + { + // The input is in phoneme mnemonics, not language text + int c1; + char lang_name[12]; + + if(memcmp(word,"_^_",3)==0) + { + // switch languages + word+=3; + for(ix=0;;) + { + c1 = *word++; + if((c1==' ') || (c1==0)) + break; + lang_name[ix++] = tolower(c1); + } + lang_name[ix] = 0; + + if((ix = LookupPhonemeTable(lang_name)) > 0) + { + SelectPhonemeTable(ix); + word_phonemes[0] = phonSWITCH; + word_phonemes[1] = ix; + word_phonemes[2] = 0; + } + } + else + { + EncodePhonemes(word,word_phonemes,bad_phoneme); + } + flags = FLAG_FOUND; + } + else + { + int c2; + ix = 0; + while(((c2 = word_copy[ix] = word[ix]) != ' ') && (c2 != 0) && (ix < N_WORD_BYTES)) ix++; + word_copy_len = ix; + + flags = TranslateWord(translator, word, next_pause, wtab); + + if(flags & FLAG_SPELLWORD) + { + // re-translate the word as individual letters, separated by spaces + memcpy(word, word_copy, word_copy_len); + return(flags); + } + + if((flags & FLAG_ALT2_TRANS) && ((sylimit = tr->langopts.param[LOPT_COMBINE_WORDS]) > 0)) + { + char *p2; + int ok = 1; + int flags2; + int c_word2; + char ph_buf[N_WORD_PHONEMES]; + + // LANG=cs,sk + // combine a preposition with the following word + p2 = word; + while(*p2 != ' ') p2++; + + utf8_in(&c_word2, p2+1); // first character of the next word; + if(!iswalpha(c_word2)) + { + ok =0; + } + + if(ok != 0) + { + if(sylimit & 0x100) + { + // only if the second word has $alt attribute + strcpy(ph_buf,word_phonemes); + flags2 = TranslateWord(translator, p2+1, 0, wtab+1); + if((flags2 & FLAG_ALT_TRANS) == 0) + { + ok = 0; + strcpy(word_phonemes,ph_buf); + } + } + + if((sylimit & 0x200) && ((wtab+1)->flags & FLAG_LAST_WORD)) + { + // not if the next word is end-of-sentence + ok = 0; + } + } + + if(ok) + { + *p2 = '-'; // replace next space by hyphen + flags = TranslateWord(translator, word, next_pause, wtab); // translate the combined word + if(CountSyllables(p) > (sylimit & 0xf)) + { + // revert to separate words + *p2 = ' '; + flags = TranslateWord(translator, word, next_pause, wtab); + } + else + { + flags |= FLAG_SKIPWORDS; + dictionary_skipwords = 1; + } + } + } + + if(p[0] == phonSWITCH) + { + // this word uses a different language + memcpy(word, word_copy, word_copy_len); + + new_language = (char *)(&p[1]); + if(new_language[0]==0) + new_language = "en"; + + switch_phonemes = SetTranslator2(new_language); + + if(switch_phonemes >= 0) + { + // re-translate the word using the new translator + flags = TranslateWord(translator2, word, next_pause, wtab); +// strcpy((char *)p,translator2->word_phonemes); + if(p[0] == phonSWITCH) + { + // the second translator doesn't want to process this word + switch_phonemes = -1; + } + } + if(switch_phonemes < 0) + { + // language code is not recognised or 2nd translator won't translate it + p[0] = phonSCHWA; // just say something + p[1] = phonSCHWA; + p[2] = 0; + } + } + + if(!(word_flags & FLAG_HYPHEN)) + { + if(flags & FLAG_PAUSE1) + { + if(pre_pause < 1) + pre_pause = 1; + } + if((flags & FLAG_PREPAUSE) && (tr->prepause_timeout == 0)) + { + // the word is marked in the dictionary list with $pause + if(pre_pause < 4) pre_pause = 4; + tr->prepause_timeout = 3; + } + } + + if((option_emphasis >= 3) && (pre_pause < 1)) + pre_pause = 1; + } + + plist2 = &ph_list2[n_ph_list2]; + stress = 0; + next_stress = 0; + srcix = 0; + max_stress = -1; + + found_dict_flag = 0; + if(flags & FLAG_FOUND) + found_dict_flag = SFLAG_DICTIONARY; + + while((pre_pause > 0) && (n_ph_list2 < N_PHONEME_LIST-4)) + { + // add pause phonemes here. Either because of punctuation (brackets or quotes) in the + // text, or because the word is marked in the dictionary lookup as a conjunction + if(pre_pause > 1) + { + SetPlist2(&ph_list2[n_ph_list2++],phonPAUSE); + pre_pause -= 2; + } + else + { + SetPlist2(&ph_list2[n_ph_list2++],phonPAUSE_NOLINK); + pre_pause--; + } + tr->end_stressed_vowel = 0; // forget about the previous word + tr->prev_dict_flags = 0; + } + + if((option_capitals==1) && (word_flags & FLAG_FIRST_UPPER)) + { + SetPlist2(&ph_list2[n_ph_list2++],phonPAUSE_SHORT); + SetPlist2(&ph_list2[n_ph_list2++],phonCAPITAL); + if((word_flags & FLAG_ALL_UPPER) && IsAlpha(word[1])) + { + // word > 1 letter and all capitals + SetPlist2(&ph_list2[n_ph_list2++],phonPAUSE_SHORT); + SetPlist2(&ph_list2[n_ph_list2++],phonCAPITAL); + } + } + + if(switch_phonemes >= 0) + { + // this word uses a different phoneme table + SetPlist2(&ph_list2[n_ph_list2],phonSWITCH); + ph_list2[n_ph_list2++].tone_number = switch_phonemes; // temporary phoneme table number + } + + // remove initial pause from a word if it follows a hyphen + if((word_flags & FLAG_HYPHEN) && (phoneme_tab[*p]->type == phPAUSE)) + p++; + + while(((ph_code = *p++) != 0) && (n_ph_list2 < N_PHONEME_LIST-4)) + { + if(ph_code == 255) + continue; // unknown phoneme + + // Add the phonemes to the first stage phoneme list (ph_list2) + ph = phoneme_tab[ph_code]; + + if(ph_code == phonSWITCH) + { + ph_list2[n_ph_list2].phcode = ph_code; + ph_list2[n_ph_list2].sourceix = 0; + ph_list2[n_ph_list2].synthflags = embedded_flag; + ph_list2[n_ph_list2++].tone_number = *p++; + } + else + if(ph->type == phSTRESS) + { + // don't add stress phonemes codes to the list, but give their stress + // value to the next vowel phoneme + // std_length is used to hold stress number or (if >10) a tone number for a tone language + if(ph->spect == 0) + next_stress = ph->std_length; + else + { + // for tone languages, the tone number for a syllable follows the vowel + if(prev_vowel >= 0) + { + ph_list2[prev_vowel].tone_number = ph_code; + } + else + { + next_tone = ph_code; // no previous vowel, apply to the next vowel + } + } + } + else + if(ph_code == phonSYLLABIC) + { + // mark the previous phoneme as a syllabic consonant + prev_vowel = n_ph_list2-1; + ph_list2[prev_vowel].synthflags |= SFLAG_SYLLABLE; + ph_list2[prev_vowel].stress = next_stress; + } + else + if(ph_code == phonLENGTHEN) + { + ph_list2[n_ph_list2-1].synthflags |= SFLAG_LENGTHEN; + } + else + if(ph_code == phonEND_WORD) + { + // a || symbol in a phoneme string was used to indicate a word boundary + // Don't add this phoneme to the list, but make sure the next phoneme has + // a newword indication + srcix = source_ix+1; + } + else + if(ph_code == phonX1) + { + // a language specific action + if(tr->langopts.param[LOPT_IT_DOUBLING]) + { + flags |= FLAG_DOUBLING; + } + } + else + { + ph_list2[n_ph_list2].phcode = ph_code; + ph_list2[n_ph_list2].tone_number = 0; + ph_list2[n_ph_list2].synthflags = embedded_flag | found_dict_flag; + embedded_flag = 0; + ph_list2[n_ph_list2].sourceix = srcix; + srcix = 0; + + if(ph->type == phVOWEL) + { + stress = next_stress; + next_stress = 0; + + if((prev_vowel >= 0) && (n_ph_list2-1) != prev_vowel) + ph_list2[n_ph_list2-1].stress = stress; // set stress for previous consonant + + ph_list2[n_ph_list2].synthflags |= SFLAG_SYLLABLE; + prev_vowel = n_ph_list2; + + if(stress > max_stress) + { + max_stress = stress; + max_stress_ix = n_ph_list2; + } + if(next_tone != 0) + { + ph_list2[n_ph_list2].tone_number = next_tone; + next_tone=0; + } + } + else + { + if(first_phoneme && tr->langopts.param[LOPT_IT_DOUBLING]) + { + if(((tr->prev_dict_flags & FLAG_DOUBLING) && (tr->langopts.param[LOPT_IT_DOUBLING] & 1)) || + (tr->end_stressed_vowel && (tr->langopts.param[LOPT_IT_DOUBLING] & 2))) + { + // italian, double the initial consonant if the previous word ends with a + // stressed vowel, or is marked with a flag + ph_list2[n_ph_list2].synthflags |= SFLAG_LENGTHEN; + } + } + } + + ph_list2[n_ph_list2].stress = stress; + n_ph_list2++; + first_phoneme = 0; + } + } + // don't set new-word if there is a hyphen before it + if((word_flags & FLAG_HYPHEN) == 0) + { + plist2->sourceix = source_ix; + } + + tr->end_stressed_vowel = 0; + if((stress >= 4) && (phoneme_tab[ph_list2[n_ph_list2-1].phcode]->type == phVOWEL)) + { + tr->end_stressed_vowel = 1; // word ends with a stressed vowel + } + + if(switch_phonemes >= 0) + { + // this word uses a different phoneme table, now switch back + SelectPhonemeTable(voice->phoneme_tab_ix); + SetPlist2(&ph_list2[n_ph_list2],phonSWITCH); + ph_list2[n_ph_list2++].tone_number = voice->phoneme_tab_ix; // original phoneme table number + } + + + if(pitch_raised > 0) + { + embedded_list[embedded_ix++] = EMBED_P+0x60+0x80 + (pitch_raised << 8); // lower pitch + SetPlist2(&ph_list2[n_ph_list2],phonPAUSE_SHORT); + ph_list2[n_ph_list2++].synthflags = SFLAG_EMBEDDED; + } + + if(flags & FLAG_STRESS_END2) + { + // this's word's stress could be increased later + ph_list2[max_stress_ix].synthflags |= SFLAG_PROMOTE_STRESS; + } + + tr->prev_dict_flags = flags; + return(flags); +} // end of TranslateWord2 + + + +static int EmbeddedCommand(unsigned int &source_index) +{//=================================================== + // An embedded command to change the pitch, volume, etc. + // returns number of commands added to embedded_list + + // pitch,speed,amplitude,expression,reverb,tone,voice,sayas + const char *commands = "PSARHTIVYMUBF"; + int value = -1; + int sign = 0; + unsigned char c; + char *p; + int cmd; + + c = source[source_index]; + if(c == '+') + { + sign = 0x40; + source_index++; + } + else + if(c == '-') + { + sign = 0x60; + source_index++; + } + + if(isdigit(source[source_index])) + { + value = atoi(&source[source_index]); + while(isdigit(source[source_index])) + source_index++; + } + + c = source[source_index++]; + if(embedded_ix >= (N_EMBEDDED_LIST - 2)) + return(0); // list is full + + if((p = strchr_w(commands,c)) == NULL) + return(0); + cmd = (p - commands)+1; + if(value == -1) + { + value = embedded_default[cmd]; + sign = 0; + } + + if(cmd == EMBED_Y) + { + option_sayas2 = value; + count_sayas_digits = 0; + } + if(cmd == EMBED_F) + { + if(value >= 3) + word_emphasis = FLAG_EMPHASIZED; + else + word_emphasis = 0; + } + + embedded_list[embedded_ix++] = cmd + sign + (value << 8); + return(1); +} // end of EmbeddedCommand + + + +static int SubstituteChar(Translator *tr, unsigned int c, unsigned int next_in, int *insert) +{//========================================================================================= + int ix; + unsigned int word; + unsigned int new_c, c2, c_lower; + int upper_case = 0; + static int ignore_next = 0; + const unsigned int *replace_chars; + + if(ignore_next) + { + ignore_next = 0; + return(8); + } + if(c == 0) return(0); + + if((replace_chars = tr->langopts.replace_chars) == NULL) + return(c); + + // there is a list of character codes to be substituted with alternative codes + + if(iswupper(c_lower = c)) + { + c_lower = towlower(c); + upper_case = 1; + } + + new_c = 0; + for(ix=0; (word = replace_chars[ix]) != 0; ix+=2) + { + if(c_lower == (word & 0xffff)) + { + if((word >> 16) == 0) + { + new_c = replace_chars[ix+1]; + break; + } + if((word >> 16) == (unsigned int)towlower(next_in)) + { + new_c = replace_chars[ix+1]; + ignore_next = 1; + break; + } + } + } + + if(new_c == 0) + return(c); // no substitution + + if(new_c & 0xffe00000) + { + // there is a second character to be inserted + // don't convert the case of the second character unless the next letter is also upper case + c2 = new_c >> 16; + if(upper_case && iswupper(next_in)) + c2 = towupper(c2); + *insert = c2; + new_c &= 0xffff; + } + + if(upper_case) + new_c = towupper(new_c); + return(new_c); + +} + + +static int TranslateChar(Translator *tr, char *ptr, int prev_in, unsigned int c, unsigned int next_in, int *insert) +{//================================================================================================================ + // To allow language specific examination and replacement of characters + + int code; + int initial; + int medial; + int final; + int next2; + + static const unsigned char hangul_compatibility[0x34] = { + 0, 0x00,0x01,0xaa,0x02,0xac,0xad,0x03, + 0x04,0x05,0xb0,0xb1,0xb2,0xb3,0xb4,0xb4, + 0xb6,0x06,0x07,0x08,0xb9,0x09,0x0a,0xbc, + 0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x61, + 0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69, + 0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,0x70,0x71, + 0x72,0x73,0x74,0x75 }; + + switch(tr->translator_name) + { + case L('a','f'): + // look for 'n and replace by a special character (unicode: schwa) + + utf8_in(&next2, &ptr[1]); + + if(!iswalpha(prev_in)) + { + if((c == '\'') && (next_in == 'n') && IsSpace(next2)) + { + // n preceded by either apostrophe or U2019 "right single quotation mark" + ptr[0] = ' '; // delete the n + return(0x0259); // replace ' by unicode schwa character + } + } + break; + + case L('k','o'): + if(((code = c - 0xac00) >= 0) && (c <= 0xd7af)) + { + // break a syllable hangul into 2 or 3 individual jamo + initial = (code/28)/21; + medial = (code/28) % 21; + final = code % 28; + + if(initial == 11) + { + // null initial + c = medial + 0x1161; + if(final > 0) + *insert = final + 0x11a7; + } + else + { + // extact the initial and insert the remainder with a null initial + c = initial + 0x1100; + *insert = (11*28*21) + (medial*28) + final + 0xac00; + } + return(c); + } + else + if(((code = c - 0x3130) >= 0) && (code < 0x34)) + { + // Hangul compatibility jamo + return(hangul_compatibility[code] + 0x1100); + } + break; + } + return(SubstituteChar(tr,c,next_in,insert)); +} + + +void *TranslateClause(Translator *tr, FILE *f_text, const void *vp_input, int *tone_out, char **voice_change) +{//========================================================================================================== + int ix; + int c; + int cc; + unsigned int source_index=0; + unsigned int prev_source_index=0; + int prev_in; + int prev_out=' '; + int prev_out2; + int prev_in2=0; + int next_in; + int char_inserted=0; + int clause_pause; + int pre_pause=0; + int pre_pause_add=0; + int word_mark = 0; + int all_upper_case=FLAG_ALL_UPPER; + int finished; + int single_quoted; + int phoneme_mode = 0; + int dict_flags; // returned from dictionary lookup + int word_flags; // set here + int next_word_flags; + int embedded_count = 0; + int letter_count = 0; + int space_inserted = 0; + int syllable_marked = 0; + int decimal_sep_count = 0; + char *word; + char *p; + int j, k; + int n_digits; + + short charix[N_TR_SOURCE+1]; + WORD_TAB words[N_CLAUSE_WORDS]; + int word_count=0; // index into words + + char sbuf[N_TR_SOURCE]; + + int terminator; + int tone; + int tone2; + + + p_textinput = (char *)vp_input; + p_wchar_input = (wchar_t *)vp_input; + + embedded_ix = 0; + embedded_read = 0; + option_phoneme_input &= ~2; // clear bit 1 (temporary indication) + + if((clause_start_char = count_characters) < 0) + clause_start_char = 0; + clause_start_word = count_words + 1; + + for(ix=0; ix<N_TR_SOURCE; ix++) + charix[ix] = 0; + terminator = ReadClause(tr, f_text, source, charix, N_TR_SOURCE, &tone2); + + charix[N_TR_SOURCE] = count_characters; + + clause_pause = (terminator & 0xfff) * 10; // mS + tone = (terminator >> 12) & 0xf; + if(tone2 != 0) + { + // override the tone type + tone = tone2; + } + + for(p=source; *p != 0; p++) + { + if(!isspace2(*p)) + { + break; + } + } + if(*p == 0) + { + // No characters except spaces. This is not a sentence. + // Don't add this pause, just make up the previous pause to this value; + clause_pause -= max_clause_pause; + if(clause_pause < 0) + clause_pause = 0; + + terminator &= ~CLAUSE_BIT_SENTENCE; // clear sentence bit + max_clause_pause += clause_pause; + } + else + { + max_clause_pause = clause_pause; + } + + if(new_sentence) + { + count_sentences++; + if(skip_sentences > 0) + { + skip_sentences--; + if(skip_sentences == 0) + skipping_text = 0; + } + } + + memset(&ph_list2[0],0,sizeof(ph_list2[0])); + ph_list2[0].phcode = phonPAUSE_SHORT; + + n_ph_list2 = 1; + tr->prev_last_stress = 0; + tr->prepause_timeout = 0; + tr->expect_verb=0; + tr->expect_noun=0; + tr->expect_past=0; + tr->expect_verb_s=0; + tr->phonemes_repeat_count = 0; + tr->end_stressed_vowel=0; + tr->prev_dict_flags = 0; + + word_count = 0; + single_quoted = 0; + word_flags = 0; + next_word_flags = 0; + + sbuf[0] = 0; + sbuf[1] = ' '; + sbuf[2] = ' '; + ix = 3; + prev_in = ' '; + + words[0].start = ix; + words[0].flags = 0; + finished = 0; + + for(j=0; charix[j]==0; j++); + words[0].sourceix = charix[j]; + k = 0; + while(charix[j] != 0) + { + // count the number of characters (excluding multibyte continuation bytes) + if(charix[j++] != -1) + k++; + } + words[0].length = k; + + while(!finished && (ix < (int)sizeof(sbuf))&& (n_ph_list2 < N_PHONEME_LIST-4)) + { + prev_out2 = prev_out; + utf8_in2(&prev_out,&sbuf[ix-1],1); // prev_out = sbuf[ix-1]; + + if(tr->langopts.tone_numbers && IsDigit09(prev_out) && IsAlpha(prev_out2)) + { + // tone numbers can be part of a word, consider them as alphabetic + prev_out = 'a'; + } + + if(prev_in2 != 0) + { + prev_in = prev_in2; + prev_in2 = 0; + } + else + if(source_index > 0) + { + utf8_in2(&prev_in,&source[source_index-1],1); // prev_in = source[source_index-1]; + } + + prev_source_index = source_index; + + if(char_inserted) + { + c = char_inserted; + char_inserted = 0; + } + else + { + source_index += utf8_in(&cc,&source[source_index]); // cc = source[source_index++]; + c = cc; + } + utf8_in(&next_in,&source[source_index]); + + if((c == CTRL_EMBEDDED) || (c == ctrl_embedded)) + { + // start of embedded command in the text + int srcix = source_index-1; + + if(prev_in != ' ') + { + c = ' '; + prev_in2 = c; + source_index--; + } + else + { + embedded_count += EmbeddedCommand(source_index); + prev_in2 = prev_in; + // replace the embedded command by spaces + memset(&source[srcix],' ',source_index-srcix); + source_index = srcix; + continue; + } + } + + if(option_sayas2 == SAYAS_KEY) + { + if(((c == '_') || (c == '-')) && IsAlpha(prev_in)) + { + c = ' '; + } + c = towlower2(c); + } + + if(phoneme_mode) + { + all_upper_case = FLAG_PHONEMES; + + if((c == ']') && (next_in == ']')) + { + phoneme_mode = 0; + source_index++; + c = ' '; + } + } + else + if((option_sayas2 & 0xf0) == SAYAS_DIGITS) + { + if(iswdigit(c)) + { + count_sayas_digits++; + if(count_sayas_digits > (option_sayas2 & 0xf)) + { + // break after the specified number of digits + c = ' '; + space_inserted = 1; + count_sayas_digits = 0; + } + } + else + { + count_sayas_digits = 0; + if(iswdigit(prev_out)) + { + c = ' '; + space_inserted = 1; + } + } + } + else + if((option_sayas2 & 0x30) == 0) + { + // speak as words + +#ifdef deleted +if((c == '/') && (tr->langopts.testing & 2) && IsDigit09(next_in) && IsAlpha(prev_out)) +{ + // TESTING, explicit indication of stressed syllable by /2 after the word + word_mark = next_in-'0'; + source_index++; + c = ' '; +} +#endif + if((c == 0x92) || (c == 0xb4) || (c == 0x2019) || (c == 0x2032)) + c = '\''; // 'microsoft' quote or sexed closing single quote, or prime - possibly used as apostrophe + + if((c == '?') && IsAlpha(prev_out) && IsAlpha(next_in)) + { + // ? between two letters may be a smart-quote replaced by ? + c = '\''; + } + + if(c == CHAR_EMPHASIS) + { + // this character is a marker that the previous word is the focus of the clause + c = ' '; + word_flags |= FLAG_FOCUS; + } + + c = TranslateChar(tr, &source[source_index], prev_in,c, next_in, &char_inserted); // optional language specific function + if(c == 8) + continue; // ignore this character + + if(char_inserted) + next_in = char_inserted; + + // allow certain punctuation within a word (usually only apostrophe) + if(!IsAlpha(c) && !IsSpace(c) && (wcschr(tr->punct_within_word,c) == 0)) + { + if(IsAlpha(prev_out)) + { + if(tr->langopts.tone_numbers && IsDigit09(c) && !IsDigit09(next_in)) + { + // allow a tone number as part of the word + } + else + { + c = ' '; // ensure we have an end-of-word terminator + space_inserted = 1; + } + } + } + + if(iswdigit(prev_out)) + { + if(!iswdigit(c) && (c != '.') && (c != ',')) + { + c = ' '; // terminate digit string with a space + space_inserted = 1; + } + } + else + { + if(prev_in != ',') + { + decimal_sep_count = 0; + } + } + + if((c == '[') && (next_in == '[') && option_phoneme_input) + { + phoneme_mode = FLAG_PHONEMES; + source_index++; + continue; + } + + if(c == 0) + { + finished = 1; + c = ' '; + } + else + if(IsAlpha(c)) + { + if(!IsAlpha(prev_out) || (tr->langopts.ideographs && ((c > 0x3040) || (prev_out > 0x3040)))) + { + if(wcschr(tr->punct_within_word,prev_out) == 0) + letter_count = 0; // don't reset count for an apostrophy within a word + + if((prev_out != ' ') && (wcschr(tr->punct_within_word,prev_out) == 0)) + { + // start of word, insert space if not one there already + c = ' '; + space_inserted = 1; + } + else + { + if(iswupper(c)) + word_flags |= FLAG_FIRST_UPPER; + + if((prev_out == ' ') && iswdigit(sbuf[ix-2]) && !iswdigit(prev_in)) + { + // word, following a number, but with a space between + // Add an extra space, to distinguish "2 a" from "2a" + sbuf[ix++] = ' '; + words[word_count].start++; + } + } + } + + letter_count++; + + if(iswupper(c)) + { + c = towlower2(c); + + if(tr->langopts.param[LOPT_SYLLABLE_CAPS]) + { + if(syllable_marked == 0) + { + char_inserted = c; + c = 0x2c8; // stress marker + syllable_marked = 1; + } + } + else + { + if(iswlower(prev_in)) + { + c = ' '; // lower case followed by upper case, treat as new word + space_inserted = 1; + prev_in2 = c; + } + else + if((c != ' ') && iswupper(prev_in) && iswlower(next_in) && + (memcmp(&source[source_index],"s ",2) != 0)) // ENGLISH specific plural + { + c = ' '; // change from upper to lower case, start new word at the last uppercase + space_inserted = 1; + prev_in2 = c; + next_word_flags |= FLAG_NOSPACE; + } + } + } + else + { + if((all_upper_case) && (letter_count > 2)) + { + if((c == 's') && (next_in==' ')) + { + c = ' '; + all_upper_case |= FLAG_HAS_PLURAL; + + if(sbuf[ix-1] == '\'') + sbuf[ix-1] = ' '; + } + else + all_upper_case = 0; // current word contains lower case letters, not "'s" + } + else + all_upper_case = 0; + } + } + else + if(c=='-') + { + if(IsAlpha(prev_in) && IsAlpha(next_in)) + { + // '-' between two letters is a hyphen, treat as a space + word_flags |= FLAG_HYPHEN; + words[word_count-1].flags |= FLAG_HYPHEN_AFTER; + c = ' '; + } + else + if((prev_in==' ') && (next_in==' ')) + { + // ' - ' dash between two spaces, treat as pause + c = ' '; + pre_pause_add = 4; + } + else + if(next_in=='-') + { + // double hyphen, treat as pause + source_index++; + c = ' '; + pre_pause_add = 4; + } + else + if((prev_out == ' ') && IsAlpha(sbuf[ix-2]) && !IsAlpha(prev_in)) + { + // insert extra space between a word + space + hyphen, to distinguish 'a -2' from 'a-2' + sbuf[ix++] = ' '; + words[word_count].start++; + } + } + else + if(c == '\'') + { + if(iswalnum(prev_in) && IsAlpha(next_in)) + { + // between two letters, consider apostrophe as part of the word + single_quoted = 0; + } + else + if((wcschr(tr->char_plus_apostrophe,prev_in) != 0) && (prev_out2 == ' ')) + { + // consider single character plus apostrophe as a word + single_quoted = 0; + if(next_in == ' ') + { + source_index++; // skip following space + } + } + else + { + if((prev_out == 's') && (single_quoted==0)) + { + // looks like apostrophe after an 's' + c = ' '; + } + else + { + if(IsSpace(prev_out)) + single_quoted = 1; + else + single_quoted = 0; + + pre_pause_add = 4; // single quote + c = ' '; + } + } + } + else + if(IsBracket(c)) + { + pre_pause_add = 4; + c = ' '; + } + else + if(lookupwchar(breaks,c) != 0) + { + c = ' '; // various characters to treat as space + } + else + if(iswdigit(c)) + { + if(tr->langopts.tone_numbers && IsAlpha(prev_out) && !IsDigit(next_in)) + { + } + else + if((prev_out != ' ') && !iswdigit(prev_out)) + { + if((prev_out != tr->langopts.decimal_sep) || ((decimal_sep_count > 0) && (tr->langopts.decimal_sep == ','))) + { + c = ' '; + space_inserted = 1; + } + else + { + decimal_sep_count = 1; + } + } + else + if((prev_out == ' ') && IsAlpha(sbuf[ix-2]) && !IsAlpha(prev_in)) + { + // insert extra space between a word and a number, to distinguish 'a 2' from 'a2' + sbuf[ix++] = ' '; + words[word_count].start++; + } + } + } + + if(IsSpace(c)) + { + if(space_inserted) + { + source_index = prev_source_index; // rewind to the previous character + char_inserted = 0; + space_inserted = 0; + } + + if(prev_out == ' ') + { + continue; // multiple spaces + } + + // end of 'word' + sbuf[ix++] = ' '; + + if((ix > words[word_count].start) && (word_count < N_CLAUSE_WORDS-1)) + { + if(embedded_count > 0) + { + // there are embedded commands before this word + embedded_list[embedded_ix-1] |= 0x80; // terminate list of commands for this word + words[word_count].flags |= FLAG_EMBEDDED; + embedded_count = 0; + } + words[word_count].pre_pause = pre_pause; + words[word_count].flags |= (all_upper_case | word_flags | word_emphasis); + words[word_count].wmark = word_mark; + + if(pre_pause > 0) + { + // insert an extra space before the word, to prevent influence from previous word across the pause + for(j=ix; j>words[word_count].start; j--) + { + sbuf[j] = sbuf[j-1]; + } + sbuf[j] = ' '; + words[word_count].start++; + ix++; + } + + word_count++; + words[word_count].start = ix; + words[word_count].flags = 0; + + for(j=source_index; charix[j] <= 0; j++); // skip blanks + words[word_count].sourceix = charix[j]; + k = 0; + while(charix[j] != 0) + { + // count the number of characters (excluding multibyte continuation bytes) + if(charix[j++] != -1) + k++; + } + words[word_count].length = k; + + word_flags = next_word_flags; + next_word_flags = 0; + pre_pause = 0; + word_mark = 0; + all_upper_case = FLAG_ALL_UPPER; + syllable_marked = 0; + } + } + else + { + ix += utf8_out(c,&sbuf[ix]); // sbuf[ix++] = c; + } + if(pre_pause_add > pre_pause) + pre_pause = pre_pause_add; + pre_pause_add = 0; + } + + if((word_count==0) && (embedded_count > 0)) + { + // add a null 'word' to carry the embedded command flag + embedded_list[embedded_ix-1] |= 0x80; + words[word_count].flags |= FLAG_EMBEDDED; + word_count = 1; + } + + tr->clause_end = &sbuf[ix-1]; + sbuf[ix] = 0; + words[0].pre_pause = 0; // don't add extra pause at beginning of clause + words[word_count].pre_pause = 8; + if(word_count > 0) + words[word_count-1].flags |= FLAG_LAST_WORD; + words[0].flags |= FLAG_FIRST_WORD; + + for(ix=0; ix<word_count; ix++) + { + int nx; + int c_temp; + char *pn; + char *pw; + static unsigned int break_numbers1 = 0x49249248; + static unsigned int break_numbers2 = 0x492492a8; // for languages which have numbers for 100,000 and 100,00,000 + static unsigned int break_numbers3 = 0x49249268; // for languages which have numbers for 100,000 and 1,000,000 + unsigned int break_numbers; + char number_buf[80]; + + // start speaking at a specified word position in the text? + count_words++; + if(skip_words > 0) + { + skip_words--; + if(skip_words == 0) + skipping_text = 0; + } + if(skipping_text) + continue; + + + // digits should have been converted to Latin alphabet ('0' to '9') + word = pw = &sbuf[words[ix].start]; + + if(iswdigit(word[0]) && (tr->langopts.numbers2 & NUM2_100000)) + { + // Languages with 100000 numbers. Remove thousands separators so that we can insert them again later + pn = number_buf; + while(pn < &number_buf[sizeof(number_buf)-3]) + { + if(iswdigit(*pw)) + { + *pn++ = *pw++; + } + else + if((*pw == tr->langopts.thousands_sep) && (pw[1] == ' ') && iswdigit(pw[2])) + { + pw += 2; + ix++; // skip "word" + } + else + { + nx = pw - word; + memset(word,' ',nx); + nx = pn - number_buf; + memcpy(word,number_buf,nx); + break; + } + } + pw = word; + } + + for(n_digits=0; iswdigit(word[n_digits]); n_digits++); // count consecutive digits + + if((n_digits > 4) && (word[0] != '0')) + { + // word is entirely digits, insert commas and break into 3 digit "words" + number_buf[0] = ' '; + pn = &number_buf[1]; + nx = n_digits; + + if(tr->langopts.numbers2 & NUM2_100000a) + break_numbers = break_numbers3; + else + if(tr->langopts.numbers2 & NUM2_100000) + break_numbers = break_numbers2; + else + break_numbers = break_numbers1; + + while(pn < &number_buf[sizeof(number_buf)-3]) + { + if(!isdigit(c = *pw++) && (c != tr->langopts.decimal_sep)) + break; + + *pn++ = c; + if((--nx > 0) && (break_numbers & (1 << nx))) + { + if(tr->langopts.thousands_sep != ' ') + { + *pn++ = tr->langopts.thousands_sep; + } + *pn++ = ' '; + if(break_numbers & (1 << (nx-1))) + { + // the next group only has 1 digits (i.e. NUM2_10000), make it three + *pn++ = '0'; + *pn++ = '0'; + } + if(break_numbers & (1 << (nx-2))) + { + // the next group only has 2 digits (i.e. NUM2_10000), make it three + *pn++ = '0'; + } + } + } + pn[0] = ' '; + pn[1] = 0; + word = pw; + + for(pw = &number_buf[1]; pw < pn;) + { + TranslateWord2(tr, pw, &words[ix], words[ix].pre_pause,0 ); + while(*pw++ != ' '); + words[ix].pre_pause = 0; + words[ix].flags = 0; + } + } + else + { + dict_flags = TranslateWord2(tr, word, &words[ix], words[ix].pre_pause, words[ix+1].pre_pause); + + if(dict_flags & FLAG_SPELLWORD) + { + // redo the word, speaking single letters + for(pw = word; *pw != ' ';) + { + memset(number_buf,' ',9); + nx = utf8_in(&c_temp, pw); + memcpy(&number_buf[2],pw,nx); + TranslateWord2(tr, &number_buf[2], &words[ix], 0, 0 ); + pw += nx; + } + } + + if(dict_flags & FLAG_SKIPWORDS) + { + ix += dictionary_skipwords; // dictionary indicates skip next word(s) + } + + if((dict_flags & FLAG_DOT) && (ix == word_count-1) && (terminator == CLAUSE_PERIOD)) + { + // probably an abbreviation such as Mr. or B. rather than end of sentence + clause_pause = 10; + tone = 4; + } + } + } + + for(ix=0; ix<2; ix++) + { + // terminate the clause with 2 PAUSE phonemes + PHONEME_LIST2 *p2; + p2 = &ph_list2[n_ph_list2 + ix]; + p2->phcode = phonPAUSE; + p2->stress = 0; + p2->sourceix = 0; + p2->synthflags = 0; + } + n_ph_list2 += 2; + + if(count_words == 0) + { + clause_pause = 0; + } + if(Eof() && ((word_count == 0) || (option_endpause==0))) + { + clause_pause = 10; + } + + MakePhonemeList(tr, clause_pause, new_sentence); + + if(embedded_count) // ???? is this needed + { + phoneme_list[n_phoneme_list-2].synthflags = SFLAG_EMBEDDED; + embedded_list[embedded_ix-1] |= 0x80; + } + + + prev_clause_pause = clause_pause; + + *tone_out = tone; + + new_sentence = 0; + if(terminator & CLAUSE_BIT_SENTENCE) + { + new_sentence = 1; // next clause is a new sentence + } + + + if(voice_change != NULL) + { + // return new voice name if an embedded voice change command terminated the clause + if(terminator & CLAUSE_BIT_VOICE) + *voice_change = &source[source_index]; + else + *voice_change = NULL; + } + + if(Eof() || (vp_input==NULL)) + return(NULL); + + if(option_multibyte == espeakCHARS_WCHAR) + return((void *)p_wchar_input); + else + return((void *)p_textinput); +} // end of TranslateClause + + + + + +void InitText(int control) +{//======================= + count_sentences = 0; + count_words = 0; + end_character_position = 0; + skip_sentences = 0; + skip_marker[0] = 0; + skip_words = 0; + skip_characters = 0; + skipping_text = 0; + new_sentence = 1; + + prev_clause_pause = 0; + + option_sayas = 0; + option_sayas2 = 0; + option_emphasis = 0; + word_emphasis = 0; + + InitText2(); + + if((control & espeakKEEP_NAMEDATA) == 0) + { + InitNamedata(); + } +} + diff --git a/Plugins/eSpeak/eSpeak/translate.h b/Plugins/eSpeak/eSpeak/translate.h new file mode 100644 index 0000000..afa59ca --- /dev/null +++ b/Plugins/eSpeak/eSpeak/translate.h @@ -0,0 +1,576 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + + +#define L(c1,c2) (c1<<8)+c2 // combine two characters into an integer for translator name + +#define CTRL_EMBEDDED 0x01 // control character at the start of an embedded command +#define REPLACED_E 'E' // 'e' replaced by silent e + +#define N_WORD_PHONEMES 160 // max phonemes in a word +#define N_WORD_BYTES 160 // max bytes for the UTF8 characters in a word +#define N_CLAUSE_WORDS 300 // max words in a clause +#define N_RULE_GROUP2 120 // max num of two-letter rule chains +#define N_HASH_DICT 1024 +#define N_CHARSETS 20 +#define N_LETTER_GROUPS 26 + + +/* dictionary flags, word 1 */ +// bits 0-3 stressed syllable, bit 6=unstressed +#define FLAG_SKIPWORDS 0x80 +#define FLAG_PREPAUSE 0x100 + +#define FLAG_ONLY 0x200 +#define FLAG_ONLY_S 0x400 +#define BITNUM_FLAG_ONLY 9 // bit 9 is set +#define BITNUM_FLAG_ONLY_S 10 // bit 10 is set + +#define FLAG_STRESS_END 0x800 /* full stress if at end of clause */ +#define FLAG_STRESS_END2 0x1000 /* full stress if at end of clause, or only followed by unstressed */ +#define FLAG_UNSTRESS_END 0x2000 /* reduce stress at end of clause */ +#define FLAG_ATEND 0x4000 /* use this pronunciation if at end of clause */ +#define FLAG_SPELLWORD 0x8000 // re-translate the word as individual letters, separated by spaces + +#define FLAG_DOT 0x10000 /* ignore '.' after word (abbreviation) */ +#define FLAG_ABBREV 0x20000 // spell as letters, even with a vowel, OR use specified pronunciation rather than split into letters +#define FLAG_STEM 0x40000 // must have a suffix + +#define FLAG_DOUBLING 0x80000 // doubles the following consonant +#define FLAG_ALT_TRANS 0x100000 // language specific +#define FLAG_ALT2_TRANS 0x200000 // language specific + +#define FLAG_MAX3 0x08000000 // limit to 3 repeats +#define FLAG_PAUSE1 0x10000000 // shorter prepause +#define FLAG_TEXTMODE 0x20000000 // word translates to replacement text, not phonemes +#define BITNUM_FLAG_TEXTMODE 29 + +#define FLAG_FOUND_ATTRIBUTES 0x40000000 // word was found in the dictionary list (has attributes) +#define FLAG_FOUND 0x80000000 // pronunciation was found in the dictionary list + +// dictionary flags, word 2 +#define FLAG_VERBF 0x1 /* verb follows */ +#define FLAG_VERBSF 0x2 /* verb follows, may have -s suffix */ +#define FLAG_NOUNF 0x4 /* noun follows */ +#define FLAG_PASTF 0x8 /* past tense follows */ +#define FLAG_VERB 0x10 /* pronunciation for verb */ +#define FLAG_NOUN 0x20 /* pronunciation for noun */ +#define FLAG_PAST 0x40 /* pronunciation for past tense */ +#define FLAG_VERB_EXT 0x100 /* extend the 'verb follows' */ +#define FLAG_CAPITAL 0x200 /* pronunciation if initial letter is upper case */ +#define FLAG_ALLCAPS 0x400 // only if the word is all capitals +#define BITNUM_FLAG_ALLCAPS 0x2a +#define FLAG_ACCENT 0x800 // character name is base-character name + accent name +#define FLAG_HYPHENATED 0x1000 // multiple-words, but needs hyphen between parts 1 and 2 +#define BITNUM_FLAG_HYPHENATED 0x2c + + +// wordflags, flags in source word +#define FLAG_ALL_UPPER 0x1 /* no lower case letters in the word */ +#define FLAG_FIRST_UPPER 0x2 /* first letter is upper case */ +#define FLAG_UPPERS 0x3 // FLAG_ALL_UPPER | FLAG_FIRST_UPPER +#define FLAG_HAS_PLURAL 0x4 /* upper-case word with s or 's lower-case ending */ +#define FLAG_PHONEMES 0x8 /* word is phonemes */ +#define FLAG_LAST_WORD 0x10 /* last word in clause */ +#define FLAG_EMBEDDED 0x40 /* word is preceded by embedded commands */ +#define FLAG_HYPHEN 0x80 +#define FLAG_NOSPACE 0x100 // word is not seperated from previous word by a space +#define FLAG_FIRST_WORD 0x200 // first word in clause +#define FLAG_FOCUS 0x400 // the focus word of a clause +#define FLAG_EMPHASIZED 0x800 +#define FLAG_EMPHASIZED2 0xc00 // FLAG_FOCUS | FLAG_EMPHASIZED +#define FLAG_DONT_SWITCH_TRANSLATOR 0x1000 +#define FLAG_SUFFIX_REMOVED 0x2000 +#define FLAG_HYPHEN_AFTER 0x4000 + +#define FLAG_NO_TRACE 0x10000 // passed to TranslateRules() to suppress dictionary lookup printout +#define FLAG_NO_PREFIX 0x20000 + +// prefix/suffix flags (bits 8 to 14, bits 16 to 22) don't use 0x8000, 0x800000 +#define SUFX_E 0x0100 // e may have been added +#define SUFX_I 0x0200 // y may have been changed to i +#define SUFX_P 0x0400 // prefix +#define SUFX_V 0x0800 // suffix means use the verb form pronunciation +#define SUFX_D 0x1000 // previous letter may have been doubles +#define SUFX_F 0x2000 // verb follows +#define SUFX_Q 0x4000 // don't retranslate +#define SUFX_T 0x10000 // don't affect the stress position in the stem +#define SUFX_B 0x20000 // break, this character breaks the word into stem and suffix (used with SUFX_P) + +#define FLAG_ALLOW_TEXTMODE 0x02 // allow dictionary to translate to text rather than phonemes +#define FLAG_SUFX 0x04 +#define FLAG_SUFX_S 0x08 +#define FLAG_SUFX_E_ADDED 0x10 + + +// codes in dictionary rules +#define RULE_PRE 1 +#define RULE_POST 2 +#define RULE_PHONEMES 3 +#define RULE_PH_COMMON 4 // At start of rule. Its phoneme string is used by subsequent rules +#define RULE_CONDITION 5 // followed by condition number (byte) +#define RULE_GROUP_START 6 +#define RULE_GROUP_END 7 +#define RULE_LINENUM 8 // next 2 bytes give a line number, for debugging purposes + +#define RULE_SPACE 32 // ascii space +#define RULE_SYLLABLE 9 +#define RULE_STRESSED 10 +#define RULE_DOUBLE 11 +#define RULE_INC_SCORE 12 +#define RULE_DEL_FWD 13 +#define RULE_ENDING 14 +#define RULE_DIGIT 15 // D digit +#define RULE_NONALPHA 16 // Z non-alpha +#define RULE_LETTERGP 17 // A B C H F G Y letter group number +#define RULE_LETTERGP2 18 // L + letter group number +#define RULE_CAPITAL 19 // word starts with a capital letter +#define RULE_REPLACEMENTS 20 // section for character replacements +#define RULE_NO_SUFFIX 24 // N +#define RULE_NOTVOWEL 25 // K +#define RULE_IFVERB 26 // V +#define RULE_ALT1 28 // T word has $alt attribute +#define RULE_NOVOWELS 29 // X no vowels up to word boundary +#define RULE_SPELLING 31 // W while spelling letter-by-letter +#define RULE_LAST_RULE 31 + +#define LETTERGP_A 0 +#define LETTERGP_B 1 +#define LETTERGP_C 2 +#define LETTERGP_H 3 +#define LETTERGP_F 4 +#define LETTERGP_G 5 +#define LETTERGP_Y 6 +#define LETTERGP_VOWEL2 7 + + +// Punctuation types returned by ReadClause() +// bits 0-7 pause x 10mS, bits 12-14 intonation type, +// bit 19=sentence, bit 18=clause, bits 17=voice change +// bit 16 used to distinguish otherwise identical types +// bit 20= punctuation character can be inside a word (Armenian) +#define CLAUSE_BIT_SENTENCE 0x80000 +#define CLAUSE_BIT_VOICE 0x20000 +#define PUNCT_IN_WORD 0x100000 + +#define CLAUSE_NONE 0 + 0x04000 +#define CLAUSE_PARAGRAPH 70 + 0x80000 +#define CLAUSE_EOF 35 + 0x90000 +#define CLAUSE_VOICE 0 + 0x24000 +#define CLAUSE_PERIOD 35 + 0x80000 +#define CLAUSE_COMMA 20 + 0x41000 +#define CLAUSE_SHORTCOMMA 5 + 0x41000 +#define CLAUSE_QUESTION 35 + 0x82000 +#define CLAUSE_EXCLAMATION 40 + 0x83000 +#define CLAUSE_COLON 30 + 0x40000 +#ifdef PLATFORM_RISCOS +#define CLAUSE_SEMICOLON 30 + 0x40000 +#else +#define CLAUSE_SEMICOLON 30 + 0x41000 +#endif + +#define SAYAS_CHARS 0x12 +#define SAYAS_GLYPHS 0x13 +#define SAYAS_SINGLE_CHARS 0x14 +#define SAYAS_KEY 0x24 +#define SAYAS_DIGITS 0x40 // + number of digits +#define SAYAS_DIGITS1 0xc1 + +#define CHAR_EMPHASIS 0x0530 // this is an unused character code + +// Rule: +// [4] [match] [1 pre] [2 post] [3 phonemes] 0 +// match 1 pre 2 post 0 - use common phoneme string +// match 1 pre 2 post 3 0 - empty phoneme string + +typedef const char * constcharptr; + +typedef struct { + int points; + const char *phonemes; + int end_type; + char *del_fwd; +} MatchRecord; + + +// used to mark words with the source[] buffer +typedef struct{ + unsigned short start; + unsigned short sourceix; + unsigned short flags; + unsigned char pre_pause; + unsigned char wmark; + unsigned char length; +} WORD_TAB; + +// a clause translated into phoneme codes (first stage) +typedef struct { + unsigned char phcode; + unsigned char stress; + unsigned char tone_number; + unsigned char synthflags; + unsigned short sourceix; +} PHONEME_LIST2; + + +typedef struct { + int type; + int parameter[N_SPEECH_PARAM]; +} PARAM_STACK; + +extern PARAM_STACK param_stack[]; +extern const int param_defaults[N_SPEECH_PARAM]; + + + +#define N_LOPTS 15 +#define LOPT_DIERESES 1 + // 1=remove [:] from unstressed syllables, 2= remove from unstressed or non-penultimate syllables + // bit 4=0, if stress < 4, bit 4=1, if not the highest stress in the word +#define LOPT_IT_LENGTHEN 2 + + // 1=german +#define LOPT_PREFIXES 3 + + // non-zero, change voiced/unoiced to match last consonant in a cluster + // bit 1=LANG=ru, don't propagate over [v] + // bit 2=don't propagate acress word boundaries + // bit 3=LANG=pl, propagate over liquids and nasals +#define LOPT_REGRESSIVE_VOICING 4 + + // 0=default, 1=no check, other allow this character as an extra initial letter (default is 's') +#define LOPT_UNPRONOUNCABLE 5 + + // select length_mods tables, (length_mod_tab) + (length_mod_tab0 * 100) +#define LOPT_LENGTH_MODS 6 + + // increase this to prevent sonorants being shortened before shortened (eg. unstressed) vowels +#define LOPT_SONORANT_MIN 7 + + // don't break vowels at word boundary +#define LOPT_WORD_MERGE 8 + + // max. amplitude for vowel at the end of a clause +#define LOPT_MAXAMP_EOC 9 + + // bit 0=reduce even if phonemes are specified in the **_list file + // bit 1=don't reduce the strongest vowel in a word which is marked 'unstressed' +#define LOPT_REDUCE 10 + + // LANG=cs,sk combine some prepositions with the following word, if the combination has N or fewer syllables + // bits 0-3 N syllables + // bit 4=only if the second word has $alt attribute + // bit 5=not if the second word is end-of-sentence +#define LOPT_COMBINE_WORDS 11 + + // change [t] when followed by unstressed vowel +#define LOPT_REDUCE_T 12 + + // stressed syllable is indicated by capitals +#define LOPT_SYLLABLE_CAPS 13 + + // bit 0=Italian "syntactic doubling" of consoants in the word after a word marked with $double attribute + // bit 1=also after a word which ends with a stressed vowel +#define LOPT_IT_DOUBLING 14 + + + +typedef struct { +// bits0-2 separate words with (1=pause_vshort, 2=pause_short, 3=pause, 4=pause_long 5=[?] phonemme) +// bit 3=don't use linking phoneme +// bit4=longer pause before STOP, VSTOP,FRIC +// bit5=length of a final vowel doesn't depend on the next phoneme + int word_gap; + int vowel_pause; + int stress_rule; // 1=first syllable, 2=penultimate, 3=last + +// bit0=don't stress monosyllables, +// bit1=don't set diminished stress, +// bit2=mark unstressed final syllables as diminished +// bit4=don't allow secondary stress on last syllable +// bit5-don't use automatic secondary stress +// bit6=light syllable followed by heavy, move secondary stress to the heavy syllable. LANG=Finnish +// bit8=stress last syllable if it doesn't end in a vowel +// bit9=stress last syllable if it doesn't end in vowel or "s" or "n" LANG=Spanish +// bit12= In a 2-syllable word, if one has primary stress then give the other secondary stress +// bit13= If there is only one syllable before the primary stress, give it a secondary stress +// bit15= Give stress to the first unstressed syllable +// bit16= Don't diminish consecutive syllables within a word. +// bit17= "priority" stress reduces other primary stress to "unstressed" not "secondary" +// bit18= don't lengthen short vowels more than long vowels at end-of-clause +// bit19=stress on final syllable if it has a long vowel, but previous syllable has a short vowel + + int stress_flags; + int unstressed_wd1; // stress for $u word of 1 syllable + int unstressed_wd2; // stress for $u word of >1 syllable + int param[N_LOPTS]; + unsigned char *length_mods; + unsigned char *length_mods0; + +#define NUM_ROMAN 0x20000 +#define NUM_ROMAN_UC 0x40000 +#define NUM_NOPAUSE 0x80000 +#define NUM_ROMAN_AFTER 0x200000 + + // bits0-1=which numbers routine to use. + // bit2= thousands separator must be space + // bit3= , decimal separator, not . + // bit4=use three-and-twenty rather than twenty-three + // bit5='and' between tens and units + // bit6=add "and" after hundred or thousand + // bit7=don't have "and" both after hundreds and also between tens and units + // bit8=only one primary stress in tens+units + // bit9=only one vowel betwen tens and units + // bit10=omit "one" before "hundred" + // bit11=say 19** as nineteen hundred + // bit12=allow space as thousands separator (in addition to langopts.thousands_sep) + // bits13-15 post-decimal-digits 0=single digits, 1=(LANG=it) 2=(LANG=pl) 3=(LANG=ro) + // bit16=dot after number indicates ordinal + // bit17=recognize roman numbers + // bit18=Roman numbers only if upper case + // bit19=don't add pause after a number + // bit20='and' before hundreds + // bit21= say "roman" after the number, not before + int numbers; + +#define NUM2_100000 0x800 // numbers for 100,000 and 10,000,000 +#define NUM2_100000a 0xc00 // numbers for 100,000 and 1,000,000 + // bits 1-4 use variant form of numbers before thousands,millions,etc. + // bit6=(LANG=pl) two forms of plural, M or MA + // bit7=(LANG-ru) use MB for 1 thousand, million, etc + // bit8=(LANG=cs,sk) two forms of plural, M or MA + // bit9=(LANG=rw) say "thousand" and "million" before its number, not after + // bit10=(LANG=sw) special word for 100,000 and 1,000,000 + // bit11=(LANG=hi) special word for 100,000 and 10,000,000 + int numbers2; + + int max_roman; + int thousands_sep; + int decimal_sep; + + // bit 0, accent name before the letter name, bit 1 "capital" after letter name + int accents; + + int tone_language; // 1=tone language + int intonation_group; + int long_stop; // extra mS pause for a lengthened stop + int phoneme_change; // TEST, change phonemes, after translation + char max_initial_consonants; + char spelling_stress; // 0=default, 1=stress first letter + char tone_numbers; + char ideographs; // treat as separate words + char textmode; // the meaning of FLAG_TEXTMODE is reversed (to save data when *_list file is compiled) + int testing; // testing options: bit 1= specify stressed syllable in the form: "outdoor/2" + int listx; // compile *_listx after *list + const unsigned int *replace_chars; // characters to be substitutes + const char *ascii_language; // switch to this language for Latin characters +} LANGUAGE_OPTIONS; + + +// a parameter of ChangePhonemes() +typedef struct { + int flags; + unsigned char stress; // stress level of this vowel + unsigned char stress_highest; // the highest stress level of a vowel in this word + unsigned char n_vowels; // number of vowels in the word + unsigned char vowel_this; // syllable number of this vowel (counting from 1) + unsigned char vowel_stressed; // syllable number of the highest stressed vowel +} CHANGEPH; + + + +#define NUM_SEP_DOT 0x0008 // . , for thousands and decimal separator +#define NUM_SEP_SPACE 0x1000 // allow space as thousands separator (in addition to langopts.thousands_sep) +#define NUM_DEC_IT 0x2000 // (LANG=it) speak post-decimal-point digits as a combined number not as single digits + +struct Translator +{//============= + + LANGUAGE_OPTIONS langopts; + int translator_name; + int transpose_offset; + int transpose_max; + int transpose_min; + + char phon_out[300]; + char phonemes_repeat[20]; + int phonemes_repeat_count; + + unsigned char stress_amps[8]; + unsigned char stress_amps_r[8]; + short stress_lengths[8]; + int dict_condition; // conditional apply some pronunciation rules and dict.lookups + const unsigned short *charset_a0; // unicodes for characters 0xa0 to oxff + const wchar_t *char_plus_apostrophe; // single chars + apostrophe treated as words + const wchar_t *punct_within_word; // allow these punctuation characters within words + +// holds properties of characters: vowel, consonant, etc for pronunciation rules + unsigned char letter_bits[256]; + int letter_bits_offset; + const wchar_t *letter_groups[8]; + + /* index1=option, index2 by 0=. 1=, 2=?, 3=! 4=none */ +#define INTONATION_TYPES 8 +#define PUNCT_INTONATIONS 6 + unsigned char punct_to_tone[INTONATION_TYPES][PUNCT_INTONATIONS]; + + char *data_dictrules; // language_1 translation rules file + char *data_dictlist; // language_2 dictionary lookup file + char *dict_hashtab[N_HASH_DICT]; // hash table to index dictionary lookup file + char *letterGroups[N_LETTER_GROUPS]; + + // groups1 and groups2 are indexes into data_dictrules, set up by InitGroups() + // the two-letter rules for each letter must be consecutive in the language_rules source + + char *groups1[256]; // translation rule lists, index by single letter + char *groups2[N_RULE_GROUP2]; // translation rule lists, indexed by two-letter pairs + unsigned int groups2_name[N_RULE_GROUP2]; // the two letter pairs for groups2[] + int n_groups2; // number of groups2[] entries used + + unsigned char groups2_count[256]; // number of 2 letter groups for this initial letter + unsigned char groups2_start[256]; // index into groups2 + + + int expect_verb; + int expect_past; // expect past tense + int expect_verb_s; + int expect_noun; + int prev_last_stress; + char *clause_end; + + int word_vowel_count; // number of vowels so far + int word_stressed_count; // number of vowels so far which could be stressed + + int clause_upper_count; // number of upper case letters in the clause + int clause_lower_count; // number of lower case letters in the clause + + int prepause_timeout; + int end_stressed_vowel; // word ends with stressed vowel + int prev_dict_flags; // dictionary flags from previous word +}; // end of class Translator + + +extern int option_tone2; +#define OPTION_EMPHASIZE_ALLCAPS 0x100 +#define OPTION_EMPHASIZE_PENULTIMATE 0x200 +extern int option_tone_flags; +extern int option_waveout; +extern int option_quiet; +extern int option_phonemes; +extern int option_phoneme_events; +extern int option_linelength; // treat lines shorter than this as end-of-clause +extern int option_multibyte; +extern int option_capitals; +extern int option_punctuation; +extern int option_endpause; +extern int option_ssml; +extern int option_phoneme_input; // allow [[phonemes]] in input text +extern int option_phoneme_variants; +extern int option_sayas; +extern int option_wordgap; + +extern int count_characters; +extern int count_words; +extern int count_sentences; +extern int skip_characters; +extern int skip_words; +extern int skip_sentences; +extern int skipping_text; +extern int end_character_position; +extern int clause_start_char; +extern int clause_start_word; +extern char *namedata; + + + +#define N_MARKER_LENGTH 50 // max.length of a mark name +extern char skip_marker[N_MARKER_LENGTH]; + +#define N_PUNCTLIST 60 +extern wchar_t option_punctlist[N_PUNCTLIST]; // which punctuation characters to announce +extern unsigned char punctuation_to_tone[INTONATION_TYPES][PUNCT_INTONATIONS]; + +extern Translator *translator; +extern Translator *translator2; +extern const unsigned short *charsets[N_CHARSETS]; +extern char dictionary_name[40]; +extern char ctrl_embedded; // to allow an alternative CTRL for embedded commands +extern char *p_textinput; +extern wchar_t *p_wchar_input; +extern int dictionary_skipwords; + +extern int (* uri_callback)(int, const char *, const char *); +extern int (* phoneme_callback)(const char *); +extern void SetLengthMods(Translator *tr, int value); + +void LoadConfig(void); +int TransposeAlphabet(char *text, int offset, int min, int max); +int utf8_in(int *c, const char *buf); +int utf8_in2(int *c, const char *buf, int backwards); +int utf8_out(unsigned int c, char *buf); +int utf8_nbytes(const char *buf); +int lookupwchar(const unsigned short *list,int c); +int Eof(void); +char *strchr_w(const char *s, int c); +int IsBracket(int c); +void InitNamedata(void); +void InitText(int flags); +void InitText2(void); +int IsDigit(unsigned int c); +int IsAlpha(unsigned int c); +int isspace2(unsigned int c); +int towlower2(unsigned int c); +void GetTranslatedPhonemeString(char *phon_out, int n_phon_out); + +Translator *SelectTranslator(const char *name); +int SetTranslator2(const char *name); +void DeleteTranslator(Translator *tr); +int Lookup(Translator *tr, const char *word, char *ph_out); + +int TranslateNumber(Translator *tr, char *word1, char *ph_out, unsigned int *flags, int wflags); +int TranslateRoman(Translator *tr, char *word, char *ph_out); + +void ChangeWordStress(Translator *tr, char *word, int new_stress); +void SetSpellingStress(Translator *tr, char *phonemes, int control, int n_chars); +int TranslateLetter(Translator *tr, char *letter, char *phonemes, int control, int word_length); +void LookupLetter(Translator *tr, unsigned int letter, int next_byte, char *ph_buf); +void LookupAccentedLetter(Translator *tr, unsigned int letter, char *ph_buf); + +int LoadDictionary(Translator *tr, const char *name, int no_error); +int LookupDictList(Translator *tr, char **wordptr, char *ph_out, unsigned int *flags, int end_flags, WORD_TAB *wtab); + +void MakePhonemeList(Translator *tr, int post_pause, int new_sentence); +int ChangePhonemes_ru(Translator *tr, PHONEME_LIST2 *phlist, int n_ph, int index, PHONEME_TAB *ph, CHANGEPH *ch); +void ApplySpecialAttribute(Translator *tr, char *phonemes, int dict_flags); +void AppendPhonemes(Translator *tr, char *string, int size, const char *ph); + +void CalcLengths(Translator *tr); +void CalcPitches(Translator *tr, int clause_tone); + +int RemoveEnding(Translator *tr, char *word, int end_type, char *word_copy); +int Unpronouncable(Translator *tr, char *word); +void SetWordStress(Translator *tr, char *output, unsigned int dictionary_flags, int tonic, int prev_stress); +int TranslateRules(Translator *tr, char *p, char *phonemes, int size, char *end_phonemes, int end_flags, unsigned int *dict_flags); +int TranslateWord(Translator *tr, char *word1, int next_pause, WORD_TAB *wtab); +void *TranslateClause(Translator *tr, FILE *f_text, const void *vp_input, int *tone, char **voice_change); +int ReadClause(Translator *tr, FILE *f_in, char *buf, short *charix, int n_buf, int *tone_type); + +void SetVoiceStack(espeak_VOICE *v); + +extern FILE *f_trans; // for logging diff --git a/Plugins/eSpeak/eSpeak/voice.h b/Plugins/eSpeak/eSpeak/voice.h new file mode 100644 index 0000000..2f647c6 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/voice.h @@ -0,0 +1,81 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + + + +typedef struct { + char v_name[40]; + + int phoneme_tab_ix; // phoneme table number + int pitch_base; // Hz<<12 + int pitch_range; // standard = 0x1000 + + int speedf1; + int speedf2; + int speedf3; + + int flutter; + int roughness; + int echo_delay; + int echo_amp; + int n_harmonic_peaks; // highest formant which is formed from adding harmonics + int peak_shape; // alternative shape for formant peaks (0=standard 1=squarer) + int voicing; // 100% = 64, level of formant-synthesized sound + int formant_factor; // adjust nominal formant frequencies by this because of the voice's pitch (256ths) + int consonant_amp; // amplitude of unvoiced consonants + int consonant_ampv; // amplitude of the noise component of voiced consonants + int klatt[8]; + + // parameters used by Wavegen + short freq[N_PEAKS]; // 100% = 256 + short height[N_PEAKS]; // 100% = 256 + short width[N_PEAKS]; // 100% = 256 + short freqadd[N_PEAKS]; // Hz + + // copies without temporary adjustments from embedded commands + short freq2[N_PEAKS]; // 100% = 256 + short height2[N_PEAKS]; // 100% = 256 + short width2[N_PEAKS]; // 100% = 256 + + int breath[N_PEAKS]; // amount of breath for each formant. breath[0] indicates whether any are set. + int breathw[N_PEAKS]; // width of each breath formant + + // This table provides the opportunity for tone control. + // Adjustment of harmonic amplitudes, steps of 8Hz + // value of 128 means no change + #define N_TONE_ADJUST 1000 + unsigned char tone_adjust[N_TONE_ADJUST]; // 8Hz steps * 1000 = 8kHz + +} voice_t; + +// percentages shown to user, ix=N_PEAKS means ALL peaks +extern USHORT voice_pcnt[N_PEAKS+1][3]; + + +extern voice_t *voice; +extern int tone_points[12]; + +const char *SelectVoice(espeak_VOICE *voice_select, int *found); +espeak_VOICE *SelectVoiceByName(espeak_VOICE **voices, const char *name); +voice_t *LoadVoice(const char *voice_name, int control); +voice_t *LoadVoiceVariant(const char *voice_name, int variant); +void DoVoiceChange(voice_t *v); +void WavegenSetVoice(voice_t *v); +void ReadTonePoints(char *string, int *tone_pts); + diff --git a/Plugins/eSpeak/eSpeak/voices.cpp b/Plugins/eSpeak/eSpeak/voices.cpp new file mode 100644 index 0000000..74173ca --- /dev/null +++ b/Plugins/eSpeak/eSpeak/voices.cpp @@ -0,0 +1,1743 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "StdAfx.h" + +#include "stdio.h" +#include "ctype.h" +#include "wctype.h" +#include "string.h" +#include "stdlib.h" +#include "speech.h" + +#ifdef PLATFORM_WINDOWS +#include "windows.h" +#else +#ifdef PLATFORM_RISCOS +#include "kernel.h" +#else +#include "dirent.h" +#endif +#endif + +#include "speak_lib.h" +#include "phoneme.h" +#include "synthesize.h" +#include "voice.h" +#include "translate.h" + + +MNEM_TAB genders [] = { + {"unknown", 0}, + {"male", 1}, + {"female", 2}, + {NULL, 0 }}; + +int tone_points[12] = {600,170, 1200,135, 2000,110, 3000,110, -1,0}; +//int tone_points[12] = {250,200, 400,170, 600,170, 1200,135, 2000,110, -1,0}; + +// limit the rate of change for each formant number +//static int formant_rate_22050[9] = {50, 104, 165, 230, 220, 220, 220, 220, 220}; // values for 22kHz sample rate +//static int formant_rate_22050[9] = {240, 180, 180, 180, 180, 180, 180, 180, 180}; // values for 22kHz sample rate +static int formant_rate_22050[9] = {240, 170, 170, 170, 170, 170, 170, 170, 170}; // values for 22kHz sample rate +int formant_rate[9]; // values adjusted for actual sample rate + + + +#define DEFAULT_LANGUAGE_PRIORITY 5 +#define N_VOICES_LIST 150 +static int n_voices_list = 0; +static espeak_VOICE *voices_list[N_VOICES_LIST]; +static int len_path_voices; + +espeak_VOICE voice_selected; + + +enum { + V_NAME = 1, + V_LANGUAGE, + V_GENDER, + V_TRANSLATOR, + V_PHONEMES, + V_DICTIONARY, + +// these affect voice quality, are independent of language + V_FORMANT, + V_PITCH, + V_ECHO, + V_FLUTTER, + V_ROUGHNESS, + V_CLARITY, + V_TONE, + V_VOICING, + V_BREATH, + V_BREATHW, + +// these override defaults set by the translator + V_WORDGAP, + V_INTONATION, + V_STRESSLENGTH, + V_STRESSAMP, + V_STRESSADD, + V_DICTRULES, + V_STRESSRULE, + V_CHARSET, + V_NUMBERS, + V_OPTION, + + V_MBROLA, + V_KLATT, + V_FAST, + +// these need a phoneme table to have been specified + V_REPLACE, + V_CONSONANTS +}; + + + +typedef struct { + const char *mnem; + int data; +} keywtab_t; + +static keywtab_t keyword_tab[] = { + {"name", V_NAME}, + {"language", V_LANGUAGE}, + {"gender", V_GENDER}, + + {"formant", V_FORMANT}, + {"pitch", V_PITCH}, + {"phonemes", V_PHONEMES}, + {"translator", V_TRANSLATOR}, + {"dictionary", V_DICTIONARY}, + {"stressLength", V_STRESSLENGTH}, + {"stressAmp", V_STRESSAMP}, + {"stressAdd", V_STRESSADD}, + {"intonation", V_INTONATION}, + {"dictrules", V_DICTRULES}, + {"stressrule", V_STRESSRULE}, + {"charset", V_CHARSET}, + {"replace", V_REPLACE}, + {"words", V_WORDGAP}, + {"echo", V_ECHO}, + {"flutter", V_FLUTTER}, + {"roughness", V_ROUGHNESS}, + {"clarity", V_CLARITY}, + {"tone", V_TONE}, + {"voicing", V_VOICING}, + {"breath", V_BREATH}, + {"breathw", V_BREATHW}, + {"numbers", V_NUMBERS}, + {"option", V_OPTION}, + {"mbrola", V_MBROLA}, + {"consonants", V_CONSONANTS}, + {"klatt", V_KLATT}, + {"fast_test", V_FAST}, + + // these just set a value in langopts.param[] + {"l_dieresis", 0x100+LOPT_DIERESES}, +// {"l_lengthen", 0x100+LOPT_IT_LENGTHEN}, + {"l_prefix", 0x100+LOPT_PREFIXES}, + {"l_regressive_v", 0x100+LOPT_REGRESSIVE_VOICING}, + {"l_unpronouncable", 0x100+LOPT_UNPRONOUNCABLE}, + {"l_sonorant_min", 0x100+LOPT_SONORANT_MIN}, + {"l_length_mods", 0x100+LOPT_LENGTH_MODS}, + {NULL, 0} }; + +#define N_VOICE_VARIANTS 12 +const char variants_either[N_VOICE_VARIANTS] = {1,2,12,3,13,4,14,5,11,0}; +const char variants_male[N_VOICE_VARIANTS] = {1,2,3,4,5,0}; +const char variants_female[N_VOICE_VARIANTS] = {11,12,13,14,0}; +const char *variant_lists[3] = {variants_either, variants_male, variants_female}; + +static voice_t voicedata; +voice_t *voice = &voicedata; + + +static char *fgets_strip(char *buf, int size, FILE *f_in) +{//====================================================== +// strip trailing spaces, and truncate lines at // comment + int len; + char *p; + + if(fgets(buf,size,f_in) == NULL) + return(NULL); + + len = strlen(buf); + while((--len > 0) && isspace(buf[len])) + buf[len] = 0; + + if((p = strstr(buf,"//")) != NULL) + *p = 0; + + return(buf); +} + + +static void SetToneAdjust(voice_t *voice, int *tone_pts) +{//===================================================== + int ix; + int pt; + int y; + int freq1=0; + int freq2; + int height1 = tone_pts[1]; + int height2; + double rate; + + for(pt=0; pt<12; pt+=2) + { + if(tone_pts[pt] == -1) + { + tone_pts[pt] = N_TONE_ADJUST*8; + if(pt > 0) + tone_pts[pt+1] = tone_pts[pt-1]; + } + freq2 = tone_pts[pt] / 8; // 8Hz steps + height2 = tone_pts[pt+1]; + if((freq2 - freq1) > 0) + { + rate = double(height2-height1)/(freq2-freq1); + + for(ix=freq1; ix<freq2; ix++) + { + y = height1 + int(rate * (ix-freq1)); + if(y > 255) + y = 255; + voice->tone_adjust[ix] = y; + } + } + freq1 = freq2; + height1 = height2; + } +} + + +void ReadTonePoints(char *string, int *tone_pts) +{//============================================= +// tone_pts[] is int[12] + int ix; + + for(ix=0; ix<12; ix++) + tone_pts[ix] = -1; + + sscanf(string,"%d %d %d %d %d %d %d %d %d %d", + &tone_pts[0],&tone_pts[1],&tone_pts[2],&tone_pts[3], + &tone_pts[4],&tone_pts[5],&tone_pts[6],&tone_pts[7], + &tone_pts[8],&tone_pts[9]); +} + + + + +static espeak_VOICE *ReadVoiceFile(FILE *f_in, const char *fname, const char*leafname) +{//=================================================================================== +// Read a Voice file, allocate a VOICE_DATA and set data from the +// file's language, gender, name lines + + char linebuf[120]; + char vname[80]; + char vgender[80]; + char vlanguage[80]; + char languages[300]; // allow space for several alternate language names and priorities + + + unsigned int len; + int langix = 0; + int n_languages = 0; + char *p; + espeak_VOICE *voice_data; + int priority; + int age; + int n_variants = 3; // default, number of variants of this voice before using another voice + int gender; + +#ifdef PLATFORM_WINDOWS + char fname_buf[sizeof(path_home)+15]; + if(memcmp(leafname,"mb-",3) == 0) + { + // check whether the mbrola speech data is present for this voice + memcpy(vname,&leafname[3],3); + vname[3] = 0; + sprintf(fname_buf,"%s/mbrola/%s",path_home,vname); + + if(GetFileLength(fname_buf) <= 0) + return(0); + } +#endif + + vname[0] = 0; + vgender[0] = 0; + age = 0; + + while(fgets_strip(linebuf,sizeof(linebuf),f_in) != NULL) + { + if(memcmp(linebuf,"name",4)==0) + { + p = &linebuf[4]; + while(isspace(*p)) p++; + strncpy0(vname,p,sizeof(vname)); + } + else + if(memcmp(linebuf,"language",8)==0) + { + priority = DEFAULT_LANGUAGE_PRIORITY; + vlanguage[0] = 0; + + sscanf(&linebuf[8],"%s %d",vlanguage,&priority); + len = strlen(vlanguage) + 2; + // check for space in languages[] + if(len < (sizeof(languages)-langix-1)) + { + languages[langix] = priority; + + strcpy(&languages[langix+1],vlanguage); + langix += len; + n_languages++; + } + } + else + if(memcmp(linebuf,"gender",6)==0) + { + sscanf(&linebuf[6],"%s %d",vgender,&age); + } + else + if(memcmp(linebuf,"variants",8)==0) + { + sscanf(&linebuf[8],"%d",&n_variants); + } + } + languages[langix++] = 0; + + gender = LookupMnem(genders,vgender); + + if(n_languages == 0) + { + return(NULL); // no language lines in the voice file + } + + p = (char *)calloc(sizeof(espeak_VOICE) + langix + strlen(fname) + strlen(vname) + 3, 1); + voice_data = (espeak_VOICE *)p; + p = &p[sizeof(espeak_VOICE)]; + + memcpy(p,languages,langix); + voice_data->languages = p; + + strcpy(&p[langix],fname); + voice_data->identifier = &p[langix]; + voice_data->name = &p[langix]; + + if(vname[0] != 0) + { + langix += strlen(fname)+1; + strcpy(&p[langix],vname); + voice_data->name = &p[langix]; + } + + voice_data->age = age; + voice_data->gender = gender; + voice_data->variant = 0; + voice_data->xx1 = n_variants; + return(voice_data); +} // end of ReadVoiceFile + + + + +void VoiceReset(int tone_only) +{//=========================== +// Set voice to the default values + + int pk; + static unsigned char default_heights[N_PEAKS] = {255,255,240,240,220,220,255,255,255}; + static unsigned char default_widths[N_PEAKS] = {128,128,128,160,171,171,128,128,128}; + + static int breath_widths[N_PEAKS] = {0,200,200,400,400,400,600,600,600}; + + // default is: pitch 82,118 +// voice->pitch_base = 0x49000; // default, 73 << 12; +// voice->pitch_range = 0x0f30; // default = 0x1000 + voice->pitch_base = 0x47000; + voice->pitch_range = 3996; + + voice->formant_factor = 256; + + voice->echo_delay = 0; + voice->echo_amp = 0; + voice->flutter = 64; + voice->n_harmonic_peaks = 5; + voice->peak_shape = 0; + voice->voicing = 64; + voice->consonant_amp = 100; + voice->consonant_ampv = 100; + memset(voice->klatt,0,sizeof(voice->klatt)); + memset(speed.fast_settings,0,sizeof(speed.fast_settings)); + +#ifdef PLATFORM_RISCOS + voice->roughness = 1; +#else + voice->roughness = 2; +#endif + + InitBreath(); + for(pk=0; pk<N_PEAKS; pk++) + { + voice->freq[pk] = 256; + voice->height[pk] = default_heights[pk]; + voice->width[pk] = default_widths[pk]*2; + voice->breath[pk] = 0; + voice->breathw[pk] = breath_widths[pk]; // default breath formant woidths + voice->freqadd[pk] = 0; + + // adjust formant smoothing depending on sample rate + formant_rate[pk] = (formant_rate_22050[pk] * 22050)/samplerate; + } + voice->height[2] = 240; // reduce F2 slightly + + // This table provides the opportunity for tone control. + // Adjustment of harmonic amplitudes, steps of 8Hz + // value of 128 means no change +// memset(voice->tone_adjust,128,sizeof(voice->tone_adjust)); +SetToneAdjust(voice,tone_points); + + // default values of speed factors + voice->speedf1 = 256; + voice->speedf2 = 238; + voice->speedf3 = 232; + + if(tone_only == 0) + { + n_replace_phonemes = 0; + option_quiet = 0; + LoadMbrolaTable(NULL,NULL,0); + } +} // end of VoiceReset + + +static void VoiceFormant(char *p) +{//============================== + // Set parameters for a formant + int ix; + int formant; + int freq = 100; + int height = 100; + int width = 100; + int freqadd = 0; + + ix = sscanf(p,"%d %d %d %d %d",&formant,&freq,&height,&width,&freqadd); + if(ix < 2) + return; + + if((formant < 0) || (formant > 8)) + return; + + if(freq >= 0) + voice->freq[formant] = int(freq * 2.56001); + if(height >= 0) + voice->height[formant] = int(height * 2.56001); + if(width >= 0) + voice->width[formant] = int(width * 2.56001); + voice->freqadd[formant] = freqadd; +} + + + + + +static void PhonemeReplacement(int type, char *p) +{//============================================== + int n; + int phon; + int flags = 0; + char phon_string1[12]; + char phon_string2[12]; + + strcpy(phon_string2,"NULL"); + n = sscanf(p,"%d %s %s",&flags,phon_string1,phon_string2); + if((n < 2) || (n_replace_phonemes >= N_REPLACE_PHONEMES)) + return; + + if((phon = LookupPhonemeString(phon_string1)) == 0) + return; // not recognised + + replace_phonemes[n_replace_phonemes].old_ph = phon; + replace_phonemes[n_replace_phonemes].new_ph = LookupPhonemeString(phon_string2); + replace_phonemes[n_replace_phonemes++].type = flags; +} // end of PhonemeReplacement + + + +static int Read8Numbers(char *data_in,int *data) +{//============================================= +// Read 8 integer numbers + return(sscanf(data_in,"%d %d %d %d %d %d %d %d", + &data[0],&data[1],&data[2],&data[3],&data[4],&data[5],&data[6],&data[7])); +} + + +voice_t *LoadVoice(const char *vname, int control) +{//=============================================== +// control, bit 0 1= no_default +// bit 1 1 = change tone only, not language +// bit 2 1 = don't report error on LoadDictionary +// bit 4 1 = vname = full path + + FILE *f_voice = NULL; + keywtab_t *k; + char *p; + int key; + int ix; + int n; + int value; + int error = 0; + int langix = 0; + int tone_only = control & 2; + int language_set = 0; + int phonemes_set = 0; + int stress_amps_set = 0; + int stress_lengths_set = 0; + int stress_add_set = 0; + int conditional_rules = 0; + LANGUAGE_OPTIONS *langopts = NULL; + + Translator *new_translator = NULL; + + char voicename[40]; + char language_name[40]; + char translator_name[40]; + char new_dictionary[40]; + char phonemes_name[40]; + const char *language_type; + char buf[200]; + char path_voices[sizeof(path_home)+12]; + char langname[4]; + + int stress_amps[8]; + int stress_lengths[8]; + int stress_add[8]; + + int pitch1; + int pitch2; + + static char voice_identifier[40]; // file name for voice_selected + static char voice_name[40]; // voice name for voice_selected + static char voice_languages[100]; // list of languages and priorities for voice_selected + + strcpy(voicename,vname); + if(voicename[0]==0) + strcpy(voicename,"default"); + + if(control & 0x10) + { + strcpy(buf,vname); + if(GetFileLength(buf) <= 0) + return(NULL); + } + else + { + sprintf(path_voices,"%s%cvoices%c",path_home,PATHSEP,PATHSEP); + sprintf(buf,"%s%s",path_voices,voicename); + + if(GetFileLength(buf) <= 0) + { + // look for the voice in a sub-directory of the language name + langname[0] = voicename[0]; + langname[1] = voicename[1]; + langname[2] = 0; + sprintf(buf,"%s%s%c%s",path_voices,langname,PATHSEP,voicename); + + if(GetFileLength(buf) <= 0) + { + // look in "test" sub-directory + sprintf(buf,"%stest%c%s",path_voices,PATHSEP,voicename); + } + } + } + + f_voice = fopen(buf,"r"); + + language_type = "en"; // default + if(f_voice == NULL) + { + if(control & 3) + return(NULL); // can't open file + + if(SelectPhonemeTableName(voicename) >= 0) + language_type = voicename; + } + + if(!tone_only && (translator != NULL)) + { + DeleteTranslator(translator); + translator = NULL; + } + + strcpy(translator_name,language_type); + strcpy(new_dictionary,language_type); + strcpy(phonemes_name,language_type); + + + if(!tone_only) + { + voice = &voicedata; + strncpy0(voice_identifier,vname,sizeof(voice_identifier)); + voice_name[0] = 0; + voice_languages[0] = 0; + + voice_selected.identifier = voice_identifier; + voice_selected.name = voice_name; + voice_selected.languages = voice_languages; + } + else + { + // append the variant file name to the voice identifier + if((p = strchr(voice_identifier,'+')) != NULL) + *p = 0; // remove previous variant name + sprintf(buf,"+%s",&vname[3]); // omit !v/ from the variant filename + strcat(voice_identifier,buf); + langopts = &translator->langopts; + } + VoiceReset(tone_only); + + if(!tone_only) + SelectPhonemeTableName(phonemes_name); // set up phoneme_tab + + + while((f_voice != NULL) && (fgets_strip(buf,sizeof(buf),f_voice) != NULL)) + { + // isolate the attribute name + for(p=buf; (*p != 0) && !isspace(*p); p++); + *p++ = 0; + + if(buf[0] == 0) continue; + + key = 0; + for(k=keyword_tab; k->mnem != NULL; k++) + { + if(strcmp(buf,k->mnem)==0) + { + key = k->data; + break; + } + } + + switch(key) + { + case V_LANGUAGE: + { + unsigned int len; + int priority; + + if(tone_only) + break; + + priority = DEFAULT_LANGUAGE_PRIORITY; + language_name[0] = 0; + + sscanf(p,"%s %d",language_name,&priority); + if(strcmp(language_name,"variant") == 0) + break; + + len = strlen(language_name) + 2; + // check for space in languages[] + if(len < (sizeof(voice_languages)-langix-1)) + { + voice_languages[langix] = priority; + + strcpy(&voice_languages[langix+1],language_name); + langix += len; + } + + // only act on the first language line + if(language_set == 0) + { + language_type = strtok(language_name,"-"); + language_set = 1; + strcpy(translator_name,language_type); + strcpy(new_dictionary,language_type); + strcpy(phonemes_name,language_type); + SelectPhonemeTableName(phonemes_name); + + if(new_translator != NULL) + DeleteTranslator(new_translator); + + new_translator = SelectTranslator(translator_name); + langopts = &new_translator->langopts; + } + } + break; + + case V_NAME: + if(tone_only == 0) + { + while(isspace(*p)) p++; + strncpy0(voice_name,p,sizeof(voice_name)); + } + break; + + case V_GENDER: + { + int age; + char vgender[80]; + sscanf(p,"%s %d",vgender,&age); + voice_selected.gender = LookupMnem(genders,vgender); + voice_selected.age = age; + } + break; + + case V_TRANSLATOR: + if(tone_only) break; + + sscanf(p,"%s",translator_name); + + if(new_translator != NULL) + DeleteTranslator(new_translator); + + new_translator = SelectTranslator(translator_name); + langopts = &new_translator->langopts; + break; + + case V_DICTIONARY: // dictionary + sscanf(p,"%s",new_dictionary); + break; + + case V_PHONEMES: // phoneme table + sscanf(p,"%s",phonemes_name); + break; + + case V_FORMANT: + VoiceFormant(p); + break; + + case V_PITCH: + { + double factor; + // default is pitch 82 118 + n = sscanf(p,"%d %d",&pitch1,&pitch2); + voice->pitch_base = (pitch1 - 9) << 12; + voice->pitch_range = (pitch2 - pitch1) * 108; + factor = double(pitch1 - 82)/82; + voice->formant_factor = (int)((1+factor/4) * 256); // nominal formant shift for a different voice pitch + } + break; + + case V_STRESSLENGTH: // stressLength + stress_lengths_set = Read8Numbers(p,stress_lengths); + break; + + case V_STRESSAMP: // stressAmp + stress_amps_set = Read8Numbers(p,stress_amps); + break; + + case V_STRESSADD: // stressAdd + stress_add_set = Read8Numbers(p,stress_add); + break; + + case V_INTONATION: // intonation + sscanf(p,"%d %d",&option_tone_flags,&option_tone2); + if((option_tone_flags & 0xff) != 0) + langopts->intonation_group = option_tone_flags & 0xff; + break; + + case V_DICTRULES: // conditional dictionary rules and list entries + while(*p != 0) + { + while(isspace(*p)) p++; + n = -1; + if(((n = atoi(p)) > 0) && (n < 32)) + { + p++; + conditional_rules |= (1 << n); + } + while(isalnum(*p)) p++; + } + break; + + case V_REPLACE: + if(phonemes_set == 0) + { + // must set up a phoneme table before we can lookup phoneme mnemonics + SelectPhonemeTableName(phonemes_name); + phonemes_set = 1; + } + PhonemeReplacement(key,p); + break; + + case V_WORDGAP: // words + sscanf(p,"%d %d",&langopts->word_gap, &langopts->vowel_pause); + break; + + case V_STRESSRULE: + sscanf(p,"%d %d %d %d",&langopts->stress_rule, + &langopts->stress_flags, + &langopts->unstressed_wd1, + &langopts->unstressed_wd2); + break; + + case V_CHARSET: + if((sscanf(p,"%d",&value)==1) && (value < N_CHARSETS)) + new_translator->charset_a0 = charsets[value]; + break; + + case V_NUMBERS: + sscanf(p,"%d %d",&langopts->numbers,&langopts->numbers2); + break; + + case V_OPTION: + if(sscanf(p,"%d %d",&ix,&value) == 2) + { + if((ix >= 0) && (ix < N_LOPTS)) + langopts->param[ix] = value; + } + break; + + case V_ECHO: + // echo. suggest: 135mS 11% + value = 0; + voice->echo_amp = 0; + sscanf(p,"%d %d",&voice->echo_delay,&voice->echo_amp); + break; + + case V_FLUTTER: // flutter + if(sscanf(p,"%d",&value)==1) + voice->flutter = value * 32; + break; + + case V_ROUGHNESS: // roughness + if(sscanf(p,"%d",&value)==1) + voice->roughness = value; + break; + + case V_CLARITY: // formantshape + if(sscanf(p,"%d",&value)==1) + { + if(value > 4) + { + voice->peak_shape = 1; // squarer formant peaks + value = 4; + } + voice->n_harmonic_peaks = 1+value; + } + break; + + case V_TONE: + { + int tone_data[10]; + ReadTonePoints(p,tone_data); + SetToneAdjust(voice,tone_data); + } + break; + + case V_VOICING: + if(sscanf(p,"%d",&value)==1) + voice->voicing = (value * 64)/100; + break; + + case V_BREATH: + voice->breath[0] = Read8Numbers(p,&voice->breath[1]); + for(ix=1; ix<8; ix++) + { + if(ix % 2) + voice->breath[ix] = -voice->breath[ix]; + } + break; + + case V_BREATHW: + voice->breathw[0] = Read8Numbers(p,&voice->breathw[1]); + break; + + case V_CONSONANTS: + value = sscanf(p,"%d %d",&voice->consonant_amp, &voice->consonant_ampv); + break; + + case V_MBROLA: + { + int srate = 16000; + char name[40]; + char phtrans[40]; + + phtrans[0] = 0; + sscanf(p,"%s %s %d",name,phtrans,&srate); + LoadMbrolaTable(name,phtrans,srate); + } + break; + + case V_KLATT: + Read8Numbers(p,voice->klatt); + voice->klatt[KLATT_Kopen] -= 40; + break; + + case V_FAST: + Read8Numbers(p,speed.fast_settings); + SetSpeed(2); + break; + + default: + if((key & 0xff00) == 0x100) + { + sscanf(p,"%d",&langopts->param[key &0xff]); + } + else + { + fprintf(stderr,"Bad voice attribute: %s\n",buf); + } + break; + } + } + if(f_voice != NULL) + fclose(f_voice); + + if((new_translator == NULL) && (!tone_only)) + { + // not set by language attribute + new_translator = SelectTranslator(translator_name); + } + + for(ix=0; ix<N_PEAKS; ix++) + { + voice->freq2[ix] = voice->freq[ix]; + voice->height2[ix] = voice->height[ix]; + voice->width2[ix] = voice->width[ix]; + } + + if(tone_only) + { + new_translator = translator; + } + else + { + if((ix = SelectPhonemeTableName(phonemes_name)) < 0) + { + fprintf(stderr,"Unknown phoneme table: '%s'\n",phonemes_name); + } + voice->phoneme_tab_ix = ix; + error = LoadDictionary(new_translator, new_dictionary, control & 4); + if(dictionary_name[0]==0) + return(NULL); // no dictionary loaded + + new_translator->dict_condition = conditional_rules; + + voice_languages[langix] = 0; + } + + langopts = &new_translator->langopts; + + + if((value = langopts->param[LOPT_LENGTH_MODS]) != 0) + { + SetLengthMods(new_translator,value); + } + + voice->width[0] = (voice->width[0] * 105)/100; + + if(!tone_only) + { + translator = new_translator; + } + + // relative lengths of different stress syllables + for(ix=0; ix<stress_lengths_set; ix++) + { + translator->stress_lengths[ix] = stress_lengths[ix]; + } + for(ix=0; ix<stress_add_set; ix++) + { + translator->stress_lengths[ix] += stress_add[ix]; + } + for(ix=0; ix<stress_amps_set; ix++) + { + translator->stress_amps[ix] = stress_amps[ix]; + translator->stress_amps_r[ix] = stress_amps[ix] -1; + } + + return(voice); +} // end of LoadVoice + + +static char *ExtractVoiceVariantName(char *vname, int variant_num) +{//=============================================================== +// Remove any voice variant suffix (name or number) from a voice name +// Returns the voice variant name + + char *p; + static char variant_name[20]; + char variant_prefix[5]; + + variant_name[0] = 0; + sprintf(variant_prefix,"!v%c",PATHSEP); + + if(vname != NULL) + { + if((p = strchr(vname,'+')) != NULL) + { + // The voice name has a +variant suffix + *p++ = 0; // delete the suffix from the voice name + if(isdigit(*p)) + { + variant_num = atoi(p); // variant number + } + else + { + // voice variant name, not number + strcpy(variant_name,variant_prefix); + strncpy0(&variant_name[3],p,sizeof(variant_name)-3); + } + } + } + + if(variant_num > 0) + { + if(variant_num < 10) + sprintf(variant_name,"%sm%d",variant_prefix, variant_num); // male + else + sprintf(variant_name,"%sf%d",variant_prefix, variant_num-10); // female + } + + return(variant_name); +} // end of ExtractVoiceVariantName + + + +voice_t *LoadVoiceVariant(const char *vname, int variant_num) +{//========================================================== +// Load a voice file. +// Also apply a voice variant if specified by "variant", or by "+number" or "+name" in the "vname" + + voice_t *v; + char *variant_name; + char buf[60]; + + strncpy0(buf,vname,sizeof(buf)); + variant_name = ExtractVoiceVariantName(buf,variant_num); + + if((v = LoadVoice(buf,0)) == NULL) + return(NULL); + + if(variant_name[0] != 0) + { + v = LoadVoice(variant_name,2); + } + return(v); +} + + + +static int __cdecl VoiceNameSorter(const void *p1, const void *p2) +{//======================================================= + int ix; + espeak_VOICE *v1 = *(espeak_VOICE **)p1; + espeak_VOICE *v2 = *(espeak_VOICE **)p2; + + + if((ix = strcmp(&v1->languages[1],&v2->languages[1])) != 0) // primary language name + return(ix); + if((ix = v1->languages[0] - v2->languages[0]) != 0) // priority number + return(ix); + return(strcmp(v1->name,v2->name)); +} + + +static int __cdecl VoiceScoreSorter(const void *p1, const void *p2) +{//======================================================== + int ix; + espeak_VOICE *v1 = *(espeak_VOICE **)p1; + espeak_VOICE *v2 = *(espeak_VOICE **)p2; + + if((ix = v2->score - v1->score) != 0) + return(ix); + return(strcmp(v1->name,v2->name)); +} + + +static int ScoreVoice(espeak_VOICE *voice_spec, const char *spec_language, int spec_n_parts, int spec_lang_len, espeak_VOICE *voice) +{//========================================================================================================================= + int ix; + const char *p; + int c1, c2; + int language_priority; + int n_parts; + int matching; + int matching_parts; + int score = 0; + int x; + int ratio; + int required_age; + int diff; + + p = voice->languages; // list of languages+dialects for which this voice is suitable + + if(strcmp(spec_language,"mbrola")==0) + { + // only list mbrola voices + if(memcmp(voice->identifier,"mb/",3) == 0) + return(100); + return(0); + } + + if(spec_n_parts == 0) + { + score = 100; + } + else + { + if((*p == 0) && (strcmp(spec_language,"variants")==0)) + { + // match on a voice with no languages if the required language is "variants" + score = 100; + } + + // compare the required language with each of the languages of this voice + while(*p != 0) + { + language_priority = *p++; + + matching = 1; + matching_parts = 0; + n_parts = 1; + + for(ix=0; ; ix++) + { + if((ix >= spec_lang_len) || ((c1 = spec_language[ix]) == '-')) + c1 = 0; + if((c2 = p[ix]) == '-') + c2 = 0; + + if(c1 != c2) + { + matching = 0; + } + + if(p[ix] == '-') + { + n_parts++; + if(matching) + matching_parts++; + } + if(p[ix] == 0) + break; + } + p += (ix+1); + matching_parts += matching; // number of parts which match + + if(matching_parts == 0) + continue; // no matching parts for this language + + x = 5; + // reduce the score if not all parts of the required language match + if((diff = (spec_n_parts - matching_parts)) > 0) + x -= diff; + + // reduce score if the language is more specific than required + if((diff = (n_parts - matching_parts)) > 0) + x -= diff; + + x = x*100 - (language_priority * 2); + + if(x > score) + score = x; + } + } + if(score == 0) + return(0); + + if(voice_spec->name != NULL) + { + if(strcmp(voice_spec->name,voice->name)==0) + { + // match on voice name + score += 500; + } + else + if(strcmp(voice_spec->name,voice->identifier)==0) + { + score += 400; + } + } + + if(((voice_spec->gender == 1) || (voice_spec->gender == 2)) && + ((voice->gender == 1) || (voice->gender == 2))) + { + if(voice_spec->gender == voice->gender) + score += 50; + else + score -= 50; + } + + if((voice_spec->age <= 12) && (voice->gender == 2) && (voice->age > 12)) + { + score += 5; // give some preference for non-child female voice if a child is requested + } + + if(voice->age != 0) + { + if(voice_spec->age == 0) + required_age = 30; + else + required_age = voice_spec->age; + + ratio = (required_age*100)/voice->age; + if(ratio < 100) + ratio = 10000/ratio; + ratio = (ratio - 100)/10; // 0=exact match, 10=out by factor of 2 + x = 5 - ratio; + if(x > 0) x = 0; + + score = score + x; + + if(voice_spec->age > 0) + score += 10; // required age specified, favour voices with a specified age (near it) + } + if(score < 1) + score = 1; + return(score); +} // end of ScoreVoice + + +static int SetVoiceScores(espeak_VOICE *voice_select, espeak_VOICE **voices, int control) +{//====================================================================================== +// control: bit0=1 include mbrola voices + int ix; + int score; + int nv; // number of candidates + int n_parts=0; + int lang_len=0; + espeak_VOICE *vp; + char language[80]; + + // count number of parts in the specified language + if((voice_select->languages != NULL) && (voice_select->languages[0] != 0)) + { + n_parts = 1; + lang_len = strlen(voice_select->languages); + for(ix=0; (ix<=lang_len) && ((unsigned)ix < sizeof(language)); ix++) + { + if((language[ix] = tolower(voice_select->languages[ix])) == '-') + n_parts++; + } + } + // select those voices which match the specified language + nv = 0; + for(ix=0; ix<n_voices_list; ix++) + { + vp = voices_list[ix]; + + if(((control & 1) == 0) && (memcmp(vp->identifier,"mb/",3) == 0)) + continue; + + if((score = ScoreVoice(voice_select, language, n_parts, lang_len, voices_list[ix])) > 0) + { + voices[nv++] = vp; + vp->score = score; + } + } + voices[nv] = NULL; // list terminator + + if(nv==0) + return(0); + + // sort the selected voices by their score + qsort(voices,nv,sizeof(espeak_VOICE *),(int (__cdecl *)(const void *,const void *))VoiceScoreSorter); + + return(nv); +} // end of SetVoiceScores + + + + +espeak_VOICE *SelectVoiceByName(espeak_VOICE **voices, const char *name) +{//===================================================================== + int ix; + int match_fname = -1; + int match_fname2 = -1; + int match_name = -1; + const char *id; + int last_part_len; + char last_part[41]; + + if(voices == NULL) + { + if(n_voices_list == 0) + espeak_ListVoices(NULL); // create the voices list + voices = voices_list; + } + + sprintf(last_part,"%c%s",PATHSEP,name); + last_part_len = strlen(last_part); + + for(ix=0; voices[ix] != NULL; ix++) + { + if(strcmp(name,voices[ix]->name)==0) + { + match_name = ix; // found matching voice name + break; + } + else + if(strcmp(name,id = voices[ix]->identifier)==0) + { + match_fname = ix; // matching identifier, use this if no matching name + } + else + if(strcmp(last_part,&id[strlen(id)-last_part_len])==0) + { + match_fname2 = ix; + } + } + + if(match_name < 0) + { + match_name = match_fname; // no matching name, try matching filename + if(match_name < 0) + match_name = match_fname2; // try matching just the last part of the filename + } + + if(match_name < 0) + return(NULL); + + return(voices[match_name]); +} // end of SelectVoiceByName + + + + +char const *SelectVoice(espeak_VOICE *voice_select, int *found) +{//============================================================ +// Returns a path within espeak-voices, with a possible +variant suffix +// variant is an output-only parameter + int nv; // number of candidates + int ix, ix2; + int j; + int n_variants; + int variant_number; + int gender; + int skip; + int aged=1; + char *variant_name; + const char *p, *p_start; + espeak_VOICE *vp = NULL; + espeak_VOICE *vp2; + espeak_VOICE voice_select2; + espeak_VOICE *voices[N_VOICES_LIST]; // list of candidates + espeak_VOICE *voices2[N_VOICES_LIST+N_VOICE_VARIANTS]; + static espeak_VOICE voice_variants[N_VOICE_VARIANTS]; + static char voice_id[50]; + + *found = 1; + memcpy(&voice_select2,voice_select,sizeof(voice_select2)); + + if(n_voices_list == 0) + espeak_ListVoices(NULL); // create the voices list + + if((voice_select2.languages == NULL) || (voice_select2.languages[0] == 0)) + { + // no language is specified. Get language from the named voice + static char buf[60]; + + if(voice_select2.name == NULL) + { + if((voice_select2.name = voice_select2.identifier) == NULL) + voice_select2.name = "default"; + } + + strncpy0(buf,voice_select2.name,sizeof(buf)); + variant_name = ExtractVoiceVariantName(buf,0); + + vp = SelectVoiceByName(voices_list,buf); + if(vp != NULL) + { + voice_select2.languages = &(vp->languages[1]); + + if((voice_select2.gender==0) && (voice_select2.age==0) && (voice_select2.variant==0)) + { + if(variant_name[0] != 0) + { + sprintf(voice_id,"%s+%s",vp->identifier,&variant_name[3]); // omit the !v/ from variant_name + return(voice_id); + } + + return(vp->identifier); + } + } + } + + // select and sort voices for the required language + nv = SetVoiceScores(&voice_select2,voices,0); + + if(nv == 0) + { + // no matching voice, choose the default + *found = 0; + if((voices[0] = SelectVoiceByName(voices_list,"default")) != NULL) + nv = 1; + } + + gender = 0; + if((voice_select2.gender == 2) || ((voice_select2.age > 0) && (voice_select2.age < 13))) + gender = 2; + else + if(voice_select2.gender == 1) + gender = 1; + +#define AGE_OLD 60 + if(voice_select2.age < AGE_OLD) + aged = 0; + + p = p_start = variant_lists[gender]; + if(aged == 0) + p++; // the first voice in the variants list is older + + // add variants for the top voices + n_variants = 0; + for(ix=0, ix2=0; ix<nv; ix++) + { + vp = voices[ix]; + // is the main voice the required gender? + skip=0; + if((gender != 0) && (vp->gender != gender)) + { + skip=1; + } + if((ix2==0) && aged && (vp->age < AGE_OLD)) + { + skip=1; + } + if(skip==0) + { + voices2[ix2++] = vp; + } + + for(j=0; (j < vp->xx1) && (n_variants < N_VOICE_VARIANTS);) + { + if((variant_number = *p) == 0) + { + p = p_start; + continue; + } + + vp2 = &voice_variants[n_variants++]; // allocate space for voice variant + memcpy(vp2,vp,sizeof(espeak_VOICE)); // copy from the original voice + vp2->variant = variant_number; + voices2[ix2++] = vp2; + p++; + j++; + } + } + // add any more variants to the end of the list + while((vp != NULL) && ((variant_number = *p++) != 0) && (n_variants < N_VOICE_VARIANTS)) + { + vp2 = &voice_variants[n_variants++]; // allocate space for voice variant + memcpy(vp2,vp,sizeof(espeak_VOICE)); // copy from the original voice + vp2->variant = variant_number; + voices2[ix2++] = vp2; + } + + // index the sorted list by the required variant number + vp = voices2[voice_select2.variant % ix2]; + + if(vp->variant != 0) + { + variant_name = ExtractVoiceVariantName(NULL,vp->variant); + sprintf(voice_id,"%s+%s",vp->identifier,&variant_name[3]); + return(voice_id); + } + + return(vp->identifier); +} // end of SelectVoice + + + +static void GetVoices(const char *path) +{//==================================== + FILE *f_voice; + espeak_VOICE *voice_data; + int ftype; + char fname[sizeof(path_home)+100]; + +#ifdef PLATFORM_RISCOS + int len; + int *type; + char *p; + _kernel_swi_regs regs; + _kernel_oserror *error; + char buf[80]; + char directory2[sizeof(path_home)+100]; + + regs.r[0] = 10; + regs.r[1] = (int)path; + regs.r[2] = (int)buf; + regs.r[3] = 1; + regs.r[4] = 0; + regs.r[5] = sizeof(buf); + regs.r[6] = 0; + + while(regs.r[3] > 0) + { + error = _kernel_swi(0x0c+0x20000,®s,®s); /* OS_GBPB 10, read directory entries */ + if((error != NULL) || (regs.r[3] == 0)) + { + break; + } + type = (int *)(&buf[16]); + len = strlen(&buf[20]); + sprintf(fname,"%s.%s",path,&buf[20]); + + if(*type == 2) + { + // a sub-directory + GetVoices(fname); + } + else + { + // a regular line, add it to the voices list + if((f_voice = fopen(fname,"r")) == NULL) + continue; + + // pass voice file name within the voices directory + voice_data = ReadVoiceFile(f_voice, fname+len_path_voices, &buf[20]); + fclose(f_voice); + + if(voice_data != NULL) + { + voices_list[n_voices_list++] = voice_data; + } + } + } +#else +#ifdef PLATFORM_WINDOWS + WIN32_FIND_DATAA FindFileData; + HANDLE hFind = INVALID_HANDLE_VALUE; + +#undef UNICODE // we need FindFirstFileA() which takes an 8-bit c-string + sprintf(fname,"%s\\*",path); + hFind = FindFirstFileA(fname, &FindFileData); + if(hFind == INVALID_HANDLE_VALUE) + return; + + do { + sprintf(fname,"%s%c%s",path,PATHSEP,FindFileData.cFileName); + + ftype = GetFileLength(fname); + + if((ftype == -2) && (FindFileData.cFileName[0] != '.')) + { + // a sub-sirectory + GetVoices(fname); + } + else + if(ftype > 0) + { + // a regular line, add it to the voices list + if((f_voice = fopen(fname,"r")) == NULL) + continue; + + // pass voice file name within the voices directory + voice_data = ReadVoiceFile(f_voice, fname+len_path_voices, FindFileData.cFileName); + fclose(f_voice); + + if(voice_data != NULL) + { + voices_list[n_voices_list++] = voice_data; + } + } + } while(FindNextFileA(hFind, &FindFileData) != 0); + FindClose(hFind); + +#else + DIR *dir; + struct dirent *ent; + + if((dir = opendir(path)) == NULL) + return; + + while((ent = readdir(dir)) != NULL) + { + if(n_voices_list >= (N_VOICES_LIST-2)) + break; // voices list is full + + sprintf(fname,"%s%c%s",path,PATHSEP,ent->d_name); + + ftype = GetFileLength(fname); + + if((ftype == -2) && (ent->d_name[0] != '.')) + { + // a sub-sirectory + GetVoices(fname); + } + else + if(ftype > 0) + { + // a regular line, add it to the voices list + if((f_voice = fopen(fname,"r")) == NULL) + continue; + + // pass voice file name within the voices directory + voice_data = ReadVoiceFile(f_voice, fname+len_path_voices, ent->d_name); + fclose(f_voice); + + if(voice_data != NULL) + { + voices_list[n_voices_list++] = voice_data; + } + } + } + closedir(dir); +#endif +#endif +} // end of GetVoices + + + +espeak_ERROR SetVoiceByName(const char *name) +{//========================================= + espeak_VOICE *v; + espeak_VOICE voice_selector; + char *variant_name; + static char buf[60]; + + strncpy0(buf,name,sizeof(buf)); + variant_name = ExtractVoiceVariantName(buf,0); + + memset(&voice_selector,0,sizeof(voice_selector)); +// voice_selector.name = buf; + voice_selector.name = (char *)name; // include variant name in voice stack ?? + + // first check for a voice with this filename + // This may avoid the need to call espeak_ListVoices(). + + if(LoadVoice(buf,1) != NULL) + { + if(variant_name[0] != 0) + { + LoadVoice(variant_name,2); + } + + DoVoiceChange(voice); + SetVoiceStack(&voice_selector); + return(EE_OK); + } + + if(n_voices_list == 0) + espeak_ListVoices(NULL); // create the voices list + + if((v = SelectVoiceByName(voices_list,buf)) != NULL) + { + if(LoadVoice(v->identifier,0) != NULL) + { + if(variant_name[0] != 0) + { + LoadVoice(variant_name,2); + } + DoVoiceChange(voice); + SetVoiceStack(&voice_selector); + return(EE_OK); + } + } + return(EE_INTERNAL_ERROR); // voice name not found +} // end of SetVoiceByName + + + +espeak_ERROR SetVoiceByProperties(espeak_VOICE *voice_selector) +{//============================================================ + const char *voice_id; + int voice_found; + + voice_id = SelectVoice(voice_selector, &voice_found); + + if(voice_found == 0) + return(EE_NOT_FOUND); + + LoadVoiceVariant(voice_id,0); + DoVoiceChange(voice); + SetVoiceStack(voice_selector); + + return(EE_OK); +} // end of SetVoiceByProperties + + + + +//======================================================================= +// Library Interface Functions +//======================================================================= +//#pragma GCC visibility push(default) + + +ESPEAK_API const espeak_VOICE **espeak_ListVoices(espeak_VOICE *voice_spec) +{//======================================================================== +#ifndef PLATFORM_RISCOS + int ix; + int j; + espeak_VOICE *v; + static espeak_VOICE *voices[N_VOICES_LIST]; + char path_voices[sizeof(path_home)+12]; + + // free previous voice list data + + for(ix=0; ix<n_voices_list; ix++) + { + if(voices_list[ix] != NULL) + free(voices_list[ix]); + } + n_voices_list = 0; + + sprintf(path_voices,"%s%cvoices",path_home,PATHSEP); + len_path_voices = strlen(path_voices)+1; + + GetVoices(path_voices); + voices_list[n_voices_list] = NULL; // voices list terminator + + // sort the voices list + qsort(voices_list,n_voices_list,sizeof(espeak_VOICE *), + (int (__cdecl *)(const void *,const void *))VoiceNameSorter); + + + if(voice_spec) + { + // select the voices which match the voice_spec, and sort them by preference + SetVoiceScores(voice_spec,voices,1); + } + else + { + // list all: omit variant voices and mbrola voices + j = 0; + for(ix=0; (v = voices_list[ix]) != NULL; ix++) + { + if((v->languages[0] != 0) && (strcmp(&v->languages[1],"variant") != 0) && (memcmp(v->identifier,"mb/",3) != 0)) + { + voices[j++] = v; + } + } + voices[j] = NULL; + } + return((const espeak_VOICE **)voices); +#endif + return((const espeak_VOICE **)voices_list); +} // end of espeak_ListVoices + + + +ESPEAK_API espeak_VOICE *espeak_GetCurrentVoice(void) +{//================================================== + return(&voice_selected); +} + +//#pragma GCC visibility pop + + diff --git a/Plugins/eSpeak/eSpeak/wave.h b/Plugins/eSpeak/eSpeak/wave.h new file mode 100644 index 0000000..8b6e606 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/wave.h @@ -0,0 +1,43 @@ +#ifndef WAVE_H +#define WAVE_H + +#ifndef PLATFORM_DOS +#include "stdint.h" +#endif + +extern int option_device_number; + +extern void wave_init(); +// TBD: the arg could be "alsa", "oss",... +extern void* wave_open(const char* the_api); + +extern size_t wave_write(void* theHandler, char* theMono16BitsWaveBuffer, size_t theSize); +extern int wave_close(void* theHandler); +extern void wave_flush(void* theHandler); +extern int wave_is_busy(void* theHandler); +extern void wave_terminate(); +extern uint32_t wave_get_read_position(void* theHandler); +extern uint32_t wave_get_write_position(void* theHandler); + +// Supply the remaining time in ms before the sample is played +// (or 0 if the event has been already played). +// sample: sample identifier +// time: supplied value in ms +// +// return 0 if ok or -1 otherwise (stream not opened). +extern int wave_get_remaining_time(uint32_t sample, uint32_t* time); + +// set the callback which informs if the output is still enabled. +// Helpful if a new sample is waiting for free space whereas sound must be stopped. +typedef int (t_wave_callback)(void); +extern void wave_set_callback_is_output_enabled(t_wave_callback* cb); + + +// general functions +extern void clock_gettime2(struct timespec *ts); +extern void add_time_in_ms(struct timespec *ts, int time_in_ms); + +// for tests +extern void *wave_test_get_write_buffer(); + +#endif diff --git a/Plugins/eSpeak/eSpeak/wavegen.cpp b/Plugins/eSpeak/eSpeak/wavegen.cpp new file mode 100644 index 0000000..a5d7df7 --- /dev/null +++ b/Plugins/eSpeak/eSpeak/wavegen.cpp @@ -0,0 +1,1917 @@ +/*************************************************************************** + * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * email: jonsd@users.sourceforge.net * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see: * + * <http://www.gnu.org/licenses/>. * + ***************************************************************************/ + +#include "StdAfx.h" + +// this version keeps wavemult window as a constant fraction +// of the cycle length - but that spreads out the HF peaks too much + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <math.h> + + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "voice.h" + +//#undef INCLUDE_KLATT + +#ifdef USE_PORTAUDIO +#include "portaudio.h" +#undef USE_PORTAUDIO +// determine portaudio version by looking for a #define which is not in V18 +#ifdef paNeverDropInput +#define USE_PORTAUDIO 19 +#else +#define USE_PORTAUDIO 18 +#endif +#endif + +#define N_SINTAB 2048 +#include "sintab.h" + + +#define PI 3.1415927 +#define PI2 6.283185307 +#define N_WAV_BUF 10 + +voice_t *wvoice; + +FILE *f_log = NULL; +int option_waveout = 0; +static int option_harmonic1 = 10; // 10 +int option_log_frames = 0; +static int flutter_amp = 64; + +static int general_amplitude = 60; +static int consonant_amp = 26; // 24 + +int embedded_value[N_EMBEDDED_VALUES]; + +static int PHASE_INC_FACTOR; +int samplerate = 0; // this is set by Wavegeninit() +int samplerate_native=0; +extern int option_device_number; +extern int option_quiet; + +static wavegen_peaks_t peaks[N_PEAKS]; +static int peak_harmonic[N_PEAKS]; +static int peak_height[N_PEAKS]; + +#define N_ECHO_BUF 5500 // max of 250mS at 22050 Hz +static int echo_head; +static int echo_tail; +static int echo_length = 0; // period (in sample\) to ensure completion of echo at the end of speech, set in WavegenSetEcho() +static int echo_amp = 0; +static short echo_buf[N_ECHO_BUF]; + +static int voicing; +static RESONATOR rbreath[N_PEAKS]; + +static int harm_sqrt_n = 0; + + +#define N_LOWHARM 30 +static int harm_inc[N_LOWHARM]; // only for these harmonics do we interpolate amplitude between steps +static int *harmspect; +static int hswitch=0; +static int hspect[2][MAX_HARMONIC]; // 2 copies, we interpolate between then +static int max_hval=0; + +static int nsamples=0; // number to do +static int modulation_type = 0; +static int glottal_flag = 0; +static int glottal_reduce = 0; + + +WGEN_DATA wdata; + +static int amp_ix; +static int amp_inc; +static unsigned char *amplitude_env = NULL; + +static int samplecount=0; // number done +static int samplecount_start=0; // count at start of this segment +static int end_wave=0; // continue to end of wave cycle +static int wavephase; +static int phaseinc; +static int cycle_samples; // number of samples in a cycle at current pitch +static int cbytes; +static int hf_factor; + +static double minus_pi_t; +static double two_pi_t; + + +unsigned char *out_ptr; +unsigned char *out_start; +unsigned char *out_end; +int outbuf_size = 0; + +// the queue of operations passed to wavegen from sythesize +long wcmdq[N_WCMDQ][4]; +int wcmdq_head=0; +int wcmdq_tail=0; + +// pitch,speed, +int embedded_default[N_EMBEDDED_VALUES] = {0,50,170,100,50, 0,0, 0,170,0,0,0,0,0}; +static int embedded_max[N_EMBEDDED_VALUES] = {0,0x7fff,600,300,99,99,99, 0,600,0,0,0,0,4}; + +#define N_CALLBACK_IX N_WAV_BUF-2 // adjust this delay to match display with the currently spoken word +int current_source_index=0; + +extern FILE *f_wave; + +#if (USE_PORTAUDIO == 18) +static PortAudioStream *pa_stream=NULL; +#endif +#if (USE_PORTAUDIO == 19) +static PaStream *pa_stream=NULL; +#endif + +/* default pitch envelope, a steady fall */ +#define ENV_LEN 128 + + +/* +unsigned char Pitch_env0[ENV_LEN] = { + 255,253,251,249,247,245,243,241,239,237,235,233,231,229,227,225, + 223,221,219,217,215,213,211,209,207,205,203,201,199,197,195,193, + 191,189,187,185,183,181,179,177,175,173,171,169,167,165,163,161, + 159,157,155,153,151,149,147,145,143,141,139,137,135,133,131,129, + 127,125,123,121,119,117,115,113,111,109,107,105,103,101, 99, 97, + 95, 93, 91, 89, 87, 85, 83, 81, 79, 77, 75, 73, 71, 69, 67, 65, + 63, 61, 59, 57, 55, 53, 51, 49, 47, 45, 43, 41, 39, 37, 35, 33, + 31, 29, 27, 25, 23, 21, 19, 17, 15, 13, 11, 9, 7, 5, 3, 1 +}; +*/ + +/* +unsigned char Pitch_long[ENV_LEN] = { + 254,249,250,251,252,253,254,254, 255,255,255,255,254,254,253,252, + 251,250,249,247,244,242,238,234, 230,225,221,217,213,209,206,203, + 199,195,191,187,183,179,175,172, 168,165,162,159,156,153,150,148, + 145,143,140,138,136,134,132,130, 128,126,123,120,117,114,111,107, + 104,100,96,91, 86,82,77,73, 70,66,63,60, 58,55,53,51, + 49,47,46,45, 43,42,40,38, 36,34,31,28, 26,24,22,20, + 18,16,14,12, 11,10,9,8, 8,8,8,8, 9,8,8,8, + 8,8,7,7, 6,6,6,5, 4,4,3,3, 2,1,1,0 +}; +*/ + +// 1st index=roughness +// 2nd index=modulation_type +// value: bits 0-3 amplitude (16ths), bits 4-7 every n cycles +#define N_ROUGHNESS 8 +static unsigned char modulation_tab[N_ROUGHNESS][8] = { + {0, 0x00, 0x00, 0x00, 0, 0x46, 0xf2, 0x29}, + {0, 0x2f, 0x00, 0x2f, 0, 0x45, 0xf2, 0x29}, + {0, 0x2f, 0x00, 0x2e, 0, 0x45, 0xf2, 0x28}, + {0, 0x2e, 0x00, 0x2d, 0, 0x34, 0xf2, 0x28}, + {0, 0x2d, 0x2d, 0x2c, 0, 0x34, 0xf2, 0x28}, + {0, 0x2b, 0x2b, 0x2b, 0, 0x34, 0xf2, 0x28}, + {0, 0x2a, 0x2a, 0x2a, 0, 0x34, 0xf2, 0x28}, + {0, 0x29, 0x29, 0x29, 0, 0x34, 0xf2, 0x28}, +}; + +// Flutter table, to add natural variations to the pitch +#define N_FLUTTER 0x170 +static int Flutter_inc; +static const unsigned char Flutter_tab[N_FLUTTER] = { + 0x80, 0x9b, 0xb5, 0xcb, 0xdc, 0xe8, 0xed, 0xec, + 0xe6, 0xdc, 0xce, 0xbf, 0xb0, 0xa3, 0x98, 0x90, + 0x8c, 0x8b, 0x8c, 0x8f, 0x92, 0x94, 0x95, 0x92, + 0x8c, 0x83, 0x78, 0x69, 0x59, 0x49, 0x3c, 0x31, + 0x2a, 0x29, 0x2d, 0x36, 0x44, 0x56, 0x69, 0x7d, + 0x8f, 0x9f, 0xaa, 0xb1, 0xb2, 0xad, 0xa4, 0x96, + 0x87, 0x78, 0x69, 0x5c, 0x53, 0x4f, 0x4f, 0x55, + 0x5e, 0x6b, 0x7a, 0x88, 0x96, 0xa2, 0xab, 0xb0, + + 0xb1, 0xae, 0xa8, 0xa0, 0x98, 0x91, 0x8b, 0x88, + 0x89, 0x8d, 0x94, 0x9d, 0xa8, 0xb2, 0xbb, 0xc0, + 0xc1, 0xbd, 0xb4, 0xa5, 0x92, 0x7c, 0x63, 0x4a, + 0x32, 0x1e, 0x0e, 0x05, 0x02, 0x05, 0x0f, 0x1e, + 0x30, 0x44, 0x59, 0x6d, 0x7f, 0x8c, 0x96, 0x9c, + 0x9f, 0x9f, 0x9d, 0x9b, 0x99, 0x99, 0x9c, 0xa1, + 0xa9, 0xb3, 0xbf, 0xca, 0xd5, 0xdc, 0xe0, 0xde, + 0xd8, 0xcc, 0xbb, 0xa6, 0x8f, 0x77, 0x60, 0x4b, + + 0x3a, 0x2e, 0x28, 0x29, 0x2f, 0x3a, 0x48, 0x59, + 0x6a, 0x7a, 0x86, 0x90, 0x94, 0x95, 0x91, 0x89, + 0x80, 0x75, 0x6b, 0x62, 0x5c, 0x5a, 0x5c, 0x61, + 0x69, 0x74, 0x80, 0x8a, 0x94, 0x9a, 0x9e, 0x9d, + 0x98, 0x90, 0x86, 0x7c, 0x71, 0x68, 0x62, 0x60, + 0x63, 0x6b, 0x78, 0x88, 0x9b, 0xaf, 0xc2, 0xd2, + 0xdf, 0xe6, 0xe7, 0xe2, 0xd7, 0xc6, 0xb2, 0x9c, + 0x84, 0x6f, 0x5b, 0x4b, 0x40, 0x39, 0x37, 0x38, + + 0x3d, 0x43, 0x4a, 0x50, 0x54, 0x56, 0x55, 0x52, + 0x4d, 0x48, 0x42, 0x3f, 0x3e, 0x41, 0x49, 0x56, + 0x67, 0x7c, 0x93, 0xab, 0xc3, 0xd9, 0xea, 0xf6, + 0xfc, 0xfb, 0xf4, 0xe7, 0xd5, 0xc0, 0xaa, 0x94, + 0x80, 0x71, 0x64, 0x5d, 0x5a, 0x5c, 0x61, 0x68, + 0x70, 0x77, 0x7d, 0x7f, 0x7f, 0x7b, 0x74, 0x6b, + 0x61, 0x57, 0x4e, 0x48, 0x46, 0x48, 0x4e, 0x59, + 0x66, 0x75, 0x84, 0x93, 0x9f, 0xa7, 0xab, 0xaa, + + 0xa4, 0x99, 0x8b, 0x7b, 0x6a, 0x5b, 0x4e, 0x46, + 0x43, 0x45, 0x4d, 0x5a, 0x6b, 0x7f, 0x92, 0xa6, + 0xb8, 0xc5, 0xcf, 0xd3, 0xd2, 0xcd, 0xc4, 0xb9, + 0xad, 0xa1, 0x96, 0x8e, 0x89, 0x87, 0x87, 0x8a, + 0x8d, 0x91, 0x92, 0x91, 0x8c, 0x84, 0x78, 0x68, + 0x55, 0x41, 0x2e, 0x1c, 0x0e, 0x05, 0x01, 0x05, + 0x0f, 0x1f, 0x34, 0x4d, 0x68, 0x81, 0x9a, 0xb0, + 0xc1, 0xcd, 0xd3, 0xd3, 0xd0, 0xc8, 0xbf, 0xb5, + + 0xab, 0xa4, 0x9f, 0x9c, 0x9d, 0xa0, 0xa5, 0xaa, + 0xae, 0xb1, 0xb0, 0xab, 0xa3, 0x96, 0x87, 0x76, + 0x63, 0x51, 0x42, 0x36, 0x2f, 0x2d, 0x31, 0x3a, + 0x48, 0x59, 0x6b, 0x7e, 0x8e, 0x9c, 0xa6, 0xaa, + 0xa9, 0xa3, 0x98, 0x8a, 0x7b, 0x6c, 0x5d, 0x52, + 0x4a, 0x48, 0x4a, 0x50, 0x5a, 0x67, 0x75, 0x82 +}; + +// waveform shape table for HF peaks, formants 6,7,8 +#define N_WAVEMULT 128 +static int wavemult_offset=0; +static int wavemult_max=0; + +// the presets are for 22050 Hz sample rate. +// A different rate will need to recalculate the presets in WavegenInit() +static unsigned char wavemult[N_WAVEMULT] = { + 0, 0, 0, 2, 3, 5, 8, 11, 14, 18, 22, 27, 32, 37, 43, 49, + 55, 62, 69, 76, 83, 90, 98,105,113,121,128,136,144,152,159,166, + 174,181,188,194,201,207,213,218,224,228,233,237,240,244,246,249, + 251,252,253,253,253,253,252,251,249,246,244,240,237,233,228,224, + 218,213,207,201,194,188,181,174,166,159,152,144,136,128,121,113, + 105, 98, 90, 83, 76, 69, 62, 55, 49, 43, 37, 32, 27, 22, 18, 14, + 11, 8, 5, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + +// set from y = pow(2,x) * 128, x=-1 to 1 +unsigned char pitch_adjust_tab[MAX_PITCH_VALUE+1] = { + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 86, 87, 88, + 89, 91, 92, 93, 94, 96, 97, 98, + 100,101,103,104,105,107,108,110, + 111,113,115,116,118,119,121,123, + 124,126,128,130,132,133,135,137, + 139,141,143,145,147,149,151,153, + 155,158,160,162,164,167,169,171, + 174,176,179,181,184,186,189,191, + 194,197,199,202,205,208,211,214, + 217,220,223,226,229,232,236,239, + 242,246,249,252, 254,255 }; + +int WavegenFill(int fill_zeros); + + +#ifdef LOG_FRAMES +static void LogMarker(int type, int value) +{//======================================= + if(option_log_frames == 0) + return; + + if((type == espeakEVENT_PHONEME) || (type == espeakEVENT_SENTENCE)) + { + f_log=fopen("log-espeakedit","a"); + if(f_log) + { + if(type == espeakEVENT_PHONEME) + fprintf(f_log,"Phoneme [%s]\n",WordToString(value)); + else + fprintf(f_log,"\n"); + fclose(f_log); + f_log = NULL; + } + } +} +#endif + +void WcmdqStop() +{//============= + wcmdq_head = 0; + wcmdq_tail = 0; +#ifdef USE_PORTAUDIO + Pa_AbortStream(pa_stream); +#endif +} + + +int WcmdqFree() +{//============ + int i; + i = wcmdq_head - wcmdq_tail; + if(i <= 0) i += N_WCMDQ; + return(i); +} + +int WcmdqUsed() +{//============ + return(N_WCMDQ - WcmdqFree()); +} + + +void WcmdqInc() +{//============ + wcmdq_tail++; + if(wcmdq_tail >= N_WCMDQ) wcmdq_tail=0; +} + +static void WcmdqIncHead() +{//======================= + wcmdq_head++; + if(wcmdq_head >= N_WCMDQ) wcmdq_head=0; +} + + + +// data points from which to make the presets for pk_shape1 and pk_shape2 +#define PEAKSHAPEW 256 +static const float pk_shape_x[2][8] = { + {0,-0.6f, 0.0f, 0.6f, 1.4f, 2.5f, 4.5f, 5.5f}, + {0,-0.6f, 0.0f, 0.6f, 1.4f, 2.0f, 4.5f, 5.5f }}; +static const float pk_shape_y[2][8] = { + {0, 67, 81, 67, 31, 14, 0, -6} , + {0, 77, 81, 77, 31, 7, 0, -6 }}; + +unsigned char pk_shape1[PEAKSHAPEW+1] = { + 255,254,254,254,254,254,253,253,252,251,251,250,249,248,247,246, + 245,244,242,241,239,238,236,234,233,231,229,227,225,223,220,218, + 216,213,211,209,207,205,203,201,199,197,195,193,191,189,187,185, + 183,180,178,176,173,171,169,166,164,161,159,156,154,151,148,146, + 143,140,138,135,132,129,126,123,120,118,115,112,108,105,102, 99, + 96, 95, 93, 91, 90, 88, 86, 85, 83, 82, 80, 79, 77, 76, 74, 73, + 72, 70, 69, 68, 67, 66, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, + 55, 54, 53, 52, 52, 51, 50, 50, 49, 48, 48, 47, 47, 46, 46, 46, + 45, 45, 45, 44, 44, 44, 44, 44, 44, 44, 43, 43, 43, 43, 44, 43, + 42, 42, 41, 40, 40, 39, 38, 38, 37, 36, 36, 35, 35, 34, 33, 33, + 32, 32, 31, 30, 30, 29, 29, 28, 28, 27, 26, 26, 25, 25, 24, 24, + 23, 23, 22, 22, 21, 21, 20, 20, 19, 19, 18, 18, 18, 17, 17, 16, + 16, 15, 15, 15, 14, 14, 13, 13, 13, 12, 12, 11, 11, 11, 10, 10, + 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 5, 5, + 5, 5, 4, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, + 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0 }; + +static unsigned char pk_shape2[PEAKSHAPEW+1] = { + 255,254,254,254,254,254,254,254,254,254,253,253,253,253,252,252, + 252,251,251,251,250,250,249,249,248,248,247,247,246,245,245,244, + 243,243,242,241,239,237,235,233,231,229,227,225,223,221,218,216, + 213,211,208,205,203,200,197,194,191,187,184,181,178,174,171,167, + 163,160,156,152,148,144,140,136,132,127,123,119,114,110,105,100, + 96, 94, 91, 88, 86, 83, 81, 78, 76, 74, 71, 69, 66, 64, 62, 60, + 57, 55, 53, 51, 49, 47, 44, 42, 40, 38, 36, 34, 32, 30, 29, 27, + 25, 23, 21, 19, 18, 16, 14, 12, 11, 9, 7, 6, 4, 3, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0 }; + +static unsigned char *pk_shape; + + +static void WavegenInitPkData(int which) +{//===================================== +// this is only needed to set up the presets for pk_shape1 and pk_shape2 +// These have already been pre-calculated and preset +#ifdef deleted + int ix; + int p; + float x; + float y[PEAKSHAPEW]; + float maxy=0; + + if(which==0) + pk_shape = pk_shape1; + else + pk_shape = pk_shape2; + + p = 0; + for(ix=0;ix<PEAKSHAPEW;ix++) + { + x = (4.5*ix)/PEAKSHAPEW; + if(x >= pk_shape_x[which][p+3]) p++; + y[ix] = polint(&pk_shape_x[which][p],&pk_shape_y[which][p],3,x); + if(y[ix] > maxy) maxy = y[ix]; + } + for(ix=0;ix<PEAKSHAPEW;ix++) + { + p = (int)(y[ix]*255/maxy); + pk_shape[ix] = (p >= 0) ? p : 0; + } + pk_shape[PEAKSHAPEW]=0; +#endif +} // end of WavegenInitPkData + + + +#ifdef USE_PORTAUDIO +// PortAudio interface + +static int userdata[4]; +static PaError pa_init_err=0; +static int out_channels=1; + +#if USE_PORTAUDIO == 18 +static int WaveCallback(void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, PaTimestamp outTime, void *userData ) +#else +static int WaveCallback(const void *inputBuffer, void *outputBuffer, + long unsigned int framesPerBuffer, const PaStreamCallbackTimeInfo *outTime, + PaStreamCallbackFlags flags, void *userData ) +#endif +{ + int ix; + int result; + unsigned char *p; + + out_ptr = out_start = (unsigned char *)outputBuffer; + out_end = out_ptr + framesPerBuffer*2; + +#ifdef LIBRARY + event_list_ix = 0; +#endif + + result = WavegenFill(1); + +#ifdef LIBRARY + count_samples += framesPerBuffer; + if(synth_callback) + { + // synchronous-playback mode, allow the calling process to abort the speech + event_list[event_list_ix].type = espeakEVENT_LIST_TERMINATED; // indicates end of event list + event_list[event_list_ix].user_data = 0; + + if(synth_callback(NULL,0,event_list) == 1) + { + SpeakNextClause(NULL,NULL,2); // stop speaking + result = 1; + } + } +#endif + +#ifdef ARCH_BIG + { + // swap the order of bytes in each sound sample in the portaudio buffer + int c; + out_ptr = (unsigned char *)outputBuffer; + out_end = out_ptr + framesPerBuffer*2; + while(out_ptr < out_end) + { + c = out_ptr[0]; + out_ptr[0] = out_ptr[1]; + out_ptr[1] = c; + out_ptr += 2; + } + } +#endif + + if(out_channels == 2) + { + // sound output can only do stereo, not mono. Duplicate each sound sample to + // produce 2 channels. + out_ptr = (unsigned char *)outputBuffer; + for(ix=framesPerBuffer-1; ix>=0; ix--) + { + p = &out_ptr[ix*4]; + p[3] = p[1] = out_ptr[ix*2 + 1]; + p[2] = p[0] = out_ptr[ix*2]; + } + } + +#if USE_PORTAUDIO == 18 +#ifdef PLATFORM_WINDOWS + return(result); +#endif + if(result != 0) + { + static int end_timer = 0; + if(end_timer == 0) + end_timer = 4; + if(end_timer > 0) + { + end_timer--; + if(end_timer == 0) + return(1); + } + } + return(0); +#else + return(result); +#endif + +} // end of WaveCallBack + + +#if USE_PORTAUDIO == 19 +/* This is a fixed version of Pa_OpenDefaultStream() for use if the version in portaudio V19 + is broken */ + +static PaError Pa_OpenDefaultStream2( PaStream** stream, + int inputChannelCount, + int outputChannelCount, + PaSampleFormat sampleFormat, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result; + PaStreamParameters hostApiOutputParameters; + + if(option_device_number >= 0) + hostApiOutputParameters.device = option_device_number; + else + hostApiOutputParameters.device = Pa_GetDefaultOutputDevice(); + + if( hostApiOutputParameters.device == paNoDevice ) + return paDeviceUnavailable; + + hostApiOutputParameters.channelCount = outputChannelCount; + hostApiOutputParameters.sampleFormat = sampleFormat; + /* defaultHighOutputLatency is used below instead of + defaultLowOutputLatency because it is more important for the default + stream to work reliably than it is for it to work with the lowest + latency. + */ + hostApiOutputParameters.suggestedLatency = + Pa_GetDeviceInfo( hostApiOutputParameters.device )->defaultHighOutputLatency; + hostApiOutputParameters.hostApiSpecificStreamInfo = NULL; + + result = Pa_OpenStream( + stream, NULL, &hostApiOutputParameters, sampleRate, framesPerBuffer, paNoFlag, streamCallback, userData ); + + return(result); +} +#endif + + +int WavegenOpenSound() +{//=================== + PaError err, err2; + PaError active; + + if(option_waveout || option_quiet) + { + // writing to WAV file, not to portaudio + return(0); + } + +#if USE_PORTAUDIO == 18 + active = Pa_StreamActive(pa_stream); +#else + active = Pa_IsStreamActive(pa_stream); +#endif + + if(active == 1) + return(0); + if(active < 0) + { + out_channels = 1; + +#if USE_PORTAUDIO == 18 + err2 = Pa_OpenDefaultStream(&pa_stream,0,1,paInt16,samplerate,512,N_WAV_BUF,WaveCallback,(void *)userdata); + + if(err2 == paInvalidChannelCount) + { + // failed to open with mono, try stereo + out_channels=2; + err2 = Pa_OpenDefaultStream(&pa_stream,0,2,paInt16,samplerate,512,N_WAV_BUF,WaveCallback,(void *)userdata); + } +#else + err2 = Pa_OpenDefaultStream2(&pa_stream,0,1,paInt16,(double)samplerate,512,WaveCallback,(void *)userdata); + + if(err2 == paInvalidChannelCount) + { + // failed to open with mono, try stereo + out_channels=2; + err2 = Pa_OpenDefaultStream(&pa_stream,0,2,paInt16,(double)samplerate,512,WaveCallback,(void *)userdata); + } +#endif + } + err = Pa_StartStream(pa_stream); + +#if USE_PORTAUDIO == 19 + if(err == paStreamIsNotStopped) + { + // not sure why we need this, but PA v19 seems to need it + err = Pa_StopStream(pa_stream); + err = Pa_StartStream(pa_stream); + } +#endif + + if(err != paNoError) + { + // exit speak if we can't open the sound device - this is OK if speak is being run for each utterance + exit(2); + } + + return(0); +} + + + +int WavegenCloseSound() +{//==================== + PaError active; + + // check whether speaking has finished, and close the stream + if(pa_stream != NULL) + { +#if USE_PORTAUDIO == 18 + active = Pa_StreamActive(pa_stream); +#else + active = Pa_IsStreamActive(pa_stream); +#endif + if(WcmdqUsed() == 0) // also check that the queue is empty + { + if(active == 0) + { + Pa_CloseStream(pa_stream); + pa_stream = NULL; + return(1); + } + } + else + { + WavegenOpenSound(); // still items in the queue, shouldn't be closed + } + } + return(0); +} + + +int WavegenInitSound() +{//=================== + PaError err; + + if(option_quiet) + return(0); + + // PortAudio sound output library + err = Pa_Initialize(); + pa_init_err = err; + if(err != paNoError) + { + fprintf(stderr,"Failed to initialise the PortAudio sound\n"); + return(1); + } + return(0); +} +#else +int WavegenOpenSound() +{//=================== + return(0); +} +int WavegenCloseSound() +{//==================== + return(0); +} +int WavegenInitSound() +{//=================== + return(0); +} +#endif + + +void WavegenInit(int rate, int wavemult_fact) +{//========================================== + int ix; + double x; + + if(wavemult_fact == 0) + wavemult_fact=60; // default + + wvoice = NULL; + samplerate = samplerate_native = rate; + PHASE_INC_FACTOR = 0x8000000 / samplerate; // assumes pitch is Hz*32 + Flutter_inc = (64 * samplerate)/rate; + samplecount = 0; + nsamples = 0; + wavephase = 0x7fffffff; + max_hval = 0; + + wdata.amplitude = 32; + wdata.prev_was_synth = 0; + + for(ix=0; ix<N_EMBEDDED_VALUES; ix++) + embedded_value[ix] = embedded_default[ix]; + + + // set up window to generate a spread of harmonics from a + // single peak for HF peaks + wavemult_max = (samplerate * wavemult_fact)/(256 * 50); + if(wavemult_max > N_WAVEMULT) wavemult_max = N_WAVEMULT; + + wavemult_offset = wavemult_max/2; + + if(samplerate != 22050) + { + // wavemult table has preset values for 22050 Hz, we only need to + // recalculate them if we have a different sample rate + for(ix=0; ix<wavemult_max; ix++) + { + x = 127*(1.0 - cos(PI2*ix/wavemult_max)); + wavemult[ix] = (int)x; + } + } + + WavegenInitPkData(1); + WavegenInitPkData(0); + pk_shape = pk_shape2; // pk_shape2 + +#ifdef INCLUDE_KLATT + KlattInit(); +#endif + +#ifdef LOG_FRAMES +remove("log-espeakedit"); +#endif +} // end of WavegenInit + + +int GetAmplitude(void) +{//=================== + int amp; + + // normal, none, reduced, moderate, strong + static const unsigned char amp_emphasis[5] = {16, 16, 10, 16, 22}; + + amp = (embedded_value[EMBED_A])*55/100; + general_amplitude = amp * amp_emphasis[embedded_value[EMBED_F]] / 16; + return(general_amplitude); +} + + +static void WavegenSetEcho(void) +{//============================= + int delay; + int amp; + + voicing = wvoice->voicing; + delay = wvoice->echo_delay; + amp = wvoice->echo_amp; + + if(delay >= N_ECHO_BUF) + delay = N_ECHO_BUF-1; + if(amp > 100) + amp = 100; + + memset(echo_buf,0,sizeof(echo_buf)); + echo_tail = 0; + + if(embedded_value[EMBED_H] > 0) + { + // set echo from an embedded command in the text + amp = embedded_value[EMBED_H]; + delay = 130; + } + if(embedded_value[EMBED_T] > 0) + { + // announcing punctuation + amp = embedded_value[EMBED_T] * 8; + delay = 60; + } + + if(delay == 0) + amp = 0; + + echo_head = (delay * samplerate)/1000; + echo_length = echo_head; // ensure completion of echo at the end of speech. Use 1 delay period? + if(amp == 0) + echo_length = 0; + if(amp > 20) + echo_length = echo_head * 2; // perhaps allow 2 echo periods if the echo is loud. + + // echo_amp units are 1/256ths of the amplitude of the original sound. + echo_amp = amp; + // compensate (partially) for increase in amplitude due to echo + general_amplitude = GetAmplitude(); + general_amplitude = ((general_amplitude * (500-amp))/500); +} // end of WavegenSetEcho + + + +int PeaksToHarmspect(wavegen_peaks_t *peaks, int pitch, int *htab, int control) +{//============================================================================ +// Calculate the amplitude of each harmonics from the formants +// Only for formants 0 to 5 + +// control 0=initial call, 1=every 64 cycles + + // pitch and freqs are Hz<<16 + + int f; + wavegen_peaks_t *p; + int fp; // centre freq of peak + int fhi; // high freq of peak + int h; // harmonic number + int pk; + int hmax; + int hmax_samplerate; // highest harmonic allowed for the samplerate + int x; + int ix; + int h1; + +#ifdef SPECT_EDITOR + if(harm_sqrt_n > 0) + return(HarmToHarmspect(pitch,htab)); +#endif + + // initialise as much of *out as we will need + if(wvoice == NULL) + return(1); + hmax = (peaks[wvoice->n_harmonic_peaks].freq + peaks[wvoice->n_harmonic_peaks].right)/pitch; + if(hmax >= MAX_HARMONIC) + hmax = MAX_HARMONIC-1; + + // restrict highest harmonic to half the samplerate + hmax_samplerate = (((samplerate * 19)/40) << 16)/pitch; // only 95% of Nyquist freq +// hmax_samplerate = (samplerate << 16)/(pitch*2); + + if(hmax > hmax_samplerate) + hmax = hmax_samplerate; + + for(h=0;h<=hmax;h++) + htab[h]=0; + + h=0; + for(pk=0; pk<=wvoice->n_harmonic_peaks; pk++) + { + p = &peaks[pk]; + if((p->height == 0) || (fp = p->freq)==0) + continue; + + fhi = p->freq + p->right; + h = ((p->freq - p->left) / pitch) + 1; + if(h <= 0) h = 1; + + for(f=pitch*h; f < fp; f+=pitch) + { + htab[h++] += pk_shape[(fp-f)/(p->left>>8)] * p->height; + } + for(; f < fhi; f+=pitch) + { + htab[h++] += pk_shape[(f-fp)/(p->right>>8)] * p->height; + } + } + +{ +int y; +int h2; + // increase bass + y = peaks[1].height * 10; // addition as a multiple of 1/256s + h2 = (1000<<16)/pitch; // decrease until 1000Hz + if(h2 > 0) + { + x = y/h2; + h = 1; + while(y > 0) + { + htab[h++] += y; + y -= x; + } + } +} + + // find the nearest harmonic for HF peaks where we don't use shape + for(; pk<N_PEAKS; pk++) + { + x = peaks[pk].height >> 14; + peak_height[pk] = (x * x * 5)/2; + + // find the nearest harmonic for HF peaks where we don't use shape + if(control == 0) + { + // set this initially, but make changes only at the quiet point + peak_harmonic[pk] = peaks[pk].freq / pitch; + } + // only use harmonics up to half the samplerate + if(peak_harmonic[pk] >= hmax_samplerate) + peak_height[pk] = 0; + } + + // convert from the square-rooted values + f = 0; + for(h=0; h<=hmax; h++, f+=pitch) + { + x = htab[h] >> 15; + htab[h] = (x * x) >> 8; + + if((ix = (f >> 19)) < N_TONE_ADJUST) + { + htab[h] = (htab[h] * wvoice->tone_adjust[ix]) >> 13; // index tone_adjust with Hz/8 + } + } + + // adjust the amplitude of the first harmonic, affects tonal quality + h1 = htab[1] * option_harmonic1; + htab[1] = h1/8; + + + // calc intermediate increments of LF harmonics + if(control & 1) + { + for(h=1; h<N_LOWHARM; h++) + { + harm_inc[h] = (htab[h] - harmspect[h]) >> 3; + } + } + + return(hmax); // highest harmonic number +} // end of PeaksToHarmspect + + + +static void AdvanceParameters() +{//============================ +// Called every 64 samples to increment the formant freq, height, and widths + + int x; + int ix; + static int Flutter_ix = 0; + + // advance the pitch + wdata.pitch_ix += wdata.pitch_inc; + if((ix = wdata.pitch_ix>>8) > 127) ix = 127; + x = wdata.pitch_env[ix] * wdata.pitch_range; + wdata.pitch = (x>>8) + wdata.pitch_base; + + amp_ix += amp_inc; + + /* add pitch flutter */ + if(Flutter_ix >= (N_FLUTTER*64)) + Flutter_ix = 0; + x = ((int)(Flutter_tab[Flutter_ix >> 6])-0x80) * flutter_amp; + Flutter_ix += Flutter_inc; + wdata.pitch += x; + if(wdata.pitch < 102400) + wdata.pitch = 102400; // min pitch, 25 Hz (25 << 12) + + if(samplecount == samplecount_start) + return; + + for(ix=0; ix <= wvoice->n_harmonic_peaks; ix++) + { + peaks[ix].freq1 += peaks[ix].freq_inc; + peaks[ix].freq = int(peaks[ix].freq1); + peaks[ix].height1 += peaks[ix].height_inc; + if((peaks[ix].height = int(peaks[ix].height1)) < 0) + peaks[ix].height = 0; + peaks[ix].left1 += peaks[ix].left_inc; + peaks[ix].left = int(peaks[ix].left1); + peaks[ix].right1 += peaks[ix].right_inc; + peaks[ix].right = int(peaks[ix].right1); + } + for(;ix < N_PEAKS; ix++) + { + // formants 6,7,8 don't have a width parameter + peaks[ix].freq1 += peaks[ix].freq_inc; + peaks[ix].freq = int(peaks[ix].freq1); + peaks[ix].height1 += peaks[ix].height_inc; + if((peaks[ix].height = int(peaks[ix].height1)) < 0) + peaks[ix].height = 0; + } + +#ifdef SPECT_EDITOR + if(harm_sqrt_n != 0) + { + // We are generating from a harmonic spectrum at a given pitch, not from formant peaks + for(ix=0; ix<harm_sqrt_n; ix++) + harm_sqrt[ix] += harm_sqrt_inc[ix]; + } +#endif +} // end of AdvanceParameters + + +#ifndef PLATFORM_RISCOS +static double resonator(RESONATOR *r, double input) +{//================================================ + double x; + + x = r->a * input + r->b * r->x1 + r->c * r->x2; + r->x2 = r->x1; + r->x1 = x; + + return x; +} + + + +static void setresonator(RESONATOR *rp, int freq, int bwidth, int init) +{//==================================================================== +// freq Frequency of resonator in Hz +// bwidth Bandwidth of resonator in Hz +// init Initialize internal data + + double x; + double arg; + + if(init) + { + rp->x1 = 0; + rp->x2 = 0; + } + + // x = exp(-pi * bwidth * t) + arg = minus_pi_t * bwidth; + x = exp(arg); + + // c = -(x*x) + rp->c = -(x * x); + + // b = x * 2*cos(2 pi * freq * t) + + arg = two_pi_t * freq; + rp->b = x * cos(arg) * 2.0; + + // a = 1.0 - b - c + rp->a = 1.0 - rp->b - rp->c; +} // end if setresonator +#endif + + +void InitBreath(void) +{//================== +#ifndef PLATFORM_RISCOS + int ix; + + minus_pi_t = -PI / samplerate; + two_pi_t = -2.0 * minus_pi_t; + + for(ix=0; ix<N_PEAKS; ix++) + { + setresonator(&rbreath[ix],2000,200,1); + } +#endif +} // end of InitBreath + + + +static void SetBreath() +{//==================== +#ifndef PLATFORM_RISCOS + int pk; + + if(wvoice->breath[0] == 0) + return; + + for(pk=1; pk<N_PEAKS; pk++) + { + if(wvoice->breath[pk] != 0) + { + // breath[0] indicates that some breath formants are needed + // set the freq from the current ynthesis formant and the width from the voice data + setresonator(&rbreath[pk], peaks[pk].freq >> 16, wvoice->breathw[pk],0); + } + } +#endif +} // end of SetBreath + + +static int ApplyBreath(void) +{//========================= + int value = 0; +#ifndef PLATFORM_RISCOS + int noise; + int ix; + int amp; + + // use two random numbers, for alternate formants + noise = (rand() & 0x3fff) - 0x2000; + + for(ix=1; ix < N_PEAKS; ix++) + { + if((amp = wvoice->breath[ix]) != 0) + { + amp *= (peaks[ix].height >> 14); + value += int(resonator(&rbreath[ix],noise) * amp); + } + } +#endif + return (value); +} + + + +int Wavegen() +{//========== + unsigned short waveph; + unsigned short theta; + int total; + int h; + int ix; + int z, z1, z2; + int echo; + int ov; + static int maxh, maxh2; + int pk; + signed char c; + int sample; + int amp; + int modn_amp, modn_period; + static int agc = 256; + static int h_switch_sign = 0; + static int cycle_count = 0; + static int amplitude2 = 0; // adjusted for pitch + + // continue until the output buffer is full, or + // the required number of samples have been produced + + for(;;) + { + if((end_wave==0) && (samplecount==nsamples)) + return(0); + + if((samplecount & 0x3f) == 0) + { + // every 64 samples, adjust the parameters + if(samplecount == 0) + { + hswitch = 0; + harmspect = hspect[0]; + maxh2 = PeaksToHarmspect(peaks, wdata.pitch<<4, hspect[0], 0); + + // adjust amplitude to compensate for fewer harmonics at higher pitch + amplitude2 = (wdata.amplitude * wdata.pitch)/(100 << 11); + + // switch sign of harmonics above about 900Hz, to reduce max peak amplitude + h_switch_sign = 890 / (wdata.pitch >> 12); + } + else + AdvanceParameters(); + + // pitch is Hz<<12 + phaseinc = (wdata.pitch>>7) * PHASE_INC_FACTOR; + cycle_samples = samplerate/(wdata.pitch >> 12); // sr/(pitch*2) + hf_factor = wdata.pitch >> 11; + + maxh = maxh2; + harmspect = hspect[hswitch]; + hswitch ^= 1; + maxh2 = PeaksToHarmspect(peaks, wdata.pitch<<4, hspect[hswitch], 1); + + SetBreath(); + } + else + if((samplecount & 0x07) == 0) + { + for(h=1; h<N_LOWHARM && h<=maxh2 && h<=maxh; h++) + { + harmspect[h] += harm_inc[h]; + } + + // bring automctic gain control back towards unity + if(agc < 256) agc++; + } + + samplecount++; + + if(wavephase > 0) + { + wavephase += phaseinc; + if(wavephase < 0) + { + // sign has changed, reached a quiet point in the waveform + cbytes = wavemult_offset - (cycle_samples)/2; + if(samplecount > nsamples) + return(0); + + cycle_count++; + + for(pk=wvoice->n_harmonic_peaks+1; pk<N_PEAKS; pk++) + { + // find the nearest harmonic for HF peaks where we don't use shape + peak_harmonic[pk] = peaks[pk].freq / (wdata.pitch*16); + } + + // adjust amplitude to compensate for fewer harmonics at higher pitch + amplitude2 = (wdata.amplitude * wdata.pitch)/(100 << 11); + + if(glottal_flag > 0) + { + if(glottal_flag == 3) + { + if((nsamples-samplecount) < (cycle_samples*2)) + { + // Vowel before glottal-stop. + // This is the start of the penultimate cycle, reduce its amplitude + glottal_flag = 2; + amplitude2 = (amplitude2 * glottal_reduce)/256; + } + } + else + if(glottal_flag == 4) + { + // Vowel following a glottal-stop. + // This is the start of the second cycle, reduce its amplitude + glottal_flag = 2; + amplitude2 = (amplitude2 * glottal_reduce)/256; + } + else + { + glottal_flag--; + } + } + + if(amplitude_env != NULL) + { + // amplitude envelope is only used for creaky voice effect on certain vowels/tones + if((ix = amp_ix>>8) > 127) ix = 127; + amp = amplitude_env[ix]; + amplitude2 = (amplitude2 * amp)/128; +// if(amp < 255) +// modulation_type = 7; + } + + // introduce roughness into the sound by reducing the amplitude of + modn_period = 0; + if(voice->roughness < N_ROUGHNESS) + { + modn_period = modulation_tab[voice->roughness][modulation_type]; + modn_amp = modn_period & 0xf; + modn_period = modn_period >> 4; + } + + if(modn_period != 0) + { + if(modn_period==0xf) + { + // just once */ + amplitude2 = (amplitude2 * modn_amp)/16; + modulation_type = 0; + } + else + { + // reduce amplitude every [modn_period} cycles + if((cycle_count % modn_period)==0) + amplitude2 = (amplitude2 * modn_amp)/16; + } + } + } + } + else + { + wavephase += phaseinc; + } + waveph = (unsigned short)(wavephase >> 16); + total = 0; + + // apply HF peaks, formants 6,7,8 + // add a single harmonic and then spread this my multiplying by a + // window. This is to reduce the processing power needed to add the + // higher frequence harmonics. + cbytes++; + if(cbytes >=0 && cbytes<wavemult_max) + { + for(pk=wvoice->n_harmonic_peaks+1; pk<N_PEAKS; pk++) + { + theta = peak_harmonic[pk] * waveph; + total += (long)sin_tab[theta >> 5] * peak_height[pk]; + } + + // spread the peaks by multiplying by a window + total = (long)(total / hf_factor) * wavemult[cbytes]; + } + + // apply main peaks, formants 0 to 5 +#ifdef USE_ASSEMBLER_1 + // use an optimised routine for this loop, if available + total += AddSineWaves(waveph, h_switch_sign, maxh, harmspect); // call an assembler code routine +#else + theta = waveph; + + for(h=1; h<=h_switch_sign; h++) + { + total += (int(sin_tab[theta >> 5]) * harmspect[h]); + theta += waveph; + } + while(h<=maxh) + { + total -= (int(sin_tab[theta >> 5]) * harmspect[h]); + theta += waveph; + h++; + } +#endif + + if(voicing != 64) + { + total = (total >> 6) * voicing; + } + +#ifndef PLATFORM_RISCOS + if(wvoice->breath[0]) + { + total += ApplyBreath(); + } +#endif + + // mix with sampled wave if required + z2 = 0; + if(wdata.mix_wavefile_ix < wdata.n_mix_wavefile) + { + if(wdata.mix_wave_scale == 0) + { + // a 16 bit sample + c = wdata.mix_wavefile[wdata.mix_wavefile_ix+1]; + sample = wdata.mix_wavefile[wdata.mix_wavefile_ix] + (c * 256); + wdata.mix_wavefile_ix += 2; + } + else + { + // a 8 bit sample, scaled + sample = (signed char)wdata.mix_wavefile[wdata.mix_wavefile_ix++] * wdata.mix_wave_scale; + } + z2 = (sample * wdata.amplitude_v) >> 10; + z2 = (z2 * wdata.mix_wave_amp)/32; + } + + z1 = z2 + (((total>>8) * amplitude2) >> 13); + + echo = (echo_buf[echo_tail++] * echo_amp); + z1 += echo >> 8; + if(echo_tail >= N_ECHO_BUF) + echo_tail=0; + + z = (z1 * agc) >> 8; + + // check for overflow, 16bit signed samples + if(z >= 32768) + { + ov = 8388608/z1 - 1; // 8388608 is 2^23, i.e. max value * 256 + if(ov < agc) agc = ov; // set agc to number of 1/256ths to multiply the sample by + z = (z1 * agc) >> 8; // reduce sample by agc value to prevent overflow + } + else + if(z <= -32768) + { + ov = -8388608/z1 - 1; + if(ov < agc) agc = ov; + z = (z1 * agc) >> 8; + } + *out_ptr++ = z; + *out_ptr++ = z >> 8; + + echo_buf[echo_head++] = z; + if(echo_head >= N_ECHO_BUF) + echo_head = 0; + + if(out_ptr >= out_end) + return(1); + } + return(0); +} // end of Wavegen + + +static int PlaySilence(int length, int resume) +{//=========================================== + static int n_samples; + int value=0; + + if(length == 0) + return(0); + + nsamples = 0; + samplecount = 0; + + if(resume==0) + n_samples = length; + + while(n_samples-- > 0) + { + value = (echo_buf[echo_tail++] * echo_amp) >> 8; + + if(echo_tail >= N_ECHO_BUF) + echo_tail = 0; + + *out_ptr++ = value; + *out_ptr++ = value >> 8; + + echo_buf[echo_head++] = value; + if(echo_head >= N_ECHO_BUF) + echo_head = 0; + + if(out_ptr >= out_end) + return(1); + } + return(0); +} // end of PlaySilence + + + +static int PlayWave(int length, int resume, unsigned char *data, int scale, int amp) +{//================================================================================= + static int n_samples; + static int ix=0; + int value; + signed char c; + + if(resume==0) + { + n_samples = length; + ix = 0; + } + + nsamples = 0; + samplecount = 0; + + while(n_samples-- > 0) + { + if(scale == 0) + { + // 16 bits data + c = data[ix+1]; + value = data[ix] + (c * 256); + ix+=2; + } + else + { + // 8 bit data, shift by the specified scale factor + value = (signed char)data[ix++] * scale; + } + value *= (consonant_amp * general_amplitude); // reduce strength of consonant + value = value >> 10; + value = (value * amp)/32; + + value += ((echo_buf[echo_tail++] * echo_amp) >> 8); + + if(value > 32767) + value = 32768; + else + if(value < -32768) + value = -32768; + + if(echo_tail >= N_ECHO_BUF) + echo_tail = 0; + + out_ptr[0] = value; + out_ptr[1] = value >> 8; + out_ptr+=2; + + echo_buf[echo_head++] = (value*3)/4; + if(echo_head >= N_ECHO_BUF) + echo_head = 0; + + if(out_ptr >= out_end) + return(1); + } + return(0); +} + + +static int SetWithRange0(int value, int max) +{//========================================= + if(value < 0) + return(0); + if(value > max) + return(max); + return(value); +} + + +void SetEmbedded(int control, int value) +{//===================================== + // there was an embedded command in the text at this point + int sign=0; + int command; + int ix; + int factor; + int pitch_value; + + command = control & 0x1f; + if((control & 0x60) == 0x60) + sign = -1; + else + if((control & 0x60) == 0x40) + sign = 1; + + if(command < N_EMBEDDED_VALUES) + { + if(sign == 0) + embedded_value[command] = value; + else + embedded_value[command] += (value * sign); + embedded_value[command] = SetWithRange0(embedded_value[command],embedded_max[command]); + } + + switch(command) + { + case EMBED_T: + WavegenSetEcho(); // and drop through to case P + case EMBED_P: + // adjust formants to give better results for a different voice pitch + if((pitch_value = embedded_value[EMBED_P]) > MAX_PITCH_VALUE) + pitch_value = MAX_PITCH_VALUE; + + factor = 256 + (25 * (pitch_value - 50))/50; + for(ix=0; ix<=5; ix++) + { + wvoice->freq[ix] = (wvoice->freq2[ix] * factor)/256; + } + factor = embedded_value[EMBED_T]*3; + wvoice->height[0] = (wvoice->height2[0] * (256 - factor*2))/256; + wvoice->height[1] = (wvoice->height2[1] * (256 - factor))/256; + break; + + case EMBED_A: // amplitude + general_amplitude = GetAmplitude(); + break; + + case EMBED_F: // emphasiis + general_amplitude = GetAmplitude(); + break; + + case EMBED_H: + WavegenSetEcho(); + break; + } +} + + +void WavegenSetVoice(voice_t *v) +{//============================= + static voice_t v2; + + memcpy(&v2,v,sizeof(v2)); + wvoice = &v2; + + if(v->peak_shape==0) + pk_shape = pk_shape1; + else + pk_shape = pk_shape2; + + consonant_amp = (v->consonant_amp * 26) /100; + if(samplerate <= 11000) + { + consonant_amp = consonant_amp*2; // emphasize consonants at low sample rates + option_harmonic1 = 6; + } + WavegenSetEcho(); +} + + +static void SetAmplitude(int length, unsigned char *amp_env, int value) +{//==================================================================== + amp_ix = 0; + if(length==0) + amp_inc = 0; + else + amp_inc = (256 * ENV_LEN * STEPSIZE)/length; + + wdata.amplitude = (value * general_amplitude)/16; + wdata.amplitude_v = (wdata.amplitude * wvoice->consonant_ampv * 15)/100; // for wave mixed with voiced sounds + + amplitude_env = amp_env; +} + + +void SetPitch2(voice_t *voice, int pitch1, int pitch2, int *pitch_base, int *pitch_range) +{//====================================================================================== + int x; + int base; + int range; + int pitch_value; + + if(pitch1 > pitch2) + { + x = pitch1; // swap values + pitch1 = pitch2; + pitch2 = x; + } + + if((pitch_value = embedded_value[EMBED_P]) > MAX_PITCH_VALUE) + pitch_value = MAX_PITCH_VALUE; + pitch_value -= embedded_value[EMBED_T]; // adjust tone for announcing punctuation + if(pitch_value < 0) + pitch_value = 0; + + base = (voice->pitch_base * pitch_adjust_tab[pitch_value])/128; + range = (voice->pitch_range * embedded_value[EMBED_R])/50; + + // compensate for change in pitch when the range is narrowed or widened + base -= (range - voice->pitch_range)*18; + + *pitch_base = base + (pitch1 * range); + *pitch_range = base + (pitch2 * range) - *pitch_base; +} + + +void SetPitch(int length, unsigned char *env, int pitch1, int pitch2) +{//================================================================== +// length in samples + +#ifdef LOG_FRAMES +if(option_log_frames) +{ + f_log=fopen("log-espeakedit","a"); + if(f_log != NULL) + { + fprintf(f_log," pitch %3d %3d %3dmS\n",pitch1,pitch2,(length*1000)/samplerate); + fclose(f_log); + f_log=NULL; + } +} +#endif + if((wdata.pitch_env = env)==NULL) + wdata.pitch_env = env_fall; // default + + wdata.pitch_ix = 0; + if(length==0) + wdata.pitch_inc = 0; + else + wdata.pitch_inc = (256 * ENV_LEN * STEPSIZE)/length; + + SetPitch2(wvoice, pitch1, pitch2, &wdata.pitch_base, &wdata.pitch_range); + // set initial pitch + wdata.pitch = ((wdata.pitch_env[0] * wdata.pitch_range) >>8) + wdata.pitch_base; // Hz << 12 + + flutter_amp = wvoice->flutter; + +} // end of SetPitch + + + + + +void SetSynth(int length, int modn, frame_t *fr1, frame_t *fr2, voice_t *v) +{//======================================================================== + int ix; + DOUBLEX next; + int length2; + int length4; + int qix; + int cmd; + static int glottal_reduce_tab1[4] = {0x30, 0x30, 0x40, 0x50}; // vowel before [?], amp * 1/256 +// static int glottal_reduce_tab1[4] = {0x30, 0x40, 0x50, 0x60}; // vowel before [?], amp * 1/256 + static int glottal_reduce_tab2[4] = {0x90, 0xa0, 0xb0, 0xc0}; // vowel after [?], amp * 1/256 + +#ifdef LOG_FRAMES +if(option_log_frames) +{ + f_log=fopen("log-espeakedit","a"); + if(f_log != NULL) + { + fprintf(f_log,"%3dmS %3d %3d %4d %4d (%3d %3d %3d %3d) to %3d %3d %4d %4d (%3d %3d %3d %3d)\n",length*1000/samplerate, + fr1->ffreq[0],fr1->ffreq[1],fr1->ffreq[2],fr1->ffreq[3], fr1->fheight[0],fr1->fheight[1],fr1->fheight[2],fr1->fheight[3], + fr2->ffreq[0],fr2->ffreq[1],fr2->ffreq[2],fr2->ffreq[3], fr2->fheight[0],fr2->fheight[1],fr2->fheight[2],fr2->fheight[3] ); + + fclose(f_log); + f_log=NULL; + } +} +#endif + + harm_sqrt_n = 0; + end_wave = 1; + + // any additional information in the param1 ? + modulation_type = modn & 0xff; + + glottal_flag = 0; + if(modn & 0x400) + { + glottal_flag = 3; // before a glottal stop + glottal_reduce = glottal_reduce_tab1[(modn >> 8) & 3]; + } + if(modn & 0x800) + { + glottal_flag = 4; // after a glottal stop + glottal_reduce = glottal_reduce_tab2[(modn >> 8) & 3]; + } + + for(qix=wcmdq_head+1;;qix++) + { + if(qix >= N_WCMDQ) qix = 0; + if(qix == wcmdq_tail) break; + + cmd = wcmdq[qix][0]; + if(cmd==WCMD_SPECT) + { + end_wave = 0; // next wave generation is from another spectrum + break; + } + if((cmd==WCMD_WAVE) || (cmd==WCMD_PAUSE)) + break; // next is not from spectrum, so continue until end of wave cycle + } + + // round the length to a multiple of the stepsize + length2 = (length + STEPSIZE/2) & ~0x3f; + if(length2 == 0) + length2 = STEPSIZE; + + // add this length to any left over from the previous synth + samplecount_start = samplecount; + nsamples += length2; + + length4 = length2/4; + for(ix=0; ix<N_PEAKS; ix++) + { + peaks[ix].freq1 = (fr1->ffreq[ix] * v->freq[ix] + v->freqadd[ix]*256) << 8; + peaks[ix].freq = int(peaks[ix].freq1); + next = (fr2->ffreq[ix] * v->freq[ix] + v->freqadd[ix]*256) << 8; + peaks[ix].freq_inc = ((next - peaks[ix].freq1) * (STEPSIZE/4)) / length4; // lower headroom for fixed point math + + peaks[ix].height1 = (fr1->fheight[ix] * v->height[ix]) << 6; + peaks[ix].height = int(peaks[ix].height1); + next = (fr2->fheight[ix] * v->height[ix]) << 6; + peaks[ix].height_inc = ((next - peaks[ix].height1) * STEPSIZE) / length2; + + if(ix <= wvoice->n_harmonic_peaks) + { + peaks[ix].left1 = (fr1->fwidth[ix] * v->width[ix]) << 10; + peaks[ix].left = int(peaks[ix].left1); + next = (fr2->fwidth[ix] * v->width[ix]) << 10; + peaks[ix].left_inc = ((next - peaks[ix].left1) * STEPSIZE) / length2; + + peaks[ix].right1 = (fr1->fright[ix] * v->width[ix]) << 10; + peaks[ix].right = int(peaks[ix].right1); + next = (fr2->fright[ix] * v->width[ix]) << 10; + peaks[ix].right_inc = ((next - peaks[ix].right1) * STEPSIZE) / length2; + } + } +} // end of SetSynth + + +static int Wavegen2(int length, int modulation, int resume, frame_t *fr1, frame_t *fr2) +{//==================================================================================== + if(resume==0) + SetSynth(length, modulation, fr1, fr2, wvoice); + + return(Wavegen()); +} + +void Write4Bytes(FILE *f, int value) +{//================================= +// Write 4 bytes to a file, least significant first + int ix; + + for(ix=0; ix<4; ix++) + { + fputc(value & 0xff,f); + value = value >> 8; + } +} + + + + +int WavegenFill(int fill_zeros) +{//============================ +// Pick up next wavegen commands from the queue +// return: 0 output buffer has been filled +// return: 1 input command queue is now empty + + long *q; + int length; + int result; + static int resume=0; + static int echo_complete=0; + +#ifdef TEST_MBROLA + if(mbrola_name[0] != 0) + return(MbrolaFill(fill_zeros)); +#endif + + while(out_ptr < out_end) + { + if(WcmdqUsed() <= 0) + { + if(echo_complete > 0) + { + // continue to play silence until echo is completed + resume = PlaySilence(echo_complete,resume); + if(resume == 1) + return(0); // not yet finished + } + + if(fill_zeros) + { + while(out_ptr < out_end) + *out_ptr++ = 0; + } + return(1); // queue empty, close sound channel + } + + result = 0; + q = wcmdq[wcmdq_head]; + length = q[1]; + + switch(q[0]) + { + case WCMD_PITCH: + SetPitch(length,(unsigned char *)q[2],q[3] >> 16,q[3] & 0xffff); + break; + + case WCMD_PAUSE: + if(resume==0) + { + echo_complete -= length; + } + wdata.n_mix_wavefile = 0; + wdata.prev_was_synth = 0; + result = PlaySilence(length,resume); + break; + + case WCMD_WAVE: + echo_complete = echo_length; + wdata.n_mix_wavefile = 0; + wdata.prev_was_synth = 0; + result = PlayWave(length,resume,(unsigned char*)q[2], q[3] & 0xff, q[3] >> 8); + break; + + case WCMD_WAVE2: + // wave file to be played at the same time as synthesis + wdata.mix_wave_amp = q[3] >> 8; + wdata.mix_wave_scale = q[3] & 0xff; + if(wdata.mix_wave_scale == 0) + wdata.n_mix_wavefile = length*2; + else + wdata.n_mix_wavefile = length; + wdata.mix_wavefile_ix = 0; + wdata.mix_wavefile = (unsigned char *)q[2]; + break; + + case WCMD_SPECT2: // as WCMD_SPECT but stop any concurrent wave file + wdata.n_mix_wavefile = 0; // ... and drop through to WCMD_SPECT case + case WCMD_SPECT: + echo_complete = echo_length; + result = Wavegen2(length & 0xffff,q[1] >> 16,resume,(frame_t *)q[2],(frame_t *)q[3]); + break; + +#ifdef INCLUDE_KLATT + case WCMD_KLATT2: // as WCMD_SPECT but stop any concurrent wave file + wdata.n_mix_wavefile = 0; // ... and drop through to WCMD_SPECT case + case WCMD_KLATT: + echo_complete = echo_length; + result = Wavegen_Klatt2(length & 0xffff,q[1] >> 16,resume,(frame_t *)q[2],(frame_t *)q[3]); + break; +#endif + + case WCMD_MARKER: + MarkerEvent(q[1],q[2],q[3],out_ptr); +#ifdef LOG_FRAMES + LogMarker(q[1],q[3]); +#endif + if(q[1] == 1) + { + current_source_index = q[2] & 0xffffff; + } + break; + + case WCMD_AMPLITUDE: + SetAmplitude(length,(unsigned char *)q[2],q[3]); + break; + + case WCMD_VOICE: + WavegenSetVoice((voice_t *)q[1]); + free((voice_t *)q[1]); + break; + + case WCMD_EMBEDDED: + SetEmbedded(q[1],q[2]); + break; + } + + if(result==0) + { + WcmdqIncHead(); + resume=0; + } + else + { + resume=1; + } + } + + return(0); +} // end of WavegenFill + + diff --git a/Plugins/eSpeak/espeak-data/af_dict b/Plugins/eSpeak/espeak-data/af_dict Binary files differnew file mode 100644 index 0000000..061ae17 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/af_dict diff --git a/Plugins/eSpeak/espeak-data/ca_dict b/Plugins/eSpeak/espeak-data/ca_dict Binary files differnew file mode 100644 index 0000000..2803ca9 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/ca_dict diff --git a/Plugins/eSpeak/espeak-data/config b/Plugins/eSpeak/espeak-data/config new file mode 100644 index 0000000..be1b624 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/config @@ -0,0 +1,9 @@ +//pa_device 7 + +// play a sound for punctuation, rather than speak its name +//soundicon _( /usr/share/sounds/sound-icons/left-round-bracket +//soundicon _) /usr/share/sounds/sound-icons/right-round-bracket +//soundicon _[ /usr/share/sounds/sound-icons/left-square-bracket +//soundicon _] /usr/share/sounds/sound-icons/right-square-bracket +//soundicon _{ /usr/share/sounds/sound-icons/left-brace +//soundicon _} /usr/share/sounds/sound-icons/right-brace diff --git a/Plugins/eSpeak/espeak-data/cs_dict b/Plugins/eSpeak/espeak-data/cs_dict Binary files differnew file mode 100644 index 0000000..54fc382 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/cs_dict diff --git a/Plugins/eSpeak/espeak-data/cy_dict b/Plugins/eSpeak/espeak-data/cy_dict Binary files differnew file mode 100644 index 0000000..c6824d2 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/cy_dict diff --git a/Plugins/eSpeak/espeak-data/de_dict b/Plugins/eSpeak/espeak-data/de_dict Binary files differnew file mode 100644 index 0000000..749e5ea --- /dev/null +++ b/Plugins/eSpeak/espeak-data/de_dict diff --git a/Plugins/eSpeak/espeak-data/el_dict b/Plugins/eSpeak/espeak-data/el_dict Binary files differnew file mode 100644 index 0000000..e132c0a --- /dev/null +++ b/Plugins/eSpeak/espeak-data/el_dict diff --git a/Plugins/eSpeak/espeak-data/en_dict b/Plugins/eSpeak/espeak-data/en_dict Binary files differnew file mode 100644 index 0000000..0cf3544 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/en_dict diff --git a/Plugins/eSpeak/espeak-data/eo_dict b/Plugins/eSpeak/espeak-data/eo_dict Binary files differnew file mode 100644 index 0000000..660be3c --- /dev/null +++ b/Plugins/eSpeak/espeak-data/eo_dict diff --git a/Plugins/eSpeak/espeak-data/es_dict b/Plugins/eSpeak/espeak-data/es_dict Binary files differnew file mode 100644 index 0000000..53dfe04 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/es_dict diff --git a/Plugins/eSpeak/espeak-data/fi_dict b/Plugins/eSpeak/espeak-data/fi_dict Binary files differnew file mode 100644 index 0000000..449c625 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/fi_dict diff --git a/Plugins/eSpeak/espeak-data/fr_dict b/Plugins/eSpeak/espeak-data/fr_dict Binary files differnew file mode 100644 index 0000000..ef4a320 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/fr_dict diff --git a/Plugins/eSpeak/espeak-data/grc_dict b/Plugins/eSpeak/espeak-data/grc_dict Binary files differnew file mode 100644 index 0000000..e56a88a --- /dev/null +++ b/Plugins/eSpeak/espeak-data/grc_dict diff --git a/Plugins/eSpeak/espeak-data/hbs_dict b/Plugins/eSpeak/espeak-data/hbs_dict Binary files differnew file mode 100644 index 0000000..f26a458 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/hbs_dict diff --git a/Plugins/eSpeak/espeak-data/hi_dict b/Plugins/eSpeak/espeak-data/hi_dict Binary files differnew file mode 100644 index 0000000..6c15876 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/hi_dict diff --git a/Plugins/eSpeak/espeak-data/hu_dict b/Plugins/eSpeak/espeak-data/hu_dict Binary files differnew file mode 100644 index 0000000..9baa276 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/hu_dict diff --git a/Plugins/eSpeak/espeak-data/hy_dict b/Plugins/eSpeak/espeak-data/hy_dict Binary files differnew file mode 100644 index 0000000..eda8060 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/hy_dict diff --git a/Plugins/eSpeak/espeak-data/id_dict b/Plugins/eSpeak/espeak-data/id_dict Binary files differnew file mode 100644 index 0000000..8fdd454 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/id_dict diff --git a/Plugins/eSpeak/espeak-data/is_dict b/Plugins/eSpeak/espeak-data/is_dict Binary files differnew file mode 100644 index 0000000..c6b30f6 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/is_dict diff --git a/Plugins/eSpeak/espeak-data/it_dict b/Plugins/eSpeak/espeak-data/it_dict Binary files differnew file mode 100644 index 0000000..3da73b7 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/it_dict diff --git a/Plugins/eSpeak/espeak-data/jbo_dict b/Plugins/eSpeak/espeak-data/jbo_dict Binary files differnew file mode 100644 index 0000000..a40053b --- /dev/null +++ b/Plugins/eSpeak/espeak-data/jbo_dict diff --git a/Plugins/eSpeak/espeak-data/ku_dict b/Plugins/eSpeak/espeak-data/ku_dict Binary files differnew file mode 100644 index 0000000..e85acef --- /dev/null +++ b/Plugins/eSpeak/espeak-data/ku_dict diff --git a/Plugins/eSpeak/espeak-data/la_dict b/Plugins/eSpeak/espeak-data/la_dict Binary files differnew file mode 100644 index 0000000..3ffff7c --- /dev/null +++ b/Plugins/eSpeak/espeak-data/la_dict diff --git a/Plugins/eSpeak/espeak-data/lv_dict b/Plugins/eSpeak/espeak-data/lv_dict Binary files differnew file mode 100644 index 0000000..308f533 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/lv_dict diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/af1_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/af1_phtrans Binary files differnew file mode 100644 index 0000000..fc9ad01 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/af1_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/ca1_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/ca1_phtrans Binary files differnew file mode 100644 index 0000000..4fe4188 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/ca1_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/cr1_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/cr1_phtrans Binary files differnew file mode 100644 index 0000000..3bc162c --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/cr1_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/cs_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/cs_phtrans Binary files differnew file mode 100644 index 0000000..85ebb03 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/cs_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/de2_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/de2_phtrans Binary files differnew file mode 100644 index 0000000..c5af1a7 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/de2_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/de4_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/de4_phtrans Binary files differnew file mode 100644 index 0000000..b10fc84 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/de4_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/de6_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/de6_phtrans Binary files differnew file mode 100644 index 0000000..4cb62d9 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/de6_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/en1_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/en1_phtrans Binary files differnew file mode 100644 index 0000000..03cdf4e --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/en1_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/es_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/es_phtrans Binary files differnew file mode 100644 index 0000000..109bfe7 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/es_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/fr1_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/fr1_phtrans Binary files differnew file mode 100644 index 0000000..e868378 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/fr1_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/gr2_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/gr2_phtrans Binary files differnew file mode 100644 index 0000000..b3775ab --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/gr2_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/grc-de6_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/grc-de6_phtrans Binary files differnew file mode 100644 index 0000000..e41d310 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/grc-de6_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/hu1_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/hu1_phtrans Binary files differnew file mode 100644 index 0000000..56dba58 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/hu1_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/id1_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/id1_phtrans Binary files differnew file mode 100644 index 0000000..fd3cc44 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/id1_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/in1_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/in1_phtrans Binary files differnew file mode 100644 index 0000000..5ec7786 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/in1_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/it3_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/it3_phtrans Binary files differnew file mode 100644 index 0000000..6d82647 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/it3_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/la1_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/la1_phtrans Binary files differnew file mode 100644 index 0000000..1f2eb92 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/la1_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/nl_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/nl_phtrans Binary files differnew file mode 100644 index 0000000..d982c18 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/nl_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/pl1_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/pl1_phtrans Binary files differnew file mode 100644 index 0000000..9d4e50f --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/pl1_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/pt_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/pt_phtrans Binary files differnew file mode 100644 index 0000000..9de1630 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/pt_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/ptbr4_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/ptbr4_phtrans Binary files differnew file mode 100644 index 0000000..0b94de7 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/ptbr4_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/ptbr_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/ptbr_phtrans Binary files differnew file mode 100644 index 0000000..a1dbba0 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/ptbr_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/ro1_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/ro1_phtrans Binary files differnew file mode 100644 index 0000000..4aeaf54 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/ro1_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/sv2_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/sv2_phtrans Binary files differnew file mode 100644 index 0000000..ae119d8 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/sv2_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/sv_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/sv_phtrans Binary files differnew file mode 100644 index 0000000..bb556eb --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/sv_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/us3_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/us3_phtrans Binary files differnew file mode 100644 index 0000000..a1b4977 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/us3_phtrans diff --git a/Plugins/eSpeak/espeak-data/mbrola_ph/us_phtrans b/Plugins/eSpeak/espeak-data/mbrola_ph/us_phtrans Binary files differnew file mode 100644 index 0000000..4822cb6 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mbrola_ph/us_phtrans diff --git a/Plugins/eSpeak/espeak-data/mk_dict b/Plugins/eSpeak/espeak-data/mk_dict Binary files differnew file mode 100644 index 0000000..25afdd0 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/mk_dict diff --git a/Plugins/eSpeak/espeak-data/nl_dict b/Plugins/eSpeak/espeak-data/nl_dict Binary files differnew file mode 100644 index 0000000..04caac5 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/nl_dict diff --git a/Plugins/eSpeak/espeak-data/no_dict b/Plugins/eSpeak/espeak-data/no_dict Binary files differnew file mode 100644 index 0000000..91253a3 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/no_dict diff --git a/Plugins/eSpeak/espeak-data/phondata b/Plugins/eSpeak/espeak-data/phondata Binary files differnew file mode 100644 index 0000000..dcbe90a --- /dev/null +++ b/Plugins/eSpeak/espeak-data/phondata diff --git a/Plugins/eSpeak/espeak-data/phonindex b/Plugins/eSpeak/espeak-data/phonindex Binary files differnew file mode 100644 index 0000000..8aaf092 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/phonindex diff --git a/Plugins/eSpeak/espeak-data/phontab b/Plugins/eSpeak/espeak-data/phontab Binary files differnew file mode 100644 index 0000000..d3f640e --- /dev/null +++ b/Plugins/eSpeak/espeak-data/phontab diff --git a/Plugins/eSpeak/espeak-data/pl_dict b/Plugins/eSpeak/espeak-data/pl_dict Binary files differnew file mode 100644 index 0000000..f830e4c --- /dev/null +++ b/Plugins/eSpeak/espeak-data/pl_dict diff --git a/Plugins/eSpeak/espeak-data/pt_dict b/Plugins/eSpeak/espeak-data/pt_dict Binary files differnew file mode 100644 index 0000000..bd51c5f --- /dev/null +++ b/Plugins/eSpeak/espeak-data/pt_dict diff --git a/Plugins/eSpeak/espeak-data/ro_dict b/Plugins/eSpeak/espeak-data/ro_dict Binary files differnew file mode 100644 index 0000000..86c81d9 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/ro_dict diff --git a/Plugins/eSpeak/espeak-data/ru_dict b/Plugins/eSpeak/espeak-data/ru_dict Binary files differnew file mode 100644 index 0000000..dbbaf7d --- /dev/null +++ b/Plugins/eSpeak/espeak-data/ru_dict diff --git a/Plugins/eSpeak/espeak-data/sk_dict b/Plugins/eSpeak/espeak-data/sk_dict Binary files differnew file mode 100644 index 0000000..3974516 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/sk_dict diff --git a/Plugins/eSpeak/espeak-data/sq_dict b/Plugins/eSpeak/espeak-data/sq_dict Binary files differnew file mode 100644 index 0000000..b947b25 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/sq_dict diff --git a/Plugins/eSpeak/espeak-data/sv_dict b/Plugins/eSpeak/espeak-data/sv_dict Binary files differnew file mode 100644 index 0000000..e78000d --- /dev/null +++ b/Plugins/eSpeak/espeak-data/sv_dict diff --git a/Plugins/eSpeak/espeak-data/sw_dict b/Plugins/eSpeak/espeak-data/sw_dict Binary files differnew file mode 100644 index 0000000..330fb42 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/sw_dict diff --git a/Plugins/eSpeak/espeak-data/ta_dict b/Plugins/eSpeak/espeak-data/ta_dict Binary files differnew file mode 100644 index 0000000..3d7e375 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/ta_dict diff --git a/Plugins/eSpeak/espeak-data/tr_dict b/Plugins/eSpeak/espeak-data/tr_dict Binary files differnew file mode 100644 index 0000000..8376542 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/tr_dict diff --git a/Plugins/eSpeak/espeak-data/vi_dict b/Plugins/eSpeak/espeak-data/vi_dict Binary files differnew file mode 100644 index 0000000..dd1f2a9 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/vi_dict diff --git a/Plugins/eSpeak/espeak-data/voices/!v/croak b/Plugins/eSpeak/espeak-data/voices/!v/croak new file mode 100644 index 0000000..ae76a4c --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/!v/croak @@ -0,0 +1,11 @@ +language variant +name croak +gender male 70 + +pitch 85 117 +flutter 20 + +formant 0 100 80 110 + + + diff --git a/Plugins/eSpeak/espeak-data/voices/!v/f1 b/Plugins/eSpeak/espeak-data/voices/!v/f1 new file mode 100644 index 0000000..13664a3 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/!v/f1 @@ -0,0 +1,18 @@ +language variant +name female1 +gender female + +pitch 145 200 +flutter 7 +roughness 4 +formant 0 115 80 150 +formant 1 120 80 180 +formant 2 100 70 150 150 +formant 3 115 70 150 +formant 4 110 80 150 +formant 5 110 90 150 +formant 6 105 80 150 +formant 7 110 70 150 +formant 8 110 70 150 + +stressAdd -10 -10 -20 -20 0 0 40 70 diff --git a/Plugins/eSpeak/espeak-data/voices/!v/f2 b/Plugins/eSpeak/espeak-data/voices/!v/f2 new file mode 100644 index 0000000..e929467 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/!v/f2 @@ -0,0 +1,20 @@ +language variant +name female2 +gender female + +pitch 142 220 +roughness 3 + +formant 0 105 80 150 +formant 1 110 80 160 +formant 2 110 70 150 +formant 3 110 70 150 +formant 4 115 80 150 +formant 5 115 80 150 +formant 6 110 70 150 +formant 7 110 70 150 +formant 8 110 70 150 + +stressAdd 0 0 -10 -10 0 0 10 40 +breath 0 2 3 3 3 3 3 2 +echo 140 10 diff --git a/Plugins/eSpeak/espeak-data/voices/!v/f3 b/Plugins/eSpeak/espeak-data/voices/!v/f3 new file mode 100644 index 0000000..92a1582 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/!v/f3 @@ -0,0 +1,22 @@ +language variant +name female3 +gender female + +pitch 140 240 +formant 0 105 80 150 +formant 1 120 75 150 -50 +formant 2 135 70 150 -250 +formant 3 125 80 150 +formant 4 125 80 150 +formant 5 125 80 150 +formant 6 120 70 150 +formant 7 110 70 150 +formant 8 110 70 150 + +stressAmp 18 18 20 20 20 20 20 20 +//breath 0 2 4 4 4 4 4 4 +breath 0 2 3 3 3 3 3 2 +echo 120 10 +roughness 4 + + diff --git a/Plugins/eSpeak/espeak-data/voices/!v/f4 b/Plugins/eSpeak/espeak-data/voices/!v/f4 new file mode 100644 index 0000000..52c5ac9 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/!v/f4 @@ -0,0 +1,18 @@ +language variant +name female4 +gender female + +echo 130 15 +pitch 142 200 +formant 0 120 80 150 +formant 1 115 80 160 -20 +formant 2 130 75 150 -200 +formant 3 123 75 150 +formant 4 125 80 150 +formant 5 125 80 150 +formant 6 110 80 150 +formant 7 110 75 150 +formant 8 110 75 150 + +stressAdd -20 -20 -20 -20 0 0 20 120 +stressAmp 18 16 20 20 20 20 20 20 diff --git a/Plugins/eSpeak/espeak-data/voices/!v/m1 b/Plugins/eSpeak/espeak-data/voices/!v/m1 new file mode 100644 index 0000000..57603a8 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/!v/m1 @@ -0,0 +1,19 @@ +language variant +name male1 +gender male 70 + +pitch 74 109 +flutter 4 +roughness 4 + +formant 0 98 95 100 +formant 1 97 95 100 +formant 2 97 95 100 +formant 3 97 100 100 +formant 4 97 100 100 +formant 5 105 100 100 +formant 6 95 100 100 +formant 7 100 100 100 +formant 8 100 100 100 + +stressAdd -10 -10 -20 -20 0 0 40 70 diff --git a/Plugins/eSpeak/espeak-data/voices/!v/m2 b/Plugins/eSpeak/espeak-data/voices/!v/m2 new file mode 100644 index 0000000..c234f46 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/!v/m2 @@ -0,0 +1,15 @@ +language variant +name male2 +gender male + +pitch 88 115 +echo 130 15 +formant 0 100 80 120 +formant 1 90 85 120 +formant 2 110 85 120 +formant 3 105 90 120 +formant 4 100 90 120 +formant 5 100 90 120 +formant 6 100 90 120 +formant 7 100 90 120 +formant 8 100 90 120 diff --git a/Plugins/eSpeak/espeak-data/voices/!v/m3 b/Plugins/eSpeak/espeak-data/voices/!v/m3 new file mode 100644 index 0000000..581cd88 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/!v/m3 @@ -0,0 +1,16 @@ +language variant +name male3 +gender male + +pitch 80 122 +formant 0 100 100 100 +formant 1 96 97 100 +formant 2 96 97 100 +formant 3 96 103 100 +formant 4 95 103 100 +formant 5 95 103 100 +formant 6 100 100 100 +formant 7 100 100 100 +formant 8 100 100 100 + +stressAdd 10 10 0 0 0 0 -30 -30 diff --git a/Plugins/eSpeak/espeak-data/voices/!v/m4 b/Plugins/eSpeak/espeak-data/voices/!v/m4 new file mode 100644 index 0000000..7199341 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/!v/m4 @@ -0,0 +1,17 @@ +language variant +name male4 +gender male + +pitch 70 110 + +formant 0 103 100 100 +formant 1 103 100 100 +formant 2 103 100 100 +formant 3 103 100 100 +formant 4 106 100 100 +formant 5 106 100 100 +formant 6 106 100 100 +formant 7 103 100 100 +formant 8 103 100 100 + +stressAdd -10 -10 -30 -30 0 0 60 90 diff --git a/Plugins/eSpeak/espeak-data/voices/!v/m5 b/Plugins/eSpeak/espeak-data/voices/!v/m5 new file mode 100644 index 0000000..d258656 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/!v/m5 @@ -0,0 +1,15 @@ +language variant +name male5 +gender male + +formant 0 100 85 130 +formant 1 90 85 130 40 +formant 2 80 85 130 310 +formant 3 105 85 130 +formant 4 105 85 130 +formant 5 105 85 130 +formant 6 105 85 150 +formant 7 105 85 150 +formant 8 105 85 150 + +intonation 2 diff --git a/Plugins/eSpeak/espeak-data/voices/!v/m6 b/Plugins/eSpeak/espeak-data/voices/!v/m6 new file mode 100644 index 0000000..bd336a9 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/!v/m6 @@ -0,0 +1,13 @@ +language variant +name male6 +gender male + +pitch 82 117 + +formant 0 100 90 120 +formant 1 100 90 140 +formant 2 100 70 140 +formant 3 100 75 140 +formant 4 100 80 140 +formant 5 100 80 140 + diff --git a/Plugins/eSpeak/espeak-data/voices/!v/wisper b/Plugins/eSpeak/espeak-data/voices/!v/wisper new file mode 100644 index 0000000..b2f8497 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/!v/wisper @@ -0,0 +1,13 @@ +language variant +name wisper +gender male + +pitch 82 117 +flutter 20 + +formant 0 100 0 100 +formant 1 100 80 100 + +voicing 17 +breath 75 75 50 40 15 10 +breathw 150 150 200 200 400 400 diff --git a/Plugins/eSpeak/espeak-data/voices/af b/Plugins/eSpeak/espeak-data/voices/af new file mode 100644 index 0000000..bcbb2a0 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/af @@ -0,0 +1,8 @@ +name afrikaans +language af +gender male +roughness 0 +pitch 63 120 + + + diff --git a/Plugins/eSpeak/espeak-data/voices/bs b/Plugins/eSpeak/espeak-data/voices/bs new file mode 100644 index 0000000..eadd707 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/bs @@ -0,0 +1,16 @@ +name bosnian +language bs +phonemes hr +dictionary hbs +gender male + +pitch 81 120 +formant 0 100 100 100 +formant 1 97 97 100 +formant 2 97 97 100 +formant 3 97 102 100 +formant 4 97 102 100 +formant 5 97 102 100 + +stressAdd 10 10 0 0 0 0 -30 -30 +dictrules 3 4 diff --git a/Plugins/eSpeak/espeak-data/voices/ca b/Plugins/eSpeak/espeak-data/voices/ca new file mode 100644 index 0000000..dc51396 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/ca @@ -0,0 +1,4 @@ +name catalan +language ca +gender male + diff --git a/Plugins/eSpeak/espeak-data/voices/cs b/Plugins/eSpeak/espeak-data/voices/cs new file mode 100644 index 0000000..1c2992d --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/cs @@ -0,0 +1,4 @@ +name czech +language cs +gender male + diff --git a/Plugins/eSpeak/espeak-data/voices/cy b/Plugins/eSpeak/espeak-data/voices/cy new file mode 100644 index 0000000..2991e99 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/cy @@ -0,0 +1,5 @@ +language cy +name welsh-test +gender male + +intonation 4 diff --git a/Plugins/eSpeak/espeak-data/voices/de b/Plugins/eSpeak/espeak-data/voices/de new file mode 100644 index 0000000..653c3f5 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/de @@ -0,0 +1,5 @@ +name german +language de +gender male + + diff --git a/Plugins/eSpeak/espeak-data/voices/default b/Plugins/eSpeak/espeak-data/voices/default new file mode 100644 index 0000000..7accc8b --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/default @@ -0,0 +1,6 @@ +name default +language en +gender male + +formant 0 100 100 110 + diff --git a/Plugins/eSpeak/espeak-data/voices/el b/Plugins/eSpeak/espeak-data/voices/el new file mode 100644 index 0000000..1e9a757 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/el @@ -0,0 +1,5 @@ +name greek +language el +gender male + + diff --git a/Plugins/eSpeak/espeak-data/voices/en/en b/Plugins/eSpeak/espeak-data/voices/en/en new file mode 100644 index 0000000..dc6a60c --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/en/en @@ -0,0 +1,11 @@ +name english +language en-uk 2 +language en 2 +gender male + +//pitch 80 117 + +replace 03 I i +replace 03 I2 i + +formant 0 100 100 105 diff --git a/Plugins/eSpeak/espeak-data/voices/en/en-n b/Plugins/eSpeak/espeak-data/voices/en/en-n new file mode 100644 index 0000000..933311d --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/en/en-n @@ -0,0 +1,14 @@ +name lancashire +language en-uk-north +language en-uk 3 +gender male + +phonemes en_n + +stressLength 160 150 180 180 220 220 290 290 + +replace 00 i@3 i@ +replace 03 N n +//replace 03 I i +//replace 03 I2 i + diff --git a/Plugins/eSpeak/espeak-data/voices/en/en-rp b/Plugins/eSpeak/espeak-data/voices/en/en-rp new file mode 100644 index 0000000..3489f28 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/en/en-rp @@ -0,0 +1,12 @@ +name english_rp +language en-uk-rp +language en-uk 4 +gender male + +phonemes en_rp +replace 00 o@ O@ +replace 00 i@3 i@ +replace 03 I i +replace 03 I2 i +replace 03 @ a2 +replace 03 3 a2 diff --git a/Plugins/eSpeak/espeak-data/voices/en/en-sc b/Plugins/eSpeak/espeak-data/voices/en/en-sc new file mode 100644 index 0000000..e16ae25 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/en/en-sc @@ -0,0 +1,16 @@ +name en-scottish +language en-sc +language en 4 +gender male + +phonemes en_sc +dictrules 5 6 7 +stressLength 180 130 200 200 0 0 250 270 + +replace 03 @ V +replace 03 I i +replace 03 I2 i +replace 01 aI aI2 +replace 02 a a/ +replace 02 u: U +replace 02 3: VR diff --git a/Plugins/eSpeak/espeak-data/voices/en/en-wi b/Plugins/eSpeak/espeak-data/voices/en/en-wi new file mode 100644 index 0000000..28a42a5 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/en/en-wi @@ -0,0 +1,19 @@ +name en-westindies +language en-wi +language en-uk 4 +gender male + +phonemes en_wi +dictrules 8 +stressLength 175 175 175 175 220 220 250 290 + +replace 00 D d +replace 00 T t[ +replace 00 U@ o@ +replace 00 i@3 i@ +replace 03 @ a2 +replace 03 3 a2 +replace 03 N n + +formant 1 98 100 100 +formant 2 98 100 100 diff --git a/Plugins/eSpeak/espeak-data/voices/en/en-wm b/Plugins/eSpeak/espeak-data/voices/en/en-wm new file mode 100644 index 0000000..aa82f88 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/en/en-wm @@ -0,0 +1,12 @@ +name english_wmids +language en-uk-wmids +gender male + +phonemes en_wm + +replace 00 h NULL +replace 00 o@ O@ +replace 00 i@3 i@ +dictrules 6 +intonation 4 +stressAdd 0 0 0 0 0 0 0 20 diff --git a/Plugins/eSpeak/espeak-data/voices/eo b/Plugins/eSpeak/espeak-data/voices/eo new file mode 100644 index 0000000..742b45a --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/eo @@ -0,0 +1,4 @@ +name esperanto +language eo +gender male + diff --git a/Plugins/eSpeak/espeak-data/voices/es b/Plugins/eSpeak/espeak-data/voices/es new file mode 100644 index 0000000..1a9e53b --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/es @@ -0,0 +1,7 @@ +name spanish +language es +gender male + +dictrules 1 +intonation 3 + diff --git a/Plugins/eSpeak/espeak-data/voices/es-la b/Plugins/eSpeak/espeak-data/voices/es-la new file mode 100644 index 0000000..c326c46 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/es-la @@ -0,0 +1,11 @@ +name spanish-latin-american +language es-la +language es-mx 6 +gender male + +phonemes es_la +dictrules 2 +intonation 2 +stressLength 170 200 180 180 0 0 250 280 + +replace 00 T s diff --git a/Plugins/eSpeak/espeak-data/voices/fi b/Plugins/eSpeak/espeak-data/voices/fi new file mode 100644 index 0000000..6e11c93 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/fi @@ -0,0 +1,4 @@ +name finnish +language fi +gender male + diff --git a/Plugins/eSpeak/espeak-data/voices/fr b/Plugins/eSpeak/espeak-data/voices/fr new file mode 100644 index 0000000..9730731 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/fr @@ -0,0 +1,7 @@ +language fr +name french +gender male + +dictrules 1 +intonation 3 + diff --git a/Plugins/eSpeak/espeak-data/voices/fr-be b/Plugins/eSpeak/espeak-data/voices/fr-be new file mode 100644 index 0000000..cba9b27 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/fr-be @@ -0,0 +1,7 @@ +language fr-be +name french (Belgium) +gender male + +dictrules 2 +intonation 3 + diff --git a/Plugins/eSpeak/espeak-data/voices/hi b/Plugins/eSpeak/espeak-data/voices/hi new file mode 100644 index 0000000..de4786c --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/hi @@ -0,0 +1,9 @@ +name hindi-test +language hi +gender male + +translator hi +phonemes hi +dictionary hi + +dictrules 1 diff --git a/Plugins/eSpeak/espeak-data/voices/hr b/Plugins/eSpeak/espeak-data/voices/hr new file mode 100644 index 0000000..d6811d3 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/hr @@ -0,0 +1,18 @@ +name croatian +language hr +language hbs +gender male + +dictionary hbs + +// attributes towards !variant3 +pitch 81 120 +formant 0 100 100 100 +formant 1 97 97 100 +formant 2 97 97 100 +formant 3 97 102 100 +formant 4 97 102 100 +formant 5 97 102 100 + +stressAdd 10 10 0 0 0 0 -30 -30 +dictrules 1 diff --git a/Plugins/eSpeak/espeak-data/voices/hu b/Plugins/eSpeak/espeak-data/voices/hu new file mode 100644 index 0000000..ba2bdde --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/hu @@ -0,0 +1,3 @@ +name hungarian +language hu +gender male diff --git a/Plugins/eSpeak/espeak-data/voices/id b/Plugins/eSpeak/espeak-data/voices/id new file mode 100644 index 0000000..ce800f7 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/id @@ -0,0 +1,8 @@ +name indonesian-test +language id +gender male + +stressLength 160 200 180 180 0 0 220 240 +stressAmp 16 18 18 18 0 0 22 21 + +consonants 80 80 diff --git a/Plugins/eSpeak/espeak-data/voices/is b/Plugins/eSpeak/espeak-data/voices/is new file mode 100644 index 0000000..9e9c4e7 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/is @@ -0,0 +1,4 @@ +name icelandic-test +language is +gender male + diff --git a/Plugins/eSpeak/espeak-data/voices/it b/Plugins/eSpeak/espeak-data/voices/it new file mode 100644 index 0000000..53c2a70 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/it @@ -0,0 +1,6 @@ +name italian +language it +gender male + +replace 03 i I + diff --git a/Plugins/eSpeak/espeak-data/voices/ku b/Plugins/eSpeak/espeak-data/voices/ku new file mode 100644 index 0000000..536957c --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/ku @@ -0,0 +1,6 @@ +name kurdish +language ku +gender male + +//words 1 48 + diff --git a/Plugins/eSpeak/espeak-data/voices/la b/Plugins/eSpeak/espeak-data/voices/la new file mode 100644 index 0000000..f3e97b5 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/la @@ -0,0 +1,13 @@ +name latin +language la +gender male +stressrule 2 33 0 2 +// rule=penultimate +// flags=0100001 (no automatic secondary stress + don't stres monosyllables) +// unstressed_wd1=0 +// unstressed_wd2=2 + +// short gap between words +words 2 + +// Note: The Latin voice needs long vowels to be marked with macrons diff --git a/Plugins/eSpeak/espeak-data/voices/lv b/Plugins/eSpeak/espeak-data/voices/lv new file mode 100644 index 0000000..0278ea2 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/lv @@ -0,0 +1,6 @@ +name latvian +language lv +gender male + +replace 03 o o: + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-af1 b/Plugins/eSpeak/espeak-data/voices/mb/mb-af1 new file mode 100644 index 0000000..03dac4f --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-af1 @@ -0,0 +1,7 @@ +name afrikaans-mbrola-1 +language af 7 +gender male + +pitch 82 117 +mbrola af1 af1_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-af1-en b/Plugins/eSpeak/espeak-data/voices/mb/mb-af1-en new file mode 100644 index 0000000..71ecab7 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-af1-en @@ -0,0 +1,7 @@ +name en-afrikaans +language en 11 +gender male + +pitch 82 117 +mbrola af1 af1_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-br1 b/Plugins/eSpeak/espeak-data/voices/mb/mb-br1 new file mode 100644 index 0000000..ba7c42c --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-br1 @@ -0,0 +1,9 @@ +language pt 7 +name brazil-mbrola-1 +gender male +pitch 82 117 + +dictrules 2 3 4 + +mbrola br1 ptbr_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-br3 b/Plugins/eSpeak/espeak-data/voices/mb/mb-br3 new file mode 100644 index 0000000..8479e65 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-br3 @@ -0,0 +1,9 @@ +language pt 7 +name brazil-mbrola-3 +gender male +pitch 82 117 + +dictrules 2 3 4 + +mbrola br3 ptbr_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-br4 b/Plugins/eSpeak/espeak-data/voices/mb/mb-br4 new file mode 100644 index 0000000..d3d7720 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-br4 @@ -0,0 +1,9 @@ +language pt 7 +name brazil-mbrola-4 +gender female +pitch 140 220 + +dictrules 2 3 4 + +mbrola br4 ptbr4_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-cr1 b/Plugins/eSpeak/espeak-data/voices/mb/mb-cr1 new file mode 100644 index 0000000..9b280bf --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-cr1 @@ -0,0 +1,9 @@ +name croatian-mbrola-1 +language hr 7 +gender male + +dictionary hbs +dictrules 1 + +pitch 82 117 +mbrola cr1 cr1_phtrans diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-cz2 b/Plugins/eSpeak/espeak-data/voices/mb/mb-cz2 new file mode 100644 index 0000000..dbde212 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-cz2 @@ -0,0 +1,6 @@ +name czech-mbrola-2 +language cs 7 +gender male + +pitch 82 117 +mbrola cz2 cs_phtrans diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-de2 b/Plugins/eSpeak/espeak-data/voices/mb/mb-de2 new file mode 100644 index 0000000..c0a5475 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-de2 @@ -0,0 +1,6 @@ +name german-mbrola-2 +language de 6 +gender male + +mbrola de2 de2_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-de4 b/Plugins/eSpeak/espeak-data/voices/mb/mb-de4 new file mode 100644 index 0000000..31bd479 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-de4 @@ -0,0 +1,6 @@ +name german-mbrola-4 +language de 6 +gender male + +mbrola de4 de4_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-de4-en b/Plugins/eSpeak/espeak-data/voices/mb/mb-de4-en new file mode 100644 index 0000000..8fd4a63 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-de4-en @@ -0,0 +1,6 @@ +name en-german +language en 9 +gender male + +mbrola de4 de4_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-de5 b/Plugins/eSpeak/espeak-data/voices/mb/mb-de5 new file mode 100644 index 0000000..569f9d0 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-de5 @@ -0,0 +1,10 @@ +name german-mbrola-5 +language de 7 +gender female + +pitch 140 220 +mbrola de5 de6_phtrans 22050 + +// avoid glottal stops. de5 assumes [?] between pause and vowel +replace 00 _! _ +replace 00 _| _ diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-de5-en b/Plugins/eSpeak/espeak-data/voices/mb/mb-de5-en new file mode 100644 index 0000000..e416c6d --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-de5-en @@ -0,0 +1,7 @@ +name en-german-5 +language en +gender female + +pitch 140 220 +mbrola de5 de6_phtrans 22050 + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-de6 b/Plugins/eSpeak/espeak-data/voices/mb/mb-de6 new file mode 100644 index 0000000..35a4a3f --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-de6 @@ -0,0 +1,6 @@ +name german-mbrola-6 +language de 6 +gender male + +mbrola de6 de6_phtrans 22050 + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-de6-grc b/Plugins/eSpeak/espeak-data/voices/mb/mb-de6-grc new file mode 100644 index 0000000..a6e0f46 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-de6-grc @@ -0,0 +1,6 @@ +name german-mbrola-6 +language grc 6 +gender male + +mbrola de6 grc-de6_phtrans 22050 + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-de7 b/Plugins/eSpeak/espeak-data/voices/mb/mb-de7 new file mode 100644 index 0000000..aa80eda --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-de7 @@ -0,0 +1,7 @@ +name german-mbrola-7 +language de 7 +gender female + +pitch 140 220 +mbrola de7 de6_phtrans 22050 + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-en1 b/Plugins/eSpeak/espeak-data/voices/mb/mb-en1 new file mode 100644 index 0000000..fc60f41 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-en1 @@ -0,0 +1,7 @@ +name english-mb-en1 +language en-uk 3 +language en 2 +gender male + +pitch 82 117 +mbrola en1 en1_phtrans diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-es1 b/Plugins/eSpeak/espeak-data/voices/mb/mb-es1 new file mode 100644 index 0000000..d59fe79 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-es1 @@ -0,0 +1,7 @@ +language es 7 +name spanish-mbrola-1 +gender male +pitch 82 117 + +mbrola es1 es_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-es2 b/Plugins/eSpeak/espeak-data/voices/mb/mb-es2 new file mode 100644 index 0000000..42de588 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-es2 @@ -0,0 +1,7 @@ +language es 7 +name spanish-mbrola-2 +gender male +pitch 82 117 + +mbrola es2 es_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-fr1 b/Plugins/eSpeak/espeak-data/voices/mb/mb-fr1 new file mode 100644 index 0000000..7cbdab3 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-fr1 @@ -0,0 +1,9 @@ +language fr 7 +name french-mbrola-1 +gender male + +dictrules 1 +stressLength 180 180 180 180 0 0 220 220 +pitch 82 117 +mbrola fr1 fr1_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-fr1-en b/Plugins/eSpeak/espeak-data/voices/mb/mb-fr1-en new file mode 100644 index 0000000..3666531 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-fr1-en @@ -0,0 +1,8 @@ +name en-french +language en 10 +gender male + +dictrules 1 +pitch 82 117 +mbrola fr1 fr1_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-fr4 b/Plugins/eSpeak/espeak-data/voices/mb/mb-fr4 new file mode 100644 index 0000000..c276bec --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-fr4 @@ -0,0 +1,8 @@ +language fr 7 +name french-mbrola-4 +gender female + +dictrules 1 +pitch 140 220 +mbrola fr1 fr1_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-fr4-en b/Plugins/eSpeak/espeak-data/voices/mb/mb-fr4-en new file mode 100644 index 0000000..b8f7829 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-fr4-en @@ -0,0 +1,8 @@ +language en 10 +name en-french +gender female + +dictrules 1 +pitch 140 220 +mbrola fr1 fr1_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-gr2 b/Plugins/eSpeak/espeak-data/voices/mb/mb-gr2 new file mode 100644 index 0000000..30dea89 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-gr2 @@ -0,0 +1,6 @@ +name greek-mbrola-1 +language el 7 +gender male + +pitch 82 117 +mbrola gr2 gr2_phtrans diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-gr2-en b/Plugins/eSpeak/espeak-data/voices/mb/mb-gr2-en new file mode 100644 index 0000000..b48b178 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-gr2-en @@ -0,0 +1,6 @@ +name en-greek +language en 7 +gender male + +pitch 82 117 +mbrola gr2 gr2_phtrans diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-hu1 b/Plugins/eSpeak/espeak-data/voices/mb/mb-hu1 new file mode 100644 index 0000000..b851955 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-hu1 @@ -0,0 +1,6 @@ +name hungarian-mbrola-1 +language hu 7 +gender female + +pitch 140 220 +mbrola hu1 hu1_phtrans diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-hu1-en b/Plugins/eSpeak/espeak-data/voices/mb/mb-hu1-en new file mode 100644 index 0000000..73ac62a --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-hu1-en @@ -0,0 +1,6 @@ +name en-hungarian +language en 10 +gender female + +pitch 140 220 +mbrola hu1 hu1_phtrans diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-id1 b/Plugins/eSpeak/espeak-data/voices/mb/mb-id1 new file mode 100644 index 0000000..b86f593 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-id1 @@ -0,0 +1,7 @@ +name indonesian-mbrola-1 +language id 7 +gender male + +pitch 82 117 +mbrola id1 id1_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-it3 b/Plugins/eSpeak/espeak-data/voices/mb/mb-it3 new file mode 100644 index 0000000..00e8886 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-it3 @@ -0,0 +1,8 @@ +name italian-mbrola-3 +language it 7 +gender male + +pitch 82 117 +mbrola it3 it3_phtrans + +replace 03 i I // final unstressed "i" diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-it4 b/Plugins/eSpeak/espeak-data/voices/mb/mb-it4 new file mode 100644 index 0000000..f2130ba --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-it4 @@ -0,0 +1,8 @@ +name italian-mbrola-4 +language it 7 +gender female + +pitch 140 220 +mbrola it4 it3_phtrans + +replace 03 i I // final unstressed "i" diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-la1 b/Plugins/eSpeak/espeak-data/voices/mb/mb-la1 new file mode 100644 index 0000000..7ef93a5 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-la1 @@ -0,0 +1,6 @@ +name latin-mbrola-1 +language la 7 +gender male + +pitch 82 117 +mbrola la1 la1_phtrans diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-nl2 b/Plugins/eSpeak/espeak-data/voices/mb/mb-nl2 new file mode 100644 index 0000000..fc37715 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-nl2 @@ -0,0 +1,7 @@ +language nl 7 +name dutch-mbrola-2 +gender male + +pitch 82 117 +mbrola nl2 nl_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-nl2-en b/Plugins/eSpeak/espeak-data/voices/mb/mb-nl2-en new file mode 100644 index 0000000..0c2d13a --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-nl2-en @@ -0,0 +1,7 @@ +language en 10 +name en-dutch +gender male + +pitch 82 117 +mbrola nl2 nl_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-pl1 b/Plugins/eSpeak/espeak-data/voices/mb/mb-pl1 new file mode 100644 index 0000000..4e2b9d2 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-pl1 @@ -0,0 +1,6 @@ +name polish-mbrola-1 +language pl 7 +gender female + +pitch 140 220 +mbrola pl1 pl1_phtrans diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-pl1-en b/Plugins/eSpeak/espeak-data/voices/mb/mb-pl1-en new file mode 100644 index 0000000..9ba872a --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-pl1-en @@ -0,0 +1,6 @@ +name en-polish +language en 11 +gender female + +pitch 140 220 +mbrola pl1 pl1_phtrans diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-pt1 b/Plugins/eSpeak/espeak-data/voices/mb/mb-pt1 new file mode 100644 index 0000000..ebd92ff --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-pt1 @@ -0,0 +1,9 @@ +language pt 7 +name portugal-mbrola-1 +gender female +pitch 140 220 + +dictrules 1 + +mbrola pt1 pt1_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-ro1 b/Plugins/eSpeak/espeak-data/voices/mb/mb-ro1 new file mode 100644 index 0000000..14417c1 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-ro1 @@ -0,0 +1,7 @@ +name romanian-mbrola-1 +language ro 7 +gender male + +pitch 82 117 +mbrola ro1 ro1_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-ro1-en b/Plugins/eSpeak/espeak-data/voices/mb/mb-ro1-en new file mode 100644 index 0000000..f310f86 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-ro1-en @@ -0,0 +1,7 @@ +name en-romanian +language en 9 +gender male + +pitch 82 117 +mbrola ro1 ro1_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-sw1 b/Plugins/eSpeak/espeak-data/voices/mb/mb-sw1 new file mode 100644 index 0000000..4c62392 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-sw1 @@ -0,0 +1,7 @@ +name swedish-mbrola-1 +language sv 7 +gender male + +pitch 82 117 +mbrola sw1 sv_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-sw1-en b/Plugins/eSpeak/espeak-data/voices/mb/mb-sw1-en new file mode 100644 index 0000000..52692c3 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-sw1-en @@ -0,0 +1,7 @@ +name en-swedish +language en 11 +gender male + +pitch 82 117 +mbrola sw1 sv_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-sw2 b/Plugins/eSpeak/espeak-data/voices/mb/mb-sw2 new file mode 100644 index 0000000..c632e26 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-sw2 @@ -0,0 +1,7 @@ +name swedish-mbrola-2 +language sv 8 +gender female + +pitch 140 220 +mbrola sw2 sv2_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-sw2-en b/Plugins/eSpeak/espeak-data/voices/mb/mb-sw2-en new file mode 100644 index 0000000..f2033dc --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-sw2-en @@ -0,0 +1,7 @@ +name en-swedish-f +language en +gender female + +pitch 140 220 +mbrola sw2 sv2_phtrans + diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-us1 b/Plugins/eSpeak/espeak-data/voices/mb/mb-us1 new file mode 100644 index 0000000..c62589b --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-us1 @@ -0,0 +1,12 @@ +name us-mbrola-1 +language en-us +language en 8 +gender female + +phonemes en_us +dictrules 3 6 + +stressLength 170 135 205 205 0 0 245 275 + +pitch 140 220 +mbrola us1 us_phtrans diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-us2 b/Plugins/eSpeak/espeak-data/voices/mb/mb-us2 new file mode 100644 index 0000000..d94fce5 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-us2 @@ -0,0 +1,12 @@ +name us-mbrola-2 +language en-us +language en 7 +gender male + +phonemes en_us +dictrules 3 6 + +stressLength 170 135 205 205 0 0 245 275 + +pitch 82 117 +mbrola us2 us_phtrans diff --git a/Plugins/eSpeak/espeak-data/voices/mb/mb-us3 b/Plugins/eSpeak/espeak-data/voices/mb/mb-us3 new file mode 100644 index 0000000..645e1b7 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mb/mb-us3 @@ -0,0 +1,12 @@ +name us-mbrola-3 +language en-us +language en 8 +gender male + +phonemes en_us +dictrules 3 6 + +stressLength 170 135 205 205 0 0 245 275 + +pitch 82 117 +mbrola us3 us3_phtrans diff --git a/Plugins/eSpeak/espeak-data/voices/mk b/Plugins/eSpeak/espeak-data/voices/mk new file mode 100644 index 0000000..4607dd0 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/mk @@ -0,0 +1,4 @@ +name macedonian-test +language mk +gender male + diff --git a/Plugins/eSpeak/espeak-data/voices/nl b/Plugins/eSpeak/espeak-data/voices/nl new file mode 100644 index 0000000..6a8d5ef --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/nl @@ -0,0 +1,3 @@ +language nl +name dutch-test +gender male diff --git a/Plugins/eSpeak/espeak-data/voices/no b/Plugins/eSpeak/espeak-data/voices/no new file mode 100644 index 0000000..77b60b9 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/no @@ -0,0 +1,6 @@ +name norwegian-test +language no +language nb +gender male + +intonation 4 diff --git a/Plugins/eSpeak/espeak-data/voices/pl b/Plugins/eSpeak/espeak-data/voices/pl new file mode 100644 index 0000000..8fc65d4 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/pl @@ -0,0 +1,5 @@ +name polish +language pl +gender male + +intonation 2 diff --git a/Plugins/eSpeak/espeak-data/voices/pt b/Plugins/eSpeak/espeak-data/voices/pt new file mode 100644 index 0000000..53cb314 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/pt @@ -0,0 +1,7 @@ +name brazil +language pt +language pt-br +gender male + +dictrules 2 + diff --git a/Plugins/eSpeak/espeak-data/voices/pt-pt b/Plugins/eSpeak/espeak-data/voices/pt-pt new file mode 100644 index 0000000..e23915f --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/pt-pt @@ -0,0 +1,7 @@ +name portugal +language pt-pt +gender male +phonemes pt_pt + +dictrules 1 +intonation 2 diff --git a/Plugins/eSpeak/espeak-data/voices/ro b/Plugins/eSpeak/espeak-data/voices/ro new file mode 100644 index 0000000..d8ecd25 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/ro @@ -0,0 +1,5 @@ +name romanian +language ro +gender male + + diff --git a/Plugins/eSpeak/espeak-data/voices/ru b/Plugins/eSpeak/espeak-data/voices/ru new file mode 100644 index 0000000..238c691 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/ru @@ -0,0 +1,6 @@ +name russian_test +language ru +gender male + +replace 03 a a# + diff --git a/Plugins/eSpeak/espeak-data/voices/sk b/Plugins/eSpeak/espeak-data/voices/sk new file mode 100644 index 0000000..026363f --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/sk @@ -0,0 +1,4 @@ +name slovak +language sk +gender male + diff --git a/Plugins/eSpeak/espeak-data/voices/sr b/Plugins/eSpeak/espeak-data/voices/sr new file mode 100644 index 0000000..a7a8223 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/sr @@ -0,0 +1,15 @@ +name serbian +language sr +gender male +dictionary hbs + +// attributes towards !variant3 pitch 80 120 +formant 0 100 100 100 +formant 1 97 97 100 +formant 2 97 97 100 +formant 3 97 102 100 +formant 4 97 102 100 +formant 5 97 102 100 + +stressAdd 10 10 0 0 0 0 -30 -30 +dictrules 2 4 diff --git a/Plugins/eSpeak/espeak-data/voices/sv b/Plugins/eSpeak/espeak-data/voices/sv new file mode 100644 index 0000000..df70f43 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/sv @@ -0,0 +1,4 @@ +name swedish +language sv +gender male + diff --git a/Plugins/eSpeak/espeak-data/voices/sw b/Plugins/eSpeak/espeak-data/voices/sw new file mode 100644 index 0000000..cf584b7 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/sw @@ -0,0 +1,4 @@ +name swahihi-test +language sw +gender male + diff --git a/Plugins/eSpeak/espeak-data/voices/ta b/Plugins/eSpeak/espeak-data/voices/ta new file mode 100644 index 0000000..8848d68 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/ta @@ -0,0 +1,6 @@ +name tamil +language ta +gender male + +intonation 2 +consonants 80 diff --git a/Plugins/eSpeak/espeak-data/voices/tr b/Plugins/eSpeak/espeak-data/voices/tr new file mode 100644 index 0000000..4f1904e --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/tr @@ -0,0 +1,4 @@ +name turkish +language tr +gender male + diff --git a/Plugins/eSpeak/espeak-data/voices/vi b/Plugins/eSpeak/espeak-data/voices/vi new file mode 100644 index 0000000..1596e3c --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/vi @@ -0,0 +1,6 @@ +name vietnam-test +language vi +gender male + +words 1 +pitch 80 118 diff --git a/Plugins/eSpeak/espeak-data/voices/zh b/Plugins/eSpeak/espeak-data/voices/zh new file mode 100644 index 0000000..03edde4 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/voices/zh @@ -0,0 +1,30 @@ +name Mandarin +language zh +gender male +words 1 +pitch 80 118 + +//for some dialects + +//[en]: replace ng with n +//[zh]: �޺�������ng���n +//replace 0 N n + +//[en]: replace rfx consonants +//[zh]: �޾�������r���l��z��er���e +//replace 0 ts.h tsh +//replace 0 ts. ts +//replace 0 s. s +//replace 0 i. i[ +//replace 0 z. l +//replace 0 z. z +//replace 0 @r @ + +//[en]: replace beginning n or l +//[zh]: ����nl��n���l��l���n +//replace 2 n l +//replace 2 l n + +//[en]: replace beginning w with v +//[zh]: w���v +//replace 0 w v
\ No newline at end of file diff --git a/Plugins/eSpeak/espeak-data/zh_dict b/Plugins/eSpeak/espeak-data/zh_dict Binary files differnew file mode 100644 index 0000000..900e0cd --- /dev/null +++ b/Plugins/eSpeak/espeak-data/zh_dict diff --git a/Plugins/eSpeak/espeak-data/zhy_dict b/Plugins/eSpeak/espeak-data/zhy_dict Binary files differnew file mode 100644 index 0000000..6626ba4 --- /dev/null +++ b/Plugins/eSpeak/espeak-data/zhy_dict diff --git a/Plugins/eSpeak/lib/PAStaticWMME.lib b/Plugins/eSpeak/lib/PAStaticWMME.lib Binary files differnew file mode 100644 index 0000000..6ddf559 --- /dev/null +++ b/Plugins/eSpeak/lib/PAStaticWMME.lib diff --git a/Plugins/eSpeak/m_speak.h b/Plugins/eSpeak/m_speak.h new file mode 100644 index 0000000..78b46c8 --- /dev/null +++ b/Plugins/eSpeak/m_speak.h @@ -0,0 +1,278 @@ +/*
+Copyright (C) 2007 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.
+*/
+
+
+#ifndef __M_SPEAK_H__
+# define __M_SPEAK_H__
+
+
+/*
+There is 2 ways of using the speak plugin:
+
+1. Older and simple way: just call
+ Speak_Say(hContact, _T("text to speak"))
+and the text will be spoken using contact settings. If hContact is NULL, it will use
+system settings.
+Previous versions only had an ascii version, so if you want to support then you need
+to call
+ Speak_SayA(hContact, "text to speak")
+
+
+2. Integrating with meSpeak GUI: for that you have first to register a speak type and
+then call the speak functions. In both case you have 2 options:
+
+2.1 Sending the full text: meSpeak GUI will only allow to enable/disable the type.
+To register call (in modules loaded):
+ Speak_Register("PluginName (DB key)", "name", "Prety name for GUI", "icon_xyz")
+And to speak call:
+ Speak_SayEx("name", hContact, _T("text to speak"))
+
+2.2 Using templates: you will not pass the text, but some variables. meSpeak handles
+the GUI to allow the user to create the text for those variables. These functions
+end with WT (with templates).
+To register call (in modules loaded):
+ char *templates[] = { "Name\nDefault\n%var1%\tDescription 1\n%var2%\tDescription2\n%var3%\tDescription 3" };
+ Speak_RegisterWT("PluginName (DB key)", "name", "Prety name for GUI", "icon_xyz",
+ templates, 1);
+And to speak call:
+ TCHAR *variables[] = { _T("var1"), _T("Value 1"), _T("var2"), _T("Value 2"), _T("var3"), _T("Value 3") };
+ Speak_SayExWT("name", hContact, 0, variables, 3);
+*/
+
+
+#define MIID_SPEAK { 0x1ef72725, 0x6a83, 0x483b, { 0xaa, 0x50, 0x89, 0x53, 0xe3, 0x59, 0xee, 0xad } }
+
+
+/*
+Speak a text
+
+wParam: (HANDLE) hContact
+lParam: (char *) text
+return: 0 on success
+*/
+#define MS_SPEAK_SAY_A "Speak/Say"
+
+
+/*
+Speak a unicode text
+
+wParam: (HANDLE) hContact
+lParam: (WCHAR *) text
+return: 0 on success
+*/
+#define MS_SPEAK_SAY_W "Speak/SayW"
+
+
+typedef struct {
+ int cbSize;
+
+ const char *module;
+ const char *name; // Internal type name
+ const char *description; // Will be translated
+ const char *icon; // Name off icolib icon
+
+ // Aditional data if wants to use add to history services
+ char **templates; // Each entry is: "Name\nDefault\n%var%\tDescription\n%var%\tDescription\n%var%\tDescription"
+ int numTemplates;
+
+} SPEAK_TYPE;
+
+
+/*
+Register and speak type
+
+wParam: (SPEAK_TYPE *) type
+lParam: 0
+return: 0 on success
+*/
+#define MS_SPEAK_REGISTER "Speak/Register"
+
+
+#define SPEAK_CHAR 1
+#define SPEAK_WCHAR 2
+
+typedef struct {
+ int cbSize;
+
+ const char *type; // Internal type name
+ HANDLE hContact;
+ int flags; // SPEAK_*
+
+ int templateNum; // -1 to use text
+ union
+ {
+ const void *text;
+
+ struct
+ {
+ void *variables;
+ int numVariables;
+ };
+ };
+
+} SPEAK_ITEM;
+
+
+/*
+Speak a text
+
+wParam: (SPEAK_ITEM *) Item
+lParam: 0
+return: 0 on success
+*/
+#define MS_SPEAK_SAYEX "Speak/SayEx"
+
+
+
+// Helper functions
+
+static int Speak_SayA(HANDLE hContact, const char *text)
+{
+ return CallService(MS_SPEAK_SAY_A, (WPARAM) hContact, (LPARAM) text);
+}
+
+static int Speak_SayW(HANDLE hContact, const WCHAR *text)
+{
+ return CallService(MS_SPEAK_SAY_W, (WPARAM) hContact, (LPARAM) text);
+}
+
+static int Speak_Register(char *module, char *name, char *description, char *icon)
+{
+ SPEAK_TYPE type;
+
+ if (!ServiceExists(MS_SPEAK_REGISTER))
+ return -1;
+
+ type.cbSize = sizeof(type);
+ type.module = module;
+ type.name = name;
+ type.description = description;
+ type.icon = icon;
+ type.templates = NULL;
+ type.numTemplates = 0;
+
+ return CallService(MS_SPEAK_REGISTER, (WPARAM) &type, 0);
+}
+
+static int Speak_RegisterWT(const char *module, const char *name, const char *description,
+ const char *icon, char **templates, int numTemplates)
+{
+ SPEAK_TYPE type;
+
+ if (!ServiceExists(MS_SPEAK_REGISTER))
+ return -1;
+
+ type.cbSize = sizeof(type);
+ type.module = module;
+ type.name = name;
+ type.description = description;
+ type.icon = icon;
+ type.templates = templates;
+ type.numTemplates = numTemplates;
+
+ return CallService(MS_SPEAK_REGISTER, (WPARAM) &type, 0);
+}
+
+static int Speak_SayExA(char *type, HANDLE hContact, const char *text)
+{
+ SPEAK_ITEM item;
+
+ if (!ServiceExists(MS_SPEAK_SAYEX))
+ // Try old service
+ return Speak_SayA(hContact, text);
+
+ item.cbSize = sizeof(item);
+ item.flags = SPEAK_CHAR;
+ item.type = type;
+ item.hContact = hContact;
+ item.templateNum = -1;
+ item.text = text;
+
+ return CallService(MS_SPEAK_SAYEX, (WPARAM) &item, 0);
+}
+
+static int Speak_SayExW(char *type, HANDLE hContact, const WCHAR *text)
+{
+ SPEAK_ITEM item;
+
+ if (!ServiceExists(MS_SPEAK_SAYEX))
+ // Try old service
+ return Speak_SayW(hContact, text);
+
+ item.cbSize = sizeof(item);
+ item.flags = SPEAK_WCHAR;
+ item.type = type;
+ item.hContact = hContact;
+ item.templateNum = -1;
+ item.text = text;
+
+ return CallService(MS_SPEAK_SAYEX, (WPARAM) &item, 0);
+}
+
+static int Speak_SayExWTA(char *type, HANDLE hContact, int templateNum, char **variables, int numVariables)
+{
+ SPEAK_ITEM item;
+
+ if (!ServiceExists(MS_SPEAK_SAYEX))
+ return -1;
+
+ item.cbSize = sizeof(item);
+ item.flags = SPEAK_CHAR;
+ item.type = type;
+ item.hContact = hContact;
+ item.templateNum = templateNum;
+ item.variables = variables;
+ item.numVariables = numVariables;
+
+ return CallService(MS_SPEAK_SAYEX, (WPARAM) &item, 0);
+}
+
+static int Speak_SayExWTW(char *type, HANDLE hContact, int templateNum, WCHAR **variables, int numVariables)
+{
+ SPEAK_ITEM item;
+
+ if (!ServiceExists(MS_SPEAK_SAYEX))
+ return -1;
+
+ item.cbSize = sizeof(item);
+ item.flags = SPEAK_WCHAR;
+ item.type = type;
+ item.hContact = hContact;
+ item.templateNum = templateNum;
+ item.variables = variables;
+ item.numVariables = numVariables;
+
+ return CallService(MS_SPEAK_SAYEX, (WPARAM) &item, 0);
+}
+
+
+#ifdef UNICODE
+# define MS_SPEAK_SAY MS_SPEAK_SAY_W
+# define Speak_Say Speak_SayW
+# define Speak_SayEx Speak_SayExW
+# define Speak_SayExWT Speak_SayExWTW
+#else
+# define MS_SPEAK_SAY MS_SPEAK_SAY_A
+# define Speak_Say Speak_SayA
+# define Speak_SayEx Speak_SayExA
+# define Speak_SayExWT Speak_SayExWTA
+#endif
+
+
+#endif // __M_SPEAK_H__
diff --git a/Plugins/eSpeak/options.cpp b/Plugins/eSpeak/options.cpp new file mode 100644 index 0000000..aaada97 --- /dev/null +++ b/Plugins/eSpeak/options.cpp @@ -0,0 +1,1036 @@ +/*
+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"
+
+
+// Prototypes /////////////////////////////////////////////////////////////////////////////////////
+
+HANDLE hOptHook = NULL;
+HANDLE hUserInfoInitHook = NULL;
+
+Options opts = {0};
+
+static int UserInfoInitialize(WPARAM wParam, LPARAM lParam);
+
+static BOOL CALLBACK UserInfoDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+static BOOL CALLBACK SystemDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+static BOOL CALLBACK OptionsDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+static BOOL CALLBACK TypesDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+
+static Language *GetLanguage(HWND hwndDlg);
+static Voice *GetVoice(HWND hwndDlg);
+static Variant *GetVariant(HWND hwndDlg);
+
+
+static OptPageControl optionsControls[] = {
+ { &opts.disable_offline, CONTROL_CHECKBOX, ID_OFFLINE, "DisableOffline", FALSE },
+ { &opts.disable_online, CONTROL_CHECKBOX, ID_ONLINE, "DisableOnline", FALSE },
+ { &opts.disable_away, CONTROL_CHECKBOX, ID_AWAY, "DisableAway", FALSE },
+ { &opts.disable_dnd, CONTROL_CHECKBOX, ID_DND, "DisableDND", FALSE },
+ { &opts.disable_na, CONTROL_CHECKBOX, ID_NA, "DisableNA", FALSE },
+ { &opts.disable_occupied, CONTROL_CHECKBOX, ID_OCCUPIED, "DisableOccupied", FALSE },
+ { &opts.disable_freechat, CONTROL_CHECKBOX, ID_FREECHAT, "DisableFreeChat", FALSE },
+ { &opts.disable_invisible, CONTROL_CHECKBOX, ID_INVISIBLE, "DisableInvisible", FALSE },
+ { &opts.disable_onthephone, CONTROL_CHECKBOX, ID_ONTHEPHONE, "DisableOnThePhone", FALSE },
+ { &opts.disable_outtolunch, CONTROL_CHECKBOX, ID_OUTTOLUNCH, "DisableOutToLunch", FALSE },
+ { &opts.enable_only_idle, CONTROL_CHECKBOX, IDC_ONLY_IDLE, "EnableOnlyIfIdle", FALSE },
+ { &opts.truncate, CONTROL_CHECKBOX, IDC_TRUNCATE_L, "Truncate", FALSE },
+ { &opts.truncate_len, CONTROL_SPIN, IDC_TRUNCATE, "TruncateLen", (WORD) 128, IDC_TRUNCATE_SPIN, (WORD) 1, (WORD) 1024 },
+ { &opts.use_flags, CONTROL_CHECKBOX, IDC_USE_FLAGS, "UseFlags", TRUE },
+ { &opts.respect_sndvol_mute, CONTROL_CHECKBOX, IDC_SNDVOL, "RespectSndVolMute", TRUE },
+ { &opts.select_variant_per_genre,CONTROL_CHECKBOX, IDC_PER_GENRE, "SelectVariantPerGenre", TRUE },
+};
+
+
+// Functions //////////////////////////////////////////////////////////////////////////////////////
+
+
+BOOL GetSettingBool(SPEAK_TYPE *type, char *setting, BOOL def)
+{
+ return GetSettingBool(type, -1, setting, def);
+}
+
+BOOL GetSettingBool(SPEAK_TYPE *type, int templ, char *setting, BOOL def)
+{
+ char tmp[128];
+ mir_snprintf(tmp, MAX_REGS(tmp), "%s_%d_%s", type->name, templ, setting);
+ return DBGetContactSettingByte(NULL, type->module == NULL ? MODULE_NAME : type->module, tmp, def) != 0;
+}
+
+void WriteSettingBool(SPEAK_TYPE *type, char *setting, BOOL val)
+{
+ WriteSettingBool(type, -1, setting, val);
+}
+
+void WriteSettingBool(SPEAK_TYPE *type, int templ, char *setting, BOOL val)
+{
+ char tmp[128];
+ mir_snprintf(tmp, MAX_REGS(tmp), "%s_%d_%s", type->name, templ, setting);
+ DBWriteContactSettingByte(NULL, type->module == NULL ? MODULE_NAME : type->module, tmp, val ? 1 : 0);
+}
+
+void WriteSettingTString(SPEAK_TYPE *type, int templ, char *setting, TCHAR *str)
+{
+ char tmp[128];
+ mir_snprintf(tmp, MAX_REGS(tmp), "%s_%d_%s", type->name, templ, setting);
+ DBWriteContactSettingTString(NULL, type->module == NULL ? MODULE_NAME : type->module, tmp, str);
+}
+
+
+int InitOptionsCallback(WPARAM wParam,LPARAM lParam)
+{
+ OPTIONSDIALOGPAGE odp;
+
+ ZeroMemory(&odp,sizeof(odp));
+ odp.cbSize=sizeof(odp);
+ odp.position=0;
+ odp.hInstance=hInst;
+ odp.ptszGroup = TranslateT("Events");
+ odp.ptszTitle = TranslateT("Speak");
+ odp.ptszTab = TranslateT("General");
+ odp.pfnDlgProc = SystemDlgProc;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS);
+ odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR;
+ CallService(MS_OPT_ADDPAGE,wParam,(LPARAM)&odp);
+
+ odp.ptszTab = TranslateT("Advanced");
+ odp.pfnDlgProc = OptionsDlgProc;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_ADVANCED);
+ odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR | ODPF_EXPERTONLY;
+ CallService(MS_OPT_ADDPAGE,wParam,(LPARAM)&odp);
+
+ if (types.getCount() > 0)
+ {
+ odp.ptszTab = TranslateT("Types");
+ odp.pfnDlgProc = TypesDlgProc;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_TYPES);
+ odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR | ODPF_EXPERTONLY;
+ CallService(MS_OPT_ADDPAGE,wParam,(LPARAM)&odp);
+ }
+
+ return 0;
+}
+
+
+void InitOptions()
+{
+ LoadOptions();
+
+ hOptHook = HookEvent(ME_OPT_INITIALISE, InitOptionsCallback);
+ hUserInfoInitHook = HookEvent(ME_USERINFO_INITIALISE, UserInfoInitialize);
+}
+
+
+void DeInitOptions()
+{
+ UnhookEvent(hOptHook);
+ UnhookEvent(hUserInfoInitHook);
+}
+
+
+void LoadOptions()
+{
+ LoadOpts(optionsControls, MAX_REGS(optionsControls), MODULE_NAME);
+
+ if (languages.getCount() <= 0)
+ return;
+
+ opts.default_language = GetContactLanguage(NULL);
+ opts.default_voice = GetContactVoice(NULL, opts.default_language);
+ opts.default_variant = GetContactVariant(NULL);
+}
+
+
+void FillLanguagesCombo(HWND hwndDlg, HANDLE hContact)
+{
+ Language *def_lang;
+ if (hContact == NULL)
+ def_lang = opts.default_language;
+ else
+ def_lang = GetContactLanguage(hContact);
+
+ for (int i = 0; i < languages.getCount(); i++)
+ {
+ int pos = SendDlgItemMessage(hwndDlg, IDC_DEF_LANG, CB_ADDSTRING, 0, (LPARAM) languages[i]->full_name);
+ if (pos >= 0)
+ SendDlgItemMessage(hwndDlg, IDC_DEF_LANG, CB_SETITEMDATA, pos, (LPARAM) languages[i]);
+ }
+
+ if (def_lang != NULL && SendDlgItemMessage(hwndDlg, IDC_DEF_LANG, CB_SELECTSTRING, -1, (WPARAM) def_lang->full_name) < 0)
+ {
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ SendDlgItemMessage(hwndDlg, IDC_DEF_LANG, CB_SETCURSEL, 0, 0);
+ }
+}
+
+
+void FillVoicesCombo(HWND hwndDlg, HANDLE hContact)
+{
+ Language *lang = GetLanguage(hwndDlg);
+
+ Voice *def_voice;
+ if (hContact == NULL)
+ def_voice = opts.default_voice;
+ else
+ def_voice = GetContactVoice(hContact, lang);
+
+ SendDlgItemMessage(hwndDlg, IDC_VOICE, CB_RESETCONTENT, 0, 0);
+
+ int sel = -1;
+ for (int i = 0; i < lang->voices.getCount(); i++)
+ {
+ TCHAR name[NAME_SIZE];
+ Voice *voice = lang->voices[i];
+
+ TCHAR *gender = NULL;
+ if (voice->gender == GENDER_MALE)
+ gender = TranslateT("Male");
+ else if (voice->gender == GENDER_FEMALE)
+ gender = TranslateT("Female");
+
+ TCHAR *age = NULL;
+ if (voice->age[0] != 0)
+ age = voice->age;
+
+ if (gender != NULL && age != NULL)
+ mir_sntprintf(name, MAX_REGS(name), _T("%s (%s, %s)"), voice->name, gender, age);
+
+ else if (gender != NULL)
+ mir_sntprintf(name, MAX_REGS(name), _T("%s (%s)"), voice->name, gender);
+
+ else if (age != NULL)
+ mir_sntprintf(name, MAX_REGS(name), _T("%s (%s)"), voice->name, age);
+
+ else
+ mir_sntprintf(name, MAX_REGS(name), _T("%s"), voice->name);
+
+ int pos = SendDlgItemMessage(hwndDlg, IDC_VOICE, CB_ADDSTRING, 0, (LPARAM) name);
+ if (pos >= 0)
+ SendDlgItemMessage(hwndDlg, IDC_VOICE, CB_SETITEMDATA, pos, (LPARAM) voice);
+
+ if (def_voice == voice)
+ sel = pos;
+ }
+ if (sel < 0)
+ {
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ sel = 0;
+ }
+
+ SendDlgItemMessage(hwndDlg, IDC_VOICE, CB_SETCURSEL, sel, 0);
+}
+
+
+void FillVariantsCombo(HWND hwndDlg, HANDLE hContact)
+{
+ SendDlgItemMessage(hwndDlg, IDC_VARIANT, CB_RESETCONTENT, 0, 0);
+
+ int pos = SendDlgItemMessage(hwndDlg, IDC_VARIANT, CB_ADDSTRING, 0, (LPARAM) _T("<None>"));
+ if (pos >= 0)
+ SendDlgItemMessage(hwndDlg, IDC_VARIANT, CB_SETITEMDATA, pos, (LPARAM) NULL);
+
+ if (GetVoice(hwndDlg)->engine == ENGINE_SAPI)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_VARIANT, CB_SETCURSEL, 0, 0);
+ }
+ else
+ {
+ Variant *def_var;
+ if (hContact == NULL)
+ def_var = opts.default_variant;
+ else
+ def_var = GetContactVariant(hContact);
+
+ for (int i = 0; i < variants.getCount(); i++)
+ {
+ int pos = SendDlgItemMessage(hwndDlg, IDC_VARIANT, CB_ADDSTRING, 0, (LPARAM) variants[i]->name);
+ if (pos >= 0)
+ SendDlgItemMessage(hwndDlg, IDC_VARIANT, CB_SETITEMDATA, pos, (LPARAM) variants[i]);
+ }
+
+ if (def_var == NULL)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_VARIANT, CB_SETCURSEL, 0, 0);
+ }
+ else if (SendDlgItemMessage(hwndDlg, IDC_VARIANT, CB_SELECTSTRING, -1, (LPARAM) def_var->name) < 0)
+ {
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ SendDlgItemMessage(hwndDlg, IDC_VARIANT, CB_SETCURSEL, 0, 0);
+ }
+ }
+}
+
+
+static Language *GetLanguage(HWND hwndDlg)
+{
+ int sel = SendDlgItemMessage(hwndDlg, IDC_DEF_LANG, CB_GETCURSEL, 0, 0);
+ if (sel < 0)
+ sel = 0;
+
+ return (Language *) SendDlgItemMessage(hwndDlg, IDC_DEF_LANG, CB_GETITEMDATA, sel, 0);
+}
+
+
+static Voice *GetVoice(HWND hwndDlg)
+{
+ int sel = SendDlgItemMessage(hwndDlg, IDC_VOICE, CB_GETCURSEL, 0, 0);
+ if (sel < 0)
+ sel = 0;
+
+ return (Voice *) SendDlgItemMessage(hwndDlg, IDC_VOICE, CB_GETITEMDATA, sel, 0);
+}
+
+
+static Variant *GetVariant(HWND hwndDlg)
+{
+ int sel = SendDlgItemMessage(hwndDlg, IDC_VARIANT, CB_GETCURSEL, 0, 0);
+ if (sel < 0)
+ sel = 0;
+
+ return (Variant *) SendDlgItemMessage(hwndDlg, IDC_VARIANT, CB_GETITEMDATA, sel, 0);
+}
+
+
+static BOOL CALLBACK BaseDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ HANDLE hContact = (HANDLE) GetWindowLong(hwndDlg, GWL_USERDATA);
+ if (languages.getCount() > 0)
+ {
+ FillLanguagesCombo(hwndDlg, hContact);
+ FillVoicesCombo(hwndDlg, hContact);
+ FillVariantsCombo(hwndDlg, hContact);
+ }
+
+ HWND item = GetDlgItem(hwndDlg, IDC_PUNCT);
+ if (item != NULL)
+ {
+ SendMessage(item, CB_ADDSTRING, 0, (LPARAM) TranslateT("None"));
+ SendMessage(item, CB_ADDSTRING, 0, (LPARAM) TranslateT("All"));
+ SendMessage(item, CB_ADDSTRING, 0, (LPARAM) TranslateT("Some"));
+ }
+
+ Voice *voice = GetVoice(hwndDlg);
+
+ for (int i = 0; i < NUM_PARAMETERS; i++)
+ {
+ item = GetDlgItem(hwndDlg, PARAMETERS[i].ctrl);
+ if (item == NULL)
+ continue;
+
+ RANGE *range;
+ if (voice->engine == ENGINE_ESPEAK)
+ range = &PARAMETERS[i].espeak;
+ else if (voice->engine == ENGINE_SAPI)
+ range = &PARAMETERS[i].sapi;
+ else
+ continue;
+
+ BOOL enabled = (range->min < range->max);
+ EnableWindow(item, enabled);
+ EnableWindow(GetDlgItem(hwndDlg, PARAMETERS[i].label), enabled);
+
+ if (enabled)
+ {
+ if (PARAMETERS[i].type == SCROLL)
+ {
+ SendMessage(item, TBM_SETRANGE, FALSE, MAKELONG(0, range->max - range->min));
+ SendMessage(item, TBM_SETPOS, TRUE, GetContactParam(hContact, i) - range->min);
+ }
+ else
+ {
+ SendMessage(item, CB_SETCURSEL, GetContactParam(hContact, i), 0);
+ }
+ }
+ }
+
+ break;
+ }
+
+ case WM_HSCROLL:
+ {
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ }
+
+ case WM_COMMAND:
+ {
+ if (HIWORD(wParam) == CBN_SELCHANGE)
+ {
+ if (languages.getCount() <= 0)
+ break;
+
+ if ((HWND)lParam == GetFocus())
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+
+ HANDLE hContact = (HANDLE) GetWindowLong(hwndDlg, GWL_USERDATA);
+
+ if (LOWORD(wParam) == IDC_DEF_LANG)
+ {
+ FillVoicesCombo(hwndDlg, hContact);
+ FillVariantsCombo(hwndDlg, hContact);
+ }
+ else if (LOWORD(wParam) == IDC_VOICE)
+ {
+ FillVariantsCombo(hwndDlg, hContact);
+ }
+
+ if (LOWORD(wParam) == IDC_DEF_LANG || LOWORD(wParam) == IDC_VOICE)
+ {
+ Voice *voice = GetVoice(hwndDlg);
+ for (int i = 0; i < NUM_PARAMETERS; i++)
+ {
+ HWND item = GetDlgItem(hwndDlg, PARAMETERS[i].ctrl);
+ if (item == NULL)
+ continue;
+
+ RANGE *range;
+ if (voice->engine == ENGINE_ESPEAK)
+ range = &PARAMETERS[i].espeak;
+ else if (voice->engine == ENGINE_SAPI)
+ range = &PARAMETERS[i].sapi;
+ else
+ continue;
+
+ BOOL enabled = (range->min < range->max);
+ EnableWindow(item, enabled);
+ EnableWindow(GetDlgItem(hwndDlg, PARAMETERS[i].label), enabled);
+
+ if (enabled)
+ {
+ if (PARAMETERS[i].type == SCROLL)
+ {
+ SendMessage(item, TBM_SETRANGE, FALSE, MAKELONG(0, range->max - range->min));
+
+ int def;
+ if (voice->engine == ENGINE_SAPI && PARAMETERS[i].eparam == espeakRATE)
+ def = SAPI_GetDefaultRateFor(voice->id);
+ else
+ def = range->def;
+
+ SendMessage(item, TBM_SETPOS, TRUE, def - range->min);
+ }
+ else
+ {
+ SendMessage(item, CB_SETCURSEL, range->def, 0);
+ }
+ }
+ }
+ }
+ }
+ else if (LOWORD(wParam) == IDC_SPEAK)
+ {
+ if (languages.getCount() <= 0)
+ break;
+
+ TCHAR text[1024];
+ GetDlgItemText(hwndDlg, IDC_TEST, text, MAX_REGS(text));
+ if (text[0] == _T('\0'))
+ break;
+
+ Language *lang = GetLanguage(hwndDlg);
+ if (lang == NULL)
+ break;
+
+ Voice *voice = GetVoice(hwndDlg);
+ if (voice == NULL)
+ break;
+
+ SpeakData *data = new SpeakData(lang, voice, GetVariant(hwndDlg), mir_tstrdup(text));
+ for (int i = 0; i < NUM_PARAMETERS; i++)
+ {
+ HWND item = GetDlgItem(hwndDlg, PARAMETERS[i].ctrl);
+ if (item == NULL)
+ {
+ data->setParameter(i, GetContactParam(NULL, i));
+ break;
+ }
+
+ RANGE *range;
+ if (voice->engine == ENGINE_ESPEAK)
+ range = &PARAMETERS[i].espeak;
+ else if (voice->engine == ENGINE_SAPI)
+ range = &PARAMETERS[i].sapi;
+ else
+ {
+ data->setParameter(i, GetContactParam(NULL, i));
+ break;
+ }
+
+ if (PARAMETERS[i].type == SCROLL)
+ data->setParameter(i, SendMessage(item, TBM_GETPOS, 0, 0) + range->min);
+ else
+ data->setParameter(i, SendMessage(item, CB_GETCURSEL, 0, 0));
+ }
+ queue->Add(0, (HANDLE) -1, data);
+ }
+
+ break;
+ }
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR lpnmhdr = (LPNMHDR)lParam;
+
+ if (lpnmhdr->idFrom == 0 && lpnmhdr->code == PSN_APPLY)
+ {
+ if (languages.getCount() <= 0)
+ break;
+
+ HANDLE hContact = (HANDLE) GetWindowLong(hwndDlg, GWL_USERDATA);
+
+ // Language
+ Language *lang = GetLanguage(hwndDlg);
+
+ BOOL remove;
+ if (hContact == NULL)
+ {
+ TCHAR def[NAME_SIZE];
+ GetLangPackLanguage(def, MAX_REGS(def));
+
+ remove = (lstrcmpi(def, lang->language) == 0);
+ }
+ else
+ remove = FALSE;
+
+ if (remove)
+ DBDeleteContactSetting(hContact, MODULE_NAME, "TalkLanguage");
+ else
+ DBWriteContactSettingTString(hContact, MODULE_NAME, "TalkLanguage", lang->language);
+
+ if (hContact == NULL)
+ opts.default_language = lang;
+
+ // Voice
+ Voice *voice = GetVoice(hwndDlg);
+ if (voice == NULL)
+ voice = lang->voices[0];
+
+ DBWriteContactSettingTString(hContact, MODULE_NAME, "Voice", voice->name);
+
+ if (hContact == NULL)
+ opts.default_voice = voice;
+
+ // Variant
+ Variant *var = GetVariant(hwndDlg);
+ DBWriteContactSettingTString(hContact, MODULE_NAME, "Variant", var->name);
+
+ if (hContact == NULL)
+ opts.default_variant = var;
+
+ for (int i = 0; i < NUM_PARAMETERS; i++)
+ {
+ HWND item = GetDlgItem(hwndDlg, PARAMETERS[i].ctrl);
+ if (item == NULL)
+ continue;
+
+ RANGE *range;
+ if (voice->engine == ENGINE_ESPEAK)
+ range = &PARAMETERS[i].espeak;
+ else if (voice->engine == ENGINE_SAPI)
+ range = &PARAMETERS[i].sapi;
+ else
+ continue;
+
+ if (PARAMETERS[i].type == SCROLL)
+ SetContactParam(hContact, i, SendMessage(item, TBM_GETPOS, 0, 0) + range->min);
+ else
+ SetContactParam(hContact, i, SendMessage(item, CB_GETCURSEL, 0, 0));
+ }
+ }
+
+ break;
+ }
+
+ case WM_DRAWITEM:
+ {
+ LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;
+ if(lpdis->CtlID != IDC_DEF_LANG)
+ break;
+ if(lpdis->itemID == -1)
+ break;
+
+ Language *lang = (Language *) lpdis->itemData;
+
+ TEXTMETRIC tm;
+ RECT rc;
+
+ GetTextMetrics(lpdis->hDC, &tm);
+
+ COLORREF clrfore = SetTextColor(lpdis->hDC,GetSysColor(lpdis->itemState & ODS_DISABLED ? COLOR_GRAYTEXT : (lpdis->itemState & ODS_SELECTED ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT)));
+ COLORREF clrback = SetBkColor(lpdis->hDC,GetSysColor(lpdis->itemState & ODS_DISABLED ? COLOR_BTNFACE : (lpdis->itemState & ODS_SELECTED ? COLOR_HIGHLIGHT : COLOR_WINDOW)));
+
+ FillRect(lpdis->hDC, &lpdis->rcItem, GetSysColorBrush(lpdis->itemState & ODS_DISABLED ? COLOR_BTNFACE : (lpdis->itemState & ODS_SELECTED ? COLOR_HIGHLIGHT : COLOR_WINDOW)));
+
+ rc.left = lpdis->rcItem.left + 2;
+
+ // Draw icon
+ if (opts.use_flags)
+ {
+ HICON hFlag = LoadIconEx(lang);
+
+ rc.top = (lpdis->rcItem.bottom + lpdis->rcItem.top - ICON_SIZE) / 2;
+ DrawIconEx(lpdis->hDC, rc.left, rc.top, hFlag, 16, 16, 0, NULL, DI_NORMAL);
+
+ rc.left += ICON_SIZE + 4;
+
+ ReleaseIconEx(hFlag);
+ }
+
+ // Draw text
+ rc.right = lpdis->rcItem.right - 2;
+ rc.top = (lpdis->rcItem.bottom + lpdis->rcItem.top - tm.tmHeight) / 2;
+ rc.bottom = rc.top + tm.tmHeight;
+ DrawText(lpdis->hDC, lang->full_name, lstrlen(lang->full_name), &rc, DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE);
+
+ // Restore old colors
+ SetTextColor(lpdis->hDC, clrfore);
+ SetBkColor(lpdis->hDC, clrback);
+
+ return TRUE;
+ }
+
+ case WM_MEASUREITEM:
+ {
+ LPMEASUREITEMSTRUCT lpmis = (LPMEASUREITEMSTRUCT)lParam;
+ if(lpmis->CtlID != IDC_DEF_LANG)
+ break;
+
+ TEXTMETRIC tm;
+ GetTextMetrics(GetDC(hwndDlg), &tm);
+
+ if (opts.use_flags)
+ lpmis->itemHeight = max(ICON_SIZE, tm.tmHeight);
+ else
+ lpmis->itemHeight = tm.tmHeight;
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+static BOOL CALLBACK OptionsDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ return SaveOptsDlgProc(optionsControls, MAX_REGS(optionsControls), MODULE_NAME, hwndDlg, msg, wParam, lParam);
+}
+
+
+static BOOL CALLBACK SystemDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ TranslateDialogDefault(hwndDlg);
+ SetWindowLong(hwndDlg, GWL_USERDATA, (LONG) NULL);
+ break;
+ }
+ }
+
+ return BaseDlgProc(hwndDlg, msg, wParam, lParam);
+}
+
+
+
+static int UserInfoInitialize(WPARAM wParam, LPARAM lParam)
+{
+ HANDLE hContact = (HANDLE) lParam;
+ if (hContact == NULL)
+ return 0;
+
+ if (languages.getCount() < 0)
+ return 0;
+
+ // Contact dialog
+ OPTIONSDIALOGPAGE odp = {0};
+ odp.cbSize = sizeof(odp);
+ odp.hInstance = hInst;
+ odp.pfnDlgProc = UserInfoDlgProc;
+ odp.position = 1000000000;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_CONTACT_LANG);
+ odp.pszTitle = LPGEN("Speak");
+ CallService(MS_USERINFO_ADDPAGE, wParam, (LPARAM)&odp);
+
+ return 0;
+}
+
+
+static void EnableDisableControls(HWND hwndDlg)
+{
+ BOOL enabled = IsDlgButtonChecked(hwndDlg, IDC_ENABLE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_DEF_LANG_L), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_DEF_LANG), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VOICE_L), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VOICE), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VARIANT_L), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_VARIANT), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_TEST_L), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_TEST), enabled);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_SPEAK), enabled);
+}
+
+
+static BOOL CALLBACK UserInfoDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ HANDLE hContact = (HANDLE) lParam;
+ SetWindowLong(hwndDlg, GWL_USERDATA, (LONG) hContact);
+
+ TranslateDialogDefault(hwndDlg);
+
+ CheckDlgButton(hwndDlg, IDC_ENABLE, DBGetContactSettingByte(hContact, MODULE_NAME, "Enabled", TRUE) ? BST_CHECKED : BST_UNCHECKED);
+
+ EnableDisableControls(hwndDlg);
+
+ break;
+ }
+
+ case WM_COMMAND:
+ {
+ if (LOWORD(wParam) == IDC_ENABLE)
+ {
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ EnableDisableControls(hwndDlg);
+ }
+
+ break;
+ }
+ case WM_NOTIFY:
+ {
+ LPNMHDR lpnmhdr = (LPNMHDR)lParam;
+
+ if (lpnmhdr->idFrom == 0 && lpnmhdr->code == PSN_APPLY)
+ {
+ HANDLE hContact = (HANDLE) GetWindowLong(hwndDlg, GWL_USERDATA);
+ DBWriteContactSettingByte(hContact, MODULE_NAME, "Enabled", (BYTE) IsDlgButtonChecked(hwndDlg, IDC_ENABLE));
+ }
+
+ break;
+ }
+ }
+
+ return BaseDlgProc(hwndDlg, msg, wParam, lParam);
+}
+
+
+static BOOL ScreenToClient(HWND hWnd, LPRECT lpRect)
+{
+ BOOL ret;
+
+ POINT pt;
+
+ pt.x = lpRect->left;
+ pt.y = lpRect->top;
+
+ ret = ScreenToClient(hWnd, &pt);
+
+ if (!ret) return ret;
+
+ lpRect->left = pt.x;
+ lpRect->top = pt.y;
+
+
+ pt.x = lpRect->right;
+ pt.y = lpRect->bottom;
+
+ ret = ScreenToClient(hWnd, &pt);
+
+ lpRect->right = pt.x;
+ lpRect->bottom = pt.y;
+
+ return ret;
+}
+
+
+static void GetTextMetric(HFONT hFont, TEXTMETRIC *tm)
+{
+ HDC hdc = GetDC(NULL);
+ HFONT hOldFont = (HFONT) SelectObject(hdc, hFont);
+ GetTextMetrics(hdc, tm);
+ SelectObject(hdc, hOldFont);
+ ReleaseDC(NULL, hdc);
+}
+
+
+static BOOL CALLBACK TypesDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ static int avaiable = 0;
+ static int total = 0;
+ static int current = 0;
+ static int lineHeight = 0;
+
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ TranslateDialogDefault(hwndDlg);
+
+ RECT rc;
+ GetWindowRect(GetDlgItem(hwndDlg, IDC_EVENT_TYPES), &rc);
+
+ POINT pt = { rc.left, rc.bottom + 5 };
+ ScreenToClient(hwndDlg, &pt);
+ int origY = pt.y;
+
+ GetClientRect(hwndDlg, &rc);
+
+ HFONT hFont = (HFONT) SendMessage(hwndDlg, WM_GETFONT, 0, 0);
+ TEXTMETRIC font;
+ GetTextMetric(hFont, &font);
+
+ int height = max(font.tmHeight, 16) + 4;
+ int width = rc.right - rc.left - 35;
+
+ lineHeight = height;
+
+ // Create all items
+ int id = IDC_EVENT_TYPES + 1;
+ for(int i = 0; i < types.getCount(); i++)
+ {
+ SPEAK_TYPE *type = types[i];
+
+ int x = pt.x;
+
+ // Event type
+
+ HWND icon = CreateWindow(_T("STATIC"), _T(""), WS_CHILD | WS_VISIBLE | SS_ICON | SS_CENTERIMAGE,
+ x, pt.y + (height - 16) / 2, 16, 16, hwndDlg, NULL, hInst, NULL);
+ x += 20;
+
+ SendMessage(icon, STM_SETICON, (WPARAM) LoadIconEx(type->icon, TRUE), 0);
+
+ HWND tmp = CreateWindowA("STATIC", type->description, WS_CHILD | WS_VISIBLE,
+ x, pt.y + (height - font.tmHeight) / 2, width - (x - pt.x), font.tmHeight,
+ hwndDlg, NULL, hInst, NULL);
+ SendMessage(tmp, WM_SETFONT, (WPARAM) hFont, FALSE);
+
+ if (type->numTemplates <= 0)
+ {
+ // No templates
+
+ pt.y += height + 3;
+ x = pt.x + 20;
+
+ HWND chk = CreateWindow(_T("BUTTON"), TranslateT("Enable"),
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_CHECKBOX | BS_AUTOCHECKBOX,
+ x, pt.y, width - (x - pt.x), height, hwndDlg, (HMENU) id, hInst, NULL);
+ SendMessage(chk, BM_SETCHECK, GetSettingBool(type, TEMPLATE_ENABLED, FALSE) ? BST_CHECKED : BST_UNCHECKED, 0);
+ SendMessage(chk, WM_SETFONT, (WPARAM) hFont, FALSE);
+
+ pt.y += height + 3;
+ x = pt.x + 20;
+
+ chk = CreateWindow(_T("BUTTON"), TranslateT("Speak contact name before text"),
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_CHECKBOX | BS_AUTOCHECKBOX,
+ x, pt.y, width - (x - pt.x), height, hwndDlg, (HMENU) id + 2, hInst, NULL);
+ SendMessage(chk, BM_SETCHECK, GetSettingBool(type, SPEAK_NAME, TRUE) ? BST_CHECKED : BST_UNCHECKED, 0);
+ SendMessage(chk, WM_SETFONT, (WPARAM) hFont, FALSE);
+ }
+ else
+ {
+ // Templates
+
+ Buffer<char> name;
+ Buffer<TCHAR> templ;
+ for (int i = 0; i < type->numTemplates; i++)
+ {
+ pt.y += height + 3;
+ x = pt.x + 20;
+
+ name.clear();
+ const char *end = strchr(type->templates[i], '\n');
+ size_t len = (end == NULL ? strlen(type->templates[i]) : end - type->templates[i]);
+ name.append(type->templates[i], len);
+ name.translate();
+ name.append(':');
+ name.pack();
+
+ HWND chk = CreateWindowA("BUTTON", name.str,
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_CHECKBOX | BS_AUTOCHECKBOX,
+ x, pt.y, 120, height, hwndDlg, (HMENU) (id + 2 * i), hInst, NULL);
+ SendMessage(chk, WM_SETFONT, (WPARAM) hFont, FALSE);
+ SendMessage(chk, BM_SETCHECK, GetSettingBool(type, i, TEMPLATE_ENABLED, FALSE) ? BST_CHECKED : BST_UNCHECKED, 0);
+ x += 120;
+
+ templ.clear();
+ GetTemplate(&templ, type, i);
+ templ.pack();
+
+ HWND edit = CreateWindowEx(WS_EX_CLIENTEDGE, _T("EDIT"), templ.str,
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_LEFT | ES_AUTOHSCROLL,
+ x, pt.y, width - (x - pt.x), height, hwndDlg, (HMENU) (id + 2 * i + 1), hInst, NULL);
+ SendMessage(edit, WM_SETFONT, (WPARAM) hFont, FALSE);
+ }
+ }
+
+ pt.y += height + 10;
+ id += 60;
+ }
+
+ avaiable = rc.bottom - rc.top;
+ total = pt.y - 7;
+ current = 0;
+
+ SCROLLINFO si;
+ si.cbSize = sizeof(si);
+ si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
+ si.nMin = 0;
+ si.nMax = total;
+ si.nPage = avaiable;
+ si.nPos = current;
+ SetScrollInfo(hwndDlg, SB_VERT, &si, TRUE);
+
+ break;
+ }
+
+ case WM_VSCROLL:
+ {
+ if (lParam != 0)
+ break;
+
+ int yDelta; // yDelta = new_pos - current_pos
+ int yNewPos; // new position
+
+ switch (LOWORD(wParam))
+ {
+ case SB_PAGEUP:
+ yNewPos = current - avaiable / 2;
+ break;
+ case SB_PAGEDOWN:
+ yNewPos = current + avaiable / 2;
+ break;
+ case SB_LINEUP:
+ yNewPos = current - lineHeight;
+ break;
+ case SB_LINEDOWN:
+ yNewPos = current + lineHeight;
+ break;
+ case SB_THUMBPOSITION:
+ yNewPos = HIWORD(wParam);
+ break;
+ case SB_THUMBTRACK:
+ yNewPos = HIWORD(wParam);
+ break;
+ default:
+ yNewPos = current;
+ }
+
+ yNewPos = min(total - avaiable, max(0, yNewPos));
+
+ if (yNewPos == current)
+ break;
+
+ yDelta = yNewPos - current;
+ current = yNewPos;
+
+ // Scroll the window. (The system repaints most of the
+ // client area when ScrollWindowEx is called; however, it is
+ // necessary to call UpdateWindow in order to repaint the
+ // rectangle of pixels that were invalidated.)
+
+ ScrollWindowEx(hwndDlg, 0, -yDelta, (CONST RECT *) NULL,
+ (CONST RECT *) NULL, (HRGN) NULL, (LPRECT) NULL,
+ /* SW_ERASE | SW_INVALIDATE | */ SW_SCROLLCHILDREN);
+// UpdateWindow(hwndDlg);
+ InvalidateRect(hwndDlg, NULL, TRUE);
+
+ // Reset the scroll bar.
+
+ SCROLLINFO si;
+ si.cbSize = sizeof(si);
+ si.fMask = SIF_POS;
+ si.nPos = current;
+ SetScrollInfo(hwndDlg, SB_VERT, &si, TRUE);
+
+ break;
+ }
+
+ case WM_COMMAND:
+ {
+ if ((HWND) lParam != GetFocus())
+ break;
+
+ int id = (LOWORD(wParam) - IDC_EVENT_TYPES - 1) % 2;
+ if (id == 0)
+ {
+ // Checkboxes
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+ else
+ {
+ if (HIWORD(wParam) == EN_CHANGE)
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+ break;
+ }
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR lpnmhdr = (LPNMHDR)lParam;
+ if (lpnmhdr->idFrom != 0 || lpnmhdr->code != PSN_APPLY)
+ break;
+
+ int id = IDC_EVENT_TYPES + 1;
+ for(int i = 0; i < types.getCount(); i++)
+ {
+ SPEAK_TYPE *type = types[i];
+
+ if (type->numTemplates <= 0)
+ {
+ // No templates
+
+ WriteSettingBool(type, TEMPLATE_ENABLED, IsDlgButtonChecked(hwndDlg, id));
+ WriteSettingBool(type, SPEAK_NAME, IsDlgButtonChecked(hwndDlg, id + 2));
+ }
+ else
+ {
+ // Templates
+
+ for(int i = 0; i < type->numTemplates; i++)
+ {
+ WriteSettingBool(type, i, TEMPLATE_ENABLED, IsDlgButtonChecked(hwndDlg, id + 2 * i));
+
+ TCHAR tmp[1024];
+ GetDlgItemText(hwndDlg, id + 2 * i + 1, tmp, 1024);
+ WriteSettingTString(type, i, TEMPLATE_TEXT, tmp);
+ }
+ }
+
+ id += 60;
+ }
+
+ return TRUE;
+ }
+
+ }
+
+ return 0;
+}
+
diff --git a/Plugins/eSpeak/options.h b/Plugins/eSpeak/options.h new file mode 100644 index 0000000..c48962d --- /dev/null +++ b/Plugins/eSpeak/options.h @@ -0,0 +1,78 @@ +/*
+Copyright (C) 2007 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.
+*/
+
+
+#ifndef __OPTIONS_H__
+# define __OPTIONS_H__
+
+
+#include <windows.h>
+
+class Language;
+class Voice;
+class Variant;
+
+struct Options {
+ Language *default_language;
+ Voice *default_voice;
+ Variant *default_variant;
+
+ BOOL disable_offline;
+ BOOL disable_online;
+ BOOL disable_away;
+ BOOL disable_dnd;
+ BOOL disable_na;
+ BOOL disable_occupied;
+ BOOL disable_freechat;
+ BOOL disable_invisible;
+ BOOL disable_onthephone;
+ BOOL disable_outtolunch;
+ BOOL enable_only_idle;
+ BOOL truncate;
+ WORD truncate_len;
+
+ BOOL use_flags;
+ BOOL respect_sndvol_mute;
+ BOOL select_variant_per_genre;
+};
+
+extern Options opts;
+
+
+// Initializations needed by options
+void InitOptions();
+
+// Deinitializations needed by options
+void DeInitOptions();
+
+
+// Loads the options from DB
+// It don't need to be called, except in some rare cases
+void LoadOptions();
+
+
+BOOL GetSettingBool(SPEAK_TYPE *type, char *setting, BOOL def);
+BOOL GetSettingBool(SPEAK_TYPE *type, int templ, char *setting, BOOL def);
+void WriteSettingBool(SPEAK_TYPE *type, char *setting, BOOL val);
+void WriteSettingBool(SPEAK_TYPE *type, int templ, char *setting, BOOL val);
+void WriteSettingTString(SPEAK_TYPE *type, int templ, char *setting, TCHAR *str);
+
+
+
+#endif // __OPTIONS_H__
diff --git a/Plugins/eSpeak/res/unknown.ico b/Plugins/eSpeak/res/unknown.ico Binary files differnew file mode 100644 index 0000000..6c21964 --- /dev/null +++ b/Plugins/eSpeak/res/unknown.ico diff --git a/Plugins/eSpeak/resource.h b/Plugins/eSpeak/resource.h new file mode 100644 index 0000000..9d7cd5a --- /dev/null +++ b/Plugins/eSpeak/resource.h @@ -0,0 +1,64 @@ +//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by resource.rc
+//
+#define IDI_UNKNOWN_FLAG 101
+#define IDD_OPTIONS 119
+#define IDD_CONTACT_LANG 120
+#define IDD_TYPES 121
+#define IDD_ADVANCED 122
+#define IDC_VOLUME 1000
+#define IDC_RATE 1001
+#define IDC_TRUNCATE_L 1001
+#define IDC_PITCH 1002
+#define IDC_TRUNCATE 1002
+#define IDC_RANGE 1003
+#define IDC_SPIN1 1003
+#define IDC_TRUNCATE_SPIN 1003
+#define ID_OFFLINE 1060
+#define IDC_ENABLE 1060
+#define ID_ONLINE 1061
+#define ID_AWAY 1062
+#define IDC_USE_FLAGS 1063
+#define ID_DND 1064
+#define ID_NA 1065
+#define ID_OCCUPIED 1066
+#define ID_FREECHAT 1067
+#define IDC_SOUNDVOL 1068
+#define IDC_SNDVOL 1068
+#define ID_INVISIBLE 1069
+#define ID_ONTHEPHONE 1070
+#define ID_OUTTOLUNCH 1071
+#define IDC_ONLY_IDLE 1072
+#define IDC_PER_GENRE 1073
+#define IDC_DEF_LANG 1075
+#define IDC_VOICE 1076
+#define IDC_ADVANCED 1077
+#define IDC_STATUS 1078
+#define IDC_SYSTEM 1079
+#define IDC_VARIANT 1080
+#define IDC_PUNCT 1081
+#define IDC_TEST 1084
+#define IDC_SPEAK 1085
+#define IDC_DEF_LANG_L 1086
+#define IDC_VARIANT_L 1088
+#define IDC_TEST_L 1089
+#define IDC_VARIANT_L2 1090
+#define IDC_VOLUME_L 1090
+#define IDC_RATE_L 1091
+#define IDC_PITCH_L 1092
+#define IDC_RANGE_L 1093
+#define IDC_PUNCT_L 1094
+#define IDC_EVENT_TYPES 1102
+#define IDC_VOICE_L 65535
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 103
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1004
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Plugins/eSpeak/resource.rc b/Plugins/eSpeak/resource.rc new file mode 100644 index 0000000..987fae2 --- /dev/null +++ b/Plugins/eSpeak/resource.rc @@ -0,0 +1,220 @@ +// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_OPTIONS DIALOGEX 0, 0, 252, 189
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX " System voice ",IDC_SYSTEM,2,6,250,177
+ LTEXT "Language:",IDC_DEF_LANG_L,11,24,52,8
+ COMBOBOX IDC_DEF_LANG,66,22,174,60,CBS_DROPDOWNLIST | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Voice:",IDC_VOICE_L,11,39,52,8
+ COMBOBOX IDC_VOICE,66,37,174,60,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Variant:",IDC_VARIANT_L,11,54,52,8
+ COMBOBOX IDC_VARIANT,66,52,107,60,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Volume:",IDC_VOLUME_L,11,69,52,8
+ CONTROL "",IDC_VOLUME,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,66,69,174,10
+ LTEXT "Rate:",IDC_RATE_L,11,84,52,8
+ CONTROL "",IDC_RATE,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,66,84,174,10
+ LTEXT "Pitch:",IDC_PITCH_L,11,99,52,8
+ CONTROL "",IDC_PITCH,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,66,99,174,10
+ LTEXT "Range:",IDC_RANGE_L,11,114,52,8
+ CONTROL "",IDC_RANGE,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,66,114,174,10
+ LTEXT "Punctuation:",IDC_PUNCT_L,11,129,52,8
+ COMBOBOX IDC_PUNCT,66,127,107,60,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Test:",IDC_TEST_L,11,163,37,8
+ EDITTEXT IDC_TEST,66,161,130,12,ES_AUTOHSCROLL
+ PUSHBUTTON "Speak",IDC_SPEAK,200,161,40,12
+END
+
+IDD_CONTACT_LANG DIALOGEX 0, 0, 224, 155
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ CONTROL "Enable speak for this contact",IDC_ENABLE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,9,4,203,13
+ LTEXT "Language:",IDC_DEF_LANG_L,8,27,52,8
+ COMBOBOX IDC_DEF_LANG,63,25,155,60,CBS_DROPDOWNLIST | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Voice:",IDC_VOICE_L,8,41,52,8
+ COMBOBOX IDC_VOICE,63,40,155,60,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Variant:",IDC_VARIANT_L,8,57,52,8
+ COMBOBOX IDC_VARIANT,63,55,107,60,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Volume:",IDC_VOLUME_L,8,72,52,8
+ CONTROL "",IDC_VOLUME,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,63,72,155,10
+ LTEXT "Rate:",IDC_RATE_L,8,87,52,8
+ CONTROL "",IDC_RATE,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,63,87,155,10
+ LTEXT "Pitch:",IDC_PITCH_L,8,102,52,8
+ CONTROL "",IDC_PITCH,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,63,102,155,10
+ LTEXT "Range:",IDC_RANGE_L,8,116,52,8
+ CONTROL "",IDC_RANGE,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,63,116,155,10
+ LTEXT "Test:",IDC_TEST_L,8,138,37,8
+ EDITTEXT IDC_TEST,63,136,110,12,ES_AUTOHSCROLL
+ PUSHBUTTON "Speak",IDC_SPEAK,178,136,40,12
+END
+
+IDD_TYPES DIALOGEX 0, 0, 318, 230
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE | WS_VSCROLL
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ LTEXT "Speak the following events:",IDC_EVENT_TYPES,3,4,297,13
+END
+
+IDD_ADVANCED DIALOGEX 0, 0, 252, 206
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ GROUPBOX " Enabled when ",IDC_STATUS,1,6,250,132
+ LTEXT " Disable when global status is: ",IDC_STATIC,10,20,230,10
+ CONTROL "Online",ID_ONLINE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,22,34,99,11
+ CONTROL "Away",ID_AWAY,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,22,47,99,11
+ CONTROL "NA",ID_NA,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,22,60,99,11
+ CONTROL "Occupied",ID_OCCUPIED,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,22,73,99,11
+ CONTROL "DND",ID_DND,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,22,86,99,11
+ CONTROL "Free for chat",ID_FREECHAT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,136,34,99,11
+ CONTROL "On the phone",ID_ONTHEPHONE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,136,47,99,11
+ CONTROL "Out to lunch",ID_OUTTOLUNCH,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,136,60,99,11
+ CONTROL "Invisible",ID_INVISIBLE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,136,73,99,11
+ CONTROL "Offline",ID_OFFLINE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,136,86,99,11
+ CONTROL "Enable only if Miranda is idle",IDC_ONLY_IDLE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,105,230,10
+ CONTROL "Truncate text if it is longer than",IDC_TRUNCATE_L,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,120,123,10
+ EDITTEXT IDC_TRUNCATE,139,118,38,12,ES_NUMBER
+ CONTROL "",IDC_TRUNCATE_SPIN,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_NOTHOUSANDS | UDS_HOTTRACK,179,117,11,14
+ GROUPBOX " Advanced ",IDC_ADVANCED,1,142,250,58
+ CONTROL "Use flags",IDC_USE_FLAGS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,157,230,10
+ CONTROL "Respect Miranda sound mute",IDC_SNDVOL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,170,230,10
+ CONTROL "Select voice variant based on contact genre",IDC_PER_GENRE,
+ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,183,230,10
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_UNKNOWN_FLAG ICON "res\\unknown.ico"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_OPTIONS, DIALOG
+ BEGIN
+ BOTTOMMARGIN, 161
+ END
+
+ IDD_CONTACT_LANG, DIALOG
+ BEGIN
+ BOTTOMMARGIN, 102
+ END
+
+ IDD_TYPES, DIALOG
+ BEGIN
+ LEFTMARGIN, 3
+ RIGHTMARGIN, 304
+ BOTTOMMARGIN, 227
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog Info
+//
+
+IDD_OPTIONS DLGINIT
+BEGIN
+ IDC_PUNCT, 0x403, 14, 0
+0x6f4e, 0x656e, 0x4120, 0x6c6c, 0x5320, 0x6d6f, 0x0065,
+ 0
+END
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Portuguese (Brazil) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_PTB)
+#ifdef _WIN32
+LANGUAGE LANG_PORTUGUESE, SUBLANG_PORTUGUESE_BRAZILIAN
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // Portuguese (Brazil) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Plugins/eSpeak/sdk/m_folders.h b/Plugins/eSpeak/sdk/m_folders.h new file mode 100644 index 0000000..c232410 --- /dev/null +++ b/Plugins/eSpeak/sdk/m_folders.h @@ -0,0 +1,205 @@ +/*
+Custom profile folders plugin for Miranda IM
+
+Copyright © 2005 Cristian Libotean
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#ifndef M_CUSTOM_FOLDERS_H
+#define M_CUSTOM_FOLDERS_H
+
+#define FOLDERS_API 501 //dunno why it's here but it is :)
+
+#define PROFILE_PATH "%profile_path%"
+#define CURRENT_PROFILE "%current_profile%"
+#define MIRANDA_PATH "%miranda_path%"
+#define PLUGINS_PATH "%miranda_path%" "\\plugins"
+
+#define TO_WIDE(x) L ## x
+
+#define PROFILE_PATHW L"%profile_path%"
+#define CURRENT_PROFILEW L"%current_profile%"
+#define MIRANDA_PATHW L"%miranda_path%"
+
+#define FOLDER_AVATARS PROFILE_PATH "\\" CURRENT_PROFILE "\\avatars"
+#define FOLDER_VCARDS PROFILE_PATH "\\" CURRENT_PROFILE "\\vcards"
+#define FOLDER_LOGS PROFILE_PATH "\\" CURRENT_PROFILE "\\logs"
+#define FOLDER_RECEIVED_FILES PROFILE_PATH "\\" CURRENT_PROFILE "\\received files"
+#define FOLDER_DOCS MIRANDA_PATH "\\" "docs"
+
+#define FOLDER_CONFIG PLUGINS_PATH "\\" "config"
+
+#define FOLDER_SCRIPTS MIRANDA_PATH "\\" "scripts"
+
+#define FOLDER_UPDATES MIRANDA_PATH "\\" "updates"
+
+#define FOLDER_CUSTOMIZE MIRANDA_PATH "\\" "customize"
+#define FOLDER_CUSTOMIZE_SOUNDS FOLDER_CUSTOMIZE "\\sounds"
+#define FOLDER_CUSTOMIZE_ICONS FOLDER_CUSTOMIZE "\\icons"
+#define FOLDER_CUSTOMIZE_SMILEYS FOLDER_CUSTOMIZE "\\smileys"
+#define FOLDER_CUSTOMIZE_SKINS FOLDER_CUSTOMIZE "\\skins"
+#define FOLDER_CUSTOMIZE_THEMES FOLDER_CUSTOMIZE "\\themes"
+
+
+#define FOLDERS_NAME_MAX_SIZE 64 //maximum name and section size
+
+#define FF_UNICODE 0x00000001
+
+typedef struct{
+ int cbSize; //size of struct
+ char szSection[FOLDERS_NAME_MAX_SIZE]; //section name, if it doesn't exist it will be created otherwise it will just add this entry to it
+ char szName[FOLDERS_NAME_MAX_SIZE]; //entry name - will be shown in options
+ union{
+ const char *szFormat; //default string format. Fallback string in case there's no entry in the database for this folder. This should be the initial value for the path, users will be able to change it later.
+ const wchar_t *szFormatW; //String is dup()'d so you can free it later. If you set the unicode string don't forget to set the flag accordingly.
+ const TCHAR *szFormatT;
+ };
+ DWORD flags; //FF_* flags
+} FOLDERSDATA;
+
+/*Folders/Register/Path service
+ wParam - not used, must be 0
+ lParam - (LPARAM) (const FOLDERDATA *) - Data structure filled with
+ the necessary information.
+ Returns a handle to the registered path or 0 on error.
+ You need to use this to call the other services.
+*/
+#define MS_FOLDERS_REGISTER_PATH "Folders/Register/Path"
+
+/*Folders/Get/PathSize service
+ wParam - (WPARAM) (int) - handle to registered path
+ lParam - (LPARAM) (int *) - pointer to the variable that receives the size of the path
+ string (not including the null character). Depending on the flags set when creating the path
+ it will either call strlen() or wcslen() to get the length of the string.
+ Returns the size of the buffer.
+*/
+#define MS_FOLDERS_GET_SIZE "Folders/Get/PathSize"
+
+typedef struct{
+ int cbSize;
+ int nMaxPathSize; //maximum size of buffer. This represents the number of characters that can be copied to it (so for unicode strings you don't send the number of bytes but the length of the string).
+ union{
+ char *szPath; //pointer to the buffer that receives the path without the last "\\"
+ wchar_t *szPathW; //unicode version of the buffer.
+ TCHAR *szPathT;
+ };
+} FOLDERSGETDATA;
+
+/*Folders/Get/Path service
+ wParam - (WPARAM) (int) - handle to registered path
+ lParam - (LPARAM) (FOLDERSGETDATA *) pointer to a FOLDERSGETDATA that has all the relevant fields filled.
+ Should return 0 on success, or nonzero otherwise.
+*/
+#define MS_FOLDERS_GET_PATH "Folders/Get/Path"
+
+typedef struct{
+ int cbSize;
+ union{
+ char **szPath; //address of a string variable (char *) or (wchar_t*) where the path should be stored (the last \ won't be copied).
+ wchar_t **szPathW; //unicode version of string.
+ TCHAR **szPathT;
+ };
+} FOLDERSGETALLOCDATA;
+
+/*Folders/GetRelativePath/Alloc service
+ wParam - (WPARAM) (int) - Handle to registered path
+ lParam - (LPARAM) (FOLDERSALLOCDATA *) data
+ This service is the same as MS_FOLDERS_GET_PATH with the difference that this service
+ allocates the needed space for the buffer. It uses miranda's memory functions for that and you need
+ to use those to free the resulting buffer.
+ Should return 0 on success, or nonzero otherwise. Currently it only returns 0.
+*/
+#define MS_FOLDERS_GET_PATH_ALLOC "Folders/Get/Path/Alloc"
+
+
+/*Folders/On/Path/Changed
+ wParam - (WPARAM) 0
+ lParam - (LPARAM) 0
+ Triggered when the folders change, you should reget the paths you registered.
+*/
+#define ME_FOLDERS_PATH_CHANGED "Folders/On/Path/Changed"
+
+#ifndef FOLDERS_NO_HELPER_FUNCTIONS
+//#include "../../../include/newpluginapi.h"
+
+__inline static int FoldersRegisterCustomPath(const char *section, const char *name, const char *defaultPath)
+{
+ FOLDERSDATA fd = {0};
+ if (!ServiceExists(MS_FOLDERS_REGISTER_PATH)) return 1;
+ fd.cbSize = sizeof(FOLDERSDATA);
+ strncpy(fd.szSection, section, FOLDERS_NAME_MAX_SIZE);
+ fd.szSection[FOLDERS_NAME_MAX_SIZE - 1] = '\0';
+ strncpy(fd.szName, name, FOLDERS_NAME_MAX_SIZE);
+ fd.szName[FOLDERS_NAME_MAX_SIZE - 1] = '\0';
+ fd.szFormat = defaultPath;
+ return CallService(MS_FOLDERS_REGISTER_PATH, 0, (LPARAM) &fd);
+}
+
+__inline static int FoldersRegisterCustomPathW(const char *section, const char *name, const wchar_t *defaultPathW)
+{
+ FOLDERSDATA fd = {0};
+ if (!ServiceExists(MS_FOLDERS_REGISTER_PATH)) return 1;
+ fd.cbSize = sizeof(FOLDERSDATA);
+ strncpy(fd.szSection, section, FOLDERS_NAME_MAX_SIZE);
+ fd.szSection[FOLDERS_NAME_MAX_SIZE - 1] = '\0'; //make sure it's NULL terminated
+ strncpy(fd.szName, name, FOLDERS_NAME_MAX_SIZE);
+ fd.szName[FOLDERS_NAME_MAX_SIZE - 1] = '\0'; //make sure it's NULL terminated
+ fd.szFormatW = defaultPathW;
+ fd.flags = FF_UNICODE;
+ return CallService(MS_FOLDERS_REGISTER_PATH, 0, (LPARAM) &fd);
+}
+
+__inline static int FoldersGetCustomPath(HANDLE hFolderEntry, char *path, const int size, char *notFound)
+{
+ FOLDERSGETDATA fgd = {0};
+ fgd.cbSize = sizeof(FOLDERSGETDATA);
+ fgd.nMaxPathSize = size;
+ fgd.szPath = path;
+ int res = CallService(MS_FOLDERS_GET_PATH, (WPARAM) hFolderEntry, (LPARAM) &fgd);
+ if (res)
+ {
+ strncpy(path, notFound, size);
+ path[size - 1] = '\0'; //make sure it's NULL terminated
+ }
+ return res;
+}
+
+__inline static int FoldersGetCustomPathW(HANDLE hFolderEntry, wchar_t *pathW, const int count, wchar_t *notFoundW)
+{
+ FOLDERSGETDATA fgd = {0};
+ fgd.cbSize = sizeof(FOLDERSGETDATA);
+ fgd.nMaxPathSize = count;
+ fgd.szPathW = pathW;
+ int res = CallService(MS_FOLDERS_GET_PATH, (WPARAM) hFolderEntry, (LPARAM) &fgd);
+ if (res)
+ {
+ wcsncpy(pathW, notFoundW, count);
+ pathW[count - 1] = '\0';
+ }
+ return res;
+}
+
+# ifdef _UNICODE
+# define FoldersGetCustomPathT FoldersGetCustomPathW
+# define FoldersRegisterCustomPathT FoldersRegisterCustomPathW
+#else
+# define FoldersGetCustomPathT FoldersGetCustomPath
+# define FoldersRegisterCustomPathT FoldersRegisterCustomPath
+#endif
+
+#endif
+
+#endif //M_CUSTOM_FOLDERS_H
\ No newline at end of file diff --git a/Plugins/eSpeak/sdk/m_metacontacts.h b/Plugins/eSpeak/sdk/m_metacontacts.h new file mode 100644 index 0000000..1da12b9 --- /dev/null +++ b/Plugins/eSpeak/sdk/m_metacontacts.h @@ -0,0 +1,162 @@ +/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
+Copyright © 2004 Scott Ellis (www.scottellis.com.au mail@scottellis.com.au)
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#ifndef M_METACONTACTS_H__
+#define M_METACONTACTS_H__ 1
+
+//get the handle for a contact's parent metacontact
+//wParam=(HANDLE)hSubContact
+//lParam=0
+//returns a handle to the parent metacontact, or null if this contact is not a subcontact
+#define MS_MC_GETMETACONTACT "MetaContacts/GetMeta"
+
+//gets the handle for the default contact
+//wParam=(HANDLE)hMetaContact
+//lParam=0
+//returns a handle to the default contact, or null on failure
+#define MS_MC_GETDEFAULTCONTACT "MetaContacts/GetDefault"
+
+//gets the contact number for the default contact
+//wParam=(HANDLE)hMetaContact
+//lParam=0
+//returns a DWORD contact number, or -1 on failure
+#define MS_MC_GETDEFAULTCONTACTNUM "MetaContacts/GetDefaultNum"
+
+//gets the handle for the 'most online' contact
+//wParam=(HANDLE)hMetaContact
+//lParam=0
+//returns a handle to the 'most online' contact
+#define MS_MC_GETMOSTONLINECONTACT "MetaContacts/GetMostOnline"
+
+//gets the number of subcontacts for a metacontact
+//wParam=(HANDLE)hMetaContact
+//lParam=0
+//returns a DWORD representing the number of subcontacts for the given metacontact
+#define MS_MC_GETNUMCONTACTS "MetaContacts/GetNumContacts"
+
+//gets the handle of a subcontact, using the subcontact's number
+//wParam=(HANDLE)hMetaContact
+//lParam=(DWORD)contact number
+//returns a handle to the specified subcontact
+#define MS_MC_GETSUBCONTACT "MetaContacts/GetSubContact"
+
+//sets the default contact, using the subcontact's contact number
+//wParam=(HANDLE)hMetaContact
+//lParam=(DWORD)contact number
+//returns 0 on success
+#define MS_MC_SETDEFAULTCONTACTNUM "MetaContacts/SetDefault"
+
+//sets the default contact, using the subcontact's handle
+//wParam=(HANDLE)hMetaContact
+//lParam=(HANDLE)hSubcontact
+//returns 0 on success
+#define MS_MC_SETDEFAULTCONTACT "MetaContacts/SetDefaultByHandle"
+
+//forces the metacontact to send using a specific subcontact, using the subcontact's contact number
+//wParam=(HANDLE)hMetaContact
+//lParam=(DWORD)contact number
+//returns 0 on success
+#define MS_MC_FORCESENDCONTACTNUM "MetaContacts/ForceSendContact"
+
+//forces the metacontact to send using a specific subcontact, using the subcontact's handle
+//wParam=(HANDLE)hMetaContact
+//lParam=(HANDLE)hSubcontact
+//returns 0 on success (will fail if 'force default' is in effect)
+#define MS_MC_FORCESENDCONTACT "MetaContacts/ForceSendContactByHandle"
+
+//'unforces' the metacontact to send using a specific subcontact
+//wParam=(HANDLE)hMetaContact
+//lParam=0
+//returns 0 on success (will fail if 'force default' is in effect)
+#define MS_MC_UNFORCESENDCONTACT "MetaContacts/UnforceSendContact"
+
+//'forces' or 'unforces' (i.e. toggles) the metacontact to send using it's default contact
+// overrides (and clears) 'force send' above, and will even force use of offline contacts
+// will send ME_MC_FORCESEND or ME_MC_UNFORCESEND event
+//wParam=(HANDLE)hMetaContact
+//lParam=0
+//returns 1(true) or 0(false) representing new state of 'force default'
+#define MS_MC_FORCEDEFAULT "MetaContacts/ForceSendDefault"
+
+// method to get state of 'force' for a metacontact
+// wParam=(HANDLE)hMetaContact
+// lParam= (DWORD)&contact_number or NULL
+//
+// if lparam supplied, the contact_number of the contatct 'in force' will be copied to the address it points to,
+// or if none is in force, the value (DWORD)-1 will be copied
+// (v0.8.0.8+ returns 1 if 'force default' is true with *lParam == default contact number, else returns 0 with *lParam as above)
+#define MS_MC_GETFORCESTATE "MetaContacts/GetForceState"
+
+// fired when a metacontact's default contact changes (fired upon creation of metacontact also, when default is initially set)
+// wParam=(HANDLE)hMetaContact
+// lParam=(HANDLE)hDefaultContact
+#define ME_MC_DEFAULTTCHANGED "MetaContacts/DefaultChanged"
+
+// fired when a metacontact's subcontacts change (fired upon creation of metacontact, when contacts are added or removed, and when
+// contacts are reordered) - a signal to re-read metacontact data
+// wParam=(HANDLE)hMetaContact
+// lParam=0
+#define ME_MC_SUBCONTACTSCHANGED "MetaContacts/SubcontactsChanged"
+
+// fired when a metacontact is forced to send using a specific subcontact
+// wParam=(HANDLE)hMetaContact
+// lParam=(HANDLE)hForceContact
+#define ME_MC_FORCESEND "MetaContacts/ForceSend"
+
+// fired when a metacontact is 'unforced' to send using a specific subcontact
+// wParam=(HANDLE)hMetaContact
+// lParam=0
+#define ME_MC_UNFORCESEND "MetaContacts/UnforceSend"
+
+// method to get protocol name - used to be sure you're dealing with a "real" metacontacts plugin :)
+// wParam=lParam=0
+#define MS_MC_GETPROTOCOLNAME "MetaContacts/GetProtoName"
+
+
+// added 0.9.5.0 (22/3/05)
+// wParam=(HANDLE)hContact
+// lParam=0
+// convert a given contact into a metacontact
+#define MS_MC_CONVERTTOMETA "MetaContacts/ConvertToMetacontact"
+
+// added 0.9.5.0 (22/3/05)
+// wParam=(HANDLE)hContact
+// lParam=(HANDLE)hMeta
+// add an existing contact to a metacontact
+#define MS_MC_ADDTOMETA "MetaContacts/AddToMetacontact"
+
+// added 0.9.5.0 (22/3/05)
+// wParam=0
+// lParam=(HANDLE)hContact
+// remove a contact from a metacontact
+#define MS_MC_REMOVEFROMMETA "MetaContacts/RemoveFromMetacontact"
+
+
+// added 0.9.13.2 (6/10/05)
+// wParam=(BOOL)disable
+// lParam=0
+// enable/disable the 'hidden group hack' - for clists that support subcontact hiding using 'IsSubcontact' setting
+// should be called once in the clist 'onmodulesloaded' event handler (which, since it's loaded after the db, will be called
+// before the metacontact onmodulesloaded handler where the subcontact hiding is usually done)
+#define MS_MC_DISABLEHIDDENGROUP "MetaContacts/DisableHiddenGroup"
+
+#endif
diff --git a/Plugins/eSpeak/sdk/m_updater.h b/Plugins/eSpeak/sdk/m_updater.h new file mode 100644 index 0000000..371b743 --- /dev/null +++ b/Plugins/eSpeak/sdk/m_updater.h @@ -0,0 +1,146 @@ +#ifndef _M_UPDATER_H
+#define _M_UPDATER_H
+
+// NOTES:
+// - For langpack updates, include a string of the following format in the langpack text file:
+// ";FLID: <file listing name> <version>"
+// version must be four numbers seperated by '.', in the range 0-255 inclusive
+// - Updater will disable plugins that are downloaded but were not active prior to the update (this is so that, if an archive contains e.g. ansi and
+// unicode versions, the correct plugin will be the only one active after the new version is installed)...so if you add a support plugin, you may need
+// to install an ini file to make the plugin activate when miranda restarts after the update
+// - Updater will replace all dlls that have the same internal shortName as a downloaded update dll (this is so that msn1.dll and msn2.dll, for example,
+// will both be updated) - so if you have a unicode and a non-unicode version of a plugin in your archive, you should make the internal names different (which will break automatic
+// updates from the file listing if there is only one file listing entry for both versions, unless you use the 'MS_UPDATE_REGISTER' service below)
+// - Updater will install all files in the root of the archive into the plugins folder, except for langpack files that contain the FLID string which go into the root folder (same
+// folder as miranda32.exe)...all folders in the archive will also be copied to miranda's root folder, and their contents transferred into the new folders. The only exception is a
+// special folder called 'root_files' - if there is a folder by that name in the archive, it's contents will also be copied into miranda's root folder - this is intended to be used
+// to install additional dlls etc that a plugin may require)
+
+// if you set Update.szUpdateURL to the following value when registering, as well as setting your beta site and version data,
+// Updater will ignore szVersionURL and pbVersionPrefix, and attempt to find the file listing URL's from the backend XML data.
+// for this to work, the plugin name in pluginInfo.shortName must match the file listing exactly (except for case)
+#define UPDATER_AUTOREGISTER "UpdaterAUTOREGISTER"
+// Updater will also use the backend xml data if you provide URL's that reference the miranda file listing for updates (so you can use that method
+// if e.g. your plugin shortName does not match the file listing) - it will grab the file listing id from the end of these URLs
+
+typedef struct Update_tag {
+ int cbSize;
+ char *szComponentName; // component name as it will appear in the UI (will be translated before displaying)
+
+ char *szVersionURL; // URL where the current version can be found (NULL to disable)
+ BYTE *pbVersionPrefix; // bytes occuring in VersionURL before the version, used to locate the version information within the URL data
+ // (note that this URL could point at a binary file - dunno why, but it could :)
+ int cpbVersionPrefix; // number of bytes pointed to by pbVersionPrefix
+ char *szUpdateURL; // URL where dll/zip is located
+ // set to UPDATER_AUTOREGISTER if you want Updater to find the file listing URLs (ensure plugin shortName matches file listing!)
+
+ char *szBetaVersionURL; // URL where the beta version can be found (NULL to disable betas)
+ BYTE *pbBetaVersionPrefix; // bytes occuring in VersionURL before the version, used to locate the version information within the URL data
+ int cpbBetaVersionPrefix; // number of bytes pointed to by pbVersionPrefix
+ char *szBetaUpdateURL; // URL where dll/zip is located
+
+ BYTE *pbVersion; // bytes of current version, used for comparison with those in VersionURL
+ int cpbVersion; // number of bytes pointed to by pbVersion
+
+ char *szBetaChangelogURL; // url for displaying changelog for beta versions
+} Update;
+
+// register a comonent with Updater
+//
+// wparam = 0
+// lparam = (LPARAM)&Update
+#define MS_UPDATE_REGISTER "Update/Register"
+
+// utility functions to create a version string from a DWORD or from pluginInfo
+// point buf at a buffer at least 16 chars wide - but note the version string returned may be shorter
+//
+__inline static char *CreateVersionString(DWORD version, char *buf) {
+ mir_snprintf(buf, 16, "%d.%d.%d.%d", (version >> 24) & 0xFF, (version >> 16) & 0xFF, (version >> 8) & 0xFF, version & 0xFF);
+ return buf;
+}
+
+__inline static char *CreateVersionStringPlugin(PLUGININFO *pluginInfo, char *buf) {
+ return CreateVersionString(pluginInfo->version, buf);
+}
+
+
+// register the 'easy' way - use this method if you have no beta URL and the plugin is on the miranda file listing
+// NOTE: the plugin version string on the file listing must be the string version of the version in pluginInfo (i.e. 0.0.0.1,
+// four numbers between 0 and 255 inclusivem, so no letters, brackets, etc.)
+//
+// wParam = (int)fileID - this is the file ID from the file listing (i.e. the number at the end of the download link)
+// lParam = (PLUGININFO*)&pluginInfo
+#define MS_UPDATE_REGISTERFL "Update/RegisterFL"
+
+// this function can be used to 'unregister' components - useful for plugins that register non-plugin/langpack components and
+// may need to change those components on the fly
+// lParam = (char *)szComponentName
+#define MS_UPDATE_UNREGISTER "Update/Unregister"
+
+// this event is fired when the startup process is complete, but NOT if a restart is imminent
+// it is designed for status managment plugins to use as a trigger for beggining their own startup process
+// wParam = lParam = 0 (unused)
+// (added in version 0.1.6.0)
+#define ME_UPDATE_STARTUPDONE "Update/StartupDone"
+
+// this service can be used to enable/disable Updater's global status control
+// it can be called from the StartupDone event handler
+// wParam = (BOOL)enable
+// lParam = 0
+// (added in version 0.1.6.0)
+#define MS_UPDATE_ENABLESTATUSCONTROL "Update/EnableStatusControl"
+
+// An description of usage of the above service and event:
+// Say you are a status control plugin that normally sets protocol or global statuses in your ModulesLoaded event handler.
+// In order to make yourself 'Updater compatible', you would move the status control code from ModulesLoaded to another function,
+// say DoStartup. Then, in ModulesLoaded you would check for the existence of the MS_UPDATE_ENABLESTATUSCONTROL service.
+// If it does not exist, call DoStartup. If it does exist, hook the ME_UPDATE_STARTUPDONE event and call DoStartup from there. You may
+// also wish to call MS_UPDATE_ENABLESTATUSCONTROL with wParam == FALSE at this time, to disable Updater's own status control feature.
+
+// this service can be used to determine whether updates are possible for a component with the given name
+// wParam = 0
+// lParam = (char *)szComponentName
+// returns TRUE if updates are supported, FALSE otherwise
+#define MS_UPDATE_ISUPDATESUPPORTED "Update/IsUpdateSupported"
+
+#endif
+
+
+/////////////// Usage Example ///////////////
+
+#ifdef EXAMPLE_CODE
+
+// you need to #include "m_updater.h" and HookEvent(ME_SYSTEM_MODULESLOADED, OnModulesLoaded) in your Load function...
+
+int OnModulesLoaded(WPARAM wParam, LPARAM lParam) {
+
+ Update update = {0}; // for c you'd use memset or ZeroMemory...
+ char szVersion[16];
+
+ update.cbSize = sizeof(Update);
+
+ update.szComponentName = pluginInfo.shortName;
+ update.pbVersion = (BYTE *)CreateVersionString(&pluginInfo, szVersion);
+ update.cpbVersion = strlen((char *)update.pbVersion);
+
+ // these are the three lines that matter - the archive, the page containing the version string, and the text (or data)
+ // before the version that we use to locate it on the page
+ // (note that if the update URL and the version URL point to standard file listing entries, the backend xml
+ // data will be used to check for updates rather than the actual web page - this is not true for beta urls)
+ update.szUpdateURL = "http://scottellis.com.au:81/test/updater.zip";
+ update.szVersionURL = "http://scottellis.com.au:81/test/updater_test.html";
+ update.pbVersionPrefix = (BYTE *)"Updater version ";
+
+ update.cpbVersionPrefix = strlen((char *)update.pbVersionPrefix);
+
+ // do the same for the beta versions of the above struct members if you wish to allow beta updates from another URL
+
+ CallService(MS_UPDATE_REGISTER, 0, (WPARAM)&update);
+
+ // Alternatively, to register a plugin with e.g. file ID 2254 on the file listing...
+ // CallService(MS_UPDATE_REGISTERFL, (WPARAM)2254, (LPARAM)&pluginInfo);
+
+ return 0;
+}
+
+#endif
diff --git a/Plugins/eSpeak/sdk/m_variables.h b/Plugins/eSpeak/sdk/m_variables.h new file mode 100644 index 0000000..3f13c96 --- /dev/null +++ b/Plugins/eSpeak/sdk/m_variables.h @@ -0,0 +1,718 @@ +/*
+ Variables Plugin for Miranda-IM (www.miranda-im.org)
+ Copyright 2003-2006 P. Boon
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef __M_VARS
+#define __M_VARS
+
+#if !defined(_TCHAR_DEFINED)
+#include <tchar.h>
+#endif
+
+#ifndef VARIABLES_NOHELPER
+#include <m_button.h>
+#endif
+
+// --------------------------------------------------------------------------
+// Memory management
+// --------------------------------------------------------------------------
+
+// Release memory that was allocated by the Variables plugin, e.g. returned
+// strings.
+
+#define MS_VARS_FREEMEMORY "Vars/FreeMemory"
+
+// Parameters:
+// ------------------------
+// wParam = (WPARAM)(void *)pntr
+// Pointer to memory that was allocated by the Variables plugin (e.g. a
+// returned string) (can be NULL).
+// lParam = 0
+
+// Return Value:
+// ------------------------
+// Does return 0 on success, nozero otherwise.
+
+// Note: Do only use this service to free memory that was *explicitliy*
+// stated that it should be free with this service.
+
+
+
+#define MS_VARS_GET_MMI "Vars/GetMMI"
+
+// Get Variable's RTL/CRT function poiners to malloc(), free() and
+// realloc().
+
+// Parameters:
+// ------------------------
+// wParam = 0
+// lParam = (LPARAM) &MM_INTERFACE
+// Pointer to a memory manager interface struct (see m_system.h).
+
+// Return Value:
+// ------------------------
+// Returns 0 on success, nozero otherwise
+
+// Note: Works exactly the same as the MS_SYSTEM_GET_MMI service
+// service of m_system.h.
+
+// Helper function for easy using:
+#ifndef VARIABLES_NOHELPER
+__inline static void variables_free(void *pntr) {
+
+ CallService(MS_VARS_FREEMEMORY, (WPARAM)pntr, 0);
+}
+#endif
+
+
+
+// --------------------------------------------------------------------------
+// String formatting
+// --------------------------------------------------------------------------
+
+#define MS_VARS_FORMATSTRING "Vars/FormatString"
+
+// This service can be used to parse tokens in a text. The tokens will be
+// replaced by their resolved values. A token can either be a field or a
+// function. A field takes no arguments and is represented between
+// %-characters, e.g. "%winampsong%". A function can take any number of
+// arguments and is represented by a ? or !-character followed by the name
+// of the function and a list of arguments, e.g. "?add(1,2)".
+
+// Parameters:
+// ------------------------
+// wParam = (WPARAM)(FORMATINFO *)&fi
+// See below.
+// lParam = 0
+
+// Return Value:
+// ------------------------
+// Returns a pointer to the resolved string or NULL in case of an error.
+
+// Note: The returned pointer needs to be freed using MS_VARS_FREEMEMORY.
+
+typedef struct {
+ int cbSize; // Set this to sizeof(FORMATINFO).
+ int flags; // Flags to use (see FIF_* below).
+ union {
+ char *szFormat; // Text in which the tokens will be replaced (can't be
+ // NULL).
+ WCHAR *wszFormat;
+ TCHAR *tszFormat;
+ };
+ union {
+ char *szExtraText; // Extra, context-specific string (can be NULL) ->
+ // The field "extratext" will be replaced by this
+ // string. (Previously szSource).
+ WCHAR *wszExtraText;
+ TCHAR *tszExtraText;
+ };
+ HANDLE hContact; // Handle to contact (can be NULL) -> The field "subject"
+ // represents this contact.
+ int pCount; // (output) Number of succesful parsed tokens, needs to be set
+ // to 0 before the call
+ int eCount; // (output) Number of failed tokens, needs to be set to 0
+ // before the call
+ union {
+ char **szaTemporaryVars; // Temporary variables valid only in the duration of the format call
+ TCHAR **tszaTemporaryVars; // By pos: [i] is var name, [i + 1] is var value
+ WCHAR **wszaTemporaryVars;
+ };
+ int cbTemporaryVarsSize; // Number of elements in szaTemporaryVars array
+
+} FORMATINFO;
+
+#define FORMATINFOV2_SIZE 28
+
+// Possible flags:
+#define FIF_UNICODE 0x01 // Expects and returns unicode text (WCHAR*).
+
+#if defined(UNICODE) || defined(_UNICODE)
+#define FIF_TCHAR FIF_UNICODE // Strings in structure are TCHAR*.
+#else
+#define FIF_TCHAR 0
+#endif
+
+// Helper functions for easy using:
+
+// Helper #1: variables_parse
+// ------------------------
+// The returned string needs to be freed using MS_VARS_FREEMEMORY.
+
+#ifndef VARIABLES_NOHELPER
+__inline static TCHAR *variables_parse(TCHAR *tszFormat, TCHAR *tszExtraText, HANDLE hContact) {
+
+ FORMATINFO fi;
+
+ ZeroMemory(&fi, sizeof(fi));
+ fi.cbSize = sizeof(fi);
+ fi.tszFormat = tszFormat;
+ fi.tszExtraText = tszExtraText;
+ fi.hContact = hContact;
+ fi.flags = FIF_TCHAR;
+
+ return (TCHAR *)CallService(MS_VARS_FORMATSTRING, (WPARAM)&fi, 0);
+}
+#endif
+
+__inline static TCHAR *variables_parse_ex(TCHAR *tszFormat, TCHAR *tszExtraText, HANDLE hContact,
+ TCHAR **tszaTemporaryVars, int cbTemporaryVarsSize) {
+
+ FORMATINFO fi;
+
+ ZeroMemory(&fi, sizeof(fi));
+ fi.cbSize = sizeof(fi);
+ fi.tszFormat = tszFormat;
+ fi.tszExtraText = tszExtraText;
+ fi.hContact = hContact;
+ fi.flags = FIF_TCHAR;
+ fi.tszaTemporaryVars = tszaTemporaryVars;
+ fi.cbTemporaryVarsSize = cbTemporaryVarsSize;
+
+ return (TCHAR *)CallService(MS_VARS_FORMATSTRING, (WPARAM)&fi, 0);
+}
+// Helper #2: variables_parsedup
+// ------------------------
+// Returns a _strdup()'ed copy of the unparsed string when Variables is not
+// installed, returns a strdup()'ed copy of the parsed result otherwise.
+
+// Note: The returned pointer needs to be released using your own free().
+
+#ifndef VARIABLES_NOHELPER
+__inline static TCHAR *variables_parsedup(TCHAR *tszFormat, TCHAR *tszExtraText, HANDLE hContact) {
+
+ if (ServiceExists(MS_VARS_FORMATSTRING)) {
+ FORMATINFO fi;
+ TCHAR *tszParsed, *tszResult;
+
+ ZeroMemory(&fi, sizeof(fi));
+ fi.cbSize = sizeof(fi);
+ fi.tszFormat = tszFormat;
+ fi.tszExtraText = tszExtraText;
+ fi.hContact = hContact;
+ fi.flags |= FIF_TCHAR;
+ tszParsed = (TCHAR *)CallService(MS_VARS_FORMATSTRING, (WPARAM)&fi, 0);
+ if (tszParsed) {
+ tszResult = _tcsdup(tszParsed);
+ CallService(MS_VARS_FREEMEMORY, (WPARAM)tszParsed, 0);
+ return tszResult;
+ }
+ }
+ return tszFormat?_tcsdup(tszFormat):tszFormat;
+}
+
+__inline static TCHAR *variables_parsedup_ex(TCHAR *tszFormat, TCHAR *tszExtraText, HANDLE hContact,
+ TCHAR **tszaTemporaryVars, int cbTemporaryVarsSize) {
+
+ if (ServiceExists(MS_VARS_FORMATSTRING)) {
+ FORMATINFO fi;
+ TCHAR *tszParsed, *tszResult;
+
+ ZeroMemory(&fi, sizeof(fi));
+ fi.cbSize = sizeof(fi);
+ fi.tszFormat = tszFormat;
+ fi.tszExtraText = tszExtraText;
+ fi.hContact = hContact;
+ fi.flags |= FIF_TCHAR;
+ fi.tszaTemporaryVars = tszaTemporaryVars;
+ fi.cbTemporaryVarsSize = cbTemporaryVarsSize;
+ tszParsed = (TCHAR *)CallService(MS_VARS_FORMATSTRING, (WPARAM)&fi, 0);
+ if (tszParsed) {
+ tszResult = _tcsdup(tszParsed);
+ CallService(MS_VARS_FREEMEMORY, (WPARAM)tszParsed, 0);
+ return tszResult;
+ }
+ }
+ return tszFormat?_tcsdup(tszFormat):tszFormat;
+}
+#endif
+
+
+
+// --------------------------------------------------------------------------
+// Register tokens
+// --------------------------------------------------------------------------
+
+// Plugins can define tokens which will be parsed by the Variables plugin.
+
+#define MS_VARS_REGISTERTOKEN "Vars/RegisterToken"
+
+// With this service you can define your own token. The newly added tokens
+// using this service are taken into account on every call to
+// MS_VARS_FORMATSTRING.
+
+// Parameters:
+// ------------------------
+// wParam = 0
+// lParam = (LPARAM)(TOKENREGISTER*)&tr
+// See below.
+
+// Return Value:
+// ------------------------
+// Returns 0 on success, nonzero otherwise. Existing tokens will be
+// 'overwritten' if registered twice.
+
+// Needed for szService and parseFunction:
+typedef struct {
+ int cbSize; // You need to check if this is >=sizeof(ARGUMENTSINFO)
+ // (already filled in).
+ FORMATINFO *fi; // Arguments passed to MS_VARS_FORMATSTRING.
+ unsigned int argc; // Number of elements in the argv array.
+ union {
+ char **argv; // Argv[0] will be the token name, the following elements
+ // are the additional arguments.
+ WCHAR **wargv; // If the registered token was registered as a unicode
+ // token, wargv should be accessed.
+ TCHAR **targv;
+ };
+ int flags; // (output) You can set flags here (initially 0), use the
+ // AIF_* flags (see below).
+} ARGUMENTSINFO;
+
+// Available flags for ARGUMENTSINFO:
+// Set the flags of the ARGUMENTSINFO struct to any of these to influence
+// further parsing.
+#define AIF_DONTPARSE 0x01 // Don't parse the result of this function,
+ // usually the result of a token is parsed
+ // again, if the `?` is used as a function
+ // character.
+#define AIF_FALSE 0x02 // The function returned logical false.
+
+// Definition of parse/cleanup functions:
+typedef char* (*VARPARSEFUNCA)(ARGUMENTSINFO *ai);
+typedef WCHAR* (*VARPARSEFUNCW)(ARGUMENTSINFO *ai);
+typedef void (*VARCLEANUPFUNCA)(char *szReturn);
+typedef void (*VARCLEANUPFUNCW)(WCHAR *wszReturn);
+
+#if defined(UNICODE) || defined(_UNICODE)
+#define VARPARSEFUNC VARPARSEFUNCW
+#define VARCLEANUPFUNC VARCLEANUPFUNCW
+#else
+#define VARPARSEFUNC VARPARSEFUNCA
+#define VARCLEANUPFUNC VARCLEANUPFUNCA
+#endif
+
+typedef struct {
+ int cbSize; // Set this to sizeof(TOKENREGISTER).
+ union {
+ char *szTokenString; // Name of the new token to be created, without %,
+ // ?, ! etc. signs (can't be NULL).
+ WCHAR *wszTokenString;
+ TCHAR *tszTokenString;
+ };
+ union {
+ char *szService; // Name of a service that is used to request the
+ // token's value, if no service is used, a function
+ // and TRF_PARSEFUNC must be used.
+ VARPARSEFUNCA parseFunction; // See above, use with TRF_PARSEFUNC.
+ VARPARSEFUNCW parseFunctionW;
+ VARPARSEFUNC parseFunctionT;
+ };
+ union {
+ char *szCleanupService; // Name of a service to be called when the
+ // memory allocated in szService can be freed
+ // (only used when flag VRF_CLEANUP is set,
+ // else set this to NULL).
+ VARCLEANUPFUNCA cleanupFunction; // See above, use with TRF_CLEANUPFUNC.
+ VARCLEANUPFUNCW cleanupFunctionW;
+ VARCLEANUPFUNC cleanupFunctionT;
+ };
+ char *szHelpText; // Help info shown in help dialog (can be NULL). Has to
+ // be in the following format:
+ // "subject\targuments\tdescription"
+ // (Example: "math\t(x, y ,...)\tx + y + ..."), or:
+ // "subject\tdescription"
+ // (Example: "miranda\tPath to the Miranda-IM
+ // executable").
+ // Note: subject and description are translated by
+ // Variables.
+ int memType; // Describes which method Varibale's plugin needs to use to
+ // free the returned buffer, use one of the VR_MEM_* values
+ // (see below). Only valid if the flag VRF_FREEMEM is set,
+ // use TR_MEM_OWNER otherwise).
+ int flags; // Flags to use (see below), one of TRF_* (see below).
+} TOKENREGISTER;
+
+// Available Memory Storage Types:
+// These values describe which method Variables Plugin will use to free the
+// buffer returned by the parse function or service
+#define TR_MEM_VARIABLES 1 // Memory is allocated using the functions
+ // retrieved by MS_VARS_GET_MMI.
+#define TR_MEM_MIRANDA 2 // Memory is allocated using Miranda's Memory
+ // Manager Interface (using the functions
+ // returned by MS_SYSTEM_GET_MMI), if
+ // VRF_FREEMEM is set, the memory will be
+ // freed by Variables.
+#define TR_MEM_OWNER 3 // Memory is owned by the calling plugin
+ // (can't be freed by Variables Plugin
+ // automatically). This should be used if
+ // VRF_FREEMEM is not specified in the flags.
+
+// Available Flags for TOKENREGISTER:
+#define TRF_FREEMEM 0x01 // Variables Plugin will automatically free the
+ // pointer returned by the parse function or
+ // service (which method it will us is
+ // specified in memType -> see above).
+#define TRF_CLEANUP 0x02 // Call cleanup service or function, notifying
+ // that the returned buffer can be freed.
+ // Normally you should use either TRF_FREEMEM
+ // or TRF_CLEANUP.
+#define TRF_PARSEFUNC 0x40 // parseFunction will be used instead of a
+ // service.
+#define TRF_CLEANUPFUNC 0x80 // cleanupFunction will be used instead of a
+ // service.
+#define TRF_USEFUNCS TRF_PARSEFUNC|TRF_CLEANUPFUNC
+#define TRF_UNPARSEDARGS 0x04 // Provide the arguments for the parse
+ // function in their raw (unparsed) form.
+ // By default, arguments are parsed before
+ // presenting them to the parse function.
+#define TRF_FIELD 0x08 // The token can be used as a %field%.
+#define TRF_FUNCTION 0x10 // The token can be used as a ?function().
+ // Normally you should use either TRF_FIELD or
+ // TRF_FUNCTION.
+#define TRF_UNICODE 0x20 // Strings in structure are unicode (WCHAR*).
+ // In this case, the strings pointing to the
+ // arguments in the ARGUMENTS struct are
+ // unicode also. The returned buffer is
+ // expected to be unicode also, and the
+ // unicode parse and cleanup functions are
+ // called.
+
+#if defined(UNICODE) || defined(_UNICODE)
+#define TRF_TCHAR TRF_UNICODE // Strings in structure are TCHAR*.
+#else
+#define TRF_TCHAR 0
+#endif
+
+// Deprecated:
+#define TRF_CALLSVC TRF_CLEANUP
+
+// Callback Service (szService) / parseFunction:
+// ------------------------
+// Service that is called automatically by the Variable's Plugin to resolve a
+// registered variable.
+
+// Parameters:
+// wParam = 0
+// lParam = (LPARAM)(ARGUMENTSINFO *)&ai
+// see above
+
+// Return Value:
+// Needs to return the pointer to a dynamically allocacated string or NULL.
+// A return value of NULL is regarded as an error (eCount will be increaded).
+// Flags in the ARGUMENTSINFO struct can be set (see above).
+
+// Callback Service (szCallbackService) / cleanupFunction:
+// ------------------------
+// This service is called when the memory that was allocated by the parse
+// function or service can be freed. Note: It will only be called when the
+// flag VRF_CLEANUP of TOKENREGISTER is set.
+
+// Parameters:
+// wParam = 0
+// lParam = (LPARAM)(char *)&res
+// Result from parse function or service (pointer to a string).
+
+// Return Value:
+// Should return 0 on success.
+
+
+
+// --------------------------------------------------------------------------
+// Show the help dialog
+// --------------------------------------------------------------------------
+
+// Plugins can invoke Variables' help dialog which can be used for easy input
+// by users.
+
+#define MS_VARS_SHOWHELPEX "Vars/ShowHelpEx"
+
+// This service can be used to open the help dialog of Variables. This dialog
+// provides easy input for the user and/or information about the available
+// tokens.
+
+// Parameters:
+// ------------------------
+// wParam = (WPARAM)(HWND)hwndParent
+// lParam = (LPARAM)(VARHELPINFO)&vhi
+// See below.
+
+// Return Value:
+// ------------------------
+// Returns 0 on succes, any other value on error.
+
+typedef struct {
+ int cbSize; // Set to sizeof(VARHELPINFO).
+ FORMATINFO *fi; // Used for both input and output. If this pointer is not
+ // NULL, the information is used as the initial values for
+ // the dialog.
+ HWND hwndCtrl; // Used for both input and output. The window text of this
+ // window will be read and used as the initial input of the
+ // input dialog. If the user presses the OK button the window
+ // text of this window will be set to the text of the input
+ // field and a EN_CHANGE message via WM_COMMAND is send to
+ // this window. (Can be NULL).
+ char *szSubjectDesc; // The description of the %subject% token will be set
+ // to this text, if not NULL. This is translated
+ // automatically.
+ char *szExtraTextDesc; // The description of the %extratext% token will be
+ // set to this text, if not NULL. This is translated
+ // automatically.
+ int flags; // Flags, see below.
+} VARHELPINFO;
+
+
+// Flags for VARHELPINFO
+#define VHF_TOKENS 0x00000001 // Create a dialog with the list of
+ // tokens
+#define VHF_INPUT 0x00000002 // Create a dialog with an input
+ // field (this contains the list of
+ // tokens as well).
+#define VHF_SUBJECT 0x00000004 // Create a dialog to select a
+ // contact for the %subject% token.
+#define VHF_EXTRATEXT 0x00000008 // Create a dialog to enter a text
+ // for the %extratext% token.
+#define VHF_HELP 0x00000010 // Create a dialog with help info.
+#define VHF_HIDESUBJECTTOKEN 0x00000020 // Hide the %subject% token in the
+ // list of tokens.
+#define VHF_HIDEEXTRATEXTTOKEN 0x00000040 // Hide the %extratext% token in
+ // the list of tokens.
+#define VHF_DONTFILLSTRUCT 0x00000080 // Don't fill the struct with the
+ // new information if OK is pressed
+#define VHF_FULLFILLSTRUCT 0x00000100 // Fill all members of the struct
+ // when OK is pressed. By default
+ // only szFormat is set. With this
+ // flag on, hContact and
+ // szExtraText are also set.
+#define VHF_SETLASTSUBJECT 0x00000200 // Set the last contact that was
+ // used in the %subject% dialog in
+ // case fi.hContact is NULL.
+
+// Predefined flags
+#define VHF_FULLDLG VHF_INPUT|VHF_SUBJECT|VHF_EXTRATEXT|VHF_HELP
+#define VHF_SIMPLEDLG VHF_INPUT|VHF_HELP
+#define VHF_NOINPUTDLG VHF_TOKENS|VHF_HELP
+
+// If the service fills information in the struct for szFormat or szExtraText,
+// these members must be free'd using the free function of Variables.
+// If wParam==NULL, the dialog is created modeless. Only one dialog can be
+// shown at the time.
+// If both hwndCtrl and fi are NULL, the user input will not be retrievable.
+// In this case, the dialog is created with only a "Close" button, instead of
+// the "OK" and "Cancel" buttons.
+// In case of modeless dialog and fi != NULL, please make sure this pointer
+// stays valid while the dialog is open.
+
+// Helper function for easy use in standard case:
+#ifndef VARIABLES_NOHELPER
+__inline static int variables_showhelp(HWND hwndDlg, UINT uIDEdit, int flags, char *szSubjectDesc, char *szExtraDesc) {
+
+ VARHELPINFO vhi;
+
+ ZeroMemory(&vhi, sizeof(VARHELPINFO));
+ vhi.cbSize = sizeof(VARHELPINFO);
+ if (flags == 0) {
+ flags = VHF_SIMPLEDLG;
+ }
+ vhi.flags = flags;
+ vhi.hwndCtrl = GetDlgItem(hwndDlg, uIDEdit);
+ vhi.szSubjectDesc = szSubjectDesc;
+ vhi.szExtraTextDesc = szExtraDesc;
+
+ return CallService(MS_VARS_SHOWHELPEX, (WPARAM)hwndDlg, (LPARAM)&vhi);
+}
+#endif
+
+
+#define MS_VARS_GETSKINITEM "Vars/GetSkinItem"
+
+// This service can be used to get the icon you can use for example on the
+// Variables help button in your options screen. You can also get the tooltip
+// text to use with such a button. If icon library is available the icon will
+// be retrieved from icon library manager, otherwise the default is returned.
+
+// Parameters:
+// ------------------------
+// wParam = (WPARAM)0
+// lParam = (LPARAM)VSI_* (see below)
+
+// Return Value:
+// ------------------------
+// Depends on the information to retrieve (see below).
+
+// VSI_ constants
+#define VSI_HELPICON 1 // Can be used on the button accessing the
+ // Variables help dialog. Returns (HICON)hIcon on
+ // success or NULL on failure;
+#define VSI_HELPTIPTEXT 2 // Returns the tooltip text you can use for the
+ // help button. Returns (char *)szTipText, a
+ // static, translated buffer containing the help
+ // text or NULL on error.
+
+// Helper to set the icon on a button accessing the help dialog.
+// Preferably a 16x14 MButtonClass control, but it works on a standard
+// button control as well. If no icon is availble (because of old version of
+// Variables) the string "V" is shown on the button. If Variables is not
+// available, the button will be hidden.
+#ifndef VARIABLES_NOHELPER
+__inline static int variables_skin_helpbutton(HWND hwndDlg, UINT uIDButton) {
+
+ int res;
+ HICON hIcon;
+ TCHAR tszClass[32];
+
+ hIcon = NULL;
+ res = 0;
+ if (ServiceExists(MS_VARS_GETSKINITEM)) {
+ hIcon = (HICON)CallService(MS_VARS_GETSKINITEM, 0, (LPARAM)VSI_HELPICON);
+ }
+ GetClassName(GetDlgItem(hwndDlg, uIDButton), tszClass, sizeof(tszClass));
+ if (!_tcscmp(tszClass, _T("Button"))) {
+ if (hIcon != NULL) {
+ SetWindowLong(GetDlgItem(hwndDlg, uIDButton), GWL_STYLE, GetWindowLong(GetDlgItem(hwndDlg, uIDButton), GWL_STYLE)|BS_ICON);
+ SendMessage(GetDlgItem(hwndDlg, uIDButton), BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hIcon);
+ }
+ else {
+ SetWindowLong(GetDlgItem(hwndDlg, uIDButton), GWL_STYLE, GetWindowLong(GetDlgItem(hwndDlg, uIDButton), GWL_STYLE)&~BS_ICON);
+ SetDlgItemText(hwndDlg, uIDButton, _T("V"));
+ }
+ }
+ else if (!_tcscmp(tszClass, MIRANDABUTTONCLASS)) {
+ if (hIcon != NULL) {
+ char *szTipInfo;
+
+ SendMessage(GetDlgItem(hwndDlg, uIDButton), BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hIcon);
+ if (ServiceExists(MS_VARS_GETSKINITEM)) {
+ szTipInfo = (char *)CallService(MS_VARS_GETSKINITEM, 0, (LPARAM)VSI_HELPTIPTEXT);
+ }
+ if (szTipInfo == NULL) {
+ szTipInfo = Translate("Open String Formatting Help");
+ }
+ SendMessage(GetDlgItem(hwndDlg, uIDButton), BUTTONADDTOOLTIP, (WPARAM)szTipInfo, 0);
+ SendDlgItemMessage(hwndDlg, uIDButton, BUTTONSETASFLATBTN, 0, 0);
+ }
+ else {
+ SetDlgItemText(hwndDlg, uIDButton, _T("V"));
+ }
+ }
+ else {
+ res = -1;
+ }
+ ShowWindow(GetDlgItem(hwndDlg, uIDButton), ServiceExists(MS_VARS_FORMATSTRING));
+
+ return res;
+}
+#endif
+
+
+#define MS_VARS_SHOWHELP "Vars/ShowHelp"
+
+// WARNING: This service is obsolete, please use MS_VARS_SHOWHELPEX
+
+// Shows a help dialog where all possible tokens are displayed. The tokens
+// are explained on the dialog, too. The user can edit the initial string and
+// insert as many tokens as he likes.
+
+// Parameters:
+// ------------------------
+// wParam = (HWND)hwndEdit
+// Handle to an edit control in which the modified string
+// should be inserted (When the user clicks OK in the dialog the edited
+// string will be set to hwndEdit) (can be NULL).
+// lParam = (char *)pszInitialString
+// String that the user is provided with initially when
+// the dialog gets opened (If this is NULL then the current text in the
+// hwndEdit edit control will be used) (can be NULL).
+
+// Return Value:
+// ------------------------
+// Returns the handle to the help dialog (HWND).
+
+// Note: Only one help dialog can be opened at a time. When the dialog gets
+// closed an EN_CHANGE of the edit controll will be triggered because the
+// contents were updated. (Only when user selected OK).
+
+// Example:
+// CallService(MS_VARS_SHOWHELP, (WPARAM)hwndEdit, (LPARAM)"some initial text");
+
+// --------------------------------------------------------------------------
+// Retrieve a contact's HANDLE given a string
+// --------------------------------------------------------------------------
+
+#define MS_VARS_GETCONTACTFROMSTRING "Vars/GetContactFromString"
+
+// Searching for contacts in the database. You can find contacts in db by
+// searching for their name, e.g first name.
+
+// Parameters:
+// ------------------------
+// wParam = (WPARAM)(CONTACTSINFO *)&ci
+// See below.
+// lParam = 0
+
+// Return Value:
+// ------------------------
+// Returns number of contacts found matching the given string representation.
+// The hContacts array of CONTACTSINFO struct contains these hContacts after
+// the call.
+
+// Note: The hContacts array needs to be freed after use using
+// MS_VARS_FREEMEMORY.
+
+typedef struct {
+ int cbSize; // Set this to sizeof(CONTACTSINFO).
+ union {
+ char *szContact; // String to search for, e.g. last name (can't be NULL).
+ WCHAR *wszContact;
+ TCHAR *tszContact;
+ };
+ HANDLE *hContacts; // (output) Array of contacts found.
+ DWORD flags; // Contact details that will be matched with the search
+ // string (flags can be combined).
+} CONTACTSINFO;
+
+// Possible flags:
+#define CI_PROTOID 0x00000001 // The contact in the string is encoded
+ // in the format <PROTOID:UNIQUEID>, e.g.
+ // <ICQ:12345678>.
+#define CI_NICK 0x00000002 // Search nick names.
+#define CI_LISTNAME 0x00000004 // Search custom names shown in contact
+ // list.
+#define CI_FIRSTNAME 0x00000008 // Search contact's first names (contact
+ // details).
+#define CI_LASTNAME 0x00000010 // Search contact's last names (contact
+ // details).
+#define CI_EMAIL 0x00000020 // Search contact's email adresses
+ // (contact details).
+#define CI_UNIQUEID 0x00000040 // Search unique ids of the contac, e.g.
+ // UIN.
+#define CI_CNFINFO 0x40000000 // Searches one of the CNF_* flags (set
+ // flags to CI_CNFINFO|CNF_X), only one
+ // CNF_ type possible
+#define CI_UNICODE 0x80000000 // tszContact is a unicode string
+ // (WCHAR*).
+
+#if defined(UNICODE) || defined(_UNICODE)
+#define CI_TCHAR CI_UNICODE // Strings in structure are TCHAR*.
+#else
+#define CI_TCHAR 0
+#endif
+
+
+
+#endif //__M_VARS
diff --git a/Plugins/eSpeak/types.cpp b/Plugins/eSpeak/types.cpp new file mode 100644 index 0000000..6bab65f --- /dev/null +++ b/Plugins/eSpeak/types.cpp @@ -0,0 +1,232 @@ +/*
+Copyright (C) 2007 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"
+
+
+// Prototypes ///////////////////////////////////////////////////////////////////////////
+
+static HANDLE hServices[2] = {0};
+
+int SortTypes(const SPEAK_TYPE *type1, const SPEAK_TYPE *type2);
+int RegisterService(WPARAM wParam, LPARAM lParam);
+int SpeakExService(WPARAM wParam, LPARAM lParam);
+
+LIST<SPEAK_TYPE> types(20, SortTypes);
+
+
+// Functions ////////////////////////////////////////////////////////////////////////////
+
+
+void InitTypes()
+{
+ hServices[0] = CreateServiceFunction(MS_SPEAK_REGISTER, RegisterService);
+ hServices[1] = CreateServiceFunction(MS_SPEAK_SAYEX, SpeakExService);
+}
+
+void FreeTypes()
+{
+ // Destroy services
+ int i;
+ for(i = 0; i < MAX_REGS(hServices); ++i)
+ DestroyServiceFunction(hServices[i]);
+
+ // Free internal structs
+ for(i = 0; i < types.getCount(); i++)
+ {
+ SPEAK_TYPE *type = types[i];
+ mir_free((void *) type->module);
+ mir_free((void *) type->name);
+ mir_free((void *) type->icon);
+
+ if (type->numTemplates > 0)
+ {
+ for(int i = 0; i < type->numTemplates; i++)
+ mir_free((void *) type->templates[i]);
+ mir_free(type->templates);
+ }
+
+ mir_free(type);
+ }
+
+ types.destroy();
+}
+
+
+int SortTypes(const SPEAK_TYPE *type1, const SPEAK_TYPE *type2)
+{
+ return stricmp(type1->description, type2->description);
+}
+
+
+int RegisterService(WPARAM wParam, LPARAM lParam)
+{
+ SPEAK_TYPE *orig = (SPEAK_TYPE *) wParam;
+ if (orig == NULL || orig->cbSize < sizeof(SPEAK_TYPE)
+ || orig->name == NULL || orig->description == NULL)
+ return -1;
+
+ SPEAK_TYPE *type = (SPEAK_TYPE *) mir_alloc0(sizeof(SPEAK_TYPE));
+ type->cbSize = orig->cbSize;
+ type->module = mir_strdup(orig->module);
+ type->name = mir_strdup(orig->name);
+ type->description = Translate(mir_strdup(orig->description));
+ type->icon = mir_strdup(orig->icon);
+ type->numTemplates = orig->numTemplates;
+
+ if (orig->numTemplates > 0)
+ {
+ type->templates = (char **) mir_alloc0(orig->numTemplates * sizeof(char *));
+ for(int i = 0; i < orig->numTemplates; i++)
+ type->templates[i] = mir_strdup(orig->templates[i] == NULL ? "" : orig->templates[i]);
+ }
+
+ types.insert(type);
+
+ return 0;
+}
+
+SPEAK_TYPE *GetType(const char *name)
+{
+ for(int i = 0; i < types.getCount(); i++)
+ {
+ SPEAK_TYPE *type = types[i];
+ if (strcmp(type->name, name) == 0)
+ return type;
+ }
+
+ return NULL;
+}
+
+void GetTemplate(Buffer<TCHAR> *buffer, SPEAK_TYPE *type, int templ)
+{
+ DBVARIANT dbv;
+
+ char setting[128];
+ mir_snprintf(setting, MAX_REGS(setting), "%s_%d_" TEMPLATE_TEXT, type->name, templ);
+
+ if (!DBGetContactSettingTString(NULL, type->module == NULL ? MODULE_NAME : type->module, setting, &dbv))
+ {
+ buffer->append(dbv.ptszVal);
+ DBFreeVariant(&dbv);
+ }
+ else
+ {
+ // Get default
+ const char *tmp = type->templates[templ];
+ tmp = strchr(tmp, '\n');
+ if (tmp == NULL)
+ return;
+ tmp++;
+ const char *end = strchr(tmp, '\n');
+ size_t len = (end == NULL ? strlen(tmp) : end - tmp);
+
+#ifdef UNICODE
+ MultiByteToWideChar(CP_ACP, 0, tmp, len, buffer->appender(len), len);
+#else
+ buffer->append(tmp, len);
+#endif
+ }
+}
+
+static TCHAR** CopyVariablesToUnicode(SPEAK_ITEM *item)
+{
+ TCHAR **variables = NULL;
+ if (item->numVariables > 0)
+ {
+ variables = (TCHAR **) mir_alloc0(item->numVariables * sizeof(TCHAR *));
+ for(int i = 0; i < item->numVariables; i++)
+ {
+ if (item->flags & SPEAK_CHAR)
+ variables[i] = mir_a2t(((char **) item->variables)[i]);
+ else
+ variables[i] = mir_u2t(((WCHAR **) item->variables)[i]);
+ }
+ }
+ return variables;
+}
+
+static void FreeVariablesCopy(SPEAK_ITEM *item, TCHAR **variables)
+{
+ if (item->numVariables > 0 && variables != NULL)
+ {
+ for(int i = 0; i < item->numVariables; i++)
+ {
+ mir_free(variables[i]);
+ }
+ mir_free(variables);
+ }
+}
+
+int SpeakExService(WPARAM wParam, LPARAM lParam)
+{
+ SPEAK_ITEM *item = (SPEAK_ITEM *) wParam;
+ if (item == NULL || item->cbSize < sizeof(SPEAK_ITEM))
+ return -1;
+
+ SPEAK_TYPE *type = GetType(item->type);
+ if (type == NULL)
+ return -2;
+
+ // Get the text to speak
+ if (item->templateNum < 0)
+ {
+ if (item->text == NULL)
+ return -3;
+
+ if (!GetSettingBool(type, TEMPLATE_ENABLED, FALSE))
+ return 1;
+
+ Buffer<TCHAR> buff;
+ if (GetSettingBool(type, SPEAK_NAME, TRUE) && item->hContact != NULL && item->hContact != (HANDLE) -1)
+ {
+ buff.append((TCHAR *) CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM) item->hContact, GCDNF_TCHAR));
+ buff.append(_T(" : "));
+ }
+
+ if (item->flags & SPEAK_CHAR)
+ buff.append((char *) item->text);
+ else
+ buff.append((WCHAR *) item->text);
+
+ return SpeakService(item->hContact, buff.detach());
+ }
+ else
+ {
+ if (!GetSettingBool(type, item->templateNum, TEMPLATE_ENABLED, FALSE))
+ return 1;
+
+ Buffer<TCHAR> templ;
+ GetTemplate(&templ, type, item->templateNum);
+ templ.pack();
+
+ Buffer<TCHAR> buffer;
+ TCHAR **variables = CopyVariablesToUnicode(item);
+ ReplaceTemplate(&buffer, item->hContact, templ.str, variables, item->numVariables);
+ FreeVariablesCopy(item, variables);
+
+ if (buffer.str == NULL)
+ return -3;
+
+ return SpeakService(item->hContact, buffer.detach());
+ }
+}
+
+
+
diff --git a/Plugins/eSpeak/types.h b/Plugins/eSpeak/types.h new file mode 100644 index 0000000..faede0b --- /dev/null +++ b/Plugins/eSpeak/types.h @@ -0,0 +1,34 @@ +/*
+Copyright (C) 2007 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.
+*/
+
+
+#ifndef __TYPES_H__
+# define __TYPES_H__
+
+
+extern LIST<SPEAK_TYPE> types;
+
+
+void InitTypes();
+void FreeTypes();
+
+void GetTemplate(Buffer<TCHAR> *buffer, SPEAK_TYPE *type, int templ);
+
+
+#endif // __TYPES_H__
|