mirror of
https://github.com/RustAudio/rodio
synced 2024-12-13 13:42:34 +00:00
Add spatial audio
This commit is contained in:
parent
ab5200cff7
commit
b4ecdfd075
7 changed files with 375 additions and 0 deletions
|
@ -15,3 +15,4 @@ futures = "0.1.1"
|
||||||
hound = "1.0.0"
|
hound = "1.0.0"
|
||||||
lazy_static = "0.1.12"
|
lazy_static = "0.1.12"
|
||||||
lewton = "0.5"
|
lewton = "0.5"
|
||||||
|
cgmath = "0.14"
|
||||||
|
|
32
examples/spatial.rs
Normal file
32
examples/spatial.rs
Normal 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();
|
||||||
|
}
|
|
@ -90,6 +90,7 @@ extern crate hound;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
extern crate lewton;
|
extern crate lewton;
|
||||||
|
extern crate cgmath;
|
||||||
|
|
||||||
pub use cpal::{Endpoint, get_endpoints_list, get_default_endpoint};
|
pub use cpal::{Endpoint, get_endpoints_list, get_default_endpoint};
|
||||||
|
|
||||||
|
@ -97,6 +98,7 @@ pub use conversions::Sample;
|
||||||
pub use decoder::Decoder;
|
pub use decoder::Decoder;
|
||||||
pub use engine::play_raw;
|
pub use engine::play_raw;
|
||||||
pub use sink::Sink;
|
pub use sink::Sink;
|
||||||
|
pub use spatial_sink::SpatialSink;
|
||||||
pub use source::Source;
|
pub use source::Source;
|
||||||
|
|
||||||
use std::io::{Read, Seek};
|
use std::io::{Read, Seek};
|
||||||
|
@ -104,6 +106,7 @@ use std::io::{Read, Seek};
|
||||||
mod conversions;
|
mod conversions;
|
||||||
mod engine;
|
mod engine;
|
||||||
mod sink;
|
mod sink;
|
||||||
|
mod spatial_sink;
|
||||||
|
|
||||||
pub mod buffer;
|
pub mod buffer;
|
||||||
pub mod decoder;
|
pub mod decoder;
|
||||||
|
|
111
src/source/channel_volume.rs
Normal file
111
src/source/channel_volume.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,8 @@ pub use self::periodic::PeriodicAccess;
|
||||||
pub use self::repeat::Repeat;
|
pub use self::repeat::Repeat;
|
||||||
pub use self::samples_converter::SamplesConverter;
|
pub use self::samples_converter::SamplesConverter;
|
||||||
pub use self::sine::SineWave;
|
pub use self::sine::SineWave;
|
||||||
|
pub use self::channel_volume::ChannelVolume;
|
||||||
|
pub use self::spatial::Spatial;
|
||||||
pub use self::speed::Speed;
|
pub use self::speed::Speed;
|
||||||
pub use self::stoppable::Stoppable;
|
pub use self::stoppable::Stoppable;
|
||||||
pub use self::take::TakeDuration;
|
pub use self::take::TakeDuration;
|
||||||
|
@ -38,6 +40,8 @@ mod periodic;
|
||||||
mod repeat;
|
mod repeat;
|
||||||
mod samples_converter;
|
mod samples_converter;
|
||||||
mod sine;
|
mod sine;
|
||||||
|
mod channel_volume;
|
||||||
|
mod spatial;
|
||||||
mod speed;
|
mod speed;
|
||||||
mod stoppable;
|
mod stoppable;
|
||||||
mod take;
|
mod take;
|
||||||
|
|
96
src/source/spatial.rs
Normal file
96
src/source/spatial.rs
Normal 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
128
src/spatial_sink.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue