/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2002 Xodnizel
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


LPDIRECTSOUND ppDS=0;           /* DirectSound object. */
LPDIRECTSOUNDBUFFER ppbuf=0;    /* Primary buffer object. */
LPDIRECTSOUNDBUFFER ppbufsec=0; /* Secondary buffer object. */
LPDIRECTSOUNDBUFFER ppbufw;     /* Buffer to actually write to. */

long DSBufferSize;              /* The size of the buffer that we can write to, in bytes. */

long BufHowMuch;                /* How many bytes we should try to buffer. */
DWORD ToWritePos;               /* Position which the next write to the buffer
                                   should write to.
                                */



DSBUFFERDESC DSBufferDesc;
WAVEFORMATEX wfa;
WAVEFORMATEX wf;

static int bittage;

void TrashSound(void)
{
 FCEUI_Sound(0);
 if(ppbufsec)
 {
  IDirectSoundBuffer_Stop(ppbufsec);
  IDirectSoundBuffer_Release(ppbufsec);
  ppbufsec=0;
 }
 if(ppbuf)
 {
  IDirectSoundBuffer_Stop(ppbuf);
  IDirectSoundBuffer_Release(ppbuf);
  ppbuf=0;
 }
 if(ppDS)
 {
  IDirectSound_Release(ppDS);
  ppDS=0;
 }
}

void CheckDStatus(void)
{
  DWORD status;
  status=0;
  IDirectSoundBuffer_GetStatus(ppbufw, &status);

  if(status&DSBSTATUS_BUFFERLOST)
  {
   IDirectSoundBuffer_Restore(ppbufw);
  }

  if(!(status&DSBSTATUS_PLAYING))
  {
   ToWritePos=0;
   IDirectSoundBuffer_SetFormat(ppbufw,&wf);
   IDirectSoundBuffer_Play(ppbufw,0,0,DSBPLAY_LOOPING);
  }
}

static uint32_t RawCanWrite(void)
{
 DWORD CurWritePos,CurPlayPos=0;

 CheckDStatus();

 CurWritePos=0;

 if(IDirectSoundBuffer_GetCurrentPosition(ppbufw,&CurPlayPos,&CurWritePos)==DS_OK)
 {
//   FCEU_DispMessage("%8d",(CurWritePos-CurPlayPos));
 }
 CurWritePos=(CurPlayPos+BufHowMuch)%DSBufferSize;

 /*  If the current write pos is >= half the buffer size less than the to write pos,
     assume DirectSound has wrapped around.
 */

 if(((int32_t)ToWritePos-(int32_t)CurWritePos) >= (DSBufferSize/2))
 {
  CurWritePos+=DSBufferSize;
  //FCEU_printf("Fixit: %d,%d,%d\n",ToWritePos,CurWritePos,CurWritePos-DSBufferSize);
 }
 if(ToWritePos<CurWritePos)
 {
  int32_t howmuch=(int32_t)CurWritePos-(int32_t)ToWritePos;
  if(howmuch > BufHowMuch)      /* Oopsie.  Severe buffer overflow... */
  {
   //FCEU_printf("Ack");
   ToWritePos=CurWritePos%DSBufferSize;
  }
  return(CurWritePos-ToWritePos);
 }
 else
  return(0);
}

int32 GetWriteSound(void)
{
 return(RawCanWrite() >> bittage);
}

int32 GetMaxSound(void)
{
 return( BufHowMuch >> bittage);
}

static int RawWrite(void *data, uint32_t len)
{
// uint32_t cw;

 //printf("Pre: %d\n",SexyALI_DSound_RawCanWrite(device));
 //fflush(stdout);

 CheckDStatus();
 /* In this block, we write as much data as we can, then we write
    the rest of it in >=1ms chunks.
 */
 while(len)
 {
  VOID *LockPtr[2]={0,0};
  DWORD LockLen[2]={0,0};
  int32_t curlen;

  while(!(curlen=RawCanWrite()))
  {
   Sleep(1);
  }

  if(curlen>len) curlen=len;

  if(DS_OK == IDirectSoundBuffer_Lock(ppbufw,ToWritePos,curlen,&LockPtr[0],&LockLen[0],&LockPtr[1],&LockLen[1],0))
  {
  }

  if(LockPtr[1] != 0 && LockPtr[1] != LockPtr[0])
  {
   memcpy(LockPtr[0],data,LockLen[0]);
   memcpy(LockPtr[1],data+LockLen[0],len-LockLen[0]);
  }
  else if(LockPtr[0])
  {
   memcpy(LockPtr[0],data,curlen);
  }
  IDirectSoundBuffer_Unlock(ppbufw,LockPtr[0],LockLen[0],LockPtr[1],LockLen[1]);
  ToWritePos=(ToWritePos+curlen)%DSBufferSize;

  len-=curlen;
  (uint8_t *) data+=curlen;
  if(len)
   Sleep(1);
 } // end while(len) loop


 return(1);
}

