Click here to Skip to main content
15,897,187 members
Articles / Mobile Apps / iPhone

ESpeakEngine - Objective-C speech synthesizer

Rate me:
Please Sign up or sign in to vote.
4.80/5 (3 votes)
23 Jan 2012BSD2 min read 75.5K   3K   22  
ESpeakEngine - Objective-C speech synthesizer
  • ESpeakTest.zip
    • __MACOSX
    • ESpeakTest
      • .DS_Store
      • .git
        • branches
        • COMMIT_EDITMSG
        • config
        • description
        • HEAD
        • hooks
          • applypatch-msg.sample
          • commit-msg.sample
          • post-commit.sample
          • post-receive.sample
          • post-update.sample
          • pre-applypatch.sample
          • pre-commit.sample
          • prepare-commit-msg.sample
          • pre-rebase.sample
          • update.sample
        • index
        • info
          • exclude
        • logs
          • HEAD
          • refs
            • heads
              • master
        • objects
          • 00
            • e88867c94b8f6728422a4ad099a53a9f392cb7
          • 01
            • 477be5465c04d4cfd46ab95003f17445731d74
          • 02
            • 37597b848a1890bb30ba0ff4102f8107cafdcb
            • 6363f6a3bb9fe78b03b09f4d0fdce472b95e20
            • 69a98a29eb1baf79dd1f6f2bbc9370b9c4c9cb
            • 78ea2136d97866104e3789d7950d39632b5046
          • 03
            • 7b0f820ace631b85715dfb13cc21fe26daa44d
            • dac4f6baff6f5a2b06f5a68b6daa265c16e0a2
            • edde41daf54530ac1fe426e9349a5544b48ccc
          • 05
            • b2d0d21b687b4190600f6ea0206972c960dd1c
          • 06
            • 23379b22e1417ed806ff4c48337afbe9457cdf
          • 0c
            • 2d13a65548266d9c8a6758599c9a86faf53b28
          • 0d
            • 51695daffab058d065e942018f5c56067942fe
            • 6fa2a91273ee3271fb1d3202d7f6ad86379ef7
          • 10
            • e91b22ea1e6c67962cd858e8d03de74e7abf6f
          • 11
            • 8095eb5859b9c2ee14be00adeb87ee66eaec94
          • 12
            • 83826f9250bed7326aed2542a0e3c5b47edbec
            • ce1096a4ef6c4538a43ebd951dcaf2b624b717
          • 13
            • 664a34fe6a3b585603e00df15b17bcef3591e0
            • 93b3551c88468dccede7550726aae27a931555
          • 14
            • 1160885f6955ee4855933ce984bc117649d220
            • 417c1fdfdb3af16ef717d90a078f5c9b8468c3
          • 15
            • 96e3c77205e1688ad5968359106b5ea92d322a
          • 17
            • 004519410f433308228397abc30d2b69e55b26
          • 19
            • 3700525eaa0aa02256a4211c820007bb005995
            • da34a5b9b6e585ece12d4a6ec24341fa98f50c
            • fd69d3bec6857b0d4b29f4f26c03ac0dc727e3
          • 1a
            • 61238c5c772724cb12490eb555630bcc0e7870
            • 9e53b6e584bd1e8245f3fc69751211b7d4e86a
          • 1c
            • 2992dd5455746aef85905cc0293031e3e9ceaa
            • a6f6abadb2e86be2baf2f823a27d16c43d2528
          • 1e
            • 9a757f891fd7ece2ff350dc9225837cb7e70f7
          • 1f
            • 2eb929019174a9b4c2654c1398ed26aca50e2c
          • 20
            • 5e59c21f973ea13de6deab139efee2f171027a
          • 21
            • 4d6f9d708a7691c1abf13b0801ee9a54a44ae6
          • 22
            • a3b80f6954fd663b59f66210e7b658c5db3a3f
            • a95d18a3ead5d0274abab7986121dccafc1842
          • 23
            • 8c69126e18387f9c2b3d059db6d7df1cd91a6f
          • 25
            • 55d5d8bec5ee0524034d96ea5aa95eeb1a90dd
            • 5fe35ca3bf7c8743abf0f95518aae822aac4f0
            • bfdbf47ea384bd8118ed197a8842cb2104eb2a
            • d78c05ca841d2019f204184927282dc69ebd61
          • 28
            • 59e093f75f1f88ca412e0bde9345afc01f15ac
          • 29
            • 91e99d136fe8e13f7c0c99eaa27c8ca397186d
          • 2a
            • 736d4d3f3c3ffe36bea620d6c28b672efc5867
            • fb1adc71b7f4e90bd02355dd8cdb75e8d23870
          • 2e
            • 1534872410e5819a69396cdbb57225baa3a888
            • 9dd1f58881b69e931f3b5a73fef461eb9b838b
          • 30
            • 441d7e239ccd9169d15b04efc59be7a158a4a5
            • dea89200864b8e84dc0fec67850491d460a78d
          • 31
            • 02c9a1a9a5db99cadf90502de610f69c994913
            • bd479a3d5873c8a970de821a84d2addc864839
          • 32
            • 49dd4f8012e94af03784f043478ebab38fd0b7
          • 33
            • 00c360065de4a18e093dd02df3f6419b656f74
            • 430a22c186f11e5cf838e81dacb386a7e4c8d0
            • 8b8c450f3027ea5d3751fc1e0b4bf969cc1afc
          • 35
            • a4a3fc517a71ec8bad8f9d0c71bd9da960c494
          • 36
            • 665314753a98e11162485805608fe1aca7bc1e
            • 669d3f0592eaeb8465b81341b486a4c9c1eb11
            • a4bff6503dfb82fcdc7a160cc36f4cdd9e333d
          • 3a
            • f2aa1af75587b7a29685fa2dbbebbd2b3592c2
          • 3b
            • 105a7fb0f9b3bb28a87a7212dfcef08daf53c0
          • 3d
            • d75260a68d0fb7b88fe70d0e3e73c916b963e5
          • 3e
            • 520ec5c8b88db8f95f6a230273278fd39bfbd6
          • 41
            • 22d96b39bf2bc1025a1c57ef01b8b03936773d
          • 42
            • dbdc5586bc89d681d23851f4a5aafd79b414cb
            • de58882d0c80e94597b575893afc8e99bd431e
          • 46
            • 07dd079dd2748f8e9cde034268e6cd68278644
            • 317618d912082d6070e4e3972809824bd35395
          • 47
            • 7b28ff8f86a3158a71c4934fbd3a2456717d7a
          • 4b
            • 6a9ae550599ea85d77fea7c0f71b4d5aaf1ba8
          • 4c
            • 6239268d21312d311a504ad0d8aeb0e4f1030b
          • 4d
            • ec15997c4e00c7a764156687431ac8c47676a5
          • 4e
            • 2b9d23e84059b93883e1a0c0f7a859a23b087e
          • 4f
            • 1904e5164410f93689beb55d46901b214271fe
            • 8f5e88f436d478b126c5c4eccf3568e398c26f
            • e4188e53b10cc21b50c3bf47e9ef3b2fc4c641
          • 50
            • 1b5a4a8620d5bd9a545c8941cdcbd565fca1c6
          • 52
            • 1be164ce6c87e1d6df58fd82ab160c8f6255db
            • 692c38546eb82aee2a7550c93798f70f02dc9a
            • c5ac93561331143a9caea14d6c0f008216b4e9
          • 53
            • 6957cb8fe03bde580784e6f97537ec3444e9c3
            • 7beb3ba82da8af147f028685e61fc839cad713
            • c2a70482993f53d6df321687b5cc4d9e95abc1
            • cb31446e077a5f159c831e126a0e3f9a2d0ed2
          • 56
            • 9f9d05432267a13b75ca2562beb85e1e1c17db
          • 58
            • 1cd883fed6aa3b84b580a543b6ec8998f4d327
          • 5a
            • 24e11ad4dc2842c79033ad323f02e2e6c2f566
            • 85640ee385afef9b9dc9b7d2889d47b81aa1bd
          • 5c
            • 3583da4700ab6982766d187d1195b37f9a3fb5
          • 5e
            • bb6a35716f489f249db8bb0e9df7dde150eba4
          • 5f
            • 3297d3a2009a6051a8ebc606bc674056ea03fc
          • 61
            • 124ff298a7d392b816cb1a71095ed1ec8ffe6b
          • 65
            • 3c3f5c4a2d2e44b8a188b88b64278085ebde27
          • 68
            • 1cad9627cb3af687a30507f05114c89ef9340f
          • 69
            • cceefb779a5236074db6871d7523d92f8a709f
          • 6a
            • 8d5efd87553a3f0e977636c6b819cddf3a99e7
            • cadba6aea97cd920745428a1c4ccd998581cc7
          • 6c
            • 65e3c6851f204d9c4cd9b616b46a9ff425b3c2
          • 6d
            • 826477b6ee1ecb3e502ffe3c26387d1bce3961
          • 6e
            • 11c93121ab5d535e4f2d50253ee4a527694a9f
          • 71
            • 99341c34f93f5fa5219ff479e82edaee5d7936
            • ecab7197ec9646efdae05bb02b465f5b5e361a
          • 73
            • ac62a4ab12374bbf6f72539b2e104d10d7d394
          • 74
            • 00c07a5c17fbbead0d252a22f1fcdb6e5f15c4
            • 0601d129aa08fd59be839301c923b684361dbe
          • 79
            • 2d8a9f9ef248c4358c36000722ba0c53a76497
          • 7c
            • bdab338114c51e83e0b54c67280b91872211d5
          • 7d
            • 276eb2b779d73c46d8ed97e4be287bec96c828
          • 7e
            • 6c16a2c28e97392d20d4f4c243ecd6f6f40a91
            • f93a5edd61fddc97d982242d7654e5ab07a09e
          • 7f
            • 4631899e208f50b855ad579b726eadea70f67d
            • fccbe6dd68c968e78da5b6265a13c62c1fc639
          • 82
            • 2c9a312addee71797811c17690f2be8746bea4
            • 98f98722e5f5f405e9631eb4a5064d87114424
          • 83
            • 71a46410d32c3f000db4c7b11254f48a3d6055
          • 84
            • 79e658ebe74cdd0b9525a41dededf8b9839858
            • ccc3a6d4681e19dbe6b982ddcee2e760944d55
          • 85
            • ebb03e3089c5055e4f76d272866738a90a7842
          • 88
            • 1634035cad7fbc213a9def0b5e5ef7f15dffa2
            • 48d6820e826b907349234a642535725247f837
          • 89
            • 28f0ff69aa677f7c5f96053ca70589552e17b0
            • e6c82914aa9457a644d5a498fa643f98b9ade3
          • 8f
            • c65d4bab0f132f34e70868a961188deabc55dc
            • d4a63a1a24c8b25eab5ed28c135457a8332e6b
          • 92
            • 3d517415d489cc9b3f91638c14264dd0df55a3
            • a1582817dc2f8256db5a02bed320a05f6e5e43
          • 95
            • 8799c61770d05ba341183cd2d6a107a1ec093c
          • 96
            • 35ac150af1804b398d67cf4703d718a16806a7
          • 97
            • 8ec49ed00c46862d89580efe68f7efd98c93c3
            • a337d34bb9ab89812b5e79c3bc2bb784d48953
          • 98
            • 9f9eab7b5ee98f4b6acf35fe8b4ef86db3a62e
          • 9b
            • 06e0bd24aa4658ed8009be3e2fe7e32ccee54e
            • 280bf8bc106ca904c9b33a90d0822c4c9b03fd
            • 2d891f12030afadc1e737914a1759ab59ef01f
            • a872a49ab896e3d7c6203bedfd2502d8cb6521
            • c60c7cbd38db3307551ae17ef2a8a5d623b3a0
          • 9d
            • e1630d90e22bf6df53a0093c212e9e1d7da9b6
            • eba8432350a07d0ab15189bb124d48b836fb62
          • 9e
            • 9c4e7476f3dce3b6808b0c8ea917a4d8503d7f
          • a3
            • 72fb170d6218ccc7298335f1ea55ff674fb3cc
          • a5
            • 4cb02cd26c7ecc2bd08debb1cfe85c3546c039
            • 504505ef08ce6040091e48eb5c1653a34574b0
          • a6
            • 56d2c7f2e9509fef687b3174282f6ec9b61498
            • e0f46bb57f876255cdb5767729aefccbe3a33e
          • a7
            • a8223dbda4d4cd47ae8796ad2be9bc70e46754
          • a9
            • 09e1af21f9dc9dccf095d02920266ea11fe7f5
            • 0c7b72565da6e015014e343f10ae50197587c9
          • aa
            • 80edaad05f0f16c6195e72a0130d803b58bcbc
          • ae
            • 76a4c4deb77d53e7ee512c3e85f45ba802dd08
            • 9247d41055f6721d3500645539ee00ae29d7e4
            • a3d895c09d5eae5411aec5fb5ecbe82451bd50
          • b0
            • d4979c857d151ef5cd27248926fb112c0c3cee
          • b1
            • a874be65ad7df189c933f4c576475f29d15f67
          • b2
            • 7a8114a03ed27348a40f37428107e02f67acc8
            • fd9d084c6df70da314916ee674421872f9ef50
          • b3
            • 2b6a6660b12a14fc5e8749eff9ba696b7f6061
          • b4
            • 8b1788b979853cdd57bb6cec6b9cc4dd7925bc
          • b7
            • 39a86e488eeec6f5693b7ee2590f84d6c95191
          • b8
            • 519559d3db834193518d4f4c4fed777734c8ee
            • 6f59306a576f4548ff3bd62e344570cc538932
            • f782946f4728a629eda8038e82dd165dadee1d
          • ba
            • 7c42cc48ae095ccfffeb03cf36d519d7cbe5a3
          • bc
            • bb2a0058b7866064b779ae246244c5342a0ccb
          • bd
            • 336a9884877889e2f85ee1849f8a068d2c6279
          • be
            • 1b6246a01d3d9c01dac0f888454369e0b32892
          • c0
            • a5475ece89c3274e62f9132560ec8afdd569d4
          • c2
            • 34f46877a761d5c98fda2cbbeb172dc2efc81a
            • 76bec0dd56b68a8e0ed0062b38dd149422b45d
          • c3
            • 2db3968df0779b3c3a47899085625a7b86339b
          • c6
            • 32e263c2a639311be4ca1512aadee97a91bc9a
          • cb
            • c4fb3a5d3dcc07f26b5ca02eb53a52b897aa80
          • cd
            • 02abedb89d8975a792d73de536d2ce82260553
          • ce
            • 800f70be34d9fba11f7d09b0c329b3f259507b
          • cf
            • 584b7dd3f005be3cffb47d27f1bfe980a6e53b
            • f13bac9dd46f2c2a2fb66db64ce78caffd5b5a
          • d0
            • 2b035cae9b12c66a5e0d5ae9cf57433041ed40
            • 864f3d6209ad6f3e8ab51dc880836e67c96fc5
            • b729579de2537a63cebdb809e8bdfb5a897a33
          • d2
            • 5865608d7d973e77c77d13bbdbe3ae454d864d
          • d3
            • d7720074a2fc7e3bfbc18cc1bacad0f9ea61d2
          • d5
            • 06e7f9f85667d054794f808f9098b2a1625372
            • 9fe79529970f627e83857f9bd516f5474bf685
          • d6
            • 737d787752a799349095b49cf81024dfd00fb6
            • 811d3ae4f710d3eb8a16c690cd42bebc4faf4e
          • d7
            • 59cfdf018f8814d995588d176dadde0820b5ca
          • d8
            • ecd252c04bc00a6dcc6d0b84d64285726b65d4
            • f35efaa2dc2293277365d811c9b6222cfe59b8
          • da
            • c1e4d0643b01e99052f575b5f6add2e10a2143
            • ef5160c9c1500d3a9f6b0eadf434ef702a009f
          • db
            • 63d6ca3d87629e693327a2ba4a719e79847b6c
            • de212341a0b131224b3e123f91d154348070ba
          • dc
            • 51396ce24aad46d1c761e2c96f8aee68d4b622
          • de
            • 4786c94cfa90c84982ca06a2a9623035993dfc
          • df
            • 70f4387ca97d30c66d7dc87d5abca9b0ffa820
            • 7fa77bc23477530674f05f4f619803983d5eb0
          • e0
            • 783ec3b1e38ce0f77b5b018d9b8641a7b924c3
            • ea6d6394ab8409463adfeb753f1632ef09a47f
          • e4
            • 16c6dc5e784243ad8d33000139178fa178de87
            • 1d3105c048e789fe605c780efe2881df833ea8
          • e7
            • 178e4d5666a3b75aa48de33b966f0590665a8e
          • e8
            • 5978c76abd7430436356cd2a126018642d6a2d
          • ea
            • dd707322e08d90e3a2208aef9060706c340799
          • eb
            • d92ffb3ec9499292d1faa7ca60adcc5edac74f
          • ed
            • 05f4240da98a569e3c9f9a5b9e2301d7caa79e
          • ee
            • 3bd233e2bc458aab63eb36f613f853f72e6bf7
          • f2
            • 033dc11fee0a1db4a2cae358c1a808149a5aa2
            • 130ba4f980783f8605eb50387ffc2e54f66b3c
          • f3
            • 10f868efea6408fc49fd4e6d6c293db2098114
            • e97b52355ea6f5384790bb3cda677222be900c
          • f4
            • 3ef84fa6caf08bc6e9e442e15d71eaa8c6962a
            • ede3296c4fb294a58715e61ecef26f74ed898a
          • f5
            • 0e89c905a3e8dd519f415585283757fc85d2d6
          • f9
            • 624b31623b500b57b74e9765ac7a2d9f039c65
          • fa
            • 4eece0b1cc7dababed3d09d560f38e00ea8755
          • fc
            • 377156b487a559efb384ec2b6e551d6c2085c6
            • 60f41672c8fb6fc69c1548552988f046a2aa14
          • fe
            • 7c4d8d002499fbaff530adfaa0543d626536dd
          • ff
            • a94206372e46914dcadeb10c212a9674daf62e
          • info
          • pack
        • refs
          • heads
            • master
          • tags
      • ESpeakTest.xcodeproj
        • project.pbxproj
        • project.xcworkspace
          • contents.xcworkspacedata
          • xcuserdata
            • jozefbozek.xcuserdatad
              • UserInterfaceState.xcuserstate
        • xcuserdata
      • ESpeakTest
      • ESpeakTestTests
  • eSpeak_1.0.zip
