/***************************************************************************
                    Gaelco Sound Hardware

                By Manuel Abadia <manu@teleline.es>

CG-1V/GAE1 (Gaelco custom GFX & Sound chip):
    The CG-1V/GAE1 can handle up to 7 stereo channels.
    The chip output is connected to a TDA1543 (16 bit DAC).

Registers per channel:
======================
    Word | Bit(s)            | Description
    -----+-FEDCBA98-76543210-+--------------------------
      0  | xxxxxxxx xxxxxxxx | not used?
      1  | xxxx---- -------- | left channel volume (0x00..0x0f)
      1  | ----xxxx -------- | right channel volume (0x00..0x0f)
      1  | -------- xxxx---- | sample type (0x0c = PCM 8 bits mono, 0x08 = PCM 8 bits stereo)
      1  | -------- ----xxxx | ROM Bank
      2  | xxxxxxxx xxxxxxxx | sample end position
      3  | xxxxxxxx xxxxxxxx | remaining bytes to play

      the following are used only when looping (usually used for music)

      4  | xxxxxxxx xxxxxxxx | not used?
      5  | xxxx---- -------- | left channel volume (0x00..0x0f)
      5  | ----xxxx -------- | right channel volume (0x00..0x0f)
      5  | -------- xxxx---- | sample type (0x0c = PCM 8 bits mono, 0x08 = PCM 8 bits stereo)
      5  | -------- ----xxxx | ROM Bank
      6  | xxxxxxxx xxxxxxxx | sample end position
      7  | xxxxxxxx xxxxxxxx | remaining bytes to play

    The samples are played from (end position + length) to (end position)!

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

#include "driver.h"
#include "gaelco.h"
#include "wavwrite.h"


/*#define LOG_SOUND 1 */
/*#define LOG_READ_WRITES 1 */
/*#define LOG_WAVE  1 */
/*#define ALT_MIX */

#define GAELCO_NUM_CHANNELS 	0x07
#define VOLUME_LEVELS 			0x10

data16_t *gaelco_sndregs;

/* fix me -- asumes that only one type can be active at a time */
static int chip_type;

/* this structure defines a channel */
struct gaelcosnd_channel
{
	int active;			/* is it playing? */
	int loop;			/* = 0 no looping, = 1 looping */
	int chunkNum;		/* current chunk if looping */
};

/* this structure defines the Gaelco custom sound chip */
struct GAELCOSND
{
	sound_stream *stream;									/* our stream */
	UINT8 *snd_data;										/* PCM data */
	int banks[4];											/* start of each ROM bank */
	struct gaelcosnd_channel channel[GAELCO_NUM_CHANNELS];	/* 7 stereo channels */

	/* table for converting from 8 to 16 bits with volume control */
	INT16 volume_table[VOLUME_LEVELS][256];
};

#ifdef LOG_WAVE
void *	wavraw;					/* raw waveform */
#endif

/*============================================================================
                        CG-1V/GAE1 Sound Update

            Writes length bytes to the sound buffer
  ============================================================================*/

