/***********************************************************/
/*                                                         */
/* 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 "pcm.h"
#include "cd_sys.h"
#include "star_68k.h"
#include "mem_m68k.h"

#define PCM_STEP_SHIFT 11

struct pcm_chip_ PCM_Chip;
unsigned char Ram_PCM[64 * 1024];
int PCM_Volume_Tab[256 * 256];
int PCM_Enable;

/* initialise the pcm chip */
int
Init_PCM (int Rate)
{
  int i, j, out;


  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;
	    }
	}
    }

  Reset_PCM ();
  Set_Rate_PCM (Rate);

  return 0;
}

/* reset the pcm chip */
void
Reset_PCM (void)
{
  int i;

  memset (Ram_PCM, 0, 64 * 1024);

  PCM_Chip.Enable = 0;

  /* clear channel registers */
  for (i = 0; i < 8; i++)
    {
      PCM_Chip.Channel[i].Enable = 0;
      PCM_Chip.Channel[i].ENV = 0;
      PCM_Chip.Channel[i].PAN = 0;
      PCM_Chip.Channel[i].St_Addr = 0;
      PCM_Chip.Channel[i].Addr = 0;
      PCM_Chip.Channel[i].Loop_Addr = 0;
      PCM_Chip.Channel[i].Step = 0;
      PCM_Chip.Channel[i].Step_B = 0;
      PCM_Chip.Channel[i].Data = 0;
    }
}

/* Change sample rate of PCM */
void
Set_Rate_PCM (int Rate)
{
  int i;

  if (Rate == 0)
    return;

//      PCM_Chip.Rate = (float) (32 * 1024) / (float) Rate;
  PCM_Chip.Rate = (float) (31.8 * 1024) / (float) Rate;

  for (i = 0; i < 8; i++)
    {
      PCM_Chip.Channel[i].Step =
	(int) ((float) PCM_Chip.Channel[i].Step_B * PCM_Chip.Rate);
    }
}

/* write pcm register */
void
Write_PCM_Reg (unsigned int Reg, unsigned int Data)
{
  Data &= 0xFF;

  switch (Reg)
    {
    case 0x00:			/* evelope register */
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].ENV = Data;
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].MUL_L =
	(Data * (PCM_Chip.Channel[PCM_Chip.Cur_Chan].PAN & 0x0F)) >> 5;
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].MUL_R =
	(Data * (PCM_Chip.Channel[PCM_Chip.Cur_Chan].PAN >> 4)) >> 5;
      break;

    case 0x01:			/* pan register */
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].PAN = Data;
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].MUL_L =
	((Data & 0x0F) * PCM_Chip.Channel[PCM_Chip.Cur_Chan].ENV) >> 5;
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].MUL_R =
	((Data >> 4) * PCM_Chip.Channel[PCM_Chip.Cur_Chan].ENV) >> 5;
      break;

    case 0x02:			/* frequency step (LB) registers */
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].Step_B &= 0xFF00;
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].Step_B += Data;
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].Step =
	(int) ((float) PCM_Chip.Channel[PCM_Chip.Cur_Chan].Step_B *
	       PCM_Chip.Rate);

#ifdef DEBUG_CD
      fprintf (debug_SCD_file,
	       "PCM : Step low = %.2X   Step calculated = %.8X\n", Data,
	       PCM_Chip.Channel[PCM_Chip.Cur_Chan].Step);
#endif
      break;

    case 0x03:			/* frequency step (HB) registers */
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].Step_B &= 0x00FF;
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].Step_B += Data << 8;
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].Step =
	(int) ((float) PCM_Chip.Channel[PCM_Chip.Cur_Chan].Step_B *
	       PCM_Chip.Rate);

#ifdef DEBUG_CD
      fprintf (debug_SCD_file,
	       "PCM : Step high = %.2X   Step calculated = %.8X\n", Data,
	       PCM_Chip.Channel[PCM_Chip.Cur_Chan].Step);
#endif
      break;

    case 0x04:
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].Loop_Addr &= 0xFF00;
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].Loop_Addr += Data;

#ifdef DEBUG_CD
      fprintf (debug_SCD_file, "PCM : Loop low = %.2X   Loop = %.8X\n", Data,
	       PCM_Chip.Channel[PCM_Chip.Cur_Chan].Loop_Addr);
#endif
      break;

    case 0x05:			/* loop address registers */
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].Loop_Addr &= 0x00FF;
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].Loop_Addr += Data << 8;

#ifdef DEBUG_CD
      fprintf (debug_SCD_file, "PCM : Loop high = %.2X   Loop = %.8X\n", Data,
	       PCM_Chip.Channel[PCM_Chip.Cur_Chan].Loop_Addr);
#endif
      break;

    case 0x06:			/* start address registers */
      PCM_Chip.Channel[PCM_Chip.Cur_Chan].St_Addr =
	Data << (PCM_STEP_SHIFT + 8);
//                      PCM_Chip.Channel[PCM_Chip.Cur_Chan].Addr = PCM_Chip.Channel[PCM_Chip.Cur_Chan].St_Addr;

#ifdef DEBUG_CD
      fprintf (debug_SCD_file, "PCM : Start addr = %.2X   New Addr = %.8X\n",
	       Data, PCM_Chip.Channel[PCM_Chip.Cur_Chan].Addr);
#endif
      break;

    case 0x07:			/* control register */
      /* mod is H */
      if (Data & 0x40)
	{
	  /* select channel */
	  PCM_Chip.Cur_Chan = Data & 0x07;
	}
      /* mod is L */
      else
	{
	  /* pcm ram bank select */
	  PCM_Chip.Bank = (Data & 0x0F) << 12;
	}

      /* sounding bit */
      if (Data & 0x80)
	{
	  PCM_Chip.Enable = 0xFF;	// Used as mask
	}
      else
	{
	  PCM_Chip.Enable = 0;
	}

