diff options
Diffstat (limited to 'pd/portaudio/pa_win_ds')
-rw-r--r-- | pd/portaudio/pa_win_ds/dsound_wrapper.c | 604 | ||||
-rw-r--r-- | pd/portaudio/pa_win_ds/dsound_wrapper.h | 129 | ||||
-rw-r--r-- | pd/portaudio/pa_win_ds/pa_dsound.c | 1021 | ||||
-rw-r--r-- | pd/portaudio/pa_win_ds/pa_win_ds.c | 1441 | ||||
-rw-r--r-- | pd/portaudio/pa_win_ds/portaudio.def | 28 |
5 files changed, 3223 insertions, 0 deletions
diff --git a/pd/portaudio/pa_win_ds/dsound_wrapper.c b/pd/portaudio/pa_win_ds/dsound_wrapper.c new file mode 100644 index 00000000..ac8c927d --- /dev/null +++ b/pd/portaudio/pa_win_ds/dsound_wrapper.c @@ -0,0 +1,604 @@ +/* + * $Id: dsound_wrapper.c,v 1.1.1.1.2.5 2002/07/01 00:49:41 philburk Exp $ + * Simplified DirectSound interface. + * + * Author: Phil Burk & Robert Marsanyi + * + * PortAudio Portable Real-Time Audio Library + * For more information see: http://www.softsynth.com/portaudio/ + * DirectSound Implementation + * Copyright (c) 1999-2000 Phil Burk & Robert Marsanyi + * + * 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 <stdio.h> +#include <stdlib.h> +#include <math.h> +#define INITGUID // Needed to build IID_IDirectSoundNotify. See objbase.h for info. +#include <objbase.h> +#include <unknwn.h> +#include "dsound_wrapper.h" +#include "pa_trace.h" + +/************************************************************************************/ +DSoundEntryPoints dswDSoundEntryPoints = { 0, 0, 0, 0, 0, 0, 0 }; +/************************************************************************************/ +static HRESULT WINAPI DummyDirectSoundCreate(LPGUID lpcGuidDevice, LPDIRECTSOUND *ppDS, LPUNKNOWN pUnkOuter) +{ + (void)lpcGuidDevice; /* unused parameter */ + (void)ppDS; /* unused parameter */ + (void)pUnkOuter; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundEnumerateW(LPDSENUMCALLBACKW lpDSEnumCallback, LPVOID lpContext) +{ + (void)lpDSEnumCallback; /* unused parameter */ + (void)lpContext; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundEnumerateA(LPDSENUMCALLBACKA lpDSEnumCallback, LPVOID lpContext) +{ + (void)lpDSEnumCallback; /* unused parameter */ + (void)lpContext; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundCaptureCreate(LPGUID lpcGUID, LPDIRECTSOUNDCAPTURE *lplpDSC, LPUNKNOWN pUnkOuter) +{ + (void)lpcGUID; /* unused parameter */ + (void)lplpDSC; /* unused parameter */ + (void)pUnkOuter; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundCaptureEnumerateW(LPDSENUMCALLBACKW lpDSCEnumCallback, LPVOID lpContext) +{ + (void)lpDSCEnumCallback; /* unused parameter */ + (void)lpContext; /* unused parameter */ + return E_NOTIMPL; +} + +static HRESULT WINAPI DummyDirectSoundCaptureEnumerateA(LPDSENUMCALLBACKA lpDSCEnumCallback, LPVOID lpContext) +{ + (void)lpDSCEnumCallback; /* unused parameter */ + (void)lpContext; /* unused parameter */ + return E_NOTIMPL; +} +/************************************************************************************/ +void DSW_InitializeDSoundEntryPoints(void) +{ + dswDSoundEntryPoints.hInstance_ = LoadLibrary("dsound.dll"); + if( dswDSoundEntryPoints.hInstance_ != NULL ) + { + dswDSoundEntryPoints.DirectSoundCreate = + (HRESULT (WINAPI *)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundCreate" ); + if( dswDSoundEntryPoints.DirectSoundCreate == NULL ) + dswDSoundEntryPoints.DirectSoundCreate = DummyDirectSoundCreate; + + dswDSoundEntryPoints.DirectSoundEnumerateW = + (HRESULT (WINAPI *)(LPDSENUMCALLBACKW, LPVOID)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundEnumerateW" ); + if( dswDSoundEntryPoints.DirectSoundEnumerateW == NULL ) + dswDSoundEntryPoints.DirectSoundEnumerateW = DummyDirectSoundEnumerateW; + + dswDSoundEntryPoints.DirectSoundEnumerateA = + (HRESULT (WINAPI *)(LPDSENUMCALLBACKA, LPVOID)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundEnumerateA" ); + if( dswDSoundEntryPoints.DirectSoundEnumerateA == NULL ) + dswDSoundEntryPoints.DirectSoundEnumerateA = DummyDirectSoundEnumerateA; + + dswDSoundEntryPoints.DirectSoundCaptureCreate = + (HRESULT (WINAPI *)(LPGUID, LPDIRECTSOUNDCAPTURE *, LPUNKNOWN)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundCaptureCreate" ); + if( dswDSoundEntryPoints.DirectSoundCaptureCreate == NULL ) + dswDSoundEntryPoints.DirectSoundCaptureCreate = DummyDirectSoundCaptureCreate; + + dswDSoundEntryPoints.DirectSoundCaptureEnumerateW = + (HRESULT (WINAPI *)(LPDSENUMCALLBACKW, LPVOID)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundCaptureEnumerateW" ); + if( dswDSoundEntryPoints.DirectSoundCaptureEnumerateW == NULL ) + dswDSoundEntryPoints.DirectSoundCaptureEnumerateW = DummyDirectSoundCaptureEnumerateW; + + dswDSoundEntryPoints.DirectSoundCaptureEnumerateA = + (HRESULT (WINAPI *)(LPDSENUMCALLBACKA, LPVOID)) + GetProcAddress( dswDSoundEntryPoints.hInstance_, "DirectSoundCaptureEnumerateA" ); + if( dswDSoundEntryPoints.DirectSoundCaptureEnumerateA == NULL ) + dswDSoundEntryPoints.DirectSoundCaptureEnumerateA = DummyDirectSoundCaptureEnumerateA; + } + else + { + /* initialize with dummy entry points to make live easy when ds isn't present */ + dswDSoundEntryPoints.DirectSoundCreate = DummyDirectSoundCreate; + dswDSoundEntryPoints.DirectSoundEnumerateW = DummyDirectSoundEnumerateW; + dswDSoundEntryPoints.DirectSoundEnumerateA = DummyDirectSoundEnumerateA; + dswDSoundEntryPoints.DirectSoundCaptureCreate = DummyDirectSoundCaptureCreate; + dswDSoundEntryPoints.DirectSoundCaptureEnumerateW = DummyDirectSoundCaptureEnumerateW; + dswDSoundEntryPoints.DirectSoundCaptureEnumerateA = DummyDirectSoundCaptureEnumerateA; + } +} +/************************************************************************************/ +void DSW_TerminateDSoundEntryPoints(void) +{ + if( dswDSoundEntryPoints.hInstance_ != NULL ) + { + FreeLibrary( dswDSoundEntryPoints.hInstance_ ); + dswDSoundEntryPoints.hInstance_ = NULL; + /* ensure that we crash reliably if the entry points arent initialised */ + dswDSoundEntryPoints.DirectSoundCreate = 0; + dswDSoundEntryPoints.DirectSoundEnumerateW = 0; + dswDSoundEntryPoints.DirectSoundEnumerateA = 0; + dswDSoundEntryPoints.DirectSoundCaptureCreate = 0; + dswDSoundEntryPoints.DirectSoundCaptureEnumerateW = 0; + dswDSoundEntryPoints.DirectSoundCaptureEnumerateA = 0; + } +} +/************************************************************************************/ +void DSW_Term( DSoundWrapper *dsw ) +{ + // Cleanup the sound buffers + if (dsw->dsw_OutputBuffer) + { + IDirectSoundBuffer_Stop( dsw->dsw_OutputBuffer ); + IDirectSoundBuffer_Release( dsw->dsw_OutputBuffer ); + dsw->dsw_OutputBuffer = NULL; + } + + if (dsw->dsw_InputBuffer) + { + IDirectSoundCaptureBuffer_Stop( dsw->dsw_InputBuffer ); + IDirectSoundCaptureBuffer_Release( dsw->dsw_InputBuffer ); + dsw->dsw_InputBuffer = NULL; + } + + if (dsw->dsw_pDirectSoundCapture) + { + IDirectSoundCapture_Release( dsw->dsw_pDirectSoundCapture ); + dsw->dsw_pDirectSoundCapture = NULL; + } + + if (dsw->dsw_pDirectSound) + { + IDirectSound_Release( dsw->dsw_pDirectSound ); + dsw->dsw_pDirectSound = NULL; + } +} +/************************************************************************************/ +HRESULT DSW_Init( DSoundWrapper *dsw ) +{ + memset( dsw, 0, sizeof(DSoundWrapper) ); + return 0; +} +/************************************************************************************/ +HRESULT DSW_InitOutputDevice( DSoundWrapper *dsw, LPGUID lpGUID ) +{ + // Create the DS object + HRESULT hr = dswDSoundEntryPoints.DirectSoundCreate( lpGUID, &dsw->dsw_pDirectSound, NULL ); + if( hr != DS_OK ) return hr; + return hr; +} + +/************************************************************************************/ +HRESULT DSW_InitOutputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, int nChannels, int bytesPerBuffer ) +{ + DWORD dwDataLen; + DWORD playCursor; + HRESULT result; + LPDIRECTSOUNDBUFFER pPrimaryBuffer; + HWND hWnd; + HRESULT hr; + WAVEFORMATEX wfFormat; + DSBUFFERDESC primaryDesc; + DSBUFFERDESC secondaryDesc; + unsigned char* pDSBuffData; + LARGE_INTEGER counterFrequency; + + dsw->dsw_OutputSize = bytesPerBuffer; + dsw->dsw_OutputRunning = FALSE; + dsw->dsw_OutputUnderflows = 0; + dsw->dsw_FramesWritten = 0; + dsw->dsw_BytesPerOutputFrame = nChannels * sizeof(short); + + // We were using getForegroundWindow() but sometimes the ForegroundWindow may not be the + // applications's window. Also if that window is closed before the Buffer is closed + // then DirectSound can crash. (Thanks for Scott Patterson for reporting this.) + // So we will use GetDesktopWindow() which was suggested by Miller Puckette. + // hWnd = GetForegroundWindow(); + hWnd = GetDesktopWindow(); + + // Set cooperative level to DSSCL_EXCLUSIVE so that we can get 16 bit output, 44.1 KHz. + // Exclusize also prevents unexpected sounds from other apps during a performance. + if ((hr = IDirectSound_SetCooperativeLevel( dsw->dsw_pDirectSound, + hWnd, DSSCL_EXCLUSIVE)) != DS_OK) + { + return hr; + } + + // ----------------------------------------------------------------------- + // Create primary buffer and set format just so we can specify our custom format. + // Otherwise we would be stuck with the default which might be 8 bit or 22050 Hz. + // Setup the primary buffer description + ZeroMemory(&primaryDesc, sizeof(DSBUFFERDESC)); + primaryDesc.dwSize = sizeof(DSBUFFERDESC); + primaryDesc.dwFlags = DSBCAPS_PRIMARYBUFFER; // all panning, mixing, etc done by synth + primaryDesc.dwBufferBytes = 0; + primaryDesc.lpwfxFormat = NULL; + // Create the buffer + if ((result = IDirectSound_CreateSoundBuffer( dsw->dsw_pDirectSound, + &primaryDesc, &pPrimaryBuffer, NULL)) != DS_OK) return result; + // Define the buffer format + wfFormat.wFormatTag = WAVE_FORMAT_PCM; + wfFormat.nChannels = nChannels; + wfFormat.nSamplesPerSec = nFrameRate; + wfFormat.wBitsPerSample = 8 * sizeof(short); + wfFormat.nBlockAlign = wfFormat.nChannels * wfFormat.wBitsPerSample / 8; + wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign; + wfFormat.cbSize = 0; /* No extended format info. */ + // Set the primary buffer's format + if((result = IDirectSoundBuffer_SetFormat( pPrimaryBuffer, &wfFormat)) != DS_OK) return result; + + // ---------------------------------------------------------------------- + // Setup the secondary buffer description + ZeroMemory(&secondaryDesc, sizeof(DSBUFFERDESC)); + secondaryDesc.dwSize = sizeof(DSBUFFERDESC); + secondaryDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; + secondaryDesc.dwBufferBytes = bytesPerBuffer; + secondaryDesc.lpwfxFormat = &wfFormat; + // Create the secondary buffer + if ((result = IDirectSound_CreateSoundBuffer( dsw->dsw_pDirectSound, + &secondaryDesc, &dsw->dsw_OutputBuffer, NULL)) != DS_OK) return result; + // Lock the DS buffer + if ((result = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, 0, dsw->dsw_OutputSize, (LPVOID*)&pDSBuffData, + &dwDataLen, NULL, 0, 0)) != DS_OK) return result; + // Zero the DS buffer + ZeroMemory(pDSBuffData, dwDataLen); + // Unlock the DS buffer + if ((result = IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, pDSBuffData, dwDataLen, NULL, 0)) != DS_OK) return result; + if( QueryPerformanceFrequency( &counterFrequency ) ) + { + int framesInBuffer = bytesPerBuffer / (nChannels * sizeof(short)); + dsw->dsw_CounterTicksPerBuffer.QuadPart = (counterFrequency.QuadPart * framesInBuffer) / nFrameRate; + } + else + { + dsw->dsw_CounterTicksPerBuffer.QuadPart = 0; + } + // Let DSound set the starting write position because if we set it to zero, it looks like the + // buffer is full to begin with. This causes a long pause before sound starts when using large buffers. + hr = IDirectSoundBuffer_GetCurrentPosition( dsw->dsw_OutputBuffer, &playCursor, &dsw->dsw_WriteOffset ); + if( hr != DS_OK ) + { + return hr; + } + dsw->dsw_FramesWritten = dsw->dsw_WriteOffset / dsw->dsw_BytesPerOutputFrame; + /* printf("DSW_InitOutputBuffer: playCursor = %d, writeCursor = %d\n", playCursor, dsw->dsw_WriteOffset ); */ + return DS_OK; +} + +/************************************************************************************/ +HRESULT DSW_StartOutput( DSoundWrapper *dsw ) +{ + HRESULT hr; + QueryPerformanceCounter( &dsw->dsw_LastPlayTime ); + dsw->dsw_LastPlayCursor = 0; + dsw->dsw_FramesPlayed = 0; + hr = IDirectSoundBuffer_SetCurrentPosition( dsw->dsw_OutputBuffer, 0 ); + if( hr != DS_OK ) + { + return hr; + } + // Start the buffer playback in a loop. + if( dsw->dsw_OutputBuffer != NULL ) + { + hr = IDirectSoundBuffer_Play( dsw->dsw_OutputBuffer, 0, 0, DSBPLAY_LOOPING ); + if( hr != DS_OK ) + { + return hr; + } + dsw->dsw_OutputRunning = TRUE; + } + + return 0; +} +/************************************************************************************/ +HRESULT DSW_StopOutput( DSoundWrapper *dsw ) +{ + // Stop the buffer playback + if( dsw->dsw_OutputBuffer != NULL ) + { + dsw->dsw_OutputRunning = FALSE; + return IDirectSoundBuffer_Stop( dsw->dsw_OutputBuffer ); + } + else return 0; +} + +/************************************************************************************/ +HRESULT DSW_QueryOutputFilled( DSoundWrapper *dsw, long *bytesFilledPtr ) +{ + HRESULT hr; + DWORD playCursor; + DWORD writeCursor; + long bytesFilled; + // Query to see where play position is. + // We don't need the writeCursor but sometimes DirectSound doesn't handle NULLS correctly + // so let's pass a pointer just to be safe. + hr = IDirectSoundBuffer_GetCurrentPosition( dsw->dsw_OutputBuffer, &playCursor, &writeCursor ); + if( hr != DS_OK ) + { + return hr; + } + bytesFilled = dsw->dsw_WriteOffset - playCursor; + if( bytesFilled < 0 ) bytesFilled += dsw->dsw_OutputSize; // unwrap offset + *bytesFilledPtr = bytesFilled; + return hr; +} + +/************************************************************************************ + * Determine how much space can be safely written to in DS buffer. + * Detect underflows and overflows. + * Does not allow writing into safety gap maintained by DirectSound. + */ +HRESULT DSW_QueryOutputSpace( DSoundWrapper *dsw, long *bytesEmpty ) +{ + HRESULT hr; + DWORD playCursor; + DWORD writeCursor; + long numBytesEmpty; + long playWriteGap; + // Query to see how much room is in buffer. + hr = IDirectSoundBuffer_GetCurrentPosition( dsw->dsw_OutputBuffer, &playCursor, &writeCursor ); + if( hr != DS_OK ) + { + return hr; + } + // Determine size of gap between playIndex and WriteIndex that we cannot write into. + playWriteGap = writeCursor - playCursor; + if( playWriteGap < 0 ) playWriteGap += dsw->dsw_OutputSize; // unwrap + /* DirectSound doesn't have a large enough playCursor so we cannot detect wrap-around. */ + /* Attempt to detect playCursor wrap-around and correct it. */ + if( dsw->dsw_OutputRunning && (dsw->dsw_CounterTicksPerBuffer.QuadPart != 0) ) + { + /* How much time has elapsed since last check. */ + LARGE_INTEGER currentTime; + LARGE_INTEGER elapsedTime; + long bytesPlayed; + long bytesExpected; + long buffersWrapped; + QueryPerformanceCounter( ¤tTime ); + elapsedTime.QuadPart = currentTime.QuadPart - dsw->dsw_LastPlayTime.QuadPart; + dsw->dsw_LastPlayTime = currentTime; + /* How many bytes does DirectSound say have been played. */ + bytesPlayed = playCursor - dsw->dsw_LastPlayCursor; + if( bytesPlayed < 0 ) bytesPlayed += dsw->dsw_OutputSize; // unwrap + dsw->dsw_LastPlayCursor = playCursor; + /* Calculate how many bytes we would have expected to been played by now. */ + bytesExpected = (long) ((elapsedTime.QuadPart * dsw->dsw_OutputSize) / dsw->dsw_CounterTicksPerBuffer.QuadPart); + buffersWrapped = (bytesExpected - bytesPlayed) / dsw->dsw_OutputSize; + if( buffersWrapped > 0 ) + { + playCursor += (buffersWrapped * dsw->dsw_OutputSize); + bytesPlayed += (buffersWrapped * dsw->dsw_OutputSize); + } + /* Maintain frame output cursor. */ + dsw->dsw_FramesPlayed += (bytesPlayed / dsw->dsw_BytesPerOutputFrame); + } + numBytesEmpty = playCursor - dsw->dsw_WriteOffset; + if( numBytesEmpty < 0 ) numBytesEmpty += dsw->dsw_OutputSize; // unwrap offset + /* Have we underflowed? */ + if( numBytesEmpty > (dsw->dsw_OutputSize - playWriteGap) ) + { + if( dsw->dsw_OutputRunning ) + { + dsw->dsw_OutputUnderflows += 1; + } + dsw->dsw_WriteOffset = writeCursor; + numBytesEmpty = dsw->dsw_OutputSize - playWriteGap; + } + *bytesEmpty = numBytesEmpty; + return hr; +} + +/************************************************************************************/ +HRESULT DSW_ZeroEmptySpace( DSoundWrapper *dsw ) +{ + HRESULT hr; + LPBYTE lpbuf1 = NULL; + LPBYTE lpbuf2 = NULL; + DWORD dwsize1 = 0; + DWORD dwsize2 = 0; + long bytesEmpty; + hr = DSW_QueryOutputSpace( dsw, &bytesEmpty ); // updates dsw_FramesPlayed + if (hr != DS_OK) return hr; + if( bytesEmpty == 0 ) return DS_OK; + // Lock free space in the DS + hr = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, dsw->dsw_WriteOffset, bytesEmpty, (void **) &lpbuf1, &dwsize1, + (void **) &lpbuf2, &dwsize2, 0); + if (hr == DS_OK) + { + // Copy the buffer into the DS + ZeroMemory(lpbuf1, dwsize1); + if(lpbuf2 != NULL) + { + ZeroMemory(lpbuf2, dwsize2); + } + // Update our buffer offset and unlock sound buffer + dsw->dsw_WriteOffset = (dsw->dsw_WriteOffset + dwsize1 + dwsize2) % dsw->dsw_OutputSize; + IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2); + dsw->dsw_FramesWritten += bytesEmpty / dsw->dsw_BytesPerOutputFrame; + } + return hr; +} + +/************************************************************************************/ +HRESULT DSW_WriteBlock( DSoundWrapper *dsw, char *buf, long numBytes ) +{ + HRESULT hr; + LPBYTE lpbuf1 = NULL; + LPBYTE lpbuf2 = NULL; + DWORD dwsize1 = 0; + DWORD dwsize2 = 0; + // Lock free space in the DS + hr = IDirectSoundBuffer_Lock( dsw->dsw_OutputBuffer, dsw->dsw_WriteOffset, numBytes, (void **) &lpbuf1, &dwsize1, + (void **) &lpbuf2, &dwsize2, 0); + if (hr == DS_OK) + { + // Copy the buffer into the DS + CopyMemory(lpbuf1, buf, dwsize1); + if(lpbuf2 != NULL) + { + CopyMemory(lpbuf2, buf+dwsize1, dwsize2); + } + // Update our buffer offset and unlock sound buffer + dsw->dsw_WriteOffset = (dsw->dsw_WriteOffset + dwsize1 + dwsize2) % dsw->dsw_OutputSize; + IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2); + dsw->dsw_FramesWritten += numBytes / dsw->dsw_BytesPerOutputFrame; + } + return hr; +} + +/************************************************************************************/ +DWORD DSW_GetOutputStatus( DSoundWrapper *dsw ) +{ + DWORD status; + if (IDirectSoundBuffer_GetStatus( dsw->dsw_OutputBuffer, &status ) != DS_OK) + return( DSERR_INVALIDPARAM ); + else + return( status ); +} + +/* These routines are used to support audio input. + * Do NOT compile these calls when using NT4 because it does + * not support the entry points. + */ +/************************************************************************************/ +HRESULT DSW_InitInputDevice( DSoundWrapper *dsw, LPGUID lpGUID ) +{ + HRESULT hr = dswDSoundEntryPoints.DirectSoundCaptureCreate( lpGUID, &dsw->dsw_pDirectSoundCapture, NULL ); + if( hr != DS_OK ) return hr; + return hr; +} +/************************************************************************************/ +HRESULT DSW_InitInputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, int nChannels, int bytesPerBuffer ) +{ + DSCBUFFERDESC captureDesc; + WAVEFORMATEX wfFormat; + HRESULT result; + + dsw->dsw_BytesPerInputFrame = nChannels * sizeof(short); + + // Define the buffer format + wfFormat.wFormatTag = WAVE_FORMAT_PCM; + wfFormat.nChannels = nChannels; + wfFormat.nSamplesPerSec = nFrameRate; + wfFormat.wBitsPerSample = 8 * sizeof(short); + wfFormat.nBlockAlign = wfFormat.nChannels * (wfFormat.wBitsPerSample / 8); + wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign; + wfFormat.cbSize = 0; /* No extended format info. */ + dsw->dsw_InputSize = bytesPerBuffer; + // ---------------------------------------------------------------------- + // Setup the secondary buffer description + ZeroMemory(&captureDesc, sizeof(DSCBUFFERDESC)); + captureDesc.dwSize = sizeof(DSCBUFFERDESC); + captureDesc.dwFlags = 0; + captureDesc.dwBufferBytes = bytesPerBuffer; + captureDesc.lpwfxFormat = &wfFormat; + // Create the capture buffer + if ((result = IDirectSoundCapture_CreateCaptureBuffer( dsw->dsw_pDirectSoundCapture, + &captureDesc, &dsw->dsw_InputBuffer, NULL)) != DS_OK) return result; + dsw->dsw_ReadOffset = 0; // reset last read position to start of buffer + return DS_OK; +} + +/************************************************************************************/ +HRESULT DSW_StartInput( DSoundWrapper *dsw ) +{ + // Start the buffer playback + if( dsw->dsw_InputBuffer != NULL ) + { + return IDirectSoundCaptureBuffer_Start( dsw->dsw_InputBuffer, DSCBSTART_LOOPING ); + } + else return 0; +} + +/************************************************************************************/ +HRESULT DSW_StopInput( DSoundWrapper *dsw ) +{ + // Stop the buffer playback + if( dsw->dsw_InputBuffer != NULL ) + { + return IDirectSoundCaptureBuffer_Stop( dsw->dsw_InputBuffer ); + } + else return 0; +} + +/************************************************************************************/ +HRESULT DSW_QueryInputFilled( DSoundWrapper *dsw, long *bytesFilled ) +{ + HRESULT hr; + DWORD capturePos; + DWORD readPos; + long filled; + // Query to see how much data is in buffer. + // We don't need the capture position but sometimes DirectSound doesn't handle NULLS correctly + // so let's pass a pointer just to be safe. + hr = IDirectSoundCaptureBuffer_GetCurrentPosition( dsw->dsw_InputBuffer, &capturePos, &readPos ); + if( hr != DS_OK ) + { + return hr; + } + filled = readPos - dsw->dsw_ReadOffset; + if( filled < 0 ) filled += dsw->dsw_InputSize; // unwrap offset + *bytesFilled = filled; + return hr; +} + +/************************************************************************************/ +HRESULT DSW_ReadBlock( DSoundWrapper *dsw, char *buf, long numBytes ) +{ + HRESULT hr; + LPBYTE lpbuf1 = NULL; + LPBYTE lpbuf2 = NULL; + DWORD dwsize1 = 0; + DWORD dwsize2 = 0; + // Lock free space in the DS + hr = IDirectSoundCaptureBuffer_Lock ( dsw->dsw_InputBuffer, dsw->dsw_ReadOffset, numBytes, (void **) &lpbuf1, &dwsize1, + (void **) &lpbuf2, &dwsize2, 0); + if (hr == DS_OK) + { + // Copy from DS to the buffer + CopyMemory( buf, lpbuf1, dwsize1); + if(lpbuf2 != NULL) + { + CopyMemory( buf+dwsize1, lpbuf2, dwsize2); + } + // Update our buffer offset and unlock sound buffer + dsw->dsw_ReadOffset = (dsw->dsw_ReadOffset + dwsize1 + dwsize2) % dsw->dsw_InputSize; + IDirectSoundCaptureBuffer_Unlock ( dsw->dsw_InputBuffer, lpbuf1, dwsize1, lpbuf2, dwsize2); + } + return hr; +} + diff --git a/pd/portaudio/pa_win_ds/dsound_wrapper.h b/pd/portaudio/pa_win_ds/dsound_wrapper.h new file mode 100644 index 00000000..e9ce4c6b --- /dev/null +++ b/pd/portaudio/pa_win_ds/dsound_wrapper.h @@ -0,0 +1,129 @@ +#ifndef __DSOUND_WRAPPER_H +#define __DSOUND_WRAPPER_H +/* + * $Id: dsound_wrapper.h,v 1.1.1.1.2.5 2002/07/03 01:43:56 rossbencina Exp $ + * Simplified DirectSound interface. + * + * Author: Phil Burk & Robert Marsanyi + * + * For PortAudio Portable Real-Time Audio Library + * For more information see: http://www.softsynth.com/portaudio/ + * DirectSound Implementation + * Copyright (c) 1999-2000 Phil Burk & Robert Marsanyi + * + * 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. + * + */ + +/* on Borland compilers, WIN32 doesn't seem to be defined by default, which + breaks DSound.h. Adding the define here fixes the problem. - rossb. */ +#ifdef __BORLANDC__ +#if !defined(WIN32) +#define WIN32 +#endif +#endif + +#include <DSound.h> +#if !defined(BOOL) +#define BOOL short +#endif + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + +typedef struct +{ + HINSTANCE hInstance_; + + HRESULT (WINAPI *DirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN); + HRESULT (WINAPI *DirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID); + HRESULT (WINAPI *DirectSoundEnumerateA)(LPDSENUMCALLBACKA, LPVOID); + + HRESULT (WINAPI *DirectSoundCaptureCreate)(LPGUID, LPDIRECTSOUNDCAPTURE *, LPUNKNOWN); + HRESULT (WINAPI *DirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID); + HRESULT (WINAPI *DirectSoundCaptureEnumerateA)(LPDSENUMCALLBACKA, LPVOID); +}DSoundEntryPoints; + +extern DSoundEntryPoints dswDSoundEntryPoints; + +void DSW_InitializeDSoundEntryPoints(void); +void DSW_TerminateDSoundEntryPoints(void); + +#define DSW_NUM_POSITIONS (4) +#define DSW_NUM_EVENTS (5) +#define DSW_TERMINATION_EVENT (DSW_NUM_POSITIONS) + +typedef struct +{ +/* Output */ + LPDIRECTSOUND dsw_pDirectSound; + LPDIRECTSOUNDBUFFER dsw_OutputBuffer; + DWORD dsw_WriteOffset; /* last write position */ + INT dsw_OutputSize; + INT dsw_BytesPerOutputFrame; + /* Try to detect play buffer underflows. */ + LARGE_INTEGER dsw_CounterTicksPerBuffer; /* counter ticks it should take to play a full buffer */ + LARGE_INTEGER dsw_LastPlayTime; + UINT dsw_LastPlayCursor; + UINT dsw_OutputUnderflows; + BOOL dsw_OutputRunning; + /* use double which lets us can play for several thousand years with enough precision */ + double dsw_FramesWritten; + double dsw_FramesPlayed; +/* Input */ + INT dsw_BytesPerInputFrame; + LPDIRECTSOUNDCAPTURE dsw_pDirectSoundCapture; + LPDIRECTSOUNDCAPTUREBUFFER dsw_InputBuffer; + UINT dsw_ReadOffset; /* last read position */ + UINT dsw_InputSize; +} DSoundWrapper; + +HRESULT DSW_Init( DSoundWrapper *dsw ); +void DSW_Term( DSoundWrapper *dsw ); +HRESULT DSW_InitOutputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, + int nChannels, int bufSize ); +HRESULT DSW_StartOutput( DSoundWrapper *dsw ); +HRESULT DSW_StopOutput( DSoundWrapper *dsw ); +DWORD DSW_GetOutputStatus( DSoundWrapper *dsw ); +HRESULT DSW_WriteBlock( DSoundWrapper *dsw, char *buf, long numBytes ); +HRESULT DSW_ZeroEmptySpace( DSoundWrapper *dsw ); +HRESULT DSW_QueryOutputSpace( DSoundWrapper *dsw, long *bytesEmpty ); +HRESULT DSW_Enumerate( DSoundWrapper *dsw ); + +HRESULT DSW_InitInputBuffer( DSoundWrapper *dsw, unsigned long nFrameRate, + int nChannels, int bufSize ); +HRESULT DSW_StartInput( DSoundWrapper *dsw ); +HRESULT DSW_StopInput( DSoundWrapper *dsw ); +HRESULT DSW_ReadBlock( DSoundWrapper *dsw, char *buf, long numBytes ); +HRESULT DSW_QueryInputFilled( DSoundWrapper *dsw, long *bytesFilled ); +HRESULT DSW_QueryOutputFilled( DSoundWrapper *dsw, long *bytesFilled ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __DSOUND_WRAPPER_H */ diff --git a/pd/portaudio/pa_win_ds/pa_dsound.c b/pd/portaudio/pa_win_ds/pa_dsound.c new file mode 100644 index 00000000..8fa343cf --- /dev/null +++ b/pd/portaudio/pa_win_ds/pa_dsound.c @@ -0,0 +1,1021 @@ +/* + * $Id: pa_dsound.c,v 1.1.1.1 2003-05-09 16:03:58 ggeiger Exp $ + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.softsynth.com/portaudio/ + * DirectSound Implementation + * + * Copyright (c) 1999-2000 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. + * + */ +/* Modifications + * 7/19/01 Mike Berry - casts for compiling with __MWERKS__ CodeWarrior + * 9/27/01 Phil Burk - use number of frames instead of real-time for CPULoad calculation. + * 4/19/02 Phil Burk - Check for Win XP for system latency calculation. + */ +/* Compiler flags: + SUPPORT_AUDIO_CAPTURE - define this flag if you want to SUPPORT_AUDIO_CAPTURE + */ + +#include <stdio.h> +#include <stdlib.h> +#ifndef __MWERKS__ +#include <malloc.h> +#include <memory.h> +#endif //__MWERKS__ +#include <math.h> +#include "portaudio.h" +#include "pa_host.h" +#include "pa_trace.h" +#include "dsound_wrapper.h" + +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) */ + +#define PA_USE_HIGH_LATENCY (0) +#if PA_USE_HIGH_LATENCY +#define PA_WIN_9X_LATENCY (500) +#define PA_WIN_NT_LATENCY (600) +#else +#define PA_WIN_9X_LATENCY (140) +#define PA_WIN_NT_LATENCY (280) +#endif + +#define PA_WIN_WDM_LATENCY (120) + +/* Trigger an underflow for testing purposes. Should normally be (0). */ +#define PA_SIMULATE_UNDERFLOW (0) +#if PA_SIMULATE_UNDERFLOW +static gUnderCallbackCounter = 0; +#define UNDER_START_GAP (10) +#define UNDER_STOP_GAP (UNDER_START_GAP + 4) +#endif + +/************************************************* Definitions ********/ +typedef struct internalPortAudioStream internalPortAudioStream; +typedef struct internalPortAudioDevice +{ + GUID pad_GUID; + GUID *pad_lpGUID; + double pad_SampleRates[10]; /* for pointing to from pad_Info FIXME?!*/ + PaDeviceInfo pad_Info; +} +internalPortAudioDevice; + +/* Define structure to contain all DirectSound and Windows specific data. */ +typedef struct PaHostSoundControl +{ + DSoundWrapper pahsc_DSoundWrapper; + MMRESULT pahsc_TimerID; + BOOL pahsc_IfInsideCallback; /* Test for reentrancy. */ + short *pahsc_NativeBuffer; + unsigned int pahsc_BytesPerBuffer; /* native buffer size in bytes */ + double pahsc_ValidFramesWritten; + int pahsc_FramesPerDSBuffer; + /* For measuring CPU utilization. */ + LARGE_INTEGER pahsc_EntryCount; + double pahsc_InverseTicksPerUserBuffer; +} +PaHostSoundControl; + +/************************************************* Shared Data ********/ +/* FIXME - put Mutex around this shared data. */ +static int sNumDevices = 0; +static int sDeviceIndex = 0; +static internalPortAudioDevice *sDevices = NULL; +static int sDefaultInputDeviceID = paNoDevice; +static int sDefaultOutputDeviceID = paNoDevice; +static int sEnumerationError; +static int sPaHostError = 0; +/************************************************* Prototypes **********/ +static internalPortAudioDevice *Pa_GetInternalDevice( PaDeviceID id ); +static BOOL CALLBACK Pa_EnumProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ); +static BOOL CALLBACK Pa_CountDevProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ); +static Pa_QueryDevices( void ); +static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, + DWORD dwUser, DWORD dw1, DWORD dw2); + +/********************************* BEGIN CPU UTILIZATION MEASUREMENT ****/ +static void Pa_StartUsageCalculation( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + /* Query system timer for usage analysis and to prevent overuse of CPU. */ + QueryPerformanceCounter( &pahsc->pahsc_EntryCount ); +} + +static void Pa_EndUsageCalculation( internalPortAudioStream *past ) +{ + LARGE_INTEGER CurrentCount = { 0, 0 }; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == 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 - pahsc->pahsc_EntryCount.QuadPart; + double newUsage = InsideCount * pahsc->pahsc_InverseTicksPerUserBuffer; + past->past_Usage = (LOWPASS_COEFFICIENT_0 * past->past_Usage) + + (LOWPASS_COEFFICIENT_1 * newUsage); + } +} + +/****************************************** END CPU UTILIZATION *******/ +static PaError Pa_QueryDevices( void ) +{ + int numBytes; + sDefaultInputDeviceID = paNoDevice; + sDefaultOutputDeviceID = paNoDevice; + /* Enumerate once just to count devices. */ + sNumDevices = 0; // for default device + DirectSoundEnumerate( (LPDSENUMCALLBACK)Pa_CountDevProc, NULL ); +#if SUPPORT_AUDIO_CAPTURE + DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)Pa_CountDevProc, NULL ); +#endif /* SUPPORT_AUDIO_CAPTURE */ + /* Allocate structures to hold device info. */ + numBytes = sNumDevices * sizeof(internalPortAudioDevice); + sDevices = (internalPortAudioDevice *)PaHost_AllocateFastMemory( numBytes ); /* MEM */ + if( sDevices == NULL ) return paInsufficientMemory; + /* Enumerate again to fill in structures. */ + sDeviceIndex = 0; + sEnumerationError = 0; + DirectSoundEnumerate( (LPDSENUMCALLBACK)Pa_EnumProc, (void *)0 ); +#if SUPPORT_AUDIO_CAPTURE + if( sEnumerationError != paNoError ) return sEnumerationError; + sEnumerationError = 0; + DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)Pa_EnumProc, (void *)1 ); +#endif /* SUPPORT_AUDIO_CAPTURE */ + return sEnumerationError; +} +/************************************************************************************/ +long Pa_GetHostError() +{ + return sPaHostError; +} +/************************************************************************************ +** Just count devices so we know how much memory to allocate. +*/ +static BOOL CALLBACK Pa_CountDevProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ) +{ + sNumDevices++; + return TRUE; +} +/************************************************************************************ +** Extract capabilities info from each device. +*/ +static BOOL CALLBACK Pa_EnumProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ) +{ + HRESULT hr; + LPDIRECTSOUND lpDirectSound; +#if SUPPORT_AUDIO_CAPTURE + LPDIRECTSOUNDCAPTURE lpDirectSoundCapture; +#endif /* SUPPORT_AUDIO_CAPTURE */ + int isInput = (int) lpContext; /* Passed from Pa_CountDevices() */ + internalPortAudioDevice *pad; + + if( sDeviceIndex >= sNumDevices ) + { + sEnumerationError = paInternalError; + return FALSE; + } + pad = &sDevices[sDeviceIndex]; + /* Copy GUID to static array. Set pointer. */ + if( lpGUID == NULL ) + { + pad->pad_lpGUID = NULL; + } + else + { + memcpy( &pad->pad_GUID, lpGUID, sizeof(GUID) ); + pad->pad_lpGUID = &pad->pad_GUID; + } + pad->pad_Info.sampleRates = pad->pad_SampleRates; /* Point to array. */ + /* Allocate room for descriptive name. */ + if( lpszDesc != NULL ) + { + int len = strlen(lpszDesc); + pad->pad_Info.name = (char *)malloc( len+1 ); + if( pad->pad_Info.name == NULL ) + { + sEnumerationError = paInsufficientMemory; + return FALSE; + } + memcpy( (void *) pad->pad_Info.name, lpszDesc, len+1 ); + } +#if SUPPORT_AUDIO_CAPTURE + if( isInput ) + { + /********** Input ******************************/ + DSCCAPS caps; + if( lpGUID == NULL ) sDefaultInputDeviceID = sDeviceIndex; + hr = DirectSoundCaptureCreate( lpGUID, &lpDirectSoundCapture, NULL ); + if( hr != DS_OK ) + { + pad->pad_Info.maxInputChannels = 0; + DBUG(("Cannot create Capture for %s. Result = 0x%x\n", lpszDesc, hr )); + } + else + { + /* Query device characteristics. */ + caps.dwSize = sizeof(caps); + IDirectSoundCapture_GetCaps( lpDirectSoundCapture, &caps ); + /* printf("caps.dwFormats = 0x%x\n", caps.dwFormats ); */ + pad->pad_Info.maxInputChannels = caps.dwChannels; + /* Determine sample rates from flags. */ + if( caps.dwChannels == 2 ) + { + int index = 0; + if( caps.dwFormats & WAVE_FORMAT_1S16) pad->pad_SampleRates[index++] = 11025.0; + if( caps.dwFormats & WAVE_FORMAT_2S16) pad->pad_SampleRates[index++] = 22050.0; + if( caps.dwFormats & WAVE_FORMAT_4S16) pad->pad_SampleRates[index++] = 44100.0; + pad->pad_Info.numSampleRates = index; + } + else if( caps.dwChannels == 1 ) + { + int index = 0; + if( caps.dwFormats & WAVE_FORMAT_1M16) pad->pad_SampleRates[index++] = 11025.0; + if( caps.dwFormats & WAVE_FORMAT_2M16) pad->pad_SampleRates[index++] = 22050.0; + if( caps.dwFormats & WAVE_FORMAT_4M16) pad->pad_SampleRates[index++] = 44100.0; + pad->pad_Info.numSampleRates = index; + } + else pad->pad_Info.numSampleRates = 0; + IDirectSoundCapture_Release( lpDirectSoundCapture ); + } + } + else +#endif /* SUPPORT_AUDIO_CAPTURE */ + + { + /********** Output ******************************/ + DSCAPS caps; + if( lpGUID == NULL ) sDefaultOutputDeviceID = sDeviceIndex; + /* Create interfaces for each object. */ + hr = DirectSoundCreate( lpGUID, &lpDirectSound, NULL ); + if( hr != DS_OK ) + { + pad->pad_Info.maxOutputChannels = 0; + DBUG(("Cannot create dsound for %s. Result = 0x%x\n", lpszDesc, hr )); + } + else + { + /* Query device characteristics. */ + caps.dwSize = sizeof(caps); + IDirectSound_GetCaps( lpDirectSound, &caps ); + pad->pad_Info.maxOutputChannels = ( caps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; + /* Get sample rates. */ + pad->pad_SampleRates[0] = (double) caps.dwMinSecondarySampleRate; + pad->pad_SampleRates[1] = (double) caps.dwMaxSecondarySampleRate; + if( caps.dwFlags & DSCAPS_CONTINUOUSRATE ) pad->pad_Info.numSampleRates = -1; + else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate ) + { + if( caps.dwMinSecondarySampleRate == 0 ) + { + /* + ** On my Thinkpad 380Z, DirectSoundV6 returns min-max=0 !! + ** But it supports continuous sampling. + ** So fake range of rates, and hope it really supports it. + */ + pad->pad_SampleRates[0] = 11025.0f; + pad->pad_SampleRates[1] = 48000.0f; + pad->pad_Info.numSampleRates = -1; /* continuous range */ + + DBUG(("PA - Reported rates both zero. Setting to fake values for device #%d\n", sDeviceIndex )); + } + else + { + pad->pad_Info.numSampleRates = 1; + } + } + else if( (caps.dwMinSecondarySampleRate < 1000.0) && (caps.dwMaxSecondarySampleRate > 50000.0) ) + { + /* The EWS88MT drivers lie, lie, lie. The say they only support two rates, 100 & 100000. + ** But we know that they really support a range of rates! + ** So when we see a ridiculous set of rates, assume it is a range. + */ + pad->pad_Info.numSampleRates = -1; + DBUG(("PA - Sample rate range used instead of two odd values for device #%d\n", sDeviceIndex )); + } + else pad->pad_Info.numSampleRates = 2; + IDirectSound_Release( lpDirectSound ); + } + } + pad->pad_Info.nativeSampleFormats = paInt16; + sDeviceIndex++; + return( TRUE ); +} +/*************************************************************************/ +int Pa_CountDevices() +{ + if( sNumDevices <= 0 ) Pa_Initialize(); + return sNumDevices; +} +static internalPortAudioDevice *Pa_GetInternalDevice( PaDeviceID id ) +{ + if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL; + return &sDevices[id]; +} +/*************************************************************************/ +const PaDeviceInfo* Pa_GetDeviceInfo( PaDeviceID id ) +{ + internalPortAudioDevice *pad; + if( (id < 0) || ( id >= Pa_CountDevices()) ) return NULL; + pad = Pa_GetInternalDevice( id ); + return &pad->pad_Info ; +} +static PaError Pa_MaybeQueryDevices( void ) +{ + if( sNumDevices == 0 ) + { + return Pa_QueryDevices(); + } + return 0; +} +/************************************************************************* +** 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; +} +PaDeviceID Pa_GetDefaultInputDeviceID( void ) +{ + PaError result; + result = PaHost_GetEnvDefaultDeviceID( PA_REC_IN_DEV_ENV_NAME ); + if( result < 0 ) + { + result = Pa_MaybeQueryDevices(); + if( result < 0 ) return result; + result = sDefaultInputDeviceID; + } + return result; +} +PaDeviceID Pa_GetDefaultOutputDeviceID( void ) +{ + PaError result; + result = PaHost_GetEnvDefaultDeviceID( PA_REC_OUT_DEV_ENV_NAME ); + if( result < 0 ) + { + result = Pa_MaybeQueryDevices(); + if( result < 0 ) return result; + result = sDefaultOutputDeviceID; + } + return result; +} +/********************************************************************** +** Make sure that we have queried the device capabilities. +*/ +PaError PaHost_Init( void ) +{ +#if PA_SIMULATE_UNDERFLOW + PRINT(("WARNING - Underflow Simulation Enabled - Expect a Big Glitch!!!\n")); +#endif + return Pa_MaybeQueryDevices(); +} +static PaError Pa_TimeSlice( internalPortAudioStream *past ) +{ + PaError result = 0; + long bytesEmpty = 0; + long bytesFilled = 0; + long bytesToXfer = 0; + long numChunks; + HRESULT hresult; + PaHostSoundControl *pahsc; + short *nativeBufPtr; + past->past_NumCallbacks += 1; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + /* How much input data is available? */ +#if SUPPORT_AUDIO_CAPTURE + if( past->past_NumInputChannels > 0 ) + { + DSW_QueryInputFilled( &pahsc->pahsc_DSoundWrapper, &bytesFilled ); + bytesToXfer = bytesFilled; + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + /* How much output room is available? */ + if( past->past_NumOutputChannels > 0 ) + { + DSW_QueryOutputSpace( &pahsc->pahsc_DSoundWrapper, &bytesEmpty ); + bytesToXfer = bytesEmpty; + } + AddTraceMessage( "bytesEmpty ", bytesEmpty ); + /* Choose smallest value if both are active. */ + if( (past->past_NumInputChannels > 0) && (past->past_NumOutputChannels > 0) ) + { + bytesToXfer = ( bytesFilled < bytesEmpty ) ? bytesFilled : bytesEmpty; + } + /* printf("bytesFilled = %d, bytesEmpty = %d, bytesToXfer = %d\n", + bytesFilled, bytesEmpty, bytesToXfer); + */ + /* Quantize to multiples of a buffer. */ + numChunks = bytesToXfer / pahsc->pahsc_BytesPerBuffer; + if( numChunks > (long)(past->past_NumUserBuffers/2) ) + { + numChunks = (long)past->past_NumUserBuffers/2; + } + else if( numChunks < 0 ) + { + numChunks = 0; + } + AddTraceMessage( "numChunks ", numChunks ); + nativeBufPtr = pahsc->pahsc_NativeBuffer; + if( numChunks > 0 ) + { + while( numChunks-- > 0 ) + { + /* Measure usage based on time to process one user buffer. */ + Pa_StartUsageCalculation( past ); +#if SUPPORT_AUDIO_CAPTURE + /* Get native data from DirectSound. */ + if( past->past_NumInputChannels > 0 ) + { + hresult = DSW_ReadBlock( &pahsc->pahsc_DSoundWrapper, (char *) nativeBufPtr, pahsc->pahsc_BytesPerBuffer ); + if( hresult < 0 ) + { + ERR_RPT(("DirectSound ReadBlock failed, hresult = 0x%x\n",hresult)); + sPaHostError = hresult; + break; + } + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + /* Convert 16 bit native data to user data and call user routine. */ + result = Pa_CallConvertInt16( past, nativeBufPtr, nativeBufPtr ); + if( result != 0) break; + /* Pass native data to DirectSound. */ + if( past->past_NumOutputChannels > 0 ) + { + /* static short DEBUGHACK = 0; + DEBUGHACK += 0x0049; + nativeBufPtr[0] = DEBUGHACK; /* Make buzz to see if DirectSound still running. */ + hresult = DSW_WriteBlock( &pahsc->pahsc_DSoundWrapper, (char *) nativeBufPtr, pahsc->pahsc_BytesPerBuffer ); + if( hresult < 0 ) + { + ERR_RPT(("DirectSound WriteBlock failed, result = 0x%x\n",hresult)); + sPaHostError = hresult; + break; + } + } + Pa_EndUsageCalculation( past ); + } + } + return result; +} +/*******************************************************************/ +static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) +{ + internalPortAudioStream *past; + PaHostSoundControl *pahsc; +#if PA_SIMULATE_UNDERFLOW + gUnderCallbackCounter++; + if( (gUnderCallbackCounter >= UNDER_START_GAP) && + (gUnderCallbackCounter <= UNDER_STOP_GAP) ) + { + if( gUnderCallbackCounter == UNDER_START_GAP) + { + AddTraceMessage("Begin stall: gUnderCallbackCounter =======", gUnderCallbackCounter ); + } + if( gUnderCallbackCounter == UNDER_STOP_GAP) + { + AddTraceMessage("End stall: gUnderCallbackCounter =======", gUnderCallbackCounter ); + } + return; + } +#endif + past = (internalPortAudioStream *) dwUser; + if( past == NULL ) return; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return; + if( !pahsc->pahsc_IfInsideCallback && past->past_IsActive ) + { + if( past->past_StopNow ) + { + past->past_IsActive = 0; + } + else if( past->past_StopSoon ) + { + DSoundWrapper *dsw = &pahsc->pahsc_DSoundWrapper; + if( past->past_NumOutputChannels > 0 ) + { + DSW_ZeroEmptySpace( dsw ); + AddTraceMessage("Pa_TimerCallback: waiting - written ", (int) dsw->dsw_FramesWritten ); + AddTraceMessage("Pa_TimerCallback: waiting - played ", (int) dsw->dsw_FramesPlayed ); + /* clear past_IsActive when all sound played */ + if( dsw->dsw_FramesPlayed >= past->past_FrameCount ) + { + past->past_IsActive = 0; + } + } + else + { + past->past_IsActive = 0; + } + } + else + { + pahsc->pahsc_IfInsideCallback = 1; + if( Pa_TimeSlice( past ) != 0) /* Call time slice independant of timing method. */ + { + past->past_StopSoon = 1; + } + pahsc->pahsc_IfInsideCallback = 0; + } + } +} +/*******************************************************************/ +PaError PaHost_OpenStream( internalPortAudioStream *past ) +{ + HRESULT hr; + PaError result = paNoError; + PaHostSoundControl *pahsc; + int numBytes, maxChannels; + unsigned int minNumBuffers; + internalPortAudioDevice *pad; + DSoundWrapper *dsw; + /* Allocate and initialize host data. */ + pahsc = (PaHostSoundControl *) PaHost_AllocateFastMemory(sizeof(PaHostSoundControl)); /* MEM */ + if( pahsc == NULL ) + { + result = paInsufficientMemory; + goto error; + } + memset( pahsc, 0, sizeof(PaHostSoundControl) ); + past->past_DeviceData = (void *) pahsc; + pahsc->pahsc_TimerID = 0; + dsw = &pahsc->pahsc_DSoundWrapper; + DSW_Init( dsw ); + /* Allocate native buffer. */ + maxChannels = ( past->past_NumOutputChannels > past->past_NumInputChannels ) ? + past->past_NumOutputChannels : past->past_NumInputChannels; + pahsc->pahsc_BytesPerBuffer = past->past_FramesPerUserBuffer * maxChannels * sizeof(short); + if( maxChannels > 0 ) + { + pahsc->pahsc_NativeBuffer = (short *) PaHost_AllocateFastMemory(pahsc->pahsc_BytesPerBuffer); /* MEM */ + if( pahsc->pahsc_NativeBuffer == NULL ) + { + result = paInsufficientMemory; + goto error; + } + } + else + { + result = paInvalidChannelCount; + goto error; + } + + DBUG(("PaHost_OpenStream: pahsc_MinFramesPerHostBuffer = %d\n", pahsc->pahsc_MinFramesPerHostBuffer )); + minNumBuffers = Pa_GetMinNumBuffers( past->past_FramesPerUserBuffer, past->past_SampleRate ); + past->past_NumUserBuffers = ( minNumBuffers > past->past_NumUserBuffers ) ? minNumBuffers : past->past_NumUserBuffers; + numBytes = pahsc->pahsc_BytesPerBuffer * past->past_NumUserBuffers; + if( numBytes < DSBSIZE_MIN ) + { + result = paBufferTooSmall; + goto error; + } + if( numBytes > DSBSIZE_MAX ) + { + result = paBufferTooBig; + goto error; + } + pahsc->pahsc_FramesPerDSBuffer = past->past_FramesPerUserBuffer * past->past_NumUserBuffers; + { + int msecLatency = (int) ((pahsc->pahsc_FramesPerDSBuffer * 1000) / past->past_SampleRate); + PRINT(("PortAudio on DirectSound - Latency = %d frames, %d msec\n", pahsc->pahsc_FramesPerDSBuffer, msecLatency )); + } + /* ------------------ OUTPUT */ + if( (past->past_OutputDeviceID >= 0) && (past->past_NumOutputChannels > 0) ) + { + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", past->past_OutputDeviceID)); + pad = Pa_GetInternalDevice( past->past_OutputDeviceID ); + hr = DirectSoundCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSound, NULL ); + /* If this fails, then try each output device until we find one that works. */ + if( hr != DS_OK ) + { + int i; + ERR_RPT(("Creation of requested Audio Output device '%s' failed.\n", + ((pad->pad_lpGUID == NULL) ? "Default" : pad->pad_Info.name) )); + for( i=0; i<Pa_CountDevices(); i++ ) + { + pad = Pa_GetInternalDevice( i ); + if( pad->pad_Info.maxOutputChannels >= past->past_NumOutputChannels ) + { + DBUG(("Try device '%s' instead.\n", pad->pad_Info.name )); + hr = DirectSoundCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSound, NULL ); + if( hr == DS_OK ) + { + ERR_RPT(("Using device '%s' instead.\n", pad->pad_Info.name )); + break; + } + } + } + } + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n")); + result = paHostError; + sPaHostError = hr; + goto error; + } + hr = DSW_InitOutputBuffer( dsw, + (unsigned long) (past->past_SampleRate + 0.5), + past->past_NumOutputChannels, numBytes ); + DBUG(("DSW_InitOutputBuffer() returns %x\n", hr)); + if( hr != DS_OK ) + { + result = paHostError; + sPaHostError = hr; + goto error; + } + past->past_FrameCount = pahsc->pahsc_DSoundWrapper.dsw_FramesWritten; + } +#if SUPPORT_AUDIO_CAPTURE + /* ------------------ INPUT */ + if( (past->past_InputDeviceID >= 0) && (past->past_NumInputChannels > 0) ) + { + pad = Pa_GetInternalDevice( past->past_InputDeviceID ); + hr = DirectSoundCaptureCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSoundCapture, NULL ); + /* If this fails, then try each input device until we find one that works. */ + if( hr != DS_OK ) + { + int i; + ERR_RPT(("Creation of requested Audio Capture device '%s' failed.\n", + ((pad->pad_lpGUID == NULL) ? "Default" : pad->pad_Info.name) )); + for( i=0; i<Pa_CountDevices(); i++ ) + { + pad = Pa_GetInternalDevice( i ); + if( pad->pad_Info.maxInputChannels >= past->past_NumInputChannels ) + { + PRINT(("Try device '%s' instead.\n", pad->pad_Info.name )); + hr = DirectSoundCaptureCreate( pad->pad_lpGUID, &dsw->dsw_pDirectSoundCapture, NULL ); + if( hr == DS_OK ) break; + } + } + } + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n")); + result = paHostError; + sPaHostError = hr; + goto error; + } + hr = DSW_InitInputBuffer( dsw, + (unsigned long) (past->past_SampleRate + 0.5), + past->past_NumInputChannels, numBytes ); + DBUG(("DSW_InitInputBuffer() returns %x\n", hr)); + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DSW_InitInputBuffer() returns %x\n", hr)); + result = paHostError; + sPaHostError = hr; + goto error; + } + } +#endif /* SUPPORT_AUDIO_CAPTURE */ + /* Calculate scalar used in CPULoad calculation. */ + { + LARGE_INTEGER frequency; + if( QueryPerformanceFrequency( &frequency ) == 0 ) + { + pahsc->pahsc_InverseTicksPerUserBuffer = 0.0; + } + else + { + pahsc->pahsc_InverseTicksPerUserBuffer = past->past_SampleRate / + ( (double)frequency.QuadPart * past->past_FramesPerUserBuffer ); + DBUG(("pahsc_InverseTicksPerUserBuffer = %g\n", pahsc->pahsc_InverseTicksPerUserBuffer )); + } + } + return result; +error: + PaHost_CloseStream( past ); + return result; +} +/*************************************************************************/ +PaError PaHost_StartOutput( internalPortAudioStream *past ) +{ + HRESULT hr; + PaHostSoundControl *pahsc; + PaError result = paNoError; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + /* Give user callback a chance to pre-fill buffer. */ + result = Pa_TimeSlice( past ); + if( result != paNoError ) return result; // FIXME - what if finished? + hr = DSW_StartOutput( &pahsc->pahsc_DSoundWrapper ); + DBUG(("PaHost_StartOutput: DSW_StartOutput returned = 0x%X.\n", hr)); + if( hr != DS_OK ) + { + result = paHostError; + sPaHostError = hr; + goto error; + } +error: + return result; +} +/*************************************************************************/ +PaError PaHost_StartInput( internalPortAudioStream *past ) +{ + PaError result = paNoError; +#if SUPPORT_AUDIO_CAPTURE + HRESULT hr; + PaHostSoundControl *pahsc; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + hr = DSW_StartInput( &pahsc->pahsc_DSoundWrapper ); + DBUG(("Pa_StartStream: DSW_StartInput returned = 0x%X.\n", hr)); + if( hr != DS_OK ) + { + result = paHostError; + sPaHostError = hr; + goto error; + } +error: +#endif /* SUPPORT_AUDIO_CAPTURE */ + return result; +} +/*************************************************************************/ +PaError PaHost_StartEngine( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + PaError result = paNoError; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + past->past_StopNow = 0; + past->past_StopSoon = 0; + past->past_IsActive = 1; + /* Create timer that will wake us up so we can fill the DSound buffer. */ + { + int msecPerBuffer; + int resolution; + int bufsPerInterrupt = past->past_NumUserBuffers/4; + if( bufsPerInterrupt < 1 ) bufsPerInterrupt = 1; + msecPerBuffer = 1000 * (bufsPerInterrupt * past->past_FramesPerUserBuffer) / (int) past->past_SampleRate; + if( msecPerBuffer < 10 ) msecPerBuffer = 10; + else if( msecPerBuffer > 100 ) msecPerBuffer = 100; + resolution = msecPerBuffer/4; + pahsc->pahsc_TimerID = timeSetEvent( msecPerBuffer, resolution, (LPTIMECALLBACK) Pa_TimerCallback, + (DWORD) past, TIME_PERIODIC ); + } + if( pahsc->pahsc_TimerID == 0 ) + { + past->past_IsActive = 0; + result = paHostError; + sPaHostError = 0; + goto error; + } +error: + return result; +} +/*************************************************************************/ +PaError PaHost_StopEngine( internalPortAudioStream *past, int abort ) +{ + int timeoutMsec; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + if( abort ) past->past_StopNow = 1; + past->past_StopSoon = 1; + /* Set timeout at 20% beyond maximum time we might wait. */ + timeoutMsec = (int) (1200.0 * pahsc->pahsc_FramesPerDSBuffer / past->past_SampleRate); + while( past->past_IsActive && (timeoutMsec > 0) ) + { + Sleep(10); + timeoutMsec -= 10; + } + if( pahsc->pahsc_TimerID != 0 ) + { + timeKillEvent(pahsc->pahsc_TimerID); /* Stop callback timer. */ + pahsc->pahsc_TimerID = 0; + } + return paNoError; +} +/*************************************************************************/ +PaError PaHost_StopInput( internalPortAudioStream *past, int abort ) +{ +#if SUPPORT_AUDIO_CAPTURE + HRESULT hr; + PaHostSoundControl *pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + (void) abort; + hr = DSW_StopInput( &pahsc->pahsc_DSoundWrapper ); + DBUG(("DSW_StopInput() result is %x\n", hr)); +#endif /* SUPPORT_AUDIO_CAPTURE */ + return paNoError; +} +/*************************************************************************/ +PaError PaHost_StopOutput( internalPortAudioStream *past, int abort ) +{ + HRESULT hr; + PaHostSoundControl *pahsc; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + (void) abort; + hr = DSW_StopOutput( &pahsc->pahsc_DSoundWrapper ); + DBUG(("DSW_StopOutput() result is %x\n", hr)); + return paNoError; +} +/*******************************************************************/ +PaError PaHost_CloseStream( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paNoError; + DSW_Term( &pahsc->pahsc_DSoundWrapper ); + if( pahsc->pahsc_NativeBuffer ) + { + PaHost_FreeFastMemory( pahsc->pahsc_NativeBuffer, pahsc->pahsc_BytesPerBuffer ); /* MEM */ + pahsc->pahsc_NativeBuffer = NULL; + } + PaHost_FreeFastMemory( pahsc, sizeof(PaHostSoundControl) ); /* MEM */ + past->past_DeviceData = NULL; + return paNoError; +} + +/* Set minimal latency based on whether NT or Win95. + * NT has higher latency. + */ +static int PaHost_GetMinSystemLatency( void ) +{ + int minLatencyMsec; + /* 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; + } + return minLatencyMsec; +} + +/************************************************************************* +** 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 ); + } + else + { + minLatencyMsec = PaHost_GetMinSystemLatency(); +#if PA_USE_HIGH_LATENCY + PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec )); +#endif + + } + minBuffers = (int) (1.0 + ((double)minLatencyMsec / msecPerBuffer)); + if( minBuffers < 2 ) minBuffers = 2; + return minBuffers; +} +/*************************************************************************/ +PaError PaHost_Term( void ) +{ + int i; + /* Free names allocated during enumeration. */ + for( i=0; i<sNumDevices; i++ ) + { + if( sDevices[i].pad_Info.name != NULL ) + { + free( (void *) sDevices[i].pad_Info.name ); + sDevices[i].pad_Info.name = NULL; + } + } + if( sDevices != NULL ) + { + PaHost_FreeFastMemory( sDevices, sNumDevices * sizeof(internalPortAudioDevice) ); /* MEM */ + sDevices = NULL; + sNumDevices = 0; + } + return 0; +} +void Pa_Sleep( long msec ) +{ + Sleep( msec ); +} +/************************************************************************* + * 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 ) +{ + void *addr = GlobalAlloc( GPTR, numBytes ); /* FIXME - do we need physical memory? Use VirtualLock() */ /* MEM */ + return addr; +} +/************************************************************************* + * 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 ) +{ + if( addr != NULL ) GlobalFree( addr ); /* MEM */ +} +/***********************************************************************/ +PaError PaHost_StreamActive( internalPortAudioStream *past ) +{ + PaHostSoundControl *pahsc; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + if( pahsc == NULL ) return paInternalError; + return (PaError) (past->past_IsActive); +} +/*************************************************************************/ +PaTimestamp Pa_StreamTime( PortAudioStream *stream ) +{ + DSoundWrapper *dsw; + internalPortAudioStream *past = (internalPortAudioStream *) stream; + PaHostSoundControl *pahsc; + if( past == NULL ) return paBadStreamPtr; + pahsc = (PaHostSoundControl *) past->past_DeviceData; + dsw = &pahsc->pahsc_DSoundWrapper; + return dsw->dsw_FramesPlayed; +} diff --git a/pd/portaudio/pa_win_ds/pa_win_ds.c b/pd/portaudio/pa_win_ds/pa_win_ds.c new file mode 100644 index 00000000..b23a407e --- /dev/null +++ b/pd/portaudio/pa_win_ds/pa_win_ds.c @@ -0,0 +1,1441 @@ +/* + * $Id: pa_win_ds.c,v 1.1.2.29 2002/12/03 06:30:40 rossbencina Exp $ + * Portable Audio I/O Library DirectSound implementation + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, 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. + */ + +/** @file + + @todo implement underflow/overflow streamCallback statusFlags, paNeverDropInput. + + @todo implement host api specific extension to set i/o buffer sizes in frames + + @todo implement initialisation of PaDeviceInfo default*Latency fields (currently set to 0.) + + @todo implement ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable + + @todo audit handling of DirectSound result codes - in many cases we could convert a HRESULT into + a native portaudio error code. Standard DirectSound result codes are documented at msdn. + + @todo implement IsFormatSupported + + @todo implement PaDeviceInfo.defaultSampleRate; +*/ + +#include <stdio.h> +#include <string.h> /* strlen() */ + +#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 "dsound_wrapper.h" + +/* TODO +O- fix "patest_stop.c" +O- Handle buffer underflow, overflow, etc. +*/ + +#define PRINT(x) { printf x; fflush(stdout); } +#define ERR_RPT(x) PRINT(x) +#define DBUG(x) /* PRINT(x) */ +#define DBUGX(x) /* PRINT(x) */ + +#define PA_USE_HIGH_LATENCY (0) +#if PA_USE_HIGH_LATENCY +#define PA_WIN_9X_LATENCY (500) +#define PA_WIN_NT_LATENCY (600) +#else +#define PA_WIN_9X_LATENCY (140) +#define PA_WIN_NT_LATENCY (280) +#endif + +#define PA_WIN_WDM_LATENCY (120) + +#define SECONDS_PER_MSEC (0.001) +#define MSEC_PER_SECOND (1000) + +/* prototypes for functions declared in this file */ + +PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ); +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +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 ); +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 ); + + +/* FIXME: should convert hr to a string */ +#define PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ) \ + PaUtil_SetLastHostErrorInfo( paDirectSound, hr, "DirectSound error" ) + +/************************************************* DX Prototypes **********/ +static BOOL CALLBACK Pa_EnumOutputProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ); +static BOOL CALLBACK Pa_CountDevProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ); + +/************************************************************************************/ +/********************** Structures **************************************************/ +/************************************************************************************/ +/* PaWinDsHostApiRepresentation - host api datastructure specific to this implementation */ + +typedef struct PaWinDsDeviceInfo +{ + GUID GUID; + GUID *lpGUID; + double sampleRates[3]; +} PaWinDsDeviceInfo; + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + /* implementation specific data goes here */ + PaWinDsDeviceInfo *winDsDeviceInfos; + PaError enumerationError; + +} PaWinDsHostApiRepresentation; + +/* PaWinDsStream - a stream data structure specifically for this implementation */ + +typedef struct PaWinDsStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + +/* DirectSound specific data. */ + DSoundWrapper directSoundWrapper; + MMRESULT timerID; + BOOL ifInsideCallback; /* Test for reentrancy. */ + int framesPerDSBuffer; + double framesWritten; + double secondsPerHostByte; /* Used to optimize latency calculation for outTime */ + +/* FIXME - move all below to PaUtilStreamRepresentation */ + volatile int isStarted; + volatile int isActive; + volatile int stopProcessing; /* stop thread once existing buffers have been returned */ + volatile int abortProcessing; /* stop thread immediately */ +} PaWinDsStream; + + +/************************************************************************************ +** Just count devices so we know how much memory to allocate. +*/ +static BOOL CALLBACK Pa_CountDevProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ) +{ + int *counterPtr = (int *)lpContext; + *counterPtr += 1; + return TRUE; +} + +/************************************************************************************ +** Extract capabilities info from each device. +*/ +static BOOL CALLBACK Pa_EnumOutputProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ) +{ + HRESULT hr; + DSCAPS caps; + LPDIRECTSOUND lpDirectSound; + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation *) lpContext; + PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep; + int index = hostApi->info.deviceCount; + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[index]; + PaWinDsDeviceInfo *winDsDeviceInfo = &winDsHostApi->winDsDeviceInfos[index]; + int deviceOK = TRUE; + + /* Copy GUID to static array. Set pointer. */ + if( lpGUID == NULL ) + { + winDsDeviceInfo->lpGUID = NULL; + } + else + { + winDsDeviceInfo->lpGUID = &winDsDeviceInfo->GUID; + memcpy( &winDsDeviceInfo->GUID, lpGUID, sizeof(GUID) ); + } + + + /********** Output ******************************/ + + /* Create interfaces for each object. */ + hr = dswDSoundEntryPoints.DirectSoundCreate( lpGUID, &lpDirectSound, NULL ); + if( hr != DS_OK ) + { + deviceInfo->maxOutputChannels = 0; + DBUG(("Cannot create dsound for %s. Result = 0x%x\n", lpszDesc, hr )); + deviceOK = FALSE; + } + else + { + /* Query device characteristics. */ + caps.dwSize = sizeof(caps); + IDirectSound_GetCaps( lpDirectSound, &caps ); + +#ifndef PA_NO_WMME + if( caps.dwFlags & DSCAPS_EMULDRIVER ) + { + /* If WMME supported, then reject Emulated drivers because they are lousy. */ + deviceOK = FALSE; + } +#endif + + if( deviceOK ) + { + /* Mono or stereo device? */ + deviceInfo->maxOutputChannels = ( caps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; + + deviceInfo->defaultSampleRate = 0.; /* @todo IMPLEMENT ME */ + + /* Get sample rates. */ + /* + winDsDeviceInfo->sampleRates[0] = (double) caps.dwMinSecondarySampleRate; + winDsDeviceInfo->sampleRates[1] = (double) caps.dwMaxSecondarySampleRate; + if( caps.dwFlags & DSCAPS_CONTINUOUSRATE ) deviceInfo->numSampleRates = -1; + else if( caps.dwMinSecondarySampleRate == caps.dwMaxSecondarySampleRate ) + { + if( caps.dwMinSecondarySampleRate == 0 ) + { + // + // On my Thinkpad 380Z, DirectSoundV6 returns min-max=0 !! + // But it supports continuous sampling. + // So fake range of rates, and hope it really supports it. + // + winDsDeviceInfo->sampleRates[0] = 11025.0f; + winDsDeviceInfo->sampleRates[1] = 48000.0f; + deviceInfo->numSampleRates = -1; // continuous range + + DBUG(("PA - Reported rates both zero. Setting to fake values for device #%d\n", sDeviceIndex )); + } + else + { + deviceInfo->numSampleRates = 1; + } + } + else if( (caps.dwMinSecondarySampleRate < 1000.0) && (caps.dwMaxSecondarySampleRate > 50000.0) ) + { + // The EWS88MT drivers lie, lie, lie. The say they only support two rates, 100 & 100000. + // But we know that they really support a range of rates! + // So when we see a ridiculous set of rates, assume it is a range. + // + deviceInfo->numSampleRates = -1; + DBUG(("PA - Sample rate range used instead of two odd values for device #%d\n", sDeviceIndex )); + } + else deviceInfo->numSampleRates = 2; + */ + } + + IDirectSound_Release( lpDirectSound ); + } + + if( deviceOK ) + { + if( lpGUID == NULL ) hostApi->info.defaultOutputDevice = index; + + /* Allocate room for descriptive name. */ + if( lpszDesc != NULL ) + { + char *deviceName; + int len = strlen(lpszDesc); + deviceName = (char*)PaUtil_GroupAllocateMemory( winDsHostApi->allocations, len + 1 ); + if( !deviceName ) + { + winDsHostApi->enumerationError = paInsufficientMemory; + return FALSE; + } + memcpy( (void *) deviceName, lpszDesc, len+1 ); + deviceInfo->name = deviceName; + } + + hostApi->info.deviceCount++; + } + + return( TRUE ); +} + + +/************************************************************************************ +** Extract capabilities info from each device. +*/ +static BOOL CALLBACK Pa_EnumInputProc(LPGUID lpGUID, + LPCTSTR lpszDesc, + LPCTSTR lpszDrvName, + LPVOID lpContext ) +{ + HRESULT hr; + DSCCAPS caps; + LPDIRECTSOUNDCAPTURE lpDirectSoundCapture; + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation *) lpContext; + PaUtilHostApiRepresentation *hostApi = &winDsHostApi->inheritedHostApiRep; + int index = hostApi->info.deviceCount; + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[index]; + PaWinDsDeviceInfo *winDsDeviceInfo = &winDsHostApi->winDsDeviceInfos[index]; + int deviceOK = TRUE; + + /* Copy GUID to static array. Set pointer. */ + if( lpGUID == NULL ) + { + winDsDeviceInfo->lpGUID = NULL; + } + else + { + winDsDeviceInfo->lpGUID = &winDsDeviceInfo->GUID; + memcpy( &winDsDeviceInfo->GUID, lpGUID, sizeof(GUID) ); + } + + /********** Input ******************************/ + + hr = dswDSoundEntryPoints.DirectSoundCaptureCreate( lpGUID, &lpDirectSoundCapture, NULL ); + if( hr != DS_OK ) + { + deviceInfo->maxInputChannels = 0; + DBUG(("Cannot create Capture for %s. Result = 0x%x\n", lpszDesc, hr )); + deviceOK = FALSE; + } + else + { + /* Query device characteristics. */ + caps.dwSize = sizeof(caps); + IDirectSoundCapture_GetCaps( lpDirectSoundCapture, &caps ); + + /* printf("caps.dwFormats = 0x%x\n", caps.dwFormats ); */ + deviceInfo->maxInputChannels = caps.dwChannels; + /* Determine sample rates from flags. */ + /* + if( caps.dwChannels == 2 ) + { + int index = 0; + if( caps.dwFormats & WAVE_FORMAT_1S16) winDsDeviceInfo->sampleRates[index++] = 11025.0; + if( caps.dwFormats & WAVE_FORMAT_2S16) winDsDeviceInfo->sampleRates[index++] = 22050.0; + if( caps.dwFormats & WAVE_FORMAT_4S16) winDsDeviceInfo->sampleRates[index++] = 44100.0; + deviceInfo->numSampleRates = index; + } + else if( caps.dwChannels == 1 ) + { + int index = 0; + if( caps.dwFormats & WAVE_FORMAT_1M16) winDsDeviceInfo->sampleRates[index++] = 11025.0; + if( caps.dwFormats & WAVE_FORMAT_2M16) winDsDeviceInfo->sampleRates[index++] = 22050.0; + if( caps.dwFormats & WAVE_FORMAT_4M16) winDsDeviceInfo->sampleRates[index++] = 44100.0; + deviceInfo->numSampleRates = index; + } + else + { + deviceInfo->numSampleRates = 0; + deviceOK = FALSE; + } + */ + IDirectSoundCapture_Release( lpDirectSoundCapture ); + } + + if( deviceOK ) + { + /* Allocate room for descriptive name. */ + if( lpszDesc != NULL ) + { + char *deviceName; + int len = strlen(lpszDesc); + deviceName = (char*)PaUtil_GroupAllocateMemory( winDsHostApi->allocations, len + 1 ); + if( !deviceName ) + { + winDsHostApi->enumerationError = paInsufficientMemory; + return FALSE; + } + memcpy( (void *) deviceName, lpszDesc, len+1 ); + deviceInfo->name = deviceName; + } + + if( lpGUID == NULL ) hostApi->info.defaultInputDevice = index; + + hostApi->info.deviceCount++; + } + + return TRUE; +} + + +/***********************************************************************************/ +PaError PaWinDs_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i, deviceCount; + PaWinDsHostApiRepresentation *winDsHostApi; + PaDeviceInfo *deviceInfoArray; + + DSW_InitializeDSoundEntryPoints(); + + winDsHostApi = (PaWinDsHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinDsHostApiRepresentation) ); + if( !winDsHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + winDsHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !winDsHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + *hostApi = &winDsHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paDirectSound; + (*hostApi)->info.name = "Windows DirectSound"; + + (*hostApi)->info.deviceCount = 0; + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + + deviceCount = 0; +/* DSound - enumerate devices to count them. */ + dswDSoundEntryPoints.DirectSoundEnumerate( (LPDSENUMCALLBACK)Pa_CountDevProc, &deviceCount ); + dswDSoundEntryPoints.DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)Pa_CountDevProc, &deviceCount ); + + if( deviceCount > 0 ) + { + /* allocate array for pointers to PaDeviceInfo structs */ + (*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaDeviceInfo*) * deviceCount ); + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all PaDeviceInfo structs in a contiguous block */ + deviceInfoArray = (PaDeviceInfo*)PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaDeviceInfo) * deviceCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all DSound specific info structs in a contiguous block */ + winDsHostApi->winDsDeviceInfos = (PaWinDsDeviceInfo*)PaUtil_GroupAllocateMemory( + winDsHostApi->allocations, sizeof(PaWinDsDeviceInfo) * deviceCount ); + if( !winDsHostApi->winDsDeviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + for( i=0; i < deviceCount; ++i ) + { + PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->name = 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 */ + + (*hostApi)->deviceInfos[i] = deviceInfo; + } + + /* DSound - Enumerate again to fill in structures. */ + winDsHostApi->enumerationError = 0; + dswDSoundEntryPoints.DirectSoundEnumerate( (LPDSENUMCALLBACK)Pa_EnumOutputProc, (void *)winDsHostApi ); + if( winDsHostApi->enumerationError != paNoError ) return winDsHostApi->enumerationError; + + winDsHostApi->enumerationError = 0; + dswDSoundEntryPoints.DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK)Pa_EnumInputProc, (void *)winDsHostApi ); + if( winDsHostApi->enumerationError != paNoError ) return winDsHostApi->enumerationError; + } + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &winDsHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyReadWrite, PaUtil_DummyReadWrite, PaUtil_DummyGetAvailable, PaUtil_DummyGetAvailable ); + + PaUtil_InitializeStreamInterface( &winDsHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + return result; + +error: + if( winDsHostApi ) + { + if( winDsHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winDsHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); + } + + PaUtil_FreeMemory( winDsHostApi ); + } + return result; +} + + +/***********************************************************************************/ +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; + + /* + IMPLEMENT ME: + - clean up any resourced not handled by the allocation group + */ + + if( winDsHostApi->allocations ) + { + PaUtil_FreeAllAllocations( winDsHostApi->allocations ); + PaUtil_DestroyAllocationGroup( winDsHostApi->allocations ); + } + + PaUtil_FreeMemory( winDsHostApi ); + + DSW_TerminateDSoundEntryPoints(); +} + + +/* Set minimal latency based on whether NT or Win95. + * NT has higher latency. + */ +static int PaWinDS_GetMinSystemLatency( void ) +{ + int minLatencyMsec; + /* 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; + } + return minLatencyMsec; +} + +/***********************************************************************************/ +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; +} + + +/************************************************************************* +** 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") +#define PA_ENV_BUF_SIZE (32) + +static int PaWinDs_GetMinLatencyFrames( double sampleRate ) +{ + char envbuf[PA_ENV_BUF_SIZE]; + DWORD hresult; + int minLatencyMsec = 0; + + /* 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 ); + } + else + { + minLatencyMsec = PaWinDS_GetMinSystemLatency(); +#if PA_USE_HIGH_LATENCY + PRINT(("PA - Minimum Latency set to %d msec!\n", minLatencyMsec )); +#endif + + } + + return (int) (minLatencyMsec * sampleRate * SECONDS_PER_MSEC); +} + +/***********************************************************************************/ +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +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; + PaWinDsHostApiRepresentation *winDsHostApi = (PaWinDsHostApiRepresentation*)hostApi; + PaWinDsStream *stream = 0; + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + unsigned long suggestedInputLatencyFrames, suggestedOutputLatencyFrames; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + suggestedInputLatencyFrames = inputParameters->suggestedLatency * sampleRate; + + /* IDEA: the following 3 checks could be performed by default by pa_front + unless some flag indicated otherwise */ + + /* 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 hostApiSpecificStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + inputChannelCount = 0; + suggestedInputLatencyFrames = 0; + } + + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + suggestedOutputLatencyFrames = outputParameters->suggestedLatency * sampleRate; + + /* 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 hostApiSpecificStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + else + { + outputChannelCount = 0; + suggestedOutputLatencyFrames = 0; + } + + + /* + IMPLEMENT ME: + + ( the following two checks are taken care of by PaUtil_InitializeBufferProcessor() ) + + - 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 + + - alter sampleRate to a close allowable rate if possible / necessary + + - validate suggestedInputLatency and suggestedOutputLatency parameters, + use default values where necessary + */ + + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + return paInvalidFlag; /* unexpected platform specific flag */ + + + stream = (PaWinDsStream*)PaUtil_AllocateMemory( sizeof(PaWinDsStream) ); + if( !stream ) + { + result = paInsufficientMemory; + goto error; + } + + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &winDsHostApi->callbackStreamInterface, streamCallback, userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &winDsHostApi->blockingStreamInterface, streamCallback, userData ); + } + + stream->streamRepresentation.streamInfo.inputLatency = 0.; /* FIXME: not initialised anywhere else */ + stream->streamRepresentation.streamInfo.outputLatency = 0.; + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + + if( inputParameters ) + { + /* IMPLEMENT ME - establish which host formats are available */ + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputParameters->sampleFormat ); + } + + if( outputParameters ) + { + /* IMPLEMENT ME - establish which host formats are available */ + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputParameters->sampleFormat ); + } + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, inputSampleFormat, hostInputSampleFormat, + outputChannelCount, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerBuffer, + framesPerBuffer, /* ignored in paUtilVariableHostBufferSizePartialUsageAllowed mode. */ + /* This next mode is required because DS can split the host buffer when it wraps around. */ + paUtilVariableHostBufferSizePartialUsageAllowed, + streamCallback, userData ); + if( result != paNoError ) + goto error; + +/* DirectSound specific initialization */ + { + HRESULT hr; + int bytesPerDirectSoundBuffer; + DSoundWrapper *dsw; + int userLatencyFrames; + int minLatencyFrames; + + stream->timerID = 0; + dsw = &stream->directSoundWrapper; + DSW_Init( dsw ); + + /* Get system minimum latency. */ + minLatencyFrames = PaWinDs_GetMinLatencyFrames( sampleRate ); + + /* Let user override latency by passing latency parameter. */ + userLatencyFrames = (suggestedInputLatencyFrames > suggestedOutputLatencyFrames) + ? suggestedInputLatencyFrames + : suggestedOutputLatencyFrames; + if( userLatencyFrames > 0 ) minLatencyFrames = userLatencyFrames; + + /* Calculate stream->framesPerDSBuffer depending on framesPerBuffer */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + /* App support variable framesPerBuffer */ + stream->framesPerDSBuffer = minLatencyFrames; + + stream->streamRepresentation.streamInfo.outputLatency = (double)(minLatencyFrames - 1) / sampleRate; + } + else + { + /* Round up to number of buffers needed to guarantee that latency. */ + int numUserBuffers = (minLatencyFrames + framesPerBuffer - 1) / framesPerBuffer; + if( numUserBuffers < 1 ) numUserBuffers = 1; + numUserBuffers += 1; /* So we have latency worth of buffers ahead of current buffer. */ + stream->framesPerDSBuffer = framesPerBuffer * numUserBuffers; + + stream->streamRepresentation.streamInfo.outputLatency = (double)(framesPerBuffer * (numUserBuffers-1)) / sampleRate; + } + + { + /* @todo REVIEW: this calculation seems incorrect to me - rossb. */ + int msecLatency = (int) ((stream->framesPerDSBuffer * MSEC_PER_SECOND) / sampleRate); + PRINT(("PortAudio on DirectSound - Latency = %d frames, %d msec\n", stream->framesPerDSBuffer, msecLatency )); + } + + + /* ------------------ OUTPUT */ + if( outputParameters ) + { + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ outputParameters->device ]; + DBUG(("PaHost_OpenStream: deviceID = 0x%x\n", outputDevice)); + + bytesPerDirectSoundBuffer = stream->framesPerDSBuffer * outputParameters->channelCount * sizeof(short); + if( bytesPerDirectSoundBuffer < DSBSIZE_MIN ) + { + result = paBufferTooSmall; + goto error; + } + else if( bytesPerDirectSoundBuffer > DSBSIZE_MAX ) + { + result = paBufferTooBig; + goto error; + } + + + hr = dswDSoundEntryPoints.DirectSoundCreate( winDsHostApi->winDsDeviceInfos[outputParameters->device].lpGUID, + &dsw->dsw_pDirectSound, NULL ); + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DirectSoundCreate() failed!\n")); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + hr = DSW_InitOutputBuffer( dsw, + (unsigned long) (sampleRate + 0.5), + outputParameters->channelCount, bytesPerDirectSoundBuffer ); + DBUG(("DSW_InitOutputBuffer() returns %x\n", hr)); + if( hr != DS_OK ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + /* Calculate value used in latency calculation to avoid real-time divides. */ + stream->secondsPerHostByte = 1.0 / + (stream->bufferProcessor.bytesPerHostOutputSample * + outputChannelCount * sampleRate); + } + + /* ------------------ INPUT */ + if( inputParameters ) + { + PaDeviceInfo *deviceInfo = hostApi->deviceInfos[ inputParameters->device ]; + + bytesPerDirectSoundBuffer = stream->framesPerDSBuffer * inputParameters->channelCount * sizeof(short); + if( bytesPerDirectSoundBuffer < DSBSIZE_MIN ) + { + result = paBufferTooSmall; + goto error; + } + else if( bytesPerDirectSoundBuffer > DSBSIZE_MAX ) + { + result = paBufferTooBig; + goto error; + } + + hr = dswDSoundEntryPoints.DirectSoundCaptureCreate( winDsHostApi->winDsDeviceInfos[inputParameters->device].lpGUID, + &dsw->dsw_pDirectSoundCapture, NULL ); + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DirectSoundCaptureCreate() failed!\n")); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + hr = DSW_InitInputBuffer( dsw, + (unsigned long) (sampleRate + 0.5), + inputParameters->channelCount, bytesPerDirectSoundBuffer ); + DBUG(("DSW_InitInputBuffer() returns %x\n", hr)); + if( hr != DS_OK ) + { + ERR_RPT(("PortAudio: DSW_InitInputBuffer() returns %x\n", hr)); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + } + + } + + *s = (PaStream*)stream; + + return result; + +error: + if( stream ) + PaUtil_FreeMemory( stream ); + + return result; +} + + +/***********************************************************************************/ +static PaError Pa_TimeSlice( PaWinDsStream *stream ) +{ + PaError result = 0; /* FIXME: this should be declared int and this function should also return that type (same as stream callback return type)*/ + DSoundWrapper *dsw; + long numFrames = 0; + long bytesEmpty = 0; + long bytesFilled = 0; + long bytesToXfer = 0; + long framesToXfer = 0; + long numInFramesReady = 0; + long numOutFramesReady = 0; + long bytesProcessed; + HRESULT hresult; + double outputLatency = 0; + PaStreamCallbackTimeInfo timeInfo = {0,0,0}; /* @todo implement inputBufferAdcTime */ +/* Input */ + LPBYTE lpInBuf1 = NULL; + LPBYTE lpInBuf2 = NULL; + DWORD dwInSize1 = 0; + DWORD dwInSize2 = 0; +/* Output */ + LPBYTE lpOutBuf1 = NULL; + LPBYTE lpOutBuf2 = NULL; + DWORD dwOutSize1 = 0; + DWORD dwOutSize2 = 0; + + dsw = &stream->directSoundWrapper; + + /* How much input data is available? */ + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + DSW_QueryInputFilled( dsw, &bytesFilled ); + framesToXfer = numInFramesReady = bytesFilled / dsw->dsw_BytesPerInputFrame; + outputLatency = ((double)bytesFilled) * stream->secondsPerHostByte; + } + + /* How much output room is available? */ + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + DSW_QueryOutputSpace( dsw, &bytesEmpty ); + framesToXfer = numOutFramesReady = bytesEmpty / dsw->dsw_BytesPerOutputFrame; + } + + if( (numInFramesReady > 0) && (numOutFramesReady > 0) ) + { + framesToXfer = (numOutFramesReady < numInFramesReady) ? numOutFramesReady : numInFramesReady; + } + + if( framesToXfer > 0 ) + { + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + /* The outputBufferDacTime parameter should indicates the time at which + the first sample of the output buffer is heard at the DACs. */ + timeInfo.currentTime = PaUtil_GetTime(); + timeInfo.outputBufferDacTime = timeInfo.currentTime + outputLatency; + + + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo ); + + /* Input */ + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + bytesToXfer = framesToXfer * dsw->dsw_BytesPerInputFrame; + hresult = IDirectSoundCaptureBuffer_Lock ( dsw->dsw_InputBuffer, + dsw->dsw_ReadOffset, bytesToXfer, + (void **) &lpInBuf1, &dwInSize1, + (void **) &lpInBuf2, &dwInSize2, 0); + if (hresult != DS_OK) + { + ERR_RPT(("DirectSound IDirectSoundCaptureBuffer_Lock failed, hresult = 0x%x\n",hresult)); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); + goto error2; + } + + numFrames = dwInSize1 / dsw->dsw_BytesPerInputFrame; + PaUtil_SetInputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf1, 0 ); + /* Is input split into two regions. */ + if( dwInSize2 > 0 ) + { + numFrames = dwInSize2 / dsw->dsw_BytesPerInputFrame; + PaUtil_Set2ndInputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_Set2ndInterleavedInputChannels( &stream->bufferProcessor, 0, lpInBuf2, 0 ); + } + } + + /* Output */ + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + bytesToXfer = framesToXfer * dsw->dsw_BytesPerOutputFrame; + hresult = IDirectSoundBuffer_Lock ( dsw->dsw_OutputBuffer, + dsw->dsw_WriteOffset, bytesToXfer, + (void **) &lpOutBuf1, &dwOutSize1, + (void **) &lpOutBuf2, &dwOutSize2, 0); + if (hresult != DS_OK) + { + ERR_RPT(("DirectSound IDirectSoundBuffer_Lock failed, hresult = 0x%x\n",hresult)); + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hresult ); + goto error1; + } + + numFrames = dwOutSize1 / dsw->dsw_BytesPerOutputFrame; + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf1, 0 ); + + /* Is output split into two regions. */ + if( dwOutSize2 > 0 ) + { + numFrames = dwOutSize2 / dsw->dsw_BytesPerOutputFrame; + PaUtil_Set2ndOutputFrameCount( &stream->bufferProcessor, numFrames ); + PaUtil_Set2ndInterleavedOutputChannels( &stream->bufferProcessor, 0, lpOutBuf2, 0 ); + } + } + + numFrames = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &result ); + stream->framesWritten += numFrames; + + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + /* Update our buffer offset and unlock sound buffer */ + bytesProcessed = numFrames * dsw->dsw_BytesPerOutputFrame; + dsw->dsw_WriteOffset = (dsw->dsw_WriteOffset + bytesProcessed) % dsw->dsw_OutputSize; + IDirectSoundBuffer_Unlock( dsw->dsw_OutputBuffer, lpOutBuf1, dwOutSize1, lpOutBuf2, dwOutSize2); + dsw->dsw_FramesWritten += numFrames; + } + +error1: + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + /* Update our buffer offset and unlock sound buffer */ + bytesProcessed = numFrames * dsw->dsw_BytesPerInputFrame; + dsw->dsw_ReadOffset = (dsw->dsw_ReadOffset + bytesProcessed) % dsw->dsw_InputSize; + IDirectSoundCaptureBuffer_Unlock( dsw->dsw_InputBuffer, lpInBuf1, dwInSize1, lpInBuf2, dwInSize2); + } +error2: + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, numFrames ); + + } + + return result; +} +/*******************************************************************/ +static void CALLBACK Pa_TimerCallback(UINT uID, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) +{ + PaWinDsStream *stream; + + stream = (PaWinDsStream *) dwUser; + if( stream == NULL ) return; + + if( stream->isActive ) + { + if( stream->abortProcessing ) + { + stream->isActive = 0; + } + else if( stream->stopProcessing ) + { + DSoundWrapper *dsw = &stream->directSoundWrapper; + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + DSW_ZeroEmptySpace( dsw ); + /* clear isActive when all sound played */ + if( dsw->dsw_FramesPlayed >= stream->framesWritten ) + { + stream->isActive = 0; + } + } + else + { + stream->isActive = 0; + } + } + else + { + if( Pa_TimeSlice( stream ) != 0) /* Call time slice independant of timing method. */ + { + /* FIXME implement handling of paComplete and paAbort if possible */ + stream->stopProcessing = 1; + } + } + + if( !stream->isActive ){ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + } +} + +/*********************************************************************************** + 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; + PaWinDsStream *stream = (PaWinDsStream*)s; + + DSW_Term( &stream->directSoundWrapper ); + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + PaUtil_FreeMemory( stream ); + + return result; +} + +/***********************************************************************************/ +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinDsStream *stream = (PaWinDsStream*)s; + HRESULT hr; + + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + hr = DSW_StartInput( &stream->directSoundWrapper ); + DBUG(("StartStream: DSW_StartInput returned = 0x%X.\n", hr)); + if( hr != DS_OK ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + } + + stream->framesWritten = 0; + + stream->abortProcessing = 0; + stream->stopProcessing = 0; + stream->isActive = 1; + + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + /* Give user callback a chance to pre-fill buffer. REVIEW - i thought we weren't pre-filling, rb. */ + result = Pa_TimeSlice( stream ); + if( result != paNoError ) return result; // FIXME - what if finished? + + hr = DSW_StartOutput( &stream->directSoundWrapper ); + DBUG(("PaHost_StartOutput: DSW_StartOutput returned = 0x%X.\n", hr)); + if( hr != DS_OK ) + { + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + } + + + /* Create timer that will wake us up so we can fill the DSound buffer. */ + { + int resolution; + int framesPerWakeup = stream->framesPerDSBuffer / 4; + int msecPerWakeup = MSEC_PER_SECOND * framesPerWakeup / (int) stream->streamRepresentation.streamInfo.sampleRate; + if( msecPerWakeup < 10 ) msecPerWakeup = 10; + else if( msecPerWakeup > 100 ) msecPerWakeup = 100; + resolution = msecPerWakeup/4; + stream->timerID = timeSetEvent( msecPerWakeup, resolution, (LPTIMECALLBACK) Pa_TimerCallback, + (DWORD) stream, TIME_PERIODIC ); + } + if( stream->timerID == 0 ) + { + stream->isActive = 0; + result = paUnanticipatedHostError; + PA_DS_SET_LAST_DIRECTSOUND_ERROR( hr ); + goto error; + } + + stream->isStarted = TRUE; + +error: + return result; +} + + +/***********************************************************************************/ +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinDsStream *stream = (PaWinDsStream*)s; + HRESULT hr; + int timeoutMsec; + + stream->stopProcessing = 1; + /* Set timeout at 20% beyond maximum time we might wait. */ + timeoutMsec = (int) (1200.0 * stream->framesPerDSBuffer / stream->streamRepresentation.streamInfo.sampleRate); + while( stream->isActive && (timeoutMsec > 0) ) + { + Sleep(10); + timeoutMsec -= 10; + } + if( stream->timerID != 0 ) + { + timeKillEvent(stream->timerID); /* Stop callback timer. */ + stream->timerID = 0; + } + + + if( stream->bufferProcessor.outputChannelCount > 0 ) + { + hr = DSW_StopOutput( &stream->directSoundWrapper ); + } + + if( stream->bufferProcessor.inputChannelCount > 0 ) + { + hr = DSW_StopInput( &stream->directSoundWrapper ); + } + + stream->isStarted = FALSE; + + return result; +} + + +/***********************************************************************************/ +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinDsStream *stream = (PaWinDsStream*)s; + + stream->abortProcessing = 1; + return StopStream( s ); +} + + +/***********************************************************************************/ +static PaError IsStreamStopped( PaStream *s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + return !stream->isStarted; +} + + +/***********************************************************************************/ +static PaError IsStreamActive( PaStream *s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + return stream->isActive; +} + +/***********************************************************************************/ +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. + + PaWinDsStream *stream = (PaWinDsStream*)s; + DSoundWrapper *dsw; + dsw = &stream->directSoundWrapper; + return dsw->dsw_FramesPlayed; +*/ + return PaUtil_GetTime(); +} + + +/***********************************************************************************/ +static double GetStreamCpuLoad( PaStream* s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)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 ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return paNoError; +} + + +/***********************************************************************************/ +static PaError WriteStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return paNoError; +} + + +/***********************************************************************************/ +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return 0; +} + + +/***********************************************************************************/ +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaWinDsStream *stream = (PaWinDsStream*)s; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + + return 0; +} + + + diff --git a/pd/portaudio/pa_win_ds/portaudio.def b/pd/portaudio/pa_win_ds/portaudio.def new file mode 100644 index 00000000..8012b99e --- /dev/null +++ b/pd/portaudio/pa_win_ds/portaudio.def @@ -0,0 +1,28 @@ +LIBRARY PortAudio +DESCRIPTION 'PortAudio Portable interface to audio HW' + +EXPORTS + ; Explicit exports can go here + Pa_Initialize @1 + Pa_Terminate @2 + Pa_GetHostError @3 + Pa_GetErrorText @4 + Pa_CountDevices @5 + Pa_GetDefaultInputDeviceID @6 + Pa_GetDefaultOutputDeviceID @7 + Pa_GetDeviceInfo @8 + Pa_OpenStream @9 + Pa_OpenDefaultStream @10 + Pa_CloseStream @11 + Pa_StartStream @12 + Pa_StopStream @13 + Pa_StreamActive @14 + Pa_StreamTime @15 + Pa_GetCPULoad @16 + Pa_GetMinNumBuffers @17 + Pa_Sleep @18 + + ;123456789012345678901234567890123456 + ;000000000111111111122222222223333333 + + |