/* Mednafen - Multi-system Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2004 Ki
 *  Copyright (C) 2007-2011 Mednafen Team
 *
 * 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
 */

/*
   PCE_FAST(less accurate, faster, etc.) fork from PCE module "pcecd.cpp".
*/

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

#include "../mednafen-types.h"
#include "../state.h"
#include "../okiadpcm.h"
#include "../cdrom/SimpleFIFO.h"
#include "../state_helpers.h"

#include "pcecd_drive.h"
#include "pcecd.h"

static unsigned int OC_Multiplier;

static void (*IRQCB)(bool asserted);

static float CDDAVolumeSetting; // User setting!

static bool	bBRAMEnabled;
static uint8	_Port[15];
static uint8 	ACKStatus;

static SimpleFIFO SubChannelFIFO;

static Blip_Buffer *sbuf[2];
static int16 RawPCMVolumeCache[2];

static int32 ClearACKDelay;

static int32 lastts;
static int32 pcecd_drive_ne = 0;

typedef Blip_Synth ADSynth;
static ADSynth ADPCMSynth;
static OKIADPCM_Decoder MSM5205;

static bool ADPCMLP;
typedef struct
{
 uint8    *RAM;	// = NULL; //0x10000;
 uint16   Addr;
 uint16   ReadAddr;
 uint16   WriteAddr;
 uint16   LengthCount;

 bool HalfReached;
 bool EndReached;
 bool Playing;

 uint8 LastCmd;
 uint32 SampleFreq;
 uint32 LPF_SampleFreq;

 uint8 PlayBuffer;
 uint8 ReadBuffer;
 int32 ReadPending;
 int32 WritePending;
 uint8 WritePendingValue;

 uint32 PlayNibble;


 int64 bigdivacc;
 int64 bigdiv;
 int32 last_pcm;

 /* One-pole IIR low-pass applied to the decoded ADPCM PCM stream when
  * the ADPCM_LPF core option is enabled. Reproduces the audible
  * high-frequency rolloff of upstream mednafen's RedoLPF()/treble_eq()
  * (which reshapes a bandlimited Blip_Synth impulse) without needing a
  * windowed-sinc kernel - our Blip_Synth is the fast linear-interpolation
  * variant. The coefficient tracks the ADPCM sample frequency so the
  * cutoff scales the same way upstream's does. lpf_coeff is Q16 fixed
  * point in [0,65536]; lpf_accum is the Q16 filter memory. */
 int32 lpf_coeff;
 int32 lpf_accum;
} ADPCM_t;

static ADPCM_t ADPCM;

typedef struct
{
 uint8 Command;
 int32 Volume;

 int32 CycleCounter;
 uint32 CountValue;     // What to reload CycleCounter with when it expires.
 bool Clocked;
} FADE_t;

static FADE_t Fader;
static int32 ADPCMFadeVolume, CDDAFadeVolume;

/* Recompute the ADPCM low-pass coefficient for sample-frequency code f
 * (0..15; ADPCM sample rate fs = 32087.5 / (16 - f)).
 *
 * Equivalent of upstream mednafen's RedoLPF(): there the cutoff is
 *   rolloff = (fs / 2) * (f >= 14 ? 0.70 : 0.80)
 * fed to Blip_Synth::treble_eq(). We instead realise the same rolloff as
 * a one-pole IIR on the PCM stream (which is generated at fs), so the
 * cutoff/fs ratio is the only thing that matters:
 *   fc/fs = 0.5 * (f >= 14 ? 0.70 : 0.80)
 *   a     = 1 - exp(-2*pi * fc/fs)
 * a is therefore one of two constants. When the option is disabled the
 * coefficient is 65536 (Q16 1.0), i.e. an identity pass-through. */
static void RedoLPF(int f)
{
   if(!ADPCMLP)
   {
      ADPCM.lpf_coeff = 65536; /* identity: out = in */
      return;
   }

   /* a = 1 - exp(-2*pi * 0.5 * ratio); ratio = 0.70 (f>=14) else 0.80.
    * 1 - exp(-pi*0.70) = 0.88922 -> 58279 ; 1 - exp(-pi*0.80) = 0.91893 -> 60226 */
   if(f >= 14)
      ADPCM.lpf_coeff = 58279;
   else
      ADPCM.lpf_coeff = 60226;
}

static INLINE void Fader_SyncWhich(void)
{
   if(Fader.Command & 0x2) // ADPCM fade
   {
      ADPCMFadeVolume = Fader.Volume;
      CDDAFadeVolume = 65536;
   }
   else   // CD-DA Fade
   {
      CDDAFadeVolume = Fader.Volume;
      ADPCMFadeVolume = 65536;
   }

   ADPCMFadeVolume >>= 6;
   PCECD_Drive_SetCDDAVolume(0.50f * CDDAFadeVolume * CDDAVolumeSetting);
}

static INLINE int32 ADPCM_ClocksToNextEvent(void)
{
   int32 ret = (ADPCM.bigdiv + 65535) >> 16;

   if(ADPCM.WritePending && ret > ADPCM.WritePending)
      ret = ADPCM.WritePending;

   if(ADPCM.ReadPending && ret > ADPCM.ReadPending)
      ret = ADPCM.ReadPending;

   return(ret);
}

static int32 CalcNextEvent(int32 base)
{
   int32 next_event = base;
   int32 ADPCM_ctne = ADPCM_ClocksToNextEvent();

   if(next_event > ADPCM_ctne)
      next_event = ADPCM_ctne;

   if(ClearACKDelay > 0 && next_event > ClearACKDelay)
      next_event = ClearACKDelay;

   if(next_event > pcecd_drive_ne)
      next_event = pcecd_drive_ne;

   if(Fader.Clocked && next_event > Fader.CycleCounter)
      next_event = Fader.CycleCounter;

   return(next_event);
}

static void update_irq_state(void)
{
        uint8           irq = _Port[2] & _Port[0x3] & (0x4|0x8|0x10|0x20|0x40);

	IRQCB((bool)irq);
}

static void StuffSubchannel(uint8 meow, int subindex)
{
   uint8 tmp_data = meow & 0x7F;

   if(subindex == -2)
      tmp_data = 0x00;
   else if(subindex == -1)
      tmp_data = 0x80;

   if(SimpleFIFO_CanWrite(&SubChannelFIFO))
      SimpleFIFO_Write(&SubChannelFIFO, &tmp_data, 1);

   _Port[0x3] |= 0x10;
   update_irq_state();
}

static void CDIRQ(int type)
{
   if(type & 0x8000)
   {
      type &= 0x7FFF;
      if(type == PCECD_Drive_IRQ_DATA_TRANSFER_DONE)
         _Port[0x3] &= ~0x20;
      else if(type == PCECD_Drive_IRQ_DATA_TRANSFER_READY)
         _Port[0x3] &= ~0x40;
   }
   else if(type == PCECD_Drive_IRQ_DATA_TRANSFER_DONE)
   {
      _Port[0x3] |= 0x20;
   }
   else if(type == PCECD_Drive_IRQ_DATA_TRANSFER_READY)
   {
      _Port[0x3] |= 0x40;
   }
   update_irq_state();
}

static void UpdateADPCMIRQState(void)
{
   _Port[0x3] &= ~0xC;

   _Port[0x3] |= ADPCM.HalfReached ? 0x4 : 0x0;	
   _Port[0x3] |= ADPCM.EndReached ? 0x8 : 0x0;

   update_irq_state();
}

static INLINE uint8 read_1808(int32 timestamp)
{
   uint8 ret = PCECD_Drive_GetDB();

   if(PCECD_Drive_GetREQ() && !PCECD_Drive_GetACK() && !PCECD_Drive_GetCD())
   {
      if(PCECD_Drive_GetIO())
      {
         PCECD_Drive_SetACK(true);
         ACKStatus = true;
         pcecd_drive_ne = PCECD_Drive_Run(timestamp);
         ClearACKDelay = 15 * 3;
      }
   }

   return(ret);
}

bool PCECD_SetSettings(const PCECD_Settings *settings)
{
   CDDAVolumeSetting = settings ? settings->CDDA_Volume : 1.0;
   Fader_SyncWhich();

   Blip_Synth_set_volume(&ADPCMSynth, 0.42735f * (settings ? settings->ADPCM_Volume : 1.0), 0x4000);
   ADPCMLP = settings ? settings->ADPCM_LPF : 0;
   RedoLPF(ADPCM.SampleFreq);

   PCECD_Drive_SetTransferRate(126000 * (settings ? settings->CD_Speed : 1));

   return true;
}

