/**********************************************************************************************

     TMS5220 simulator

     Written for MAME by Frank Palazzolo
     With help from Neill Corlett
     Additional tweaking by Aaron Giles
     Speech ROM support and a few bug fixes by R Nabet

***********************************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <memory.h>
#include <windows.h>

#include "..\tiemul.h"
#include "tms5220.h"

/* Pull in the ROM tables */
#include "tms5220r.cpp"

/* these contain data that describes the 128-bit data FIFO */
#define FIFO_SIZE 16
static unsigned char fifo[FIFO_SIZE];
static int fifo_head;
static int fifo_tail;
static int fifo_count;
static int bits_taken;

// extra debug
//#define DEBUG_5220
#define debug_write(x) OutputDebugString(x); OutputDebugString("\n");

/* these contain global status bits */
/* R Nabet : speak_external is only set when a speak external command is going on.
 tms5220_speaking is set whenever a speak or speak external command is going on...
Note that we really need to invoke tms5220_process and play sample only when tms5220_speaking is
true.  Else, we can play nothing as well, which is a speed-up...*/
static int tms5220_speaking;
static int first_frame;
static int irq_pin;

int talk_status;
int buffer_low;
int buffer_empty;
int speak_external;
extern int talk_status;
extern int buffer_low;
extern int buffer_empty;
extern int speak_external;

static void (*irq_func)(void);


/* these contain data describing the current and previous voice frames */
static unsigned short old_energy = 0;
static unsigned short old_pitch = 0;
static int old_k[10] = {0,0,0,0,0,0,0,0,0,0};

static unsigned short new_energy = 0;
static unsigned short new_pitch = 0;
static int new_k[10] = {0,0,0,0,0,0,0,0,0,0};


/* these are all used to contain the current state of the sound generation */
static unsigned short current_energy = 0;
static unsigned short current_pitch = 0;
static int current_k[10] = {0,0,0,0,0,0,0,0,0,0};

static unsigned short target_energy = 0;
static unsigned short target_pitch = 0;
static int target_k[10] = {0,0,0,0,0,0,0,0,0,0};

static int interp_count = 0;       /* number of interp periods (0-7) */
static int sample_count = 0;       /* sample number within interp (0-24) */
static int pitch_count = 0;

static int u[11] = {0,0,0,0,0,0,0,0,0,0,0};
static int x[10] = {0,0,0,0,0,0,0,0,0,0};

static int randbit = 0;


/* Static function prototypes */
static int tms5220_status_read (void);
static void process_command (unsigned char cmd);
static int extract_bits (int count);
static int parse_frame (int removeit);
static void check_buffer_low (void);
static void cause_interrupt (void);

/*#define DEBUG_5220*/
#ifdef DEBUG_5220
    static FILE *f;
#endif

// R Nabet : These have been added to emulate speech Roms
unsigned char *tms5220_speechrom = NULL;
unsigned long tms5220_speechROMlen = 0; // lenght of data pointed by tms5220_speechrom, from 0 to 2^18
// 18 bit address
static unsigned long speechROMaddr;
static Byte data_register;
static int external_read_from_data;

/**********************************************************************************************

     tms5220_reset -- resets the TMS5220

***********************************************************************************************/

void tms5220_reset (void)
{
    /* initialize the FIFO */
    //memset (fifo, 0, sizeof (fifo));
    fifo_head = fifo_tail = fifo_count = bits_taken = 0;

    /* an interrupt might occur or not occur, I am not quite sure... */
    if (! buffer_empty) // (! buffer_low) implies (! buffer_empty)
    {
      buffer_empty = 1;
      cause_interrupt ();
    }

    buffer_low = 1;

    /* initialize the chip state */
    tms5220_speaking = speak_external = talk_status = irq_pin = 0;

    external_read_from_data = false;

    speechROMaddr = 0;  // any 18-bit value, actually

    /* initialize the energy/pitch/k states */
    old_energy = new_energy = current_energy = target_energy = 0;
    old_pitch = new_pitch = current_pitch = target_pitch = 0;
    memset (old_k, 0, sizeof (old_k));
    memset (new_k, 0, sizeof (new_k));
    memset (current_k, 0, sizeof (current_k));
    memset (target_k, 0, sizeof (target_k));

    /* initialize the sample generators */
    interp_count = sample_count = pitch_count = 0;
    randbit = 0;
    memset (u, 0, sizeof (u));
    memset (x, 0, sizeof (x));

    #ifdef DEBUG_5220
//        f = fopen ("tms.log", "w");
    #endif
}


