/***********************************************************/
/*                                                         */
/* PCM.C : PCM RF5C164 emulator                            */
/*                                                         */
/* This source is a part of Gens project                   */
/* Written by Stéphane Dallongeville (gens@consolemul.com) */
/* Copyright (c) 2002 by Stéphane Dallongeville            */
/*                                                         */
/***********************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "mamedef.h"
#include "scd_pcm.h"
int  PCM_Init(void *chip, int Rate);
void PCM_Set_Rate(void *chip, int Rate);
void PCM_Reset(void *chip);
void PCM_Write_Reg(void *chip, unsigned int Reg, unsigned int Data);
int  PCM_Update(void *chip, int **buf, int Length);

#define PCM_STEP_SHIFT 11

/*static unsigned char VolTabIsInit = 0x00;
static int PCM_Volume_Tab[256 * 256];*/

//unsigned char Ram_PCM[64 * 1024];
//int PCM_Enable;


/**
 * PCM_Init(): Initialize the PCM chip.
 * @param Rate Sample rate.
 * @return 0 if successful.
 */
int PCM_Init(void *_info, int Rate)
{
	struct pcm_chip_ *chip = (struct pcm_chip_ *)_info;
	int i/*, j, out*/;
	
	/*if (! VolTabIsInit)
	{
		for (i = 0; i < 0x100; i++)
		{
			for (j = 0; j < 0x100; j++)
			{
				if (i & 0x80)
				{
					out = -(i & 0x7F);
					out *= j;
					PCM_Volume_Tab[(j << 8) + i] = out;
				}
				else
				{
					out = i * j;
					PCM_Volume_Tab[(j << 8) + i] = out;
				}
			}
		}
		VolTabIsInit = 0x01;
	}*/
	
	chip->Smpl0Patch = 0;
	for (i = 0; i < 8; i ++)
		chip->Channel[i].Muted = 0x00;
	
	chip->RAMSize = 64 * 1024;
	chip->RAM = (unsigned char*)malloc(chip->RAMSize);
	PCM_Reset(chip);
	PCM_Set_Rate(chip, Rate);

	return 0;
}


/**
 * PCM_Reset(): Reset the PCM chip.
 */
void PCM_Reset(void *_info)
{
	struct pcm_chip_ *chip = (struct pcm_chip_ *)_info;
	int i;
	struct pcm_chan_* chan;
	
	// Clear the PCM memory.
	memset(chip->RAM, 0x00, chip->RAMSize);
	
	chip->Enable = 0;
	chip->Cur_Chan = 0;
	chip->Bank = 0;
	
	/* clear channel registers */
	for (i = 0; i < 8; i++)
	{
		chan = &chip->Channel[i];
		chan->Enable = 0;
		chan->ENV = 0;
		chan->PAN = 0;
		chan->St_Addr = 0;
		chan->Addr = 0;
		chan->Loop_Addr = 0;
		chan->Step = 0;
		chan->Step_B = 0;
		chan->Data = 0;
	}
}


/**
 * PCM_Set_Rate(): Change the PCM sample rate.
 * @param Rate New sample rate.
 */
void PCM_Set_Rate(void *_info, int Rate)
{
	struct pcm_chip_ *chip = (struct pcm_chip_ *)_info;
	int i;
	
	if (Rate == 0)
		return;
	
	//chip->Rate = (float) (32 * 1024) / (float) Rate;
	chip->Rate = (float) (31.8 * 1024) / (float) Rate;
	
	for (i = 0; i < 8; i++)
	{
		chip->Channel[i].Step =
			(int) ((float) chip->Channel[i].Step_B * chip->Rate);
	}
}


/**
 * PCM_Write_Reg(): Write to a PCM register.
 * @param Reg Register ID.
 * @param Data Data to write.
 */
