/*
    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 (!output->SetBitsPerSample(16)) {
        return false;
    }
    if (!output->SetChannels(2)) {
        return false;
    }
    if (!output->begin()) {
        return false;
    }
    if (!output->SetRate(8000)) {
        return false;
    }


    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;
    }


    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;
}
