Source file: /~heha/basteln/Haus/Telefon/sprech.zip/src/render.cpp

#include <stdio.h>
#include <string.h>
#include <stdlib.h> // abs()

#include "render.h"
#include "debug.h"

typedef unsigned char byte;
#include "pgmspace.h"

byte speed = 72;
byte pitch = 64;
bool singmode = 0;

//timetable for more accurate c64 simulation
static const byte timetable[5][5] PROGMEM = {
 {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}
};

static void Output8BitAry(int index, byte ary[5]) {
 static byte oldtimetableindex;
 static byte lastAry[5];
 static int bufferpos;
 int newbufferpos =  bufferpos + pgm_read_byte(&timetable[oldtimetableindex][index]);
 int bp0 = bufferpos / 50;
 int bp1 = newbufferpos / 50;
 int k=0,i;
 for (i=bp0; i<bp1; i++, k++) putchar(lastAry[k]);
 memcpy(lastAry, ary, 5);
 bufferpos = newbufferpos;
 oldtimetableindex = index;
}

static void Output8Bit(int index, byte A) {
 byte ary[5] = {A,A,A,A,A};
 Output8BitAry(index, ary);
}

#define TLEN 256	// Uh, hier steckt der RAM-Vielfraß!

static byte pitches[TLEN];
static byte frequency1[TLEN];
static byte frequency2[TLEN];
static byte frequency3[TLEN];
static byte amplitude1[TLEN];
static byte amplitude2[TLEN];
static byte amplitude3[TLEN];
static byte sampledConsonantFlag[TLEN];

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];
 }
	//printf("Error reading to tables");
 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;
 }
	//printf("Error writing to tables\n");
}