/**********************************************************************************************

     tms5220_set_irq -- executes an interrupt

***********************************************************************************************/

void tms5220_set_irq (void (*func)(void))
{
    irq_func = func;
}


/**********************************************************************************************

     tms5220_data_write -- handle a write to the TMS5220

***********************************************************************************************/

void tms5220_data_write (int data)
{
  /* R Nabet : we parse the command at once.  It is necessary for such commands as read. */
  if (! speak_external)
    process_command (data);
  else
  { /* speak external is going on -> write to FIFO */

    /* add this byte to the FIFO */
    if (fifo_count < FIFO_SIZE)
    {
        fifo[fifo_tail] = data;
        fifo_tail = (fifo_tail + 1) % FIFO_SIZE;
        fifo_count++;

        #ifdef DEBUG_5220
            debug_write("Added byte to FIFO");
        #endif
    }
    else
    {
        #ifdef DEBUG_5220
            debug_write("Ran out of room in the FIFO!");
        #endif
    }

    /* update the buffer low state */
    check_buffer_low ();
}
}

/**********************************************************************************************

     tms5220_read -- handle a read from the TMS5220

***********************************************************************************************/

int tms5220_read (void)
{
  if (external_read_from_data)
  { // if last command was read, return data register
    external_read_from_data = false;
    return(data_register);
  }
  else
    // read status
    return (tms5220_status_read());
}


/**********************************************************************************************

     tms5220_status_read -- read status from the TMS5220

	  From the data sheet:
        bit 0 = TS - Talk Status is active (high) when the VSP is processing speech data.
                Talk Status goes active at the initiation of a Speak command or after nine
                bytes of data are loaded into the FIFO following a Speak External command. It
                goes inactive (low) when the stop code (Energy=1111) is processed, or
                immediately by a buffer empty condition or a reset command.
        bit 1 = BL - Buffer Low is active (high) when the FIFO buffer is more than half empty.
                Buffer Low is set when the "Last-In" byte is shifted down past the half-full
                boundary of the stack. Buffer Low is cleared when data is loaded to the stack
                so that the "Last-In" byte lies above the half-full boundary and becomes the
                ninth data byte of the stack.
        bit 2 = BE - Buffer Empty is active (high) when the FIFO buffer has run out of data
                while executing a Speak External command. Buffer Empty is set when the last bit
                of the "Last-In" byte is shifted out to the Synthesis Section. This causes
                Talk Status to be cleared. Speed is terminated at some abnormal point and the
                Speak External command execution is terminated.

***********************************************************************************************/

static int tms5220_status_read (void)
{
    /* clear the interrupt pin */
    irq_pin = 0;

    #ifdef DEBUG_5220
        //if (f) debug_write("Status read: TS=%d BL=%d BE=%d\n", talk_status, buffer_low, buffer_empty);
    #endif

    return (talk_status << 7) | (buffer_low << 6) | (buffer_empty << 5);
}



/**********************************************************************************************

     tms5220_ready_read -- returns the ready state of the TMS5220

***********************************************************************************************/

int tms5220_ready_read (void)
{
    return (fifo_count < FIFO_SIZE-1);
}



/**********************************************************************************************

     tms5220_int_read -- returns the interrupt state of the TMS5220

***********************************************************************************************/

int tms5220_int_read (void)
{
    return irq_pin;
}


/**********************************************************************************************

     process_command -- process a command

***********************************************************************************************/

static void process_command (unsigned char cmd)
{
  // parse the command
  switch (cmd & 0x70)
  {
  case 0x10 : // read byte
    if (speechROMaddr >= tms5220_speechROMlen)
      data_register = 0;
    else
      data_register = tms5220_speechrom[speechROMaddr];

    speechROMaddr++;

    external_read_from_data = true;

    break;
  case 0x30 : // read and branch
    #ifdef DEBUG_5220
      //if more than one speech ROM, this is an error
        debug_write("read and branch called");
    #endif
    external_read_from_data = false;
    speechROMaddr = (speechROMaddr & 0x3c000UL) |
                      (((((unsigned long) tms5220_speechrom[speechROMaddr&0x7fff]) << 8) |
                      tms5220_speechrom[(speechROMaddr+1)&0x7fff]) & 0x3fffUL);
    break;
  case 0x40 : // load address
    // R Nabet : I think emulation here could be wrong, cf tms5220 tech spec.
    // But it works fine, as far as I know.
    speechROMaddr = (speechROMaddr >> 4) | (((unsigned long) (cmd & 0xf)) << 16);
    break;
  case 0x50 : // speak
    tms5220_speaking = 1;
    speak_external = 0;
    first_frame = 1;
    talk_status = 1;  // start immediately
	debug_write("Speak");
    break;
  case 0x60 : // speak external
	debug_write("Speak External");
    
	tms5220_speaking = speak_external = 1;

    external_read_from_data = false;

    /* clear FIFO */
    fifo_head = fifo_tail = fifo_count = bits_taken = 0;
    buffer_low = 1;
    /* according to the datasheet, this will cause an interrupt due to a BE condition */
    if (! buffer_empty)
    {
      buffer_empty = 1;
      cause_interrupt ();
    }

    talk_status = 0;  // wait to have 8 bytes in buffer before starting
    break;
  case 0x70 : // reset
	debug_write("Speech Reset");
    tms5220_reset();
    break;
  }
}


