From b4ecdfd07501534c5a8948d27ae3b9562d6c248f Mon Sep 17 00:00:00 2001 From: Jacob Kiesel Date: Sat, 1 Jul 2017 08:57:59 -0600 Subject: [PATCH] Add spatial audio --- Cargo.toml | 1 + examples/spatial.rs | 32 +++++++++ src/lib.rs | 3 + src/source/channel_volume.rs | 111 ++++++++++++++++++++++++++++++ src/source/mod.rs | 4 ++ src/source/spatial.rs | 96 ++++++++++++++++++++++++++ src/spatial_sink.rs | 128 +++++++++++++++++++++++++++++++++++ 7 files changed, 375 insertions(+) create mode 100644 examples/spatial.rs create mode 100644 src/source/channel_volume.rs create mode 100644 src/source/spatial.rs create mode 100644 src/spatial_sink.rs diff --git a/Cargo.toml b/Cargo.toml index 13d5bff..d6a5767 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ futures = "0.1.1" hound = "1.0.0" lazy_static = "0.1.12" lewton = "0.5" +cgmath = "0.14" diff --git a/examples/spatial.rs b/examples/spatial.rs new file mode 100644 index 0000000..edabead --- /dev/null +++ b/examples/spatial.rs @@ -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(); +} diff --git a/src/lib.rs b/src/lib.rs index 1c344e7..6c7c3a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/source/channel_volume.rs b/src/source/channel_volume.rs new file mode 100644 index 0000000..e3fb2c6 --- /dev/null +++ b/src/source/channel_volume.rs @@ -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 + where I: Source, + I::Item: Sample +{ + input: I, + // Channel number is used as index for amplification value. + channel_volumes: Vec, + // Current listener being processed. + current_channel: usize, + current_sample: I::Item, +} + +impl ChannelVolume + where I: Source, + I::Item: Sample +{ + pub fn new(mut input: I, channel_volumes: Vec) -> ChannelVolume + 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 Iterator for ChannelVolume + where I: Source, + I::Item: Sample +{ + type Item = I::Item; + + #[inline] + fn next(&mut self) -> Option { + // 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) { + self.input.size_hint() + } +} + +impl ExactSizeIterator for ChannelVolume + where I: Source + ExactSizeIterator, + I::Item: Sample +{ +} + +impl Source for ChannelVolume + where I: Source, + I::Item: Sample +{ + #[inline] + fn current_frame_len(&self) -> Option { + 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 { + self.input.total_duration() + } +} diff --git a/src/source/mod.rs b/src/source/mod.rs index e6db778..ce55e80 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -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; diff --git a/src/source/spatial.rs b/src/source/spatial.rs new file mode 100644 index 0000000..d1a8e2d --- /dev/null +++ b/src/source/spatial.rs @@ -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 + where I: Source, + I::Item: Sample + Debug +{ + input: ChannelVolume, +} + +impl Spatial + 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 + 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 Iterator for Spatial + where I: Source, + I::Item: Sample + Debug +{ + type Item = I::Item; + + #[inline] + fn next(&mut self) -> Option { + self.input.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.input.size_hint() + } +} + +impl ExactSizeIterator for Spatial + where I: Source + ExactSizeIterator, + I::Item: Sample + Debug +{ +} + +impl Source for Spatial + where I: Source, + I::Item: Sample + Debug +{ + #[inline] + fn current_frame_len(&self) -> Option { + 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 { + self.input.total_duration() + } +} diff --git a/src/spatial_sink.rs b/src/spatial_sink.rs new file mode 100644 index 0000000..94b5370 --- /dev/null +++ b/src/spatial_sink.rs @@ -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>, +} + +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(&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(); + } +}