text-to-speech through DAC to audio amp/speaker

Dependencies:   mbed

text-to-speech TTS

Committer:
manitou
Date:
Sat Jun 24 14:10:48 2017 +0000
Revision:
2:eceeac07154b
Parent:
1:548323cfdb5d
Child:
3:d12c34704b6d
don't dac.write(0) for soundOn/Off -- speaker pop

Who changed what in which revision?

UserRevisionLine numberNew contents of line
manitou 0:bcd16e4a0207 1 /**
manitou 0:bcd16e4a0207 2 * Text To Speech synthesis library
manitou 0:bcd16e4a0207 3 * Copyright (c) 2008 Clive Webster. All rights reserved.
manitou 0:bcd16e4a0207 4 *
manitou 0:bcd16e4a0207 5 * Nov. 29th 2009 - Modified to work with Arduino by Gabriel Petrut:
manitou 0:bcd16e4a0207 6 * The Text To Speech library uses Timer1 to generate the PWM
manitou 0:bcd16e4a0207 7 * output on digital pin 10. The output signal needs to be fed
manitou 0:bcd16e4a0207 8 * to an RC filter then through an amplifier to the speaker.
manitou 0:bcd16e4a0207 9 * http://www.tehnorama.ro/minieric-modulul-de-control-si-sinteza-vocala/
manitou 0:bcd16e4a0207 10 *
manitou 0:bcd16e4a0207 11 * Modified to allow use of different PWM pins by Stephen Crane.
manitou 0:bcd16e4a0207 12 * Modified for Timer5 on Arduino Mega2560 by Peter Dambrowsky.
manitou 0:bcd16e4a0207 13 */
manitou 0:bcd16e4a0207 14
manitou 0:bcd16e4a0207 15 #include "TTS.h"
manitou 0:bcd16e4a0207 16
manitou 0:bcd16e4a0207 17 AnalogOut dac(DACpin);
manitou 0:bcd16e4a0207 18
manitou 0:bcd16e4a0207 19 // Random number seed
manitou 0:bcd16e4a0207 20 static byte seed0;
manitou 0:bcd16e4a0207 21 static byte seed1;
manitou 0:bcd16e4a0207 22 static byte seed2;
manitou 0:bcd16e4a0207 23
manitou 0:bcd16e4a0207 24 static char phonemes[128];
manitou 0:bcd16e4a0207 25 static char modifier[128]; // must be same size as 'phonemes'
manitou 0:bcd16e4a0207 26 static char g_text[128];
manitou 0:bcd16e4a0207 27
manitou 0:bcd16e4a0207 28 static byte defaultPitch = 7;
manitou 0:bcd16e4a0207 29
manitou 0:bcd16e4a0207 30 // Lookup user specified pitch changes
manitou 0:bcd16e4a0207 31 static const byte PROGMEM PitchesP[] = { 1, 2, 4, 6, 8, 10, 13, 16 };
manitou 0:bcd16e4a0207 32
manitou 0:bcd16e4a0207 33 /**
manitou 0:bcd16e4a0207 34 * Find the single character 'token' in 'vocab'
manitou 0:bcd16e4a0207 35 * and append its phonemes to dest[x]
manitou 0:bcd16e4a0207 36 */
manitou 0:bcd16e4a0207 37 static int copyToken(char token, char *dest, int x, const VOCAB * vocab)
manitou 0:bcd16e4a0207 38 {
manitou 0:bcd16e4a0207 39 for (unsigned int ph = 0; ph < numVocab; ph++) {
manitou 0:bcd16e4a0207 40 const char *txt = (const char *) pgm_read_word(&vocab[ph].txt);
manitou 0:bcd16e4a0207 41 if (pgm_read_byte(&txt[0]) == token && pgm_read_byte(&txt[1]) == 0) {
manitou 0:bcd16e4a0207 42 const char *src =
manitou 0:bcd16e4a0207 43 (const char *) pgm_read_word(&vocab[ph].phoneme);
manitou 0:bcd16e4a0207 44 while (pgm_read_byte(src)) {
manitou 0:bcd16e4a0207 45 dest[x++] = pgm_read_byte(src);
manitou 0:bcd16e4a0207 46 src++;
manitou 0:bcd16e4a0207 47 }
manitou 0:bcd16e4a0207 48 break;
manitou 0:bcd16e4a0207 49 }
manitou 0:bcd16e4a0207 50 }
manitou 0:bcd16e4a0207 51 return x;
manitou 0:bcd16e4a0207 52 }
manitou 0:bcd16e4a0207 53
manitou 0:bcd16e4a0207 54 static byte whitespace(char c)
manitou 0:bcd16e4a0207 55 {
manitou 0:bcd16e4a0207 56 return (c == 0 || c == ' ' || c == ',' || c == '.' || c == '?'
manitou 0:bcd16e4a0207 57 || c == '\'' || c == '!' || c == ':' || c == '/');
manitou 0:bcd16e4a0207 58 }
manitou 0:bcd16e4a0207 59
manitou 0:bcd16e4a0207 60 /**
manitou 0:bcd16e4a0207 61 * Enter:
manitou 0:bcd16e4a0207 62 * src => English text in upper case
manitou 0:bcd16e4a0207 63 * vocab => VOCAB array
manitou 0:bcd16e4a0207 64 * dest => address to return result
manitou 0:bcd16e4a0207 65 * return 1 if ok, or 0 if error
manitou 0:bcd16e4a0207 66 */
manitou 0:bcd16e4a0207 67 static int textToPhonemes(const char *src, const VOCAB * vocab, char *dest)
manitou 0:bcd16e4a0207 68 {
manitou 0:bcd16e4a0207 69 int outIndex = 0; // Current offset into dest
manitou 0:bcd16e4a0207 70 int inIndex = -1; // Starts at -1 so that a leading space is assumed
manitou 0:bcd16e4a0207 71
manitou 0:bcd16e4a0207 72 while (inIndex == -1 || src[inIndex]) { // until end of text
manitou 0:bcd16e4a0207 73 int maxMatch = 0; // Max chars matched on input text
manitou 0:bcd16e4a0207 74 int numOut = 0; // Number of characters copied to output stream for the best match
manitou 0:bcd16e4a0207 75 boolean endsInWhiteSpace = FALSE;
manitou 0:bcd16e4a0207 76 int maxWildcardPos = 0;
manitou 0:bcd16e4a0207 77
manitou 0:bcd16e4a0207 78 // Get next phoneme, P2
manitou 0:bcd16e4a0207 79 for (unsigned int ph = 0; ph < numVocab; ph++) {
manitou 0:bcd16e4a0207 80 int y, x;
manitou 0:bcd16e4a0207 81 char wildcard = 0; // modifier
manitou 0:bcd16e4a0207 82 int wildcardInPos = 0;
manitou 0:bcd16e4a0207 83 boolean hasWhiteSpace = FALSE;
manitou 0:bcd16e4a0207 84 const char *text =
manitou 0:bcd16e4a0207 85 (const char *) pgm_read_word(&vocab[ph].txt);
manitou 0:bcd16e4a0207 86 const char *phon =
manitou 0:bcd16e4a0207 87 (const char *) pgm_read_word(&vocab[ph].phoneme);
manitou 0:bcd16e4a0207 88
manitou 0:bcd16e4a0207 89 for (y = 0;; y++) {
manitou 0:bcd16e4a0207 90 char nextVocabChar = pgm_read_byte(&text[y]);
manitou 0:bcd16e4a0207 91 char nextCharIn =
manitou 0:bcd16e4a0207 92 (y + inIndex == -1) ? ' ' : src[y + inIndex];
manitou 0:bcd16e4a0207 93 if (nextCharIn >= 'a' && nextCharIn <= 'z')
manitou 0:bcd16e4a0207 94 nextCharIn = nextCharIn - 'a' + 'A';
manitou 0:bcd16e4a0207 95
manitou 0:bcd16e4a0207 96 if (nextVocabChar == '#' && nextCharIn >= 'A'
manitou 0:bcd16e4a0207 97 && nextCharIn <= 'Z') {
manitou 0:bcd16e4a0207 98 wildcard = nextCharIn; // The character equivalent to the '#'
manitou 0:bcd16e4a0207 99 wildcardInPos = y;
manitou 0:bcd16e4a0207 100 continue;
manitou 0:bcd16e4a0207 101 }
manitou 0:bcd16e4a0207 102
manitou 0:bcd16e4a0207 103 if (nextVocabChar == '_') {
manitou 0:bcd16e4a0207 104 // try to match against a white space
manitou 0:bcd16e4a0207 105 hasWhiteSpace = TRUE;
manitou 0:bcd16e4a0207 106 if (whitespace(nextCharIn))
manitou 0:bcd16e4a0207 107 continue;
manitou 0:bcd16e4a0207 108 y--;
manitou 0:bcd16e4a0207 109 break;
manitou 0:bcd16e4a0207 110 }
manitou 0:bcd16e4a0207 111 // check for end of either string
manitou 0:bcd16e4a0207 112 if (nextVocabChar == 0 || nextCharIn == 0)
manitou 0:bcd16e4a0207 113 break;
manitou 0:bcd16e4a0207 114
manitou 0:bcd16e4a0207 115 if (nextVocabChar != nextCharIn)
manitou 0:bcd16e4a0207 116 break;
manitou 0:bcd16e4a0207 117 }
manitou 0:bcd16e4a0207 118
manitou 0:bcd16e4a0207 119 // See if its the longest complete match so far
manitou 0:bcd16e4a0207 120 if (y <= maxMatch || pgm_read_byte(&text[y]))
manitou 0:bcd16e4a0207 121 continue;
manitou 0:bcd16e4a0207 122
manitou 0:bcd16e4a0207 123 // This is the longest complete match
manitou 0:bcd16e4a0207 124 maxMatch = y;
manitou 0:bcd16e4a0207 125 maxWildcardPos = 0;
manitou 0:bcd16e4a0207 126 x = outIndex; // offset into phoneme return data
manitou 0:bcd16e4a0207 127
manitou 0:bcd16e4a0207 128 // Copy the matching phrase changing any '#' to the phoneme for the wildcard
manitou 0:bcd16e4a0207 129 for (y = 0;; y++) {
manitou 0:bcd16e4a0207 130 char c = pgm_read_byte(&phon[y]);
manitou 0:bcd16e4a0207 131 if (c == 0)
manitou 0:bcd16e4a0207 132 break;
manitou 0:bcd16e4a0207 133 if (c == '#') {
manitou 0:bcd16e4a0207 134 if (pgm_read_byte(&phon[y + 1]) == 0) {
manitou 0:bcd16e4a0207 135 // replacement ends in wildcard
manitou 0:bcd16e4a0207 136 maxWildcardPos = wildcardInPos;
manitou 0:bcd16e4a0207 137 } else {
manitou 0:bcd16e4a0207 138 x = copyToken(wildcard, dest, x, vocab); // Copy the phonemes for the wildcard character
manitou 0:bcd16e4a0207 139 }
manitou 0:bcd16e4a0207 140 } else {
manitou 0:bcd16e4a0207 141 dest[x++] = c;
manitou 0:bcd16e4a0207 142 }
manitou 0:bcd16e4a0207 143 }
manitou 0:bcd16e4a0207 144 dest[x] = 0;
manitou 0:bcd16e4a0207 145 endsInWhiteSpace = hasWhiteSpace;
manitou 0:bcd16e4a0207 146
manitou 0:bcd16e4a0207 147 // 14
manitou 0:bcd16e4a0207 148 numOut = x - outIndex; // The number of bytes added
manitou 0:bcd16e4a0207 149 }
manitou 0:bcd16e4a0207 150 // 15 - end of vocab table
manitou 0:bcd16e4a0207 151
manitou 0:bcd16e4a0207 152 // 16
manitou 0:bcd16e4a0207 153 if (endsInWhiteSpace)
manitou 0:bcd16e4a0207 154 maxMatch--;
manitou 0:bcd16e4a0207 155
manitou 0:bcd16e4a0207 156 // 17
manitou 0:bcd16e4a0207 157 if (maxMatch == 0) {
manitou 0:bcd16e4a0207 158 //loggerP(PSTR("Mistake in SAY, no token for "));
manitou 0:bcd16e4a0207 159 //logger(&src[inIndex]);
manitou 0:bcd16e4a0207 160 //loggerCRLF();
manitou 0:bcd16e4a0207 161 return 0;
manitou 0:bcd16e4a0207 162 }
manitou 0:bcd16e4a0207 163 // 20
manitou 0:bcd16e4a0207 164 outIndex += numOut;
manitou 0:bcd16e4a0207 165 if (outIndex > 128 - 16) {
manitou 0:bcd16e4a0207 166 //loggerP(PSTR("Mistake in SAY, text too long\n"));
manitou 0:bcd16e4a0207 167 return 0;
manitou 0:bcd16e4a0207 168 }
manitou 0:bcd16e4a0207 169 // 21
manitou 0:bcd16e4a0207 170 inIndex += (maxWildcardPos > 0) ? maxWildcardPos : maxMatch;
manitou 0:bcd16e4a0207 171 }
manitou 0:bcd16e4a0207 172 return 1;
manitou 0:bcd16e4a0207 173 }
manitou 0:bcd16e4a0207 174
manitou 0:bcd16e4a0207 175 /**
manitou 0:bcd16e4a0207 176 * Convert phonemes to data string
manitou 0:bcd16e4a0207 177 * Enter: textp = phonemes string
manitou 0:bcd16e4a0207 178 * Return: phonemes = string of sound data
manitou 0:bcd16e4a0207 179 * modifier = 2 bytes per sound data
manitou 0:bcd16e4a0207 180 */
manitou 0:bcd16e4a0207 181 static int phonemesToData(const char *textp, const PHONEME * phoneme)
manitou 0:bcd16e4a0207 182 {
manitou 0:bcd16e4a0207 183 unsigned int phonemeOut = 0; // offset into the phonemes array
manitou 0:bcd16e4a0207 184 unsigned int modifierOut = 0; // offset into the modifiers array
manitou 0:bcd16e4a0207 185 unsigned int L81 = 0; // attenuate
manitou 0:bcd16e4a0207 186 unsigned int L80 = 16;
manitou 0:bcd16e4a0207 187
manitou 0:bcd16e4a0207 188 while (*textp) {
manitou 0:bcd16e4a0207 189 // P20: Get next phoneme
manitou 0:bcd16e4a0207 190 boolean anyMatch = FALSE;
manitou 0:bcd16e4a0207 191 int longestMatch = 0;
manitou 0:bcd16e4a0207 192 int numOut = 0; // The number of bytes copied to the output for the longest match
manitou 0:bcd16e4a0207 193
manitou 0:bcd16e4a0207 194 // Get next phoneme, P2
manitou 0:bcd16e4a0207 195 for (unsigned int ph = 0; ph < numPhoneme; ph++) {
manitou 0:bcd16e4a0207 196 int numChars;
manitou 0:bcd16e4a0207 197
manitou 0:bcd16e4a0207 198 // Locate start of next phoneme
manitou 0:bcd16e4a0207 199 const char *ph_text =
manitou 0:bcd16e4a0207 200 (const char *) pgm_read_word(&phoneme[ph].txt);
manitou 0:bcd16e4a0207 201
manitou 0:bcd16e4a0207 202 // Set 'numChars' to the number of characters
manitou 0:bcd16e4a0207 203 // that we match against this phoneme
manitou 0:bcd16e4a0207 204 for (numChars = 0; textp[numChars]; numChars++) {
manitou 0:bcd16e4a0207 205
manitou 0:bcd16e4a0207 206 // get next input character and make lower case
manitou 0:bcd16e4a0207 207 char nextChar = textp[numChars];
manitou 0:bcd16e4a0207 208 if (nextChar >= 'A' && nextChar <= 'Z')
manitou 0:bcd16e4a0207 209 nextChar = nextChar - 'A' + 'a';
manitou 0:bcd16e4a0207 210
manitou 0:bcd16e4a0207 211 if (nextChar != pgm_read_byte(&ph_text[numChars]))
manitou 0:bcd16e4a0207 212 break;
manitou 0:bcd16e4a0207 213 }
manitou 0:bcd16e4a0207 214
manitou 0:bcd16e4a0207 215 // if not the longest match so far then ignore
manitou 0:bcd16e4a0207 216 if (numChars <= longestMatch)
manitou 0:bcd16e4a0207 217 continue;
manitou 0:bcd16e4a0207 218
manitou 0:bcd16e4a0207 219 // partial phoneme match
manitou 0:bcd16e4a0207 220 if (pgm_read_byte(&ph_text[numChars]))
manitou 0:bcd16e4a0207 221 continue;
manitou 0:bcd16e4a0207 222
manitou 0:bcd16e4a0207 223 // P7: we have matched the whole phoneme
manitou 0:bcd16e4a0207 224 longestMatch = numChars;
manitou 0:bcd16e4a0207 225
manitou 0:bcd16e4a0207 226 // Copy phoneme data to 'phonemes'
manitou 0:bcd16e4a0207 227 const char *ph_ph =
manitou 0:bcd16e4a0207 228 (const char *) pgm_read_word(&phoneme[ph].phoneme);
manitou 0:bcd16e4a0207 229 for (numOut = 0; pgm_read_byte(&ph_ph[numOut]); numOut++)
manitou 0:bcd16e4a0207 230 phonemes[phonemeOut + numOut] =
manitou 0:bcd16e4a0207 231 pgm_read_byte(&ph_ph[numOut]);
manitou 0:bcd16e4a0207 232
manitou 0:bcd16e4a0207 233 L81 = pgm_read_byte(&phoneme[ph].attenuate) + '0';
manitou 0:bcd16e4a0207 234 anyMatch = TRUE; // phoneme match found
manitou 0:bcd16e4a0207 235
manitou 0:bcd16e4a0207 236 modifier[modifierOut] = -1;
manitou 0:bcd16e4a0207 237 modifier[modifierOut + 1] = 0;
manitou 0:bcd16e4a0207 238
manitou 0:bcd16e4a0207 239 // Get char from text after the phoneme and test if it is a numeric
manitou 0:bcd16e4a0207 240 if (textp[longestMatch] >= '0' && textp[longestMatch] <= '9') {
manitou 0:bcd16e4a0207 241 // Pitch change requested
manitou 0:bcd16e4a0207 242 modifier[modifierOut] =
manitou 0:bcd16e4a0207 243 pgm_read_byte(&PitchesP[textp[longestMatch] - '1']);
manitou 0:bcd16e4a0207 244 modifier[modifierOut + 1] = L81;
manitou 0:bcd16e4a0207 245 longestMatch++;
manitou 0:bcd16e4a0207 246 }
manitou 0:bcd16e4a0207 247 // P10
manitou 0:bcd16e4a0207 248 if (L81 != '0' && L81 != L80 && modifier[modifierOut] >= 0) {
manitou 0:bcd16e4a0207 249 modifier[modifierOut - 2] = modifier[modifierOut];
manitou 0:bcd16e4a0207 250 modifier[modifierOut - 1] = '0';
manitou 0:bcd16e4a0207 251 continue;
manitou 0:bcd16e4a0207 252 }
manitou 0:bcd16e4a0207 253 // P11
manitou 0:bcd16e4a0207 254 if ((textp[longestMatch - 1] | 0x20) == 0x20) {
manitou 0:bcd16e4a0207 255 // end of input string or a space
manitou 0:bcd16e4a0207 256 modifier[modifierOut] =
manitou 0:bcd16e4a0207 257 (modifierOut == 0) ? 16 : modifier[modifierOut - 2];
manitou 0:bcd16e4a0207 258 }
manitou 0:bcd16e4a0207 259 } // next phoneme
manitou 0:bcd16e4a0207 260
manitou 0:bcd16e4a0207 261 // p13
manitou 0:bcd16e4a0207 262 L80 = L81;
manitou 0:bcd16e4a0207 263 if (longestMatch == 0 && !anyMatch) {
manitou 0:bcd16e4a0207 264 //loggerP(PSTR("Mistake in speech at "));
manitou 0:bcd16e4a0207 265 //logger(textp);
manitou 0:bcd16e4a0207 266 //loggerCRLF();
manitou 0:bcd16e4a0207 267 return 0;
manitou 0:bcd16e4a0207 268 }
manitou 0:bcd16e4a0207 269 // Move over the bytes we have copied to the output
manitou 0:bcd16e4a0207 270 phonemeOut += numOut;
manitou 0:bcd16e4a0207 271
manitou 0:bcd16e4a0207 272 if (phonemeOut > sizeof(phonemes) - 16) {
manitou 0:bcd16e4a0207 273 //loggerP(PSTR("Line too long\n"));
manitou 0:bcd16e4a0207 274 return 0;
manitou 0:bcd16e4a0207 275 }
manitou 0:bcd16e4a0207 276 // P16
manitou 0:bcd16e4a0207 277
manitou 0:bcd16e4a0207 278 // Copy the modifier setting to each sound data element for this phoneme
manitou 0:bcd16e4a0207 279 if (numOut > 2)
manitou 0:bcd16e4a0207 280 for (int count = 0; count != numOut; count += 2) {
manitou 0:bcd16e4a0207 281 modifier[modifierOut + count + 2] =
manitou 0:bcd16e4a0207 282 modifier[modifierOut + count];
manitou 0:bcd16e4a0207 283 modifier[modifierOut + count + 3] = 0;
manitou 0:bcd16e4a0207 284 }
manitou 0:bcd16e4a0207 285 modifierOut += numOut;
manitou 0:bcd16e4a0207 286
manitou 0:bcd16e4a0207 287 //p21
manitou 0:bcd16e4a0207 288 textp += longestMatch;
manitou 0:bcd16e4a0207 289 }
manitou 0:bcd16e4a0207 290
manitou 0:bcd16e4a0207 291 phonemes[phonemeOut++] = 'z';
manitou 0:bcd16e4a0207 292 phonemes[phonemeOut++] = 'z';
manitou 0:bcd16e4a0207 293 phonemes[phonemeOut++] = 'z';
manitou 0:bcd16e4a0207 294 phonemes[phonemeOut++] = 'z';
manitou 0:bcd16e4a0207 295
manitou 0:bcd16e4a0207 296 while (phonemeOut < sizeof(phonemes))
manitou 0:bcd16e4a0207 297 phonemes[phonemeOut++] = 0;
manitou 0:bcd16e4a0207 298
manitou 0:bcd16e4a0207 299 while (modifierOut < sizeof(modifier)) {
manitou 0:bcd16e4a0207 300 modifier[modifierOut++] = -1;
manitou 0:bcd16e4a0207 301 modifier[modifierOut++] = 0;
manitou 0:bcd16e4a0207 302 }
manitou 0:bcd16e4a0207 303
manitou 0:bcd16e4a0207 304 return 1;
manitou 0:bcd16e4a0207 305 }
manitou 0:bcd16e4a0207 306
manitou 0:bcd16e4a0207 307 /*
manitou 0:bcd16e4a0207 308 * A delay loop that doesn't change with different optimisation settings
manitou 0:bcd16e4a0207 309 */
manitou 0:bcd16e4a0207 310
manitou 0:bcd16e4a0207 311
manitou 0:bcd16e4a0207 312 static void pause(byte delays)
manitou 0:bcd16e4a0207 313 {
manitou 0:bcd16e4a0207 314 wait_us(delays*6);
manitou 0:bcd16e4a0207 315 }
manitou 0:bcd16e4a0207 316
manitou 0:bcd16e4a0207 317 static void delay2(byte d)
manitou 0:bcd16e4a0207 318 {
manitou 0:bcd16e4a0207 319 wait_us(d*3127);
manitou 0:bcd16e4a0207 320 }
manitou 0:bcd16e4a0207 321
manitou 0:bcd16e4a0207 322 /*
manitou 0:bcd16e4a0207 323 * Generate a random number
manitou 0:bcd16e4a0207 324 */
manitou 0:bcd16e4a0207 325 static byte random2(void)
manitou 0:bcd16e4a0207 326 {
manitou 0:bcd16e4a0207 327 byte tmp = (seed0 & 0x48) + 0x38;
manitou 0:bcd16e4a0207 328 seed0 <<= 1;
manitou 0:bcd16e4a0207 329 if (seed1 & 0x80)
manitou 0:bcd16e4a0207 330 seed0++;
manitou 0:bcd16e4a0207 331 seed1 <<= 1;
manitou 0:bcd16e4a0207 332 if (seed2 & 0x80)
manitou 0:bcd16e4a0207 333 seed1++;
manitou 0:bcd16e4a0207 334 seed2 <<= 1;
manitou 0:bcd16e4a0207 335 if (tmp & 0x40)
manitou 0:bcd16e4a0207 336 seed2++;
manitou 0:bcd16e4a0207 337 return seed0;
manitou 0:bcd16e4a0207 338 }
manitou 0:bcd16e4a0207 339
manitou 0:bcd16e4a0207 340 static int pin;
manitou 0:bcd16e4a0207 341
manitou 0:bcd16e4a0207 342 static void soundOff(void)
manitou 0:bcd16e4a0207 343 {
manitou 2:eceeac07154b 344 //dac.write(0);
manitou 0:bcd16e4a0207 345 }
manitou 0:bcd16e4a0207 346
manitou 0:bcd16e4a0207 347 #define PWM_TOP (1200/2)
manitou 0:bcd16e4a0207 348
manitou 0:bcd16e4a0207 349 //https://sites.google.com/site/qeewiki/books/avr-guide/pwm-on-the-atmega328
manitou 0:bcd16e4a0207 350 static void soundOn(void)
manitou 0:bcd16e4a0207 351 {
manitou 2:eceeac07154b 352 // dac.write(0);
manitou 0:bcd16e4a0207 353
manitou 0:bcd16e4a0207 354 // initialise random number seed
manitou 0:bcd16e4a0207 355 seed0 = 0xecu;
manitou 0:bcd16e4a0207 356 seed1 = 7;
manitou 0:bcd16e4a0207 357 seed2 = 0xcfu;
manitou 0:bcd16e4a0207 358 }
manitou 0:bcd16e4a0207 359
manitou 0:bcd16e4a0207 360 // Logarithmic scale
manitou 0:bcd16e4a0207 361 //static const int16_t PROGMEM Volume[8] =
manitou 0:bcd16e4a0207 362 //{ 0, PWM_TOP * 0.01, PWM_TOP * 0.02, PWM_TOP * 0.03, PWM_TOP * 0.06,
manitou 0:bcd16e4a0207 363 //PWM_TOP * 0.12, PWM_TOP * 0.25, PWM_TOP * 0.5 };
manitou 0:bcd16e4a0207 364
manitou 0:bcd16e4a0207 365 // Linear scale
manitou 0:bcd16e4a0207 366 static const int16_t PROGMEM Volume[8] =
manitou 0:bcd16e4a0207 367 { 0, (uint16_t)(PWM_TOP * 0.07), (uint16_t)(PWM_TOP * 0.14), (uint16_t)(PWM_TOP * 0.21), (uint16_t)(PWM_TOP * 0.29),
manitou 0:bcd16e4a0207 368 (uint16_t)(PWM_TOP * 0.36), (uint16_t)(PWM_TOP * 0.43), (uint16_t)(PWM_TOP * 0.5)
manitou 0:bcd16e4a0207 369 };
manitou 0:bcd16e4a0207 370
manitou 0:bcd16e4a0207 371 static void sound(byte b)
manitou 0:bcd16e4a0207 372 {
manitou 0:bcd16e4a0207 373 // Update PWM volume
manitou 0:bcd16e4a0207 374 b = (b & 15);
manitou 0:bcd16e4a0207 375 dac.write(0.5*b/16.);
manitou 0:bcd16e4a0207 376 }
manitou 0:bcd16e4a0207 377
manitou 0:bcd16e4a0207 378 static byte playTone(byte soundNum, byte soundPos, char pitch1,
manitou 0:bcd16e4a0207 379 char pitch2, byte count, byte volume)
manitou 0:bcd16e4a0207 380 {
manitou 0:bcd16e4a0207 381 const byte *soundData = &SoundData[soundNum * 0x40];
manitou 0:bcd16e4a0207 382 while (count-- > 0) {
manitou 0:bcd16e4a0207 383 byte s = pgm_read_byte(&soundData[soundPos & 0x3fu]);
manitou 0:bcd16e4a0207 384 sound((byte) (s & volume));
manitou 0:bcd16e4a0207 385 pause(pitch1);
manitou 0:bcd16e4a0207 386 sound((byte) ((s >> 4) & volume));
manitou 0:bcd16e4a0207 387 pause(pitch2);
manitou 0:bcd16e4a0207 388
manitou 0:bcd16e4a0207 389 soundPos++;
manitou 0:bcd16e4a0207 390 }
manitou 0:bcd16e4a0207 391 return soundPos & 0x3fu;
manitou 0:bcd16e4a0207 392 }
manitou 0:bcd16e4a0207 393
manitou 0:bcd16e4a0207 394 static void play(byte duration, byte soundNumber)
manitou 0:bcd16e4a0207 395 {
manitou 0:bcd16e4a0207 396 while (duration--)
manitou 0:bcd16e4a0207 397 playTone(soundNumber, random2(), 7, 7, 10, 15);
manitou 0:bcd16e4a0207 398 }
manitou 0:bcd16e4a0207 399
manitou 0:bcd16e4a0207 400 /******************************************************************************
manitou 0:bcd16e4a0207 401 * User API
manitou 0:bcd16e4a0207 402 ******************************************************************************/
manitou 0:bcd16e4a0207 403 TTS::TTS()
manitou 0:bcd16e4a0207 404 {
manitou 0:bcd16e4a0207 405 }
manitou 0:bcd16e4a0207 406
manitou 0:bcd16e4a0207 407 void TTS::setPitch(byte pitch)
manitou 0:bcd16e4a0207 408 {
manitou 0:bcd16e4a0207 409 defaultPitch = pitch;
manitou 0:bcd16e4a0207 410 }
manitou 0:bcd16e4a0207 411
manitou 0:bcd16e4a0207 412 byte TTS::getPitch(void)
manitou 0:bcd16e4a0207 413 {
manitou 0:bcd16e4a0207 414 return defaultPitch;
manitou 0:bcd16e4a0207 415 }
manitou 0:bcd16e4a0207 416
manitou 0:bcd16e4a0207 417 /*
manitou 0:bcd16e4a0207 418 * Speak a string of phonemes
manitou 0:bcd16e4a0207 419 */
manitou 0:bcd16e4a0207 420 void TTS::sayPhonemes(const char *textp)
manitou 0:bcd16e4a0207 421 {
manitou 0:bcd16e4a0207 422 byte phonemeIn, // offset into text
manitou 0:bcd16e4a0207 423 byte2, modifierIn, // offset into stuff in modifier
manitou 0:bcd16e4a0207 424 punctuationPitchDelta; // change in pitch due to fullstop or question mark
manitou 0:bcd16e4a0207 425 int8_t byte1;
manitou 0:bcd16e4a0207 426 char phoneme;
manitou 0:bcd16e4a0207 427 const SOUND_INDEX *soundIndex;
manitou 0:bcd16e4a0207 428 byte sound1Num; // Sound data for the current phoneme
manitou 0:bcd16e4a0207 429 byte sound2Num; // Sound data for the next phoneme
manitou 0:bcd16e4a0207 430 byte sound2Stop; // Where the second sound should stop
manitou 0:bcd16e4a0207 431 char pitch1; // pitch for the first sound
manitou 0:bcd16e4a0207 432 char pitch2; // pitch for the second sound
manitou 0:bcd16e4a0207 433 short i;
manitou 0:bcd16e4a0207 434 byte sound1Duration; // the duration for sound 1
manitou 0:bcd16e4a0207 435
manitou 0:bcd16e4a0207 436 if (phonemesToData(textp, s_phonemes)) {
manitou 0:bcd16e4a0207 437 // phonemes has list of sound bytes
manitou 0:bcd16e4a0207 438 soundOn();
manitou 0:bcd16e4a0207 439
manitou 0:bcd16e4a0207 440 // _630C
manitou 0:bcd16e4a0207 441 byte1 = 0;
manitou 0:bcd16e4a0207 442 punctuationPitchDelta = 0;
manitou 0:bcd16e4a0207 443
manitou 0:bcd16e4a0207 444 // Q19
manitou 0:bcd16e4a0207 445 for (phonemeIn = 0, modifierIn = 0; phonemes[phonemeIn];
manitou 0:bcd16e4a0207 446 phonemeIn += 2, modifierIn += 2) {
manitou 0:bcd16e4a0207 447 byte duration; // duration from text line
manitou 0:bcd16e4a0207 448 byte SoundPos; // offset into sound data
manitou 0:bcd16e4a0207 449 byte fadeSpeed = 0;
manitou 0:bcd16e4a0207 450
manitou 0:bcd16e4a0207 451 phoneme = phonemes[phonemeIn];
manitou 0:bcd16e4a0207 452 if (phoneme == 'z') {
manitou 0:bcd16e4a0207 453 delay2(15);
manitou 0:bcd16e4a0207 454 continue;
manitou 0:bcd16e4a0207 455 } else if (phoneme == '#') {
manitou 0:bcd16e4a0207 456 continue;
manitou 0:bcd16e4a0207 457 } else {
manitou 0:bcd16e4a0207 458
manitou 0:bcd16e4a0207 459 // Collect info on sound 1
manitou 0:bcd16e4a0207 460 soundIndex = &SoundIndex[phoneme - 'A'];
manitou 0:bcd16e4a0207 461 sound1Num = pgm_read_byte(&soundIndex->SoundNumber);
manitou 0:bcd16e4a0207 462 byte1 = pgm_read_byte(&soundIndex->byte1);
manitou 0:bcd16e4a0207 463 byte2 = pgm_read_byte(&soundIndex->byte2);
manitou 0:bcd16e4a0207 464
manitou 0:bcd16e4a0207 465 duration = phonemes[phonemeIn + 1] - '0'; // Get duration from the input line
manitou 0:bcd16e4a0207 466 if (duration != 1)
manitou 0:bcd16e4a0207 467 duration <<= 1;
manitou 0:bcd16e4a0207 468
manitou 0:bcd16e4a0207 469 duration += 6; // scaled duration from the input line (at least 6)
manitou 0:bcd16e4a0207 470 sound2Stop = 0x40 >> 1;
manitou 0:bcd16e4a0207 471
manitou 0:bcd16e4a0207 472 pitch1 = modifier[modifierIn];
manitou 0:bcd16e4a0207 473 if (modifier[modifierIn + 1] == 0 || pitch1 == -1) {
manitou 0:bcd16e4a0207 474 pitch1 = 10;
manitou 0:bcd16e4a0207 475 duration -= 6;
manitou 0:bcd16e4a0207 476 } else if (modifier[modifierIn + 1] == '0'
manitou 0:bcd16e4a0207 477 || duration == 6) {
manitou 0:bcd16e4a0207 478 duration -= 6;
manitou 0:bcd16e4a0207 479 }
manitou 0:bcd16e4a0207 480 // q8
manitou 0:bcd16e4a0207 481 pitch2 = modifier[modifierIn + 2];
manitou 0:bcd16e4a0207 482 if (modifier[modifierIn + 3] == 0 || pitch2 == -1)
manitou 0:bcd16e4a0207 483 pitch2 = 10;
manitou 0:bcd16e4a0207 484
manitou 0:bcd16e4a0207 485 // q10
manitou 0:bcd16e4a0207 486 if (byte1 < 0) {
manitou 0:bcd16e4a0207 487 sound1Num = 0;
manitou 0:bcd16e4a0207 488 random2();
manitou 0:bcd16e4a0207 489 sound2Stop = (0x40 >> 1) + 2;
manitou 0:bcd16e4a0207 490 } else {
manitou 0:bcd16e4a0207 491 // is positive
manitou 0:bcd16e4a0207 492 if (byte1 == 2) {
manitou 0:bcd16e4a0207 493 // 64A4
manitou 0:bcd16e4a0207 494 // Make a white noise sound !
manitou 0:bcd16e4a0207 495 byte volume = (duration == 6) ? 15 : 1; // volume mask
manitou 0:bcd16e4a0207 496 for (duration <<= 2; duration > 0; duration--) {
manitou 0:bcd16e4a0207 497 playTone(sound1Num, random2(), 8, 12, 11,
manitou 0:bcd16e4a0207 498 volume);
manitou 0:bcd16e4a0207 499 // Increase the volume
manitou 0:bcd16e4a0207 500 if (++volume == 16)
manitou 0:bcd16e4a0207 501 volume = 15; // full volume from now on
manitou 0:bcd16e4a0207 502 }
manitou 0:bcd16e4a0207 503 continue;
manitou 0:bcd16e4a0207 504
manitou 0:bcd16e4a0207 505 } else {
manitou 0:bcd16e4a0207 506 // q11
manitou 0:bcd16e4a0207 507 if (byte1)
manitou 0:bcd16e4a0207 508 delay2(25);
manitou 0:bcd16e4a0207 509 }
manitou 0:bcd16e4a0207 510 }
manitou 0:bcd16e4a0207 511 }
manitou 0:bcd16e4a0207 512
manitou 0:bcd16e4a0207 513 // 6186
manitou 0:bcd16e4a0207 514 pitch1 += defaultPitch + punctuationPitchDelta;
manitou 0:bcd16e4a0207 515 if (pitch1 < 1)
manitou 0:bcd16e4a0207 516 pitch1 = 1;
manitou 0:bcd16e4a0207 517
manitou 0:bcd16e4a0207 518 pitch2 += defaultPitch + punctuationPitchDelta;
manitou 0:bcd16e4a0207 519 if (pitch2 < 1)
manitou 0:bcd16e4a0207 520 pitch2 = 1;
manitou 0:bcd16e4a0207 521
manitou 0:bcd16e4a0207 522 // get next phoneme
manitou 0:bcd16e4a0207 523 phoneme = phonemes[phonemeIn + 2];
manitou 0:bcd16e4a0207 524
manitou 0:bcd16e4a0207 525 if (phoneme == 0 || phoneme == 'z') {
manitou 0:bcd16e4a0207 526 if (duration == 1)
manitou 0:bcd16e4a0207 527 delay2(60);
manitou 0:bcd16e4a0207 528 phoneme = 'a'; // change to a pause
manitou 0:bcd16e4a0207 529 } else {
manitou 0:bcd16e4a0207 530 // s6
manitou 0:bcd16e4a0207 531 if (byte2 != 1)
manitou 0:bcd16e4a0207 532 byte2 =
manitou 0:bcd16e4a0207 533 (byte2 +
manitou 0:bcd16e4a0207 534 pgm_read_byte(&SoundIndex[phoneme - 'A'].byte2))
manitou 0:bcd16e4a0207 535 >> 1;
manitou 0:bcd16e4a0207 536
manitou 0:bcd16e4a0207 537 if (byte1 < 0
manitou 0:bcd16e4a0207 538 || pgm_read_byte(&SoundIndex[phoneme - 'A'].byte1))
manitou 0:bcd16e4a0207 539 phoneme = 'a'; // change to a pause
manitou 0:bcd16e4a0207 540 }
manitou 0:bcd16e4a0207 541
manitou 0:bcd16e4a0207 542 // S10
manitou 0:bcd16e4a0207 543 sound2Num =
manitou 0:bcd16e4a0207 544 pgm_read_byte(&SoundIndex[phoneme - 'A'].SoundNumber);
manitou 0:bcd16e4a0207 545
manitou 0:bcd16e4a0207 546 sound1Duration = 0x80; // play half of sound 1
manitou 0:bcd16e4a0207 547 if (sound2Num == sound1Num)
manitou 0:bcd16e4a0207 548 byte2 = duration;
manitou 0:bcd16e4a0207 549
manitou 0:bcd16e4a0207 550 // S11
manitou 0:bcd16e4a0207 551 if ((byte2 >> 1) == 0) {
manitou 0:bcd16e4a0207 552 sound1Duration = 0xff; // play all of sound 1
manitou 0:bcd16e4a0207 553 } else {
manitou 0:bcd16e4a0207 554 // The fade speed between the two sounds
manitou 0:bcd16e4a0207 555 fadeSpeed = (sound1Duration + (byte2 >> 1)) / byte2;
manitou 0:bcd16e4a0207 556
manitou 0:bcd16e4a0207 557 if (duration == 1) {
manitou 0:bcd16e4a0207 558 sound2Stop = 0x40; // dont play sound2
manitou 0:bcd16e4a0207 559 sound1Duration = 0xff; // play all of sound 1
manitou 0:bcd16e4a0207 560 pitch1 = 12;
manitou 0:bcd16e4a0207 561 }
manitou 0:bcd16e4a0207 562 }
manitou 0:bcd16e4a0207 563
manitou 0:bcd16e4a0207 564 SoundPos = 0;
manitou 0:bcd16e4a0207 565 do {
manitou 0:bcd16e4a0207 566 byte sound1Stop = (sound1Duration >> 2) & 0x3fu;
manitou 0:bcd16e4a0207 567 byte sound1End = sound1Stop;
manitou 0:bcd16e4a0207 568 if (sound2Stop < sound1End) sound1End = sound2Stop; // min
manitou 0:bcd16e4a0207 569
manitou 0:bcd16e4a0207 570 if (sound1Stop)
manitou 0:bcd16e4a0207 571 SoundPos =
manitou 0:bcd16e4a0207 572 playTone(sound1Num, SoundPos, pitch1, pitch1,
manitou 0:bcd16e4a0207 573 sound1End, 15);
manitou 0:bcd16e4a0207 574
manitou 0:bcd16e4a0207 575 // s18
manitou 0:bcd16e4a0207 576 if (sound2Stop != 0x40) {
manitou 0:bcd16e4a0207 577 SoundPos =
manitou 0:bcd16e4a0207 578 playTone(sound2Num, SoundPos, pitch2, pitch2,
manitou 0:bcd16e4a0207 579 (byte) (sound2Stop - sound1End), 15);
manitou 0:bcd16e4a0207 580 }
manitou 0:bcd16e4a0207 581 // s23
manitou 0:bcd16e4a0207 582 if (sound1Duration != 0xff && duration < byte2) {
manitou 0:bcd16e4a0207 583 // Fade sound1 out
manitou 0:bcd16e4a0207 584 sound1Duration -= fadeSpeed;
manitou 0:bcd16e4a0207 585 if (sound1Duration >= (byte) 0xC8)
manitou 0:bcd16e4a0207 586 sound1Duration = 0; // stop playing sound 1
manitou 0:bcd16e4a0207 587 }
manitou 0:bcd16e4a0207 588 // Call any additional sound
manitou 0:bcd16e4a0207 589 if (byte1 == -1)
manitou 0:bcd16e4a0207 590 play(3, 30); // make an 'f' sound
manitou 0:bcd16e4a0207 591 else if (byte1 == -2)
manitou 0:bcd16e4a0207 592 play(3, 29); // make an 's' sound
manitou 0:bcd16e4a0207 593 else if (byte1 == -3)
manitou 0:bcd16e4a0207 594 play(3, 33); // make a 'th' sound
manitou 0:bcd16e4a0207 595 else if (byte1 == -4)
manitou 0:bcd16e4a0207 596 play(3, 27); // make a 'sh' sound
manitou 0:bcd16e4a0207 597
manitou 0:bcd16e4a0207 598 } while (--duration);
manitou 0:bcd16e4a0207 599
manitou 0:bcd16e4a0207 600 // Scan ahead to find a '.' or a '?' as this will change the pitch
manitou 0:bcd16e4a0207 601 punctuationPitchDelta = 0;
manitou 0:bcd16e4a0207 602 for (i = 6; i > 0; i--) {
manitou 0:bcd16e4a0207 603 char next = phonemes[phonemeIn + (i * 2)];
manitou 0:bcd16e4a0207 604 if (next == 'i')
manitou 0:bcd16e4a0207 605 // found a full stop
manitou 0:bcd16e4a0207 606 punctuationPitchDelta = 6 - i; // Lower the pitch
manitou 0:bcd16e4a0207 607 else if (next == 'h')
manitou 0:bcd16e4a0207 608 // found a question mark
manitou 0:bcd16e4a0207 609 punctuationPitchDelta = i - 6; // Raise the pitch
manitou 0:bcd16e4a0207 610 }
manitou 0:bcd16e4a0207 611
manitou 0:bcd16e4a0207 612 if (byte1 == 1)
manitou 0:bcd16e4a0207 613 delay2(25);
manitou 0:bcd16e4a0207 614 } // next phoneme
manitou 0:bcd16e4a0207 615 }
manitou 0:bcd16e4a0207 616 soundOff();
manitou 0:bcd16e4a0207 617 }
manitou 0:bcd16e4a0207 618
manitou 0:bcd16e4a0207 619 /*
manitou 0:bcd16e4a0207 620 * Speak an English command line of text
manitou 0:bcd16e4a0207 621 */
manitou 0:bcd16e4a0207 622 void TTS::sayText(const char *original)
manitou 0:bcd16e4a0207 623 {
manitou 0:bcd16e4a0207 624 unsigned int i;
manitou 0:bcd16e4a0207 625 if (textToPhonemes(original, s_vocab, g_text)) {
manitou 0:bcd16e4a0207 626 sayPhonemes(g_text);
manitou 0:bcd16e4a0207 627 }
manitou 0:bcd16e4a0207 628 }