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

                            -= Seta Hardware =-

                    driver by   Luca Elia (l.elia@tin.it)

                    rewrite by Manbow-J(manbowj@hamal.freemail.ne.jp)

                    X1-010 Seta Custom Sound Chip (80 Pin PQFP)

 Custom programmed Mitsubishi M60016 Gate Array, 3608 gates, 148 Max I/O ports

    The X1-010 is 16 Voices sound generator, each channel gets it's
    waveform from RAM (128 bytes per waveform, 8 bit unsigned data)
    or sampling PCM(8bit unsigned data).

Registers:
    8 registers per channel (mapped to the lower bytes of 16 words on the 68K)

    Reg:    Bits:       Meaning:

    0       7654 3---
            ---- -2--   PCM/Waveform repeat flag (0:Ones 1:Repeat) (*1)
            ---- --1-   Sound out select (0:PCM 1:Waveform)
            ---- ---0   Key on / off

    1       7654 ----   PCM Volume 1 (L?)
            ---- 3210   PCM Volume 2 (R?)
                        Waveform No.

    2                   PCM Frequency
                        Waveform Pitch Lo

    3                   Waveform Pitch Hi

    4                   PCM Sample Start / 0x1000           [Start/End in bytes]
                        Waveform Envelope Time

    5                   PCM Sample End 0x100 - (Sample End / 0x1000)    [PCM ROM is Max 1MB?]
                        Waveform Envelope No.
    6                   Reserved
    7                   Reserved

    offset 0x0000 - 0x0fff  Wave form data
    offset 0x1000 - 0x1fff  Envelope data

    *1 : when 0 is specified, hardware interrupt is caused(allways return soon)

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

#include "emu.h"
#include "streams.h"
#include "x1_010.h"


#define VERBOSE_SOUND 0
#define VERBOSE_REGISTER_WRITE 0
#define VERBOSE_REGISTER_READ 0

#define LOG_SOUND(x) do { if (VERBOSE_SOUND) logerror x; } while (0)
#define LOG_REGISTER_WRITE(x) do { if (VERBOSE_REGISTER_WRITE) logerror x; } while (0)
#define LOG_REGISTER_READ(x) do { if (VERBOSE_REGISTER_READ) logerror x; } while (0)

#define SETA_NUM_CHANNELS 16

#define FREQ_BASE_BITS		  8					// Frequency fixed decimal shift bits
#define ENV_BASE_BITS		 16					// wave form envelope fixed decimal shift bits
#define	VOL_BASE	(2*32*256/30)					// Volume base

/* this structure defines the parameters for a channel */
typedef struct {
	unsigned char	status;
	unsigned char	volume;						//        volume / wave form no.
	unsigned char	frequency;					//     frequency / pitch lo
	unsigned char	pitch_hi;					//      reserved / pitch hi
	unsigned char	start;						// start address / envelope time
	unsigned char	end;						//   end address / envelope no.
	unsigned char	reserve[2];
} X1_010_CHANNEL;

typedef struct _x1_010_state x1_010_state;
struct _x1_010_state
{
	/* Variables only used here */
	int	rate;								// Output sampling rate (Hz)
	sound_stream *	stream;					// Stream handle
	int	address;							// address eor data
	const UINT8 *region;					// region name
	int	sound_enable;						// sound output enable/disable
	UINT8	reg[0x2000];				// X1-010 Register & wave form area
	UINT8	HI_WORD_BUF[0x2000];			// X1-010 16bit access ram check avoidance work
	UINT32	smp_offset[SETA_NUM_CHANNELS];
	UINT32	env_offset[SETA_NUM_CHANNELS];

	UINT32 base_clock;
};

/* mixer tables and internal buffers */
//static short  *mixer_buffer = NULL;

INLINE x1_010_state *get_safe_token(running_device *device)
{
	assert(device != NULL);
	assert(device->type() == SOUND_X1_010);
	return (x1_010_state *)downcast<legacy_device_base *>(device)->token();
}


/*--------------------------------------------------------------
 generate sound to the mix buffer
--------------------------------------------------------------*/
static STREAM_UPDATE( seta_update )
{
	x1_010_state *info = (x1_010_state *)param;
	X1_010_CHANNEL	*reg;
	int		ch, i, volL, volR, freq;
	register INT8	*start, *end, data;
	register UINT8	*env;
	register UINT32	smp_offs, smp_step, env_offs, env_step, delta;
	const UINT8 *snd1 = info->region;

	// mixer buffer zero clear
	memset( outputs[0], 0, samples*sizeof(*outputs[0]) );
	memset( outputs[1], 0, samples*sizeof(*outputs[1]) );

//  if( info->sound_enable == 0 ) return;

	for( ch = 0; ch < SETA_NUM_CHANNELS; ch++ ) {
		reg = (X1_010_CHANNEL *)&(info->reg[ch*sizeof(X1_010_CHANNEL)]);
		if( (reg->status&1) != 0 ) {							// Key On
			stream_sample_t *bufL = outputs[0];
			stream_sample_t *bufR = outputs[1];
			if( (reg->status&2) == 0 ) {						// PCM sampling
				start    = (INT8 *)(reg->start      *0x1000+snd1);
				end      = (INT8 *)((0x100-reg->end)*0x1000+snd1);
				volL     = ((reg->volume>>4)&0xf)*VOL_BASE;
				volR     = ((reg->volume>>0)&0xf)*VOL_BASE;
				smp_offs = info->smp_offset[ch];
				freq     = reg->frequency&0x1f;
				// Meta Fox does not write the frequency register. Ever
				if( freq == 0 ) freq = 4;
				smp_step = (UINT32)((float)info->base_clock/8192.0
							*freq*(1<<FREQ_BASE_BITS)/(float)info->rate);
				if( smp_offs == 0 ) {
					LOG_SOUND(( "Play sample %p - %p, channel %X volume %d:%d freq %X step %X offset %X\n",
						start, end, ch, volL, volR, freq, smp_step, smp_offs ));
				}
				for( i = 0; i < samples; i++ ) {
					delta = smp_offs>>FREQ_BASE_BITS;
					// sample ended?
					if( start+delta >= end ) {
						reg->status &= 0xfe;					// Key off
						break;
					}
					data = *(start+delta);
					*bufL++ += (data*volL/256);
					*bufR++ += (data*volR/256);
					smp_offs += smp_step;
				}
				info->smp_offset[ch] = smp_offs;
			} else {											// Wave form
				start    = (INT8 *)&(info->reg[reg->volume*128+0x1000]);
				smp_offs = info->smp_offset[ch];
				freq     = (reg->pitch_hi<<8)+reg->frequency;
				smp_step = (UINT32)((float)info->base_clock/128.0/1024.0/4.0*freq*(1<<FREQ_BASE_BITS)/(float)info->rate);

				env      = (UINT8 *)&(info->reg[reg->end*128]);
				env_offs = info->env_offset[ch];
				env_step = (UINT32)((float)info->base_clock/128.0/1024.0/4.0*reg->start*(1<<ENV_BASE_BITS)/(float)info->rate);
				/* Print some more debug info */
				if( smp_offs == 0 ) {
					LOG_SOUND(( "Play waveform %X, channel %X volume %X freq %4X step %X offset %X\n",
						reg->volume, ch, reg->end, freq, smp_step, smp_offs ));
				}
				for( i = 0; i < samples; i++ ) {
					int vol;
					delta = env_offs>>ENV_BASE_BITS;
					// Envelope one shot mode
					if( (reg->status&4) != 0 && delta >= 0x80 ) {
						reg->status &= 0xfe;					// Key off
						break;
					}
					vol = *(env+(delta&0x7f));
					volL = ((vol>>4)&0xf)*VOL_BASE;
					volR = ((vol>>0)&0xf)*VOL_BASE;
					data  = *(start+((smp_offs>>FREQ_BASE_BITS)&0x7f));
					*bufL++ += (data*volL/256);
					*bufR++ += (data*volR/256);
					smp_offs += smp_step;
					env_offs += env_step;
				}
				info->smp_offset[ch] = smp_offs;
				info->env_offset[ch] = env_offs;
			}
		}
	}
}



static DEVICE_START( x1_010 )
{
	int i;
	const x1_010_interface *intf = (const x1_010_interface *)device->baseconfig().static_config();
	x1_010_state *info = get_safe_token(device);

	info->region		= *device->region();
	info->base_clock	= device->clock();
	info->rate			= device->clock() / 1024;
	info->address		= intf->adr;

	for( i = 0; i < SETA_NUM_CHANNELS; i++ ) {
		info->smp_offset[i] = 0;
		info->env_offset[i] = 0;
	}
	/* Print some more debug info */
	LOG_SOUND(("masterclock = %d rate = %d\n", device->clock(), info->rate ));

	/* get stream channels */
	info->stream = stream_create(device,0,2,info->rate,info,seta_update);
}


void seta_sound_enable_w(running_device *device, int data)
{
	x1_010_state *info = get_safe_token(device);
	info->sound_enable = data;
}



/* Use these for 8 bit CPUs */


READ8_DEVICE_HANDLER( seta_sound_r )
{
	x1_010_state *info = get_safe_token(device);
	offset ^= info->address;
	return info->reg[offset];
}




WRITE8_DEVICE_HANDLER( seta_sound_w )
{
	x1_010_state *info = get_safe_token(device);
	int channel, reg;
	offset ^= info->address;

	channel	= offset/sizeof(X1_010_CHANNEL);
	reg		= offset%sizeof(X1_010_CHANNEL);

	if( channel < SETA_NUM_CHANNELS && reg == 0
	 && (info->reg[offset]&1) == 0 && (data&1) != 0 ) {
		info->smp_offset[channel] = 0;
		info->env_offset[channel] = 0;
	}
	LOG_REGISTER_WRITE(("%s: offset %6X : data %2X\n", cpuexec_describe_context(device->machine), offset, data ));
	info->reg[offset] = data;
}




/* Use these for 16 bit CPUs */

READ16_DEVICE_HANDLER( seta_sound_word_r )
{
	x1_010_state *info = get_safe_token(device);
	UINT16	ret;

	ret = info->HI_WORD_BUF[offset]<<8;
	ret += (seta_sound_r( device, offset )&0xff);
	LOG_REGISTER_READ(( "%s: Read X1-010 Offset:%04X Data:%04X\n", cpuexec_describe_context(device->machine), offset, ret ));
	return ret;
}

WRITE16_DEVICE_HANDLER( seta_sound_word_w )
{
	x1_010_state *info = get_safe_token(device);
	info->HI_WORD_BUF[offset] = (data>>8)&0xff;
	seta_sound_w( device, offset, data&0xff );
	LOG_REGISTER_WRITE(( "%s: Write X1-010 Offset:%04X Data:%04X\n", cpuexec_describe_context(device->machine), offset, data ));
}



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

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

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:							info->start = DEVICE_START_NAME( x1_010 );			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, "X1-010");						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(X1_010, x1_010);
