diff --git a/CHANGELOG.md b/CHANGELOG.md index afbe164d5..0cd617ab1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,17 +30,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 to select 48 kHz or 44.1 kHz from a range. - `realtime` feature for high-priority audio scheduling without a D-Bus build dependency. - Add `CODE_OF_CONDUCT.md`, `CONTRIBUTING.md` and `SECURITY.md`. +- `BufferSize` now implements `Default` (returns `BufferSize::Default`). +- `SupportedBufferSize` now implements `Default` (returns `SupportedBufferSize::Unknown`). +- `SupportedStreamConfig` now implements `Copy`. - **AAudio**: Streams now request `PERFORMANCE_MODE_LOW_LATENCY` when the `realtime` feature is enabled; stream error callback receives `ErrorKind::RealtimeDenied` if not granted. - **AAudio**: `Device` now implements `PartialEq`, `Eq`, `Hash`, and `Debug`. +- **AAudio**: `Host` and `Device` now implement `Default`; added `Device::new()`. - **ALSA**: `device_by_id()` now accepts PCM shorthand names such as `hw:0,0` and `plughw:foo`. - **ASIO**: `Device` now implements `Debug`. +- **AudioWorklet**: `Device` and `Host` now implement `Default`; added `Device::new()`. +- **CoreAudio**: `Host` now implements `Default`. - **CoreAudio**: tvOS target support (Tier 3, requires nightly). - **CoreAudio**: `Device` now implements `PartialEq`, `Eq`, and `Hash`. +- **CoreAudio (iOS)**: `Device` now implements `Default`; added `Device::new()`. - **JACK**: `Device` now implements `PartialEq`, `Eq`, and `Hash`. +- **Null**: `Device`, `Host`, `Stream`, `SupportedInputConfigs`, and `SupportedOutputConfigs` now + implement `Default`, added `new()` on each. - **PipeWire**: New host for Linux and some BSDs using the PipeWire API. - **PulseAudio**: New host for Linux and some BSDs using the PulseAudio API. - **WASAPI**: `Device` now implements `PartialEq`, `Eq`, `Hash`, and `Debug`. +- **WASAPI**: `Host` now implements `Default`. +- **WebAudio**: `Device` and `Host` now implement `Default`; added `Device::new()`. ### Changed @@ -178,6 +189,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **ASIO**: Fix `driver.sample_rate()` failures at stream creation being silently ignored. - **ASIO**: Fix callbacks firing before `build_*_stream` returns the `Stream` handle. - **ASIO**: Fix overrun not being reported when the driver reports `kAsioOverload`. +- **AudioWorklet**: Fix `Devices::default()` to enumerate only if `Host::is_available()`. - **CoreAudio**: Fix default output streams silently stopping when the system default output device is unplugged; they now reroute automatically or report `ErrorKind::DeviceNotAvailable`. - **CoreAudio**: Fix undefined behaviour and silent failure in loopback device creation. diff --git a/src/host/aaudio/java_interface/audio_features.rs b/src/host/aaudio/java_interface/audio_features.rs index bbd88090c..50e2be832 100644 --- a/src/host/aaudio/java_interface/audio_features.rs +++ b/src/host/aaudio/java_interface/audio_features.rs @@ -9,7 +9,7 @@ use super::{ /** * The Android audio features */ -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy)] pub enum AudioFeature { LowLatency, Output, diff --git a/src/host/aaudio/java_interface/definitions.rs b/src/host/aaudio/java_interface/definitions.rs index 0c4e4251a..ffe6fb810 100644 --- a/src/host/aaudio/java_interface/definitions.rs +++ b/src/host/aaudio/java_interface/definitions.rs @@ -75,10 +75,11 @@ pub struct AudioDeviceInfo { /** * The type of audio device */ -#[derive(Debug, Clone, Copy, FromPrimitive)] +#[derive(Debug, Clone, Copy, Default, FromPrimitive)] #[non_exhaustive] #[repr(i32)] pub enum AudioDeviceType { + #[default] Unknown = 0, AuxLine = 19, BleBroadcast = 30, diff --git a/src/host/aaudio/mod.rs b/src/host/aaudio/mod.rs index b31ed8268..39a402b66 100644 --- a/src/host/aaudio/mod.rs +++ b/src/host/aaudio/mod.rs @@ -121,10 +121,18 @@ const SAMPLE_RATES: [i32; 15] = [ /// The same default for blocking operations as Oboe uses const DEFAULT_TIMEOUT_NANOS: i64 = 2_000_000_000; +#[derive(Default)] pub struct Host; -#[derive(Clone, Debug)] + +#[derive(Clone, Debug, Default)] pub struct Device(Option); +impl Device { + pub fn new() -> Self { + Self::default() + } +} + /// Stream wraps AudioStream in Arc> to provide Send + Sync semantics. /// /// While the underlying ndk::audio::AudioStream is neither Send nor Sync in ndk 0.9.0 @@ -191,16 +199,16 @@ impl HostTrait for Host { .collect::>() .into_iter()) } else { - Ok(vec![Device(None)].into_iter()) + Ok(vec![Device::new()].into_iter()) } } fn default_input_device(&self) -> Option { - Some(Device(None)) + Some(Device::default()) } fn default_output_device(&self) -> Option { - Some(Device(None)) + Some(Device::default()) } } diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index f967811f4..6e2cbca12 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -667,7 +667,7 @@ impl Default for Device { } /// Strategy for pre-filling an output buffer with the equilibrium value. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Debug)] enum EquilibriumFill { /// Equilibrium is represented as a single repeating byte value. Byte(u8), @@ -704,7 +704,7 @@ impl EquilibriumFill { } // How callback timestamps are produced. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum TimestampMode { // Hardware timestamps are unavailable (e.g. PulseAudio ALSA plugin returns zero htstamp). // Timestamps are monotonic elapsed time since stream creation, sourced from Instant::now(). diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index dec23b1ac..c74e9e15e 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -24,6 +24,7 @@ use crate::{ /// Shared state for extending the 32-bit `timeGetTime()` millisecond counter into a /// monotonic 64-bit nanosecond value, shared between `now()` and audio callbacks. +#[derive(Default)] struct TimeBase { last_ns: AtomicU64, epoch_ns: AtomicU64, @@ -33,13 +34,6 @@ struct TimeBase { const TIMEGETIME_WRAP_NS: u64 = (u32::MAX as u64 + 1) * 1_000_000; impl TimeBase { - fn new() -> Self { - Self { - last_ns: AtomicU64::new(0), - epoch_ns: AtomicU64::new(0), - } - } - /// Convert a nanosecond timestamp to a monotonic `StreamInstant`. fn to_stream_instant(&self, ns: u64) -> StreamInstant { // `Relaxed` is sufficient: callbacks run on a single ASIO thread. The only @@ -60,9 +54,8 @@ impl TimeBase { const ASIO_EVENT_DEBOUNCE: Duration = Duration::from_millis(500); #[repr(u8)] -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, PartialEq, Eq)] enum StreamState { - #[default] Starting = 0, Paused = 1, Playing = 2, @@ -211,7 +204,7 @@ impl Device { let mut current_buffer_size = buffer_size as i32; let mut last_buffer_index: i32 = -1; - let time_base = Arc::new(TimeBase::new()); + let time_base = Arc::new(TimeBase::default()); let time_base_cb = Arc::clone(&time_base); // Set the input callback. @@ -544,7 +537,7 @@ impl Device { let mut current_buffer_size = buffer_size as i32; let mut last_buffer_index: i32 = -1; - let time_base = Arc::new(TimeBase::new()); + let time_base = Arc::new(TimeBase::default()); let time_base_cb = Arc::clone(&time_base); let callback_id = driver.add_callback(move |callback_info| unsafe { diff --git a/src/host/audioworklet/mod.rs b/src/host/audioworklet/mod.rs index 1b6491596..d578cd9ca 100644 --- a/src/host/audioworklet/mod.rs +++ b/src/host/audioworklet/mod.rs @@ -30,9 +30,15 @@ use crate::dependent_module; /// Content is false if the iterator is empty. pub struct Devices(bool); -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub struct Device; +impl Device { + pub fn new() -> Self { + Self + } +} + impl fmt::Display for Device { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let desc = self.description().map_err(|_| fmt::Error)?; @@ -40,6 +46,7 @@ impl fmt::Display for Device { } } +#[derive(Default)] pub struct Host; pub struct Stream { @@ -103,7 +110,11 @@ impl HostTrait for Host { } fn default_output_device(&self) -> Option { - Some(Device) + if Self::is_available() { + Some(Device) + } else { + None + } } } @@ -408,7 +419,7 @@ impl Drop for Stream { impl Default for Devices { fn default() -> Self { - Self(true) + Self(Host::is_available()) } } diff --git a/src/host/coreaudio/ios/mod.rs b/src/host/coreaudio/ios/mod.rs index cc54c2dcf..a4283baaa 100644 --- a/src/host/coreaudio/ios/mod.rs +++ b/src/host/coreaudio/ios/mod.rs @@ -36,7 +36,7 @@ use session_event_manager::SessionEventManager; // These days the default of iOS is now F32 and no longer I16 const SUPPORTED_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32; -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub struct Device; impl fmt::Display for Device { @@ -46,6 +46,7 @@ impl fmt::Display for Device { } } +#[derive(Default)] pub struct Host; impl Host { @@ -76,6 +77,10 @@ impl HostTrait for Host { } impl Device { + pub fn new() -> Self { + Self + } + fn description(&self) -> Result { // Query AVAudioSession to determine actual input/output availability // SAFETY: AVAudioSession::sharedInstance() returns the global audio session singleton diff --git a/src/host/coreaudio/macos/device.rs b/src/host/coreaudio/macos/device.rs index 077019abb..783c9e5f7 100644 --- a/src/host/coreaudio/macos/device.rs +++ b/src/host/coreaudio/macos/device.rs @@ -214,7 +214,7 @@ fn set_sample_rate( Ok(()) } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy)] enum AudioUnitMode { /// HAL Output AudioUnit with input enabled, pinned to a specific device. Input, diff --git a/src/host/coreaudio/macos/mod.rs b/src/host/coreaudio/macos/mod.rs index 82b0b0ee1..d740bb4d8 100644 --- a/src/host/coreaudio/macos/mod.rs +++ b/src/host/coreaudio/macos/mod.rs @@ -25,7 +25,7 @@ mod property_listener; pub use device::Device; /// Coreaudio host, the default host on macOS. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Host; impl Host { diff --git a/src/host/jack/stream.rs b/src/host/jack/stream.rs index de6ea0f46..72a6fc46d 100644 --- a/src/host/jack/stream.rs +++ b/src/host/jack/stream.rs @@ -12,9 +12,8 @@ use crate::{ }; #[repr(u8)] -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, PartialEq, Eq)] enum StreamState { - #[default] Starting = 0, Paused = 1, Playing = 2, diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 37eb1c650..463c68558 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -15,7 +15,7 @@ use crate::{ #[derive(Default)] pub struct Devices; -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub struct Device; impl fmt::Display for Device { @@ -25,20 +25,27 @@ impl fmt::Display for Device { } } +#[derive(Default)] pub struct Host; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] pub struct Stream; // Compile-time assertion that Stream is Send and Sync crate::assert_stream_send!(Stream); crate::assert_stream_sync!(Stream); -#[derive(Clone)] +#[derive(Clone, Default)] pub struct SupportedInputConfigs; -#[derive(Clone)] +#[derive(Clone, Default)] pub struct SupportedOutputConfigs; +impl Device { + pub fn new() -> Self { + Self + } +} + impl Host { #[allow(dead_code)] pub fn new() -> Result { @@ -46,6 +53,24 @@ impl Host { } } +impl Stream { + pub fn new() -> Self { + Self + } +} + +impl SupportedInputConfigs { + pub fn new() -> Self { + Self + } +} + +impl SupportedOutputConfigs { + pub fn new() -> Self { + Self + } +} + impl Devices { pub fn new() -> Result { Ok(Self) diff --git a/src/host/pipewire/device.rs b/src/host/pipewire/device.rs index dd67af326..cccf744d8 100644 --- a/src/host/pipewire/device.rs +++ b/src/host/pipewire/device.rs @@ -54,9 +54,9 @@ pub(crate) enum Class { } #[derive(Clone, Debug, Default, Copy)] -pub enum Role { - Sink, +enum Role { #[default] + Sink, Source, Duplex, StreamOutput, @@ -668,7 +668,7 @@ impl DeviceTrait for Device { } } -#[derive(Debug, Clone, Default)] +#[derive(Clone, Default)] struct Settings { rate: SampleRate, allow_rates: Box<[SampleRate]>, diff --git a/src/host/pipewire/stream.rs b/src/host/pipewire/stream.rs index 32907f7dd..0cf4d8b7d 100644 --- a/src/host/pipewire/stream.rs +++ b/src/host/pipewire/stream.rs @@ -68,8 +68,7 @@ impl Drop for PwInitGuard { } } -#[derive(Debug, Clone, Copy)] -pub enum StreamCommand { +pub(super) enum StreamCommand { Toggle(bool), Stop, } @@ -83,7 +82,7 @@ pub struct Stream { } impl Stream { - pub(crate) fn new( + pub(super) fn new( handle: JoinHandle<()>, controller: pw::channel::Sender, last_quantum: Arc, diff --git a/src/host/wasapi/mod.rs b/src/host/wasapi/mod.rs index 18e5e4f86..3562d4014 100644 --- a/src/host/wasapi/mod.rs +++ b/src/host/wasapi/mod.rs @@ -23,7 +23,7 @@ mod stream; /// Note: If you use a WASAPI output device as an input device it will /// transparently enable loopback mode (see /// https://docs.microsoft.com/en-us/windows/win32/coreaudio/loopback-recording). -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Host; impl Host { diff --git a/src/host/webaudio/mod.rs b/src/host/webaudio/mod.rs index cb05372c3..d4866a5ac 100644 --- a/src/host/webaudio/mod.rs +++ b/src/host/webaudio/mod.rs @@ -37,7 +37,7 @@ type ClosureHandle = Arc>>>; /// Content is false if the iterator is empty. pub struct Devices(bool); -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub struct Device; impl fmt::Display for Device { @@ -47,6 +47,7 @@ impl fmt::Display for Device { } } +#[derive(Default)] pub struct Host; pub struct Stream { @@ -112,6 +113,10 @@ impl Devices { } impl Device { + pub fn new() -> Self { + Self + } + fn description(&self) -> Result { Ok(DeviceDescriptionBuilder::new("Default Device") .direction(DeviceDirection::Output) diff --git a/src/lib.rs b/src/lib.rs index de042cfc9..749f4c16e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -368,8 +368,9 @@ impl std::str::FromStr for DeviceId { /// [`BufferSize::Fixed(x)`]: BufferSize::Fixed /// [`SupportedBufferSize`]: SupportedStreamConfig::buffer_size /// [`SupportedStreamConfig`]: SupportedStreamConfig -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum BufferSize { + #[default] Default, Fixed(FrameCount), } @@ -420,7 +421,7 @@ pub struct StreamConfig { } /// Describes the minimum and maximum supported buffer size for the device -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum SupportedBufferSize { Range { min: FrameCount, @@ -428,6 +429,7 @@ pub enum SupportedBufferSize { }, /// In the case that the platform provides no way of getting the default /// buffer size before starting a stream. + #[default] Unknown, } @@ -469,7 +471,7 @@ pub(crate) mod iter { /// Describes a single supported stream configuration, retrieved via either a /// [`SupportedStreamConfigRange`] instance or one of the /// [`Device::default_input/output_config`](traits::DeviceTrait#required-methods) methods. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SupportedStreamConfig { channels: ChannelCount, sample_rate: SampleRate,