SamSharp/Sam.cs

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