/***************************************************************************
 *   Copyright (C) 2005 to 2010 by Jonathan Duddington                     *
 *   email: jonsd@users.sourceforge.net                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write see:                           *
 *               <http://www.gnu.org/licenses/>.                           *
 ***************************************************************************/

#include "StdAfx.h"

#include "stdio.h"
#include "ctype.h"
#include "wctype.h"
#include "string.h"
#include "stdlib.h"
#include "speech.h"

#ifdef PLATFORM_WINDOWS
#include "windows.h"
#else
#ifdef PLATFORM_RISCOS
#include "kernel.h"
#else
#include "dirent.h"
#endif
#endif

#include "speak_lib.h"
#include "phoneme.h"
#include "synthesize.h"
#include "voice.h"
#include "translate.h"


MNEM_TAB genders [] = {
	{"unknown", 0},
	{"male", 1},
	{"female", 2},
	{NULL, 0 }};

int tone_points[12] = {600,170, 1200,135, 2000,110, 3000,110, -1,0};
//int tone_points[12] = {250,200,  400,170, 600,170, 1200,135, 2000,110, -1,0};

// limit the rate of change for each formant number
//static int formant_rate_22050[9] = {50, 104, 165, 230, 220, 220, 220, 220, 220};  // values for 22kHz sample rate
//static int formant_rate_22050[9] = {240, 180, 180, 180, 180, 180, 180, 180, 180};  // values for 22kHz sample rate
static int formant_rate_22050[9] = {240, 170, 170, 170, 170, 170, 170, 170, 170};  // values for 22kHz sample rate
int formant_rate[9];         // values adjusted for actual sample rate



#define DEFAULT_LANGUAGE_PRIORITY  5
#define N_VOICES_LIST  150
static int n_voices_list = 0;
static espeak_VOICE *voices_list[N_VOICES_LIST];
static int len_path_voices;

espeak_VOICE voice_selected;


enum {
	V_NAME = 1,
	V_LANGUAGE,
	V_GENDER,
	V_TRANSLATOR,
	V_PHONEMES,
	V_DICTIONARY,

// these affect voice quality, are independent of language
	V_FORMANT,
	V_PITCH,
	V_ECHO,
	V_FLUTTER,
	V_ROUGHNESS,
	V_CLARITY,
	V_TONE,
	V_VOICING,
	V_BREATH,
	V_BREATHW,

// these override defaults set by the translator
	V_WORDGAP,
	V_INTONATION,
	V_TUNES,
	V_STRESSLENGTH,
	V_STRESSAMP,
	V_STRESSADD,
	V_DICTRULES,
	V_STRESSRULE,
	V_CHARSET,
	V_NUMBERS,
	V_OPTION,

	V_MBROLA,
	V_KLATT,
	V_FAST,
	V_SPEED,

// these need a phoneme table to have been specified
	V_REPLACE,
	V_CONSONANTS
};



static MNEM_TAB options_tab[] = {
	{"reduce_t",  LOPT_REDUCE_T},
	{"bracket", LOPT_BRACKET_PAUSE},
	{NULL,   -1} };

static MNEM_TAB keyword_tab[] = {
	{"name",       V_NAME},
	{"language",   V_LANGUAGE},
	{"gender",     V_GENDER},

	{"formant",    V_FORMANT},
	{"pitch",      V_PITCH},
	{"phonemes",   V_PHONEMES},
   {"translator", V_TRANSLATOR},
	{"dictionary", V_DICTIONARY},
	{"stressLength", V_STRESSLENGTH},
	{"stressAmp",  V_STRESSAMP},
	{"stressAdd",  V_STRESSADD},
	{"intonation", V_INTONATION},
	{"tunes",      V_TUNES},
	{"dictrules",	V_DICTRULES},
	{"stressrule", V_STRESSRULE},
	{"charset",    V_CHARSET},
	{"replace",    V_REPLACE},
	{"words",      V_WORDGAP},
	{"echo",       V_ECHO},
	{"flutter",    V_FLUTTER},
	{"roughness",  V_ROUGHNESS},
	{"clarity",    V_CLARITY},
	{"tone",       V_TONE},
	{"voicing",    V_VOICING},
	{"breath",     V_BREATH},
	{"breathw",    V_BREATHW},
	{"numbers",    V_NUMBERS},
	{"option",     V_OPTION},
	{"mbrola",     V_MBROLA},
	{"consonants", V_CONSONANTS},
	{"klatt",      V_KLATT},
	{"fast_test",  V_FAST},
	{"speed",      V_SPEED},

	// these just set a value in langopts.param[]
	{"l_dieresis", 0x100+LOPT_DIERESES},
//	{"l_lengthen", 0x100+LOPT_IT_LENGTHEN},
	{"l_prefix",   0x100+LOPT_PREFIXES},
	{"l_regressive_v", 0x100+LOPT_REGRESSIVE_VOICING},
	{"l_unpronouncable", 0x100+LOPT_UNPRONOUNCABLE},
	{"l_sonorant_min", 0x100+LOPT_SONORANT_MIN},
	{"l_length_mods", 0x100+LOPT_LENGTH_MODS},
	{NULL,   0} };

#define N_VOICE_VARIANTS   12
const char variants_either[N_VOICE_VARIANTS] = {1,2,12,3,13,4,14,5,11,0};
const char variants_male[N_VOICE_VARIANTS] = {1,2,3,4,5,0};
const char variants_female[N_VOICE_VARIANTS] = {11,12,13,14,0};
const char *variant_lists[3] = {variants_either, variants_male, variants_female};

static voice_t voicedata;
voice_t *voice = &voicedata;


static char *fgets_strip(char *buf, int size, FILE *f_in)
{//======================================================
// strip trailing spaces, and truncate lines at // comment
	int len;
	char *p;

	if(fgets(buf,size,f_in) == NULL)
		return(NULL);

	len = strlen(buf);
	while((--len > 0) && isspace(buf[len]))
		buf[len] = 0;

	if((p = strstr(buf,"//")) != NULL)
		*p = 0;

	return(buf);
}


static int LookupTune(const char *name)
{//====================================
	int ix;

	for(ix=0; ix<n_tunes; ix++)
	{
		if(strcmp(name, tunes[ix].name) == 0)
			return(ix);
	}
	return(-1);
}  // end of LookupTune



static void SetToneAdjust(voice_t *voice, int *tone_pts)
{//=====================================================
	int ix;
	int pt;
	int y;
	int freq1=0;
	int freq2;
	int height1 = tone_pts[1];
	int height2;
	double rate;

	for(pt=0; pt<12; pt+=2)
	{
		if(tone_pts[pt] == -1)
		{
			tone_pts[pt] = N_TONE_ADJUST*8;
			if(pt > 0)
				tone_pts[pt+1] = tone_pts[pt-1];
		}
		freq2 = tone_pts[pt] / 8;   // 8Hz steps
		height2 = tone_pts[pt+1];
		if((freq2 - freq1) > 0)
		{
			rate = double(height2-height1)/(freq2-freq1);

			for(ix=freq1; ix<freq2; ix++)
			{
				y = height1 + int(rate * (ix-freq1));
				if(y > 255)
					y = 255;
				voice->tone_adjust[ix] = y;
			}
		}
		freq1 = freq2;
		height1 = height2;
	}
}