static void gaelco_update(void *param, stream_sample_t **inputs, stream_sample_t **buffer, int length)
{
	struct GAELCOSND *info = param;
	int j, ch;

    /* fill all data needed */
	for(j = 0; j < length; j++){
		int output_l = 0, output_r = 0;

		/* for each channel */
		for (ch = 0; ch < GAELCO_NUM_CHANNELS; ch ++){
			int ch_data_l = 0, ch_data_r = 0;
			struct gaelcosnd_channel *channel = &info->channel[ch];

			/* if the channel is playing */
			if (channel->active == 1){
				int data, chunkNum = 0;
				int base_offset, type, bank, vol_r, vol_l, end_pos;

				/* if the channel is looping, get current chunk to play */
				if (channel->loop == 1){
					chunkNum = channel->chunkNum;
				}

				base_offset = ch*8 + chunkNum*4;

				/* get channel parameters */
				type = ((gaelco_sndregs[base_offset + 1] >> 4) & 0x0f);
				bank = info->banks[((gaelco_sndregs[base_offset + 1] >> 0) & 0x03)];
				vol_l = ((gaelco_sndregs[base_offset + 1] >> 12) & 0x0f);
				vol_r = ((gaelco_sndregs[base_offset + 1] >> 8) & 0x0f);
				end_pos = gaelco_sndregs[base_offset + 2] << 8;

				/* generates output data (range 0x00000..0xffff) */
				if (type == 0x08){
					/* PCM, 8 bits mono */
					data = info->snd_data[bank + end_pos + gaelco_sndregs[base_offset + 3]];
					ch_data_l = info->volume_table[vol_l][data];
					ch_data_r = info->volume_table[vol_r][data];

					gaelco_sndregs[base_offset + 3]--;
				} else if (type == 0x0c){
					/* PCM, 8 bits stereo */
					data = info->snd_data[bank + end_pos + gaelco_sndregs[base_offset + 3]];
					ch_data_l = info->volume_table[vol_l][data];

					gaelco_sndregs[base_offset + 3]--;

					if (gaelco_sndregs[base_offset + 3] > 0){
						data = info->snd_data[bank + end_pos + gaelco_sndregs[base_offset + 3]];
						ch_data_r = info->volume_table[vol_r][data];

						gaelco_sndregs[base_offset + 3]--;
					}
				} else {
#ifdef LOG_SOUND
	logerror("(GAE1) Playing unknown sample format in channel: %02d, type: %02x, bank: %02x, end: %08x, Length: %04x\n", ch, type, bank, end_pos, gaelco_sndregs[base_offset + 3]);
#endif
					channel->active = 0;
				}

				/* check if the current sample has finished playing */
				if (gaelco_sndregs[base_offset + 3] == 0){
					if (channel->loop == 0){	/* if no looping, we're done */
						channel->active = 0;
					} else {					/* if we're looping, swap chunks */
						channel->chunkNum = (channel->chunkNum + 1) & 0x01;

						/* if the length of the next chunk is 0, we're done */
						if (gaelco_sndregs[ch*8 + channel->chunkNum*4 + 3] == 0){
							channel->active = 0;
						}
					}
				}
			}

			/* add the contribution of this channel to the current data output */
			output_l += ch_data_l;
			output_r += ch_data_r;
		}

#ifndef ALT_MIX
		/* clip to max or min value */
		if (output_l > 32767) output_l = 32767;
		if (output_r > 32767) output_r = 32767;
		if (output_l < -32768) output_l = -32768;
		if (output_r < -32768) output_r = -32768;
#else
		/* ponderate channels */
		output_l /= GAELCO_NUM_CHANNELS;
		output_r /= GAELCO_NUM_CHANNELS;
#endif

		/* now that we have computed all channels, save current data to the output buffer */
		buffer[0][j] = output_l;
		buffer[1][j] = output_r;
	}

#ifdef LOG_WAVE
	wav_add_data_16lr(wavraw, buffer[0], buffer[1], length);
#endif
}

/*============================================================================
                        CG-1V/GAE1 Read Handler
  ============================================================================*/

READ16_HANDLER( gaelcosnd_r )
{
#ifdef LOG_READ_WRITES
	logerror("%06x: (GAE1): read from %04x\n", activecpu_get_pc(), offset);
#endif

	return gaelco_sndregs[offset];
}

/*============================================================================
                        CG-1V/GAE1 Write Handler
  ============================================================================*/

