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"
|
||||
lazy_static = "0.1.12"
|
||||
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]
|
||||
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;
|
||||
|
|
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::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
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