Merge pull request #125 from Xaeroxe/spatial

Add spatial audio
This commit is contained in:
tomaka 2017-07-02 19:00:42 +02:00 committed by GitHub
commit 1fc7ef3a77
7 changed files with 375 additions and 0 deletions

View file

@ -15,3 +15,4 @@ futures = "0.1.1"
hound = "1.0.0"
lazy_static = "0.1.12"
lewton = "0.5"
cgmath = "0.14"

32
examples/spatial.rs Normal file
View file

@ -0,0 +1,32 @@
extern crate rodio;
use std::io::BufReader;
use std::thread;
use std::time::Duration;
fn main() {
let endpoint = rodio::get_default_endpoint().unwrap();
let mut sink = rodio::SpatialSink::new(&endpoint,
[-10.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[-1.0, 0.0, 0.0]);
let file = std::fs::File::open("examples/music.ogg").unwrap();
let source = rodio::Decoder::new(BufReader::new(file)).unwrap();
sink.append(source);
// A sound emitter playing the music starting at the left gradually moves to the right
// eventually passing through the listener, then it continues on to the right for a distance until it
// stops and begins traveling to the left, it will eventually pass through the listener again.
// This is repeated 5 times.
for _ in 0..5 {
for i in 1..1001 {
thread::sleep(Duration::from_millis(5));
sink.set_emitter_position([(i - 500) as f32 / 50.0, 0.0, 0.0]);
}
for i in 1..1001 {
thread::sleep(Duration::from_millis(5));
sink.set_emitter_position([-(i - 500) as f32 / 50.0, 0.0, 0.0]);
}
}
sink.sleep_until_end();
}

View file

@ -90,6 +90,7 @@ extern crate hound;
#[macro_use]
extern crate lazy_static;
extern crate lewton;
extern crate cgmath;
pub use cpal::{Endpoint, get_endpoints_list, get_default_endpoint};
@ -97,6 +98,7 @@ pub use conversions::Sample;
pub use decoder::Decoder;
pub use engine::play_raw;
pub use sink::Sink;
pub use spatial_sink::SpatialSink;
pub use source::Source;
use std::io::{Read, Seek};
@ -104,6 +106,7 @@ use std::io::{Read, Seek};
mod conversions;
mod engine;
mod sink;
mod spatial_sink;
pub mod buffer;
pub mod decoder;

View file

@ -0,0 +1,111 @@
use Sample;
use Source;
use std::time::Duration;
/// Combines channels in input into a single mono source, then plays that mono sound
/// to each channel at the volume given for that channel.
#[derive(Clone, Debug)]
pub struct ChannelVolume<I>
where I: Source,
I::Item: Sample
{
input: I,
// Channel number is used as index for amplification value.
channel_volumes: Vec<f32>,
// Current listener being processed.
current_channel: usize,
current_sample: I::Item,
}
impl<I> ChannelVolume<I>
where I: Source,
I::Item: Sample
{
pub fn new(mut input: I, channel_volumes: Vec<f32>) -> ChannelVolume<I>
where I: Source,
I::Item: Sample
{
let mut sample = I::Item::zero_value();
for _ in 0..input.channels() {
if let Some(s) = input.next() {
sample = sample.saturating_add(s);
}
}
ChannelVolume {
input,
channel_volumes,
current_channel: 0,
current_sample: sample,
}
}
/// Sets the volume for a given channel number. Will panic if channel number
/// was invalid.
pub fn set_volume(&mut self, channel: usize, volume: f32) {
self.channel_volumes[channel] = volume;
}
}
impl<I> Iterator for ChannelVolume<I>
where I: Source,
I::Item: Sample
{
type Item = I::Item;
#[inline]
fn next(&mut self) -> Option<I::Item> {
// return value
let ret = self.current_sample
.amplify(self.channel_volumes[self.current_channel]);
self.current_channel += 1;
if self.current_channel >= self.channel_volumes.len() {
self.current_channel = 0;
let mut sample = I::Item::zero_value();
for _ in 0..self.input.channels() {
if let Some(s) = self.input.next() {
sample = sample.saturating_add(s);
} else {
return None;
}
}
self.current_sample = sample;
}
return Some(ret);
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.input.size_hint()
}
}
impl<I> ExactSizeIterator for ChannelVolume<I>
where I: Source + ExactSizeIterator,
I::Item: Sample
{
}
impl<I> Source for ChannelVolume<I>
where I: Source,
I::Item: Sample
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
self.input.current_frame_len()
}
#[inline]
fn channels(&self) -> u16 {
self.channel_volumes.len() as u16
}
#[inline]
fn samples_rate(&self) -> u32 {
self.input.samples_rate()
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
}
}

View file

@ -18,6 +18,8 @@ pub use self::periodic::PeriodicAccess;
pub use self::repeat::Repeat;
pub use self::samples_converter::SamplesConverter;
pub use self::sine::SineWave;
pub use self::channel_volume::ChannelVolume;
pub use self::spatial::Spatial;
pub use self::speed::Speed;
pub use self::stoppable::Stoppable;
pub use self::take::TakeDuration;
@ -38,6 +40,8 @@ mod periodic;
mod repeat;
mod samples_converter;
mod sine;
mod channel_volume;
mod spatial;
mod speed;
mod stoppable;
mod take;

96
src/source/spatial.rs Normal file
View file

@ -0,0 +1,96 @@
use Sample;
use Source;
use source::ChannelVolume;
use std::time::Duration;
use std::fmt::Debug;
use cgmath::{InnerSpace, Point3};
/// Combines channels in input into a single mono source, then plays that mono sound
/// to each channel at the volume given for that channel.
#[derive(Clone, Debug)]
pub struct Spatial<I>
where I: Source,
I::Item: Sample + Debug
{
input: ChannelVolume<I>,
}
impl<I> Spatial<I>
where I: Source,
I::Item: Sample + Debug
{
pub fn new(input: I, emitter_position: [f32; 3], left_ear: [f32; 3], right_ear: [f32; 3]) -> Spatial<I>
where I: Source,
I::Item: Sample
{
let mut ret = Spatial {
input: ChannelVolume::new(input, vec![0.0, 0.0]),
};
ret.set_positions(emitter_position, left_ear, right_ear);
ret
}
/// Sets the position of the emitter and ears in the 3D world.
pub fn set_positions(&mut self, emitter_pos: [f32; 3], left_ear: [f32; 3], right_ear: [f32; 3]) {
let emitter_position = Point3::from(emitter_pos);
let left_ear = Point3::from(left_ear);
let right_ear = Point3::from(right_ear);
let left_distance = (left_ear - emitter_position).magnitude();
let right_distance = (right_ear - emitter_position).magnitude();
let max_diff = (left_ear - right_ear).magnitude();
let left_diff_modifier = ((left_distance - right_distance) / max_diff + 1.0) / 4.0 + 0.5;
let right_diff_modifier = ((right_distance - left_distance) / max_diff + 1.0) / 4.0 + 0.5;
let left_dist_modifier = (1.0 / left_distance.powi(2)).min(1.0);
let right_dist_modifier = (1.0 / right_distance.powi(2)).min(1.0);
self.input.set_volume(0, left_diff_modifier * left_dist_modifier);
self.input.set_volume(1, right_diff_modifier * right_dist_modifier);
}
}
impl<I> Iterator for Spatial<I>
where I: Source,
I::Item: Sample + Debug
{
type Item = I::Item;
#[inline]
fn next(&mut self) -> Option<I::Item> {
self.input.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.input.size_hint()
}
}
impl<I> ExactSizeIterator for Spatial<I>
where I: Source + ExactSizeIterator,
I::Item: Sample + Debug
{
}
impl<I> Source for Spatial<I>
where I: Source,
I::Item: Sample + Debug
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
self.input.current_frame_len()
}
#[inline]
fn channels(&self) -> u16 {
self.input.channels()
}
#[inline]
fn samples_rate(&self) -> u32 {
self.input.samples_rate()
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
}
}

128
src/spatial_sink.rs Normal file
View file

@ -0,0 +1,128 @@
use std::f32;
use std::sync::{Arc, Mutex};
use Sink;
use Endpoint;
use Source;
use Sample;
use source::Spatial;
use std::time::Duration;
use std::fmt::Debug;
pub struct SpatialSink {
sink: Sink,
positions: Arc<Mutex<SoundPositions>>,
}
struct SoundPositions {
emitter_position: [f32; 3],
left_ear: [f32; 3],
right_ear: [f32; 3],
}
impl SpatialSink {
/// Builds a new `SpatialSink`.
#[inline]
pub fn new(endpoint: &Endpoint,
emitter_position: [f32; 3],
left_ear: [f32; 3],
right_ear: [f32; 3])
-> SpatialSink {
SpatialSink {
sink: Sink::new(endpoint),
positions: Arc::new(Mutex::new(SoundPositions {
emitter_position,
left_ear,
right_ear,
})),
}
}
/// Sets the position of the sound emitter in 3 dimensional space.
pub fn set_emitter_position(&mut self, pos: [f32; 3]) {
self.positions.lock().unwrap().emitter_position = pos;
}
/// Sets the position of the left ear in 3 dimensional space.
pub fn set_left_ear_position(&mut self, pos: [f32; 3]) {
self.positions.lock().unwrap().left_ear = pos;
}
/// Sets the position of the right ear in 3 dimensional space.
pub fn set_right_ear_position(&mut self, pos: [f32; 3]) {
self.positions.lock().unwrap().right_ear = pos;
}
/// Appends a sound to the queue of sounds to play.
#[inline]
pub fn append<S>(&self, source: S)
where S: Source + Send + 'static,
S::Item: Sample + Send + Debug,
{
let positions = self.positions.clone();
let pos_lock = self.positions.lock().unwrap();
let source = Spatial::new(
source,
pos_lock.emitter_position,
pos_lock.left_ear,
pos_lock.right_ear
).periodic_access(Duration::from_millis(10), move |i| {
let pos = positions.lock().unwrap();
i.set_positions(pos.emitter_position, pos.left_ear, pos.right_ear);
});
self.sink.append(source);
}
// Gets the volume of the sound.
///
/// The value `1.0` is the "normal" volume (unfiltered input). Any value other than 1.0 will
/// multiply each sample by this value.
#[inline]
pub fn volume(&self) -> f32 {
self.sink.volume()
}
/// Changes the volume of the sound.
///
/// The value `1.0` is the "normal" volume (unfiltered input). Any value other than 1.0 will
/// multiply each sample by this value.
#[inline]
pub fn set_volume(&mut self, value: f32) {
self.sink.set_volume(value);
}
/// Resumes playback of a paused sound.
///
/// No effect if not paused.
#[inline]
pub fn play(&self) {
self.sink.play();
}
/// Pauses playback of this sink.
///
/// No effect if already paused.
///
/// A paused sound can be resumed with `play()`.
pub fn pause(&self) {
self.sink.pause();
}
/// Gets if a sound is paused
///
/// Sounds can be paused and resumed using pause() and play(). This gets if a sound is paused.
pub fn is_paused(&self) -> bool {
self.sink.is_paused()
}
/// Destroys the sink without stopping the sounds that are still playing.
#[inline]
pub fn detach(self) {
self.sink.detach();
}
/// Sleeps the current thread until the sound ends.
#[inline]
pub fn sleep_until_end(&self) {
self.sink.sleep_until_end();
}
}