/*********************************************************/
/*    SEGA 16ch 8bit PCM                                 */
/*********************************************************/

#include "sndintrf.h"
#include "streams.h"
#include "segapcm.h"

struct segapcm
{
	UINT8  *ram;
	UINT8 low[16];
	const UINT8 *rom;
	UINT32 max_addr;
	int bankshift;
	int bankmask;
	sound_stream * stream;
};

static void SEGAPCM_update(void *param, stream_sample_t **inputs, stream_sample_t **buffer, int length)
{
	struct segapcm *spcm = param;
	int ch;

	/* clear the buffers */
	memset(buffer[0], 0, length*sizeof(*buffer[0]));
	memset(buffer[1], 0, length*sizeof(*buffer[1]));

	/* loop over channels */
	for (ch = 0; ch < 16; ch++)

		/* only process active channels */
		if (!(spcm->ram[0x86+8*ch] & 1))
		{
			UINT8 *base = spcm->ram+8*ch;
			UINT8 flags = base[0x86];
			const UINT8 *rom = spcm->rom + ((flags & spcm->bankmask) << spcm->bankshift);
			UINT32 addr = (base[5] << 16) | (base[4] << 8) | spcm->low[ch];
			UINT16 loop = (base[0x85] << 8) | base[0x84];
			UINT8 end = base[6] + 1;
			UINT8 delta = base[7];
			UINT8 voll = base[2];
			UINT8 volr = base[3];
			int i;

			/* loop over samples on this channel */
			for (i = 0; i < length; i++)
			{
				INT8 v = 0;

				/* handle looping if we've hit the end */
				if ((addr >> 16) == end)
				{
					if (!(flags & 2))
						addr = loop << 8;
					else
					{
						flags |= 1;
						break;
					}
				}

				/* fetch the sample */
				if (addr < spcm->max_addr)
					v = rom[addr >> 8] - 0x80;

				/* apply panning and advance */
				buffer[0][i] += v * voll;
				buffer[1][i] += v * volr;
				addr += delta;
			}

			/* store back the updated address and info */
			base[0x86] = flags;
			base[4] = addr >> 8;
			base[5] = addr >> 16;
			spcm->low[ch] = flags & 1 ? 0 : addr;
		}
}

static void *segapcm_start(int sndindex, int clock, const void *config)
{
	const struct SEGAPCMinterface *intf = config;
	int mask, rom_mask;
	struct segapcm *spcm;

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

	spcm->rom = (const UINT8 *)memory_region(intf->region);
	spcm->max_addr = memory_region_length(intf->region) << 8;
	spcm->ram = auto_malloc(0x800);

	memset(spcm->ram, 0xff, 0x800);

	spcm->bankshift = (UINT8)(intf->bank);
	mask = intf->bank >> 16;
	if(!mask)
		mask = BANK_MASK7>>16;

	for(rom_mask = 1; rom_mask < memory_region_length(intf->region); rom_mask *= 2);
	rom_mask--;

	spcm->bankmask = mask & (rom_mask >> spcm->bankshift);

	spcm->stream = stream_create(0, 2, clock / 128, spcm, SEGAPCM_update);

	state_save_register_item_array("segapcm", sndindex, spcm->low);
	state_save_register_item_pointer("segapcm", sndindex, spcm->ram, 0x800);

	return spcm;
}


WRITE8_HANDLER( SegaPCM_w )
{
	struct segapcm *spcm = sndti_token(SOUND_SEGAPCM, 0);
	stream_update(spcm->stream, 0);
	spcm->ram[offset & 0x07ff] = data;
}

READ8_HANDLER( SegaPCM_r )
{
	struct segapcm *spcm = sndti_token(SOUND_SEGAPCM, 0);
	stream_update(spcm->stream, 0);
	return spcm->ram[offset & 0x07ff];
}



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

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


void segapcm_get_info(void *token, UINT32 state, 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 = segapcm_set_info;		break;
		case SNDINFO_PTR_START:							info->start = segapcm_start;			break;
		case SNDINFO_PTR_STOP:							/* Nothing */							break;
		case SNDINFO_PTR_RESET:							/* Nothing */							break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case SNDINFO_STR_NAME:							info->s = "Sega PCM";					break;
		case SNDINFO_STR_CORE_FAMILY:					info->s = "Sega 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;
	}
}