void ReadTonePoints(char *string, int *tone_pts)
{//=============================================
// tone_pts[] is int[12]
	int ix;

	for(ix=0; ix<12; ix++)
		tone_pts[ix] = -1;

	sscanf(string,"%d %d %d %d %d %d %d %d %d %d",
		&tone_pts[0],&tone_pts[1],&tone_pts[2],&tone_pts[3],
		&tone_pts[4],&tone_pts[5],&tone_pts[6],&tone_pts[7],
		&tone_pts[8],&tone_pts[9]);
}




static espeak_VOICE *ReadVoiceFile(FILE *f_in, const char *fname, const char*leafname)
{//===================================================================================
// Read a Voice file, allocate a VOICE_DATA and set data from the
// file's  language, gender, name  lines

	char linebuf[120];
	char vname[80];
	char vgender[80];
	char vlanguage[80];
	char languages[300];  // allow space for several alternate language names and priorities


	unsigned int len;
	int langix = 0;
	int n_languages = 0;
	char *p;
	espeak_VOICE *voice_data;
	int priority;
	int age;
	int n_variants = 3;    // default, number of variants of this voice before using another voice
	int gender;

#ifdef PLATFORM_WINDOWS
	char fname_buf[sizeof(path_home)+15];
	if(memcmp(leafname,"mb-",3) == 0)
	{
		// check whether the mbrola speech data is present for this voice
		memcpy(vname,&leafname[3],3);
		vname[3] = 0;
		sprintf(fname_buf,"%s/mbrola/%s",path_home,vname);

		if(GetFileLength(fname_buf) <= 0)
			return(0);
	}
#endif

	vname[0] = 0;
	vgender[0] = 0;
	age = 0;

	while(fgets_strip(linebuf,sizeof(linebuf),f_in) != NULL)
	{
		if(memcmp(linebuf,"name",4)==0)
		{
			p = &linebuf[4];
			while(isspace(*p)) p++;
			strncpy0(vname,p,sizeof(vname));
		}
		else
		if(memcmp(linebuf,"language",8)==0)
		{
			priority = DEFAULT_LANGUAGE_PRIORITY;
			vlanguage[0] = 0;

			sscanf(&linebuf[8],"%s %d",vlanguage,&priority);
			len = strlen(vlanguage) + 2;
			// check for space in languages[]
			if(len < (sizeof(languages)-langix-1))
			{
				languages[langix] = priority;

				strcpy(&languages[langix+1],vlanguage);
				langix += len;
				n_languages++;
			}
		}
		else
		if(memcmp(linebuf,"gender",6)==0)
		{
			sscanf(&linebuf[6],"%s %d",vgender,&age);
		}
		else
		if(memcmp(linebuf,"variants",8)==0)
		{
			sscanf(&linebuf[8],"%d",&n_variants);
		}
	}
	languages[langix++] = 0;

	gender = LookupMnem(genders,vgender);

	if(n_languages == 0)
	{
		return(NULL);    // no language lines in the voice file
	}

	p = (char *)calloc(sizeof(espeak_VOICE) + langix + strlen(fname) + strlen(vname) + 3, 1);
	voice_data = (espeak_VOICE *)p;
	p = &p[sizeof(espeak_VOICE)];

	memcpy(p,languages,langix);
	voice_data->languages = p;

	strcpy(&p[langix],fname);
	voice_data->identifier = &p[langix];
	voice_data->name = &p[langix];

	if(vname[0] != 0)
	{
		langix += strlen(fname)+1;
		strcpy(&p[langix],vname);
		voice_data->name = &p[langix];
	}

	voice_data->age = age;
	voice_data->gender = gender;
	voice_data->variant = 0;
	voice_data->xx1 = n_variants;
	return(voice_data);
}  // end of ReadVoiceFile




void VoiceReset(int tone_only)
{//===========================
// Set voice to the default values

	int  pk;
	static unsigned char default_heights[N_PEAKS] = {128,128,120,120,110,110,128,128,128};
	static unsigned char default_widths[N_PEAKS] = {128,128,128,160,171,171,128,128,128};

	static int breath_widths[N_PEAKS] = {0,200,200,400,400,400,600,600,600};

	// default is:  pitch 80,118
	voice->pitch_base = 0x47000;
	voice->pitch_range = 4104;

//	default is:  pitch 80,117
//	voice->pitch_base = 0x47000;
//	voice->pitch_range = 3996;

	voice->formant_factor = 256;

	voice->speed_percent = 100;
	voice->echo_delay = 0;
	voice->echo_amp = 0;
	voice->flutter = 64;
	voice->n_harmonic_peaks = 5;
	voice->peak_shape = 0;
	voice->voicing = 64;
	voice->consonant_amp = 100;
	voice->consonant_ampv = 100;
	voice->samplerate = 22050;
	memset(voice->klattv,0,sizeof(voice->klattv));
	memset(speed.fast_settings,0,sizeof(speed.fast_settings));

#ifdef PLATFORM_RISCOS
	voice->roughness = 1;
#else
	voice->roughness = 2;
#endif

	InitBreath();
	for(pk=0; pk<N_PEAKS; pk++)
	{
		voice->freq[pk] = 256;
		voice->height[pk] = default_heights[pk]*2;
		voice->width[pk] = default_widths[pk]*2;
		voice->breath[pk] = 0;
		voice->breathw[pk] = breath_widths[pk];  // default breath formant woidths
		voice->freqadd[pk] = 0;

		// adjust formant smoothing depending on sample rate
		formant_rate[pk] = (formant_rate_22050[pk] * 22050)/samplerate;
	}
	voice->height[2] = 240;  // reduce F2 slightly

	// This table provides the opportunity for tone control.
	// Adjustment of harmonic amplitudes, steps of 8Hz
	// value of 128 means no change
//	memset(voice->tone_adjust,128,sizeof(voice->tone_adjust));
SetToneAdjust(voice,tone_points);

	// default values of speed factors
	voice->speedf1 = 256;
	voice->speedf2 = 238;
	voice->speedf3 = 232;

	if(tone_only == 0)
	{
		n_replace_phonemes = 0;
		option_quiet = 0;
		LoadMbrolaTable(NULL,NULL,0);
	}
}  // end of VoiceReset


static void VoiceFormant(char *p)
{//==============================
	// Set parameters for a formant
	int ix;
	int formant;
	int freq = 100;
	int height = 100;
	int width = 100;
	int freqadd = 0;

	ix = sscanf(p,"%d %d %d %d %d",&formant,&freq,&height,&width,&freqadd);
	if(ix < 2)
		return;

	if((formant < 0) || (formant > 8))
		return;

	if(freq >= 0)
		voice->freq[formant] = int(freq * 2.56001);
	if(height >= 0)
		voice->height[formant] = int(height * 2.56001);
	if(width >= 0)
		voice->width[formant] = int(width * 2.56001);
	voice->freqadd[formant] = freqadd;
}





static void PhonemeReplacement(int type, char *p)
{//==============================================
	int n;
	int  phon;
	int flags = 0;
	char phon_string1[12];
	char phon_string2[12];

	strcpy(phon_string2,"NULL");
	n = sscanf(p,"%d %s %s",&flags,phon_string1,phon_string2);
	if((n < 2) || (n_replace_phonemes >= N_REPLACE_PHONEMES))
		return;

	if((phon = LookupPhonemeString(phon_string1)) == 0)
		return;  // not recognised

	replace_phonemes[n_replace_phonemes].old_ph = phon;
	replace_phonemes[n_replace_phonemes].new_ph = LookupPhonemeString(phon_string2);
	replace_phonemes[n_replace_phonemes++].type = flags;
}  //  end of PhonemeReplacement



static int Read8Numbers(char *data_in,int *data)
{//=============================================
// Read 8 integer numbers
	return(sscanf(data_in,"%d %d %d %d %d %d %d %d",
		&data[0],&data[1],&data[2],&data[3],&data[4],&data[5],&data[6],&data[7]));
}


