846 lines
34 KiB
C#
846 lines
34 KiB
C#
|
using static SAMSharp.Renderer;
|
|||
|
|
|||
|
namespace SAMSharp
|
|||
|
{
|
|||
|
class Sam
|
|||
|
{
|
|||
|
//tab40672
|
|||
|
static byte[] stressInputTable =
|
|||
|
{
|
|||
|
(byte)'*',(byte)'1',(byte)'2',(byte)'3',(byte)'4',(byte)'5',(byte)'6',(byte)'7',(byte)'8'
|
|||
|
};
|
|||
|
|
|||
|
//tab40682
|
|||
|
static byte[] signInputTable1 =
|
|||
|
{
|
|||
|
(byte)' ',(byte)'.',(byte)'?',(byte)',',(byte)'-',(byte)'I',(byte)'I',(byte)'E',
|
|||
|
(byte)'A',(byte)'A',(byte)'A',(byte)'A',(byte)'U',(byte)'A',(byte)'I',(byte)'E',
|
|||
|
(byte)'U',(byte)'O',(byte)'R',(byte)'L',(byte)'W',(byte)'Y',(byte)'W',(byte)'R',
|
|||
|
(byte)'L',(byte)'W',(byte)'Y',(byte)'M',(byte)'N',(byte)'N',(byte)'D',(byte)'Q',
|
|||
|
(byte)'S',(byte)'S',(byte)'F',(byte)'T',(byte)'/',(byte)'/',(byte)'Z',(byte)'Z',
|
|||
|
(byte)'V',(byte)'D',(byte)'C',(byte)'*',(byte)'J',(byte)'*',(byte)'*',(byte)'*',
|
|||
|
(byte)'E',(byte)'A',(byte)'O',(byte)'A',(byte)'O',(byte)'U',(byte)'B',(byte)'*',
|
|||
|
(byte)'*',(byte)'D',(byte)'*',(byte)'*',(byte)'G',(byte)'*',(byte)'*',(byte)'G',
|
|||
|
(byte)'*',(byte)'*',(byte)'P',(byte)'*',(byte)'*',(byte)'T',(byte)'*',(byte)'*',
|
|||
|
(byte)'K',(byte)'*',(byte)'*',(byte)'K',(byte)'*',(byte)'*',(byte)'U',(byte)'U',
|
|||
|
(byte)'U'
|
|||
|
};
|
|||
|
|
|||
|
//tab40763
|
|||
|
static byte[] signInputTable2 =
|
|||
|
{
|
|||
|
(byte)'*',(byte)'*',(byte)'*',(byte)'*',(byte)'*',(byte)'Y',(byte)'H',(byte)'H',
|
|||
|
(byte)'E',(byte)'A',(byte)'H',(byte)'O',(byte)'H',(byte)'X',(byte)'X',(byte)'R',
|
|||
|
(byte)'X',(byte)'H',(byte)'X',(byte)'X',(byte)'X',(byte)'X',(byte)'H',(byte)'*',
|
|||
|
(byte)'*',(byte)'*',(byte)'*',(byte)'*',(byte)'*',(byte)'X',(byte)'X',(byte)'*',
|
|||
|
(byte)'*',(byte)'H',(byte)'*',(byte)'H',(byte)'H',(byte)'X',(byte)'*',(byte)'H',
|
|||
|
(byte)'*',(byte)'H',(byte)'H',(byte)'*',(byte)'*',(byte)'*',(byte)'*',(byte)'*',
|
|||
|
(byte)'Y',(byte)'Y',(byte)'Y',(byte)'W',(byte)'W',(byte)'W',(byte)'*',(byte)'*',
|
|||
|
(byte)'*',(byte)'*',(byte)'*',(byte)'*',(byte)'*',(byte)'*',(byte)'*',(byte)'X',
|
|||
|
(byte)'*',(byte)'*',(byte)'*',(byte)'*',(byte)'*',(byte)'*',(byte)'*',(byte)'*',
|
|||
|
(byte)'*',(byte)'*',(byte)'*',(byte)'X',(byte)'*',(byte)'*',(byte)'L',(byte)'M',
|
|||
|
(byte)'N'
|
|||
|
};
|
|||
|
|
|||
|
//loc_9F8C
|
|||
|
enum Flags
|
|||
|
{
|
|||
|
FLAG_PLOSIVE = 0x0001,
|
|||
|
FLAG_STOPCONS = 0x0002, /* stop consonant */
|
|||
|
FLAG_VOICED = 0x0004,
|
|||
|
/* 0x08 */
|
|||
|
FLAG_DIPTHONG = 0x0010,
|
|||
|
FLAG_DIP_YX = 0x0020, /* dipthong ending with YX */
|
|||
|
FLAG_CONSONANT = 0x0040,
|
|||
|
FLAG_VOWEL = 0x0080,
|
|||
|
FLAG_PUNCT = 0x0100,
|
|||
|
/* 0x200 */
|
|||
|
FLAG_ALVEOLAR = 0x0400,
|
|||
|
FLAG_NASAL = 0x0800,
|
|||
|
FLAG_LIQUIC = 0x1000, /* liquic consonant */
|
|||
|
FLAG_FRICATIVE = 0x2000
|
|||
|
};
|
|||
|
|
|||
|
static ushort[] flags = {
|
|||
|
0x8000 , 0xC100 , 0xC100 , 0xC100 , 0xC100 , 0x00A4 , 0x00A4 , 0x00A4 ,
|
|||
|
0x00A4 , 0x00A4 , 0x00A4 , 0x0084 , 0x0084 , 0x00A4 , 0x00A4 , 0x0084 ,
|
|||
|
0x0084 , 0x0084 , 0x0084 , 0x0084 , 0x0084 , 0x0084 , 0x0044 , 0x1044 ,
|
|||
|
0x1044 , 0x1044 , 0x1044 , 0x084C , 0x0C4C , 0x084C , 0x0448 , 0x404C ,
|
|||
|
|
|||
|
0x2440 , 0x2040 , 0x2040 , 0x2440 , 0x0040 , 0x0040 , 0x2444 , 0x2044 ,
|
|||
|
0x2044 , 0x2444 , 0x2048 , 0x2040 , 0x004C , 0x2044 , 0x0000 , 0x0000 ,
|
|||
|
0x00B4 , 0x00B4 , 0x00B4 , 0x0094 , 0x0094 , 0x0094 , 0x004E , 0x004E ,
|
|||
|
|
|||
|
0x004E , 0x044E , 0x044E , 0x044E , 0x004E , 0x004E , 0x004E , 0x004E ,
|
|||
|
0x004E , 0x004E , 0x004B , 0x004B , 0x004B , 0x044B , 0x044B , 0x044B ,
|
|||
|
0x004B , 0x004B , 0x004B , 0x004B , 0x004B , 0x004B , 0x0080 , 0x00C1 ,
|
|||
|
0x00C1
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
//tab45616???
|
|||
|
static byte[] phonemeStressedLengthTable =
|
|||
|
{
|
|||
|
0x00 , 0x12 , 0x12 , 0x12 , 8 ,0xB , 9 ,0xB ,
|
|||
|
0xE ,0xF ,0xB , 0x10 ,0xC , 6 , 6 ,0xE ,
|
|||
|
0xC ,0xE ,0xC ,0xB , 8 , 8 ,0xB ,0xA ,
|
|||
|
9 , 8 , 8 , 8 , 8 , 8 , 3 , 5 ,
|
|||
|
2 , 2 , 2 , 2 , 2 , 2 , 6 , 6 ,
|
|||
|
8 , 6 , 6 , 2 , 9 , 4 , 2 , 1 ,
|
|||
|
0xE ,0xF ,0xF ,0xF ,0xE ,0xE , 8 , 2 ,
|
|||
|
2 , 7 , 2 , 1 , 7 , 2 , 2 , 7 ,
|
|||
|
2 , 2 , 8 , 2 , 2 , 6 , 2 , 2 ,
|
|||
|
7 , 2 , 4 , 7 , 1 , 4 , 5 , 5
|
|||
|
};
|
|||
|
|
|||
|
//tab45536???
|
|||
|
static byte[] phonemeLengthTable =
|
|||
|
{
|
|||
|
0 , 0x12 , 0x12 , 0x12 , 8 , 8 , 8 , 8 ,
|
|||
|
8 ,0xB , 6 ,0xC ,0xA , 5 , 5 ,0xB ,
|
|||
|
0xA ,0xA ,0xA , 9 , 8 , 7 , 9 , 7 ,
|
|||
|
6 , 8 , 6 , 7 , 7 , 7 , 2 , 5 ,
|
|||
|
2 , 2 , 2 , 2 , 2 , 2 , 6 , 6 ,
|
|||
|
7 , 6 , 6 , 2 , 8 , 3 , 1 , 0x1E ,
|
|||
|
0xD ,0xC ,0xC ,0xC ,0xE , 9 , 6 , 1 ,
|
|||
|
2 , 5 , 1 , 1 , 6 , 1 , 2 , 6 ,
|
|||
|
1 , 2 , 8 , 2 , 2 , 4 , 2 , 2 ,
|
|||
|
6 , 1 , 4 , 6 , 1 , 4 , 0xC7 , 0xFF
|
|||
|
};
|
|||
|
|
|||
|
enum Handler : byte
|
|||
|
{
|
|||
|
pR = 23,
|
|||
|
pD = 57,
|
|||
|
pT = 69,
|
|||
|
BREAK = 254,
|
|||
|
END = 255
|
|||
|
};
|
|||
|
|
|||
|
static byte[] input = new byte[256]; //tab39445
|
|||
|
//standard sam sound
|
|||
|
public static byte speed = 72;
|
|||
|
public static byte pitch = 64;
|
|||
|
public static byte mouth = 128;
|
|||
|
public static byte throat = 128;
|
|||
|
public static int singmode = 0;
|
|||
|
|
|||
|
public static byte[] stress = new byte[256]; //numbers from 0 to 8
|
|||
|
public static byte[] phonemeLength = new byte[256]; //tab40160
|
|||
|
public static byte[] phonemeindex = new byte[256];
|
|||
|
|
|||
|
public static byte[] phonemeIndexOutput = new byte[60]; //tab47296
|
|||
|
public static byte[] stressOutput = new byte[60]; //tab47365
|
|||
|
public static byte[] phonemeLengthOutput = new byte[60]; //tab47416
|
|||
|
|
|||
|
// contains the final soundbuffer
|
|||
|
public static int bufferpos = 0;
|
|||
|
public static byte[] buffer = null;
|
|||
|
|
|||
|
public static void SetInput(byte[] _input)
|
|||
|
{
|
|||
|
int i, l;
|
|||
|
l = input.Length;
|
|||
|
if (l > 254) l = 254;
|
|||
|
for (i = 0; i < l; i++)
|
|||
|
input[i] = _input[i];
|
|||
|
input[l] = 0;
|
|||
|
}
|
|||
|
|
|||
|
void SetSpeed(byte _speed) => speed = _speed;
|
|||
|
void SetPitch(byte _pitch) => pitch = _pitch;
|
|||
|
void SetMouth(byte _mouth) => mouth = _mouth;
|
|||
|
void SetThroat(byte _throat) => throat = _throat;
|
|||
|
void EnableSingmode() => singmode = 1;
|
|||
|
public static byte[] GetBuffer() => buffer;
|
|||
|
public static int GetBufferLength() => bufferpos;
|
|||
|
|
|||
|
static void Init()
|
|||
|
{
|
|||
|
int i;
|
|||
|
SetMouthThroat(mouth, throat);
|
|||
|
|
|||
|
bufferpos = 0;
|
|||
|
// TODO, check for free the memory, 10 seconds of output should be more than enough
|
|||
|
buffer = new byte[22050 * 10];
|
|||
|
|
|||
|
for (i = 0; i < 256; i++)
|
|||
|
{
|
|||
|
stress[i] = 0;
|
|||
|
phonemeLength[i] = 0;
|
|||
|
}
|
|||
|
|
|||
|
for (i = 0; i < 60; i++)
|
|||
|
{
|
|||
|
phonemeIndexOutput[i] = 0;
|
|||
|
stressOutput[i] = 0;
|
|||
|
phonemeLengthOutput[i] = 0;
|
|||
|
}
|
|||
|
phonemeindex[255] = (byte)Handler.END; //to prevent buffer overflow // ML : changed from 32 to 255 to stop freezing with long inputs
|
|||
|
}
|
|||
|
|
|||
|
public static int SAMMain()
|
|||
|
{
|
|||
|
byte X = 0; //!! is this intended like this?
|
|||
|
Init();
|
|||
|
/* FIXME: At odds with assignment in Init() */
|
|||
|
phonemeindex[255] = 32; //to prevent buffer overflow
|
|||
|
|
|||
|
if (Parser1() == 0) return 0;
|
|||
|
Parser2();
|
|||
|
CopyStress();
|
|||
|
SetPhonemeLength();
|
|||
|
AdjustLengths();
|
|||
|
Code41240();
|
|||
|
do
|
|||
|
{
|
|||
|
if (phonemeindex[X] > 80)
|
|||
|
{
|
|||
|
phonemeindex[X] = (byte)Handler.END;
|
|||
|
break; // error: delete all behind it
|
|||
|
}
|
|||
|
} while (++X != 0);
|
|||
|
InsertBreath();
|
|||
|
|
|||
|
PrepareOutput();
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
static void PrepareOutput()
|
|||
|
{
|
|||
|
byte srcpos = 0; // Position in source
|
|||
|
byte destpos = 0; // Position in output
|
|||
|
|
|||
|
while (true)
|
|||
|
{
|
|||
|
byte A = phonemeindex[srcpos];
|
|||
|
phonemeIndexOutput[destpos] = A;
|
|||
|
switch (A)
|
|||
|
{
|
|||
|
case (byte)Handler.END:
|
|||
|
Render();
|
|||
|
return;
|
|||
|
case (byte)Handler.BREAK:
|
|||
|
phonemeIndexOutput[destpos] = (byte)Handler.END;
|
|||
|
Render();
|
|||
|
destpos = 0;
|
|||
|
break;
|
|||
|
case 0:
|
|||
|
break;
|
|||
|
default:
|
|||
|
phonemeLengthOutput[destpos] = phonemeLength[srcpos];
|
|||
|
stressOutput[destpos] = stress[srcpos];
|
|||
|
++destpos;
|
|||
|
break;
|
|||
|
}
|
|||
|
++srcpos;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void InsertBreath()
|
|||
|
{
|
|||
|
byte mem54 = 255;
|
|||
|
byte len = 0;
|
|||
|
byte index; //variable Y
|
|||
|
|
|||
|
byte pos = 0;
|
|||
|
|
|||
|
while ((index = phonemeindex[pos]) != (byte)Handler.END)
|
|||
|
{
|
|||
|
len += phonemeLength[pos];
|
|||
|
if (len < 232)
|
|||
|
{
|
|||
|
if (index == (byte)Handler.BREAK)
|
|||
|
{
|
|||
|
}
|
|||
|
else if ((flags[index] & (ushort)Flags.FLAG_PUNCT) == 0)
|
|||
|
{
|
|||
|
if (index == 0) mem54 = pos;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
len = 0;
|
|||
|
Insert(++pos, (byte)Handler.BREAK, 0, 0);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
pos = mem54;
|
|||
|
phonemeindex[pos] = 31; // 'Q*' glottal stop
|
|||
|
phonemeLength[pos] = 4;
|
|||
|
stress[pos] = 0;
|
|||
|
|
|||
|
len = 0;
|
|||
|
Insert(++pos, (byte)Handler.BREAK, 0, 0);
|
|||
|
}
|
|||
|
++pos;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// Iterates through the phoneme buffer, copying the stress value from
|
|||
|
// the following phoneme under the following circumstance:
|
|||
|
|
|||
|
// 1. The current phoneme is voiced, excluding plosives and fricatives
|
|||
|
// 2. The following phoneme is voiced, excluding plosives and fricatives, and
|
|||
|
// 3. The following phoneme is stressed
|
|||
|
//
|
|||
|
// In those cases, the stress value+1 from the following phoneme is copied.
|
|||
|
//
|
|||
|
// For example, the word LOITER is represented as LOY5TER, with as stress
|
|||
|
// of 5 on the dipthong OY. This routine will copy the stress value of 6 (5+1)
|
|||
|
// to the L that precedes it.
|
|||
|
|
|||
|
static void CopyStress()
|
|||
|
{
|
|||
|
// loop thought all the phonemes to be output
|
|||
|
byte pos = 0; //mem66
|
|||
|
byte Y;
|
|||
|
|
|||
|
while ((Y = phonemeindex[pos]) != (byte)Handler.END)
|
|||
|
{
|
|||
|
// if CONSONANT_FLAG set, skip - only vowels get stress
|
|||
|
if ((flags[Y] & 64) != 0)
|
|||
|
{
|
|||
|
Y = phonemeindex[pos + 1];
|
|||
|
|
|||
|
// if the following phoneme is the end, or a vowel, skip
|
|||
|
if (Y != (byte)Handler.END && (flags[Y] & 128) != 0)
|
|||
|
{
|
|||
|
// get the stress value at the next position
|
|||
|
Y = stress[pos + 1];
|
|||
|
if (Y != 0 && (Y & 128) == 0)
|
|||
|
{
|
|||
|
// if next phoneme is stressed, and a VOWEL OR ER
|
|||
|
// copy stress from next phoneme to this one
|
|||
|
stress[pos] = (byte)(Y + 1);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
++pos;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void Insert(byte position/*var57*/, byte mem60, byte mem59, byte mem58)
|
|||
|
{
|
|||
|
int i;
|
|||
|
for (i = 253; i >= position; i--) // ML : always keep last safe-guarding 255
|
|||
|
{
|
|||
|
phonemeindex[i + 1] = phonemeindex[i];
|
|||
|
phonemeLength[i + 1] = phonemeLength[i];
|
|||
|
stress[i + 1] = stress[i];
|
|||
|
}
|
|||
|
|
|||
|
phonemeindex[position] = mem60;
|
|||
|
phonemeLength[position] = mem59;
|
|||
|
stress[position] = mem58;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
static int full_match(byte sign1, byte sign2)
|
|||
|
{
|
|||
|
byte Y = 0;
|
|||
|
do
|
|||
|
{
|
|||
|
// GET FIRST CHARACTER AT POSITION Y IN signInputTable
|
|||
|
// --> should change name to PhonemeNameTable1
|
|||
|
byte A = signInputTable1[Y];
|
|||
|
|
|||
|
if (A == sign1)
|
|||
|
{
|
|||
|
A = signInputTable2[Y];
|
|||
|
// NOT A SPECIAL AND MATCHES SECOND CHARACTER?
|
|||
|
if ((A != '*') && (A == sign2)) return Y;
|
|||
|
}
|
|||
|
} while (++Y != 81);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
static int wild_match(byte sign1)
|
|||
|
{
|
|||
|
int Y = 0;
|
|||
|
do
|
|||
|
{
|
|||
|
if (signInputTable2[Y] == '*')
|
|||
|
{
|
|||
|
if (signInputTable1[Y] == sign1) return Y;
|
|||
|
}
|
|||
|
} while (++Y != 81);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
// The input[] buffer contains a string of phonemes and stress markers along
|
|||
|
// the lines of:
|
|||
|
//
|
|||
|
// DHAX KAET IHZ AH5GLIY. <0x9B>
|
|||
|
//
|
|||
|
// The byte 0x9B marks the end of the buffer. Some phonemes are 2 bytes
|
|||
|
// long, such as "DH" and "AX". Others are 1 byte long, such as "T" and "Z".
|
|||
|
// There are also stress markers, such as "5" and ".".
|
|||
|
//
|
|||
|
// The first character of the phonemes are stored in the table signInputTable1[].
|
|||
|
// The second character of the phonemes are stored in the table signInputTable2[].
|
|||
|
// The stress characters are arranged in low to high stress order in stressInputTable[].
|
|||
|
//
|
|||
|
// The following process is used to parse the input[] buffer:
|
|||
|
//
|
|||
|
// Repeat until the <0x9B> character is reached:
|
|||
|
//
|
|||
|
// First, a search is made for a 2 character match for phonemes that do not
|
|||
|
// end with the '*' (wildcard) character. On a match, the index of the phoneme
|
|||
|
// is added to phonemeIndex[] and the buffer position is advanced 2 bytes.
|
|||
|
//
|
|||
|
// If this fails, a search is made for a 1 character match against all
|
|||
|
// phoneme names ending with a '*' (wildcard). If this succeeds, the
|
|||
|
// phoneme is added to phonemeIndex[] and the buffer position is advanced
|
|||
|
// 1 byte.
|
|||
|
//
|
|||
|
// If this fails, search for a 1 character match in the stressInputTable[].
|
|||
|
// If this succeeds, the stress value is placed in the last stress[] table
|
|||
|
// at the same index of the last added phoneme, and the buffer position is
|
|||
|
// advanced by 1 byte.
|
|||
|
//
|
|||
|
// If this fails, return a 0.
|
|||
|
//
|
|||
|
// On success:
|
|||
|
//
|
|||
|
// 1. phonemeIndex[] will contain the index of all the phonemes.
|
|||
|
// 2. The last index in phonemeIndex[] will be 255.
|
|||
|
// 3. stress[] will contain the stress value for each phoneme
|
|||
|
|
|||
|
// input[] holds the string of phonemes, each two bytes wide
|
|||
|
// signInputTable1[] holds the first character of each phoneme
|
|||
|
// signInputTable2[] holds te second character of each phoneme
|
|||
|
// phonemeIndex[] holds the indexes of the phonemes after parsing input[]
|
|||
|
//
|
|||
|
// The parser scans through the input[], finding the names of the phonemes
|
|||
|
// by searching signInputTable1[] and signInputTable2[]. On a match, it
|
|||
|
// copies the index of the phoneme into the phonemeIndexTable[].
|
|||
|
//
|
|||
|
// The character <0x9B> marks the end of text in input[]. When it is reached,
|
|||
|
// the index 255 is placed at the end of the phonemeIndexTable[], and the
|
|||
|
// function returns with a 1 indicating success.
|
|||
|
static int Parser1()
|
|||
|
{
|
|||
|
byte sign1;
|
|||
|
byte position = 0;
|
|||
|
byte srcpos = 0;
|
|||
|
|
|||
|
stress = new byte[256]; // Clear the stress table.
|
|||
|
|
|||
|
while ((sign1 = input[srcpos]) != 155)
|
|||
|
{ // 155 (\233) is end of line marker
|
|||
|
int match;
|
|||
|
byte sign2 = input[++srcpos];
|
|||
|
if ((match = full_match(sign1, sign2)) != -1)
|
|||
|
{
|
|||
|
// Matched both characters (no wildcards)
|
|||
|
phonemeindex[position++] = (byte)match;
|
|||
|
++srcpos; // Skip the second character of the input as we've matched it
|
|||
|
}
|
|||
|
else if ((match = wild_match(sign1)) != -1)
|
|||
|
{
|
|||
|
// Matched just the first character (with second character matching '*'
|
|||
|
phonemeindex[position++] = (byte)match;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Should be a stress character. Search through the
|
|||
|
// stress table backwards.
|
|||
|
match = 8; // End of stress table. FIXME: Don't hardcode.
|
|||
|
while ((sign1 != stressInputTable[match]) && (match > 0)) --match;
|
|||
|
|
|||
|
if (match == 0) return 0; // failure
|
|||
|
|
|||
|
stress[position - 1] = (byte)match; // Set stress for prior phoneme
|
|||
|
}
|
|||
|
} //while
|
|||
|
|
|||
|
phonemeindex[position] = (byte)Handler.END;
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//change phonemelength depedendent on stress
|
|||
|
static void SetPhonemeLength()
|
|||
|
{
|
|||
|
int position = 0;
|
|||
|
while (phonemeindex[position] != 255)
|
|||
|
{
|
|||
|
byte A = stress[position];
|
|||
|
if ((A == 0) || ((A & 128) != 0))
|
|||
|
{
|
|||
|
phonemeLength[position] = phonemeLengthTable[phonemeindex[position]];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
phonemeLength[position] = phonemeStressedLengthTable[phonemeindex[position]];
|
|||
|
}
|
|||
|
position++;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void Code41240()
|
|||
|
{
|
|||
|
byte pos = 0;
|
|||
|
|
|||
|
while (phonemeindex[pos] != (byte)Handler.END)
|
|||
|
{
|
|||
|
byte index = phonemeindex[pos];
|
|||
|
|
|||
|
if ((flags[index] & (ushort)Flags.FLAG_STOPCONS) != 0)
|
|||
|
{
|
|||
|
if ((flags[index] & (ushort)Flags.FLAG_PLOSIVE) != 0)
|
|||
|
{
|
|||
|
byte A;
|
|||
|
byte X = pos;
|
|||
|
while (phonemeindex[++X] == 0) ; /* Skip pause */
|
|||
|
A = phonemeindex[X];
|
|||
|
if (A != (byte)Handler.END)
|
|||
|
{
|
|||
|
if ((flags[A] & 8) != 0 || (A == 36) || (A == 37)) { ++pos; continue; } // '/H' '/X'
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
Insert((byte)(pos + 1), (byte)(index + 1), phonemeLengthTable[index + 1], stress[pos]);
|
|||
|
Insert((byte)(pos + 2), (byte)(index + 2), phonemeLengthTable[index + 2], stress[pos]);
|
|||
|
pos += 2;
|
|||
|
}
|
|||
|
++pos;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
static void ChangeRule(byte position, byte mem60)
|
|||
|
{
|
|||
|
phonemeindex[position] = 13; //rule;
|
|||
|
Insert((byte)(position + 1), mem60, 0, stress[position]);
|
|||
|
}
|
|||
|
|
|||
|
// Rewrites the phonemes using the following rules:
|
|||
|
//
|
|||
|
// <DIPTHONG ENDING WITH WX> -> <DIPTHONG ENDING WITH WX> WX
|
|||
|
// <DIPTHONG NOT ENDING WITH WX> -> <DIPTHONG NOT ENDING WITH WX> YX
|
|||
|
// UL -> AX L
|
|||
|
// UM -> AX M
|
|||
|
// <STRESSED VOWEL> <SILENCE> <STRESSED VOWEL> -> <STRESSED VOWEL> <SILENCE> Q <VOWEL>
|
|||
|
// T R -> CH R
|
|||
|
// D R -> J R
|
|||
|
// <VOWEL> R -> <VOWEL> RX
|
|||
|
// <VOWEL> L -> <VOWEL> LX
|
|||
|
// G S -> G Z
|
|||
|
// K <VOWEL OR DIPTHONG NOT ENDING WITH IY> -> KX <VOWEL OR DIPTHONG NOT ENDING WITH IY>
|
|||
|
// G <VOWEL OR DIPTHONG NOT ENDING WITH IY> -> GX <VOWEL OR DIPTHONG NOT ENDING WITH IY>
|
|||
|
// S P -> S B
|
|||
|
// S T -> S D
|
|||
|
// S K -> S G
|
|||
|
// S KX -> S GX
|
|||
|
// <ALVEOLAR> UW -> <ALVEOLAR> UX
|
|||
|
// CH -> CH CH' (CH requires two phonemes to represent it)
|
|||
|
// J -> J J' (J requires two phonemes to represent it)
|
|||
|
// <UNSTRESSED VOWEL> T <PAUSE> -> <UNSTRESSED VOWEL> DX <PAUSE>
|
|||
|
// <UNSTRESSED VOWEL> D <PAUSE> -> <UNSTRESSED VOWEL> DX <PAUSE>
|
|||
|
|
|||
|
|
|||
|
static void rule_alveolar_uw(byte X)
|
|||
|
{
|
|||
|
// ALVEOLAR flag set?
|
|||
|
if ((flags[phonemeindex[X - 1]] & (ushort)Flags.FLAG_ALVEOLAR) != 0)
|
|||
|
phonemeindex[X] = 16;
|
|||
|
}
|
|||
|
|
|||
|
static void rule_ch(byte X)
|
|||
|
{
|
|||
|
Insert((byte)(X + 1), 43, 0, stress[X]);
|
|||
|
}
|
|||
|
|
|||
|
static void rule_j(byte X)
|
|||
|
{
|
|||
|
Insert((byte)(X + 1), 45, 0, stress[X]);
|
|||
|
}
|
|||
|
|
|||
|
static void rule_g(byte pos)
|
|||
|
{
|
|||
|
// G <VOWEL OR DIPTHONG NOT ENDING WITH IY> -> GX <VOWEL OR DIPTHONG NOT ENDING WITH IY>
|
|||
|
// Example: GO
|
|||
|
|
|||
|
byte index = phonemeindex[pos + 1];
|
|||
|
|
|||
|
// If dipthong ending with YX, move continue processing next phoneme
|
|||
|
if ((index != 255) && ((flags[index] & (ushort)Flags.FLAG_DIP_YX) == 0))
|
|||
|
// replace G with GX and continue processing next phoneme
|
|||
|
phonemeindex[pos] = 63; // 'GX'
|
|||
|
}
|
|||
|
|
|||
|
static void change(byte pos, byte val)
|
|||
|
{
|
|||
|
phonemeindex[pos] = val;
|
|||
|
}
|
|||
|
|
|||
|
static void rule_dipthong(byte p, ushort pf, byte pos)
|
|||
|
{
|
|||
|
// <DIPTHONG ENDING WITH WX> -> <DIPTHONG ENDING WITH WX> WX
|
|||
|
// <DIPTHONG NOT ENDING WITH WX> -> <DIPTHONG NOT ENDING WITH WX> YX
|
|||
|
// Example: OIL, COW
|
|||
|
|
|||
|
// If ends with IY, use YX, else use WX
|
|||
|
byte A = ((pf & (ushort)Flags.FLAG_DIP_YX) != 0) ? (byte)21 : (byte)20; // 'WX' = 20 'YX' = 21
|
|||
|
|
|||
|
// Insert at WX or YX following, copying the stress
|
|||
|
Insert((byte)(pos + 1), A, 0, stress[pos]);
|
|||
|
|
|||
|
if (p == 53) rule_alveolar_uw(pos); // Example: NEW, DEW, SUE, ZOO, THOO, TOO
|
|||
|
else if (p == 42) rule_ch(pos); // Example: CHEW
|
|||
|
else if (p == 44) rule_j(pos); // Example: JAY
|
|||
|
}
|
|||
|
|
|||
|
static void Parser2()
|
|||
|
{
|
|||
|
byte pos = 0; //mem66;
|
|||
|
byte p;
|
|||
|
|
|||
|
while ((p = phonemeindex[pos]) != (byte)Handler.END)
|
|||
|
{
|
|||
|
ushort pf;
|
|||
|
byte prior;
|
|||
|
|
|||
|
if (p == 0)
|
|||
|
{ // Is phoneme pause?
|
|||
|
++pos;
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
pf = flags[p];
|
|||
|
prior = phonemeindex[pos - 1];
|
|||
|
|
|||
|
if ((pf & (ushort)Flags.FLAG_DIPTHONG) != 0) rule_dipthong(p, pf, pos);
|
|||
|
else if (p == 78) ChangeRule(pos, 24); // Example: MEDDLE
|
|||
|
else if (p == 79) ChangeRule(pos, 27); // Example: ASTRONOMY
|
|||
|
else if (p == 80) ChangeRule(pos, 28); // Example: FUNCTION
|
|||
|
else if ((pf & (ushort)Flags.FLAG_VOWEL) != 0 && stress[pos] != 0)
|
|||
|
{
|
|||
|
// RULE:
|
|||
|
// <STRESSED VOWEL> <SILENCE> <STRESSED VOWEL> -> <STRESSED VOWEL> <SILENCE> Q <VOWEL>
|
|||
|
// EXAMPLE: AWAY EIGHT
|
|||
|
if (phonemeindex[pos + 1] == 0)
|
|||
|
{ // If following phoneme is a pause, get next
|
|||
|
p = phonemeindex[pos + 2];
|
|||
|
if (p != (byte)Handler.END && (flags[p] & (ushort)Flags.FLAG_VOWEL) != 0 && stress[pos + 2] != 0)
|
|||
|
Insert((byte)(pos + 2), 31, 0, 0); // 31 = 'Q'
|
|||
|
}
|
|||
|
}
|
|||
|
else if (p == (byte)Handler.pR)
|
|||
|
{ // RULES FOR PHONEMES BEFORE R
|
|||
|
if (prior == (byte)Handler.pT) change((byte)(pos - 1), 42); // Example: TRACK
|
|||
|
else if (prior == (byte)Handler.pD) change((byte)(pos - 1), 44); // Example: DRY
|
|||
|
else if ((flags[prior] & (ushort)Flags.FLAG_VOWEL) != 0) change(pos, 18); // Example: ART
|
|||
|
}
|
|||
|
else if (p == 24 && (flags[prior] & (ushort)Flags.FLAG_VOWEL) != 0) change(pos, 19); // Example: ALL
|
|||
|
else if (prior == 60 && p == 32)
|
|||
|
{ // 'G' 'S'
|
|||
|
// Can't get to fire -
|
|||
|
// 1. The G -> GX rule intervenes
|
|||
|
// 2. Reciter already replaces GS -> GZ
|
|||
|
change(pos, 38);
|
|||
|
}
|
|||
|
else if (p == 60) rule_g(pos);
|
|||
|
else
|
|||
|
{
|
|||
|
if (p == 72)
|
|||
|
{ // 'K'
|
|||
|
// K <VOWEL OR DIPTHONG NOT ENDING WITH IY> -> KX <VOWEL OR DIPTHONG NOT ENDING WITH IY>
|
|||
|
// Example: COW
|
|||
|
byte Y = phonemeindex[pos + 1];
|
|||
|
// If at end, replace current phoneme with KX
|
|||
|
if ((flags[Y] & (ushort)Flags.FLAG_DIP_YX) == 0 || Y == (byte)Handler.END)
|
|||
|
{ // VOWELS AND DIPTHONGS ENDING WITH IY SOUND flag set?
|
|||
|
change(pos, 75);
|
|||
|
p = 75;
|
|||
|
pf = flags[p];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Replace with softer version?
|
|||
|
if ((flags[p] & (ushort)Flags.FLAG_PLOSIVE) != 0 && (prior == 32))
|
|||
|
{ // 'S'
|
|||
|
// RULE:
|
|||
|
// S P -> S B
|
|||
|
// S T -> S D
|
|||
|
// S K -> S G
|
|||
|
// S KX -> S GX
|
|||
|
// Examples: SPY, STY, SKY, SCOWL
|
|||
|
|
|||
|
phonemeindex[pos] = (byte)(p - 12);
|
|||
|
}
|
|||
|
else if ((pf & (ushort)Flags.FLAG_PLOSIVE) == 0)
|
|||
|
{
|
|||
|
p = phonemeindex[pos];
|
|||
|
if (p == 53) rule_alveolar_uw(pos); // Example: NEW, DEW, SUE, ZOO, THOO, TOO
|
|||
|
else if (p == 42) rule_ch(pos); // Example: CHEW
|
|||
|
else if (p == 44) rule_j(pos); // Example: JAY
|
|||
|
}
|
|||
|
|
|||
|
if (p == 69 || p == 57)
|
|||
|
{ // 'T', 'D'
|
|||
|
// RULE: Soften T following vowel
|
|||
|
// NOTE: This rule fails for cases such as "ODD"
|
|||
|
// <UNSTRESSED VOWEL> T <PAUSE> -> <UNSTRESSED VOWEL> DX <PAUSE>
|
|||
|
// <UNSTRESSED VOWEL> D <PAUSE> -> <UNSTRESSED VOWEL> DX <PAUSE>
|
|||
|
// Example: PARTY, TARDY
|
|||
|
if (flags[phonemeindex[pos - 1]] != 0 & (ushort)Flags.FLAG_VOWEL != 0)
|
|||
|
{
|
|||
|
p = phonemeindex[pos + 1];
|
|||
|
if (p == 0) p = phonemeindex[pos + 2];
|
|||
|
if ((flags[p] & (ushort)Flags.FLAG_VOWEL) != 0 && stress[pos + 1] == 0) change(pos, 30);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
pos++;
|
|||
|
} // while
|
|||
|
}
|
|||
|
|
|||
|
// Applies various rules that adjust the lengths of phonemes
|
|||
|
//
|
|||
|
// Lengthen <FRICATIVE> or <VOICED> between <VOWEL> and <PUNCTUATION> by 1.5
|
|||
|
// <VOWEL> <RX | LX> <CONSONANT> - decrease <VOWEL> length by 1
|
|||
|
// <VOWEL> <UNVOICED PLOSIVE> - decrease vowel by 1/8th
|
|||
|
// <VOWEL> <UNVOICED CONSONANT> - increase vowel by 1/2 + 1
|
|||
|
// <NASAL> <STOP CONSONANT> - set nasal = 5, consonant = 6
|
|||
|
// <VOICED STOP CONSONANT> {optional silence} <STOP CONSONANT> - shorten both to 1/2 + 1
|
|||
|
// <LIQUID CONSONANT> <DIPTHONG> - decrease by 2
|
|||
|
//
|
|||
|
static void AdjustLengths()
|
|||
|
{
|
|||
|
// LENGTHEN VOWELS PRECEDING PUNCTUATION
|
|||
|
//
|
|||
|
// Search for punctuation. If found, back up to the first vowel, then
|
|||
|
// process all phonemes between there and up to (but not including) the punctuation.
|
|||
|
// If any phoneme is found that is a either a fricative or voiced, the duration is
|
|||
|
// increased by (length * 1.5) + 1
|
|||
|
|
|||
|
// loop index
|
|||
|
{
|
|||
|
byte X = 0;
|
|||
|
byte index1;
|
|||
|
|
|||
|
while ((index1 = phonemeindex[X]) != (byte)Handler.END)
|
|||
|
{
|
|||
|
byte loopIndex1;
|
|||
|
|
|||
|
// not punctuation?
|
|||
|
if ((flags[index1] & (ushort)Flags.FLAG_PUNCT) == 0)
|
|||
|
{
|
|||
|
++X;
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
loopIndex1 = X;
|
|||
|
|
|||
|
while (--X != 0 && (flags[phonemeindex[X]] & (ushort)Flags.FLAG_VOWEL) == 0) ; // back up while not a vowel
|
|||
|
if (X == 0) break;
|
|||
|
|
|||
|
do
|
|||
|
{
|
|||
|
// test for vowel
|
|||
|
index1 = phonemeindex[X];
|
|||
|
|
|||
|
// test for fricative/unvoiced or not voiced
|
|||
|
if ((flags[index1] & (ushort)Flags.FLAG_FRICATIVE) == 0 || (flags[index1] & (ushort)Flags.FLAG_VOICED) != 0)
|
|||
|
{ //nochmal überprüfen
|
|||
|
byte A = phonemeLength[X];
|
|||
|
// change phoneme length to (length * 1.5) + 1
|
|||
|
phonemeLength[X] = (byte)((A >> 1) + A + 1);
|
|||
|
}
|
|||
|
} while (++X != loopIndex1);
|
|||
|
X++;
|
|||
|
} // while
|
|||
|
}
|
|||
|
|
|||
|
// Similar to the above routine, but shorten vowels under some circumstances
|
|||
|
|
|||
|
// Loop through all phonemes
|
|||
|
byte loopIndex = 0;
|
|||
|
byte index;
|
|||
|
|
|||
|
while ((index = phonemeindex[loopIndex]) != (byte)Handler.END)
|
|||
|
{
|
|||
|
byte X = loopIndex;
|
|||
|
|
|||
|
if ((flags[index] & (ushort)Flags.FLAG_VOWEL) != 0)
|
|||
|
{
|
|||
|
index = phonemeindex[loopIndex + 1];
|
|||
|
if ((flags[index] & (ushort)Flags.FLAG_CONSONANT) == 0)
|
|||
|
{
|
|||
|
if ((index == 18) || (index == 19))
|
|||
|
{ // 'RX', 'LX'
|
|||
|
index = phonemeindex[loopIndex + 2];
|
|||
|
if ((flags[index] & (ushort)Flags.FLAG_CONSONANT) != 0)
|
|||
|
phonemeLength[loopIndex]--;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{ // Got here if not <VOWEL>
|
|||
|
ushort flag = (index == (byte)Handler.END) ? (ushort)65 : (ushort)flags[index]; // 65 if end marker
|
|||
|
|
|||
|
if ((flag & (ushort)Flags.FLAG_VOICED) == 0)
|
|||
|
{ // Unvoiced
|
|||
|
// *, .*, ?*, ,*, -*, DX, S*, SH, F*, TH, /H, /X, CH, P*, T*, K*, KX
|
|||
|
if ((flag & (ushort)Flags.FLAG_PLOSIVE) != 0)
|
|||
|
{ // unvoiced plosive
|
|||
|
// RULE: <VOWEL> <UNVOICED PLOSIVE>
|
|||
|
// <VOWEL> <P*, T*, K*, KX>
|
|||
|
phonemeLength[loopIndex] -= (byte)(phonemeLength[loopIndex] >> 3);
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
byte A;
|
|||
|
// decrease length
|
|||
|
A = phonemeLength[loopIndex];
|
|||
|
phonemeLength[loopIndex] = (byte)((A >> 2) + A + 1); // 5/4*A + 1
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else if ((flags[index] & (ushort)Flags.FLAG_NASAL) != 0)
|
|||
|
{ // nasal?
|
|||
|
// RULE: <NASAL> <STOP CONSONANT>
|
|||
|
// Set punctuation length to 6
|
|||
|
// Set stop consonant length to 5
|
|||
|
index = phonemeindex[++X];
|
|||
|
if (index != (byte)Handler.END && (flags[index] & (ushort)Flags.FLAG_STOPCONS) != 0)
|
|||
|
{
|
|||
|
phonemeLength[X] = 6; // set stop consonant length to 6
|
|||
|
phonemeLength[X - 1] = 5; // set nasal length to 5
|
|||
|
}
|
|||
|
}
|
|||
|
else if ((flags[index] & (ushort)Flags.FLAG_STOPCONS) != 0)
|
|||
|
{ // (voiced) stop consonant?
|
|||
|
// RULE: <VOICED STOP CONSONANT> {optional silence} <STOP CONSONANT>
|
|||
|
// Shorten both to (length/2 + 1)
|
|||
|
|
|||
|
// move past silence
|
|||
|
while ((index = phonemeindex[++X]) == 0) ;
|
|||
|
|
|||
|
if (index != (byte)Handler.END && (flags[index] & (ushort)Flags.FLAG_STOPCONS) != 0)
|
|||
|
{
|
|||
|
// FIXME, this looks wrong?
|
|||
|
// RULE: <UNVOICED STOP CONSONANT> {optional silence} <STOP CONSONANT>
|
|||
|
phonemeLength[X] = (byte)((phonemeLength[X] >> 1) + 1);
|
|||
|
phonemeLength[loopIndex] = (byte)((phonemeLength[loopIndex] >> 1) + 1);
|
|||
|
X = loopIndex;
|
|||
|
}
|
|||
|
}
|
|||
|
else if ((flags[index] & (ushort)Flags.FLAG_LIQUIC) != 0)
|
|||
|
{ // liquic consonant?
|
|||
|
// RULE: <VOICED NON-VOWEL> <DIPTHONG>
|
|||
|
// Decrease <DIPTHONG> by 2
|
|||
|
index = phonemeindex[X - 1]; // prior phoneme;
|
|||
|
phonemeLength[X] -= 2; // 20ms
|
|||
|
}
|
|||
|
|
|||
|
++loopIndex;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|