
/*
#    Sfront, a SAOL to C translator    
#    This file: Win32 DirectSound audio driver for sfront
#    Copyright (C) 1999  Regents of the University of California
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License (Version 2) as
#    published by the Free Software Foundation.
#
#    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
#
#    Author: Professor John Wawrzynek, UCB CS Division
#    Maintainance Email To: John Lazzaro, lazzaro@cs.berkeley.edu
*/

 
//////////////////////////
// dsound output
// by Vincent Siliakus & Thomas Jongepier
// november 1999
//////////////////////////
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <dsound.h>
#include <objbase.h>
#include <initguid.h> 

#include <stdio.h>

#define ASYS_SUCCESS ASYS_DONE

#if defined(ASYS_HASOUTPUT)


/* Global variables for audio output.
 */
 

char asyso_playing = 0;
int asyso_wrtCursor = 0;
int asyso_bufsize;                                /* buffer size in
bytes        */
int asyso_blksize;                                /* size preferred by
sfront    */

double asyso_speriod;
LPDIRECTSOUND 		lpDirectSound;
LPDIRECTSOUNDBUFFER lpDsb;	
LPDIRECTSOUNDBUFFER lpPrDsb;
LPDIRECTSOUNDNOTIFY lpDsNotify;
HANDLE				*hBuffPos;
DSBPOSITIONNOTIFY   *PositionNotify;
PCMWAVEFORMAT		pcmwfp;
PCMWAVEFORMAT		pcmwf;
DSBUFFERDESC		dsprbdescp;
DSBUFFERDESC		dsbdesc;
DSCAPS				dscaps;
DSBCAPS				pr,sec;
HRESULT       		hr;	
unsigned long		ds_numofsubbuff;
unsigned long		ds_subbuffercounter=0;
unsigned long		ds_writeposition=0;
unsigned long		ds_counter=0;
unsigned long		ds_resetwritecrspoint=0;
unsigned long		SECBUFFBYTESIZE;
unsigned long		SUBBUFFBYTESIZE;
unsigned long		SUBBUFFSAMPLESIZE;
unsigned long		WRITEPOSITIONOFFSET;
unsigned long		TIMES;
unsigned long		EXCEPTIONTIMES;
 
  
void DevCaps(DSCAPS *);
void PrBuffCaps(DSBCAPS *);
void SecBuffCaps(DSBCAPS *);
void WaitForNotification();
void Close();
void Play();
void Stop();
int InitOutput(unsigned long);
 
void DevCaps(DSCAPS *DSCaps)
{	
	DSCaps->dwSize = sizeof(DSCAPS);
	IDirectSound_GetCaps(lpDirectSound,DSCaps);
}
 
void PrBuffCaps(DSBCAPS *PrBCaps)
{	
	PrBCaps->dwSize = sizeof(DSBCAPS); 
	IDirectSoundBuffer_GetCaps(lpPrDsb,PrBCaps); 
}
 
void SecBuffCaps(DSBCAPS *SecBCaps)
{	
	SecBCaps->dwSize = sizeof(DSBCAPS);
	IDirectSoundBuffer_GetCaps(lpDsb,SecBCaps);
}
 
void WaitForNotification()
{
	DWORD EventNumber = (WaitForMultipleObjects
		(ds_numofsubbuff, hBuffPos, FALSE, INFINITE));
	//ResetEvent(hBuffPos[EventNumber]); //for manual ResetEvent. 
}
 
void Close()
{ 
	for(ds_counter=0;ds_counter<ds_numofsubbuff;ds_counter++)
		CloseHandle(hBuffPos[ds_counter]);
	IDirectSoundBuffer_Release(lpDsb);
	IDirectSound_Release(lpDirectSound);
}
 
void Play() 
{ 
	IDirectSoundBuffer_Play(lpDsb,0,0,DSBPLAY_LOOPING ); 
} 
  