bool PCECD_Init(const PCECD_Settings *settings, void (*irqcb)(bool), double master_clock, unsigned int ocm, Blip_Buffer *soundbuf_l, Blip_Buffer *soundbuf_r)
{
   lastts = 0;

   OC_Multiplier = ocm;

   IRQCB = irqcb;

   sbuf[0] = soundbuf_l;
   sbuf[1] = soundbuf_r;

   /* The SubChannelFIFO buffer was allocated by the C++ ctor at static
    * init; allocate it explicitly now (capacity 16, a power of two). */
   if(!SimpleFIFO_Init(&SubChannelFIFO, 16))
      return(0);

   // Warning: magic number 126000 in PCECD_SetSettings() too
   PCECD_Drive_Init(3 * OC_Multiplier, sbuf[0], sbuf[1], 126000 * (settings ? settings->CD_Speed : 1), master_clock * OC_Multiplier, CDIRQ, StuffSubchannel);

   if(!(ADPCM.RAM = (uint8 *)malloc(0x10000)))
   {
      return(0);
   }

   PCECD_SetSettings(settings);

   ADPCM.bigdivacc = (int64)((double)master_clock * OC_Multiplier * 65536 / 32087.5);

   return true;
}


void PCECD_Close(void)
{
   if(ADPCM.RAM)
      free(ADPCM.RAM);
   ADPCM.RAM = NULL;
   SimpleFIFO_Deinit(&SubChannelFIFO);
   PCECD_Drive_Close();
}


void PCECD_Power(uint32 timestamp)
{
   if((int32)timestamp != lastts)
      (void)PCECD_Run(timestamp);

   IRQCB(0);

   PCECD_Drive_Power(timestamp);
   pcecd_drive_ne = 0x7fffffff;

   bBRAMEnabled = false;
   memset(_Port, 0, sizeof(_Port));
   ACKStatus = 0;
   ClearACKDelay = 0;

   memset(ADPCM.RAM, 0x00, 65536);

   ADPCM.ReadPending = ADPCM.WritePending = 0;
   ADPCM.ReadBuffer = 0;
   ADPCM.PlayBuffer = 0;

   ADPCM.LastCmd = 0;
   OKIADPCM_SetSample(&MSM5205, 0x800);
   OKIADPCM_SetSSI(&MSM5205, 0);

   ADPCM.SampleFreq = 0;
   ADPCM.LPF_SampleFreq = 0;
   ADPCM.lpf_accum = 0;
   RedoLPF(ADPCM.SampleFreq);
   ADPCM.bigdiv = ADPCM.bigdivacc * (16 - ADPCM.SampleFreq);

   ADPCM.Addr = 0;
   ADPCM.ReadAddr = 0;
   ADPCM.WriteAddr = 0;
   ADPCM.LengthCount = 0;
   ADPCM.LastCmd = 0;

   ADPCM.HalfReached = false;
   ADPCM.EndReached = false;
   ADPCM.Playing = false;
   ADPCM.PlayNibble = 0;

   UpdateADPCMIRQState();

   Fader.Command = 0x00;
   Fader.Volume = 0;
   Fader.CycleCounter = 0;
   Fader.CountValue = 0;
   Fader.Clocked = false;
}

bool PCECD_IsBRAMEnabled(void)
{
   return bBRAMEnabled;
}

