/*********************************************************/
/*    ricoh RF5C68(or clone) PCM controller              */
/*********************************************************/

#include "driver.h"
#include <math.h>

#define PCM_NORMALIZE

enum
{
	RF_L_PAN = 0, RF_R_PAN = 1, RF_LR_PAN = 2
};

static RF5C68PCM    rpcm;
static int  reg_port;
static int emulation_rate;

static int buffer_len;
static int stream;

//static unsigned char pcmbuf[0x10000];
static unsigned char *pcmbuf=NULL;

static struct RF5C68interface *intf;

static unsigned char wreg[0x10]; /* write data */

#define   BASE_SHIFT    (11+4)

#define    RF_ON     (1<<0)
#define    RF_START  (1<<1)

/************************************************/
/*    RF5C68 start                              */
/************************************************/
int RF5C68_sh_start( const struct MachineSound *msound )
{
	int i;
	int rate = Machine->sample_rate;
	struct RF5C68interface *inintf = msound->sound_interface;

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

	if(pcmbuf==NULL) pcmbuf=(unsigned char *)malloc(0x10000);
	if(pcmbuf==NULL) return 1;

	intf = inintf;
	buffer_len = rate / Machine->drv->frames_per_second;
	emulation_rate = buffer_len * Machine->drv->frames_per_second;

	rpcm.clock = intf->clock;
	for( i = 0; i < RF5C68_PCM_MAX; i++ )
	{
		rpcm.env[i] = 0;
		rpcm.pan[i] = 0;
		rpcm.start[i] = 0;
		rpcm.step[i] = 0;
		rpcm.flag[i] = 0;
	}
	for( i = 0; i < 0x10; i++ )  wreg[i] = 0;
	reg_port = 0;

	{
		char buf[LR_PAN][40];
		const char *name[LR_PAN];
		int  vol[2];
		name[0] = buf[0];
		name[1] = buf[1];
		sprintf( buf[0], "%s Left", sound_name(msound) );
		sprintf( buf[1], "%s Right", sound_name(msound) );
		vol[0] = (MIXER_PAN_LEFT<<8)  | (intf->volume&0xff);
		vol[1] = (MIXER_PAN_RIGHT<<8) | (intf->volume&0xff);

		stream = stream_init_multi( RF_LR_PAN, name, vol, rate, 16, 0, RF5C68Update );
		if(stream == -1) return 1;
	}
	return 0;
}

/************************************************/
/*    RF5C68 stop                               */
/************************************************/
void RF5C68_sh_stop( void )
{
	if(pcmbuf!=NULL) free(pcmbuf);
	pcmbuf=NULL;
}

/************************************************/
/*    RF5C68 update                             */
/************************************************/

INLINE int ILimit(int v, int max, int min) { return v > max ? max : (v < min ? min : v); }

void RF5C68Update( int num, void **buffer, int length )
{
	int i, j, tmp;
	unsigned int addr, old_addr;
	signed int ld, rd;
	RF5C68_SAMPLE  *datap[2];

	datap[RF_L_PAN] = (RF5C68_SAMPLE *)buffer[0];
	datap[RF_R_PAN] = (RF5C68_SAMPLE *)buffer[1];

	memset( datap[RF_L_PAN], 0x00, length * sizeof(RF5C68_SAMPLE) );
	memset( datap[RF_R_PAN], 0x00, length * sizeof(RF5C68_SAMPLE) );

	for( i = 0; i < RF5C68_PCM_MAX; i++ )
	{
		if( (rpcm.flag[i]&(RF_START|RF_ON)) == (RF_START|RF_ON) )
		{
			/**** PCM setup ****/
			addr = (rpcm.addr[i]>>BASE_SHIFT)&0xffff;
			ld = (rpcm.pan[i]&0x0f);
			rd = ((rpcm.pan[i]>>4)&0x0f);
			/*       cen = (ld + rd) / 4; */
			/*       ld = (rpcm.env[i]&0xff) * (ld + cen); */
			/*       rd = (rpcm.env[i]&0xff) * (rd + cen); */
			ld = (rpcm.env[i]&0xff) * ld;
			rd = (rpcm.env[i]&0xff) * rd;

			for( j = 0; j < length; j++ )
			{
				old_addr = addr;
				addr = (rpcm.addr[i]>>BASE_SHIFT)&0xffff;
				for(; old_addr <= addr; old_addr++ )
				{
					/**** PCM end check ****/
					if( (((unsigned int)pcmbuf[old_addr])&0x00ff) == (unsigned int)0x00ff )
					{
						rpcm.addr[i] = rpcm.loop[i] + ((addr - old_addr)<<BASE_SHIFT);
						addr = (rpcm.addr[i]>>BASE_SHIFT)&0xffff;
						/**** PCM loop check ****/
						if( (((unsigned int)pcmbuf[addr])&0x00ff) == (unsigned int)0x00ff )	// this causes looping problems
						{
							rpcm.flag[i] = 0;
							break;
						}
						else
						{
							old_addr = addr;
						}
					}
#ifdef PCM_NORMALIZE
					tmp = rpcm.pcmd[i];
					rpcm.pcmd[i] = (pcmbuf[old_addr]&0x7f) * (-1 + (2 * ((pcmbuf[old_addr]>>7)&0x01)));
					rpcm.pcma[i] = (tmp - rpcm.pcmd[i]) / 2;
					rpcm.pcmd[i] += rpcm.pcma[i];
#endif
				}
				rpcm.addr[i] += rpcm.step[i];
				if( !rpcm.flag[i] )  break; /* skip PCM */
#ifndef PCM_NORMALIZE
				if( pcmbuf[addr]&0x80 )
				{
					rpcm.pcmx[0][i] = ((signed int)(pcmbuf[addr]&0x7f))*ld;
					rpcm.pcmx[1][i] = ((signed int)(pcmbuf[addr]&0x7f))*rd;
				}
				else
				{
					rpcm.pcmx[0][i] = ((signed int)(-(pcmbuf[addr]&0x7f)))*ld;
					rpcm.pcmx[1][i] = ((signed int)(-(pcmbuf[addr]&0x7f)))*rd;
				}
#else
				rpcm.pcmx[0][i] = rpcm.pcmd[i] * ld;
				rpcm.pcmx[1][i] = rpcm.pcmd[i] * rd;
#endif
				*(datap[RF_L_PAN] + j) = ILimit( ((int)*(datap[RF_L_PAN] + j) + ((rpcm.pcmx[0][i])>>4)), 32767, -32768 );
				*(datap[RF_R_PAN] + j) = ILimit( ((int)*(datap[RF_R_PAN] + j) + ((rpcm.pcmx[1][i])>>4)), 32767, -32768 );
			}
		}
	}
}

/************************************************/
/*    RF5C68 write register                     */
/************************************************/
void RF5C68WriteReg( int r, int v )
{
	int  i;
	int  data;

	wreg[r] = v;			/* stock write data */
	/**** set PCM registers ****/
	if( (wreg[0x07]&0x40) )  reg_port = wreg[0x07]&0x07;	/* select port # */

	switch( r )
	{
		case 0x00:
			rpcm.env[reg_port] = v;		/* set env. */
			break;
		case 0x01:
			rpcm.pan[reg_port] = v;		/* set pan */
			break;
		case 0x02:
		case 0x03:
			/**** address step ****/
			data = (((((int)wreg[0x03])<<8)&0xff00) | (((int)wreg[0x02])&0x00ff));
			rpcm.step[reg_port] = (int)(
			(
			( 28456.0 / (float)emulation_rate ) *
			( data / (float)(0x0800) ) *
			(rpcm.clock / 8000000.0) *
			(1<<BASE_SHIFT)
			)
			);
			break;
		case 0x04:
		case 0x05:
			/**** loop address ****/
			rpcm.loop[reg_port] = (((((unsigned int)wreg[0x05])<<8)&0xff00)|(((unsigned int)wreg[0x04])&0x00ff))<<(BASE_SHIFT);
			break;
		case 0x06:
			/**** start address ****/
			rpcm.start[reg_port] = (((unsigned int)wreg[0x06])&0x00ff)<<(BASE_SHIFT + 8);
			rpcm.addr[reg_port] = rpcm.start[reg_port];
			break;
		case 0x07:
			if( (v&0xc0) == 0xc0 )
			{
				i = v&0x07;		/* register port */
				rpcm.pcmx[0][i] = 0;
				rpcm.pcmx[1][i] = 0;
				rpcm.flag[i] |= RF_START;
			}
			break;

		case 0x08:
			/**** pcm on/off ****/
			for( i = 0; i < RF5C68_PCM_MAX; i++ )
			{
				if( !(v&(1<<i)) )
				{
					rpcm.flag[i] |= RF_ON;	/* PCM on */
				}
				else
				{
					rpcm.flag[i] &= ~(RF_ON); /* PCM off */
				}
			}
			break;
	}
}

/************************************************/
/*    RF5C68 read memory                        */
/************************************************/
int RF5C68ReadMem( int r )
{
	unsigned int  bank;
	bank = ((unsigned int)(wreg[0x07]&0x0f))<<(8+4);
	return pcmbuf[bank + r];
}
/************************************************/
/*    RF5C68 write memory                       */
/************************************************/
void RF5C68WriteMem( int r, int v )
{
	unsigned int  bank;
	bank = ((unsigned int)(wreg[0x07]&0x0f))<<(8+4);
	pcmbuf[bank + r] = v;
}


/**************** end of file ****************/
