Remove the conversion system

This commit is contained in:
Pierre Krieger 2015-08-20 14:38:25 +02:00
parent e641ac93d8
commit ca72d1d67e
8 changed files with 82 additions and 525 deletions

View File

@ -1,3 +0,0 @@
## License
The `music.ogg` file in this directory is under cc-by-sa.

View File

@ -4,17 +4,33 @@ fn main() {
let mut channel = cpal::Voice::new();
// Produce a sinusoid of maximum amplitude.
let max = std::u16::MAX as f32;
let mut data_source = (0u64..).map(|t| t as f32 * 0.03)
.map(|t| ((t.sin() * 0.5 + 0.5) * max) as u16);
.map(|t| t.sin());
loop {
{
let mut buffer = channel.append_data(1, cpal::SamplesRate(44100), 32768);
match channel.append_data(32768) {
cpal::UnknownTypeBuffer::U16(mut buffer) => {
for (sample, value) in buffer.chunks_mut(2).zip(&mut data_source) {
let value = ((value * 0.5 + 0.5) * std::u16::MAX as f32) as u16;
sample[0] = value;
sample[1] = value;
}
},
for (sample, value) in buffer.iter_mut().zip(&mut data_source) {
*sample = value;
}
cpal::UnknownTypeBuffer::I16(mut buffer) => {
for (sample, value) in buffer.chunks_mut(2).zip(&mut data_source) {
let value = (value * std::i16::MAX as f32) as i16;
sample[0] = value;
sample[1] = value;
}
},
cpal::UnknownTypeBuffer::F32(mut buffer) => {
for (sample, value) in buffer.chunks_mut(2).zip(&mut data_source) {
sample[0] = value;
sample[1] = value;
}
},
}
channel.play();

Binary file not shown.

View File

@ -1,47 +0,0 @@
extern crate cpal;
extern crate vorbis;
use std::io::Cursor;
fn main() {
let mut channel = cpal::Voice::new();
channel.play();
let mut decoder = vorbis::Decoder::new(Cursor::new(&include_bytes!("music.ogg")[..]))
.unwrap();
'main: for packet in decoder.packets() {
let packet = packet.unwrap();
let vorbis::Packet { channels, rate, data, .. } = packet;
let mut data = &data[..];
loop {
if data.len() == 0 {
continue 'main;
}
{
let mut buffer = channel.append_data(channels, cpal::SamplesRate(rate as u32),
data.len());
let mut buffer = buffer.iter_mut();
loop {
let next_sample = match data.get(0) {
Some(s) => *s,
None => continue 'main
};
if let Some(output) = buffer.next() {
*output = next_sample;
data = &data[1..];
} else {
break;
}
}
}
channel.play();
}
}
}

View File

@ -1,136 +0,0 @@
/*!
This module contains function that will convert from one PCM format to another.
This includes conversion between samples formats, channels or sample rates.
*/
use samples_formats::Sample;
/// Converts between samples rates while preserving the pitch.
pub fn convert_samples_rate<T>(input: &[T], from: ::SamplesRate, to: ::SamplesRate,
channels: ::ChannelsCount) -> Vec<T>
where T: Sample
{
let from = from.0;
let to = to.0;
// if `from` is a multiple of `to` (for example `from` is 44100 and `to` is 22050),
// then we simply skip some samples
if from % to == 0 {
let mut result = Vec::new();
for element in input.chunks(channels as usize * (from / to) as usize) {
for i in (0 .. channels) {
result.push(element[i as usize]);
}
}
return result;
}
// if `to` is twice `from` (for example `to` is 44100 and `from` is 22050)
// TODO: more generic
if to == from * 2 {
let mut result = Vec::new();
let mut previous: Option<Vec<T>> = None;
for element in input.chunks(channels as usize) {
if let Some(previous) = previous.take() {
for (prev, curr) in previous.into_iter().zip(element.iter()) {
result.push(prev.interpolate(*curr));
}
for curr in element.iter() {
result.push(*curr);
}
} else {
for e in element.iter() {
result.push(*e);
}
}
previous = Some(element.to_vec());
}
return result;
}
unimplemented!()
}
/// Converts between a certain number of channels.
///
/// If the target number is inferior to the source number, additional channels are removed.
///
/// If the target number is superior to the source number, the value of channel `N` is equal
/// to the value of channel `N % source_channels`.
///
/// ## Panic
///
/// Panics if `from` is 0, `to` is 0, or if the data length is not a multiple of `from`.
pub fn convert_channels<T>(input: &[T], from: ::ChannelsCount, to: ::ChannelsCount) -> Vec<T>
where T: Sample
{
assert!(from != 0);
assert!(to != 0);
assert!(input.len() % from as usize == 0);
let mut result = Vec::new();
for element in input.chunks(from as usize) {
// copying the common channels
for i in (0 .. ::std::cmp::min(from, to)) {
result.push(element[i as usize]);
}
// adding extra ones
if to > from {
for i in (0 .. to - from) {
result.push(element[i as usize % element.len()]);
}
}
}
result
}
#[cfg(test)]
mod test {
use super::convert_channels;
use super::convert_samples_rate;
#[test]
fn remove_channels() {
let result = convert_channels(&[1u16, 2, 3, 1, 2, 3], 3, 2);
assert_eq!(result, [1, 2, 1, 2]);
let result = convert_channels(&[1u16, 2, 3, 4, 1, 2, 3, 4], 4, 1);
assert_eq!(result, [1, 1]);
}
#[test]
fn add_channels() {
let result = convert_channels(&[1u16, 2, 1, 2], 2, 3);
assert_eq!(result, [1, 2, 1, 1, 2, 1]);
let result = convert_channels(&[1u16, 2, 1, 2], 2, 4);
assert_eq!(result, [1, 2, 1, 2, 1, 2, 1, 2]);
}
#[test]
#[should_panic]
fn convert_channels_wrong_data_len() {
convert_channels(&[1u16, 2, 3], 2, 1);
}
#[test]
fn half_samples_rate() {
let result = convert_samples_rate(&[1u16, 16, 2, 17, 3, 18, 4, 19],
::SamplesRate(44100), ::SamplesRate(22050), 2);
assert_eq!(result, [1, 16, 3, 18]);
}
#[test]
fn double_samples_rate() {
let result = convert_samples_rate(&[2u16, 16, 4, 18, 6, 20, 8, 22],
::SamplesRate(22050), ::SamplesRate(44100), 2);
assert_eq!(result, [2, 16, 3, 17, 4, 18, 5, 19, 6, 20, 7, 21, 8, 22]);
}
}

View File

@ -7,21 +7,10 @@ In order to play a sound, first you need to create a `Voice`.
let mut voice = cpal::Voice::new();
```
Then you must send raw samples to it by calling `append_data`.
This function takes three parameters: the number of channels, the number of samples
that must be played per second, and the number of samples that you have available.
Then you must send raw samples to it by calling `append_data`. You must take the number of channels
and samples rate into account when writing the data.
You can then fill the buffer with the data.
```no_run
# let mut voice = cpal::Voice::new();
let mut buffer: cpal::Buffer<f32> = voice.append_data(2, cpal::SamplesRate(44100), 1024);
// filling the buffer with 0s
for e in buffer.iter_mut() {
*e = 0.0f32;
}
```
TODO: add example
**Important**: the `append_data` function can return a buffer shorter than what you requested.
This is the case if the device doesn't have enough space available. **It happens very often**,
@ -38,21 +27,11 @@ The audio device of the user will read the buffer that you sent, and play it. If
reaches the end of the data, it will stop playing. You must continuously fill the buffer by
calling `append_data` repeatedly if you don't want the audio to stop playing.
# Native format
Each `Voice` is bound to a specific number of channels, samples rate, and samples format.
If you call `append_data` with values different than these, then cpal will automatically perform
a conversion on your data.
If you have the possibility, you should try to match the format of the voice.
*/
pub use samples_formats::{SampleFormat, Sample};
use std::ops::{Deref, DerefMut};
mod conversions;
mod samples_formats;
#[cfg(target_os = "linux")]
@ -71,20 +50,6 @@ mod cpal_impl;
#[path="null/mod.rs"]
mod cpal_impl;
/// Controls a sound output. A typical application has one `Voice` for each sound
/// it wants to output.
///
/// A voice must be periodically filled with new data by calling `append_data`, or the sound
/// will stop playing.
///
/// Each `Voice` is bound to a specific number of channels, samples rate, and samples format,
/// which can be retreived by calling `get_channels`, `get_samples_rate` and `get_samples_format`.
/// If you call `append_data` with values different than these, then cpal will automatically
/// perform a conversion on your data.
///
/// If you have the possibility, you should try to match the format of the voice.
pub struct Voice(cpal_impl::Voice);
/// Number of channels.
pub type ChannelsCount = u16;
@ -100,21 +65,34 @@ pub struct SamplesRate(pub u32);
pub struct Buffer<'a, T: 'a> where T: Sample {
// also contains something, taken by `Drop`
target: Option<cpal_impl::Buffer<'a, T>>,
// if this is non-none, then the data will be written to `conversion.intermediate_buffer`
// instead of `target`, and the conversion will be done in buffer's destructor
conversion: Option<RequiredConversion<T>>,
}
struct RequiredConversion<T> {
intermediate_buffer: Vec<T>,
from_sample_rate: SamplesRate,
to_sample_rate: SamplesRate,
to_format: SampleFormat,
from_channels: ChannelsCount,
to_channels: ChannelsCount,
/// This is the struct that is provided to you by cpal when you want to write samples to a buffer.
///
/// Since the type of data is only known at runtime, you have to fill the right buffer.
pub enum UnknownTypeBuffer<'a> {
/// Samples whose format is `u16`.
U16(Buffer<'a, u16>),
/// Samples whose format is `i16`.
I16(Buffer<'a, i16>),
/// Samples whose format is `f32`.
F32(Buffer<'a, f32>),
}
/// Controls a sound output. A typical application has one `Voice` for each sound
/// it wants to output.
///
/// A voice must be periodically filled with new data by calling `append_data`, or the sound
/// will stop playing.
///
/// Each `Voice` is bound to a specific number of channels, samples rate, and samples format,
/// which can be retreived by calling `get_channels`, `get_samples_rate` and `get_samples_format`.
/// If you call `append_data` with values different than these, then cpal will automatically
/// perform a conversion on your data.
///
/// If you have the possibility, you should try to match the format of the voice.
pub struct Voice(cpal_impl::Voice);
impl Voice {
/// Builds a new channel.
pub fn new() -> Voice {
@ -148,11 +126,11 @@ impl Voice {
/// Adds some PCM data to the voice's buffer.
///
/// This function returns a `Buffer` object that must be filled with the audio data.
/// This function indirectly returns a `Buffer` object that must be filled with the audio data.
/// The size of the buffer being returned depends on the current state of the backend
/// and can't be known in advance. However it is never greater than `max_elements`.
/// and can't be known in advance. However it is never greater than `max_samples`.
///
/// You must fill the buffer *entirely*, so do not set `max_elements` to a value greater
/// You must fill the buffer *entirely*, so do not set `max_samples` to a value greater
/// than the amount of data available to you.
///
/// Channels are interleaved. For example if you have two channels, you must write
@ -160,65 +138,23 @@ impl Voice {
/// then the second sample of the first channel, then the second sample of the second
/// channel, etc.
///
/// ## Parameters
///
/// * `channels`: number of channels (1 for mono, 2 for stereo, etc.)
/// * `samples_rate`: number of samples that must be played by second for each channel
/// * `max_elements`: maximum size of the returned buffer
///
/// ## Panic
///
/// Panics if `max_elements` is 0 or is not a multiple of `channels`.
/// Panics if `max_samples` is 0.
///
pub fn append_data<'a, T>(&'a mut self, channels: ChannelsCount,
samples_rate: SamplesRate, max_elements: usize)
-> Buffer<'a, T> where T: Sample + Clone
{
assert!(max_elements != 0);
assert!(max_elements % channels as usize == 0);
pub fn append_data(&mut self, max_samples: usize) -> UnknownTypeBuffer {
assert!(max_samples != 0);
let target_samples_rate = self.0.get_samples_rate();
let target_channels = self.0.get_channels();
let source_samples_format = Sample::get_format(None::<T>);
let target_samples_format = self.0.get_samples_format();
// if we need to convert the incoming data
if samples_rate != target_samples_rate || channels != target_channels ||
source_samples_format != target_samples_format
{
let max_elements = max_elements * target_channels as usize / channels as usize;
let max_elements = max_elements * target_samples_rate.0 as usize /
samples_rate.0 as usize;
let mut target_buffer = self.0.append_data(max_elements);
// computing the length of the intermediary buffer
let intermediate_buffer_length = target_buffer.get_buffer().len();
let intermediate_buffer_length = intermediate_buffer_length * channels as usize /
target_channels as usize;
let intermediate_buffer_length = intermediate_buffer_length * samples_rate.0 as usize /
target_samples_rate.0 as usize;
let intermediate_buffer = std::iter::repeat(unsafe { std::mem::uninitialized() })
.take(intermediate_buffer_length).collect();
Buffer {
target: Some(target_buffer),
conversion: Some(RequiredConversion {
intermediate_buffer: intermediate_buffer,
from_sample_rate: samples_rate,
to_sample_rate: target_samples_rate,
to_format: target_samples_format,
from_channels: channels,
to_channels: target_channels,
}),
}
} else {
Buffer {
target: Some(self.0.append_data(max_elements)),
conversion: None,
}
match self.get_samples_format() {
SampleFormat::U16 => UnknownTypeBuffer::U16(Buffer {
target: Some(self.0.append_data(max_samples))
}),
SampleFormat::I16 => UnknownTypeBuffer::I16(Buffer {
target: Some(self.0.append_data(max_samples))
}),
SampleFormat::F32 => UnknownTypeBuffer::F32(Buffer {
target: Some(self.0.append_data(max_samples))
}),
}
}
@ -252,74 +188,12 @@ impl<'a, T> Deref for Buffer<'a, T> where T: Sample {
impl<'a, T> DerefMut for Buffer<'a, T> where T: Sample {
fn deref_mut(&mut self) -> &mut [T] {
if let Some(ref mut conversion) = self.conversion {
&mut conversion.intermediate_buffer
} else {
self.target.as_mut().unwrap().get_buffer()
}
self.target.as_mut().unwrap().get_buffer()
}
}
impl<'a, T> Drop for Buffer<'a, T> where T: Sample {
fn drop(&mut self) {
if let Some(conversion) = self.conversion.take() {
let buffer = conversion.intermediate_buffer;
let buffer = if conversion.from_channels != conversion.to_channels {
conversions::convert_channels(&buffer, conversion.from_channels,
conversion.to_channels)
} else {
buffer
};
let buffer = if conversion.from_sample_rate != conversion.to_sample_rate {
conversions::convert_samples_rate(&buffer, conversion.from_sample_rate,
conversion.to_sample_rate,
conversion.to_channels)
} else {
buffer
};
let output = self.target.as_mut().unwrap().get_buffer();
assert!(buffer.len() == output.len(), "Buffers length mismatch: {} vs {}", buffer.len(), output.len());
macro_rules! write_to_buf(
($buf:expr, $output:expr, $ty:ty) => ({
use std::borrow::Cow;
let output: &mut [$ty] = unsafe { std::mem::transmute($output) };
match $buf {
Cow::Borrowed(buf) => {
for (i, o) in buf.iter().zip(output.iter_mut()) {
*o = *i;
}
},
Cow::Owned(buf) => {
for (i, o) in buf.into_iter().zip(output.iter_mut()) {
*o = i;
}
}
}
})
);
match conversion.to_format {
SampleFormat::I16 => {
let buffer = Sample::to_vec_i16(&buffer);
write_to_buf!(buffer, output, i16);
},
SampleFormat::U16 => {
let buffer = Sample::to_vec_u16(&buffer);
write_to_buf!(buffer, output, u16);
},
SampleFormat::F32 => {
let buffer = Sample::to_vec_f32(&buffer);
write_to_buf!(buffer, output, f32);
},
}
}
self.target.take().unwrap().finish();
}
}

View File

@ -1,4 +1,3 @@
use std::borrow::Cow;
use std::mem;
/// Format that each sample has.
@ -24,171 +23,25 @@ impl SampleFormat {
}
/// Trait for containers that contain PCM data.
pub trait Sample: Copy + Clone {
fn get_format(Option<Self>) -> SampleFormat;
/// Returns `(self + other) / 2`.
fn interpolate(self, other: Self) -> Self;
/// Turns the data into samples of type `I16`.
fn to_vec_i16(&[Self]) -> Cow<[i16]>;
/// Turns the data into samples of type `U16`.
fn to_vec_u16(&[Self]) -> Cow<[u16]>;
/// Turns the data into samples of type `F32`.
fn to_vec_f32(&[Self]) -> Cow<[f32]>;
pub unsafe trait Sample: Copy + Clone {
/// Returns the `SampleFormat` corresponding to this data type.
fn get_format() -> SampleFormat;
}
impl Sample for u16 {
fn get_format(_: Option<u16>) -> SampleFormat {
unsafe impl Sample for u16 {
fn get_format() -> SampleFormat {
SampleFormat::U16
}
fn interpolate(self, other: u16) -> u16 {
(self + other) / 2
}
fn to_vec_i16(input: &[u16]) -> Cow<[i16]> {
Cow::Owned(input.iter().map(|&value| {
if value >= 32768 {
(value - 32768) as i16
} else {
(value as i16) - 32767 - 1
}
}).collect())
}
fn to_vec_u16(input: &[u16]) -> Cow<[u16]> {
Cow::Borrowed(input)
}
fn to_vec_f32(input: &[u16]) -> Cow<[f32]> {
Cow::Owned(Sample::to_vec_f32(&Sample::to_vec_i16(input)).to_vec())
}
}
impl Sample for i16 {
fn get_format(_: Option<i16>) -> SampleFormat {
unsafe impl Sample for i16 {
fn get_format() -> SampleFormat {
SampleFormat::I16
}
fn interpolate(self, other: i16) -> i16 {
(self + other) / 2
}
fn to_vec_i16(input: &[i16]) -> Cow<[i16]> {
Cow::Borrowed(input)
}
fn to_vec_u16(input: &[i16]) -> Cow<[u16]> {
Cow::Owned(input.iter().map(|&value| {
if value < 0 {
(value + 32767 + 1) as u16
} else {
(value as u16) + 32768
}
}).collect())
}
fn to_vec_f32(input: &[i16]) -> Cow<[f32]> {
Cow::Owned(input.iter().map(|&value| {
if value > 0 {
value as f32 / 32767.0
} else {
value as f32 / 32768.0
}
}).collect())
}
}
impl Sample for f32 {
fn get_format(_: Option<f32>) -> SampleFormat {
unsafe impl Sample for f32 {
fn get_format() -> SampleFormat {
SampleFormat::F32
}
fn interpolate(self, other: f32) -> f32 {
(self + other) / 2.0
}
fn to_vec_i16(input: &[f32]) -> Cow<[i16]> {
Cow::Owned(input.iter().map(|&value| {
if value >= 0.0 {
(value * 32767.0) as i16
} else {
(value * 32768.0) as i16
}
}).collect())
}
fn to_vec_u16(input: &[f32]) -> Cow<[u16]> {
Cow::Owned(input.iter().map(|&value| {
if value >= 0.0 {
((value * 32767.0) + 32768.0) as u16
} else {
((value * 32768.0) + 32768.0) as u16
}
}).collect())
}
fn to_vec_f32(input: &[f32]) -> Cow<[f32]> {
Cow::Borrowed(input)
}
}
#[cfg(test)]
mod test {
use super::Sample;
#[test]
fn i16_to_i16() {
let out = Sample::to_vec_i16(&[0i16, -467, 32767, -32768]).into_owned();
assert_eq!(out, vec![0, -467, 32767, -32768]);
}
#[test]
fn i16_to_u16() {
let out = Sample::to_vec_u16(&[0i16, -16384, 32767, -32768]).into_owned();
assert_eq!(out, vec![32768, 16384, 65535, 0]);
}
#[test]
fn i16_to_f32() {
let out = Sample::to_vec_f32(&[0i16, -16384, 32767, -32768]).into_owned();
assert_eq!(out, vec![0.0, -0.5, 1.0, -1.0]);
}
#[test]
fn u16_to_i16() {
let out = Sample::to_vec_i16(&[32768u16, 16384, 65535, 0]).into_owned();
assert_eq!(out, vec![0, -16384, 32767, -32768]);
}
#[test]
fn u16_to_u16() {
let out = Sample::to_vec_u16(&[0u16, 467, 32767, 65535]).into_owned();
assert_eq!(out, vec![0, 467, 32767, 65535]);
}
#[test]
fn u16_to_f32() {
let out = Sample::to_vec_f32(&[0u16, 32768, 65535]).into_owned();
assert_eq!(out, vec![-1.0, 0.0, 1.0]);
}
#[test]
fn f32_to_i16() {
let out = Sample::to_vec_i16(&[0.0f32, -0.5, 1.0, -1.0]).into_owned();
assert_eq!(out, vec![0, -16384, 32767, -32768]);
}
#[test]
fn f32_to_u16() {
let out = Sample::to_vec_u16(&[-1.0f32, 0.0, 1.0]).into_owned();
assert_eq!(out, vec![0, 32768, 65535]);
}
#[test]
fn f32_to_f32() {
let out = Sample::to_vec_f32(&[0.1f32, -0.7, 1.0]).into_owned();
assert_eq!(out, vec![0.1, -0.7, 1.0]);
}
}

View File

@ -2,7 +2,7 @@ extern crate libc;
extern crate winapi;
extern crate ole32;
use std::{slice, mem, ptr};
use std::{cmp, slice, mem, ptr};
use std::marker::PhantomData;
// TODO: determine if should be NoSend or not
@ -62,9 +62,9 @@ impl Voice {
continue;
}
let frames_available = ::std::cmp::min(frames_available,
max_elements as u32 * mem::size_of::<T>() as u32 /
self.bytes_per_frame as u32);
let frames_available = cmp::min(frames_available,
max_elements as u32 * mem::size_of::<T>() as u32 /
self.bytes_per_frame as u32);
assert!(frames_available != 0);
// loading buffer
@ -147,11 +147,11 @@ impl<'a, T> Buffer<'a, T> {
fn init() -> Result<Voice, String> {
// FIXME: release everything
unsafe {
try!(check_result(ole32::CoInitializeEx(::std::ptr::null_mut(), 0)));
try!(check_result(ole32::CoInitializeEx(ptr::null_mut(), 0)));
// building the devices enumerator object
let enumerator = {
let mut enumerator: *mut winapi::IMMDeviceEnumerator = ::std::mem::uninitialized();
let mut enumerator: *mut winapi::IMMDeviceEnumerator = mem::uninitialized();
let hresult = ole32::CoCreateInstance(&winapi::CLSID_MMDeviceEnumerator,
ptr::null_mut(), winapi::CLSCTX_ALL,