Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/fixed_source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Sources of sound and various filters which never change sample rate or
//! channel count.
use std::time::Duration;

use crate::{ChannelCount, Sample, SampleRate};

/// Similar to `Source`, something that can produce interleaved samples for a
/// fixed amount of channels at a fixed sample rate. Those parameters never
/// change.
pub trait FixedSource: Iterator<Item = Sample> {
/// May NEVER return something else once its returned a value
fn channels(&self) -> ChannelCount;
/// May NEVER return something else once its returned a value
fn sample_rate(&self) -> SampleRate;
/// Returns the total duration of this source, if known.
///
/// `None` indicates at the same time "infinite" or "unknown".
fn total_duration(&self) -> Option<Duration>;
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ mod wav_output;
pub mod buffer;
pub mod conversions;
pub mod decoder;
#[cfg(feature = "experimental")]
pub mod fixed_source;
pub mod math;
#[cfg(feature = "recording")]
/// Microphone input support for audio recording.
Expand All @@ -201,6 +203,8 @@ pub mod static_buffer;

pub use crate::common::{BitDepth, ChannelCount, Float, Sample, SampleRate};
pub use crate::decoder::Decoder;
#[cfg(feature = "experimental")]
pub use crate::fixed_source::FixedSource;
pub use crate::player::Player;
pub use crate::source::Source;
pub use crate::spatial_player::SpatialPlayer;
Expand Down
21 changes: 3 additions & 18 deletions src/speakers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,21 +101,16 @@

use core::fmt;

use cpal::{
traits::{DeviceTrait, HostTrait},
Device,
};
use cpal::traits::{DeviceTrait, HostTrait};

use crate::{common::assert_error_traits, DeviceSinkError};
use crate::common::assert_error_traits;

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")]
Expand All @@ -130,7 +125,7 @@ pub struct Output {
}

impl Output {
/// TODO doc comment also mirror to microphone api
/// Whether this output is the default sound output for the OS
pub fn is_default(&self) -> bool {
self.default
}
Expand Down Expand Up @@ -176,13 +171,3 @@ pub fn available_outputs() -> Result<Vec<Output>, ListError> {
});
Ok(devices.collect())
}

impl Speakers {
fn open(
device: Device,
config: OutputConfig,
error_callback: impl FnMut(cpal::StreamError) + Send + 'static,
) -> Result<crate::stream::MixerDeviceSink, DeviceSinkError> {
crate::stream::MixerDeviceSink::open(&device, &config.into_cpal_config(), error_callback)
}
}
119 changes: 110 additions & 9 deletions src/speakers/builder.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use std::{fmt::Debug, marker::PhantomData};

use cpal::{
traits::{DeviceTrait, HostTrait},
traits::{DeviceTrait, HostTrait, StreamTrait},
SupportedStreamConfigRange,
};

use crate::{
common::assert_error_traits,
speakers::{self, config::OutputConfig},
ChannelCount, MixerDeviceSink, SampleRate,
common::assert_error_traits, speakers::config::OutputConfig, ChannelCount, DeviceSinkError,
FixedSource, MixerDeviceSink, SampleRate,
};

/// Error configuring or opening speakers output
Expand Down Expand Up @@ -546,10 +545,112 @@ where
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn open_mixer(&self) -> Result<MixerDeviceSink, crate::DeviceSinkError> {
speakers::Speakers::open(
self.device.as_ref().expect("DeviceIsSet").0.clone(),
*self.config.as_ref().expect("ConfigIsSet"),
self.error_callback.clone(),
)
let device = self.device.as_ref().expect("DeviceIsSet").0.clone();
let config = *self.config.as_ref().expect("ConfigIsSet");
let error_callback = self.error_callback.clone();
crate::stream::MixerDeviceSink::open(&device, &config.into_cpal_config(), error_callback)
}

// TODO
// pub fn open_queue() -> Result<QueueSink, DeviceSinkError> {
// todo!()
// }

/// Open the device with the current configuration and play a single
/// `FixedSource` on it.
pub fn play(
self,
mut source: impl FixedSource + Send + 'static,
) -> Result<SinkHandle, PlayError> {
use cpal::Sample as _;

let config = self.config.expect("ConfigIsSet");
let device = self.device.expect("DeviceIsSet").0;

if config.channel_count != source.channels() {
return Err(PlayError::WrongChannelCount {
sink: config.channel_count,
fixed_source: source.channels(),
});
}
if config.sample_rate != source.sample_rate() {
return Err(PlayError::WrongSampleRate {
sink: config.sample_rate,
fixed_source: source.sample_rate(),
});
}

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(DeviceSinkError::UnsupportedSampleFormat.into()),
}
};
}

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
);

let stream = result.map_err(DeviceSinkError::BuildError)?;
stream.play().map_err(DeviceSinkError::PlayError)?;

Ok(SinkHandle { _stream: stream })
}
}

// TODO cant introduce till we have introduced the other fixed source parts
// pub struct QueueSink;

/// A sink handle. When this is dropped anything playing through this Sink will
/// stop playing.
pub struct SinkHandle {
_stream: cpal::Stream,
}

#[derive(Debug, thiserror::Error)]
pub enum PlayError {
#[error("DeviceSink channel count ({sink}) does not match the source channel count ({fixed_source})")]
WrongChannelCount {
sink: ChannelCount,
fixed_source: ChannelCount,
},
#[error(
"DeviceSink sample rate ({sink}) does not match the source sample rate ({fixed_source})"
)]
WrongSampleRate {
sink: SampleRate,
fixed_source: SampleRate,
},
#[error(transparent)]
DeviceSink(#[from] crate::DeviceSinkError),
}