diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cb03c86..381a89ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -165,7 +165,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 implementation. ### Fixed -- `Sink.try_seek` now updates `controls.position` before returning. Calls to `Sink.get_pos` +- `player.try_seek` now updates `controls.position` before returning. Calls to `player.get_pos` done immediately after a seek will now return the correct value. ### Changed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2fb03fd0..34f2062b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,7 @@ src/: - Follow [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/) - Use `rustfmt` for formatting - Implement `Source` trait for new audio sources -- Use `Sink` for playback management +- Use `Player` for playback management ## Common Tasks diff --git a/UPGRADE.md b/UPGRADE.md index 884d2f9b..e7c91532 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -56,12 +56,12 @@ No changes are required. The following Rodio *0.20* code: ```rust let (_stream, handle) = rodio::OutputStream::try_default()?; -let sink = rodio::Sink::try_new(&handle)?; +let player = rodio::Player::try_new(&handle)?; ``` Should be written like this in Rodio *0.21*: ```rust let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; -let sink = rodio::Sink::connect_new(stream_handle.mixer()); +let player = rodio::Player::connect_new(stream_handle.mixer()); ``` The `SpatialSink` changes mirror those in `Sink` described above. diff --git a/examples/automatic_gain_control.rs b/examples/automatic_gain_control.rs index 625f982c..07f01c62 100644 --- a/examples/automatic_gain_control.rs +++ b/examples/automatic_gain_control.rs @@ -8,8 +8,8 @@ use std::thread; use std::time::Duration; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); // Decode the sound file into a source let file = File::open("assets/music.flac")?; @@ -29,7 +29,7 @@ fn main() -> Result<(), Box> { // Add the source now equipped with automatic gain control and controlled via // periodic_access to the sink for the playback. - sink.append(controlled); + player.append(controlled); // After 5 seconds of playback disable automatic gain control using the // shared AtomicBool `agc_enabled`. You could do this from another part @@ -42,6 +42,6 @@ fn main() -> Result<(), Box> { agc_enabled.store(false, Ordering::Relaxed); // Keep the program running until the playback is complete. - sink.sleep_until_end(); + player.sleep_until_end(); Ok(()) } diff --git a/examples/basic.rs b/examples/basic.rs index 5628bd3b..4c45190e 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -6,15 +6,15 @@ use std::thread; use std::time::Duration; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; let mixer = stream_handle.mixer(); 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 + let player = rodio::play(mixer, BufReader::new(file))?; + player.set_volume(0.2); + player }; println!("Started beep1"); thread::sleep(Duration::from_millis(1500)); @@ -32,9 +32,9 @@ fn main() -> Result<(), Box> { 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 + let player = rodio::play(mixer, BufReader::new(file))?; + player.set_volume(0.2); + player }; println!("Started beep3"); thread::sleep(Duration::from_millis(1500)); diff --git a/examples/callback_on_end.rs b/examples/callback_on_end.rs index f367f7f4..4df0dc81 100644 --- a/examples/callback_on_end.rs +++ b/examples/callback_on_end.rs @@ -3,11 +3,11 @@ use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); let file = std::fs::File::open("assets/music.wav")?; - sink.append(rodio::Decoder::try_from(file)?); + player.append(rodio::Decoder::try_from(file)?); // lets increment a number after `music.wav` has played. We are going to use atomics // however you could also use a `Mutex` or send a message through a `std::sync::mpsc`. @@ -17,7 +17,7 @@ fn main() -> Result<(), Box> { // playlist_pos into the closure. That way we can still access playlist_pos // after appending the EmptyCallback. let playlist_pos_clone = playlist_pos.clone(); - sink.append(rodio::source::EmptyCallback::new(Box::new(move || { + player.append(rodio::source::EmptyCallback::new(Box::new(move || { println!("empty callback is now running"); playlist_pos_clone.fetch_add(1, Ordering::Relaxed); }))); @@ -27,7 +27,7 @@ fn main() -> Result<(), Box> { "playlist position is: {}", playlist_pos.load(Ordering::Relaxed) ); - sink.sleep_until_end(); + player.sleep_until_end(); assert_eq!(playlist_pos.load(Ordering::Relaxed), 1); println!( "playlist position is: {}", diff --git a/examples/custom_config.rs b/examples/custom_config.rs index aa83a7ee..0afd87ee 100644 --- a/examples/custom_config.rs +++ b/examples/custom_config.rs @@ -12,7 +12,7 @@ fn main() -> Result<(), Box> { 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)? + let stream_handle = rodio::OsSinkBuilder::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)) @@ -20,8 +20,8 @@ fn main() -> Result<(), Box> { .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()?; + // then use OsSinkBuilder::open_sink() instead. + .open_sink_or_fallback()?; let mixer = stream_handle.mixer(); let wave = SineWave::new(740.0) diff --git a/examples/distortion.rs b/examples/distortion.rs index eae17bf7..16962397 100644 --- a/examples/distortion.rs +++ b/examples/distortion.rs @@ -5,7 +5,7 @@ use std::time::Duration; fn main() -> Result<(), Box> { // Open the default output stream and get the mixer - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; let mixer = stream_handle.mixer(); // Create a sine wave source and apply distortion diff --git a/examples/distortion_mp3.rs b/examples/distortion_mp3.rs index 895c7647..52f7f056 100644 --- a/examples/distortion_mp3.rs +++ b/examples/distortion_mp3.rs @@ -3,15 +3,15 @@ use std::error::Error; use rodio::Source; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); let file = std::fs::File::open("assets/music.mp3")?; // Apply distortion effect before appending to the sink let source = rodio::Decoder::try_from(file)?.distortion(4.0, 0.3); - sink.append(source); + player.append(source); - sink.sleep_until_end(); + player.sleep_until_end(); Ok(()) } diff --git a/examples/distortion_wav.rs b/examples/distortion_wav.rs index 0955fd51..825433cb 100644 --- a/examples/distortion_wav.rs +++ b/examples/distortion_wav.rs @@ -3,15 +3,15 @@ use std::error::Error; use rodio::Source; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); let file = std::fs::File::open("assets/music.wav")?; // Apply distortion effect before appending to the sink let source = rodio::Decoder::try_from(file)?.distortion(4.0, 0.3); - sink.append(source); + player.append(source); - sink.sleep_until_end(); + player.sleep_until_end(); Ok(()) } diff --git a/examples/distortion_wav_alternate.rs b/examples/distortion_wav_alternate.rs index 8d3f36b4..cead8298 100644 --- a/examples/distortion_wav_alternate.rs +++ b/examples/distortion_wav_alternate.rs @@ -9,8 +9,8 @@ use std::time::Duration; use rodio::Source; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); let file = std::fs::File::open("assets/music.wav")?; let source = rodio::Decoder::try_from(file)?; @@ -31,7 +31,7 @@ fn main() -> Result<(), Box> { src.set_threshold(if enable { 0.3 } else { 1.0 }); }); - sink.append(distorted); + player.append(distorted); println!("Playing music.wav with alternating distortion effect..."); // Alternate the distortion effect every second for 10 seconds @@ -43,7 +43,7 @@ fn main() -> Result<(), Box> { } // Wait for playback to finish - sink.sleep_until_end(); + player.sleep_until_end(); Ok(()) } diff --git a/examples/error_callback.rs b/examples/error_callback.rs index ab335583..096348a6 100644 --- a/examples/error_callback.rs +++ b/examples/error_callback.rs @@ -12,7 +12,7 @@ fn main() -> Result<(), Box> { let (tx, rx) = std::sync::mpsc::channel(); - let stream_handle = rodio::OutputStreamBuilder::from_device(default_device)? + let stream_handle = rodio::OsSinkBuilder::from_device(default_device)? .with_error_callback(move |err| { // Filter for where err is an actionable error. if matches!( @@ -24,7 +24,7 @@ fn main() -> Result<(), Box> { } } }) - .open_stream_or_fallback()?; + .open_sink_or_fallback()?; let mixer = stream_handle.mixer(); diff --git a/examples/limit_wav.rs b/examples/limit_wav.rs index b104d3ff..ddb9e8c0 100644 --- a/examples/limit_wav.rs +++ b/examples/limit_wav.rs @@ -2,18 +2,18 @@ use rodio::{source::LimitSettings, Source}; use std::error::Error; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); let file = std::fs::File::open("assets/music.wav")?; let source = rodio::Decoder::try_from(file)? .amplify(3.0) .limit(LimitSettings::default()); - sink.append(source); + player.append(source); println!("Playing music.wav with limiting until finished..."); - sink.sleep_until_end(); + player.sleep_until_end(); println!("Done."); Ok(()) diff --git a/examples/low_pass.rs b/examples/low_pass.rs index ec343888..ab8e575d 100644 --- a/examples/low_pass.rs +++ b/examples/low_pass.rs @@ -4,15 +4,15 @@ use std::io::BufReader; use rodio::Source; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); let file = std::fs::File::open("assets/music.wav")?; let decoder = rodio::Decoder::new(BufReader::new(file))?; let source = decoder.low_pass(200); - sink.append(source); + player.append(source); - sink.sleep_until_end(); + player.sleep_until_end(); Ok(()) } diff --git a/examples/microphone.rs b/examples/microphone.rs index 97419327..107e9f8d 100644 --- a/examples/microphone.rs +++ b/examples/microphone.rs @@ -13,7 +13,7 @@ fn main() -> Result<(), Box> { .prompt()?; let input = MicrophoneBuilder::new() - .device(input.into_inner())? + .device(input)? .default_config()? .open_stream()?; @@ -21,7 +21,7 @@ fn main() -> Result<(), Box> { let recording = input.take_duration(Duration::from_secs(5)).record(); println!("Playing the recording"); - let mut output = rodio::OutputStreamBuilder::open_default_stream()?; + let mut output = rodio::OsSinkBuilder::open_default_sink()?; output.mixer().add(recording); thread::sleep(Duration::from_secs(5)); diff --git a/examples/mix_multiple_sources.rs b/examples/mix_multiple_sources.rs index 4d4360d6..7cb8df39 100644 --- a/examples/mix_multiple_sources.rs +++ b/examples/mix_multiple_sources.rs @@ -9,10 +9,10 @@ const NOTE_DURATION: Duration = Duration::from_secs(1); const NOTE_AMPLITUDE: Float = 0.20; fn main() -> Result<(), Box> { - // Construct a dynamic controller and mixer, stream_handle, and sink. + // Construct a dynamic controller and mixer, stream_handle, and player. let (controller, mixer) = mixer::mixer(NonZero::new(2).unwrap(), NonZero::new(44_100).unwrap()); - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::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, @@ -37,10 +37,10 @@ fn main() -> Result<(), Box> { controller.add(source_a); // Append the dynamic mixer to the sink to play a C major 6th chord. - sink.append(mixer); + player.append(mixer); // Sleep the thread until sink is empty. - sink.sleep_until_end(); + player.sleep_until_end(); Ok(()) } diff --git a/examples/music_flac.rs b/examples/music_flac.rs index f93028be..76c688d4 100644 --- a/examples/music_flac.rs +++ b/examples/music_flac.rs @@ -1,13 +1,13 @@ use std::error::Error; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); let file = std::fs::File::open("assets/music.flac")?; - sink.append(rodio::Decoder::try_from(file)?); + player.append(rodio::Decoder::try_from(file)?); - sink.sleep_until_end(); + player.sleep_until_end(); Ok(()) } diff --git a/examples/music_m4a.rs b/examples/music_m4a.rs index b8a20921..1f1d7af1 100644 --- a/examples/music_m4a.rs +++ b/examples/music_m4a.rs @@ -1,13 +1,13 @@ use std::error::Error; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); let file = std::fs::File::open("assets/music.m4a")?; - sink.append(rodio::Decoder::try_from(file)?); + player.append(rodio::Decoder::try_from(file)?); - sink.sleep_until_end(); + player.sleep_until_end(); Ok(()) } diff --git a/examples/music_mp3.rs b/examples/music_mp3.rs index bacc7309..1d2851ae 100644 --- a/examples/music_mp3.rs +++ b/examples/music_mp3.rs @@ -1,13 +1,13 @@ use std::error::Error; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); let file = std::fs::File::open("assets/music.mp3")?; - sink.append(rodio::Decoder::try_from(file)?); + player.append(rodio::Decoder::try_from(file)?); - sink.sleep_until_end(); + player.sleep_until_end(); Ok(()) } diff --git a/examples/music_ogg.rs b/examples/music_ogg.rs index e9ed41d2..6ea3aafc 100644 --- a/examples/music_ogg.rs +++ b/examples/music_ogg.rs @@ -1,14 +1,14 @@ use std::{error::Error, io::Cursor}; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); let file = include_bytes!("../assets/music.ogg"); let cursor = Cursor::new(file); - sink.append(rodio::Decoder::try_from(cursor)?); + player.append(rodio::Decoder::try_from(cursor)?); - sink.sleep_until_end(); + player.sleep_until_end(); Ok(()) } diff --git a/examples/music_wav.rs b/examples/music_wav.rs index ea50de38..d48c75fb 100644 --- a/examples/music_wav.rs +++ b/examples/music_wav.rs @@ -1,13 +1,13 @@ use std::error::Error; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); let file = std::fs::File::open("assets/music.wav")?; - sink.append(rodio::Decoder::try_from(file)?); + player.append(rodio::Decoder::try_from(file)?); - sink.sleep_until_end(); + player.sleep_until_end(); Ok(()) } diff --git a/examples/noise_generator.rs b/examples/noise_generator.rs index a3c3d444..64a6b3ca 100644 --- a/examples/noise_generator.rs +++ b/examples/noise_generator.rs @@ -7,11 +7,11 @@ use rodio::{ source::noise::{ Blue, Brownian, Pink, Velvet, Violet, WhiteGaussian, WhiteTriangular, WhiteUniform, }, - Sample, Source, + MixerOsSink, Sample, Source, }; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; let sample_rate = stream_handle.config().sample_rate(); play_noise( @@ -74,7 +74,7 @@ fn main() -> Result<(), Box> { } /// Helper function to play a noise type with description -fn play_noise(stream_handle: &rodio::OutputStream, source: S, name: &str, description: &str) +fn play_noise(stream_handle: &MixerOsSink, source: S, name: &str, description: &str) where S: Source + Send + 'static, { diff --git a/examples/reverb.rs b/examples/reverb.rs index b8c04b52..5a4e636c 100644 --- a/examples/reverb.rs +++ b/examples/reverb.rs @@ -3,15 +3,15 @@ use std::error::Error; use std::time::Duration; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); let file = std::fs::File::open("assets/music.ogg")?; let source = rodio::Decoder::try_from(file)?; let with_reverb = source.buffered().reverb(Duration::from_millis(40), 0.7); - sink.append(with_reverb); + player.append(with_reverb); - sink.sleep_until_end(); + player.sleep_until_end(); Ok(()) } diff --git a/examples/seek_mp3.rs b/examples/seek_mp3.rs index c27f5346..0788ea89 100644 --- a/examples/seek_mp3.rs +++ b/examples/seek_mp3.rs @@ -2,22 +2,22 @@ use std::error::Error; use std::time::Duration; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); let file = std::fs::File::open("assets/music.mp3")?; - sink.append(rodio::Decoder::try_from(file)?); + player.append(rodio::Decoder::try_from(file)?); std::thread::sleep(std::time::Duration::from_secs(2)); - sink.try_seek(Duration::from_secs(0))?; + player.try_seek(Duration::from_secs(0))?; std::thread::sleep(std::time::Duration::from_secs(2)); - sink.try_seek(Duration::from_secs(4))?; + player.try_seek(Duration::from_secs(4))?; - sink.sleep_until_end(); + player.sleep_until_end(); // This doesn't do anything since the sound has ended already. - sink.try_seek(Duration::from_secs(5))?; + player.try_seek(Duration::from_secs(5))?; println!("seek example ended"); Ok(()) diff --git a/examples/signal_generator.rs b/examples/signal_generator.rs index 2257f30d..8949d171 100644 --- a/examples/signal_generator.rs +++ b/examples/signal_generator.rs @@ -8,7 +8,7 @@ fn main() -> Result<(), Box> { use std::thread; use std::time::Duration; - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; let test_signal_duration = Duration::from_millis(1000); let interval_duration = Duration::from_millis(1500); diff --git a/examples/spatial.rs b/examples/spatial.rs index f1b97e44..0013d379 100644 --- a/examples/spatial.rs +++ b/examples/spatial.rs @@ -18,10 +18,10 @@ fn main() -> Result<(), Box> { let total_duration = iter_duration * 2 * repeats; - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; let mut positions = ([0., 0., 0.], [-1., 0., 0.], [1., 0., 0.]); - let sink = rodio::SpatialSink::connect_new( + let player = rodio::SpatialPlayer::connect_new( stream_handle.mixer(), positions.0, positions.1, @@ -32,7 +32,7 @@ fn main() -> Result<(), Box> { let source = rodio::Decoder::try_from(file)? .repeat_infinite() .take_duration(total_duration); - sink.append(source); + player.append(source); // A sound emitter playing the music starting at the centre gradually moves to the right // until it stops and begins traveling to the left, it will eventually pass through the // listener again and go to the far left. @@ -41,20 +41,20 @@ fn main() -> Result<(), Box> { for _ in 0..num_steps { thread::sleep(refresh_duration); positions.0[0] += step_distance; - sink.set_emitter_position(positions.0); + player.set_emitter_position(positions.0); } for _ in 0..(num_steps * 2) { thread::sleep(refresh_duration); positions.0[0] -= step_distance; - sink.set_emitter_position(positions.0); + player.set_emitter_position(positions.0); } for _ in 0..num_steps { thread::sleep(refresh_duration); positions.0[0] += step_distance; - sink.set_emitter_position(positions.0); + player.set_emitter_position(positions.0); } } - sink.sleep_until_end(); + player.sleep_until_end(); Ok(()) } diff --git a/examples/stereo.rs b/examples/stereo.rs index c86eef73..872fa436 100644 --- a/examples/stereo.rs +++ b/examples/stereo.rs @@ -4,13 +4,13 @@ use rodio::Source; use std::error::Error; fn main() -> Result<(), Box> { - let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; - let sink = rodio::Sink::connect_new(stream_handle.mixer()); + let stream_handle = rodio::OsSinkBuilder::open_default_sink()?; + let player = rodio::Player::connect_new(stream_handle.mixer()); let file = std::fs::File::open("assets/RL.ogg")?; - sink.append(rodio::Decoder::try_from(file)?.amplify(0.2)); + player.append(rodio::Decoder::try_from(file)?.amplify(0.2)); - sink.sleep_until_end(); + player.sleep_until_end(); Ok(()) } diff --git a/src/common.rs b/src/common.rs index f6da20eb..ca2359e2 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,7 +1,7 @@ use std::fmt::{Debug, Display}; use std::num::NonZero; -/// Stream sample rate (a frame rate or samples per second per channel). +/// Sample rate (a frame rate or samples per second per channel). pub type SampleRate = NonZero; /// Number of channels in a stream. Can never be Zero diff --git a/src/lib.rs b/src/lib.rs index 1b71e929..a499f7e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,31 +3,31 @@ //! 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()`]. +//! - Get an OS-Sink handle to a physical device. For example, get a sink to the system's +//! default sound device with [`OsSinkBuilder::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. -//! - Add the source to the output stream using [`OutputStream::mixer()`](OutputStream::mixer) -//! on the output stream handle. +//! - Add the source to the OS-Sink using [`OsSink::mixer()`](OutputStream::mixer) +//! on the OS-Sink handle. //! //! Here is a complete example of how you would play an audio file: //! #![cfg_attr(not(feature = "playback"), doc = "```ignore")] #![cfg_attr(feature = "playback", doc = "```no_run")] //! use std::fs::File; -//! use rodio::{Decoder, OutputStream, source::Source}; +//! use rodio::{Decoder, MixerOsSink, 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() +//! // Get an OS-Sink handle to the default physical sound device. +//! // Note that the playback stops when the handle is dropped.//! +//! let handle = rodio::OsSinkBuilder::open_default_sink() //! .expect("open default audio stream"); -//! let sink = rodio::Sink::connect_new(&stream_handle.mixer()); +//! let player = rodio::Player::connect_new(&handle.mixer()); //! // Load a sound from a file, using a path relative to Cargo.toml //! let file = File::open("examples/music.ogg").unwrap(); //! // Decode that sound file into a source //! let source = Decoder::try_from(file).unwrap(); //! // Play the sound directly on the device -//! stream_handle.mixer().add(source); +//! handle.mixer().add(source); //! //! // The sound plays in a separate audio thread, //! // so we need to keep the main thread alive while it's playing. @@ -39,17 +39,17 @@ #![cfg_attr(feature = "playback", doc = "```no_run")] //! use std::fs::File; //! use std::io::BufReader; -//! use rodio::{Decoder, OutputStream, source::Source}; +//! use rodio::{Decoder, MixerOsSink, 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() +//! // Get an OS-Sink handle to the default physical sound device. +//! // Note that the playback stops when the sink_handle is dropped. +//! let sink_handle = rodio::OsSinkBuilder::open_default_sink() //! .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()); -//! // Note that the playback stops when the sink is dropped -//! let sink = rodio::play(&stream_handle.mixer(), file).unwrap(); +//! // Note that the playback stops when the player is dropped +//! let player = rodio::play(&sink_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. @@ -57,43 +57,43 @@ //! ``` //! //! -//! ## Sink +//! ## Player //! //! In order to make it easier to control the playback, the rodio library also provides a type -//! named [`Sink`] which represents an audio track. [`Sink`] plays its input sources sequentially, +//! named [`Player`] which represents an audio track. [`Player`] plays its input sources sequentially, //! one after another. To play sounds in simultaneously in parallel, use [`mixer::Mixer`] instead. //! -//! To play a song Create a [`Sink`] connect it to the output stream, -//! and [`.append()`](Sink::append) your sound to it. +//! To play a song Create a [`Player`], connect it to the OS-Sink, +//! and [`.append()`](Player::append) your sound to it. //! #![cfg_attr(not(feature = "playback"), doc = "```ignore")] #![cfg_attr(feature = "playback", doc = "```no_run")] //! use std::time::Duration; -//! use rodio::{OutputStream, Sink}; +//! use rodio::{MixerOsSink, Player}; //! use rodio::source::{SineWave, Source}; //! //! // _stream must live as long as the sink -//! let stream_handle = rodio::OutputStreamBuilder::open_default_stream() +//! let handle = rodio::OsSinkBuilder::open_default_sink() //! .expect("open default audio stream"); -//! let sink = rodio::Sink::connect_new(&stream_handle.mixer()); +//! let player = rodio::Player::connect_new(&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); -//! sink.append(source); +//! player.append(source); //! -//! // The sound plays in a separate thread. This call will block the current thread until the sink -//! // has finished playing all its queued sounds. -//! sink.sleep_until_end(); +//! // The sound plays in a separate thread. This call will block the current thread until the +//! // player has finished playing all its queued sounds. +//! player.sleep_until_end(); //! ``` //! -//! The [`append`](Sink::append) method will add the sound at the end of the -//! sink. It will be played when all the previous sounds have been played. If you want multiple -//! sounds to play simultaneously, you should create multiple [`Sink`]s. +//! The [`append`](Player::append) method will add the sound at the end of the +//! player. It will be played when all the previous sounds have been played. If you want multiple +//! sounds to play simultaneously consider building your own [`Player`] from rodio parts. //! -//! The [`Sink`] type also provides utilities such as playing/pausing or controlling the volume. +//! The [`Player`] type also provides utilities such as playing/pausing or controlling the volume. //! -//!
Note that playback through Sink will end if the associated -//! OutputStream is dropped.
+//!
Note that playback through Player will end if the associated +//! OsSink is dropped.
//! //! ## Filters //! @@ -177,8 +177,10 @@ pub use cpal::{ }; mod common; -mod sink; -mod spatial_sink; +mod player; +mod spatial_player; +#[cfg(all(feature = "playback", feature = "experimental"))] +pub mod speakers; #[cfg(feature = "playback")] pub mod stream; #[cfg(feature = "wav_output")] @@ -199,11 +201,11 @@ pub mod static_buffer; pub use crate::common::{BitDepth, ChannelCount, Float, Sample, SampleRate}; pub use crate::decoder::Decoder; -pub use crate::sink::Sink; +pub use crate::player::Player as Player; pub use crate::source::Source; -pub use crate::spatial_sink::SpatialSink; +pub use crate::spatial_player::SpatialPlayer; #[cfg(feature = "playback")] -pub use crate::stream::{play, OutputStream, OutputStreamBuilder, PlayError, StreamError}; +pub use crate::stream::{play, MixerOsSink, OsSinkBuilder, PlayError, OsSinkError}; #[cfg(feature = "wav_output")] #[cfg_attr(docsrs, doc(cfg(feature = "wav_output")))] pub use crate::wav_output::wav_to_file; diff --git a/src/microphone.rs b/src/microphone.rs index e5641cfe..eabfa807 100644 --- a/src/microphone.rs +++ b/src/microphone.rs @@ -91,7 +91,7 @@ //! //! // Use a specific device (e.g., the second one) //! let mic = MicrophoneBuilder::new() -//! .device(inputs[1].clone().into_inner())? +//! .device(inputs[1].clone())? //! .default_config()? //! .open_stream()?; //! # Ok(()) diff --git a/src/microphone/builder.rs b/src/microphone/builder.rs index cc10b383..9925d035 100644 --- a/src/microphone/builder.rs +++ b/src/microphone/builder.rs @@ -38,11 +38,11 @@ pub enum Error { } assert_error_traits! {Error} -/// Generic on the `MicrophoneBuilder` which is only present when a config has been set. +/// Generic on the `MicrophoneBuilder` which is only present when a device has been set. /// Methods needing a config are only available on MicrophoneBuilder with this /// Generic set. pub struct DeviceIsSet; -/// Generic on the `MicrophoneBuilder` which is only present when a device has been set. +/// Generic on the `MicrophoneBuilder` which is only present when a config has been set. /// Methods needing a device set are only available on MicrophoneBuilder with this /// Generic set. pub struct ConfigIsSet; @@ -129,14 +129,14 @@ where /// ```no_run /// # use rodio::microphone::{MicrophoneBuilder, available_inputs}; /// let input = available_inputs()?.remove(2); - /// let builder = MicrophoneBuilder::new().device(input.into_inner())?; + /// let builder = MicrophoneBuilder::new().device(input)?; /// # Ok::<(), Box>(()) /// ``` pub fn device( &self, - device: impl Into, + device: super::Input, ) -> Result, Error> { - let device = device.into(); + let device = device.into_inner(); let supported_configs = device .supported_input_configs() .map_err(|source| Error::InputConfigs { @@ -283,7 +283,12 @@ where Ok(()) } } +} +impl MicrophoneBuilder +where + E: FnMut(cpal::StreamError) + Send + Clone + 'static, +{ /// Sets the sample rate for input. /// /// # Error @@ -474,8 +479,7 @@ where /// let builder = MicrophoneBuilder::new() /// .default_device()? /// .default_config()? - /// // We want mono, if thats not possible give - /// // us the lowest channel count + /// // Multiples of two work well for us /// .prefer_buffer_sizes([ /// 2048.try_into().expect("not zero"), /// 4096.try_into().expect("not_zero"), @@ -489,8 +493,8 @@ where /// let builder = MicrophoneBuilder::new() /// .default_device()? /// .default_config()? - /// // We want mono, if thats not possible give - /// // us the lowest channel count + /// // We need a minimum buffer of 4096 + /// // or we get glitches. /// .prefer_buffer_sizes(4096..); /// # Ok::<(), Box>(()) /// ``` @@ -523,8 +527,8 @@ where /// println!("Channel count: {}", config.channel_count.get()); /// # Ok::<(), Box>(()) /// ``` - pub fn get_config(&self) -> &InputConfig { - self.config.as_ref().expect("ConfigIsSet") + pub fn get_config(&self) -> InputConfig { + self.config.expect("ConfigIsSet") } } diff --git a/src/mixer.rs b/src/mixer.rs index 4f3241e8..896deaa5 100644 --- a/src/mixer.rs +++ b/src/mixer.rs @@ -15,9 +15,9 @@ use std::time::Duration; /// After creating a mixer, you can add new sounds with the controller. /// /// Note that mixer without any input source behaves like an `Empty` (not: `Zero`) source, -/// and thus, just after appending to a sink, the mixer is removed from the sink. -/// As a result, input sources added to the mixer later might not be forwarded to the sink. -/// Add `Zero` source to prevent detaching the mixer from sink. +/// and thus, just after appending to a player, the mixer is removed from the player. +/// As a result, input sources added to the mixer later might not be forwarded to the player. +/// Add `Zero` source to prevent detaching the mixer from player. pub fn mixer(channels: ChannelCount, sample_rate: SampleRate) -> (Mixer, MixerSource) { let input = Mixer(Arc::new(Inner { has_pending: AtomicBool::new(false), diff --git a/src/sink.rs b/src/player.rs similarity index 87% rename from src/sink.rs rename to src/player.rs index 8c1f61d2..9be3bfdc 100644 --- a/src/sink.rs +++ b/src/player.rs @@ -15,9 +15,9 @@ use crate::{queue, source::Done, Source}; /// Handle to a device that outputs sounds. /// -/// Dropping the `Sink` stops all its sounds. You can use `detach` if you want the sounds to continue +/// Dropping the `Player` stops all its sounds. You can use `detach` if you want the sounds to continue /// playing. -pub struct Sink { +pub struct Player { queue_tx: Arc, sleep_until_end: Mutex>>, @@ -67,21 +67,21 @@ struct Controls { position: Mutex, } -impl Sink { - /// Builds a new `Sink`, beginning playback on a stream. +impl Player { + /// Builds a new `Player`, beginning playback on a stream. #[inline] - pub fn connect_new(mixer: &Mixer) -> Sink { - let (sink, source) = Sink::new(); + pub fn connect_new(mixer: &Mixer) -> Player { + let (sink, source) = Player::new(); mixer.add(source); sink } - /// Builds a new `Sink`. + /// Builds a new `Player`. #[inline] - pub fn new() -> (Sink, queue::SourcesQueueOutput) { + pub fn new() -> (Player, queue::SourcesQueueOutput) { let (queue_tx, queue_rx) = queue::queue(true); - let sink = Sink { + let sink = Player { queue_tx, sleep_until_end: Mutex::new(None), controls: Arc::new(Controls { @@ -181,7 +181,7 @@ impl Sink { /// Gets the speed of the sound. /// - /// See [`Sink::set_speed`] for details on what *speed* means. + /// See [`Player::set_speed`] for details on what *speed* means. #[inline] pub fn speed(&self) -> f32 { *self.controls.speed.lock().unwrap() @@ -205,7 +205,7 @@ impl Sink { *self.controls.speed.lock().unwrap() = value; } - /// Resumes playback of a paused sink. + /// Resumes playback of a paused player. /// /// No effect if not paused. #[inline] @@ -256,7 +256,7 @@ impl Sink { } } - /// Pauses playback of this sink. + /// Pauses playback of this player. /// /// No effect if already paused. /// @@ -267,15 +267,15 @@ impl Sink { /// Gets if a sink is paused /// - /// Sinks can be paused and resumed using `pause()` and `play()`. This returns `true` if the + /// Players can be paused and resumed using `pause()` and `play()`. This returns `true` if the /// sink is paused. pub fn is_paused(&self) -> bool { self.controls.pause.load(Ordering::SeqCst) } - /// Removes all currently loaded `Source`s from the `Sink`, and pauses it. + /// Removes all currently loaded `Source`s from the `Player`, and pauses it. /// - /// See `pause()` for information about pausing a `Sink`. + /// See `pause()` for information about pausing a `Player`. pub fn clear(&self) { let len = self.sound_count.load(Ordering::SeqCst) as u32; *self.controls.to_clear.lock().unwrap() = len; @@ -283,10 +283,10 @@ impl Sink { self.pause(); } - /// Skips to the next `Source` in the `Sink` + /// Skips to the next `Source` in the `Player` /// - /// If there are more `Source`s appended to the `Sink` at the time, - /// it will play the next one. Otherwise, the `Sink` will finish as if + /// If there are more `Source`s appended to the `Player` at the time, + /// it will play the next one. Otherwise, the `Player` will finish as if /// it had finished playing a `Source` all the way through. pub fn skip_one(&self) { let len = self.sound_count.load(Ordering::SeqCst) as u32; @@ -334,7 +334,7 @@ impl Sink { /// This takes into account any speedup or delay applied. /// /// Example: if you apply a speedup of *2* to an mp3 decoder source and - /// [`get_pos()`](Sink::get_pos) returns *5s* then the position in the mp3 + /// [`get_pos()`](Player::get_pos) returns *5s* then the position in the mp3 /// recording is *10s* from its start. #[inline] pub fn get_pos(&self) -> Duration { @@ -342,7 +342,7 @@ impl Sink { } } -impl Drop for Sink { +impl Drop for Player { #[inline] fn drop(&mut self) { self.queue_tx.set_keep_alive_if_empty(false); @@ -359,11 +359,11 @@ mod tests { use crate::buffer::SamplesBuffer; use crate::math::nz; - use crate::{Sink, Source}; + use crate::{Player, Source}; #[test] fn test_pause_and_stop() { - let (sink, mut source) = Sink::new(); + let (player, mut source) = Player::new(); assert_eq!(source.next(), Some(0.0)); // TODO (review) How did this test passed before? I might have broken something but @@ -375,49 +375,49 @@ mod tests { let v = vec![10.0, -10.0, 20.0, -20.0, 30.0, -30.0]; // Low rate to ensure immediate control. - sink.append(SamplesBuffer::new(nz!(1), nz!(1), v.clone())); + player.append(SamplesBuffer::new(nz!(1), nz!(1), v.clone())); let mut reference_src = SamplesBuffer::new(nz!(1), nz!(1), v); assert_eq!(source.next(), reference_src.next()); assert_eq!(source.next(), reference_src.next()); - sink.pause(); + player.pause(); assert_eq!(source.next(), Some(0.0)); - sink.play(); + player.play(); assert_eq!(source.next(), reference_src.next()); assert_eq!(source.next(), reference_src.next()); - sink.stop(); + player.stop(); assert_eq!(source.next(), Some(0.0)); - assert!(sink.empty()); + assert!(player.empty()); } #[test] fn test_stop_and_start() { - let (sink, mut queue_rx) = Sink::new(); + let (player, mut queue_rx) = Player::new(); let v = vec![10.0, -10.0, 20.0, -20.0, 30.0, -30.0]; - sink.append(SamplesBuffer::new(nz!(1), nz!(1), v.clone())); + player.append(SamplesBuffer::new(nz!(1), nz!(1), v.clone())); let mut src = SamplesBuffer::new(nz!(1), nz!(1), v.clone()); assert_eq!(queue_rx.next(), src.next()); assert_eq!(queue_rx.next(), src.next()); - sink.stop(); + player.stop(); - assert!(sink.controls.stopped.load(Ordering::SeqCst)); + assert!(player.controls.stopped.load(Ordering::SeqCst)); assert_eq!(queue_rx.next(), Some(0.0)); src = SamplesBuffer::new(nz!(1), nz!(1), v.clone()); - sink.append(SamplesBuffer::new(nz!(1), nz!(1), v)); + player.append(SamplesBuffer::new(nz!(1), nz!(1), v)); - assert!(!sink.controls.stopped.load(Ordering::SeqCst)); + assert!(!player.controls.stopped.load(Ordering::SeqCst)); // Flush silence let mut queue_rx = queue_rx.skip_while(|v| *v == 0.0); @@ -427,16 +427,16 @@ mod tests { #[test] fn test_volume() { - let (sink, mut queue_rx) = Sink::new(); + let (player, mut queue_rx) = Player::new(); let v = vec![10.0, -10.0, 20.0, -20.0, 30.0, -30.0]; // High rate to avoid immediate control. - sink.append(SamplesBuffer::new(nz!(2), nz!(44100), v.clone())); + player.append(SamplesBuffer::new(nz!(2), nz!(44100), v.clone())); let src = SamplesBuffer::new(nz!(2), nz!(44100), v.clone()); let mut src = src.amplify(0.5); - sink.set_volume(0.5); + player.set_volume(0.5); for _ in 0..v.len() { assert_eq!(queue_rx.next(), src.next()); diff --git a/src/queue.rs b/src/queue.rs index 3bf1695e..e7234b6a 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -61,10 +61,9 @@ impl SourcesQueueInput { where T: Source + Send + 'static, { - self.next_sounds - .lock() - .unwrap() - .push((Box::new(source) as Box<_>, None)); + let mut next_sounds = self.next_sounds.lock().unwrap(); + next_sounds.push((Box::new(source) as Box<_>, None)); + next_sounds.len(); } /// Adds a new source to the end of the queue. diff --git a/src/source/dither.rs b/src/source/dither.rs index f87e472c..adc8b436 100644 --- a/src/source/dither.rs +++ b/src/source/dither.rs @@ -22,7 +22,7 @@ //! - **Choose TPDF** for most professional audio applications (it's the default) //! - **Use target output bit depth** - Not the source bit depth! //! -//! When you later change volume (e.g., with `Sink::set_volume()`), both the signal +//! When you later change volume (e.g., with `Player::set_volume()`), both the signal //! and dither noise scale together, maintaining proper dithering behavior. use rand::{rngs::SmallRng, Rng}; diff --git a/src/source/mod.rs b/src/source/mod.rs index 5cc890b1..b458d13a 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -397,15 +397,15 @@ pub trait Source: Iterator { /// ```rust /// // Apply Automatic Gain Control to the source (AGC is on by default) /// use rodio::source::{Source, SineWave, AutomaticGainControlSettings}; - /// use rodio::Sink; + /// use rodio::Player; /// use std::time::Duration; /// let source = SineWave::new(444.0); // An example. - /// let (sink, output) = Sink::new(); // An example. + /// let (player, output) = Player::new(); // An example. /// /// let agc_source = source.automatic_gain_control(AutomaticGainControlSettings::default()); /// /// // Add the AGC-controlled source to the sink - /// sink.append(agc_source); + /// player.append(agc_source); /// /// ``` #[inline] @@ -642,7 +642,7 @@ pub trait Source: Iterator { } /// Adds a method [`Skippable::skip`] for skipping this source. Skipping - /// makes Source::next() return None. Which in turn makes the Sink skip to + /// makes Source::next() return None. Which in turn makes the Player skip to /// the next source. fn skippable(self) -> Skippable where diff --git a/src/source/skippable.rs b/src/source/skippable.rs index ba4d43d9..0fc46560 100644 --- a/src/source/skippable.rs +++ b/src/source/skippable.rs @@ -5,7 +5,7 @@ use std::time::Duration; use super::SeekError; /// Wrap the source in a skippable. It allows ending the current source early by -/// calling [`Skippable::skip`]. If this source is in a queue such as the Sink +/// calling [`Skippable::skip`]. If this source is in a queue such as the Player /// ending the source early is equal to skipping the source. pub fn skippable(source: I) -> Skippable { Skippable { @@ -15,7 +15,7 @@ pub fn skippable(source: I) -> Skippable { } /// Wrap the source in a skippable. It allows ending the current source early by -/// calling [`Skippable::skip`]. If this source is in a queue such as the Sink +/// calling [`Skippable::skip`]. If this source is in a queue such as the Player /// ending the source early is equal to skipping the source. #[derive(Clone, Debug)] pub struct Skippable { diff --git a/src/source/spatial.rs b/src/source/spatial.rs index 8aad0b7e..136dad9d 100644 --- a/src/source/spatial.rs +++ b/src/source/spatial.rs @@ -27,7 +27,7 @@ impl Spatial where I: Source, { - /// Builds a new `SpatialSink`, beginning playback on a stream. + /// Builds a new `SpatialPlayer`, beginning playback on a stream. pub fn new( input: I, emitter_position: [f32; 3], diff --git a/src/source/speed.rs b/src/source/speed.rs index e38c2b99..b9ea51da 100644 --- a/src/source/speed.rs +++ b/src/source/speed.rs @@ -1,7 +1,7 @@ //! Playback Speed control Module. //! //! The main concept of this module is the [`Speed`] struct, which -//! encapsulates playback speed controls of the current sink. +//! encapsulates playback speed controls of the current player. //! //! In order to speed up a sink, the speed struct: //! - Increases the current sample rate by the given factor. @@ -14,18 +14,18 @@ #![cfg_attr(not(feature = "playback"), doc = "```ignore")] #![cfg_attr(feature = "playback", doc = "```no_run")] //!# use std::fs::File; -//!# use rodio::{Decoder, Sink, OutputStream, source::{Source, SineWave}}; +//!# use rodio::{Decoder, Player, source::{Source, SineWave}}; //! -//! // Get an output stream handle to the default physical sound device. -//! // 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"); +//! // Get an OS-Sink handle to the default physical sound device. +//! // Note that no sound will be played if the _handle_ is dropped. +//! let handle = rodio::OsSinkBuilder::open_default_sink() +//! .expect("open default audio sink"); //! // Load a sound from a file, using a path relative to `Cargo.toml` //! let file = File::open("examples/music.ogg").unwrap(); //! // Decode that sound file into a source //! let source = Decoder::try_from(file).unwrap(); //! // Play the sound directly on the device 2x faster -//! stream_handle.mixer().add(source.speed(2.0)); +//! handle.mixer().add(source.speed(2.0)); //! std::thread::sleep(std::time::Duration::from_secs(5)); //! ``` //! Here is how you would do it using the sink: @@ -35,11 +35,11 @@ //! let source = SineWave::new(440.0) //! .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); +//! let handle = rodio::OsSinkBuilder::open_default_sink() +//! .expect("open default audio sink"); +//! let player = rodio::Player::connect_new(&handle.mixer()); +//! player.set_speed(2.0); +//! player.append(source); //! std::thread::sleep(std::time::Duration::from_secs(5)); //! ``` //! Notice the increase in pitch as the factor increases diff --git a/src/spatial_sink.rs b/src/spatial_player.rs similarity index 86% rename from src/spatial_sink.rs rename to src/spatial_player.rs index 8a0719c9..d1fe3cb2 100644 --- a/src/spatial_sink.rs +++ b/src/spatial_player.rs @@ -6,13 +6,13 @@ use dasp_sample::FromSample; use crate::mixer::Mixer; use crate::source::{SeekError, Spatial}; -use crate::{Float, Sink, Source}; +use crate::{Float, Player, Source}; /// A sink that allows changing the position of the source and the listeners /// ears while playing. The sources played are then transformed to give a simple /// spatial effect. See [`Spatial`] for details. -pub struct SpatialSink { - sink: Sink, +pub struct SpatialPlayer { + player: Player, positions: Arc>, } @@ -22,16 +22,16 @@ struct SoundPositions { right_ear: [f32; 3], } -impl SpatialSink { - /// Builds a new `SpatialSink`. +impl SpatialPlayer { + /// Builds a new `SpatialPlayer`. pub fn connect_new( mixer: &Mixer, emitter_position: [f32; 3], left_ear: [f32; 3], right_ear: [f32; 3], - ) -> SpatialSink { - SpatialSink { - sink: Sink::connect_new(mixer), + ) -> SpatialPlayer { + SpatialPlayer { + player: Player::connect_new(mixer), positions: Arc::new(Mutex::new(SoundPositions { emitter_position, left_ear, @@ -74,7 +74,7 @@ impl SpatialSink { let pos = positions.lock().unwrap(); i.set_positions(pos.emitter_position, pos.left_ear, pos.right_ear); }); - self.sink.append(source); + self.player.append(source); } // Gets the volume of the sound. @@ -83,7 +83,7 @@ impl SpatialSink { /// multiply each sample by this value. #[inline] pub fn volume(&self) -> Float { - self.sink.volume() + self.player.volume() } /// Changes the volume of the sound. @@ -92,7 +92,7 @@ impl SpatialSink { /// multiply each sample by this value. #[inline] pub fn set_volume(&self, value: Float) { - self.sink.set_volume(value); + self.player.set_volume(value); } /// Changes the play speed of the sound. Does not adjust the samples, only the playback speed. @@ -111,7 +111,7 @@ impl SpatialSink { /// See [`Speed`](crate::source::Speed) for details #[inline] pub fn speed(&self) -> f32 { - self.sink.speed() + self.player.speed() } /// Changes the speed of the sound. @@ -120,7 +120,7 @@ impl SpatialSink { /// change the play speed of the sound. #[inline] pub fn set_speed(&self, value: f32) { - self.sink.set_speed(value) + self.player.set_speed(value) } /// Resumes playback of a paused sound. @@ -128,62 +128,62 @@ impl SpatialSink { /// No effect if not paused. #[inline] pub fn play(&self) { - self.sink.play(); + self.player.play(); } - /// Pauses playback of this sink. + /// Pauses playback of this player. /// /// No effect if already paused. /// /// A paused sound can be resumed with `play()`. pub fn pause(&self) { - self.sink.pause(); + self.player.pause(); } /// Gets if a sound is paused /// /// Sounds can be paused and resumed using pause() and play(). This gets if a sound is paused. pub fn is_paused(&self) -> bool { - self.sink.is_paused() + self.player.is_paused() } - /// Removes all currently loaded `Source`s from the `SpatialSink` and pauses it. + /// Removes all currently loaded `Source`s from the `SpatialPlayer` and pauses it. /// - /// See `pause()` for information about pausing a `Sink`. + /// See `pause()` for information about pausing a `Player`. #[inline] pub fn clear(&self) { - self.sink.clear(); + self.player.clear(); } /// Stops the sink by emptying the queue. #[inline] pub fn stop(&self) { - self.sink.stop() + self.player.stop() } /// Destroys the sink without stopping the sounds that are still playing. #[inline] pub fn detach(self) { - self.sink.detach(); + self.player.detach(); } /// Sleeps the current thread until the sound ends. #[inline] pub fn sleep_until_end(&self) { - self.sink.sleep_until_end(); + self.player.sleep_until_end(); } /// Returns true if this sink has no more sounds to play. #[inline] pub fn empty(&self) -> bool { - self.sink.empty() + self.player.empty() } /// Returns the number of sounds currently in the queue. #[allow(clippy::len_without_is_empty)] #[inline] pub fn len(&self) -> usize { - self.sink.len() + self.player.len() } /// Attempts to seek to a given position in the current source. @@ -205,7 +205,7 @@ impl SpatialSink { /// When seeking beyond the end of a source this /// function might return an error if the duration of the source is not known. pub fn try_seek(&self, pos: Duration) -> Result<(), SeekError> { - self.sink.try_seek(pos) + self.player.try_seek(pos) } /// Returns the position of the sound that's being played. @@ -213,10 +213,10 @@ impl SpatialSink { /// This takes into account any speedup or delay applied. /// /// Example: if you apply a speedup of *2* to an mp3 decoder source and - /// [`get_pos()`](Sink::get_pos) returns *5s* then the position in the mp3 + /// [`get_pos()`](Player::get_pos) returns *5s* then the position in the mp3 /// recording is *10s* from its start. #[inline] pub fn get_pos(&self) -> Duration { - self.sink.get_pos() + self.player.get_pos() } } diff --git a/src/speakers.rs b/src/speakers.rs new file mode 100644 index 00000000..c74c7e10 --- /dev/null +++ b/src/speakers.rs @@ -0,0 +1,190 @@ +//! A speakers sink +//! +//! An audio *stream* originates at a [Source] and flows to a player. This is a +//! Sink that plays audio over the systems speakers or headphones through an +//! audio output device; +//! +//! # Basic Usage +//! +//! ```no_run +//! # use rodio::speakers::SpeakersBuilder; +//! # use rodio::{Source, source::SineWave}; +//! # use std::time::Duration; +//! let speakers = SpeakersBuilder::new() +//! .default_device()? +//! .default_config()? +//! .open_mixer_sink()?; +//! let mixer = speakers.mixer(); +//! +//! // Play a beep for 4 seconds +//! mixer.add(SineWave::new(440.).take_duration(Duration::from_secs(4))); +//! std::thread::sleep(Duration::from_secs(4)); +//! +//! # Ok::<(), Box>(()) +//! ``` +//! +//! # Use preferred parameters if supported +//! Attempt to set a specific channel count, sample rate and buffer size but +//! fall back to the default if the device does not support these +//! +//! ```no_run +//! use rodio::speakers::SpeakersBuilder; +//! use rodio::Source; +//! use std::time::Duration; +//! +//! # fn main() -> Result<(), Box> { +//! let mut builder = SpeakersBuilder::new() +//! .default_device()? +//! .default_config()? +//! .prefer_channel_counts([ +//! 1.try_into().expect("not zero"), +//! 2.try_into().expect("not zero"), +//! ]) +//! .prefer_sample_rates([ +//! 16_000.try_into().expect("not zero"), +//! 32_000.try_into().expect("not zero"), +//! ]) +//! .prefer_buffer_sizes(512..); +//! +//! let mixer = builder.open_mixer_sink()?; +//! # Ok(()) +//! # } +//! ``` +//! +//! # Configuration with Error Handling +//! Attempt to set a specific channel count but fall back to the default if +//! the device doesn't support it: +//! +//! ```no_run +//! use rodio::speakers::SpeakersBuilder; +//! use rodio::Source; +//! use std::time::Duration; +//! +//! # fn main() -> Result<(), Box> { +//! let mut builder = SpeakersBuilder::new() +//! .default_device()? +//! .default_config()?; +//! +//! // Try to set stereo recording (2 channels), but continue with default if unsupported +//! if let Ok(configured_builder) = builder.try_channels(2.try_into()?) { +//! builder = configured_builder; +//! } else { +//! println!("Stereo recording not supported, using default channel configuration"); +//! // builder remains unchanged with default configuration +//! } +//! +//! let speakers = builder.open_mixer_sink()?; +//! # Ok(()) +//! # } +//! ``` +//! +//! # Device Selection +//! +//! ```no_run +//! use rodio::speakers::{SpeakersBuilder, available_outputs}; +//! +//! # fn main() -> Result<(), Box> { +//! // List all available output devices +//! let outputs = available_outputs()?; +//! for (i, output) in outputs.iter().enumerate() { +//! println!("output {}: {}", i, output); +//! } +//! +//! // Use a specific device (e.g., the second one) +//! let speakers = SpeakersBuilder::new() +//! .device(outputs[1].clone())? +//! .default_config()? +//! .open_mixer_sink()?; +//! # Ok(()) +//! # } +//! ``` + +use core::fmt; + +use cpal::{ + traits::{DeviceTrait, HostTrait}, + Device, +}; + +use crate::{common::assert_error_traits, OsSinkError}; + +mod builder; +mod config; + +pub use builder::SpeakersBuilder; +pub use config::OutputConfig; + +struct Speakers; + +/// Error that can occur when we can not list the output devices +#[derive(Debug, thiserror::Error, Clone)] +#[error("Could not list output devices")] +pub struct ListError(#[source] cpal::DevicesError); +assert_error_traits! {ListError} + +/// An output device +#[derive(Clone)] +pub struct Output { + inner: cpal::Device, + default: bool, +} + +impl Output { + /// TODO doc comment also mirror to microphone api + pub fn is_default(&self) -> bool { + self.default + } + + pub(crate) fn into_inner(self) -> cpal::Device { + self.inner + } +} + +impl fmt::Debug for Output { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Device") + .field( + "inner", + &self + .inner + .description() + .map(|d| d.name().to_string()) + .unwrap_or("unknown".to_string()), + ) + .finish() + } +} + +impl fmt::Display for Output { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + self.inner + .description() + .map(|d| d.name().to_string()) + .unwrap_or("unknown".to_string()), + ) + } +} + +/// Returns a list of available output devices on the system. +pub fn available_outputs() -> Result, ListError> { + let host = cpal::default_host(); + let default = host.default_output_device().map(|d| d.id()); + let devices = host.output_devices().map_err(ListError)?.map(|dev| Output { + default: Some(dev.id()) == default, + inner: dev, + }); + Ok(devices.collect()) +} + +impl Speakers { + fn open( + device: Device, + config: OutputConfig, + error_callback: impl FnMut(cpal::StreamError) + Send + 'static, + ) -> Result { + crate::stream::MixerOsSink::open(&device, &config.into_cpal_config(), error_callback) + } +} diff --git a/src/speakers/builder.rs b/src/speakers/builder.rs new file mode 100644 index 00000000..f744eb30 --- /dev/null +++ b/src/speakers/builder.rs @@ -0,0 +1,635 @@ +use std::{fmt::Debug, marker::PhantomData}; + +use cpal::{ + traits::{DeviceTrait, HostTrait, StreamTrait}, + SupportedStreamConfigRange, +}; + +use crate::{ + common::assert_error_traits, + fixed_source::FixedSource, + speakers::{self, config::OutputConfig}, + ChannelCount, MixerOsSink, SampleRate, OsSinkError, +}; + +/// Error configuring or opening speakers output +#[allow(missing_docs)] +#[derive(Debug, thiserror::Error, Clone)] +pub enum Error { + /// No output device is available on the system. + #[error("There is no output device")] + NoDevice, + /// Failed to get the default output configuration for the device. + #[error("Could not get default output configuration for output device: '{device_name}'")] + DefaultOutputConfig { + #[source] + source: cpal::DefaultStreamConfigError, + device_name: String, + }, + /// Failed to get the supported output configurations for the device. + #[error("Could not get supported output configurations for output device: '{device_name}'")] + OutputConfigs { + #[source] + source: cpal::SupportedStreamConfigsError, + device_name: String, + }, + /// The requested output configuration is not supported by the device. + #[error("The output configuration is not supported by output device: '{device_name}'")] + UnsupportedByDevice { device_name: String }, +} +assert_error_traits! {Error} + +/// Generic on the `SpeakersBuilder` which is only present when a config has been set. +/// Methods needing a config are only available on SpeakersBuilder with this +/// Generic set. +pub struct DeviceIsSet; +/// Generic on the `SpeakersBuilder` which is only present when a device has been set. +/// Methods needing a device set are only available on SpeakersBuilder with this +/// Generic set. +pub struct ConfigIsSet; + +/// Generic on the `SpeakersBuilder` which indicates no config has been set. +/// Some methods are only available when this types counterpart: `ConfigIsSet` is present. +pub struct ConfigNotSet; +/// Generic on the `SpeakersBuilder` which indicates no device has been set. +/// Some methods are only available when this types counterpart: `DeviceIsSet` is present. +pub struct DeviceNotSet; + +/// Builder for configuring and opening an OS-Sink, usually a speaker or headphone. +#[must_use] +pub struct SpeakersBuilder +where + E: FnMut(cpal::StreamError) + Send + Clone + 'static, +{ + device: Option<(cpal::Device, Vec)>, + config: Option, + error_callback: E, + + device_set: PhantomData, + config_set: PhantomData, +} + +impl Debug for SpeakersBuilder +where + E: FnMut(cpal::StreamError) + Send + Clone + 'static, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SpeakersBuilder") + .field( + "device", + &self.device.as_ref().map(|d| { + d.0.description() + .map(|d| d.name().to_string()) + .unwrap_or("unknown".to_string()) + }), + ) + .field("config", &self.config) + .finish() + } +} + +impl Default for SpeakersBuilder { + fn default() -> Self { + Self { + device: None, + config: None, + error_callback: default_error_callback, + + device_set: PhantomData, + config_set: PhantomData, + } + } +} + +fn default_error_callback(err: cpal::StreamError) { + #[cfg(feature = "tracing")] + tracing::error!("audio stream error: {err}"); + #[cfg(not(feature = "tracing"))] + eprintln!("audio stream error: {err}"); +} + +impl SpeakersBuilder { + /// Creates a new speakers builder. + /// + /// # Example + /// ```no_run + /// let builder = rodio::speakers::SpeakersBuilder::new(); + /// ``` + pub fn new() -> SpeakersBuilder { + Self::default() + } +} + +impl SpeakersBuilder +where + E: FnMut(cpal::StreamError) + Send + Clone + 'static, +{ + /// Sets the output device to use. + /// + /// # Example + /// ```no_run + /// # use rodio::speakers::{SpeakersBuilder, available_outputs}; + /// let output = available_outputs()?.remove(2); + /// let builder = SpeakersBuilder::new().device(output)?; + /// # Ok::<(), Box>(()) + /// ``` + pub fn device( + &self, + device: super::Output, + ) -> Result, Error> { + let device = device.into_inner(); + let supported_configs = device + .supported_output_configs() + .map_err(|source| Error::OutputConfigs { + source, + device_name: device + .description() + .map(|d| d.name().to_string()) + .unwrap_or("unknown".to_string()), + })? + .collect(); + Ok(SpeakersBuilder { + device: Some((device, supported_configs)), + config: self.config, + error_callback: self.error_callback.clone(), + device_set: PhantomData, + config_set: PhantomData, + }) + } + + /// Uses the system's default output device. + /// + /// # Example + /// ```no_run + /// # use rodio::speakers::SpeakersBuilder; + /// let builder = SpeakersBuilder::new().default_device()?; + /// # Ok::<(), Box>(()) + /// ``` + pub fn default_device(&self) -> Result, Error> { + let default_device = cpal::default_host() + .default_output_device() + .ok_or(Error::NoDevice)?; + let supported_configs = default_device + .supported_output_configs() + .map_err(|source| Error::OutputConfigs { + source, + device_name: default_device + .description() + .map(|d| d.name().to_string()) + .unwrap_or("unknown".to_string()), + })? + .collect(); + Ok(SpeakersBuilder { + device: Some((default_device, supported_configs)), + config: self.config, + error_callback: self.error_callback.clone(), + device_set: PhantomData, + config_set: PhantomData, + }) + } +} + +impl SpeakersBuilder +where + E: FnMut(cpal::StreamError) + Send + Clone + 'static, +{ + /// Uses the device's default output configuration. + /// + /// # Example + /// ```no_run + /// # use rodio::speakers::SpeakersBuilder; + /// let builder = SpeakersBuilder::new() + /// .default_device()? + /// .default_config()?; + /// # Ok::<(), Box>(()) + /// ``` + pub fn default_config(&self) -> Result, Error> { + let device = &self.device.as_ref().expect("DeviceIsSet").0; + let default_config: OutputConfig = device + .default_output_config() + .map_err(|source| Error::DefaultOutputConfig { + source, + device_name: device + .description() + .map(|d| d.name().to_string()) + .unwrap_or("unknown".to_string()), + })? + .into(); + + // Lets try getting f32 output from the default config, as thats + // what rodio uses internally + let config = if self + .check_config(&default_config.with_f32_samples()) + .is_ok() + { + default_config.with_f32_samples() + } else { + default_config + }; + + Ok(SpeakersBuilder { + device: self.device.clone(), + config: Some(config), + error_callback: self.error_callback.clone(), + device_set: PhantomData, + config_set: PhantomData, + }) + } + + /// Sets a custom output configuration. + /// + /// # Example + /// ```no_run + /// # use rodio::speakers::{SpeakersBuilder, OutputConfig}; + /// # use std::num::NonZero; + /// let config = OutputConfig { + /// sample_rate: NonZero::new(44_100).expect("44100 is not zero"), + /// channel_count: NonZero::new(2).expect("2 is not zero"), + /// buffer_size: cpal::BufferSize::Fixed(42_000), + /// sample_format: cpal::SampleFormat::U16, + /// }; + /// let builder = SpeakersBuilder::new() + /// .default_device()? + /// .config(config)?; + /// # Ok::<(), Box>(()) + /// ``` + pub fn config( + &self, + config: OutputConfig, + ) -> Result, Error> { + self.check_config(&config)?; + + Ok(SpeakersBuilder { + device: self.device.clone(), + config: Some(config), + error_callback: self.error_callback.clone(), + device_set: PhantomData, + config_set: PhantomData, + }) + } + + fn check_config(&self, config: &OutputConfig) -> Result<(), Error> { + let (device, supported_configs) = self.device.as_ref().expect("DeviceIsSet"); + if !supported_configs + .iter() + .any(|range| config.supported_given(range)) + { + Err(Error::UnsupportedByDevice { + device_name: device + .description() + .map(|d| d.name().to_string()) + .unwrap_or("unknown".to_string()), + }) + } else { + Ok(()) + } + } +} + +impl SpeakersBuilder +where + E: FnMut(cpal::StreamError) + Send + Clone + 'static, +{ + /// Sets the sample rate for output. + /// + /// # Error + /// Returns an error if the requested sample rate combined with the + /// other parameters can not be supported. + /// + /// # Example + /// ```no_run + /// # use rodio::speakers::SpeakersBuilder; + /// let builder = SpeakersBuilder::new() + /// .default_device()? + /// .default_config()? + /// .try_sample_rate(44_100.try_into()?)?; + /// # Ok::<(), Box>(()) + /// ``` + pub fn try_sample_rate( + &self, + sample_rate: SampleRate, + ) -> Result, Error> { + let mut new_config = self.config.expect("ConfigIsSet"); + new_config.sample_rate = sample_rate; + self.check_config(&new_config)?; + + Ok(SpeakersBuilder { + device: self.device.clone(), + config: Some(new_config), + error_callback: self.error_callback.clone(), + device_set: PhantomData, + config_set: PhantomData, + }) + } + + /// Try multiple sample rates, fall back to the default it non match. The + /// sample rates are in order of preference. If the first can be supported + /// the second will never be tried. + /// + /// # Example + /// ```no_run + /// # use rodio::speakers::SpeakersBuilder; + /// let builder = SpeakersBuilder::new() + /// .default_device()? + /// .default_config()? + /// // 16k or its double with can trivially be resampled to 16k + /// .prefer_sample_rates([ + /// 16_000.try_into().expect("not zero"), + /// 32_000.try_into().expect("not_zero"), + /// ]); + /// # Ok::<(), Box>(()) + /// ``` + pub fn prefer_sample_rates( + &self, + sample_rates: impl IntoIterator, + ) -> SpeakersBuilder { + self.set_preferred_if_supported(sample_rates, |config, sample_rate| { + config.sample_rate = sample_rate + }) + } + + fn set_preferred_if_supported( + &self, + options: impl IntoIterator, + setter: impl Fn(&mut OutputConfig, T), + ) -> SpeakersBuilder { + let mut config = self.config.expect("ConfigIsSet"); + let mut final_config = config; + + for option in options.into_iter() { + setter(&mut config, option); + if self.check_config(&config).is_ok() { + final_config = config; + break; + } + } + + SpeakersBuilder { + device: self.device.clone(), + config: Some(final_config), + error_callback: self.error_callback.clone(), + device_set: PhantomData, + config_set: PhantomData, + } + } + + /// Sets the number of output channels. + /// + /// # Example + /// ```no_run + /// # use rodio::speakers::SpeakersBuilder; + /// let builder = SpeakersBuilder::new() + /// .default_device()? + /// .default_config()? + /// .try_channels(2.try_into()?)?; + /// # Ok::<(), Box>(()) + /// ``` + pub fn try_channels( + &self, + channel_count: ChannelCount, + ) -> Result, Error> { + let mut new_config = self.config.expect("ConfigIsSet"); + new_config.channel_count = channel_count; + self.check_config(&new_config)?; + + Ok(SpeakersBuilder { + device: self.device.clone(), + config: Some(new_config), + error_callback: self.error_callback.clone(), + device_set: PhantomData, + config_set: PhantomData, + }) + } + + /// Try multiple channel counts, fall back to the default it non match. The + /// channel counts are in order of preference. If the first can be supported + /// the second will never be tried. + /// + /// # Example + /// ```no_run + /// # use rodio::speakers::SpeakersBuilder; + /// let builder = SpeakersBuilder::new() + /// .default_device()? + /// .default_config()? + /// // We want mono, if thats not possible give + /// // us the lowest channel count + /// .prefer_channel_counts([ + /// 1.try_into().expect("not zero"), + /// 2.try_into().expect("not_zero"), + /// 3.try_into().expect("not_zero"), + /// ]); + /// # Ok::<(), Box>(()) + /// ``` + pub fn prefer_channel_counts( + &self, + channel_counts: impl IntoIterator, + ) -> SpeakersBuilder { + self.set_preferred_if_supported(channel_counts, |config, count| { + config.channel_count = count + }) + } + + /// Sets the buffer size for the output. + /// + /// This has no impact on latency, though a too small buffer can lead to audio + /// artifacts if your program can not get samples out of the buffer before they + /// get overridden again. + /// + /// Normally the default output config will have this set up correctly. + /// + /// # Example + /// ```no_run + /// # use rodio::speakers::SpeakersBuilder; + /// let builder = SpeakersBuilder::new() + /// .default_device()? + /// .default_config()? + /// .try_buffer_size(4096)?; + /// # Ok::<(), Box>(()) + /// ``` + pub fn try_buffer_size( + &self, + buffer_size: u32, + ) -> Result, Error> { + let mut new_config = self.config.expect("ConfigIsSet"); + new_config.buffer_size = cpal::BufferSize::Fixed(buffer_size); + self.check_config(&new_config)?; + + Ok(SpeakersBuilder { + device: self.device.clone(), + config: Some(new_config), + error_callback: self.error_callback.clone(), + device_set: PhantomData, + config_set: PhantomData, + }) + } + + /// See the docs of [`try_buffer_size`](SpeakersBuilder::try_buffer_size) + /// for more. + /// + /// Try multiple buffer sizes, fall back to the default it non match. The + /// buffer sizes are in order of preference. If the first can be supported + /// the second will never be tried. + /// + /// # Note + /// We will not try buffer sizes larger then 100_000 to prevent this + /// from hanging too long on open ranges. + /// + /// # Example + /// ```no_run + /// # use rodio::speakers::SpeakersBuilder; + /// let builder = SpeakersBuilder::new() + /// .default_device()? + /// .default_config()? + /// .prefer_buffer_sizes([ + /// 2048.try_into().expect("not zero"), + /// 4096.try_into().expect("not_zero"), + /// ]); + /// # Ok::<(), Box>(()) + /// ``` + /// + /// Get the smallest buffer size larger then 512. + /// ```no_run + /// # use rodio::speakers::SpeakersBuilder; + /// let builder = SpeakersBuilder::new() + /// .default_device()? + /// .default_config()? + /// // We need a minimum buffer of 4096 + /// // or we get glitches. + /// .prefer_buffer_sizes(4096..); + /// # Ok::<(), Box>(()) + /// ``` + pub fn prefer_buffer_sizes( + &self, + buffer_sizes: impl IntoIterator, + ) -> SpeakersBuilder { + let buffer_sizes = buffer_sizes.into_iter().take_while(|size| *size < 100_000); + + self.set_preferred_if_supported(buffer_sizes, |config, size| { + config.buffer_size = cpal::BufferSize::Fixed(size) + }) + } +} + +impl SpeakersBuilder +where + E: FnMut(cpal::StreamError) + Send + Clone + 'static, +{ + /// Returns the current output configuration. + /// + /// # Example + /// ```no_run + /// # use rodio::speakers::SpeakersBuilder; + /// let builder = SpeakersBuilder::new() + /// .default_device()? + /// .default_config()?; + /// let config = builder.get_config(); + /// println!("Sample rate: {}", config.sample_rate.get()); + /// println!("Channel count: {}", config.channel_count.get()); + /// # Ok::<(), Box>(()) + /// ``` + pub fn get_config(&self) -> OutputConfig { + self.config.expect("ConfigIsSet") + } +} + +impl SpeakersBuilder +where + E: FnMut(cpal::StreamError) + Send + Clone + 'static, +{ + /// Opens the OS-Sink and provide a mixer for playing sources on it. + /// + /// # Example + /// ```no_run + /// # use rodio::speakers::SpeakersBuilder; + /// # use rodio::{Source, source::SineWave}; + /// # use std::time::Duration; + /// let speakers = SpeakersBuilder::new() + /// .default_device()? + /// .default_config()? + /// .open_mixer_sink()?; + /// let mixer = speakers.mixer(); + /// mixer.add(SineWave::new(440.).take_duration(Duration::from_secs(4))); + /// std::thread::sleep(Duration::from_secs(4)); + /// + /// # Ok::<(), Box>(()) + /// ``` + pub fn open_mixer_sink(&self) -> Result { + speakers::Speakers::open( + self.device.as_ref().expect("DeviceIsSet").0.clone(), + *self.config.as_ref().expect("ConfigIsSet"), + self.error_callback.clone(), + ) + } + + /// TODO + pub fn open_queue_sink(&self) -> Result { + todo!() + } + + /// TODO + pub fn play( + self, + mut source: impl FixedSource + Send + 'static, + ) -> Result { + use cpal::Sample as _; + + let config = self.config.expect("ConfigIsSet"); + let device = self.device.expect("DeviceIsSet").0; + let cpal_config1 = config.into_cpal_config(); + let cpal_config2 = (&cpal_config1).into(); + + macro_rules! build_output_streams { + ($($sample_format:tt, $generic:ty);+) => { + match config.sample_format { + $( + cpal::SampleFormat::$sample_format => device.build_output_stream::<$generic, _, _>( + &cpal_config2, + move |data, _| { + data.iter_mut().for_each(|d| { + *d = source + .next() + .map(cpal::Sample::from_sample) + .unwrap_or(<$generic>::EQUILIBRIUM) + }) + }, + self.error_callback, + None, + ), + )+ + _ => return Err(OsSinkError::UnsupportedSampleFormat), + } + }; + } + + let result = build_output_streams!( + F32, f32; + F64, f64; + I8, i8; + I16, i16; + I24, cpal::I24; + I32, i32; + I64, i64; + U8, u8; + U16, u16; + U24, cpal::U24; + U32, u32; + U64, u64 + ); + + result + .map_err(OsSinkError::BuildError) + .map(|stream| { + stream + .play() + .map_err(OsSinkError::PlayError) + .map(|()| stream) + })? + .map(SinkHandle) + } +} + +// TODO +pub struct QueueSink; + +// TODO +pub struct SinkHandle(cpal::Stream); diff --git a/src/speakers/config.rs b/src/speakers/config.rs new file mode 100644 index 00000000..947532bc --- /dev/null +++ b/src/speakers/config.rs @@ -0,0 +1,83 @@ +use std::num::NonZero; + +use crate::{math::nz, stream::OsSinkConfig, ChannelCount, SampleRate}; + +/// Describes the input stream's configuration +#[derive(Copy, Clone, Debug)] +pub struct OutputConfig { + /// The number of channels + pub channel_count: ChannelCount, + /// The sample rate the audio card will be playing back at + pub sample_rate: SampleRate, + /// The buffersize, see a thorough explanation in SpeakerBuilder::with_buffer_size + pub buffer_size: cpal::BufferSize, + /// The sample format used by the audio card. + /// Note we will always convert to this from f32 + pub sample_format: cpal::SampleFormat, +} +impl OutputConfig { + pub(crate) fn supported_given(&self, supported: &cpal::SupportedStreamConfigRange) -> bool { + let buffer_ok = match (self.buffer_size, supported.buffer_size()) { + (cpal::BufferSize::Default, _) | (_, cpal::SupportedBufferSize::Unknown) => true, + ( + cpal::BufferSize::Fixed(n_frames), + cpal::SupportedBufferSize::Range { + min: min_samples, + max: max_samples, + }, + ) => { + let n_samples = n_frames * self.channel_count.get() as u32; + (*min_samples..*max_samples).contains(&n_samples) + } + }; + + buffer_ok + && self.channel_count.get() == supported.channels() + && self.sample_format == supported.sample_format() + && self.sample_rate.get() <= supported.max_sample_rate() + && self.sample_rate.get() >= supported.min_sample_rate() + } + + pub(crate) fn with_f32_samples(&self) -> Self { + let mut this = *self; + this.sample_format = cpal::SampleFormat::F32; + this + } + + pub(crate) fn into_cpal_config(self) -> crate::stream::OsSinkConfig { + OsSinkConfig { + channel_count: self.channel_count, + sample_rate: self.sample_rate, + buffer_size: self.buffer_size, + sample_format: self.sample_format, + } + } +} + +impl From for OutputConfig { + fn from(value: cpal::SupportedStreamConfig) -> Self { + let buffer_size = match value.buffer_size() { + cpal::SupportedBufferSize::Range { .. } => cpal::BufferSize::Default, + cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default, + }; + Self { + channel_count: NonZero::new(value.channels()) + .expect("A supported config never has 0 channels"), + sample_rate: NonZero::new(value.sample_rate()) + .expect("A supported config produces samples"), + buffer_size, + sample_format: value.sample_format(), + } + } +} + +impl Default for OutputConfig { + fn default() -> Self { + Self { + channel_count: nz!(1), + sample_rate: nz!(44_100), + buffer_size: cpal::BufferSize::Default, + sample_format: cpal::SampleFormat::F32, + } + } +} diff --git a/src/stream.rs b/src/stream.rs index e7bac2f6..eab18e7d 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -1,14 +1,14 @@ //! Output audio via the OS via mixers or play directly //! //! This module provides a builder that's used to configure and open audio output. Once -//! opened sources can be mixed into the output via `OutputStream::mixer`. +//! opened sources can be mixed into the output via `OsSink::mixer`. //! //! There is also a convenience function `play` for using that output mixer to //! play a single sound. use crate::common::{assert_error_traits, ChannelCount, SampleRate}; use crate::math::nz; use crate::mixer::{mixer, Mixer}; -use crate::sink::Sink; +use crate::player::Player; use crate::{decoder, Source}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::{BufferSize, Sample, SampleFormat, StreamConfig, I24}; @@ -22,80 +22,80 @@ const HZ_44100: SampleRate = nz!(44_100); /// `cpal::Stream` container. Use `mixer()` method to control output. /// ///
When dropped playback will end, and the associated -/// output stream will be disposed
+/// OS-Sink will be disposed /// /// # Note /// On drop this will print a message to stderr or emit a log msg when tracing is /// enabled. Though we recommend you do not you can disable that print/log with: -/// [`OutputStream::log_on_drop(false)`](OutputStream::log_on_drop). -/// If the `OutputStream` is dropped because the program is panicking we do not print +/// [`OsSink::log_on_drop(false)`](OsSink::log_on_drop). +/// If the `OsSink` is dropped because the program is panicking we do not print /// or log anything. /// /// # Example /// ```no_run -/// # use rodio::OutputStreamBuilder; +/// # use rodio::OsSinkBuilder; /// # fn main() -> Result<(), Box> { -/// let mut stream_handle = OutputStreamBuilder::open_default_stream()?; -/// stream_handle.log_on_drop(false); // Not recommended during development -/// println!("Output config: {:?}", stream_handle.config()); -/// let mixer = stream_handle.mixer(); +/// let mut handle = OsSinkBuilder::open_default_sink()?; +/// handle.log_on_drop(false); // Not recommended during development +/// println!("Output config: {:?}", handle.config()); +/// let mixer = handle.mixer(); /// # Ok(()) /// # } /// ``` -pub struct OutputStream { - config: OutputStreamConfig, +pub struct MixerOsSink { + config: OsSinkConfig, mixer: Mixer, log_on_drop: bool, _stream: cpal::Stream, } -impl OutputStream { - /// Access the output stream's mixer. +impl MixerOsSink { + /// Access the sink's mixer. pub fn mixer(&self) -> &Mixer { &self.mixer } - /// Access the output stream's config. - pub fn config(&self) -> &OutputStreamConfig { + /// Access the sink's config. + pub fn config(&self) -> &OsSinkConfig { &self.config } - /// When [`OutputStream`] is dropped a message is logged to stderr or + /// When [`OS-Sink`] is dropped a message is logged to stderr or /// emitted through tracing if the tracing feature is enabled. pub fn log_on_drop(&mut self, enabled: bool) { self.log_on_drop = enabled; } } -impl Drop for OutputStream { +impl Drop for MixerOsSink { fn drop(&mut self) { if self.log_on_drop && !std::thread::panicking() { #[cfg(feature = "tracing")] - tracing::debug!("Dropping OutputStream, audio playing through this stream will stop"); + tracing::debug!("Dropping OsSink, audio playing through this sink will stop"); #[cfg(not(feature = "tracing"))] - eprintln!("Dropping OutputStream, audio playing through this stream will stop, to prevent this message from appearing use tracing or call `.log_on_drop(false)` on this OutputStream") + eprintln!("Dropping OsSink, audio playing through this sink will stop, to prevent this message from appearing use tracing or call `.log_on_drop(false)` on this OsSink") } } } -impl fmt::Debug for OutputStream { +impl fmt::Debug for MixerOsSink { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("OutputStream") + f.debug_struct("MixerOsSink") .field("config", &self.config) .finish_non_exhaustive() } } -/// Describes the output stream's configuration +/// Describes the OS-Sink's configuration #[derive(Copy, Clone, Debug)] -pub struct OutputStreamConfig { - channel_count: ChannelCount, - sample_rate: SampleRate, - buffer_size: BufferSize, - sample_format: SampleFormat, +pub struct OsSinkConfig { + pub(crate) channel_count: ChannelCount, + pub(crate) sample_rate: SampleRate, + pub(crate) buffer_size: BufferSize, + pub(crate) sample_format: SampleFormat, } -impl Default for OutputStreamConfig { +impl Default for OsSinkConfig { fn default() -> Self { Self { channel_count: nz!(2), @@ -106,29 +106,29 @@ impl Default for OutputStreamConfig { } } -impl OutputStreamConfig { - /// Access the output stream config's channel count. +impl OsSinkConfig { + /// Access the OS-Sink config's channel count. pub fn channel_count(&self) -> ChannelCount { self.channel_count } - /// Access the output stream config's sample rate. + /// Access the OS-Sink config's sample rate. pub fn sample_rate(&self) -> SampleRate { self.sample_rate } - /// Access the output stream config's buffer size. + /// Access the OS-Sink config's buffer size. pub fn buffer_size(&self) -> &BufferSize { &self.buffer_size } - /// Access the output stream config's sample format. + /// Access the OS-Sink config's sample format. pub fn sample_format(&self) -> SampleFormat { self.sample_format } } -impl core::fmt::Debug for OutputStreamBuilder { +impl core::fmt::Debug for OsSinkBuilder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let device = if let Some(device) = &self.device { "Some(".to_owned() @@ -141,7 +141,7 @@ impl core::fmt::Debug for OutputStreamBuilder { "None".to_owned() }; - f.debug_struct("OutputStreamBuilder") + f.debug_struct("OsSinkBuilder") .field("device", &device) .field("config", &self.config) .finish() @@ -155,56 +155,56 @@ fn default_error_callback(err: cpal::StreamError) { eprintln!("audio stream error: {err}"); } -/// Convenience builder for audio output stream. +/// Convenience builder for audio OS-player. /// It provides methods to configure several parameters of the audio output and opening default /// device. See examples for use-cases. /// -///
When the OutputStream is dropped playback will end, and the associated -/// output stream will be disposed
-pub struct OutputStreamBuilder +///
When the OsSink is dropped playback will end, and the associated +/// OS-Sink will be disposed
+pub struct OsSinkBuilder where E: FnMut(cpal::StreamError) + Send + 'static, { device: Option, - config: OutputStreamConfig, + config: OsSinkConfig, error_callback: E, } -impl Default for OutputStreamBuilder { +impl Default for OsSinkBuilder { fn default() -> Self { Self { device: None, - config: OutputStreamConfig::default(), + config: OsSinkConfig::default(), error_callback: default_error_callback, } } } -impl OutputStreamBuilder { +impl OsSinkBuilder { /// Sets output device and its default parameters. - pub fn from_device(device: cpal::Device) -> Result { + pub fn from_device(device: cpal::Device) -> Result { let default_config = device .default_output_config() - .map_err(StreamError::DefaultStreamConfigError)?; + .map_err(OsSinkError::DefaultSinkConfigError)?; Ok(Self::default() .with_device(device) .with_supported_config(&default_config)) } - /// Sets default output stream parameters for default output audio device. - pub fn from_default_device() -> Result { + /// Sets default OS-Sink parameters for default output audio device. + pub fn from_default_device() -> Result { let default_device = cpal::default_host() .default_output_device() - .ok_or(StreamError::NoDevice)?; + .ok_or(OsSinkError::NoDevice)?; Self::from_device(default_device) } - /// 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 + /// Try to open a new OS-Sink for the default output device with its default configuration. + /// Failing that attempt to open OS-Sink 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 { + pub fn open_default_sink() -> Result { Self::from_default_device() .and_then(|x| x.open_stream()) .or_else(|original_err| { @@ -221,7 +221,7 @@ impl OutputStreamBuilder { devices .find_map(|d| { Self::from_device(d) - .and_then(|x| x.open_stream_or_fallback()) + .and_then(|x| x.open_sink_or_fallback()) .ok() }) .ok_or(original_err) @@ -229,27 +229,27 @@ impl OutputStreamBuilder { } } -impl OutputStreamBuilder +impl OsSinkBuilder where E: FnMut(cpal::StreamError) + Send + 'static, { /// 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 { + pub fn with_device(mut self, device: cpal::Device) -> OsSinkBuilder { self.device = Some(device); self } - /// Sets number of output stream's channels. - pub fn with_channels(mut self, channel_count: ChannelCount) -> OutputStreamBuilder { + /// Sets number of OS-Sink's channels. + pub fn with_channels(mut self, channel_count: ChannelCount) -> OsSinkBuilder { assert!(channel_count.get() > 0); self.config.channel_count = channel_count; self } - /// Sets output stream's sample rate. - pub fn with_sample_rate(mut self, sample_rate: SampleRate) -> OutputStreamBuilder { + /// Sets OS-Sink's sample rate. + pub fn with_sample_rate(mut self, sample_rate: SampleRate) -> OsSinkBuilder { self.config.sample_rate = sample_rate; self } @@ -292,13 +292,13 @@ where /// - Low-latency (audio production, live monitoring): 512-1024 /// - General use (games, media playback): 1024-2048 /// - Stability-focused (background music, non-interactive): 2048-4096 - pub fn with_buffer_size(mut self, buffer_size: cpal::BufferSize) -> OutputStreamBuilder { + pub fn with_buffer_size(mut self, buffer_size: cpal::BufferSize) -> OsSinkBuilder { 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 { + pub fn with_sample_format(mut self, sample_format: SampleFormat) -> OsSinkBuilder { self.config.sample_format = sample_format; self } @@ -308,8 +308,8 @@ where pub fn with_supported_config( mut self, config: &cpal::SupportedStreamConfig, - ) -> OutputStreamBuilder { - self.config = OutputStreamConfig { + ) -> OsSinkBuilder { + self.config = OsSinkConfig { channel_count: NonZero::new(config.channels()) .expect("no valid cpal config has zero channels"), sample_rate: NonZero::new(config.sample_rate()) @@ -320,9 +320,9 @@ where 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 { + /// Set all OS-Sink parameters at once from CPAL stream config. + pub fn with_config(mut self, config: &cpal::StreamConfig) -> OsSinkBuilder { + self.config = OsSinkConfig { channel_count: NonZero::new(config.channels) .expect("no valid cpal config has zero channels"), sample_rate: NonZero::new(config.sample_rate) @@ -334,38 +334,38 @@ where } /// Set a callback that will be called when an error occurs with the stream - pub fn with_error_callback(self, callback: F) -> OutputStreamBuilder + pub fn with_error_callback(self, callback: F) -> OsSinkBuilder where F: FnMut(cpal::StreamError) + Send + 'static, { - OutputStreamBuilder { + OsSinkBuilder { device: self.device, config: self.config, error_callback: callback, } } - /// Open output stream using parameters configured so far. - pub fn open_stream(self) -> Result { + /// Open OS-Sink using parameters configured so far. + pub fn open_stream(self) -> Result { let device = self.device.as_ref().expect("No output device specified"); - OutputStream::open(device, &self.config, self.error_callback) + MixerOsSink::open(device, &self.config, self.error_callback) } - /// Try opening a new output stream with the builder's current stream configuration. + /// Try opening a new OS-Sink 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 + pub fn open_sink_or_fallback(&self) -> Result where E: Clone, { let device = self.device.as_ref().expect("No output device specified"); let error_callback = &self.error_callback; - OutputStream::open(device, &self.config, error_callback.clone()).or_else(|err| { + MixerOsSink::open(device, &self.config, error_callback.clone()).or_else(|err| { for supported_config in supported_output_configs(device)? { - if let Ok(handle) = OutputStreamBuilder::default() + if let Ok(handle) = OsSinkBuilder::default() .with_device(device.clone()) .with_supported_config(&supported_config) .with_error_callback(error_callback.clone()) @@ -380,19 +380,19 @@ where } /// A convenience function. Plays a sound once. -/// Returns a `Sink` that can be used to control the sound. -pub fn play(mixer: &Mixer, input: R) -> Result +/// Returns a `Player` that can be used to control the sound. +pub fn play(mixer: &Mixer, input: R) -> Result where R: Read + Seek + Send + Sync + 'static, { let input = decoder::Decoder::new(input)?; - let sink = Sink::connect_new(mixer); - sink.append(input); - Ok(sink) + let player = Player::connect_new(mixer); + player.append(input); + Ok(player) } -impl From<&OutputStreamConfig> for StreamConfig { - fn from(config: &OutputStreamConfig) -> Self { +impl From<&OsSinkConfig> for StreamConfig { + fn from(config: &OsSinkConfig) -> Self { cpal::StreamConfig { channels: config.channel_count.get() as cpal::ChannelCount, sample_rate: config.sample_rate.get(), @@ -422,22 +422,22 @@ assert_error_traits!(PlayError); /// Errors that might occur when interfacing with audio output. #[derive(Debug, thiserror::Error)] -pub enum StreamError { - /// Could not start playing the stream, see [cpal::PlayStreamError] for +pub enum OsSinkError { + /// Could not start playing the sink, see [cpal::PlayStreamError] for /// details. #[error("Could not start playing the stream")] - PlayStreamError(#[source] cpal::PlayStreamError), + PlayError(#[source] cpal::PlayStreamError), /// Failed to get the stream config for the given device. See /// [cpal::DefaultStreamConfigError] for details. - #[error("Failed to get the stream config for the given device")] - DefaultStreamConfigError(#[source] cpal::DefaultStreamConfigError), - /// Error opening stream with OS. See [cpal::BuildStreamError] for details. + #[error("Failed to get the config for the given device")] + DefaultSinkConfigError(#[source] cpal::DefaultStreamConfigError), + /// Error opening sink with OS. See [cpal::BuildStreamError] for details. #[error("Error opening the stream with the OS")] - BuildStreamError(#[source] cpal::BuildStreamError), - /// Could not list supported stream configs for the device. Maybe it + BuildError(#[source] cpal::BuildStreamError), + /// Could not list supported configs for the device. Maybe it /// disconnected. For details see: [cpal::SupportedStreamConfigsError]. - #[error("Could not list supported stream configs for the device. Maybe its disconnected?")] - SupportedStreamConfigsError(#[source] cpal::SupportedStreamConfigsError), + #[error("Could not list supported configs for the device. Maybe its disconnected?")] + SupportedConfigsError(#[source] cpal::SupportedStreamConfigsError), /// Could not find any output device #[error("Could not find any output device")] NoDevice, @@ -447,25 +447,25 @@ pub enum StreamError { UnsupportedSampleFormat, } -impl OutputStream { - fn validate_config(config: &OutputStreamConfig) { +impl MixerOsSink { + fn validate_config(config: &OsSinkConfig) { if let BufferSize::Fixed(sz) = config.buffer_size { assert!(sz > 0, "fixed buffer size must be greater than zero"); } } - fn open( + pub(crate) fn open( device: &cpal::Device, - config: &OutputStreamConfig, + config: &OsSinkConfig, error_callback: E, - ) -> Result + ) -> Result where E: FnMut(cpal::StreamError) + Send + 'static, { Self::validate_config(config); let (controller, source) = mixer(config.channel_count, config.sample_rate); Self::init_stream(device, config, source, error_callback).and_then(|stream| { - stream.play().map_err(StreamError::PlayStreamError)?; + stream.play().map_err(OsSinkError::PlayError)?; Ok(Self { _stream: stream, mixer: controller, @@ -477,10 +477,10 @@ impl OutputStream { fn init_stream( device: &cpal::Device, - config: &OutputStreamConfig, + config: &OsSinkConfig, mut samples: S, error_callback: E, - ) -> Result + ) -> Result where S: Source + Send + 'static, E: FnMut(cpal::StreamError) + Send + 'static, @@ -505,7 +505,7 @@ impl OutputStream { None, ), )+ - _ => return Err(StreamError::UnsupportedSampleFormat), + _ => return Err(OsSinkError::UnsupportedSampleFormat), } }; } @@ -525,17 +525,17 @@ impl OutputStream { U64, u64 ); - result.map_err(StreamError::BuildStreamError) + result.map_err(OsSinkError::BuildError) } } /// Return all formats supported by the device. pub fn supported_output_configs( device: &cpal::Device, -) -> Result, StreamError> { +) -> Result, OsSinkError> { let mut supported: Vec<_> = device .supported_output_configs() - .map_err(StreamError::SupportedStreamConfigsError)? + .map_err(OsSinkError::SupportedConfigsError)? .collect(); supported.sort_by(|a, b| b.cmp_default_heuristics(a)); diff --git a/src/wav_output.rs b/src/wav_output.rs index 981be50d..f9234169 100644 --- a/src/wav_output.rs +++ b/src/wav_output.rs @@ -24,7 +24,7 @@ assert_error_traits!(ToWavError); /// Saves Source's output into a wav file. The output samples format is 32-bit /// float. This function is intended primarily for testing and diagnostics. It can be used to see -/// the output without opening output stream to a real audio device. +/// the output without opening OS-Sink to a real audio device. /// /// If the file already exists it will be overwritten. /// @@ -42,7 +42,7 @@ pub fn wav_to_file( /// Saves Source's output into a writer. The output samples format is 32-bit float. This function /// is intended primarily for testing and diagnostics. It can be used to see the output without -/// opening output stream to a real audio device. +/// opening an OS-Sink to a real audio device. /// /// # Example /// ```rust