pub const WAVE_FORMAT_PCM: u16 = 0x0001;
pub const WAVE_FORMAT_IEEE_FLOAT: u16 = 0x0003;
pub const WAVE_FORMAT_EXTENSIBLE: u16 = 0xFFFE;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct Format {
format_tag: u16,
channels: u16,
samples_per_sec: u32,
avg_bytes_per_sec: u32,
block_align: u16,
bits_per_sample: u16,
valid_bits_per_sample: u16,
channel_mask: u32,
extensible: bool,
}
impl Format {
#[must_use]
pub const fn from_raw(
format_tag: u16,
channels: u16,
samples_per_sec: u32,
bits_per_sample: u16,
) -> Self {
let block_align = channels * (bits_per_sample / 8);
let avg_bytes_per_sec = samples_per_sec * block_align as u32;
Self {
format_tag,
channels,
samples_per_sec,
avg_bytes_per_sec,
block_align,
bits_per_sample,
valid_bits_per_sample: 0,
channel_mask: 0,
extensible: false,
}
}
#[must_use]
pub const fn pcm_int16(sample_rate: u32, channels: u16) -> Self {
Self::from_raw(WAVE_FORMAT_PCM, channels, sample_rate, 16)
}
#[must_use]
pub const fn pcm_int24(sample_rate: u32, channels: u16) -> Self {
Self::from_raw(WAVE_FORMAT_PCM, channels, sample_rate, 24)
}
#[must_use]
pub const fn pcm_int32(sample_rate: u32, channels: u16) -> Self {
Self::from_raw(WAVE_FORMAT_PCM, channels, sample_rate, 32)
}
#[must_use]
pub const fn pcm_float32(sample_rate: u32, channels: u16) -> Self {
Self::from_raw(WAVE_FORMAT_IEEE_FLOAT, channels, sample_rate, 32)
}
#[must_use]
pub const fn pcm_float64(sample_rate: u32, channels: u16) -> Self {
Self::from_raw(WAVE_FORMAT_IEEE_FLOAT, channels, sample_rate, 64)
}
#[inline]
#[must_use]
pub const fn format_tag(&self) -> u16 {
self.format_tag
}
#[inline]
#[must_use]
pub const fn channels(&self) -> u16 {
self.channels
}
#[inline]
#[must_use]
pub const fn sample_rate(&self) -> u32 {
self.samples_per_sec
}
#[inline]
#[must_use]
pub const fn bits_per_sample(&self) -> u16 {
self.bits_per_sample
}
#[inline]
#[must_use]
pub const fn block_align(&self) -> u16 {
self.block_align
}
#[inline]
#[must_use]
pub const fn avg_bytes_per_sec(&self) -> u32 {
self.avg_bytes_per_sec
}
#[inline]
#[must_use]
pub const fn is_float(&self) -> bool {
self.format_tag == WAVE_FORMAT_IEEE_FLOAT
}
#[inline]
#[must_use]
pub const fn is_int_pcm(&self) -> bool {
self.format_tag == WAVE_FORMAT_PCM
}
#[inline]
#[must_use]
pub const fn is_extensible(&self) -> bool {
self.extensible
}
#[inline]
#[must_use]
pub const fn channel_mask(&self) -> u32 {
self.channel_mask
}
#[inline]
#[must_use]
pub const fn valid_bits_per_sample(&self) -> u16 {
self.valid_bits_per_sample
}
#[inline]
#[must_use]
pub const fn with_extensible(mut self) -> Self {
self.extensible = true;
if self.channel_mask == 0 {
self.channel_mask = default_channel_mask(self.channels);
}
if self.valid_bits_per_sample == 0 {
self.valid_bits_per_sample = self.bits_per_sample;
}
self
}
#[inline]
#[must_use]
pub const fn with_channel_mask(mut self, mask: u32) -> Self {
self.channel_mask = mask;
self
}
#[inline]
#[must_use]
pub const fn with_valid_bits_per_sample(mut self, bits: u16) -> Self {
self.valid_bits_per_sample = bits;
self
}
}
#[must_use]
pub const fn default_channel_mask(channels: u16) -> u32 {
const SPEAKER_FRONT_LEFT: u32 = 0x1;
const SPEAKER_FRONT_RIGHT: u32 = 0x2;
const SPEAKER_FRONT_CENTER: u32 = 0x4;
const SPEAKER_LOW_FREQUENCY: u32 = 0x8;
const SPEAKER_BACK_LEFT: u32 = 0x10;
const SPEAKER_BACK_RIGHT: u32 = 0x20;
const SPEAKER_SIDE_LEFT: u32 = 0x200;
const SPEAKER_SIDE_RIGHT: u32 = 0x400;
match channels {
1 => SPEAKER_FRONT_CENTER,
2 => SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
6 => {
SPEAKER_FRONT_LEFT
| SPEAKER_FRONT_RIGHT
| SPEAKER_FRONT_CENTER
| SPEAKER_LOW_FREQUENCY
| SPEAKER_BACK_LEFT
| SPEAKER_BACK_RIGHT
}
8 => {
SPEAKER_FRONT_LEFT
| SPEAKER_FRONT_RIGHT
| SPEAKER_FRONT_CENTER
| SPEAKER_LOW_FREQUENCY
| SPEAKER_BACK_LEFT
| SPEAKER_BACK_RIGHT
| SPEAKER_SIDE_LEFT
| SPEAKER_SIDE_RIGHT
}
_ => 0,
}
}
#[cfg(windows)]
impl Format {
#[must_use]
pub fn from_waveformatex(wf: &windows::Win32::Media::Audio::WAVEFORMATEX) -> Self {
Self {
format_tag: { wf.wFormatTag },
channels: { wf.nChannels },
samples_per_sec: { wf.nSamplesPerSec },
avg_bytes_per_sec: { wf.nAvgBytesPerSec },
block_align: { wf.nBlockAlign },
bits_per_sample: { wf.wBitsPerSample },
valid_bits_per_sample: 0,
channel_mask: 0,
extensible: false,
}
}
#[must_use]
pub fn to_waveformatex(&self) -> windows::Win32::Media::Audio::WAVEFORMATEX {
windows::Win32::Media::Audio::WAVEFORMATEX {
wFormatTag: if self.extensible {
WAVE_FORMAT_EXTENSIBLE
} else {
self.format_tag
},
nChannels: self.channels,
nSamplesPerSec: self.samples_per_sec,
nAvgBytesPerSec: self.avg_bytes_per_sec,
nBlockAlign: self.block_align,
wBitsPerSample: self.bits_per_sample,
cbSize: if self.extensible { 22 } else { 0 },
}
}
#[must_use]
pub fn from_waveformatextensible(
wfx: &windows::Win32::Media::Audio::WAVEFORMATEXTENSIBLE,
) -> Self {
let base = wfx.Format;
let valid_bits = unsafe { wfx.Samples.wValidBitsPerSample };
const KSDATAFORMAT_SUBTYPE_IEEE_FLOAT: windows_core::GUID =
windows_core::GUID::from_u128(0x00000003_0000_0010_8000_00aa00389b71);
let sub: windows_core::GUID = wfx.SubFormat;
let logical_tag = if sub == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT {
WAVE_FORMAT_IEEE_FLOAT
} else {
WAVE_FORMAT_PCM
};
Self {
format_tag: logical_tag,
channels: { base.nChannels },
samples_per_sec: { base.nSamplesPerSec },
avg_bytes_per_sec: { base.nAvgBytesPerSec },
block_align: { base.nBlockAlign },
bits_per_sample: { base.wBitsPerSample },
valid_bits_per_sample: valid_bits,
channel_mask: { wfx.dwChannelMask },
extensible: true,
}
}
#[must_use]
pub fn to_waveformatextensible(&self) -> windows::Win32::Media::Audio::WAVEFORMATEXTENSIBLE {
use windows::Win32::Media::Audio::{WAVEFORMATEXTENSIBLE, WAVEFORMATEXTENSIBLE_0};
let base = windows::Win32::Media::Audio::WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_EXTENSIBLE,
nChannels: self.channels,
nSamplesPerSec: self.samples_per_sec,
nAvgBytesPerSec: self.avg_bytes_per_sec,
nBlockAlign: self.block_align,
wBitsPerSample: self.bits_per_sample,
cbSize: 22,
};
let samples = WAVEFORMATEXTENSIBLE_0 {
wValidBitsPerSample: if self.valid_bits_per_sample == 0 {
self.bits_per_sample
} else {
self.valid_bits_per_sample
},
};
WAVEFORMATEXTENSIBLE {
Format: base,
Samples: samples,
dwChannelMask: self.channel_mask,
SubFormat: self.sub_format_guid(),
}
}
fn sub_format_guid(&self) -> windows_core::GUID {
const KSDATAFORMAT_SUBTYPE_IEEE_FLOAT: windows_core::GUID =
windows_core::GUID::from_u128(0x00000003_0000_0010_8000_00aa00389b71);
if self.is_int_pcm() {
windows::Win32::Media::KernelStreaming::KSDATAFORMAT_SUBTYPE_PCM
} else {
KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
}
}
#[must_use]
pub unsafe fn from_waveformatex_ptr(
wf: *const windows::Win32::Media::Audio::WAVEFORMATEX,
) -> Self {
let base = unsafe { &*wf };
if { base.cbSize } >= 22 && { base.wFormatTag } == WAVE_FORMAT_EXTENSIBLE {
let wfx = wf as *const windows::Win32::Media::Audio::WAVEFORMATEXTENSIBLE;
Self::from_waveformatextensible(unsafe { &*wfx })
} else {
Self::from_waveformatex(base)
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum FormatNegotiation {
Accept,
Suggest(Format),
Reject,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pcm_float32_48k_mono_has_expected_fields() {
let f = Format::pcm_float32(48_000, 1);
assert_eq!(f.format_tag(), WAVE_FORMAT_IEEE_FLOAT);
assert_eq!(f.channels(), 1);
assert_eq!(f.sample_rate(), 48_000);
assert_eq!(f.bits_per_sample(), 32);
assert_eq!(f.block_align(), 4);
assert_eq!(f.avg_bytes_per_sec(), 48_000 * 4);
assert!(f.is_float());
assert!(!f.is_int_pcm());
}
#[test]
fn pcm_int16_44k1_stereo_has_expected_fields() {
let f = Format::pcm_int16(44_100, 2);
assert_eq!(f.format_tag(), WAVE_FORMAT_PCM);
assert_eq!(f.channels(), 2);
assert_eq!(f.sample_rate(), 44_100);
assert_eq!(f.bits_per_sample(), 16);
assert_eq!(f.block_align(), 4);
assert_eq!(f.avg_bytes_per_sec(), 44_100 * 4);
assert!(f.is_int_pcm());
assert!(!f.is_float());
}
#[test]
fn pcm_int24_48k_mono_block_align_is_3() {
let f = Format::pcm_int24(48_000, 1);
assert_eq!(f.block_align(), 3);
assert_eq!(f.avg_bytes_per_sec(), 48_000 * 3);
}
#[test]
fn pcm_float64_48k_5_1_block_align_is_48() {
let f = Format::pcm_float64(48_000, 6);
assert_eq!(f.block_align(), 48);
assert_eq!(f.avg_bytes_per_sec(), 48_000 * 48);
}
#[test]
fn negotiation_variants_distinguish() {
let a = FormatNegotiation::Accept;
let s = FormatNegotiation::Suggest(Format::pcm_float32(48_000, 1));
let r = FormatNegotiation::Reject;
assert_ne!(a, s);
assert_ne!(s, r);
assert_ne!(a, r);
}
}
#[cfg(all(test, windows))]
mod windows_conv_tests {
use super::*;
use windows::Win32::Media::Audio::WAVEFORMATEX;
#[test]
fn windows_waveformatex_is_18_bytes_packed_one() {
assert_eq!(core::mem::size_of::<WAVEFORMATEX>(), 18);
assert_eq!(core::mem::align_of::<WAVEFORMATEX>(), 1);
}
#[test]
fn pcm_float32_48k_mono_round_trips() {
let f = Format::pcm_float32(48_000, 1);
let wf = f.to_waveformatex();
assert_eq!({ wf.wFormatTag }, WAVE_FORMAT_IEEE_FLOAT);
assert_eq!({ wf.nChannels }, 1);
assert_eq!({ wf.nSamplesPerSec }, 48_000);
assert_eq!({ wf.nAvgBytesPerSec }, 48_000 * 4);
assert_eq!({ wf.nBlockAlign }, 4);
assert_eq!({ wf.wBitsPerSample }, 32);
assert_eq!({ wf.cbSize }, 0);
let f2 = Format::from_waveformatex(&wf);
assert_eq!(f, f2);
}
#[test]
fn every_typed_constructor_round_trips() {
for f in [
Format::pcm_int16(44_100, 2),
Format::pcm_int24(48_000, 1),
Format::pcm_int32(96_000, 4),
Format::pcm_float32(48_000, 1),
Format::pcm_float64(192_000, 8),
] {
let wf = f.to_waveformatex();
let f2 = Format::from_waveformatex(&wf);
assert_eq!(f, f2, "round-trip failed for {f:?}");
}
}
#[test]
fn from_waveformatex_preserves_all_base_fields() {
let wf = WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_PCM,
nChannels: 2,
nSamplesPerSec: 44_100,
nAvgBytesPerSec: 44_100 * 4,
nBlockAlign: 4,
wBitsPerSample: 16,
cbSize: 0,
};
let f = Format::from_waveformatex(&wf);
assert_eq!(f.format_tag(), WAVE_FORMAT_PCM);
assert_eq!(f.channels(), 2);
assert_eq!(f.sample_rate(), 44_100);
assert_eq!(f.avg_bytes_per_sec(), 44_100 * 4);
assert_eq!(f.block_align(), 4);
assert_eq!(f.bits_per_sample(), 16);
}
#[test]
fn from_waveformatex_ignores_cbsize() {
let wf = WAVEFORMATEX {
wFormatTag: WAVE_FORMAT_EXTENSIBLE,
nChannels: 6,
nSamplesPerSec: 48_000,
nAvgBytesPerSec: 48_000 * 24,
nBlockAlign: 24,
wBitsPerSample: 32,
cbSize: 22, };
let f = Format::from_waveformatex(&wf);
assert_eq!(f.format_tag(), WAVE_FORMAT_EXTENSIBLE);
assert_eq!(f.channels(), 6);
assert_eq!(f.sample_rate(), 48_000);
assert_eq!({ f.to_waveformatex().cbSize }, 0);
}
#[test]
fn waveformatextensible_is_40_bytes_packed_one() {
assert_eq!(
core::mem::size_of::<windows::Win32::Media::Audio::WAVEFORMATEXTENSIBLE>(),
40
);
assert_eq!(
core::mem::align_of::<windows::Win32::Media::Audio::WAVEFORMATEXTENSIBLE>(),
1
);
}
#[test]
fn to_waveformatextensible_sets_wire_tag_and_subformat_for_float32() {
let f = Format::pcm_float32(48_000, 8).with_extensible();
let wfx = f.to_waveformatextensible();
assert_eq!({ wfx.Format.wFormatTag }, WAVE_FORMAT_EXTENSIBLE);
assert_eq!({ wfx.Format.cbSize }, 22);
assert_eq!({ wfx.Format.nChannels }, 8);
const KSDATAFORMAT_SUBTYPE_IEEE_FLOAT: windows_core::GUID =
windows_core::GUID::from_u128(0x00000003_0000_0010_8000_00aa00389b71);
assert_eq!({ wfx.SubFormat }, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT);
assert_eq!(unsafe { wfx.Samples.wValidBitsPerSample }, 32);
assert!({ wfx.dwChannelMask } != 0);
}
#[test]
fn to_waveformatextensible_sets_pcm_subformat_for_int16() {
let f = Format::pcm_int16(48_000, 2).with_extensible();
let wfx = f.to_waveformatextensible();
assert_eq!(
{ wfx.SubFormat },
windows::Win32::Media::KernelStreaming::KSDATAFORMAT_SUBTYPE_PCM
);
}
#[test]
fn extensible_round_trips_through_waveformatextensible() {
let original = Format::pcm_float32(48_000, 6)
.with_extensible()
.with_valid_bits_per_sample(24);
let wfx = original.to_waveformatextensible();
let parsed = Format::from_waveformatextensible(&wfx);
assert!(parsed.is_extensible());
assert_eq!(parsed.format_tag(), WAVE_FORMAT_IEEE_FLOAT);
assert_eq!(parsed.channels(), original.channels());
assert_eq!(parsed.sample_rate(), original.sample_rate());
assert_eq!(parsed.channel_mask(), original.channel_mask());
assert_eq!(parsed.valid_bits_per_sample(), 24);
}
#[test]
fn from_waveformatex_ptr_picks_extensible_when_cbsize_22() {
let f = Format::pcm_float32(48_000, 8).with_extensible();
let wfx = f.to_waveformatextensible();
let prefix: *const windows::Win32::Media::Audio::WAVEFORMATEX =
core::ptr::addr_of!(wfx.Format);
let parsed = unsafe { Format::from_waveformatex_ptr(prefix) };
assert!(parsed.is_extensible());
assert_eq!(parsed.channels(), 8);
assert_eq!(parsed.format_tag(), WAVE_FORMAT_IEEE_FLOAT);
}
#[test]
fn from_waveformatex_ptr_keeps_base_when_cbsize_zero() {
let f = Format::pcm_float32(48_000, 2);
let wf = f.to_waveformatex();
let ptr: *const windows::Win32::Media::Audio::WAVEFORMATEX = &wf;
let parsed = unsafe { Format::from_waveformatex_ptr(ptr) };
assert!(!parsed.is_extensible());
assert_eq!(parsed.format_tag(), WAVE_FORMAT_IEEE_FLOAT);
}
#[test]
fn default_channel_mask_returns_known_layouts() {
assert_eq!(default_channel_mask(1), 0x4); assert_eq!(default_channel_mask(2), 0x3); assert_eq!(default_channel_mask(6), 0x3F); assert_eq!(default_channel_mask(8), 0x63F); assert_eq!(default_channel_mask(3), 0); }
}