mirror of
https://github.com/RustAudio/rodio
synced 2024-12-04 17:29:20 +00:00
Merge remote-tracking branch 'rust-audio/master' into test-fixes
This commit is contained in:
commit
b7dbe3f33b
23 changed files with 593 additions and 365 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -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
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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
35
examples/custom_config.rs
Normal 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(())
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
stream_handle.mixer().add(
|
||||
white(cpal::SampleRate(48000))
|
||||
.amplify(0.1)
|
||||
.take_duration(noise_duration),
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
println!("Playing white noise");
|
||||
|
||||
thread::sleep(interval_duration);
|
||||
|
||||
stream_handle
|
||||
.play_raw(
|
||||
stream_handle.mixer().add(
|
||||
pink(cpal::SampleRate(48000))
|
||||
.amplify(0.1)
|
||||
.take_duration(noise_duration),
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
println!("Playing pink noise");
|
||||
|
||||
thread::sleep(interval_duration);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "noise"))]
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
stream_handle.mixer().add(
|
||||
SignalGenerator::new(sample_rate, 1000.0, Function::Sine)
|
||||
.amplify(0.1)
|
||||
.take_duration(test_signal_duration),
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
thread::sleep(interval_duration);
|
||||
|
||||
println!("Playing 10,000 Hz tone");
|
||||
stream_handle
|
||||
.play_raw(
|
||||
SignalGenerator::new(cpal::SampleRate(48000), 10000.0, Function::Sine)
|
||||
stream_handle.mixer().add(
|
||||
SignalGenerator::new(sample_rate, 10000.0, Function::Sine)
|
||||
.amplify(0.1)
|
||||
.take_duration(test_signal_duration),
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
thread::sleep(interval_duration);
|
||||
|
||||
println!("Playing 440 Hz Triangle Wave");
|
||||
stream_handle
|
||||
.play_raw(
|
||||
SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Triangle)
|
||||
stream_handle.mixer().add(
|
||||
SignalGenerator::new(sample_rate, 440.0, Function::Triangle)
|
||||
.amplify(0.1)
|
||||
.take_duration(test_signal_duration),
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
thread::sleep(interval_duration);
|
||||
|
||||
println!("Playing 440 Hz Sawtooth Wave");
|
||||
stream_handle
|
||||
.play_raw(
|
||||
SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Sawtooth)
|
||||
stream_handle.mixer().add(
|
||||
SignalGenerator::new(sample_rate, 440.0, Function::Sawtooth)
|
||||
.amplify(0.1)
|
||||
.take_duration(test_signal_duration),
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
thread::sleep(interval_duration);
|
||||
|
||||
println!("Playing 440 Hz Square Wave");
|
||||
stream_handle
|
||||
.play_raw(
|
||||
SignalGenerator::new(cpal::SampleRate(48000), 440.0, Function::Square)
|
||||
stream_handle.mixer().add(
|
||||
SignalGenerator::new(sample_rate, 440.0, Function::Square)
|
||||
.amplify(0.1)
|
||||
.take_duration(test_signal_duration),
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
61
src/lib.rs
61
src/lib.rs
|
@ -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};
|
||||
|
|
|
@ -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]));
|
||||
|
27
src/sink.rs
27
src/sink.rs
|
@ -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];
|
||||
|
||||
|
|
|
@ -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>),
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
//! .take_duration(std::time::Duration::from_secs_f32(20.25))
|
||||
//! .amplify(0.20);
|
||||
//!
|
||||
//! 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());
|
||||
//! sink.set_speed(2.0);
|
||||
//! sink.append(source);
|
||||
//! std::thread::sleep(std::time::Duration::from_secs(5));
|
||||
|
|
|
@ -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.
|
||||
|
|
388
src/stream.rs
388
src/stream.rs
|
@ -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
|
||||
}
|
||||
|
||||
/// 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::try_from_device(&d).ok())
|
||||
.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
|
||||
/// 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::try_new(self)?;
|
||||
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)?
|
||||
|
|
Loading…
Reference in a new issue