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

     TMS5110 simulator (modified from TMS5220 by Jarek Burczynski)

     Written for MAME by Frank Palazzolo
     With help from Neill Corlett
     Additional tweaking by Aaron Giles

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

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

#include "driver.h"
#include "tms5110.h"


/* Pull in the ROM tables */
#include "tms5110r.c"


struct tms5110
{
	/* these contain data that describes the 64 bits FIFO */
	#define FIFO_SIZE 64
	UINT8 fifo[FIFO_SIZE];
	UINT8 fifo_head;
	UINT8 fifo_tail;
	UINT8 fifo_count;

	/* these contain global status bits */
	UINT8 PDC;
	UINT8 CTL_pins;
	UINT8 speaking_now;
	UINT8 speak_delay_frames;
	UINT8 talk_status;


	int (*M0_callback)(void);

	/* these contain data describing the current and previous voice frames */
	UINT16 old_energy;
	UINT16 old_pitch;
	int old_k[10];

	UINT16 new_energy;
	UINT16 new_pitch;
	int new_k[10];


	/* these are all used to contain the current state of the sound generation */
	UINT16 current_energy;
	UINT16 current_pitch;
	int current_k[10];

	UINT16 target_energy;
	UINT16 target_pitch;
	int target_k[10];

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

	int u[11];
	int x[10];

	int RNG;	/* the random noise generator configuration is: 1 + x + x^3 + x^4 + x^13 */
};


/* Static function prototypes */
static void parse_frame(struct tms5110 *tms);


#define DEBUG_5110	0


void *tms5110_create(void)
{
	struct tms5110 *tms;

	tms = malloc(sizeof(*tms));
	memset(tms, 0, sizeof(*tms));
	return tms;
}


void tms5110_destroy(void *chip)
{
	free(chip);
}




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

     tms5110_reset -- resets the TMS5110

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

void tms5110_reset_chip(void *chip)
{
	struct tms5110 *tms = chip;

    /* initialize the FIFO */
    memset(tms->fifo, 0, sizeof(tms->fifo));
    tms->fifo_head = tms->fifo_tail = tms->fifo_count = 0;

    /* initialize the chip state */
    tms->speaking_now = tms->speak_delay_frames = tms->talk_status = 0;
    tms->CTL_pins = 0;
	tms->RNG = 0xfff;

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

    /* initialize the sample generators */
    tms->interp_count = tms->sample_count = tms->pitch_count = 0;
    memset(tms->u, 0, sizeof(tms->u));
    memset(tms->x, 0, sizeof(tms->x));
}



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

     tms5110_set_M0_callback -- set M0 callback for the TMS5110

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

void tms5110_set_M0_callback(void *chip, int (*func)(void))
{
	struct tms5110 *tms = chip;
    tms->M0_callback = func;
}


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

     FIFO_data_write -- handle bit data write to the TMS5110 (as a result of toggling M0 pin)

******************************************************************************************/
static void FIFO_data_write(struct tms5110 *tms, int data)
{
	/* add this bit to the FIFO */
	if (tms->fifo_count < FIFO_SIZE)
	{
		tms->fifo[tms->fifo_tail] = (data&1); /* set bit to 1 or 0 */

		tms->fifo_tail = (tms->fifo_tail + 1) % FIFO_SIZE;
		tms->fifo_count++;

		if (DEBUG_5110) logerror("Added bit to FIFO (size=%2d)\n", tms->fifo_count);
	}
	else
	{
		if (DEBUG_5110) logerror("Ran out of room in the FIFO!\n");
	}
}

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

     extract_bits -- extract a specific number of bits from the FIFO

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

static int extract_bits(struct tms5110 *tms, int count)
{
    int val = 0;

    while (count--)
    {
        val = (val << 1) | (tms->fifo[tms->fifo_head] & 1);
        tms->fifo_count--;
        tms->fifo_head = (tms->fifo_head + 1) % FIFO_SIZE;
    }
    return val;
}

static void request_bits(struct tms5110 *tms, int no)
{
int i;
	for (i=0; i<no; i++)
	{
		if (tms->M0_callback)
		{
			int data = (*tms->M0_callback)();
			FIFO_data_write(tms, data);
		}
		else
			if (DEBUG_5110) logerror("-->ERROR: TMS5110 missing M0 callback function\n");
	}
}

