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