aboutsummaryrefslogtreecommitdiff
path: root/pd/portaudio_v18/pa_win_wmme/pa_win_wmme.c
diff options
context:
space:
mode:
Diffstat (limited to 'pd/portaudio_v18/pa_win_wmme/pa_win_wmme.c')
-rw-r--r--pd/portaudio_v18/pa_win_wmme/pa_win_wmme.c1714
1 files changed, 1714 insertions, 0 deletions
diff --git a/pd/portaudio_v18/pa_win_wmme/pa_win_wmme.c b/pd/portaudio_v18/pa_win_wmme/pa_win_wmme.c
new file mode 100644
index 00000000..9dc03826
--- /dev/null
+++ b/pd/portaudio_v18/pa_win_wmme/pa_win_wmme.c
@@ -0,0 +1,1714 @@
+/*
+ * $Id: pa_win_wmme.c,v 1.6.4.3 2003/04/28 17:43:48 philburk Exp $
+ * pa_win_wmme.c
+ * Implementation of PortAudio for Windows MultiMedia Extensions (WMME)
+ *
+ * PortAudio Portable Real-Time Audio Library
+ * Latest Version at: http://www.portaudio.com
+ *
+ * Authors: Ross Bencina and Phil Burk
+ * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+/*
+ All memory allocations and frees are marked with MEM for quick review.
+*/
+
+/* Modification History:
+ PLB = Phil Burk
+ JM = Julien Maillard
+ RDB = Ross Bencina
+ PLB20010402 - sDevicePtrs now allocates based on sizeof(pointer)
+ PLB20010413 - check for excessive numbers of channels
+ PLB20010422 - apply Mike Berry's changes for CodeWarrior on PC
+ including condition including of memory.h,
+ and explicit typecasting on memory allocation
+ PLB20010802 - use GlobalAlloc for sDevicesPtr instead of PaHost_AllocFastMemory
+ PLB20010816 - pass process instead of thread to SetPriorityClass()
+ PLB20010927 - use number of frames instead of real-time for CPULoad calculation.
+ JM20020118 - prevent hung thread when buffers underflow.
+ PLB20020321 - detect Win XP versus NT, 9x; fix DBUG typo; removed init of CurrentCount
+ RDB20020411 - various renaming cleanups, factored streamData alloc and cpu usage init
+ RDB20020417 - stopped counting WAVE_MAPPER when there were no real devices
+ refactoring, renaming and fixed a few edge case bugs
+ PLB20020612 - added 8000.0 Hz to custom sampling rates array
+*/
+#pragma warning (disable: 4115)
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <windows.h>
+#include <mmsystem.h>
+#include <process.h>
+/* PLB20010422 - "memory.h" doesn't work on CodeWarrior for PC. Thanks Mike Berry for the mod. */
+#ifndef __MWERKS__
+#include <malloc.h>
+#include <memory.h>
+#endif /* __MWERKS__ */
+#include "portaudio.h"
+#include "pa_host.h"
+#include "pa_trace.h"
+
+/************************************************* Constants ********/
+#define PA_TRACK_MEMORY (0)
+
+#define PA_USE_TIMER_CALLBACK (0) /* Select between two options for background task. 0=thread, 1=timer */
+/* Switches for debugging. */
+#define PA_SIMULATE_UNDERFLOW (0) /* Set to one to force an underflow of the output buffer. */
+
+/* To trace program, enable TRACE_REALTIME_EVENTS in pa_trace.h */
+#define PA_TRACE_RUN (0)
+#define PA_TRACE_START_STOP (1)
+
+#define PA_USE_HIGH_LATENCY (0) /* For debugging glitches. */
+
+#if PA_USE_HIGH_LATENCY
+ #define PA_MIN_MSEC_PER_HOST_BUFFER (100)
+ #define PA_MAX_MSEC_PER_HOST_BUFFER (300) /* Do not exceed unless user buffer exceeds */
+ #define PA_MIN_NUM_HOST_BUFFERS (4)
+ #define PA_MAX_NUM_HOST_BUFFERS (16) /* OK to exceed if necessary */
+ #define PA_WIN_9X_LATENCY (400)
+#else
+ #define PA_MIN_MSEC_PER_HOST_BUFFER (10)
+ #define PA_MAX_MSEC_PER_HOST_BUFFER (100) /* Do not exceed unless user buffer exceeds */
+ #define PA_MIN_NUM_HOST_BUFFERS (3)
+ #define PA_MAX_NUM_HOST_BUFFERS (16) /* OK to exceed if necessary */
+ #define PA_WIN_9X_LATENCY (200)
+#endif
+#define MIN_TIMEOUT_MSEC (1000)
+
+/*
+** Use higher latency for NT because it is even worse at real-time
+** operation than Win9x.
+*/
+#define PA_WIN_NT_LATENCY (PA_WIN_9X_LATENCY * 2)
+#define PA_WIN_WDM_LATENCY (PA_WIN_9X_LATENCY)
+
+#if PA_SIMULATE_UNDERFLOW
+static gUnderCallbackCounter = 0;
+#define UNDER_SLEEP_AT (40)
+#define UNDER_SLEEP_FOR (500)
+#endif
+
+#define PRINT(x) { printf x; fflush(stdout); }
+#define ERR_RPT(x) PRINT(x)
+#define DBUG(x) /* PRINT(x) */
+#define DBUGX(x) /* PRINT(x) */
+/************************************************* Definitions ********/
+/**************************************************************
+ * Structure for internal host specific stream data.
+ * This is allocated on a per stream basis.
+ */
+typedef struct PaWMMEStreamData
+{
+ /* Input -------------- */
+ HWAVEIN hWaveIn;
+ WAVEHDR *inputBuffers;
+ int currentInputBuffer;
+ int bytesPerHostInputBuffer;
+ int bytesPerUserInputBuffer; /* native buffer size in bytes */
+ /* Output -------------- */
+ HWAVEOUT hWaveOut;
+ WAVEHDR *outputBuffers;
+ int currentOutputBuffer;
+ int bytesPerHostOutputBuffer;
+ int bytesPerUserOutputBuffer; /* native buffer size in bytes */
+ /* Run Time -------------- */
+ PaTimestamp framesPlayed;
+ long lastPosition; /* used to track frames played. */
+ /* For measuring CPU utilization. */
+ LARGE_INTEGER entryCount;
+ double inverseTicksPerHostBuffer;
+ /* Init Time -------------- */
+ int numHostBuffers;
+ int framesPerHostBuffer;
+ int userBuffersPerHostBuffer;
+ CRITICAL_SECTION streamLock; /* Mutext to prevent threads from colliding. */
+ INT streamLockInited;
+#if PA_USE_TIMER_CALLBACK
+ BOOL ifInsideCallback; /* Test for reentrancy. */
+ MMRESULT timerID;
+#else
+ HANDLE abortEvent;
+ int abortEventInited;
+ HANDLE bufferEvent;
+ int bufferEventInited;
+ HANDLE engineThread;
+ DWORD engineThreadID;
+#endif
+}
+PaWMMEStreamData;
+/************************************************* Shared Data ********/
+/* FIXME - put Mutex around this shared data. */
+static int sNumInputDevices = 0;
+static int sNumOutputDevices = 0;
+static int sNumDevices = 0;
+static PaDeviceInfo **sDevicePtrs = NULL;
+static int sDefaultInputDeviceID = paNoDevice;
+static int sDefaultOutputDeviceID = paNoDevice;
+static int sPaHostError = 0;
+static const char sMapperSuffixInput[] = " - Input";
+static const char sMapperSuffixOutput[] = " - Output";
+
+#if PA_TRACK_MEMORY
+static int sNumAllocations = 0;
+#endif
+
+/************************************************* Macros ********/
+/* Convert external PA ID to an internal ID that includes WAVE_MAPPER */
+#define PaDeviceIdToWinId(id) (((id) < sNumInputDevices) ? (id - 1) : (id - sNumInputDevices - 1))
+/************************************************* Prototypes **********/
+
+void Pa_InitializeNumDevices( void );
+PaError Pa_AllocateDevicePtrs( void );
+
+static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg,
+ DWORD dwUser, DWORD dw1, DWORD dw2);
+PaError PaHost_GetTotalBufferFrames( internalPortAudioStream *past );
+static PaError PaHost_UpdateStreamTime( PaWMMEStreamData *wmmeStreamData );
+static PaError PaHost_BackgroundManager( internalPortAudioStream *past );
+
+static void *PaHost_AllocateTrackedMemory( long numBytes );
+static void PaHost_FreeTrackedMemory( void *addr );
+
+/*******************************************************************/
+static PaError PaHost_AllocateWMMEStreamData( internalPortAudioStream *stream )
+{
+ PaError result = paNoError;
+ PaWMMEStreamData *wmmeStreamData;
+
+ wmmeStreamData = (PaWMMEStreamData *) PaHost_AllocateFastMemory(sizeof(PaWMMEStreamData)); /* MEM */
+ if( wmmeStreamData == NULL )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ memset( wmmeStreamData, 0, sizeof(PaWMMEStreamData) );
+ stream->past_DeviceData = (void *) wmmeStreamData;
+
+ return result;
+
+error:
+ return result;
+}
+
+/*******************************************************************/
+static void PaHost_FreeWMMEStreamData( internalPortAudioStream *internalStream )
+{
+ PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) internalStream->past_DeviceData;
+
+ PaHost_FreeFastMemory( wmmeStreamData, sizeof(PaWMMEStreamData) ); /* MEM */
+ internalStream->past_DeviceData = NULL;
+}
+/*************************************************************************/
+static PaWMMEStreamData* PaHost_GetWMMEStreamData( internalPortAudioStream* internalStream )
+{
+ PaWMMEStreamData *result = NULL;
+
+ if( internalStream != NULL )
+ {
+ result = (PaWMMEStreamData *) internalStream->past_DeviceData;
+ }
+ return result;
+}
+/********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/
+/* FIXME: the cpu usage code should be factored out into a common module */
+static void Pa_InitializeCpuUsageScalar( internalPortAudioStream *stream )
+{
+ PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
+
+ LARGE_INTEGER frequency;
+ if( QueryPerformanceFrequency( &frequency ) == 0 )
+ {
+ wmmeStreamData->inverseTicksPerHostBuffer = 0.0;
+ }
+ else
+ {
+ wmmeStreamData->inverseTicksPerHostBuffer = stream->past_SampleRate /
+ ( (double)frequency.QuadPart * stream->past_FramesPerUserBuffer * wmmeStreamData->userBuffersPerHostBuffer );
+ DBUG(("inverseTicksPerHostBuffer = %g\n", wmmeStreamData->inverseTicksPerHostBuffer ));
+ }
+}
+static void Pa_StartUsageCalculation( internalPortAudioStream *stream )
+{
+ PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
+
+ if( wmmeStreamData == NULL ) return;
+ /* Query system timer for usage analysis and to prevent overuse of CPU. */
+ QueryPerformanceCounter( &wmmeStreamData->entryCount );
+}
+static void Pa_EndUsageCalculation( internalPortAudioStream *stream )
+{
+ LARGE_INTEGER CurrentCount;
+ PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
+
+ if( wmmeStreamData == NULL ) return;
+ /*
+ * Measure CPU utilization during this callback. Note that this calculation
+ * assumes that we had the processor the whole time.
+ */
+#define LOWPASS_COEFFICIENT_0 (0.9)
+#define LOWPASS_COEFFICIENT_1 (0.99999 - LOWPASS_COEFFICIENT_0)
+ if( QueryPerformanceCounter( &CurrentCount ) )
+ {
+ LONGLONG InsideCount = CurrentCount.QuadPart - wmmeStreamData->entryCount.QuadPart;
+ double newUsage = InsideCount * wmmeStreamData->inverseTicksPerHostBuffer;
+ stream->past_Usage = (LOWPASS_COEFFICIENT_0 * stream->past_Usage) +
+ (LOWPASS_COEFFICIENT_1 * newUsage);
+ }
+}
+/****************************************** END CPU UTILIZATION *******/
+
+static void Pa_InitializeNumDevices( void )
+{
+ sNumInputDevices = waveInGetNumDevs();
+ if( sNumInputDevices > 0 )
+ {
+ sNumInputDevices += 1; /* add one extra for the WAVE_MAPPER */
+ sDefaultInputDeviceID = 0;
+ }
+ else
+ {
+ sDefaultInputDeviceID = paNoDevice;
+ }
+
+ sNumOutputDevices = waveOutGetNumDevs();
+ if( sNumOutputDevices > 0 )
+ {
+ sNumOutputDevices += 1; /* add one extra for the WAVE_MAPPER */
+ sDefaultOutputDeviceID = sNumInputDevices;
+ }
+ else
+ {
+ sDefaultOutputDeviceID = paNoDevice;
+ }
+
+ sNumDevices = sNumInputDevices + sNumOutputDevices;
+}
+
+static PaError Pa_AllocateDevicePtrs( void )
+{
+ int numBytes;
+ int i;
+
+ /* Allocate structures to hold device info. */
+ /* PLB20010402 - was allocating too much memory. */
+ /* numBytes = sNumDevices * sizeof(PaDeviceInfo); // PLB20010402 */
+
+ if( sNumDevices > 0 )
+ {
+ numBytes = sNumDevices * sizeof(PaDeviceInfo *); /* PLB20010402 */
+ sDevicePtrs = (PaDeviceInfo **) PaHost_AllocateTrackedMemory( numBytes ); /* MEM */
+ if( sDevicePtrs == NULL ) return paInsufficientMemory;
+
+ for( i = 0; i < sNumDevices; i++ )
+ sDevicePtrs[i] = NULL; /* RDB20020417 explicitly set each ptr to NULL */
+ }
+ else
+ {
+ sDevicePtrs = NULL;
+ }
+
+ return paNoError;
+}
+/*************************************************************************/
+long Pa_GetHostError()
+{
+ return sPaHostError;
+}
+/*************************************************************************/
+int Pa_CountDevices()
+{
+ if( PaHost_IsInitialized() )
+ return sNumDevices;
+ else
+ return 0;
+}
+/*************************************************************************
+ * If a PaDeviceInfo structure has not already been created,
+ * then allocate one and fill it in for the selected device.
+ *
+ * We create one extra input and one extra output device for the WAVE_MAPPER.
+ * [Does anyone know how to query the default device and get its name?]
+ */
+const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id )
+{
+#define NUM_STANDARDSAMPLINGRATES 3 /* 11025, 22050, 44100 */
+ static DWORD customSamplingRates[] = { 8000, 32000, 48000, 64000, 88200, 96000 };
+#define NUM_CUSTOMSAMPLINGRATES (sizeof(customSamplingRates)/sizeof(DWORD))
+#define MAX_NUMSAMPLINGRATES (NUM_STANDARDSAMPLINGRATES+NUM_CUSTOMSAMPLINGRATES)
+
+ PaDeviceInfo *deviceInfo;
+ double *sampleRates; /* non-const ptr */
+ int i;
+ char *s;
+
+ DBUG(( "Pa_GetDeviceInfo( %d )\n", id ));
+ if( id < 0 || id >= sNumDevices )
+ return NULL;
+ if( sDevicePtrs[ id ] != NULL )
+ {
+ return sDevicePtrs[ id ];
+ }
+ deviceInfo = (PaDeviceInfo *)PaHost_AllocateTrackedMemory( sizeof(PaDeviceInfo) ); /* MEM */
+ if( deviceInfo == NULL ) return NULL;
+ deviceInfo->structVersion = 1;
+ deviceInfo->maxInputChannels = 0;
+ deviceInfo->maxOutputChannels = 0;
+ deviceInfo->numSampleRates = 0;
+ sampleRates = (double*)PaHost_AllocateTrackedMemory( MAX_NUMSAMPLINGRATES * sizeof(double) ); /* MEM */
+ deviceInfo->sampleRates = sampleRates;
+ deviceInfo->nativeSampleFormats = paInt16; /* should query for higher bit depths below */
+ if( id < sNumInputDevices )
+ {
+ /* input device */
+ int inputMmID = PaDeviceIdToWinId(id);
+ WAVEINCAPS wic;
+ if( waveInGetDevCaps( inputMmID, &wic, sizeof( WAVEINCAPS ) ) != MMSYSERR_NOERROR )
+ goto error;
+
+ /* Append I/O suffix to WAVE_MAPPER device. */
+ if( inputMmID == WAVE_MAPPER )
+ {
+ s = (char *) PaHost_AllocateTrackedMemory( strlen( wic.szPname ) + 1 + sizeof(sMapperSuffixInput) ); /* MEM */
+ strcpy( s, wic.szPname );
+ strcat( s, sMapperSuffixInput );
+ }
+ else
+ {
+ s = (char *) PaHost_AllocateTrackedMemory( strlen( wic.szPname ) + 1 ); /* MEM */
+ strcpy( s, wic.szPname );
+ }
+ deviceInfo->name = s;
+ deviceInfo->maxInputChannels = wic.wChannels;
+ DBUG(( "Pa_GetDeviceInfo: input %s, maxChannels = %d\n", deviceInfo->name, deviceInfo->maxInputChannels ));
+ /* Sometimes a device can return a rediculously large number of channels.
+ * This happened with an SBLive card on a Windows ME box.
+ * If that happens, then force it to 2 channels. PLB20010413
+ */
+ if( (deviceInfo->maxInputChannels < 1) || (deviceInfo->maxInputChannels > 256) )
+ {
+ ERR_RPT(("Pa_GetDeviceInfo: Num input channels reported as %d! Changed to 2.\n", deviceInfo->maxOutputChannels ));
+ deviceInfo->maxInputChannels = 2;
+ }
+ /* Add a sample rate to the list if we can do stereo 16 bit at that rate
+ * based on the format flags. */
+ if( wic.dwFormats & WAVE_FORMAT_1M16 ||wic.dwFormats & WAVE_FORMAT_1S16 )
+ sampleRates[ deviceInfo->numSampleRates++ ] = 11025.;
+ if( wic.dwFormats & WAVE_FORMAT_2M16 ||wic.dwFormats & WAVE_FORMAT_2S16 )
+ sampleRates[ deviceInfo->numSampleRates++ ] = 22050.;
+ if( wic.dwFormats & WAVE_FORMAT_4M16 ||wic.dwFormats & WAVE_FORMAT_4S16 )
+ sampleRates[ deviceInfo->numSampleRates++ ] = 44100.;
+ /* Add a sample rate to the list if we can do stereo 16 bit at that rate
+ * based on opening the device successfully. */
+ for( i=0; i < NUM_CUSTOMSAMPLINGRATES; i++ )
+ {
+ WAVEFORMATEX wfx;
+ wfx.wFormatTag = WAVE_FORMAT_PCM;
+ wfx.nSamplesPerSec = customSamplingRates[i];
+ wfx.wBitsPerSample = 16;
+ wfx.cbSize = 0; /* ignored */
+ wfx.nChannels = (WORD)deviceInfo->maxInputChannels;
+ wfx.nAvgBytesPerSec = wfx.nChannels * wfx.nSamplesPerSec * sizeof(short);
+ wfx.nBlockAlign = (WORD)(wfx.nChannels * sizeof(short));
+ if( waveInOpen( NULL, inputMmID, &wfx, 0, 0, WAVE_FORMAT_QUERY ) == MMSYSERR_NOERROR )
+ {
+ sampleRates[ deviceInfo->numSampleRates++ ] = customSamplingRates[i];
+ }
+ }
+
+ }
+ else if( id - sNumInputDevices < sNumOutputDevices )
+ {
+ /* output device */
+ int outputMmID = PaDeviceIdToWinId(id);
+ WAVEOUTCAPS woc;
+ if( waveOutGetDevCaps( outputMmID, &woc, sizeof( WAVEOUTCAPS ) ) != MMSYSERR_NOERROR )
+ goto error;
+ /* Append I/O suffix to WAVE_MAPPER device. */
+ if( outputMmID == WAVE_MAPPER )
+ {
+ s = (char *) PaHost_AllocateTrackedMemory( strlen( woc.szPname ) + 1 + sizeof(sMapperSuffixOutput) ); /* MEM */
+ strcpy( s, woc.szPname );
+ strcat( s, sMapperSuffixOutput );
+ }
+ else
+ {
+ s = (char *) PaHost_AllocateTrackedMemory( strlen( woc.szPname ) + 1 ); /* MEM */
+ strcpy( s, woc.szPname );
+ }
+ deviceInfo->name = s;
+ deviceInfo->maxOutputChannels = woc.wChannels;
+ DBUG(( "Pa_GetDeviceInfo: output %s, maxChannels = %d\n", deviceInfo->name, deviceInfo->maxOutputChannels ));
+ /* Sometimes a device can return a rediculously large number of channels.
+ * This happened with an SBLive card on a Windows ME box.
+ * It also happens on Win XP!
+ */
+ if( (deviceInfo->maxOutputChannels < 1) || (deviceInfo->maxOutputChannels > 256) )
+ {
+#if 1
+ deviceInfo->maxOutputChannels = 2;
+#else
+ /* If channel max is goofy, then query for max channels. PLB20020228
+ * This doesn't seem to help. Disable code for now. Remove it later.
+ */
+ ERR_RPT(("Pa_GetDeviceInfo: Num output channels reported as %d!", deviceInfo->maxOutputChannels ));
+ deviceInfo->maxOutputChannels = 0;
+ /* Attempt to find the correct maximum by querying the device. */
+ for( i=2; i<16; i += 2 )
+ {
+ WAVEFORMATEX wfx;
+ wfx.wFormatTag = WAVE_FORMAT_PCM;
+ wfx.nSamplesPerSec = 44100;
+ wfx.wBitsPerSample = 16;
+ wfx.cbSize = 0; /* ignored */
+ wfx.nChannels = (WORD) i;
+ wfx.nAvgBytesPerSec = wfx.nChannels * wfx.nSamplesPerSec * sizeof(short);
+ wfx.nBlockAlign = (WORD)(wfx.nChannels * sizeof(short));
+ if( waveOutOpen( NULL, outputMmID, &wfx, 0, 0, WAVE_FORMAT_QUERY ) == MMSYSERR_NOERROR )
+ {
+ deviceInfo->maxOutputChannels = i;
+ }
+ else
+ {
+ break;
+ }
+ }
+#endif
+ ERR_RPT((" Changed to %d.\n", deviceInfo->maxOutputChannels ));
+ }
+
+ /* Add a sample rate to the list if we can do stereo 16 bit at that rate
+ * based on the format flags. */
+ if( woc.dwFormats & WAVE_FORMAT_1M16 ||woc.dwFormats & WAVE_FORMAT_1S16 )
+ sampleRates[ deviceInfo->numSampleRates++ ] = 11025.;
+ if( woc.dwFormats & WAVE_FORMAT_2M16 ||woc.dwFormats & WAVE_FORMAT_2S16 )
+ sampleRates[ deviceInfo->numSampleRates++ ] = 22050.;
+ if( woc.dwFormats & WAVE_FORMAT_4M16 ||woc.dwFormats & WAVE_FORMAT_4S16 )
+ sampleRates[ deviceInfo->numSampleRates++ ] = 44100.;
+
+ /* Add a sample rate to the list if we can do stereo 16 bit at that rate
+ * based on opening the device successfully. */
+ for( i=0; i < NUM_CUSTOMSAMPLINGRATES; i++ )
+ {
+ WAVEFORMATEX wfx;
+ wfx.wFormatTag = WAVE_FORMAT_PCM;
+ wfx.nSamplesPerSec = customSamplingRates[i];
+ wfx.wBitsPerSample = 16;
+ wfx.cbSize = 0; /* ignored */
+ wfx.nChannels = (WORD)deviceInfo->maxOutputChannels;
+ wfx.nAvgBytesPerSec = wfx.nChannels * wfx.nSamplesPerSec * sizeof(short);
+ wfx.nBlockAlign = (WORD)(wfx.nChannels * sizeof(short));
+ DBUG(( "Pa_GetDeviceInfo: waveOutOpen( ... WAVE_FORMAT_QUERY at SR = %d\n", customSamplingRates[i] ));
+ if( waveOutOpen( NULL, outputMmID, &wfx, 0, 0, WAVE_FORMAT_QUERY ) == MMSYSERR_NOERROR )
+ {
+ sampleRates[ deviceInfo->numSampleRates++ ] = customSamplingRates[i];
+ }
+ }
+ }
+ DBUG(( "Pa_GetDeviceInfo: done.\n" ));
+ sDevicePtrs[ id ] = deviceInfo;
+ return deviceInfo;
+
+error:
+ PaHost_FreeTrackedMemory( sampleRates ); /* MEM */
+ PaHost_FreeTrackedMemory( deviceInfo ); /* MEM */
+
+ return NULL;
+}
+/*************************************************************************
+ * Returns recommended device ID.
+ * On the PC, the recommended device can be specified by the user by
+ * setting an environment variable. For example, to use device #1.
+ *
+ * set PA_RECOMMENDED_OUTPUT_DEVICE=1
+ *
+ * The user should first determine the available device ID by using
+ * the supplied application "pa_devs".
+ */
+#define PA_ENV_BUF_SIZE (32)
+#define PA_REC_IN_DEV_ENV_NAME ("PA_RECOMMENDED_INPUT_DEVICE")
+#define PA_REC_OUT_DEV_ENV_NAME ("PA_RECOMMENDED_OUTPUT_DEVICE")
+static PaDeviceID PaHost_GetEnvDefaultDeviceID( char *envName )
+{
+ DWORD hresult;
+ char envbuf[PA_ENV_BUF_SIZE];
+ PaDeviceID recommendedID = paNoDevice;
+
+ /* Let user determine default device by setting environment variable. */
+ hresult = GetEnvironmentVariable( envName, envbuf, PA_ENV_BUF_SIZE );
+ if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) )
+ {
+ recommendedID = atoi( envbuf );
+ }
+ return recommendedID;
+}
+/**********************************************************************
+ * Check for environment variable, else query devices and use result.
+ */
+PaDeviceID Pa_GetDefaultInputDeviceID( void )
+{
+ PaDeviceID result;
+
+ result = PaHost_GetEnvDefaultDeviceID( PA_REC_IN_DEV_ENV_NAME );
+ if( result == paNoDevice || result < 0 || result >= sNumInputDevices )
+ {
+ result = sDefaultInputDeviceID;
+ }
+ return result;
+}
+PaDeviceID Pa_GetDefaultOutputDeviceID( void )
+{
+ PaDeviceID result;
+
+ result = PaHost_GetEnvDefaultDeviceID( PA_REC_OUT_DEV_ENV_NAME );
+ if( result == paNoDevice || result < sNumInputDevices || result >= sNumDevices )
+ {
+ result = sDefaultOutputDeviceID;
+ }
+ return result;
+}
+/**********************************************************************
+ * Initialize Host dependant part of API.
+ */
+PaError PaHost_Init( void )
+{
+
+#if PA_TRACK_MEMORY
+ PRINT(("PaHost_Init: sNumAllocations = %d\n", sNumAllocations ));
+#endif
+
+#if PA_SIMULATE_UNDERFLOW
+ PRINT(("WARNING - Underflow Simulation Enabled - Expect a Big Glitch!!!\n"));
+#endif
+
+
+ Pa_InitializeNumDevices();
+
+ return Pa_AllocateDevicePtrs();
+}
+
+/**********************************************************************
+ * Check WAVE buffers to see if they are done.
+ * Fill any available output buffers and use any available
+ * input buffers by calling user callback.
+ *
+ * This routine will loop until:
+ * user callback returns !=0 OR
+ * all output buffers are filled OR
+ * past->past_StopSoon is set OR
+ * an error occurs when calling WMME.
+ *
+ * Returns >0 when user requests a stop, <0 on error.
+ *
+ */
+static PaError Pa_TimeSlice( internalPortAudioStream *stream )
+{
+ PaError result = paNoError;
+ MMRESULT mmresult;
+ char *inBufPtr;
+ char *outBufPtr;
+ int gotInput = 0;
+ int gotOutput = 0;
+ int i;
+ int buffersProcessed = 0;
+ int done = 0;
+ PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
+
+ if( wmmeStreamData == NULL ) return paInternalError;
+
+ stream->past_NumCallbacks += 1;
+#if PA_TRACE_RUN
+ AddTraceMessage("Pa_TimeSlice: past_NumCallbacks ", stream->past_NumCallbacks );
+#endif
+
+ /* JM20020118 - prevent hung thread when buffers underflow. */
+ /* while( !done ) /* BAD */
+ while( !done && !stream->past_StopSoon ) /* GOOD */
+ {
+#if PA_SIMULATE_UNDERFLOW
+ if(gUnderCallbackCounter++ == UNDER_SLEEP_AT)
+ {
+ Sleep(UNDER_SLEEP_FOR);
+ }
+#endif
+
+ /* If we are using output, then we need an empty output buffer. */
+ gotOutput = 0;
+ outBufPtr = NULL;
+ if( stream->past_NumOutputChannels > 0 )
+ {
+ if((wmmeStreamData->outputBuffers[ wmmeStreamData->currentOutputBuffer ].dwFlags & WHDR_DONE) == 0)
+ {
+ break; /* If none empty then bail and try again later. */
+ }
+ else
+ {
+ outBufPtr = wmmeStreamData->outputBuffers[ wmmeStreamData->currentOutputBuffer ].lpData;
+ gotOutput = 1;
+ }
+ }
+ /* Use an input buffer if one is available. */
+ gotInput = 0;
+ inBufPtr = NULL;
+ if( ( stream->past_NumInputChannels > 0 ) &&
+ (wmmeStreamData->inputBuffers[ wmmeStreamData->currentInputBuffer ].dwFlags & WHDR_DONE) )
+ {
+ inBufPtr = wmmeStreamData->inputBuffers[ wmmeStreamData->currentInputBuffer ].lpData;
+ gotInput = 1;
+#if PA_TRACE_RUN
+ AddTraceMessage("Pa_TimeSlice: got input buffer at ", (int)inBufPtr );
+ AddTraceMessage("Pa_TimeSlice: got input buffer # ", wmmeStreamData->currentInputBuffer );
+#endif
+
+ }
+ /* If we can't do anything then bail out. */
+ if( !gotInput && !gotOutput ) break;
+ buffersProcessed += 1;
+ /* Each Wave buffer contains multiple user buffers so do them all now. */
+ /* Base Usage on time it took to process one host buffer. */
+ Pa_StartUsageCalculation( stream );
+ for( i=0; i<wmmeStreamData->userBuffersPerHostBuffer; i++ )
+ {
+ if( done )
+ {
+ if( gotOutput )
+ {
+ /* Clear remainder of wave buffer if we are waiting for stop. */
+ AddTraceMessage("Pa_TimeSlice: zero rest of wave buffer ", i );
+ memset( outBufPtr, 0, wmmeStreamData->bytesPerUserOutputBuffer );
+ }
+ }
+ else
+ {
+ /* Convert 16 bit native data to user data and call user routine. */
+ result = Pa_CallConvertInt16( stream, (short *) inBufPtr, (short *) outBufPtr );
+ if( result != 0) done = 1;
+ }
+ if( gotInput ) inBufPtr += wmmeStreamData->bytesPerUserInputBuffer;
+ if( gotOutput) outBufPtr += wmmeStreamData->bytesPerUserOutputBuffer;
+ }
+ Pa_EndUsageCalculation( stream );
+ /* Send WAVE buffer to Wave Device to be refilled. */
+ if( gotInput )
+ {
+ mmresult = waveInAddBuffer( wmmeStreamData->hWaveIn,
+ &wmmeStreamData->inputBuffers[ wmmeStreamData->currentInputBuffer ],
+ sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ sPaHostError = mmresult;
+ result = paHostError;
+ break;
+ }
+ wmmeStreamData->currentInputBuffer = (wmmeStreamData->currentInputBuffer+1 >= wmmeStreamData->numHostBuffers) ?
+ 0 : wmmeStreamData->currentInputBuffer+1;
+ }
+ /* Write WAVE buffer to Wave Device. */
+ if( gotOutput )
+ {
+#if PA_TRACE_START_STOP
+ AddTraceMessage( "Pa_TimeSlice: writing buffer ", wmmeStreamData->currentOutputBuffer );
+#endif
+ mmresult = waveOutWrite( wmmeStreamData->hWaveOut,
+ &wmmeStreamData->outputBuffers[ wmmeStreamData->currentOutputBuffer ],
+ sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ sPaHostError = mmresult;
+ result = paHostError;
+ break;
+ }
+ wmmeStreamData->currentOutputBuffer = (wmmeStreamData->currentOutputBuffer+1 >= wmmeStreamData->numHostBuffers) ?
+ 0 : wmmeStreamData->currentOutputBuffer+1;
+ }
+
+ }
+
+#if PA_TRACE_RUN
+ AddTraceMessage("Pa_TimeSlice: buffersProcessed ", buffersProcessed );
+#endif
+ return (result != 0) ? result : done;
+}
+
+/*******************************************************************/
+static PaError PaHost_BackgroundManager( internalPortAudioStream *stream )
+{
+ PaError result = paNoError;
+ int i;
+ int numQueuedoutputBuffers = 0;
+ PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
+
+ /* Has someone asked us to abort by calling Pa_AbortStream()? */
+ if( stream->past_StopNow )
+ {
+ stream->past_IsActive = 0; /* Will cause thread to return. */
+ }
+ /* Has someone asked us to stop by calling Pa_StopStream()
+ * OR has a user callback returned '1' to indicate finished.
+ */
+ else if( stream->past_StopSoon )
+ {
+ /* Poll buffer and when all have played then exit thread. */
+ /* Count how many output buffers are queued. */
+ numQueuedoutputBuffers = 0;
+ if( stream->past_NumOutputChannels > 0 )
+ {
+ for( i=0; i<wmmeStreamData->numHostBuffers; i++ )
+ {
+ if( !( wmmeStreamData->outputBuffers[ i ].dwFlags & WHDR_DONE) )
+ {
+#if PA_TRACE_START_STOP
+ AddTraceMessage( "PaHost_BackgroundManager: waiting for buffer ", i );
+#endif
+ numQueuedoutputBuffers++;
+ }
+ }
+ }
+#if PA_TRACE_START_STOP
+ AddTraceMessage( "PaHost_BackgroundManager: numQueuedoutputBuffers ", numQueuedoutputBuffers );
+#endif
+ if( numQueuedoutputBuffers == 0 )
+ {
+ stream->past_IsActive = 0; /* Will cause thread to return. */
+ }
+ }
+ else
+ {
+ /* Process full input buffer and fill up empty output buffers. */
+ if( (result = Pa_TimeSlice( stream )) != 0)
+ {
+ /* User callback has asked us to stop. */
+#if PA_TRACE_START_STOP
+ AddTraceMessage( "PaHost_BackgroundManager: TimeSlice() returned ", result );
+#endif
+ stream->past_StopSoon = 1; /* Request that audio play out then stop. */
+ result = paNoError;
+ }
+ }
+
+ PaHost_UpdateStreamTime( wmmeStreamData );
+ return result;
+}
+
+#if PA_USE_TIMER_CALLBACK
+/*******************************************************************/
+static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2)
+{
+ internalPortAudioStream *stream;
+ PaWMMEStreamData *wmmeStreamData;
+ PaError result;
+
+ stream = (internalPortAudioStream *) dwUser;
+ if( stream == NULL ) return;
+ wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
+ if( wmmeStreamData == NULL ) return;
+ if( wmmeStreamData->ifInsideCallback )
+ {
+ if( wmmeStreamData->timerID != 0 )
+ {
+ timeKillEvent(wmmeStreamData->timerID); /* Stop callback timer. */
+ wmmeStreamData->timerID = 0;
+ }
+ return;
+ }
+ wmmeStreamData->ifInsideCallback = 1;
+ /* Manage flags and audio processing. */
+ result = PaHost_BackgroundManager( stream );
+ if( result != paNoError )
+ {
+ stream->past_IsActive = 0;
+ }
+ wmmeStreamData->ifInsideCallback = 0;
+}
+#else /* PA_USE_TIMER_CALLBACK */
+/*******************************************************************/
+static DWORD WINAPI WinMMPa_OutputThreadProc( void *pArg )
+{
+ internalPortAudioStream *stream;
+ PaWMMEStreamData *wmmeStreamData;
+ HANDLE events[2];
+ int numEvents = 0;
+ DWORD result = 0;
+ DWORD waitResult;
+ DWORD numTimeouts = 0;
+ DWORD timeOut;
+ stream = (internalPortAudioStream *) pArg;
+ wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
+#if PA_TRACE_START_STOP
+ AddTraceMessage( "WinMMPa_OutputThreadProc: timeoutPeriod", timeoutPeriod );
+ AddTraceMessage( "WinMMPa_OutputThreadProc: past_NumUserBuffers", stream->past_NumUserBuffers );
+#endif
+ /* Calculate timeOut as half the time it would take to play all buffers. */
+ timeOut = (DWORD) (500.0 * PaHost_GetTotalBufferFrames( stream ) / stream->past_SampleRate);
+ /* Get event(s) ready for wait. */
+ events[numEvents++] = wmmeStreamData->bufferEvent;
+ if( wmmeStreamData->abortEventInited ) events[numEvents++] = wmmeStreamData->abortEvent;
+ /* Stay in this thread as long as we are "active". */
+ while( stream->past_IsActive )
+ {
+ /*******************************************************************/
+ /******** WAIT here for an event from WMME or PA *******************/
+ /*******************************************************************/
+ waitResult = WaitForMultipleObjects( numEvents, events, FALSE, timeOut );
+ /* Error? */
+ if( waitResult == WAIT_FAILED )
+ {
+ sPaHostError = GetLastError();
+ result = paHostError;
+ stream->past_IsActive = 0;
+ }
+ /* Timeout? Don't stop. Just keep polling for DONE.*/
+ else if( waitResult == WAIT_TIMEOUT )
+ {
+#if PA_TRACE_START_STOP
+ AddTraceMessage( "WinMMPa_OutputThreadProc: timed out ", numQueuedoutputBuffers );
+#endif
+ numTimeouts += 1;
+ }
+ /* Manage flags and audio processing. */
+ result = PaHost_BackgroundManager( stream );
+ if( result != paNoError )
+ {
+ stream->past_IsActive = 0;
+ }
+ }
+ return result;
+}
+#endif
+
+/*******************************************************************/
+PaError PaHost_OpenInputStream( internalPortAudioStream *stream )
+{
+ PaError result = paNoError;
+ MMRESULT mmresult;
+ PaWMMEStreamData *wmmeStreamData;
+ int i;
+ int inputMmId;
+ int bytesPerInputFrame;
+ WAVEFORMATEX wfx;
+ const PaDeviceInfo *deviceInfo;
+
+ wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
+ DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", stream->past_InputDeviceID));
+ deviceInfo = Pa_GetDeviceInfo( stream->past_InputDeviceID );
+ if( deviceInfo == NULL ) return paInternalError;
+
+ switch( deviceInfo->nativeSampleFormats )
+ {
+ case paInt32:
+ case paFloat32:
+ bytesPerInputFrame = sizeof(float) * stream->past_NumInputChannels;
+ break;
+ default:
+ bytesPerInputFrame = sizeof(short) * stream->past_NumInputChannels;
+ break;
+ }
+ wfx.wFormatTag = WAVE_FORMAT_PCM;
+ wfx.nChannels = (WORD) stream->past_NumInputChannels;
+ wfx.nSamplesPerSec = (DWORD) stream->past_SampleRate;
+ wfx.nAvgBytesPerSec = (DWORD)(bytesPerInputFrame * stream->past_SampleRate);
+ wfx.nBlockAlign = (WORD)bytesPerInputFrame;
+ wfx.wBitsPerSample = (WORD)((bytesPerInputFrame/stream->past_NumInputChannels) * 8);
+ wfx.cbSize = 0;
+ inputMmId = PaDeviceIdToWinId( stream->past_InputDeviceID );
+#if PA_USE_TIMER_CALLBACK
+ mmresult = waveInOpen( &wmmeStreamData->hWaveIn, inputMmId, &wfx,
+ 0, 0, CALLBACK_NULL );
+#else
+ mmresult = waveInOpen( &wmmeStreamData->hWaveIn, inputMmId, &wfx,
+ (DWORD)wmmeStreamData->bufferEvent, (DWORD) stream, CALLBACK_EVENT );
+#endif
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ ERR_RPT(("PortAudio: PaHost_OpenInputStream() failed!\n"));
+ result = paHostError;
+ sPaHostError = mmresult;
+ goto error;
+ }
+ /* Allocate an array to hold the buffer pointers. */
+ wmmeStreamData->inputBuffers = (WAVEHDR *) PaHost_AllocateTrackedMemory( sizeof(WAVEHDR)*wmmeStreamData->numHostBuffers ); /* MEM */
+ if( wmmeStreamData->inputBuffers == NULL )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ /* Allocate each buffer. */
+ for( i=0; i<wmmeStreamData->numHostBuffers; i++ )
+ {
+ wmmeStreamData->inputBuffers[i].lpData = (char *)PaHost_AllocateTrackedMemory( wmmeStreamData->bytesPerHostInputBuffer ); /* MEM */
+ if( wmmeStreamData->inputBuffers[i].lpData == NULL )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ wmmeStreamData->inputBuffers[i].dwBufferLength = wmmeStreamData->bytesPerHostInputBuffer;
+ wmmeStreamData->inputBuffers[i].dwUser = i;
+ if( ( mmresult = waveInPrepareHeader( wmmeStreamData->hWaveIn, &wmmeStreamData->inputBuffers[i], sizeof(WAVEHDR) )) != MMSYSERR_NOERROR )
+ {
+ result = paHostError;
+ sPaHostError = mmresult;
+ goto error;
+ }
+ }
+ return result;
+
+error:
+ return result;
+}
+/*******************************************************************/
+PaError PaHost_OpenOutputStream( internalPortAudioStream *stream )
+{
+ PaError result = paNoError;
+ MMRESULT mmresult;
+ PaWMMEStreamData *wmmeStreamData;
+ int i;
+ int outputMmID;
+ int bytesPerOutputFrame;
+ WAVEFORMATEX wfx;
+ const PaDeviceInfo *deviceInfo;
+
+ wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
+ DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", stream->past_OutputDeviceID));
+
+ deviceInfo = Pa_GetDeviceInfo( stream->past_OutputDeviceID );
+ if( deviceInfo == NULL ) return paInternalError;
+
+ switch( deviceInfo->nativeSampleFormats )
+ {
+ case paInt32:
+ case paFloat32:
+ bytesPerOutputFrame = sizeof(float) * stream->past_NumOutputChannels;
+ break;
+ default:
+ bytesPerOutputFrame = sizeof(short) * stream->past_NumOutputChannels;
+ break;
+ }
+ wfx.wFormatTag = WAVE_FORMAT_PCM;
+ wfx.nChannels = (WORD) stream->past_NumOutputChannels;
+ wfx.nSamplesPerSec = (DWORD) stream->past_SampleRate;
+ wfx.nAvgBytesPerSec = (DWORD)(bytesPerOutputFrame * stream->past_SampleRate);
+ wfx.nBlockAlign = (WORD)bytesPerOutputFrame;
+ wfx.wBitsPerSample = (WORD)((bytesPerOutputFrame/stream->past_NumOutputChannels) * 8);
+ wfx.cbSize = 0;
+ outputMmID = PaDeviceIdToWinId( stream->past_OutputDeviceID );
+#if PA_USE_TIMER_CALLBACK
+ mmresult = waveOutOpen( &wmmeStreamData->hWaveOut, outputMmID, &wfx,
+ 0, 0, CALLBACK_NULL );
+#else
+
+ wmmeStreamData->abortEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
+ if( wmmeStreamData->abortEvent == NULL )
+ {
+ result = paHostError;
+ sPaHostError = GetLastError();
+ goto error;
+ }
+ wmmeStreamData->abortEventInited = 1;
+ mmresult = waveOutOpen( &wmmeStreamData->hWaveOut, outputMmID, &wfx,
+ (DWORD)wmmeStreamData->bufferEvent, (DWORD) stream, CALLBACK_EVENT );
+#endif
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ ERR_RPT(("PortAudio: PaHost_OpenOutputStream() failed!\n"));
+ result = paHostError;
+ sPaHostError = mmresult;
+ goto error;
+ }
+ /* Allocate an array to hold the buffer pointers. */
+ wmmeStreamData->outputBuffers = (WAVEHDR *) PaHost_AllocateTrackedMemory( sizeof(WAVEHDR)*wmmeStreamData->numHostBuffers ); /* MEM */
+ if( wmmeStreamData->outputBuffers == NULL )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ /* Allocate each buffer. */
+ for( i=0; i<wmmeStreamData->numHostBuffers; i++ )
+ {
+ wmmeStreamData->outputBuffers[i].lpData = (char *) PaHost_AllocateTrackedMemory( wmmeStreamData->bytesPerHostOutputBuffer ); /* MEM */
+ if( wmmeStreamData->outputBuffers[i].lpData == NULL )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ wmmeStreamData->outputBuffers[i].dwBufferLength = wmmeStreamData->bytesPerHostOutputBuffer;
+ wmmeStreamData->outputBuffers[i].dwUser = i;
+ if( (mmresult = waveOutPrepareHeader( wmmeStreamData->hWaveOut, &wmmeStreamData->outputBuffers[i], sizeof(WAVEHDR) )) != MMSYSERR_NOERROR )
+ {
+ result = paHostError;
+ sPaHostError = mmresult;
+ goto error;
+ }
+ }
+ return result;
+
+error:
+ return result;
+}
+/*******************************************************************/
+PaError PaHost_GetTotalBufferFrames( internalPortAudioStream *stream )
+{
+ PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
+ return wmmeStreamData->numHostBuffers * wmmeStreamData->framesPerHostBuffer;
+}
+/*******************************************************************
+ * Determine number of WAVE Buffers
+ * and how many User Buffers we can put into each WAVE buffer.
+ */
+static void PaHost_CalcNumHostBuffers( internalPortAudioStream *stream )
+{
+ PaWMMEStreamData *wmmeStreamData = (PaWMMEStreamData *) stream->past_DeviceData;
+ unsigned int minNumBuffers;
+ int minframesPerHostBuffer;
+ int maxframesPerHostBuffer;
+ int minTotalFrames;
+ int userBuffersPerHostBuffer;
+ int framesPerHostBuffer;
+ int numHostBuffers;
+
+ /* Calculate minimum and maximum sizes based on timing and sample rate. */
+ minframesPerHostBuffer = (int) (PA_MIN_MSEC_PER_HOST_BUFFER * stream->past_SampleRate * 0.001);
+ minframesPerHostBuffer = (minframesPerHostBuffer + 7) & ~7;
+ DBUG(("PaHost_CalcNumHostBuffers: minframesPerHostBuffer = %d\n", minframesPerHostBuffer ));
+ maxframesPerHostBuffer = (int) (PA_MAX_MSEC_PER_HOST_BUFFER * stream->past_SampleRate * 0.001);
+ maxframesPerHostBuffer = (maxframesPerHostBuffer + 7) & ~7;
+ DBUG(("PaHost_CalcNumHostBuffers: maxframesPerHostBuffer = %d\n", maxframesPerHostBuffer ));
+ /* Determine number of user buffers based on minimum latency. */
+ minNumBuffers = Pa_GetMinNumBuffers( stream->past_FramesPerUserBuffer, stream->past_SampleRate );
+ stream->past_NumUserBuffers = ( minNumBuffers > stream->past_NumUserBuffers ) ? minNumBuffers : stream->past_NumUserBuffers;
+ DBUG(("PaHost_CalcNumHostBuffers: min past_NumUserBuffers = %d\n", stream->past_NumUserBuffers ));
+ minTotalFrames = stream->past_NumUserBuffers * stream->past_FramesPerUserBuffer;
+ /* We cannot make the WAVE buffers too small because they may not get serviced quickly enough. */
+ if( (int) stream->past_FramesPerUserBuffer < minframesPerHostBuffer )
+ {
+ userBuffersPerHostBuffer =
+ (minframesPerHostBuffer + stream->past_FramesPerUserBuffer - 1) /
+ stream->past_FramesPerUserBuffer;
+ }
+ else
+ {
+ userBuffersPerHostBuffer = 1;
+ }
+ framesPerHostBuffer = stream->past_FramesPerUserBuffer * userBuffersPerHostBuffer;
+ /* Calculate number of WAVE buffers needed. Round up to cover minTotalFrames. */
+ numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer;
+ /* Make sure we have anough WAVE buffers. */
+ if( numHostBuffers < PA_MIN_NUM_HOST_BUFFERS)
+ {
+ numHostBuffers = PA_MIN_NUM_HOST_BUFFERS;
+ }
+ else if( (numHostBuffers > PA_MAX_NUM_HOST_BUFFERS) &&
+ ((int) stream->past_FramesPerUserBuffer < (maxframesPerHostBuffer/2) ) )
+ {
+ /* If we have too many WAVE buffers, try to put more user buffers in a wave buffer. */
+ while(numHostBuffers > PA_MAX_NUM_HOST_BUFFERS)
+ {
+ userBuffersPerHostBuffer += 1;
+ framesPerHostBuffer = stream->past_FramesPerUserBuffer * userBuffersPerHostBuffer;
+ numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer;
+ /* If we have gone too far, back up one. */
+ if( (framesPerHostBuffer > maxframesPerHostBuffer) ||
+ (numHostBuffers < PA_MAX_NUM_HOST_BUFFERS) )
+ {
+ userBuffersPerHostBuffer -= 1;
+ framesPerHostBuffer = stream->past_FramesPerUserBuffer * userBuffersPerHostBuffer;
+ numHostBuffers = (minTotalFrames + framesPerHostBuffer - 1) / framesPerHostBuffer;
+ break;
+ }
+ }
+ }
+
+ wmmeStreamData->userBuffersPerHostBuffer = userBuffersPerHostBuffer;
+ wmmeStreamData->framesPerHostBuffer = framesPerHostBuffer;
+ wmmeStreamData->numHostBuffers = numHostBuffers;
+ DBUG(("PaHost_CalcNumHostBuffers: userBuffersPerHostBuffer = %d\n", wmmeStreamData->userBuffersPerHostBuffer ));
+ DBUG(("PaHost_CalcNumHostBuffers: numHostBuffers = %d\n", wmmeStreamData->numHostBuffers ));
+ DBUG(("PaHost_CalcNumHostBuffers: framesPerHostBuffer = %d\n", wmmeStreamData->framesPerHostBuffer ));
+ DBUG(("PaHost_CalcNumHostBuffers: past_NumUserBuffers = %d\n", stream->past_NumUserBuffers ));
+}
+/*******************************************************************/
+PaError PaHost_OpenStream( internalPortAudioStream *stream )
+{
+ PaError result = paNoError;
+ PaWMMEStreamData *wmmeStreamData;
+
+ result = PaHost_AllocateWMMEStreamData( stream );
+ if( result != paNoError ) return result;
+
+ wmmeStreamData = PaHost_GetWMMEStreamData( stream );
+
+ /* Figure out how user buffers fit into WAVE buffers. */
+ PaHost_CalcNumHostBuffers( stream );
+ {
+ int msecLatency = (int) ((PaHost_GetTotalBufferFrames(stream) * 1000) / stream->past_SampleRate);
+ DBUG(("PortAudio on WMME - Latency = %d frames, %d msec\n", PaHost_GetTotalBufferFrames(stream), msecLatency ));
+ }
+ InitializeCriticalSection( &wmmeStreamData->streamLock );
+ wmmeStreamData->streamLockInited = 1;
+
+#if (PA_USE_TIMER_CALLBACK == 0)
+ wmmeStreamData->bufferEventInited = 0;
+ wmmeStreamData->bufferEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
+ if( wmmeStreamData->bufferEvent == NULL )
+ {
+ result = paHostError;
+ sPaHostError = GetLastError();
+ goto error;
+ }
+ wmmeStreamData->bufferEventInited = 1;
+#endif /* (PA_USE_TIMER_CALLBACK == 0) */
+ /* ------------------ OUTPUT */
+ wmmeStreamData->bytesPerUserOutputBuffer = stream->past_FramesPerUserBuffer * stream->past_NumOutputChannels * sizeof(short);
+ wmmeStreamData->bytesPerHostOutputBuffer = wmmeStreamData->userBuffersPerHostBuffer * wmmeStreamData->bytesPerUserOutputBuffer;
+ if( (stream->past_OutputDeviceID != paNoDevice) && (stream->past_NumOutputChannels > 0) )
+ {
+ result = PaHost_OpenOutputStream( stream );
+ if( result < 0 ) goto error;
+ }
+ /* ------------------ INPUT */
+ wmmeStreamData->bytesPerUserInputBuffer = stream->past_FramesPerUserBuffer * stream->past_NumInputChannels * sizeof(short);
+ wmmeStreamData->bytesPerHostInputBuffer = wmmeStreamData->userBuffersPerHostBuffer * wmmeStreamData->bytesPerUserInputBuffer;
+ if( (stream->past_InputDeviceID != paNoDevice) && (stream->past_NumInputChannels > 0) )
+ {
+ result = PaHost_OpenInputStream( stream );
+ if( result < 0 ) goto error;
+ }
+
+ Pa_InitializeCpuUsageScalar( stream );
+
+ return result;
+
+error:
+ PaHost_CloseStream( stream );
+ return result;
+}
+/*************************************************************************/
+PaError PaHost_StartOutput( internalPortAudioStream *stream )
+{
+ PaError result = paNoError;
+ MMRESULT mmresult;
+ int i;
+ PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( stream );
+
+ if( wmmeStreamData == NULL ) return paInternalError;
+
+ if( stream->past_OutputDeviceID != paNoDevice )
+ {
+ if( (mmresult = waveOutPause( wmmeStreamData->hWaveOut )) != MMSYSERR_NOERROR )
+ {
+ result = paHostError;
+ sPaHostError = mmresult;
+ goto error;
+ }
+ for( i=0; i<wmmeStreamData->numHostBuffers; i++ )
+ {
+ ZeroMemory( wmmeStreamData->outputBuffers[i].lpData, wmmeStreamData->outputBuffers[i].dwBufferLength );
+ mmresult = waveOutWrite( wmmeStreamData->hWaveOut, &wmmeStreamData->outputBuffers[i], sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paHostError;
+ sPaHostError = mmresult;
+ goto error;
+ }
+ stream->past_FrameCount += wmmeStreamData->framesPerHostBuffer;
+ }
+ wmmeStreamData->currentOutputBuffer = 0;
+ if( (mmresult = waveOutRestart( wmmeStreamData->hWaveOut )) != MMSYSERR_NOERROR )
+ {
+ result = paHostError;
+ sPaHostError = mmresult;
+ goto error;
+ }
+ }
+
+error:
+ DBUG(("PaHost_StartOutput: wave returned mmresult = 0x%X.\n", mmresult));
+ return result;
+}
+/*************************************************************************/
+PaError PaHost_StartInput( internalPortAudioStream *internalStream )
+{
+ PaError result = paNoError;
+ MMRESULT mmresult;
+ int i;
+ PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( internalStream );
+
+ if( wmmeStreamData == NULL ) return paInternalError;
+
+ if( internalStream->past_InputDeviceID != paNoDevice )
+ {
+ for( i=0; i<wmmeStreamData->numHostBuffers; i++ )
+ {
+ mmresult = waveInAddBuffer( wmmeStreamData->hWaveIn, &wmmeStreamData->inputBuffers[i], sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paHostError;
+ sPaHostError = mmresult;
+ goto error;
+ }
+ }
+ wmmeStreamData->currentInputBuffer = 0;
+ mmresult = waveInStart( wmmeStreamData->hWaveIn );
+ DBUG(("Pa_StartStream: waveInStart returned = 0x%X.\n", mmresult));
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paHostError;
+ sPaHostError = mmresult;
+ goto error;
+ }
+ }
+
+error:
+ return result;
+}
+/*************************************************************************/
+PaError PaHost_StartEngine( internalPortAudioStream *stream )
+{
+ PaError result = paNoError;
+ PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( stream );
+#if PA_USE_TIMER_CALLBACK
+ int resolution;
+ int bufsPerTimerCallback;
+ int msecPerBuffer;
+#endif /* PA_USE_TIMER_CALLBACK */
+
+ if( wmmeStreamData == NULL ) return paInternalError;
+
+ stream->past_StopSoon = 0;
+ stream->past_StopNow = 0;
+ stream->past_IsActive = 1;
+ wmmeStreamData->framesPlayed = 0.0;
+ wmmeStreamData->lastPosition = 0;
+#if PA_TRACE_START_STOP
+ AddTraceMessage( "PaHost_StartEngine: TimeSlice() returned ", result );
+#endif
+#if PA_USE_TIMER_CALLBACK
+ /* Create timer that will wake us up so we can fill the DSound buffer. */
+ bufsPerTimerCallback = wmmeStreamData->numHostBuffers/4;
+ if( bufsPerTimerCallback < 1 ) bufsPerTimerCallback = 1;
+ if( bufsPerTimerCallback < 1 ) bufsPerTimerCallback = 1;
+ msecPerBuffer = (1000 * bufsPerTimerCallback *
+ wmmeStreamData->userBuffersPerHostBuffer *
+ internalStream->past_FramesPerUserBuffer ) / (int) internalStream->past_SampleRate;
+ if( msecPerBuffer < 10 ) msecPerBuffer = 10;
+ else if( msecPerBuffer > 100 ) msecPerBuffer = 100;
+ resolution = msecPerBuffer/4;
+ wmmeStreamData->timerID = timeSetEvent( msecPerBuffer, resolution,
+ (LPTIMECALLBACK) Pa_TimerCallback,
+ (DWORD) stream, TIME_PERIODIC );
+ if( wmmeStreamData->timerID == 0 )
+ {
+ result = paHostError;
+ sPaHostError = GetLastError();;
+ goto error;
+ }
+#else /* PA_USE_TIMER_CALLBACK */
+ ResetEvent( wmmeStreamData->abortEvent );
+ /* Create thread that waits for audio buffers to be ready for processing. */
+ wmmeStreamData->engineThread = CreateThread( 0, 0, WinMMPa_OutputThreadProc, stream, 0, &wmmeStreamData->engineThreadID );
+ if( wmmeStreamData->engineThread == NULL )
+ {
+ result = paHostError;
+ sPaHostError = GetLastError();;
+ goto error;
+ }
+#if PA_TRACE_START_STOP
+ AddTraceMessage( "PaHost_StartEngine: thread ", (int) wmmeStreamData->engineThread );
+#endif
+ /* I used to pass the thread which was failing. I now pass GetCurrentProcess().
+ * This fix could improve latency for some applications. It could also result in CPU
+ * starvation if the callback did too much processing.
+ * I also added result checks, so we might see more failures at initialization.
+ * Thanks to Alberto di Bene for spotting this.
+ */
+ if( !SetPriorityClass( GetCurrentProcess(), HIGH_PRIORITY_CLASS ) ) /* PLB20010816 */
+ {
+ result = paHostError;
+ sPaHostError = GetLastError();;
+ goto error;
+ }
+ if( !SetThreadPriority( wmmeStreamData->engineThread, THREAD_PRIORITY_HIGHEST ) )
+ {
+ result = paHostError;
+ sPaHostError = GetLastError();;
+ goto error;
+ }
+#endif
+
+error:
+ return result;
+}
+/*************************************************************************/
+PaError PaHost_StopEngine( internalPortAudioStream *internalStream, int abort )
+{
+ int timeOut;
+ PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( internalStream );
+
+ if( wmmeStreamData == NULL ) return paNoError;
+
+ /* Tell background thread to stop generating more data and to let current data play out. */
+ internalStream->past_StopSoon = 1;
+ /* If aborting, tell background thread to stop NOW! */
+ if( abort ) internalStream->past_StopNow = 1;
+
+ /* Calculate timeOut longer than longest time it could take to play all buffers. */
+ timeOut = (DWORD) (1500.0 * PaHost_GetTotalBufferFrames( internalStream ) / internalStream->past_SampleRate);
+ if( timeOut < MIN_TIMEOUT_MSEC ) timeOut = MIN_TIMEOUT_MSEC;
+
+#if PA_USE_TIMER_CALLBACK
+ if( (internalStream->past_OutputDeviceID != paNoDevice) &&
+ internalStream->past_IsActive &&
+ (wmmeStreamData->timerID != 0) )
+ {
+ /* Wait for IsActive to drop. */
+ while( (internalStream->past_IsActive) && (timeOut > 0) )
+ {
+ Sleep(10);
+ timeOut -= 10;
+ }
+ timeKillEvent( wmmeStreamData->timerID ); /* Stop callback timer. */
+ wmmeStreamData->timerID = 0;
+ }
+#else /* PA_USE_TIMER_CALLBACK */
+#if PA_TRACE_START_STOP
+ AddTraceMessage( "PaHost_StopEngine: thread ", (int) wmmeStreamData->engineThread );
+#endif
+ if( (internalStream->past_OutputDeviceID != paNoDevice) &&
+ (internalStream->past_IsActive) &&
+ (wmmeStreamData->engineThread != NULL) )
+ {
+ DWORD got;
+ /* Tell background thread to stop generating more data and to let current data play out. */
+ DBUG(("PaHost_StopEngine: waiting for background thread.\n"));
+ got = WaitForSingleObject( wmmeStreamData->engineThread, timeOut );
+ if( got == WAIT_TIMEOUT )
+ {
+ ERR_RPT(("PaHost_StopEngine: timed out while waiting for background thread to finish.\n"));
+ return paTimedOut;
+ }
+ CloseHandle( wmmeStreamData->engineThread );
+ wmmeStreamData->engineThread = NULL;
+ }
+#endif /* PA_USE_TIMER_CALLBACK */
+
+ internalStream->past_IsActive = 0;
+ return paNoError;
+}
+/*************************************************************************/
+PaError PaHost_StopInput( internalPortAudioStream *stream, int abort )
+{
+ MMRESULT mmresult;
+ PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( stream );
+
+ if( wmmeStreamData == NULL ) return paNoError; /* FIXME: why return paNoError? */
+ (void) abort; /* unused parameter */
+
+ if( wmmeStreamData->hWaveIn != NULL )
+ {
+ mmresult = waveInReset( wmmeStreamData->hWaveIn );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ sPaHostError = mmresult;
+ return paHostError;
+ }
+ }
+ return paNoError;
+}
+/*************************************************************************/
+PaError PaHost_StopOutput( internalPortAudioStream *internalStream, int abort )
+{
+ MMRESULT mmresult;
+ PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( internalStream );
+
+ if( wmmeStreamData == NULL ) return paNoError; /* FIXME: why return paNoError? */
+ (void) abort; /* unused parameter */
+
+#if PA_TRACE_START_STOP
+ AddTraceMessage( "PaHost_StopOutput: hWaveOut ", (int) wmmeStreamData->hWaveOut );
+#endif
+ if( wmmeStreamData->hWaveOut != NULL )
+ {
+ mmresult = waveOutReset( wmmeStreamData->hWaveOut );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ sPaHostError = mmresult;
+ return paHostError;
+ }
+ }
+ return paNoError;
+}
+/*******************************************************************/
+PaError PaHost_CloseStream( internalPortAudioStream *stream )
+{
+ int i;
+ PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( stream );
+
+ if( stream == NULL ) return paBadStreamPtr;
+ if( wmmeStreamData == NULL ) return paNoError; /* FIXME: why return no error? */
+
+#if PA_TRACE_START_STOP
+ AddTraceMessage( "PaHost_CloseStream: hWaveOut ", (int) wmmeStreamData->hWaveOut );
+#endif
+ /* Free data and device for output. */
+ if( wmmeStreamData->hWaveOut )
+ {
+ if( wmmeStreamData->outputBuffers )
+ {
+ for( i=0; i<wmmeStreamData->numHostBuffers; i++ )
+ {
+ waveOutUnprepareHeader( wmmeStreamData->hWaveOut, &wmmeStreamData->outputBuffers[i], sizeof(WAVEHDR) );
+ PaHost_FreeTrackedMemory( wmmeStreamData->outputBuffers[i].lpData ); /* MEM */
+ }
+ PaHost_FreeTrackedMemory( wmmeStreamData->outputBuffers ); /* MEM */
+ }
+ waveOutClose( wmmeStreamData->hWaveOut );
+ }
+ /* Free data and device for input. */
+ if( wmmeStreamData->hWaveIn )
+ {
+ if( wmmeStreamData->inputBuffers )
+ {
+ for( i=0; i<wmmeStreamData->numHostBuffers; i++ )
+ {
+ waveInUnprepareHeader( wmmeStreamData->hWaveIn, &wmmeStreamData->inputBuffers[i], sizeof(WAVEHDR) );
+ PaHost_FreeTrackedMemory( wmmeStreamData->inputBuffers[i].lpData ); /* MEM */
+ }
+ PaHost_FreeTrackedMemory( wmmeStreamData->inputBuffers ); /* MEM */
+ }
+ waveInClose( wmmeStreamData->hWaveIn );
+ }
+#if (PA_USE_TIMER_CALLBACK == 0)
+ if( wmmeStreamData->abortEventInited ) CloseHandle( wmmeStreamData->abortEvent );
+ if( wmmeStreamData->bufferEventInited ) CloseHandle( wmmeStreamData->bufferEvent );
+#endif
+ if( wmmeStreamData->streamLockInited )
+ DeleteCriticalSection( &wmmeStreamData->streamLock );
+
+ PaHost_FreeWMMEStreamData( stream );
+
+ return paNoError;
+}
+/*************************************************************************
+ * Determine minimum number of buffers required for this host based
+ * on minimum latency. Latency can be optionally set by user by setting
+ * an environment variable. For example, to set latency to 200 msec, put:
+ *
+ * set PA_MIN_LATENCY_MSEC=200
+ *
+ * in the AUTOEXEC.BAT file and reboot.
+ * If the environment variable is not set, then the latency will be determined
+ * based on the OS. Windows NT has higher latency than Win95.
+ */
+#define PA_LATENCY_ENV_NAME ("PA_MIN_LATENCY_MSEC")
+int Pa_GetMinNumBuffers( int framesPerBuffer, double sampleRate )
+{
+ char envbuf[PA_ENV_BUF_SIZE];
+ DWORD hresult;
+ int minLatencyMsec = 0;
+ double msecPerBuffer = (1000.0 * framesPerBuffer) / sampleRate;
+ int minBuffers;
+
+ /* Let user determine minimal latency by setting environment variable. */
+ hresult = GetEnvironmentVariable( PA_LATENCY_ENV_NAME, envbuf, PA_ENV_BUF_SIZE );
+ if( (hresult > 0) && (hresult < PA_ENV_BUF_SIZE) )
+ {
+ minLatencyMsec = atoi( envbuf ); /* REVIEW: will we crash if the environment variable contains some nasty value? */
+ }
+ else
+ {
+ /* Set minimal latency based on whether NT or other OS.
+ * NT has higher latency.
+ */
+ OSVERSIONINFO osvi;
+ osvi.dwOSVersionInfoSize = sizeof( osvi );
+ GetVersionEx( &osvi );
+ DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId ));
+ DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion ));
+ DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion ));
+ /* Check for NT */
+ if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) )
+ {
+ minLatencyMsec = PA_WIN_NT_LATENCY;
+ }
+ else if(osvi.dwMajorVersion >= 5)
+ {
+ minLatencyMsec = PA_WIN_WDM_LATENCY;
+ }
+ else
+ {
+ minLatencyMsec = PA_WIN_9X_LATENCY;
+ }
+#if PA_USE_HIGH_LATENCY
+ PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec ));
+#endif
+
+ }
+ DBUG(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec ));
+ minBuffers = (int) (1.0 + ((double)minLatencyMsec / msecPerBuffer));
+ if( minBuffers < 2 ) minBuffers = 2;
+ return minBuffers;
+}
+/*************************************************************************
+ * Cleanup device info.
+ */
+PaError PaHost_Term( void )
+{
+ int i;
+
+ if( sNumDevices > 0 )
+ {
+ if( sDevicePtrs != NULL )
+ {
+ for( i=0; i<sNumDevices; i++ )
+ {
+ if( sDevicePtrs[i] != NULL )
+ {
+ PaHost_FreeTrackedMemory( (char*)sDevicePtrs[i]->name ); /* MEM */
+ PaHost_FreeTrackedMemory( (void*)sDevicePtrs[i]->sampleRates ); /* MEM */
+ PaHost_FreeTrackedMemory( sDevicePtrs[i] ); /* MEM */
+ }
+ }
+ PaHost_FreeTrackedMemory( sDevicePtrs ); /* MEM */
+ sDevicePtrs = NULL;
+ }
+ sNumDevices = 0;
+ }
+
+#if PA_TRACK_MEMORY
+ PRINT(("PaHost_Term: sNumAllocations = %d\n", sNumAllocations ));
+#endif
+
+ return paNoError;
+}
+/*************************************************************************/
+void Pa_Sleep( long msec )
+{
+ Sleep( msec );
+}
+/*************************************************************************
+FIXME: the following memory allocation routines should not be declared here
+ * Allocate memory that can be accessed in real-time.
+ * This may need to be held in physical memory so that it is not
+ * paged to virtual memory.
+ * This call MUST be balanced with a call to PaHost_FreeFastMemory().
+ * Memory will be set to zero.
+ */
+void *PaHost_AllocateFastMemory( long numBytes )
+{
+ return PaHost_AllocateTrackedMemory( numBytes ); /* FIXME - do we need physical memory? Use VirtualLock() */ /* MEM */
+}
+/*************************************************************************
+ * Free memory that could be accessed in real-time.
+ * This call MUST be balanced with a call to PaHost_AllocateFastMemory().
+ */
+void PaHost_FreeFastMemory( void *addr, long numBytes )
+{
+ (void) numBytes; /* unused parameter */
+
+ PaHost_FreeTrackedMemory( addr ); /* MEM */
+}
+
+/*************************************************************************
+ * Track memory allocations to avoid leaks.
+ */
+static void *PaHost_AllocateTrackedMemory( long numBytes )
+{
+ void *result = GlobalAlloc( GPTR, numBytes ); /* MEM */
+
+#if PA_TRACK_MEMORY
+ if( result != NULL ) sNumAllocations += 1;
+#endif
+ return result;
+}
+
+static void PaHost_FreeTrackedMemory( void *addr )
+{
+ if( addr != NULL )
+ {
+ GlobalFree( addr ); /* MEM */
+#if PA_TRACK_MEMORY
+ sNumAllocations -= 1;
+#endif
+ }
+}
+
+/***********************************************************************/
+PaError PaHost_StreamActive( internalPortAudioStream *internalStream )
+{
+ if( internalStream == NULL ) return paBadStreamPtr;
+
+ return (PaError) internalStream->past_IsActive;
+}
+/*************************************************************************
+ * This must be called periodically because mmtime.u.sample
+ * is a DWORD and can wrap and lose sync after a few hours.
+ */
+static PaError PaHost_UpdateStreamTime( PaWMMEStreamData *wmmeStreamData )
+{
+ MMRESULT mmresult;
+ MMTIME mmtime;
+ mmtime.wType = TIME_SAMPLES;
+
+ if( wmmeStreamData->hWaveOut != NULL )
+ {
+ mmresult = waveOutGetPosition( wmmeStreamData->hWaveOut, &mmtime, sizeof(mmtime) );
+ }
+ else
+ {
+ mmresult = waveInGetPosition( wmmeStreamData->hWaveIn, &mmtime, sizeof(mmtime) );
+ }
+
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ sPaHostError = mmresult;
+ return paHostError;
+ }
+
+ /* This data has two variables and is shared by foreground and background.
+ * So we need to make it thread safe. */
+ EnterCriticalSection( &wmmeStreamData->streamLock );
+ wmmeStreamData->framesPlayed += ((long)mmtime.u.sample) - wmmeStreamData->lastPosition;
+ wmmeStreamData->lastPosition = (long)mmtime.u.sample;
+ LeaveCriticalSection( &wmmeStreamData->streamLock );
+
+ return paNoError;
+}
+/*************************************************************************/
+PaTimestamp Pa_StreamTime( PortAudioStream *stream )
+{
+ internalPortAudioStream *internalStream = PaHost_GetStreamRepresentation( stream );
+ PaWMMEStreamData *wmmeStreamData = PaHost_GetWMMEStreamData( internalStream );
+
+ if( internalStream == NULL ) return paBadStreamPtr;
+ if( wmmeStreamData == NULL ) return paInternalError;
+
+ PaHost_UpdateStreamTime( wmmeStreamData );
+ return wmmeStreamData->framesPlayed;
+}
+/*************************************************************************/
+
+
+