Merge pull request #60 from tomaka/handle-channels-pos
Handle channels positionning
This commit is contained in:
commit
74286fcbf0
|
@ -6,31 +6,28 @@ fn main() {
|
||||||
let mut channel = cpal::Voice::new(&endpoint, &format).unwrap();
|
let mut channel = cpal::Voice::new(&endpoint, &format).unwrap();
|
||||||
|
|
||||||
// Produce a sinusoid of maximum amplitude.
|
// Produce a sinusoid of maximum amplitude.
|
||||||
let mut data_source = (0u64..).map(|t| t as f32 * 0.03)
|
let mut data_source = (0u64..).map(|t| t as f32 * 440.0 * 2.0 * 3.141592 / format.samples_rate.0 as f32) // 440 Hz
|
||||||
.map(|t| t.sin());
|
.map(|t| t.sin());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match channel.append_data(32768) {
|
match channel.append_data(32768) {
|
||||||
cpal::UnknownTypeBuffer::U16(mut buffer) => {
|
cpal::UnknownTypeBuffer::U16(mut buffer) => {
|
||||||
for (sample, value) in buffer.chunks_mut(2).zip(&mut data_source) {
|
for (sample, value) in buffer.chunks_mut(format.channels.len()).zip(&mut data_source) {
|
||||||
let value = ((value * 0.5 + 0.5) * std::u16::MAX as f32) as u16;
|
let value = ((value * 0.5 + 0.5) * std::u16::MAX as f32) as u16;
|
||||||
sample[0] = value;
|
for out in sample.iter_mut() { *out = value; }
|
||||||
sample[1] = value;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
cpal::UnknownTypeBuffer::I16(mut buffer) => {
|
cpal::UnknownTypeBuffer::I16(mut buffer) => {
|
||||||
for (sample, value) in buffer.chunks_mut(2).zip(&mut data_source) {
|
for (sample, value) in buffer.chunks_mut(format.channels.len()).zip(&mut data_source) {
|
||||||
let value = (value * std::i16::MAX as f32) as i16;
|
let value = (value * std::i16::MAX as f32) as i16;
|
||||||
sample[0] = value;
|
for out in sample.iter_mut() { *out = value; }
|
||||||
sample[1] = value;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
cpal::UnknownTypeBuffer::F32(mut buffer) => {
|
cpal::UnknownTypeBuffer::F32(mut buffer) => {
|
||||||
for (sample, value) in buffer.chunks_mut(2).zip(&mut data_source) {
|
for (sample, value) in buffer.chunks_mut(format.channels.len()).zip(&mut data_source) {
|
||||||
sample[0] = value;
|
for out in sample.iter_mut() { *out = value; }
|
||||||
sample[1] = value;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
27
src/lib.rs
27
src/lib.rs
|
@ -107,14 +107,37 @@ impl Endpoint {
|
||||||
/// Number of channels.
|
/// Number of channels.
|
||||||
pub type ChannelsCount = u16;
|
pub type ChannelsCount = u16;
|
||||||
|
|
||||||
|
/// Possible position of a channel.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum ChannelPosition {
|
||||||
|
FrontLeft,
|
||||||
|
FrontRight,
|
||||||
|
FrontCenter,
|
||||||
|
LowFrequency,
|
||||||
|
BackLeft,
|
||||||
|
BackRight,
|
||||||
|
FrontLeftOfCenter,
|
||||||
|
FrontRightOfCenter,
|
||||||
|
BackCenter,
|
||||||
|
SideLeft,
|
||||||
|
SideRight,
|
||||||
|
TopCenter,
|
||||||
|
TopFrontLeft,
|
||||||
|
TopFrontCenter,
|
||||||
|
TopFrontRight,
|
||||||
|
TopBackLeft,
|
||||||
|
TopBackCenter,
|
||||||
|
TopBackRight,
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct SamplesRate(pub u32);
|
pub struct SamplesRate(pub u32);
|
||||||
|
|
||||||
/// Describes a format.
|
/// Describes a format.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Format {
|
pub struct Format {
|
||||||
pub channels: ChannelsCount,
|
pub channels: Vec<ChannelPosition>,
|
||||||
pub samples_rate: SamplesRate,
|
pub samples_rate: SamplesRate,
|
||||||
pub data_type: SampleFormat,
|
pub data_type: SampleFormat,
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ use std::mem;
|
||||||
|
|
||||||
use Format;
|
use Format;
|
||||||
use FormatsEnumerationError;
|
use FormatsEnumerationError;
|
||||||
|
use ChannelPosition;
|
||||||
use SamplesRate;
|
use SamplesRate;
|
||||||
use SampleFormat;
|
use SampleFormat;
|
||||||
|
|
||||||
|
@ -18,6 +19,26 @@ pub use self::voice::{Voice, Buffer};
|
||||||
|
|
||||||
pub type SupportedFormatsIterator = OptionIntoIter<Format>;
|
pub type SupportedFormatsIterator = OptionIntoIter<Format>;
|
||||||
|
|
||||||
|
// TODO: these constants should be moved to winapi
|
||||||
|
const SPEAKER_FRONT_LEFT: winapi::DWORD = 0x1;
|
||||||
|
const SPEAKER_FRONT_RIGHT: winapi::DWORD = 0x2;
|
||||||
|
const SPEAKER_FRONT_CENTER: winapi::DWORD = 0x4;
|
||||||
|
const SPEAKER_LOW_FREQUENCY: winapi::DWORD = 0x8;
|
||||||
|
const SPEAKER_BACK_LEFT: winapi::DWORD = 0x10;
|
||||||
|
const SPEAKER_BACK_RIGHT: winapi::DWORD = 0x20;
|
||||||
|
const SPEAKER_FRONT_LEFT_OF_CENTER: winapi::DWORD = 0x40;
|
||||||
|
const SPEAKER_FRONT_RIGHT_OF_CENTER: winapi::DWORD = 0x80;
|
||||||
|
const SPEAKER_BACK_CENTER: winapi::DWORD = 0x100;
|
||||||
|
const SPEAKER_SIDE_LEFT: winapi::DWORD = 0x200;
|
||||||
|
const SPEAKER_SIDE_RIGHT: winapi::DWORD = 0x400;
|
||||||
|
const SPEAKER_TOP_CENTER: winapi::DWORD = 0x800;
|
||||||
|
const SPEAKER_TOP_FRONT_LEFT: winapi::DWORD = 0x1000;
|
||||||
|
const SPEAKER_TOP_FRONT_CENTER: winapi::DWORD = 0x2000;
|
||||||
|
const SPEAKER_TOP_FRONT_RIGHT: winapi::DWORD = 0x4000;
|
||||||
|
const SPEAKER_TOP_BACK_LEFT: winapi::DWORD = 0x8000;
|
||||||
|
const SPEAKER_TOP_BACK_CENTER: winapi::DWORD = 0x10000;
|
||||||
|
const SPEAKER_TOP_BACK_RIGHT: winapi::DWORD = 0x20000;
|
||||||
|
|
||||||
mod com;
|
mod com;
|
||||||
mod enumerate;
|
mod enumerate;
|
||||||
mod voice;
|
mod voice;
|
||||||
|
@ -119,21 +140,57 @@ impl Endpoint {
|
||||||
};
|
};
|
||||||
|
|
||||||
let format = {
|
let format = {
|
||||||
let data_type = match (*format_ptr).wFormatTag {
|
let (channels, data_type) = match (*format_ptr).wFormatTag {
|
||||||
winapi::WAVE_FORMAT_PCM => SampleFormat::I16,
|
winapi::WAVE_FORMAT_PCM => {
|
||||||
|
(
|
||||||
|
vec![ChannelPosition::FrontLeft, ChannelPosition::FrontRight],
|
||||||
|
SampleFormat::I16
|
||||||
|
)
|
||||||
|
},
|
||||||
winapi::WAVE_FORMAT_EXTENSIBLE => {
|
winapi::WAVE_FORMAT_EXTENSIBLE => {
|
||||||
let format_ptr = format_ptr as *const winapi::WAVEFORMATEXTENSIBLE;
|
let format_ptr = format_ptr as *const winapi::WAVEFORMATEXTENSIBLE;
|
||||||
match (*format_ptr).SubFormat {
|
|
||||||
|
let channels = {
|
||||||
|
let mut channels = Vec::new();
|
||||||
|
|
||||||
|
let mask = (*format_ptr).dwChannelMask;
|
||||||
|
if (mask & SPEAKER_FRONT_LEFT) != 0 { channels.push(ChannelPosition::FrontLeft); }
|
||||||
|
if (mask & SPEAKER_FRONT_RIGHT) != 0 { channels.push(ChannelPosition::FrontRight); }
|
||||||
|
if (mask & SPEAKER_FRONT_CENTER) != 0 { channels.push(ChannelPosition::FrontCenter); }
|
||||||
|
if (mask & SPEAKER_LOW_FREQUENCY) != 0 { channels.push(ChannelPosition::LowFrequency); }
|
||||||
|
if (mask & SPEAKER_BACK_LEFT) != 0 { channels.push(ChannelPosition::BackLeft); }
|
||||||
|
if (mask & SPEAKER_BACK_RIGHT) != 0 { channels.push(ChannelPosition::BackRight); }
|
||||||
|
if (mask & SPEAKER_FRONT_LEFT_OF_CENTER) != 0 { channels.push(ChannelPosition::FrontLeftOfCenter); }
|
||||||
|
if (mask & SPEAKER_FRONT_RIGHT_OF_CENTER) != 0 { channels.push(ChannelPosition::FrontRightOfCenter); }
|
||||||
|
if (mask & SPEAKER_BACK_CENTER) != 0 { channels.push(ChannelPosition::BackCenter); }
|
||||||
|
if (mask & SPEAKER_SIDE_LEFT) != 0 { channels.push(ChannelPosition::SideLeft); }
|
||||||
|
if (mask & SPEAKER_SIDE_RIGHT) != 0 { channels.push(ChannelPosition::SideRight); }
|
||||||
|
if (mask & SPEAKER_TOP_CENTER) != 0 { channels.push(ChannelPosition::TopCenter); }
|
||||||
|
if (mask & SPEAKER_TOP_FRONT_LEFT) != 0 { channels.push(ChannelPosition::TopFrontLeft); }
|
||||||
|
if (mask & SPEAKER_TOP_FRONT_CENTER) != 0 { channels.push(ChannelPosition::TopFrontCenter); }
|
||||||
|
if (mask & SPEAKER_TOP_FRONT_RIGHT) != 0 { channels.push(ChannelPosition::TopFrontRight); }
|
||||||
|
if (mask & SPEAKER_TOP_BACK_LEFT) != 0 { channels.push(ChannelPosition::TopBackLeft); }
|
||||||
|
if (mask & SPEAKER_TOP_BACK_CENTER) != 0 { channels.push(ChannelPosition::TopBackCenter); }
|
||||||
|
if (mask & SPEAKER_TOP_BACK_RIGHT) != 0 { channels.push(ChannelPosition::TopBackRight); }
|
||||||
|
|
||||||
|
assert_eq!((*format_ptr).Format.nChannels as usize, channels.len());
|
||||||
|
channels
|
||||||
|
};
|
||||||
|
|
||||||
|
let format = match (*format_ptr).SubFormat {
|
||||||
winapi::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT => SampleFormat::F32,
|
winapi::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT => SampleFormat::F32,
|
||||||
winapi::KSDATAFORMAT_SUBTYPE_PCM => SampleFormat::I16,
|
winapi::KSDATAFORMAT_SUBTYPE_PCM => SampleFormat::I16,
|
||||||
g => panic!("Unknown SubFormat GUID returned by GetMixFormat: {:?}", g)
|
g => panic!("Unknown SubFormat GUID returned by GetMixFormat: {:?}", g)
|
||||||
}
|
};
|
||||||
|
|
||||||
|
(channels, format)
|
||||||
},
|
},
|
||||||
|
|
||||||
f => panic!("Unknown data format returned by GetMixFormat: {:?}", f)
|
f => panic!("Unknown data format returned by GetMixFormat: {:?}", f)
|
||||||
};
|
};
|
||||||
|
|
||||||
Format {
|
Format {
|
||||||
channels: (*format_ptr).nChannels,
|
channels: channels,
|
||||||
samples_rate: SamplesRate((*format_ptr).nSamplesPerSec),
|
samples_rate: SamplesRate((*format_ptr).nSamplesPerSec),
|
||||||
data_type: data_type,
|
data_type: data_type,
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ use std::ptr;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use CreationError;
|
use CreationError;
|
||||||
|
use ChannelPosition;
|
||||||
use Format;
|
use Format;
|
||||||
use SampleFormat;
|
use SampleFormat;
|
||||||
|
|
||||||
|
@ -52,12 +53,12 @@ impl Voice {
|
||||||
SampleFormat::F32 => winapi::WAVE_FORMAT_EXTENSIBLE,
|
SampleFormat::F32 => winapi::WAVE_FORMAT_EXTENSIBLE,
|
||||||
SampleFormat::U16 => return Err(CreationError::FormatNotSupported),
|
SampleFormat::U16 => return Err(CreationError::FormatNotSupported),
|
||||||
},
|
},
|
||||||
nChannels: format.channels as winapi::WORD,
|
nChannels: format.channels.len() as winapi::WORD,
|
||||||
nSamplesPerSec: format.samples_rate.0 as winapi::DWORD,
|
nSamplesPerSec: format.samples_rate.0 as winapi::DWORD,
|
||||||
nAvgBytesPerSec: format.channels as winapi::DWORD *
|
nAvgBytesPerSec: format.channels.len() as winapi::DWORD *
|
||||||
format.samples_rate.0 as winapi::DWORD *
|
format.samples_rate.0 as winapi::DWORD *
|
||||||
format.data_type.get_sample_size() as winapi::DWORD,
|
format.data_type.get_sample_size() as winapi::DWORD,
|
||||||
nBlockAlign: format.channels as winapi::WORD *
|
nBlockAlign: format.channels.len() as winapi::WORD *
|
||||||
format.data_type.get_sample_size() as winapi::WORD,
|
format.data_type.get_sample_size() as winapi::WORD,
|
||||||
wBitsPerSample: 8 * format.data_type.get_sample_size() as winapi::WORD,
|
wBitsPerSample: 8 * format.data_type.get_sample_size() as winapi::WORD,
|
||||||
cbSize: match format.data_type {
|
cbSize: match format.data_type {
|
||||||
|
@ -68,7 +69,40 @@ impl Voice {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Samples: 8 * format.data_type.get_sample_size() as winapi::WORD,
|
Samples: 8 * format.data_type.get_sample_size() as winapi::WORD,
|
||||||
dwChannelMask: 3, // LEFT | RIGHT
|
dwChannelMask: {
|
||||||
|
let mut mask = 0;
|
||||||
|
for &channel in format.channels.iter() {
|
||||||
|
let raw_value = match channel {
|
||||||
|
ChannelPosition::FrontLeft => super::SPEAKER_FRONT_LEFT,
|
||||||
|
ChannelPosition::FrontRight => super::SPEAKER_FRONT_RIGHT,
|
||||||
|
ChannelPosition::FrontCenter => super::SPEAKER_FRONT_CENTER,
|
||||||
|
ChannelPosition::LowFrequency => super::SPEAKER_LOW_FREQUENCY,
|
||||||
|
ChannelPosition::BackLeft => super::SPEAKER_BACK_LEFT,
|
||||||
|
ChannelPosition::BackRight => super::SPEAKER_BACK_RIGHT,
|
||||||
|
ChannelPosition::FrontLeftOfCenter => super::SPEAKER_FRONT_LEFT_OF_CENTER,
|
||||||
|
ChannelPosition::FrontRightOfCenter => super::SPEAKER_FRONT_RIGHT_OF_CENTER,
|
||||||
|
ChannelPosition::BackCenter => super::SPEAKER_BACK_CENTER,
|
||||||
|
ChannelPosition::SideLeft => super::SPEAKER_SIDE_LEFT,
|
||||||
|
ChannelPosition::SideRight => super::SPEAKER_SIDE_RIGHT,
|
||||||
|
ChannelPosition::TopCenter => super::SPEAKER_TOP_CENTER,
|
||||||
|
ChannelPosition::TopFrontLeft => super::SPEAKER_TOP_FRONT_LEFT,
|
||||||
|
ChannelPosition::TopFrontCenter => super::SPEAKER_TOP_FRONT_CENTER,
|
||||||
|
ChannelPosition::TopFrontRight => super::SPEAKER_TOP_FRONT_RIGHT,
|
||||||
|
ChannelPosition::TopBackLeft => super::SPEAKER_TOP_BACK_LEFT,
|
||||||
|
ChannelPosition::TopBackCenter => super::SPEAKER_TOP_BACK_CENTER,
|
||||||
|
ChannelPosition::TopBackRight => super::SPEAKER_TOP_BACK_RIGHT,
|
||||||
|
};
|
||||||
|
|
||||||
|
// channels must be in the right order
|
||||||
|
if raw_value <= mask {
|
||||||
|
return Err(CreationError::FormatNotSupported);
|
||||||
|
}
|
||||||
|
|
||||||
|
mask = mask | raw_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
mask
|
||||||
|
},
|
||||||
SubFormat: match format.data_type {
|
SubFormat: match format.data_type {
|
||||||
SampleFormat::I16 => winapi::KSDATAFORMAT_SUBTYPE_PCM,
|
SampleFormat::I16 => winapi::KSDATAFORMAT_SUBTYPE_PCM,
|
||||||
SampleFormat::F32 => winapi::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
|
SampleFormat::F32 => winapi::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT,
|
||||||
|
|
Loading…
Reference in New Issue