/* Mednafen - Multi-system Emulator
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "pce.h"
#include "psg.h"
#include "huc.h"
#include "adpcm.h"
#include "input.h"

static Blip_Synth<blip_good_quality, 4096> WaveSynth;
static Blip_Synth<blip_med_quality, 4096> NoiseSynth;

Blip_Buffer sbuf[2];		// Used in the CDROM ADPCM code

t_psg psg;

void PSG_SetSoundMultiplier(double multiplier)
{
 for(int y = 0; y < 2; y++)
 {
  sbuf[y].clock_rate((long)(1789772.727272 * 4 * multiplier));
 }
}

static void RedoVolume(void)
{
 extern int8 CDDAStatus;
 if(PCE_IsCD)
 {
  if(CDDAStatus == 1 )
  {
   WaveSynth.volume((double)FSettings.SoundVolume / 100 / 6 * 0.33);
   NoiseSynth.volume((double)FSettings.SoundVolume / 100 / 6 * 0.33);
  }
  else
  {
   WaveSynth.volume((double)FSettings.SoundVolume / 100 / 6 * 0.50);
   NoiseSynth.volume((double)FSettings.SoundVolume / 100 / 6 * 0.50);
  }
 }
 else
 {
  WaveSynth.volume((double)FSettings.SoundVolume / 100 / 6);
  NoiseSynth.volume((double)FSettings.SoundVolume / 100 / 6);
 }
}

void PSG_SetSoundVolume(uint32 volume)
{
 RedoVolume();
}

void PSG_Sound(int rate)
{
 int x, y;

 for(y = 0; y < 2; y++)
 {
  sbuf[y].set_sample_rate(rate ? rate : 44100, 50);
  sbuf[y].clock_rate((long)(1789772.727272 * 4));
  sbuf[y].bass_freq(20);
 }

 if(psg.WaveIL)
  free(psg.WaveIL);

 psg.WaveFinalLen = rate / 60 * 2; // *2 for extra room
 psg.WaveIL = (int16 *)malloc(sizeof(int16) * psg.WaveFinalLen * 2); // *2 for stereo

 RedoVolume();
}

static int32 dbtable[32][32 * 3];
 static const int scale_tab[] = {
        0x00, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F,
        0x10, 0x13, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F
        };

#define redo_ddacache(ch)	\
{	\
 int vll,vlr;	\
 vll = (ch)->lal + (ch)->al + psg.lmal;	\
 vlr = (ch)->ral + (ch)->al + psg.rmal;	\
 (ch)->dda_cache[0] = dbtable[(ch)->dda][vll];	\
 (ch)->dda_cache[1] = dbtable[(ch)->dda][vlr];	\
}

int PSG_Init(bool WantMono)
{
    int x;

    memset(&psg, 0, sizeof(psg));
    if(FSettings.SndRate)
     PSG_Sound(FSettings.SndRate);

    for(x=0;x<32 * 3;x++)
    {
     int y;
     double flub;

     flub = 1;

     if(x)
      flub /= pow(2, (double)1/4*x); //4*x);		// ~1.5dB reduction per increment of x
     for(y=0;y<32;y++)
      dbtable[y][x] = (int32)(flub * (y - 0x10) * 128);
     //printf("%f\n",flub);
    }
    
    psg.forcemono = WantMono;
    return (1);
}

void PSG_Kill(void)
{
	if(psg.WaveIL)
	 free(psg.WaveIL);
	psg.WaveIL = 0;
}

/*--------------------------------------------------------------------------*/
/* PSG emulation                                                            */
/*--------------------------------------------------------------------------*/

/* Macro to access currently selected PSG channel */
#define PSGCH   psg.channel[psg.select]
void PSG_Update(void);

DECLFW(PSG_Write)
{
    PSG_Update();
    int x;

    A &= 0x0F;

    if(A == 0x00)
     psg.select = (V & 0x07);
    else
     PSG_Update();

    switch(A)
    {
        case 0x01: /* Global sound balance */
            psg.globalbalance = V;
	    psg.lmal = 0x1F - scale_tab[(psg.globalbalance >> 4) & 0xF];
	    psg.rmal = 0x1F - scale_tab[(psg.globalbalance >> 0) & 0xF];
	    for(x=0;x<6;x++)
	     redo_ddacache(&psg.channel[x]);
            break;

        case 0x02: /* Channel frequency (LSB) */
	    if(psg.select > 5) return; // no more than 6 channels, silly game.
            PSGCH.frequency = (PSGCH.frequency & 0x0F00) | V;
	    PSGCH.base_frequency = PSGCH.frequency;
            break;

        case 0x03: /* Channel frequency (MSB) */
	    if(psg.select > 5) return; // no more than 6 channels, silly game.
            PSGCH.frequency = (PSGCH.frequency & 0x00FF) | ((V & 0x0F) << 8);
            PSGCH.base_frequency = PSGCH.frequency;
            break;

        case 0x04: /* Channel enable, DDA, volume */
	    if(psg.select > 5) return; // no more than 6 channels, silly game.
            PSGCH.control = V;
	    PSGCH.al = 0x1F - (PSGCH.control & 0x1F);
            if((V & 0xC0) == 0x40) PSGCH.waveform_index = 0;
            redo_ddacache(&PSGCH);
            break;

        case 0x05: /* Channel balance */
	    if(psg.select > 5) return; // no more than 6 channels, silly game.
            PSGCH.balance = V;
	    PSGCH.lal = 0x1F - scale_tab[(V >> 4) & 0xF];
	    PSGCH.ral = 0x1F - scale_tab[(V >> 0) & 0xF];
            redo_ddacache(&PSGCH);
            break;

        case 0x06: /* Channel waveform data */
	    if(psg.select > 5) return; // no more than 6 channels, silly game.
            V &= 0x1F;
            PSGCH.waveform[PSGCH.waveform_index] = V;

	    if((PSGCH.control & 0xC0) == 0x00)
             PSGCH.waveform_index = ((PSGCH.waveform_index + 1) & 0x1F);

	    if(PSGCH.control & 0x80)
	     PSGCH.dda = V;

	    redo_ddacache(&PSGCH);
            break;

        case 0x07: /* Noise enable and frequency */
	    if(psg.select > 5) return; // no more than 6 channels, silly game.
            if(psg.select >= 4) PSGCH.noisectrl = V;
            break;

        case 0x08: /* LFO frequency */
            psg.lfofreq = V;
            break;

        case 0x09: /* LFO trigger and control */
	    if((V & 0x80) && !(psg.lfoctrl & 0x80))
	     psg.channel[1].waveform_index = 0;
            psg.lfoctrl = V;
            break;
    }
}

static void DoLFOUpdate(int32 run_time, int32 timestamp)
{
  int chc;
  for(chc = 1; chc < 6; chc++)
  {
   psg_channel *ch = &psg.channel[chc];
   int disabled = ((ch->control & 0x80)^0x80) >> 7;
   //puts("ROO");
   if(disabled) 
    continue; 
   #include "psg-loop.h"
  }

  #define LFO_ON
  chc = 0;
  psg_channel *ch = &psg.channel[0];
  #include "psg-loop.h"
  #undef LFO_ON
}

static void DoNormalUpdate(int32 run_time, int32 timestamp)
{
 int chc;

 for(chc = 0; chc < 6; chc++)
 {
  psg_channel *ch = &psg.channel[chc];
  int disabled = ((ch->control & 0x80)^0x80) >> 7;
  if(disabled)
   continue; 
  #include "psg-loop.h"
 }
}

void PSG_Update(void)
{
 int32 timestamp = HuCPU.timestamp / pce_overclocked;
 int32 run_time = timestamp - psg.lastts;

 if(!FSettings.SndRate) return;

 if(timestamp == psg.lastts) return;

 int lfo_on = psg.lfoctrl & 0x03;
 if(!(psg.channel[1].control & 0x80))
  lfo_on = 0;

 if(!(psg.channel[0].control & 0x80))
  lfo_on = 0;

 if(!(psg.lfoctrl & 0x80))
  lfo_on = 0;

 if(lfo_on)
  DoLFOUpdate(run_time, timestamp);
 else
  DoNormalUpdate(run_time, timestamp);

 psg.lastts = timestamp;
}

int16 *PSG_Flush(int32 *len, int needrew)
{
 int32 timestamp;

 if(PCE_IsCD)
  ADPCM_Update();

 if(FSettings.SndRate);
  PSG_Update();

 timestamp = HuCPU.timestamp / pce_overclocked;
 if(FSettings.SndRate)
 {
  int love;
  for(int y = 0; y < 2; y++)
  {
   sbuf[y].end_frame(timestamp);
   love = sbuf[y].read_samples(&psg.WaveIL[y], psg.WaveFinalLen, 1);
  }
  if(psg.forcemono)
  {
   for(int x = 0; x < love; x++)
    psg.WaveIL[x] = ((int32)psg.WaveIL[x * 2] + psg.WaveIL[x * 2 + 1]) >> 1;
  }
  *len = love;
 }
 if(PCE_IsCD)
  RedoVolume();

 INPUT_FixTS();

 HuCPU.timestamp = 0;
 psg.lastts = 0;

 if(PCE_IsCD)
  ADPCM_ResetTS();

 if(!FSettings.SndRate)
 {
  *len = 0;
  return(NULL);
 }

 return(psg.WaveIL);
}

void PSG_Reset(void)
{
 memset(psg.channel, 0, sizeof(psg.channel));
 psg.select = 0;
 psg.globalbalance = 0;
 psg.lfofreq = 0;
 psg.lfoctrl = 0;
 psg.lfo_counter = 0;
}

void PSG_Power(void)
{
 memset(psg.channel, 0, sizeof(psg.channel));
 psg.select = 0;
 psg.globalbalance = 0;
 psg.lfofreq = 0;
 psg.lfoctrl = 0;
 psg.lfo_counter = 0;
}

int PSG_StateAction(StateMem *sm, int load, int data_only)
{
 int ret = 1;

 for(int ch = 0; ch < 6; ch++)
 {
  char tmpstr[5] = "SCHx";
  psg_channel *pt = &psg.channel[ch];

  SFORMAT CH_StateRegs[] = 
  {
   SFVARN(pt->counter, "counter"),
   SFVARN(pt->frequency, "frequency"),
   SFVARN(pt->control, "control"),
   SFVARN(pt->balance, "balance"),
   SFARRAYN(pt->waveform, 32, "waveform"),
   SFVARN(pt->waveform_index, "waveform_index"),
   SFVARN(pt->dda, "dda"),
   SFVARN(pt->noisectrl, "noisectrl"),
   SFVARN(pt->noisecount, "noisecount"),
   SFVARN(pt->lfsr, "lfsr"),
   SFEND
  };
  tmpstr[3] = '0' + ch;
  ret &= MDFNSS_StateAction(sm, load, data_only, CH_StateRegs, tmpstr);
 }

 SFORMAT PSG_StateRegs[] =
 {
  SFVARN(psg.select, "select"),
  SFVARN(psg.globalbalance, "globalbalance"),
  SFVARN(psg.lfofreq, "lfofreq"),
  SFVARN(psg.lfoctrl, "lfoctrl"),
  SFEND
 };
 
 ret &= MDFNSS_StateAction(sm, load, data_only, PSG_StateRegs, "PSG");

 if(load)
 {
  psg.lmal = 0x1F - scale_tab[(psg.globalbalance >> 4) & 0xF];
  psg.rmal = 0x1F - scale_tab[(psg.globalbalance >> 0) & 0xF];

  for(int ch = 0; ch < 6; ch++)
  {
   psg.channel[ch].lal = 0x1F - scale_tab[(psg.channel[ch].balance >> 4) & 0xF];
   psg.channel[ch].ral = 0x1F - scale_tab[(psg.channel[ch].balance >> 0) & 0xF];
   psg.channel[ch].al = 0x1F - (psg.channel[ch].control & 0x1F);
   redo_ddacache(&psg.channel[ch]);
  }
 }
 return(ret); 
}
