Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ASIO messages #519

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 79 additions & 1 deletion include/pa_asio.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
EvanBalster marked this conversation as resolved.
Show resolved Hide resolved

/** 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.
Expand Down Expand Up @@ -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;
RossBencina marked this conversation as resolved.
Show resolved Hide resolved

Expand All @@ -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;


Expand Down
120 changes: 105 additions & 15 deletions src/hostapi/asio/pa_asio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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*) )
philburk marked this conversation as resolved.
Show resolved Hide resolved
RossBencina marked this conversation as resolved.
Show resolved Hide resolved
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 )
EvanBalster marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -1889,6 +1910,12 @@ static PaError ValidateAsioSpecificStreamInfo(
return paInvalidChannelCount;
}
}

if( streamInfo->flags & paAsioUseMessageCallback )
{
if( streamInfo->version < 2 || !streamInfo->messageCallback )
return paIncompatibleHostApiSpecificStreamInfo;
}
}

return paNoError;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) )
RossBencina marked this conversation as resolved.
Show resolved Hide resolved
stream->messageCallback[0] = inputStreamInfo->messageCallback;
if( outputStreamInfo && (outputStreamInfo->flags & paAsioUseMessageCallback) )
stream->messageCallback[1] = outputStreamInfo->messageCallback;
}

asioHostApi->openAsioDeviceIndex = asioDeviceIndex;

theAsioStream = stream;
Expand Down Expand Up @@ -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
Expand All @@ -3186,28 +3248,52 @@ 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 */
(void) opt;

PA_DEBUG( ("asioMessages : %d , %d \n", selector, value));

if (!theAsioStream)
{
/* Some ASIO drivers will fire messages during OpenStream. We ignore them. */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like incomplete code (same in the sampleRateChanged handler). Is there a rationale for dropping these messages?

To me, it seems that if we're going to pass through driver messages, we should pass through all of them. All it would take would be to stash the required callback data for use here even before the stream is fully created.

Copy link
Contributor Author

@EvanBalster EvanBalster Mar 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has been quite a while since I visited this code. My faint recollection is that some ASIO drivers were generating spurious ResetRequest messages while opening the stream, but I'd need to look into it.

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
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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;

Expand All @@ -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;
}

Expand Down
Loading