aboutsummaryrefslogtreecommitdiff
path: root/pd/portaudio/pa_win_wmme
diff options
context:
space:
mode:
authorGuenter Geiger <ggeiger@users.sourceforge.net>2003-05-09 16:04:00 +0000
committerGuenter Geiger <ggeiger@users.sourceforge.net>2003-05-09 16:04:00 +0000
commit9c0e19a3be2288db79e2502e5fa450c3e20a668d (patch)
treeca97ce615e037a533304fc4660dcf372ca3b9cd6 /pd/portaudio/pa_win_wmme
parentef50dd62804d54af7da18d8bd8413c0dccd729b8 (diff)
This commit was generated by cvs2svn to compensate for changes in r610,
which included commits to RCS files with non-trunk default branches. svn path=/trunk/; revision=611
Diffstat (limited to 'pd/portaudio/pa_win_wmme')
-rw-r--r--pd/portaudio/pa_win_wmme/Makefile.cygwin34
-rw-r--r--pd/portaudio/pa_win_wmme/pa_win_wmme.c2672
-rw-r--r--pd/portaudio/pa_win_wmme/pa_win_wmme.h105
3 files changed, 2811 insertions, 0 deletions
diff --git a/pd/portaudio/pa_win_wmme/Makefile.cygwin b/pd/portaudio/pa_win_wmme/Makefile.cygwin
new file mode 100644
index 00000000..5cb4acef
--- /dev/null
+++ b/pd/portaudio/pa_win_wmme/Makefile.cygwin
@@ -0,0 +1,34 @@
+
+# Makefile for PortAudio on cygwin
+# Contributed by Bill Eldridge on 6/13/2001
+
+ARCH= pa_win_wmme
+
+TESTS:= $(wildcard pa_tests/pa*.c pa_tests/debug*.c)
+
+.c.o:
+ -gcc -c -I./pa_common $< -o $*.o
+ -gcc $*.o -o $*.exe -L/usr/local/lib -L$(ARCH) -lportaudio.dll -lwinmm
+
+all: sharedlib tests
+
+sharedlib: ./pa_common/pa_lib.c
+ gcc -c -I./pa_common pa_common/pa_lib.c -o pa_common/pa_lib.o
+ gcc -c -I./pa_common pa_win_wmme/pa_win_wmme.c -o pa_win_wmme/pa_win_wmme.o
+ dlltool --export-all --output-def pa_win_wmme/pa_lib.def pa_common/pa_lib.o pa_win_wmme/pa_win_wmme.o
+ gcc -shared -Wl,--enable-auto-image-base -o pa_win_wmme/portaudio.dll -Wl,--out-implib=pa_win_wmme/libportaudio.dll.a pa_win_wmme/pa_lib.def pa_common/pa_lib.o pa_win_wmme/pa_win_wmme.o -L/usr/lib/w32api -lwinmm
+ cp pa_win_wmme/portaudio.dll /usr/local/bin
+
+tests: $(TESTS:.c=.o)
+
+sine:
+ gcc -c -I./pa_common pa_tests/patest_sine.c -o pa_tests/patest_sine.o
+ gcc pa_tests/patest_sine.o -o pa_tests/patest_sine.exe -L/usr/local/lib -lportaudio.dll -lwinmm
+
+clean:
+ -rm ./pa_tests/*.exe
+
+nothing:
+ gcc pa_tests/patest_sine.o -L/usr/lib/w32api -L./pa_win_wmme -lportaudio.dll -lwinmm
+
+
diff --git a/pd/portaudio/pa_win_wmme/pa_win_wmme.c b/pd/portaudio/pa_win_wmme/pa_win_wmme.c
new file mode 100644
index 00000000..2b1cc57d
--- /dev/null
+++ b/pd/portaudio/pa_win_wmme/pa_win_wmme.c
@@ -0,0 +1,2672 @@
+/*
+ * $Id: pa_win_wmme.c,v 1.6.2.44 2003/03/18 14:27:58 rossbencina 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.
+ *
+ */
+
+/* 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 conditional inclusion 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
+ RDB20020531 - converted to V19 framework
+ ** NOTE maintanance history is now stored in CVS **
+*/
+
+/** @file
+
+ @todo Handle case where user supplied full duplex buffer sizes are not compatible
+ (must be common multiples)
+
+ @todo Fix buffer catch up code, can sometimes get stuck
+
+ @todo Implement "close sample rate matching" if needed - is this really needed
+ in mme?
+
+ @todo Investigate supporting host buffer formats > 16 bits
+
+ @todo Implement buffer size and number of buffers code,
+ this code should generate defaults the way the old code did
+
+ @todo implement underflow/overflow streamCallback statusFlags, paNeverDropInput.
+
+ @todo Fix fixmes
+
+ @todo implement initialisation of PaDeviceInfo default*Latency fields (currently set to 0.)
+
+ @todo implement ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable
+
+ @todo implement IsFormatSupported
+
+ @todo implement PaDeviceInfo.defaultSampleRate;
+
+ @todo define UNICODE and _UNICODE in the project settings and see what breaks
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <windows.h>
+#include <mmsystem.h>
+#include <process.h>
+#include <assert.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_trace.h"
+#include "pa_util.h"
+#include "pa_allocation.h"
+#include "pa_hostapi.h"
+#include "pa_stream.h"
+#include "pa_cpuload.h"
+#include "pa_process.h"
+
+#include "pa_win_wmme.h"
+
+/************************************************* Constants ********/
+
+/* Switches for debugging. */
+#define PA_SIMULATE_UNDERFLOW_ (0) /* Set to one to force an underflow of the output buffer. */
+
+#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 PA_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_)
+
+
+static const char constInputMapperSuffix_[] = " - Input";
+static const char constOutputMapperSuffix_[] = " - Output";
+
+
+typedef struct PaWinMmeStream PaWinMmeStream; /* forward reference */
+
+/* prototypes for functions declared in this file */
+
+PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex );
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi );
+static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
+ PaStream** stream,
+ const PaStreamParameters *inputParameters,
+ const PaStreamParameters *outputParameters,
+ double sampleRate,
+ unsigned long framesPerBuffer,
+ PaStreamFlags streamFlags,
+ PaStreamCallback *streamCallback,
+ void *userData );
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+ const PaStreamParameters *inputParameters,
+ const PaStreamParameters *outputParameters,
+ double sampleRate );
+static PaError CloseStream( PaStream* stream );
+static PaError StartStream( PaStream *stream );
+static PaError StopStream( PaStream *stream );
+static PaError AbortStream( PaStream *stream );
+static PaError IsStreamStopped( PaStream *s );
+static PaError IsStreamActive( PaStream *stream );
+static PaTime GetStreamTime( PaStream *stream );
+static double GetStreamCpuLoad( PaStream* stream );
+static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames );
+static PaError WriteStream( PaStream* stream, void *buffer, unsigned long frames );
+static signed long GetStreamReadAvailable( PaStream* stream );
+static signed long GetStreamWriteAvailable( PaStream* stream );
+
+static PaError UpdateStreamTime( PaWinMmeStream *stream );
+
+/* macros for setting last host error information */
+
+#ifdef UNICODE
+
+#define PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ) \
+ { \
+ wchar_t mmeErrorTextWide[ MAXERRORLENGTH ]; \
+ char mmeErrorText[ MAXERRORLENGTH ]; \
+ waveInGetErrorText( mmresult, mmeErrorTextWide, MAXERRORLENGTH ); \
+ WideCharToMultiByte( CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,\
+ mmeErrorTextWide, -1, mmeErrorText, MAXERRORLENGTH, NULL, NULL ); \
+ PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \
+ }
+
+#define PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ) \
+ { \
+ wchar_t mmeErrorTextWide[ MAXERRORLENGTH ]; \
+ char mmeErrorText[ MAXERRORLENGTH ]; \
+ waveOutGetErrorText( mmresult, mmeErrorTextWide, MAXERRORLENGTH ); \
+ WideCharToMultiByte( CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,\
+ mmeErrorTextWide, -1, mmeErrorText, MAXERRORLENGTH, NULL, NULL ); \
+ PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \
+ }
+
+#else /* !UNICODE */
+
+#define PA_MME_SET_LAST_WAVEIN_ERROR( mmresult ) \
+ { \
+ char mmeErrorText[ MAXERRORLENGTH ]; \
+ waveInGetErrorText( mmresult, mmeErrorText, MAXERRORLENGTH ); \
+ PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \
+ }
+
+#define PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult ) \
+ { \
+ char mmeErrorText[ MAXERRORLENGTH ]; \
+ waveOutGetErrorText( mmresult, mmeErrorText, MAXERRORLENGTH ); \
+ PaUtil_SetLastHostErrorInfo( paMME, mmresult, mmeErrorText ); \
+ }
+
+#endif /* UNICODE */
+
+
+static void PaMme_SetLastSystemError( DWORD errorCode )
+{
+ LPVOID lpMsgBuf;
+ FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,
+ errorCode,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &lpMsgBuf,
+ 0,
+ NULL
+ );
+ PaUtil_SetLastHostErrorInfo( paMME, errorCode, lpMsgBuf );
+ LocalFree( lpMsgBuf );
+}
+
+#define PA_MME_SET_LAST_SYSTEM_ERROR( errorCode ) \
+ PaMme_SetLastSystemError( errorCode )
+
+
+/* PaWinMmeHostApiRepresentation - host api datastructure specific to this implementation */
+
+typedef struct
+{
+ PaUtilHostApiRepresentation inheritedHostApiRep;
+ PaUtilStreamInterface callbackStreamInterface;
+ PaUtilStreamInterface blockingStreamInterface;
+
+ PaUtilAllocationGroup *allocations;
+
+ int numInputDevices, numOutputDevices;
+
+ /** winMmeDeviceIds is an array of WinMme device ids.
+ fields in the range [0, numInputDevices) are input device ids,
+ and [numInputDevices, numInputDevices + numOutputDevices) are output
+ device ids.
+ */
+ int *winMmeDeviceIds;
+}
+PaWinMmeHostApiRepresentation;
+
+
+/*************************************************************************
+ * 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 PaDeviceIndex GetEnvDefaultDeviceID( char *envName )
+{
+ PaDeviceIndex recommendedIndex = paNoDevice;
+ DWORD hresult;
+ char envbuf[PA_ENV_BUF_SIZE_];
+
+#ifndef WIN32_PLATFORM_PSPC /* no GetEnvironmentVariable on PocketPC */
+
+ /* 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_) )
+ {
+ recommendedIndex = atoi( envbuf );
+ }
+#endif
+
+ return recommendedIndex;
+}
+
+static void InitializeDefaultDeviceIdsFromEnv( PaWinMmeHostApiRepresentation *hostApi )
+{
+ PaDeviceIndex device;
+
+ /* input */
+ device = GetEnvDefaultDeviceID( PA_REC_IN_DEV_ENV_NAME_ );
+ if( device != paNoDevice &&
+ ( device >= 0 && device < hostApi->inheritedHostApiRep.info.deviceCount ) &&
+ hostApi->inheritedHostApiRep.deviceInfos[ device ]->maxInputChannels > 0 )
+ {
+ hostApi->inheritedHostApiRep.info.defaultInputDevice = device;
+ }
+
+ /* output */
+ device = GetEnvDefaultDeviceID( PA_REC_OUT_DEV_ENV_NAME_ );
+ if( device != paNoDevice &&
+ ( device >= 0 && device < hostApi->inheritedHostApiRep.info.deviceCount ) &&
+ hostApi->inheritedHostApiRep.deviceInfos[ device ]->maxOutputChannels > 0 )
+ {
+ hostApi->inheritedHostApiRep.info.defaultOutputDevice = device;
+ }
+}
+
+
+/** Convert external PA ID to a windows multimedia device ID
+*/
+static int LocalDeviceIndexToWinMmeDeviceId( PaWinMmeHostApiRepresentation *hostApi, PaDeviceIndex device )
+{
+ assert( device >= 0 && device < hostApi->numInputDevices + hostApi->numOutputDevices );
+
+ return hostApi->winMmeDeviceIds[ device ];
+}
+
+
+static PaError InitializeInputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi,
+ PaDeviceInfo *deviceInfo, int winMmeInputDeviceId, int *success )
+{
+ PaError result = paNoError;
+ char *deviceName; /* non-const ptr */
+ MMRESULT mmresult;
+ WAVEINCAPS wic;
+
+ *success = 0;
+
+ mmresult = waveInGetDevCaps( winMmeInputDeviceId, &wic, sizeof( WAVEINCAPS ) );
+ if( mmresult == MMSYSERR_NOMEM )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ else if( mmresult != MMSYSERR_NOERROR )
+ {
+ /* instead of returning paUnanticipatedHostError we return
+ paNoError, but leave success set as 0. This allows
+ Pa_Initialize to just ignore this device, without failing
+ the entire initialisation process.
+ */
+ return paNoError;
+ }
+
+ if( winMmeInputDeviceId == WAVE_MAPPER )
+ {
+ /* Append I/O suffix to WAVE_MAPPER device. */
+ deviceName = (char *)PaUtil_GroupAllocateMemory(
+ winMmeHostApi->allocations, strlen( wic.szPname ) + 1 + sizeof(constInputMapperSuffix_) );
+ if( !deviceName )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ strcpy( deviceName, wic.szPname );
+ strcat( deviceName, constInputMapperSuffix_ );
+ }
+ else
+ {
+ deviceName = (char*)PaUtil_GroupAllocateMemory(
+ winMmeHostApi->allocations, strlen( wic.szPname ) + 1 );
+ if( !deviceName )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ strcpy( deviceName, wic.szPname );
+ }
+ deviceInfo->name = deviceName;
+
+ deviceInfo->maxInputChannels = wic.wChannels;
+ /* 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) )
+ {
+ PA_DEBUG(("Pa_GetDeviceInfo: Num input channels reported as %d! Changed to 2.\n", deviceInfo->maxInputChannels ));
+ deviceInfo->maxInputChannels = 2;
+ }
+
+ deviceInfo->defaultSampleRate = 0.; /* @todo IMPLEMENT ME */
+
+ *success = 1;
+
+error:
+ return result;
+}
+
+
+static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMmeHostApi,
+ PaDeviceInfo *deviceInfo, int winMmeOutputDeviceId, int *success )
+{
+ PaError result = paNoError;
+ char *deviceName; /* non-const ptr */
+ MMRESULT mmresult;
+ WAVEOUTCAPS woc;
+
+ *success = 0;
+
+ mmresult = waveOutGetDevCaps( winMmeOutputDeviceId, &woc, sizeof( WAVEOUTCAPS ) );
+ if( mmresult == MMSYSERR_NOMEM )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ else if( mmresult != MMSYSERR_NOERROR )
+ {
+ /* instead of returning paUnanticipatedHostError we return
+ paNoError, but leave success set as 0. This allows
+ Pa_Initialize to just ignore this device, without failing
+ the entire initialisation process.
+ */
+ return paNoError;
+ }
+
+ if( winMmeOutputDeviceId == WAVE_MAPPER )
+ {
+ /* Append I/O suffix to WAVE_MAPPER device. */
+ deviceName = (char *)PaUtil_GroupAllocateMemory(
+ winMmeHostApi->allocations, strlen( woc.szPname ) + 1 + sizeof(constOutputMapperSuffix_) );
+ if( !deviceName )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ strcpy( deviceName, woc.szPname );
+ strcat( deviceName, constOutputMapperSuffix_ );
+ }
+ else
+ {
+ deviceName = (char*)PaUtil_GroupAllocateMemory(
+ winMmeHostApi->allocations, strlen( woc.szPname ) + 1 );
+ if( !deviceName )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ strcpy( deviceName, woc.szPname );
+ }
+ deviceInfo->name = deviceName;
+
+ deviceInfo->maxOutputChannels = woc.wChannels;
+ /* 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) )
+ {
+ PA_DEBUG(("Pa_GetDeviceInfo: Num output channels reported as %d! Changed to 2.\n", deviceInfo->maxOutputChannels ));
+ deviceInfo->maxOutputChannels = 2;
+ }
+
+ deviceInfo->defaultSampleRate = 0.; /* @todo IMPLEMENT ME */
+
+ *success = 1;
+
+error:
+ return result;
+}
+
+
+PaError PaWinMme_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
+{
+ PaError result = paNoError;
+ int i;
+ PaWinMmeHostApiRepresentation *winMmeHostApi;
+ int numInputDevices, numOutputDevices, maximumPossibleNumDevices;
+ PaDeviceInfo *deviceInfoArray;
+ int deviceInfoInitializationSucceeded;
+
+ winMmeHostApi = (PaWinMmeHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinMmeHostApiRepresentation) );
+ if( !winMmeHostApi )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ winMmeHostApi->allocations = PaUtil_CreateAllocationGroup();
+ if( !winMmeHostApi->allocations )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ *hostApi = &winMmeHostApi->inheritedHostApiRep;
+ (*hostApi)->info.structVersion = 1;
+ (*hostApi)->info.type = paMME;
+ (*hostApi)->info.name = "MME";
+
+
+ /* initialise device counts and default devices under the assumption that
+ there are no devices. These values are incremented below if and when
+ devices are successfully initialized.
+ */
+ (*hostApi)->info.deviceCount = 0;
+ (*hostApi)->info.defaultInputDevice = paNoDevice;
+ (*hostApi)->info.defaultOutputDevice = paNoDevice;
+ winMmeHostApi->numInputDevices = 0;
+ winMmeHostApi->numOutputDevices = 0;
+
+
+ maximumPossibleNumDevices = 0;
+
+ numInputDevices = waveInGetNumDevs();
+ if( numInputDevices > 0 )
+ maximumPossibleNumDevices += numInputDevices + 1; /* assume there is a WAVE_MAPPER */
+
+ numOutputDevices = waveOutGetNumDevs();
+ if( numOutputDevices > 0 )
+ maximumPossibleNumDevices += numOutputDevices + 1; /* assume there is a WAVE_MAPPER */
+
+
+ if( maximumPossibleNumDevices > 0 ){
+
+ (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory(
+ winMmeHostApi->allocations, sizeof(PaDeviceInfo*) * maximumPossibleNumDevices );
+ if( !(*hostApi)->deviceInfos )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ /* allocate all device info structs in a contiguous block */
+ deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory(
+ winMmeHostApi->allocations, sizeof(PaDeviceInfo) * maximumPossibleNumDevices );
+ if( !deviceInfoArray )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ winMmeHostApi->winMmeDeviceIds = (int*)PaUtil_GroupAllocateMemory(
+ winMmeHostApi->allocations, sizeof(int) * maximumPossibleNumDevices );
+ if( !winMmeHostApi->winMmeDeviceIds )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ if( numInputDevices > 0 ){
+ // -1 is the WAVE_MAPPER
+ for( i = -1; i < numInputDevices; ++i ){
+ PaDeviceInfo *deviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ];
+ deviceInfo->structVersion = 2;
+ deviceInfo->hostApi = hostApiIndex;
+
+ deviceInfo->maxInputChannels = 0;
+ deviceInfo->maxOutputChannels = 0;
+
+ deviceInfo->defaultLowInputLatency = 0.; /* @todo IMPLEMENT ME */
+ deviceInfo->defaultLowOutputLatency = 0.; /* @todo IMPLEMENT ME */
+ deviceInfo->defaultHighInputLatency = 0.; /* @todo IMPLEMENT ME */
+ deviceInfo->defaultHighOutputLatency = 0.; /* @todo IMPLEMENT ME */
+
+ result = InitializeInputDeviceInfo( winMmeHostApi, deviceInfo, i, &deviceInfoInitializationSucceeded );
+ if( result != paNoError )
+ goto error;
+
+ if( deviceInfoInitializationSucceeded ){
+ if( (*hostApi)->info.defaultInputDevice == paNoDevice )
+ (*hostApi)->info.defaultInputDevice = (*hostApi)->info.deviceCount;
+
+ winMmeHostApi->winMmeDeviceIds[ (*hostApi)->info.deviceCount ] = i;
+ (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo;
+
+ winMmeHostApi->numInputDevices++;
+ (*hostApi)->info.deviceCount++;
+ }
+ }
+ }
+
+ if( numOutputDevices > 0 ){
+ // -1 is the WAVE_MAPPER
+ for( i = -1; i < numOutputDevices; ++i ){
+ PaDeviceInfo *deviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ];
+ deviceInfo->structVersion = 2;
+ deviceInfo->hostApi = hostApiIndex;
+
+ deviceInfo->maxInputChannels = 0;
+ deviceInfo->maxOutputChannels = 0;
+
+ deviceInfo->defaultLowInputLatency = 0.; /* @todo IMPLEMENT ME */
+ deviceInfo->defaultLowOutputLatency = 0.; /* @todo IMPLEMENT ME */
+ deviceInfo->defaultHighInputLatency = 0.; /* @todo IMPLEMENT ME */
+ deviceInfo->defaultHighOutputLatency = 0.; /* @todo IMPLEMENT ME */
+
+ result = InitializeOutputDeviceInfo( winMmeHostApi, deviceInfo, i, &deviceInfoInitializationSucceeded );
+ if( result != paNoError )
+ goto error;
+
+ if( deviceInfoInitializationSucceeded ){
+ if( (*hostApi)->info.defaultOutputDevice == paNoDevice )
+ (*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount;
+
+ winMmeHostApi->winMmeDeviceIds[ (*hostApi)->info.deviceCount ] = i;
+ (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo;
+
+ winMmeHostApi->numOutputDevices++;
+ (*hostApi)->info.deviceCount++;
+ }
+ }
+ }
+ }
+
+
+ InitializeDefaultDeviceIdsFromEnv( winMmeHostApi );
+
+ (*hostApi)->Terminate = Terminate;
+ (*hostApi)->OpenStream = OpenStream;
+ (*hostApi)->IsFormatSupported = IsFormatSupported;
+
+ PaUtil_InitializeStreamInterface( &winMmeHostApi->callbackStreamInterface, CloseStream, StartStream,
+ StopStream, AbortStream, IsStreamStopped, IsStreamActive,
+ GetStreamTime, GetStreamCpuLoad,
+ PaUtil_DummyReadWrite, PaUtil_DummyReadWrite, PaUtil_DummyGetAvailable, PaUtil_DummyGetAvailable );
+
+ PaUtil_InitializeStreamInterface( &winMmeHostApi->blockingStreamInterface, CloseStream, StartStream,
+ StopStream, AbortStream, IsStreamStopped, IsStreamActive,
+ GetStreamTime, PaUtil_DummyGetCpuLoad,
+ ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable );
+
+ return result;
+
+error:
+ if( winMmeHostApi )
+ {
+ if( winMmeHostApi->allocations )
+ {
+ PaUtil_FreeAllAllocations( winMmeHostApi->allocations );
+ PaUtil_DestroyAllocationGroup( winMmeHostApi->allocations );
+ }
+
+ PaUtil_FreeMemory( winMmeHostApi );
+ }
+
+ return result;
+}
+
+
+static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
+{
+ PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi;
+
+ if( winMmeHostApi->allocations )
+ {
+ PaUtil_FreeAllAllocations( winMmeHostApi->allocations );
+ PaUtil_DestroyAllocationGroup( winMmeHostApi->allocations );
+ }
+
+ PaUtil_FreeMemory( winMmeHostApi );
+}
+
+
+static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi,
+ const PaStreamParameters *inputParameters,
+ const PaStreamParameters *outputParameters,
+ double sampleRate )
+{
+ int inputChannelCount, outputChannelCount;
+ PaSampleFormat inputSampleFormat, outputSampleFormat;
+
+ if( inputParameters )
+ {
+ inputChannelCount = inputParameters->channelCount;
+ inputSampleFormat = inputParameters->sampleFormat;
+
+ /* unless alternate device specification is supported, reject the use of
+ paUseHostApiSpecificDeviceSpecification */
+
+ if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
+ return paInvalidDevice;
+
+ /* check that input device can support inputChannelCount */
+ if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
+ return paInvalidChannelCount;
+
+ /* validate inputStreamInfo */
+ if( inputParameters->hostApiSpecificStreamInfo )
+ return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
+ }
+ else
+ {
+ inputChannelCount = 0;
+ }
+
+ if( outputParameters )
+ {
+ outputChannelCount = outputParameters->channelCount;
+ outputSampleFormat = outputParameters->sampleFormat;
+
+ /* unless alternate device specification is supported, reject the use of
+ paUseHostApiSpecificDeviceSpecification */
+
+ if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
+ return paInvalidDevice;
+
+ /* check that output device can support inputChannelCount */
+ if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
+ return paInvalidChannelCount;
+
+ /* validate outputStreamInfo */
+ if( outputParameters->hostApiSpecificStreamInfo )
+ return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
+ }
+ else
+ {
+ outputChannelCount = 0;
+ }
+
+ /*
+ IMPLEMENT ME:
+ - check that input device can support inputSampleFormat, or that
+ we have the capability to convert from outputSampleFormat to
+ a native format
+
+ - check that output device can support outputSampleFormat, or that
+ we have the capability to convert from outputSampleFormat to
+ a native format
+
+ - if a full duplex stream is requested, check that the combination
+ of input and output parameters is supported
+
+ - check that the device supports sampleRate
+ */
+
+ return paFormatIsSupported;
+}
+
+
+/* CalculateBufferSettings() fills the framesPerHostInputBuffer, numHostInputBuffers,
+ framesPerHostOutputBuffer and numHostOutputBuffers parameters based on the values
+ of the other parameters.
+
+*/
+
+static PaError CalculateBufferSettings(
+ unsigned long *framesPerHostInputBuffer, unsigned long *numHostInputBuffers,
+ unsigned long *framesPerHostOutputBuffer, unsigned long *numHostOutputBuffers,
+ int inputChannelCount, PaSampleFormat hostInputSampleFormat,
+ PaTime suggestedInputLatency, PaWinMmeStreamInfo *inputStreamInfo,
+ int outputChannelCount, PaSampleFormat hostOutputSampleFormat,
+ PaTime suggestedOutputLatency, PaWinMmeStreamInfo *outputStreamInfo,
+ double sampleRate, unsigned long framesPerBuffer )
+{
+ PaError result = paNoError;
+
+ if( inputChannelCount > 0 )
+ {
+ if( inputStreamInfo )
+ {
+ if( inputStreamInfo->flags & PaWinMmeUseLowLevelLatencyParameters )
+ {
+ if( inputStreamInfo->numBuffers <= 0
+ || inputStreamInfo->framesPerBuffer <= 0 )
+ {
+ result = paIncompatibleHostApiSpecificStreamInfo;
+ goto error;
+ }
+
+ *framesPerHostInputBuffer = inputStreamInfo->framesPerBuffer;
+ *numHostInputBuffers = inputStreamInfo->numBuffers;
+ }
+ }
+ else
+ {
+ /* hardwire for now, FIXME */
+ /* don't forget that there will be one more buffer than the number required to achieve the requested latency */
+ *framesPerHostInputBuffer = 4096;
+ *numHostInputBuffers = 4;
+
+ /*
+ Need to determine the right heuristic for mapping latency in
+ seconds to buffer sizes and number of buffers.
+
+ - for output don't allocate less than 1+1 buffers
+
+ - for input don't allocate less than 2+1 buffers
+ (less than 1+1 if input only)
+
+ - don't allocate buffers smaller than framesPerBuffer
+
+ - if the client doesn't care about the buffer size use a power
+ of two buffer size. otherwise use a multiple of the user
+ buffer size. if the user buffer size is a power of 2, it
+ might be wise to make the host buffer size a power of 2 too.
+
+ - there probably shouldn't be too many buffers ( 3 to 10 seems
+ reasonable).
+
+ - aside from a limit on what constitutes a "reasonable" number
+ of buffers, there should be as many buffers as possible,
+ because this will place a less bursty load on CPU resources
+
+ - the host buffers should be as big as practical (ie multiple
+ user buffers per host buffer).
+
+
+ . One way to achieve the above is to say: Try to have 8
+ host buffers, and host buffers cannot be larger than 32k
+ unless the user buffer size requires it"
+ I say 32k because buffers larger than this are known to
+ crash some drivers (Turtle Beach for example.)
+
+ just some idle rambling:
+
+ if( framesPerBuffer == 0 ){
+ // use a power of two buffer size
+
+ }else{
+ latencySamples = ceil(requestedLatency * sampleRate)
+
+ numBuffers = ceil(latencySamples / framesPerBuffer);
+
+ bufferSize = framesPerBuffer;
+
+ minBuffers = ( inputChannelCount > 0 && outputChannelCount > 0 ) ? 2 : 1;
+
+ if( numBuffers <= minBuffers ){
+ numBuffers = minBuffers;
+ }else{
+ make buffer size a multiple of framesPerBuffer until numBuffers is
+ greater than 4 and less than 10.
+ }
+ }
+ */
+ }
+ }
+ else
+ {
+ *framesPerHostInputBuffer = 0;
+ *numHostInputBuffers = 0;
+ }
+
+ if( outputChannelCount > 0 )
+ {
+ if( outputStreamInfo )
+ {
+ if( outputStreamInfo->flags & PaWinMmeUseLowLevelLatencyParameters )
+ {
+ if( outputStreamInfo->numBuffers <= 0
+ || outputStreamInfo->framesPerBuffer <= 0 )
+ {
+ result = paIncompatibleHostApiSpecificStreamInfo;
+ goto error;
+ }
+
+ *framesPerHostOutputBuffer = outputStreamInfo->framesPerBuffer;
+ *numHostOutputBuffers = outputStreamInfo->numBuffers;
+ }
+ }
+ else
+ {
+ /* hardwire for now, FIXME */
+ /* don't forget that there will be one more buffer than the number required to achieve the requested latency */
+ *framesPerHostOutputBuffer = 4096;
+ *numHostOutputBuffers = 4;
+ }
+ }
+ else
+ {
+ *framesPerHostOutputBuffer = 0;
+ *numHostOutputBuffers = 0;
+ }
+
+error:
+ return result;
+}
+
+
+
+typedef HWAVEIN MmeHandle;
+
+static PaError InitializeBufferSet( WAVEHDR **bufferSet, int numBuffers, int bufferBytes,
+ int isInput, /* if 0, then output */
+ MmeHandle mmeWaveHandle, int numDeviceChannels )
+{
+ PaError result = paNoError;
+ MMRESULT mmresult;
+ int i;
+
+ *bufferSet = 0;
+
+ /* Allocate an array to hold the buffer pointers. */
+ *bufferSet = (WAVEHDR *) PaUtil_AllocateMemory( sizeof(WAVEHDR)*numBuffers );
+ if( !*bufferSet )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ for( i=0; i<numBuffers; ++i )
+ {
+ (*bufferSet)[i].lpData = 0;
+ }
+
+ /* Allocate each buffer. */
+ for( i=0; i<numBuffers; ++i )
+ {
+ (*bufferSet)[i].lpData = (char *)PaUtil_AllocateMemory( bufferBytes );
+ if( !(*bufferSet)[i].lpData )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+ (*bufferSet)[i].dwBufferLength = bufferBytes;
+ (*bufferSet)[i].dwUser = 0xFFFFFFFF; /* indicates unprepared to error clean up code */
+
+ if( isInput )
+ {
+ mmresult = waveInPrepareHeader( mmeWaveHandle, &(*bufferSet)[i], sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ goto error;
+ }
+ }
+ else /* output */
+ {
+ mmresult = waveOutPrepareHeader( (HWAVEOUT)mmeWaveHandle, &(*bufferSet)[i], sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ goto error;
+ }
+ }
+
+ (*bufferSet)[i].dwUser = numDeviceChannels;
+ }
+
+ return result;
+
+error:
+ if( *bufferSet )
+ {
+ for( i=0; i<numBuffers; ++i )
+ {
+ if( (*bufferSet)[i].lpData )
+ {
+
+ if( (*bufferSet)[i].dwUser != 0xFFFFFFFF )
+ {
+ if( isInput )
+ waveInUnprepareHeader( mmeWaveHandle, &(*bufferSet)[i], sizeof(WAVEHDR) );
+ else
+ waveOutUnprepareHeader( (HWAVEOUT)mmeWaveHandle, &(*bufferSet)[i], sizeof(WAVEHDR) );
+ }
+ PaUtil_FreeMemory( (*bufferSet)[i].lpData );
+ }
+ }
+
+ PaUtil_FreeMemory( *bufferSet );
+ }
+
+ return result;
+}
+
+
+static void TerminateBufferSet( WAVEHDR * *bufferSet, unsigned int numBuffers, int isInput, MmeHandle mmeWaveHandle )
+{
+ unsigned int i;
+
+ for( i=0; i<numBuffers; ++i )
+ {
+ if( (*bufferSet)[i].lpData )
+ {
+ if( isInput )
+ waveInUnprepareHeader( mmeWaveHandle, &(*bufferSet)[i], sizeof(WAVEHDR) );
+ else
+ waveOutUnprepareHeader( (HWAVEOUT)mmeWaveHandle, &(*bufferSet)[i], sizeof(WAVEHDR) );
+
+ PaUtil_FreeMemory( (*bufferSet)[i].lpData );
+ }
+ }
+
+ if( *bufferSet )
+ PaUtil_FreeMemory( *bufferSet );
+}
+
+
+/* PaWinMmeStream - a stream data structure specifically for this implementation */
+
+typedef struct PaWinMmeStream
+{
+ PaUtilStreamRepresentation streamRepresentation;
+ PaUtilCpuLoadMeasurer cpuLoadMeasurer;
+ PaUtilBufferProcessor bufferProcessor;
+
+ CRITICAL_SECTION lock;
+
+ /* Input -------------- */
+ HWAVEIN *hWaveIns;
+ unsigned int numInputDevices;
+ /* unsigned int inputChannelCount; */
+ WAVEHDR **inputBuffers;
+ unsigned int numInputBuffers;
+ unsigned int currentInputBufferIndex;
+ unsigned int framesPerInputBuffer;
+ unsigned int framesUsedInCurrentInputBuffer;
+
+ /* Output -------------- */
+ HWAVEOUT *hWaveOuts;
+ unsigned int numOutputDevices;
+ /* unsigned int outputChannelCount; */
+ WAVEHDR **outputBuffers;
+ unsigned int numOutputBuffers;
+ unsigned int currentOutputBufferIndex;
+ unsigned int framesPerOutputBuffer;
+ unsigned int framesUsedInCurrentOutputBuffer;
+
+ /* Processing thread management -------------- */
+ HANDLE abortEvent;
+ HANDLE bufferEvent;
+ HANDLE processingThread;
+ DWORD processingThreadId;
+
+ char noHighPriorityProcessClass;
+ char useTimeCriticalProcessingThreadPriority;
+ char throttleProcessingThreadOnOverload; /* 0 -> don't throtte, non-0 -> throttle */
+ int processingThreadPriority;
+ int highThreadPriority;
+ int throttledThreadPriority;
+
+ volatile int isActive;
+ volatile int stopProcessing; /* stop thread once existing buffers have been returned */
+ volatile int abortProcessing; /* stop thread immediately */
+
+ DWORD allBuffersDurationMs; /* used to calculate timeouts */
+
+ /* @todo FIXME: we no longer need the following for GetStreamTime support */
+ /* GetStreamTime() support ------------- */
+
+ PaTime streamPosition;
+ long previousStreamPosition; /* used to track frames played. */
+}
+PaWinMmeStream;
+
+
+/* the following macros are intended to improve the readability of the following code */
+#define PA_IS_INPUT_STREAM_( stream ) ( stream ->hWaveIns )
+#define PA_IS_OUTPUT_STREAM_( stream ) ( stream ->hWaveOuts )
+#define PA_IS_FULL_DUPLEX_STREAM_( stream ) ( stream ->hWaveIns && stream ->hWaveOuts )
+
+
+static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi,
+ PaStream** s,
+ const PaStreamParameters *inputParameters,
+ const PaStreamParameters *outputParameters,
+ double sampleRate,
+ unsigned long framesPerBuffer,
+ PaStreamFlags streamFlags,
+ PaStreamCallback *streamCallback,
+ void *userData )
+{
+ PaError result = paNoError;
+ PaWinMmeHostApiRepresentation *winMmeHostApi = (PaWinMmeHostApiRepresentation*)hostApi;
+ PaWinMmeStream *stream = 0;
+ PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat;
+ int inputChannelCount, outputChannelCount;
+ PaSampleFormat inputSampleFormat, outputSampleFormat;
+ double suggestedInputLatency, suggestedOutputLatency;
+ PaWinMmeStreamInfo *inputStreamInfo, *outputStreamInfo;
+ unsigned long bytesPerInputFrame, bytesPerOutputFrame;
+ unsigned long framesPerHostInputBuffer;
+ unsigned long numHostInputBuffers;
+ unsigned long framesPerHostOutputBuffer;
+ unsigned long numHostOutputBuffers;
+ unsigned long framesPerBufferProcessorCall;
+ int lockInited = 0;
+ int bufferEventInited = 0;
+ int abortEventInited = 0;
+ WAVEFORMATEX wfx;
+ MMRESULT mmresult;
+ unsigned int i;
+ int channelCount;
+ PaWinMmeDeviceAndChannelCount *inputDevices = 0;
+ unsigned long numInputDevices = (inputParameters) ? 1 : 0;
+ PaWinMmeDeviceAndChannelCount *outputDevices = 0;
+ unsigned long numOutputDevices = (outputParameters) ? 1 : 0;
+ char noHighPriorityProcessClass = 0;
+ char useTimeCriticalProcessingThreadPriority = 0;
+ char throttleProcessingThreadOnOverload = 1;
+
+
+ if( inputParameters )
+ {
+ inputChannelCount = inputParameters->channelCount;
+ inputSampleFormat = inputParameters->sampleFormat;
+ suggestedInputLatency = inputParameters->suggestedLatency;
+
+ /* check that input device can support inputChannelCount */
+ if( (inputParameters->device != paUseHostApiSpecificDeviceSpecification) &&
+ (inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels) )
+ return paInvalidChannelCount;
+
+
+ /* validate input hostApiSpecificStreamInfo */
+ inputStreamInfo = (PaWinMmeStreamInfo*)inputParameters->hostApiSpecificStreamInfo;
+ if( inputStreamInfo )
+ {
+ if( inputStreamInfo->size != sizeof( PaWinMmeStreamInfo )
+ || inputStreamInfo->version != 1 )
+ {
+ return paIncompatibleHostApiSpecificStreamInfo;
+ }
+
+ if( inputStreamInfo->flags & PaWinMmeNoHighPriorityProcessClass )
+ noHighPriorityProcessClass = 1;
+ if( inputStreamInfo->flags & PaWinMmeDontThrottleOverloadedProcessingThread )
+ throttleProcessingThreadOnOverload = 0;
+ if( inputStreamInfo->flags & PaWinMmeUseTimeCriticalThreadPriority )
+ useTimeCriticalProcessingThreadPriority = 1;
+
+ /* validate multidevice fields */
+
+ if( inputStreamInfo->flags & PaWinMmeUseMultipleDevices )
+ {
+ int totalChannels = 0;
+ for( i=0; i< inputStreamInfo->deviceCount; ++i )
+ {
+ /* validate that the device number is within range, and that
+ the number of channels is legal */
+ PaDeviceIndex hostApiDevice;
+
+ if( inputParameters->device != paUseHostApiSpecificDeviceSpecification )
+ return paInvalidDevice;
+
+ channelCount = inputStreamInfo->devices[i].channelCount;
+
+ result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice,
+ inputStreamInfo->devices[i].device, hostApi );
+ if( result != paNoError )
+ return result;
+
+ if( channelCount < 1 || channelCount > hostApi->deviceInfos[ hostApiDevice ]->maxInputChannels )
+ return paInvalidChannelCount;
+
+ /* FIXME this validation might be easier and better if there was a pautil
+ function which performed the validation in pa_front:ValidateOpenStreamParameters() */
+
+ totalChannels += channelCount;
+ }
+
+ if( totalChannels != inputChannelCount )
+ {
+ /* inputChannelCount must match total channels specified by multiple devices */
+ return paInvalidChannelCount; /* REVIEW use of this error code */
+ }
+
+ inputDevices = inputStreamInfo->devices;
+ numInputDevices = inputStreamInfo->deviceCount;
+ }
+ }
+
+ /* FIXME: establish which host formats are available */
+ hostInputSampleFormat =
+ PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputSampleFormat );
+
+ }
+ else
+ {
+ inputChannelCount = 0;
+ inputSampleFormat = -1;
+ suggestedInputLatency = 0.;
+ inputStreamInfo = 0;
+ hostInputSampleFormat = -1;
+ }
+
+
+ if( outputParameters )
+ {
+ outputChannelCount = outputParameters->channelCount;
+ outputSampleFormat = outputParameters->sampleFormat;
+ suggestedOutputLatency = outputParameters->suggestedLatency;
+
+ /* check that input device can support inputChannelCount */
+ if( (outputParameters->device != paUseHostApiSpecificDeviceSpecification) &&
+ (inputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels) )
+ return paInvalidChannelCount;
+
+
+ /* validate input hostApiSpecificStreamInfo */
+ outputStreamInfo = (PaWinMmeStreamInfo*)outputParameters->hostApiSpecificStreamInfo;
+ if( outputStreamInfo )
+ {
+ if( outputStreamInfo->size != sizeof( PaWinMmeStreamInfo )
+ || outputStreamInfo->version != 1 )
+ {
+ return paIncompatibleHostApiSpecificStreamInfo;
+ }
+
+ if( outputStreamInfo->flags & PaWinMmeNoHighPriorityProcessClass )
+ noHighPriorityProcessClass = 1;
+ if( outputStreamInfo->flags & PaWinMmeDontThrottleOverloadedProcessingThread )
+ throttleProcessingThreadOnOverload = 0;
+ if( outputStreamInfo->flags & PaWinMmeUseTimeCriticalThreadPriority )
+ useTimeCriticalProcessingThreadPriority = 1;
+
+ /* validate multidevice fields */
+
+ if( outputStreamInfo->flags & PaWinMmeUseMultipleDevices )
+ {
+ int totalChannels = 0;
+ for( i=0; i< outputStreamInfo->deviceCount; ++i )
+ {
+ /* validate that the device number is within range, and that
+ the number of channels is legal */
+ PaDeviceIndex hostApiDevice;
+
+ if( outputParameters->device != paUseHostApiSpecificDeviceSpecification )
+ return paInvalidDevice;
+
+ channelCount = outputStreamInfo->devices[i].channelCount;
+
+ result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice,
+ outputStreamInfo->devices[i].device,
+ hostApi );
+ if( result != paNoError )
+ return result;
+
+ if( channelCount < 1 || channelCount > hostApi->deviceInfos[ hostApiDevice ]->maxOutputChannels )
+ return paInvalidChannelCount;
+
+ /* FIXME this validation might be easier and better if there was a pautil
+ function which performed the validation in pa_front:ValidateOpenStreamParameters() */
+
+ totalChannels += channelCount;
+ }
+
+ if( totalChannels != outputChannelCount )
+ {
+ /* outputChannelCount must match total channels specified by multiple devices */
+ return paInvalidChannelCount; /* REVIEW use of this error code */
+ }
+
+ outputDevices = outputStreamInfo->devices;
+ numOutputDevices = outputStreamInfo->deviceCount;
+ }
+ }
+
+ /* FIXME: establish which host formats are available */
+ hostOutputSampleFormat =
+ PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputSampleFormat );
+ }
+ else
+ {
+ outputChannelCount = 0;
+ outputSampleFormat = -1;
+ outputStreamInfo = 0;
+ hostOutputSampleFormat = -1;
+ suggestedOutputLatency = 0.;
+ }
+
+
+ /*
+ IMPLEMENT ME:
+ - alter sampleRate to a close allowable rate if possible / necessary
+ */
+
+
+ /* validate platform specific flags */
+ if( (streamFlags & paPlatformSpecificFlags) != 0 )
+ return paInvalidFlag; /* unexpected platform specific flag */
+
+
+ result = CalculateBufferSettings( &framesPerHostInputBuffer, &numHostInputBuffers,
+ &framesPerHostOutputBuffer, &numHostOutputBuffers,
+ inputChannelCount, hostInputSampleFormat, suggestedInputLatency, inputStreamInfo,
+ outputChannelCount, hostOutputSampleFormat, suggestedOutputLatency, outputStreamInfo,
+ sampleRate, framesPerBuffer );
+ if( result != paNoError )
+ goto error;
+
+
+ stream = (PaWinMmeStream*)PaUtil_AllocateMemory( sizeof(PaWinMmeStream) );
+ if( !stream )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ stream->hWaveIns = 0;
+ stream->inputBuffers = 0;
+ stream->hWaveOuts = 0;
+ stream->outputBuffers = 0;
+ stream->processingThread = 0;
+
+ stream->noHighPriorityProcessClass = noHighPriorityProcessClass;
+ stream->useTimeCriticalProcessingThreadPriority = useTimeCriticalProcessingThreadPriority;
+ stream->throttleProcessingThreadOnOverload = throttleProcessingThreadOnOverload;
+
+ PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation,
+ &winMmeHostApi->callbackStreamInterface, streamCallback, userData );
+
+ stream->streamRepresentation.streamInfo.inputLatency = (double)(framesPerHostInputBuffer * (numHostInputBuffers-1)) / sampleRate;
+ stream->streamRepresentation.streamInfo.outputLatency = (double)(framesPerHostOutputBuffer * (numHostOutputBuffers-1)) / sampleRate;
+ stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
+
+ PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );
+
+
+ if( inputParameters && outputParameters ) /* full duplex */
+ {
+ /*
+ either host input and output buffers must be the same size, or the
+ larger one must be an integer multiple of the smaller one.
+ FIXME: should this return an error if the host specific latency
+ settings don't fulfill these constraints? rb: probably
+ */
+
+ if( framesPerHostInputBuffer < framesPerHostOutputBuffer )
+ {
+ assert( (framesPerHostOutputBuffer % framesPerHostInputBuffer) == 0 );
+
+ framesPerBufferProcessorCall = framesPerHostInputBuffer;
+ }
+ else
+ {
+ assert( (framesPerHostInputBuffer % framesPerHostOutputBuffer) == 0 );
+
+ framesPerBufferProcessorCall = framesPerHostOutputBuffer;
+ }
+ }
+ else if( inputParameters )
+ {
+ framesPerBufferProcessorCall = framesPerHostInputBuffer;
+ }
+ else if( outputParameters )
+ {
+ framesPerBufferProcessorCall = framesPerHostOutputBuffer;
+ }
+
+ stream->framesPerInputBuffer = framesPerHostInputBuffer;
+ stream->framesPerOutputBuffer = framesPerHostOutputBuffer;
+
+ result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor,
+ inputChannelCount, inputSampleFormat, hostInputSampleFormat,
+ outputChannelCount, outputSampleFormat, hostOutputSampleFormat,
+ sampleRate, streamFlags, framesPerBuffer,
+ framesPerBufferProcessorCall, paUtilFixedHostBufferSize,
+ streamCallback, userData );
+ if( result != paNoError )
+ goto error;
+
+ stream->isActive = 0;
+
+ stream->streamPosition = 0.;
+ stream->previousStreamPosition = 0;
+
+
+ stream->bufferEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
+ if( stream->bufferEvent == NULL )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() );
+ goto error;
+ }
+ bufferEventInited = 1;
+
+ if( inputParameters )
+ {
+ wfx.wFormatTag = WAVE_FORMAT_PCM;
+ wfx.nSamplesPerSec = (DWORD) sampleRate;
+ wfx.cbSize = 0;
+
+ stream->numInputDevices = numInputDevices;
+ stream->hWaveIns = (HWAVEIN*)PaUtil_AllocateMemory( sizeof(HWAVEIN) * stream->numInputDevices );
+ if( !stream->hWaveIns )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ for( i = 0; i < stream->numInputDevices; ++i )
+ stream->hWaveIns[i] = 0;
+
+ for( i = 0; i < stream->numInputDevices; ++i )
+ {
+ int inputWinMmeId;
+
+ if( inputDevices )
+ {
+ PaDeviceIndex hostApiDevice;
+
+ result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice,
+ inputDevices[i].device, hostApi );
+ if( result != paNoError )
+ return result;
+
+ inputWinMmeId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, hostApiDevice );
+ wfx.nChannels = (WORD) inputDevices[i].channelCount;
+ }
+ else
+ {
+ inputWinMmeId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, inputParameters->device );
+ wfx.nChannels = (WORD) inputChannelCount;
+ }
+
+ bytesPerInputFrame = wfx.nChannels * stream->bufferProcessor.bytesPerHostInputSample;
+
+ wfx.nAvgBytesPerSec = (DWORD)(bytesPerInputFrame * sampleRate);
+ wfx.nBlockAlign = (WORD)bytesPerInputFrame;
+ wfx.wBitsPerSample = (WORD)((bytesPerInputFrame/wfx.nChannels) * 8);
+
+ /* REVIEW: consider not firing an event for input when a full duplex stream is being used */
+
+ mmresult = waveInOpen( &stream->hWaveIns[i], inputWinMmeId, &wfx,
+ (DWORD)stream->bufferEvent, (DWORD) stream, CALLBACK_EVENT );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ switch( mmresult )
+ {
+ case MMSYSERR_ALLOCATED: /* Specified resource is already allocated. */
+ result = paDeviceUnavailable;
+ break;
+ case MMSYSERR_BADDEVICEID: /* Specified device identifier is out of range. */
+ result = paInternalError; /* portaudio should ensure that only good device ids are used */
+ break;
+ case MMSYSERR_NODRIVER: /* No device driver is present. */
+ result = paDeviceUnavailable;
+ break;
+ case MMSYSERR_NOMEM: /* Unable to allocate or lock memory. */
+ result = paInsufficientMemory;
+ break;
+ case WAVERR_BADFORMAT: /* Attempted to open with an unsupported waveform-audio format. */
+ result = paInternalError; /* REVIEW: port audio shouldn't get this far without using compatible format info */
+ break;
+ default:
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ }
+ goto error;
+ }
+ }
+ }
+
+ if( outputParameters )
+ {
+ wfx.wFormatTag = WAVE_FORMAT_PCM;
+ wfx.nSamplesPerSec = (DWORD) sampleRate;
+ wfx.cbSize = 0;
+
+ stream->numOutputDevices = numOutputDevices;
+ stream->hWaveOuts = (HWAVEOUT*)PaUtil_AllocateMemory( sizeof(HWAVEOUT) * stream->numOutputDevices );
+ if( !stream->hWaveOuts )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ for( i = 0; i < stream->numOutputDevices; ++i )
+ stream->hWaveOuts[i] = 0;
+
+ for( i = 0; i < stream->numOutputDevices; ++i )
+ {
+ int outputWinMmeId;
+
+ if( outputDevices )
+ {
+ PaDeviceIndex hostApiDevice;
+
+ result = PaUtil_DeviceIndexToHostApiDeviceIndex( &hostApiDevice,
+ outputDevices[i].device, hostApi );
+ if( result != paNoError )
+ return result;
+
+ outputWinMmeId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, hostApiDevice );
+ wfx.nChannels = (WORD) outputDevices[i].channelCount;
+ }
+ else
+ {
+ outputWinMmeId = LocalDeviceIndexToWinMmeDeviceId( winMmeHostApi, outputParameters->device );
+ wfx.nChannels = (WORD) outputChannelCount;
+ }
+
+ bytesPerOutputFrame = wfx.nChannels * stream->bufferProcessor.bytesPerHostOutputSample;
+
+ wfx.nAvgBytesPerSec = (DWORD)(bytesPerOutputFrame * sampleRate);
+ wfx.nBlockAlign = (WORD)bytesPerOutputFrame;
+ wfx.wBitsPerSample = (WORD)((bytesPerOutputFrame/wfx.nChannels) * 8);
+
+ mmresult = waveOutOpen( &stream->hWaveOuts[i], outputWinMmeId, &wfx,
+ (DWORD)stream->bufferEvent, (DWORD) stream, CALLBACK_EVENT );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ switch( mmresult )
+ {
+ case MMSYSERR_ALLOCATED: /* Specified resource is already allocated. */
+ result = paDeviceUnavailable;
+ break;
+ case MMSYSERR_BADDEVICEID: /* Specified device identifier is out of range. */
+ result = paInternalError; /* portaudio should ensure that only good device ids are used */
+ break;
+ case MMSYSERR_NODRIVER: /* No device driver is present. */
+ result = paDeviceUnavailable;
+ break;
+ case MMSYSERR_NOMEM: /* Unable to allocate or lock memory. */
+ result = paInsufficientMemory;
+ break;
+ case WAVERR_BADFORMAT: /* Attempted to open with an unsupported waveform-audio format. */
+ result = paInternalError; /* REVIEW: port audio shouldn't get this far without using compatible format info */
+ break;
+ default:
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ }
+ goto error;
+ }
+ }
+ }
+
+ if( PA_IS_INPUT_STREAM_(stream) )
+ {
+ stream->inputBuffers = (WAVEHDR**)PaUtil_AllocateMemory( sizeof(WAVEHDR*) * stream->numInputDevices );
+ if( stream->inputBuffers == 0 )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ for( i =0; i < stream->numInputDevices; ++i )
+ stream->inputBuffers[i] = 0;
+
+ stream->numInputBuffers = numHostInputBuffers;
+
+ for( i =0; i < stream->numInputDevices; ++i )
+ {
+ int hostInputBufferBytes = Pa_GetSampleSize( hostInputSampleFormat ) *
+ framesPerHostInputBuffer *
+ ((inputDevices) ? inputDevices[i].channelCount : inputChannelCount);
+ if( hostInputBufferBytes < 0 )
+ {
+ result = paInternalError;
+ goto error;
+ }
+
+ result = InitializeBufferSet( &stream->inputBuffers[i], numHostInputBuffers, hostInputBufferBytes,
+ 1 /* isInput */,
+ (MmeHandle)stream->hWaveIns[i],
+ ((inputDevices) ? inputDevices[i].channelCount : inputChannelCount) );
+
+ if( result != paNoError )
+ goto error;
+ }
+ }
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ stream->outputBuffers = (WAVEHDR**)PaUtil_AllocateMemory( sizeof(WAVEHDR*) * stream->numOutputDevices );
+ if( stream->outputBuffers == 0 )
+ {
+ result = paInsufficientMemory;
+ goto error;
+ }
+
+ for( i =0; i < stream->numOutputDevices; ++i )
+ stream->outputBuffers[i] = 0;
+
+ stream->numOutputBuffers = numHostOutputBuffers;
+
+ for( i=0; i < stream->numOutputDevices; ++i )
+ {
+ int hostOutputBufferBytes = Pa_GetSampleSize( hostOutputSampleFormat ) *
+ framesPerHostOutputBuffer *
+ ((outputDevices) ? outputDevices[i].channelCount : outputChannelCount);
+ if( hostOutputBufferBytes < 0 )
+ {
+ result = paInternalError;
+ goto error;
+ }
+
+ result = InitializeBufferSet( &stream->outputBuffers[i], numHostOutputBuffers, hostOutputBufferBytes,
+ 0 /* not isInput */,
+ (MmeHandle)stream->hWaveOuts[i],
+ ((outputDevices) ? outputDevices[i].channelCount : outputChannelCount) );
+
+ if( result != paNoError )
+ goto error;
+ }
+ }
+
+ stream->abortEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
+ if( stream->abortEvent == NULL )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() );
+ goto error;
+ }
+ abortEventInited = 1;
+
+ InitializeCriticalSection( &stream->lock );
+ lockInited = 1;
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ stream->allBuffersDurationMs = (DWORD) (1000.0 * (framesPerHostOutputBuffer * stream->numOutputBuffers) / sampleRate);
+ else
+ stream->allBuffersDurationMs = (DWORD) (1000.0 * (framesPerHostInputBuffer * stream->numInputBuffers) / sampleRate);
+
+
+ *s = (PaStream*)stream;
+
+ return result;
+
+error:
+ if( lockInited )
+ DeleteCriticalSection( &stream->lock );
+
+ if( abortEventInited )
+ CloseHandle( stream->abortEvent );
+
+
+ if( stream->inputBuffers )
+ {
+ for( i =0 ; i< stream->numInputDevices; ++i )
+ {
+ if( stream->inputBuffers[i] )
+ {
+ TerminateBufferSet( &stream->inputBuffers[i], stream->numInputBuffers,
+ 1 /* isInput */, (MmeHandle)stream->hWaveIns[i] );
+ }
+ }
+
+ PaUtil_FreeMemory( stream->inputBuffers );
+ }
+
+ if( stream->outputBuffers )
+ {
+ for( i =0 ; i< stream->numOutputDevices; ++i )
+ {
+ if( stream->outputBuffers[i] )
+ {
+ TerminateBufferSet( &stream->outputBuffers[i], stream->numOutputBuffers,
+ 0 /* not isInput */, (MmeHandle)stream->hWaveOuts[i] );
+ }
+ }
+
+ PaUtil_FreeMemory( stream->outputBuffers );
+ }
+
+ if( stream->hWaveIns )
+ {
+ for( i =0 ; i< stream->numInputDevices; ++i )
+ {
+ if( stream->hWaveIns[i] )
+ waveInClose( stream->hWaveIns[i] );
+ }
+
+ PaUtil_FreeMemory( stream->hWaveIns );
+ }
+
+ if( stream->hWaveOuts )
+ {
+ for( i =0 ; i< stream->numOutputDevices; ++i )
+ {
+ if( stream->hWaveOuts[i] )
+ waveOutClose( stream->hWaveOuts[i] );
+ }
+
+ PaUtil_FreeMemory( stream->hWaveOuts );
+ }
+
+ if( bufferEventInited )
+ CloseHandle( stream->bufferEvent );
+
+ if( stream )
+ PaUtil_FreeMemory( stream );
+
+ return result;
+}
+
+
+/* return non-zero if any output buffers are queued */
+static int OutputBuffersAreQueued( PaWinMmeStream *stream )
+{
+ int result = 0;
+ unsigned int i, j;
+
+ if( PA_IS_OUTPUT_STREAM_( stream ) )
+ {
+ for( i=0; i<stream->numOutputBuffers; ++i )
+ {
+ for( j=0; j < stream->numOutputDevices; ++j )
+ {
+ if( !( stream->outputBuffers[ j ][ i ].dwFlags & WHDR_DONE) )
+ {
+ result++;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+
+static PaError AdvanceToNextInputBuffer( PaWinMmeStream *stream )
+{
+ PaError result = paNoError;
+ MMRESULT mmresult;
+ unsigned int i;
+
+ for( i=0; i< stream->numInputDevices; ++i )
+ {
+ mmresult = waveInAddBuffer( stream->hWaveIns[i],
+ &stream->inputBuffers[i][ stream->currentInputBufferIndex ],
+ sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ }
+ }
+ stream->currentInputBufferIndex = (stream->currentInputBufferIndex+1 >= stream->numInputBuffers) ?
+ 0 : stream->currentInputBufferIndex+1;
+
+ stream->framesUsedInCurrentInputBuffer = 0;
+
+ return result;
+}
+
+
+static PaError AdvanceToNextOutputBuffer( PaWinMmeStream *stream )
+{
+ PaError result = paNoError;
+ MMRESULT mmresult;
+ unsigned int i;
+
+ for( i=0; i< stream->numOutputDevices; ++i )
+ {
+ mmresult = waveOutWrite( stream->hWaveOuts[i],
+ &stream->outputBuffers[i][ stream->currentOutputBufferIndex ],
+ sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ }
+ }
+
+ stream->currentOutputBufferIndex = (stream->currentOutputBufferIndex+1 >= stream->numOutputBuffers) ?
+ 0 : stream->currentOutputBufferIndex+1;
+
+ stream->framesUsedInCurrentOutputBuffer = 0;
+
+ return result;
+}
+
+
+static DWORD WINAPI ProcessingThreadProc( void *pArg )
+{
+ PaWinMmeStream *stream = (PaWinMmeStream *)pArg;
+ HANDLE events[2];
+ int numEvents = 0;
+ DWORD result = paNoError;
+ DWORD waitResult;
+/** @todo:
+Gordon Gidluck:
+> function: ProcessingThreadProc()
+> line #1665 DWORD timeout = stream->allBuffersDurationMs * 0.5;
+> conversion from 'double ' to 'unsigned long ', possible loss of data
+*/
+ DWORD timeout = stream->allBuffersDurationMs * 0.5;
+ DWORD numTimeouts = 0;
+ int hostBuffersAvailable;
+ signed int hostInputBufferIndex, hostOutputBufferIndex;
+ int callbackResult;
+ int done = 0;
+ unsigned int channel, i, j;
+ unsigned long framesProcessed;
+
+ /* prepare event array for call to WaitForMultipleObjects() */
+ events[numEvents++] = stream->bufferEvent;
+ events[numEvents++] = stream->abortEvent;
+
+ /* loop until something causes us to stop */
+ while( !done )
+ {
+ /* wait for MME to signal that a buffer is available, or for
+ the PA abort event to be signaled */
+ waitResult = WaitForMultipleObjects( numEvents, events, FALSE, timeout );
+ if( waitResult == WAIT_FAILED )
+ {
+ result = paUnanticipatedHostError;
+ /* FIXME/REVIEW: can't return host error info from an asyncronous thread */
+ done = 1;
+ }
+ else if( waitResult == WAIT_TIMEOUT )
+ {
+ /* if a timeout is encountered, continue */
+ numTimeouts += 1;
+ }
+
+ if( stream->abortProcessing )
+ {
+ /* Pa_AbortStream() has been called, stop processing immediately */
+ done = 1;
+ }
+ else if( stream->stopProcessing )
+ {
+ /* Pa_StopStream() has been called or the user callback returned
+ non-zero, processing will continue until all output buffers
+ are marked as done. The stream will stop immediately if it
+ is input-only.
+ */
+
+ if( !OutputBuffersAreQueued( stream ) )
+ {
+ done = 1; /* Will cause thread to return. */
+ }
+ }
+ else
+ {
+ hostBuffersAvailable = 1;
+
+ /* process all available host buffers */
+ do
+ {
+ hostInputBufferIndex = -1;
+ hostOutputBufferIndex = -1;
+
+ if( PA_IS_INPUT_STREAM_(stream))
+ {
+ hostInputBufferIndex = stream->currentInputBufferIndex;
+ for( i=0; i<stream->numInputDevices; ++i )
+ {
+ if( !(stream->inputBuffers[i][ stream->currentInputBufferIndex ].dwFlags & WHDR_DONE) )
+ {
+ hostInputBufferIndex = -1;
+ break;
+ }
+ }
+
+ if( hostInputBufferIndex != -1 )
+ {
+ /* if all of the other buffers are also ready then we dicard all but the
+ most recent. */
+ int inputCatchUp = 1;
+
+ for( i=0; i < stream->numInputBuffers && inputCatchUp == 1; ++i )
+ {
+ for( j=0; j<stream->numInputDevices; ++j )
+ {
+ if( !(stream->inputBuffers[ j ][ i ].dwFlags & WHDR_DONE) )
+ {
+ inputCatchUp = 0;
+ break;
+ }
+ }
+ }
+
+ if( inputCatchUp )
+ {
+ for( i=0; i < stream->numInputBuffers - 1; ++i )
+ {
+ result = AdvanceToNextInputBuffer( stream );
+ if( result != paNoError )
+ done = 1;
+ }
+ }
+ }
+ }
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ hostOutputBufferIndex = stream->currentOutputBufferIndex;
+ for( i=0; i<stream->numOutputDevices; ++i )
+ {
+ if( !(stream->outputBuffers[i][ stream->currentOutputBufferIndex ].dwFlags & WHDR_DONE) )
+ {
+ hostOutputBufferIndex = -1;
+ break;
+ }
+ }
+
+ if( hostOutputBufferIndex != - 1 )
+ {
+ /* if all of the other buffers are also ready, catch up by copying
+ the most recently generated buffer into all but one of the output
+ buffers */
+ int outputCatchUp = 1;
+
+ for( i=0; i < stream->numOutputBuffers && outputCatchUp == 1; ++i )
+ {
+ for( j=0; j<stream->numOutputDevices; ++j )
+ {
+ if( !(stream->outputBuffers[ j ][ i ].dwFlags & WHDR_DONE) )
+ {
+ outputCatchUp = 0;
+ break;
+ }
+ }
+ }
+
+ if( outputCatchUp )
+ {
+ /* FIXME: this is an output underflow buffer slip and should be flagged as such */
+ unsigned int previousBufferIndex = (stream->currentOutputBufferIndex==0)
+ ? stream->numOutputBuffers - 1
+ : stream->currentOutputBufferIndex - 1;
+
+ for( i=0; i < stream->numOutputBuffers - 1; ++i )
+ {
+ for( j=0; j<stream->numOutputDevices; ++j )
+ {
+ if( stream->outputBuffers[j][ stream->currentOutputBufferIndex ].lpData
+ != stream->outputBuffers[j][ previousBufferIndex ].lpData )
+ {
+ CopyMemory( stream->outputBuffers[j][ stream->currentOutputBufferIndex ].lpData,
+ stream->outputBuffers[j][ previousBufferIndex ].lpData,
+ stream->outputBuffers[j][ stream->currentOutputBufferIndex ].dwBufferLength );
+ }
+ }
+
+ result = AdvanceToNextOutputBuffer( stream );
+ if( result != paNoError )
+ done = 1;
+ }
+ }
+ }
+ }
+
+
+ if( (PA_IS_FULL_DUPLEX_STREAM_(stream) && hostInputBufferIndex != -1 && hostOutputBufferIndex != -1) ||
+ (!PA_IS_FULL_DUPLEX_STREAM_(stream) && ( hostInputBufferIndex != -1 || hostOutputBufferIndex != -1 ) ) )
+ {
+ PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /* @todo implement inputBufferAdcTime and currentTime */
+
+
+ if( hostOutputBufferIndex != -1 ){
+ MMTIME time;
+ double now;
+ long totalRingFrames;
+ long ringPosition;
+ long playbackPosition;
+
+ time.wType = TIME_SAMPLES;
+ waveOutGetPosition( stream->hWaveOuts[0], &time, sizeof(MMTIME) );
+ now = PaUtil_GetTime();
+
+ totalRingFrames = stream->numOutputBuffers * stream->bufferProcessor.framesPerHostBuffer;
+
+ ringPosition = stream->currentOutputBufferIndex * stream->bufferProcessor.framesPerHostBuffer;
+
+ playbackPosition = time.u.sample % totalRingFrames;
+
+ if( playbackPosition >= ringPosition ){
+ timeInfo.outputBufferDacTime =
+ now + ((double)( ringPosition + (totalRingFrames - playbackPosition) ) * stream->bufferProcessor.samplePeriod );
+ }else{
+ timeInfo.outputBufferDacTime =
+ now + ((double)( ringPosition - playbackPosition ) * stream->bufferProcessor.samplePeriod );
+ }
+ }
+
+
+ PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
+
+ PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo );
+
+ if( hostInputBufferIndex != -1 )
+ {
+ PaUtil_SetInputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ );
+
+ channel = 0;
+ for( i=0; i<stream->numInputDevices; ++i )
+ {
+ /* we have stored the number of channels in the buffer in dwUser */
+ int channelCount = stream->inputBuffers[i][ hostInputBufferIndex ].dwUser;
+
+ PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, channel,
+ stream->inputBuffers[i][ hostInputBufferIndex ].lpData +
+ stream->framesUsedInCurrentInputBuffer * channelCount *
+ stream->bufferProcessor.bytesPerHostInputSample,
+ channelCount );
+
+
+ channel += channelCount;
+ }
+ }
+
+ if( hostOutputBufferIndex != -1 )
+ {
+ PaUtil_SetOutputFrameCount( &stream->bufferProcessor, 0 /* default to host buffer size */ );
+
+ channel = 0;
+ for( i=0; i<stream->numOutputDevices; ++i )
+ {
+ /* we have stored the number of channels in the buffer in dwUser */
+ int channelCount = stream->outputBuffers[i][ hostOutputBufferIndex ].dwUser;
+
+ PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, channel,
+ stream->outputBuffers[i][ hostOutputBufferIndex ].lpData +
+ stream->framesUsedInCurrentOutputBuffer * channelCount *
+ stream->bufferProcessor.bytesPerHostOutputSample,
+ channelCount );
+
+ /* we have stored the number of channels in the buffer in dwUser */
+ channel += channelCount;
+ }
+ }
+
+ framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult );
+
+ stream->framesUsedInCurrentInputBuffer += framesProcessed;
+ stream->framesUsedInCurrentOutputBuffer += framesProcessed;
+
+ PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed );
+
+ if( callbackResult == paContinue )
+ {
+ /* nothing special to do */
+ }
+ else if( callbackResult == paAbort )
+ {
+ stream->abortProcessing = 1;
+ done = 1;
+ /* FIXME: should probably do a reset here */
+ result = paNoError;
+ }
+ else
+ {
+ /* User cllback has asked us to stop with paComplete or other non-zero value */
+ stream->stopProcessing = 1; /* stop once currently queued audio has finished */
+ result = paNoError;
+ }
+
+ /*
+ FIXME: the following code is incorrect, because stopProcessing should
+ still queue the current buffer.
+ */
+ if( stream->stopProcessing == 0 && stream->abortProcessing == 0 )
+ {
+ if( stream->throttleProcessingThreadOnOverload != 0 )
+ {
+ if( PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ) > 1. )
+ {
+ if( stream->processingThreadPriority != stream->throttledThreadPriority )
+ {
+ SetThreadPriority( stream->processingThread, stream->throttledThreadPriority );
+ stream->processingThreadPriority = stream->throttledThreadPriority;
+ }
+
+/** @todo:
+Gordon Gidluck:
+> function: ProcessingThreadProc()
+> line #1947/1948 Sleep( stream->bufferProcessor.framesPerHostBuffer *
+> stream->bufferProcessor.samplePeriod * .25 );
+> conversion from 'double ' to 'unsigned long ', possible loss of data
+> integral size mismatch in argument; conversion supplied
+*/
+ /* sleep for a quater of a buffer's duration to give other processes a go */
+ Sleep( stream->bufferProcessor.framesPerHostBuffer *
+ stream->bufferProcessor.samplePeriod * .25 );
+ }
+ else
+ {
+ if( stream->processingThreadPriority != stream->highThreadPriority )
+ {
+ SetThreadPriority( stream->processingThread, stream->highThreadPriority );
+ stream->processingThreadPriority = stream->highThreadPriority;
+ }
+ }
+ }
+
+ if( PA_IS_INPUT_STREAM_(stream) &&
+ stream->framesUsedInCurrentInputBuffer == stream->framesPerInputBuffer )
+ {
+ result = AdvanceToNextInputBuffer( stream );
+ if( result != paNoError )
+ done = 1;
+ }
+
+ if( PA_IS_OUTPUT_STREAM_(stream) &&
+ stream->framesUsedInCurrentOutputBuffer == stream->framesPerOutputBuffer )
+ {
+ result = AdvanceToNextOutputBuffer( stream );
+ if( result != paNoError )
+ done = 1;
+ }
+ }
+ }
+ else
+ {
+ hostBuffersAvailable = 0;
+ }
+ }
+ while( hostBuffersAvailable &&
+ stream->stopProcessing == 0 &&
+ stream->abortProcessing == 0 &&
+ !done );
+ }
+
+ result = UpdateStreamTime( stream );
+ if( result != paNoError )
+ done = 1;
+ }
+
+ stream->isActive = 0;
+
+ if( stream->streamRepresentation.streamFinishedCallback != 0 )
+ stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
+
+ return result;
+}
+
+
+/*
+ When CloseStream() is called, the multi-api layer ensures that
+ the stream has already been stopped or aborted.
+*/
+static PaError CloseStream( PaStream* s )
+{
+ PaError result = paNoError;
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+ MMRESULT mmresult;
+ unsigned int i;
+
+ if( PA_IS_INPUT_STREAM_(stream) )
+ {
+ for( i=0; i<stream->numInputDevices; ++i )
+ {
+ TerminateBufferSet( &stream->inputBuffers[i], stream->numInputBuffers,
+ 1 /* isInput */, (MmeHandle)stream->hWaveIns[i] );
+ }
+
+ PaUtil_FreeMemory( stream->inputBuffers );
+ }
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ for( i=0; i<stream->numOutputDevices; ++i )
+ {
+ TerminateBufferSet( &stream->outputBuffers[i], stream->numOutputBuffers,
+ 0 /* not isInput */, (MmeHandle)stream->hWaveOuts[i] );
+ }
+
+ PaUtil_FreeMemory( stream->outputBuffers );
+ }
+
+
+ if( PA_IS_INPUT_STREAM_(stream) )
+ {
+ for( i=0; i<stream->numInputDevices; ++i )
+ {
+ mmresult = waveInClose( stream->hWaveIns[i] );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ goto error;
+ }
+ }
+
+ PaUtil_FreeMemory( stream->hWaveIns );
+ }
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ for( i=0; i<stream->numOutputDevices; ++i )
+ {
+ mmresult = waveOutClose( stream->hWaveOuts[i] );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ goto error;
+ }
+ }
+
+ PaUtil_FreeMemory( stream->hWaveOuts );
+ }
+
+ if( CloseHandle( stream->bufferEvent ) == 0 )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() );
+ goto error;
+ }
+
+ if( CloseHandle( stream->abortEvent ) == 0 )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() );
+ goto error;
+ }
+
+ DeleteCriticalSection( &stream->lock );
+
+ PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
+ PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
+ PaUtil_FreeMemory( stream );
+
+error:
+ /* FIXME: consider how to best clean up on failure */
+ return result;
+}
+
+
+static PaError StartStream( PaStream *s )
+{
+ PaError result = paNoError;
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+ MMRESULT mmresult;
+ unsigned int i, j;
+
+ if( PA_IS_INPUT_STREAM_(stream) )
+ {
+ for( i=0; i<stream->numInputBuffers; ++i )
+ {
+ for( j=0; j<stream->numInputDevices; ++j )
+ {
+ mmresult = waveInAddBuffer( stream->hWaveIns[j], &stream->inputBuffers[j][i], sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ goto error;
+ }
+ }
+ }
+ stream->currentInputBufferIndex = 0;
+ stream->framesUsedInCurrentInputBuffer = 0;
+ }
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ for( i=0; i<stream->numOutputDevices; ++i )
+ {
+ if( (mmresult = waveOutPause( stream->hWaveOuts[i] )) != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ goto error;
+ }
+ }
+
+ for( i=0; i<stream->numOutputBuffers; ++i )
+ {
+ for( j=0; j<stream->numOutputDevices; ++j )
+ {
+ ZeroMemory( stream->outputBuffers[j][i].lpData, stream->outputBuffers[j][i].dwBufferLength );
+ mmresult = waveOutWrite( stream->hWaveOuts[j], &stream->outputBuffers[j][i], sizeof(WAVEHDR) );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ goto error;
+ }
+ }
+ }
+ stream->currentOutputBufferIndex = 0;
+ stream->framesUsedInCurrentOutputBuffer = 0;
+ }
+
+ stream->streamPosition = 0.;
+ stream->previousStreamPosition = 0;
+
+ stream->isActive = 1;
+ stream->stopProcessing = 0;
+ stream->abortProcessing = 0;
+
+ if( ResetEvent( stream->bufferEvent ) == 0 )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() );
+ goto error;
+ }
+
+ if( ResetEvent( stream->abortEvent ) == 0 )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() );
+ goto error;
+ }
+
+ /* Create thread that waits for audio buffers to be ready for processing. */
+ stream->processingThread = CreateThread( 0, 0, ProcessingThreadProc, stream, 0, &stream->processingThreadId );
+ if( !stream->processingThread )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() );
+ goto error;
+ }
+
+ /* 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.
+ */
+ /* REVIEW: should we reset the priority class when the stream has stopped?
+ - would be best to refcount priority boosts incase more than one
+ stream is open
+ */
+
+ if( !stream->noHighPriorityProcessClass )
+ {
+#ifndef WIN32_PLATFORM_PSPC /* no SetPriorityClass or HIGH_PRIORITY_CLASS on PocketPC */
+
+ if( !SetPriorityClass( GetCurrentProcess(), HIGH_PRIORITY_CLASS ) ) /* PLB20010816 */
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() );
+ goto error;
+ }
+#endif
+ }
+
+ if( stream->useTimeCriticalProcessingThreadPriority )
+ stream->highThreadPriority = THREAD_PRIORITY_TIME_CRITICAL;
+ else
+ stream->highThreadPriority = THREAD_PRIORITY_HIGHEST;
+
+ stream->throttledThreadPriority = THREAD_PRIORITY_NORMAL;
+
+ if( !SetThreadPriority( stream->processingThread, stream->highThreadPriority ) )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_SYSTEM_ERROR( GetLastError() );
+ goto error;
+ }
+ stream->processingThreadPriority = stream->highThreadPriority;
+
+
+ if( PA_IS_INPUT_STREAM_(stream) )
+ {
+ for( i=0; i < stream->numInputDevices; ++i )
+ {
+ mmresult = waveInStart( stream->hWaveIns[i] );
+ PA_DEBUG(("Pa_StartStream: waveInStart returned = 0x%X.\n", mmresult));
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ goto error;
+ }
+ }
+ }
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ for( i=0; i < stream->numOutputDevices; ++i )
+ {
+ if( (mmresult = waveOutRestart( stream->hWaveOuts[i] )) != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ goto error;
+ }
+ }
+ }
+
+ return result;
+
+error:
+ /* FIXME: implement recovery as best we can
+ This should involve rolling back to a state as-if this function had never been called
+ */
+ return result;
+}
+
+
+static PaError StopStream( PaStream *s )
+{
+ PaError result = paNoError;
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+ int timeout;
+ DWORD waitResult;
+ MMRESULT mmresult;
+ unsigned int i;
+
+ /*
+ FIXME: the error checking in this function needs review. the basic
+ idea is to return from this function in a known state - for example
+ there is no point avoiding calling waveInReset just because
+ the thread times out.
+ */
+
+
+ /* Tell processing thread to stop generating more data and to let current data play out. */
+ stream->stopProcessing = 1;
+
+ /* Calculate timeOut longer than longest time it could take to return all buffers. */
+ timeout = stream->allBuffersDurationMs * 1.5;
+ if( timeout < PA_MIN_TIMEOUT_MSEC_ )
+ timeout = PA_MIN_TIMEOUT_MSEC_;
+
+ PA_DEBUG(("WinMME StopStream: waiting for background thread.\n"));
+
+ waitResult = WaitForSingleObject( stream->processingThread, timeout );
+ if( waitResult == WAIT_TIMEOUT )
+ {
+ /* try to abort */
+ stream->abortProcessing = 1;
+ SetEvent( stream->abortEvent );
+ waitResult = WaitForSingleObject( stream->processingThread, timeout );
+ if( waitResult == WAIT_TIMEOUT )
+ {
+ PA_DEBUG(("WinMME StopStream: timed out while waiting for background thread to finish.\n"));
+ result = paTimedOut;
+ }
+ }
+
+ CloseHandle( stream->processingThread );
+ stream->processingThread = NULL;
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ for( i =0; i < stream->numOutputDevices; ++i )
+ {
+ mmresult = waveOutReset( stream->hWaveOuts[i] );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ }
+ }
+ }
+
+ if( PA_IS_INPUT_STREAM_(stream) )
+ {
+ for( i=0; i < stream->numInputDevices; ++i )
+ {
+ mmresult = waveInReset( stream->hWaveIns[i] );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ result = paUnanticipatedHostError;
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ }
+ }
+ }
+
+ stream->isActive = 0;
+
+ return result;
+}
+
+
+static PaError AbortStream( PaStream *s )
+{
+ PaError result = paNoError;
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+ int timeout;
+ DWORD waitResult;
+ MMRESULT mmresult;
+ unsigned int i;
+
+ /*
+ FIXME: the error checking in this function needs review. the basic
+ idea is to return from this function in a known state - for example
+ there is no point avoiding calling waveInReset just because
+ the thread times out.
+ */
+
+ /* Tell processing thread to abort immediately */
+ stream->abortProcessing = 1;
+ SetEvent( stream->abortEvent );
+
+ /* Calculate timeOut longer than longest time it could take to return all buffers. */
+ timeout = stream->allBuffersDurationMs * 1.5;
+ if( timeout < PA_MIN_TIMEOUT_MSEC_ )
+ timeout = PA_MIN_TIMEOUT_MSEC_;
+
+ if( PA_IS_OUTPUT_STREAM_(stream) )
+ {
+ for( i =0; i < stream->numOutputDevices; ++i )
+ {
+ mmresult = waveOutReset( stream->hWaveOuts[i] );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ return paUnanticipatedHostError;
+ }
+ }
+ }
+
+ if( PA_IS_INPUT_STREAM_(stream) )
+ {
+ for( i=0; i < stream->numInputDevices; ++i )
+ {
+ mmresult = waveInReset( stream->hWaveIns[i] );
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ return paUnanticipatedHostError;
+ }
+ }
+ }
+
+
+ PA_DEBUG(("WinMME AbortStream: waiting for background thread.\n"));
+
+ waitResult = WaitForSingleObject( stream->processingThread, timeout );
+ if( waitResult == WAIT_TIMEOUT )
+ {
+ PA_DEBUG(("WinMME AbortStream: timed out while waiting for background thread to finish.\n"));
+ return paTimedOut;
+ }
+
+ CloseHandle( stream->processingThread );
+ stream->processingThread = NULL;
+
+ stream->isActive = 0;
+
+ return result;
+}
+
+
+static PaError IsStreamStopped( PaStream *s )
+{
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+
+ return ( stream->processingThread == NULL );
+}
+
+
+static PaError IsStreamActive( PaStream *s )
+{
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+
+ return stream->isActive;
+}
+
+
+/* UpdateStreamTime() must be called periodically because mmtime.u.sample
+ is a DWORD and can wrap and lose sync after a few hours.
+ */
+static PaError UpdateStreamTime( PaWinMmeStream *stream )
+{
+ MMRESULT mmresult;
+ MMTIME mmtime;
+ mmtime.wType = TIME_SAMPLES;
+
+ if( stream->hWaveOuts )
+ {
+ /* assume that all devices have the same position */
+ mmresult = waveOutGetPosition( stream->hWaveOuts[0], &mmtime, sizeof(mmtime) );
+
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ PA_MME_SET_LAST_WAVEOUT_ERROR( mmresult );
+ return paUnanticipatedHostError;
+ }
+ }
+ else
+ {
+ /* assume that all devices have the same position */
+ mmresult = waveInGetPosition( stream->hWaveIns[0], &mmtime, sizeof(mmtime) );
+
+ if( mmresult != MMSYSERR_NOERROR )
+ {
+ PA_MME_SET_LAST_WAVEIN_ERROR( mmresult );
+ return paUnanticipatedHostError;
+ }
+ }
+
+
+ /* This data has two variables and is shared by foreground and background.
+ * So we need to make it thread safe. */
+ EnterCriticalSection( &stream->lock );
+ stream->streamPosition += ((long)mmtime.u.sample) - stream->previousStreamPosition;
+ stream->previousStreamPosition = (long)mmtime.u.sample;
+ LeaveCriticalSection( &stream->lock );
+
+ return paNoError;
+}
+
+
+static PaTime GetStreamTime( PaStream *s )
+{
+/*
+ new behavior for GetStreamTime is to return a stream based seconds clock
+ used for the outTime parameter to the callback.
+ FIXME: delete this comment when the other unnecessary related code has
+ been cleaned from this file.
+
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+ PaError error = UpdateStreamTime( stream );
+
+ if( error == paNoError )
+ return stream->streamPosition;
+ else
+ return 0;
+*/
+ (void) s; /* unused parameter */
+ return PaUtil_GetTime();
+}
+
+
+static double GetStreamCpuLoad( PaStream* s )
+{
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+
+ return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer );
+}
+
+
+/*
+ As separate stream interfaces are used for blocking and callback
+ streams, the following functions can be guaranteed to only be called
+ for blocking streams.
+*/
+
+static PaError ReadStream( PaStream* s,
+ void *buffer,
+ unsigned long frames )
+{
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+
+ /* IMPLEMENT ME, see portaudio.h for required behavior*/
+ (void) stream; /* unused parameters */
+ (void) buffer;
+ (void) frames;
+
+ return paNoError;
+}
+
+
+static PaError WriteStream( PaStream* s,
+ void *buffer,
+ unsigned long frames )
+{
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+
+ /* IMPLEMENT ME, see portaudio.h for required behavior*/
+ (void) stream; /* unused parameters */
+ (void) buffer;
+ (void) frames;
+
+ return paNoError;
+}
+
+
+static signed long GetStreamReadAvailable( PaStream* s )
+{
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+
+ /* IMPLEMENT ME, see portaudio.h for required behavior*/
+ (void) stream; /* unused parameter */
+
+ return 0;
+}
+
+
+static signed long GetStreamWriteAvailable( PaStream* s )
+{
+ PaWinMmeStream *stream = (PaWinMmeStream*)s;
+
+ /* IMPLEMENT ME, see portaudio.h for required behavior*/
+ (void) stream; /* unused parameter */
+
+ return 0;
+}
+
+
+
+
diff --git a/pd/portaudio/pa_win_wmme/pa_win_wmme.h b/pd/portaudio/pa_win_wmme/pa_win_wmme.h
new file mode 100644
index 00000000..d5c18a28
--- /dev/null
+++ b/pd/portaudio/pa_win_wmme/pa_win_wmme.h
@@ -0,0 +1,105 @@
+#ifndef PA_WIN_WMME_H
+#define PA_WIN_WMME_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+/*
+ *
+ * PortAudio Portable Real-Time Audio Library
+ * MME specific extensions
+ *
+ * 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.
+ *
+ */
+
+
+#include "portaudio.h"
+
+#define PaWinMmeUseLowLevelLatencyParameters (0x01)
+#define PaWinMmeUseMultipleDevices (0x02) /* use mme specific multiple device feature */
+
+/* by default, the mme implementation boosts the process priority class to
+ HIGH_PRIORITY_CLASS. This flag disables that priority boost */
+#define PaWinMmeNoHighPriorityProcessClass (0x03)
+
+/* by default, the mme implementation drops the processing thread's priority
+ to THREAD_PRIORITY_NORMAL and sleeps the thread if the CPU load exceeds 100% */
+#define PaWinMmeDontThrottleOverloadedProcessingThread (0x04)
+
+/* by default, the mme implementation sets the processing thread's priority to
+ THREAD_PRIORITY_HIGHEST. This flag sets the priority to
+ THREAD_PRIORITY_TIME_CRITICAL instead. Note that this has the potential
+ to freeze the machine, especially when used in combination with
+ PaWinMmeDontThrottleOverloadedProcessingThread */
+#define PaWinMmeUseTimeCriticalThreadPriority (0x05)
+
+typedef struct PaWinMmeDeviceAndChannelCount{
+ PaDeviceIndex device;
+ int channelCount;
+}PaWinMmeDeviceAndChannelCount;
+
+
+typedef struct PaWinMmeStreamInfo{
+ unsigned long size; /* sizeof(PaWinMmeStreamInfo) */
+ PaHostApiTypeId hostApiType; /* paMME */
+ unsigned long version; /* 1 */
+
+ unsigned long flags;
+
+ /* low-level latency setting support
+ These settings control the number and size of host buffers in order
+ to set latency. They will be used instead of the generic parameters
+ to Pa_OpenStream() if flags contains the PaWinMmeUseLowLevelLatencyParameters
+ flag.
+ */
+ unsigned long framesPerBuffer;
+ unsigned long numBuffers;
+
+ /* multiple devices per direction support
+ If flags contains the PaWinMmeUseMultipleDevices flag,
+ this functionality will be used, otherwise the device parameter to
+ Pa_OpenStream() will be used instead.
+ If devices are specified here, the corresponding device parameter
+ to Pa_OpenStream() should be set to paUseHostApiSpecificDeviceSpecification,
+ otherwise an paInvalidDevice error will result.
+ The total number of channels accross all specified devices
+ must agree with the corresponding channelCount parameter to
+ Pa_OpenStream() otherwise a paInvalidChannelCount error will result.
+ */
+ PaWinMmeDeviceAndChannelCount *devices;
+ unsigned long deviceCount;
+
+}PaWinMmeStreamInfo;
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* PA_WIN_WMME_H */