/**********************************************************************************************

     tms5220_process -- fill the buffer with a specific number of samples

***********************************************************************************************/

void tms5220_process(unsigned char *buffer, unsigned int size)
{
	int buf_count = 0;
    int i, interp_period;
	int value;

    /* if there's nothing to do, bail */
    if (! size)
    {
		return;
	}

    /* if we're empty and still not speaking, fill with nothingness */
    if (! tms5220_speaking)
        goto empty;

    /* if we're to speak, but haven't started, wait for the 8th byte */
    if (! talk_status /*&& speak_external*/)
    {
        /* R Nabet : the correct value is 8, not 9 */
        if /*(fifo_count < 8)*/(buffer_low) /* the two variants are equivalent */
           goto empty;

        /* parse but don't remove the first frame, and set the status to 1 */
        //parse_frame (0);
        talk_status = 1;
        first_frame = 1;
        buffer_empty = 0;
    }

    /* parse first frame with speak command */
    if (first_frame)
        parse_frame (0);

    /* loop until the buffer is full or we've stopped speaking */
    while ((size > 0) && tms5220_speaking)
    {
        int current_val;

        /* if we're ready for a new frame */
        if ((interp_count == 0) && (sample_count == 0))
        {
            /* Parse a new frame */
            if (!parse_frame (1))
                break;

            /* Set old target as new start of frame */
            current_energy = old_energy;
            current_pitch = old_pitch;
            for (i = 0; i < 10; i++)
                current_k[i] = old_k[i];

            /* is this a zero energy frame? */
            if (current_energy == 0)
            {
				#ifdef DEBUG_5220
					debug_write("processing frame: zero energy");
				#endif
                target_energy = 0;
                target_pitch = current_pitch;
                for (i = 0; i < 10; i++)
                    target_k[i] = current_k[i];
            }

            /* is this a stop frame? */
            else if (current_energy == (energytable[15] >> 6))
            {
				#ifdef DEBUG_5220
					debug_write("processing frame: stop frame");
				#endif
                current_energy = energytable[0] >> 6;
                target_energy = current_energy;
                speak_external = tms5220_speaking = talk_status = 0;
                interp_count = sample_count = pitch_count = 0;

                /* generate an interrupt if necessary */
                cause_interrupt ();

                /* try to fetch commands again */
                /*goto tryagain;*/

                goto empty;
            }
            else
            {
                /* is this the ramp down frame? */
                if (new_energy == (energytable[15] >> 6))
                {
					#ifdef DEBUG_5220
	                    debug_write("processing frame: ramp down");
					#endif
                    target_energy = 0;
                    target_pitch = current_pitch;
                    for (i = 0; i < 10; i++)
                        target_k[i] = current_k[i];
                }
                /* Reset the step size */
                else
                {
					#ifdef DEBUG_5220
						debug_write("processing frame: Normal");
						//debug_write("*** Energy = %d",current_energy);
						//debug_write("proc: %d %d",last_fbuf_head,fbuf_head);
					#endif
                    target_energy = new_energy;
                    target_pitch = new_pitch;

                    for (i = 0; i < 4; i++)
                        target_k[i] = new_k[i];
                    if (current_pitch == 0)
                        for (i = 4; i < 10; i++)
                        {
                            target_k[i] = current_k[i] = 0;
                        }
                    else
                        for (i = 4; i < 10; i++)
                            target_k[i] = new_k[i];
                }
            }
        }
        else if (interp_count == 0)
        {
            /* Update values based on step values */
            /*printf("\n");*/

            interp_period = sample_count / 25;
            current_energy += (target_energy - current_energy) / interp_coeff[interp_period];
            if (old_pitch != 0)
                current_pitch += (target_pitch - current_pitch) / interp_coeff[interp_period];

            /*printf("*** Energy = %d\n",current_energy);*/

            for (i = 0; i < 10; i++)
            {
                current_k[i] += ((long) target_k[i] - current_k[i]) / interp_coeff[interp_period];
            }
        }

        if (old_energy == 0)
        {
            /* generate silent samples here */
            current_val = 0x00;
        }
        else if (old_pitch == 0)
        {
            /* generate unvoiced samples here */
            randbit = (rand () % 2) * 2 - 1;
            current_val = ((long) randbit * current_energy) / 4;
        }
        else
        {
            /* generate voiced samples here */
            if (pitch_count < sizeof (chirptable))
                current_val = ((long) chirptable[pitch_count] * current_energy) / 256;
            else
                current_val = 0x00;
        }

        /* Lattice filter here */

        u[10] = current_val;

        for (i = 9; i >= 0; i--)
        {
            u[i] = u[i+1] - (((long) current_k[i] * x[i]) / 32768L);
        }
        for (i = 9; i >= 1; i--)
        {
            x[i] = x[i-1] + (((long) current_k[i-1] * u[i-1]) / 32768L);
        }

        x[0] = u[0];

        /* clipping, just like the chip */
#if 0
        if (u[0] > 511)
            value = 127;
        else if (u[0] < -512)
            value = -127;
        else
            value = u[0] >> 2;

		// make unsigned
		if (value < 0)
			value *= -1;
		buffer[buf_count] = value*2;
//		buffer[buf_count]=value;
#else
		// This causes a DC offset but sounds better
		value=u[0];
		//if (value < 0) value *= -1;
		value>>=2;
		if (value > 127) value=127;
		if (value < -127) value=-127;
		value+=127;
		buffer[buf_count]=value;
#endif

		
        /* Update all counts */
        size--;
        sample_count = (sample_count + 1) % 200;

        if (current_pitch != 0)
            pitch_count = (pitch_count + 1) % current_pitch;
        else
            pitch_count = 0;

        interp_count = (interp_count + 1) % 25;
        buf_count++;
    }

empty:

    while (size > 0)
    {
        buffer[buf_count] = 0x80;	// maintain the DC offset to reduce clicking
        buf_count++;
        size--;
    }
}