//Zischlaute
static const byte sampleTable[0x500] PROGMEM = {
/* 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,0x08,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,0x08,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,0x08,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,0x0C,0xCD,0x32,0x88,0xC4,0x73,0x18,0x86,0x73,0x08,0xD6,0x63,0x58,
/*100*/	0x07,0x81,0xE0,0xF0,0x3C,0x07,0x87,0x90,0x3C,0x7C,0x0F,0xC7,0xC0,0xC0,0xF0,0x7C,
	0x1E,0x07,0x80,0x80,0x00,0x1C,0x78,0x70,0xF1,0xC7,0x1F,0xC0,0x0C,0xFE,0x1C,0x1F,
/*120*/	0x1F,0x0E,0x0A,0x7A,0xC0,0x71,0xF2,0x83,0x8F,0x03,0x0F,0x0F,0x0C,0x00,0x79,0xF8,
	0x61,0xE0,0x43 ,0xF,0x83,0xE7,0x18,0xF9,0xC1,0x13,0xDA,0xE9,0x63,0x8F,0x0F,0x83,
/*140*/	0x83,0x87,0xC3,0x1F,0x3C,0x70,0xF0,0xE1,0xE1,0xE3,0x87,0xB8,0x71,0x0E,0x20,0xE3,
	0x8D,0x48,0x78,0x1C,0x93,0x87,0x30,0xE1,0xC1,0xC1,0xE4,0x78,0x21,0x83,0x83,0xC3,
/*160*/	0x87,0x06,0x39,0xE5,0xC3,0x87,0x07,0x0E,0x1C,0x1C,0x70,0xF4,0x71,0x9C,0x60,0x36,
	0x32,0xC3,0x1E,0x3C,0xF3,0x8F,0x0E,0x3C,0x70,0xE3,0xC7,0x8F,0x0F,0x0F,0x0E,0x3C,
/*180*/	0x78,0xF0,0xE3,0x87,0x06,0xF0,0xE3,0x07,0xC1,0x99,0x87,0x0F,0x18,0x78,0x70,0x70,
	0xFC,0xF3,0x10,0xB1,0x8C,0x8C,0x31,0x7C,0x70,0xE1,0x86,0x3C,0x64,0x6C,0xB0,0xE1,
/*1A0*/	0xE3,0x0F,0x23,0x8F,0x0F,0x1E,0x3E,0x38,0x3C,0x38,0x7B,0x8F,0x07,0x0E,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,0x0E,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,0x0E,0x0E,0x38,0x78,0x73,0x17,0x1E,0x39,0x1E,0x38,0x64,0xE1,0xF1,0xC1,
/*200*/	0x4E,0x0F,0x40,0xA2,0x02,0xC5,0x8F,0x81,0xA1,0xFC,0x12,0x08,0x64,0xE0,0x3C,0x22,
	0xE0,0x45,0x07,0x8E,0x0C,0x32,0x90,0xF0,0x1F,0x20,0x49,0xE0,0xF8,0x0C,0x60,0xF0,
/*220*/	0x17,0x1A,0x41,0xAA,0xA4,0xD0,0x8D,0x12,0x82,0x1E,0x1E,0x03,0xF8,0x3E,0x03,0x0C,
	0x73,0x80,0x70,0x44,0x26,0x03,0x24,0xE1,0x3E,0x04,0x4E,0x04,0x1C,0xC1,0x09,0xCC,
/*240*/	0x9E,0x90,0x21,0x07,0x90,0x43,0x64,0xC0,0x0F,0xC6,0x90,0x9C,0xC1,0x5B,0x03,0xE2,
	0x1D,0x81,0xE0,0x5E,0x1D,0x03,0x84,0xB8,0x2C,0x0F,0x80,0xB1,0x83,0xE0,0x30,0x41,
/*260*/	0x1E,0x43,0x89,0x83,0x50,0xFC,0x24,0x2E,0x13,0x83,0xF1,0x7C,0x4C,0x2C,0xC9,0x0D,
	0x83,0xB0,0xB5,0x82,0xE4,0xE8,0x06,0x9C,0x07,0xA0,0x99,0x1D,0x07,0x3E,0x82,0x8F,
/*280*/	0x70,0x30,0x74,0x40,0xCA,0x10,0xE4,0xE8,0x0F,0x92,0x14,0x3F,0x06,0xF8,0x84,0x88,
	0x43,0x81 ,0xA,0x34,0x39,0x41,0xC6,0xE3,0x1C,0x47,0x03,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,0x01,0xB0,0x26,0x40,0xF4,0x0B,0xC3,0x42,0x1F,0x85,0x32,
/*2C0*/	0x26,0x60,0x40,0xC9,0xCB,0x01,0xEC,0x11,0x28,0x40,0xFA,0x04,0x34,0xE0,0x70,0x4C,
	0x8C,0x1D,0x07,0x69,0x03,0x16,0xC8,0x04,0x23,0xE8,0xC6,0x9A,0x0B,0x1A,0x03,0xE0,
/*2E0*/	0x76,0x06,0x05,0xCF,0x1E,0xBC,0x58,0x31,0x71,0x66,0x00,0xF8,0x3F,0x04,0xFC,0x0C,
	0x74,0x27,0x8A,0x80,0x71,0xC2,0x3A,0x26,0x06,0xC0,0x1F,0x05,0x0F,0x98,0x40,0xAE,
/*300*/	0x01,0x7F,0xC0,0x07,0xFF,0x00 ,0xE,0xFE,0x00,0x03,0xDF,0x80,0x03,0xEF,0x80,0x1B,
	0xF1,0xC2,0x00,0xE7,0xE0,0x18,0xFC,0xE0,0x21,0xFC,0x80,0x3C,0xFC,0x40,0x0E,0x7E,
/*320*/	0x00,0x3F,0x3E,0x00,0x0F,0xFE,0x00,0x1F,0xFF,0x00,0x3E,0xF0,0x07,0xFC,0x00,0x7E,
	0x10,0x3F,0xFF,0x00,0x3F,0x38,0x0E,0x7C,0x01,0x87,0x0C,0xFC,0xC7,0x00,0x3E,0x04,
/*340*/	0x0F,0x3E,0x1F,0x0F,0x0F,0x1F,0x0F,0x02,0x83,0x87,0xCF,0x03,0x87,0x0F,0x3F,0xC0,
	0x07,0x9E,0x60,0x3F,0xC0,0x03,0xFE,0x00,0x3F,0xE0,0x77,0xE1,0xC0,0xFE,0xE0,0xC3,
/*360*/	0xE0,0x01,0xDF,0xF8,0x03,0x07,0x00,0x7E,0x70,0x00,0x7C,0x38,0x18,0xFE,0x0C,0x1E,
	0x78,0x1C,0x7C,0x3E ,0xE,0x1F,0x1E,0x1E,0x3E,0x00,0x7F,0x83,0x07,0xDB,0x87,0x83,
/*380*/	0x07,0xC7,0x07,0x10,0x71,0xFF,0x00,0x3F,0xE2,0x01,0xE0,0xC1,0xC3,0xE1,0x00,0x7F,
	0xC0,0x05,0xF0,0x20,0xF8,0xF0,0x70,0xFE,0x78,0x79,0xF8,0x02,0x3F,0x0C,0x8F,0x03,
/*3a0*/	0x0F,0x9F,0xE0,0xC1,0xC7,0x87,0x03,0xC3,0xC3,0xB0,0xE1,0xE1,0xC1,0xE3,0xE0,0x71,
	0xF0,0x00,0xFC,0x70,0x7C,0x0C,0x3E,0x38,0x0E,0x1C,0x70,0xC3,0xC7,0x03,0x81,0xC1,
/*3c0*/	0xC7,0xE7,0x00,0x0F,0xC7,0x87,0x19,0x09,0xEF,0xC4,0x33,0xE0,0xC1,0xFC,0xF8,0x70,
	0xF0,0x78,0xF8,0xF0,0x61,0xC7,0x00,0x1F,0xF8,0x01,0x7C,0xF8,0xF0,0x78,0x70,0x3C,
/*3e0*/	0x7C,0xCE ,0xE,0x21,0x83,0xCF,0x08,0x07,0x8F,0x08,0xC1,0x87,0x8F,0x80,0xC7,0xE3,
	0x00,0x07,0xF8,0xE0,0xEF,0x00,0x39,0xF7,0x80,0x0E,0xF8,0xE1,0xE3,0xF8,0x21,0x9F,
/*400*/	0xC0,0xFF,0x03,0xF8,0x07,0xC0,0x1F,0xF8,0xC4,0x04,0xFC,0xC4,0xC1,0xBC,0x87,0xF0,
	0x0F,0xC0,0x7F,0x05,0xE0,0x25,0xEC,0xC0,0x3E,0x84,0x47,0xF0,0x8E,0x03,0xF8,0x03,
/*420*/	0xFB,0xC0,0x19,0xF8,0x07,0x9C,0x0C,0x17,0xF8,0x07,0xE0,0x1F,0xA1,0xFC,0x0F,0xFC,
	0x01,0xF0,0x3F,0x00,0xFE,0x03,0xF0,0x1F,0x00,0xFD,0x00,0xFF,0x88,0x0D,0xF9,0x01,
/*440*/	0xFF,0x00,0x70,0x07,0xC0,0x3E,0x42,0xF3,0x0D,0xC4,0x7F,0x80,0xFC,0x07,0xF0,0x5E,
	0xC0,0x3F,0x00,0x78,0x3F,0x81,0xFF,0x01,0xF8,0x01,0xC3,0xE8,0x0C,0xE4,0x64,0x8F,
/*460*/	0xE4,0x0F,0xF0,0x07,0xF0,0xC2,0x1F,0x00,0x7F,0xC0,0x6F,0x80,0x7E,0x03,0xF8,0x07,
	0xF0,0x3F,0xC0,0x78,0x0F,0x82,0x07,0xFE,0x22,0x77,0x70,0x02,0x76,0x03,0xFE,0x00,
/*480*/	0xFE,0x67,0x00,0x7C,0xC7,0xF1,0x8E,0xC6,0x3B,0xE0,0x3F,0x84,0xF3,0x19,0xD8,0x03,
	0x99,0xFC,0x09,0xB8,0x0F,0xF8,0x00,0x9D,0x24,0x61,0xF9,0x0D,0x00,0xFD,0x03,0xF0,
/*4a0*/	0x1F,0x90,0x3F,0x01,0xF8,0x1F,0xD0,0x0F,0xF8,0x37,0x01,0xF8,0x07,0xF0 ,0xF,0xC0,
	0x3F,0x00,0xFE,0x03,0xF8,0x0F,0xC0,0x3F,0x00,0xFA,0x03,0xF0,0x0F,0x80,0xFF,0x01,
/*4c0*/	0xB8,0x07,0xF0,0x01,0xFC,0x01,0xBC,0x80,0x13,0x1E,0x00,0x7F,0xE1,0x40,0x7F,0xA0,
	0x7F,0xB0,0x00,0x3F,0xC0,0x1F,0xC0,0x38,0x0F,0xF0,0x1F,0x80,0xFF,0x01,0xFC,0x03,
/*4e0*/	0xF1,0x7E,0x01,0xFE,0x01,0xF0,0xFF,0x00,0x7F,0xC0,0x1D,0x07,0xF0,0x0F,0xC0,0x7E,
	0x06,0xE0,0x07,0xE0,0x0F,0xF8,0x06,0xC1,0xFE,0x01,0xFC,0x03,0xE0,0x0F,0x00,0xFC};

