This commit is contained in:
Simon 2019-03-13 08:46:21 +00:00
parent a9ad0525e4
commit caf33d2dc8
5 changed files with 2078 additions and 0 deletions

107
Process.cs Normal file
View file

@ -0,0 +1,107 @@
using static SAMSharp.Sam;
using static SAMSharp.Renderer;
namespace SAMSharp
{
class Process
{
static void CombineGlottalAndFormants(byte phase1, byte phase2, byte phase3, byte Y)
{
byte tmp;
tmp = multtable[sinus[phase1] | amplitude1[Y]];
tmp += multtable[sinus[phase2] | amplitude2[Y]];
tmp += (byte)(tmp > 255 ? 1 : 0); // if addition above overflows, we for some reason add one;
tmp += multtable[rectangle[phase3] | amplitude3[Y]];
tmp += 136;
tmp >>= 4; // Scale down to 0..15 range of C64 audio.
Output(0, (byte)(tmp & 0xf));
}
// PROCESS THE FRAMES
//
// In traditional vocal synthesis, the glottal pulse drives filters, which
// are attenuated to the frequencies of the formants.
//
// SAM generates these formants directly with sin and rectangular waves.
// To simulate them being driven by the glottal pulse, the waveforms are
// reset at the beginning of each glottal pulse.
//
public static void ProcessFrames(byte mem48)
{
byte speedcounter = 72;
byte phase1 = 0;
byte phase2 = 0;
byte phase3 = 0;
byte mem66 = 0; //!! was not initialized
byte Y = 0;
byte glottal_pulse = pitches[0];
byte mem38 = (byte)(glottal_pulse - (glottal_pulse >> 2)); // mem44 * 0.75
while (mem48 != 0)
{
byte flags = sampledConsonantFlag[Y];
// unvoiced sampled phoneme?
if ((flags & 248) != 0)
{
RenderSample(mem66, flags, Y);
// skip ahead two in the phoneme buffer
Y += 2;
mem48 -= 2;
speedcounter = speed;
}
else
{
CombineGlottalAndFormants(phase1, phase2, phase3, Y);
speedcounter--;
if (speedcounter == 0)
{
Y++; //go to next amplitude
// decrement the frame count
mem48--;
if (mem48 == 0) return;
speedcounter = speed;
}
--glottal_pulse;
if (glottal_pulse != 0)
{
// not finished with a glottal pulse
--mem38;
// within the first 75% of the glottal pulse?
// is the count non-zero and the sampled flag is zero?
if ((mem38 != 0) || (flags == 0))
{
// reset the phase of the formants to match the pulse
phase1 += frequency1[Y];
phase2 += frequency2[Y];
phase3 += frequency3[Y];
continue;
}
// voiced sampled phonemes interleave the sample with the
// glottal pulse. The sample flag is non-zero, so render
// the sample for the phoneme.
RenderSample(mem66, flags, Y);
}
}
glottal_pulse = pitches[Y];
mem38 = (byte)(glottal_pulse - (glottal_pulse >> 2)); // mem44 * 0.75
// reset the formant wave generators to keep them in
// sync with the glottal pulse
phase1 = 0;
phase2 = 0;
phase3 = 0;
}
}
}
}

863
Renderer.cs Normal file
View file