/**********************************************************************************************

     extract_bits -- extract a specific number of bits from the FIFO or speech ROM

***********************************************************************************************/

static int extract_bits (int count)
{
    int val = 0;

  if (speak_external)
  {
    while (count--)
    {
        val = (val << 1) | ((fifo[fifo_head] >> bits_taken) & 1);
        bits_taken++;
        if (bits_taken >= 8)
        {
            fifo_count--;
            fifo_head = (fifo_head + 1) % FIFO_SIZE;
            bits_taken = 0;
        }
    }
  }
  else
  {
    // Maybe this could be optimised, since we don't need to reverse bit order like with the FIFO.
    while (count--)
    {
        val = (val << 1);
        if ((tms5220_speechrom[speechROMaddr&0x7fff] << bits_taken) & 0x80)
          val |= 1;
        bits_taken++;
        if (bits_taken >= 8)
        {
            speechROMaddr++;
            bits_taken = 0;
        }
    }
  }

    return val;
}



/**********************************************************************************************

     parse_frame -- parse a new frame's worth of data; returns 0 if not enough bits in buffer

***********************************************************************************************/

static int parse_frame (int removeit)
{
    int old_head, old_taken, old_count;
    int bits, indx, i, rep_flag;
    unsigned long old_addr;

    /* remember previous frame */
    old_energy = new_energy;
    old_pitch = new_pitch;
    for (i = 0; i < 10; i++)
        old_k[i] = new_k[i];

    /* clear out the new frame */
    new_energy = 0;
    new_pitch = 0;
    for (i = 0; i < 10; i++)
        new_k[i] = 0;

    /* if the previous frame was a stop frame, don't do anything */
    if (old_energy == (energytable[15] >> 6))
        return 1;

    /* remember the original FIFO counts and speech ROM addr, for the 'removeit == FALSE' mode */
    old_count = fifo_count;
    old_head = fifo_head;
    old_taken = bits_taken;
    old_addr = speechROMaddr;

    /* count the total number of bits available */
    bits = fifo_count * 8 - bits_taken;

    /* attempt to extract the energy index */
    bits -= 4;
    if (speak_external)
    if (bits < 0)
        goto ranout;
    indx = extract_bits (4);
    new_energy = energytable[indx] >> 6;

	/* if the index is 0 or 15, we're done */
	if (indx == 0 || indx == 15)
	{
		#ifdef DEBUG_5220
			//if (f) debug_write(f, "  (4-bit energy=%d frame)\n",new_energy);
		#endif

		/* clear fifo if stop frame encountered */
		if (indx == 15)
		{
			fifo_head = fifo_tail = fifo_count = bits_taken = 0;
			removeit = 1;
		}
		goto done;
	}

    /* attempt to extract the repeat flag */
    bits -= 1;
    if (speak_external)
    if (bits < 0)
        goto ranout;
    rep_flag = extract_bits (1);

    /* attempt to extract the pitch */
    bits -= 6;
    if (speak_external)
    if (bits < 0)
        goto ranout;
    indx = extract_bits (6);
    new_pitch = pitchtable[indx] / 256;

    /* if this is a repeat frame, just copy the k's */
    if (rep_flag)
    {
        for (i = 0; i < 10; i++)
            new_k[i] = old_k[i];

        #ifdef DEBUG_5220
            //if (f) debug_write("  (11-bit energy=%d pitch=%d rep=%d frame)\n", new_energy, new_pitch, rep_flag);
        #endif
        goto done;
    }

    /* if the pitch index was zero, we need 4 k's */
    if (indx == 0)
    {
        /* attempt to extract 4 K's */
        bits -= 18;
        if (speak_external)
        if (bits < 0)
            goto ranout;
        new_k[0] = k1table[extract_bits (5)];
        new_k[1] = k2table[extract_bits (5)];
        new_k[2] = k3table[extract_bits (4)];
        new_k[3] = k4table[extract_bits (4)];

        #ifdef DEBUG_5220
            //if (f) debug_write("  (29-bit energy=%d pitch=%d rep=%d 4K frame)\n", new_energy, new_pitch, rep_flag);
        #endif
        goto done;
    }

    /* else we need 10 K's */
    bits -= 39;
    if (speak_external)
    if (bits < 0)
        goto ranout;
    new_k[0] = k1table[extract_bits (5)];
    new_k[1] = k2table[extract_bits (5)];
    new_k[2] = k3table[extract_bits (4)];
    new_k[3] = k4table[extract_bits (4)];
    new_k[4] = k5table[extract_bits (4)];
    new_k[5] = k6table[extract_bits (4)];
    new_k[6] = k7table[extract_bits (4)];
    new_k[7] = k8table[extract_bits (3)];
    new_k[8] = k9table[extract_bits (3)];
    new_k[9] = k10table[extract_bits (3)];

    #ifdef DEBUG_5220
        //if (f) debug_write("  (50-bit energy=%d pitch=%d rep=%d 10K frame)\n", new_energy, new_pitch, rep_flag);
    #endif

done:

    #ifdef DEBUG_5220
        debug_write("Parsed a frame successfully");
    #endif

    /* if we're not to remove this one, restore the FIFO */
    if (!removeit)
    {
        fifo_count = old_count;
        fifo_head = old_head;
        bits_taken = old_taken;
        speechROMaddr = old_addr;
    }

    /* update the buffer_low status */
    check_buffer_low ();
    return 1;

ranout:

    debug_write("Ran out of speech bits on a parse!");

    /* this is an error condition; mark the buffer empty and turn off speaking */
    buffer_empty = 1;
    talk_status = speak_external = tms5220_speaking = 0;
    fifo_count = fifo_head = fifo_tail = 0;

    external_read_from_data = false;

    /* generate an interrupt if necessary */
    cause_interrupt ();
    return 0;
}



/**********************************************************************************************

     check_buffer_low -- check to see if the buffer low flag should be on or off

***********************************************************************************************/

static void check_buffer_low (void)
{
    /* did we just become low? */
    if (fifo_count < 8)
    {
        /* generate an interrupt if necessary */
        if (! buffer_low)
        {
            cause_interrupt ();
            buffer_low = 1;
        }

        #ifdef DEBUG_5220
            debug_write("Buffer low set");
        #endif
    }

    /* did we just become full? */
    else
    {
        buffer_low = 0;

        #ifdef DEBUG_5220
            debug_write("Buffer low cleared");
        #endif
    }
}



/**********************************************************************************************

     cause_interrupt -- generate an interrupt

***********************************************************************************************/

static void cause_interrupt (void)
{
    irq_pin = 1;
    if (irq_func) irq_func ();
}