uint8 PCECD_Read(uint32 timestamp, uint32 A)
{
   uint8 ret = 0;

   if((A & 0x18c0) == 0x18c0)
   {
      switch (A & 0x18cf)
      {
         case 0x18c1: ret = 0xaa; break;
         case 0x18c2:	ret = 0x55; break;
         case 0x18c3: ret = 0x00; break;
         case 0x18c5:	ret = 0xaa; break;
         case 0x18c6: ret = 0x55; break;
         case 0x18c7:	ret = 0x03; break;
      }
   }
   else
   {
      PCECD_Run(timestamp);

      switch(A & 0xf)
      {
         case 0x0:
            ret = 0;
            ret |= PCECD_Drive_GetBSY() ? 0x80 : 0x00;
            ret |= PCECD_Drive_GetREQ() ? 0x40 : 0x00;
            ret |= PCECD_Drive_GetMSG() ? 0x20 : 0x00;
            ret |= PCECD_Drive_GetCD() ? 0x10 : 0x00;
            ret |= PCECD_Drive_GetIO() ? 0x08 : 0x00;
            break;

         case 0x1: ret = PCECD_Drive_GetDB();
                   break;

         case 0x2: ret = _Port[2];
                   break;

         case 0x3: bBRAMEnabled = false;

                   /* switch left/right of digitized cd playback */
                   ret = _Port[0x3];
                   _Port[0x3] ^= 2;
                   break;

         case 0x4: ret = _Port[4];
                   break;

         case 0x5: if(_Port[0x3] & 0x2)
                      ret = RawPCMVolumeCache[1] & 0xff;	// Right
                   else
                      ret = RawPCMVolumeCache[0] & 0xff;	// Left
                   break;

         case 0x6: if(_Port[0x3] & 0x2)
                      ret = ((uint16)RawPCMVolumeCache[1]) >> 8;	// Right
                   else
                      ret = ((uint16)RawPCMVolumeCache[0]) >> 8;	// Left
                   break;

         case 0x7:
                   if(SubChannelFIFO.in_count > 0)
                      ret = SimpleFIFO_ReadUnit(&SubChannelFIFO);
                   else
                      ret = 0x00;	// Not sure if it's 0, 0xFF, the last byte read, or something else.

                   if(SubChannelFIFO.in_count == 0)
                   {
                      _Port[0x3] &= ~0x10;
                      update_irq_state();
                   }
                   break;

         case 0x8:
                   ret = read_1808(timestamp);
                   break;

         case 0xa: 
                   ADPCM.ReadPending = 19 * 3; //24 * 3;
                   ret = ADPCM.ReadBuffer;
                   break;

         case 0xb: 
                   ret = _Port[0xb];
                   break;

         case 0xc:
                   ret = 0x00;

                   ret |= (ADPCM.EndReached) ? 0x01 : 0x00;
                   ret |= (ADPCM.Playing) ? 0x08 : 0x00;
                   ret |= (ADPCM.WritePending > 0) ? 0x04 : 0x00;
                   ret |= (ADPCM.ReadPending > 0) ? 0x80 : 0x00;
                   break;   

         case 0xd: 
                   ret = ADPCM.LastCmd;
                   break;
      }
   }

   return(ret);
}

static INLINE void Fader_Run(const int32 clocks)
{
   if(Fader.Clocked)
   {
      Fader.CycleCounter -= clocks;
      while(Fader.CycleCounter <= 0)
      {
         if(Fader.Volume)
            Fader.Volume--;

         Fader_SyncWhich();

         Fader.CycleCounter += Fader.CountValue;
      }
   }
}