@ -0,0 +1,863 @@
using static SAMSharp.Process;
using static SAMSharp.Sam;
using static SAMSharp.Transitions;
namespace SAMSharp
{
class Renderer
{
const int PHONEME_PERIOD = 1;
const int PHONEME_QUESTION = 2;
const int RISING_INFLECTION = 1;
const int FALLING_INFLECTION = 255;
static byte[] tab48426 = { 0x18, 0x1A, 0x17, 0x17, 0x17 };
static byte[] tab47492 =
{
0 , 0 , 0xE0 , 0xE6 , 0xEC , 0xF3 , 0xF9 , 0 ,
6 , 0xC , 6
};
static byte[] amplitudeRescale =
{
0 , 1 , 2 , 2 , 2 , 3 , 3 , 4 ,
4 , 5 , 6 , 8 , 9 ,0xB ,0xD ,0xF, 0 //17 elements?
};
// Used to decide which phoneme's blend lengths. The candidate with the lower score is selected.
// tab45856
public static byte[] blendRank =
{
0 , 0x1F , 0x1F , 0x1F , 0x1F , 2 , 2 , 2 ,
2 , 2 , 2 , 2 , 2 , 2 , 5 , 5 ,
2 ,0xA , 2 , 8 , 5 , 5 ,0xB ,0xA ,
9 , 8 , 8 , 0xA0 , 8 , 8 , 0x17 , 0x1F ,
0x12 , 0x12 , 0x12 , 0x12 , 0x1E , 0x1E , 0x14 , 0x14 ,
0x14 , 0x14 , 0x17 , 0x17 , 0x1A , 0x1A , 0x1D , 0x1D ,
2 , 2 , 2 , 2 , 2 , 2 , 0x1A , 0x1D ,
0x1B , 0x1A , 0x1D , 0x1B , 0x1A , 0x1D , 0x1B , 0x1A ,
0x1D , 0x1B , 0x17 , 0x1D , 0x17 , 0x17 , 0x1D , 0x17 ,
0x17 , 0x1D , 0x17 , 0x17 , 0x1D , 0x17 , 0x17 , 0x17
};
// Number of frames at the end of a phoneme devoted to interpolating to next phoneme's final value
//tab45696
public static byte[] outBlendLength =
{
0 , 2 , 2 , 2 , 2 , 4 , 4 , 4 ,
4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 ,
4 , 4 , 3 , 2 , 4 , 4 , 2 , 2 ,
2 , 2 , 2 , 1 , 1 , 1 , 1 , 1 ,
1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 ,
2 , 1 , 0 , 1 , 0 , 1 , 0 , 5 ,
5 , 5 , 5 , 5 , 4 , 4 , 2 , 0 ,
1 , 2 , 0 , 1 , 2 , 0 , 1 , 2 ,
0 , 1 , 2 , 0 , 2 , 2 , 0 , 1 ,
3 , 0 , 2 , 3 , 0 , 2 , 0xA0 , 0xA0
};
// Number of frames at beginning of a phoneme devoted to interpolating to phoneme's final value
// tab45776
public static byte[] inBlendLength =
{
0 , 2 , 2 , 2 , 2 , 4 , 4 , 4 ,
4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 ,
4 , 4 , 3 , 3 , 4 , 4 , 3 , 3 ,
3 , 3 , 3 , 1 , 2 , 3 , 2 , 1 ,
3 , 3 , 3 , 3 , 1 , 1 , 3 , 3 ,
3 , 2 , 2 , 3 , 2 , 3 , 0 , 0 ,
5 , 5 , 5 , 5 , 4 , 4 , 2 , 0 ,
2 , 2 , 0 , 3 , 2 , 0 , 4 , 2 ,
0 , 3 , 2 , 0 , 2 , 2 , 0 , 2 ,
3 , 0 , 3 , 3 , 0 , 3 , 0xB0 , 0xA0
};
// Looks like it's used as bit flags
// High bits masked by 248 (11111000)
//
// 32: S* 241 11110001
// 33: SH 226 11100010
// 34: F* 211 11010011
// 35: TH 187 10111011
// 36: /H 124 01111100
// 37: /X 149 10010101
// 38: Z* 1 00000001
// 39: ZH 2 00000010
// 40: V* 3 00000011
// 41: DH 3 00000011
// 43: ** 114 01110010
// 45: ** 2 00000010
// 67: ** 27 00011011
// 70: ** 25 00011001
// tab45936
public static byte[] sampledConsonantFlags =
{
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0xF1 , 0xE2 , 0xD3 , 0xBB , 0x7C , 0x95 , 1 , 2 ,
3 , 3 , 0 , 0x72 , 0 , 2 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0x1B , 0 , 0 , 0x19 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0
};
//tab45056
static byte[] freq1data =
{
0x00 ,0x13 ,0x13 ,0x13 ,0x13 , 0xA , 0xE ,0x12
, 0x18 ,0x1A ,0x16 ,0x14 ,0x10 ,0x14 , 0xE ,0x12
, 0xE ,0x12 ,0x12 ,0x10 , 0xC , 0xE , 0xA ,0x12
, 0xE ,0xA , 8 , 6 , 6 , 6 , 6 ,0x11
, 6 , 6 , 6 , 6 ,0xE , 0x10 , 9 ,0xA
, 8 ,0xA , 6 , 6 , 6 , 5 , 6 , 0
, 0x12 , 0x1A , 0x14 , 0x1A , 0x12 ,0xC , 6 , 6
, 6 , 6 , 6 , 6 , 6 , 6 , 6 , 6
, 6 , 6 , 6 , 6 , 6 , 6 , 6 , 6
, 6 ,0xA ,0xA , 6 , 6 , 6 , 0x2C , 0x13
};
//tab451356
static byte[] freq2data =
{
0x00 , 0x43 , 0x43 , 0x43 , 0x43 , 0x54 , 0x48 , 0x42 ,
0x3E , 0x28 , 0x2C , 0x1E , 0x24 , 0x2C , 0x48 , 0x30 ,
0x24 , 0x1E , 0x32 , 0x24 , 0x1C , 0x44 , 0x18 , 0x32 ,
0x1E , 0x18 , 0x52 , 0x2E , 0x36 , 0x56 , 0x36 , 0x43 ,
0x49 , 0x4F , 0x1A , 0x42 , 0x49 , 0x25 , 0x33 , 0x42 ,
0x28 , 0x2F , 0x4F , 0x4F , 0x42 , 0x4F , 0x6E , 0x00 ,
0x48 , 0x26 , 0x1E , 0x2A , 0x1E , 0x22 , 0x1A , 0x1A ,
0x1A , 0x42 , 0x42 , 0x42 , 0x6E , 0x6E , 0x6E , 0x54 ,
0x54 , 0x54 , 0x1A , 0x1A , 0x1A , 0x42 , 0x42 , 0x42 ,
0x6D , 0x56 , 0x6D , 0x54 , 0x54 , 0x54 , 0x7F , 0x7F
};
//tab45216
static byte[] freq3data =
{
0x00 , 0x5B , 0x5B , 0x5B , 0x5B , 0x6E , 0x5D , 0x5B ,
0x58 , 0x59 , 0x57 , 0x58 , 0x52 , 0x59 , 0x5D , 0x3E ,
0x52 , 0x58 , 0x3E , 0x6E , 0x50 , 0x5D , 0x5A , 0x3C ,
0x6E , 0x5A , 0x6E , 0x51 , 0x79 , 0x65 , 0x79 , 0x5B ,
0x63 , 0x6A , 0x51 , 0x79 , 0x5D , 0x52 , 0x5D , 0x67 ,
0x4C , 0x5D , 0x65 , 0x65 , 0x79 , 0x65 , 0x79 , 0x00 ,
0x5A , 0x58 , 0x58 , 0x58 , 0x58 , 0x52 , 0x51 , 0x51 ,
0x51 , 0x79 , 0x79 , 0x79 , 0x70 , 0x6E , 0x6E , 0x5E ,
0x5E , 0x5E , 0x51 , 0x51 , 0x51 , 0x79 , 0x79 , 0x79 ,
0x65 , 0x65 , 0x70 , 0x5E , 0x5E , 0x5E , 0x08 , 0x01
};
static byte[] ampl1data =
{
0 , 0 , 0 , 0 , 0 ,0xD ,0xD ,0xE ,
0xF ,0xF ,0xF ,0xF ,0xF ,0xC ,0xD ,0xC ,
0xF ,0xF ,0xD ,0xD ,0xD ,0xE ,0xD ,0xC ,
0xD ,0xD ,0xD ,0xC , 9 , 9 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 ,0xB ,0xB ,
0xB ,0xB , 0 , 0 , 1 ,0xB , 0 , 2 ,
0xE ,0xF ,0xF ,0xF ,0xF ,0xD , 2 , 4 ,
0 , 2 , 4 , 0 , 1 , 4 , 0 , 1 ,
4 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 ,0xC , 0 , 0 , 0 , 0 ,0xF ,0xF
};
static byte[] ampl2data =
{
0 , 0 , 0 , 0 , 0 ,0xA ,0xB ,0xD ,
0xE ,0xD ,0xC ,0xC ,0xB , 9 ,0xB ,0xB ,
0xC ,0xC ,0xC , 8 , 8 ,0xC , 8 ,0xA ,
8 , 8 ,0xA , 3 , 9 , 6 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 3 , 5 ,
3 , 4 , 0 , 0 , 0 , 5 ,0xA , 2 ,
0xE ,0xD ,0xC ,0xD ,0xC , 8 , 0 , 1 ,
0 , 0 , 1 , 0 , 0 , 1 , 0 , 0 ,
1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 ,0xA , 0 , 0 ,0xA , 0 , 0 , 0
};
static byte[] ampl3data =
{
0 , 0 , 0 , 0 , 0 , 8 , 7 , 8 ,
8 , 1 , 1 , 0 , 1 , 0 , 7 , 5 ,
1 , 0 , 6 , 1 , 0 , 7 , 0 , 5 ,
1 , 0 , 8 , 0 , 0 , 3 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 ,
0 , 0 , 0 , 0 , 0 , 1 ,0xE , 1 ,
9 , 1 , 0 , 1 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
0 , 7 , 0 , 0 , 5 , 0 , 0x13 , 0x10
};
//tab42240
public static byte[] sinus =
{
0x00 , 0x00 , 0x00 , 0x10 , 0x10 , 0x10 , 0x10 , 0x10 ,
0x10 , 0x20 , 0x20 , 0x20 , 0x20 , 0x20 , 0x20 , 0x30 ,
0x30 , 0x30 , 0x30 , 0x30 , 0x30 , 0x30 , 0x40 , 0x40 ,
0x40 , 0x40 , 0x40 , 0x40 , 0x40 , 0x50 , 0x50 , 0x50 ,
0x50 , 0x50 , 0x50 , 0x50 , 0x50 , 0x60 , 0x60 , 0x60 ,
0x60 , 0x60 , 0x60 , 0x60 , 0x60 , 0x60 , 0x60 , 0x60 ,
0x60 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x60 , 0x60 , 0x60 , 0x60 , 0x60 , 0x60 , 0x60 , 0x60 ,
0x60 , 0x60 , 0x60 , 0x60 , 0x50 , 0x50 , 0x50 , 0x50 ,
0x50 , 0x50 , 0x50 , 0x50 , 0x40 , 0x40 , 0x40 , 0x40 ,
0x40 , 0x40 , 0x40 , 0x30 , 0x30 , 0x30 , 0x30 , 0x30 ,
0x30 , 0x30 , 0x20 , 0x20 , 0x20 , 0x20 , 0x20 , 0x20 ,
0x10 , 0x10 , 0x10 , 0x10 , 0x10 , 0x10 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x00 , 0xF0 , 0xF0 , 0xF0 , 0xF0 , 0xF0 ,
0xF0 , 0xE0 , 0xE0 , 0xE0 , 0xE0 , 0xE0 , 0xE0 , 0xD0 ,
0xD0 , 0xD0 , 0xD0 , 0xD0 , 0xD0 , 0xD0 , 0xC0 , 0xC0 ,
0xC0 , 0xC0 , 0xC0 , 0xC0 , 0xC0 , 0xB0 , 0xB0 , 0xB0 ,
0xB0 , 0xB0 , 0xB0 , 0xB0 , 0xB0 , 0xA0 , 0xA0 , 0xA0 ,
0xA0 , 0xA0 , 0xA0 , 0xA0 , 0xA0 , 0xA0 , 0xA0 , 0xA0 ,
0xA0 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0xA0 , 0xA0 , 0xA0 , 0xA0 , 0xA0 , 0xA0 , 0xA0 , 0xA0 ,
0xA0 , 0xA0 , 0xA0 , 0xA0 , 0xB0 , 0xB0 , 0xB0 , 0xB0 ,
0xB0 , 0xB0 , 0xB0 , 0xB0 , 0xC0 , 0xC0 , 0xC0 , 0xC0 ,
0xC0 , 0xC0 , 0xC0 , 0xD0 , 0xD0 , 0xD0 , 0xD0 , 0xD0 ,
0xD0 , 0xD0 , 0xE0 , 0xE0 , 0xE0 , 0xE0 , 0xE0 , 0xE0 ,
0xF0 , 0xF0 , 0xF0 , 0xF0 , 0xF0 , 0xF0 , 0x00 , 0x00
};
//tab42496
public static byte[] rectangle =
{
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 ,
0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70 , 0x70
};
//tab42752
public static byte[] multtable =
{
0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x01 , 0x01 , 0x02 , 0x02 , 0x03 , 0x03 ,
0x04 , 0x04 , 0x05 , 0x05 , 0x06 , 0x06 , 0x07 , 0x07 ,
0x00 , 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 ,
0x08 , 0x09 , 0x0A , 0x0B , 0x0C , 0x0D , 0x0E , 0x0F ,
0x00 , 0x01 , 0x03 , 0x04 , 0x06 , 0x07 , 0x09 , 0x0A ,
0x0C , 0x0D , 0x0F , 0x10 , 0x12 , 0x13 , 0x15 , 0x16 ,
0x00 , 0x02 , 0x04 , 0x06 , 0x08 , 0x0A , 0x0C , 0x0E ,
0x10 , 0x12 , 0x14 , 0x16 , 0x18 , 0x1A , 0x1C , 0x1E ,
0x00 , 0x02 , 0x05 , 0x07 , 0x0A , 0x0C , 0x0F , 0x11 ,
0x14 , 0x16 , 0x19 , 0x1B , 0x1E , 0x20 , 0x23 , 0x25 ,
0x00 , 0x03 , 0x06 , 0x09 , 0x0C , 0x0F , 0x12 , 0x15 ,
0x18 , 0x1B , 0x1E , 0x21 , 0x24 , 0x27 , 0x2A , 0x2D ,
0x00 , 0x03 , 0x07 , 0x0A , 0x0E , 0x11 , 0x15 , 0x18 ,
0x1C , 0x1F , 0x23 , 0x26 , 0x2A , 0x2D , 0x31 , 0x34 ,
0x00 , 0xFC , 0xF8 , 0xF4 , 0xF0 , 0xEC , 0xE8 , 0xE4 ,
0xE0 , 0xDC , 0xD8 , 0xD4 , 0xD0 , 0xCC , 0xC8 , 0xC4 ,
0x00 , 0xFC , 0xF9 , 0xF5 , 0xF2 , 0xEE , 0xEB , 0xE7 ,
0xE4 , 0xE0 , 0xDD , 0xD9 , 0xD6 , 0xD2 , 0xCF , 0xCB ,
0x00 , 0xFD , 0xFA , 0xF7 , 0xF4 , 0xF1 , 0xEE , 0xEB ,
0xE8 , 0xE5 , 0xE2 , 0xDF , 0xDC , 0xD9 , 0xD6 , 0xD3 ,
0x00 , 0xFD , 0xFB , 0xF8 , 0xF6 , 0xF3 , 0xF1 , 0xEE ,
0xEC , 0xE9 , 0xE7 , 0xE4 , 0xE2 , 0xDF , 0xDD , 0xDA ,
0x00 , 0xFE , 0xFC , 0xFA , 0xF8 , 0xF6 , 0xF4 , 0xF2 ,
0xF0 , 0xEE , 0xEC , 0xEA , 0xE8 , 0xE6 , 0xE4 , 0xE2 ,
0x00 , 0xFE , 0xFD , 0xFB , 0xFA , 0xF8 , 0xF7 , 0xF5 ,
0xF4 , 0xF2 , 0xF1 , 0xEF , 0xEE , 0xEC , 0xEB , 0xE9 ,
0x00 , 0xFF , 0xFE , 0xFD , 0xFC , 0xFB , 0xFA , 0xF9 ,
0xF8 , 0xF7 , 0xF6 , 0xF5 , 0xF4 , 0xF3 , 0xF2 , 0xF1 ,
0x00 , 0xFF , 0xFF , 0xFE , 0xFE , 0xFD , 0xFD , 0xFC ,
0xFC , 0xFB , 0xFB , 0xFA , 0xFA , 0xF9 , 0xF9 , 0xF8
};
//random data ?
static byte[] sampleTable =
{
//00
0x38 , 0x84 , 0x6B , 0x19 , 0xC6 , 0x63 , 0x18 , 0x86
, 0x73 , 0x98 , 0xC6 , 0xB1 , 0x1C , 0xCA , 0x31 , 0x8C
, 0xC7 , 0x31 , 0x88 , 0xC2 , 0x30 , 0x98 , 0x46 , 0x31
, 0x18 , 0xC6 , 0x35 ,0xC , 0xCA , 0x31 ,0xC , 0xC6
//20
, 0x21 , 0x10 , 0x24 , 0x69 , 0x12 , 0xC2 , 0x31 , 0x14
, 0xC4 , 0x71 , 8 , 0x4A , 0x22 , 0x49 , 0xAB , 0x6A
, 0xA8 , 0xAC , 0x49 , 0x51 , 0x32 , 0xD5 , 0x52 , 0x88
, 0x93 , 0x6C , 0x94 , 0x22 , 0x15 , 0x54 , 0xD2 , 0x25
//40
, 0x96 , 0xD4 , 0x50 , 0xA5 , 0x46 , 0x21 , 8 , 0x85
, 0x6B , 0x18 , 0xC4 , 0x63 , 0x10 , 0xCE , 0x6B , 0x18
, 0x8C , 0x71 , 0x19 , 0x8C , 0x63 , 0x35 ,0xC , 0xC6
, 0x33 , 0x99 , 0xCC , 0x6C , 0xB5 , 0x4E , 0xA2 , 0x99
//60
, 0x46 , 0x21 , 0x28 , 0x82 , 0x95 , 0x2E , 0xE3 , 0x30
, 0x9C , 0xC5 , 0x30 , 0x9C , 0xA2 , 0xB1 , 0x9C , 0x67
, 0x31 , 0x88 , 0x66 , 0x59 , 0x2C , 0x53 , 0x18 , 0x84
, 0x67 , 0x50 , 0xCA , 0xE3 ,0xA , 0xAC , 0xAB , 0x30
//80
, 0xAC , 0x62 , 0x30 , 0x8C , 0x63 , 0x10 , 0x94 , 0x62
, 0xB1 , 0x8C , 0x82 , 0x28 , 0x96 , 0x33 , 0x98 , 0xD6
, 0xB5 , 0x4C , 0x62 , 0x29 , 0xA5 , 0x4A , 0xB5 , 0x9C
, 0xC6 , 0x31 , 0x14 , 0xD6 , 0x38 , 0x9C , 0x4B , 0xB4
//A0
, 0x86 , 0x65 , 0x18 , 0xAE , 0x67 , 0x1C , 0xA6 , 0x63
, 0x19 , 0x96 , 0x23 , 0x19 , 0x84 , 0x13 , 8 , 0xA6
, 0x52 , 0xAC , 0xCA , 0x22 , 0x89 , 0x6E , 0xAB , 0x19
, 0x8C , 0x62 , 0x34 , 0xC4 , 0x62 , 0x19 , 0x86 , 0x63
//C0
, 0x18 , 0xC4 , 0x23 , 0x58 , 0xD6 , 0xA3 , 0x50 , 0x42
, 0x54 , 0x4A , 0xAD , 0x4A , 0x25 , 0x11 , 0x6B , 0x64
, 0x89 , 0x4A , 0x63 , 0x39 , 0x8A , 0x23 , 0x31 , 0x2A
, 0xEA , 0xA2 , 0xA9 , 0x44 , 0xC5 , 0x12 , 0xCD , 0x42
//E0
, 0x34 , 0x8C , 0x62 , 0x18 , 0x8C , 0x63 , 0x11 , 0x48
, 0x66 , 0x31 , 0x9D , 0x44 , 0x33 , 0x1D , 0x46 , 0x31
, 0x9C , 0xC6 , 0xB1 ,0xC , 0xCD , 0x32 , 0x88 , 0xC4
, 0x73 , 0x18 , 0x86 , 0x73 , 8 , 0xD6 , 0x63 , 0x58
//100
, 7 , 0x81 , 0xE0 , 0xF0 , 0x3C , 7 , 0x87 , 0x90
, 0x3C , 0x7C ,0xF , 0xC7 , 0xC0 , 0xC0 , 0xF0 , 0x7C
, 0x1E , 7 , 0x80 , 0x80 , 0 , 0x1C , 0x78 , 0x70
, 0xF1 , 0xC7 , 0x1F , 0xC0 ,0xC , 0xFE , 0x1C , 0x1F
//120
, 0x1F ,0xE ,0xA , 0x7A , 0xC0 , 0x71 , 0xF2 , 0x83
, 0x8F , 3 ,0xF ,0xF ,0xC , 0 , 0x79 , 0xF8
, 0x61 , 0xE0 , 0x43 ,0xF , 0x83 , 0xE7 , 0x18 , 0xF9
, 0xC1 , 0x13 , 0xDA , 0xE9 , 0x63 , 0x8F ,0xF , 0x83
//140
, 0x83 , 0x87 , 0xC3 , 0x1F , 0x3C , 0x70 , 0xF0 , 0xE1
, 0xE1 , 0xE3 , 0x87 , 0xB8 , 0x71 ,0xE , 0x20 , 0xE3
, 0x8D , 0x48 , 0x78 , 0x1C , 0x93 , 0x87 , 0x30 , 0xE1
, 0xC1 , 0xC1 , 0xE4 , 0x78 , 0x21 , 0x83 , 0x83 , 0xC3
//160
, 0x87 , 6 , 0x39 , 0xE5 , 0xC3 , 0x87 , 7 ,0xE
, 0x1C , 0x1C , 0x70 , 0xF4 , 0x71 , 0x9C , 0x60 , 0x36
, 0x32 , 0xC3 , 0x1E , 0x3C , 0xF3 , 0x8F ,0xE , 0x3C
, 0x70 , 0xE3 , 0xC7 , 0x8F ,0xF ,0xF ,0xE , 0x3C
//180
, 0x78 , 0xF0 , 0xE3 , 0x87 , 6 , 0xF0 , 0xE3 , 7
, 0xC1 , 0x99 , 0x87 ,0xF , 0x18 , 0x78 , 0x70 , 0x70
, 0xFC , 0xF3 , 0x10 , 0xB1 , 0x8C , 0x8C , 0x31 , 0x7C
, 0x70 , 0xE1 , 0x86 , 0x3C , 0x64 , 0x6C , 0xB0 , 0xE1
//1A0
, 0xE3 ,0xF , 0x23 , 0x8F ,0xF , 0x1E , 0x3E , 0x38
, 0x3C , 0x38 , 0x7B , 0x8F , 7 ,0xE , 0x3C , 0xF4
, 0x17 , 0x1E , 0x3C , 0x78 , 0xF2 , 0x9E , 0x72 , 0x49
, 0xE3 , 0x25 , 0x36 , 0x38 , 0x58 , 0x39 , 0xE2 , 0xDE
//1C0
, 0x3C , 0x78 , 0x78 , 0xE1 , 0xC7 , 0x61 , 0xE1 , 0xE1
, 0xB0 , 0xF0 , 0xF0 , 0xC3 , 0xC7 ,0xE , 0x38 , 0xC0
, 0xF0 , 0xCE , 0x73 , 0x73 , 0x18 , 0x34 , 0xB0 , 0xE1
, 0xC7 , 0x8E , 0x1C , 0x3C , 0xF8 , 0x38 , 0xF0 , 0xE1
//1E0
, 0xC1 , 0x8B , 0x86 , 0x8F , 0x1C , 0x78 , 0x70 , 0xF0
, 0x78 , 0xAC , 0xB1 , 0x8F , 0x39 , 0x31 , 0xDB , 0x38
, 0x61 , 0xC3 ,0xE ,0xE , 0x38 , 0x78 , 0x73 , 0x17
, 0x1E , 0x39 , 0x1E , 0x38 , 0x64 , 0xE1 , 0xF1 , 0xC1
//200
, 0x4E ,0xF , 0x40 , 0xA2 , 2 , 0xC5 , 0x8F , 0x81
, 0xA1 , 0xFC , 0x12 , 8 , 0x64 , 0xE0 , 0x3C , 0x22
, 0xE0 , 0x45 , 7 , 0x8E ,0xC , 0x32 , 0x90 , 0xF0
, 0x1F , 0x20 , 0x49 , 0xE0 , 0xF8 ,0xC , 0x60 , 0xF0
//220
, 0x17 , 0x1A , 0x41 , 0xAA , 0xA4 , 0xD0 , 0x8D , 0x12
, 0x82 , 0x1E , 0x1E , 3 , 0xF8 , 0x3E , 3 ,0xC
, 0x73 , 0x80 , 0x70 , 0x44 , 0x26 , 3 , 0x24 , 0xE1
, 0x3E , 4 , 0x4E , 4 , 0x1C , 0xC1 , 9 , 0xCC
//240
, 0x9E , 0x90 , 0x21 , 7 , 0x90 , 0x43 , 0x64 , 0xC0
, 0xF , 0xC6 , 0x90 , 0x9C , 0xC1 , 0x5B , 3 , 0xE2
, 0x1D , 0x81 , 0xE0 , 0x5E , 0x1D , 3 , 0x84 , 0xB8
, 0x2C ,0xF , 0x80 , 0xB1 , 0x83 , 0xE0 , 0x30 , 0x41
//260
, 0x1E , 0x43 , 0x89 , 0x83 , 0x50 , 0xFC , 0x24 , 0x2E
, 0x13 , 0x83 , 0xF1 , 0x7C , 0x4C , 0x2C , 0xC9 ,0xD
, 0x83 , 0xB0 , 0xB5 , 0x82 , 0xE4 , 0xE8 , 6 , 0x9C
, 7 , 0xA0 , 0x99 , 0x1D , 7 , 0x3E , 0x82 , 0x8F
//280
, 0x70 , 0x30 , 0x74 , 0x40 , 0xCA , 0x10 , 0xE4 , 0xE8
, 0xF , 0x92 , 0x14 , 0x3F , 6 , 0xF8 , 0x84 , 0x88
, 0x43 , 0x81 ,0xA , 0x34 , 0x39 , 0x41 , 0xC6 , 0xE3
, 0x1C , 0x47 , 3 , 0xB0 , 0xB8 , 0x13 ,0xA , 0xC2
//2A0
, 0x64 , 0xF8 , 0x18 , 0xF9 , 0x60 , 0xB3 , 0xC0 , 0x65
, 0x20 , 0x60 , 0xA6 , 0x8C , 0xC3 , 0x81 , 0x20 , 0x30
, 0x26 , 0x1E , 0x1C , 0x38 , 0xD3 , 1 , 0xB0 , 0x26
, 0x40 , 0xF4 ,0xB , 0xC3 , 0x42 , 0x1F , 0x85 , 0x32
//2C0
, 0x26 , 0x60 , 0x40 , 0xC9 , 0xCB , 1 , 0xEC , 0x11
, 0x28 , 0x40 , 0xFA , 4 , 0x34 , 0xE0 , 0x70 , 0x4C
, 0x8C , 0x1D , 7 , 0x69 , 3 , 0x16 , 0xC8 , 4
, 0x23 , 0xE8 , 0xC6 , 0x9A ,0xB , 0x1A , 3 , 0xE0
//2E0
, 0x76 , 6 , 5 , 0xCF , 0x1E , 0xBC , 0x58 , 0x31
, 0x71 , 0x66 , 0 , 0xF8 , 0x3F , 4 , 0xFC ,0xC
, 0x74 , 0x27 , 0x8A , 0x80 , 0x71 , 0xC2 , 0x3A , 0x26
, 6 , 0xC0 , 0x1F , 5 ,0xF , 0x98 , 0x40 , 0xAE
//300
, 1 , 0x7F , 0xC0 , 7 , 0xFF , 0 ,0xE , 0xFE
, 0 , 3 , 0xDF , 0x80 , 3 , 0xEF , 0x80 , 0x1B
, 0xF1 , 0xC2 , 0 , 0xE7 , 0xE0 , 0x18 , 0xFC , 0xE0
, 0x21 , 0xFC , 0x80 , 0x3C , 0xFC , 0x40 ,0xE , 0x7E
//320
, 0 , 0x3F , 0x3E , 0 ,0xF , 0xFE , 0 , 0x1F
, 0xFF , 0 , 0x3E , 0xF0 , 7 , 0xFC , 0 , 0x7E
, 0x10 , 0x3F , 0xFF , 0 , 0x3F , 0x38 ,0xE , 0x7C
, 1 , 0x87 ,0xC , 0xFC , 0xC7 , 0 , 0x3E , 4
//340
, 0xF , 0x3E , 0x1F ,0xF ,0xF , 0x1F ,0xF , 2
, 0x83 , 0x87 , 0xCF , 3 , 0x87 ,0xF , 0x3F , 0xC0
, 7 , 0x9E , 0x60 , 0x3F , 0xC0 , 3 , 0xFE , 0
, 0x3F , 0xE0 , 0x77 , 0xE1 , 0xC0 , 0xFE , 0xE0 , 0xC3
//360
, 0xE0 , 1 , 0xDF , 0xF8 , 3 , 7 , 0 , 0x7E
, 0x70 , 0 , 0x7C , 0x38 , 0x18 , 0xFE ,0xC , 0x1E
, 0x78 , 0x1C , 0x7C , 0x3E ,0xE , 0x1F , 0x1E , 0x1E
, 0x3E , 0 , 0x7F , 0x83 , 7 , 0xDB , 0x87 , 0x83
//380
, 7 , 0xC7 , 7 , 0x10 , 0x71 , 0xFF , 0 , 0x3F
, 0xE2 , 1 , 0xE0 , 0xC1 , 0xC3 , 0xE1 , 0 , 0x7F
, 0xC0 , 5 , 0xF0 , 0x20 , 0xF8 , 0xF0 , 0x70 , 0xFE
, 0x78 , 0x79 , 0xF8 , 2 , 0x3F ,0xC , 0x8F , 3
//3a0
, 0xF , 0x9F , 0xE0 , 0xC1 , 0xC7 , 0x87 , 3 , 0xC3
, 0xC3 , 0xB0 , 0xE1 , 0xE1 , 0xC1 , 0xE3 , 0xE0 , 0x71
, 0xF0 , 0 , 0xFC , 0x70 , 0x7C ,0xC , 0x3E , 0x38
, 0xE , 0x1C , 0x70 , 0xC3 , 0xC7 , 3 , 0x81 , 0xC1
//3c0
, 0xC7 , 0xE7 , 0 ,0xF , 0xC7 , 0x87 , 0x19 , 9
, 0xEF , 0xC4 , 0x33 , 0xE0 , 0xC1 , 0xFC , 0xF8 , 0x70
, 0xF0 , 0x78 , 0xF8 , 0xF0 , 0x61 , 0xC7 , 0 , 0x1F
, 0xF8 , 1 , 0x7C , 0xF8 , 0xF0 , 0x78 , 0x70 , 0x3C
//3e0
, 0x7C , 0xCE ,0xE , 0x21 , 0x83 , 0xCF , 8 , 7
, 0x8F , 8 , 0xC1 , 0x87 , 0x8F , 0x80 , 0xC7 , 0xE3
, 0 , 7 , 0xF8 , 0xE0 , 0xEF , 0 , 0x39 , 0xF7
, 0x80 ,0xE , 0xF8 , 0xE1 , 0xE3 , 0xF8 , 0x21 , 0x9F
//400
, 0xC0 , 0xFF , 3 , 0xF8 , 7 , 0xC0 , 0x1F , 0xF8
, 0xC4 , 4 , 0xFC , 0xC4 , 0xC1 , 0xBC , 0x87 , 0xF0
, 0xF , 0xC0 , 0x7F , 5 , 0xE0 , 0x25 , 0xEC , 0xC0
, 0x3E , 0x84 , 0x47 , 0xF0 , 0x8E , 3 , 0xF8 , 3
//420
, 0xFB , 0xC0 , 0x19 , 0xF8 , 7 , 0x9C ,0xC , 0x17
, 0xF8 , 7 , 0xE0 , 0x1F , 0xA1 , 0xFC ,0xF , 0xFC
, 1 , 0xF0 , 0x3F , 0 , 0xFE , 3 , 0xF0 , 0x1F
, 0 , 0xFD , 0 , 0xFF , 0x88 ,0xD , 0xF9 , 1
//440
, 0xFF , 0 , 0x70 , 7 , 0xC0 , 0x3E , 0x42 , 0xF3
, 0xD , 0xC4 , 0x7F , 0x80 , 0xFC , 7 , 0xF0 , 0x5E
, 0xC0 , 0x3F , 0 , 0x78 , 0x3F , 0x81 , 0xFF , 1
, 0xF8 , 1 , 0xC3 , 0xE8 ,0xC , 0xE4 , 0x64 , 0x8F
////460
, 0xE4 ,0xF , 0xF0 , 7 , 0xF0 , 0xC2 , 0x1F , 0
, 0x7F , 0xC0 , 0x6F , 0x80 , 0x7E , 3 , 0xF8 , 7
, 0xF0 , 0x3F , 0xC0 , 0x78 ,0xF , 0x82 , 7 , 0xFE
, 0x22 , 0x77 , 0x70 , 2 , 0x76 , 3 , 0xFE , 0
//480
, 0xFE , 0x67 , 0 , 0x7C , 0xC7 , 0xF1 , 0x8E , 0xC6
, 0x3B , 0xE0 , 0x3F , 0x84 , 0xF3 , 0x19 , 0xD8 , 3
, 0x99 , 0xFC , 9 , 0xB8 ,0xF , 0xF8 , 0 , 0x9D
, 0x24 , 0x61 , 0xF9 ,0xD , 0 , 0xFD , 3 , 0xF0
//4a0
, 0x1F , 0x90 , 0x3F , 1 , 0xF8 , 0x1F , 0xD0 ,0xF
, 0xF8 , 0x37 , 1 , 0xF8 , 7 , 0xF0 ,0xF , 0xC0
, 0x3F , 0 , 0xFE , 3 , 0xF8 ,0xF , 0xC0 , 0x3F
, 0 , 0xFA , 3 , 0xF0 ,0xF , 0x80 , 0xFF , 1
//4c0
, 0xB8 , 7 , 0xF0 , 1 , 0xFC , 1 , 0xBC , 0x80
, 0x13 , 0x1E , 0 , 0x7F , 0xE1 , 0x40 , 0x7F , 0xA0
, 0x7F , 0xB0 , 0 , 0x3F , 0xC0 , 0x1F , 0xC0 , 0x38
, 0xF , 0xF0 , 0x1F , 0x80 , 0xFF , 1 , 0xFC , 3
//4e0
, 0xF1 , 0x7E , 1 , 0xFE , 1 , 0xF0 , 0xFF , 0
, 0x7F , 0xC0 , 0x1D , 7 , 0xF0 ,0xF , 0xC0 , 0x7E
, 6 , 0xE0 , 7 , 0xE0 ,0xF , 0xF8 , 6 , 0xC1
, 0xFE , 1 , 0xFC , 3 , 0xE0 ,0xF , 0 , 0xFC
};
public static byte[] pitches = new byte[256]; // tab43008
public static byte[] frequency1 = new byte[256];
public static byte[] frequency2 = new byte[256];
public static byte[] frequency3 = new byte[256];
public static byte[] amplitude1 = new byte[256];
public static byte[] amplitude2 = new byte[256];
public static byte[] amplitude3 = new byte[256];
public static byte[] sampledConsonantFlag = new byte[256]; // tab44800
//return = hibyte(mem39212*mem39213) << 1
static byte trans(byte a, byte b) => (byte)((((uint)a * b) >> 8) << 1);
//timetable for more accurate c64 simulation
static readonly int[,] timetable =
{
{162, 167, 167, 127, 128},
{226, 60, 60, 0, 0},
{225, 60, 59, 0, 0},
{200, 0, 0, 54, 55},
{199, 0, 0, 54, 54}
};
public static void Output(int index, byte A)
{
uint oldtimetableindex = 0;
int k;
bufferpos += timetable[oldtimetableindex,index];
oldtimetableindex = (uint)index;
// write a little bit in advance
for (k = 0; k < 5; k++)
buffer[bufferpos / 50 + k] = (byte)((A & 15) * 16);
}
static byte RenderVoicedSample(ushort hi, byte off, byte phase1)
{
do
{
byte bit = 8;
byte sample = sampleTable[hi + off];
do
{
if ((sample & 128) != 0) Output(3, 26);
else Output(4, 6);
sample <<= 1;
} while (--bit != 0);
off++;
} while (++phase1 != 0);
return off;
}
static void RenderUnvoicedSample(ushort hi, byte off, byte mem53)
{
do
{
byte bit = 8;
byte sample = sampleTable[hi + off];
do
{
if ((sample & 128) != 0) Output(2, 5);
else Output(1, mem53);
sample <<= 1;
} while (--bit != 0);
} while (++off != 0);
}
// -------------------------------------------------------------------------
//Code48227
// Render a sampled sound from the sampleTable.
//
// Phoneme Sample Start Sample End
// 32: S* 15 255
// 33: SH 257 511
// 34: F* 559 767
// 35: TH 583 767
// 36: /H 903 1023
// 37: /X 1135 1279
// 38: Z* 84 119
// 39: ZH 340 375
// 40: V* 596 639
// 41: DH 596 631
//
// 42: CH
// 43: ** 399 511
//
// 44: J*
// 45: ** 257 276
// 46: **
//
// 66: P*
// 67: ** 743 767
// 68: **
//
// 69: T*
// 70: ** 231 255
// 71: **
//
// The SampledPhonemesTable[] holds flags indicating if a phoneme is
// voiced or not. If the upper 5 bits are zero, the sample is voiced.
//
// Samples in the sampleTable are compressed, with bits being converted to
// bytes from high bit to low, as follows:
//
// unvoiced 0 bit -> X
// unvoiced 1 bit -> 5
//
// voiced 0 bit -> 6
// voiced 1 bit -> 24
//
// Where X is a value from the table:
//
// { 0x18, 0x1A, 0x17, 0x17, 0x17 };
//
// The index into this table is determined by masking off the lower
// 3 bits from the SampledPhonemesTable:
//
// index = (SampledPhonemesTable[i] & 7) - 1;
//
// For voices samples, samples are interleaved between voiced output.
public static void RenderSample(byte mem66, byte consonantFlag, byte mem49)
{
// mem49 == current phoneme's index
// mask low three bits and subtract 1 get value to
// convert 0 bits on unvoiced samples.
byte hibyte = (byte)((consonantFlag & 7) - 1);
// determine which offset to use from table { 0x18, 0x1A, 0x17, 0x17, 0x17 }
// T, S, Z 0 0x18
// CH, J, SH, ZH 1 0x1A
// P, F*, V, TH, DH 2 0x17
// /H 3 0x17
// /X 4 0x17
ushort hi = (ushort)(hibyte * 256);
// voiced sample?
byte pitchl = (byte)(consonantFlag & 248);
if (pitchl == 0)
{
// voiced phoneme: Z*, ZH, V*, DH
pitchl = (byte)(pitches[mem49] >> 4);
mem66 = RenderVoicedSample(hi, mem66, (byte)(pitchl ^ 255));
}
else
RenderUnvoicedSample(hi, (byte)(pitchl ^ 255), tab48426[hibyte]);
}
// CREATE FRAMES
//
// The length parameter in the list corresponds to the number of frames
// to expand the phoneme to. Each frame represents 10 milliseconds of time.
// So a phoneme with a length of 7 = 7 frames = 70 milliseconds duration.
//
// The parameters are copied from the phoneme to the frame verbatim.
//
static void CreateFrames()
{
byte X = 0;
uint i = 0;
while (i < 256)
{
// get the phoneme at the index
byte phoneme = phonemeIndexOutput[i];
byte phase1;
uint phase2;
// if terminal phoneme, exit the loop
if (phoneme == 255) break;
if (phoneme == PHONEME_PERIOD) AddInflection(RISING_INFLECTION, X);
else if (phoneme == PHONEME_QUESTION) AddInflection(FALLING_INFLECTION, X);
// get the stress amount (more stress = higher pitch)
phase1 = tab47492[stressOutput[i] + 1];
// get number of frames to write
phase2 = phonemeLengthOutput[i];
// copy from the source to the frames list
do
{
frequency1[X] = freq1data[phoneme]; // F1 frequency
frequency2[X] = freq2data[phoneme]; // F2 frequency
frequency3[X] = freq3data[phoneme]; // F3 frequency
amplitude1[X] = ampl1data[phoneme]; // F1 amplitude
amplitude2[X] = ampl2data[phoneme]; // F2 amplitude
amplitude3[X] = ampl3data[phoneme]; // F3 amplitude
sampledConsonantFlag[X] = sampledConsonantFlags[phoneme]; // phoneme data for sampled consonants
pitches[X] = (byte)(pitch + phase1); // pitch
++X;
} while (--phase2 != 0);
++i;
}
}
// RESCALE AMPLITUDE
//
// Rescale volume from a linear scale to decibels.
//
static void RescaleAmplitude()
{
for (int i = 255; i >= 0; i--)
{
try { amplitude1[i] = amplitudeRescale[amplitude1[i]]; } catch { }
try { amplitude2[i] = amplitudeRescale[amplitude2[i]]; } catch { }
try { amplitude3[i] = amplitudeRescale[amplitude3[i]]; } catch { }
}
}
// ASSIGN PITCH CONTOUR
//
// This subtracts the F1 frequency from the pitch to create a
// pitch contour. Without this, the output would be at a single
// pitch level (monotone).
static void AssignPitchContour()
{
int i;
for (i = 0; i < 256; i++)
{
// subtract half the frequency of the formant 1.
// this adds variety to the voice
pitches[i] -= (byte)(frequency1[i] >> 1);
}
}
// RENDER THE PHONEMES IN THE LIST
//
// The phoneme list is converted into sound through the steps:
//
// 1. Copy each phoneme <length> number of times into the frames list,
// where each frame represents 10 milliseconds of sound.
//
// 2. Determine the transitions lengths between phonemes, and linearly
// interpolate the values across the frames.
//
// 3. Offset the pitches by the fundamental frequency.
//
// 4. Render the each frame.
public static void Render()
{
byte t;
if (phonemeIndexOutput[0] == 255) return; //exit if no data
CreateFrames();
t = CreateTransitions();
if (singmode == 0) AssignPitchContour();
RescaleAmplitude();
ProcessFrames(t);
}
// Create a rising or falling inflection 30 frames prior to
// index X. A rising inflection is used for questions, and
// a falling inflection is used for statements.
static void AddInflection(byte inflection, byte pos)
{
byte A;
// store the location of the punctuation
byte end = pos;
if (pos < 30) pos = 0;
else pos -= 30;
// FIXME: Explain this fix better, it's not obvious
// ML : A =, fixes a problem with invalid pitch with '.'
while ((A = pitches[pos]) == 127) ++pos;
while (pos != end)
{
// add the inflection direction
A += inflection;
// set the inflection
pitches[pos] = A;
while ((++pos != end) && pitches[pos] == 255) ;
}
}
/*
SAM's voice can be altered by changing the frequencies of the
mouth formant (F1) and the throat formant (F2). Only the voiced
phonemes (5-29 and 48-53) are altered.
*/
public static void SetMouthThroat(byte mouth, byte throat)
{
// mouth formants (F1) 5..29
byte[] mouthFormants5_29 = {
0, 0, 0, 0, 0, 10,
14, 19, 24, 27, 23, 21, 16, 20, 14, 18, 14, 18, 18,
16, 13, 15, 11, 18, 14, 11, 9, 6, 6, 6};
// throat formants (F2) 5..29
byte[] throatFormants5_29 = {
255, 255,
255, 255, 255, 84, 73, 67, 63, 40, 44, 31, 37, 45, 73, 49,
36, 30, 51, 37, 29, 69, 24, 50, 30, 24, 83, 46, 54, 86,
};
// there must be no zeros in this 2 tables
// formant 1 frequencies (mouth) 48..53
byte[] mouthFormants48_53 = { 19, 27, 21, 27, 18, 13 };
// formant 2 frequencies (throat) 48..53
byte[] throatFormants48_53 = { 72, 39, 31, 43, 30, 34 };
byte newFrequency = 0;
byte pos = 5;
// recalculate formant frequencies 5..29 for the mouth (F1) and throat (F2)
while (pos < 30)
{
// recalculate mouth frequency
byte initialFrequency = mouthFormants5_29[pos];
if (initialFrequency != 0) newFrequency = trans(mouth, initialFrequency);
freq1data[pos] = newFrequency;
// recalculate throat frequency
initialFrequency = throatFormants5_29[pos];
if (initialFrequency != 0) newFrequency = trans(throat, initialFrequency);
freq2data[pos] = newFrequency;
pos++;
}
// recalculate formant frequencies 48..53
pos = 0;
while (pos < 6)
{
// recalculate F1 (mouth formant)
byte initialFrequency = mouthFormants48_53[pos];
freq1data[pos + 48] = trans(mouth, initialFrequency);
// recalculate F2 (throat formant)
initialFrequency = throatFormants48_53[pos];
freq2data[pos + 48] = trans(throat, initialFrequency);
pos++;
}
}
}
}

