/**********************************************************************************************
 *
 *  Streaming singe channel ADPCM core for the ES8712 chip
 *  Chip is branded by Excellent Systems, probably OEM'd.
 *
 *  Samples are currently looped, but whether they should and how, is unknown.
 *  Interface to the chip is also not 100% clear.
 *  Should there be any status signals signifying busy, end of sample - etc?
 *
 *  Heavily borrowed from the OKI M6295 source
 *
 **********************************************************************************************/


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

#include "driver.h"
#include "state.h"
#include "es8712.h"

#define MAX_SAMPLE_CHUNK	10000

#define FRAC_BITS			14
#define FRAC_ONE			(1 << FRAC_BITS)
#define FRAC_MASK			(FRAC_ONE - 1)


/* struct describing a playing ADPCM chip */
struct es8712
{
	UINT8 playing;			/* 1 if we're actively playing */

	UINT32 base_offset;		/* pointer to the base memory location */
	UINT32 sample;			/* current sample number */
	UINT32 count;			/* total samples to play */

	UINT32 signal;			/* current ADPCM signal */
	UINT32 step;			/* current ADPCM step */

	INT16 last_sample;		/* last sample output */
	INT16 curr_sample;		/* current sample target */
	UINT32 source_step;		/* step value for frequency conversion */
	UINT32 source_pos;		/* current fractional position */

	UINT32 start;			/* starting address for the next loop */
	UINT32 end;				/* ending address for the next loop */
	UINT8  repeat;			/* Repeat current sample when 1 */

	INT32 bank_offset;
	UINT8 *region_base;		/* pointer to the base of the region */
	sound_stream *stream;	/* which stream are we playing on? */
};

/* step size index shift table */
static const int index_shift[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };

/* lookup table for the precomputed difference */
static int diff_lookup[49*16];

/* useful interfaces */
const struct ES8712interface es8712_interface_region_1 = { REGION_SOUND1 };
const struct ES8712interface es8712_interface_region_2 = { REGION_SOUND2 };
const struct ES8712interface es8712_interface_region_3 = { REGION_SOUND3 };
const struct ES8712interface es8712_interface_region_4 = { REGION_SOUND4 };


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

     compute_tables -- compute the difference tables

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

static void compute_tables(void)
{
	/* nibble to bit map */
	static const int nbl2bit[16][4] =
	{
		{ 1, 0, 0, 0}, { 1, 0, 0, 1}, { 1, 0, 1, 0}, { 1, 0, 1, 1},
		{ 1, 1, 0, 0}, { 1, 1, 0, 1}, { 1, 1, 1, 0}, { 1, 1, 1, 1},
		{-1, 0, 0, 0}, {-1, 0, 0, 1}, {-1, 0, 1, 0}, {-1, 0, 1, 1},
		{-1, 1, 0, 0}, {-1, 1, 0, 1}, {-1, 1, 1, 0}, {-1, 1, 1, 1}
	};

	int step, nib;

	/* loop over all possible steps */
	for (step = 0; step <= 48; step++)
	{
		/* compute the step value */
		int stepval = floor(16.0 * pow(11.0 / 10.0, (double)step));

		/* loop over all nibbles and compute the difference */
		for (nib = 0; nib < 16; nib++)
		{
			diff_lookup[step*16 + nib] = nbl2bit[nib][0] *
				(stepval   * nbl2bit[nib][1] +
				 stepval/2 * nbl2bit[nib][2] +
				 stepval/4 * nbl2bit[nib][3] +
				 stepval/8);
		}
	}
}



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

    generate_adpcm -- general ADPCM decoding routine

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

