Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ View this project on [CADLAB.io](https://cadlab.io/project/1281).
## Text-to-Speech Library for Arduino

- checkout this project into your ``sketchbook/libraries`` folder
- requires an amplifier on the PWM output pin (see below)
- requires an amplifier on the PWM output pin (see below)
- alternative output via Stream or callback method
- see [blog articles](http://programmablehardware.blogspot.ie/search/label/tts)


## Supported Hardware

- ATmega328-based Arduinos (e.g., Uno, Pro, Pro Mini, etc.): pins 3, 9, 10
Expand Down Expand Up @@ -43,3 +45,4 @@ and ARM processors with DAC (Teensy, Due)
- Teensy [forum](https://forum.pjrc.com/threads/44587-TTS-(Text-to-Speech)-Library-Port)
- separate port/hack for MBED ARM with DAC [repository](https://developer.mbed.org/users/manitou/code/tts/)
- Hackaday article on [LM386 amplifiers](https://hackaday.com/2016/12/07/you-can-have-my-lm386s-when-you-pry-them-from-my-cold-dead-hands/)
- Example on how to use the [Output via Streams](https://www.pschatzmann.ch/home/2021/06/22/text-to-speach-in-arduino-using-tts/)
58 changes: 42 additions & 16 deletions TTS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

#include "TTS.h"
#include "english.h"
#include "sound.h"

#define NUM_VOCAB sizeof(s_vocab)/sizeof(VOCAB)
#define NUM_PHONEME sizeof(s_phonemes)/sizeof(PHONEME)
Expand All @@ -29,6 +28,7 @@ static byte seed2;
static char phonemes[128];
static char modifier[sizeof(phonemes)]; // must be same size as 'phonemes'


// Lookup user specified pitch changes
static const byte PROGMEM PitchesP[] = { 1, 2, 4, 6, 8, 10, 13, 16 };

Expand Down Expand Up @@ -319,39 +319,61 @@ static byte random2(void)
return seed0;
}

static byte playTone(int pin, byte soundNum, byte soundPos, char pitch1, char pitch2, byte count, byte volume)
byte TTS::playTone(byte soundNum, byte soundPos, char pitch1, char pitch2, byte count, byte volume)
{
const byte *soundData = &SoundData[soundNum * 0x40];
while (count-- > 0) {
byte s = pgm_read_byte(&soundData[soundPos & 0x3fu]);
sound(pin, s & volume);
sound_api->sound(s & volume);
pause(pitch1);
sound(pin, (s >> 4) & volume);
sound_api->sound((s >> 4) & volume);
pause(pitch2);

soundPos++;
}
return soundPos & 0x3fu;
}

static void play(int pin, byte duration, byte soundNumber)
void TTS::play(byte duration, byte soundNumber)
{
while (duration--)
playTone(pin, soundNumber, random2(), 7, 7, 10, 15);
playTone(soundNumber, random2(), 7, 7, 10, 15);
}

/******************************************************************************
* User API
******************************************************************************/


TTS::TTS(int p)
{
pin = p;
sound_api = new Sound(p);
defaultPitch = 7;
stream_ptr = nullptr;
#ifdef __AVR__
pinMode(pin, OUTPUT);
#endif
}

TTS::TTS(tts_data_callback_type cb, int len) {
sound_api = new SoundCallback(cb, this, len);
defaultPitch = 7;
stream_ptr = nullptr;
}

/**
* @brief Construct a new TTS object which outputs the data to an Arduino Stream
*
* @param out
*/
TTS::TTS(Print &out, int bits_per_sample) {
stream_ptr = &out;
sound_api = new SoundCallback((tts_data_callback_type)TTS::stream_data_callback, this, 512);
defaultPitch = 7;
getInfo().bits_per_sample = bits_per_sample;
}


/*
* Speak a string of phonemes
*/
Expand All @@ -373,7 +395,7 @@ void TTS::sayPhonemes(const char *textp)

if (phonemesToData(textp, s_phonemes)) {
// phonemes has list of sound bytes
soundOn(pin);
sound_api->soundOn();

// initialise random number seed
seed0 = 0xecu;
Expand Down Expand Up @@ -435,7 +457,7 @@ void TTS::sayPhonemes(const char *textp)
// Make a white noise sound!
byte volume = (duration == 6) ? 15 : 1; // volume mask
for (duration <<= 2; duration > 0; duration--) {
playTone(pin, sound1Num, random2(), 8, 12, 11, volume);
playTone(sound1Num, random2(), 8, 12, 11, volume);
// Increase the volume
if (++volume == 16)
volume = 15; // full volume from now on
Expand Down Expand Up @@ -502,11 +524,11 @@ void TTS::sayPhonemes(const char *textp)
byte sound1End = min(sound1Stop, sound2Stop);

if (sound1Stop)
soundPos = playTone(pin, sound1Num, soundPos, pitch1, pitch1, sound1End, 15);
soundPos = playTone(sound1Num, soundPos, pitch1, pitch1, sound1End, 15);

// s18
if (sound2Stop != 0x40) {
soundPos = playTone(pin, sound2Num, soundPos, pitch2, pitch2, sound2Stop - sound1End, 15);
soundPos = playTone(sound2Num, soundPos, pitch2, pitch2, sound2Stop - sound1End, 15);
}
// s23
if (sound1Duration != 0xff && duration < byte2) {
Expand All @@ -517,13 +539,13 @@ void TTS::sayPhonemes(const char *textp)
}
// Call any additional sound
if (byte1 == -1)
play(pin, 3, 30); // make an 'f' sound
play(3, 30); // make an 'f' sound
else if (byte1 == -2)
play(pin, 3, 29); // make an 's' sound
play(3, 29); // make an 's' sound
else if (byte1 == -3)
play(pin, 3, 33); // make a 'th' sound
play(3, 33); // make a 'th' sound
else if (byte1 == -4)
play(pin, 3, 27); // make a 'sh' sound
play(3, 27); // make a 'sh' sound

} while (--duration);

Expand All @@ -543,7 +565,7 @@ void TTS::sayPhonemes(const char *textp)
delay2(25);
} // next phoneme
}
soundOff(pin);
sound_api->soundOff();
}

/*
Expand All @@ -556,3 +578,7 @@ void TTS::sayText(const char *original)
if (textToPhonemes(original, s_vocab, text))
sayPhonemes(text);
}




88 changes: 85 additions & 3 deletions TTS.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,56 @@

#ifndef _TTS_H_
#define _TTS_H_
#include "sound.h"

/**
* @brief TTS Output Information
*
*/
struct TTSInfo {
int channels = 1;
int sample_rate = 12000;
int bits_per_sample = 8;
};

/**
* @brief TTS API
*
*/

class TTS {
public:

/**
* @brief Construct a new TTS object using the default pin
*
*/
TTS(int pin);


/**
* @brief Construct a new TTS object - Uses the Callback to provide the result
*
*/
TTS(tts_data_callback_type cb, int len=512);

/**
* @brief Construct a new TTS object which outputs the data to an Arduino Stream
*
* @param out
*/
TTS(Print &out, int bits_per_sample=16);

/**
* @brief Destroy the TTS object
*
*/
~TTS(){
if(sound_api!=nullptr) {
delete sound_api;
}
}


/**
* speaks a string of (english) text
*/
Expand All @@ -39,9 +83,47 @@ class TTS {
*/
byte getPitch(void) { return defaultPitch; }

private:
/**
* @brief Get additional output information
*
* @return TTSInfo
*/
static TTSInfo& getInfo() {
static TTSInfo info;
return info;
}

// allow callback to access private fields
friend void stream_data_callback(void *vtts, int len, byte *data);

protected:
byte defaultPitch;
int pin;
BaseSound *sound_api = nullptr;
Print *stream_ptr = nullptr;

void play(byte duration, byte soundNumber);
byte playTone(byte soundNum, byte soundPos, char pitch1, char pitch2, byte count, byte volume);

// callback to write sound data to stream
static void stream_data_callback(void *vtts, int len, byte *data){
TTS *tts = (TTS *) vtts;
TTSInfo *info = &getInfo();
if (tts->stream_ptr!=nullptr && len>0 && data!=nullptr){
if (info->bits_per_sample==8){
tts->stream_ptr->write((const char*)data, len);
} else {
// convert 8 to 16 bits
for (int j=0;j<len;j++){
int8_t sample = data[j];
int16_t sample16 = sample<<8;
tts->stream_ptr->write((const char*)&sample16, 2);
}
}
}
}

};



#endif
22 changes: 22 additions & 0 deletions examples/Speak/Speak.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

/**
* @brief Simple example which should work on most environments
*/

#include "TTS.h"


// TTS tts(3); // with explicit pin
TTS tts; // with default pin

void setup(){
Serial.begin(115200);
// display pin
Serial.print("Using Pin ");
Serial.println(DEFAULT_PIN);
}

void loop(){
tts.sayText("hallo");
delay(5000);
}
22 changes: 22 additions & 0 deletions examples/SpeakWithCallback/SpeakWithCallback.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @brief Example with Callback which just prints the result
*
*/
#include "TTS.h"

void data_callback(int len, byte *data){
for (int j=0;j<len;j++){
Serial.println(data[j]);
}
}

TTS tts(data_callback);

void setup(){
Serial.begin(115200);
}

void loop(){
tts.sayText("hallo");
delay(5000);
}
35 changes: 32 additions & 3 deletions sound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
#define CHANNEL_FOR(p) (p == 25)? DAC_CHANNEL_1: DAC_CHANNEL_2
#endif

void soundOff(int pin)
Sound::Sound(int pin)
{
this->pin = pin;
}


void Sound::soundOff()
{

#if defined(__AVR__)
Expand Down Expand Up @@ -53,7 +59,7 @@ void soundOff(int pin)
}

//https://sites.google.com/site/qeewiki/books/avr-guide/pwm-on-the-atmega328
void soundOn(int pin)
void Sound::soundOn()
{

#if defined(__AVR__)
Expand Down Expand Up @@ -126,7 +132,7 @@ void soundOn(int pin)
#endif
}

void sound(int pin, byte b)
void Sound::sound(byte b)
{
// Update PWM volume
b = (b & 15);
Expand Down Expand Up @@ -195,4 +201,27 @@ void sound(int pin, byte b)

analogWrite(pin, b*8);
#endif

}

void SoundCallback::soundOff(){
(*tts_callback)(tts, current_length, out_data);
memset(out_data,0, current_length);
current_length = 0;
active = false;
}

void SoundCallback::soundOn() {
active = true;
}

void SoundCallback::sound(byte b) {
out_data[current_length++] = b ;
if (current_length>=max_length){
if (tts_callback!=nullptr) {
(*tts_callback)(tts, current_length, out_data);
}
memset(out_data, 0, current_length);
current_length = 0;
}
}
Loading