1 /* Mednafen - Multi-system Emulator
2  *
3  * This program is free software; you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 2 of the License, or
6  * (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16  */
17 
18 #include "../sexyal.h"
19 
20 #include <windows.h>
21 #include <windowsx.h>
22 
23 #undef  WINNT
24 #define NONAMELESSUNION
25 
26 #define DIRECTSOUND_VERSION  0x0300
27 
28 #include <dsound.h>
29 
30 namespace Mednafen
31 {
32 
33 struct SexyAL_DSOUND
34 {
35 	LPDIRECTSOUND ppDS;		/* DirectSound interface object. */
36 	LPDIRECTSOUNDBUFFER ppbuf;	/* Primary buffer. */
37 	LPDIRECTSOUNDBUFFER ppbufsec;	/* Secondary buffer. */
38 	LPDIRECTSOUNDBUFFER ppbufw;	/* Buffer to do writes to. */
39 	WAVEFORMATEX wf;		/* Format of the primary and secondary buffers. */
40 	long DSBufferSize;		/* The size of the buffer that we can write to, in bytes. */
41 
42 	long BufHowMuch;		/* How many bytes of buffering we sync/wait to. */
43 	long BufHowMuchOBM;		/* How many bytes of buffering we actually do(at least temporarily). */
44 
45 	DWORD ToWritePos;		/* Position which the next write to the buffer
46 					   should write to.
47 					*/
48 };
49 
50 
51 static int Close(SexyAL_device *device);
52 static int RawCanWrite(SexyAL_device *device, uint32 *can_write);
53 static int RawWrite(SexyAL_device *device, const void *data, uint32 len);
54 
CheckStatus(SexyAL_DSOUND * tmp)55 static int CheckStatus(SexyAL_DSOUND *tmp)
56 {
57  DWORD status = 0;
58 
59  if(IDirectSoundBuffer_GetStatus(tmp->ppbufw, &status) != DS_OK)
60   return(0);
61 
62  if(status & DSBSTATUS_BUFFERLOST)
63   IDirectSoundBuffer_Restore(tmp->ppbufw);
64 
65  if(!(status&DSBSTATUS_PLAYING))
66  {
67   tmp->ToWritePos = 0;
68   IDirectSoundBuffer_SetCurrentPosition(tmp->ppbufsec, 0);
69   IDirectSoundBuffer_SetFormat(tmp->ppbufw, &tmp->wf);
70   IDirectSoundBuffer_Play(tmp->ppbufw, 0, 0, DSBPLAY_LOOPING);
71  }
72 
73  return(1);
74 }
75 
Pause(SexyAL_device * device,int state)76 static int Pause(SexyAL_device *device, int state)
77 {
78  return(0);
79 }
80 
81 
82 
SexyALI_DSound_Open(const char * id,SexyAL_format * format,SexyAL_buffering * buffering)83 SexyAL_device *SexyALI_DSound_Open(const char *id, SexyAL_format *format, SexyAL_buffering *buffering)
84 {
85  SexyAL_device *dev;
86  SexyAL_DSOUND *fobby;
87 
88  DSBUFFERDESC DSBufferDesc;
89  DSCAPS dscaps;
90  //DSBCAPS dsbcaps;
91 
92  dev = (SexyAL_device *)calloc(1, sizeof(SexyAL_device));
93  fobby = (SexyAL_DSOUND *)calloc(1, sizeof(SexyAL_DSOUND));
94 
95  memset(&fobby->wf,0,sizeof(WAVEFORMATEX));
96  fobby->wf.wFormatTag = WAVE_FORMAT_PCM;
97  fobby->wf.nChannels = format->channels;
98  fobby->wf.nSamplesPerSec = format->rate;
99 
100  if(DirectSoundCreate(0,&fobby->ppDS,0) != DS_OK)
101  {
102   free(dev);
103   free(fobby);
104   return(0);
105  }
106 
107  {
108   //HWND hWnd = GetForegroundWindow();    // Ugly.
109   //if(!hWnd)
110   //{ hWnd=GetDesktopWindow(); exit(1); }
111   HWND hWnd;
112   hWnd = GetDesktopWindow();
113   IDirectSound_SetCooperativeLevel(fobby->ppDS, hWnd, DSSCL_PRIORITY);
114  }
115  memset(&dscaps,0x00,sizeof(dscaps));
116  dscaps.dwSize=sizeof(dscaps);
117  IDirectSound_GetCaps(fobby->ppDS,&dscaps);
118  IDirectSound_Compact(fobby->ppDS);
119 
120  /* Create primary buffer */
121  memset(&DSBufferDesc,0x00,sizeof(DSBUFFERDESC));
122  DSBufferDesc.dwSize=sizeof(DSBufferDesc);
123  DSBufferDesc.dwFlags=DSBCAPS_PRIMARYBUFFER;
124 
125  if(IDirectSound_CreateSoundBuffer(fobby->ppDS,&DSBufferDesc,&fobby->ppbuf,0) != DS_OK)
126  {
127   IDirectSound_Release(fobby->ppDS);
128   free(dev);
129   free(fobby);
130   return(0);
131  }
132 
133  /* Set primary buffer format. */
134  if(format->sampformat == SEXYAL_FMT_PCMU8)
135   fobby->wf.wBitsPerSample=8;
136  else // if(format->sampformat == SEXYAL_FMT_PCMS16)
137  {
138   fobby->wf.wBitsPerSample=16;
139   format->sampformat=SEXYAL_FMT_PCMS16;
140  }
141 
142  fobby->wf.nBlockAlign = fobby->wf.nChannels * (fobby->wf.wBitsPerSample / 8);
143  fobby->wf.nAvgBytesPerSec=fobby->wf.nSamplesPerSec*fobby->wf.nBlockAlign;
144  if(IDirectSoundBuffer_SetFormat(fobby->ppbuf,&fobby->wf) != DS_OK)
145  {
146   IDirectSound_Release(fobby->ppbuf);
147   IDirectSound_Release(fobby->ppDS);
148   free(dev);
149   free(fobby);
150   return(0);
151  }
152 
153  //
154  // Buffers yay!
155  //
156  if(!buffering->ms)
157  {
158   buffering->ms = 52;
159  }
160  else if(buffering->overhead_kludge)
161  {
162   buffering->ms += 20;
163  }
164 
165  buffering->buffer_size = (int64)format->rate * buffering->ms / 1000;
166 
167  fobby->BufHowMuch = buffering->buffer_size * format->channels * SAMPFORMAT_BYTES(format->sampformat);
168  fobby->BufHowMuchOBM = fobby->BufHowMuch + ((30 * format->rate + 999) / 1000) * format->channels * SAMPFORMAT_BYTES(format->sampformat);
169 
170  buffering->latency = buffering->buffer_size; // TODO:  Add estimated WaveOut latency when using an emulated DirectSound device.
171  buffering->period_size = 0;
172 
173  /* Create secondary sound buffer */
174  {
175   int64 padding_extra = (((int64)format->rate * 200 + 999) / 1000) * format->channels * SAMPFORMAT_BYTES(format->sampformat);
176 
177   if(padding_extra < (fobby->BufHowMuchOBM * 2))
178    padding_extra = fobby->BufHowMuchOBM * 2;
179 
180   fobby->DSBufferSize = round_up_pow2(fobby->BufHowMuchOBM + padding_extra);
181 
182   //printf("Bufferbytesizenomnom: %u --- BufHowMuch: %u, BufHowMuchOBM: %u\n", fobby->DSBufferSize, fobby->BufHowMuch, fobby->BufHowMuchOBM);
183 
184   if(fobby->DSBufferSize < 65536)
185    fobby->DSBufferSize = 65536;
186  }
187  IDirectSoundBuffer_GetFormat(fobby->ppbuf,&fobby->wf,sizeof(WAVEFORMATEX),0);
188  memset(&DSBufferDesc,0x00,sizeof(DSBUFFERDESC));
189  DSBufferDesc.dwSize=sizeof(DSBufferDesc);
190  DSBufferDesc.dwFlags=DSBCAPS_GETCURRENTPOSITION2;
191  DSBufferDesc.dwFlags|=DSBCAPS_GLOBALFOCUS;
192  DSBufferDesc.dwBufferBytes = fobby->DSBufferSize;
193  DSBufferDesc.lpwfxFormat=&fobby->wf;
194  if(IDirectSound_CreateSoundBuffer(fobby->ppDS, &DSBufferDesc, &fobby->ppbufsec, 0) != DS_OK)
195  {
196   IDirectSound_Release(fobby->ppbuf);
197   IDirectSound_Release(fobby->ppDS);
198   free(dev);
199   free(fobby);
200   return(0);
201  }
202 
203  IDirectSoundBuffer_SetCurrentPosition(fobby->ppbufsec,0);
204  fobby->ppbufw=fobby->ppbufsec;
205 
206  memcpy(&dev->format, format, sizeof(SexyAL_format));
207 
208  //printf("%d\n",fobby->BufHowMuch);
209  //fflush(stdout);
210 
211  dev->private_data=fobby;
212  timeBeginPeriod(1);
213 
214  dev->RawWrite = RawWrite;
215  dev->RawCanWrite = RawCanWrite;
216  dev->RawClose = Close;
217  dev->Pause = Pause;
218 
219  return(dev);
220 }
221 
RawCanWriteInternal(SexyAL_device * device,uint32 * can_write,const bool OBM,const bool signal_nega=false)222 static int RawCanWriteInternal(SexyAL_device *device, uint32 *can_write, const bool OBM, const bool signal_nega = false)
223 {
224  SexyAL_DSOUND *tmp = (SexyAL_DSOUND *)device->private_data;
225  DWORD CurWritePos, CurPlayPos = 0;
226 
227  if(!CheckStatus(tmp))
228   return(0);
229 
230  CurWritePos=0;
231 
232  if(IDirectSoundBuffer_GetCurrentPosition(tmp->ppbufw,&CurPlayPos,&CurWritePos)==DS_OK)
233  {
234    //MDFN_DispMessage("%d",CurWritePos-CurPlayPos);
235  }
236 
237  CurWritePos = (CurPlayPos + tmp->BufHowMuchOBM) % tmp->DSBufferSize;
238 
239  /*  If the current write pos is >= half the buffer size less than the to write pos,
240      assume DirectSound has wrapped around.
241  */
242  if(((int32)tmp->ToWritePos-(int32)CurWritePos) >= (tmp->DSBufferSize/2))
243  {
244   CurWritePos += tmp->DSBufferSize;
245   //printf("Fixit: %d,%d,%d\n",tmp->ToWritePos,CurWritePos,CurWritePos-tmp->DSBufferSize);
246  }
247 
248  if(tmp->ToWritePos < CurWritePos)
249  {
250   int32 howmuch = (int32)CurWritePos - (int32)tmp->ToWritePos;
251 
252   //printf(" HM: %u\n", howmuch);
253 
254   // Handle (probably severe) buffer-underflow condition.
255   if(howmuch > tmp->BufHowMuchOBM)
256   {
257    //printf("Underrun: %d %d --- %d --- CWP=%d, TWP=%d\n", howmuch, tmp->BufHowMuchOBM, OBM, CurWritePos, tmp->ToWritePos);
258 
259    howmuch = tmp->BufHowMuchOBM;
260    tmp->ToWritePos = (CurWritePos + tmp->DSBufferSize - tmp->BufHowMuchOBM) % tmp->DSBufferSize;
261   }
262 
263   if(false == OBM)
264    howmuch -= tmp->BufHowMuchOBM - tmp->BufHowMuch;
265 
266   if(howmuch < 0)
267   {
268    if(signal_nega)
269     howmuch = ~0U;
270    else
271     howmuch = 0;
272   }
273 
274   *can_write = howmuch;
275  }
276  else
277   *can_write = 0;
278 
279  return(1);
280 }
281 
RawCanWrite(SexyAL_device * device,uint32 * can_write)282 static int RawCanWrite(SexyAL_device *device, uint32 *can_write)
283 {
284  return RawCanWriteInternal(device, can_write, false);
285 }
286 
RawWrite(SexyAL_device * device,const void * data,uint32 len)287 static int RawWrite(SexyAL_device *device, const void *data, uint32 len)
288 {
289  SexyAL_DSOUND *tmp = (SexyAL_DSOUND *)device->private_data;
290 
291  //printf("Pre: %d\n",SexyALI_DSound_RawCanWrite(device));
292  //fflush(stdout);
293 
294  if(!CheckStatus(tmp))
295   return(0);
296 
297  /* In this block, we write as much data as we can, then we write
298     the rest of it in >=1ms chunks.
299  */
300  while(len)
301  {
302   VOID *LockPtr[2]={0,0};
303   DWORD LockLen[2]={0,0};
304   uint32 curlen;
305   int rcw_rv;
306 
307   while((rcw_rv = RawCanWriteInternal(device, &curlen, true)) && !curlen)
308   {
309    //puts("WAITER1");
310    Sleep(1);
311   }
312 
313   // If RawCanWrite() failed, RawWrite must fail~
314   if(!rcw_rv)
315    return(0);
316 
317   if(curlen > len)
318    curlen = len;
319 
320   if(IDirectSoundBuffer_Lock(tmp->ppbufw, tmp->ToWritePos, curlen, &LockPtr[0], &LockLen[0], &LockPtr[1], &LockLen[1], 0) != DS_OK)
321   {
322    return(0);
323   }
324 
325   if(LockPtr[1] != 0 && LockPtr[1] != LockPtr[0])
326   {
327    memcpy(LockPtr[0], data, LockLen[0]);
328    memcpy(LockPtr[1], (uint8 *)data + LockLen[0], len - LockLen[0]);
329   }
330   else if(LockPtr[0])
331   {
332    memcpy(LockPtr[0], data, curlen);
333   }
334 
335   IDirectSoundBuffer_Unlock(tmp->ppbufw, LockPtr[0], LockLen[0], LockPtr[1], LockLen[1]);
336 
337   tmp->ToWritePos = (tmp->ToWritePos + curlen) % tmp->DSBufferSize;
338 
339   len -= curlen;
340   data = (uint8 *)data + curlen;
341 
342   if(len)
343    Sleep(1);
344  } // end while(len) loop
345 
346 
347  // Synchronize to effective buffer size.
348  {
349   uint32 curlen;
350   int rcw_rv;
351 
352   while((rcw_rv = RawCanWriteInternal(device, &curlen, false, true)) && (curlen == ~0U))
353   {
354    //puts("WAITER2");
355    Sleep(1);
356   }
357   // If RawCanWrite() failed, RawWrite must fail~
358   if(!rcw_rv)
359    return(0);
360  }
361 
362 
363  return(1);
364 }
365 
366 
367 
Close(SexyAL_device * device)368 static int Close(SexyAL_device *device)
369 {
370  if(device)
371  {
372   if(device->private_data)
373   {
374    SexyAL_DSOUND *tmp = (SexyAL_DSOUND *)device->private_data;
375    if(tmp->ppbufsec)
376    {
377     IDirectSoundBuffer_Stop(tmp->ppbufsec);
378     IDirectSoundBuffer_Release(tmp->ppbufsec);
379    }
380    if(tmp->ppbuf)
381    {
382     IDirectSoundBuffer_Stop(tmp->ppbuf);
383     IDirectSoundBuffer_Release(tmp->ppbuf);
384    }
385    if(tmp->ppDS)
386    {
387     IDirectSound_Release(tmp->ppDS);
388    }
389    free(device->private_data);
390   }
391   free(device);
392   timeEndPeriod(1);
393   return(1);
394  }
395  return(0);
396 }
397 
398 }
399