void PCECD_Write(uint32 timestamp, uint32 physAddr, uint8 data)
{
   const uint8 V = data;

   PCECD_Run(timestamp);

   switch (physAddr & 0xf)
   {
      case 0x0:
         PCECD_Drive_SetSEL(1);
         PCECD_Drive_Run(timestamp);
         PCECD_Drive_SetSEL(0);
         pcecd_drive_ne = PCECD_Drive_Run(timestamp);

         /* reset irq status */
         _Port[0x3] &= ~(0x20 | 0x40);	// TODO: Confirm writing this register really reset these bits.
         update_irq_state();
         break;

      case 0x1:		// $1801
         _Port[1] = data;
         PCECD_Drive_SetDB(data);
         pcecd_drive_ne = PCECD_Drive_Run(timestamp);
         break;

      case 0x2:		// $1802

         PCECD_Drive_SetACK(data & 0x80);
         pcecd_drive_ne = PCECD_Drive_Run(timestamp);
         _Port[2] = data;
         ACKStatus = (bool)(data & 0x80);
         update_irq_state();
         break;

      case 0x3:		// read only
         break;

      case 0x4:
         PCECD_Drive_SetRST(data & 0x2);
         pcecd_drive_ne = PCECD_Drive_Run(timestamp);
         if(data & 0x2)
         {
            _Port[0x3] &= ~0x70;
            update_irq_state();
         }
         _Port[4] = data;
         break;

      case 0x5:
      case 0x6:
         {
            int16 left, right;
            PCECD_Drive_GetCDDAValues(&left, &right);
            RawPCMVolumeCache[0] = ((int64)abs(left) * CDDAFadeVolume) >> 16;
            RawPCMVolumeCache[1] = ((int64)abs(right) * CDDAFadeVolume) >> 16;
         }
         break;

      case 0x7:	// $1807: D7=1 enables backup ram 
         if (data & 0x80)
            bBRAMEnabled = true;
         break;

      case 0x8:	// Set ADPCM address low
         if(ADPCM.LastCmd & 0x80)
            break;

         ADPCM.Addr &= 0xFF00;
         ADPCM.Addr |= V;

         // Length appears to be constantly latched when D4 is set(tested on a real system)
         if(ADPCM.LastCmd & 0x10)
            ADPCM.LengthCount = ADPCM.Addr;
         break;

      case 0x9:	// Set ADPCM address high
         if(ADPCM.LastCmd & 0x80)
            break;

         ADPCM.Addr &= 0x00FF;
         ADPCM.Addr |= V << 8;

         // Length appears to be constantly latched when D4 is set(tested on a real system)
         if(ADPCM.LastCmd & 0x10)
            ADPCM.LengthCount = ADPCM.Addr;
         break;

      case 0xa:
         ADPCM.WritePending = 3 * 11;
         ADPCM.WritePendingValue = data;
         break;

      case 0xb:	// adpcm dma
         _Port[0xb] = data;
         break;

      case 0xc:		// read-only
         break;

      case 0xd:
         if(data & 0x80)
         {
            ADPCM.Addr = 0;
            ADPCM.ReadAddr = 0;
            ADPCM.WriteAddr = 0;
            ADPCM.LengthCount = 0;
            ADPCM.LastCmd = 0;

            ADPCM.Playing = false;
            ADPCM.HalfReached = false;
            ADPCM.EndReached = false;

            ADPCM.PlayNibble = 0;

            UpdateADPCMIRQState();

            OKIADPCM_SetSample(&MSM5205, 0x800);
            OKIADPCM_SetSSI(&MSM5205, 0);
            break;
         }

         if(ADPCM.Playing && !(data & 0x20))
            ADPCM.Playing = false;

         if(!ADPCM.Playing && (data & 0x20))
         {
            ADPCM.bigdiv = ADPCM.bigdivacc * (16 - ADPCM.SampleFreq);
            ADPCM.Playing = true;
            ADPCM.HalfReached = false;	// Not sure about this.
            ADPCM.PlayNibble = 0;
            OKIADPCM_SetSample(&MSM5205, 0x800);
            OKIADPCM_SetSSI(&MSM5205, 0);
         }

         // Length appears to be constantly latched when D4 is set(tested on a real system)
         if(data & 0x10)
         {
            ADPCM.LengthCount = ADPCM.Addr;
            ADPCM.EndReached = false;
         }

         // D2 and D3 control read address
         if(!(ADPCM.LastCmd & 0x8) && (data & 0x08))
         {
            if(data & 0x4)
               ADPCM.ReadAddr = ADPCM.Addr;
            else
               ADPCM.ReadAddr = (ADPCM.Addr - 1) & 0xFFFF;

         }

         // D0 and D1 control write address
         if(!(ADPCM.LastCmd & 0x2) && (data & 0x2))
         {
            ADPCM.WriteAddr = ADPCM.Addr;
            if(!(data & 0x1))
               ADPCM.WriteAddr = (ADPCM.WriteAddr - 1) & 0xFFFF;
         }
         ADPCM.LastCmd = data;
         UpdateADPCMIRQState();
         break;

      case 0xe:		// Set ADPCM playback rate
         {
            uint8 freq = V & 0x0F;
            ADPCM.SampleFreq = freq;
         }
         break;

      case 0xf:
         Fader.Command = V;

         // Cancel fade
         if(!(V & 0x8))
         {
            Fader.Volume = 65536;
            Fader.CycleCounter = 0;
            Fader.CountValue = 0;
            Fader.Clocked = false;
         }
         else
         {
            Fader.CountValue = OC_Multiplier * 3 * ((V & 0x4) ? 273 : 655);	// 2.500s : 6.000s;

            if(!Fader.Clocked)
               Fader.CycleCounter = Fader.CountValue;

            Fader.Clocked = true;
         }
         Fader_SyncWhich();
         break;
   }
}

static INLINE void ADPCM_PB_Run(int32 basetime, int32 run_time)
{
   ADPCM.bigdiv -= run_time * 65536;

   while(ADPCM.bigdiv <= 0)
   {
      ADPCM.bigdiv += ADPCM.bigdivacc * (16 - ADPCM.SampleFreq);

      if(ADPCM.Playing && !ADPCM.PlayNibble)	// Do playback sample buffer fetch.
      {
         ADPCM.HalfReached = (ADPCM.LengthCount < 32768);
         if(!ADPCM.LengthCount && !(ADPCM.LastCmd & 0x10))
         {
            if(ADPCM.EndReached)
               ADPCM.HalfReached = false;

            ADPCM.EndReached = true;

            if(ADPCM.LastCmd & 0x40)
               ADPCM.Playing = false;
         }

         ADPCM.PlayBuffer = ADPCM.RAM[ADPCM.ReadAddr];
         ADPCM.ReadAddr = (ADPCM.ReadAddr + 1) & 0xFFFF;

         if(ADPCM.LengthCount && !(ADPCM.LastCmd & 0x10))
            ADPCM.LengthCount--;
      }

      if(ADPCM.Playing)
      {
         int32 pcm;
         uint8 nibble;

         uint32 synthtime;
         nibble = (ADPCM.PlayBuffer >> (ADPCM.PlayNibble ^ 4)) & 0x0F;
         pcm = OKIADPCM_Decode(&MSM5205, nibble) - 2048;

         ADPCM.PlayNibble ^= 4;

         pcm = (pcm * ADPCMFadeVolume) >> 8;

         /* One-pole IIR low-pass (ADPCM_LPF option). With lpf_coeff ==
          * 65536 (option off) this is an exact pass-through:
          * accum += (pcm - accum) * 1, out = accum = pcm. Otherwise it
          * rolls off highs at the cutoff RedoLPF() picked for the current
          * ADPCM sample frequency. accum is kept in Q16. */
         ADPCM.lpf_accum += (int32)((((int64)(pcm << 16) - ADPCM.lpf_accum) * ADPCM.lpf_coeff) >> 16);
         pcm = ADPCM.lpf_accum >> 16;

         synthtime = ((basetime + (ADPCM.bigdiv >> 16))) / (3 * OC_Multiplier);

         if(sbuf[0] && sbuf[1])
         {
            Blip_Synth_offset(&ADPCMSynth, synthtime, pcm - ADPCM.last_pcm, sbuf[0]);
            Blip_Synth_offset(&ADPCMSynth, synthtime, pcm - ADPCM.last_pcm, sbuf[1]);
         }
         ADPCM.last_pcm = pcm;
      }
   }
}

static INLINE void ADPCM_Run(const int32 clocks, const int32 timestamp)
{
   ADPCM_PB_Run(timestamp, clocks);

   if(ADPCM.WritePending)
   {
      ADPCM.WritePending -= clocks;
      if(ADPCM.WritePending <= 0)
      {
         ADPCM.HalfReached = (ADPCM.LengthCount < 32768);
         if(!(ADPCM.LastCmd & 0x10) && ADPCM.LengthCount < 0xFFFF)
            ADPCM.LengthCount++;

         ADPCM.RAM[ADPCM.WriteAddr++] = ADPCM.WritePendingValue;
         ADPCM.WritePending = 0;
      }
   }

   if(!ADPCM.WritePending)
   {
      if(_Port[0xb] & 0x3)
      {
         // Run PCECD_Drive before we examine the signals.
         pcecd_drive_ne = PCECD_Drive_Run(timestamp);

         if(!PCECD_Drive_GetCD() && PCECD_Drive_GetIO() && PCECD_Drive_GetREQ() && !PCECD_Drive_GetACK())
         {
            ADPCM.WritePendingValue = read_1808(timestamp);
            ADPCM.WritePending = 10 * 3;
         }
      }
   }

   if(ADPCM.ReadPending)
   {
      ADPCM.ReadPending -= clocks;
      if(ADPCM.ReadPending <= 0)
      {
         ADPCM.ReadBuffer = ADPCM.RAM[ADPCM.ReadAddr];
         ADPCM.ReadAddr = (ADPCM.ReadAddr + 1) & 0xFFFF;
         ADPCM.ReadPending = 0;

         ADPCM.HalfReached = (ADPCM.LengthCount < 32768);
         if(!(ADPCM.LastCmd & 0x10))
         {
            if(ADPCM.LengthCount)
               ADPCM.LengthCount--;
            else
            {
               ADPCM.EndReached = true;
               ADPCM.HalfReached = false;

               if(ADPCM.LastCmd & 0x40)
                  ADPCM.Playing = false;
            }
         }
      }
   }

   UpdateADPCMIRQState();
}

void PCECD_Run(uint32 in_timestamp)
{
   int32 clocks = in_timestamp - lastts;
   int32 running_ts = lastts;

   while(clocks > 0)
   {
      int32 chunk_clocks = CalcNextEvent(clocks);

      running_ts += chunk_clocks;

      if(ClearACKDelay > 0)
      {
         ClearACKDelay -= chunk_clocks;
         if(ClearACKDelay <= 0)
         {
            ACKStatus = false;
            PCECD_Drive_SetACK(false);
            PCECD_Drive_Run(running_ts);
            if(PCECD_Drive_GetCD())
            {
               _Port[0xb] &= ~1;
            }
         }
      }

      Fader_Run(chunk_clocks);

      ADPCM_Run(chunk_clocks, running_ts);
      pcecd_drive_ne = PCECD_Drive_Run(running_ts);

      clocks -= chunk_clocks;
   }

   lastts = in_timestamp;
}

void PCECD_ResetTS(void)
{
   if(ADPCM.SampleFreq != ADPCM.LPF_SampleFreq)
   {
      ADPCM.LPF_SampleFreq = ADPCM.SampleFreq;
      RedoLPF(ADPCM.LPF_SampleFreq);
   }
   PCECD_Drive_ResetTS();
   lastts = 0;
}

static int ADPCM_StateAction(StateMem *sm, int load, int data_only)
{
   uint32 ad_sample = OKIADPCM_GetSample(&MSM5205);
   int32  ad_ref_index = OKIADPCM_GetSSI(&MSM5205);

   SFORMAT StateRegs[] =
   {
      SFARRAY(ADPCM.RAM, 0x10000),

      SFVAR(ADPCM.bigdiv),
      SFVAR(ADPCM.Addr),
      SFVAR(ADPCM.ReadAddr),
      SFVAR(ADPCM.WriteAddr),
      SFVAR(ADPCM.LengthCount),
      SFVAR(ADPCM.LastCmd),
      SFVAR(ADPCM.SampleFreq),

      SFVAR(ADPCM.ReadPending),
      SFVAR(ADPCM.ReadBuffer),
      SFVAR(ADPCM.PlayBuffer),

      SFVAR(ADPCM.WritePending),
      SFVAR(ADPCM.WritePendingValue),

      SFVAR_BOOL(ADPCM.HalfReached),
      SFVAR_BOOL(ADPCM.EndReached),
      SFVAR_BOOL(ADPCM.Playing),

      SFVAR(ADPCM.PlayNibble),

      SFVAR(ad_sample),
      SFVAR(ad_ref_index),
      SFEND
   };

   int ret = MDFNSS_StateAction(sm, load, data_only, StateRegs, "APCM", false);
   if(load)
   {
      OKIADPCM_SetSample(&MSM5205, ad_sample);
      OKIADPCM_SetSSI(&MSM5205, ad_ref_index);
      RedoLPF(ADPCM.SampleFreq);
   }
   return(ret);
}

int PCECD_StateAction(StateMem *sm, int load, int data_only)
{
   SFORMAT StateRegs[] =
   {
      SFVAR_BOOL(bBRAMEnabled),
      SFVAR(ACKStatus),
      SFVAR(ClearACKDelay),
      SFARRAY16(RawPCMVolumeCache, 2),
      SFARRAY(_Port, sizeof(_Port)),

      SFVAR(Fader.Command),
      SFVAR(Fader.Volume),
      SFVAR(Fader.CycleCounter),
      SFVAR(Fader.CountValue),
      SFVAR_BOOL(Fader.Clocked),

      SFARRAY(&SubChannelFIFO.data[0], SubChannelFIFO.size),
      SFVAR(SubChannelFIFO.read_pos),
      SFVAR(SubChannelFIFO.write_pos),
      SFVAR(SubChannelFIFO.in_count),

      SFEND
   };

   int ret = MDFNSS_StateAction(sm, load, data_only, StateRegs, "PECD", false);
   ret &= PCECD_Drive_StateAction(sm, load, data_only, "CDRM");
   ret &= ADPCM_StateAction(sm, load, data_only);

   if(load)
   {
      Fader_SyncWhich();
      //PCECD_Drive_SetDB(_Port[1]);
      PCECD_Drive_SetACK(ACKStatus);
      PCECD_Drive_SetRST(_Port[4] & 0x2);

      SubChannelFIFO.read_pos %= SubChannelFIFO.size;
      SubChannelFIFO.write_pos %= SubChannelFIFO.size;
   }
   return(ret);
}
