Skip to content

Comments

Add support for ASIO messages#519

Open
EvanBalster wants to merge 17 commits intoPortAudio:masterfrom
EvanBalster:eb-asio-messages
Open

Add support for ASIO messages#519
EvanBalster wants to merge 17 commits intoPortAudio:masterfrom
EvanBalster:eb-asio-messages

Conversation

@EvanBalster
Copy link
Contributor

@EvanBalster EvanBalster commented Feb 23, 2021

This PR addresses PortAudio's bad habit of ignoring certain highly actionable messages from ASIO drivers by providing a callback mechanism to the application. Applications may receive the following events:

  • Reset Request
  • Resync Request
  • Sample Rate Changed
  • Buffer Size Changed
  • Latencies Changed

In particular, Reset Request is commonly used to notify the ASIO client when the stream should be reset in order to put configuration changes into effect or resume the interrupted flow of audio. Implementing automatic reset allows the user to interactively re-configure the ASIO stream by way of a driver-specific UI without superfluous manual steps in the client application.

The patch works by extending the ASIO configuration structure and, therefore, does not change PortAudio's ABI. I have been using this functionality in production for over four years in an application with an integrated crash forensics system and haven't identified any faults.

@EvanBalster
Copy link
Contributor Author

Additional context: This PR is a recreation of an old merge request on PortAudio's previous Assembla-hosted repository. readers can find that Merge Request here...

https://app.assembla.com/spaces/portaudio/git/merge_requests/3939463

@npostavs
Copy link

npostavs commented Mar 15, 2021

I have a PR that overlaps somewhat with this, so we should probably coordinate that (#473). The difference is that I only cover kAsioResetRequest and kAsioBufferSizeChange, and the callback isn't tied to a stream. What happens with our code if the user changes the buffer size before the application is playing/recording sound? I think I mixed up between opening vs starting the stream here. I believe this PR can simply replace mine.

@philburk philburk added this to the V19.8 milestone Apr 8, 2021
@RossBencina RossBencina self-assigned this Apr 29, 2021
@RossBencina RossBencina added the src-asio Steinberg ASIO Host API /src/hostapi/asio label Apr 29, 2021
@RossBencina
Copy link
Collaborator

I still need to review this properly, but two obvious things:

  1. This extension should be selected using a flag (same as other host API extensions) not (only) by inspecting the struct version
  2. it needs to be documented is that it is the client's problem to deal with synchronising notification callbacks that occur during Pa_CloseStream() to avoid race conditions.

@EvanBalster
Copy link
Contributor Author

I notice this was tagged for inclusion in the next release of PortAudio.

I'm currently working on ASIO functionality in my company's application and expect I will have an opportunity to revisit this Pull Request in the coming weeks.

@RossBencina RossBencina removed this from the V19.8 milestone Aug 29, 2022
@dyfer
Copy link

dyfer commented Mar 8, 2023

If I understand correctly, this PR would prevent PortAudio apps from using JackRouter (routing audio through JACK) on Windows, is this correct? If so, such change would affect SuperCollider Windows users, if they use JACK. I understand that one can't make everyone happy, but I think this would be an unfortunate regression.

To clarify, the issue only occurs with older versions of JACK (pre-2021); it does not occur with the current version.

EDIT: maybe blocking JackRouter could be enabled/disabled at compile time with a CMake option? That way those building PortAudio themselves could make the appropriate decision as they see fit.

@EvanBalster
Copy link
Contributor Author

EvanBalster commented Mar 9, 2023

If I understand correctly, this PR would prevent PortAudio apps from using JackRouter (routing audio through JACK) on Windows, is this correct? If so, such change would affect SuperCollider Windows users, if they use JACK. I understand that one can't make everyone happy, but I think this would be an unfortunate regression.

To clarify, the issue only occurs with older versions of JACK (pre-2021); it does not occur with the current version.

EDIT: maybe blocking JackRouter could be enabled/disabled at compile time with a CMake option? That way those building PortAudio themselves could make the appropriate decision as they see fit.

This PR blocks interfacing with JACK through its ASIO adaptor, "JackRouter", in order to avoid a problem that historically rendered the JACK ASIO adaptor nonfunctional for 64-bit apps. PortAudio also supports interfacing directly with the JACK API on Windows (ie, natively rather than through ASIO/JackRouter) which will understandably be the better approach to making Windows applications with Jack support.

If the bug in JackRouter has been fixed, and that fix has propagated widely, the guard could be removed -- perhaps with a CMake option as you suggest. But be advised that the JackRouter problem causes applications to crash when initializing PortAudio on any system with the faulty ASIO driver; this usually means an app without this guard will be unopenable on affected systems, regardless of whether the user intends to interface with JACK. The nature of the crash also makes inspection of the debug trace rather difficult.

@dyfer
Copy link

dyfer commented Mar 9, 2023

I'm aware of the bug, and that it prevents the application from starting.

Is building PortAudio with the JACK backend on Windows actually operational? I don't think that was the case in the past. Also, would the application be then linked against the Jack library? IMO it's quite important to allow the same binary to run both on systems that do and don't have JACK installed.

If PA's JACK backend works on Windows, AND if the JACK library would be dynamically loaded, then indeed it is a viable workaround.

As I wrote, the fixed JackRouter has been around for over two years now. I don't have the data on how well it is propagated. Since SuperCollider uses PortAudio on Windows, this has come up in SuperCollider's issue tracker, so it is likely that there are users who use PortAudio + Jack (with fixed JackRouter) on Windows.

@EvanBalster
Copy link
Contributor Author

Pardon my two year (minus one day) delay.

  • Removed the rule blacklisting the JackRouter virtual ASIO driver.
  • Added PaAsioUseMessageCallback flag as requested by @RossBencina, and added validation code.

Note that the latter change will break compatibility with any existing applications that use my branch (the message callback will not be used because the flag is not set).

I considered checking for this in ValidateAsioSpecificStreamInfo but that would represent a departure from the way channelSelectors is validated (ie, non-null channelSelectors is simply ignored if the flag is not set). This gives me the impression the library is meant to tolerate the case where unused fields in the structure are uninitialized rather than zero-initialized.

@RossBencina
Copy link
Collaborator

Phil and I discussed the struct version check. Phil raised the point that the check will fail if a newer version of the struct is passed even if it does not use new features. Something to think about.

Given that I don't think we have a clear and stable global policy on struct version management I think the way that the version check is being handled in this PR is fine to merge as-is even if it's not perfect.

@EvanBalster
Copy link
Contributor Author

Phil and I discussed the struct version check. Phil raised the point that the check will fail if a newer version of the struct is passed even if it does not use new features. Something to think about.

Given that I don't think we have a clear and stable global policy on struct version management I think the way that the version check is being handled in this PR is fine to merge as-is even if it's not perfect.

I noticed this. I was copying the behavior of the previous code (the current library will fail any version other than 1). At your discretion I'd be happy to modify the validation check to facilitate forward compatibility.

@EvanBalster
Copy link
Contributor Author

I went ahead and implemented the forward-compatibility as it seems unlikely to cause problems compared to the alternative. Implementors may need a fallback to deal with the possibility of an older portaudio DLL that rejects non-1 versions though. 0308b1e

@philburk
Copy link
Collaborator

philburk commented Mar 1, 2024

The CI is still finding some trailing white space.

@EvanBalster
Copy link
Contributor Author

At Ross' suggestion I've split off #885 which fixes a bug in stream info validation. 885 should be merged before this PR; otherwise it won't be possible to use ASIO messages without also specifying a channel mapping.

@RossBencina
Copy link
Collaborator

#885 is merged now


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.

@philburk
Copy link
Collaborator

According to Gemini, this cryptic command might trigger a CI test.

$ bors r+

@philburk
Copy link
Collaborator

My command did not trigger a CI test. I think this PR needs to be rebased on master.

@RossBencina
Copy link
Collaborator

Discussion invited:

This PR has stalled because there is a policy decision to resolve about what to do with ASIO driver callbacks while the stream is being opened. I don't like the idea of suppressing messages in a low-level pass-through API -- thatshould be a decision for higher layers to make.

The current PR code suppresses ASIO message callbacks "while the stream is being opened," in-fact only up to the sequence point in pa_asio.cpp OpenStream() that sets theAsioStream = stream; [1]. Note that pa_front.c Pa_OpenStream does more work after that before returning to the client. So the currently implemented mechanism may still result in callbacks being made prior to when Pa_OpenStream returns. This is unavoidable and consistent with other asynchronous callbacks in PortAudio.

If I understand correctly, the purpose of suppressing callbacks is to avoid issues with the client getting stuck in a loop of infinite resets while the stream is being opened. On the other hand, if a legitimate reset happens after LoadAsioDriver but before Pa_OpenStream returns the current PR code will suppress that event. This is undesirable.

Baseline proposal: Don't suppress any callbacks. The client will have to do the work to ignore callbacks up to some sequence point after Pa_OpenStream has returned, either using a flag or a timer. This has effectively the same behavior as I described in the previous paragraph but gives the client full control over exactly when the stop-ignoring sequence point happens. Document that the notification callback can occur prior to Pa_OpenStream returning and client's should avoid getting into infinite reset loops.

Enhancement: add a flags parameter to the message notification callback. Define at least two flag fields: paAsioLoadingDriver, paAsioConfiguringDriver . The first is set for notifications that occur prior to LoadAsioDriver returning. The second is set prior to OpenStream() returning. This allows the client to distinguish between "reset while opening ASIO driver" callbacks and "reset after opening" callbacks, which greatly reduces or eliminates race conditions.

[1] NB assignment should be atomic/have release memory barrier if this mechanism is used

@EvanBalster
Copy link
Contributor Author

EvanBalster commented Sep 27, 2025

I think the flags parameter would probably be the best way to do it. Or adding a couple new event codes corresponding to the transitions between those stages of initialization.

Apologies for leaving this untouched for so long; I was in poor health throughout 2023-2024.

@RossBencina
Copy link
Collaborator

@EvanBalster sorry to hear about your health issues. Phil and I are discussing moving this PR off the 19.8 release milestone. I think there's still time to ship it if you (or someone else) would like to work on it now, but there's no pressure, we can just take care of it later.

@EvanBalster
Copy link
Contributor Author

Those health issues (post-COVID syndrome) made an unexpected comeback so I can't commit to anything short term.

@RossBencina
Copy link
Collaborator

@EvanBalster no problems. I wish you the best for your recovery.

Unless someone else is interested in working on this PR we will move it to v19.9

@RossBencina RossBencina modified the milestones: V19.8, V19.9 Nov 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Priority: High src-asio Steinberg ASIO Host API /src/hostapi/asio

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants