Memahami / Membalik mesin TTS sederhana (tapi berkualitas baik)

Dec 15 2020

Untuk waktu yang cukup lama saya ingin menambahkan TTS (text-to-speech) ke aplikasi MCU saya dan saya mencoba beberapa di antaranya dengan kurang lebih berhasil selalu mengenai dinding yang kualitasnya tidak bagus atau membutuhkan daya CPU yang terlalu banyak .

Namun baru-baru ini saya menemukan TTS yang sangat tua dari ZX Spectrum (di tautannya ada info lebih lanjut dan juga tautan ke repositori file tap asli) yang sangat bagus dan sederhana (hanya 801 Bytes kode Z80 asm). Jadi saya mencobanya, membongkarnya (ekstrak file basic dan asm dari file tap dengan utilitas saya sendiri dan dibongkar dengan YAZD) dan port hasilnya ke C ++ dengan sukses penuh. Kedengarannya bagus di PC dan MCU dengan sedikit daya CPU yang dibutuhkan. Ini menghasilkan suara digital 1 bit.

Berikut adalah kode sumber C ++ yang saya buat:

//---------------------------------------------------------------------------
//---  ZX Hlasovy program voicesoft 1985  -----------------------------------    
//--- ported to C++ by Spektre ver: 1.001 -----------------------------------
//---------------------------------------------------------------------------
#ifndef _speech_h
#define _speech_h
//---------------------------------------------------------------------------
// API:
void sound_out(bool on);    // you need to code this function (should add a sample to sound output)
void say_text(char *txt);   // say null terminated text, "a'c'" -> "áè"
//---------------------------------------------------------------------------
// internals:
void say_char(char chr);    // internal function for single character (do not use it !!!)
void say_wait(WORD ws);     // internal wait (do not use it !!!)
//---------------------------------------------------------------------------
// vars:
bool _sound_on=false;       // global state of the reproductor/sound output
//---------------------------------------------------------------------------
// config: (recomputed for 44100 Hz samplerate)
const static BYTE t_speed=5;        // [samples] 1/(speech speed) (pitch)
const static WORD t_pause=183;      // [samples] pause between chars
const static WORD t_space=2925;     // [samples] pause ` `
const static WORD t_comma=5851;     // [samples] pause `,`
//---------------------------------------------------------------------------
// tables:
const static BYTE tab_char0[52]=    //  0..25 normal alphabet A..Z
    {                               // 26..51 diacritic alphabet A..Z
    0x00,0x02,0x06,0x0a,0x0e,0x10,0x12,0x16,0x1a,0x1c,0x22,0x26,0x2a,0x2e,0x32,
    0x34,0x38,0x42,0x48,0x4a,0x4e,0x50,0x50,0x56,0x1a,0x5c,0x64,0x66,0x70,0x74,
    0x7a,0x7c,0xc2,0x84,0x86,0xc2,0xc2,0xc2,0x88,0x8c,0x92,0x94,0xc2,0x9e,0xa6,
    0xa8,0xae,0xb0,0xc2,0xc2,0x86,0xbc
    };
const static BYTE tab_char1[196]=
    {
    0x36,0x81,0x34,0x19,0x31,0xab,0x18,0x19,0x91,0xc3,0x34,0x19,0x31,0xe0,0x36,
    0x84,0x92,0xe3,0x35,0x19,0x51,0x9c,0x31,0x31,0x34,0x96,0x36,0x87,0x33,0x3a,
    0x32,0x3d,0x32,0xc0,0x18,0x19,0x51,0x9c,0x33,0x22,0x31,0xb1,0x31,0x31,0x36,
    0xa5,0x31,0x31,0x36,0xa8,0x36,0x8a,0x18,0x19,0x31,0xab,0x18,0x19,0x51,0x1c,
    0x34,0x31,0x32,0x34,0x32,0xb7,0x22,0x10,0x13,0x19,0x21,0xae,0x92,0xc3,0x18,
    0x19,0x31,0xe0,0x36,0x8d,0x34,0x31,0x32,0x34,0x32,0xb7,0x18,0x19,0x71,0x1c,
    0x92,0xc3,0x32,0x31,0x32,0x43,0x32,0x44,0x32,0xc5,0x3f,0x81,0x34,0x19,0x31,
    0x2b,0x33,0x3a,0x32,0x3d,0x32,0xc0,0x18,0x19,0x91,0xd3,0x33,0x19,0x71,0x6d,
    0x32,0x93,0x3e,0x84,0x92,0x63,0x33,0x3a,0x32,0x3d,0x32,0xc0,0x92,0xf3,0x3e,
    0x87,0x31,0x31,0x36,0x25,0x31,0x31,0x35,0x25,0x32,0x93,0x3e,0x8a,0x18,0x19,
    0x31,0x2b,0x33,0x3a,0x32,0x3d,0x32,0xc0,0x13,0x19,0x32,0x60,0x13,0x19,0x71,
    0xdd,0x92,0xd3,0x18,0x19,0x71,0x6d,0x32,0x93,0x3e,0x8d,0x34,0x31,0x32,0x34,
    0x32,0x37,0x33,0x3a,0x32,0x3d,0x32,0xc0,0x32,0x53,0x32,0x54,0x32,0xd5,0x1a,
    0x99
    };
const static BYTE tab_char2[262]=
    {
    0x1a,0x99,0xe1,0xc3,0xe1,0xc7,0x8f,0x0f,0xf8,0x03,0x0f,0x07,0xc1,0xe3,0xff,
    0x40,0x17,0xff,0x00,0x03,0xf8,0x7c,0xc1,0xf1,0xf8,0x03,0xfe,0x00,0x7f,0xfc,
    0x00,0x03,0xf8,0x0f,0x09,0xf1,0xfe,0x03,0xef,0x40,0x17,0xff,0x00,0x03,0xe1,
    0x5c,0x35,0xc5,0xaa,0x35,0x00,0x00,0x00,0x00,0x00,0x00,0x3e,0x8e,0x38,0x73,
    0xcf,0xf8,0x78,0xc3,0xdf,0x1c,0xf1,0xc7,0xfe,0x03,0xc0,0xff,0x00,0x00,0xff,
    0xf8,0x00,0x7f,0xf8,0x03,0xff,0xf0,0x01,0xff,0xe0,0x03,0xaa,0xca,0x5a,0xd5,
    0x21,0x3d,0xfe,0x1f,0xf8,0x00,0x00,0x1f,0xff,0xfc,0x20,0x00,0x00,0x03,0xff,
    0xff,0x08,0x79,0x00,0x02,0xff,0xe1,0xc7,0x1f,0xe0,0x03,0xff,0xd0,0x01,0xff,
    0xf0,0x03,0x7f,0x01,0xfa,0x5f,0xc0,0x07,0xf8,0x0f,0xc0,0xff,0x00,0x42,0xaa,
    0xa5,0x55,0x5a,0xaa,0xaa,0x5a,0xa5,0x5a,0xaa,0x55,0x55,0xaa,0xaa,0xa5,0x55,
    0xaa,0x5a,0xaa,0xa5,0x55,0xaa,0xaa,0xa5,0x55,0xaa,0xaa,0x55,0xa5,0xa5,0xaa,
    0xa5,0xb7,0x66,0x6c,0xd8,0xf9,0xb3,0x6c,0xad,0x37,0x37,0x66,0xfc,0x9b,0x87,
    0xf6,0xc0,0xd3,0xb6,0x60,0xf7,0xf7,0x3e,0x4d,0xfb,0xfe,0x5d,0xb7,0xde,0x46,
    0xf6,0x96,0xb4,0x4f,0xaa,0xa9,0x55,0xaa,0xaa,0xa5,0x69,0x59,0x9a,0x6a,0x95,
    0x55,0x95,0x55,0x6a,0xa5,0x55,0xa9,0x4d,0x66,0x6a,0x92,0xec,0xa5,0x55,0xd2,
    0x96,0x55,0xa2,0xba,0xcd,0x00,0x66,0x99,0xcc,0x67,0x31,0x8e,0x66,0x39,0xa6,
    0x6b,0x19,0x66,0x59,0xc6,0x71,0x09,0x67,0x19,0xcb,0x01,0x71,0xcc,0x73,0x19,
    0x99,0xcc,0xc6,0x67,0x19,0x9a,0xc6,
    };
const static BYTE tab_char3[5]={ 0x00,0x2e,0x5a,0x5e,0xfe };
//---------------------------------------------------------------------------
void say_text(char *txt)
    {
    WORD hl;
    BYTE a,b,c;
    for (b=0xBB,hl=0;;hl++)     // process txt
        {
        a=b;                    // a,c char from last iteration
        c=b;
        if (!a) break;          // end of txt
        b=txt[hl];              // b actual char
        if ((b>='a')&&(b<='z')) b=b+'A'-'a'; // must be uppercase
        a=c;
        if ((a>='A')&&(a<='Z'))
            {
            // handle diacritic
            if (a!='C'){ a=b; if (a!='\'') a=c; else{ a=c; a+=0x1A; b=0xBB; }}
            else{
                a=b;
                if (a=='H'){ a+=0x1A; b=0xBB; }
                 else{ if (a!='\'') a=c; else{ a=c; a+=0x1A; b=0xBB; }}
                }
            // syntetize sound
            say_char(a);
            continue;
            }
        if (a==',')say_wait(t_comma);
        if (a==' ')say_wait(t_space);
        }
    }
//----------------------------------------------------------------------
void say_wait(WORD ws)
    {
    for (;ws;ws--) sound_out(_sound_on);
    }
//----------------------------------------------------------------------
void say_char(char chr) // chr =  < `A` , `Z`+26 >
    {
    WORD hl,hl0;
    BYTE a,b,c,cy,cy0,ws;
    hl=tab_char0[chr-'A'];
    for (;;)
        {
        c =tab_char1[hl  ]&0x0F;
        c|=tab_char1[hl+1]&0x80;
        for (;;)
            {
            a=tab_char1[hl];
            a=(a>>5)&7;
            cy=a&1;
            hl0=hl;
            if (a!=0)
                {
                b=tab_char3[a];
                hl=hl0;
                a=tab_char1[hl+1];
                hl0=hl;
                cy0=(a>>7)&1;
                a=((a<<1)&254)|cy;
                cy=cy0;
                hl=a;
                a=0x80;
                for (;;)
                    {
                    _sound_on=(a&tab_char2[hl]);
                    for (ws=t_speed;ws;ws--) sound_out(_sound_on);
                    b--;
                    if (!b) break;
                    cy=a&1;     
                    a=((a>>1)&127)|(cy<<7);
                    if (!cy) continue;
                    hl++;
                    }
                }
            a^=a;
            say_wait(t_pause);
            c--;
            a=c&0x0F;
            hl=hl0; 
            if (a==0) break;
            }
        cy0=(c>>7)&1;
        a=((c<<1)&254)|cy;
        cy=cy0;
        if (cy) return;
        hl+=2;
        }
    }
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------

Ini berfungsi dengan sempurna, namun saya ingin memahami bagaimana suara disintesis. Saya tidak bisa memahaminya ... apakah itu semacam kompresi sampel atau menggunakan filter forman untuk mensintesis suara atau menggabungkannya atau sesuatu yang lain?

Jadi saya ingin membedah say_charfungsi untuk memahami / arti tab_char?[]tabel LUT.

[Sunting2] berkat Edward baru yang lebih C / C ++ seperti versi

Saya mengatur ulang tabel dan menambahkan banyak info komentar agar lebih didaktis dan memungkinkan untuk diubah:

//---------------------------------------------------------------------------
//---  ZX Hlasovy program voicesoft 1985  -----------------------------------
//--- ported to C++ by Spektre ver: 2.001 -----------------------------------
//---------------------------------------------------------------------------
#ifndef _speech_h
#define _speech_h
//---------------------------------------------------------------------------
// API:
void sound_out(bool on);    // you need to code this function (should add a sample to sound output)
void say_text(char *txt);   // say null terminated text, "a'c'" -> "áč"
//---------------------------------------------------------------------------
// internals:
void say_char(char chr);    // internal function for single character (do not use it !!!)
void say_wait(WORD ws);     // internal wait (do not use it !!!)
//---------------------------------------------------------------------------
// vars:
bool _sound_on=false;       // global state of the reproductor/sound output
//---------------------------------------------------------------------------
// config: (recomputed for 44100 Hz samplerate)
const static BYTE t_speed=5;        // [samples] 1/(speech speed) (pitch)
const static WORD t_pause=183;      // [samples] pause between chars
const static WORD t_space=2925;     // [samples] pause ` `
const static WORD t_comma=5851;     // [samples] pause `,`
//---------------------------------------------------------------------------
// point to RLE encoded character sound (RLE_ix)
const static BYTE tab_char[52]=
    {
//   A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z
     0, 1, 3, 5, 7, 8, 9,11,13,14,17,19,21,23,25,26,28,33,36,37,39,40,40,43,13,46,
//   A' B' C' D' E' F' G' H' I' J' K' L' M' N' O' P' Q' R' S' T' U' V' W' X' Y' Z'
    50,51,56,58,61,62,97,66,67,97,97,97,68,70,73,74,97,79,83,84,87,88,97,97,67,94,
    };
// RLE encoded character sounds
const static WORD tab_RLE[98]=
    {
    //  15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
    // end -----num------ ------------PCM_ix-----------
                                                // ix char
    0x9804,                                     //  0 A
    0x103D,0x8473,                              //  1 B
    0x203C,0x84AB,                              //  3 C
    0x103D,0x8524,                              //  5 D
    0x980B,                                     //  7 E
    0x892B,                                     //  8 F
    0x143D,0x8444,                              //  9 G
    0x0481,0x9035,                              // 11 H
    0x9812,                                     // 13 I,Y
    0x0C96,0x089D,0x88A4,                       // 14 J
    0x203C,0x8444,                              // 17 K
    0x0C5E,0x8481,                              // 19 L
    0x0481,0x9865,                              // 21 M
    0x0481,0x986C,                              // 23 N
    0x9819,                                     // 25 O
    0x203C,0x8473,                              // 26 P
    0x203C,0x0444,0x1081,0x0888,0x888F,         // 28 Q
    0x0827,0x0C3C,0x847A,                       // 33 R
    0x88AB,                                     // 36 S
    0x203C,0x8524,                              // 37 T
    0x9820,                                     // 39 U
    0x1081,0x0888,0x888F,                       // 40 V,W
    0x203C,0x0451,0x88AB,                       // 43 X
    0x0881,0x08CC,0x08D3,0x88DA,                // 46 Z
    0xBC04,                                     // 50 A'
    0x103D,0x0473,0x0C96,0x089D,0x88A4,         // 51 B' *
    0x203C,0x84E1,                              // 56 C'
    0x0C3D,0x054C,0x882E,                       // 58 D'
    0xB80B,                                     // 61 E'
    0x092B,0x0C96,0x089D,0x88A4,                // 62 F' *
    0x8959,                                     // 66 CH,H'
    0xB812,                                     // 67 I',Y'
    0x0481,0x1865,                              // 68 M' overlap with N' *
                  0x0481,0x1465,0x882E,         // 70 N' overlap with M'
    0xB819,                                     // 73 O'
    0x203C,0x0473,0x0C96,0x089D,0x88A4,         // 74 P' *
    0x0C3C,0x0924,0x0C3C,0x8517,                // 79 R'
    0x88E1,                                     // 83 S'
    0x203C,0x054C,0x882E,                       // 84 T'
    0xB820,                                     // 87 U'
    0x1081,0x0888,0x088F,0x0C96,0x089D,0x88A4,  // 88 V',W' *
    0x0902,0x0909,0x8910,                       // 94 Z'
    0xA83C,                                     // 97 G',J',K',L',Q',X',W' (no sound)
    // missing: Ľ/Ĺ,Ř/Ŕ,Ú/ˇU,ô,ä,é/ě
    // accent?: B',F',M',P',V'
    // nosound: G',J',K',L',Q',X',W'
    };
// formant sounds sampled as 1bit PCM
const static BYTE tab_PCM[]=
    {
// bits,1bit PCM samples                            //  ix,sample in binary
     24,0x1A,0x99,0xE1,                             //   0,000110101001100111100001
     46,0xC3,0xE1,0xC7,0x8F,0x0F,0xF8,              //   4,110000111110000111000111100011110000111111111000
     46,0x03,0x0F,0x07,0xC1,0xE3,0xFF,              //  11,000000110000111100000111110000011110001111111111
     46,0x40,0x17,0xFF,0x00,0x03,0xF8,              //  18,010000000001011111111111000000000000001111111000
     46,0x7C,0xC1,0xF1,0xF8,0x03,0xFE,              //  25,011111001100000111110001111110000000001111111110
     46,0x00,0x7F,0xFC,0x00,0x03,0xF8,              //  32,000000000111111111111100000000000000001111111000
     46,0x0F,0x09,0xF1,0xFE,0x03,0xEF,              //  39,000011110000100111110001111111100000001111101111
     46,0x40,0x17,0xFF,0x00,0x03,0xE1,              //  46,010000000001011111111111000000000000001111100001
     46,0x5C,0x35,0xC5,0xAA,0x35,0x00,              //  53,010111000011010111000101101010100011010100000000
      0,                                            //  60,
     46,0x00,0x00,0x00,0x00,0x00,0x3E,              //  61,000000000000000000000000000000000000000000111110
     90,0x3E,0x8E,0x38,0x73,0xCF,0xF8,0x78,0xC3,    //  68,0011111010001110001110000111001111001111111110000111100011000011
        0xDF,0x1C,0xF1,0xC7,                        //     11011111000111001111000111000111
     94,0x8E,0x38,0x73,0xCF,0xF8,0x78,0xC3,0xDF,    //  81,1000111000111000011100111100111111111000011110001100001111011111
        0x1C,0xF1,0xC7,0xFE,                        //     00011100111100011100011111111110
     46,0x03,0xC0,0xFF,0x00,0x00,0xFF,              //  94,000000111100000011111111000000000000000011111111
     46,0xF8,0x00,0x7F,0xF8,0x03,0xFF,              // 101,111110000000000001111111111110000000001111111111
     46,0xF0,0x01,0xFF,0xE0,0x03,0xAA,              // 108,111100000000000111111111111000000000001110101010
     46,0xCA,0x5A,0xD5,0x21,0x3D,0xFE,              // 115,110010100101101011010101001000010011110111111110
     46,0x1F,0xF8,0x00,0x00,0x1F,0xFF,              // 122,000111111111100000000000000000000001111111111111
     46,0xFC,0x20,0x00,0x00,0x03,0xFF,              // 129,111111000010000000000000000000000000001111111111
     46,0xFF,0x08,0x79,0x00,0x02,0xFF,              // 136,111111110000100001111001000000000000001011111111
     46,0xE1,0xC7,0x1F,0xE0,0x03,0xFF,              // 143,111000011100011100011111111000000000001111111111
     46,0xD0,0x01,0xFF,0xF0,0x03,0x7F,              // 150,110100000000000111111111111100000000001101111111
     46,0x01,0xFA,0x5F,0xC0,0x07,0xF8,              // 157,000000011111101001011111110000000000011111111000
     46,0x0F,0xC0,0xFF,0x00,0x42,0xAA,              // 164,000011111100000011111111000000000100001010101010
    254,0xAA,0xA5,0x55,0x5A,0xAA,0xAA,0x5A,0xA5,    // 171,1010101010100101010101010101101010101010101010100101101010100101
        0x5A,0xAA,0x55,0x55,0xAA,0xAA,0xA5,0x55,    //     0101101010101010010101010101010110101010101010101010010101010101
        0xAA,0x5A,0xAA,0xA5,0x55,0xAA,0xAA,0xA5,    //     1010101001011010101010101010010101010101101010101010101010100101
        0x55,0xAA,0xAA,0x55,0xA5,0xA5,0xAA,0xA5,    //     0101010110101010101010100101010110100101101001011010101010100101
     46,0xA5,0x55,0x5A,0xAA,0xAA,0x5A,              // 204,101001010101010101011010101010101010101001011010
     46,0x5A,0xAA,0xAA,0x5A,0xA5,0x5A,              // 211,010110101010101010101010010110101010010101011010
     46,0xAA,0x5A,0xA5,0x5A,0xAA,0x55,              // 218,101010100101101010100101010110101010101001010101
    254,0xB7,0x66,0x6C,0xD8,0xF9,0xB3,0x6C,0xAD,    // 225,1011011101100110011011001101100011111001101100110110110010101101
        0x37,0x37,0x66,0xFC,0x9B,0x87,0xF6,0xC0,    //     0011011100110111011001101111110010011011100001111111011011000000
        0xD3,0xB6,0x60,0xF7,0xF7,0x3E,0x4D,0xFB,    //     1101001110110110011000001111011111110111001111100100110111111011
        0xFE,0x5D,0xB7,0xDE,0x46,0xF6,0x96,0xB4,    //     1111111001011101101101111101111001000110111101101001011010110100
     46,0x66,0x6C,0xD8,0xF9,0xB3,0x6C,              // 258,011001100110110011011000111110011011001101101100
     46,0xD8,0xF9,0xB3,0x6C,0xAD,0x37,              // 265,110110001111100110110011011011001010110100110111
     46,0xB3,0x6C,0xAD,0x37,0x37,0x66,              // 272,101100110110110010101101001101110011011101100110
     94,0x3E,0x4D,0xFB,0xFE,0x5D,0xB7,0xDE,0x46,    // 279,0011111001001101111110111111111001011101101101111101111001000110
        0xF6,0x96,0xB4,0x4F,                        //     11110110100101101011010001001111
     46,0xDE,0x46,0xF6,0x96,0xB4,0x4F,              // 292,110111100100011011110110100101101011010001001111
    254,0x4F,0xAA,0xA9,0x55,0xAA,0xAA,0xA5,0x69,    // 299,0100111110101010101010010101010110101010101010101010010101101001
        0x59,0x9A,0x6A,0x95,0x55,0x95,0x55,0x6A,    //     0101100110011010011010101001010101010101100101010101010101101010
        0xA5,0x55,0xA9,0x4D,0x66,0x6A,0x92,0xEC,    //     1010010101010101101010010100110101100110011010101001001011101100
        0xA5,0x55,0xD2,0x96,0x55,0xA2,0xBA,0xCD,    //     1010010101010101110100101001011001010101101000101011101011001101
     94,0x6A,0x92,0xEC,0xA5,0x55,0xD2,0x96,0x55,    // 332,0110101010010010111011001010010101010101110100101001011001010101
        0xA2,0xBA,0xCD,0x00,                        //     10100010101110101100110100000000
    254,0x00,0x66,0x99,0xCC,0x67,0x31,0x8E,0x66,    // 345,0000000001100110100110011100110001100111001100011000111001100110
        0x39,0xA6,0x6B,0x19,0x66,0x59,0xC6,0x71,    //     0011100110100110011010110001100101100110010110011100011001110001
        0x09,0x67,0x19,0xCB,0x01,0x71,0xCC,0x73,    //     0000100101100111000110011100101100000001011100011100110001110011
        0x19,0x99,0xCC,0xC6,0x67,0x19,0x9A,0xC6,    //     0001100110011001110011001100011001100111000110011001101011000110
    };
//---------------------------------------------------------------------------
void say_text(char *txt)
    {
    int i;
    char a0,a1;
    for (a1=0xBB,i=0;a1;i++)                            // process txt
        {
        a0=a1; a1=txt[i];                               // a0,a1 are last,actual char
        if ((a1>='a')&&(a1<='z')) a1+='A'-'a';          // a..z -> A..Z
        if ((a0=='C')&&(a1=='H')){ a0='H'; a1='\''; }   // CH -> H'
        if ((a0>='A')&&(a0<='Z'))
            {
            if (a1=='\''){ a0+=0x1A; a1=0xBB; }         // handle diacritic
            say_char(a0);                               // syntetize sound
            continue;
            }
        if (a0==',') say_wait(t_comma);
        if (a0==' ') say_wait(t_space);
        }
    }
//----------------------------------------------------------------------
void say_wait(WORD ws)
    {
    for (;ws;ws--) sound_out(_sound_on);
    }
//----------------------------------------------------------------------
void say_char(char chr) // chr =  < `A` , `Z`+26 >
    {
    WORD a;
    BYTE ws,pcm;
    int i,j,e,num,pcm_ix,bits;
    i=tab_char[chr-'A'];
    for (e=1;e;i++)
        {
        a=tab_RLE[i];
        e     =!(a     &0x8000);
        num   = (a>>10)&0x001F;
        pcm_ix=  a     &0x03FF;
        for (;num;num--)
            {
            for (j=pcm_ix,bits=tab_PCM[j],j++;bits;j++)
             for (pcm=tab_PCM[j],a=0x80;(bits)&&(a);a>>=1,bits--)
              for (_sound_on=(a&pcm),ws=t_speed;ws;ws--)
               sound_out(_sound_on);
            say_wait(t_pause);
            }
        }
    }
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------

Jawaban

5 Edward Dec 16 2020 at 02:33

Saya sama sekali tidak dapat memahami pidato dari program Hlasový , tetapi mungkin pidato itu sesuai dengan kebutuhan Anda.

Saya tidak memiliki pengetahuan khusus tentang perangkat lunak khusus ini, tetapi berdasarkan waktu rilis dan ukurannya, hampir tidak diragukan lagi ini adalah sistem berbasis forman. Perangkat lunak tipikal (pada komputer 8-bit model lama itu) menggunakan teks-ke-fonem dan kemudian konversi fonem-ke-forman.

Sistem yang agak lebih besar tetapi lebih dapat dipahami dari era itu adalah "SAM" atau "Software Automated Mouth" yang sekarang telah di- porting seseorang ke Javascript . Ikuti tautan dari sana untuk membaca lebih lanjut, termasuk kode C yang direkayasa ulang.

Penulis perangkat lunak itu dari awal 1980-an, Mark Barton, sebenarnya baru - baru ini diwawancarai dan menawarkan beberapa wawasan tentang perangkat lunak itu.

Program ini

Berikut analisis lebih lanjut dari perangkat lunak rekayasa balik Anda. Saya akan memberi tahu Anda bagaimana saya melakukannya serta menunjukkan hasilnya. Pertama, saya mulai melihat loop paling dalam dan secara berturut-turut menulis ulang, menguji hasilnya setiap kali untuk memastikannya menghasilkan hasil yang identik di setiap langkah. Kemudian saya pada dasarnya mengulanginya untuk bagian fungsi yang lebih besar dan lebih besar. Saya juga mengganti nama dan menambahkan variabel agar lebih mencerminkan bagaimana perangkat lunak benar-benar menggunakannya. Meskipun Z80 terbatas dalam register yang dapat digunakannya (dan apa yang dapat dilakukan oleh register tersebut), kami tidak memiliki batasan yang sama di C ++, sehingga kode tersebut ditulis ulang untuk kejelasan.

say_char ()

void say_char(char chr)         // chr =  < `A` , `Z`+26 >
{
    const Chain *chain = &chain_sequence[chain_start[chr - 'A']];
    for (BYTE c=0; (c & 0x80) == 0; ++chain) {
        // count is in low four bits of c, end flag is high bit
        for (c = chain->copies_and_end(); c & 0xf; --c) {
            BYTE a = chain->numbits_lookup();
            if (a != 0) {
                BYTE bitcount = num_bits[a];
                BYTE bitloc = chain->start_index();

                // bitcount is the number of bits to emit
                // starting with the MSB of sound_bits[bitloc]
                for ( ;bitcount; ++bitloc) {
                    for (BYTE mask = 0x80; mask; mask >>= 1) {
                        _sound_on = (mask & sound_bits[bitloc]);
                        for (BYTE ws = t_speed; ws; ws--)
                            sound_out(_sound_on);
                        if (--bitcount == 0)
                            break;
                    }
                }
            }
            say_wait(t_pause);
        }
    }
}

Berikut penjelasannya. Pertama, saya mengganti nama strukturnya:

tab_char0 --> chain_start
tab_char1 --> chain_sequence
tab_char2 --> sound_bits
tab_char3 --> num_bits

Kemudian saya memodifikasi chain_sequenceuntuk menggunakan struktur C ++ dua byte sebagai gantinya. Definisinya begini:

struct Chain {
        // bits: 7    6    5    4    3    2    1    0
    BYTE a;  //  m2   m1   c0   -    l3   l2   l1   l0
    BYTE b;  // end | c7   c6   c5   c4   c3   c2   c1

    bool end() const { return b & 0x80; }
    BYTE copies() const { return a & 0x0F; }
    BYTE start_index() const { return ((b & 0x7f) << 1) | ((a & 0x20) >> 5); }
    BYTE copies_and_end() const {
        return (a & 0x0F) | (b & 0x80);
    }
    BYTE numbits_lookup() const {
        return (a >> 5) & 7;
    }
    friend std::ostream& operator<<(std::ostream& out, const Chain& ch) {
        return out 
            << "copies = " << unsigned(ch.copies())
            << ", start_index = " << unsigned(ch.start_index())
            << ", numbits_lookup = " << unsigned(ch.numbits_lookup())
            << ", end = " << std::boolalpha << bool(ch.b & 0x80)
            << ", useless = " << bool(ch.a & 0x10);
    }
};

Karena perubahan ini, saya harus memodifikasi chain_starttabel untuk membagi dua entri.

Bagaimana itu bekerja

Untuk setiap huruf, kode dimulai dengan pencarian di chain_starttabel. Itu adalah indeks ke dalam chain_sequencetabel. Jika kita memilih tiga entri pertama dalam tabel itu, mereka akan terlihat seperti ini:

const static Chain chain_sequence[98] = {
    /* A = 0 */ { 0x36, 0x81, },
    /* B = 1 */ { 0x34, 0x19, }, { 0x31, 0xab, },
    /* C = 3 */ { 0x18, 0x19, }, { 0x91, 0xc3, },

Masing-masing adalah urutan rantai, dengan item terakhir diidentifikasi dengan bit tinggi dari kumpulan byte kedua. Untuk huruf 'A', diterjemahkan menjadi ini:

copies = 6, start_index = 3, numbits_lookup = 1, end = true 

Apa artinya ini adalah bahwa kode tersebut membuat enam salinan dari pola bit. Setiap salinan diakhiri dengan t_pausebit nol. Untuk bit awal setiap salinan, kode menggunakan numbits_lookupnilai untuk mencari panjang yang diinginkan dalam 5-byte num_bits. Jadi untuk 'A', pencariannya adalah 1 dan itu sesuai dengan 0x2e = 46, tetapi cara kode ditulis, yang sebenarnya sesuai dengan satu bit lebih sedikit yang sebenarnya dipancarkan, atau 45 dalam kasus ini.

Selanjutnya menggunakan start_indexsebagai indeks ke sound_bits. Setiap byte dalam tabel kemudian dihitung mulai dengan bit paling signifikan dari setiap byte. Jadi dalam kasus ini, indeks 3 dan panjang 45 bit sesuai dengan entri berikut dalam tabel:

0xc3 0xe1 0xc7 0x8f, 0x0f, 0xf8

1100 0011  1110 0001  1100 0111  1000 1111  0000 1111  1111 10xx

Dua bit terakhir, bertanda xx tidak digunakan. Jadi efeknya adalah outputnya sesuai dengan enam salinan ini:

1100001111100001110001111000111100001111111110
... followed by `t_pause` 0 bits

Komentar

Bug terjemahan

Ada bug di kode. Jika Anda melihat lebih dekat, salah satu bit dalam apa yang saya panggil Chaintidak digunakan (bit 4 dari byte pertama), tetapi salah satu bit lainnya digunakan dua kali (bit 5 dari byte pertama).

Memang, saya membongkar kode Z80 asli dan menemukan ini:

add hl,de       ; cy = 0 (can't overflow)
ld b,(hl)       ; b = bitlen[a];
pop hl          ;
inc hl          ;
ld a,(hl)       ; a = chain_sequence[hl + 1]
dec hl          ;
push hl         ;
rla             ; the carry shifted in is always zero
ld de,sound_bits    ; point to bit table
ld l,a          ;
ld h,000h       ;
add hl,de       ; hl = sound_bits[a]
ld a,080h       ; start with mask = 0x80

Kode Anda tampaknya menyiratkan bahwa bit carry disetel saat memanggil apa yang telah saya beri label start_index(), tetapi lebih dekat ke rlainstruksi relevan yang membuat sound_bitsbyte indeks, bit carry dijamin nol. Instruksi add, seperti disebutkan di atas, tidak dapat meluap sehingga menghapus bit carry. Tidak ada instruksi dari sana ke rlainstruksi yang mengubah bit carry, jadi nol pada saat itu.

Pengamatan lainnya

Juga tiga byte pertama dari sound_bitsarray tampaknya tidak digunakan.

Tampaknya tidak banyak data yang tumpang tindih, tetapi mungkin saja ada. Urutan rantai untuk salah satu huruf digunakan kembali. Saya belum bekerja untuk memecahkan kode diakritik sebenarnya yang digunakan di sini, tetapi jika 26 huruf kedua ditandai A 'ke Z', huruf untuk M 'dimulai pada indeks 68 dan mencakup 5 segmen rantai. Satu untuk N 'menggunakan tiga segmen terakhir ini.

Juga untuk versi pendek dan panjang dari vokal yang sama, seperti A dan A '(A dengan čárka menandakan vokal panjang dalam bahasa Ceko), kode saat ini mengulangi token rantai, tetapi hanya dengan urutan yang lebih panjang. Dimungkinkan untuk menggabungkannya dan menggunakan satu bendera bit untuk menunjukkan vokal.

Pada mesin 16-bit, ini dapat dibuat jauh lebih efisien dengan merestrukturisasi data. Itu juga bisa dimodifikasi menjadi event driven pada sistem tertanam. Misalnya, ini bisa digerakkan oleh interupsi oleh timer interupsi. Atau seseorang dapat membuat antrian sampel dan menggunakan transfer DMA untuk memasukkannya ke speaker.

Fisika

Apa yang dilakukan ini adalah menciptakan frekuensi terendah melalui urutan bit (minimal 45) diikuti oleh t_pausenol. Frekuensi yang lebih tinggi dibuat dalam pola bit terdepan di setiap salinan. Seperti yang diharapkan, ini adalah synthesizer berbasis forman dengan resolusi yang relatif rendah.