diff --git a/include/pa_asio.h b/include/pa_asio.h index 27cbd3b81..7644ebb4f 100644 --- a/include/pa_asio.h +++ b/include/pa_asio.h @@ -88,6 +88,76 @@ PaError PaAsio_ShowControlPanel( PaDeviceIndex device, void* systemSpecific ); +/** ASIO message types. + + These mostly correspond with asioMessage calls from the ASIO SDK. + ASIO's sampleRateDidChange is adapted to use this callback. + Refer to ASIO SDK documentation for complete information. +*/ +typedef enum PaAsioMessageType +{ + /** The driver requests that it be reset (by closing and re-opening the stream). + Typically dispatched when the user changes driver settings. + Recommend closing, re-opening and restarting stream, and always returning 1. + + params: + none. + */ + paAsioResetRequest = 1, + + /** Informs the application that a sample-rate change was detected. + Recommend noting the new sample-rate, but no action is needed. + + params: + opt[0] -- the new sample-rate. + */ + paAsioSampleRateChanged = 2, + + /** Informs the application that the driver has a new preferred buffer size. + Recommend handling like paAsioResetRequest. + + params: + value -- the new preferred buffer size. + */ + paAsioBufferSizeChange = 3, + + /** Informs the application that the driver has gone out of sync, invalidating timestamps. + Recommend handling like paAsioResetRequest. + + params: + none. + */ + paAsioResyncRequest = 4, + + /** Informs the application that the driver's latencies have changed. + FIXME: The only way to query the new latencies is to reset the stream. + Recommend ignoring unless latency reporting is critical. + + params: + none. + */ + paAsioLatenciesChanged = 5, + +} PaAsioMessageType; + +/** ASIO message callback, set in PaAsioStreamInfo. + Do not call PortAudio or PaAsio functions inside this callback! + + @param value Message-specific integer value. + Indicates buffer size in paAsioBufferSizeChange. + + @param message Message-specific pointer value. + Unused as of the ASIO 2.2 SDK. + + @param opt Message-specific double value. + opt[0] indicates sample rate in paAsioSampleRateChange. + + @param userData The value of a user supplied pointer passed to + Pa_OpenStream() intended for storing synthesis data etc. + + @return True if the application handled the message, false otherwise. +*/ +typedef long PaAsio_MessageCallback( long messageType, long value, void *message, double *opt, void *userData ); /** Retrieve a pointer to a string containing the name of the specified input channel. The string is valid until Pa_Terminate is called. @@ -121,11 +191,12 @@ PaError PaAsio_SetStreamSampleRate( PaStream* stream, double sampleRate ); #define paAsioUseChannelSelectors (0x01) +#define paAsioUseMessageCallback (0x02) typedef struct PaAsioStreamInfo{ unsigned long size; /**< sizeof(PaAsioStreamInfo) */ PaHostApiTypeId hostApiType; /**< paASIO */ - unsigned long version; /**< 1 */ + unsigned long version; /**< 2 */ unsigned long flags; @@ -140,6 +211,13 @@ typedef struct PaAsioStreamInfo{ result. */ int *channelSelectors; + + /** ASIO message callback. + Include paAsioUseMessageCallback in flags to enable. + Unsupported in Blocking I/O mode. + If a callback is supplied for both input and output, it will be called twice! + */ + PaAsio_MessageCallback *messageCallback; }PaAsioStreamInfo; diff --git a/src/hostapi/asio/pa_asio.cpp b/src/hostapi/asio/pa_asio.cpp index 63ab6a7b8..f391bb5f9 100644 --- a/src/hostapi/asio/pa_asio.cpp +++ b/src/hostapi/asio/pa_asio.cpp @@ -1629,6 +1629,9 @@ typedef struct PaAsioStream PaStreamCallbackFlags callbackFlags; PaAsioStreamBlockingState *blockingState; /**< Blocking i/o data struct, or NULL when using callback interface. */ + + /* Message callbacks copied from PaAsioStreamInfo */ + PaAsio_MessageCallback *messageCallback[2]; } PaAsioStream; @@ -1871,10 +1874,28 @@ static PaError ValidateAsioSpecificStreamInfo( { if( streamInfo ) { - if( streamInfo->size != sizeof( PaAsioStreamInfo ) - || streamInfo->version != 1 ) + switch( streamInfo->version ) { + case 0: + /* NOTE: Struct version 0 is invalid, possibly indicative of uninitialized data. */ return paIncompatibleHostApiSpecificStreamInfo; + + case 1: + /* NOTE: V1 structure's size is smaller by one pointer. */ + if( streamInfo->size < sizeof( PaAsioStreamInfo ) - sizeof(PaAsio_MessageCallback*) ) + return paIncompatibleHostApiSpecificStreamInfo; + break; + + case 2: + if( streamInfo->size != sizeof( PaAsioStreamInfo ) ) + return paIncompatibleHostApiSpecificStreamInfo; + break; + + default: + /* Newer versions of the struct must not be smaller. */ + if( streamInfo->size < sizeof( PaAsioStreamInfo ) ) + return paIncompatibleHostApiSpecificStreamInfo; + break; } if( streamInfo->flags & paAsioUseChannelSelectors ) @@ -1889,6 +1910,12 @@ static PaError ValidateAsioSpecificStreamInfo( return paInvalidChannelCount; } } + + if( streamInfo->flags & paAsioUseMessageCallback ) + { + if( streamInfo->version < 2 || !streamInfo->messageCallback ) + return paIncompatibleHostApiSpecificStreamInfo; + } } return paNoError; @@ -2002,7 +2029,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, PaError result = paNoError; PaAsioHostApiRepresentation *asioHostApi = (PaAsioHostApiRepresentation*)hostApi; PaAsioStream *stream = 0; - PaAsioStreamInfo *inputStreamInfo, *outputStreamInfo; + PaAsioStreamInfo *inputStreamInfo = NULL, *outputStreamInfo = NULL; unsigned long framesPerHostBuffer; int inputChannelCount, outputChannelCount; PaSampleFormat inputSampleFormat, outputSampleFormat; @@ -2742,6 +2769,21 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->isStopped = 1; stream->isActive = 0; + stream->messageCallback[0] = NULL; + stream->messageCallback[1] = NULL; + if( usingBlockingIo ) + { + /* Disable message callback due to policy of overwriting userData above. */ + } + else + { + /* Message callback may be defined in versions >= 2 of ASIO streamInfo. */ + if( inputStreamInfo && ( inputStreamInfo->flags & paAsioUseMessageCallback) ) + stream->messageCallback[0] = inputStreamInfo->messageCallback; + if( outputStreamInfo && (outputStreamInfo->flags & paAsioUseMessageCallback) ) + stream->messageCallback[1] = outputStreamInfo->messageCallback; + } + asioHostApi->openAsioDeviceIndex = asioDeviceIndex; theAsioStream = stream; @@ -3176,6 +3218,26 @@ previousTime = paTimeInfo.currentTime; } +static long fireMessageCallback( PaAsioStream *asioStream, long messageType, long value, void* message, double* opt ) +{ + void *userData = asioStream->streamRepresentation.userData; + + long ret = 0; + + /* Fire callbacks and determine whether one and/or the other handles the message. */ + if( asioStream->messageCallback[0] ) + { + if( (*asioStream->messageCallback[0])( messageType, value, message, opt, userData ) ) ret = 1; + } + if( asioStream->messageCallback[1] ) + { + if( (*asioStream->messageCallback[1])( messageType, value, message, opt, userData ) ) ret = 1; + } + + return ret; +} + + static void sampleRateChanged(ASIOSampleRate sRate) { // TAKEN FROM THE ASIO SDK @@ -3186,14 +3248,25 @@ static void sampleRateChanged(ASIOSampleRate sRate) // AES/EBU or S/PDIF digital input at the audio device. // You might have to update time/sample related conversion routines, etc. - (void) sRate; /* unused parameter */ - PA_DEBUG( ("sampleRateChanged : %d \n", sRate)); + double opt = sRate; + + PA_DEBUG( ("asioSampleRateChanged : %f hz\n", opt)); + + if (!theAsioStream) + { + /* Some ASIO drivers will fire messages during OpenStream. We ignore them. */ + PA_DEBUG( (" ...message blocked, device not open yet.\n")); + return; + } + + fireMessageCallback( theAsioStream, paAsioSampleRateChanged, 0, 0, &opt ); } static long asioMessages(long selector, long value, void* message, double* opt) { // TAKEN FROM THE ASIO SDK // currently the parameters "value", "message" and "opt" are not used. + int doClientCallback = 0, paAsioMessageType = 0; long ret = 0; (void) message; /* unused parameters */ @@ -3201,13 +3274,26 @@ static long asioMessages(long selector, long value, void* message, double* opt) PA_DEBUG( ("asioMessages : %d , %d \n", selector, value)); + if (!theAsioStream) + { + /* Some ASIO drivers will fire messages during OpenStream. We ignore them. */ + PA_DEBUG( (" ...message blocked, device not open yet.\n")); + return 0; + } + switch(selector) { case kAsioSelectorSupported: if(value == kAsioResetRequest - || value == kAsioEngineVersion || value == kAsioResyncRequest || value == kAsioLatenciesChanged + || value == kAsioBufferSizeChange) + { + /* Did the client register a callback? */ + if( theAsioStream->messageCallback[0] || theAsioStream->messageCallback[1] ) + ret = 1L; + } + if(value == kAsioEngineVersion // the following three were added for ASIO 2.0, you don't necessarily have to support them || value == kAsioSupportsTimeInfo || value == kAsioSupportsTimeCode @@ -3217,6 +3303,8 @@ static long asioMessages(long selector, long value, void* message, double* opt) case kAsioBufferSizeChange: //printf("kAsioBufferSizeChange \n"); + paAsioMessageType = paAsioBufferSizeChange; + doClientCallback = 1; break; case kAsioResetRequest: @@ -3225,13 +3313,9 @@ static long asioMessages(long selector, long value, void* message, double* opt) // Reset the driver is done by completely destruct is. I.e. ASIOStop(), ASIODisposeBuffers(), Destruction // Afterwards you initialize the driver again. - /*FIXME: commented the next line out - - see: "PA/ASIO ignores some driver notifications it probably shouldn't" - http://www.portaudio.com/trac/ticket/108 - */ - //asioDriverInfo.stopped; // In this sample the processing will just stop - ret = 1L; + // Notify the application, which should then reset the stream. + paAsioMessageType = paAsioResetRequest; + doClientCallback = 1; break; case kAsioResyncRequest: @@ -3241,14 +3325,16 @@ static long asioMessages(long selector, long value, void* message, double* opt) // Windows Multimedia system, which could loose data because the Mutex was hold too long // by another thread. // However a driver can issue it in other situations, too. - ret = 1L; + paAsioMessageType = paAsioResyncRequest; + doClientCallback = 1; break; case kAsioLatenciesChanged: // This will inform the host application that the drivers were latencies changed. // Beware, it this does not mean that the buffer sizes have changed! // You might need to update internal delay data. - ret = 1L; + paAsioMessageType = paAsioLatenciesChanged; + doClientCallback = 1; //printf("kAsioLatenciesChanged \n"); break; @@ -3274,6 +3360,10 @@ static long asioMessages(long selector, long value, void* message, double* opt) ret = 0; break; } + + if( doClientCallback != 0 ) + ret = fireMessageCallback( theAsioStream, paAsioMessageType, value, message, opt ); + return ret; }