1 /*
2  * PortAudio Portable Real-Time Audio Library
3  * Latest Version at: http://www.softsynth.com/portaudio/
4  * DirectSound Implementation
5  *
6  * Copyright (c) 1999-2000 Phil Burk
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining
9  * a copy of this software and associated documentation files
10  * (the "Software"), to deal in the Software without restriction,
11  * including without limitation the rights to use, copy, modify, merge,
12  * publish, distribute, sublicense, and/or sell copies of the Software,
13  * and to permit persons to whom the Software is furnished to do so,
14  * subject to the following conditions:
15  *
16  * The above copyright notice and this permission notice shall be
17  * included in all copies or substantial portions of the Software.
18  *
19  * Any person wishing to distribute modifications to the Software is
20  * requested to send the modifications to the original developer so that
21  * they can be incorporated into the canonical version.
22  *
23  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
26  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
27  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
28  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30  *
31  */
32 /* Modifications
33  *    7/19/01 Mike Berry - casts for compiling with __MWERKS__ CodeWarrior
34  *    9/27/01 Phil Burk - use number of frames instead of real-time for CPULoad calculation.
35  */
36 /* Compiler flags:
37 	SUPPORT_AUDIO_CAPTURE - define this flag if you want to SUPPORT_AUDIO_CAPTURE
38  */
39 #include <stdio.h>
40 #include <stdlib.h>
41 #ifndef __MWERKS__
42 #include <malloc.h>
43 #include <memory.h>
44 #endif //__MWERKS__
45 #include <math.h>
46 #if 0
47 #include "portaudio.h"
48 #include "pa_host.h"
49 #include "pa_trace.h"
50 #include "dsound_wrapper.h"
51 #endif
52 #define PRINT(x) { printf x; fflush(stdout); }
53 #define ERR_RPT(x) PRINT(x)
54 #define DBUG(x)  /* PRINT(x) */
55 #define DBUGX(x) /* PRINT(x) */
56 #define PA_USE_HIGH_LATENCY   (0)
57 #if PA_USE_HIGH_LATENCY
58 #define PA_WIN_9X_LATENCY     (500)
59 #define PA_WIN_NT_LATENCY     (600)
60 #else
61 #define PA_WIN_9X_LATENCY     (140)
62 #define PA_WIN_NT_LATENCY     (280)
63 #endif
64 /* Trigger an underflow for testing purposes. Should normally be (0). */
65 #define PA_SIMULATE_UNDERFLOW (0)
66 #if PA_SIMULATE_UNDERFLOW
67 static  gUnderCallbackCounter = 0;
68 #define UNDER_START_GAP       (10)
69 #define UNDER_STOP_GAP        (UNDER_START_GAP + 4)
70 #endif
71 /************************************************* Definitions ********/
72 typedef struct internalPortAudioStream internalPortAudioStream;
73 typedef struct internalPortAudioDevice
74 {
75 	GUID                             pad_GUID;
76 	GUID                            *pad_lpGUID;
77 	double                           pad_SampleRates[10]; /* for pointing to from pad_Info FIXME?!*/
78 	PaDeviceInfo                     pad_Info;
79 } internalPortAudioDevice;
80 /* Define structure to contain all DirectSound and Windows specific data. */
81 typedef struct PaHostSoundControl
82 {
83 	DSoundWrapper    pahsc_DSoundWrapper;
84 	MMRESULT         pahsc_TimerID;
85 	BOOL             pahsc_IfInsideCallback;  /* Test for reentrancy. */
86 	short           *pahsc_NativeBuffer;
87 	unsigned int     pahsc_BytesPerBuffer;    /* native buffer size in bytes */
88 	double           pahsc_ValidFramesWritten;
89 	int              pahsc_FramesPerDSBuffer;
90 /* For measuring CPU utilization. */
91 	LARGE_INTEGER    pahsc_EntryCount;
92 	double           pahsc_InverseTicksPerUserBuffer;
93 } PaHostSoundControl;
94 /************************************************* Shared Data ********/
95 /* FIXME - put Mutex around this shared data. */
96 static int sNumDevices = 0;
97 static int sDeviceIndex = 0;
98 static internalPortAudioDevice *sDevices = NULL;
99 static int sDefaultInputDeviceID = paNoDevice;
100 static int sDefaultOutputDeviceID = paNoDevice;
101 static int sEnumerationError;
102 static int sPaHostError = 0;
103 /************************************************* Prototypes **********/
104 static internalPortAudioDevice *Pa_GetInternalDevice( PaDeviceID id );
105 static BOOL CALLBACK Pa_EnumProc(LPGUID lpGUID,
106 				         LPCTSTR lpszDesc,
107 				         LPCTSTR lpszDrvName,
108 				         LPVOID lpContext );
109 static BOOL CALLBACK Pa_CountDevProc(LPGUID lpGUID,
110 				         LPCTSTR lpszDesc,
111 				         LPCTSTR lpszDrvName,
112 				         LPVOID lpContext );
113 static Pa_QueryDevices( void );
114 static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg,
115 									  DWORD dwUser, DWORD dw1, DWORD dw2);
116 /********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/
117 static void Pa_StartUsageCalculation( internalPortAudioStream   *past )
118 {
119 	PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
120 	if( pahsc == NULL ) return;
121 /* Query system timer for usage analysis and to prevent overuse of CPU. */
122 	QueryPerformanceCounter( &pahsc->pahsc_EntryCount );
123 }
124 static void Pa_EndUsageCalculation( internalPortAudioStream   *past )
125 {
126 	LARGE_INTEGER CurrentCount = { 0, 0 };
127 	PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
128 	if( pahsc == NULL ) return;
129 /*
130 ** Measure CPU utilization during this callback. Note that this calculation
131 ** assumes that we had the processor the whole time.
132 */
133 #define LOWPASS_COEFFICIENT_0   (0.9)
134 #define LOWPASS_COEFFICIENT_1   (0.99999 - LOWPASS_COEFFICIENT_0)
135 	if( QueryPerformanceCounter( &CurrentCount ) )
136 	{
137 		LONGLONG InsideCount = CurrentCount.QuadPart - pahsc->pahsc_EntryCount.QuadPart;
138 		double newUsage = InsideCount * pahsc->pahsc_InverseTicksPerUserBuffer;
139 		past->past_Usage = (LOWPASS_COEFFICIENT_0 * past->past_Usage) +
140 			(LOWPASS_COEFFICIENT_1 * newUsage);
141 	}
142 }
143 /****************************************** END CPU UTILIZATION *******/
144 static PaError Pa_QueryDevices( void )
145 {
146 	int numBytes;
147 	sDefaultInputDeviceID = paNoDevice;
148 	sDefaultOutputDeviceID = paNoDevice;
149 /* Enumerate once just to count devices. */
150 	sNumDevices = 0; // for default device
151 	DirectSoundEnumerate( (LPDSENUMCALLBACK)Pa_CountDevProc, NULL );
152 #if SUPPORT_AUDIO_CAPTURE
153 	DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)Pa_CountDevProc, NULL );
154 #endif /* SUPPORT_AUDIO_CAPTURE */
155 /* Allocate structures to hold device info. */
156 	numBytes = sNumDevices * sizeof(internalPortAudioDevice);
157 	sDevices = (internalPortAudioDevice *)PaHost_AllocateFastMemory( numBytes ); /* MEM */
158 	if( sDevices == NULL ) return paInsufficientMemory;
159 /* Enumerate again to fill in structures. */
160 	sDeviceIndex = 0;
161 	sEnumerationError = 0;
162 	DirectSoundEnumerate( (LPDSENUMCALLBACK)Pa_EnumProc, (void *)0 );
163 #if SUPPORT_AUDIO_CAPTURE
164 	if( sEnumerationError != paNoError ) return sEnumerationError;
165 	sEnumerationError = 0;
166 	DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)Pa_EnumProc, (void *)1 );
167 #endif /* SUPPORT_AUDIO_CAPTURE */
168 	return sEnumerationError;
169 }
170 /************************************************************************************/
171 long Pa_GetHostError()
172 {
173 	return sPaHostError;
174 }
175 /************************************************************************************
176 ** Just count devices so we know how much memory to allocate.
177 */
178 static BOOL CALLBACK Pa_CountDevProc(LPGUID lpGUID,
179 				         LPCTSTR lpszDesc,
180 				         LPCTSTR lpszDrvName,
181 				         LPVOID lpContext )
182 {
183 	sNumDevices++;
184 	return TRUE;
185 }
186 /************************************************************************************
187 ** Extract capabilities info from each device.
188 */
189 static BOOL CALLBACK Pa_EnumProc(LPGUID lpGUID,
190 				         LPCTSTR lpszDesc,
191 				         LPCTSTR lpszDrvName,
192 				         LPVOID lpContext )
193 {
194 	HRESULT    hr;
195 	LPDIRECTSOUND          lpDirectSound;
196 #if SUPPORT_AUDIO_CAPTURE
197 	LPDIRECTSOUNDCAPTURE   lpDirectSoundCapture;
198 #endif /* SUPPORT_AUDIO_CAPTURE */
199 	int        isInput  = (int) lpContext;  /* Passed from Pa_CountDevices() */
200 	internalPortAudioDevice *pad;
201 
202 	if( sDeviceIndex >= sNumDevices )
203 	{
204 		sEnumerationError = paInternalError;
205 		return FALSE;
206 	}
207 	pad = &sDevices[sDeviceIndex];
208 /* Copy GUID to static array. Set pointer. */
209 	if( lpGUID == NULL )
210 	{
211 		pad->pad_lpGUID = NULL;
212 	}
213 	else
214 	{
215 		memcpy( &pad->pad_GUID, lpGUID, sizeof(GUID) );
216 		pad->pad_lpGUID = &pad->pad_GUID;
217 	}
218 	pad->pad_Info.sampleRates = pad->pad_SampleRates;  /* Point to array. */
219 /* Allocate room for descriptive name. */
220 	if( lpszDesc != NULL )
221 	{
222 		int len = strlen(lpszDesc);
223 		pad->pad_Info.name = (char *)malloc( len+1 );
224 		if( pad->pad_Info.name == NULL )
225 		{
226 			sEnumerationError = paInsufficientMemory;
227 			return FALSE;
228 		}
229 		memcpy( (void *) pad->pad_Info.name, lpszDesc, len+1 );
230 	}
231 #if SUPPORT_AUDIO_CAPTURE
232 	if( isInput )
233 	{
234 	/********** Input ******************************/
235 		DSCCAPS     caps;
236 		if( lpGUID == NULL ) sDefaultInputDeviceID = sDeviceIndex;
237 		hr = DirectSoundCaptureCreate(  lpGUID, &lpDirectSoundCapture,   NULL );
238 		if( hr != DS_OK )
239 		{
240 			pad->pad_Info.maxInputChannels = 0;
241 			DBUG(("Cannot create Capture for %s. Result = 0x%x\n", lpszDesc, hr ));
242 		}
243 		else
244 		{
245 	/* Query device characteristics. */
246 			caps.dwSize = sizeof(caps);
247 			IDirectSoundCapture_GetCaps( lpDirectSoundCapture, &caps );
248 			/* printf("caps.dwFormats = 0x%x\n", caps.dwFormats ); */
249 			pad->pad_Info.maxInputChannels = caps.dwChannels;
250 	/* Determine sample rates from flags. */
251 			if( caps.dwChannels == 2 )
252 			{
253 				int index = 0;
254 				if( caps.dwFormats & WAVE_FORMAT_1S16) pad->pad_SampleRates[index++] = 11025.0;
255 				if( caps.dwFormats & WAVE_FORMAT_2S16) pad->pad_SampleRates[index++] = 22050.0;
256 				if( caps.dwFormats & WAVE_FORMAT_4S16) pad->pad_SampleRates[index++] = 44100.0;
257 				pad->pad_Info.numSampleRates = index;
258 			}
259 			else if( caps.dwChannels == 1 )
260 			{
261 				int index = 0;
262 				if( caps.dwFormats & WAVE_FORMAT_1M16) pad->pad_SampleRates[index++] = 11025.0;
263 				if( caps.dwFormats & WAVE_FORMAT_2M16) pad->pad_SampleRates[index++] = 22050.0;
264 				if( caps.dwFormats & WAVE_FORMAT_4M16) pad->pad_SampleRates[index++] = 44100.0;
265 				pad->pad_Info.numSampleRates = index;
266 			}
267 			else pad->pad_Info.numSampleRates = 0;
268 			IDirectSoundCapture_Release( lpDirectSoundCapture );
269 		}
270 	}
271 	else
272 #endif /* SUPPORT_AUDIO_CAPTURE */
273 	{
274 	/********** Output ******************************/
275 		DSCAPS     caps;
276 		if( lpGUID == NULL ) sDefaultOutputDeviceID = sDeviceIndex;
277 	/* Create interfaces for each object. */
278 		hr = DirectSoundCreate(  lpGUID, &lpDirectSound,   NULL );
279 		if( hr != DS_OK )
280 		{
281 			pad->pad_Info.maxOutputChannels = 0;
282 			DBUG(("Cannot create dsound for %s. Result = 0x%x\n", lpszDesc, hr ));
283 		}
284 		else
285 		{
286 		/* Query device characteristics. */
287 			caps.dwSize = sizeof(caps);
288 			IDirectSound_GetCaps( lpDirectSound, &caps );
289 			pad->pad_Info.maxOutputChannels = ( caps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1;
290 		/* Get sample rates. */
291 			pad->pad_SampleRates[0] = (double) caps.dwMinSecondarySampleRate;
292 			pad->pad_SampleRates[1] = (double) caps.dwMaxSecondarySampleRate;
293 			if( caps.dwFlags & DSCAPS_CONTINUOUSRATE ) pad->pad_Info.numSampleRates = -1;
294 			else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate )
295 			{
296 				if( caps.dwMinSecondarySampleRate == 0 )
297 				{
298 			/*
299 			** On my Thinkpad 380Z, DirectSoundV6 returns min-max=0 !!
300 			** But it supports continuous sampling.
301 			** So fake range of rates, and hope it really supports it.
302 			*/
303 					pad->pad_SampleRates[0] = 11025.0f;
304 					pad->pad_SampleRates[1] = 48000.0f;
305 					pad->pad_Info.numSampleRates = -1; /* continuous range */
306 
307 					DBUG(("PA - Reported rates both zero. Setting to fake values for device #%d\n", sDeviceIndex ));
308 				}
309 				else
310 				{
311 					pad->pad_Info.numSampleRates = 1;
312 				}
313 			}
314 			else if( (caps.dwMinSecondarySampleRate < 1000.0) && (caps.dwMaxSecondarySampleRate > 50000.0) )
315 			{
316 			/* The EWS88MT drivers lie, lie, lie. The say they only support two rates, 100 & 100000.
317 			** But we know that they really support a range of rates!
318 			** So when we see a ridiculous set of rates, assume it is a range.
319 			*/
320 				pad->pad_Info.numSampleRates = -1;
321 				DBUG(("PA - Sample rate range used instead of two odd values for device #%d\n", sDeviceIndex ));
322 			}
323 			else pad->pad_Info.numSampleRates = 2;
324 			IDirectSound_Release( lpDirectSound );
325 		}
326 	}
327 	pad->pad_Info.nativeSampleFormats = paInt16;
328 	sDeviceIndex++;
329 	return( TRUE );
330 }
331 /*************************************************************************/
332 int Pa_CountDevices()
333 {
334 	if( sNumDevices <= 0 ) Pa_Initialize();
335 	return sNumDevices;
336 }
337 static internalPortAudioDevice *Pa_GetInternalDevice( PaDeviceID id )
338 {
339 	if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL;
340 	return &sDevices[id];
341 }
342 /*************************************************************************/
343 const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id )
344 {
345 	internalPortAudioDevice *pad;
346 	if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL;
347 	pad = Pa_GetInternalDevice( id );
348 	return  &pad->pad_Info ;
349 }
350 static PaError Pa_MaybeQueryDevices( void )
351 {
352 	if( sNumDevices == 0 )
353 	{
354 		return Pa_QueryDevices();
355 	}
356 	return 0;
357 }
358 /*************************************************************************
359 ** Returns recommended device ID.
360 ** On the PC, the recommended device can be specified by the user by
361 ** setting an environment variable. For example, to use device #1.
362 **
363 **    set PA_RECOMMENDED_OUTPUT_DEVICE=1
364 **
365 ** The user should first determine the available device ID by using
366 ** the supplied application "pa_devs".
367 */
368 #define PA_ENV_BUF_SIZE  (32)
369 #define PA_REC_IN_DEV_ENV_NAME  ("PA_RECOMMENDED_INPUT_DEVICE")
370 #define PA_REC_OUT_DEV_ENV_NAME  ("PA_RECOMMENDED_OUTPUT_DEVICE")
371 static PaDeviceID PaHost_GetEnvDefaultDeviceID( char *envName )
372 {
373 	DWORD   hresult;
374 	char    envbuf[PA_ENV_BUF_SIZE];
375 	PaDeviceID recommendedID = paNoDevice;
376 /* Let user determine default device by setting environment variable. */
377 	hresult = GetEnvironmentVariable( envName, envbuf, PA_ENV_BUF_SIZE );
378 	if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) )
379 	{
380 		recommendedID = atoi( envbuf );
381 	}
382 	return recommendedID;
383 }
384 PaDeviceID Pa_GetDefaultInputDeviceID( void )
385 {
386 	PaError result;
387 	result = PaHost_GetEnvDefaultDeviceID( PA_REC_IN_DEV_ENV_NAME );
388 	if( result < 0 )
389 	{
390 		result = Pa_MaybeQueryDevices();
391 		if( result < 0 ) return result;
392 		result = sDefaultInputDeviceID;
393 	}
394 	return result;
395 }
396 PaDeviceID Pa_GetDefaultOutputDeviceID( void )
397 {
398 	PaError result;
399 	result = PaHost_GetEnvDefaultDeviceID( PA_REC_OUT_DEV_ENV_NAME );
400 	if( result < 0 )
401 	{
402 		result = Pa_MaybeQueryDevices();
403 		if( result < 0 ) return result;
404 		result = sDefaultOutputDeviceID;
405 	}
406 	return result;
407 }
408 /**********************************************************************
409 ** Make sure that we have queried the device capabilities.
410 */
411 PaError PaHost_Init( void )
412 {
413 #if PA_SIMULATE_UNDERFLOW
414 	PRINT(("WARNING - Underflow Simulation Enabled - Expect a Big Glitch!!!\n"));
415 #endif
416 	return Pa_MaybeQueryDevices();
417 }
418 static PaError Pa_TimeSlice( internalPortAudioStream   *past )
419 {
420 	PaError           result = 0;
421 	long              bytesEmpty = 0;
422 	long              bytesFilled = 0;
423 	long              bytesToXfer = 0;
424 	long              numChunks;
425 	HRESULT           hresult;
426 	PaHostSoundControl  *pahsc;
427 	short            *nativeBufPtr;
428 	past->past_NumCallbacks += 1;
429 	pahsc = (PaHostSoundControl *) past->past_DeviceData;
430 	if( pahsc == NULL ) return paInternalError;
431 /* How much input data is available? */
432 #if SUPPORT_AUDIO_CAPTURE
433 	if( past->past_NumInputChannels > 0 )
434 	{
435 		DSW_QueryInputFilled( &pahsc->pahsc_DSoundWrapper, &bytesFilled );
436 		bytesToXfer = bytesFilled;
437 	}
438 #endif /* SUPPORT_AUDIO_CAPTURE */
439 /* How much output room is available? */
440 	if( past->past_NumOutputChannels > 0 )
441 	{
442 		DSW_QueryOutputSpace( &pahsc->pahsc_DSoundWrapper, &bytesEmpty );
443 		bytesToXfer = bytesEmpty;
444 	}
445 	AddTraceMessage( "bytesEmpty ", bytesEmpty );
446 /* Choose smallest value if both are active. */
447 	if( (past->past_NumInputChannels > 0) && (past->past_NumOutputChannels > 0) )
448 	{
449 		bytesToXfer = ( bytesFilled < bytesEmpty ) ? bytesFilled : bytesEmpty;
450 	}
451 /*	printf("bytesFilled = %d, bytesEmpty = %d, bytesToXfer = %d\n",
452 		bytesFilled, bytesEmpty, bytesToXfer);
453 */
454 /* Quantize to multiples of a buffer. */
455 	numChunks = bytesToXfer / pahsc->pahsc_BytesPerBuffer;
456 	if( numChunks > (long)(past->past_NumUserBuffers/2) )
457 	{
458 		numChunks = (long)past->past_NumUserBuffers/2;
459 	}
460 	else if( numChunks < 0 )
461 	{
462 		numChunks = 0;
463 	}
464 	AddTraceMessage( "numChunks ", numChunks );
465 	nativeBufPtr = pahsc->pahsc_NativeBuffer;
466 	if( numChunks > 0 )
467 	{
468 		while( numChunks-- > 0 )
469 		{
470 		/* Measure usage based on time to process one user buffer. */
471 			Pa_StartUsageCalculation( past );
472 	#if SUPPORT_AUDIO_CAPTURE
473 	/* Get native data from DirectSound. */
474 			if( past->past_NumInputChannels > 0 )
475 			{
476 				hresult = DSW_ReadBlock( &pahsc->pahsc_DSoundWrapper, (char *) nativeBufPtr, pahsc->pahsc_BytesPerBuffer );
477 				if( hresult < 0 )
478 				{
479 					ERR_RPT(("DirectSound ReadBlock failed, hresult = 0x%x\n",hresult));
480 					sPaHostError = hresult;
481 					break;
482 				}
483 			}
484 	#endif /* SUPPORT_AUDIO_CAPTURE */
485 	/* Convert 16 bit native data to user data and call user routine. */
486 			result = Pa_CallConvertInt16( past, nativeBufPtr, nativeBufPtr );
487 			if( result != 0) break;
488 	/* Pass native data to DirectSound. */
489 			if( past->past_NumOutputChannels > 0 )
490 			{
491 			/*	static short DEBUGHACK = 0;
492 				DEBUGHACK += 0x0049;
493 				nativeBufPtr[0] = DEBUGHACK; /* Make buzz to see if DirectSound still running. */
494 				hresult = DSW_WriteBlock( &pahsc->pahsc_DSoundWrapper, (char *) nativeBufPtr, pahsc->pahsc_BytesPerBuffer );
495 				if( hresult < 0 )
496 				{
497 					ERR_RPT(("DirectSound WriteBlock failed, result = 0x%x\n",hresult));
498 					sPaHostError = hresult;
499 					break;
500 				}
501 			}
502 			Pa_EndUsageCalculation( past );
503 		}
504 	}
505 	return result;
506 }
507 /*******************************************************************/
508 static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
509 {
510 	internalPortAudioStream   *past;
511 	PaHostSoundControl  *pahsc;
512 #if PA_SIMULATE_UNDERFLOW
513 	gUnderCallbackCounter++;
514 	if( (gUnderCallbackCounter >= UNDER_START_GAP) &&
515 		(gUnderCallbackCounter <= UNDER_STOP_GAP) )
516 	{
517 		if( gUnderCallbackCounter == UNDER_START_GAP)
518 		{
519 			AddTraceMessage("Begin stall: gUnderCallbackCounter =======", gUnderCallbackCounter );
520 		}
521 		if( gUnderCallbackCounter == UNDER_STOP_GAP)
522 		{
523 			AddTraceMessage("End stall: gUnderCallbackCounter =======", gUnderCallbackCounter );
524 		}
525 		return;
526 	}
527 #endif
528 	past = (internalPortAudioStream *) dwUser;
529 	if( past == NULL ) return;
530 	pahsc = (PaHostSoundControl *) past->past_DeviceData;
531 	if( pahsc == NULL ) return;
532 	if( !pahsc->pahsc_IfInsideCallback && past->past_IsActive )
533 	{
534 		if( past->past_StopNow )
535 		{
536 			past->past_IsActive = 0;
537 		}
538 		else if( past->past_StopSoon )
539 		{
540 			DSoundWrapper   *dsw = &pahsc->pahsc_DSoundWrapper;
541 			if( past->past_NumOutputChannels > 0 )
542 			{
543 				DSW_ZeroEmptySpace( dsw );
544 				AddTraceMessage("Pa_TimerCallback: waiting - written ", (int) dsw->dsw_FramesWritten );
545 				AddTraceMessage("Pa_TimerCallback: waiting - played ", (int) dsw->dsw_FramesPlayed );
546 		/* clear past_IsActive when all sound played */
547 				if( dsw->dsw_FramesPlayed >= past->past_FrameCount )
548 				{
549 					past->past_IsActive = 0;
550 				}
551 			}
552 			else
553 			{
554 				past->past_IsActive = 0;
555 			}
556 		}
557 		else
558 		{
559 			pahsc->pahsc_IfInsideCallback = 1;
560 			if( Pa_TimeSlice( past ) != 0)  /* Call time slice independant of timing method. */
561 			{
562 				past->past_StopSoon = 1;
563 			}
564 			pahsc->pahsc_IfInsideCallback = 0;
565 		}
566 	}
567 }
568 /*******************************************************************/
569 PaError PaHost_OpenStream( internalPortAudioStream   *past )
570 {
571 	HRESULT          hr;
572 	PaError          result = paNoError;
573 	PaHostSoundControl *pahsc;
574 	int              numBytes, maxChannels;
575 	unsigned int     minNumBuffers;
576 	internalPortAudioDevice *pad;
577 	DSoundWrapper   *dsw;
578 /* Allocate and initialize host data. */
579 	pahsc = (PaHostSoundControl *) PaHost_AllocateFastMemory(sizeof(PaHostSoundControl)); /* MEM */
580 	if( pahsc == NULL )
581 	{
582 		result = paInsufficientMemory;
583 		goto error;
584 	}
585 	memset( pahsc, 0, sizeof(PaHostSoundControl) );
586 	past->past_DeviceData = (void *) pahsc;
587 	pahsc->pahsc_TimerID = 0;
588 	dsw = &pahsc->pahsc_DSoundWrapper;
589 	DSW_Init( dsw );
590 /* Allocate native buffer. */
591 	maxChannels = ( past->past_NumOutputChannels > past->past_NumInputChannels ) ?
592 		past->past_NumOutputChannels : past->past_NumInputChannels;
593 	pahsc->pahsc_BytesPerBuffer = past->past_FramesPerUserBuffer * maxChannels * sizeof(short);
594 	if( maxChannels > 0 )
595 	{
596 		pahsc->pahsc_NativeBuffer = (short *) PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerBuffer); /* MEM */
597 		if( pahsc->pahsc_NativeBuffer == NULL )
598 		{
599 			result = paInsufficientMemory;
600 			goto error;
601 		}
602 	}
603 	else
604 	{
605 		result = paInvalidChannelCount;
606 		goto error;
607 	}
608 
609 	DBUG(("PaHost_OpenStream: pahsc_MinFramesPerHostBuffer = %d\n", pahsc->pahsc_MinFramesPerHostBuffer ));
610 	minNumBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate );
611 	past->past_NumUserBuffers = ( minNumBuffers > past->past_NumUserBuffers ) ? minNumBuffers : past->past_NumUserBuffers;
612 	numBytes = pahsc->pahsc_BytesPerBuffer * past->past_NumUserBuffers;
613 	if( numBytes < DSBSIZE_MIN )
614 	{
615 		result = paBufferTooSmall;
616 		goto error;
617 	}
618 	if( numBytes > DSBSIZE_MAX )
619 	{
620 		result = paBufferTooBig;
621 		goto error;
622 	}
623 	pahsc->pahsc_FramesPerDSBuffer = past->past_FramesPerUserBuffer * past->past_NumUserBuffers;
624 	{
625 		int msecLatency = (int) ((pahsc->pahsc_FramesPerDSBuffer * 1000) / past->past_SampleRate);
626 		PRINT(("PortAudio on DirectSound - Latency = %d frames, %d msec\n", pahsc->pahsc_FramesPerDSBuffer, msecLatency ));
627 	}
628 /* ------------------ OUTPUT */
629 	if( (past->past_OutputDeviceID >= 0) && (past->past_NumOutputChannels > 0) )
630 	{
631 		DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", past->past_OutputDeviceID));
632 		pad = Pa_GetInternalDevice( past->past_OutputDeviceID );
633 		hr = DirectSoundCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSound,   NULL );
634 /* If this fails, then try each output device until we find one that works. */
635 		if( hr != DS_OK )
636 		{
637 			int i;
638 			ERR_RPT(("Creation of requested Audio Output device '%s' failed.\n",
639 				((pad->pad_lpGUID == NULL) ? "Default" : pad->pad_Info.name) ));
640 			for( i=0; i<Pa_CountDevices(); i++ )
641 			{
642 				pad = Pa_GetInternalDevice( i );
643 				if( pad->pad_Info.maxOutputChannels >= past->past_NumOutputChannels )
644 				{
645 					DBUG(("Try device '%s' instead.\n", pad->pad_Info.name ));
646 					hr = DirectSoundCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSound,   NULL );
647 					if( hr == DS_OK )
648 					{
649 						ERR_RPT(("Using device '%s' instead.\n", pad->pad_Info.name ));
650 						break;
651 					}
652 				}
653 			}
654 		}
655 		if( hr != DS_OK )
656 		{
657 			ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n"));
658 			result = paHostError;
659 			sPaHostError = hr;
660 			goto error;
661 		}
662 		hr = DSW_InitOutputBuffer( dsw,
663 			(unsigned long) (past->past_SampleRate + 0.5),
664 			past->past_NumOutputChannels, numBytes );
665 		DBUG(("DSW_InitOutputBuffer() returns %x\n", hr));
666 		if( hr != DS_OK )
667 		{
668 			result = paHostError;
669 			sPaHostError = hr;
670 			goto error;
671 		}
672 		past->past_FrameCount = pahsc->pahsc_DSoundWrapper.dsw_FramesWritten;
673 	}
674 #if SUPPORT_AUDIO_CAPTURE
675 /* ------------------ INPUT */
676 	if( (past->past_InputDeviceID >= 0) && (past->past_NumInputChannels > 0) )
677 	{
678 		pad = Pa_GetInternalDevice( past->past_InputDeviceID );
679 		hr = DirectSoundCaptureCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSoundCapture,   NULL );
680 /* If this fails, then try each input device until we find one that works. */
681 		if( hr != DS_OK )
682 		{
683 			int i;
684 			ERR_RPT(("Creation of requested Audio Capture device '%s' failed.\n",
685 				((pad->pad_lpGUID == NULL) ? "Default" : pad->pad_Info.name) ));
686 			for( i=0; i<Pa_CountDevices(); i++ )
687 			{
688 				pad = Pa_GetInternalDevice( i );
689 				if( pad->pad_Info.maxInputChannels >= past->past_NumInputChannels )
690 				{
691 					PRINT(("Try device '%s' instead.\n", pad->pad_Info.name ));
692 					hr = DirectSoundCaptureCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSoundCapture,   NULL );
693 					if( hr == DS_OK ) break;
694 				}
695 			}
696 		}
697 		if( hr != DS_OK )
698 		{
699 			ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n"));
700 			result = paHostError;
701 			sPaHostError = hr;
702 			goto error;
703 		}
704 		hr = DSW_InitInputBuffer( dsw,
705 			(unsigned long) (past->past_SampleRate + 0.5),
706 			past->past_NumInputChannels, numBytes );
707 		DBUG(("DSW_InitInputBuffer() returns %x\n", hr));
708 		if( hr != DS_OK )
709 		{
710 			ERR_RPT(("PortAudio: DSW_InitInputBuffer() returns %x\n", hr));
711 			result = paHostError;
712 			sPaHostError = hr;
713 			goto error;
714 		}
715 	}
716 #endif /* SUPPORT_AUDIO_CAPTURE */
717 	/* Calculate scalar used in CPULoad calculation. */
718 	{
719 		LARGE_INTEGER frequency;
720 		if( QueryPerformanceFrequency( &frequency ) == 0 )
721 		{
722 			pahsc->pahsc_InverseTicksPerUserBuffer = 0.0;
723 		}
724 		else
725 		{
726 			pahsc->pahsc_InverseTicksPerUserBuffer = past->past_SampleRate /
727 				( (double)frequency.QuadPart * past->past_FramesPerUserBuffer );
728 			DBUG(("pahsc_InverseTicksPerUserBuffer = %g\n", pahsc->pahsc_InverseTicksPerUserBuffer ));
729 		}
730 	}
731 	return result;
732 error:
733 	PaHost_CloseStream( past );
734 	return result;
735 }
736 /*************************************************************************/
737 PaError PaHost_StartOutput( internalPortAudioStream *past )
738 {
739 	HRESULT          hr;
740 	PaHostSoundControl *pahsc;
741 	PaError          result = paNoError;
742 	pahsc = (PaHostSoundControl *) past->past_DeviceData;
743 /* Give user callback a chance to pre-fill buffer. */
744 	result = Pa_TimeSlice( past );
745 	if( result != paNoError ) return result; // FIXME - what if finished?
746 	hr = DSW_StartOutput( &pahsc->pahsc_DSoundWrapper );
747 	DBUG(("PaHost_StartOutput: DSW_StartOutput returned = 0x%X.\n", hr));
748 	if( hr != DS_OK )
749 	{
750 		result = paHostError;
751 		sPaHostError = hr;
752 		goto error;
753 	}
754 error:
755 	return result;
756 }
757 /*************************************************************************/
758 PaError PaHost_StartInput( internalPortAudioStream *past )
759 {
760 	PaError          result = paNoError;
761 #if SUPPORT_AUDIO_CAPTURE
762 	HRESULT          hr;
763 	PaHostSoundControl *pahsc;
764 	pahsc = (PaHostSoundControl *) past->past_DeviceData;
765 	hr = DSW_StartInput( &pahsc->pahsc_DSoundWrapper );
766 	DBUG(("Pa_StartStream: DSW_StartInput returned = 0x%X.\n", hr));
767 	if( hr != DS_OK )
768 	{
769 		result = paHostError;
770 		sPaHostError = hr;
771 		goto error;
772 	}
773 error:
774 #endif /* SUPPORT_AUDIO_CAPTURE */
775 	return result;
776 }
777 /*************************************************************************/
778 PaError PaHost_StartEngine( internalPortAudioStream *past )
779 {
780 	PaHostSoundControl *pahsc;
781 	PaError          result = paNoError;
782 	pahsc = (PaHostSoundControl *) past->past_DeviceData;
783 	past->past_StopNow = 0;
784 	past->past_StopSoon = 0;
785 	past->past_IsActive = 1;
786 /* Create timer that will wake us up so we can fill the DSound buffer. */
787 	{
788 		int msecPerBuffer;
789 		int resolution;
790 		int bufsPerInterrupt = past->past_NumUserBuffers/4;
791 		if( bufsPerInterrupt < 1 ) bufsPerInterrupt = 1;
792 		msecPerBuffer = 1000 * (bufsPerInterrupt * past->past_FramesPerUserBuffer) / (int) past->past_SampleRate;
793 		if( msecPerBuffer < 10 ) msecPerBuffer = 10;
794 		else if( msecPerBuffer > 100 ) msecPerBuffer = 100;
795 		resolution = msecPerBuffer/4;
796 		pahsc->pahsc_TimerID = timeSetEvent( msecPerBuffer, resolution, (LPTIMECALLBACK) Pa_TimerCallback,
797 		(DWORD) past, TIME_PERIODIC );
798 	}
799 	if( pahsc->pahsc_TimerID == 0 )
800 	{
801 		past->past_IsActive = 0;
802 		result = paHostError;
803 		sPaHostError = 0;
804 		goto error;
805 	}
806 error:
807 	return result;
808 }
809 /*************************************************************************/
810 PaError PaHost_StopEngine( internalPortAudioStream *past, int abort )
811 {
812 	int timeoutMsec;
813 	PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
814 	if( pahsc == NULL ) return paNoError;
815 	if( abort ) past->past_StopNow = 1;
816 	past->past_StopSoon = 1;
817 /* Set timeout at 20% beyond maximum time we might wait. */
818 	timeoutMsec = (int) (1200.0 * pahsc->pahsc_FramesPerDSBuffer / past->past_SampleRate);
819 	while( past->past_IsActive && (timeoutMsec > 0)  )
820 	{
821 		Sleep(10);
822 		timeoutMsec -= 10;
823 	}
824 	if( pahsc->pahsc_TimerID != 0 )
825 	{
826 		timeKillEvent(pahsc->pahsc_TimerID);  /* Stop callback timer. */
827 		pahsc->pahsc_TimerID = 0;
828 	}
829 	return paNoError;
830 }
831 /*************************************************************************/
832 PaError PaHost_StopInput( internalPortAudioStream *past, int abort )
833 {
834 #if SUPPORT_AUDIO_CAPTURE
835 	HRESULT hr;
836 	PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData;
837 	if( pahsc == NULL ) return paNoError;
838 	(void) abort;
839 	hr = DSW_StopInput( &pahsc->pahsc_DSoundWrapper );
840 	DBUG(("DSW_StopInput() result is %x\n", hr));
841 #endif /* SUPPORT_AUDIO_CAPTURE */
842 	return paNoError;
843 }
844 /*************************************************************************/
845 PaError PaHost_StopOutput( internalPortAudioStream *past, int abort )
846 {
847 	HRESULT hr;
848 	PaHostSoundControl *pahsc;
849 	pahsc = (PaHostSoundControl *) past->past_DeviceData;
850 	if( pahsc == NULL ) return paNoError;
851 	(void) abort;
852 	hr = DSW_StopOutput( &pahsc->pahsc_DSoundWrapper );
853 	DBUG(("DSW_StopOutput() result is %x\n", hr));
854 	return paNoError;
855 }
856 /*******************************************************************/
857 PaError PaHost_CloseStream( internalPortAudioStream   *past )
858 {
859 	PaHostSoundControl *pahsc;
860 	if( past == NULL ) return paBadStreamPtr;
861 	pahsc = (PaHostSoundControl *) past->past_DeviceData;
862 	if( pahsc == NULL ) return paNoError;
863 	DSW_Term( &pahsc->pahsc_DSoundWrapper );
864 	if( pahsc->pahsc_NativeBuffer )
865 	{
866 		PaHost_FreeFastMemory( pahsc->pahsc_NativeBuffer, pahsc->pahsc_BytesPerBuffer ); /* MEM */
867 		pahsc->pahsc_NativeBuffer = NULL;
868 	}
869 	PaHost_FreeFastMemory( pahsc, sizeof(PaHostSoundControl) ); /* MEM */
870 	past->past_DeviceData = NULL;
871 	return paNoError;
872 }
873 /*************************************************************************
874 ** Determine minimum number of buffers required for this host based
875 ** on minimum latency. Latency can be optionally set by user by setting
876 ** an environment variable. For example, to set latency to 200 msec, put:
877 **
878 **    set PA_MIN_LATENCY_MSEC=200
879 **
880 ** in the AUTOEXEC.BAT file and reboot.
881 ** If the environment variable is not set, then the latency will be determined
882 ** based on the OS. Windows NT has higher latency than Win95.
883 */
884 #define PA_LATENCY_ENV_NAME  ("PA_MIN_LATENCY_MSEC")
885 int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate )
886 {
887 	char      envbuf[PA_ENV_BUF_SIZE];
888 	DWORD     hostVersion;
889 	DWORD     hresult;
890 	int       minLatencyMsec = 0;
891 	double    msecPerBuffer = (1000.0 * framesPerBuffer) / sampleRate;
892 	int       minBuffers;
893 /* Let user determine minimal latency by setting environment variable. */
894 	hresult = GetEnvironmentVariable( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE );
895 	if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) )
896 	{
897 		minLatencyMsec = atoi( envbuf );
898 	}
899 	else
900 	{
901 /* Set minimal latency based on whether NT or Win95.
902  * NT has higher latency.
903  */
904 		hostVersion = GetVersion();
905 /* High bit clear if NT */
906 		minLatencyMsec = ( (hostVersion & 0x80000000) == 0 ) ? PA_WIN_NT_LATENCY : PA_WIN_9X_LATENCY  ;
907 #if PA_USE_HIGH_LATENCY
908 		PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec ));
909 #endif
910 	}
911 	minBuffers = (int) (1.0 + ((double)minLatencyMsec / msecPerBuffer));
912 	if( minBuffers < 2 ) minBuffers = 2;
913 	return minBuffers;
914 }
915 /*************************************************************************/
916 PaError PaHost_Term( void )
917 {
918 	int i;
919 /* Free names allocated during enumeration. */
920 	for( i=0; i<sNumDevices; i++ )
921 	{
922 		if( sDevices[i].pad_Info.name != NULL )
923 		{
924 			free( (void *) sDevices[i].pad_Info.name );
925 			sDevices[i].pad_Info.name = NULL;
926 		}
927 	}
928 	if( sDevices != NULL )
929 	{
930 		PaHost_FreeFastMemory( sDevices, sNumDevices * sizeof(internalPortAudioDevice) ); /* MEM */
931 		sDevices = NULL;
932 		sNumDevices = 0;
933 	}
934 	return 0;
935 }
936 void Pa_Sleep( long msec )
937 {
938 	Sleep( msec );
939 }
940 /*************************************************************************
941  * Allocate memory that can be accessed in real-time.
942  * This may need to be held in physical memory so that it is not
943  * paged to virtual memory.
944  * This call MUST be balanced with a call to PaHost_FreeFastMemory().
945  * Memory will be set to zero.
946  */
947 void *PaHost_AllocateFastMemory( long numBytes )
948 {
949 	void *addr = GlobalAlloc( GPTR, numBytes ); /* FIXME - do we need physical memory? Use VirtualLock() */ /* MEM */
950 	return addr;
951 }
952 /*************************************************************************
953  * Free memory that could be accessed in real-time.
954  * This call MUST be balanced with a call to PaHost_AllocateFastMemory().
955  */
956 void PaHost_FreeFastMemory( void *addr, long numBytes )
957 {
958 	if( addr != NULL ) GlobalFree( addr ); /* MEM */
959 }
960 /***********************************************************************/
961 PaError PaHost_StreamActive( internalPortAudioStream   *past )
962 {
963 	PaHostSoundControl *pahsc;
964 	if( past == NULL ) return paBadStreamPtr;
965 	pahsc = (PaHostSoundControl *) past->past_DeviceData;
966 	if( pahsc == NULL ) return paInternalError;
967 	return (PaError) (past->past_IsActive);
968 }
969 /*************************************************************************/
970 PaTimestamp Pa_StreamTime( PortAudioStream *stream )
971 {
972 	DSoundWrapper   *dsw;
973 	internalPortAudioStream   *past = (internalPortAudioStream *) stream;
974 	PaHostSoundControl *pahsc;
975 	if( past == NULL ) return paBadStreamPtr;
976 	pahsc = (PaHostSoundControl *) past->past_DeviceData;
977 	dsw = &pahsc->pahsc_DSoundWrapper;
978 	return dsw->dsw_FramesPlayed;
979 }
980