// -------------------------------------------------------------------------
//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.

static byte mem39;

static void RenderSample(byte Y,byte*mem66) {
	// mask low three bits and subtract 1 get value to
	// convert 0 bits on unvoiced samples.
 byte A = mem39&7;
 byte X = A-1;
// mem56 = X;    // store the result
	// 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
    // get value from the table
 static const byte tab48426[5] PROGMEM = { 0x18, 0x1A, 0x17, 0x17, 0x17 };

 byte mem53 = pgm_read_byte(tab48426+X); //tab48426[X];
 byte mem47 = X;      //46016+mem[56]*256
	// voiced sample?
 A = mem39 & 248;
 if (!A) {	// handle voiced samples here
  byte phase1=(pitches[Y]>>4)+1;	// voiced phoneme: Z*, ZH, V*, DH   // number of samples?
  byte Y=*mem66;
  do {
   byte A=pgm_read_byte(sampleTable + mem47*256+Y);	// fetch value from table
   byte cnt=8; do{	// shift through all 8 bits
    if (A&0x80) {	// check high bit
     Output8Bit(3, (26&0xf)<<4);	// if bit set, output 26
    }else{	//timetable 4
     Output8Bit(4, 6<<4);	// bit is not set, output a 6
    }
    A<<=1;
   }while(--cnt);
   Y++;		// move ahead in the table
  }while (--phase1);	// continue until counter done
  *mem66=Y;	// restore value and return
 }else{
  byte Y=A+1;
  do{    // step through the 8 bits in the sample
   byte A = pgm_read_byte(sampleTable + mem47*256+Y);
   byte cnt=8; do{
    if (!(A&0x80)) {	// bit not set?
     Output8Bit(1, (mem53&0xf)<<4);	// output the byte
     if (!mem53) Output8Bit(2, 5<<4);	// if X != 0, exit loop
    }else{
     Output8Bit(2, 5<<4);	// output a 5 for the on bit
    }
    A<<=1;
   }while (--cnt);	// if not done, jump to top of loop
	// increment position
  }while (--Y);
 }
}

static byte X;

// 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(unsigned char mem48, byte phase1) {
    // store the location of the punctuation
 byte mem49 = X;
 byte A;
 X = X>=30?X-30:0;
	// backup 30 frames
	// if index is before buffer, point to start of buffer
	// FIXME: Explain this fix better, it's not obvious
	// ML : A =, fixes a problem with invalid pitch with '.'
 while((A=pitches[X]) == 127) X++;
 for(;;){	// add the inflection direction
  A += mem48;
  phase1 = A;
  pitches[X] = A;	// set the inflection
  do{	// increment the position
   if (++X == mem49) return;	// exit if the punctuation has been reached
  }while (pitches[X]==255);
  A = phase1;
 }
}

static void Print(const struct phonem_t*p) {
#ifndef __AVR__
 printf("phonems = L\"");
 for(; p->index; p++) {
  printf("\\x%04X",p->w);
 }
 printf("\"\n");
#endif
}

void Render1(const struct phonem_t*phonems) {
 X = 0;
// 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 = 220 samples.
// So a phoneme with a length of 7 = 7 frames = 70 milliseconds duration.
// The parameters are copied from the phoneme to the frame verbatim.
 for(;phonems->index;phonems++) {
  byte phase1,phase2;
  byte A = phonems->index;
	// period phoneme *.
  if (A == 1) AddInflection(1, phase1);// add rising inflection
	// question mark phoneme?
  if (A == 2) AddInflection(255, phase1);	// create falling inflection
    // get the stress amount (more stress = higher pitch)
  static const byte tab47492[11] PROGMEM = {0,0,0xE0,0xE6,0xEC,0xF3,0xF9,0,6,0xC,6};
  phase1 = pgm_read_byte(tab47492 + phonems->stress + 1); // tab47492[stressOutput[Y] + 1];
    // get number of frames to write
  phase2 = phonems->length;
	// copy from the source to the frames list
  do {
   static const byte freq1data[80] PROGMEM = {
     0x00,0x13,0x13,0x13,0x13,0x0A,0x0E,0x12,0x18,0x1A,0x16,0x14,0x10,0x14,0x0E,0x12,
     0x0E,0x12,0x12,0x10,0x0C,0x0E,0x0A,0x12,0x0E,0x0A,   8,   6,   6,   6,   6,0x11,
        6,   6,   6,   6,0x0E,0x10,   9,0x0A,   8,0x0A,   6,   6,   6,   5,   6,   0,
     0x12,0x1A,0x14,0x1A,0x12,0x0C,   6,   6,   6,   6,   6,   6,   6,   6,   6,   6,
        6,   6,   6,   6,   6,   6,   6,   6,   6,0x0A,0x0A,   6,   6,   6,0x2C,0x13};
   static const byte freq2data[80] PROGMEM = {
     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};
   static const byte freq3data[80] PROGMEM = {
     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 const byte ampl1data[80] PROGMEM = {
       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 const byte ampl2data[80] PROGMEM = {
     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 const byte ampl3data[80] PROGMEM = {
     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};

   frequency1[X] = pgm_read_byte(freq1data+A);	// F1 frequency
   frequency2[X] = pgm_read_byte(freq2data+A);	// F2 frequency
   frequency3[X] = pgm_read_byte(freq3data+A);	// F3 frequency
   amplitude1[X] = pgm_read_byte(ampl1data+A);	// F1 amplitude
   amplitude2[X] = pgm_read_byte(ampl2data+A);	// F2 amplitude
   amplitude3[X] = pgm_read_byte(ampl3data+A);	// F3 amplitude
	// 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
   static const byte sampledConsonantFlags[80] PROGMEM = {
     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};

   sampledConsonantFlag[X] = pgm_read_byte(&sampledConsonantFlags[A]);	// phoneme data for sampled consonants
   pitches[X] = pitch + phase1;	// pitch
   X++;
  } while(--phase2);
 }
}

// 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.

static char rectangle(byte pha) {return pha>=0x80 ? 0x70 : -0x70;}
static char sinus(byte pha) {
 static const char sintab[64] PROGMEM = {	// nur 1 Quadrant
   0,3,6,9,12,16,19,22,25,28,31,34,37,40,43,46,
   49,51,54,57,60,63,65,68,71,73,76,78,81,83,85,88,
   90,92,94,96,98,100,102,104,106,107,109,111,112,113,115,116,
   117,118,120,121,122,122,123,124,125,125,126,126,126,127,127,127};
 if (pha&0x40) pha=128-pha;	// 64->64, 65->63, 127->1; 192->192, 193->191, 255->129
 if (pha&0x40) --pha;		// nur 64->63 und 192->191 (Gipfel bzw. Tal des Sinusbogens)
 char ret=(char)pgm_read_byte(sintab+(pha&0x7F));
 if (pha&0x80) ret=-ret;
 return ret;
}

//void Code47574()
void Render(const struct phonem_t*phonems) {
 byte mem44;
 byte mem47;// ... 168=pitches 169=frequency1 170=frequency2 171=frequency3 172=amplitude1 173=amplitude2 174=amplitude3
 byte mem49=0;
 byte mem50;
 byte mem51;
 byte mem53;
 byte mem56;
 byte A, Y;
 byte phase1=0, phase2=0, phase3=0;
 byte mem66=0, mem38=0, mem40=0;
 byte speedcounter=0;
 byte mem48=0;
 int i;
 Print(phonems);
 if (!phonems->index) return; //exit if no data
 
 Render1(phonems);
 if (debug)
   PrintOutput(sampledConsonantFlag, frequency1, frequency2, frequency3, amplitude1, amplitude2, amplitude3, pitches);
// -------------------
//pos47694:

// CREATE TRANSITIONS
//
// Linear transitions are now created to smoothly connect the
// end of one sustained portion of a phoneme to the following
// phoneme.
//
// To do this, three tables are used:
//
//  Table         Purpose
//  =========     ==================================================
//  blendRank     Determines which phoneme's blend values are used.
//
//  blendOut      The number of frames at the end of the phoneme that
//                will be used to transition to the following phoneme.
//
//  blendIn       The number of frames of the following phoneme that
//                will be used to transition into that phoneme.
//
// In creating a transition between two phonemes, the phoneme
// with the HIGHEST rank is used. Phonemes are ranked on how much
// their identity is based on their transitions. For example,
// vowels are and diphthongs are identified by their sustained portion,
// rather than the transitions, so they are given low values. In contrast,
// stop consonants (P, B, T, K) and glides (Y, L) are almost entirely
// defined by their transitions, and are given high rank values.
//
// Here are the rankings used by SAM:
//
//     Rank    Type                         Phonemes
//     2       All vowels                   IY, IH, etc.
//     5       Diphthong endings            YX, WX, ER
//     8       Terminal liquid consonants   LX, WX, YX, N, NX
//     9       Liquid consonants            L, RX, W
//     10      Glide                        R, OH
//     11      Glide                        WH
//     18      Voiceless fricatives         S, SH, F, TH
//     20      Voiced fricatives            Z, ZH, V, DH
//     23      Plosives, stop consonants    P, T, K, KX, DX, CH
//     26      Stop consonants              J, GX, B, D, G
//     27-29   Stop consonants (internal)   **
//     30      Unvoiced consonants          /H, /X and Q*
//     160     Nasal                        M
//
// To determine how many frames to use, the two phonemes are
// compared using the blendRank[] table. The phoneme with the
// higher rank is selected. 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!
//          outBlendFrames = inBlendLength[phoneme2]
//          inBlendFrames = outBlendLength[phoneme2]
//
// Blend lengths can't be less than zero.
//
// Transitions are assumed to be symetrical, so if the transition
// values for the second phoneme are used, the inBlendLength and
// outBlendLength values are SWAPPED.
//
// 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.
//
// Here are two examples. First, For example, consider the word "SUN" (S AH N)
//
//    Phoneme   Duration    BlendWeight    OutBlendFrames    InBlendFrames
//    S         2           18             1                 3
//    AH        8           2              4                 4
//    N         7           8              1                 2
//
// The formant transitions for the output frames are calculated as follows:
//
//     flags ampl1 freq1 ampl2 freq2 ampl3 freq3 pitch
//    ------------------------------------------------
// S
//    241     0     6     0    73     0    99    61   Use S (weight 18) for transition instead of AH (weight 2)
//    241     0     6     0    73     0    99    61   <-- (OutBlendFrames-1) = (1-1) = 0 frames
// AH
//      0     2    10     2    66     0    96    59 * <-- InBlendFrames = 3 frames
//      0     4    14     3    59     0    93    57 *
//      0     8    18     5    52     0    90    55 *
//      0    15    22     9    44     1    87    53
//      0    15    22     9    44     1    87    53
//      0    15    22     9    44     1    87    53   Use N (weight 8) for transition instead of AH (weight 2).
//      0    15    22     9    44     1    87    53   Since N is second phoneme, reverse the IN and OUT values.
//      0    11    17     8    47     1    98    56 * <-- (InBlendFrames-1) = (2-1) = 1 frames
// N
//      0     8    12     6    50     1   109    58 * <-- OutBlendFrames = 1
//      0     5     6     5    54     0   121    61
//      0     5     6     5    54     0   121    61
//      0     5     6     5    54     0   121    61
//      0     5     6     5    54     0   121    61
//      0     5     6     5    54     0   121    61
//      0     5     6     5    54     0   121    61
//
// Now, consider the reverse "NUS" (N AH S):
//
//     flags ampl1 freq1 ampl2 freq2 ampl3 freq3 pitch
//    ------------------------------------------------
// N
//     0     5     6     5    54     0   121    61
//     0     5     6     5    54     0   121    61
//     0     5     6     5    54     0   121    61
//     0     5     6     5    54     0   121    61
//     0     5     6     5    54     0   121    61
//     0     5     6     5    54     0   121    61   Use N (weight 8) for transition instead of AH (weight 2)
//     0     5     6     5    54     0   121    61   <-- (OutBlendFrames-1) = (1-1) = 0 frames
// AH
//     0     8    11     6    51     0   110    59 * <-- InBlendFrames = 2
//     0    11    16     8    48     0    99    56 *
//     0    15    22     9    44     1    87    53   Use S (weight 18) for transition instead of AH (weight 2)
//     0    15    22     9    44     1    87    53   Since S is second phoneme, reverse the IN and OUT values.
//     0     9    18     5    51     1    90    55 * <-- (InBlendFrames-1) = (3-1) = 2
//     0     4    14     3    58     1    93    57 *
// S
//   241     2    10     2    65     1    96    59 * <-- OutBlendFrames = 1
//   241     0     6     0    73     0    99    61

 for (;phonems->index;phonems++) {
        // get the current and following phoneme
  Y = phonems->index;
  A = phonems[1].index;
        // get the ranking of each phoneme
  X = A;
// Used to decide which phoneme's blend lengths. The candidate with the lower score is selected.
// tab45856
  static const byte blendRank[80] PROGMEM = {
    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};

  mem56 = pgm_read_byte(blendRank+A); //blendRank[A];
  A = pgm_read_byte(blendRank+Y); //blendRank[Y];

// Number of frames at the end of a phoneme devoted to interpolating to next phoneme's final value
//tab45696
  static const byte outBlendLength[80] PROGMEM = {
    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
  static const byte inBlendLength[80] PROGMEM = {
    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};

		// compare the rank - lower rank value is stronger
  if (A == mem56) {
            // same rank, so use out blend lengths from each phoneme
   phase1 = pgm_read_byte(outBlendLength+Y);//outBlendLength[Y];
   phase2 = pgm_read_byte(outBlendLength+X);//outBlendLength[X];
  }else if (A < mem56) {
            // first phoneme is stronger, so us it's blend lengths
   phase1 = pgm_read_byte(inBlendLength+X);//inBlendLength[X];
   phase2 = pgm_read_byte(outBlendLength+X);//outBlendLength[X];
  }else{
            // second phoneme is stronger, so use it's blend lengths
            // note the out/in are swapped
   phase1 = pgm_read_byte(outBlendLength+Y);//outBlendLength[Y];
   phase2 = pgm_read_byte(inBlendLength+Y);//inBlendLength[Y];
  }
  A = mem49 + phonems->length; // A is mem49 + length
  mem49 = A; // mem49 now holds length + position
  A+=phase2; //Maybe Problem because of carry flag
		//47776: ADC 42
  speedcounter = A;
  mem47 = 168;
  phase3 = mem49 - phase1; // what is mem49
  A = phase1 + phase2; // total transition?
  mem38 = A;

  X=A-2;
  if (!(X&0x80)) do {   //while No. 2
	// mem47 is used to index the tables:
	// 168  pitches[]
	// 169  frequency1
	// 170  frequency2
	// 171  frequency3
	// 172  amplitude1
	// 173  amplitude2
	// 174  amplitude3
   mem40 = mem38;
   if (mem47 == 168) {	// pitch
               // unlike the other values, the pitches[] interpolates from
               // the middle of the current phoneme to the middle of the
               // next phoneme
    byte mem36=phonems->length>>1;	// half the width of the current phoneme
    byte mem37=phonems[1].length>>1;	// half the width of the next phoneme
				// sum the values
    mem40 = mem36 + mem37; // length of both halves
    mem37 += mem49; // center of next phoneme
    mem36 = mem49 - mem36; // center index of current phoneme
    A = Read(mem47, mem37); // value at center of next phoneme - end interpolation value
				//A = mem[address];
    Y = mem36; // start index of interpolation
    mem53 = A - Read(mem47, mem36); // value to center of current phoneme
   }else{
                // value to interpolate to
    A = Read(mem47, speedcounter);
				// position to start interpolation from
    Y = phase3;
				// value to interpolate from
    mem53 = A - Read(mem47, phase3);
   }
			// ML : Code47503 is division with remainder, and mem50 gets the sign
			// calculate change per frame
   {signed char m53 = (signed char)mem53;
    mem50 = mem53 & 128;
    mem51 = (unsigned char)abs(m53) % mem40; //abs((char)m53) % mem40;
    mem53 = (unsigned char)(m53/mem40);
   }
            // interpolation range
   X = mem40; // number of frames to interpolate over
   Y = phase3; // starting frame
            // linearly interpolate values
   mem56 = 0;
   while(1) {     //while No. 3
    A = Read(mem47, Y) + mem53; //carry alway cleared
    mem48 = A;
    Y++;
    X--;
    if (!X) break;
    mem56 += mem51;
    if (mem56 >= mem40) { //???
     mem56 -= mem40; //carry? is set
     if (!(mem50&0x80)) {
      if (mem48) mem48++;
     }else mem48--;
    }
    Write(mem47, Y, mem48);
   } //while No. 3
   mem47++;
  } while (mem47 != 175);     //while No. 2
 }  //while No. 1

    // add the length of this phoneme
 mem48 = mem49 + phonems->length;

// 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).

	// don't adjust pitch if in sing mode
 if (!singmode)	for(i=0; i<256; i++) {// iterate through the buffer
            // subtract half the frequency of the formant 1.
            // this adds variety to the voice
  pitches[i] -= (frequency1[i] >> 1);
 }
 phase1 = 0;
 phase2 = 0;
 phase3 = 0;
 mem49 = 0;
 speedcounter = 72; //sam standard speed
// RESCALE AMPLITUDE
// Rescale volume from a linear scale to decibels.
 static const byte amplitudeRescale[17] PROGMEM = {
   0,1,2,2,2,3,3,4,4,5,6,8,9,11,13,15,0};  //17 elements??
 for(i=255; i>=0; i--) {
  amplitude1[i] = pgm_read_byte(amplitudeRescale + amplitude1[i]);
  amplitude2[i] = pgm_read_byte(amplitudeRescale + amplitude2[i]);
  amplitude3[i] = pgm_read_byte(amplitudeRescale + amplitude3[i]);
 }
 Y = 0;
 A = pitches[0];
 mem44 = A;
 X = A;
 mem38 = A - (A>>2);     // 3/4*A ???
 if (debug)
   PrintOutput(sampledConsonantFlag, frequency1, frequency2, frequency3, amplitude1, amplitude2, amplitude3, pitches);

// 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.

	//finally the loop for sound output
	//pos48078:
 while(1) {
        // get the sampled information on the phoneme
  A = sampledConsonantFlag[Y];
  mem39 = A;

		// unvoiced sampled phoneme?
  A&=248;
  if (A) { // render the sample for the phoneme
   RenderSample(Y,&mem66);
   Y += 2;	// skip ahead two in the phoneme buffer
   mem48 -= 2;
  }else{
            // simulate the glottal pulse and formants
   byte ary[5];
   unsigned p1 = phase1 * 256; // Fixed point integers because we need to divide later on
   unsigned p2 = phase2 * 256;
   unsigned p3 = phase3 * 256;
   int k;
   for (k=0; k<5; k++) {
    signed char sp1 = sinus(p1>>8);
    signed char sp2 = sinus(p2>>8);
    signed char rp3 = rectangle(p3>>8);
    int sin1 = sp1 * ((unsigned char)amplitude1[Y] & 0x0f);
    int sin2 = sp2 * ((unsigned char)amplitude2[Y] & 0x0f);
    int rect = rp3 * ((unsigned char)amplitude3[Y] & 0x0f);
    int mux = sin1 + sin2 + rect;
    mux >>= 5;
    mux += 128; // Go from signed to unsigned amplitude
    ary[k] = mux;
    p1 += frequency1[Y]<<6; // Compromise, this becomes a shift and works well
    p2 += frequency2[Y]<<6;
    p3 += frequency3[Y]<<6;
   }
   Output8BitAry(0, ary);			// output the accumulated value
   if (--speedcounter) goto havespeed;
   Y++; //go to next amplitude
   mem48--;	// decrement the frame count
  }
  if (!mem48) return;	// if the frame count is zero, exit the loop
  speedcounter = speed;
havespeed:	// decrement the remaining length of the glottal pulse
  if (!--mem44)	goto finish;	// finished with a glottal pulse?
            // fetch the next glottal pulse length
  else if (--mem38 || !mem39) {
// decrement the count
// is the count non-zero and the sampled flag is zero?
            // reset the phase of the formants to match the pulse
   phase1 += frequency1[Y];
   phase2 += frequency2[Y];
   phase3 += frequency3[Y];
  }else{
		// voiced sampled phonemes interleave the sample with the
		// glottal pulse. The sample flag is non-zero, so render
		// the sample for the phoneme.
   RenderSample(Y,&mem66);
finish:
   A = pitches[Y];
   mem44 = A;
   A-=(A>>2);
   mem38 = A;
			// reset the formant wave generators to keep them in
			// sync with the glottal pulse
   phase1 = 0;
   phase2 = 0;
   phase3 = 0;
  }
 }
}
Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded