/************************************
      Seta custom Nile ST-0026 chip
      sound emulation by Tomasz Slanina
      based on ST-0016 emulation

8 voices, 16 words of config data for each:

   00
   01 - sptr  ?? (always 0)
   02 - sptr  LO
   03 - sptr  HI
   04
   05 - flags? 00000000 0000?L0?   - bit 0 loops, other bits appear to be not used by the chip
   06 - freq
   07 - lsptr LO
   08
   09 - lsptr HI
   0a - leptr LO
   0b - leptr HI
   0c - eptr  LO
   0d - eptr  HI
   0e - vol R
   0f - vol L

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

#include "emu.h"
#include "nile.h"

#define NILE_VOICES 8

enum
{
	NILE_REG_UNK0=0,
	NILE_REG_SPTR_TOP,
	NILE_REG_SPTR_LO,
	NILE_REG_SPTR_HI,
	NILE_REG_UNK_4,
	NILE_REG_FLAGS,
	NILE_REG_FREQ,
	NILE_REG_LSPTR_LO,
	MILE_REG_UNK_8,
	NILE_REG_LSPTR_HI,
	NILE_REG_LEPTR_LO,
	NILE_REG_LEPTR_HI,
	NILE_REG_EPTR_LO,
	NILE_REG_EPTR_HI,
	NILE_REG_VOL_R,
	NILE_REG_VOL_L
};



typedef struct _nile_state nile_state;
struct _nile_state
{
	sound_stream * stream;
	UINT8 *sound_ram;
	UINT16 sound_regs[0x80];
	int vpos[NILE_VOICES], frac[NILE_VOICES], lponce[NILE_VOICES];
	UINT16 ctrl;
};

INLINE nile_state *get_safe_token(device_t *device)
{
	assert(device != NULL);
	assert(device->type() == NILE);
	return (nile_state *)downcast<legacy_device_base *>(device)->token();
}


WRITE16_DEVICE_HANDLER( nile_sndctrl_w )
{
	nile_state *info = get_safe_token(device);
	UINT16 ctrl=info->ctrl;

	info->stream->update();

	COMBINE_DATA(&info->ctrl);

//  printf("CTRL: %04x -> %04x (PC=%x)\n", ctrl, info->ctrl, cpu_get_pc(&space->device()));

	ctrl^=info->ctrl;
}

READ16_DEVICE_HANDLER( nile_sndctrl_r )
{
	nile_state *info = get_safe_token(device);

	info->stream->update();

	return info->ctrl;
}

READ16_DEVICE_HANDLER( nile_snd_r )
{
	nile_state *info = get_safe_token(device);
	int reg=offset&0xf;

	info->stream->update();

	if(reg==2 || reg==3)
	{
		int slot=offset/16;
		int sptr = ((info->sound_regs[slot*16+3]<<16)|info->sound_regs[slot*16+2])+info->vpos[slot];

		if(reg==2)
		{
			return sptr&0xffff;
		}
		else
		{
			return sptr>>16;
		}
	}
	return info->sound_regs[offset];
}

WRITE16_DEVICE_HANDLER( nile_snd_w )
{
	nile_state *info = get_safe_token(device);
	int v, r;

	info->stream->update();

	COMBINE_DATA(&info->sound_regs[offset]);

	v = offset / 16;
	r = offset % 16;

	if ((r == 2) || (r == 3))
	{
		info->vpos[v] = info->frac[v] = info->lponce[v] = 0;
	}

	//printf("v%02d: %04x to reg %02d (PC=%x)\n", v, info->sound_regs[offset], r, cpu_get_pc(&space->device()));
}

static STREAM_UPDATE( nile_update )
{
	nile_state *info = (nile_state *)param;
	UINT8 *sound_ram = info->sound_ram;
	int v, i, snum;
	UINT16 *slot;
	INT32 mix[48000*2];
	INT32 *mixp;
	INT16 sample;
	int sptr, eptr, freq, lsptr, leptr;

	lsptr=leptr=0;

	memset(mix, 0, sizeof(mix[0])*samples*2);

	for (v = 0; v < NILE_VOICES; v++)
	{
		slot = &info->sound_regs[v * 16];

		if (info->ctrl&(1<<v))
		{
			mixp = &mix[0];

			sptr = slot[NILE_REG_SPTR_HI]<<16 | slot[NILE_REG_SPTR_LO];
			eptr = slot[NILE_REG_EPTR_HI]<<16 | slot[NILE_REG_EPTR_LO];

			freq=slot[NILE_REG_FREQ]*14;
			lsptr = slot[NILE_REG_LSPTR_HI]<<16 | slot[NILE_REG_LSPTR_LO];
			leptr = slot[NILE_REG_LEPTR_HI]<<16 | slot[NILE_REG_LEPTR_LO];

			for (snum = 0; snum < samples; snum++)
			{
				sample = sound_ram[sptr + info->vpos[v]]<<8;

				*mixp++ += (sample * (INT32)slot[NILE_REG_VOL_R]) >> 16;
				*mixp++ += (sample * (INT32)slot[NILE_REG_VOL_L]) >> 16;

				info->frac[v] += freq;
				info->vpos[v] += info->frac[v]>>16;
				info->frac[v] &= 0xffff;

				// stop if we're at the end
				if (info->lponce[v])
				{
					// we've looped once, check loop end rather than sample end
					if ((info->vpos[v] + sptr) >= leptr)
					{
						info->vpos[v] = (lsptr - sptr);
					}
				}
				else
				{
					// not looped yet, check sample end
					if ((info->vpos[v] + sptr) >= eptr)
					{
						// code at 11d8c:
						// if bit 2 (0x4) is set, check if loop start = loop end.
						// if they are equal, clear bit 0 and don't set the loop start/end
						// registers in the NiLe.  if they aren't, set bit 0 and set
						// the loop start/end registers in the NiLe.
						if ((slot[NILE_REG_FLAGS] & 0x5) == 0x5)
						{
							info->vpos[v] = (lsptr - sptr);
							info->lponce[v] = 1;
						}
						else
						{
							info->ctrl &= ~(1<<v);
							info->vpos[v] = (eptr - sptr);
							info->frac[v] = 0;
						}

					}
				}
			}
		}
	}
	mixp = &mix[0];
	for (i = 0; i < samples; i++)
	{
		outputs[0][i] = (*mixp++)>>4;
		outputs[1][i] = (*mixp++)>>4;
	}
}

static DEVICE_START( nile )
{
	nile_state *info = get_safe_token(device);

	info->sound_ram = *device->region();

	info->stream = device->machine().sound().stream_alloc(*device, 0, 2, 44100, info, nile_update);
}



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

DEVICE_GET_INFO( nile )
{
	switch (state)
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case DEVINFO_INT_TOKEN_BYTES:					info->i = sizeof(nile_state);				break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:							info->start = DEVICE_START_NAME( nile );	break;
		case DEVINFO_FCT_STOP:							/* Nothing */								break;
		case DEVINFO_FCT_RESET:							/* Nothing */								break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case DEVINFO_STR_NAME:							strcpy(info->s, "NiLe");					break;
		case DEVINFO_STR_FAMILY:					strcpy(info->s, "Seta custom");				break;
		case DEVINFO_STR_VERSION:					strcpy(info->s, "1.0");						break;
		case DEVINFO_STR_SOURCE_FILE:						strcpy(info->s, __FILE__);					break;
		case DEVINFO_STR_CREDITS:					strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break;
	}
}


DEFINE_LEGACY_SOUND_DEVICE(NILE, nile);