voice_t *LoadVoice(const char *vname, int control)
{//===============================================
// control, bit 0  1= no_default
//          bit 1  1 = change tone only, not language
//          bit 2  1 = don't report error on LoadDictionary
//          bit 4  1 = vname = full path

	FILE *f_voice = NULL;
	char *p;
	int  key;
	int  ix;
	int  n;
	int  value;
	int  value2;
	int  error = 0;
	int  langix = 0;
	int  tone_only = control & 2;
	int  language_set = 0;
	int  phonemes_set = 0;
	int  stress_amps_set = 0;
	int  stress_lengths_set = 0;
	int  stress_add_set = 0;
	int  conditional_rules = 0;
	LANGUAGE_OPTIONS *langopts = NULL;

	Translator *new_translator = NULL;

	char voicename[40];
	char language_name[40];
	char translator_name[40];
	char new_dictionary[40];
	char phonemes_name[40];
	char option_name[40];
	const char *language_type;
	char buf[200];
	char path_voices[sizeof(path_home)+12];
	char langname[4];

	int stress_amps[8];
	int stress_lengths[8];
	int stress_add[8];
	char names[8][40];

	int pitch1;
	int pitch2;

	static char voice_identifier[40];  // file name for  voice_selected
	static char voice_name[40];        // voice name for voice_selected
	static char voice_languages[100];  // list of languages and priorities for voice_selected

	strcpy(voicename,vname);
	if(voicename[0]==0)
		strcpy(voicename,"default");

	if(control & 0x10)
	{
		strcpy(buf,vname);
		if(GetFileLength(buf) <= 0)
			return(NULL);
	}
	else
	{
		sprintf(path_voices,"%s%cvoices%c",path_home,PATHSEP,PATHSEP);
		sprintf(buf,"%s%s",path_voices,voicename);

		if(GetFileLength(buf) <= 0)
		{
			// look for the voice in a sub-directory of the language name
			langname[0] = voicename[0];
			langname[1] = voicename[1];
			langname[2] = 0;
			sprintf(buf,"%s%s%c%s",path_voices,langname,PATHSEP,voicename);

			if(GetFileLength(buf) <= 0)
			{
				// look in "extra" sub-directory
				sprintf(buf,"%sextra%c%s",path_voices,PATHSEP,voicename);

				if(GetFileLength(buf) <= 0)
				{
					// look in "test" sub-directory
					sprintf(buf,"%stest%c%s",path_voices,PATHSEP,voicename);
				}
			}
		}
	}

	f_voice = fopen(buf,"r");

	language_type = "en";    // default
	if(f_voice == NULL)
	{
		if(control & 3)
			return(NULL);  // can't open file

		if(SelectPhonemeTableName(voicename) >= 0)
			language_type = voicename;
	}

	if(!tone_only && (translator != NULL))
	{
		DeleteTranslator(translator);
		translator = NULL;
	}

	strcpy(translator_name,language_type);
	strcpy(new_dictionary,language_type);
	strcpy(phonemes_name,language_type);


	if(!tone_only)
	{
		voice = &voicedata;
		strncpy0(voice_identifier,vname,sizeof(voice_identifier));
		voice_name[0] = 0;
		voice_languages[0] = 0;

		voice_selected.identifier = voice_identifier;
		voice_selected.name = voice_name;
		voice_selected.languages = voice_languages;
	}
	else
	{
		// append the variant file name to the voice identifier
		if((p = strchr(voice_identifier,'+')) != NULL)
			*p = 0;    // remove previous variant name
		sprintf(buf,"+%s",&vname[3]);    // omit  !v/  from the variant filename
		strcat(voice_identifier,buf);
		langopts = &translator->langopts;
	}
	VoiceReset(tone_only);

	if(!tone_only)
		SelectPhonemeTableName(phonemes_name);  // set up phoneme_tab


	while((f_voice != NULL) && (fgets_strip(buf,sizeof(buf),f_voice) != NULL))
	{
		// isolate the attribute name
		for(p=buf; (*p != 0) && !isspace(*p); p++);
		*p++ = 0;

		if(buf[0] == 0) continue;

		key = LookupMnem(keyword_tab, buf);

		switch(key)
		{
		case V_LANGUAGE:
			{
				unsigned int len;
				int priority;

				if(tone_only)
					break;
	
				priority = DEFAULT_LANGUAGE_PRIORITY;
				language_name[0] = 0;
	
				sscanf(p,"%s %d",language_name,&priority);
				if(strcmp(language_name,"variant") == 0)
					break;
	
				len = strlen(language_name) + 2;
				// check for space in languages[]
				if(len < (sizeof(voice_languages)-langix-1))
				{
					voice_languages[langix] = priority;
	
					strcpy(&voice_languages[langix+1],language_name);
					langix += len;
				}
	
				// only act on the first language line
				if(language_set == 0)
				{
					language_type = strtok(language_name,"-");
					language_set = 1;
					strcpy(translator_name,language_type);
					strcpy(new_dictionary,language_type);
					strcpy(phonemes_name,language_type);
					SelectPhonemeTableName(phonemes_name);
		
					if(new_translator != NULL)
							DeleteTranslator(new_translator);
		
					new_translator = SelectTranslator(translator_name);
					langopts = &new_translator->langopts;
				}
			}
			break;

		case V_NAME:
			if(tone_only == 0)
			{
				while(isspace(*p)) p++;
				strncpy0(voice_name,p,sizeof(voice_name));
			}
			break;

		case V_GENDER:
			{
				int age;
				char vgender[80];
				sscanf(p,"%s %d",vgender,&age);
				voice_selected.gender = LookupMnem(genders,vgender);
				voice_selected.age = age;
			}
			break;

		case V_TRANSLATOR:
			if(tone_only) break;

			sscanf(p,"%s",translator_name);

			if(new_translator != NULL)
					DeleteTranslator(new_translator);

			new_translator = SelectTranslator(translator_name);
			langopts = &new_translator->langopts;
			break;

		case V_DICTIONARY:        // dictionary
			sscanf(p,"%s",new_dictionary);
			break;

		case V_PHONEMES:        // phoneme table
			sscanf(p,"%s",phonemes_name);
			break;

		case V_FORMANT:
			VoiceFormant(p);
			break;

		case V_PITCH:
			{
				double factor;
				// default is  pitch 82 118
				n = sscanf(p,"%d %d",&pitch1,&pitch2);
				voice->pitch_base = (pitch1 - 9) << 12;
				voice->pitch_range = (pitch2 - pitch1) * 108;
				factor = double(pitch1 - 82)/82;
				voice->formant_factor = (int)((1+factor/4) * 256);  // nominal formant shift for a different voice pitch
			}
			break;

		case V_STRESSLENGTH:   // stressLength
			stress_lengths_set = Read8Numbers(p,stress_lengths);
			break;

		case V_STRESSAMP:   // stressAmp
			stress_amps_set = Read8Numbers(p,stress_amps);
			break;

		case V_STRESSADD:   // stressAdd
			stress_add_set = Read8Numbers(p,stress_add);
			break;

		case V_INTONATION:   // intonation
			sscanf(p,"%d %d",&option_tone_flags,&option_tone2);
			if((option_tone_flags & 0xff) != 0)
				langopts->intonation_group = option_tone_flags & 0xff;
			break;

		case V_TUNES:
			n = sscanf(p,"%s %s %s %s %s %s",names[0],names[1],names[2],names[3],names[4],names[5]);
			langopts->intonation_group = 0;
			for(ix=0; ix<n; ix++)
			{
				if(strcmp(names[ix],"NULL")==0)
					continue;

				if((value = LookupTune(names[ix])) < 0)
					fprintf(stderr,"Unknown tune '%s'\n",names[ix]);
				else
					langopts->tunes[ix] = value;
			}
			break;

		case V_DICTRULES:   // conditional dictionary rules and list entries
			while(*p != 0)
			{
				while(isspace(*p)) p++;
				n = -1;
				if(((n = atoi(p)) > 0) && (n < 32))
				{
					p++;
					conditional_rules |= (1 << n);
				}
				while(isalnum(*p)) p++;
			}
			break;

		case V_REPLACE:
			if(phonemes_set == 0)
			{
				// must set up a phoneme table before we can lookup phoneme mnemonics
				SelectPhonemeTableName(phonemes_name);
				phonemes_set = 1;
			}
			PhonemeReplacement(key,p);
			break;

		case V_WORDGAP:   // words
			sscanf(p,"%d %d",&langopts->word_gap, &langopts->vowel_pause);
			break;

		case V_STRESSRULE:
			sscanf(p,"%d %d %d %d",&langopts->stress_rule,
				&langopts->stress_flags,
				&langopts->unstressed_wd1,
				&langopts->unstressed_wd2);
			break;

		case V_CHARSET:
			if((sscanf(p,"%d",&value)==1) && (value < N_CHARSETS))
				new_translator->charset_a0 = charsets[value];
			break;

		case V_NUMBERS:
			sscanf(p,"%d %d",&langopts->numbers,&langopts->numbers2);
			break;

		case V_OPTION:
			value2 = 0;
			if((sscanf(p,"%s %d %d",option_name,&value,&value2) >= 2) && ((ix = LookupMnem(options_tab, option_name)) >= 0))
			{
				langopts->param[ix] = value;
				langopts->param2[ix] = value2;
			}
			else
			{
				fprintf(stderr,"Bad voice option: %s %s\n",buf,p);
			}
			break;

		case V_ECHO:
			// echo.  suggest: 135mS  11%
			value = 0;
			voice->echo_amp = 0;
			sscanf(p,"%d %d",&voice->echo_delay,&voice->echo_amp);
			break;

		case V_FLUTTER:   // flutter
			if(sscanf(p,"%d",&value)==1)
				voice->flutter = value * 32;
			break;

		case V_ROUGHNESS:   // roughness
			if(sscanf(p,"%d",&value)==1)
				voice->roughness = value;
			break;

		case V_CLARITY:  // formantshape
			if(sscanf(p,"%d",&value)==1)
			{
				if(value > 4)
				{
					voice->peak_shape = 1;  // squarer formant peaks
					value = 4;
				}
				voice->n_harmonic_peaks = 1+value;
			}
			break;

		case V_TONE:
			{
				int tone_data[12];
				ReadTonePoints(p,tone_data);
				SetToneAdjust(voice,tone_data);
			}
			break;

		case V_VOICING:
			if(sscanf(p,"%d",&value)==1)
				voice->voicing = (value * 64)/100;
			break;

		case V_BREATH:
				voice->breath[0] = Read8Numbers(p,&voice->breath[1]);
				for(ix=1; ix<8; ix++)
				{
					if(ix % 2)
						voice->breath[ix] = -voice->breath[ix];
				}
			break;

		case V_BREATHW:
				voice->breathw[0] = Read8Numbers(p,&voice->breathw[1]);
			break;

		case V_CONSONANTS:
			value = sscanf(p,"%d %d",&voice->consonant_amp, &voice->consonant_ampv);
			break;

		case V_SPEED:
			sscanf(p,"%d",&voice->speed_percent);
			break;

		case V_MBROLA:
			{
				int srate = 16000;
				char name[40];
				char phtrans[40];

				phtrans[0] = 0;
				sscanf(p,"%s %s %d",name,phtrans,&srate);
				if(LoadMbrolaTable(name,phtrans,srate) != EE_OK)
				{
					fprintf(stderr,"mbrola voice not found\n");
				}
				voice->samplerate = srate;
			}
			break;

		case V_KLATT:
			voice->klattv[0] = 1;  // default source: IMPULSIVE
			Read8Numbers(p,voice->klattv);
			voice->klattv[KLATT_Kopen] -= 40;
			break;

		case V_FAST:
			Read8Numbers(p,speed.fast_settings);
			SetSpeed(3);
			break;

		default:
			if((key & 0xff00) == 0x100)
			{
				sscanf(p,"%d",&langopts->param[key &0xff]);
			}
			else
			{
				fprintf(stderr,"Bad voice attribute: %s\n",buf);
			}
			break;
		}
	}
	if(f_voice != NULL)
		fclose(f_voice);

	if((new_translator == NULL) && (!tone_only))
	{
		// not set by language attribute
		new_translator = SelectTranslator(translator_name);
	}

	SetSpeed(3);   // for speed_percent

	for(ix=0; ix<N_PEAKS; ix++)
	{
		voice->freq2[ix] = voice->freq[ix];
		voice->height2[ix] = voice->height[ix];
		voice->width2[ix] = voice->width[ix];
	}

	if(tone_only)
	{
		new_translator = translator;
	}
	else
	{
		if((ix = SelectPhonemeTableName(phonemes_name)) < 0)
		{
			fprintf(stderr,"Unknown phoneme table: '%s'\n",phonemes_name);
		}
		voice->phoneme_tab_ix = ix;
		new_translator->phoneme_tab_ix = ix;
		error = LoadDictionary(new_translator, new_dictionary, control & 4);
		if(dictionary_name[0]==0)
			return(NULL);   // no dictionary loaded

		new_translator->dict_condition = conditional_rules;

		voice_languages[langix] = 0;
	}

	langopts = &new_translator->langopts;


	if((value = langopts->param[LOPT_LENGTH_MODS]) != 0)
	{
		SetLengthMods(new_translator,value);
	}

	voice->width[0] = (voice->width[0] * 105)/100;

	if(!tone_only)
	{
		translator = new_translator;
	}

	// relative lengths of different stress syllables
	for(ix=0; ix<stress_lengths_set; ix++)
	{
		translator->stress_lengths[ix] = stress_lengths[ix];
	}
	for(ix=0; ix<stress_add_set; ix++)
	{
		translator->stress_lengths[ix] += stress_add[ix];
	}
	for(ix=0; ix<stress_amps_set; ix++)
	{
		translator->stress_amps[ix] = stress_amps[ix];
		translator->stress_amps_r[ix] = stress_amps[ix] -1;
	}

	return(voice);
}  //  end of LoadVoice