static void perform_dummy_read(struct tms5110 *tms)
{
	if (tms->M0_callback)
	{
		int data = (*tms->M0_callback)();
	        if (DEBUG_5110) logerror("TMS5110 performing dummy read; value read = %1i\n", data&1);
	}
    	else
	        if (DEBUG_5110) logerror("-->ERROR: TMS5110 missing M0 callback function\n");
}

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

     tms5110_status_read -- read status from the TMS5110

        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.
                It goes inactive (low) when the stop code (Energy=1111) is processed, or
                immediately(?????? not TMS5110) by a RESET command.
        TMS5110 datasheets mention this is only available as a result of executing
                TEST TALK command.


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

int tms5110_status_read(void *chip)
{
	struct tms5110 *tms = chip;
    if (DEBUG_5110) logerror("Status read: TS=%d\n", tms->talk_status);

    return (tms->talk_status << 0); /*CTL1 = still talking ? */
}



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

     tms5110_ready_read -- returns the ready state of the TMS5110

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

int tms5110_ready_read(void *chip)
{
	struct tms5110 *tms = chip;
    return (tms->fifo_count < FIFO_SIZE-1);
}



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

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

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

void tms5110_process(void *chip, INT16 *buffer, unsigned int size)
{
	struct tms5110 *tms = chip;
    int buf_count=0;
    int i, interp_period;

    /* if we're not speaking, fill with nothingness */
    if (!tms->speaking_now)
        goto empty;

    /* if we're to speak, but haven't started */
    if (!tms->talk_status)
    {

    /* a "dummy read" is mentioned in the tms5200 datasheet */
    /* The Bagman speech roms data are organized in such a way that
    ** the bit at address 0 is NOT a speech data. The bit at address 1
    ** is the speech data. It seems that the tms5110 performs a dummy read
    ** just before it executes a SPEAK command.
    */
		perform_dummy_read(tms);

        /* clear out the new frame parameters (it will become old frame just before the first call to parse_frame() ) */
        tms->new_energy = 0;
        tms->new_pitch = 0;
        for (i = 0; i < 10; i++)
            tms->new_k[i] = 0;

        tms->talk_status = 1;
    }


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

        /* if we're ready for a new frame */
        if ((tms->interp_count == 0) && (tms->sample_count == 0))
        {

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


            /* if the old frame was a stop frame, exit and do not process any more frames */
            if (tms->old_energy == energytable[15])
            {
                /*if (DEBUG_5110) logerror("processing frame: stop frame\n");*/
                tms->target_energy = tms->current_energy = 0;
                tms->speaking_now = tms->talk_status = 0;
                tms->interp_count = tms->sample_count = tms->pitch_count = 0;
                goto empty;
            }


            /* Parse a new frame into the new_energy, new_pitch and new_k[] */
            parse_frame(tms);


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


            /* is this the stop (ramp down) frame? */
            if (tms->new_energy == energytable[15])
            {
                /*logerror("processing frame: ramp down\n");*/
                tms->target_energy = 0;
                tms->target_pitch = tms->old_pitch;
                for (i = 0; i < 10; i++)
                    tms->target_k[i] = tms->old_k[i];
            }
            else if ((tms->old_energy == 0) && (tms->new_energy != 0)) /* was the old frame a zero-energy frame? */
            {
                /* if so, and if the new frame is non-zero energy frame then the new parameters
                   should become our current and target parameters immediately,
                   i.e. we should NOT interpolate them slowly in.
                */

                /*logerror("processing non-zero energy frame after zero-energy frame\n");*/
                tms->target_energy = tms->new_energy;
                tms->target_pitch = tms->current_pitch = tms->new_pitch;
                for (i = 0; i < 10; i++)
                    tms->target_k[i] = tms->current_k[i] = tms->new_k[i];
            }
            else if ((tms->old_pitch == 0) && (tms->new_pitch != 0))    /* is this a change from unvoiced to voiced frame ? */
            {
                /* if so, then the new parameters should become our current and target parameters immediately,
                   i.e. we should NOT interpolate them slowly in.
                */
                /*if (DEBUG_5110) logerror("processing frame: UNVOICED->VOICED frame change\n");*/
                tms->target_energy = tms->new_energy;
                tms->target_pitch = tms->current_pitch = tms->new_pitch;
                for (i = 0; i < 10; i++)
                    tms->target_k[i] = tms->current_k[i] = tms->new_k[i];
            }
            else if ((tms->old_pitch != 0) && (tms->new_pitch == 0))    /* is this a change from voiced to unvoiced frame ? */
            {
                /* if so, then the new parameters should become our current and target parameters immediately,
                   i.e. we should NOT interpolate them slowly in.
                */
                /*if (DEBUG_5110) logerror("processing frame: VOICED->UNVOICED frame change\n");*/
                tms->target_energy = tms->new_energy;
                tms->target_pitch = tms->current_pitch = tms->new_pitch;
                for (i = 0; i < 10; i++)
                    tms->target_k[i] = tms->current_k[i] = tms->new_k[i];
            }
            else
            {
                /*logerror("processing frame: Normal\n");*/
                /*logerror("*** Energy = %d\n",current_energy);*/
                /*logerror("proc: %d %d\n",last_fbuf_head,fbuf_head);*/

                tms->target_energy = tms->new_energy;
                tms->target_pitch = tms->new_pitch;
                for (i = 0; i < 10; i++)
                    tms->target_k[i] = tms->new_k[i];
            }
        }
        else if (tms->interp_count == 0)
        {
            /* interpolate (update) values based on step values */
            /*logerror("\n");*/

            interp_period = tms->sample_count / 25;
            tms->current_energy += (tms->target_energy - tms->current_energy) / interp_coeff[interp_period];
            tms->current_pitch += (tms->target_pitch - tms->current_pitch) / interp_coeff[interp_period];

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

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





	/* calculate the output */

        if (tms->current_energy == 0)
        {
            /* generate silent samples here */
            current_val = 0x00;
        }
        else if (tms->old_pitch == 0)
        {
            int bitout, randbit;

            /* generate unvoiced samples here */
            if (tms->RNG&1)
                randbit = -64; /* according to the patent it is (either + or -) half of the maximum value in the chirp table */
            else
                randbit = 64;

            bitout = ((tms->RNG>>12)&1) ^
                     ((tms->RNG>>10)&1) ^
                     ((tms->RNG>> 9)&1) ^
                     ((tms->RNG>> 0)&1);
            tms->RNG >>= 1;
            tms->RNG |= (bitout<<12);

            current_val = (randbit * tms->current_energy) / 256;
        }
        else
        {
            /* generate voiced samples here */
            if (tms->pitch_count < sizeof(chirptable))
                current_val = (chirptable[tms->pitch_count] * tms->current_energy) / 256;
            else
                current_val = 0x00;
        }


        /* Lattice filter here */

        tms->u[10] = current_val;

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

        tms->x[0] = tms->u[0];


        /* clipping, just like the chip */

        if (tms->u[0] > 511)
            buffer[buf_count] = 127<<8;
        else if (tms->u[0] < -512)
            buffer[buf_count] = -128<<8;
        else
            buffer[buf_count] = tms->u[0] << 6;


        /* Update all counts */

        tms->sample_count = (tms->sample_count + 1) % 200;

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

        tms->interp_count = (tms->interp_count + 1) % 25;

        buf_count++;
        size--;
    }

empty:

    while (size > 0)
    {
        tms->sample_count = (tms->sample_count + 1) % 200;
        tms->interp_count = (tms->interp_count + 1) % 25;

        buffer[buf_count] = 0x00;
        buf_count++;
        size--;
    }
}




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

     CTL_set -- set CTL pins named CTL1, CTL2, CTL4 and CTL8

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

void tms5110_CTL_set(void *chip, int data)
{
	struct tms5110 *tms = chip;
	tms->CTL_pins = data & 0xf;
}


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

     PDC_set -- set Processor Data Clock. Execute CTL_pins command on hi-lo transition.

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

void tms5110_PDC_set(void *chip, int data)
{
	struct tms5110 *tms = chip;
	if (tms->PDC != (data & 0x1) )
	{
		tms->PDC = data & 0x1;
		if (tms->PDC == 0) /* toggling 1->0 processes command on CTL_pins */
		{
			/* the only real commands we handle now are SPEAK and RESET */

			switch (tms->CTL_pins & 0xe) /*CTL1 - don't care*/
			{
			case TMS5110_CMD_SPEAK:
				tms->speaking_now = 1;

				//should FIFO be cleared now ?????

				break;

        		case TMS5110_CMD_RESET:
				tms->speaking_now = 0;
				tms->talk_status = 0;
				break;

			default:
				break;
			}
		}
	}
}



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

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

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

static void parse_frame(struct tms5110 *tms)
{
    int bits, indx, i, rep_flag;


    /* count the total number of bits available */
    bits = tms->fifo_count;


    /* attempt to extract the energy index */
    bits -= 4;
    if (bits < 0)
    {
        request_bits( tms,-bits ); /* toggle M0 to receive needed bits */
	bits = 0;
    }
    indx = extract_bits(tms,4);
    tms->new_energy = energytable[indx];


    /* if the energy index is 0 or 15, we're done */

    if ((indx == 0) || (indx == 15))
    {
        if (DEBUG_5110) logerror("  (4-bit energy=%d frame)\n",tms->new_energy);

	/* clear the k's */
        if (indx == 0)
        {
            for (i = 0; i < 10; i++)
                tms->new_k[i] = 0;
	}

        /* clear fifo if stop frame encountered */
        if (indx == 15)
        {
            if (DEBUG_5110) logerror("  (4-bit energy=%d STOP frame)\n",tms->new_energy);
            tms->fifo_head = tms->fifo_tail = tms->fifo_count = 0;
            //speaking_now = talk_status = 0;
        }
        return;
    }


    /* attempt to extract the repeat flag */
    bits -= 1;
    if (bits < 0)
    {
        request_bits( tms,-bits ); /* toggle M0 to receive needed bits */
	bits = 0;
    }
    rep_flag = extract_bits(tms,1);

    /* attempt to extract the pitch */
    bits -= 5;
    if (bits < 0)
    {
        request_bits( tms,-bits ); /* toggle M0 to receive needed bits */
        bits = 0;
    }
    indx = extract_bits(tms,5);
    tms->new_pitch = pitchtable[indx];


    /* if this is a repeat frame, just copy the k's */
    if (rep_flag)
    {
	//actually, we do nothing because the k's were already loaded (on parsing the previous frame)

        if (DEBUG_5110) logerror("  (10-bit energy=%d pitch=%d rep=%d frame)\n", tms->new_energy, tms->new_pitch, rep_flag);
        return;
    }


    /* if the pitch index was zero, we need 4 k's */
    if (indx == 0)
    {
        /* attempt to extract 4 K's */
        bits -= 18;
        if (bits < 0)
        {
		request_bits( tms,-bits ); /* toggle M0 to receive needed bits */
		bits = 0;
        }
        tms->new_k[0] = k1table[extract_bits(tms,5)];
        tms->new_k[1] = k2table[extract_bits(tms,5)];
        tms->new_k[2] = k3table[extract_bits(tms,4)];
        tms->new_k[3] = k4table[extract_bits(tms,4)];

	/* and clear the rest of the new_k[] */
        for (i = 4; i < 10; i++)
            tms->new_k[i] = 0;

        if (DEBUG_5110) logerror("  (28-bit energy=%d pitch=%d rep=%d 4K frame)\n", tms->new_energy, tms->new_pitch, rep_flag);
        return;
    }

    /* else we need 10 K's */
    bits -= 39;
    if (bits < 0)
    {
	        request_bits( tms,-bits ); /* toggle M0 to receive needed bits */
		bits = 0;
    }
    tms->new_k[0] = k1table[extract_bits(tms,5)];
    tms->new_k[1] = k2table[extract_bits(tms,5)];
    tms->new_k[2] = k3table[extract_bits(tms,4)];
    tms->new_k[3] = k4table[extract_bits(tms,4)];
    tms->new_k[4] = k5table[extract_bits(tms,4)];
    tms->new_k[5] = k6table[extract_bits(tms,4)];
    tms->new_k[6] = k7table[extract_bits(tms,4)];
    tms->new_k[7] = k8table[extract_bits(tms,3)];
    tms->new_k[8] = k9table[extract_bits(tms,3)];
    tms->new_k[9] = k10table[extract_bits(tms,3)];

    if (DEBUG_5110) logerror("  (49-bit energy=%d pitch=%d rep=%d 10K frame)\n", tms->new_energy, tms->new_pitch, rep_flag);

}



