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

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


void MMFastSound::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
MMFastSound::~MMFastSound() {
  CleanUp();
}


// synchronize to 100% and return idle time for display refresh rate adjustment
int MMFastSound::GetDeltaClocksAndWait() { 
  MMTIME mmt;
  mmt.wType = TIME_BYTES;
  mm(waveOutGetPosition(hwo, &mmt, sizeof mmt));
  
  trace("mmt.u.cb = %d", mmt.u.cb);

  return -999999;
}

// get the next sample from SID and put it into the sound output buffer
void MMFastSound::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) {
    iRecordSample = 0;
    iRecordBuffer++;
    if (iRecordBuffer == iBuffers) {
      iRecordBuffer = 0;
    }
  }
}

#define Class MMFastSound
extern Class MSVC4Bug;

static MMFastSound* gp;
proc(OnSample)
  mov gp,ThisReg
  } gp->OnSample(); __asm {
  // iClockSum = iClockSum + iClockAdd - iC64ClockFrequency;
  // if (iClockSum < 0) {
  //   iClockSum += iSampleRate;
  // }
  mov ECX,mvar(iClockSum)
  mov EBX,mvar(iClocksPerSample)
  add ECX,mvar(iClockAdd)
  lea EAX,mvar(Sample)
  sub ECX,mvar(iC64ClockFrequency)
  jnc OK
  add ECX,mvar(iSampleRate)
  inc EBX
OK:
  mov mvar(iClockSum),ECX
  mov mvar(iLastClocksPerSample),EBX
  jmp AddEvent
endp


// constructor
MMFastSound::MMFastSound(CEvent* pNewEventRoot, SID6581* pNewSID, int iNewC64ClockFrequency, int iNewSampleRate, int iNewSampleResolution) {

  pEventRoot = pNewEventRoot;
  pSID = pNewSID;

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

    iBufferSize = iSampleRate * (iSampleResolution / 8) / 1;
    for (int i = 0; i < iBuffers; i++) {
      win(awh[i].lpData = (char*)MemAlloc(iBufferSize));
      memset(awh[i].lpData, 0, iBufferSize);
      awh[i].dwBufferLength = iBufferSize;
    }
    awh[0].dwFlags = WHDR_BEGINLOOP;
    awh[0].dwLoops = 0xFFFFFFFF;
    awh[iBuffers - 1].dwFlags = WHDR_ENDLOOP;
    for (int iBugInMSVC4 = 0; iBugInMSVC4 < iBuffers; iBugInMSVC4++) {
      mm(waveOutPrepareHeader(hwo, &awh[iBugInMSVC4], sizeof WAVEHDR));
      mm(waveOutWrite(hwo, &awh[iBugInMSVC4], sizeof WAVEHDR));
    }

    assert(iBuffers == 20);
    //iLatency = (bound(10, GetInt("Sound", "MMFastSound Latency (10..200 ms)", 100), 200) + 5) / 10;

    __asm {
      pushad
      mov ThisReg,this
      mov ClockReg,giClockReg
      mov mvar(Sample.pObject),ThisReg
      mov mvar(Sample.pfnOnEvent),offset fn(OnSample)
      lea EAX,mvar(Sample)
      mov EBX,1
      call AddEvent
      mov giClockReg,ClockReg
      popad
    }

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

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

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

