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


#define ds(x) \
  HRESULT h = (x); \
  if (h != DS_OK) { \
    DSError(h, #x, __FILE__, __LINE__); \
  }

global void DSError(HRESULT hError, char* pcFunction, char* pcFile, int iLine);


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

void DirectSound::CleanUp() {

  // unlock the buffer
  if (pAudioPtr1 != NULL) {
    ds(pPrimaryBuffer->Unlock(pAudioPtr1, dwAudioBytes1, pAudioPtr2, dwAudioBytes2));
    pAudioPtr1 = NULL;
  }

  // release the buffer
  if (pPrimaryBuffer != NULL) {
    ds(pPrimaryBuffer->Release());
    pPrimaryBuffer = NULL;
  }

  // release the DirectSound object
  if (pDirectSound != NULL) {
    ds(pDirectSound->Release());
    pDirectSound = NULL;
  }

  // free the DirectSound library
  if (hDirectSoundLib != NULL) {
    win(FreeLibrary(hDirectSoundLib));
    hDirectSoundLib = NULL;
  }

}

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


// synchronize to 100% and return idle time for display refresh rate adjustment
int DirectSound::Synchronize() { 
  return false;
}


void DirectSound::OnSample() {
  if (iSampleResolution == 16) {
    *(word*)pbRecord = (word)(0x8000 + pSID->GetNextSample(iLastClocksPerSample));
    pbRecord += 2;
  } else {
    *pbRecord++ = (byte)(pSID->GetNextSample(iLastClocksPerSample) >> 8);
  }
  iLastClocksPerSample = iClocksPerSample;
  iClockSum = iClockSum + iClockAdd - iC64ClockFrequency;
  if (iClockSum < 0) {
    iClockSum += iSampleRate;
    iLastClocksPerSample++;
  }
  Sample.SetCounter(iLastClocksPerSample);
}


// constructor
DirectSound::DirectSound(SID6581* pNewSID, int iNewC64ClockFrequency, int iNewSampleRate, int iNewSampleResolution, HWND hwnd) {

  pSID = pNewSID;

  hDirectSoundLib = NULL;
  pDirectSound = NULL;
  pPrimaryBuffer = NULL;

  pAudioPtr1 = NULL;
  dwAudioBytes1 = 0;
  pAudioPtr2 = NULL;
  dwAudioBytes2 = 0;

  pbRecord = NULL;

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

  try {

    // load DirectSound library
    win(hDirectSoundLib = LoadLibrary("DSound.dll"));

    // create the DirectSound object
    HRESULT (WINAPI* pfnDirectSoundCreate)(GUID*, LPDIRECTSOUND*, IUnknown*);
    win(pfnDirectSoundCreate = (HRESULT (WINAPI*)(GUID*, LPDIRECTSOUND*, IUnknown*))GetProcAddress(hDirectSoundLib, "DirectSoundCreate"));
    ds(pfnDirectSoundCreate(NULL, &pDirectSound, NULL));
    ds(pDirectSound->SetCooperativeLevel(hwnd, DSSCL_WRITEPRIMARY));

    // create the primary buffer
    DSBUFFERDESC dsbd;
    dsbd.dwSize = sizeof DSBUFFERDESC;
    dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLALL;
    dsbd.dwBufferBytes = 0;
    dsbd.dwReserved = 0;
    dsbd.lpwfxFormat = NULL;
    ds(pDirectSound->CreateSoundBuffer(&dsbd, &pPrimaryBuffer, NULL));

    // set primary buffer attributes
    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;
    ds(pPrimaryBuffer->SetFormat(&wfx));

    Sample.Init("Sample", this, (pfn)OnSample);
    Sample.SetCounter(1);

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

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