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

Provide a Host API? (A path towards supporting alt-backends like ASIO, Windows Exclusive Mode, PulseAudio, Jack) #204

Closed
mitchmindtree opened this issue Feb 12, 2018 · 7 comments

Comments

@mitchmindtree
Copy link
Member

mitchmindtree commented Feb 12, 2018

PortAudio (a C library with similar goals to CPAL) provide what they call a Host API for choosing between different audio APIs that might exist on a single system.

Any operating system may have any number of Host APIs to the system's audio devices. E.g. on Linux this might include Alsa, Jack and PulseAudio. In the case of Windows, perhaps Asio, Shared and Exclusive hosts could be included. In the case of macOS and iOS, CoreAudio would likey be the only host in most cases.

I imagine general use might look something like this:

let host = cpal::default_host();
let device = host.default_output_device().unwrap();
// etc

Selecting a specific host might look something like this:

let host = cpal::hosts()
    .find(|host| host.name().contains("jack"))
    .expect("cannot find jack audio backend");
let device = host.default_input_device().unwrap();
// etc

This way a low-latency application could attempt to select low-latency supporting hosts with something like this:

let host = if cfg!(target_os = "linux") {
    cpal::hosts().find(|h| h.name().contains("jack")).unwrap_or_else(cpal::default_host)
} else if cfg!(target_os = "windows") {
    cpal::hosts().find(|h| h.name().contains("asio")).unwrap_or_else(cpal::default_host)
} else {
    cpal::default_host()
};
This was referenced Feb 12, 2018
@mitchmindtree
Copy link
Member Author

It's looking like my current project may need support for ASIO soon. @tomaka it would be great to get some feedback from you on this API as I'd like to go ahead and begin implementing it soon (along with the ASIO backend) if you agree it sounds like a decent approach?

@tomaka
Copy link
Collaborator

tomaka commented Feb 13, 2018

I'm really not a fan of this. The "host" is supposed to be cpal because cpal is supposed to be as low-level as possible.

However I'm ok with adding platform-specific methods that allow choosing a backend, similar to what winit/glutin do.

@mitchmindtree
Copy link
Member Author

OK I see where you're coming from, I'll refer to winit's approach when attempting to implement this.

@mitchmindtree
Copy link
Member Author

OK, I've been reflecting a lot on this over the past year and a half since opening this issue and I'm convinced that providing some sort of Hosts API is the correct approach for CPAL.

The "host" is supposed to be cpal because cpal is supposed to be as low-level as possible.

I totally agree that CPAL should be as low-level as possible, however I don't think it is possible for CPAL to be "the" host (singular) unless two conditions are met:

  • There is only one, clear, standard host API (for interfacing with devices, spawning streams, etc) for each platform.
  • Users do not need the ability to switch between multiple hosts at run-time.

Unfortunately, neither of these are the case, especially in the pro-audio community, e.g. VST, DAW, Multimedia workstation, etc, developers. On Windows, the default WDM API lacks support for many popular audio workstations and audio devices. E.g. in a recent commercial installation with @freesig we had to add ASIO support (#221) just to interface with Dante in a manner that allowed for more than 2 channels. It is also common for DAWs and other multimedia workstations to allow for selecting between multiple available hosts on the system in order to ensure users can access their hardware devices and achieve the desired latency that they require. Again, this is especially the case on Windows (ASIO, WDM) and Linux (ALSA, Pulse, Jack, who knows). Bitwig and Reaper come to mind as examples I use regularly on Linux, though I'm sure there are many more examples on both platforms.

Thoughts on the API

While this would be a breaking change, the changes should be trivial. Rather than enumerating and selecting devices via a global function, the user instead retrieves devices via a host instance.

The host instance would take care of ensuring the API is initialised and de-initialised correclty via RAII (ASIO for example needs an initialisation and termination process).

Perhaps a Host trait could be used to:

  1. Ensure API consistency across the different hosts and
  2. Allow for the user to select between different Hosts at runtime.

Host / Device Exclusivity?

In most cases, a host API requires exclusive access to the device(s) with which it has established streams, etc. Should we restrict the API to only allow for one host instance at any point in time? Or perhaps we should try to provide a more fine-grained API that works on a per-device basis (e.g. allowing host "foo" to connect to device "A" while host "bar" connects to device "B")? I guess the final option is to not enfoce this API-wise, and instead document the behaviour?


I'd like to get around to addressing this issue within the next week or two - I'll continue to add thoughts related to the design and progress here.

@ishitatsuyuki
Copy link
Collaborator

the user instead retrieves devices via a host instance.

I think this is good.

A few thoughts:

  1. WASAPI is a host, Shared and Exclusive is not. They're API options, and also stream options.
  2. I think host should be selected with concrete types, not string filtering. I currently don't have a big picture of how this would fit into API, but as we may need to set backend specific options, it should be type safe.

I guess the final option is to not enforce this API-wise, and instead document the behavior?

Agreed.

@mitchmindtree
Copy link
Member Author

WASAPI is a host, Shared and Exclusive is not. They're API options, and also stream options.

Yes agreed, not sure what I was thinking here exactly in my OP.

I think host should be selected with concrete types, not string filtering.

Yes this sounds good to me!

mitchmindtree added a commit to mitchmindtree/cpal that referenced this issue Jun 24, 2019
This is an implementation of the API described at RustAudio#204. Please see that
issue for more details on the motivation.

-----

A **Host** provides access to the available audio devices on the system.
Some platforms have more than one host available, e.g.
wasapi/asio/dsound on windows, alsa/pulse/jack on linux and so on. As a
result, some audio devices are only available on certain hosts, while
others are only available on other hosts. Every platform supported by
CPAL has at least one **DefaultHost** that is guaranteed to be available
(alsa, wasapi and coreaudio). Currently, the default hosts are the only
hosts supported by CPAL, however this will change as of landing RustAudio#221 (cc
@freesig). These changes should also accommodate support for other hosts
such as jack RustAudio#250 (cc @derekdreery) and pulseaudio (cc @knappador) RustAudio#259.

This introduces a suite of traits allowing for both compile time and
runtime dispatch of different hosts and their uniquely associated device
and event loop types.

A new private **host** module has been added containing the individual
host implementations, each in their own submodule gated to the platforms
on which they are available.

A new **platform** module has been added containing platform-specific
items, including a dynamically dispatched host type that allows for
easily switching between hosts at runtime.

The **ALL_HOSTS** slice contains a **HostId** for each host supported on
the current platform. The **available_hosts** function produces a
**HostId** for each host that is currently *available* on the platform.
The **host_from_id** function allows for initialising a host from its
associated ID, failing with a **HostUnavailable** error. The
**default_host** function returns the default host and should never
fail.

Please see the examples for a demonstration of the change in usage. For
the most part, things look the same at the surface level, however the
role of device enumeration and creating the event loop have been moved
from global functions to host methods. The enumerate.rs example has been
updated to enumerate all devices for each host, not just the default.

**TODO**

- [x] Add the new **Host** API
- [x] Update examples for the new API.
- [x] ALSA host
- [ ] WASAPI host
- [ ] CoreAudio host
- [ ] Emscripten host **Follow-up PR**
- [ ] ASIO host RustAudio#221

cc @ishitatsuyuki more to review for you if you're interested, but it
might be easier after RustAudio#288 lands and this gets rebased.
mitchmindtree added a commit to mitchmindtree/cpal that referenced this issue Jun 24, 2019
This is an implementation of the API described at RustAudio#204. Please see that
issue for more details on the motivation.

-----

A **Host** provides access to the available audio devices on the system.
Some platforms have more than one host available, e.g.
wasapi/asio/dsound on windows, alsa/pulse/jack on linux and so on. As a
result, some audio devices are only available on certain hosts, while
others are only available on other hosts. Every platform supported by
CPAL has at least one **DefaultHost** that is guaranteed to be available
(alsa, wasapi and coreaudio). Currently, the default hosts are the only
hosts supported by CPAL, however this will change as of landing RustAudio#221 (cc
@freesig). These changes should also accommodate support for other hosts
such as jack RustAudio#250 (cc @derekdreery) and pulseaudio (cc @knappador) RustAudio#259.

This introduces a suite of traits allowing for both compile time and
runtime dispatch of different hosts and their uniquely associated device
and event loop types.

A new private **host** module has been added containing the individual
host implementations, each in their own submodule gated to the platforms
on which they are available.

A new **platform** module has been added containing platform-specific
items, including a dynamically dispatched host type that allows for
easily switching between hosts at runtime.

The **ALL_HOSTS** slice contains a **HostId** for each host supported on
the current platform. The **available_hosts** function produces a
**HostId** for each host that is currently *available* on the platform.
The **host_from_id** function allows for initialising a host from its
associated ID, failing with a **HostUnavailable** error. The
**default_host** function returns the default host and should never
fail.

Please see the examples for a demonstration of the change in usage. For
the most part, things look the same at the surface level, however the
role of device enumeration and creating the event loop have been moved
from global functions to host methods. The enumerate.rs example has been
updated to enumerate all devices for each host, not just the default.

**TODO**

- [x] Add the new **Host** API
- [x] Update examples for the new API.
- [x] ALSA host
- [ ] WASAPI host
- [ ] CoreAudio host
- [ ] Emscripten host **Follow-up PR**
- [ ] ASIO host RustAudio#221

cc @ishitatsuyuki more to review for you if you're interested, but it
might be easier after RustAudio#288 lands and this gets rebased.
mitchmindtree added a commit to mitchmindtree/cpal that referenced this issue Jun 24, 2019
This is an implementation of the API described at RustAudio#204. Please see that
issue for more details on the motivation.

-----

A **Host** provides access to the available audio devices on the system.
Some platforms have more than one host available, e.g.
wasapi/asio/dsound on windows, alsa/pulse/jack on linux and so on. As a
result, some audio devices are only available on certain hosts, while
others are only available on other hosts. Every platform supported by
CPAL has at least one **DefaultHost** that is guaranteed to be available
(alsa, wasapi and coreaudio). Currently, the default hosts are the only
hosts supported by CPAL, however this will change as of landing RustAudio#221 (cc
@freesig). These changes should also accommodate support for other hosts
such as jack RustAudio#250 (cc @derekdreery) and pulseaudio (cc @knappador) RustAudio#259.

This introduces a suite of traits allowing for both compile time and
runtime dispatch of different hosts and their uniquely associated device
and event loop types.

A new private **host** module has been added containing the individual
host implementations, each in their own submodule gated to the platforms
on which they are available.

A new **platform** module has been added containing platform-specific
items, including a dynamically dispatched host type that allows for
easily switching between hosts at runtime.

The **ALL_HOSTS** slice contains a **HostId** for each host supported on
the current platform. The **available_hosts** function produces a
**HostId** for each host that is currently *available* on the platform.
The **host_from_id** function allows for initialising a host from its
associated ID, failing with a **HostUnavailable** error. The
**default_host** function returns the default host and should never
fail.

Please see the examples for a demonstration of the change in usage. For
the most part, things look the same at the surface level, however the
role of device enumeration and creating the event loop have been moved
from global functions to host methods. The enumerate.rs example has been
updated to enumerate all devices for each host, not just the default.

**TODO**

- [x] Add the new **Host** API
- [x] Update examples for the new API.
- [x] ALSA host
- [ ] WASAPI host
- [ ] CoreAudio host
- [ ] Emscripten host **Follow-up PR**
- [ ] ASIO host RustAudio#221

cc @ishitatsuyuki more to review for you if you're interested, but it
might be easier after RustAudio#288 lands and this gets rebased.
@mitchmindtree mitchmindtree mentioned this issue Jun 24, 2019
8 tasks
@mitchmindtree
Copy link
Member Author

Closed by #289.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants