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