1 // DirectSound module
2 #include "burner.h"
3 #include "aud_dsp.h"
4 #include <math.h>
5
6 #include <initguid.h>
7 #define DIRECTSOUND_VERSION 0x0300 // Only need version from DirectX 3
8 #include <dsound.h>
9
10 #include "dsound_core.h"
11
12 // Sound is split into a series of 'segs', one seg for each frame
13 // The Loop buffer is a multiple of this seg length.
14
15 static IDirectSound* pDS = NULL; // DirectSound interface
16 static IDirectSoundBuffer* pdsbPrim = NULL; // Primary buffer
17 static IDirectSoundBuffer* pdsbLoop = NULL; // (Secondary) Loop buffer
18 static int cbLoopLen = 0; // Loop length (in bytes) calculated
19
20 int (*DSoundGetNextSound)(int); // Callback used to request more sound
21 static int DxSoundSetVolume(); // forward
22
23 static HANDLE hDSoundEvent = NULL;
24 static HANDLE hAbortEvent = NULL;
25 static HANDLE hAbortAckEvent = NULL;
26
27 static int nDSoundFps; // Application fps * 100
28 static long nDSoundVol = 0;
29
DSoundGetNextSoundFiller(int)30 static int DSoundGetNextSoundFiller(int) // int bDraw
31 {
32 if (nAudNextSound == NULL) {
33 return 1;
34 }
35 memset(nAudNextSound, 0, nAudSegLen << 2); // Write silence into the buffer
36
37 return 0;
38 }
39
DxSetCallback(int (* pCallback)(int))40 static inline int DxSetCallback(int (*pCallback)(int))
41 {
42 if (pCallback == NULL) {
43 DSoundGetNextSound = DSoundGetNextSoundFiller;
44 } else {
45 DSoundGetNextSound = pCallback;
46 }
47
48 return 0;
49 }
50
DxBlankSound()51 static int DxBlankSound()
52 {
53 void *pData = NULL, *pData2 = NULL;
54 DWORD cbLen = 0, cbLen2 = 0;
55
56 // blank the nAudNextSound buffer
57 if (nAudNextSound) {
58 memset(nAudNextSound, 0, nAudSegLen << 2);
59 }
60
61 // Lock the Loop buffer
62 if (FAILED(pdsbLoop->Lock(0, cbLoopLen, &pData, &cbLen, &pData2, &cbLen2, 0))) {
63 return 1;
64 }
65 memset(pData, 0, cbLen);
66
67 // Unlock (2nd 0 is because we wrote nothing to second part)
68 pdsbLoop->Unlock(pData, cbLen, pData2, 0);
69
70 return 0;
71 }
72
73 static int nDSoundNextSeg = 0; // We have filled the sound in the loop up to the beginning of 'nNextSeg'
74
75 #define WRAP_INC(x) { x++; if (x >= nAudSegCount) x = 0; }
76
77 bool bAbortSound = false;
78
79 // This function checks the DSound loop, and if necessary does a callback to update the emulation
DxSoundCheck()80 static int DxSoundCheck()
81 {
82 int nPlaySeg = 0, nFollowingSeg = 0;
83 DWORD nPlay = 0, nWrite = 0;
84
85 if (bAbortSound)
86 {
87 bAbortSound = false;
88 DWORD wait_object = SignalObjectAndWait(hAbortEvent, hAbortAckEvent, false, 2500);
89 if (wait_object != WAIT_OBJECT_0)
90 {
91
92 #ifdef PRINT_DEBUG_INFO
93 //if (wait_object == WAIT_TIMEOUT)
94 // bprintf(0, _T("*** DirectSound playback stalled.\n"));
95 #endif
96
97 ResetEvent(hAbortAckEvent);
98 }
99
100 return 0;
101 }
102
103 HANDLE handles[] = { hDSoundEvent, hAbortEvent };
104
105
106 DWORD wait_object = WaitForMultipleObjects(2, handles, false, 2500);
107 if (wait_object != WAIT_OBJECT_0)
108 {
109
110 #ifdef PRINT_DEBUG_INFO
111 if (wait_object == WAIT_TIMEOUT)
112 bprintf(0, _T("*** DirectSound playback notification timeout.\n"));
113 if (wait_object == WAIT_FAILED)
114 bprintf(0, _T("*** DirectSound playback wait failed.\n"));
115 // if (wait_object == WAIT_OBJECT_0 + 1)
116 // bprintf(0, _T("*** DirectSound playback wait aborted.\n"));
117 #endif
118
119 if (wait_object == WAIT_OBJECT_0 + 1)
120 {
121 SetEvent(hAbortAckEvent);
122 ResetEvent(hAbortEvent);
123 }
124
125 return 1;
126 }
127
128 ResetEvent(hDSoundEvent);
129
130 if (!bAudPlaying)
131 return 1;
132
133 if (pdsbLoop == NULL) {
134 return 1;
135 }
136
137 // We should do nothing until nPlay has left nDSoundNextSeg
138 pdsbLoop->GetCurrentPosition(&nPlay, &nWrite);
139
140 nPlaySeg = nPlay / (nAudSegLen << 2);
141
142 if (nPlaySeg > nAudSegCount - 1) {
143 nPlaySeg = nAudSegCount - 1;
144 }
145 if (nPlaySeg < 0) { // important to ensure nPlaySeg clipped for below
146 nPlaySeg = 0;
147 }
148
149 if (nDSoundNextSeg == nPlaySeg) {
150 //Sleep(2); // Don't need to do anything for a bit
151 return 0;
152 }
153
154 // work out which seg we will fill next
155 nFollowingSeg = nDSoundNextSeg;
156 WRAP_INC(nFollowingSeg);
157
158 while (nDSoundNextSeg != nPlaySeg) {
159 void *pData = NULL, *pData2 = NULL;
160 DWORD cbLen = 0, cbLen2 = 0;
161
162 // fill nNextSeg
163
164 // Lock the relevant seg of the loop buffer
165 if (SUCCEEDED(pdsbLoop->Lock(nDSoundNextSeg * (nAudSegLen << 2), nAudSegLen << 2, &pData, &cbLen, &pData2, &cbLen2, 0))) {
166 // Locked the segment, so write the sound we calculated last time
167 memcpy(pData, nAudNextSound, nAudSegLen << 2);
168
169 // Unlock (2nd 0 is because we wrote nothing to second part)
170 pdsbLoop->Unlock(pData, cbLen, pData2, 0);
171 }
172
173 // get more sound into nAudNextSound
174 DSoundGetNextSound((nFollowingSeg == nPlaySeg) || bAlwaysDrawFrames); // If this is the last seg of sound, draw the graphics (frameskipping)
175
176 if (nAudDSPModule[0]) {
177 DspDo(nAudNextSound, nAudSegLen);
178 }
179
180 nDSoundNextSeg = nFollowingSeg;
181 WRAP_INC(nFollowingSeg);
182 }
183
184 return 0;
185 }
186
DxSoundExit()187 static int DxSoundExit()
188 {
189 ResetEvent(hAbortEvent);
190 ResetEvent(hAbortAckEvent);
191
192 DspExit();
193
194 if (nAudNextSound) {
195 free(nAudNextSound);
196 nAudNextSound = NULL;
197 }
198
199 DSoundGetNextSound = NULL;
200
201 // Release the (Secondary) Loop Sound Buffer
202 RELEASE(pdsbLoop);
203 // Release the Primary Sound Buffer
204 RELEASE(pdsbPrim);
205 // Release the DirectSound interface
206 RELEASE(pDS);
207
208 if (hAbortAckEvent)
209 CloseHandle(hAbortAckEvent);
210 hAbortAckEvent = NULL;
211
212 if (hAbortEvent)
213 CloseHandle(hAbortEvent);
214 hAbortEvent = NULL;
215
216 if (hDSoundEvent)
217 CloseHandle(hDSoundEvent);
218 hDSoundEvent = NULL;
219
220 return 0;
221 }
222
DxSoundInit()223 static int DxSoundInit()
224 {
225 int nRet = 0;
226 DSBUFFERDESC dsbd;
227 WAVEFORMATEX wfx;
228 LPDIRECTSOUNDNOTIFY lpDsNotify;
229 LPDSBPOSITIONNOTIFY lpPositionNotify = 0;
230
231 if (nAudSampleRate[0] <= 0) {
232 return 1;
233 }
234
235 hDSoundEvent = CreateEvent(NULL, TRUE, false, NULL);
236 if (hDSoundEvent == NULL)
237 return 1;
238
239 hAbortEvent = CreateEvent(NULL, TRUE, false, NULL);
240 if (hAbortEvent == NULL)
241 return 1;
242
243 hAbortAckEvent = CreateEvent(NULL, TRUE, false, NULL);
244 if (hAbortAckEvent == NULL)
245 return 1;
246
247 nDSoundFps = nAppVirtualFps;
248
249 // Calculate the Seg Length and Loop length (round to nearest sample)
250 nAudSegLen = (nAudSampleRate[0] * 100 + (nDSoundFps >> 1)) / nDSoundFps;
251 cbLoopLen = (nAudSegLen * nAudSegCount) << 2;
252
253 // Make the format of the sound
254 memset(&wfx, 0, sizeof(wfx));
255 wfx.cbSize = sizeof(wfx);
256 wfx.wFormatTag = WAVE_FORMAT_PCM;
257 wfx.nChannels = 2; // stereo
258 wfx.nSamplesPerSec = nAudSampleRate[0]; // sample rate
259 wfx.wBitsPerSample = 16; // 16-bit
260 wfx.nBlockAlign = 4; // bytes per sample
261 wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
262
263 // Create the DirectSound interface
264 if (FAILED(_DirectSoundCreate(NULL, &pDS, NULL))) {
265 return 1;
266 }
267
268 // Set the coop level
269 nRet = pDS->SetCooperativeLevel(hScrnWnd, DSSCL_PRIORITY);
270
271 // Make the primary sound buffer
272 memset(&dsbd, 0, sizeof(dsbd));
273 dsbd.dwSize = sizeof(dsbd);
274 dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
275 if (FAILED(pDS->CreateSoundBuffer(&dsbd, &pdsbPrim, NULL))) {
276 DxSoundExit();
277 return 1;
278 }
279
280 {
281 // Set the format of the primary sound buffer (not critical if it fails)
282 if (nAudSampleRate[0] < 44100) {
283 wfx.nSamplesPerSec = 44100;
284 }
285 pdsbPrim->SetFormat(&wfx);
286
287 wfx.nSamplesPerSec = nAudSampleRate[0];
288 }
289
290 // Make the loop sound buffer
291 memset(&dsbd, 0, sizeof(dsbd));
292 dsbd.dwSize = sizeof(dsbd);
293 // A standard secondary buffer (accurate position, plays in the background, and can notify).
294 dsbd.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_CTRLVOLUME;
295 dsbd.dwBufferBytes = cbLoopLen;
296 dsbd.lpwfxFormat = &wfx; // Same format as the primary buffer
297 if (FAILED(pDS->CreateSoundBuffer(&dsbd, &pdsbLoop, NULL))) {
298 AudSoundExit();
299 return 1;
300 }
301
302 lpPositionNotify = (LPDSBPOSITIONNOTIFY)malloc(nAudSegCount * sizeof(DSBPOSITIONNOTIFY));
303 if (lpPositionNotify == NULL)
304 goto error;
305
306 if (FAILED(pdsbLoop->QueryInterface(IID_IDirectSoundNotify, (void**)&lpDsNotify)))
307 goto error;
308
309 for (int i = 0; i < nAudSegCount; i++)
310 {
311 lpPositionNotify[i].dwOffset = (i * nAudSegLen) << 2;
312 lpPositionNotify[i].hEventNotify = hDSoundEvent;
313 }
314
315 if (FAILED(lpDsNotify->SetNotificationPositions(nAudSegCount, lpPositionNotify)))
316 {
317 lpDsNotify->Release();
318
319 goto error;
320 }
321
322 lpDsNotify->Release();
323 free(lpPositionNotify);
324
325 // Note: +2 is a hacky work-around for crashy Midway games (mk2, etc) via Kaillera
326 nAudNextSound = (short*)malloc(nAudSegLen << (2 + 2)); // The next sound block to put in the stream
327 if (nAudNextSound == NULL) {
328 DxSoundExit();
329 return 1;
330 }
331
332 DxSetCallback(NULL);
333
334 DspInit();
335
336 return 0;
337
338 error:
339 free(lpPositionNotify);
340 DxSoundExit();
341
342 return 1;
343 }
344
DxSoundPlay()345 static int DxSoundPlay()
346 {
347 DxBlankSound();
348 DxSoundSetVolume();
349
350 // Play the looping buffer
351 if (FAILED(pdsbLoop->Play(0, 0, DSBPLAY_LOOPING))) {
352 return 1;
353 }
354 bAudPlaying = 1;
355
356 return 0;
357 }
358
DxSoundStop()359 static int DxSoundStop()
360 {
361 bAudPlaying = 0;
362
363 if (bAudOkay == 0) {
364 return 1;
365 }
366
367 // Stop the looping buffer
368 pdsbLoop->Stop();
369
370 bAbortSound = true;
371 DxSoundCheck();
372
373 return 0;
374 }
375
DxSoundSetVolume()376 static int DxSoundSetVolume()
377 {
378 if (nAudVolume == 10000) {
379 nDSoundVol = DSBVOLUME_MAX;
380 } else {
381 if (nAudVolume == 0) {
382 nDSoundVol = DSBVOLUME_MIN;
383 } else {
384 nDSoundVol = DSBVOLUME_MAX - (long)(10000.0 * pow(10.0, nAudVolume / -5000.0)) + 100;
385 }
386 }
387
388 if (nDSoundVol < DSBVOLUME_MIN) {
389 nDSoundVol = DSBVOLUME_MIN;
390 }
391
392 if (FAILED(pdsbLoop->SetVolume(nDSoundVol))) {
393 return 0;
394 }
395
396 return 1;
397 }
398
DxGetSettings(InterfaceInfo * pInfo)399 static int DxGetSettings(InterfaceInfo* pInfo)
400 {
401 TCHAR szString[MAX_PATH] = _T("");
402
403 _sntprintf(szString, MAX_PATH, _T("Audio is delayed by approx. %ims"), int(100000.0 / (nDSoundFps / (nAudSegCount - 1.0))));
404 IntInfoAddStringModule(pInfo, szString);
405
406 return 0;
407 }
408
409 struct AudOut AudOutDx = { DxBlankSound, DxSoundCheck, DxSoundInit, DxSetCallback, DxSoundPlay, DxSoundStop, DxSoundExit, DxSoundSetVolume, DxGetSettings, _T("DirectSound3 audio output") };
410