#if 0
/*This is an example word TEN taken from the TMS5110A datasheet*/
static unsigned int example_word_TEN[619]={
/* 1*/1,0,0,0,	0,	0,0,0,0,0,	1,1,0,0,0,	0,0,0,1,0,	0,1,1,1,	0,1,0,1,
/* 2*/1,0,0,0,	0,	0,0,0,0,0,	1,0,0,1,0,	0,0,1,1,0,	0,0,1,1,	0,1,0,1,
/* 3*/1,1,0,0,	0,	1,0,0,0,0,	1,0,1,0,0,	0,1,0,1,0,	0,1,0,0,	1,0,1,0,	1,0,0,0,	1,0,0,1,	0,1,0,1,	0,0,1,	0,1,0,	0,1,1,
/* 4*/1,1,1,0,	0,	0,1,1,1,1,	1,0,1,0,1,	0,1,1,1,0,	0,1,0,1,	0,1,1,1,	0,1,1,1,	1,0,1,1,	1,0,1,0,	0,1,1,	0,1,0,	0,1,1,
/* 5*/1,1,1,0,	0,	1,0,0,0,0,	1,0,1,0,0,	0,1,1,1,0,	0,1,0,1,	1,0,1,0,	1,0,0,0,	1,1,0,0,	1,0,1,1,	1,0,0,	0,1,0,	0,1,1,
/* 6*/1,1,1,0,	0,	1,0,0,0,1,	1,0,1,0,1,	0,1,1,0,1,	0,1,1,0,	0,1,1,1,	0,1,1,1,	1,0,1,0,	1,0,1,0,	1,1,0,	0,0,1,	1,0,0,
/* 7*/1,1,1,0,	0,	1,0,0,1,0,	1,0,1,1,1,	0,1,1,1,0,	0,1,1,1,	0,1,1,1,	0,1,0,1,	0,1,1,0,	1,0,0,1,	1,1,0,	0,1,0,	0,1,1,
/* 8*/1,1,1,0,	1,	1,0,1,0,1,
/* 9*/1,1,1,0,	0,	1,1,0,0,1,	1,0,1,1,1,	0,1,0,1,1,	1,0,1,1,	0,1,1,1,	0,1,0,0,	1,0,0,0,	1,0,0,0,	1,1,0,	0,1,1,	0,1,1,
/*10*/1,1,0,1,	0,	1,1,0,1,0,	1,0,1,0,1,	0,1,1,0,1,	1,0,1,1,	0,1,0,1,	0,1,0,0,	1,0,0,0,	1,0,1,0,	1,1,0,	0,1,0,	1,0,0,
/*11*/1,0,1,1,	0,	1,1,0,1,1,	1,0,0,1,1,	1,0,0,1,0,	0,1,1,0,	0,0,1,1,	0,1,0,1,	1,0,0,1,	1,0,1,0,	1,0,0,	0,1,1,	0,1,1,
/*12*/1,0,0,0,	0,	1,1,1,0,0,	1,0,0,1,1,	0,0,1,1,0,	0,1,0,0,	0,1,1,0,	1,1,0,0,	0,1,0,1,	1,0,0,0,	1,0,0,	0,1,0,	1,0,1,
/*13*/0,1,1,1,	1,	1,1,1,0,1,
/*14*/0,1,1,1,	0,	1,1,1,1,0,	1,0,0,1,1,	0,0,1,1,1,	0,1,0,1,	0,1,0,1,	1,1,0,0,	0,1,1,1,	1,0,0,0,	1,0,0,	0,1,0,	1,0,1,
/*15*/0,1,1,0,	0,	1,1,1,1,0,	1,0,1,0,1,	0,0,1,1,0,	0,1,0,0,	0,0,1,1,	1,1,0,0,	1,0,0,1,	0,1,1,1,	1,0,1,	0,1,0,	1,0,1,
/*16*/1,1,1,1
};
#endif

