Merge remote-tracking branch 'rust-audio/master' into test-fixes

This commit is contained in:
Petr Gladkikh 2024-11-26 16:24:51 +04:00
commit b7dbe3f33b
23 changed files with 593 additions and 365 deletions

View file

@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Output audio stream buffer size can now be adjusted.
### Changed
- Breaking: `OutputStreamBuilder` should now be used to initialize audio output stream.
- Breaking: `OutputStreamHandle` removed, use `OutputStream` and `OutputStream::mixer()` instead.
- Breaking: `DynamicMixerController` renamed to `Mixer`, `DynamicMixer` renamed to `MixerSource`.
- Breaking: `Sink::try_new` renamed to `connect_new` and does not return error anymore.
`Sink::new_idle` was renamed to `new`.
# Version 0.20.1 (2024-11-08)
### Fixed

View file

@ -1,5 +1,6 @@
use rodio::source::Source;
use rodio::Decoder;
use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use std::sync::atomic::{AtomicBool, Ordering};
@ -7,13 +8,13 @@ use std::sync::Arc;
use std::thread;
use std::time::Duration;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
fn main() -> Result<(), Box<dyn Error>> {
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
let sink = rodio::Sink::connect_new(&stream_handle.mixer());
// Decode the sound file into a source
let file = BufReader::new(File::open("assets/music.flac").unwrap());
let source = Decoder::new(file).unwrap();
let file = BufReader::new(File::open("assets/music.flac")?);
let source = Decoder::new(file)?;
// Apply automatic gain control to the source
let agc_source = source.automatic_gain_control(1.0, 4.0, 0.005, 5.0);
@ -22,14 +23,13 @@ fn main() {
// enabled or disabled every 5 milliseconds. We must clone `agc_enabled`,
// or we would lose it when we move it into the periodic access.
let agc_enabled = Arc::new(AtomicBool::new(true));
let agc_enabled_clone = agc_enabled.clone();
let controlled = agc_source.periodic_access(Duration::from_millis(5), move |agc_source| {
agc_source.set_enabled(agc_enabled_clone.load(Ordering::Relaxed));
});
// Add the source now equipped with automatic gain control and controlled via
// periodic_access to the sink for playback.
// periodic_access to the sink for the playback.
sink.append(controlled);
// After 5 seconds of playback disable automatic gain control using the
@ -42,6 +42,7 @@ fn main() {
thread::sleep(Duration::from_secs(5));
agc_enabled.store(false, Ordering::Relaxed);
// Keep the program running until playback is complete
// Keep the program running until the playback is complete.
sink.sleep_until_end();
Ok(())
}

View file

@ -1,30 +1,46 @@
use rodio::source::SineWave;
use rodio::Source;
use std::error::Error;
use std::io::BufReader;
use std::thread;
use std::time::Duration;
#[cfg(feature = "tracing")]
use tracing;
fn main() {
let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
fn main() -> Result<(), Box<dyn Error>> {
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
let mixer = stream_handle.mixer();
let file = std::fs::File::open("assets/beep.wav").unwrap();
let beep1 = stream_handle.play_once(BufReader::new(file)).unwrap();
beep1.set_volume(0.2);
let beep1 = {
// Play a WAV file.
let file = std::fs::File::open("assets/beep.wav")?;
let sink = rodio::play(&mixer, BufReader::new(file))?;
sink.set_volume(0.2);
sink
};
println!("Started beep1");
thread::sleep(Duration::from_millis(1500));
let file = std::fs::File::open("assets/beep2.wav").unwrap();
let beep2 = stream_handle.play_once(BufReader::new(file)).unwrap();
beep2.set_volume(0.3);
beep2.detach();
{
// Generate sine wave.
let wave = SineWave::new(740.0)
.amplify(0.2)
.take_duration(Duration::from_secs(3));
mixer.add(wave);
}
println!("Started beep2");
thread::sleep(Duration::from_millis(1500));
let file = std::fs::File::open("assets/beep3.ogg").unwrap();
let beep3 = stream_handle.play_once(file).unwrap();
beep3.set_volume(0.2);
let beep3 = {
// Play an OGG file.
let file = std::fs::File::open("assets/beep3.ogg")?;
let sink = rodio::play(&mixer, BufReader::new(file))?;
sink.set_volume(0.2);
sink
};
println!("Started beep3");
thread::sleep(Duration::from_millis(1500));
drop(beep1);
println!("Stopped beep1");
@ -33,4 +49,6 @@ fn main() {
println!("Stopped beep3");
thread::sleep(Duration::from_millis(1500));
Ok(())
}

35
examples/custom_config.rs Normal file
View file

@ -0,0 +1,35 @@
use cpal::traits::HostTrait;
use cpal::{BufferSize, SampleFormat, SampleRate};
use rodio::source::SineWave;
use rodio::Source;
use std::error::Error;
use std::thread;
use std::time::Duration;
fn main() -> Result<(), Box<dyn Error>> {
// You can use any other output device that can be queried from CPAL.
let default_device = cpal::default_host()
.default_output_device()
.ok_or("No default audio output device is found.")?;
let stream_handle = rodio::OutputStreamBuilder::from_device(default_device)?
// No need to set all parameters explicitly here,
// the defaults were set from the device's description.
.with_buffer_size(BufferSize::Fixed(256))
.with_sample_rate(SampleRate(48_000))
.with_sample_format(SampleFormat::F32)
// Note that the function below still tries alternative configs if the specified one fails.
// If you need to only use the exact specified configuration,
// then use OutputStreamBuilder::open_stream() instead.
.open_stream_or_fallback()?;
let mixer = stream_handle.mixer();
let wave = SineWave::new(740.0)
.amplify(0.1)
.take_duration(Duration::from_secs(1));
mixer.add(wave);
println!("Beep...");
thread::sleep(Duration::from_millis(1500));
Ok(())
}

View file

@ -1,12 +1,13 @@
use rodio::mixer;
use rodio::source::{SineWave, Source};
use rodio::{dynamic_mixer, OutputStream, Sink};
use std::error::Error;
use std::time::Duration;
fn main() {
fn main() -> Result<(), Box<dyn Error>> {
// Construct a dynamic controller and mixer, stream_handle, and sink.
let (controller, mixer) = dynamic_mixer::mixer::<f32>(2, 44_100);
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
let sink = Sink::try_new(&stream_handle).unwrap();
let (controller, mixer) = mixer::mixer::<f32>(2, 44_100);
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
let sink = rodio::Sink::connect_new(&stream_handle.mixer());
// Create four unique sources. The frequencies used here correspond
// notes in the key of C and in octave 4: C4, or middle C on a piano,
@ -35,4 +36,6 @@ fn main() {
// Sleep the thread until sink is empty.
sink.sleep_until_end();
Ok(())
}

View file

@ -1,11 +1,14 @@
use std::error::Error;
use std::io::BufReader;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
fn main() -> Result<(), Box<dyn Error>> {
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
let sink = rodio::Sink::connect_new(&stream_handle.mixer());
let file = std::fs::File::open("assets/music.flac").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
let file = std::fs::File::open("assets/music.flac")?;
sink.append(rodio::Decoder::new(BufReader::new(file))?);
sink.sleep_until_end();
Ok(())
}

View file

@ -1,11 +1,14 @@
use std::error::Error;
use std::io::BufReader;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
fn main() -> Result<(), Box<dyn Error>> {
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
let sink = rodio::Sink::connect_new(&stream_handle.mixer());
let file = std::fs::File::open("assets/music.m4a").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
let file = std::fs::File::open("assets/music.m4a")?;
sink.append(rodio::Decoder::new(BufReader::new(file))?);
sink.sleep_until_end();
Ok(())
}

View file

@ -1,11 +1,14 @@
use std::error::Error;
use std::io::BufReader;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
fn main() -> Result<(), Box<dyn Error>> {
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
let sink = rodio::Sink::connect_new(&stream_handle.mixer());
let file = std::fs::File::open("assets/music.mp3").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
let file = std::fs::File::open("assets/music.mp3")?;
sink.append(rodio::Decoder::new(BufReader::new(file))?);
sink.sleep_until_end();
Ok(())
}

View file

@ -1,11 +1,14 @@
use std::error::Error;
use std::io::BufReader;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
fn main() -> Result<(), Box<dyn Error>> {
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
let sink = rodio::Sink::connect_new(&stream_handle.mixer());
let file = std::fs::File::open("assets/music.ogg").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
let file = std::fs::File::open("assets/music.ogg")?;
sink.append(rodio::Decoder::new(BufReader::new(file))?);
sink.sleep_until_end();
Ok(())
}

View file

@ -1,11 +1,14 @@
use std::error::Error;
use std::io::BufReader;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
fn main() -> Result<(), Box<dyn Error>> {
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
let sink = rodio::Sink::connect_new(&stream_handle.mixer());
let file = std::fs::File::open("assets/music.wav").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
let file = std::fs::File::open("assets/music.wav")?;
sink.append(rodio::Decoder::new(BufReader::new(file))?);
sink.sleep_until_end();
Ok(())
}

View file

@ -1,37 +1,37 @@
//! Noise generator example. Use the "noise" feature to enable the noise generator sources.
use std::error::Error;
#[cfg(feature = "noise")]
fn main() {
fn main() -> Result<(), Box<dyn Error>> {
use rodio::source::{pink, white, Source};
use std::thread;
use std::time::Duration;
let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
let noise_duration = Duration::from_millis(1000);
let interval_duration = Duration::from_millis(1500);
stream_handle
.play_raw(
white(cpal::SampleRate(48000))
.amplify(0.1)
.take_duration(noise_duration),
)
.unwrap();
stream_handle.mixer().add(
white(cpal::SampleRate(48000))
.amplify(0.1)
.take_duration(noise_duration),
);
println!("Playing white noise");
thread::sleep(interval_duration);
stream_handle
.play_raw(
pink(cpal::SampleRate(48000))
.amplify(0.1)
.take_duration(noise_duration),
)
.unwrap();
stream_handle.mixer().add(
pink(cpal::SampleRate(48000))
.amplify(0.1)
.take_duration(noise_duration),
);
println!("Playing pink noise");
thread::sleep(interval_duration);
Ok(())
}
#[cfg(not(feature = "noise"))]

View file

@ -1,15 +1,18 @@
use rodio::Source;
use std::error::Error;
use std::io::BufReader;
use std::time::Duration;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
fn main() -> Result<(), Box<dyn Error>> {
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
let sink = rodio::Sink::connect_new(&stream_handle.mixer());
let file = std::fs::File::open("assets/music.ogg").unwrap();
let source = rodio::Decoder::new(BufReader::new(file)).unwrap();
let file = std::fs::File::open("assets/music.ogg")?;
let source = rodio::Decoder::new(BufReader::new(file))?;
let with_reverb = source.buffered().reverb(Duration::from_millis(40), 0.7);
sink.append(with_reverb);
sink.sleep_until_end();
Ok(())
}

View file

@ -1,22 +1,25 @@
use std::error::Error;
use std::io::BufReader;
use std::time::Duration;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
fn main() -> Result<(), Box<dyn Error>> {
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
let sink = rodio::Sink::connect_new(&stream_handle.mixer());
let file = std::fs::File::open("assets/music.mp3").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
let file = std::fs::File::open("assets/music.mp3")?;
sink.append(rodio::Decoder::new(BufReader::new(file))?);
std::thread::sleep(std::time::Duration::from_secs(2));
sink.try_seek(Duration::from_secs(0)).unwrap();
sink.try_seek(Duration::from_secs(0))?;
std::thread::sleep(std::time::Duration::from_secs(2));
sink.try_seek(Duration::from_secs(4)).unwrap();
sink.try_seek(Duration::from_secs(4))?;
sink.sleep_until_end();
// wont do anything since the sound has ended already
sink.try_seek(Duration::from_secs(5)).unwrap();
// This doesn't do anything since the sound has ended already.
sink.try_seek(Duration::from_secs(5))?;
println!("seek example ended");
Ok(())
}

View file

@ -1,83 +1,71 @@
//! Test signal generator example.
fn main() {
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
use rodio::source::{chirp, Function, SignalGenerator, Source};
use std::thread;
use std::time::Duration;
let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
let test_signal_duration = Duration::from_millis(1000);
let interval_duration = Duration::from_millis(1500);
let sample_rate = cpal::SampleRate(48000);
println!("Playing 1000 Hz tone");
stream_handle
.play_raw(
SignalGenerator::new(cpal::SampleRate(48000), 1000.0, Function::Sine)
.amplify(0.1)
.take_duration(test_signal_duration),
)
.unwrap();
stream_handle.mixer().add(
SignalGenerator::new(sample_rate, 1000.0, Function::Sine)
.amplify(0.1)
.take_duration(test_signal_duration),
);
thread::sleep(interval_duration);
println!("Playing 10,000 Hz tone");
stream_handle
.play_raw(
SignalGenerator::new(cpal::SampleRate(48000), 10000.0, Function::Sine)
.amplify(0.1)
.take_duration(test_signal_duration),
)
.unwrap();
stream_handle.mixer().add(
SignalGenerator::new(sample_rate, 10000.0, Function::Sine)
.amplify(0.1)
.take_duration(test_signal_duration),
);
thread::sleep(interval_duration);
println!("Playing 440 Hz Triangle Wave");
stream_handle
.play_raw(
SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Triangle)
.amplify(0.1)
.take_duration(test_signal_duration),
)
.unwrap();
stream_handle.mixer().add(
SignalGenerator::new(sample_rate, 440.0, Function::Triangle)
.amplify(0.1)
.take_duration(test_signal_duration),
);
thread::sleep(interval_duration);
println!("Playing 440 Hz Sawtooth Wave");
stream_handle
.play_raw(
SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Sawtooth)
.amplify(0.1)
.take_duration(test_signal_duration),
)
.unwrap();
stream_handle.mixer().add(
SignalGenerator::new(sample_rate, 440.0, Function::Sawtooth)
.amplify(0.1)
.take_duration(test_signal_duration),
);
thread::sleep(interval_duration);
println!("Playing 440 Hz Square Wave");
stream_handle
.play_raw(
SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Square)
.amplify(0.1)
.take_duration(test_signal_duration),
)
.unwrap();
stream_handle.mixer().add(
SignalGenerator::new(sample_rate, 440.0, Function::Square)
.amplify(0.1)
.take_duration(test_signal_duration),
);
thread::sleep(interval_duration);
println!("Playing 20-10000 Hz Sweep");
stream_handle
.play_raw(
chirp(
cpal::SampleRate(48000),
20.0,
10000.0,
Duration::from_secs(1),
)
stream_handle.mixer().add(
chirp(sample_rate, 20.0, 10000.0, Duration::from_secs(1))
.amplify(0.1)
.take_duration(test_signal_duration),
)
.unwrap();
);
thread::sleep(interval_duration);
Ok(())
}

View file

@ -1,10 +1,11 @@
use std::error::Error;
use std::io::BufReader;
use std::thread;
use std::time::Duration;
use rodio::Source;
fn main() {
fn main() -> Result<(), Box<dyn Error>> {
let iter_duration = Duration::from_secs(5);
let iter_distance = 5.;
@ -18,13 +19,18 @@ fn main() {
let total_duration = iter_duration * 2 * repeats;
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let mut positions = ([0., 0., 0.], [-1., 0., 0.], [1., 0., 0.]);
let sink = rodio::SpatialSink::try_new(&handle, positions.0, positions.1, positions.2).unwrap();
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
let file = std::fs::File::open("assets/music.ogg").unwrap();
let source = rodio::Decoder::new(BufReader::new(file))
.unwrap()
let mut positions = ([0., 0., 0.], [-1., 0., 0.], [1., 0., 0.]);
let sink = rodio::SpatialSink::connect_new(
&stream_handle.mixer(),
positions.0,
positions.1,
positions.2,
);
let file = std::fs::File::open("assets/music.ogg")?;
let source = rodio::Decoder::new(BufReader::new(file))?
.repeat_infinite()
.take_duration(total_duration);
sink.append(source);
@ -50,4 +56,6 @@ fn main() {
}
}
sink.sleep_until_end();
Ok(())
}

View file

@ -1,12 +1,17 @@
//! Plays a tone alternating between right and left ears, with right being first.
use rodio::Source;
use std::error::Error;
use std::io::BufReader;
fn main() {
let (_stream, handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&handle).unwrap();
fn main() -> Result<(), Box<dyn Error>> {
let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?;
let sink = rodio::Sink::connect_new(&stream_handle.mixer());
let file = std::fs::File::open("assets/RL.ogg").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
let file = std::fs::File::open("assets/RL.ogg")?;
sink.append(rodio::Decoder::new(BufReader::new(file))?.amplify(0.2));
sink.sleep_until_end();
Ok(())
}

View file

@ -3,17 +3,17 @@
//! The main concept of this library is the [`Source`] trait, which
//! represents a sound (streaming or not). In order to play a sound, there are three steps:
//!
//! - Get an output stream handle to a physical device. For example, get a stream to the system's
//! default sound device with [`OutputStreamBuilder::open_default_stream()`].
//! - Create an object that represents the streaming sound. It can be a sine wave, a buffer, a
//! [`decoder`], etc. or even your own type that implements the [`Source`] trait.
//! - Get an output stream handle to a physical device. For example, get a stream to the system's
//! default sound device with [`OutputStream::try_default()`]
//! - Call [`.play_raw(source)`](OutputStreamHandle::play_raw) on the output stream handle.
//! - Add the source to the output stream using [`OutputStream::mixer()`](OutputStream::mixer)
//! on the output stream handle.
//!
//! The [`play_raw`](OutputStreamHandle::play_raw) function expects the source to produce [`f32`]s,
//! which may not be the case. If you get a compilation error, try calling
//! [`.convert_samples()`](Source::convert_samples) on the source to fix it.
//! The output stream expects the sources to produce [`f32`]s. In case the output sample format
//! is different use [`.convert_samples()`](Source::convert_samples) to adapt them.
//!
//! For example, here is how you would play an audio file:
//! Here is a complete example of how you would play an audio file:
//!
//! ```no_run
//! use std::fs::File;
@ -21,29 +21,51 @@
//! use rodio::{Decoder, OutputStream, source::Source};
//!
//! // Get an output stream handle to the default physical sound device.
//! // Note that no sound will be played if _stream is dropped
//! let (_stream, stream_handle) = OutputStream::try_default().unwrap();
//! // Note that the playback stops when the stream_handle is dropped.
//! let stream_handle = rodio::OutputStreamBuilder::open_default_stream()
//! .expect("open default audio stream");
//! let sink = rodio::Sink::connect_new(&stream_handle.mixer());
//! // Load a sound from a file, using a path relative to Cargo.toml
//! let file = BufReader::new(File::open("examples/music.ogg").unwrap());
//! // Decode that sound file into a source
//! let source = Decoder::new(file).unwrap();
//! // Play the sound directly on the device
//! stream_handle.play_raw(source.convert_samples());
//! stream_handle.mixer().add(source.convert_samples());
//!
//! // The sound plays in a separate audio thread,
//! // so we need to keep the main thread alive while it's playing.
//! std::thread::sleep(std::time::Duration::from_secs(5));
//! ```
//!
//! [rodio::play()] helps to simplify the above
//! ```no_run
//! use std::fs::File;
//! use std::io::BufReader;
//! use rodio::{Decoder, OutputStream, source::Source};
//!
//! // Get an output stream handle to the default physical sound device.
//! // Note that the playback stops when the stream_handle is dropped.
//! let stream_handle = rodio::OutputStreamBuilder::open_default_stream()
//! .expect("open default audio stream");
//!
//! // Load a sound from a file, using a path relative to Cargo.toml
//! let file = BufReader::new(File::open("examples/music.ogg").unwrap());
//! rodio::play(&stream_handle.mixer(), file).unwrap();
//!
//! // The sound plays in a separate audio thread,
//! // so we need to keep the main thread alive while it's playing.
//! std::thread::sleep(std::time::Duration::from_secs(5));
//! ```
//!
//!
//! ## Sink
//!
//! In order to make it easier to control the playback, the rodio library also provides a type
//! named [`Sink`] which represents an audio track.
//! named [`Sink`] which represents an audio track. [`Sink`] plays its input sources sequentially,
//! one after another. To play sounds in simultaneously in parallel, use [`mixer::Mixer`] instead.
//!
//! Instead of playing the sound with [`play_raw`](OutputStreamHandle::play_raw), you can add it to
//! a [`Sink`] instead.
//!
//! - Get a [`Sink`] to the output stream, and [`.append()`](Sink::append) your sound to it.
//! To play a soung Create a [`Sink`] connect it to the output stream,
//! and [`.append()`](Sink::append) your sound to it.
//!
//! ```no_run
//! use std::fs::File;
@ -53,8 +75,9 @@
//! use rodio::source::{SineWave, Source};
//!
//! // _stream must live as long as the sink
//! let (_stream, stream_handle) = OutputStream::try_default().unwrap();
//! let sink = Sink::try_new(&stream_handle).unwrap();
//! let stream_handle = rodio::OutputStreamBuilder::open_default_stream()
//! .expect("open default audio stream");
//! let sink = rodio::Sink::connect_new(&stream_handle.mixer());
//!
//! // Add a dummy source of the sake of the example.
//! let source = SineWave::new(440.0).take_duration(Duration::from_secs_f32(0.25)).amplify(0.20);
@ -139,7 +162,7 @@ mod stream;
pub mod buffer;
pub mod decoder;
pub mod dynamic_mixer;
pub mod mixer;
pub mod queue;
pub mod source;
pub mod static_buffer;
@ -149,4 +172,4 @@ pub use crate::decoder::Decoder;
pub use crate::sink::Sink;
pub use crate::source::Source;
pub use crate::spatial_sink::SpatialSink;
pub use crate::stream::{OutputStream, OutputStreamHandle, PlayError, StreamError};
pub use crate::stream::{play, OutputStream, OutputStreamBuilder, PlayError, StreamError};

View file

@ -13,21 +13,18 @@ use crate::Sample;
/// added to the mixer will be converted to these values.
///
/// After creating a mixer, you can add new sounds with the controller.
pub fn mixer<S>(
channels: u16,
sample_rate: u32,
) -> (Arc<DynamicMixerController<S>>, DynamicMixer<S>)
pub fn mixer<S>(channels: u16, sample_rate: u32) -> (Arc<Mixer<S>>, MixerSource<S>)
where
S: Sample + Send + 'static,
{
let input = Arc::new(DynamicMixerController {
let input = Arc::new(Mixer {
has_pending: AtomicBool::new(false),
pending_sources: Mutex::new(Vec::new()),
channels,
sample_rate,
});
let output = DynamicMixer {
let output = MixerSource {
current_sources: Vec::with_capacity(16),
input: input.clone(),
sample_count: 0,
@ -39,14 +36,14 @@ where
}
/// The input of the mixer.
pub struct DynamicMixerController<S> {
pub struct Mixer<S> {
has_pending: AtomicBool,
pending_sources: Mutex<Vec<Box<dyn Source<Item = S> + Send>>>,
channels: u16,
sample_rate: u32,
}
impl<S> DynamicMixerController<S>
impl<S> Mixer<S>
where
S: Sample + Send + 'static,
{
@ -66,12 +63,12 @@ where
}
/// The output of the mixer. Implements `Source`.
pub struct DynamicMixer<S> {
pub struct MixerSource<S> {
// The current iterator that produces samples.
current_sources: Vec<Box<dyn Source<Item = S> + Send>>,
// The pending sounds.
input: Arc<DynamicMixerController<S>>,
input: Arc<Mixer<S>>,
// The number of samples produced so far.
sample_count: usize,
@ -83,7 +80,7 @@ pub struct DynamicMixer<S> {
still_current: Vec<Box<dyn Source<Item = S> + Send>>,
}
impl<S> Source for DynamicMixer<S>
impl<S> Source for MixerSource<S>
where
S: Sample + Send + 'static,
{
@ -144,7 +141,7 @@ where
}
}
impl<S> Iterator for DynamicMixer<S>
impl<S> Iterator for MixerSource<S>
where
S: Sample + Send + 'static,
{
@ -173,7 +170,7 @@ where
}
}
impl<S> DynamicMixer<S>
impl<S> MixerSource<S>
where
S: Sample + Send + 'static,
{
@ -217,12 +214,12 @@ where
#[cfg(test)]
mod tests {
use crate::buffer::SamplesBuffer;
use crate::dynamic_mixer;
use crate::mixer;
use crate::source::Source;
#[test]
fn basic() {
let (tx, mut rx) = dynamic_mixer::mixer(1, 48000);
let (tx, mut rx) = mixer::mixer(1, 48000);
tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
tx.add(SamplesBuffer::new(1, 48000, vec![5i16, 5, 5, 5]));
@ -238,7 +235,7 @@ mod tests {
#[test]
fn channels_conv() {
let (tx, mut rx) = dynamic_mixer::mixer(2, 48000);
let (tx, mut rx) = mixer::mixer(2, 48000);
tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
tx.add(SamplesBuffer::new(1, 48000, vec![5i16, 5, 5, 5]));
@ -258,7 +255,7 @@ mod tests {
#[test]
fn rate_conv() {
let (tx, mut rx) = dynamic_mixer::mixer(1, 96000);
let (tx, mut rx) = mixer::mixer(1, 96000);
tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));
tx.add(SamplesBuffer::new(1, 48000, vec![5i16, 5, 5, 5]));
@ -277,7 +274,7 @@ mod tests {
#[test]
fn start_afterwards() {
let (tx, mut rx) = dynamic_mixer::mixer(1, 48000);
let (tx, mut rx) = mixer::mixer(1, 48000);
tx.add(SamplesBuffer::new(1, 48000, vec![10i16, -10, 10, -10]));

View file

@ -2,19 +2,19 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use cpal::FromSample;
#[cfg(feature = "crossbeam-channel")]
use crossbeam_channel::{Receiver, Sender};
#[cfg(not(feature = "crossbeam-channel"))]
use std::sync::mpsc::{Receiver, Sender};
use crate::mixer::Mixer;
use crate::source::SeekError;
use crate::stream::{OutputStreamHandle, PlayError};
use crate::{queue, source::Done, Sample, Source};
use cpal::FromSample;
/// Handle to a device that outputs sounds.
///
/// Dropping the `Sink` stops all sounds. You can use `detach` if you want the sounds to continue
/// Dropping the `Sink` stops all its sounds. You can use `detach` if you want the sounds to continue
/// playing.
pub struct Sink {
queue_tx: Arc<queue::SourcesQueueInput<f32>>,
@ -70,15 +70,15 @@ struct Controls {
impl Sink {
/// Builds a new `Sink`, beginning playback on a stream.
#[inline]
pub fn try_new(stream: &OutputStreamHandle) -> Result<Sink, PlayError> {
let (sink, queue_rx) = Sink::new_idle();
stream.play_raw(queue_rx)?;
Ok(sink)
pub fn connect_new(mixer: &Mixer<f32>) -> Sink {
let (sink, source) = Sink::new();
mixer.add(source);
sink
}
/// Builds a new `Sink`.
#[inline]
pub fn new_idle() -> (Sink, queue::SourcesQueueOutput<f32>) {
pub fn new() -> (Sink, queue::SourcesQueueOutput<f32>) {
let (queue_tx, queue_rx) = queue::queue(true);
let sink = Sink {
@ -107,7 +107,7 @@ impl Sink {
f32: FromSample<S::Item>,
S::Item: Sample + Send,
{
// Wait for queue to flush then resume stopped playback
// Wait for the queue to flush then resume stopped playback
if self.controls.stopped.load(Ordering::SeqCst) {
if self.sound_count.load(Ordering::SeqCst) > 0 {
self.sleep_until_end();
@ -366,13 +366,14 @@ impl Drop for Sink {
#[cfg(test)]
mod tests {
use std::sync::atomic::Ordering;
use crate::buffer::SamplesBuffer;
use crate::{Sink, Source};
use std::sync::atomic::Ordering;
#[test]
fn test_pause_and_stop() {
let (sink, mut queue_rx) = Sink::new_idle();
let (sink, mut queue_rx) = Sink::new();
// assert_eq!(queue_rx.next(), Some(0.0));
@ -403,7 +404,7 @@ mod tests {
#[test]
fn test_stop_and_start() {
let (sink, mut queue_rx) = Sink::new_idle();
let (sink, mut queue_rx) = Sink::new();
let v = vec![10i16, -10, 20, -20, 30, -30];
@ -431,7 +432,7 @@ mod tests {
#[test]
fn test_volume() {
let (sink, mut queue_rx) = Sink::new_idle();
let (sink, mut queue_rx) = Sink::new();
let v = vec![10i16, -10, 20, -20, 30, -30];

View file

@ -303,16 +303,17 @@ where
/// # Example (Quick start)
///
/// ```rust
/// // Apply Automatic Gain Control to the source.
/// use rodio::source::{Source, SineWave, AutomaticGainControl};
/// // Apply Automatic Gain Control to the source (AGC is on by default)
/// use rodio::source::{Source, SineWave};
/// use rodio::Sink;
/// let source = SineWave::new(444.0); // An example.
/// let (sink, output) = Sink::new_idle(); // An example, makes no sound unless connected to an output.
/// let (sink, output) = Sink::new(); // An example.
///
/// let agc_source = source.automatic_gain_control(1.0, 4.0, 0.005, 5.0);
///
/// // Add the AGC-controlled source to the sink
/// sink.append(agc_source);
///
/// ```
#[inline]
fn automatic_gain_control(
@ -500,7 +501,7 @@ where
///
/// This can get confusing when using [`get_pos()`](TrackPosition::get_pos)
/// together with [`Source::try_seek()`] as the latter does take all
/// speedup's and delay's into account. Its recommended therefore to apply
/// speedup's and delay's into account. It's recommended therefore to apply
/// track_position after speedup's and delay's.
fn track_position(self) -> TrackPosition<Self>
where
@ -556,7 +557,7 @@ where
/// Attempts to seek to a given position in the current source.
///
/// As long as the duration of the source is known seek is guaranteed to saturate
/// As long as the duration of the source is known, seek is guaranteed to saturate
/// at the end of the source. For example given a source that reports a total duration
/// of 42 seconds calling `try_seek()` with 60 seconds as argument will seek to
/// 42 seconds.
@ -579,8 +580,8 @@ where
}
// We might add decoders requiring new error types, without non_exhaustive
// this would break users builds
/// Occurs when try_seek fails because the underlying decoder has an error or
// this would break users' builds.
/// Occurs when `try_seek` fails because the underlying decoder has an error or
/// does not support seeking.
#[non_exhaustive]
#[derive(Debug)]
@ -596,8 +597,8 @@ pub enum SeekError {
#[cfg(feature = "wav")]
/// The hound (wav) decoder ran into an issue
HoundDecoder(std::io::Error),
// Prefer adding an enum variant to using this. Its meant for end users their
// own try_seek implementations
// Prefer adding an enum variant to using this. It's meant for end users their
// own `try_seek` implementations.
/// Any other error probably in a custom Source
Other(Box<dyn std::error::Error + Send>),
}

View file

@ -9,7 +9,7 @@
//! - Updates the try_seek function by multiplying the audio position by the factor.
//!
//! To speed up a source from sink all you need to do is call the `set_speed(factor: f32)` function
//! For example, here is how you speed up your sound by using sink or playing raw.
//! For example, here is how you speed up your sound by using sink or playing raw:
//!
//! ```no_run
//!# use std::fs::File;
@ -17,23 +17,26 @@
//!# use rodio::{Decoder, Sink, OutputStream, source::{Source, SineWave}};
//!
//! // Get an output stream handle to the default physical sound device.
//! // Note that no sound will be played if _stream is dropped.
//! let (_stream, stream_handle) = OutputStream::try_default().unwrap();
//! // Load a sound from a file, using a path relative to Cargo.toml
//! let file = BufReader::new(File::open("assets/music.ogg").unwrap());
//! // Note that no sound will be played if the _stream is dropped.
//! let stream_handle = rodio::OutputStreamBuilder::open_default_stream()
//! .expect("open default audio stream");
//! // Load a sound from a file, using a path relative to `Cargo.toml`
//! let file = BufReader::new(File::open("examples/music.ogg").unwrap());
//! // Decode that sound file into a source
//! let source = Decoder::new(file).unwrap();
//! // Play the sound directly on the device 2x faster
//! stream_handle.play_raw(source.convert_samples().speed(2.0)).unwrap();
//! stream_handle.mixer().add(source.convert_samples().speed(2.0));
//! std::thread::sleep(std::time::Duration::from_secs(5));
//!
//! // Here is how you would do it using the sink.
//!
//! ```
//! Here is how you would do it using the sink:
//!```no_run
//! use rodio::source::{Source, SineWave};
//! let source = SineWave::new(440.0)
//! .take_duration(std::time::Duration::from_secs_f32(3.25))
//! .amplify(0.20);
//!
//! let sink = Sink::try_new(&stream_handle).unwrap();
//! .take_duration(std::time::Duration::from_secs_f32(20.25))
//! .amplify(0.20);
//! let stream_handle = rodio::OutputStreamBuilder::open_default_stream()
//! .expect("open default audio stream");
//! let sink = rodio::Sink::connect_new(&stream_handle.mixer());
//! sink.set_speed(2.0);
//! sink.append(source);
//! std::thread::sleep(std::time::Duration::from_secs(5));

View file

@ -4,8 +4,8 @@ use std::time::Duration;
use cpal::FromSample;
use crate::mixer::Mixer;
use crate::source::{SeekError, Spatial};
use crate::stream::{OutputStreamHandle, PlayError};
use crate::{Sample, Sink, Source};
/// A sink that allows changing the position of the source and the listeners
@ -24,20 +24,20 @@ struct SoundPositions {
impl SpatialSink {
/// Builds a new `SpatialSink`.
pub fn try_new(
stream: &OutputStreamHandle,
pub fn connect_new(
mixer: &Mixer<f32>,
emitter_position: [f32; 3],
left_ear: [f32; 3],
right_ear: [f32; 3],
) -> Result<SpatialSink, PlayError> {
Ok(SpatialSink {
sink: Sink::try_new(stream)?,
) -> SpatialSink {
SpatialSink {
sink: Sink::connect_new(mixer),
positions: Arc::new(Mutex::new(SoundPositions {
emitter_position,
left_ear,
right_ear,
})),
})
}
}
/// Sets the position of the sound emitter in 3 dimensional space.

View file

@ -1,101 +1,229 @@
use std::io::{Read, Seek};
use std::sync::{Arc, Weak};
use std::marker::Sync;
use std::sync::Arc;
use std::{error, fmt};
use crate::decoder;
use crate::dynamic_mixer::{self, DynamicMixerController};
use crate::mixer::{mixer, Mixer, MixerSource};
use crate::sink::Sink;
use crate::source::Source;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{Sample, SupportedStreamConfig};
use cpal::{
BufferSize, ChannelCount, FrameCount, Sample, SampleFormat, SampleRate, StreamConfig,
SupportedBufferSize,
};
/// `cpal::Stream` container. Also see the more useful `OutputStreamHandle`.
///
/// If this is dropped, playback will end & attached `OutputStreamHandle`s will no longer work.
const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100);
/// `cpal::Stream` container.
/// Use `mixer()` method to control output.
/// If this is dropped, playback will end, and the associated output stream will be disposed.
pub struct OutputStream {
mixer: Arc<DynamicMixerController<f32>>,
mixer: Arc<Mixer<f32>>,
_stream: cpal::Stream,
}
/// More flexible handle to a `OutputStream` that provides playback.
#[derive(Clone)]
pub struct OutputStreamHandle {
mixer: Weak<DynamicMixerController<f32>>,
impl OutputStream {
/// Access the output stream's mixer.
pub fn mixer(&self) -> Arc<Mixer<f32>> {
self.mixer.clone()
}
}
impl OutputStream {
/// Returns a new stream & handle using the given output device and the default output
/// configuration.
pub fn try_from_device(
device: &cpal::Device,
) -> Result<(Self, OutputStreamHandle), StreamError> {
#[derive(Copy, Clone, Debug)]
struct OutputStreamConfig {
channel_count: ChannelCount,
sample_rate: SampleRate,
buffer_size: BufferSize,
sample_format: SampleFormat,
}
/// Convenience builder for audio output stream.
/// It provides methods to configure several parameters of the audio output and opening default
/// device. See examples for use-cases.
#[derive(Default)]
pub struct OutputStreamBuilder {
device: Option<cpal::Device>,
config: OutputStreamConfig,
}
impl Default for OutputStreamConfig {
fn default() -> Self {
Self {
channel_count: 2,
sample_rate: HZ_44100,
buffer_size: BufferSize::Default,
sample_format: SampleFormat::I8,
}
}
}
impl OutputStreamBuilder {
/// Sets output device and its default parameters.
pub fn from_device(device: cpal::Device) -> Result<OutputStreamBuilder, StreamError> {
let default_config = device
.default_output_config()
.map_err(StreamError::DefaultStreamConfigError)?;
OutputStream::try_from_device_config(device, default_config)
Ok(Self::default()
.with_device(device)
.with_supported_config(&default_config))
}
/// Returns a new stream & handle using the given device and stream config.
///
/// If the supplied `SupportedStreamConfig` is invalid for the device this function will
/// fail to create an output stream and instead return a `StreamError`
pub fn try_from_device_config(
device: &cpal::Device,
config: SupportedStreamConfig,
) -> Result<(Self, OutputStreamHandle), StreamError> {
let (mixer, _stream) = device.try_new_output_stream_config(config)?;
_stream.play().map_err(StreamError::PlayStreamError)?;
let out = Self { mixer, _stream };
let handle = OutputStreamHandle {
mixer: Arc::downgrade(&out.mixer),
};
Ok((out, handle))
}
/// Return a new stream & handle using the default output device.
///
/// On failure will fallback to trying any non-default output devices.
pub fn try_default() -> Result<(Self, OutputStreamHandle), StreamError> {
/// Sets default output stream parameters for default output audio device.
pub fn from_default_device() -> Result<OutputStreamBuilder, StreamError> {
let default_device = cpal::default_host()
.default_output_device()
.ok_or(StreamError::NoDevice)?;
Self::from_device(default_device)
}
let default_stream = Self::try_from_device(&default_device);
/// Sets output audio device keeping all existing stream parameters intact.
/// This method is useful if you want to set other parameters yourself.
/// To also set parameters that are appropriate for the device use [Self::from_device()] instead.
pub fn with_device(mut self, device: cpal::Device) -> OutputStreamBuilder {
self.device = Some(device);
self
}
default_stream.or_else(|original_err| {
// default device didn't work, try other ones
let mut devices = match cpal::default_host().output_devices() {
Ok(d) => d,
Err(_) => return Err(original_err),
};
/// Sets number of output stream's channels.
pub fn with_channels(mut self, channel_count: cpal::ChannelCount) -> OutputStreamBuilder {
assert!(channel_count > 0);
self.config.channel_count = channel_count;
self
}
devices
.find_map(|d| Self::try_from_device(&d).ok())
.ok_or(original_err)
/// Sets output stream's sample rate.
pub fn with_sample_rate(mut self, sample_rate: cpal::SampleRate) -> OutputStreamBuilder {
self.config.sample_rate = sample_rate;
self
}
/// Sets preferred output buffer size.
/// Larger buffer size causes longer playback delays. Buffer sizes that are too small
/// may cause higher CPU usage or playback interruptions.
pub fn with_buffer_size(mut self, buffer_size: cpal::BufferSize) -> OutputStreamBuilder {
self.config.buffer_size = buffer_size;
self
}
/// Select scalar type that will carry a sample.
pub fn with_sample_format(mut self, sample_format: SampleFormat) -> OutputStreamBuilder {
self.config.sample_format = sample_format;
self
}
/// Set available parameters from a CPAL supported config. You can ge list of
/// such configurations for an output device using [crate::stream::supported_output_configs()]
pub fn with_supported_config(
mut self,
config: &cpal::SupportedStreamConfig,
) -> OutputStreamBuilder {
self.config = OutputStreamConfig {
channel_count: config.channels(),
sample_rate: config.sample_rate(),
// In case of supported range limit buffer size to avoid unexpectedly long playback delays.
buffer_size: clamp_supported_buffer_size(config.buffer_size(), 1024),
sample_format: config.sample_format(),
};
self
}
/// Set all output stream parameters at once from CPAL stream config.
pub fn with_config(mut self, config: &cpal::StreamConfig) -> OutputStreamBuilder {
self.config = OutputStreamConfig {
channel_count: config.channels,
sample_rate: config.sample_rate,
buffer_size: config.buffer_size,
..self.config
};
self
}
/// Open output stream using parameters configured so far.
pub fn open_stream(&self) -> Result<OutputStream, StreamError> {
let device = self.device.as_ref().expect("output device specified");
OutputStream::open(device, &self.config)
}
/// Try opening a new output stream with the builder's current stream configuration.
/// Failing that attempt to open stream with other available configurations
/// supported by the device.
/// If all attempts fail returns initial error.
pub fn open_stream_or_fallback(&self) -> Result<OutputStream, StreamError> {
let device = self.device.as_ref().expect("output device specified");
OutputStream::open(device, &self.config).or_else(|err| {
for supported_config in supported_output_configs(device)? {
if let Ok(handle) = Self::default()
.with_device(device.clone())
.with_supported_config(&supported_config)
.open_stream()
{
return Ok(handle);
}
}
Err(err)
})
}
/// Try to open a new output stream for the default output device with its default configuration.
/// Failing that attempt to open output stream with alternative configuration and/or non default
/// output devices. Returns stream for first of the tried configurations that succeeds.
/// If all attempts fail return the initial error.
pub fn open_default_stream() -> Result<OutputStream, StreamError> {
Self::from_default_device()
.and_then(|x| x.open_stream())
.or_else(|original_err| {
let mut devices = match cpal::default_host().output_devices() {
Ok(devices) => devices,
Err(err) => {
#[cfg(feature = "tracing")]
tracing::error!("error getting list of output devices: {err}");
#[cfg(not(feature = "tracing"))]
eprintln!("error getting list of output devices: {err}");
return Err(original_err);
}
};
devices
.find_map(|d| {
Self::from_device(d)
.and_then(|x| x.open_stream_or_fallback())
.ok()
})
.ok_or(original_err)
})
}
}
impl OutputStreamHandle {
/// Plays a source with a device until it ends.
pub fn play_raw<S>(&self, source: S) -> Result<(), PlayError>
where
S: Source<Item = f32> + Send + 'static,
{
let mixer = self.mixer.upgrade().ok_or(PlayError::NoDevice)?;
mixer.add(source);
Ok(())
fn clamp_supported_buffer_size(
buffer_size: &SupportedBufferSize,
preferred_size: FrameCount,
) -> BufferSize {
match buffer_size {
SupportedBufferSize::Range { min, max } => {
BufferSize::Fixed(preferred_size.clamp(*min, *max))
}
SupportedBufferSize::Unknown => BufferSize::Default,
}
}
/// Plays a sound once. Returns a `Sink` that can be used to control the sound.
pub fn play_once<R>(&self, input: R) -> Result<Sink, PlayError>
where
R: Read + Seek + Send + Sync + 'static,
{
let input = decoder::Decoder::new(input)?;
let sink = Sink::try_new(self)?;
sink.append(input);
Ok(sink)
/// A convenience function. Plays a sound once.
/// Returns a `Sink` that can be used to control the sound.
pub fn play<R>(mixer: &Mixer<f32>, input: R) -> Result<Sink, PlayError>
where
R: Read + Seek + Send + Sync + 'static,
{
let input = decoder::Decoder::new(input)?;
let sink = Sink::connect_new(mixer);
sink.append(input);
Ok(sink)
}
impl From<&OutputStreamConfig> for StreamConfig {
fn from(config: &OutputStreamConfig) -> Self {
cpal::StreamConfig {
channels: config.channel_count,
sample_rate: config.sample_rate,
buffer_size: config.buffer_size,
}
}
}
@ -138,10 +266,10 @@ pub enum StreamError {
/// Could not start playing the stream, see [cpal::PlayStreamError] for
/// details.
PlayStreamError(cpal::PlayStreamError),
/// Failed to get the stream config for device the given device. See
/// [cpal::DefaultStreamConfigError] for details
/// Failed to get the stream config for the given device. See
/// [cpal::DefaultStreamConfigError] for details.
DefaultStreamConfigError(cpal::DefaultStreamConfigError),
/// Error opening stream with OS. See [cpal::BuildStreamError] for details
/// Error opening stream with OS. See [cpal::BuildStreamError] for details.
BuildStreamError(cpal::BuildStreamError),
/// Could not list supported stream configs for device. Maybe it
/// disconnected, for details see: [cpal::SupportedStreamConfigsError].
@ -174,94 +302,96 @@ impl error::Error for StreamError {
}
}
/// Extensions to `cpal::Device`
pub(crate) trait CpalDeviceExt {
fn new_output_stream_with_format(
&self,
format: cpal::SupportedStreamConfig,
) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), cpal::BuildStreamError>;
fn try_new_output_stream_config(
&self,
config: cpal::SupportedStreamConfig,
) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), StreamError>;
}
impl CpalDeviceExt for cpal::Device {
fn new_output_stream_with_format(
&self,
format: cpal::SupportedStreamConfig,
) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), cpal::BuildStreamError> {
let (mixer_tx, mut mixer_rx) =
dynamic_mixer::mixer::<f32>(format.channels(), format.sample_rate().0);
impl OutputStream {
fn open(
device: &cpal::Device,
config: &OutputStreamConfig,
) -> Result<OutputStream, StreamError> {
let (controller, source) = mixer(config.channel_count, config.sample_rate.0);
Self::init_stream(device, config, source)
.map_err(StreamError::BuildStreamError)
.and_then(|stream| {
stream.play().map_err(StreamError::PlayStreamError)?;
Ok(Self {
_stream: stream,
mixer: controller,
})
})
}
fn init_stream(
device: &cpal::Device,
config: &OutputStreamConfig,
mut samples: MixerSource<f32>,
) -> Result<cpal::Stream, cpal::BuildStreamError> {
let error_callback = |err| {
#[cfg(feature = "tracing")]
tracing::error!("an error occurred on output stream: {err}");
tracing::error!("error initializing output stream: {err}");
#[cfg(not(feature = "tracing"))]
eprintln!("an error occurred on output stream: {err}");
eprintln!("error initializing output stream: {err}");
};
match format.sample_format() {
cpal::SampleFormat::F32 => self.build_output_stream::<f32, _, _>(
&format.config(),
let sample_format = config.sample_format;
let config = config.into();
match sample_format {
cpal::SampleFormat::F32 => device.build_output_stream::<f32, _, _>(
&config,
move |data, _| {
data.iter_mut()
.for_each(|d| *d = mixer_rx.next().unwrap_or(0f32))
.for_each(|d| *d = samples.next().unwrap_or(0f32))
},
error_callback,
None,
),
cpal::SampleFormat::F64 => self.build_output_stream::<f64, _, _>(
&format.config(),
cpal::SampleFormat::F64 => device.build_output_stream::<f64, _, _>(
&config,
move |data, _| {
data.iter_mut()
.for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0f64))
.for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0f64))
},
error_callback,
None,
),
cpal::SampleFormat::I8 => self.build_output_stream::<i8, _, _>(
&format.config(),
cpal::SampleFormat::I8 => device.build_output_stream::<i8, _, _>(
&config,
move |data, _| {
data.iter_mut()
.for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i8))
.for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i8))
},
error_callback,
None,
),
cpal::SampleFormat::I16 => self.build_output_stream::<i16, _, _>(
&format.config(),
cpal::SampleFormat::I16 => device.build_output_stream::<i16, _, _>(
&config,
move |data, _| {
data.iter_mut()
.for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i16))
.for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i16))
},
error_callback,
None,
),
cpal::SampleFormat::I32 => self.build_output_stream::<i32, _, _>(
&format.config(),
cpal::SampleFormat::I32 => device.build_output_stream::<i32, _, _>(
&config,
move |data, _| {
data.iter_mut()
.for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i32))
.for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i32))
},
error_callback,
None,
),
cpal::SampleFormat::I64 => self.build_output_stream::<i64, _, _>(
&format.config(),
cpal::SampleFormat::I64 => device.build_output_stream::<i64, _, _>(
&config,
move |data, _| {
data.iter_mut()
.for_each(|d| *d = mixer_rx.next().map(Sample::from_sample).unwrap_or(0i64))
.for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i64))
},
error_callback,
None,
),
cpal::SampleFormat::U8 => self.build_output_stream::<u8, _, _>(
&format.config(),
cpal::SampleFormat::U8 => device.build_output_stream::<u8, _, _>(
&config,
move |data, _| {
data.iter_mut().for_each(|d| {
*d = mixer_rx
*d = samples
.next()
.map(Sample::from_sample)
.unwrap_or(u8::MAX / 2)
@ -270,11 +400,11 @@ impl CpalDeviceExt for cpal::Device {
error_callback,
None,
),
cpal::SampleFormat::U16 => self.build_output_stream::<u16, _, _>(
&format.config(),
cpal::SampleFormat::U16 => device.build_output_stream::<u16, _, _>(
&config,
move |data, _| {
data.iter_mut().for_each(|d| {
*d = mixer_rx
*d = samples
.next()
.map(Sample::from_sample)
.unwrap_or(u16::MAX / 2)
@ -283,11 +413,11 @@ impl CpalDeviceExt for cpal::Device {
error_callback,
None,
),
cpal::SampleFormat::U32 => self.build_output_stream::<u32, _, _>(
&format.config(),
cpal::SampleFormat::U32 => device.build_output_stream::<u32, _, _>(
&config,
move |data, _| {
data.iter_mut().for_each(|d| {
*d = mixer_rx
*d = samples
.next()
.map(Sample::from_sample)
.unwrap_or(u32::MAX / 2)
@ -296,11 +426,11 @@ impl CpalDeviceExt for cpal::Device {
error_callback,
None,
),
cpal::SampleFormat::U64 => self.build_output_stream::<u64, _, _>(
&format.config(),
cpal::SampleFormat::U64 => device.build_output_stream::<u64, _, _>(
&config,
move |data, _| {
data.iter_mut().for_each(|d| {
*d = mixer_rx
*d = samples
.next()
.map(Sample::from_sample)
.unwrap_or(u64::MAX / 2)
@ -309,31 +439,15 @@ impl CpalDeviceExt for cpal::Device {
error_callback,
None,
),
_ => return Err(cpal::BuildStreamError::StreamConfigNotSupported),
_ => Err(cpal::BuildStreamError::StreamConfigNotSupported),
}
.map(|stream| (mixer_tx, stream))
}
fn try_new_output_stream_config(
&self,
config: SupportedStreamConfig,
) -> Result<(Arc<DynamicMixerController<f32>>, cpal::Stream), StreamError> {
self.new_output_stream_with_format(config).or_else(|err| {
// look through all supported formats to see if another works
supported_output_formats(self)?
.find_map(|format| self.new_output_stream_with_format(format).ok())
// return original error if nothing works
.ok_or(StreamError::BuildStreamError(err))
})
}
}
/// All the supported output formats with sample rates
fn supported_output_formats(
/// Return all formats supported by the device.
fn supported_output_configs(
device: &cpal::Device,
) -> Result<impl Iterator<Item = cpal::SupportedStreamConfig>, StreamError> {
const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100);
let mut supported: Vec<_> = device
.supported_output_configs()
.map_err(StreamError::SupportedStreamConfigsError)?