#ifdef DEBUG_CD
      fprintf (debug_SCD_file, "PCM : General Enable = %.2X\n", Data);
#endif
      break;

    case 0x08:			/* sound on/off register */
      Data ^= 0xFF;

#ifdef DEBUG_CD
      fprintf (debug_SCD_file, "PCM : Channel Enable = %.2X\n", Data);
#endif

      if (!PCM_Chip.Channel[0].Enable)
	{
	  PCM_Chip.Channel[0].Addr = PCM_Chip.Channel[0].St_Addr;
	}

      if (!PCM_Chip.Channel[1].Enable)
	{
	  PCM_Chip.Channel[1].Addr = PCM_Chip.Channel[1].St_Addr;
	}

      if (!PCM_Chip.Channel[2].Enable)
	{
	  PCM_Chip.Channel[2].Addr = PCM_Chip.Channel[2].St_Addr;
	}

      if (!PCM_Chip.Channel[3].Enable)
	{
	  PCM_Chip.Channel[3].Addr = PCM_Chip.Channel[3].St_Addr;
	}

      if (!PCM_Chip.Channel[4].Enable)
	{
	  PCM_Chip.Channel[4].Addr = PCM_Chip.Channel[4].St_Addr;
	}

      if (!PCM_Chip.Channel[5].Enable)
	{
	  PCM_Chip.Channel[5].Addr = PCM_Chip.Channel[5].St_Addr;
	}

      if (!PCM_Chip.Channel[6].Enable)
	{
	  PCM_Chip.Channel[6].Addr = PCM_Chip.Channel[6].St_Addr;
	}

      if (!PCM_Chip.Channel[7].Enable)
	{
	  PCM_Chip.Channel[7].Addr = PCM_Chip.Channel[7].St_Addr;
	}

      PCM_Chip.Channel[0].Enable = Data & 0x01;
      PCM_Chip.Channel[1].Enable = Data & 0x02;
      PCM_Chip.Channel[2].Enable = Data & 0x04;
      PCM_Chip.Channel[3].Enable = Data & 0x08;
      PCM_Chip.Channel[4].Enable = Data & 0x10;
      PCM_Chip.Channel[5].Enable = Data & 0x20;
      PCM_Chip.Channel[6].Enable = Data & 0x40;
      PCM_Chip.Channel[7].Enable = Data & 0x80;
    }
}


int
Update_PCM (int **buf, int Length)
{
  int i, j;
  int *bufL, *bufR;		//, *volL, *volR;
  unsigned int Addr, k;
  struct pcm_chan_ *CH;

  // if PCM disable, no sound
  if (!PCM_Chip.Enable)
    return 1;

  bufL = buf[0];
  bufR = buf[1];

/*
	// faster for short update
	for (j = 0; j < Length; j++)
	{
		for (i = 0; i < 8; i++)
		{
			CH = &(PCM_Chip.Channel[i]);

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

				if (Addr & 0x10000)
				{
					for(k = CH->Old_Addr; k < 0x10000; k++)
					{
						if (Ram_PCM[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 (Ram_PCM[k] == 0xFF)
						{
							CH->Old_Addr = Addr = CH->Loop_Addr;
							CH->Addr = Addr << PCM_STEP_SHIFT;
							break;
						}
					}
				}

				// test for loop signal
				if (Ram_PCM[Addr] == 0xFF)
				{
					Addr = CH->Loop_Addr;
					CH->Addr = Addr << PCM_STEP_SHIFT;
				}

				if (Ram_PCM[Addr] & 0x80)
				{
					CH->Data = Ram_PCM[Addr] & 0x7F;
					bufL[j] -= CH->Data * CH->MUL_L;
					bufR[j] -= CH->Data * CH->MUL_R;
				}
				else
				{
					CH->Data = Ram_PCM[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;
			}
		}
	}
*/

  // for long update
  for (i = 0; i < 8; i++)
    {
      CH = &(PCM_Chip.Channel[i]);

      // only loop when sounding and on
      if (CH->Enable)
	{
	  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 (Ram_PCM[Addr] == 0xFF)
		{
		  CH->Addr = (Addr = CH->Loop_Addr) << PCM_STEP_SHIFT;
		  if (Ram_PCM[Addr] == 0xFF)
		    break;
		  else
		    j--;
		}
	      else
		{
/*
					CH->Data = Ram_PCM[Addr];
					
					bufL[j] += volL[CH->Data];
					bufR[j] += volR[CH->Data];
*/
		  if (Ram_PCM[Addr] & 0x80)
		    {
		      CH->Data = Ram_PCM[Addr] & 0x7F;
		      bufL[j] -= CH->Data * CH->MUL_L;
		      bufR[j] -= CH->Data * CH->MUL_R;
		    }
		  else
		    {
		      CH->Data = Ram_PCM[Addr];
		      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 (Ram_PCM[k] == 0xFF)
			{
			  CH->Addr = (Addr = CH->Loop_Addr) << PCM_STEP_SHIFT;
			  break;
			}
		    }
		}
	    }

	  if (Ram_PCM[Addr] == 0xFF)
	    {
	      CH->Addr = CH->Loop_Addr << PCM_STEP_SHIFT;
	    }
	}
    }

  return 0;
}

void _Write_PCM_Reg (unsigned int Reg, unsigned int Data)
  __attribute__ ((alias ("Write_PCM_Reg")));