void FCEUD_WriteSoundData(int32 *Buffer, int Count)
{
 static int16 MBuffer[2 * 96000 / 50];  /* * 2 for safety. */
 int P;

 if(!bittage)
 {
  for(P=0;P<Count;P++)
   *(((uint8*)MBuffer)+P)=((int8)(Buffer[P]>>8))^128;
  RawWrite(MBuffer,Count);
 }
 else
 {
  for(P=0;P<Count;P++)
   MBuffer[P]=Buffer[P];
  //FCEU_printf("Pre: %d\n",RawCanWrite() / 2);
  RawWrite(MBuffer,Count * 2);
  //FCEU_printf("Post: %d\n",RawCanWrite() / 2);
 }
}

int InitSound()
{
 DSCAPS dscaps;
 DSBCAPS dsbcaps;

 memset(&wf,0x00,sizeof(wf));
 wf.wFormatTag = WAVE_FORMAT_PCM;
 wf.nChannels = 1;
 wf.nSamplesPerSec = soundrate;

 ddrval=DirectSoundCreate(0,&ppDS,0);
 if(ddrval != DS_OK)
 {
  FCEUD_PrintError("DirectSound: Error creating DirectSound object.");
  return 0;
 }

 if(soundoptions&SO_SECONDARY)
 {
  trysecondary:
  ddrval=IDirectSound_SetCooperativeLevel(ppDS,hAppWnd,DSSCL_PRIORITY);
  if(ddrval != DS_OK)
  {
   FCEUD_PrintError("DirectSound: Error setting cooperative level to DDSCL_PRIORITY.");
   TrashSound();
   return 0;
  }
 }
 else
 {
  ddrval=IDirectSound_SetCooperativeLevel(ppDS,hAppWnd,DSSCL_WRITEPRIMARY);
  if(ddrval != DS_OK)
  {
   FCEUD_PrintError("DirectSound: Error setting cooperative level to DDSCL_WRITEPRIMARY.  Forcing use of secondary sound buffer and trying again...");
   soundoptions|=SO_SECONDARY;
   goto trysecondary;
  }
 }
 memset(&dscaps,0x00,sizeof(dscaps));
 dscaps.dwSize=sizeof(dscaps);
 ddrval=IDirectSound_GetCaps(ppDS,&dscaps);
 if(ddrval!=DS_OK)
 {
  FCEUD_PrintError("DirectSound: Error getting capabilities.");
  return 0;
 }

 if(dscaps.dwFlags&DSCAPS_EMULDRIVER)
  FCEUD_PrintError("DirectSound: Sound device is being emulated through waveform-audio functions.  Sound quality will most likely be awful.  Try to update your sound device's sound drivers.");

 IDirectSound_Compact(ppDS);

 memset(&DSBufferDesc,0x00,sizeof(DSBUFFERDESC));
 DSBufferDesc.dwSize=sizeof(DSBufferDesc);
 if(soundoptions&SO_SECONDARY)
  DSBufferDesc.dwFlags=DSBCAPS_PRIMARYBUFFER;
 else
  DSBufferDesc.dwFlags=DSBCAPS_PRIMARYBUFFER|DSBCAPS_GETCURRENTPOSITION2;

 ddrval=IDirectSound_CreateSoundBuffer(ppDS,&DSBufferDesc,&ppbuf,0);
 if(ddrval != DS_OK)
 {
  FCEUD_PrintError("DirectSound: Error creating primary buffer.");
  TrashSound();
  return 0;
 }

 memset(&wfa,0x00,sizeof(wfa));

 if(soundoptions&SO_FORCE8BIT)
  bittage=0;
 else
 {
  bittage=1;
  if( (!(dscaps.dwFlags&DSCAPS_PRIMARY16BIT) && !(soundoptions&SO_SECONDARY)) ||
      (!(dscaps.dwFlags&DSCAPS_SECONDARY16BIT) && (soundoptions&SO_SECONDARY)))
  {
   FCEUD_PrintError("DirectSound: 16-bit sound is not supported.  Forcing 8-bit sound.");
   bittage=0;
   soundoptions|=SO_FORCE8BIT;
  }
 }

 wf.wBitsPerSample=8<<bittage;
 wf.nBlockAlign = bittage+1;
 wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;

 ddrval=IDirectSoundBuffer_SetFormat(ppbuf,&wf);
 if(ddrval != DS_OK)
 {
  FCEUD_PrintError("DirectSound: Error setting primary buffer format.");
  TrashSound();
  return 0;
 }

 IDirectSoundBuffer_GetFormat(ppbuf,&wfa,sizeof(wfa),0);

 if(soundoptions&SO_SECONDARY)
 {
  memset(&DSBufferDesc,0x00,sizeof(DSBUFFERDESC));
  DSBufferDesc.dwSize=sizeof(DSBufferDesc);
  DSBufferDesc.dwFlags=DSBCAPS_GETCURRENTPOSITION2;
  if(soundoptions&SO_GFOCUS)
   DSBufferDesc.dwFlags|=DSBCAPS_GLOBALFOCUS;
  DSBufferDesc.dwBufferBytes=65536;
  DSBufferDesc.lpwfxFormat=&wfa;
  ddrval=IDirectSound_CreateSoundBuffer(ppDS, &DSBufferDesc, &ppbufsec, 0);
  if(ddrval != DS_OK)
  {
   FCEUD_PrintError("DirectSound: Error creating secondary buffer.");
   TrashSound();
   return 0;
  }
 }

 //sprintf(TempArray,"%d\n",wfa.nSamplesPerSec);
 //FCEUD_PrintError(TempArray);

 if(soundoptions&SO_SECONDARY)
 {
  DSBufferSize=65536;
  IDirectSoundBuffer_SetCurrentPosition(ppbufsec,0);
  ppbufw=ppbufsec;
 }
 else
 {
  memset(&dsbcaps,0,sizeof(dsbcaps));
  dsbcaps.dwSize=sizeof(dsbcaps);
  ddrval=IDirectSoundBuffer_GetCaps(ppbuf,&dsbcaps);
  if(ddrval != DS_OK)
  {
   FCEUD_PrintError("DirectSound: Error getting buffer capabilities.");
   TrashSound();
   return 0;
  }

  DSBufferSize=dsbcaps.dwBufferBytes;

  if(DSBufferSize<8192)
  {
   FCEUD_PrintError("DirectSound: Primary buffer size is too small!");
   TrashSound();
   return 0;
  }
  ppbufw=ppbuf;
 }

 BufHowMuch=(soundbuftime*soundrate/1000)<<bittage;
 FCEUI_Sound(soundrate);
 return 1;
}

