From 065a8b303fedb962cfda73856209c24b86407d24 Mon Sep 17 00:00:00 2001 From: defiantnerd <97224712+defiantnerd@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:07:52 +0200 Subject: [PATCH] ASIO: provide bufferSwitchTimeInfo instead of leaving it NULL RtApiAsio installed three of the four ASIO host callbacks and set asioCallbacks.bufferSwitchTimeInfo to NULL. Any driver that calls it therefore transfers control to address 0 and takes the process down. asioMessages() answers kAsioSupportsTimeInfo with 0, so by the letter of the SDK a driver should use bufferSwitch instead. Steinberg's Generic Low Latency ASIO driver calls bufferSwitchTimeInfo regardless, crashing on the first ASIOStart(). Removing kAsioSupportsTimeInfo from the kAsioSelectorSupported list as well does not help, so it consults neither answer. Since the host cannot control how a third-party driver behaves, and a call through a null function pointer is unrecoverable, supply the callback and forward it to bufferSwitch. The time info is not used. Fixes #485. Very likely the root cause of #409, and possibly #443. --- RtAudio.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/RtAudio.cpp b/RtAudio.cpp index b7dc15a9..58b7d770 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -3365,6 +3365,8 @@ struct AsioHandle { static const char* getAsioErrorString( ASIOError result ); static void sampleRateChanged( ASIOSampleRate sRate ); static long asioMessages( long selector, long value, void* message, double* opt ); +static void bufferSwitch( long index, ASIOBool processNow ); +static ASIOTime *bufferSwitchTimeInfo( ASIOTime *timeInfo, long index, ASIOBool processNow ); RtApiAsio :: RtApiAsio() { @@ -3548,6 +3550,16 @@ static void bufferSwitch( long index, ASIOBool /*processNow*/ ) object->callbackEvent( index ); } +// Some drivers call bufferSwitchTimeInfo() regardless of what the host answers to +// kAsioSupportsTimeInfo, so this callback must not be left NULL: those drivers would +// call through a null pointer and take the process down. We make no use of the time +// info, so hand the work to bufferSwitch(). +static ASIOTime *bufferSwitchTimeInfo( ASIOTime * /*timeInfo*/, long index, ASIOBool processNow ) +{ + bufferSwitch( index, processNow ); + return 0L; +} + bool RtApiAsio :: probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, @@ -3825,7 +3837,7 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsig asioCallbacks.bufferSwitch = &bufferSwitch; asioCallbacks.sampleRateDidChange = &sampleRateChanged; asioCallbacks.asioMessage = &asioMessages; - asioCallbacks.bufferSwitchTimeInfo = NULL; + asioCallbacks.bufferSwitchTimeInfo = &bufferSwitchTimeInfo; result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); if ( result != ASE_OK ) { // Standard method failed. This can happen with strict/misbehaving drivers that return valid buffer size ranges