Skip to content

WASAPI settings cause exception with other host API devices #55

New issue

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

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

Already on GitHub? # to your account

Closed
hiccup7 opened this issue Nov 29, 2016 · 11 comments
Closed

WASAPI settings cause exception with other host API devices #55

hiccup7 opened this issue Nov 29, 2016 · 11 comments

Comments

@hiccup7
Copy link

hiccup7 commented Nov 29, 2016

This is a minor annoyance. Not worth fixing if it delays the sounddevice release for the next WinPython release.

Problem: With the WDM-KS and ASIO host API devices are used for the same USB external DAC, sd.default.extra_settings = sd.WasapiSettings(exclusive=False) and sd.default.extra_settings = sd.WasapiSettings(exclusive=True) cause sd.RawOutputStream() to raise an exception. Without sd.default.extra_settings, these host APIs play fine.

I would like to set sd.default.extra_settings = sd.WasapiSettings(exclusive=True) as my normal default. It is not intuitive to me that WASAPI settings should cause an exception for WDM-KS and ASIO host API devices. I expect WDM-KS and ASIO host API devices to ignore WASAPI settings.

@mgeier
Copy link
Member

mgeier commented Nov 30, 2016

Ignoring the settings of other host APIs would raise several problems:

  1. It violates one of the Python Zen mantras: "Errors should never pass silently." If you want to use WASAPI exclusive mode, but by mistake use a non-WASAPI device, you will get no error. People might simply forget to specify a device and they would think they are using a WASAPI device while in reality they are using the non-WASAPI default device.
  2. To be consistent, it would have to be possible to use multiple extra_settings at the same time, which is currently not possible.
  3. Now I'm just following the PortAudio API, so I can blame it for being unintuitive. If I change the behavior, I'll have to take the blame myself ...

What about something completely new instead of extra_settings?
Any ideas?

Currently, extra_settings can only be used for two things: WASAPI exclusive mode and ASIO channel selection. It seems a bit strange to have this whole extra_settings thing just for 2 parameters.
Theoretically, there could be more platform-specific features in the future, but some of them won't work with extra_settings, like e.g. PaAlsa_EnableRealtimeScheduling or PaJack_SetClientName. See also #4.

Another thing that's currently strange, is that I can set WASPI exclusive mode on the input and non-exclusive mode on the output (and vice versa). I don't know if that even makes sense? Is this even possible?

I would be open to removing the platform-specific settings from Stream and play() and all the other classes and functions and just keeping them as module-level settings.
I'm not sure, however, how a sensible API for that would look like.
I guess this would have to be done with individual function calls like sd.set_wasapi_something(some_setting).

@hiccup7
Copy link
Author

hiccup7 commented Nov 30, 2016

WASAPI is unusual because it supports separate client apps for input and output. This suggests independent streams, so maybe one could be shared mode and the other exclusive mode. I have not tried that yet, as I have only been developing Python code for output. With ASIO and WDM-KS, on the other hand, I have experienced failures when I try to use one app for input and another for output to the same device.

Exclusive mode is supported for some WASAPI devices, while other WASAPI devices only support shared mode. So WASAPI settings are not necessarily fixed for a platform. I just happen to have a Python module where exclusive mode is desired for all WASAPI devices used (I won't use WASAPI devices that don't support exclusive mode in this module).

I posted this PR because I found WASAPI settings a little unintuitive -- not really a problem for me. Please consider my feedback in the evolution of sounddevice settings. Module default settings would work well for platform settings, while stream class settings would work well for device-specific settings.

@mgeier
Copy link
Member

mgeier commented Nov 30, 2016

I have not tried that yet

Would be nice if you could try that.

Please consider my feedback

Sure, I'm thankful for that and I mentioned my concerns above.

Do you agree that ignoring non-matching extra_settings has some disadvantages?

Do you have a concrete suggestion for a different API for the host API specific settings?

@hiccup7
Copy link
Author

hiccup7 commented Dec 1, 2016

Do you agree that ignoring non-matching extra_settings has some disadvantages?

Yes. Your Python Zen philosophy has been working well.

Do you have a concrete suggestion for a different API for the host API specific settings?

How about class settings for each host API? Then support all of them in the default class for use as platform settings, with no exception checking. And also support all of them as separate keyword arguments in Stream and play(), but raise an exception if a keyword argument's associated host API does not match the selected device's associated host API. This way, the sounddevice user has the choice of whether he/she wants exception checking or not.

The reason I would like this is because PortAudio provides five virtual devices for one hardware device on my PC. I know which hardware device I want to use when I write my code, so I want to configure all host APIs in the default class. Then I switch between the (virtual) devices to compare latency, sound quality, etc.

@mgeier
Copy link
Member

mgeier commented Dec 1, 2016

I think I understand most of your suggestion but not everything. Can you please provide some concrete (but still hypothetical) code examples which show the proposed usage?

If I understand correctly, you want to pass the default object to the individual functions.
I definitely don't like that idea. The function arguments should be passed individually, not packed into some container object.
If you want a container object (e.g. a dict), you can try something like this (untested!):

settings1 = dict(device=3)
settings2 = dict(device=7, extra_settings=extra_settings)

settings = settings1

sd.play(mydata, fs, **settings)

This way, you could collect all your possible combinations of settings but still quickly switch between them.

@hiccup7
Copy link
Author

hiccup7 commented Dec 1, 2016

I haven't spent enough time reading sounddevice.py to understand it in detail. How are settings from the default object used by Stream and play()? Is there a base class common to all of these?

@mgeier
Copy link
Member

mgeier commented Dec 1, 2016

You don't need to understand the current implementation, I'd just like to do some brainstorming how the usage could look like in the future. Let's worry about the implementation afterwards.

But since you asked: The default object is just sitting there in the module namespace and any function that needs to know about a default value, just gets it from there. There nothing more behind it.

All stream classes have a common base class (named _StreamBase), but that's not really relevant for how the default object works.
play() is just a free function that happens to use an OutputStream inside.

@hiccup7
Copy link
Author

hiccup7 commented Dec 1, 2016

So I am imaging a common base class with some extra settings specific to each host API. These settings could be read and written using a default class. The Stream class would use settings from the base class unless over-ridden by parameters to self._init_().

Since the default class doesn't know which host API will be used, it would not raise an exception for writing WASAPI extra settings even if the Stream class later uses an ASIO device. But the Stream class would raise an exception for WASAPI parameters when an ASIO device is selected.

@mgeier
Copy link
Member

mgeier commented Dec 1, 2016

Now I understand your suggestion less than before.
Can you please give concrete code examples how your suggested API should be used (not how it should be implemented!) and which calls should be successful and which calls should raise errors.

@mgeier
Copy link
Member

mgeier commented Jan 26, 2017

@hiccup7 Do you want to continue on this?

@hiccup7
Copy link
Author

hiccup7 commented May 3, 2017

sounddevice is working great for me. As I mentioned in the opening post, this issue is a minor annoyance for me. I have worked around it, so I am satisfied. No need to continue on this topic.

@hiccup7 hiccup7 closed this as completed May 3, 2017
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants