Memahami / Membalik mesin TTS sederhana (tapi berkualitas baik)
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_char
fungsi 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
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_sequence
untuk 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_start
tabel untuk membagi dua entri.
Bagaimana itu bekerja
Untuk setiap huruf, kode dimulai dengan pencarian di chain_start
tabel. Itu adalah indeks ke dalam chain_sequence
tabel. 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_pause
bit nol. Untuk bit awal setiap salinan, kode menggunakan numbits_lookup
nilai 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_index
sebagai 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 Chain
tidak 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 rla
instruksi relevan yang membuat sound_bits
byte indeks, bit carry dijamin nol. Instruksi add, seperti disebutkan di atas, tidak dapat meluap sehingga menghapus bit carry. Tidak ada instruksi dari sana ke rla
instruksi yang mengubah bit carry, jadi nol pada saat itu.
Pengamatan lainnya
Juga tiga byte pertama dari sound_bits
array 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_pause
nol. 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.