static void generate_adpcm(struct es8712 *chip, INT16 *buffer, int samples)
{
	/* if this chip is active */
	if (chip->playing)
	{
		UINT8 *base = chip->region_base + chip->bank_offset + chip->base_offset;
		int sample = chip->sample;
		int signal = chip->signal;
		int count = chip->count;
		int step = chip->step;
		int val;

		/* loop while we still have samples to generate */
		while (samples)
		{
			/* compute the new amplitude and update the current step */
			val = base[sample / 2] >> (((sample & 1) << 2) ^ 4);
			signal += diff_lookup[step * 16 + (val & 15)];

			/* clamp to the maximum */
			if (signal > 2047)
				signal = 2047;
			else if (signal < -2048)
				signal = -2048;

			/* adjust the step size and clamp */
			step += index_shift[val & 7];
			if (step > 48)
				step = 48;
			else if (step < 0)
				step = 0;

			/* output to the buffer */
			*buffer++ = signal * 16;
			samples--;

			/* next! */
			if (++sample >= count)
			{
				if (chip->repeat)
				{
					sample = 0;
					signal = -2;
					step = 0;
				}
				else
				{
					chip->playing = 0;
					break;
				}
			}
		}

		/* update the parameters */
		chip->sample = sample;
		chip->signal = signal;
		chip->step = step;
	}

	/* fill the rest with silence */
	while (samples--)
		*buffer++ = 0;
}


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

    es8712_update -- update the sound chip so that it is in sync with CPU execution

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

static void es8712_update(void *param, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
	struct es8712 *chip = param;

	INT16 sample_data[MAX_SAMPLE_CHUNK], *curr_data = sample_data;
	INT16 prev = chip->last_sample, curr = chip->curr_sample;
	UINT32 final_pos;
	UINT32 new_samples;
	stream_sample_t *buffer = outputs[0];
	int length = samples;

	memset(outputs[0], 0, samples * sizeof(*outputs[0]));


	/* finish off the current sample */
	if (chip->source_pos > 0)
	{
		/* interpolate */
		while (length > 0 && chip->source_pos < FRAC_ONE)
		{
			*buffer++ += (((INT32)prev * (INT32)(FRAC_ONE - chip->source_pos)) + ((INT32)curr * (INT32)chip->source_pos)) >> FRAC_BITS;
			chip->source_pos += chip->source_step;
			length--;
		}

		/* if we're over, continue; otherwise, we're done */
		if (chip->source_pos >= FRAC_ONE)
			chip->source_pos -= FRAC_ONE;
		else
			return;
	}

	/* compute how many new samples we need */
	final_pos = chip->source_pos + length * chip->source_step;
	new_samples = (final_pos + FRAC_ONE - 1) >> FRAC_BITS;
	if (new_samples > MAX_SAMPLE_CHUNK)
		new_samples = MAX_SAMPLE_CHUNK;

	/* generate them into our buffer */
	generate_adpcm(chip, sample_data, new_samples);
	prev = curr;
	curr = *curr_data++;

	/* then sample-rate convert with linear interpolation */
	while (length > 0)
	{
		/* interpolate */
		while (length > 0 && chip->source_pos < FRAC_ONE)
		{
			*buffer++ += (((INT32)prev * (INT32)(FRAC_ONE - chip->source_pos)) + ((INT32)curr * (INT32)chip->source_pos)) >> FRAC_BITS;
			chip->source_pos += chip->source_step;
			length--;
		}

		/* if we're over, grab the next samples */
		if (chip->source_pos >= FRAC_ONE)
		{
			chip->source_pos -= FRAC_ONE;
			prev = curr;
			curr = *curr_data++;
		}
	}

	/* remember the last samples */
	chip->last_sample = prev;
	chip->curr_sample = curr;
}



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

     state save support for MAME

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