static char *ExtractVoiceVariantName(char *vname, int variant_num)
{//===============================================================
// Remove any voice variant suffix (name or number) from a voice name
// Returns the voice variant name

	char *p;
	static char variant_name[20];
	char variant_prefix[5];

	variant_name[0] = 0;
	sprintf(variant_prefix,"!v%c",PATHSEP);

	if(vname != NULL)
	{
		if((p = strchr(vname,'+')) != NULL)
		{
			// The voice name has a +variant suffix
			*p++ = 0;   // delete the suffix from the voice name
			if(isdigit(*p))
			{
				variant_num = atoi(p);  // variant number
			}
			else
			{
				// voice variant name, not number
				strcpy(variant_name,variant_prefix);
				strncpy0(&variant_name[3],p,sizeof(variant_name)-3);
			}	
		}
	}
	
	if(variant_num > 0)
	{
		if(variant_num < 10)
			sprintf(variant_name,"%sm%d",variant_prefix, variant_num);  // male
		else
			sprintf(variant_name,"%sf%d",variant_prefix, variant_num-10);  // female
	}

	return(variant_name);
}  //  end of ExtractVoiceVariantName



voice_t *LoadVoiceVariant(const char *vname, int variant_num)
{//==========================================================
// Load a voice file.
// Also apply a voice variant if specified by "variant", or by "+number" or "+name" in the "vname"

	voice_t *v;
	char *variant_name;
	char buf[60];

	strncpy0(buf,vname,sizeof(buf));
	variant_name = ExtractVoiceVariantName(buf,variant_num);

	if((v = LoadVoice(buf,0)) == NULL)
		return(NULL);

	if(variant_name[0] != 0)
	{
		v = LoadVoice(variant_name,2);
	}
	return(v);
}



static int __cdecl VoiceNameSorter(const void *p1, const void *p2)
{//=======================================================
	int ix;
	espeak_VOICE *v1 = *(espeak_VOICE **)p1;
	espeak_VOICE *v2 = *(espeak_VOICE **)p2;


	if((ix = strcmp(&v1->languages[1],&v2->languages[1])) != 0)  // primary language name
		return(ix);
	if((ix = v1->languages[0] - v2->languages[0]) != 0)  // priority number
		return(ix);
	return(strcmp(v1->name,v2->name));
}


static int __cdecl VoiceScoreSorter(const void *p1, const void *p2)
{//========================================================
	int ix;
	espeak_VOICE *v1 = *(espeak_VOICE **)p1;
	espeak_VOICE *v2 = *(espeak_VOICE **)p2;

	if((ix = v2->score - v1->score) != 0)
		return(ix);
	return(strcmp(v1->name,v2->name));
}


