#include "cps.h"
// QSound - emulator for the actual QSound Chip

#include "cmc_int.h"


int nQscRate=0;

int Tams = -1;
int *Qs_s = NULL;

struct QChan
{
  unsigned char bKey;  // 1 if channel is playing

  unsigned int nStart; // Start of sample 16.12
  unsigned int nEnd;   // End of sample   16.12

  unsigned int nPos;   // Current position within the bank 16.12
  unsigned int nLoop;  // Loop offset from end

  unsigned int nVol;
  unsigned short nBank; // Bank we are currently playing a sample from
  char *PlayBank;      // Direct pointer
  unsigned short nPitch;
  unsigned int nAdvance;
  int nSide[2];

  unsigned char bDidKeyOn; // used for display
};

static struct QChan QChan[16];

void QsFree()
{
	free(Qs_s);
	Qs_s = NULL;
	Tams = -1;
	return;
}

static void MapBank(struct QChan *pc)
{
  unsigned int nBank;
  // Banks are 0x10000 samples long
  nBank=(pc->nBank&0xff)<<16;
  // Confirm whole bank is in range:
  // If bank is out of range use bank 0 instead
  if ((nBank+0x10000) > nCpsQSamLen) nBank=0;
  pc->PlayBank=(char *)CpsQSam+nBank;
}

static void CalcAdvance(struct QChan *pc)
{
  if (nQscRate) pc->nAdvance=pc->nPitch*24100/nQscRate;
}

int QscReset()
{
  int i=0;
  memset(QChan,0,sizeof(QChan));
  // Point all to bank 0
  for (i=0;i<16;i++) QChan[i].PlayBank=(char *)CpsQSam;
  return 0;
}

int QscScan(int nAction)
{
  int i=0;

  SCAN_VAR(QChan)
  if (nAction&2)
  {
    // Update bank pointers with new banks, and recalc nAdvance
    for (i=0;i<16;i++) { MapBank(QChan+i); CalcAdvance(QChan+i); }
  }
  return 0;
}

int QscDispInfo(int *pbKey,unsigned int *pnVol,int *pbDidKeyOn,int i)
{
  *pbKey=QChan[i].bKey;
  *pnVol=QChan[i].nVol;
  *pbDidKeyOn=QChan[i].bDidKeyOn;
  QChan[i].bDidKeyOn=0; // reset the flag for did key on since last call
  return 0;
}

void QscWrite(int a,int d)
{
  int nChanNum,r; struct QChan *pc;

  if (a>=0x90)
  {
    // unknown
//	printf("QSound reg %02X->%02X.\n",a,d);
	return;
  }
  if (a>=0x80)
  {
    int nPan;
    // Panning for channel
    nChanNum=a&15;
    pc=QChan+nChanNum; // Find channel
    nPan=d-0x110; // nPan = 0x00 to 0x20 now
    if (nPan<0x00) nPan=0x00;
    if (nPan>0x20) nPan=0x20;
    pc->nSide[0]=0x08+0x20-nPan;
    pc->nSide[1]=0x08+nPan;
    return;
  }

  // Get channel and register number
  nChanNum=a>>3; r=a&7;
  if (r==0) nChanNum++; // very strange!
  nChanNum&=15;

  pc=QChan+nChanNum; // Find channel

  if (r==0) { pc->nBank=(unsigned short)d; MapBank(pc); }
  if (r==1) { pc->nStart=d<<12; return; }
  if (r==2)
  {
    if (d==0) { pc->bKey=0; return; } // Key off

//    printf("QSound voice %02X keyed.\n", nChanNum);
	
	pc->nPitch=(unsigned short)d;
    CalcAdvance(pc);

    if (pc->bKey) return; // Key already on
    // Key on

    // Start playing the sample
    pc->nPos=pc->nStart;
    pc->bKey=1;
    pc->bDidKeyOn=1; // used for display

    return;
  }

  if (r==4) { pc->nLoop=d<<12; return; } // Loop offset
  if (r==5) { pc->nEnd= d<<12; return; }
  if (r==6)
  {
    pc->nVol=d;
    if (d==0) pc->bKey=0;
    return;
  }
}

// Write nLen*2 16-bit samples (stereo interleved) into Dest
int QscUpdate(short *Dest,int nLen)
{
  // int i=0; short *pd=NULL;

  if (!nLen) { return 0; }

  if (Tams < nLen)
  {
    if (Qs_s) { free(Qs_s); }
    Tams = nLen;
    Qs_s = (int *)malloc(sizeof(int) * 2 * Tams);
  }

  if (!Precalc) { cmc_4p_Precalc(); }

  memset(Qs_s, 0, nLen * 2 * sizeof(int));

  // Go through output buffer
  for (int c = 0; c < 16; c++)
  {
	// Add in all channels
    if (QChan[c].bKey)
    {
	  QChan[c].bKey = (unsigned char)ChannelMix_8S(Qs_s, nLen,
				    QChan[c].PlayBank,
	  			    QChan[c].nEnd - 0x1000,
				    (int *)&(QChan[c].nPos),
				    QChan[c].nVol * QChan[c].nSide[0] / 640,
				    QChan[c].nVol * QChan[c].nSide[1] / 640,
				    QChan[c].nLoop & 0xFFFFF000,
				    QChan[c].nAdvance);
    }
  }

  AdaptSoundBuff(Qs_s, Dest, nLen);

  return 0;
}