WRITE16_HANDLER( gaelcosnd_w )
{
	struct GAELCOSND *info = sndti_token(chip_type, 0);
	struct gaelcosnd_channel *channel = &info->channel[offset >> 3];

#ifdef LOG_READ_WRITES
	logerror("%06x: (GAE1): write %04x to %04x\n", activecpu_get_pc(), data, offset);
#endif

	/* first update the stream to this point in time */
	stream_update(info->stream, 0);

	COMBINE_DATA(&gaelco_sndregs[offset]);

	switch(offset & 0x07){
		case 0x03:
			/* trigger sound */
			if ((gaelco_sndregs[offset - 1] != 0) && (data != 0)){
				if (!channel->active){
					channel->active = 1;
					channel->chunkNum = 0;
					channel->loop = 0;
#ifdef LOG_SOUND
	logerror("(GAE1) Playing sample channel: %02d, type: %02x, bank: %02x, end: %08x, Length: %04x\n", offset >> 3, (gaelco_sndregs[offset - 2] >> 4) & 0x0f, gaelco_sndregs[offset - 2] & 0x03, gaelco_sndregs[offset - 1] << 8, data);
#endif
				}
			} else {
				channel->active = 0;
			}

			break;

		case 0x07: /* enable/disable looping */
			if ((gaelco_sndregs[offset - 1] != 0) && (data != 0)){
#ifdef LOG_SOUND
	logerror("(GAE1) Looping in channel: %02d, type: %02x, bank: %02x, end: %08x, Length: %04x\n", offset >> 3, (gaelco_sndregs[offset - 2] >> 4) & 0x0f, gaelco_sndregs[offset - 2] & 0x03, gaelco_sndregs[offset - 1] << 8, data);
#endif
				channel->loop = 1;
			} else {
				channel->loop = 0;
			}

			break;
	}
}

/*============================================================================
                        CG-1V/GAE1 Init
  ============================================================================*/

static void *gaelcosnd_start(int sndtype, int sndindex, int clock, const void *config)
{
	int j, vol;
	const struct gaelcosnd_interface *intf = config;

	struct GAELCOSND *info;
	info = auto_malloc(sizeof(*info));
	memset(info, 0, sizeof(*info));

	chip_type = sndtype;

	/* bag on a 0 sample_rate */
	if (Machine->sample_rate == 0)	return info;

	/* copy rom banks */
	for (j = 0; j < 4; j++){
		info->banks[j] = intf->banks[j];
	}
	info->stream = stream_create(0, 2, 8000, info, gaelco_update);
	info->snd_data = (UINT8 *)memory_region(intf->region);

	/* init volume table */
	for (vol = 0; vol < VOLUME_LEVELS; vol++){
		for (j = -128; j <= 127; j++){
			info->volume_table[vol][(j ^ 0x80) & 0xff] = (vol*j*256)/(VOLUME_LEVELS - 1);
		}
	}

#ifdef LOG_WAVE
	wavraw = wav_open("gae1_snd.wav", 8000, 2);
#endif

	return info;
}

static void *gaelco_gae1_start(int sndindex, int clock, const void *config)
{
	return gaelcosnd_start(SOUND_GAELCO_GAE1, sndindex, clock, config);
}

static void *gaelco_cg1v_start(int sndindex, int clock, const void *config)
{
	return gaelcosnd_start(SOUND_GAELCO_CG1V, sndindex, clock, config);
}


static void gaelco_stop(void *chip)
{
#ifdef LOG_WAVE
	wav_close(wavraw);
#endif
}




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

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


void gaelco_gae1_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 = gaelco_gae1_set_info;	break;
		case SNDINFO_PTR_START:							info->start = gaelco_gae1_start;		break;
		case SNDINFO_PTR_STOP:							info->stop = gaelco_stop;				break;
		case SNDINFO_PTR_RESET:							/* nothing */							break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case SNDINFO_STR_NAME:							info->s = "Gaelco GAE1";				break;
		case SNDINFO_STR_CORE_FAMILY:					info->s = "Gaelco custom";				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) 2004, The MAME Team"; break;
	}
}



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

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


void gaelco_cg1v_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 = gaelco_cg1v_set_info;	break;
		case SNDINFO_PTR_START:							info->start = gaelco_cg1v_start;		break;
		case SNDINFO_PTR_STOP:							info->stop = gaelco_stop;				break;
		case SNDINFO_PTR_RESET:							/* nothing */							break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case SNDINFO_STR_NAME:							info->s = "Gaelco CG1V";				break;
		case SNDINFO_STR_CORE_FAMILY:					info->s = "Gaelco custom";				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) 2004, The MAME Team"; break;
	}
}