static int ScoreVoice(espeak_VOICE *voice_spec, const char *spec_language, int spec_n_parts, int spec_lang_len, espeak_VOICE *voice)
{//=========================================================================================================================
	int ix;
	const char *p;
	int c1, c2;
	int language_priority;
	int n_parts;
	int matching;
	int matching_parts;
	int score = 0;
	int x;
	int ratio;
	int required_age;
	int diff;

	p = voice->languages;  // list of languages+dialects for which this voice is suitable

	if(strcmp(spec_language,"mbrola")==0)
	{
		// only list mbrola voices
		if(memcmp(voice->identifier,"mb/",3) == 0)
			return(100);
		return(0);
	}

	if(spec_n_parts == 0)
	{
		score = 100;
	}
	else
	{
		if((*p == 0) && (strcmp(spec_language,"variants")==0))
		{
			// match on a voice with no languages if the required language is "variants"
			score = 100;
		}

		// compare the required language with each of the languages of this voice
		while(*p != 0)
		{
			language_priority = *p++;

			matching = 1;
			matching_parts = 0;
			n_parts = 1;

			for(ix=0; ; ix++)
			{
				if((ix >= spec_lang_len) || ((c1 = spec_language[ix]) == '-'))
					c1 = 0;
				if((c2 = p[ix]) == '-')
					c2 = 0;

				if(c1 != c2)
				{
					matching = 0;
				}

				if(p[ix] == '-')
				{
					n_parts++;
					if(matching)
						matching_parts++;
				}
				if(p[ix] == 0)
					break;
			}
			p += (ix+1);
			matching_parts += matching;  // number of parts which match

			if(matching_parts == 0)
				continue;   // no matching parts for this language

			x = 5;
			// reduce the score if not all parts of the required language match
			if((diff = (spec_n_parts - matching_parts)) > 0)
				x -= diff;

			// reduce score if the language is more specific than required
			if((diff = (n_parts - matching_parts)) > 0)
				x -= diff;

			x = x*100 - (language_priority * 2);

			if(x > score)
				score = x;
		}
	}
	if(score == 0)
		return(0);

	if(voice_spec->name != NULL)
	{
		if(strcmp(voice_spec->name,voice->name)==0)
		{
			// match on voice name
			score += 500;
		}
		else
		if(strcmp(voice_spec->name,voice->identifier)==0)
		{
			score += 400;
		}
	}

	if(((voice_spec->gender == 1) || (voice_spec->gender == 2)) &&
		((voice->gender == 1) || (voice->gender == 2)))
	{
		if(voice_spec->gender == voice->gender)
			score += 50;
		else
			score -= 50;
	}

	if((voice_spec->age <= 12) && (voice->gender == 2) && (voice->age > 12))
	{
		score += 5;  // give some preference for non-child female voice if a child is requested
	}

	if(voice->age != 0)
	{
		if(voice_spec->age == 0)
			required_age = 30;
		else
			required_age = voice_spec->age;

		ratio = (required_age*100)/voice->age;
		if(ratio < 100)
			ratio = 10000/ratio;
		ratio = (ratio - 100)/10;    // 0=exact match, 10=out by factor of 2
		x = 5 - ratio;
		if(x > 0) x = 0;

		score = score + x;

		if(voice_spec->age > 0)
			score += 10;    // required age specified, favour voices with a specified age (near it)
	}
	if(score < 1)
		score = 1;
	return(score);
}  // end of ScoreVoice


static int SetVoiceScores(espeak_VOICE *voice_select, espeak_VOICE **voices, int control)
{//======================================================================================
// control: bit0=1  include mbrola voices
	int ix;
	int score;
	int nv;           // number of candidates
	int n_parts=0;
	int lang_len=0;
	espeak_VOICE *vp;
	char language[80];

	// count number of parts in the specified language
	if((voice_select->languages != NULL) && (voice_select->languages[0] != 0))
	{
		n_parts = 1;
		lang_len = strlen(voice_select->languages);
		for(ix=0; (ix<=lang_len) && ((unsigned)ix < sizeof(language)); ix++)
		{
			if((language[ix] = tolower(voice_select->languages[ix])) == '-')
				n_parts++;
		}
	}
	// select those voices which match the specified language
	nv = 0;
	for(ix=0; ix<n_voices_list; ix++)
	{
		vp = voices_list[ix];

		if(((control & 1) == 0) && (memcmp(vp->identifier,"mb/",3) == 0))
			continue;

		if((score = ScoreVoice(voice_select, language, n_parts, lang_len, voices_list[ix])) > 0)
		{
			voices[nv++] = vp;
			vp->score = score;
		}
	}
	voices[nv] = NULL;  // list terminator

	if(nv==0)
		return(0);

	// sort the selected voices by their score
	qsort(voices,nv,sizeof(espeak_VOICE *),(int (__cdecl *)(const void *,const void *))VoiceScoreSorter);

	return(nv);
}  // end of SetVoiceScores




espeak_VOICE *SelectVoiceByName(espeak_VOICE **voices, const char *name)
{//=====================================================================
	int ix;
	int match_fname = -1;
	int match_fname2 = -1;
	int match_name = -1;
	const char *id;
	int last_part_len;
	char last_part[41];

	if(voices == NULL)
	{
		if(n_voices_list == 0)
			espeak_ListVoices(NULL);   // create the voices list
		voices = voices_list;
	}

	sprintf(last_part,"%c%s",PATHSEP,name);
	last_part_len = strlen(last_part);

	for(ix=0; voices[ix] != NULL; ix++)
	{
		if(strcmp(name,voices[ix]->name)==0)
		{
			match_name = ix;   // found matching voice name
			break;
		}
		else
		if(strcmp(name,id = voices[ix]->identifier)==0)
		{
			match_fname = ix;  // matching identifier, use this if no matching name
		}
		else
		if(strcmp(last_part,&id[strlen(id)-last_part_len])==0)
		{
			match_fname2 = ix;
		}
	}

	if(match_name < 0)
	{
		match_name = match_fname;  // no matching name, try matching filename
		if(match_name < 0)
			match_name = match_fname2;  // try matching just the last part of the filename
	}

	if(match_name < 0)
		return(NULL);

	return(voices[match_name]);
}  //  end of SelectVoiceByName




char const *SelectVoice(espeak_VOICE *voice_select, int *found)
{//============================================================
// Returns a path within espeak-voices, with a possible +variant suffix
// variant is an output-only parameter
	int nv;           // number of candidates
	int ix, ix2;
	int j;
	int n_variants;
	int variant_number;
	int gender;
	int skip;
	int aged=1;
	char *variant_name;
	const char *p, *p_start;
	espeak_VOICE *vp = NULL;
	espeak_VOICE *vp2;
	espeak_VOICE voice_select2;
	espeak_VOICE *voices[N_VOICES_LIST]; // list of candidates
	espeak_VOICE *voices2[N_VOICES_LIST+N_VOICE_VARIANTS];
	static espeak_VOICE voice_variants[N_VOICE_VARIANTS];
	static char voice_id[50];

	*found = 1;
	memcpy(&voice_select2,voice_select,sizeof(voice_select2));

	if(n_voices_list == 0)
		espeak_ListVoices(NULL);   // create the voices list

	if((voice_select2.languages == NULL) || (voice_select2.languages[0] == 0))
	{
		// no language is specified. Get language from the named voice
		static char buf[60];
	
		if(voice_select2.name == NULL)
		{
			if((voice_select2.name = voice_select2.identifier) == NULL)
				voice_select2.name = "default";
		}
	
		strncpy0(buf,voice_select2.name,sizeof(buf));
		variant_name = ExtractVoiceVariantName(buf,0);

		vp = SelectVoiceByName(voices_list,buf);
		if(vp != NULL)
		{
			voice_select2.languages = &(vp->languages[1]);

			if((voice_select2.gender==0) && (voice_select2.age==0) && (voice_select2.variant==0))
			{
				if(variant_name[0] != 0)
				{
					sprintf(voice_id,"%s+%s",vp->identifier,&variant_name[3]);  // omit the  !v/  from variant_name
					return(voice_id);
				}

				return(vp->identifier);
			}
		}
	}

	// select and sort voices for the required language
	nv = SetVoiceScores(&voice_select2,voices,0);

	if(nv == 0)
	{
		// no matching voice, choose the default
		*found = 0;
		if((voices[0] = SelectVoiceByName(voices_list,"default")) != NULL)
			nv = 1;
	}

	gender = 0;
	if((voice_select2.gender == 2) || ((voice_select2.age > 0) && (voice_select2.age < 13)))
		gender = 2;
	else
	if(voice_select2.gender == 1)
		gender = 1;

#define AGE_OLD  60
	if(voice_select2.age < AGE_OLD)
		aged = 0;

	p = p_start = variant_lists[gender];
	if(aged == 0)
		p++;   // the first voice in the variants list is older

	// add variants for the top voices
	n_variants = 0;
	for(ix=0, ix2=0; ix<nv; ix++)
	{
		vp = voices[ix];
		// is the main voice the required gender?
		skip=0;
		if((gender != 0) && (vp->gender != gender))
		{
			skip=1;
		}
		if((ix2==0) && aged && (vp->age < AGE_OLD))
		{
			skip=1;
		}
		if(skip==0)
		{
			voices2[ix2++] = vp; 
		}

		for(j=0; (j < vp->xx1) && (n_variants < N_VOICE_VARIANTS);)
		{
			if((variant_number = *p) == 0)
			{
				p = p_start;
				continue;
			}

			vp2 = &voice_variants[n_variants++];        // allocate space for voice variant
			memcpy(vp2,vp,sizeof(espeak_VOICE));        // copy from the original voice
			vp2->variant = variant_number;
			voices2[ix2++] = vp2;
			p++;
			j++;
		}
	}
	// add any more variants to the end of the list
	while((vp != NULL) && ((variant_number = *p++) != 0) && (n_variants < N_VOICE_VARIANTS))
	{
		vp2 = &voice_variants[n_variants++];        // allocate space for voice variant
		memcpy(vp2,vp,sizeof(espeak_VOICE));        // copy from the original voice
		vp2->variant = variant_number;
		voices2[ix2++] = vp2;
	}

	// index the sorted list by the required variant number
	vp = voices2[voice_select2.variant % ix2];

	if(vp->variant != 0)
	{
		variant_name = ExtractVoiceVariantName(NULL,vp->variant);
		sprintf(voice_id,"%s+%s",vp->identifier,&variant_name[3]);
		return(voice_id);
	}

	return(vp->identifier);
}  //  end of SelectVoice