static void es8712_state_save_register(struct es8712 *chip, int sndindex)
{
	char buf[20];

	sprintf(buf,"ES8712");

	state_save_register_INT32  (buf, sndindex, "bank_offset", &chip->bank_offset, 1);

	state_save_register_UINT8  (buf, sndindex, "playing", &chip->playing, 1);
	state_save_register_UINT32 (buf, sndindex, "sample" , &chip->sample,  1);
	state_save_register_UINT32 (buf, sndindex, "count"  , &chip->count,   1);
	state_save_register_UINT32 (buf, sndindex, "signal" , &chip->signal,  1);
	state_save_register_UINT32 (buf, sndindex, "step"   , &chip->step,    1);

	state_save_register_INT16  (buf, sndindex, "last_sample", &chip->last_sample, 1);
	state_save_register_INT16  (buf, sndindex, "curr_sample", &chip->curr_sample, 1);
	state_save_register_UINT32 (buf, sndindex, "source_step", &chip->source_step, 1);
	state_save_register_UINT32 (buf, sndindex, "source_pos" , &chip->source_pos,  1);
	state_save_register_UINT32 (buf, sndindex, "base_offset" , &chip->base_offset, 1);

	state_save_register_UINT32 (buf, sndindex, "start", &chip->start, 1);
	state_save_register_UINT32 (buf, sndindex, "end", &chip->end, 1);
	state_save_register_UINT8  (buf, sndindex, "repeat", &chip->repeat, 1);
}



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

    ES8712_start -- start emulation of an ES8712 chip

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

static void *es8712_start(int sndindex, int clock, const void *config)
{
	const struct ES8712interface *intf = config;
	struct es8712 *chip;

	chip = auto_malloc(sizeof(*chip));
	memset(chip, 0, sizeof(*chip));

	compute_tables();

	chip->start = 0;
	chip->end = 0;
	chip->repeat = 0;

	chip->bank_offset = 0;
	chip->region_base = memory_region(intf->region);

	/* generate the name and create the stream */
	chip->stream = stream_create(0, 1, Machine->sample_rate, chip, es8712_update);

	/* initialize the rest of the structure */
	chip->signal = -2;
	if (Machine->sample_rate)
		chip->source_step = (UINT32)((double)clock * (double)FRAC_ONE / (double)Machine->sample_rate);

	es8712_state_save_register(chip, sndindex);

	/* success */
	return chip;
}



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

     ES8712_reset -- stop emulation of an ES8712-compatible chip

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

static void es8712_reset(void *chip_src)
{
	struct es8712 *chip = chip_src;

	if (chip->playing)
	{
		/* update the stream, then turn it off */
		stream_update(chip->stream, 0);
		chip->playing = 0;
		chip->repeat = 0;
	}
}


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

    ES8712_set_bank_base -- set the base of the bank on a given chip

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

void ES8712_set_bank_base(int which, int base)
{
	struct es8712 *chip = sndti_token(SOUND_ES8712, which);
	stream_update(chip->stream, 0);
	chip->bank_offset = base;
}


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

    ES8712_set_frequency -- dynamically adjusts the frequency of a given ADPCM chip

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

void ES8712_set_frequency(int which, int frequency)
{
	struct es8712 *chip = sndti_token(SOUND_ES8712, which);

	/* update the stream and set the new base */
	stream_update(chip->stream, 0);

	if (Machine->sample_rate)
		chip->source_step = (UINT32)((double)frequency * (double)FRAC_ONE / (double)Machine->sample_rate);
}



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

    ES8712_play -- Begin playing the addressed sample

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

void ES8712_play(int which)
{
	struct es8712 *chip = sndti_token(SOUND_ES8712, which);


	if (Machine->sample_rate == 0) return;

	if (chip->start < chip->end)
	{
		if (!chip->playing)
		{
			chip->playing = 1;
			chip->base_offset = chip->start;
			chip->sample = 0;
			chip->count = 2 * (chip->end - chip->start + 1);
			chip->repeat = 1;

			/* also reset the ADPCM parameters */
			chip->signal = -2;
			chip->step = 0;
		}
	}
	/* invalid samples go here */
	else
	{
		logerror("ES871295:%d requested to play invalid sample range %06x-%06x\n",which,chip->start,chip->end);

		if (chip->playing)
		{
			/* update the stream */
			stream_update(chip->stream, 0);
			chip->playing = 0;
		}
	}
}



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

     ES8712_data_0_w -- generic data write functions
     ES8712_data_1_w

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

