diff --git a/Process.cs b/Process.cs new file mode 100644 index 0000000..94fd7f8 --- /dev/null +++ b/Process.cs @@ -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; + } + } + } +} diff --git a/Renderer.cs b/Renderer.cs new file mode 100644 index 0000000..fc1b7e4 --- /dev/null +++ b/Renderer.cs @@ -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 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++; + } + } + } +} diff --git a/SAMSharp.csproj b/SAMSharp.csproj new file mode 100644 index 0000000..367ad37 --- /dev/null +++ b/SAMSharp.csproj @@ -0,0 +1,58 @@ + + + + + Debug + AnyCPU + {B666563F-F769-4BB0-BB7E-49A5E8C6A2ED} + Exe + SAMSharp + SAMSharp + v4.6.1 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sam.cs b/Sam.cs new file mode 100644 index 0000000..4bf7d08 --- /dev/null +++ b/Sam.cs @@ -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: + // + // -> WX + // -> YX + // UL -> AX L + // UM -> AX M + // -> Q + // T R -> CH R + // D R -> J R + // R -> RX + // L -> LX + // G S -> G Z + // K -> KX + // G -> GX + // S P -> S B + // S T -> S D + // S K -> S G + // S KX -> S GX + // UW -> UX + // CH -> CH CH' (CH requires two phonemes to represent it) + // J -> J J' (J requires two phonemes to represent it) + // T -> DX + // D -> DX + + + 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 -> GX + // 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) + { + // -> 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: + // -> Q + // 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 -> KX + // 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" + // T -> DX + // D -> DX + // 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 or between and by 1.5 + // - decrease length by 1 + // - decrease vowel by 1/8th + // - increase vowel by 1/2 + 1 + // - set nasal = 5, consonant = 6 + // {optional silence} - shorten both to 1/2 + 1 + // - 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 + 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: + // + 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: + // 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: {optional silence} + // 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: {optional silence} + 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: + // Decrease by 2 + index = phonemeindex[X - 1]; // prior phoneme; + phonemeLength[X] -= 2; // 20ms + } + + ++loopIndex; + } + } + } +} \ No newline at end of file diff --git a/Transitions.cs b/Transitions.cs new file mode 100644 index 0000000..e792647 --- /dev/null +++ b/Transitions.cs @@ -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]); + } + } +}