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

Dependencies:   mbed

text-to-speech TTS

Committer:
manitou
Date:
Sun Jun 11 11:03:23 2017 +0000
Revision:
0:bcd16e4a0207
Child:
1:548323cfdb5d
text-to-speech TTS through DAC to audio amp/speaker

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