static HWND uug=0;

static void UpdateSD(HWND hwndDlg)
{
 int t;

 CheckDlgButton(hwndDlg,126,soundo?BST_CHECKED:BST_UNCHECKED);
 CheckDlgButton(hwndDlg,122,(soundoptions&SO_FORCE8BIT)?BST_CHECKED:BST_UNCHECKED);
 CheckDlgButton(hwndDlg,123,(soundoptions&SO_SECONDARY)?BST_CHECKED:BST_UNCHECKED);
 CheckDlgButton(hwndDlg,124,(soundoptions&SO_GFOCUS)?BST_CHECKED:BST_UNCHECKED);
 SendDlgItemMessage(hwndDlg,129,CB_SETCURSEL,soundquality,(LPARAM)(LPSTR)0);
 t=0;
 if(soundrate==22050) t=1;
 else if(soundrate==44100) t=2;
 else if(soundrate==48000) t=3;
 else if(soundrate==96000) t=4;
 SendDlgItemMessage(hwndDlg,200,CB_SETCURSEL,t,(LPARAM)(LPSTR)0);
}

BOOL CALLBACK SoundConCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{

  switch(uMsg) {
   case WM_NCRBUTTONDOWN:
   case WM_NCMBUTTONDOWN:
   case WM_NCLBUTTONDOWN:StopSound();break;

   case WM_INITDIALOG:
                /* Volume Trackbar */
                SendDlgItemMessage(hwndDlg,500,TBM_SETRANGE,1,MAKELONG(0,150));
                SendDlgItemMessage(hwndDlg,500,TBM_SETTICFREQ,25,0);
                SendDlgItemMessage(hwndDlg,500,TBM_SETPOS,1,150-soundvolume);

                /* buffer size time trackbar */
                SendDlgItemMessage(hwndDlg,128,TBM_SETRANGE,1,MAKELONG(15,200));
                SendDlgItemMessage(hwndDlg,128,TBM_SETTICFREQ,1,0);
                SendDlgItemMessage(hwndDlg,128,TBM_SETPOS,1,soundbuftime);

                {
                 char tbuf[8];
                 sprintf(tbuf,"%d",soundbuftime);
                 SetDlgItemText(hwndDlg,666,(LPTSTR)tbuf);
                }

                SendDlgItemMessage(hwndDlg,129,CB_ADDSTRING,0,(LPARAM)(LPSTR)"Low");
                SendDlgItemMessage(hwndDlg,129,CB_ADDSTRING,0,(LPARAM)(LPSTR)"High");
                SendDlgItemMessage(hwndDlg,129,CB_ADDSTRING,0,(LPARAM)(LPSTR)"Highest");

                SendDlgItemMessage(hwndDlg,200,CB_ADDSTRING,0,(LPARAM)(LPSTR)"11025");
                SendDlgItemMessage(hwndDlg,200,CB_ADDSTRING,0,(LPARAM)(LPSTR)"22050");
                SendDlgItemMessage(hwndDlg,200,CB_ADDSTRING,0,(LPARAM)(LPSTR)"44100");
                SendDlgItemMessage(hwndDlg,200,CB_ADDSTRING,0,(LPARAM)(LPSTR)"48000");
                SendDlgItemMessage(hwndDlg,200,CB_ADDSTRING,0,(LPARAM)(LPSTR)"96000");

                UpdateSD(hwndDlg);
                break;
   case WM_VSCROLL:
                soundvolume=150-SendDlgItemMessage(hwndDlg,500,TBM_GETPOS,0,0);
                FCEUI_SetSoundVolume(soundvolume);
                break;
   case WM_HSCROLL:
                {
                 char tbuf[8];
                 soundbuftime=SendDlgItemMessage(hwndDlg,128,TBM_GETPOS,0,0);
                 sprintf(tbuf,"%d",soundbuftime);
                 SetDlgItemText(hwndDlg,666,(LPTSTR)tbuf);
                 BufHowMuch=(soundbuftime*soundrate/1000)<<bittage;
                 //soundbufsize=(soundbuftime*soundrate/1000);
                }
                break;
   case WM_CLOSE:
   case WM_QUIT: goto gornk;
   case WM_COMMAND:
                switch(HIWORD(wParam))
                {
                 case CBN_SELENDOK:
                        switch(LOWORD(wParam))
                        {
                         case 200:
                         {
                          int tmp;
                          tmp=SendDlgItemMessage(hwndDlg,200,CB_GETCURSEL,0,(LPARAM)(LPSTR)0);
                          if(tmp==0) tmp=11025;
                          else if(tmp==1) tmp=22050;
                          else if(tmp==2) tmp=44100;
                          else if(tmp==3) tmp=48000;
                          else tmp=96000;
                          if(tmp!=soundrate)
                          {
                           soundrate=tmp;
                           if(soundrate<44100)
                           {
                            soundquality=0;
                            FCEUI_SetSoundQuality(0);
                            UpdateSD(hwndDlg);
                           }
                           if(soundo)
                           {
                            TrashSound();
                            soundo=InitSound();
                            UpdateSD(hwndDlg);
                           }
                          }
                         }
                         break;

                        case 129:
                         soundquality=SendDlgItemMessage(hwndDlg,129,CB_GETCURSEL,0,(LPARAM)(LPSTR)0);
                         if(soundrate<44100) soundquality=0;
                         FCEUI_SetSoundQuality(soundquality);
                         UpdateSD(hwndDlg);
                         break;
                        }
                        break;

                 case BN_CLICKED:
                        switch(LOWORD(wParam))
                        {
                         case 122:soundoptions^=SO_FORCE8BIT;
                                  if(soundo)
                                  {
                                   TrashSound();
                                   soundo=InitSound();
                                   UpdateSD(hwndDlg);
                                  }
                                  break;
                         case 123:soundoptions^=SO_SECONDARY;
                                  if(soundo)
                                  {
                                   TrashSound();
                                   soundo=InitSound();
                                   UpdateSD(hwndDlg);
                                  }
                                  break;
                         case 124:soundoptions^=SO_GFOCUS;
                                  if(soundo)
                                  {
                                   TrashSound();
                                   soundo=InitSound();
                                   UpdateSD(hwndDlg);
                                  }
                                  break;
                         case 126:soundo=!soundo;
                                  if(!soundo) TrashSound();
                                  else soundo=InitSound();
                                  UpdateSD(hwndDlg);
                                  break;
                        }
                }

                if(!(wParam>>16))
                switch(wParam&0xFFFF)
                {
                 case 1:
                        gornk:
                        DestroyWindow(hwndDlg);
                        uug=0;
                        break;
               }
              }
  return 0;
}


void ConfigSound(void)
{
 if(!uug)
  uug=CreateDialog(fceu_hInstance,"SOUNDCONFIG",0,SoundConCallB);
 else
  SetFocus(uug);
}


void StopSound(void)
{
 if(soundo)
 {
  VOID *LockPtr=0;
  DWORD LockLen=0;
  if(DS_OK == IDirectSoundBuffer_Lock(ppbufw,0,DSBufferSize,&LockPtr,&LockLen,0,0,0))
  {
   //FCEUD_PrintError("K");
   if(bittage)
    memset(LockPtr, 0, LockLen);
   else
    memset(LockPtr, 0x80, LockLen);
   IDirectSoundBuffer_Unlock(ppbufw,LockPtr,LockLen,0,0);
  }

  //IDirectSoundBuffer_Stop(ppbufw);
 }
}

#include "wave.c"
