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