/**********************************************************************************************
 *
 *  offset  Start       End
 *          0hmmll  -  0HMMLL
 *    00    ----ll
 *    01    --mm--
 *    02    0h----
 *    03               ----LL
 *    04               --MM--
 *    05               0H----
 *    06           Go!
 *
 * Offsets are written in the order -> 00, 02, 01, 03, 05, 04, 06
 * Offset 06 is written with the same value as offset 04.
 *
***********************************************************************************************/

static void ES8712_data_w(int which, int offset, UINT32 data)
{
	struct es8712 *chip = sndti_token(SOUND_ES8712, which);

	switch (offset)
	{
		case 00:	chip->start &= 0x000fff00;
					chip->start |= ((data & 0xff) <<  0); break;
		case 01:	chip->start &= 0x000f00ff;
					chip->start |= ((data & 0xff) <<  8); break;
		case 02:	chip->start &= 0x0000ffff;
					chip->start |= ((data & 0x0f) << 16); break;
		case 03:	chip->end   &= 0x000fff00;
					chip->end   |= ((data & 0xff) <<  0); break;
		case 04:	chip->end   &= 0x000f00ff;
					chip->end   |= ((data & 0xff) <<  8); break;
		case 05:	chip->end   &= 0x0000ffff;
					chip->end   |= ((data & 0x0f) << 16); break;
		case 06:
		default:	break;
	}
	chip->start &= 0xfffff; chip->end &= 0xfffff;
}

WRITE8_HANDLER( ES8712_data_0_w )
{
	ES8712_data_w(0, offset, data);
}

WRITE8_HANDLER( ES8712_data_1_w )
{
	ES8712_data_w(1, offset, data);
}

WRITE8_HANDLER( ES8712_data_2_w )
{
	ES8712_data_w(2, offset, data);
}

WRITE16_HANDLER( ES8712_data_0_lsb_w )
{
	if (ACCESSING_LSB)
		ES8712_data_w(0, offset, data & 0xff);
}

WRITE16_HANDLER( ES8712_data_1_lsb_w )
{
	if (ACCESSING_LSB)
		ES8712_data_w(1, offset, data & 0xff);
}

WRITE16_HANDLER( ES8712_data_2_lsb_w )
{
	if (ACCESSING_LSB)
		ES8712_data_w(2, offset, data & 0xff);
}

WRITE16_HANDLER( ES8712_data_0_msb_w )
{
	if (ACCESSING_MSB)
		ES8712_data_w(0, offset, data >> 8);
}

WRITE16_HANDLER( ES8712_data_1_msb_w )
{
	if (ACCESSING_MSB)
		ES8712_data_w(1, offset, data >> 8);
}

WRITE16_HANDLER( ES8712_data_2_msb_w )
{
	if (ACCESSING_MSB)
		ES8712_data_w(2, offset, data >> 8);
}



/**************************************************************************
 * Generic get_info
 **************************************************************************/

static void es8712_set_info(void *token, UINT32 state, union sndinfo *info)
{
	switch (state)
	{
		/* no parameters to set */
	}
}


void es8712_get_info(void *token, UINT32 state, union sndinfo *info)
{
	switch (state)
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case SNDINFO_PTR_SET_INFO:						info->set_info = es8712_set_info;		break;
		case SNDINFO_PTR_START:							info->start = es8712_start;				break;
		case SNDINFO_PTR_STOP:							/* nothing */							break;
		case SNDINFO_PTR_RESET:							info->reset = es8712_reset;				break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case SNDINFO_STR_NAME:							info->s = "ES8712";						break;
		case SNDINFO_STR_CORE_FAMILY:					info->s = "Excellent Systems ADPCM";			break;
		case SNDINFO_STR_CORE_VERSION:					info->s = "1.0";						break;
		case SNDINFO_STR_CORE_FILE:						info->s = __FILE__;						break;
		case SNDINFO_STR_CORE_CREDITS:					info->s = "Copyright (c) 2005, The MAME Team"; break;
	}
}