static void GetVoices(const char *path)
{//====================================
	FILE *f_voice;
	espeak_VOICE *voice_data;
	int ftype;
	char fname[sizeof(path_home)+100];

#ifdef PLATFORM_RISCOS
	int len;
	int *type;
	char *p;
	_kernel_swi_regs regs;
	_kernel_oserror *error;
	char buf[80];
	char directory2[sizeof(path_home)+100];

	regs.r[0] = 10;
	regs.r[1] = (int)path;
	regs.r[2] = (int)buf;
	regs.r[3] = 1;
	regs.r[4] = 0;
	regs.r[5] = sizeof(buf);
	regs.r[6] = 0;

	while(regs.r[3] > 0)
	{
		error = _kernel_swi(0x0c+0x20000,&regs,&regs);      /* OS_GBPB 10, read directory entries */
		if((error != NULL) || (regs.r[3] == 0))
		{
			break;
		}
		type = (int *)(&buf[16]);
		len = strlen(&buf[20]);
		sprintf(fname,"%s.%s",path,&buf[20]);

		if(*type == 2)
		{
			// a sub-directory
			GetVoices(fname);
		}
		else
		{
			// a regular line, add it to the voices list	
			if((f_voice = fopen(fname,"r")) == NULL)
				continue;
		
			// pass voice file name within the voices directory
			voice_data = ReadVoiceFile(f_voice, fname+len_path_voices, &buf[20]);
			fclose(f_voice);

			if(voice_data != NULL)
			{
				voices_list[n_voices_list++] = voice_data;
			}
		}
	}
#else
#ifdef PLATFORM_WINDOWS
   WIN32_FIND_DATAA FindFileData;
   HANDLE hFind = INVALID_HANDLE_VALUE;

#undef UNICODE         // we need FindFirstFileA() which takes an 8-bit c-string
	sprintf(fname,"%s\\*",path);
	hFind = FindFirstFileA(fname, &FindFileData);
	if(hFind == INVALID_HANDLE_VALUE)
		return;

	do {
		sprintf(fname,"%s%c%s",path,PATHSEP,FindFileData.cFileName);

		ftype = GetFileLength(fname);

		if((ftype == -2) && (FindFileData.cFileName[0] != '.'))
		{
			// a sub-sirectory
			GetVoices(fname);
		}
		else
		if(ftype > 0)
		{
			// a regular line, add it to the voices list	
			if((f_voice = fopen(fname,"r")) == NULL)
				continue;
		
			// pass voice file name within the voices directory
			voice_data = ReadVoiceFile(f_voice, fname+len_path_voices, FindFileData.cFileName);
			fclose(f_voice);

			if(voice_data != NULL)
			{
				voices_list[n_voices_list++] = voice_data;
			}
		}
	} while(FindNextFileA(hFind, &FindFileData) != 0);
	FindClose(hFind);

#else
	DIR *dir;
	struct dirent *ent;

	if((dir = opendir((char *)path)) == NULL)    // note: (char *) is needed for WINCE
		return;

	while((ent = readdir(dir)) != NULL)
	{
		if(n_voices_list >= (N_VOICES_LIST-2))
			break;   // voices list is full

		sprintf(fname,"%s%c%s",path,PATHSEP,ent->d_name);

		ftype = GetFileLength(fname);

		if((ftype == -2) && (ent->d_name[0] != '.'))
		{
			// a sub-sirectory
			GetVoices(fname);
		}
		else
		if(ftype > 0)
		{
			// a regular line, add it to the voices list	
			if((f_voice = fopen(fname,"r")) == NULL)
				continue;
		
			// pass voice file name within the voices directory
			voice_data = ReadVoiceFile(f_voice, fname+len_path_voices, ent->d_name);
			fclose(f_voice);

			if(voice_data != NULL)
			{
				voices_list[n_voices_list++] = voice_data;
			}
		}
	}
	closedir(dir);
#endif
#endif
}   // end of GetVoices



espeak_ERROR SetVoiceByName(const char *name)
{//=========================================
	espeak_VOICE *v;
	espeak_VOICE voice_selector;
	char *variant_name;
	static char buf[60];

	strncpy0(buf,name,sizeof(buf));
	variant_name = ExtractVoiceVariantName(buf,0);

	memset(&voice_selector,0,sizeof(voice_selector));
//	voice_selector.name = buf;
	voice_selector.name = (char *)name;  // include variant name in voice stack ??

	// first check for a voice with this filename
	// This may avoid the need to call espeak_ListVoices().

	if(LoadVoice(buf,1) != NULL)
	{
		if(variant_name[0] != 0)
		{
			LoadVoice(variant_name,2);
		}

		DoVoiceChange(voice);
		SetVoiceStack(&voice_selector);
		return(EE_OK);
	}

	if(n_voices_list == 0)
		espeak_ListVoices(NULL);   // create the voices list

	if((v = SelectVoiceByName(voices_list,buf)) != NULL)
	{
		if(LoadVoice(v->identifier,0) != NULL)
		{
			if(variant_name[0] != 0)
			{
				LoadVoice(variant_name,2);
			}
			DoVoiceChange(voice);
			SetVoiceStack(&voice_selector);
			return(EE_OK);
		}
	}
	return(EE_INTERNAL_ERROR);   // voice name not found
}  // end of SetVoiceByName



espeak_ERROR SetVoiceByProperties(espeak_VOICE *voice_selector)
{//============================================================
	const char *voice_id;
	int voice_found;

	voice_id = SelectVoice(voice_selector, &voice_found);

	if(voice_found == 0)
		return(EE_NOT_FOUND);

	LoadVoiceVariant(voice_id,0);
	DoVoiceChange(voice);
	SetVoiceStack(voice_selector);

	return(EE_OK);
}  //  end of SetVoiceByProperties




//=======================================================================
//  Library Interface Functions
//=======================================================================
#pragma GCC visibility push(default)


ESPEAK_API const espeak_VOICE **espeak_ListVoices(espeak_VOICE *voice_spec)
{//========================================================================
#ifndef PLATFORM_RISCOS
	int ix;
	int j;
	espeak_VOICE *v;
	static espeak_VOICE *voices[N_VOICES_LIST];
	char path_voices[sizeof(path_home)+12];

	// free previous voice list data

	for(ix=0; ix<n_voices_list; ix++)
	{
		if(voices_list[ix] != NULL)
			free(voices_list[ix]);
	}
	n_voices_list = 0;

	sprintf(path_voices,"%s%cvoices",path_home,PATHSEP);
	len_path_voices = strlen(path_voices)+1;

	GetVoices(path_voices);
	voices_list[n_voices_list] = NULL;  // voices list terminator

	// sort the voices list
	qsort(voices_list,n_voices_list,sizeof(espeak_VOICE *),
		(int (__cdecl *)(const void *,const void *))VoiceNameSorter);


	if(voice_spec)
	{
		// select the voices which match the voice_spec, and sort them by preference
		SetVoiceScores(voice_spec,voices,1);
	}
	else
	{
		// list all: omit variant voices and mbrola voices
		j = 0;
		for(ix=0; (v = voices_list[ix]) != NULL; ix++)
		{
			if((v->languages[0] != 0) && (strcmp(&v->languages[1],"variant") != 0) && (memcmp(v->identifier,"mb/",3) != 0))
			{
				voices[j++] = v;
			}
		}
		voices[j] = NULL;
	}
	return((const espeak_VOICE **)voices);
#endif
	return((const espeak_VOICE **)voices_list);
}  //  end of espeak_ListVoices



ESPEAK_API espeak_VOICE *espeak_GetCurrentVoice(void)
{//==================================================
	return(&voice_selected);
}

#pragma GCC visibility pop


By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
CEO bring-it-together s.r.o.
Slovakia Slovakia
Jozef Božek is currently a software engineer at bring-it-together s.r.o. in area of large scale infomation systems and mobile applications development.
He has been developing in C++ nearly full time since 2000, in Java since 2004 and in Objective-C since 2009. He is programming using Java EE SDK, iOS SDK, COM/DCOM, MFC, ATL, STL and so on Smile | :)

Comments and Discussions