2018-02-17 04:16:03 +00:00
|
|
|
//! Feeds back the input stream directly into the output stream.
|
|
|
|
//!
|
|
|
|
//! Assumes that the input and output devices can use the same stream format and that they support
|
|
|
|
//! the f32 sample format.
|
|
|
|
//!
|
|
|
|
//! Uses a delay of `LATENCY_MS` milliseconds in case the default input and output streams are not
|
|
|
|
//! precisely synchronised.
|
|
|
|
|
|
|
|
extern crate cpal;
|
2019-06-21 12:55:21 +00:00
|
|
|
extern crate failure;
|
2018-02-17 04:16:03 +00:00
|
|
|
|
2019-06-28 21:42:07 +00:00
|
|
|
use cpal::traits::{DeviceTrait, EventLoopTrait, HostTrait};
|
[WIP] Introduce a `Host` API
This is an implementation of the API described at #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 #221 (cc
@freesig). These changes should also accommodate support for other hosts
such as jack #250 (cc @derekdreery) and pulseaudio (cc @knappador) #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 #221
cc @ishitatsuyuki more to review for you if you're interested, but it
might be easier after #288 lands and this gets rebased.
2019-06-23 13:49:48 +00:00
|
|
|
|
2018-02-17 04:16:03 +00:00
|
|
|
const LATENCY_MS: f32 = 150.0;
|
|
|
|
|
2019-06-21 12:55:21 +00:00
|
|
|
fn main() -> Result<(), failure::Error> {
|
[WIP] Introduce a `Host` API
This is an implementation of the API described at #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 #221 (cc
@freesig). These changes should also accommodate support for other hosts
such as jack #250 (cc @derekdreery) and pulseaudio (cc @knappador) #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 #221
cc @ishitatsuyuki more to review for you if you're interested, but it
might be easier after #288 lands and this gets rebased.
2019-06-23 13:49:48 +00:00
|
|
|
let host = cpal::default_host();
|
|
|
|
let event_loop = host.event_loop();
|
2018-02-17 04:16:03 +00:00
|
|
|
|
|
|
|
// Default devices.
|
[WIP] Introduce a `Host` API
This is an implementation of the API described at #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 #221 (cc
@freesig). These changes should also accommodate support for other hosts
such as jack #250 (cc @derekdreery) and pulseaudio (cc @knappador) #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 #221
cc @ishitatsuyuki more to review for you if you're interested, but it
might be easier after #288 lands and this gets rebased.
2019-06-23 13:49:48 +00:00
|
|
|
let input_device = host.default_input_device().expect("failed to get default input device");
|
|
|
|
let output_device = host.default_output_device().expect("failed to get default output device");
|
2019-06-21 12:55:21 +00:00
|
|
|
println!("Using default input device: \"{}\"", input_device.name()?);
|
|
|
|
println!("Using default output device: \"{}\"", output_device.name()?);
|
2018-02-17 04:16:03 +00:00
|
|
|
|
|
|
|
// We'll try and use the same format between streams to keep it simple
|
2019-06-21 12:55:21 +00:00
|
|
|
let mut format = input_device.default_input_format()?;
|
2018-02-17 04:16:03 +00:00
|
|
|
format.data_type = cpal::SampleFormat::F32;
|
|
|
|
|
|
|
|
// Build streams.
|
|
|
|
println!("Attempting to build both streams with `{:?}`.", format);
|
2019-06-21 12:55:21 +00:00
|
|
|
let input_stream_id = event_loop.build_input_stream(&input_device, &format)?;
|
|
|
|
let output_stream_id = event_loop.build_output_stream(&output_device, &format)?;
|
2018-02-17 04:16:03 +00:00
|
|
|
println!("Successfully built streams.");
|
|
|
|
|
|
|
|
// Create a delay in case the input and output devices aren't synced.
|
|
|
|
let latency_frames = (LATENCY_MS / 1_000.0) * format.sample_rate.0 as f32;
|
|
|
|
let latency_samples = latency_frames as usize * format.channels as usize;
|
|
|
|
|
|
|
|
// The channel to share samples.
|
|
|
|
let (tx, rx) = std::sync::mpsc::sync_channel(latency_samples * 2);
|
|
|
|
|
|
|
|
// Fill the samples with 0.0 equal to the length of the delay.
|
|
|
|
for _ in 0..latency_samples {
|
2019-06-21 12:55:21 +00:00
|
|
|
tx.send(0.0)?;
|
2018-02-17 04:16:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Play the streams.
|
|
|
|
println!("Starting the input and output streams with `{}` milliseconds of latency.", LATENCY_MS);
|
2019-06-21 12:55:21 +00:00
|
|
|
event_loop.play_stream(input_stream_id.clone())?;
|
|
|
|
event_loop.play_stream(output_stream_id.clone())?;
|
2018-02-17 04:16:03 +00:00
|
|
|
|
|
|
|
// Run the event loop on a separate thread.
|
|
|
|
std::thread::spawn(move || {
|
2019-06-24 18:43:27 +00:00
|
|
|
event_loop.run(move |id, result| {
|
|
|
|
let data = match result {
|
|
|
|
Ok(data) => data,
|
|
|
|
Err(err) => {
|
2019-06-24 18:57:01 +00:00
|
|
|
eprintln!("an error occurred on stream {:?}: {}", id, err);
|
2019-06-21 22:10:40 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-02-17 04:16:03 +00:00
|
|
|
match data {
|
|
|
|
cpal::StreamData::Input { buffer: cpal::UnknownTypeInputBuffer::F32(buffer) } => {
|
|
|
|
assert_eq!(id, input_stream_id);
|
|
|
|
let mut output_fell_behind = false;
|
|
|
|
for &sample in buffer.iter() {
|
|
|
|
if tx.try_send(sample).is_err() {
|
|
|
|
output_fell_behind = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if output_fell_behind {
|
|
|
|
eprintln!("output stream fell behind: try increasing latency");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
cpal::StreamData::Output { buffer: cpal::UnknownTypeOutputBuffer::F32(mut buffer) } => {
|
|
|
|
assert_eq!(id, output_stream_id);
|
|
|
|
let mut input_fell_behind = None;
|
|
|
|
for sample in buffer.iter_mut() {
|
|
|
|
*sample = match rx.try_recv() {
|
|
|
|
Ok(s) => s,
|
|
|
|
Err(err) => {
|
|
|
|
input_fell_behind = Some(err);
|
|
|
|
0.0
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if let Some(err) = input_fell_behind {
|
|
|
|
eprintln!("input stream fell behind: {}: try increasing latency", err);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => panic!("we're expecting f32 data"),
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// Run for 3 seconds before closing.
|
|
|
|
println!("Playing for 3 seconds... ");
|
|
|
|
std::thread::sleep(std::time::Duration::from_secs(3));
|
|
|
|
println!("Done!");
|
2019-06-21 12:55:21 +00:00
|
|
|
Ok(())
|
2018-02-17 04:16:03 +00:00
|
|
|
}
|