void PCM_Write_Reg(void *_info, unsigned int Reg, unsigned int Data)
{
	struct pcm_chip_ *chip = (struct pcm_chip_ *)_info;
	int i;
	struct pcm_chan_* chan = &chip->Channel[chip->Cur_Chan];
	
	Data &= 0xFF;
	
	switch (Reg)
	{
		case 0x00:
			/* evelope register */
			chan->ENV = Data;
			chan->MUL_L = (Data * (chan->PAN & 0x0F)) >> 5;
			chan->MUL_R = (Data * (chan->PAN >> 4)) >> 5;
		break;
		
		case 0x01:
			/* pan register */
			chan->PAN = Data;
			chan->MUL_L = ((Data & 0x0F) * chan->ENV) >> 5;
			chan->MUL_R = ((Data >> 4) * chan->ENV) >> 5;
			break;
		
		case 0x02:
			/* frequency step (LB) registers */
			chan->Step_B &= 0xFF00;
			chan->Step_B += Data;
			chan->Step = (int)((float)chan->Step_B * chip->Rate);
			
			//LOG_MSG(pcm, LOG_MSG_LEVEL_DEBUG1,
			//	"Step low = %.2X   Step calculated = %.8X",
			//	Data, chan->Step);
			break;
		
		case 0x03:
			/* frequency step (HB) registers */
			chan->Step_B &= 0x00FF;
			chan->Step_B += Data << 8;
			chan->Step = (int)((float)chan->Step_B * chip->Rate);
			
			//LOG_MSG(pcm, LOG_MSG_LEVEL_DEBUG1,
			//	"Step high = %.2X   Step calculated = %.8X",
			//	Data, chan->Step);
			break;
		
		case 0x04:
			chan->Loop_Addr &= 0xFF00;
			chan->Loop_Addr += Data;			
			
			//LOG_MSG(pcm, LOG_MSG_LEVEL_DEBUG1,
			//	"Loop low = %.2X   Loop = %.8X",
			//	Data, chan->Loop_Addr);
			break;
		
		case 0x05:
			/* loop address registers */
			chan->Loop_Addr &= 0x00FF;
			chan->Loop_Addr += Data << 8;
			
			//LOG_MSG(pcm, LOG_MSG_LEVEL_DEBUG1,
			//	"Loop high = %.2X   Loop = %.8X",
			//	Data, chan->Loop_Addr);
			break;
		
		case 0x06:
			/* start address registers */
			chan->St_Addr = Data << (PCM_STEP_SHIFT + 8);
			//chan->Addr = chan->St_Addr;
			
			//LOG_MSG(pcm, LOG_MSG_LEVEL_DEBUG1,
			//	"Start addr = %.2X   New Addr = %.8X",
			//	Data, chan->Addr);
			break;
		
		case 0x07:
			/* control register */
			/* mod is H */
			if (Data & 0x40)
			{
				/* select channel */
				chip->Cur_Chan = Data & 0x07;
			}
			/* mod is L */
			else
			{
				/* pcm ram bank select */
				chip->Bank = (Data & 0x0F) << 12;
			}
			
			/* sounding bit */
			if (Data & 0x80)
				chip->Enable = 0xFF;	// Used as mask
			else
				chip->Enable = 0;
			
			//LOG_MSG(pcm, LOG_MSG_LEVEL_DEBUG1,
			//	"General Enable = %.2X", Data);
			break;
		
		case 0x08:
			/* sound on/off register */
			Data ^= 0xFF;
			
			//LOG_MSG(pcm, LOG_MSG_LEVEL_DEBUG1,
			//	"Channel Enable = %.2X", Data);
			
			for (i = 0; i < 8; i++)
			{
				chan = &chip->Channel[i];
				if (!chan->Enable)
					chan->Addr = chan->St_Addr;
			}
			
			for (i = 0; i < 8; i++)
			{
				chip->Channel[i].Enable = Data & (1 << i);
			}
	}
}


/**
 * PCM_Update(): Update the PCM buffer.
 * @param buf PCM buffer.
 * @param Length Buffer length.
 */
int PCM_Update(void *_info, int **buf, int Length)
{
	struct pcm_chip_ *chip = (struct pcm_chip_ *)_info;
	int i, j;
	int *bufL, *bufR;		//, *volL, *volR;
	unsigned int Addr, k;
	struct pcm_chan_ *CH;
	
	bufL = buf[0];
	bufR = buf[1];
	
	// clear buffers
	memset(bufL, 0, Length * sizeof(int));
	memset(bufR, 0, Length * sizeof(int));

	// if PCM disable, no sound
	if (!chip->Enable)
		return 1;
	
#if 0
	// faster for short update
	for (j = 0; j < Length; j++)
	{
		for (i = 0; i < 8; i++)
		{
			CH = &(chip->Channel[i]);

			// only loop when sounding and on
			if (CH->Enable && ! CH->Muted)
			{
				Addr = CH->Addr >> PCM_STEP_SHIFT;

				if (Addr & 0x10000)
				{
					for(k = CH->Old_Addr; k < 0x10000; k++)
					{
						if (chip->RAM[k] == 0xFF)
						{
							CH->Old_Addr = Addr = CH->Loop_Addr;
							CH->Addr = Addr << PCM_STEP_SHIFT;
							break;
						}
					}

					if (Addr & 0x10000)
					{
						//CH->Addr -= CH->Step;
						CH->Enable = 0;
						break;
					}
				}
				else
				{
					for(k = CH->Old_Addr; k <= Addr; k++)
					{
						if (chip->RAM[k] == 0xFF)
						{
							CH->Old_Addr = Addr = CH->Loop_Addr;
							CH->Addr = Addr << PCM_STEP_SHIFT;
							break;
						}
					}
				}
				
				// test for loop signal
				if (chip->RAM[Addr] == 0xFF)
				{
					Addr = CH->Loop_Addr;
					CH->Addr = Addr << PCM_STEP_SHIFT;
				}
				
				if (chip->RAM[Addr] & 0x80)
				{
					CH->Data = chip->RAM[Addr] & 0x7F;
					bufL[j] -= CH->Data * CH->MUL_L;
					bufR[j] -= CH->Data * CH->MUL_R;
				}
				else
				{
					CH->Data = chip->RAM[Addr];
					bufL[j] += CH->Data * CH->MUL_L;
					bufR[j] += CH->Data * CH->MUL_R;
				}
				
				// update address register
				//CH->Addr = (CH->Addr + CH->Step) & 0x7FFFFFF;
				CH->Addr += CH->Step;
				CH->Old_Addr = Addr + 1;
			}
		}
	}
#endif

#if 1
	// for long update
	for (i = 0; i < 8; i++)
	{
		CH = &(chip->Channel[i]);
		
		// only loop when sounding and on
		if (CH->Enable && ! CH->Muted)
		{
			Addr = CH->Addr >> PCM_STEP_SHIFT;
			//volL = &(PCM_Volume_Tab[CH->MUL_L << 8]);
			//volR = &(PCM_Volume_Tab[CH->MUL_R << 8]);
			
			for (j = 0; j < Length; j++)
			{
				// test for loop signal
				if (chip->RAM[Addr] == 0xFF)
				{
					CH->Addr = (Addr = CH->Loop_Addr) << PCM_STEP_SHIFT;
					if (chip->RAM[Addr] == 0xFF)
						break;
					else
						j--;
				}
				else
				{
					if (chip->RAM[Addr] & 0x80)
					{
						CH->Data = chip->RAM[Addr] & 0x7F;
						bufL[j] -= CH->Data * CH->MUL_L;
						bufR[j] -= CH->Data * CH->MUL_R;
					}
					else
					{
						CH->Data = chip->RAM[Addr];
						// this improves the sound of Cosmic Fantasy Stories,
						// although it's definately false behaviour
						if (! CH->Data && chip->Smpl0Patch)
							CH->Data = -0x7F;
						bufL[j] += CH->Data * CH->MUL_L;
						bufR[j] += CH->Data * CH->MUL_R;
					}
					
					// update address register
					k = Addr + 1;
					CH->Addr = (CH->Addr + CH->Step) & 0x7FFFFFF;
					Addr = CH->Addr >> PCM_STEP_SHIFT;
					
					for (; k < Addr; k++)
					{
						if (chip->RAM[k] == 0xFF)
						{
							CH->Addr = (Addr = CH->Loop_Addr) << PCM_STEP_SHIFT;
							break;
						}
					}
				}
			}
			
			if (chip->RAM[Addr] == 0xFF)
			{
				CH->Addr = CH->Loop_Addr << PCM_STEP_SHIFT;
			}
		}
	}
#endif
	
	return 0;
}


void rf5c164_update(void *chip, stream_sample_t **outputs, int samples)
{
	PCM_Update(chip, outputs, samples);
}

int device_start_rf5c164(void **_info, int clock, int CHIP_SAMPLING_MODE, int CHIP_SAMPLE_RATE)
{
	/* allocate memory for the chip */
	//rf5c164_state *chip = get_safe_token(device);
	struct pcm_chip_ *chip;
	int rate;
	
	chip = (struct pcm_chip_ *) calloc(1, sizeof(struct pcm_chip_));
	*_info = (void *) chip;	

	rate = (clock & 0x7FFFFFFF) / 384;
	if (((CHIP_SAMPLING_MODE & 0x01) && rate < CHIP_SAMPLE_RATE) ||
		CHIP_SAMPLING_MODE == 0x02)
		rate = CHIP_SAMPLE_RATE;
	
	PCM_Init(chip, rate);
	chip->Smpl0Patch = (clock & 0x80000000) >> 31;
	
	/* allocate the stream */
	//chip->stream = stream_create(device, 0, 2, device->clock / 384, chip, rf5c68_update);

	return rate;
}

void device_stop_rf5c164(void *_info)
{
	struct pcm_chip_ *chip = (struct pcm_chip_ *)_info;
	free(chip->RAM);	chip->RAM = NULL;
	free(chip);	

	return;
}

void device_reset_rf5c164(void *chip)
{
	//struct pcm_chip_ *chip = &PCM_Chip[ChipID];
	PCM_Reset(chip);
}

void rf5c164_w(void *chip, offs_t offset, UINT8 data)
{
	//struct pcm_chip_ *chip = &PCM_Chip[ChipID];
	PCM_Write_Reg(chip, offset, data);
}

void rf5c164_mem_w(void *_info, offs_t offset, UINT8 data)
{
	struct pcm_chip_ *chip = (struct pcm_chip_ *)_info;
	chip->RAM[chip->Bank | offset] = data;
}

void rf5c164_write_ram(void *_info, offs_t DataStart, offs_t DataLength, const UINT8* RAMData)
{
	struct pcm_chip_ *chip = (struct pcm_chip_ *)_info;
	
	DataStart |= chip->Bank;
	if (DataStart >= chip->RAMSize)
		return;
	if (DataStart + DataLength > chip->RAMSize)
		DataLength = chip->RAMSize - DataStart;
	
	memcpy(chip->RAM + DataStart, RAMData, DataLength);
	
	return;
}


void rf5c164_set_mute_mask(void *_info, UINT32 MuteMask)
{
	struct pcm_chip_ *chip = (struct pcm_chip_ *)_info;
	unsigned char CurChn;
	
	for (CurChn = 0; CurChn < 8; CurChn ++)
		chip->Channel[CurChn].Muted = (MuteMask >> CurChn) & 0x01;
	
	return;
}