58
SAMSharp.csproj Normal file
View file

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B666563F-F769-4BB0-BB7E-49A5E8C6A2ED}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>SAMSharp</RootNamespace>
<AssemblyName>SAMSharp</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Process.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Reciter.cs" />
<Compile Include="Renderer.cs" />
<Compile Include="Sam.cs" />
<Compile Include="Transitions.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

846
Sam.cs Normal file
View file

@ -0,0 +1,846 @@
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;
}
}
}
}

204
Transitions.cs Normal file
View file

@ -0,0 +1,204 @@
using System;
using static SAMSharp.Sam;
using static SAMSharp.Renderer;
namespace SAMSharp
{
class Transitions
{
// CREATE TRANSITIONS
//
// Linear transitions are now created to smoothly connect each
// phoeneme. This transition is spread between the ending frames
// of the old phoneme (outBlendLength), and the beginning frames
// of the new phoneme (inBlendLength).
//
// To determine how many frames to use, the two phonemes are
// compared using the blendRank[] table. The phoneme with the
// smaller score is used. In case of a tie, a blend of each is used:
//
// if blendRank[phoneme1] == blendRank[phomneme2]
// // use lengths from each phoneme
// outBlendFrames = outBlend[phoneme1]
// inBlendFrames = outBlend[phoneme2]
// else if blendRank[phoneme1] < blendRank[phoneme2]
// // use lengths from first phoneme
// outBlendFrames = outBlendLength[phoneme1]
// inBlendFrames = inBlendLength[phoneme1]
// else
// // use lengths from the second phoneme
// // note that in and out are swapped around!
// outBlendFrames = inBlendLength[phoneme2]
// inBlendFrames = outBlendLength[phoneme2]
//
// Blend lengths can't be less than zero.
//
// For most of the parameters, SAM interpolates over the range of the last
// outBlendFrames-1 and the first inBlendFrames.
//
// The exception to this is the Pitch[] parameter, which is interpolates the
// pitch from the center of the current phoneme to the center of the next
// phoneme.
//written by me because of different table positions.
// mem[47] = ...
// 168=pitches
// 169=frequency1
// 170=frequency2
// 171=frequency3
// 172=amplitude1
// 173=amplitude2
// 174=amplitude3
static byte Read(byte p, byte Y)
{
switch (p)
{
case 168: return pitches[Y];
case 169: return frequency1[Y];
case 170: return frequency2[Y];
case 171: return frequency3[Y];
case 172: return amplitude1[Y];
case 173: return amplitude2[Y];
case 174: return amplitude3[Y];
default:
return 0;
}
}
static void Write(byte p, byte Y, byte value)
{
switch (p)
{
case 168: pitches[Y] = value; return;
case 169: frequency1[Y] = value; return;
case 170: frequency2[Y] = value; return;
case 171: frequency3[Y] = value; return;
case 172: amplitude1[Y] = value; return;
case 173: amplitude2[Y] = value; return;
case 174: amplitude3[Y] = value; return;
default:
return;
}
}
// linearly interpolate values
static void interpolate(byte width, byte table, byte frame, char mem53)
{
bool sign = (mem53 < 0);
byte remainder = (byte)(Math.Abs(mem53) % width);
byte div = (byte)(mem53 / width);
byte error = 0;
byte pos = width;
byte val = (byte)(Read(table, frame) + div);
while (--pos != 0)
{
error += remainder;
if (error >= width)
{ // accumulated a whole integer error, so adjust output
error -= width;
if (sign) val--;
else if (val != 0) val++; // if input is 0, we always leave it alone
}
Write(table, ++frame, val); // Write updated value back to next frame.
val += div;
}
}
static void interpolate_pitch(byte pos, byte mem49, byte phase3)
{
// unlike the other values, the pitches[] interpolates from
// the middle of the current phoneme to the middle of the
// next phoneme
// half the width of the current and next phoneme
byte cur_width = (byte)(phonemeLengthOutput[pos] / 2);
byte next_width = (byte)(phonemeLengthOutput[pos + 1] / 2);
// sum the values
byte width = (byte)(cur_width + next_width);
char pitch = (char)(pitches[next_width + mem49] - pitches[mem49 - cur_width]);
interpolate(width, 168, phase3, pitch);
}
public static byte CreateTransitions()
{
byte mem49 = 0;
byte pos = 0;
while (true)
{
byte next_rank;
byte rank;
byte speedcounter;
byte phase1;
byte phase2;
byte phase3;
byte transition;
byte phoneme = phonemeIndexOutput[pos];
byte next_phoneme = phonemeIndexOutput[pos + 1];
if (next_phoneme == 255) break; // 255 == end_token
// get the ranking of each phoneme
next_rank = blendRank[next_phoneme];
rank = blendRank[phoneme];
// compare the rank - lower rank value is stronger
if (rank == next_rank)
{
// same rank, so use out blend lengths from each phoneme
phase1 = outBlendLength[phoneme];
phase2 = outBlendLength[next_phoneme];
}
else if (rank < next_rank)
{
// next phoneme is stronger, so us its blend lengths
phase1 = inBlendLength[next_phoneme];
phase2 = outBlendLength[next_phoneme];
}
else
{
// current phoneme is stronger, so use its blend lengths
// note the out/in are swapped
phase1 = outBlendLength[phoneme];
phase2 = inBlendLength[phoneme];
}
mem49 += phonemeLengthOutput[pos];
speedcounter = (byte)(mem49 + phase2);
phase3 = (byte)(mem49 - phase1);
transition = (byte)(phase1 + phase2); // total transition?
if (((transition - 2) & 128) == 0)
{
byte table = 169;
interpolate_pitch(pos, mem49, phase3);
while (table < 175)
{
// tables:
// 168 pitches[]
// 169 frequency1
// 170 frequency2
// 171 frequency3
// 172 amplitude1
// 173 amplitude2
// 174 amplitude3
char value = (char)(Read(table, speedcounter) - Read(table, phase3));
interpolate(transition, table, phase3, value);
table++;
}
}
++pos;
}
// add the length of this phoneme
return (byte)(mem49 + phonemeLengthOutput[pos]);
}
}
}