From 223f963d85f8bcd0e371f63c6434df11cdc50d2e Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Wed, 1 Aug 2018 14:43:20 -0700 Subject: [PATCH 01/13] Add AudioListenerNode --- audio/src/block.rs | 8 ++++ audio/src/graph.rs | 32 ++++++++++++++- audio/src/lib.rs | 1 + audio/src/listener.rs | 90 +++++++++++++++++++++++++++++++++++++++++++ audio/src/node.rs | 8 +++- audio/src/param.rs | 32 +++++++++++++++ 6 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 audio/src/listener.rs diff --git a/audio/src/block.rs b/audio/src/block.rs index 2c6a479f..4c5def33 100644 --- a/audio/src/block.rs +++ b/audio/src/block.rs @@ -82,6 +82,14 @@ impl Block { } } + pub fn for_channels_explicit(channels: u8) -> Self { + Block { + channels, + repeat: true, + buffer: vec![0.; FRAMES_PER_BLOCK_USIZE * channels as usize] + } + } + /// This provides the entire buffer as a mutable slice of u8 pub fn as_mut_byte_slice(&mut self) -> &mut [u8] { self.data_mut().as_mut_byte_slice().expect("casting failed") diff --git a/audio/src/graph.rs b/audio/src/graph.rs index ffeecb09..595ff5f0 100644 --- a/audio/src/graph.rs +++ b/audio/src/graph.rs @@ -1,6 +1,7 @@ use param::ParamType; use block::{Block, Chunk}; use destination_node::DestinationNode; +use listener::AudioListenerNode; use node::{AudioNodeEngine, BlockInfo, ChannelCountMode, ChannelInterpretation}; use petgraph::graph::DefaultIx; use petgraph::stable_graph::NodeIndex; @@ -26,6 +27,9 @@ impl NodeId { pub fn output(self, port: u32) -> PortId { PortId(self, PortIndex::Port(port)) } + pub(crate) fn listener(self) -> PortId { + PortId(self, PortIndex::Listener(())) + } } /// A zero-indexed "port" for a node. Most nodes have one @@ -40,7 +44,10 @@ impl NodeId { #[derive(Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Debug)] pub enum PortIndex { Port(u32), - Param(Kind::ParamId) + Param(Kind::ParamId), + /// special variant only used for the implicit connection + /// from listeners to params + Listener(Kind::Listener) } impl PortId { @@ -52,6 +59,8 @@ impl PortId { pub trait PortKind { type ParamId: Copy + Eq + PartialEq + Ord + PartialOrd + hash::Hash + fmt::Debug; + type Listener: Copy + Eq + PartialEq + Ord + + PartialOrd + hash::Hash + fmt::Debug; } /// An identifier for a port. @@ -69,6 +78,7 @@ pub struct OutputPort; impl PortKind for InputPort { type ParamId = ParamType; + type Listener = (); } impl PortKind for OutputPort { @@ -77,12 +87,14 @@ impl PortKind for OutputPort { // taking up no extra discriminant space and eliminating PortIndex::Param // branches entirely from the compiled code type ParamId = !; + type Listener = !; } pub struct AudioGraph { graph: StableGraph, dest_id: NodeId, + listener_id: NodeId, } pub(crate) struct Node { @@ -148,7 +160,8 @@ impl AudioGraph { pub fn new(channel_count: u8) -> Self { let mut graph = StableGraph::new(); let dest_id = NodeId(graph.add_node(Node::new(Box::new(DestinationNode::new(channel_count))))); - AudioGraph { graph, dest_id } + let listener_id = NodeId(graph.add_node(Node::new(Box::new(AudioListenerNode::new())))); + AudioGraph { graph, dest_id, listener_id } } /// Create a node, obtain its id @@ -308,6 +321,18 @@ impl AudioGraph { self.dest_id } + /// Get the id of the AudioListener in this graph + /// + /// All graphs have a single listener, with no ports (but nine AudioParams) + /// + /// N.B. The listener actually has a single output port containing + /// its position data for the block, however this should + /// not be exposed to the DOM. + pub fn listener_id(&self) -> NodeId { + self.listener_id + } + + /// For a given block, process all the data on this graph pub fn process(&mut self, info: &BlockInfo) -> Chunk { // DFS post order: Children are processed before their parent, @@ -360,6 +385,9 @@ impl AudioGraph { block.mix(1, ChannelInterpretation::Speakers); curr.get_param(param).add_block(block) } + PortIndex::Listener(_) => { + curr.set_listenerdata(block) + } } } } diff --git a/audio/src/lib.rs b/audio/src/lib.rs index 36489454..a7519fa0 100644 --- a/audio/src/lib.rs +++ b/audio/src/lib.rs @@ -19,6 +19,7 @@ pub mod decoder; pub mod destination_node; pub mod gain_node; pub mod graph; +pub mod listener; pub mod node; pub mod offline_sink; pub mod oscillator_node; diff --git a/audio/src/listener.rs b/audio/src/listener.rs new file mode 100644 index 00000000..224dc30b --- /dev/null +++ b/audio/src/listener.rs @@ -0,0 +1,90 @@ +use block::{Block, Chunk}; +use node::{AudioNodeEngine, BlockInfo}; +use node::{AudioNodeType, ChannelInfo}; +use param::{Param, ParamDir, ParamType}; + +/// AudioListeners are fake nodes; from the user's point of view they're +/// a non-node entity with zero inputs and outputs, but with AudioParams +/// that can be manipulated. +/// +/// Internally, PannerNodes all have an implicit PortIndex::Listener connection +/// from a hidden output port on AudioListeners that contains all the position data. +/// +/// This encodes the otherwise implicit dependency between AudioListeners and PannerNodes +/// so that if there is a cycle involving panner nodes and the audio params on the listener, +/// the cycle breaking algorithm can deal with it. +#[derive(AudioNodeCommon)] +pub(crate) struct AudioListenerNode { + channel_info: ChannelInfo, + position_x: Param, + position_y: Param, + position_z: Param, + forward_x: Param, + forward_y: Param, + forward_z: Param, + up_x: Param, + up_y: Param, + up_z: Param, +} + +impl AudioListenerNode { + pub fn new() -> Self { + Self { + channel_info: Default::default(), + position_x: Param::new(0.), + position_y: Param::new(0.), + position_z: Param::new(0.), + forward_x: Param::new(0.), + forward_y: Param::new(0.), + forward_z: Param::new(-1.), + up_x: Param::new(0.), + up_y: Param::new(1.), + up_z: Param::new(0.), + } + } +} + +impl AudioNodeEngine for AudioListenerNode { + fn node_type(&self) -> AudioNodeType { + AudioNodeType::AudioListenerNode + } + + fn process(&mut self, mut inputs: Chunk, info: &BlockInfo) -> Chunk { + debug_assert!(inputs.len() == 0); + + // XXXManishearth in the common case when all of these are constant, + // it would be nice to instead send just the constant values down + let mut block = Block::for_channels_explicit(9); + self.position_x.flush_to_block(info, block.data_chan_mut(0)); + self.position_y.flush_to_block(info, block.data_chan_mut(1)); + self.position_z.flush_to_block(info, block.data_chan_mut(2)); + self.forward_x.flush_to_block(info, block.data_chan_mut(3)); + self.forward_y.flush_to_block(info, block.data_chan_mut(4)); + self.forward_z.flush_to_block(info, block.data_chan_mut(5)); + self.up_x.flush_to_block(info, block.data_chan_mut(6)); + self.up_y.flush_to_block(info, block.data_chan_mut(7)); + self.up_z.flush_to_block(info, block.data_chan_mut(8)); + + inputs.blocks.push(block); + inputs + } + + fn input_count(&self) -> u32 { + 0 + } + + fn get_param(&mut self, id: ParamType) -> &mut Param { + match id { + ParamType::Position(ParamDir::X) => &mut self.position_x, + ParamType::Position(ParamDir::Y) => &mut self.position_y, + ParamType::Position(ParamDir::Z) => &mut self.position_z, + ParamType::Forward(ParamDir::X) => &mut self.forward_x, + ParamType::Forward(ParamDir::Y) => &mut self.forward_y, + ParamType::Forward(ParamDir::Z) => &mut self.forward_z, + ParamType::Up(ParamDir::X) => &mut self.up_x, + ParamType::Up(ParamDir::Y) => &mut self.up_y, + ParamType::Up(ParamDir::Z) => &mut self.up_z, + _ => panic!("Unknown param {:?} for AudioListenerNode", id), + } + } +} diff --git a/audio/src/node.rs b/audio/src/node.rs index 5a8f13a8..87cbae93 100644 --- a/audio/src/node.rs +++ b/audio/src/node.rs @@ -1,4 +1,4 @@ -use block::{Chunk, Tick}; +use block::{Block, Chunk, Tick}; use buffer_source_node::{AudioBufferSourceNodeMessage, AudioBufferSourceNodeOptions}; use channel_node::ChannelNodeOptions; use gain_node::GainNodeOptions; @@ -33,6 +33,8 @@ pub enum AudioNodeInit { /// Type of AudioNodeEngine. #[derive(Debug, Clone, Copy)] pub enum AudioNodeType { + /// Not a constructable node + AudioListenerNode, AnalyserNode, BiquadFilterNode, AudioBuffer, @@ -167,6 +169,10 @@ pub(crate) trait AudioNodeEngine: Send + AudioNodeCommon { fn get_param(&mut self, _: ParamType) -> &mut Param { panic!("No params on node {:?}", self.node_type()) } + + fn set_listenerdata(&mut self, _: Block) { + panic!("{:?} can't accept listener connections") + } } pub enum AudioNodeMessage { diff --git a/audio/src/param.rs b/audio/src/param.rs index 416910ae..a1c654fe 100644 --- a/audio/src/param.rs +++ b/audio/src/param.rs @@ -1,3 +1,4 @@ +use block::FRAMES_PER_BLOCK_USIZE; use block::Block; use block::Tick; use node::BlockInfo; @@ -8,6 +9,14 @@ pub enum ParamType { Detune, Gain, PlaybackRate, + Position(ParamDir), + Forward(ParamDir), + Up(ParamDir), +} + +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] +pub enum ParamDir { + X, Y, Z } /// An AudioParam. @@ -206,6 +215,29 @@ impl Param { } self.blocks.push(block) } + + /// Flush an entire block of values into a buffer + /// + /// Only for use with AudioListener. + /// + /// Invariant: `block` must be a FRAMES_PER_BLOCK length array filled with silence + pub(crate) fn flush_to_block(&mut self, info: &BlockInfo, block: &mut [f32]) { + // common case + if self.current_event >= self.events.len() && self.blocks.is_empty() { + if self.val != 0. { + for tick in 0..(FRAMES_PER_BLOCK_USIZE) { + // ideally this can use some kind of vectorized memset() + block[tick] = self.val; + } + } + // if the value is zero, our buffer is already zeroed + } else { + for tick in 0..(FRAMES_PER_BLOCK_USIZE) { + self.update(info, Tick(tick as u64)); + block[tick] = self.val; + } + } + } } #[derive(Clone, Copy, Eq, PartialEq, Debug)] From 99803f5a61d14da2f566896cfeb3b84d816d9297 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Fri, 3 Aug 2018 15:32:35 -0700 Subject: [PATCH 02/13] Add PannerNode --- Cargo.lock | 1 + audio/Cargo.toml | 1 + audio/src/block.rs | 48 ++++- audio/src/lib.rs | 4 +- audio/src/node.rs | 3 +- audio/src/panner_node.rs | 357 +++++++++++++++++++++++++++++++++++++ audio/src/param.rs | 1 + audio/src/render_thread.rs | 13 +- 8 files changed, 424 insertions(+), 4 deletions(-) create mode 100644 audio/src/panner_node.rs diff --git a/Cargo.lock b/Cargo.lock index a4ba3975..25d9ad17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1135,6 +1135,7 @@ name = "servo-media-audio" version = "0.1.0" dependencies = [ "byte-slice-cast 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "petgraph 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "servo_media_derive 0.1.0", diff --git a/audio/Cargo.toml b/audio/Cargo.toml index a7b0d926..cefced47 100644 --- a/audio/Cargo.toml +++ b/audio/Cargo.toml @@ -10,6 +10,7 @@ name = "servo_media_audio" [dependencies] smallvec = "0.6.1" servo_media_derive = { path = "../servo-media-derive" } +euclid = "0.19.0" [dependencies.petgraph] version = "0.4.12" diff --git a/audio/src/block.rs b/audio/src/block.rs index 4c5def33..193fdc84 100644 --- a/audio/src/block.rs +++ b/audio/src/block.rs @@ -1,8 +1,10 @@ +use euclid::Vector3D; use byte_slice_cast::*; use graph::{PortIndex, PortKind}; use node::ChannelInterpretation; use smallvec::SmallVec; use std::f32::consts::SQRT_2; +use std::iter::Step; use std::mem; use std::ops::*; @@ -403,7 +405,7 @@ impl Block { } /// Resize to add or remove channels, fill extra channels with silence - fn resize_silence(&mut self, channels: u8) { + pub fn resize_silence(&mut self, channels: u8) { self.explicit_repeat(); self.buffer .resize(FRAMES_PER_BLOCK_USIZE * channels as usize, 0.); @@ -437,6 +439,29 @@ impl Block { pub fn is_empty(&self) -> bool { self.buffer.is_empty() } + + /// Get the position, forward, and up vectors for a given + /// AudioListener-produced block + pub fn listener_data(&self, frame: Tick) -> (Vector3D, Vector3D, Vector3D) { + let frame = frame.0 as usize; + ( + Vector3D::new( + self.data_chan_frame(frame, 0), + self.data_chan_frame(frame, 1), + self.data_chan_frame(frame, 2), + ), + Vector3D::new( + self.data_chan_frame(frame, 3), + self.data_chan_frame(frame, 4), + self.data_chan_frame(frame, 5), + ), + Vector3D::new( + self.data_chan_frame(frame, 6), + self.data_chan_frame(frame, 7), + self.data_chan_frame(frame, 8), + ), + ) + } } /// An iterator over frames in a block @@ -577,6 +602,27 @@ impl Div for Tick { } } +impl Step for Tick { + fn steps_between(start: &Self, end: &Self) -> Option { + Step::steps_between(&start.0, &end.0) + } + fn replace_one(&mut self) -> Self { + Tick(self.0.replace_one()) + } + fn replace_zero(&mut self) -> Self { + Tick(self.0.replace_zero()) + } + fn add_one(&self) -> Self { + Tick(self.0.add_one()) + } + fn sub_one(&self) -> Self { + Tick(self.0.sub_one()) + } + fn add_usize(&self, n: usize) -> Option { + self.0.add_usize(n).map(Tick) + } +} + impl Tick { pub fn from_time(time: f64, rate: f32) -> Tick { Tick((0.5 + time * rate as f64).floor() as u64) diff --git a/audio/src/lib.rs b/audio/src/lib.rs index a7519fa0..2eb9aa70 100644 --- a/audio/src/lib.rs +++ b/audio/src/lib.rs @@ -1,10 +1,11 @@ #![feature(cell_update)] -#![feature(fnbox, never_type)] +#![feature(fnbox, never_type, step_trait)] #[macro_use] extern crate servo_media_derive; extern crate byte_slice_cast; +extern crate euclid; extern crate num_traits; extern crate petgraph; extern crate smallvec; @@ -23,6 +24,7 @@ pub mod listener; pub mod node; pub mod offline_sink; pub mod oscillator_node; +pub mod panner_node; pub mod param; pub mod render_thread; pub mod sink; diff --git a/audio/src/node.rs b/audio/src/node.rs index 87cbae93..d402d861 100644 --- a/audio/src/node.rs +++ b/audio/src/node.rs @@ -3,6 +3,7 @@ use buffer_source_node::{AudioBufferSourceNodeMessage, AudioBufferSourceNodeOpti use channel_node::ChannelNodeOptions; use gain_node::GainNodeOptions; use oscillator_node::OscillatorNodeOptions; +use panner_node::PannerNodeOptions; use param::{Param, ParamRate, ParamType, UserAutomationEvent}; use std::boxed::FnBox; use std::sync::mpsc::Sender; @@ -23,7 +24,7 @@ pub enum AudioNodeInit { GainNode(GainNodeOptions), IIRFilterNode, OscillatorNode(OscillatorNodeOptions), - PannerNode, + PannerNode(PannerNodeOptions), PeriodicWave, ScriptProcessorNode, StereoPannerNode, diff --git a/audio/src/panner_node.rs b/audio/src/panner_node.rs new file mode 100644 index 00000000..cf1e25f3 --- /dev/null +++ b/audio/src/panner_node.rs @@ -0,0 +1,357 @@ +use euclid::Vector3D; +use block::{Block, Chunk, FRAMES_PER_BLOCK, Tick}; +use node::{AudioNodeEngine, BlockInfo}; +use node::{AudioNodeType, ChannelInfo, ChannelCountMode, ChannelInterpretation}; +use param::{Param, ParamDir, ParamType}; +use std::f32::consts::PI; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum PanningModel { + EqualPower, + HRTF +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum DistanceModel { + Linear, + Inverse, + Exponential +} + +#[derive(Copy, Clone, Debug)] +pub struct PannerNodeOptions { + pub panning_model: PanningModel, + pub distance_model: DistanceModel, + pub position_x: f32, + pub position_y: f32, + pub position_z: f32, + pub orientation_x: f32, + pub orientation_y: f32, + pub orientation_z: f32, + pub ref_distance: f64, + pub max_distance: f64, + pub rolloff_factor: f64, + pub cone_inner_angle: f64, + pub cone_outer_angle: f64, + pub cone_outer_gain: f64, +} + +impl Default for PannerNodeOptions { + fn default() -> Self { + PannerNodeOptions { + panning_model: PanningModel::EqualPower, + distance_model: DistanceModel::Linear, + position_x: 0., + position_y: 0., + position_z: 0., + orientation_x: 1., + orientation_y: 0., + orientation_z: 0., + ref_distance: 1., + max_distance: 10000., + rolloff_factor: 1., + cone_inner_angle: 360., + cone_outer_angle: 360., + cone_outer_gain: 0., + } + } +} + +#[derive(AudioNodeCommon)] +pub(crate) struct PannerNode { + channel_info: ChannelInfo, + panning_model: PanningModel, + distance_model: DistanceModel, + position_x: Param, + position_y: Param, + position_z: Param, + orientation_x: Param, + orientation_y: Param, + orientation_z: Param, + ref_distance: f64, + max_distance: f64, + rolloff_factor: f64, + cone_inner_angle: f64, + cone_outer_angle: f64, + cone_outer_gain: f64, + listener_data: Option, +} + +impl PannerNode { + pub fn new(options: PannerNodeOptions) -> Self { + Self { + channel_info: ChannelInfo { + count: 2, + mode: ChannelCountMode::Max, + interpretation: ChannelInterpretation::Speakers, + }, + panning_model: options.panning_model, + distance_model: options.distance_model, + position_x: Param::new(options.position_x), + position_y: Param::new(options.position_y), + position_z: Param::new(options.position_z), + orientation_x: Param::new(options.orientation_x), + orientation_y: Param::new(options.orientation_y), + orientation_z: Param::new(options.orientation_z), + ref_distance: options.ref_distance, + max_distance: options.max_distance, + rolloff_factor: options.rolloff_factor, + cone_inner_angle: options.cone_inner_angle, + cone_outer_angle: options.cone_outer_angle, + cone_outer_gain: options.cone_outer_gain, + listener_data: None, + } + } + + pub fn update_parameters(&mut self, info: &BlockInfo, tick: Tick) -> bool { + self.position_x.update(info, tick) || + self.position_y.update(info, tick) || + self.position_z.update(info, tick) || + self.orientation_x.update(info, tick) || + self.orientation_y.update(info, tick) || + self.orientation_z.update(info, tick) + } + + /// Computes azimuthm elevation, and distance of source with respect to a + /// given AudioListener's position, forward, up vectors + /// in degrees + /// + /// https://webaudio.github.io/web-audio-api/#azimuth-elevation + /// https://webaudio.github.io/web-audio-api/#Spatialization-distance-effects + fn azimuth_elevation_distance(&self, + listener: (Vector3D, Vector3D, Vector3D)) + -> (f32, f32, f64) { + let (listener_position, listener_forward, listener_up) = listener; + let source_position = Vector3D::new( + self.position_x.value(), + self.position_y.value(), + self.position_z.value() + ); + + // degenerate case + if source_position == listener_position { + return (0., 0., 0.) + } + + let diff = source_position - listener_position; + let distance = diff.length(); + let source_listener = diff.normalize(); + let listener_right = listener_forward.cross(listener_up); + let listener_right_norm = listener_right.normalize(); + let listener_forward_norm = listener_forward.normalize(); + + let up = listener_right_norm.cross(listener_forward_norm); + + let up_projection = source_listener.dot(up); + let projected_source = (source_listener - up * up_projection).normalize(); + let mut azimuth = 180. * projected_source.dot(listener_right_norm).acos() / PI; + + let front_back = projected_source.dot(listener_forward_norm); + if front_back < 0. { + azimuth = 360. - azimuth; + } + if (azimuth >= 0.) && (azimuth <= 270.) { + azimuth = 90. - azimuth; + } else { + azimuth = 450. - azimuth; + } + + let mut elevation = 90. - 180. * source_listener.dot(up).acos() / PI; + + if elevation > 90. { + elevation = 180. - elevation; + } else if elevation < -90. { + elevation = -180. - elevation; + } + + (azimuth, elevation, distance as f64) + } + + fn cone_gain(&self, + listener: (Vector3D, Vector3D, Vector3D)) + -> f64 { + let (listener_position, _, _) = listener; + let source_position = Vector3D::new( + self.position_x.value(), + self.position_y.value(), + self.position_z.value() + ); + let source_orientation = Vector3D::new( + self.orientation_x.value(), + self.orientation_y.value(), + self.orientation_z.value() + ); + + if source_orientation == Vector3D::zero() || + (self.cone_inner_angle == 360. && self.cone_outer_angle == 360.) { + return 0. + } + + let normalized_source_orientation = source_orientation.normalize(); + + let source_to_listener = (source_position - listener_position).normalize(); + // Angle between the source orientation vector and the source-listener vector + let angle = 180. * source_to_listener.dot(normalized_source_orientation) / PI; + let abs_angle = angle.abs() as f64; + + // Divide by 2 here since API is entire angle (not half-angle) + let abs_inner_angle = self.cone_inner_angle.abs() / 2.; + let abs_outer_angle = self.cone_outer_angle.abs() / 2.; + + if abs_angle < abs_inner_angle { + // no attenuation + 1. + } else if abs_angle >= abs_outer_angle { + // max attenuation + self.cone_outer_gain + } else { + // gain changes linearly from 1 to cone_outer_gain + // as we go from inner to outer + let x = (abs_angle - abs_inner_angle) / (abs_outer_angle - abs_inner_angle); + (1. - x) + self.cone_outer_gain * x + } + } + + fn linear_distance(&self, mut distance: f64) -> f64 { + if distance > self.max_distance { + distance = self.max_distance; + } + if distance < self.ref_distance { + distance = self.ref_distance; + } + let denom = self.max_distance - self.ref_distance; + 1. - self.rolloff_factor * (distance - self.ref_distance) / denom + } + + fn inverse_distance(&self, mut distance: f64) -> f64 { + if distance < self.ref_distance { + distance = self.ref_distance; + } + let denom = self.ref_distance + self.rolloff_factor * (distance - self.ref_distance); + self.ref_distance / denom + } + + fn exponential_distance(&self, mut distance: f64) -> f64 { + if distance < self.ref_distance { + distance = self.ref_distance; + } + + (distance / self.ref_distance).powf(-self.rolloff_factor) + } + + fn distance_gain_fn(&self) -> fn(&Self, f64) -> f64 { + match self.distance_model { + DistanceModel::Linear => |x, d| x.linear_distance(d), + DistanceModel::Inverse => |x, d| x.inverse_distance(d), + DistanceModel::Exponential => |x, d| x.exponential_distance(d), + } + } +} + +impl AudioNodeEngine for PannerNode { + fn node_type(&self) -> AudioNodeType { + AudioNodeType::PannerNode + } + + fn process(&mut self, mut inputs: Chunk, info: &BlockInfo) -> Chunk { + debug_assert!(inputs.len() == 1); + + let listener_data = if let Some(listener_data) = self.listener_data.take() { + listener_data + } else { + return inputs + }; + + { + let block = &mut inputs.blocks[0]; + + block.explicit_repeat(); + + let mono = if block.chan_count() == 1 { + block.resize_silence(2); + true + } else { + debug_assert!(block.chan_count() == 2); + false + }; + + let distance_gain_fn = self.distance_gain_fn(); + + if self.panning_model == PanningModel::HRTF { + unimplemented!() + } else { + let (l, r) = block.data_mut().split_at_mut(FRAMES_PER_BLOCK.0 as usize); + for frame in Tick(0)..FRAMES_PER_BLOCK { + self.update_parameters(info, frame); + let data = listener_data.listener_data(frame); + let (mut azimuth, _elev, dist) = self.azimuth_elevation_distance(data); + let distance_gain = distance_gain_fn(self, dist); + let cone_gain = self.cone_gain(data); + + // https://webaudio.github.io/web-audio-api/#Spatialization-equal-power-panning + + // clamp to [-180, 180], then wrap to [-90, 90] + if azimuth < -180. { + azimuth = -180. + } else if azimuth > 180. { + azimuth = 180. + } + if azimuth < -90. { + azimuth = -180. - azimuth; + } else if azimuth > 90. { + azimuth = 180. - azimuth; + } + + let x = if mono { + (azimuth + 90.) / 180. + } else if azimuth <= 0. { + (azimuth + 90. / 90.) + } else { + azimuth / 90. + }; + let x = x * PI / 2.; + + let gain_l = x.cos(); + let gain_r = x.sin(); + + let index = frame.0 as usize; + if mono { + let input = l[index]; + l[index] = input * gain_l; + r[index] = input * gain_r; + } else if azimuth <= 0. { + l[index] = l[index] + r[index] * gain_l; + r[index] = r[index] * gain_r; + } else { + r[index] = r[index] + l[index] * gain_r; + l[index] = l[index] * gain_r; + } + l[index] = l[index] * distance_gain as f32 * cone_gain as f32; + r[index] = r[index] * distance_gain as f32 * cone_gain as f32; + } + } + } + + inputs + } + + fn input_count(&self) -> u32 { + 1 + } + + fn get_param(&mut self, id: ParamType) -> &mut Param { + match id { + ParamType::Position(ParamDir::X) => &mut self.position_x, + ParamType::Position(ParamDir::Y) => &mut self.position_y, + ParamType::Position(ParamDir::Z) => &mut self.position_z, + ParamType::Orientation(ParamDir::X) => &mut self.orientation_x, + ParamType::Orientation(ParamDir::Y) => &mut self.orientation_y, + ParamType::Orientation(ParamDir::Z) => &mut self.orientation_z, + _ => panic!("Unknown param {:?} for PannerNode", id), + } + } + + fn set_listenerdata(&mut self, data: Block) { + self.listener_data = Some(data); + } +} diff --git a/audio/src/param.rs b/audio/src/param.rs index a1c654fe..fc77a243 100644 --- a/audio/src/param.rs +++ b/audio/src/param.rs @@ -12,6 +12,7 @@ pub enum ParamType { Position(ParamDir), Forward(ParamDir), Up(ParamDir), + Orientation(ParamDir), } #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] diff --git a/audio/src/render_thread.rs b/audio/src/render_thread.rs index 9c9c3c66..d45e6723 100644 --- a/audio/src/render_thread.rs +++ b/audio/src/render_thread.rs @@ -8,6 +8,7 @@ use node::BlockInfo; use node::{AudioNodeEngine, AudioNodeInit, AudioNodeMessage}; use offline_sink::OfflineAudioSink; use oscillator_node::OscillatorNode; +use panner_node::PannerNode; use sink::AudioSink; use std::sync::mpsc::{Receiver, Sender}; use AudioBackend; @@ -126,11 +127,16 @@ impl AudioRenderThread { make_render_thread_state_change!(suspend, Suspended, stop); fn create_node(&mut self, node_type: AudioNodeInit) -> NodeId { + let mut needs_listener = false; let node: Box = match node_type { AudioNodeInit::AudioBufferSourceNode(options) => { Box::new(AudioBufferSourceNode::new(options)) } AudioNodeInit::GainNode(options) => Box::new(GainNode::new(options)), + AudioNodeInit::PannerNode(options) => { + needs_listener = true; + Box::new(PannerNode::new(options)) + }, AudioNodeInit::OscillatorNode(options) => Box::new(OscillatorNode::new(options)), AudioNodeInit::ChannelMergerNode(options) => Box::new(ChannelMergerNode::new(options)), AudioNodeInit::ChannelSplitterNode(options) => { @@ -138,7 +144,12 @@ impl AudioRenderThread { } _ => unimplemented!(), }; - self.graph.add_node(node) + let id = self.graph.add_node(node); + if needs_listener { + let listener = self.graph.listener_id().output(0); + self.graph.add_edge(listener, id.listener()); + } + id } fn connect_ports(&mut self, output: PortId, input: PortId) { From ec9a6cc8ca5c4dc6da7f03554acf0ad5d01b0e54 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 6 Aug 2018 16:44:07 -0700 Subject: [PATCH 03/13] Add getter for listener --- audio/src/context.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/audio/src/context.rs b/audio/src/context.rs index 8c09ea79..c23257a2 100644 --- a/audio/src/context.rs +++ b/audio/src/context.rs @@ -108,6 +108,7 @@ pub struct AudioContext { /// The identifier of an AudioDestinationNode with a single input /// representing the final destination for all audio. dest_node: NodeId, + listener: NodeId, backend: PhantomData, } @@ -123,6 +124,7 @@ impl AudioContext { let sender_ = sender.clone(); let graph = AudioGraph::new(channels); let dest_node = graph.dest_id(); + let listener = graph.listener_id(); Builder::new() .name("AudioRenderThread".to_owned()) .spawn(move || { @@ -135,6 +137,7 @@ impl AudioContext { state: Cell::new(ProcessingState::Suspended), sample_rate, dest_node, + listener, backend: PhantomData, } } @@ -147,6 +150,10 @@ impl AudioContext { self.dest_node } + pub fn listener(&self) -> NodeId { + self.listener + } + pub fn current_time(&self) -> f64 { let (tx, rx) = mpsc::channel(); let _ = self.sender.send(AudioRenderThreadMsg::GetCurrentTime(tx)); From 74e00a5a5f6bdf01a9b857dab8638ef90c40febc Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 6 Aug 2018 16:53:24 -0700 Subject: [PATCH 04/13] Add example for panner --- examples/Cargo.toml | 4 ++++ examples/panner.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 examples/panner.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 05b49180..68f00cc6 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -30,6 +30,10 @@ path = "channelsum.rs" name = "offline" path = "offline_context.rs" +[[bin]] +name = "panner" +path = "panner.rs" + [[bin]] name = "params" path = "params.rs" diff --git a/examples/panner.rs b/examples/panner.rs new file mode 100644 index 00000000..f9e2da73 --- /dev/null +++ b/examples/panner.rs @@ -0,0 +1,49 @@ +extern crate servo_media; + +use servo_media::audio::panner_node::PannerNodeOptions; +use servo_media::audio::node::{AudioNodeInit, AudioNodeMessage, AudioScheduledSourceNodeMessage}; +use servo_media::audio::param::{ParamDir, ParamType, RampKind, UserAutomationEvent}; +use servo_media::ServoMedia; +use std::sync::Arc; +use std::{thread, time}; + +fn run_example(servo_media: Arc) { + let context = servo_media.create_audio_context(Default::default()); + let dest = context.dest_node(); + let listener = context.listener(); + let osc = context.create_node(AudioNodeInit::OscillatorNode(Default::default())); + let mut options = PannerNodeOptions::default(); + options.cone_outer_angle = 0.; + options.position_x = 100.; + let panner = context.create_node(AudioNodeInit::PannerNode(options)); + context.connect_ports(osc.output(0), panner.input(0)); + context.connect_ports(panner.output(0), dest.input(0)); + let _ = context.resume(); + context.message_node( + osc, + AudioNodeMessage::AudioScheduledSourceNode(AudioScheduledSourceNodeMessage::Start(0.)), + ); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Orientation(ParamDir::X), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 0., 1.0), + ), + ); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Orientation(ParamDir::Z), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 1., 1.0), + ), + ); + thread::sleep(time::Duration::from_millis(5000)); +} + +fn main() { + if let Ok(servo_media) = ServoMedia::get() { + run_example(servo_media); + } else { + unreachable!(); + } +} From d787963601133831ce6e220e2296f40b182fbcd6 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 6 Aug 2018 17:31:04 -0700 Subject: [PATCH 05/13] Fixup Block::for_channels_explicit --- audio/src/block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio/src/block.rs b/audio/src/block.rs index 193fdc84..0b3d890c 100644 --- a/audio/src/block.rs +++ b/audio/src/block.rs @@ -87,7 +87,7 @@ impl Block { pub fn for_channels_explicit(channels: u8) -> Self { Block { channels, - repeat: true, + repeat: false, buffer: vec![0.; FRAMES_PER_BLOCK_USIZE * channels as usize] } } From f0487819b6d98503793568ba6870841c4035902e Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 6 Aug 2018 17:38:58 -0700 Subject: [PATCH 06/13] explicit_repeat shouldn't choke on single-channel repeats --- audio/src/block.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/audio/src/block.rs b/audio/src/block.rs index 0b3d890c..04b5ffe2 100644 --- a/audio/src/block.rs +++ b/audio/src/block.rs @@ -132,13 +132,16 @@ impl Block { } pub fn explicit_repeat(&mut self) { - if self.repeat && self.channels > 1 { - let mut new = Vec::with_capacity(FRAMES_PER_BLOCK_USIZE * self.channels as usize); - for _ in 0..self.channels { - new.extend(&self.buffer) - } + if self.repeat { + debug_assert!(self.buffer.len() == FRAMES_PER_BLOCK_USIZE); + if self.channels > 1 { + let mut new = Vec::with_capacity(FRAMES_PER_BLOCK_USIZE * self.channels as usize); + for _ in 0..self.channels { + new.extend(&self.buffer) + } - self.buffer = new; + self.buffer = new; + } self.repeat = false; } else if self.is_silence() { self.buffer From c88686a1e7d6d2310e374dad94afc59bac1a3150 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 6 Aug 2018 17:50:34 -0700 Subject: [PATCH 07/13] Fixups in panning algorithm --- audio/src/panner_node.rs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/audio/src/panner_node.rs b/audio/src/panner_node.rs index cf1e25f3..adad27c9 100644 --- a/audio/src/panner_node.rs +++ b/audio/src/panner_node.rs @@ -5,6 +5,16 @@ use node::{AudioNodeType, ChannelInfo, ChannelCountMode, ChannelInterpretation}; use param::{Param, ParamDir, ParamType}; use std::f32::consts::PI; +// .normalize(), but it takes into account zero vectors +fn normalize_zero(v: Vector3D) -> Vector3D { + let len = v.length(); + if len == 0. { + v + } else { + v / len + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum PanningModel { EqualPower, @@ -135,15 +145,15 @@ impl PannerNode { let diff = source_position - listener_position; let distance = diff.length(); - let source_listener = diff.normalize(); + let source_listener = normalize_zero(diff); let listener_right = listener_forward.cross(listener_up); - let listener_right_norm = listener_right.normalize(); - let listener_forward_norm = listener_forward.normalize(); + let listener_right_norm = normalize_zero(listener_right); + let listener_forward_norm = normalize_zero(listener_forward); let up = listener_right_norm.cross(listener_forward_norm); let up_projection = source_listener.dot(up); - let projected_source = (source_listener - up * up_projection).normalize(); + let projected_source = normalize_zero(source_listener - up * up_projection); let mut azimuth = 180. * projected_source.dot(listener_right_norm).acos() / PI; let front_back = projected_source.dot(listener_forward_norm); @@ -187,9 +197,9 @@ impl PannerNode { return 0. } - let normalized_source_orientation = source_orientation.normalize(); + let normalized_source_orientation = normalize_zero(source_orientation); - let source_to_listener = (source_position - listener_position).normalize(); + let source_to_listener = normalize_zero(source_position - listener_position); // Angle between the source orientation vector and the source-listener vector let angle = 180. * source_to_listener.dot(normalized_source_orientation) / PI; let abs_angle = angle.abs() as f64; @@ -324,10 +334,10 @@ impl AudioNodeEngine for PannerNode { r[index] = r[index] * gain_r; } else { r[index] = r[index] + l[index] * gain_r; - l[index] = l[index] * gain_r; + l[index] = l[index] * gain_l; } - l[index] = l[index] * distance_gain as f32 * cone_gain as f32; - r[index] = r[index] * distance_gain as f32 * cone_gain as f32; + // l[index] = l[index] * distance_gain as f32 * cone_gain as f32; + // r[index] = r[index] * distance_gain as f32 * cone_gain as f32; } } } From 752d756c09ef1f4257f8e3a10f1b1d22ca83671f Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 6 Aug 2018 18:14:54 -0700 Subject: [PATCH 08/13] Keep gain positive --- audio/src/panner_node.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/audio/src/panner_node.rs b/audio/src/panner_node.rs index adad27c9..005fc086 100644 --- a/audio/src/panner_node.rs +++ b/audio/src/panner_node.rs @@ -321,8 +321,15 @@ impl AudioNodeEngine for PannerNode { }; let x = x * PI / 2.; - let gain_l = x.cos(); - let gain_r = x.sin(); + let mut gain_l = x.cos(); + let mut gain_r = x.sin(); + // 9. * PI / 2 is often slightly negative, clamp + if gain_l <= 0. {; + gain_l = 0. + } + if gain_r <= 0. { + gain_r = 0.; + } let index = frame.0 as usize; if mono { From 675dfd232a6c53f9806b1487053637368846fdc9 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 6 Aug 2018 18:35:33 -0700 Subject: [PATCH 09/13] exponential ramp edge case --- audio/src/param.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/audio/src/param.rs b/audio/src/param.rs index fc77a243..2e7af915 100644 --- a/audio/src/param.rs +++ b/audio/src/param.rs @@ -380,7 +380,16 @@ impl AutomationEvent { *value = event_start_value + (val - event_start_value) * progress; } RampKind::Exponential => { - *value = event_start_value * (val / event_start_value).powf(progress); + let ratio = val / event_start_value; + if event_start_value == 0. || ratio < 0. { + if time == current_tick { + *value = val; + } else { + *value = event_start_value; + } + } else { + *value = event_start_value * (ratio).powf(progress); + } } } true From 7b57271cc3fc1d718b3480429eab3291c28c9cab Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 6 Aug 2018 18:41:25 -0700 Subject: [PATCH 10/13] Uncomment distance and cone gain --- audio/src/panner_node.rs | 4 ++-- examples/panner.rs | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/audio/src/panner_node.rs b/audio/src/panner_node.rs index 005fc086..0c08a81a 100644 --- a/audio/src/panner_node.rs +++ b/audio/src/panner_node.rs @@ -343,8 +343,8 @@ impl AudioNodeEngine for PannerNode { r[index] = r[index] + l[index] * gain_r; l[index] = l[index] * gain_l; } - // l[index] = l[index] * distance_gain as f32 * cone_gain as f32; - // r[index] = r[index] * distance_gain as f32 * cone_gain as f32; + l[index] = l[index] * distance_gain as f32 * cone_gain as f32; + r[index] = r[index] * distance_gain as f32 * cone_gain as f32; } } } diff --git a/examples/panner.rs b/examples/panner.rs index f9e2da73..e33b4ffa 100644 --- a/examples/panner.rs +++ b/examples/panner.rs @@ -15,6 +15,8 @@ fn run_example(servo_media: Arc) { let mut options = PannerNodeOptions::default(); options.cone_outer_angle = 0.; options.position_x = 100.; + options.position_y = 0.; + options.position_z = 100.; let panner = context.create_node(AudioNodeInit::PannerNode(options)); context.connect_ports(osc.output(0), panner.input(0)); context.connect_ports(panner.output(0), dest.input(0)); @@ -27,14 +29,14 @@ fn run_example(servo_media: Arc) { panner, AudioNodeMessage::SetParam( ParamType::Orientation(ParamDir::X), - UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 0., 1.0), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 0., 3.0), ), ); context.message_node( panner, AudioNodeMessage::SetParam( ParamType::Orientation(ParamDir::Z), - UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 1., 1.0), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 1., 3.0), ), ); thread::sleep(time::Duration::from_millis(5000)); From dff9d138a0eaa7a6e22acef17e9e64fdaef235bb Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Mon, 6 Aug 2018 21:11:36 -0700 Subject: [PATCH 11/13] Improve panner example --- examples/panner.rs | 133 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 128 insertions(+), 5 deletions(-) diff --git a/examples/panner.rs b/examples/panner.rs index e33b4ffa..47dfe3fe 100644 --- a/examples/panner.rs +++ b/examples/panner.rs @@ -25,21 +25,144 @@ fn run_example(servo_media: Arc) { osc, AudioNodeMessage::AudioScheduledSourceNode(AudioScheduledSourceNodeMessage::Start(0.)), ); + // trace a square around your head twice context.message_node( panner, AudioNodeMessage::SetParam( - ParamType::Orientation(ParamDir::X), - UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 0., 3.0), + ParamType::Position(ParamDir::X), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, -100., 0.2), ), ); context.message_node( panner, AudioNodeMessage::SetParam( - ParamType::Orientation(ParamDir::Z), - UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 1., 3.0), + ParamType::Position(ParamDir::Z), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 100., 0.2), ), ); - thread::sleep(time::Duration::from_millis(5000)); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::X), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, -100., 0.4), + ), + ); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::Z), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, -100., 0.4), + ), + ); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::X), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 100., 0.6), + ), + ); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::Z), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, -100., 0.6), + ), + ); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::X), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 100., 0.8), + ), + ); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::Z), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 100., 0.8), + ), + ); + + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::X), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, -100., 1.0), + ), + ); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::Z), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 100., 1.0), + ), + ); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::X), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, -100., 1.2), + ), + ); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::Z), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, -100., 1.2), + ), + ); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::X), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 100., 1.4), + ), + ); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::Z), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, -100., 1.4), + ), + ); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::X), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 100., 1.6), + ), + ); + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::Z), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 100., 1.6), + ), + ); + // now it runs away + context.message_node( + panner, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::Z), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 10000., 3.), + ), + ); + context.message_node( + listener, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::Z), + UserAutomationEvent::SetValueAtTime(0., 3.), + ), + ); + // chase it + context.message_node( + listener, + AudioNodeMessage::SetParam( + ParamType::Position(ParamDir::Z), + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 10000., 4.), + ), + ); + thread::sleep(time::Duration::from_millis(4000)); } fn main() { From 0bab9a4c9b5922662d3c2a005ca50ea4fd974ed2 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Thu, 23 Aug 2018 08:54:47 -0700 Subject: [PATCH 12/13] Inverse is default --- audio/src/panner_node.rs | 2 +- examples/panner.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/audio/src/panner_node.rs b/audio/src/panner_node.rs index 0c08a81a..c5fe0393 100644 --- a/audio/src/panner_node.rs +++ b/audio/src/panner_node.rs @@ -50,7 +50,7 @@ impl Default for PannerNodeOptions { fn default() -> Self { PannerNodeOptions { panning_model: PanningModel::EqualPower, - distance_model: DistanceModel::Linear, + distance_model: DistanceModel::Inverse, position_x: 0., position_y: 0., position_z: 0., diff --git a/examples/panner.rs b/examples/panner.rs index 47dfe3fe..bad7f5e7 100644 --- a/examples/panner.rs +++ b/examples/panner.rs @@ -17,6 +17,7 @@ fn run_example(servo_media: Arc) { options.position_x = 100.; options.position_y = 0.; options.position_z = 100.; + options.ref_distance = 100.; let panner = context.create_node(AudioNodeInit::PannerNode(options)); context.connect_ports(osc.output(0), panner.input(0)); context.connect_ports(panner.output(0), dest.input(0)); From c54806044238696c91960351fe00360d00e97589 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Thu, 23 Aug 2018 08:56:38 -0700 Subject: [PATCH 13/13] Fix nits and mistakes --- audio/src/panner_node.rs | 9 +++++---- examples/panner.rs | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/audio/src/panner_node.rs b/audio/src/panner_node.rs index c5fe0393..56c0bd3d 100644 --- a/audio/src/panner_node.rs +++ b/audio/src/panner_node.rs @@ -92,7 +92,7 @@ impl PannerNode { Self { channel_info: ChannelInfo { count: 2, - mode: ChannelCountMode::Max, + mode: ChannelCountMode::ClampedMax, interpretation: ChannelInterpretation::Speakers, }, panning_model: options.panning_model, @@ -122,8 +122,8 @@ impl PannerNode { self.orientation_z.update(info, tick) } - /// Computes azimuthm elevation, and distance of source with respect to a - /// given AudioListener's position, forward, up vectors + /// Computes azimuth, elevation, and distance of source with respect to a + /// given AudioListener's position, forward, and up vectors /// in degrees /// /// https://webaudio.github.io/web-audio-api/#azimuth-elevation @@ -177,6 +177,7 @@ impl PannerNode { (azimuth, elevation, distance as f64) } + /// https://webaudio.github.io/web-audio-api/#Spatialization-sound-cones fn cone_gain(&self, listener: (Vector3D, Vector3D, Vector3D)) -> f64 { @@ -201,7 +202,7 @@ impl PannerNode { let source_to_listener = normalize_zero(source_position - listener_position); // Angle between the source orientation vector and the source-listener vector - let angle = 180. * source_to_listener.dot(normalized_source_orientation) / PI; + let angle = 180. * source_to_listener.dot(normalized_source_orientation).acos() / PI; let abs_angle = angle.abs() as f64; // Divide by 2 here since API is entire angle (not half-angle) diff --git a/examples/panner.rs b/examples/panner.rs index bad7f5e7..c7645b89 100644 --- a/examples/panner.rs +++ b/examples/panner.rs @@ -18,6 +18,7 @@ fn run_example(servo_media: Arc) { options.position_y = 0.; options.position_z = 100.; options.ref_distance = 100.; + options.rolloff_factor = 0.01; let panner = context.create_node(AudioNodeInit::PannerNode(options)); context.connect_ports(osc.output(0), panner.input(0)); context.connect_ports(panner.output(0), dest.input(0));