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

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


void MMSound::CleanUp() {

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

  // free buffer memory
  for (int i = 0; i < iBuffers; i++) {
    if (awh[i].lpData != NULL) {
      MemFree(awh[i].lpData);
      awh[i].lpData = NULL;
    }
  }

}


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


// synchronize to 100% and tell whether there is time to display the next frame
int MMSound::Synchronize() { 
  for (int i = 1; i < iBuffers; i++) {
    if ((awh[(iRecordBuffer + i) % iBuffers].dwFlags & WHDR_DONE) == 0) {
      break;
    }
  }
  return (i - 1) * iPCClocksPerBuffer < 50000;
}


// get the next sample from SID and play the buffer if it is full
void MMSound::OnSample() {

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

  if (iRecordSample == iBufferSize) {
    awh[iRecordBuffer].dwFlags = 0;
    mm(waveOutPrepareHeader(hwo, &awh[iRecordBuffer], sizeof WAVEHDR));
    mm(waveOutWrite(hwo, &awh[iRecordBuffer], sizeof WAVEHDR));
    iRecordBuffer = (iRecordBuffer + 1) % iBuffers;
    while ((awh[iRecordBuffer].dwFlags & WHDR_DONE) == 0) {
      Sleep(0);
    }
    mm(waveOutUnprepareHeader(hwo, &awh[iRecordBuffer], sizeof WAVEHDR));
    iRecordSample = 0;
  }

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

}


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

  pSID = pNewSID;

  hwo = NULL;
  iBuffers = 0;
  iBufferSize = 0;
  iPCClocksPerBuffer = 0;
  memset(awh, 0, sizeof awh);
  iRecordBuffer = 0;
  iRecordSample = 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));

    assert(iMaxBuffers == 10);
    iBuffers = bound(3, GetInt("Sound", "MMSound Buffers à 100 ms (3..10)", 8), iMaxBuffers);
    iBufferSize = iSampleRate * (iSampleResolution / 8) / 10;
    for (int i = 0; i < iBuffers; i++) {
      awh[i].lpData = (char*)MemAlloc(iBufferSize);
      awh[i].dwBufferLength = iBufferSize;
      awh[i].dwFlags = WHDR_DONE;
    }

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

    LARGE_INTEGER PCClocks;
    win(QueryPerformanceFrequency(&PCClocks));
    assert(PCClocks.HighPart == 0);
    iPCClocksPerBuffer = PCClocks.LowPart / 10;

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

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