Files
ESP8266Audio/src/AudioGeneratorTalkie.cpp
2021-09-27 11:00:34 +02:00

303 lines
9.0 KiB
C++

/*
AudioGeneratorTalkie
Audio output generator that speaks using the LPC code in old TI speech chips
Output is locked at 8khz as that's that the hardcoded LPC coefficients are built around
Based on the Talkie Arduino library by Peter Knight, https://github.com/going-digital/Talkie
Copyright (C) 2020 Earle F. Philhower, III
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AudioGeneratorTalkie.h"
AudioGeneratorTalkie::AudioGeneratorTalkie()
{
running = false;
lastFrame = false;
file = nullptr;
output = nullptr;
buff = nullptr;
}
AudioGeneratorTalkie::~AudioGeneratorTalkie()
{
free(buff);
}
bool AudioGeneratorTalkie::say(const uint8_t *data, size_t len, bool async) {
// Finish saying anything in the pipe
while (running) {
loop();
delay(0);
}
buff = (uint8_t*)realloc(buff, len);
if (!buff) return false;
memcpy_P(buff, data, len);
// Reset the interpreter to the start of the stream
ptrAddr = buff;
ptrBit = 0;
frameLeft = 0;
lastFrame = false;
running = true;
if (!async) {
// Finish saying anything in the pipe
while (running) {
loop();
delay(0);
}
}
return true;
}
bool AudioGeneratorTalkie::begin(AudioFileSource *source, AudioOutput *output)
{
if (!output) return false;
this->output = output;
if (source) {
file = source;
if (!file->isOpen()) return false; // Error
auto len = file->getSize();
uint8_t *temp = (uint8_t *)malloc(len);
if (!temp) return false;
if (file->read(temp, len) != (uint32_t)len) return false;
say(temp, len);
free(temp);
} else {
// Reset the interpreter to the start of the stream
ptrAddr = buff;
ptrBit = 0;
frameLeft = 0;
running = false;
}
if (!output->SetRate( 8000 )) return false;
if (!output->SetBitsPerSample( 16 )) return false;
if (!output->SetChannels( 2 )) return false;
if (!output->begin()) return false;
return true;
}
bool AudioGeneratorTalkie::stop()
{
if (!running) return true;
running = false;
output->stop();
return file ? file->close() : true;
}
bool AudioGeneratorTalkie::isRunning()
{
return running;
}
bool AudioGeneratorTalkie::loop()
{
if (!running) goto done; // Nothing to do here!
if (!frameLeft) {
if (lastFrame) {
running = false;
goto done;
}
lastFrame = genOneFrame();
}
if (frameLeft) {
for ( ; frameLeft; frameLeft--) {
auto res = genOneSample();
int16_t r[2] = {res, res};
if (!output->ConsumeSample(r)) break;
}
}
done:
if (file) file->loop();
output->loop();
return running;
}
// The ROMs used with the TI speech were serial, not byte wide.
// Here's a handy routine to flip ROM data which is usually reversed.
uint8_t AudioGeneratorTalkie::rev(uint8_t a)
{
// 76543210
a = (a>>4) | (a<<4); // Swap in groups of 4
// 32107654
a = ((a & 0xcc)>>2) | ((a & 0x33)<<2); // Swap in groups of 2
// 10325476
a = ((a & 0xaa)>>1) | ((a & 0x55)<<1); // Swap bit pairs
// 01234567
return a;
}
uint8_t AudioGeneratorTalkie::getBits(uint8_t bits) {
uint8_t value;
uint16_t data;
data = rev(ptrAddr[0])<<8;
if (ptrBit+bits > 8) {
data |= rev(ptrAddr[1]);
}
data <<= ptrBit;
value = data >> (16-bits);
ptrBit += bits;
if (ptrBit >= 8) {
ptrBit -= 8;
ptrAddr++;
}
return value;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnarrowing"
// Constant LPC coefficient tables
static const uint8_t tmsEnergy[0x10] = {0x00,0x02,0x03,0x04,0x05,0x07,0x0a,0x0f,0x14,0x20,0x29,0x39,0x51,0x72,0xa1,0xff};
static const uint8_t tmsPeriod[0x40] = {0x00,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2D,0x2F,0x31,0x33,0x35,0x36,0x39,0x3B,0x3D,0x3F,0x42,0x45,0x47,0x49,0x4D,0x4F,0x51,0x55,0x57,0x5C,0x5F,0x63,0x66,0x6A,0x6E,0x73,0x77,0x7B,0x80,0x85,0x8A,0x8F,0x95,0x9A,0xA0};
static const int16_t tmsK1[0x20] = {0x82C0,0x8380,0x83C0,0x8440,0x84C0,0x8540,0x8600,0x8780,0x8880,0x8980,0x8AC0,0x8C00,0x8D40,0x8F00,0x90C0,0x92C0,0x9900,0xA140,0xAB80,0xB840,0xC740,0xD8C0,0xEBC0,0x0000,0x1440,0x2740,0x38C0,0x47C0,0x5480,0x5EC0,0x6700,0x6D40};
static const int16_t tmsK2[0x20] = {0xAE00,0xB480,0xBB80,0xC340,0xCB80,0xD440,0xDDC0,0xE780,0xF180,0xFBC0,0x0600,0x1040,0x1A40,0x2400,0x2D40,0x3600,0x3E40,0x45C0,0x4CC0,0x5300,0x5880,0x5DC0,0x6240,0x6640,0x69C0,0x6CC0,0x6F80,0x71C0,0x73C0,0x7580,0x7700,0x7E80};
static const int8_t tmsK3[0x10] = {0x92,0x9F,0xAD,0xBA,0xC8,0xD5,0xE3,0xF0,0xFE,0x0B,0x19,0x26,0x34,0x41,0x4F,0x5C};
static const int8_t tmsK4[0x10] = {0xAE,0xBC,0xCA,0xD8,0xE6,0xF4,0x01,0x0F,0x1D,0x2B,0x39,0x47,0x55,0x63,0x71,0x7E};
static const int8_t tmsK5[0x10] = {0xAE,0xBA,0xC5,0xD1,0xDD,0xE8,0xF4,0xFF,0x0B,0x17,0x22,0x2E,0x39,0x45,0x51,0x5C};
static const int8_t tmsK6[0x10] = {0xC0,0xCB,0xD6,0xE1,0xEC,0xF7,0x03,0x0E,0x19,0x24,0x2F,0x3A,0x45,0x50,0x5B,0x66};
static const int8_t tmsK7[0x10] = {0xB3,0xBF,0xCB,0xD7,0xE3,0xEF,0xFB,0x07,0x13,0x1F,0x2B,0x37,0x43,0x4F,0x5A,0x66};
static const int8_t tmsK8[0x08] = {0xC0,0xD8,0xF0,0x07,0x1F,0x37,0x4F,0x66};
static const int8_t tmsK9[0x08] = {0xC0,0xD4,0xE8,0xFC,0x10,0x25,0x39,0x4D};
static const int8_t tmsK10[0x08] = {0xCD,0xDF,0xF1,0x04,0x16,0x20,0x3B,0x4D};
// The chirp we active the filter using
static const int8_t chirp[] = {0x00,0x2a,0xd4,0x32,0xb2,0x12,0x25,0x14,0x02,0xe1,0xc5,0x02,0x5f,0x5a,0x05,0x0f,0x26,0xfc,0xa5,0xa5,0xd6,0xdd,0xdc,0xfc,0x25,0x2b,0x22,0x21,0x0f,0xff,0xf8,0xee,0xed,0xef,0xf7,0xf6,0xfa,0x00,0x03,0x02,0x01};
#pragma GCC diagnostic pop
bool AudioGeneratorTalkie::genOneFrame() {
uint8_t energy;
uint8_t repeat;
// Read speech data, processing the variable size frames.
energy = getBits(4);
if (energy == 0) {
// Energy = 0: rest frame
synthEnergy = 0;
} else if (energy == 0xf) {
// Energy = 15: stop frame. Silence the synthesiser.
synthEnergy = 0;
synthK1 = 0;
synthK2 = 0;
synthK3 = 0;
synthK4 = 0;
synthK5 = 0;
synthK6 = 0;
synthK7 = 0;
synthK8 = 0;
synthK9 = 0;
synthK10 = 0;
} else {
synthEnergy = tmsEnergy[energy];
repeat = getBits(1);
synthPeriod = tmsPeriod[getBits(6)];
// A repeat frame uses the last coefficients
if (!repeat) {
// All frames use the first 4 coefficients
synthK1 = tmsK1[getBits(5)];
synthK2 = tmsK2[getBits(5)];
synthK3 = tmsK3[getBits(4)];
synthK4 = tmsK4[getBits(4)];
if (synthPeriod) {
// Voiced frames use 6 extra coefficients.
synthK5 = tmsK5[getBits(4)];
synthK6 = tmsK6[getBits(4)];
synthK7 = tmsK7[getBits(4)];
synthK8 = tmsK8[getBits(3)];
synthK9 = tmsK9[getBits(3)];
synthK10 = tmsK10[getBits(3)];
}
}
}
frameLeft = 8000 / 40;
return (energy == 0xf); // Last frame will return true
}
int16_t AudioGeneratorTalkie::genOneSample()
{
static uint8_t periodCounter;
static int16_t x0,x1,x2,x3,x4,x5,x6,x7,x8,x9;
int16_t u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10;
if (synthPeriod) {
// Voiced source
if (periodCounter < synthPeriod) {
periodCounter++;
} else {
periodCounter = 0;
}
if (periodCounter < sizeof(chirp)) {
u10 = ((chirp[periodCounter]) * (uint32_t) synthEnergy) >> 8;
} else {
u10 = 0;
}
} else {
// Unvoiced source
static uint16_t synthRand = 1;
synthRand = (synthRand >> 1) ^ ((synthRand & 1) ? 0xB800 : 0);
u10 = (synthRand & 1) ? synthEnergy : -synthEnergy;
}
// Lattice filter forward path
u9 = u10 - (((int16_t)synthK10*x9) >> 7);
u8 = u9 - (((int16_t)synthK9*x8) >> 7);
u7 = u8 - (((int16_t)synthK8*x7) >> 7);
u6 = u7 - (((int16_t)synthK7*x6) >> 7);
u5 = u6 - (((int16_t)synthK6*x5) >> 7);
u4 = u5 - (((int16_t)synthK5*x4) >> 7);
u3 = u4 - (((int16_t)synthK4*x3) >> 7);
u2 = u3 - (((int16_t)synthK3*x2) >> 7);
u1 = u2 - (((int32_t)synthK2*x1) >> 15);
u0 = u1 - (((int32_t)synthK1*x0) >> 15);
// Output clamp
if (u0 > 511) u0 = 511;
if (u0 < -512) u0 = -512;
// Lattice filter reverse path
x9 = x8 + (((int16_t)synthK9*u8) >> 7);
x8 = x7 + (((int16_t)synthK8*u7) >> 7);
x7 = x6 + (((int16_t)synthK7*u6) >> 7);
x6 = x5 + (((int16_t)synthK6*u5) >> 7);
x5 = x4 + (((int16_t)synthK5*u4) >> 7);
x4 = x3 + (((int16_t)synthK4*u3) >> 7);
x3 = x2 + (((int16_t)synthK3*u2) >> 7);
x2 = x1 + (((int32_t)synthK2*u1) >> 15);
x1 = x0 + (((int32_t)synthK1*u0) >> 15);
x0 = u0;
uint16_t v = u0; // 10 bits
v <<= 6; // Now full 16
return v;
}