void Stop()
{
	IDirectSoundBuffer_Stop(lpDsb);
}
 
 
int InitOutput(unsigned long sys_osize, 
			   unsigned long samplerate, 
			   unsigned long numchannels)
{ 
	HWND hwnd = GetForegroundWindow();
	if(hwnd==NULL)hwnd = GetDesktopWindow();
	SECBUFFBYTESIZE	 =	sys_osize*16;//use 16 or 32	
	SUBBUFFBYTESIZE	 =	sys_osize;
	SUBBUFFSAMPLESIZE=	sys_osize/2;
  	WRITEPOSITIONOFFSET = SUBBUFFBYTESIZE;
	TIMES=SECBUFFBYTESIZE/SUBBUFFBYTESIZE;
	EXCEPTIONTIMES = (WRITEPOSITIONOFFSET/SUBBUFFBYTESIZE);
	ds_resetwritecrspoint =  
		SECBUFFBYTESIZE-(EXCEPTIONTIMES*SUBBUFFBYTESIZE);
	ds_numofsubbuff=SECBUFFBYTESIZE/SUBBUFFBYTESIZE;
	////////////////////////////////
	//dsound init
	////////////////////////////////
	//Create DirectSound Object
	if FAILED(DirectSoundCreate(NULL, &lpDirectSound, NULL))
		return ASYS_ERROR;
	//
	//Set Cooperative level of DirectSound Object
	if FAILED(IDirectSound_SetCooperativeLevel(lpDirectSound,hwnd, 
			DSSCL_PRIORITY))return ASYS_ERROR;
	//    
	////////////////////////////////  
   // Set up wave format structure.
   memset(&pcmwfp, 0, sizeof(PCMWAVEFORMAT));
    pcmwfp.wf.wFormatTag = WAVE_FORMAT_PCM;
    pcmwfp.wf.nChannels = numchannels;
    pcmwfp.wf.nSamplesPerSec = samplerate;
    pcmwfp.wf.nBlockAlign = 2*numchannels;
    pcmwfp.wf.nAvgBytesPerSec =
		pcmwfp.wf.nSamplesPerSec * pcmwfp.wf.nBlockAlign;
    pcmwfp.wBitsPerSample = 16;
	//
	////////////////////////////////
    // Set up DSBUFFERDESC structure.
    memset(&dsprbdescp, 0, sizeof(DSBUFFERDESC)); // Zero it out.
    dsprbdescp.dwSize = sizeof(DSBUFFERDESC);
    dsprbdescp.dwFlags = DSBCAPS_PRIMARYBUFFER;
    dsprbdescp.dwBufferBytes = 0;
    dsprbdescp.lpwfxFormat = NULL;
	//
	////////////////////////////////
	// Create Primary buffer.  
    if FAILED(IDirectSound_CreateSoundBuffer(lpDirectSound,&dsprbdescp, 
	&lpPrDsb, NULL))return ASYS_ERROR;
	// Set primary buffer to desired format.
    if FAILED(IDirectSoundBuffer_SetFormat(lpPrDsb,
		(LPWAVEFORMATEX)&pcmwfp))return ASYS_ERROR;
	//start playing (no sound yet)
    IDirectSoundBuffer_Play(lpPrDsb,0,0,DSBPLAY_LOOPING);
	//
    // Set up wave format structure.
    memset(&pcmwf, 0, sizeof(PCMWAVEFORMAT));
    pcmwf.wf.wFormatTag = WAVE_FORMAT_PCM;
    pcmwf.wf.nChannels = numchannels;
    pcmwf.wf.nSamplesPerSec = samplerate;
    pcmwf.wf.nBlockAlign = 2*numchannels;
    pcmwf.wf.nAvgBytesPerSec =
		pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign;
    pcmwf.wBitsPerSample = 16;	
	//
    // Set up DSBUFFERDESC structure.
    memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); // Zero it out.
    dsbdesc.dwSize = sizeof(DSBUFFERDESC);
    // Need default controls (pan, volume, frequency).
    dsbdesc.dwFlags = 
					 DSBCAPS_GLOBALFOCUS 
					|DSBCAPS_GETCURRENTPOSITION2
					|DSBCAPS_CTRLPOSITIONNOTIFY ;
    dsbdesc.dwBufferBytes = SECBUFFBYTESIZE;
    dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf;
	//
	////////////////////////////////
	if FAILED(IDirectSound_CreateSoundBuffer(lpDirectSound,&dsbdesc, 
		&lpDsb, NULL))return ASYS_ERROR;
    //
	PositionNotify=(DSBPOSITIONNOTIFY*)malloc(ds_numofsubbuff*
		(sizeof(DSBPOSITIONNOTIFY)));
	hBuffPos=(HANDLE*)malloc(ds_numofsubbuff*sizeof(HANDLE));
	//
	for(ds_counter=0;ds_counter<ds_numofsubbuff;ds_counter++){
		hBuffPos[ds_counter]=CreateEvent(NULL,FALSE,FALSE,NULL);
		PositionNotify[ds_counter].dwOffset = SUBBUFFBYTESIZE*ds_counter;
		PositionNotify[ds_counter].hEventNotify=hBuffPos[ds_counter]; 
	}
	//
	if FAILED(IDirectSoundBuffer_QueryInterface(lpDsb,
		&IID_IDirectSoundNotify,(LPVOID *)&lpDsNotify))return ASYS_ERROR;  
	//
	IDirectSoundNotify_SetNotificationPositions(lpDsNotify,ds_numofsubbuff,
		PositionNotify); 
	//\\\\\\\\\\\\\\\
	//
	Play();
	return ASYS_SUCCESS;
}
 
 
#endif
 
#if (defined(ASYS_HASOUTPUT) && !defined(ASYS_HASINPUT))
 
/* Opens waveform output for a given srate/channels.
 */
 
int asys_osetup(long srate, long ochannels, long osample, 
                char * oname, long toption)
{

  long osize = ASYS_OCHAN*ACYCLE;

  InitOutput(osize,srate,ochannels);
  return ASYS_SUCCESS;
}
 
#endif
 
#if defined(ASYS_HASOUTPUT)
  
/*
 * Shuts down audio system.
 */
 
void asys_oshutdown(void)
 
{
  Stop();
  Close();
}
 
#endif
 
#if defined(ASYS_HASOUTPUT)
 
int asys_putbuf(short **buffer, long * osize)
 
{ 	
	DWORD dwBytes;
	*osize=SUBBUFFSAMPLESIZE; 
	ds_writeposition=ds_subbuffercounter*SUBBUFFBYTESIZE;	
	WaitForNotification();	
	if(ds_writeposition>ds_resetwritecrspoint)
	{
		ds_subbuffercounter=0;
		ds_writeposition=0;
	}
	hr = IDirectSoundBuffer_Unlock(lpDsb,*buffer, 
		SUBBUFFBYTESIZE, NULL,NULL);  
    hr = IDirectSoundBuffer_Lock(lpDsb,ds_writeposition, 
		SUBBUFFBYTESIZE, (LPVOID*)buffer,&dwBytes, NULL, 0, NULL);    
	ds_subbuffercounter++;
	return ASYS_DONE;
}
   
/****************************************************************/
/*        creates buffer, and generates starting silence        */
/****************************************************************/

int asys_preamble(ASYS_OTYPE * asys_obuf[], long * osize)

{
  int i;

  /* emulates old interface, since I don't understand asys_putbuf */
  /* well enough to make direct modifications  */

  /* set up first call, which allocates buffer */

  *asys_obuf = NULL;
  *osize = 0;

  if (asys_putbuf(asys_obuf, osize) == ASYS_ERROR)
    return ASYS_ERROR;

  /* clear allocated buffer */

  for (i = 0; i < (*osize); i++)
    (*asys_obuf)[i] = 0;

  /* send the 4 silence putbuf's of old API */

  for(i = 0; i < 4; i++)
    if (asys_putbuf(asys_obuf, osize) == ASYS_ERROR)
      return ASYS_ERROR;

  return ASYS_DONE;
}


#endif
 

/* Currently not supported audio input.  All routines are dummies.
 */

#if (!defined(ASYS_HASOUTPUT) && defined(ASYS_HASINPUT))

/* Initializes audio input.
 */
int asys_isetup(long srate, long ichannels, long isample, 
                char * iname, long toption)

{
  return ASYS_ERROR;
}

#endif

#if (defined(ASYS_HASOUTPUT) && defined(ASYS_HASINPUT))

/* Initialized bidirectional audio flows. */
int asys_iosetup(long srate, long ichannels, long ochannels,
                 long isample, long osample, 
                 char * iname, char * oname, long toption)

{
  return ASYS_ERROR;
}

#endif

#if defined(ASYS_HASINPUT)

/* Shuts down audio input device.
 */

void asys_ishutdown(void)

{
}

#endif

#if defined(ASYS_HASINPUT)

/* Gets one frame of audio from input.
 */

int asys_getbuf(ASYS_ITYPE * asys_ibuf[], long * isize)
{
  return ASYS_ERROR;
}

#endif


/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*  ksync() and ksyncinit() for -playback and -timesync modes  */
/*_____________________________________________________________*/

#if defined(ASYS_KSYNC)

#if (ASYS_TIMEOPTION != ASYS_TIMESYNC)

#include <windows.h>
#include <windowsx.h>
#include <winbase.h>
#include <mmsystem.h>

#define SYNC_MAXCOUNT 4294967296L
#define SYNC_KMTIME KUTIME/1000

DWORD sync_last, sync_this, sync_delay;
TIMECAPS sync_tc;
BOOL sync_waitFlag;

/***********************************************************/
/*         support routines                                */
/***********************************************************/

void CALLBACK sync_callBack(UINT uID, UINT uMsg, 
                            DWORD dwUser, DWORD dw1, DWORD dw2)
{
  sync_waitFlag = TRUE;
}

BOOL sync_sleep(UINT ms)
{
  sync_waitFlag = FALSE;
  if FAILED(timeSetEvent(ms, (UINT) 1, &sync_callBack, 0L, TIME_ONESHOT))
    {
      fprintf(stderr, "timeSetEvent error.\n");
      exit(-1);
    }
  // spin wait
  while (!sync_waitFlag) {}
  return TRUE;
}

/***********************************************************/
/*         synchronizes on k-rate boundaries               */
/***********************************************************/

float ksync()

{
  float ret;

  sync_this = timeGetTime();
  
  if (sync_this<sync_last)   // timer wrap around
    sync_last = (DWORD) (sync_last - SYNC_MAXCOUNT) + SYNC_KMTIME;
  else 
    sync_last += SYNC_KMTIME;

  sync_delay = sync_last - sync_this;
  if (sync_delay>0)
    {
      /* sync_sleep(sync_delay); */   /* can this be safely commented out? */
      ret = (SYNC_KMTIME - sync_delay)/SYNC_KMTIME;
    }
  else 
    ret = 1.0F;

  return ret;
}

/***********************************************************/
/*         initializes k-rate boundaries sync              */
/***********************************************************/

void ksyncinit()

{
  // Set 1ms time resolution
  if FAILED(timeGetDevCaps(&sync_tc, sizeof(TIMECAPS)))
    {
      fprintf(stderr, "TimeGetDevCaps error.\n");
      exit(-1);
    }
  sync_last = timeGetTime();
}

#endif


#if (ASYS_TIMEOPTION == ASYS_TIMESYNC)

#include <windows.h>
#include <windowsx.h>
#include <winbase.h>
#include <mmsystem.h>

#define SYNC_MAXCOUNT 4294967296L
#define SYNC_KMTIME KUTIME/1000

DWORD sync_last, sync_this, sync_delay;
TIMECAPS sync_tc;
BOOL sync_waitFlag;

/***********************************************************/
/*         support routines                                */
/***********************************************************/

void CALLBACK sync_callBack(UINT uID, UINT uMsg, 
                            DWORD dwUser, DWORD dw1, DWORD dw2)
{
  sync_waitFlag = TRUE;
}

BOOL sync_sleep(UINT ms)
{
  sync_waitFlag = FALSE;
  if FAILED(timeSetEvent(ms, (UINT) 1, &sync_callBack, 0L, TIME_ONESHOT))
    {
      fprintf(stderr, "timeSetEvent error.\n");
      exit(-1);
    }
  // spin wait
  while (!sync_waitFlag) {}
  return TRUE;
}

/***********************************************************/
/*         synchronizes on k-rate boundaries               */
/***********************************************************/

float ksync()

{
  float ret;

  sync_this = timeGetTime();
  
  if (sync_this<sync_last)   // timer wrap around
    sync_last = (DWORD) (sync_last - SYNC_MAXCOUNT) + SYNC_KMTIME;
  else 
    sync_last += SYNC_KMTIME;

  sync_delay = sync_last - sync_this;
  if (sync_delay>0)
    {
      sync_sleep(sync_delay);
      ret = (SYNC_KMTIME - sync_delay)/SYNC_KMTIME;
    }
  else 
    ret = 1.0F;

  return ret;
}

/***********************************************************/
/*         initializes k-rate boundaries sync              */
/***********************************************************/

void ksyncinit()

{
  // Set 1ms time resolution
  if FAILED(timeGetDevCaps(&sync_tc, sizeof(TIMECAPS)))
    {
      fprintf(stderr, "TimeGetDevCaps error.\n");
      exit(-1);
    }
  sync_last = timeGetTime();
}

#endif

#endif  /* ASYS_KSYNC */
