1 /*	MikMod sound library
2 	(c) 1998-2005 Miodrag Vallat and others - see file AUTHORS for
3 	complete list.
4 
5 	This library is free software; you can redistribute it and/or modify
6 	it under the terms of the GNU Library General Public License as
7 	published by the Free Software Foundation; either version 2 of
8 	the License, or (at your option) any later version.
9 
10 	This program is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 	GNU Library General Public License for more details.
14 
15 	You should have received a copy of the GNU Library General Public
16 	License along with this library; if not, write to the Free Software
17 	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18 	02111-1307, USA.
19 */
20 
21 /*==============================================================================
22 
23   $Id$
24 
25   Driver for output on win32 platforms using XAudio2
26 
27 ==============================================================================*/
28 
29 /*
30 	Originally written by 'honza.c' <honzac@users.sourceforge.net>
31 	Fixes, C-only conversion and float support by O.Sezer
32 					<sezero@users.sourceforge.net>
33 */
34 
35 #ifdef HAVE_CONFIG_H
36 #include "config.h"
37 #endif
38 
39 #ifdef DRV_XAUDIO2
40 
41 #ifndef _WIN32_WINNT
42 /* 0x0501 for WinXP, 0x0602 for Win8 */
43 #define _WIN32_WINNT 0x0501
44 #endif
45 #if defined(DRV_XAUDIO28) && (_WIN32_WINNT < 0x0602)
46 #undef _WIN32_WINNT
47 #define _WIN32_WINNT 0x0602
48 #endif
49 #include "mikmod_internals.h"
50 
51 #define INITGUID
52 #include <xaudio2.h>
53 
54 #if defined(_MSC_VER)
55 #ifdef DRV_XAUDIO28
56 #pragma comment(lib,"xaudio2.lib")
57 #endif
58 #pragma comment(lib,"ole32.lib")
59 #endif
60 
61 /* PF_XMMI64_INSTRUCTIONS_AVAILABLE not in all SDKs */
62 #ifndef PF_XMMI64_INSTRUCTIONS_AVAILABLE
63 #define PF_XMMI64_INSTRUCTIONS_AVAILABLE 10
64 #endif
65 
66 #ifndef WAVE_FORMAT_IEEE_FLOAT
67 #define WAVE_FORMAT_IEEE_FLOAT 0x0003
68 #endif
69 
70 
71 #define XAUDIO2_NUM_BUFFERS	4
72 #define XAUDIO2_BUFFER_SIZE	32768
73 
74 #if defined(MIKMOD_DEBUG)
75 # define dbgprintf			fprintf
76 #elif defined (__GNUC__) && !(defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)
77 # define dbgprintf(f, fmt, args...)	do {} while (0)
78 #else
79 # define dbgprintf(f, ...)		do {} while (0)
80 #endif
81 
82 #ifndef __cplusplus
83 /* doing things C-only .. */
84 static HANDLE hBufferEvent;
85 
cb_OnVoiceProcessPassStart(IXAudio2VoiceCallback * p,UINT32 SamplesRequired)86 static void STDMETHODCALLTYPE cb_OnVoiceProcessPassStart(IXAudio2VoiceCallback* p,
87 							 UINT32 SamplesRequired) {
88 	dbgprintf(stderr, "\n>XAudio2: OnVoiceProcessingPassStart<\n");
89 }
cb_OnVoiceProcessPassEnd(IXAudio2VoiceCallback * p)90 static void STDMETHODCALLTYPE cb_OnVoiceProcessPassEnd(IXAudio2VoiceCallback* p) {
91 	dbgprintf(stderr, "\n>XAudio2: OnVoiceProcessingPassEnd<\n");
92 }
cb_OnStreamEnd(IXAudio2VoiceCallback * p)93 static void STDMETHODCALLTYPE cb_OnStreamEnd(IXAudio2VoiceCallback* p) {
94 	dbgprintf(stderr, "\n>XAudio2: OnStreamEnd<\n");
95 }
cb_OnBufferStart(IXAudio2VoiceCallback * p,void * pBufferContext)96 static void STDMETHODCALLTYPE cb_OnBufferStart(IXAudio2VoiceCallback* p,
97 						 void* pBufferContext) {
98 	dbgprintf(stderr, "\n>XAudio2: OnBufferStart<\n");
99 }
cb_OnBufferEnd(IXAudio2VoiceCallback * p,void * pBufferContext)100 static void STDMETHODCALLTYPE cb_OnBufferEnd(IXAudio2VoiceCallback* p,
101 						 void* pBufferContext) {
102 	SetEvent(hBufferEvent);
103 	dbgprintf(stderr, "\n>XAudio2: OnBufferEnd<\n");
104 }
cb_OnLoopEnd(IXAudio2VoiceCallback * p,void * pBufferContext)105 static void STDMETHODCALLTYPE cb_OnLoopEnd(IXAudio2VoiceCallback* p,
106 						 void* pBufferContext) {
107 	dbgprintf(stderr, "\n>XAudio2: OnLoopEnd<\n");
108 }
cb_OnVoiceError(IXAudio2VoiceCallback * p,void * pBufferContext,HRESULT Error)109 static void STDMETHODCALLTYPE cb_OnVoiceError(IXAudio2VoiceCallback* p,
110 						 void* pBufferContext,
111 						 HRESULT Error) {
112 	dbgprintf(stderr, "\n>XAudio2: OnVoiceError: %ld <\n", Error);
113 }
114 static IXAudio2VoiceCallbackVtbl cbVoice_vtbl = {
115 	cb_OnVoiceProcessPassStart,
116 	cb_OnVoiceProcessPassEnd,
117 	cb_OnStreamEnd,
118 	cb_OnBufferStart,
119 	cb_OnBufferEnd,
120 	cb_OnLoopEnd,
121 	cb_OnVoiceError
122 };
123 static IXAudio2VoiceCallback cbVoice = {
124 	&cbVoice_vtbl
125 };
126 #define pcbVoice		&cbVoice
127 
128 #else /* __cplusplus: */
129 class XAudio2VoiceCallback: public IXAudio2VoiceCallback {
130 public:
131 	HANDLE hBufferEvent;
132 public:
XAudio2VoiceCallback()133 	XAudio2VoiceCallback(): hBufferEvent(CreateEvent(NULL, FALSE, FALSE, TEXT("libmikmod XAudio2 Driver buffer Event"))) { }
~XAudio2VoiceCallback()134 	virtual ~XAudio2VoiceCallback() { CloseHandle(hBufferEvent); }
STDMETHOD_(void,cb_OnVoiceProcessPassStart)135 	STDMETHOD_(void, cb_OnVoiceProcessPassStart)(UINT32 SamplesRequired) {
136 		dbgprintf(stderr, "\n>XAudio2: OnVoiceProcessingPassStart<\n");
137 	}
STDMETHOD_(void,cb_OnVoiceProcessPassEnd)138 	STDMETHOD_(void, cb_OnVoiceProcessPassEnd)() {
139 		dbgprintf(stderr, "\n>XAudio2: OnVoiceProcessingPassEnd<\n");
140 	}
STDMETHOD_(void,cb_OnStreamEnd)141 	STDMETHOD_(void, cb_OnStreamEnd)() {
142 		dbgprintf(stderr, "\n>XAudio2: OnStreamEnd<\n");
143 	}
STDMETHOD_(void,cb_OnBufferStart)144 	STDMETHOD_(void, cb_OnBufferStart)(void* pBufferContext) {
145 		dbgprintf(stderr, "\n>XAudio2: OnBufferStart<\n");
146 	}
STDMETHOD_(void,cb_OnBufferEnd)147 	STDMETHOD_(void, cb_OnBufferEnd)(void* pBufferContext) {
148 		SetEvent(hBufferEvent);
149 		dbgprintf(stderr, "\n>XAudio2: OnBufferStart<\n");
150 	}
STDMETHOD_(void,cb_OnLoopEnd)151 	STDMETHOD_(void, cb_OnLoopEnd)(void* pBufferContext) {
152 		dbgprintf(stderr, "\n>XAudio2: OnLoopEnd<\n");
153 	}
STDMETHOD_(void,cb_OnVoiceError)154 	STDMETHOD_(void, cb_OnVoiceError)(void* pBufferContext, HRESULT Error) {
155 		dbgprintf(stderr, "\n>XAudio2: OnVoiceError: %ld <\n", Error);
156 	}
157 };
158 static XAudio2VoiceCallback *pcbVoice = NULL;
159 #define hBufferEvent	pcbVoice->hBufferEvent
160 #define IXAudio2_Release(p)				((p)->Release())
161 #ifndef DRV_XAUDIO28
162 #define IXAudio2_CreateMasteringVoice(p,a,b,c,d,e,f)	((p)->CreateMasteringVoice(a,b,c,d,e,f))
163 #define IXAudio2SourceVoice_GetState(p,a)		((p)->GetState(a))
164 #else
165 #define IXAudio2_CreateMasteringVoice(p,a,b,c,d,e,f,g)	((p)->CreateMasteringVoice(a,b,c,d,e,f,g))
166 #define IXAudio2SourceVoice_GetState(p,a,b)		((p)->GetState(a,b))
167 #endif
168 #define IXAudio2SourceVoice_SubmitSourceBuffer(p,a,b)	((p)->SubmitSourceBuffer(a,b))
169 #define IXAudio2_CreateSourceVoice(p,a,b,c,d,e,f,g)	((p)->CreateSourceVoice(a,b,c,d,e,f,g))
170 #define IXAudio2SourceVoice_DestroyVoice(p)		((p)->DestroyVoice())
171 #define IXAudio2MasteringVoice_DestroyVoice(p)		((p)->DestroyVoice())
172 #define IXAudio2SourceVoice_Start(p,a,b)		((p)->Start(a,b))
173 #define IXAudio2SourceVoice_Stop( p,a,b)		((p)->Stop(a,b))
174 #endif /* __cplusplus */
175 
176 static IXAudio2* pXAudio2 = NULL;
177 static IXAudio2MasteringVoice* pMasterVoice = NULL;
178 static IXAudio2SourceVoice* pSourceVoice = NULL;
179 static HANDLE UpdateBufferHandle = NULL;
180 static BOOL threadInUse = FALSE;
181 static BYTE buffers[XAUDIO2_NUM_BUFFERS][XAUDIO2_BUFFER_SIZE];
182 static DWORD current_buf = 0;
183 
184 
UpdateBufferProc(LPVOID lpParameter)185 static DWORD WINAPI UpdateBufferProc(LPVOID lpParameter) {
186 	while (threadInUse) {
187 		while (1) {
188 			XAUDIO2_VOICE_STATE state;
189 			XAUDIO2_BUFFER audio_buf;
190 
191 			#ifndef DRV_XAUDIO28
192 			IXAudio2SourceVoice_GetState(pSourceVoice, &state);
193 			#else
194 			IXAudio2SourceVoice_GetState(pSourceVoice, &state, XAUDIO2_VOICE_NOSAMPLESPLAYED);
195 			#endif
196 			if (state.BuffersQueued >= XAUDIO2_NUM_BUFFERS - 1)
197 				break;
198 			MUTEX_LOCK(vars);
199 			if (Player_Paused_internal())
200 				VC_SilenceBytes((SBYTE *) buffers[current_buf], (ULONG) XAUDIO2_BUFFER_SIZE);
201 			else
202 				VC_WriteBytes((SBYTE *) buffers[current_buf], (ULONG) XAUDIO2_BUFFER_SIZE);
203 			MUTEX_UNLOCK(vars);
204 			memset(&audio_buf, 0, sizeof(XAUDIO2_BUFFER));
205 			audio_buf.AudioBytes = XAUDIO2_BUFFER_SIZE;
206 			audio_buf.pAudioData = buffers[current_buf];
207 			IXAudio2SourceVoice_SubmitSourceBuffer(pSourceVoice, &audio_buf, NULL);
208 			current_buf++;
209 			current_buf %= XAUDIO2_NUM_BUFFERS;
210 		}
211 		WaitForSingleObject(hBufferEvent, INFINITE);
212 	}
213 	return 0;
214 }
215 
XAudio2_CommandLine(const CHAR * cmdline)216 static void XAudio2_CommandLine(const CHAR *cmdline) {
217 /* no options */
218 }
219 
XAudio2_IsPresent(void)220 static BOOL XAudio2_IsPresent(void) {
221 	HRESULT r;
222 
223 	if (pXAudio2 == NULL) {
224 #ifndef _XBOX
225 		CoInitializeEx(NULL, COINIT_MULTITHREADED);
226 #endif
227 		r = XAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR);
228 		if (pXAudio2) {
229 			IXAudio2_Release(pXAudio2);
230 			pXAudio2 = NULL;
231 		}
232 #ifndef _XBOX
233 		CoUninitialize();
234 #endif
235 		if (FAILED(r))
236 			return 0;
237 	}
238 	return 1;
239 }
240 
XAudio2_Init(void)241 static int XAudio2_Init(void) {
242 	UINT32 flags;
243 	DWORD thread_id;
244 	WAVEFORMATEX wfmt;
245 
246 	memset(&wfmt, 0, sizeof(WAVEFORMATEX));
247 	wfmt.wFormatTag= (md_mode & DMODE_FLOAT)? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
248 	wfmt.nChannels = (md_mode & DMODE_STEREO)? 2: 1;
249 	wfmt.nSamplesPerSec = md_mixfreq;
250 	wfmt.wBitsPerSample = (md_mode & DMODE_FLOAT)? 32: (md_mode & DMODE_16BITS)? 16: 8;
251 	wfmt.nBlockAlign = (wfmt.wBitsPerSample * wfmt.nChannels) / 8;
252 	wfmt.nAvgBytesPerSec = wfmt.nSamplesPerSec * wfmt.nBlockAlign;
253 	if (wfmt.nSamplesPerSec < XAUDIO2_MIN_SAMPLE_RATE ||
254 	    wfmt.nSamplesPerSec > XAUDIO2_MAX_SAMPLE_RATE ||
255 	    wfmt.nChannels > XAUDIO2_MAX_AUDIO_CHANNELS) {
256 		return 1;
257 	}
258 
259 	current_buf = 0;
260 	flags = 0;
261 #if defined(_DEBUG) && !defined(DRV_XAUDIO28)
262 /*	flags |= XAUDIO2_DEBUG_ENGINE;*/
263 #endif
264 #ifndef _XBOX
265 	CoInitializeEx(NULL, COINIT_MULTITHREADED);
266 #endif
267 	if (FAILED(XAudio2Create(&pXAudio2, flags, XAUDIO2_DEFAULT_PROCESSOR))) {
268 		goto fail;
269 	}
270 #ifdef DRV_XAUDIO28
271 	if (FAILED(IXAudio2_CreateMasteringVoice(pXAudio2, &pMasterVoice, XAUDIO2_DEFAULT_CHANNELS, XAUDIO2_DEFAULT_SAMPLERATE,
272 						 0, NULL, NULL, AudioCategory_Other))) {
273 		goto fail;
274 	}
275 #else
276 	if (FAILED(IXAudio2_CreateMasteringVoice(pXAudio2, &pMasterVoice, XAUDIO2_DEFAULT_CHANNELS, XAUDIO2_DEFAULT_SAMPLERATE, 0, 0, NULL))) {
277 		goto fail;
278 	}
279 #endif
280 	if (FAILED(IXAudio2_CreateSourceVoice(pXAudio2, &pSourceVoice, &wfmt, 0, 1.0f, pcbVoice, NULL, NULL))) {
281 		goto fail;
282 	}
283 #ifndef __cplusplus
284 	if ((hBufferEvent = CreateEvent(NULL, FALSE, FALSE, TEXT("libmikmod XAudio2 Driver buffer Event"))) == NULL) {
285 		goto fail;
286 	}
287 #endif
288 	if ((UpdateBufferHandle = CreateThread(NULL, 0, UpdateBufferProc, NULL, CREATE_SUSPENDED, &thread_id)) == NULL) {
289 		goto fail;
290 	}
291 #if defined HAVE_SSE2
292 	/* this test only works on Windows XP or later */
293 	if (IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE)) {
294 		md_mode|=DMODE_SIMDMIXER;
295 	}
296 #endif
297 	return VC_Init();
298 
299 fail:
300 	if (pSourceVoice) {
301 		IXAudio2SourceVoice_DestroyVoice(pSourceVoice);
302 		pSourceVoice = NULL;
303 	}
304 	if (pMasterVoice) {
305 		IXAudio2MasteringVoice_DestroyVoice(pMasterVoice);
306 		pMasterVoice = NULL;
307 	}
308 	if (pXAudio2) {
309 		IXAudio2_Release(pXAudio2);
310 		pXAudio2 = NULL;
311 	}
312 #ifndef _XBOX
313 	CoUninitialize();
314 #endif
315 	return 1;
316 }
317 
XAudio2_Exit(void)318 static void XAudio2_Exit(void) {
319 	if (UpdateBufferHandle != NULL) {
320 		/* signal thread to exit and wait for the exit */
321 		if (threadInUse) {
322 			threadInUse = 0;
323 			MUTEX_UNLOCK(vars);
324 			SetEvent(hBufferEvent);
325 			WaitForSingleObject(UpdateBufferHandle, INFINITE);
326 			MUTEX_LOCK(vars);
327 		}
328 		CloseHandle(UpdateBufferHandle);
329 		UpdateBufferHandle = NULL;
330 	}
331 	IXAudio2SourceVoice_Stop(pSourceVoice, 0, 0);
332 	if (pSourceVoice) {
333 		IXAudio2SourceVoice_DestroyVoice(pSourceVoice);
334 		pSourceVoice = NULL;
335 	}
336 	if (pMasterVoice) {
337 		IXAudio2MasteringVoice_DestroyVoice(pMasterVoice);
338 		pMasterVoice = NULL;
339 	}
340 	if (pXAudio2) {
341 		IXAudio2_Release(pXAudio2);
342 		pXAudio2 = NULL;
343 	}
344 #ifndef __cplusplus
345 	if (hBufferEvent != NULL) {
346 		CloseHandle(hBufferEvent);
347 		hBufferEvent = NULL;
348 	}
349 #endif
350 #ifndef _XBOX
351 	CoUninitialize();
352 #endif
353 	VC_Exit();
354 }
355 
356 static BOOL do_update = 0;
357 
XAudio2_Update(void)358 static void XAudio2_Update(void) {
359 	if (do_update && pSourceVoice) {
360 		do_update = 0;
361 
362 		while (1) {
363 			XAUDIO2_VOICE_STATE state;
364 			XAUDIO2_BUFFER audio_buf;
365 
366 			#ifndef DRV_XAUDIO28
367 			IXAudio2SourceVoice_GetState(pSourceVoice, &state);
368 			#else
369 			IXAudio2SourceVoice_GetState(pSourceVoice, &state, XAUDIO2_VOICE_NOSAMPLESPLAYED);
370 			#endif
371 			if (state.BuffersQueued > 0)
372 				break;
373 			current_buf %= XAUDIO2_NUM_BUFFERS;
374 			if (Player_Paused_internal())
375 				VC_SilenceBytes((SBYTE *) buffers[current_buf], (ULONG) XAUDIO2_BUFFER_SIZE);
376 			else
377 				VC_WriteBytes((SBYTE *) buffers[current_buf], (ULONG) XAUDIO2_BUFFER_SIZE);
378 			memset(&audio_buf, 0, sizeof(XAUDIO2_BUFFER));
379 			audio_buf.AudioBytes = XAUDIO2_BUFFER_SIZE;
380 			audio_buf.pAudioData = buffers[current_buf];
381 			IXAudio2SourceVoice_SubmitSourceBuffer(pSourceVoice, &audio_buf, NULL);
382 			current_buf++;
383 			current_buf %= XAUDIO2_NUM_BUFFERS;
384 		}
385 		IXAudio2SourceVoice_Start(pSourceVoice, 0, 0);
386 		threadInUse = 1;
387 		ResumeThread(UpdateBufferHandle);
388 	}
389 }
390 
XAudio2_PlayStop(void)391 static void XAudio2_PlayStop(void) {
392 	do_update = 0;
393 	if (pSourceVoice)
394 		IXAudio2SourceVoice_Stop(pSourceVoice, 0, 0);
395 	VC_PlayStop();
396 }
397 
XAudio2_PlayStart(void)398 static int XAudio2_PlayStart(void) {
399 	do_update = 1;
400 	return VC_PlayStart();
401 }
402 
403 MIKMODAPI MDRIVER drv_xaudio2 = {
404 	NULL,
405 	"XAudio2",
406 	"DirectX XAudio2 Driver v0.3",
407 	0,255,
408 	"xaudio2",
409 	"",
410 	XAudio2_CommandLine,
411 	XAudio2_IsPresent,
412 	VC_SampleLoad,
413 	VC_SampleUnload,
414 	VC_SampleSpace,
415 	VC_SampleLength,
416 	XAudio2_Init,
417 	XAudio2_Exit,
418 	NULL,
419 	VC_SetNumVoices,
420 	XAudio2_PlayStart,
421 	XAudio2_PlayStop,
422 	XAudio2_Update,
423 	NULL,
424 	VC_VoiceSetVolume,
425 	VC_VoiceGetVolume,
426 	VC_VoiceSetFrequency,
427 	VC_VoiceGetFrequency,
428 	VC_VoiceSetPanning,
429 	VC_VoiceGetPanning,
430 	VC_VoicePlay,
431 	VC_VoiceStop,
432 	VC_VoiceStopped,
433 	VC_VoiceGetPosition,
434 	VC_VoiceRealVolume
435 };
436 
437 #else
438 
439 #include "mikmod_internals.h"
440 MISSING(drv_xaudio2);
441 
442 #endif
443 
444 /* ex:set ts=8: */
445