From e9bf8a6e2d782dc480fb97cb59928c8cfe1dd777 Mon Sep 17 00:00:00 2001 From: pescuma Date: Mon, 10 Oct 2011 01:39:18 +0000 Subject: Moved files from BerliOS git-svn-id: http://pescuma.googlecode.com/svn/trunk/Miranda@229 c086bb3d-8645-0410-b8da-73a8550f86e7 --- Plugins/eSpeak/Docs/langpack_meSpeak.txt | 2 + Plugins/eSpeak/Docs/meSpeak.png | Bin 0 -> 34081 bytes Plugins/eSpeak/Docs/meSpeak_changelog.txt | 57 + Plugins/eSpeak/Docs/meSpeak_readme.txt | 30 + Plugins/eSpeak/Docs/meSpeak_version.txt | 1 + Plugins/eSpeak/ZIP/doit.bat | 132 + Plugins/eSpeak/commons.h | 243 ++ Plugins/eSpeak/eSpeak.cpp | 1348 ++++++++ Plugins/eSpeak/eSpeak.dsp | 383 +++ Plugins/eSpeak/eSpeak.dsw | 29 + Plugins/eSpeak/eSpeak.sln | 26 + Plugins/eSpeak/eSpeak.vcproj | 1402 ++++++++ Plugins/eSpeak/eSpeak/StdAfx.h | 3 + Plugins/eSpeak/eSpeak/compiledict.cpp | 1649 ++++++++++ Plugins/eSpeak/eSpeak/debug.cpp | 74 + Plugins/eSpeak/eSpeak/debug.h | 26 + Plugins/eSpeak/eSpeak/dictionary.cpp | 3433 ++++++++++++++++++++ Plugins/eSpeak/eSpeak/espeak_command.h | 129 + Plugins/eSpeak/eSpeak/event.h | 51 + Plugins/eSpeak/eSpeak/fifo.h | 58 + Plugins/eSpeak/eSpeak/intonation.cpp | 1104 +++++++ Plugins/eSpeak/eSpeak/klatt.cpp | 1282 ++++++++ Plugins/eSpeak/eSpeak/klatt.h | 138 + Plugins/eSpeak/eSpeak/mbrolib.h | 205 ++ Plugins/eSpeak/eSpeak/numbers.cpp | 1401 ++++++++ Plugins/eSpeak/eSpeak/phoneme.h | 168 + Plugins/eSpeak/eSpeak/phonemelist.cpp | 687 ++++ Plugins/eSpeak/eSpeak/portaudio.h | 466 +++ Plugins/eSpeak/eSpeak/portaudio18.h | 466 +++ Plugins/eSpeak/eSpeak/portaudio19.h | 1127 +++++++ Plugins/eSpeak/eSpeak/readclause.cpp | 2402 ++++++++++++++ Plugins/eSpeak/eSpeak/setlengths.cpp | 673 ++++ Plugins/eSpeak/eSpeak/sintab.h | 258 ++ Plugins/eSpeak/eSpeak/speak_lib.cpp | 1153 +++++++ Plugins/eSpeak/eSpeak/speak_lib.h | 604 ++++ Plugins/eSpeak/eSpeak/speech.h | 84 + Plugins/eSpeak/eSpeak/stdint.h | 4 + Plugins/eSpeak/eSpeak/synth_mbrola.cpp | 760 +++++ Plugins/eSpeak/eSpeak/synthdata.cpp | 681 ++++ Plugins/eSpeak/eSpeak/synthesize.cpp | 1660 ++++++++++ Plugins/eSpeak/eSpeak/synthesize.h | 349 ++ Plugins/eSpeak/eSpeak/tr_languages.cpp | 1310 ++++++++ Plugins/eSpeak/eSpeak/translate.cpp | 2771 ++++++++++++++++ Plugins/eSpeak/eSpeak/translate.h | 576 ++++ Plugins/eSpeak/eSpeak/voice.h | 81 + Plugins/eSpeak/eSpeak/voices.cpp | 1743 ++++++++++ Plugins/eSpeak/eSpeak/wave.h | 43 + Plugins/eSpeak/eSpeak/wavegen.cpp | 1917 +++++++++++ Plugins/eSpeak/espeak-data/af_dict | Bin 0 -> 65248 bytes Plugins/eSpeak/espeak-data/ca_dict | Bin 0 -> 4231 bytes Plugins/eSpeak/espeak-data/config | 9 + Plugins/eSpeak/espeak-data/cs_dict | Bin 0 -> 7833 bytes Plugins/eSpeak/espeak-data/cy_dict | Bin 0 -> 3541 bytes Plugins/eSpeak/espeak-data/de_dict | Bin 0 -> 19044 bytes Plugins/eSpeak/espeak-data/el_dict | Bin 0 -> 4767 bytes Plugins/eSpeak/espeak-data/en_dict | Bin 0 -> 82255 bytes Plugins/eSpeak/espeak-data/eo_dict | Bin 0 -> 3467 bytes Plugins/eSpeak/espeak-data/es_dict | Bin 0 -> 5489 bytes Plugins/eSpeak/espeak-data/fi_dict | Bin 0 -> 4570 bytes Plugins/eSpeak/espeak-data/fr_dict | Bin 0 -> 16820 bytes Plugins/eSpeak/espeak-data/grc_dict | Bin 0 -> 3390 bytes Plugins/eSpeak/espeak-data/hbs_dict | Bin 0 -> 7444 bytes Plugins/eSpeak/espeak-data/hi_dict | Bin 0 -> 5825 bytes Plugins/eSpeak/espeak-data/hu_dict | Bin 0 -> 5266 bytes Plugins/eSpeak/espeak-data/hy_dict | Bin 0 -> 1752 bytes Plugins/eSpeak/espeak-data/id_dict | Bin 0 -> 3083 bytes Plugins/eSpeak/espeak-data/is_dict | Bin 0 -> 5550 bytes Plugins/eSpeak/espeak-data/it_dict | Bin 0 -> 6457 bytes Plugins/eSpeak/espeak-data/jbo_dict | Bin 0 -> 2025 bytes Plugins/eSpeak/espeak-data/ku_dict | Bin 0 -> 2277 bytes Plugins/eSpeak/espeak-data/la_dict | Bin 0 -> 3911 bytes Plugins/eSpeak/espeak-data/lv_dict | Bin 0 -> 5758 bytes Plugins/eSpeak/espeak-data/mbrola_ph/af1_phtrans | Bin 0 -> 1636 bytes Plugins/eSpeak/espeak-data/mbrola_ph/ca1_phtrans | Bin 0 -> 1372 bytes Plugins/eSpeak/espeak-data/mbrola_ph/cr1_phtrans | Bin 0 -> 2140 bytes Plugins/eSpeak/espeak-data/mbrola_ph/cs_phtrans | Bin 0 -> 580 bytes Plugins/eSpeak/espeak-data/mbrola_ph/de2_phtrans | Bin 0 -> 1444 bytes Plugins/eSpeak/espeak-data/mbrola_ph/de4_phtrans | Bin 0 -> 1588 bytes Plugins/eSpeak/espeak-data/mbrola_ph/de6_phtrans | Bin 0 -> 1204 bytes Plugins/eSpeak/espeak-data/mbrola_ph/en1_phtrans | Bin 0 -> 772 bytes Plugins/eSpeak/espeak-data/mbrola_ph/es_phtrans | Bin 0 -> 1684 bytes Plugins/eSpeak/espeak-data/mbrola_ph/fr1_phtrans | Bin 0 -> 1780 bytes Plugins/eSpeak/espeak-data/mbrola_ph/gr2_phtrans | Bin 0 -> 2212 bytes .../eSpeak/espeak-data/mbrola_ph/grc-de6_phtrans | Bin 0 -> 484 bytes Plugins/eSpeak/espeak-data/mbrola_ph/hu1_phtrans | Bin 0 -> 1396 bytes Plugins/eSpeak/espeak-data/mbrola_ph/id1_phtrans | Bin 0 -> 820 bytes Plugins/eSpeak/espeak-data/mbrola_ph/in1_phtrans | Bin 0 -> 1156 bytes Plugins/eSpeak/espeak-data/mbrola_ph/it3_phtrans | Bin 0 -> 892 bytes Plugins/eSpeak/espeak-data/mbrola_ph/la1_phtrans | Bin 0 -> 748 bytes Plugins/eSpeak/espeak-data/mbrola_ph/nl_phtrans | Bin 0 -> 1612 bytes Plugins/eSpeak/espeak-data/mbrola_ph/pl1_phtrans | Bin 0 -> 1540 bytes Plugins/eSpeak/espeak-data/mbrola_ph/pt_phtrans | Bin 0 -> 2092 bytes Plugins/eSpeak/espeak-data/mbrola_ph/ptbr4_phtrans | Bin 0 -> 2356 bytes Plugins/eSpeak/espeak-data/mbrola_ph/ptbr_phtrans | Bin 0 -> 2500 bytes Plugins/eSpeak/espeak-data/mbrola_ph/ro1_phtrans | Bin 0 -> 2116 bytes Plugins/eSpeak/espeak-data/mbrola_ph/sv2_phtrans | Bin 0 -> 1564 bytes Plugins/eSpeak/espeak-data/mbrola_ph/sv_phtrans | Bin 0 -> 1564 bytes Plugins/eSpeak/espeak-data/mbrola_ph/us3_phtrans | Bin 0 -> 988 bytes Plugins/eSpeak/espeak-data/mbrola_ph/us_phtrans | Bin 0 -> 1060 bytes Plugins/eSpeak/espeak-data/mk_dict | Bin 0 -> 4955 bytes Plugins/eSpeak/espeak-data/nl_dict | Bin 0 -> 4124 bytes Plugins/eSpeak/espeak-data/no_dict | Bin 0 -> 3735 bytes Plugins/eSpeak/espeak-data/phondata | Bin 0 -> 292152 bytes Plugins/eSpeak/espeak-data/phonindex | Bin 0 -> 26836 bytes Plugins/eSpeak/espeak-data/phontab | Bin 0 -> 32824 bytes Plugins/eSpeak/espeak-data/pl_dict | Bin 0 -> 40527 bytes Plugins/eSpeak/espeak-data/pt_dict | Bin 0 -> 15111 bytes Plugins/eSpeak/espeak-data/ro_dict | Bin 0 -> 24984 bytes Plugins/eSpeak/espeak-data/ru_dict | Bin 0 -> 5701 bytes Plugins/eSpeak/espeak-data/sk_dict | Bin 0 -> 8762 bytes Plugins/eSpeak/espeak-data/sq_dict | Bin 0 -> 2114 bytes Plugins/eSpeak/espeak-data/sv_dict | Bin 0 -> 9508 bytes Plugins/eSpeak/espeak-data/sw_dict | Bin 0 -> 3048 bytes Plugins/eSpeak/espeak-data/ta_dict | Bin 0 -> 2582 bytes Plugins/eSpeak/espeak-data/tr_dict | Bin 0 -> 4768 bytes Plugins/eSpeak/espeak-data/vi_dict | Bin 0 -> 4265 bytes Plugins/eSpeak/espeak-data/voices/!v/croak | 11 + Plugins/eSpeak/espeak-data/voices/!v/f1 | 18 + Plugins/eSpeak/espeak-data/voices/!v/f2 | 20 + Plugins/eSpeak/espeak-data/voices/!v/f3 | 22 + Plugins/eSpeak/espeak-data/voices/!v/f4 | 18 + Plugins/eSpeak/espeak-data/voices/!v/m1 | 19 + Plugins/eSpeak/espeak-data/voices/!v/m2 | 15 + Plugins/eSpeak/espeak-data/voices/!v/m3 | 16 + Plugins/eSpeak/espeak-data/voices/!v/m4 | 17 + Plugins/eSpeak/espeak-data/voices/!v/m5 | 15 + Plugins/eSpeak/espeak-data/voices/!v/m6 | 13 + Plugins/eSpeak/espeak-data/voices/!v/wisper | 13 + Plugins/eSpeak/espeak-data/voices/af | 8 + Plugins/eSpeak/espeak-data/voices/bs | 16 + Plugins/eSpeak/espeak-data/voices/ca | 4 + Plugins/eSpeak/espeak-data/voices/cs | 4 + Plugins/eSpeak/espeak-data/voices/cy | 5 + Plugins/eSpeak/espeak-data/voices/de | 5 + Plugins/eSpeak/espeak-data/voices/default | 6 + Plugins/eSpeak/espeak-data/voices/el | 5 + Plugins/eSpeak/espeak-data/voices/en/en | 11 + Plugins/eSpeak/espeak-data/voices/en/en-n | 14 + Plugins/eSpeak/espeak-data/voices/en/en-rp | 12 + Plugins/eSpeak/espeak-data/voices/en/en-sc | 16 + Plugins/eSpeak/espeak-data/voices/en/en-wi | 19 + Plugins/eSpeak/espeak-data/voices/en/en-wm | 12 + Plugins/eSpeak/espeak-data/voices/eo | 4 + Plugins/eSpeak/espeak-data/voices/es | 7 + Plugins/eSpeak/espeak-data/voices/es-la | 11 + Plugins/eSpeak/espeak-data/voices/fi | 4 + Plugins/eSpeak/espeak-data/voices/fr | 7 + Plugins/eSpeak/espeak-data/voices/fr-be | 7 + Plugins/eSpeak/espeak-data/voices/hi | 9 + Plugins/eSpeak/espeak-data/voices/hr | 18 + Plugins/eSpeak/espeak-data/voices/hu | 3 + Plugins/eSpeak/espeak-data/voices/id | 8 + Plugins/eSpeak/espeak-data/voices/is | 4 + Plugins/eSpeak/espeak-data/voices/it | 6 + Plugins/eSpeak/espeak-data/voices/ku | 6 + Plugins/eSpeak/espeak-data/voices/la | 13 + Plugins/eSpeak/espeak-data/voices/lv | 6 + Plugins/eSpeak/espeak-data/voices/mb/mb-af1 | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-af1-en | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-br1 | 9 + Plugins/eSpeak/espeak-data/voices/mb/mb-br3 | 9 + Plugins/eSpeak/espeak-data/voices/mb/mb-br4 | 9 + Plugins/eSpeak/espeak-data/voices/mb/mb-cr1 | 9 + Plugins/eSpeak/espeak-data/voices/mb/mb-cz2 | 6 + Plugins/eSpeak/espeak-data/voices/mb/mb-de2 | 6 + Plugins/eSpeak/espeak-data/voices/mb/mb-de4 | 6 + Plugins/eSpeak/espeak-data/voices/mb/mb-de4-en | 6 + Plugins/eSpeak/espeak-data/voices/mb/mb-de5 | 10 + Plugins/eSpeak/espeak-data/voices/mb/mb-de5-en | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-de6 | 6 + Plugins/eSpeak/espeak-data/voices/mb/mb-de6-grc | 6 + Plugins/eSpeak/espeak-data/voices/mb/mb-de7 | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-en1 | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-es1 | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-es2 | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-fr1 | 9 + Plugins/eSpeak/espeak-data/voices/mb/mb-fr1-en | 8 + Plugins/eSpeak/espeak-data/voices/mb/mb-fr4 | 8 + Plugins/eSpeak/espeak-data/voices/mb/mb-fr4-en | 8 + Plugins/eSpeak/espeak-data/voices/mb/mb-gr2 | 6 + Plugins/eSpeak/espeak-data/voices/mb/mb-gr2-en | 6 + Plugins/eSpeak/espeak-data/voices/mb/mb-hu1 | 6 + Plugins/eSpeak/espeak-data/voices/mb/mb-hu1-en | 6 + Plugins/eSpeak/espeak-data/voices/mb/mb-id1 | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-it3 | 8 + Plugins/eSpeak/espeak-data/voices/mb/mb-it4 | 8 + Plugins/eSpeak/espeak-data/voices/mb/mb-la1 | 6 + Plugins/eSpeak/espeak-data/voices/mb/mb-nl2 | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-nl2-en | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-pl1 | 6 + Plugins/eSpeak/espeak-data/voices/mb/mb-pl1-en | 6 + Plugins/eSpeak/espeak-data/voices/mb/mb-pt1 | 9 + Plugins/eSpeak/espeak-data/voices/mb/mb-ro1 | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-ro1-en | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-sw1 | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-sw1-en | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-sw2 | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-sw2-en | 7 + Plugins/eSpeak/espeak-data/voices/mb/mb-us1 | 12 + Plugins/eSpeak/espeak-data/voices/mb/mb-us2 | 12 + Plugins/eSpeak/espeak-data/voices/mb/mb-us3 | 12 + Plugins/eSpeak/espeak-data/voices/mk | 4 + Plugins/eSpeak/espeak-data/voices/nl | 3 + Plugins/eSpeak/espeak-data/voices/no | 6 + Plugins/eSpeak/espeak-data/voices/pl | 5 + Plugins/eSpeak/espeak-data/voices/pt | 7 + Plugins/eSpeak/espeak-data/voices/pt-pt | 7 + Plugins/eSpeak/espeak-data/voices/ro | 5 + Plugins/eSpeak/espeak-data/voices/ru | 6 + Plugins/eSpeak/espeak-data/voices/sk | 4 + Plugins/eSpeak/espeak-data/voices/sr | 15 + Plugins/eSpeak/espeak-data/voices/sv | 4 + Plugins/eSpeak/espeak-data/voices/sw | 4 + Plugins/eSpeak/espeak-data/voices/ta | 6 + Plugins/eSpeak/espeak-data/voices/tr | 4 + Plugins/eSpeak/espeak-data/voices/vi | 6 + Plugins/eSpeak/espeak-data/voices/zh | 30 + Plugins/eSpeak/espeak-data/zh_dict | Bin 0 -> 41747 bytes Plugins/eSpeak/espeak-data/zhy_dict | Bin 0 -> 1561 bytes Plugins/eSpeak/lib/PAStaticWMME.lib | Bin 0 -> 30392 bytes Plugins/eSpeak/m_speak.h | 278 ++ Plugins/eSpeak/options.cpp | 1036 ++++++ Plugins/eSpeak/options.h | 78 + Plugins/eSpeak/res/unknown.ico | Bin 0 -> 894 bytes Plugins/eSpeak/resource.h | 64 + Plugins/eSpeak/resource.rc | 220 ++ Plugins/eSpeak/sdk/m_folders.h | 205 ++ Plugins/eSpeak/sdk/m_metacontacts.h | 162 + Plugins/eSpeak/sdk/m_updater.h | 146 + Plugins/eSpeak/sdk/m_variables.h | 718 ++++ Plugins/eSpeak/types.cpp | 232 ++ Plugins/eSpeak/types.h | 34 + 232 files changed, 37264 insertions(+) create mode 100644 Plugins/eSpeak/Docs/langpack_meSpeak.txt create mode 100644 Plugins/eSpeak/Docs/meSpeak.png create mode 100644 Plugins/eSpeak/Docs/meSpeak_changelog.txt create mode 100644 Plugins/eSpeak/Docs/meSpeak_readme.txt create mode 100644 Plugins/eSpeak/Docs/meSpeak_version.txt create mode 100644 Plugins/eSpeak/ZIP/doit.bat create mode 100644 Plugins/eSpeak/commons.h create mode 100644 Plugins/eSpeak/eSpeak.cpp create mode 100644 Plugins/eSpeak/eSpeak.dsp create mode 100644 Plugins/eSpeak/eSpeak.dsw create mode 100644 Plugins/eSpeak/eSpeak.sln create mode 100644 Plugins/eSpeak/eSpeak.vcproj create mode 100644 Plugins/eSpeak/eSpeak/StdAfx.h create mode 100644 Plugins/eSpeak/eSpeak/compiledict.cpp create mode 100644 Plugins/eSpeak/eSpeak/debug.cpp create mode 100644 Plugins/eSpeak/eSpeak/debug.h create mode 100644 Plugins/eSpeak/eSpeak/dictionary.cpp create mode 100644 Plugins/eSpeak/eSpeak/espeak_command.h create mode 100644 Plugins/eSpeak/eSpeak/event.h create mode 100644 Plugins/eSpeak/eSpeak/fifo.h create mode 100644 Plugins/eSpeak/eSpeak/intonation.cpp create mode 100644 Plugins/eSpeak/eSpeak/klatt.cpp create mode 100644 Plugins/eSpeak/eSpeak/klatt.h create mode 100644 Plugins/eSpeak/eSpeak/mbrolib.h create mode 100644 Plugins/eSpeak/eSpeak/numbers.cpp create mode 100644 Plugins/eSpeak/eSpeak/phoneme.h create mode 100644 Plugins/eSpeak/eSpeak/phonemelist.cpp create mode 100644 Plugins/eSpeak/eSpeak/portaudio.h create mode 100644 Plugins/eSpeak/eSpeak/portaudio18.h create mode 100644 Plugins/eSpeak/eSpeak/portaudio19.h create mode 100644 Plugins/eSpeak/eSpeak/readclause.cpp create mode 100644 Plugins/eSpeak/eSpeak/setlengths.cpp create mode 100644 Plugins/eSpeak/eSpeak/sintab.h create mode 100644 Plugins/eSpeak/eSpeak/speak_lib.cpp create mode 100644 Plugins/eSpeak/eSpeak/speak_lib.h create mode 100644 Plugins/eSpeak/eSpeak/speech.h create mode 100644 Plugins/eSpeak/eSpeak/stdint.h create mode 100644 Plugins/eSpeak/eSpeak/synth_mbrola.cpp create mode 100644 Plugins/eSpeak/eSpeak/synthdata.cpp create mode 100644 Plugins/eSpeak/eSpeak/synthesize.cpp create mode 100644 Plugins/eSpeak/eSpeak/synthesize.h create mode 100644 Plugins/eSpeak/eSpeak/tr_languages.cpp create mode 100644 Plugins/eSpeak/eSpeak/translate.cpp create mode 100644 Plugins/eSpeak/eSpeak/translate.h create mode 100644 Plugins/eSpeak/eSpeak/voice.h create mode 100644 Plugins/eSpeak/eSpeak/voices.cpp create mode 100644 Plugins/eSpeak/eSpeak/wave.h create mode 100644 Plugins/eSpeak/eSpeak/wavegen.cpp create mode 100644 Plugins/eSpeak/espeak-data/af_dict create mode 100644 Plugins/eSpeak/espeak-data/ca_dict create mode 100644 Plugins/eSpeak/espeak-data/config create mode 100644 Plugins/eSpeak/espeak-data/cs_dict create mode 100644 Plugins/eSpeak/espeak-data/cy_dict create mode 100644 Plugins/eSpeak/espeak-data/de_dict create mode 100644 Plugins/eSpeak/espeak-data/el_dict create mode 100644 Plugins/eSpeak/espeak-data/en_dict create mode 100644 Plugins/eSpeak/espeak-data/eo_dict create mode 100644 Plugins/eSpeak/espeak-data/es_dict create mode 100644 Plugins/eSpeak/espeak-data/fi_dict create mode 100644 Plugins/eSpeak/espeak-data/fr_dict create mode 100644 Plugins/eSpeak/espeak-data/grc_dict create mode 100644 Plugins/eSpeak/espeak-data/hbs_dict create mode 100644 Plugins/eSpeak/espeak-data/hi_dict create mode 100644 Plugins/eSpeak/espeak-data/hu_dict create mode 100644 Plugins/eSpeak/espeak-data/hy_dict create mode 100644 Plugins/eSpeak/espeak-data/id_dict create mode 100644 Plugins/eSpeak/espeak-data/is_dict create mode 100644 Plugins/eSpeak/espeak-data/it_dict create mode 100644 Plugins/eSpeak/espeak-data/jbo_dict create mode 100644 Plugins/eSpeak/espeak-data/ku_dict create mode 100644 Plugins/eSpeak/espeak-data/la_dict create mode 100644 Plugins/eSpeak/espeak-data/lv_dict create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/af1_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/ca1_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/cr1_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/cs_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/de2_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/de4_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/de6_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/en1_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/es_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/fr1_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/gr2_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/grc-de6_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/hu1_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/id1_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/in1_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/it3_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/la1_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/nl_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/pl1_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/pt_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/ptbr4_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/ptbr_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/ro1_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/sv2_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/sv_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/us3_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mbrola_ph/us_phtrans create mode 100644 Plugins/eSpeak/espeak-data/mk_dict create mode 100644 Plugins/eSpeak/espeak-data/nl_dict create mode 100644 Plugins/eSpeak/espeak-data/no_dict create mode 100644 Plugins/eSpeak/espeak-data/phondata create mode 100644 Plugins/eSpeak/espeak-data/phonindex create mode 100644 Plugins/eSpeak/espeak-data/phontab create mode 100644 Plugins/eSpeak/espeak-data/pl_dict create mode 100644 Plugins/eSpeak/espeak-data/pt_dict create mode 100644 Plugins/eSpeak/espeak-data/ro_dict create mode 100644 Plugins/eSpeak/espeak-data/ru_dict create mode 100644 Plugins/eSpeak/espeak-data/sk_dict create mode 100644 Plugins/eSpeak/espeak-data/sq_dict create mode 100644 Plugins/eSpeak/espeak-data/sv_dict create mode 100644 Plugins/eSpeak/espeak-data/sw_dict create mode 100644 Plugins/eSpeak/espeak-data/ta_dict create mode 100644 Plugins/eSpeak/espeak-data/tr_dict create mode 100644 Plugins/eSpeak/espeak-data/vi_dict create mode 100644 Plugins/eSpeak/espeak-data/voices/!v/croak create mode 100644 Plugins/eSpeak/espeak-data/voices/!v/f1 create mode 100644 Plugins/eSpeak/espeak-data/voices/!v/f2 create mode 100644 Plugins/eSpeak/espeak-data/voices/!v/f3 create mode 100644 Plugins/eSpeak/espeak-data/voices/!v/f4 create mode 100644 Plugins/eSpeak/espeak-data/voices/!v/m1 create mode 100644 Plugins/eSpeak/espeak-data/voices/!v/m2 create mode 100644 Plugins/eSpeak/espeak-data/voices/!v/m3 create mode 100644 Plugins/eSpeak/espeak-data/voices/!v/m4 create mode 100644 Plugins/eSpeak/espeak-data/voices/!v/m5 create mode 100644 Plugins/eSpeak/espeak-data/voices/!v/m6 create mode 100644 Plugins/eSpeak/espeak-data/voices/!v/wisper create mode 100644 Plugins/eSpeak/espeak-data/voices/af create mode 100644 Plugins/eSpeak/espeak-data/voices/bs create mode 100644 Plugins/eSpeak/espeak-data/voices/ca create mode 100644 Plugins/eSpeak/espeak-data/voices/cs create mode 100644 Plugins/eSpeak/espeak-data/voices/cy create mode 100644 Plugins/eSpeak/espeak-data/voices/de create mode 100644 Plugins/eSpeak/espeak-data/voices/default create mode 100644 Plugins/eSpeak/espeak-data/voices/el create mode 100644 Plugins/eSpeak/espeak-data/voices/en/en create mode 100644 Plugins/eSpeak/espeak-data/voices/en/en-n create mode 100644 Plugins/eSpeak/espeak-data/voices/en/en-rp create mode 100644 Plugins/eSpeak/espeak-data/voices/en/en-sc create mode 100644 Plugins/eSpeak/espeak-data/voices/en/en-wi create mode 100644 Plugins/eSpeak/espeak-data/voices/en/en-wm create mode 100644 Plugins/eSpeak/espeak-data/voices/eo create mode 100644 Plugins/eSpeak/espeak-data/voices/es create mode 100644 Plugins/eSpeak/espeak-data/voices/es-la create mode 100644 Plugins/eSpeak/espeak-data/voices/fi create mode 100644 Plugins/eSpeak/espeak-data/voices/fr create mode 100644 Plugins/eSpeak/espeak-data/voices/fr-be create mode 100644 Plugins/eSpeak/espeak-data/voices/hi create mode 100644 Plugins/eSpeak/espeak-data/voices/hr create mode 100644 Plugins/eSpeak/espeak-data/voices/hu create mode 100644 Plugins/eSpeak/espeak-data/voices/id create mode 100644 Plugins/eSpeak/espeak-data/voices/is create mode 100644 Plugins/eSpeak/espeak-data/voices/it create mode 100644 Plugins/eSpeak/espeak-data/voices/ku create mode 100644 Plugins/eSpeak/espeak-data/voices/la create mode 100644 Plugins/eSpeak/espeak-data/voices/lv create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-af1 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-af1-en create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-br1 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-br3 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-br4 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-cr1 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-cz2 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-de2 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-de4 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-de4-en create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-de5 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-de5-en create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-de6 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-de6-grc create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-de7 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-en1 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-es1 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-es2 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-fr1 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-fr1-en create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-fr4 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-fr4-en create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-gr2 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-gr2-en create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-hu1 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-hu1-en create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-id1 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-it3 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-it4 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-la1 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-nl2 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-nl2-en create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-pl1 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-pl1-en create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-pt1 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-ro1 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-ro1-en create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-sw1 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-sw1-en create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-sw2 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-sw2-en create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-us1 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-us2 create mode 100644 Plugins/eSpeak/espeak-data/voices/mb/mb-us3 create mode 100644 Plugins/eSpeak/espeak-data/voices/mk create mode 100644 Plugins/eSpeak/espeak-data/voices/nl create mode 100644 Plugins/eSpeak/espeak-data/voices/no create mode 100644 Plugins/eSpeak/espeak-data/voices/pl create mode 100644 Plugins/eSpeak/espeak-data/voices/pt create mode 100644 Plugins/eSpeak/espeak-data/voices/pt-pt create mode 100644 Plugins/eSpeak/espeak-data/voices/ro create mode 100644 Plugins/eSpeak/espeak-data/voices/ru create mode 100644 Plugins/eSpeak/espeak-data/voices/sk create mode 100644 Plugins/eSpeak/espeak-data/voices/sr create mode 100644 Plugins/eSpeak/espeak-data/voices/sv create mode 100644 Plugins/eSpeak/espeak-data/voices/sw create mode 100644 Plugins/eSpeak/espeak-data/voices/ta create mode 100644 Plugins/eSpeak/espeak-data/voices/tr create mode 100644 Plugins/eSpeak/espeak-data/voices/vi create mode 100644 Plugins/eSpeak/espeak-data/voices/zh create mode 100644 Plugins/eSpeak/espeak-data/zh_dict create mode 100644 Plugins/eSpeak/espeak-data/zhy_dict create mode 100644 Plugins/eSpeak/lib/PAStaticWMME.lib create mode 100644 Plugins/eSpeak/m_speak.h create mode 100644 Plugins/eSpeak/options.cpp create mode 100644 Plugins/eSpeak/options.h create mode 100644 Plugins/eSpeak/res/unknown.ico create mode 100644 Plugins/eSpeak/resource.h create mode 100644 Plugins/eSpeak/resource.rc create mode 100644 Plugins/eSpeak/sdk/m_folders.h create mode 100644 Plugins/eSpeak/sdk/m_metacontacts.h create mode 100644 Plugins/eSpeak/sdk/m_updater.h create mode 100644 Plugins/eSpeak/sdk/m_variables.h create mode 100644 Plugins/eSpeak/types.cpp create mode 100644 Plugins/eSpeak/types.h (limited to 'Plugins/eSpeak') 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 new file mode 100644 index 0000000..dc0451e Binary files /dev/null and b/Plugins/eSpeak/Docs/meSpeak.png differ 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://:@/ + +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 +#include +#include +#include +#include +#include +#include + + +// Miranda headers +#define MIRANDA_VER 0x0700 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 languages; +extern LIST 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 hHooks; +static std::vector hServices; + +LIST languages(20); +LIST 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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: * + * . * + ***************************************************************************/ + +#include "StdAfx.h" + +#include +#include +#include +#include +#include + +#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 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 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= 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 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 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 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 +#include +#include "speech.h" +#include "debug.h" + +#ifdef DEBUG_ENABLED +#include +#include + +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: * + * . * + ***************************************************************************/ + +#include "StdAfx.h" + +#define LOG_TRANSLATE + +#include +#include +#include +#include + +#include +#include + +#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; hashdict_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; ixtype == 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; ixstd_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; ixlangopts.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= max_stress) + { + vowel_stress[ix] = new_stress; + break; + } + } + } + else + { + // remove primary stress + for(ix=1; 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_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 2) && (vowel_stress[2] >= 4)) + { + vowel_stress[1] = 3; + } + } +#endif + + done = 0; + for(v=1; 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= 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; lengthphonemes_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; iexpect_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 +#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: * + * . * + ***************************************************************************/ + +#include "StdAfx.h" + +#include +#include +#include + +#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= 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; ixtype == 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; ixtype == 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; ixsynthflags & 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_ixstress >= 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; ixstress = 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 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: * + * . * + ***************************************************************************/ + +// See URL: ftp://svr-ftp.eng.cam.ac.uk/pub/comp.speech/synthesis/klatt.3.04.tar.gz + +#include "StdAfx.h" + +#include +#include +#include +#include +#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.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>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; ixklattp[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; ixffreq[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 + * + * 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: * + * . * + ***************************************************************************/ + +#include "StdAfx.h" + +#include +#include +#include +#include + +#include +#include + +#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: * + * . * + ***************************************************************************/ + + + +// 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: * + * . * + ***************************************************************************/ + +#include "StdAfx.h" + +#include +#include +#include + +#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; kphcode == 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. +
+ set PA_RECOMMENDED_OUTPUT_DEVICE=1
+
+ 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: * + * . * + ***************************************************************************/ + +#include "StdAfx.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "speak_lib.h" +#include "speech.h" +#include "phoneme.h" +#include "synthesize.h" +#include "voice.h" +#include "translate.h" + +#ifdef PLATFORM_POSIX +#include +#endif + +#include +#define N_XML_BUF 256 + + +static const char *xmlbase = ""; // base URL from + +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 ... 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_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. +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; ixvoice_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= 0) + new_parameters[param] = param_stack[ix].parameter[param]; + } + } + + for(param=0; paramtype = tag_type; + for(ix=0; ixparameter[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 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); // or

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 or 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 or 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 & and & 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 + 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. * + ***************************************************************************/ + +#include "StdAfx.h" + +#include +#include +#include + +#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; ixtone & 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; ix2pitch2 = 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: * + * . * + ***************************************************************************/ + +#include "StdAfx.h" + +#include "stdio.h" +#include "ctype.h" +#include "string.h" +#include "stdlib.h" +#include "wchar.h" +#include "locale.h" +#include +#include + +#include "speech.h" + +#include +#ifndef PLATFORM_WINDOWS +#include +#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 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