From: robiwan Date: Wed, 9 Apr 2014 14:27:21 +0000 (+0000) Subject: CMake: Added PA_WDMKS_NO_KSGUID_LIB to WDMKS and solution folders X-Git-Tag: pa_stable_v190600_20161030~55 X-Git-Url: https://andrewgundersen.net/repos?a=commitdiff_plain;h=4aa340a57e98e1bfd7f3ab1449226de76ed8f9aa;p=portaudio CMake: Added PA_WDMKS_NO_KSGUID_LIB to WDMKS and solution folders WDMKS: Support for default device (see caveat for input devices though in ScanDeviceInfos) Added possibility to set channel mask --- diff --git a/CMakeLists.txt b/CMakeLists.txt index ad76118..bb2ef92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,8 @@ PROJECT( portaudio ) CMAKE_MINIMUM_REQUIRED(VERSION 2.8) +SET_PROPERTY(GLOBAL PROPERTY USE_FOLDERS ON) + OPTION(PA_CONFIG_LIB_OUTPUT_PATH "Make sure that output paths are kept neat" OFF) IF(CMAKE_CL_64) SET(TARGET_POSTFIX x64) @@ -173,6 +175,7 @@ SOURCE_GROUP("hostapi\\wasapi" FILES ENDIF(PA_USE_WASAPI) IF(PA_USE_WDMKS) +ADD_DEFINITIONS(-DPA_WDMKS_NO_KSGUID_LIB) SET(PA_WDMKS_INCLUDES include/pa_win_wdmks.h @@ -328,8 +331,10 @@ IF(PA_USE_WDMKS) TARGET_LINK_LIBRARIES(portaudio setupapi) ENDIF(PA_USE_WDMKS) -SET_TARGET_PROPERTIES(portaudio PROPERTIES OUTPUT_NAME portaudio_${TARGET_POSTFIX}) -SET_TARGET_PROPERTIES(portaudio_static PROPERTIES OUTPUT_NAME portaudio_static_${TARGET_POSTFIX}) +SET_TARGET_PROPERTIES(portaudio PROPERTIES OUTPUT_NAME portaudio_${TARGET_POSTFIX} + FOLDER "Portaudio") +SET_TARGET_PROPERTIES(portaudio_static PROPERTIES OUTPUT_NAME portaudio_static_${TARGET_POSTFIX} + FOLDER "Portaudio") ENDIF(WIN32) OPTION(PA_BUILD_TESTS "Include test projects" OFF) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 67a2cfd..f79e8fd 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -3,11 +3,19 @@ MACRO(ADD_EXAMPLE appl_name) ADD_EXECUTABLE(${appl_name} "${appl_name}.c") TARGET_LINK_LIBRARIES(${appl_name} portaudio_static) + SET_TARGET_PROPERTIES(${appl_name} + PROPERTIES + FOLDER "Examples C" + ) ENDMACRO(ADD_EXAMPLE) MACRO(ADD_EXAMPLE_CPP appl_name) ADD_EXECUTABLE(${appl_name} "${appl_name}.cpp") TARGET_LINK_LIBRARIES(${appl_name} portaudio_static) + SET_TARGET_PROPERTIES(${appl_name} + PROPERTIES + FOLDER "Examples C++" + ) ENDMACRO(ADD_EXAMPLE_CPP) ADD_EXAMPLE(pa_devs) diff --git a/examples/paex_sine_c++.cpp b/examples/paex_sine_c++.cpp index 5b96aee..c1ee8aa 100644 --- a/examples/paex_sine_c++.cpp +++ b/examples/paex_sine_c++.cpp @@ -78,6 +78,12 @@ public: return false; } + const PaDeviceInfo* pInfo = Pa_GetDeviceInfo(index); + if (pInfo != 0) + { + printf("Output device name: '%s'\r", pInfo->name); + } + outputParameters.channelCount = 2; /* stereo output */ outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */ outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; @@ -88,7 +94,7 @@ public: NULL, /* no input */ &outputParameters, SAMPLE_RATE, - FRAMES_PER_BUFFER, + paFramesPerBufferUnspecified, paClipOff, /* we won't output out of range samples so don't bother clipping them */ &Sine::paCallback, this /* Using 'this' for userData so we can cast to Sine* in paCallback method */ @@ -212,18 +218,38 @@ private: char message[20]; }; +class ScopedPaHandler +{ +public: + ScopedPaHandler() + : _result(Pa_Initialize()) + { + } + ~ScopedPaHandler() + { + if (_result == paNoError) + { + Pa_Terminate(); + } + } + + PaError result() const { return _result; } + +private: + PaError _result; +}; + /*******************************************************************/ int main(void); int main(void) { - PaError err; Sine sine; printf("PortAudio Test: output sine wave. SR = %d, BufSize = %d\n", SAMPLE_RATE, FRAMES_PER_BUFFER); - err = Pa_Initialize(); - if( err != paNoError ) goto error; + ScopedPaHandler paInit; + if( paInit.result() != paNoError ) goto error; if (sine.open(Pa_GetDefaultOutputDevice())) { @@ -238,15 +264,12 @@ int main(void) sine.close(); } - Pa_Terminate(); printf("Test finished.\n"); - - return err; + return paNoError; error: - Pa_Terminate(); fprintf( stderr, "An error occured while using the portaudio stream\n" ); - fprintf( stderr, "Error number: %d\n", err ); - fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); - return err; + fprintf( stderr, "Error number: %d\n", paInit.result() ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( paInit.result() ) ); + return 1; } diff --git a/include/pa_win_wdmks.h b/include/pa_win_wdmks.h index f7f7612..9a08c8f 100644 --- a/include/pa_win_wdmks.h +++ b/include/pa_win_wdmks.h @@ -52,13 +52,29 @@ extern "C" { #endif /* __cplusplus */ + + /* Setup flags */ + typedef enum PaWinWDMKSFlags + { + /* Makes WDMKS use the supplied latency figures instead of relying on the frame size reported + by the WaveCyclic device. Use at own risk! */ + paWinWDMKSOverrideFramesize = (1 << 0), + + /* Makes WDMKS (output stream) use the given channelMask instead of the default */ + paWinWDMKSUseGivenChannelMask = (1 << 1), + + } PaWinWDMKSFlags; + typedef struct PaWinWDMKSInfo{ unsigned long size; /**< sizeof(PaWinWDMKSInfo) */ PaHostApiTypeId hostApiType; /**< paWDMKS */ unsigned long version; /**< 1 */ + unsigned long flags; /* The number of packets to use for WaveCyclic devices, range is [2, 8]. Set to zero for default value of 2. */ unsigned noOfPackets; + /* If paWinWDMKSUseGivenChannelMask bit is set in flags, use this as channelMask instead of default */ + unsigned channelMask; } PaWinWDMKSInfo; typedef enum PaWDMKSType diff --git a/src/hostapi/wdmks/pa_win_wdmks.c b/src/hostapi/wdmks/pa_win_wdmks.c index bbdc39a..848d285 100644 --- a/src/hostapi/wdmks/pa_win_wdmks.c +++ b/src/hostapi/wdmks/pa_win_wdmks.c @@ -86,6 +86,13 @@ of a device for the duration of active stream using those devices #include "pa_win_wdmks.h" +#ifndef DRV_QUERYDEVICEINTERFACE +#define DRV_QUERYDEVICEINTERFACE (DRV_RESERVED + 12) +#endif +#ifndef DRV_QUERYDEVICEINTERFACESIZE +#define DRV_QUERYDEVICEINTERFACESIZE (DRV_RESERVED + 13) +#endif + #include #include #include @@ -506,8 +513,9 @@ static PaError PinRegisterPositionRegister(PaWinWdmPin* pPin); static PaError PinRegisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle); static PaError PinUnregisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle); static PaError PinGetHwLatency(PaWinWdmPin* pPin, ULONG* pFifoSize, ULONG* pChipsetDelay, ULONG* pCodecDelay); -static PaError PinGetAudioPositionDirect(PaWinWdmPin* pPin, ULONG* pPosition); -static PaError PinGetAudioPositionViaIOCTL(PaWinWdmPin* pPin, ULONG* pPosition); +static PaError PinGetAudioPositionMemoryMapped(PaWinWdmPin* pPin, ULONG* pPosition); +static PaError PinGetAudioPositionViaIOCTLRead(PaWinWdmPin* pPin, ULONG* pPosition); +static PaError PinGetAudioPositionViaIOCTLWrite(PaWinWdmPin* pPin, ULONG* pPosition); /* Filter management functions */ static PaWinWdmFilter* FilterNew(PaWDMKSType type, DWORD devNode, const wchar_t* filterName, const wchar_t* friendlyName, PaError* error); @@ -2221,12 +2229,7 @@ static PaError PinIsFormatSupported(PaWinWdmPin* pin, const WAVEFORMATEX* format if (pFormatExt != 0) { - if ( dataRange->MinimumBitsPerSample > pFormatExt->Samples.wValidBitsPerSample ) - { - result = paSampleFormatNotSupported; - continue; - } - if ( dataRange->MaximumBitsPerSample < pFormatExt->Samples.wValidBitsPerSample ) + if (!IsBitsWithinRange(dataRange, pFormatExt->Samples.wValidBitsPerSample)) { result = paSampleFormatNotSupported; continue; @@ -2234,26 +2237,14 @@ static PaError PinIsFormatSupported(PaWinWdmPin* pin, const WAVEFORMATEX* format } else { - if( dataRange->MinimumBitsPerSample > format->wBitsPerSample ) - { - result = paSampleFormatNotSupported; - continue; - } - - if( dataRange->MaximumBitsPerSample < format->wBitsPerSample ) + if (!IsBitsWithinRange(dataRange, format->wBitsPerSample)) { result = paSampleFormatNotSupported; continue; } } - if( dataRange->MinimumSampleFrequency > format->nSamplesPerSec ) - { - result = paInvalidSampleRate; - continue; - } - - if( dataRange->MaximumSampleFrequency < format->nSamplesPerSec ) + if (!IsFrequencyWithinRange(dataRange, format->nSamplesPerSec)) { result = paInvalidSampleRate; continue; @@ -2466,7 +2457,7 @@ static PaError PinRegisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle) prop.NotificationEvent = handle; prop.Property.Set = KSPROPSETID_RtAudio; prop.Property.Id = KSPROPERTY_RTAUDIO_REGISTER_NOTIFICATION_EVENT; - prop.Property.Flags = KSPROPERTY_TYPE_GET; + prop.Property.Flags = KSPROPERTY_TYPE_SET; result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY, @@ -2497,7 +2488,7 @@ static PaError PinUnregisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle) prop.NotificationEvent = handle; prop.Property.Set = KSPROPSETID_RtAudio; prop.Property.Id = KSPROPERTY_RTAUDIO_UNREGISTER_NOTIFICATION_EVENT; - prop.Property.Flags = KSPROPERTY_TYPE_GET; + prop.Property.Flags = KSPROPERTY_TYPE_SET; result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY, @@ -2552,14 +2543,14 @@ static PaError PinGetHwLatency(PaWinWdmPin* pPin, ULONG* pFifoSize, ULONG* pChip } /* This one is used for WaveRT */ -static PaError PinGetAudioPositionDirect(PaWinWdmPin* pPin, ULONG* pPosition) +static PaError PinGetAudioPositionMemoryMapped(PaWinWdmPin* pPin, ULONG* pPosition) { *pPosition = (*pPin->positionRegister); return paNoError; } /* This one also, but in case the driver hasn't implemented memory mapped access to the position register */ -static PaError PinGetAudioPositionViaIOCTL(PaWinWdmPin* pPin, ULONG* pPosition) +static PaError PinGetAudioPositionViaIOCTLRead(PaWinWdmPin* pPin, ULONG* pPosition) { PaError result = paNoError; KSPROPERTY propIn; @@ -2583,7 +2574,41 @@ static PaError PinGetAudioPositionViaIOCTL(PaWinWdmPin* pPin, ULONG* pPosition) } else { - PA_DEBUG(("Failed to get audio position!\n")); + PA_DEBUG(("Failed to get audio play position!\n")); + } + + PA_LOGL_; + + return result; + +} + +/* This one also, but in case the driver hasn't implemented memory mapped access to the position register */ +static PaError PinGetAudioPositionViaIOCTLWrite(PaWinWdmPin* pPin, ULONG* pPosition) +{ + PaError result = paNoError; + KSPROPERTY propIn; + KSAUDIO_POSITION propOut; + + PA_LOGE_; + + propIn.Set = KSPROPSETID_Audio; + propIn.Id = KSPROPERTY_AUDIO_POSITION; + propIn.Flags = KSPROPERTY_TYPE_GET; + + result = WdmSyncIoctl(pPin->handle, + IOCTL_KS_PROPERTY, + &propIn, sizeof(KSPROPERTY), + &propOut, sizeof(KSAUDIO_POSITION), + NULL); + + if (result == paNoError) + { + *pPosition = (ULONG)(propOut.WriteOffset); + } + else + { + PA_DEBUG(("Failed to get audio write position!\n")); } PA_LOGL_; @@ -2800,7 +2825,6 @@ error: */ static void FilterFree(PaWinWdmFilter* filter) { - int pinId; PA_LOGL_; if( filter ) { @@ -2810,13 +2834,14 @@ static void FilterFree(PaWinWdmFilter* filter) return; } - if (filter->topologyFilter) + if ( filter->topologyFilter ) { FilterFree(filter->topologyFilter); filter->topologyFilter = 0; } if ( filter->pins ) { + int pinId; for( pinId = 0; pinId < filter->pinCount; pinId++ ) PinFree(filter->pins[pinId]); PaUtil_FreeMemory( filter->pins ); @@ -3374,6 +3399,10 @@ static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaH int filterCount = 0; int totalDeviceCount = 0; int idxDevice = 0; + DWORD defaultInDevPathSize = 0; + DWORD defaultOutDevPathSize = 0; + wchar_t* defaultInDevPath = 0; + wchar_t* defaultOutDevPath = 0; ppFilters = BuildFilterList( &filterCount, &totalDeviceCount, &result ); if( result != paNoError ) @@ -3381,11 +3410,24 @@ static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaH goto error; } + // Get hold of default device paths for capture & playback + if( waveInMessage(0, DRV_QUERYDEVICEINTERFACESIZE, (DWORD_PTR)&defaultInDevPathSize, 0 ) == MMSYSERR_NOERROR ) + { + defaultInDevPath = (wchar_t *)PaUtil_AllocateMemory((defaultInDevPathSize + 1) * sizeof(wchar_t)); + waveInMessage(0, DRV_QUERYDEVICEINTERFACE, (DWORD_PTR)defaultInDevPath, defaultInDevPathSize); + } + if( waveOutMessage(0, DRV_QUERYDEVICEINTERFACESIZE, (DWORD_PTR)&defaultOutDevPathSize, 0 ) == MMSYSERR_NOERROR ) + { + defaultOutDevPath = (wchar_t *)PaUtil_AllocateMemory((defaultOutDevPathSize + 1) * sizeof(wchar_t)); + waveOutMessage(0, DRV_QUERYDEVICEINTERFACE, (DWORD_PTR)defaultOutDevPath, defaultOutDevPathSize); + } + if( totalDeviceCount > 0 ) { PaWinWdmDeviceInfo *deviceInfoArray = 0; int idxFilter; int i; + unsigned devIsDefaultIn = 0, devIsDefaultOut = 0; /* Allocate the out param for all the info we need */ outArgument = (PaWinWDMScanDeviceInfosResults *) PaUtil_GroupAllocateMemory( @@ -3440,6 +3482,9 @@ static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaH continue; } + devIsDefaultIn = (defaultInDevPath && (_wcsicmp(pFilter->devInfo.filterPath, defaultInDevPath) == 0)); + devIsDefaultOut = (defaultOutDevPath && (_wcsicmp(pFilter->devInfo.filterPath, defaultOutDevPath) == 0)); + for (i = 0; i < pFilter->pinCount; ++i) { unsigned m; @@ -3501,14 +3546,19 @@ static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaH /* Convert wide char string to utf-8 */ WideCharToMultiByte(CP_UTF8, 0, localCompositeName, -1, wdmDeviceInfo->compositeName, MAX_PATH, NULL, NULL); - /* NB! WDM/KS has no concept of a full-duplex device, each pin is either an input or and output */ + /* NB! WDM/KS has no concept of a full-duplex device, each pin is either an input or an output */ if (isInput) { /* INPUT ! */ deviceInfo->maxInputChannels = pin->maxChannels; deviceInfo->maxOutputChannels = 0; - if (outArgument->defaultInputDevice == paNoDevice) + /* RoBi NB: Due to the fact that input audio endpoints in Vista (& later OSs) can be the same device, but with + different input mux settings, there might be a discrepancy between the default input device chosen, and + that which will be used by Portaudio. Not much to do about that unfortunately. + */ + if ((defaultInDevPath == 0 || devIsDefaultIn) && + outArgument->defaultInputDevice == paNoDevice) { outArgument->defaultInputDevice = idxDevice; } @@ -3519,7 +3569,8 @@ static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaH deviceInfo->maxInputChannels = 0; deviceInfo->maxOutputChannels = pin->maxChannels; - if (outArgument->defaultOutputDevice == paNoDevice) + if ((defaultOutDevPath == 0 || devIsDefaultOut) && + outArgument->defaultOutputDevice == paNoDevice) { outArgument->defaultOutputDevice = idxDevice; } @@ -4159,7 +4210,8 @@ static unsigned NextPowerOf2(unsigned val) static PaError ValidateSpecificStreamParameters( const PaStreamParameters *streamParameters, - const PaWinWDMKSInfo *streamInfo) + const PaWinWDMKSInfo *streamInfo, + unsigned isInput) { if( streamInfo ) { @@ -4170,6 +4222,12 @@ static PaError ValidateSpecificStreamParameters( return paIncompatibleHostApiSpecificStreamInfo; } + if (!!(streamInfo->flags & ~(paWinWDMKSOverrideFramesize | paWinWDMKSUseGivenChannelMask))) + { + PA_DEBUG(("Stream parameters: non supported flags set")); + return paIncompatibleHostApiSpecificStreamInfo; + } + if (streamInfo->noOfPackets != 0 && (streamInfo->noOfPackets < 2 || streamInfo->noOfPackets > 8)) { @@ -4177,6 +4235,21 @@ static PaError ValidateSpecificStreamParameters( return paIncompatibleHostApiSpecificStreamInfo; } + if (streamInfo->flags & paWinWDMKSUseGivenChannelMask) + { + if (isInput) + { + PA_DEBUG(("Stream parameters: Channels mask setting not supported for input stream")); + return paIncompatibleHostApiSpecificStreamInfo; + } + + if (streamInfo->channelMask & PAWIN_SPEAKER_RESERVED) + { + PA_DEBUG(("Stream parameters: Given channels mask 0x%08X not supported", streamInfo->channelMask)); + return paIncompatibleHostApiSpecificStreamInfo; + } + } + } return paNoError; @@ -4184,6 +4257,7 @@ static PaError ValidateSpecificStreamParameters( + /* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, @@ -4231,7 +4305,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } /* validate inputStreamInfo */ - result = ValidateSpecificStreamParameters(inputParameters, inputParameters->hostApiSpecificStreamInfo); + result = ValidateSpecificStreamParameters(inputParameters, inputParameters->hostApiSpecificStreamInfo, 1 ); if(result != paNoError) { PaWinWDM_SetLastErrorInfo(result, "Host API stream info not supported (in)"); @@ -4266,7 +4340,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } /* validate outputStreamInfo */ - result = ValidateSpecificStreamParameters( outputParameters, outputParameters->hostApiSpecificStreamInfo ); + result = ValidateSpecificStreamParameters( outputParameters, outputParameters->hostApiSpecificStreamInfo, 0 ); if (result != paNoError) { PaWinWDM_SetLastErrorInfo(result, "Host API stream info not supported (out)"); @@ -4471,8 +4545,16 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, PaWinWdmFilter* pFilter; PaWinWdmDeviceInfo* pDeviceInfo; PaWinWdmPin* pPin; + PaWinWDMKSInfo* pInfo = (PaWinWDMKSInfo*)(outputParameters->hostApiSpecificStreamInfo); unsigned validBitsPerSample = 0; PaWinWaveFormatChannelMask channelMask = PaWin_DefaultChannelMask( userOutputChannels ); + if (pInfo && (pInfo->flags & paWinWDMKSUseGivenChannelMask)) + { + PA_DEBUG(("Using channelMask 0x%08X instead of default 0x%08X\n", + pInfo->channelMask, + channelMask)); + channelMask = pInfo->channelMask; + } result = paSampleFormatNotSupported; pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[outputParameters->device]; @@ -4960,8 +5042,8 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if (result != paNoError) { unsigned long pos = 0xdeadc0de; - PA_DEBUG(("Failed to register capture position register, using PinGetAudioPositionViaIOCTL\n")); - stream->capture.pPin->fnAudioPosition = PinGetAudioPositionViaIOCTL; + PA_DEBUG(("Failed to register capture position register, using PinGetAudioPositionViaIOCTLWrite\n")); + stream->capture.pPin->fnAudioPosition = PinGetAudioPositionViaIOCTLWrite; /* Test position function */ result = (stream->capture.pPin->fnAudioPosition)(stream->capture.pPin, &pos); if (result != paNoError || pos != 0x0) @@ -4974,7 +5056,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } else { - stream->capture.pPin->fnAudioPosition = PinGetAudioPositionDirect; + stream->capture.pPin->fnAudioPosition = PinGetAudioPositionMemoryMapped; } } break; @@ -5082,8 +5164,8 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if (result != paNoError) { unsigned long pos = 0xdeadc0de; - PA_DEBUG(("Failed to register rendering position register, using PinGetAudioPositionViaIOCTL\n")); - stream->render.pPin->fnAudioPosition = PinGetAudioPositionViaIOCTL; + PA_DEBUG(("Failed to register rendering position register, using PinGetAudioPositionViaIOCTLRead\n")); + stream->render.pPin->fnAudioPosition = PinGetAudioPositionViaIOCTLRead; /* Test position function */ result = (stream->render.pPin->fnAudioPosition)(stream->render.pPin, &pos); if (result != paNoError || pos != 0x0) @@ -5096,7 +5178,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } else { - stream->render.pPin->fnAudioPosition = PinGetAudioPositionDirect; + stream->render.pPin->fnAudioPosition = PinGetAudioPositionMemoryMapped; } } break; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 67dfac6..973568f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,6 +4,10 @@ MACRO(ADD_TEST appl_name) ADD_EXECUTABLE(${appl_name} "${appl_name}.c") TARGET_LINK_LIBRARIES(${appl_name} portaudio_static) + SET_TARGET_PROPERTIES(${appl_name} + PROPERTIES + FOLDER "Test" + ) ENDMACRO(ADD_TEST) ADD_TEST(patest_longsine)