#include <EDK.h>
#include "General.h"
#include "Sound.h"

////////////////////////////////////////////////////////////////////////////////


void QuickSound::CleanUp() {

  // stop playback
  if (hwo != NULL) {
    mm(waveOutReset(hwo));
    mm(waveOutClose(hwo));
  }

  // free buffer memory
  if (wh.lpData != NULL) {
    MemFree(wh.lpData);
    wh.lpData = NULL;
  }

}


// destructor
QuickSound::~QuickSound() {
  CleanUp();
}


// synchronize to 100% and return flag for display refresh rate adjustment
flag QuickSound::Synchronize() { 

  // get playback position
  MMTIME mmt;
  mmt.wType = TIME_BYTES;
  mm(waveOutGetPosition(hwo, &mmt, sizeof mmt));

  // calculate difference
  int iDelta = (iRecordPos - mmt.u.cb) & (iBufferSize - 1);
  if (iDelta > iBufferSize / 2) {
    iDelta -= iBufferSize;
  }

  // adjust recording position on very slow computers (generates a dropout)
  if (iDelta < iLatency / 2) {
    iRecordPos = (mmt.u.cb + iLatency) & (iBufferSize - 1);

  // wait on very fast computers
  } else if (iDelta > (iLatency + iLatency / 2)) {
    do {
      Sleep(0);
      mm(waveOutGetPosition(hwo, &mmt, sizeof mmt));
    } while ((int)((iRecordPos - mmt.u.cb) & (iBufferSize - 1)) > (iLatency + iLatency / 2));
  }

  // tell the display driver whether to display the next frame or not
  return iDelta > iLatency;

}


// get the next sample from the SID and write it to the buffer
void QuickSound::OnSample() {

  if (iSampleResolution == 16) {
    *(word*)&wh.lpData[iRecordPos] = (word)(0x8000 + pSID->GetNextSample(iLastClocksPerSample));
    iRecordPos += 2;
  } else {
    wh.lpData[iRecordPos++] = (byte)(pSID->GetNextSample(iLastClocksPerSample) >> 8);
  }

  if (iRecordPos == iBufferSize) {
    iRecordPos = 0;
  }

  iLastClocksPerSample = iClocksPerSample;
  iClockSum = iClockSum + iClockAdd - iC64ClockFrequency;
  if (iClockSum < 0) {
    iClockSum += iSampleRate;
    iLastClocksPerSample++;
  }
  Sample.StartCounter(iLastClocksPerSample);

}


// constructor
QuickSound::QuickSound(SID6581* pNewSID, int iNewC64ClockFrequency, int iNewSampleRate, int iNewSampleResolution) {

  pSID = pNewSID;

  hwo = NULL;
  iBufferSize = 0;
  memset(&wh, 0, sizeof wh);
  iRecordPos = 0;
  iLatency = 0;

  iSampleRate = iNewSampleRate;
  iC64ClockFrequency = iNewC64ClockFrequency;
  iSampleResolution = iNewSampleResolution;
  iClocksPerSample = iC64ClockFrequency / iSampleRate;
  iClockAdd = iClocksPerSample * iSampleRate;
  iClockSum = iClockAdd;
  iLastClocksPerSample = 1;

  try {

    WAVEFORMATEX wfx;
    wfx.wFormatTag = WAVE_FORMAT_PCM;
    wfx.nChannels = 1;
    wfx.nSamplesPerSec = iSampleRate;
    wfx.nAvgBytesPerSec = iSampleRate * (iSampleResolution / 8);
    wfx.nBlockAlign = (word)(iSampleResolution / 8);
    wfx.wBitsPerSample = (word)iSampleResolution;
    wfx.cbSize = 0;
    mm(waveOutOpen(&hwo, WAVE_MAPPER, &wfx, 0, 0, CALLBACK_NULL));

    iBufferSize = 32768;
    if (iSampleResolution > 8) {
      iBufferSize *= 2;
    }
    if (iSampleRate > 22050) {
      iBufferSize *= 2;
    }
    win(wh.lpData = (char*)MemAlloc(iBufferSize));
    memset(wh.lpData, 0, iBufferSize);
    wh.dwBufferLength = iBufferSize;
    wh.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP;
    wh.dwLoops = 0xFFFFFFFF;
    mm(waveOutPrepareHeader(hwo, &wh, sizeof WAVEHDR));
    mm(waveOutWrite(hwo, &wh, sizeof WAVEHDR));

    int i = GetInt("Sound", "QuickSound Latency (10..500 ms)", 200);
    iLatency = i * iSampleRate * (iSampleResolution / 8) / 1000;

    Sample.Init("Sample", this);
    Sample.SetOnFire((pfn)OnSample);
	extern Clock* gpClock;
	Sample.SetClock(*gpClock);
    Sample.StartCounter(1);

  } catch(...) {
    CleanUp();
    throw;
  }
}

// get description for display
const char* QuickSound::GetDescription() {
  return "QuickSound %d bit at %d Hz";
}
