1 /*
2  * Copyright (C) 2010, Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1.  Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2.  Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
23  * DAMAGE.
24  */
25 
26 #include "third_party/blink/renderer/modules/webaudio/panner_node.h"
27 
28 #include "base/metrics/histogram_functions.h"
29 #include "third_party/blink/renderer/bindings/modules/v8/v8_panner_options.h"
30 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
31 #include "third_party/blink/renderer/modules/webaudio/audio_buffer_source_node.h"
32 #include "third_party/blink/renderer/modules/webaudio/audio_node_input.h"
33 #include "third_party/blink/renderer/modules/webaudio/audio_node_output.h"
34 #include "third_party/blink/renderer/modules/webaudio/base_audio_context.h"
35 #include "third_party/blink/renderer/platform/audio/hrtf_panner.h"
36 #include "third_party/blink/renderer/platform/bindings/exception_messages.h"
37 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
38 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
39 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
40 
41 namespace blink {
42 
FixNANs(double & x)43 static void FixNANs(double& x) {
44   if (std::isnan(x) || std::isinf(x))
45     x = 0.0;
46 }
47 
PannerHandler(AudioNode & node,float sample_rate,AudioParamHandler & position_x,AudioParamHandler & position_y,AudioParamHandler & position_z,AudioParamHandler & orientation_x,AudioParamHandler & orientation_y,AudioParamHandler & orientation_z)48 PannerHandler::PannerHandler(AudioNode& node,
49                              float sample_rate,
50                              AudioParamHandler& position_x,
51                              AudioParamHandler& position_y,
52                              AudioParamHandler& position_z,
53                              AudioParamHandler& orientation_x,
54                              AudioParamHandler& orientation_y,
55                              AudioParamHandler& orientation_z)
56     : AudioHandler(kNodeTypePanner, node, sample_rate),
57       listener_(node.context()->listener()),
58       distance_model_(DistanceEffect::kModelInverse),
59       is_azimuth_elevation_dirty_(true),
60       is_distance_cone_gain_dirty_(true),
61       cached_azimuth_(0),
62       cached_elevation_(0),
63       cached_distance_cone_gain_(1.0f),
64       position_x_(&position_x),
65       position_y_(&position_y),
66       position_z_(&position_z),
67       orientation_x_(&orientation_x),
68       orientation_y_(&orientation_y),
69       orientation_z_(&orientation_z) {
70   AddInput();
71   AddOutput(2);
72 
73   // Node-specific default mixing rules.
74   channel_count_ = 2;
75   SetInternalChannelCountMode(kClampedMax);
76   SetInternalChannelInterpretation(AudioBus::kSpeakers);
77 
78   // Explicitly set the default panning model here so that the histograms
79   // include the default value.
80   SetPanningModel("equalpower");
81 
82   Initialize();
83 }
84 
Create(AudioNode & node,float sample_rate,AudioParamHandler & position_x,AudioParamHandler & position_y,AudioParamHandler & position_z,AudioParamHandler & orientation_x,AudioParamHandler & orientation_y,AudioParamHandler & orientation_z)85 scoped_refptr<PannerHandler> PannerHandler::Create(
86     AudioNode& node,
87     float sample_rate,
88     AudioParamHandler& position_x,
89     AudioParamHandler& position_y,
90     AudioParamHandler& position_z,
91     AudioParamHandler& orientation_x,
92     AudioParamHandler& orientation_y,
93     AudioParamHandler& orientation_z) {
94   return base::AdoptRef(new PannerHandler(node, sample_rate, position_x,
95                                           position_y, position_z, orientation_x,
96                                           orientation_y, orientation_z));
97 }
98 
~PannerHandler()99 PannerHandler::~PannerHandler() {
100   Uninitialize();
101 }
102 
103 // PannerNode needs a custom ProcessIfNecessary to get the process lock when
104 // computing PropagatesSilence() to protect processing from changes happening to
105 // the panning model.  This is very similar to AudioNode::ProcessIfNecessary.
ProcessIfNecessary(uint32_t frames_to_process)106 void PannerHandler::ProcessIfNecessary(uint32_t frames_to_process) {
107   DCHECK(Context()->IsAudioThread());
108 
109   if (!IsInitialized())
110     return;
111 
112   // Ensure that we only process once per rendering quantum.
113   // This handles the "fanout" problem where an output is connected to multiple
114   // inputs.  The first time we're called during this time slice we process, but
115   // after that we don't want to re-process, instead our output(s) will already
116   // have the results cached in their bus;
117   double current_time = Context()->currentTime();
118   if (last_processing_time_ != current_time) {
119     // important to first update this time because of feedback loops in the
120     // rendering graph.
121     last_processing_time_ = current_time;
122 
123     PullInputs(frames_to_process);
124 
125     bool silent_inputs = InputsAreSilent();
126 
127     {
128       // Need to protect calls to PropagetesSilence (and Process) because the
129       // main threda may be changing the panning model that modifies the
130       // TailTime and LatencyTime methods called by PropagatesSilence.
131       MutexTryLocker try_locker(process_lock_);
132       if (try_locker.Locked()) {
133         if (silent_inputs && PropagatesSilence()) {
134           SilenceOutputs();
135           // AudioParams still need to be processed so that the value can be
136           // updated if there are automations or so that the upstream nodes get
137           // pulled if any are connected to the AudioParam.
138           ProcessOnlyAudioParams(frames_to_process);
139         } else {
140           // Unsilence the outputs first because the processing of the node may
141           // cause the outputs to go silent and we want to propagate that hint
142           // to the downstream nodes.  (For example, a Gain node with a gain of
143           // 0 will want to silence its output.)
144           UnsilenceOutputs();
145           Process(frames_to_process);
146         }
147       } else {
148         // We must be in the middle of changing the properties of the panner.
149         // Just output silence.
150         AudioBus* destination = Output(0).Bus();
151         destination->Zero();
152       }
153     }
154 
155     if (!silent_inputs) {
156       // Update |last_non_silent_time| AFTER processing this block.
157       // Doing it before causes |PropagateSilence()| to be one render
158       // quantum longer than necessary.
159       last_non_silent_time_ =
160           (Context()->CurrentSampleFrame() + frames_to_process) /
161           static_cast<double>(Context()->sampleRate());
162     }
163   }
164 }
165 
Process(uint32_t frames_to_process)166 void PannerHandler::Process(uint32_t frames_to_process) {
167   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("webaudio.audionode"),
168                "PannerHandler::Process");
169 
170   AudioBus* destination = Output(0).Bus();
171 
172   if (!IsInitialized() || !panner_.get()) {
173     destination->Zero();
174     return;
175   }
176 
177   scoped_refptr<AudioBus> source = Input(0).Bus();
178   if (!source) {
179     destination->Zero();
180     return;
181   }
182 
183   // The audio thread can't block on this lock, so we call tryLock() instead.
184   MutexTryLocker try_listener_locker(Listener()->ListenerLock());
185 
186   if (try_listener_locker.Locked()) {
187     if (!Context()->HasRealtimeConstraint() &&
188         panning_model_ == Panner::PanningModel::kHRTF) {
189       // For an OfflineAudioContext, we need to make sure the HRTFDatabase
190       // is loaded before proceeding.  For realtime contexts, we don't
191       // have to wait.  The HRTF panner handles that case itself.
192       Listener()->WaitForHRTFDatabaseLoaderThreadCompletion();
193     }
194 
195     if (HasSampleAccurateValues() || Listener()->HasSampleAccurateValues()) {
196       // It's tempting to skip sample-accurate processing if
197       // isAzimuthElevationDirty() and isDistanceConeGain() both return false.
198       // But in general we can't because something may scheduled to start in the
199       // middle of the rendering quantum.  On the other hand, the audible effect
200       // may be small enough that we can afford to do this optimization.
201       ProcessSampleAccurateValues(destination, source.get(), frames_to_process);
202     } else {
203       // Apply the panning effect.
204       double azimuth;
205       double elevation;
206 
207       // Update dirty state in case something has moved; this can happen if the
208       // AudioParam for the position or orientation component is set directly.
209       UpdateDirtyState();
210 
211       AzimuthElevation(&azimuth, &elevation);
212 
213       panner_->Pan(azimuth, elevation, source.get(), destination,
214                    frames_to_process, InternalChannelInterpretation());
215 
216       // Get the distance and cone gain.
217       float total_gain = DistanceConeGain();
218 
219       // Apply gain in-place.
220       destination->CopyWithGainFrom(*destination, total_gain);
221     }
222   } else {
223     // Too bad - The tryLock() failed.  We must be in the middle of changing the
224     // properties of the panner or the listener.
225     destination->Zero();
226   }
227 }
228 
ProcessSampleAccurateValues(AudioBus * destination,const AudioBus * source,uint32_t frames_to_process)229 void PannerHandler::ProcessSampleAccurateValues(AudioBus* destination,
230                                                 const AudioBus* source,
231                                                 uint32_t frames_to_process) {
232   CHECK_LE(frames_to_process, audio_utilities::kRenderQuantumFrames);
233 
234   // Get the sample accurate values from all of the AudioParams, including the
235   // values from the AudioListener.
236   float panner_x[audio_utilities::kRenderQuantumFrames];
237   float panner_y[audio_utilities::kRenderQuantumFrames];
238   float panner_z[audio_utilities::kRenderQuantumFrames];
239 
240   float orientation_x[audio_utilities::kRenderQuantumFrames];
241   float orientation_y[audio_utilities::kRenderQuantumFrames];
242   float orientation_z[audio_utilities::kRenderQuantumFrames];
243 
244   position_x_->CalculateSampleAccurateValues(panner_x, frames_to_process);
245   position_y_->CalculateSampleAccurateValues(panner_y, frames_to_process);
246   position_z_->CalculateSampleAccurateValues(panner_z, frames_to_process);
247   orientation_x_->CalculateSampleAccurateValues(orientation_x,
248                                                 frames_to_process);
249   orientation_y_->CalculateSampleAccurateValues(orientation_y,
250                                                 frames_to_process);
251   orientation_z_->CalculateSampleAccurateValues(orientation_z,
252                                                 frames_to_process);
253 
254   // Get the automation values from the listener.
255   const float* listener_x =
256       Listener()->GetPositionXValues(audio_utilities::kRenderQuantumFrames);
257   const float* listener_y =
258       Listener()->GetPositionYValues(audio_utilities::kRenderQuantumFrames);
259   const float* listener_z =
260       Listener()->GetPositionZValues(audio_utilities::kRenderQuantumFrames);
261 
262   const float* forward_x =
263       Listener()->GetForwardXValues(audio_utilities::kRenderQuantumFrames);
264   const float* forward_y =
265       Listener()->GetForwardYValues(audio_utilities::kRenderQuantumFrames);
266   const float* forward_z =
267       Listener()->GetForwardZValues(audio_utilities::kRenderQuantumFrames);
268 
269   const float* up_x =
270       Listener()->GetUpXValues(audio_utilities::kRenderQuantumFrames);
271   const float* up_y =
272       Listener()->GetUpYValues(audio_utilities::kRenderQuantumFrames);
273   const float* up_z =
274       Listener()->GetUpZValues(audio_utilities::kRenderQuantumFrames);
275 
276   // Compute the azimuth, elevation, and total gains for each position.
277   double azimuth[audio_utilities::kRenderQuantumFrames];
278   double elevation[audio_utilities::kRenderQuantumFrames];
279   float total_gain[audio_utilities::kRenderQuantumFrames];
280 
281   for (unsigned k = 0; k < frames_to_process; ++k) {
282     FloatPoint3D panner_position(panner_x[k], panner_y[k], panner_z[k]);
283     FloatPoint3D orientation(orientation_x[k], orientation_y[k],
284                              orientation_z[k]);
285     FloatPoint3D listener_position(listener_x[k], listener_y[k], listener_z[k]);
286     FloatPoint3D listener_forward(forward_x[k], forward_y[k], forward_z[k]);
287     FloatPoint3D listener_up(up_x[k], up_y[k], up_z[k]);
288 
289     CalculateAzimuthElevation(&azimuth[k], &elevation[k], panner_position,
290                               listener_position, listener_forward, listener_up);
291 
292     // Get distance and cone gain
293     total_gain[k] = CalculateDistanceConeGain(panner_position, orientation,
294                                               listener_position);
295   }
296 
297   // Update cached values in case automations end.
298   if (frames_to_process > 0) {
299     cached_azimuth_ = azimuth[frames_to_process - 1];
300     cached_elevation_ = elevation[frames_to_process - 1];
301     cached_distance_cone_gain_ = total_gain[frames_to_process - 1];
302   }
303 
304   panner_->PanWithSampleAccurateValues(azimuth, elevation, source, destination,
305                                        frames_to_process,
306                                        InternalChannelInterpretation());
307   destination->CopyWithSampleAccurateGainValuesFrom(*destination, total_gain,
308                                                     frames_to_process);
309 }
310 
ProcessOnlyAudioParams(uint32_t frames_to_process)311 void PannerHandler::ProcessOnlyAudioParams(uint32_t frames_to_process) {
312   float values[audio_utilities::kRenderQuantumFrames];
313 
314   DCHECK_LE(frames_to_process, audio_utilities::kRenderQuantumFrames);
315 
316   position_x_->CalculateSampleAccurateValues(values, frames_to_process);
317   position_y_->CalculateSampleAccurateValues(values, frames_to_process);
318   position_z_->CalculateSampleAccurateValues(values, frames_to_process);
319 
320   orientation_x_->CalculateSampleAccurateValues(values, frames_to_process);
321   orientation_y_->CalculateSampleAccurateValues(values, frames_to_process);
322   orientation_z_->CalculateSampleAccurateValues(values, frames_to_process);
323 }
324 
Initialize()325 void PannerHandler::Initialize() {
326   if (IsInitialized())
327     return;
328 
329   panner_ = Panner::Create(panning_model_, Context()->sampleRate(),
330                            Listener()->HrtfDatabaseLoader());
331   Listener()->AddPanner(*this);
332 
333   // Set the cached values to the current values to start things off.  The
334   // panner is already marked as dirty, so this won't matter.
335   last_position_ = GetPosition();
336   last_orientation_ = Orientation();
337 
338   AudioHandler::Initialize();
339 }
340 
Uninitialize()341 void PannerHandler::Uninitialize() {
342   if (!IsInitialized())
343     return;
344 
345   panner_.reset();
346   if (Listener()) {
347     // Listener may have gone in the same garbage collection cycle, which means
348     // that the panner does not need to be removed.
349     Listener()->RemovePanner(*this);
350   }
351 
352   AudioHandler::Uninitialize();
353 }
354 
Listener()355 AudioListener* PannerHandler::Listener() {
356   return listener_;
357 }
358 
PanningModel() const359 String PannerHandler::PanningModel() const {
360   switch (panning_model_) {
361     case Panner::PanningModel::kEqualPower:
362       return "equalpower";
363     case Panner::PanningModel::kHRTF:
364       return "HRTF";
365   }
366   NOTREACHED();
367   return "equalpower";
368 }
369 
SetPanningModel(const String & model)370 void PannerHandler::SetPanningModel(const String& model) {
371   // WebIDL should guarantee that we are never called with an invalid string
372   // for the model.
373   if (model == "equalpower")
374     SetPanningModel(Panner::PanningModel::kEqualPower);
375   else if (model == "HRTF")
376     SetPanningModel(Panner::PanningModel::kHRTF);
377   else
378     NOTREACHED();
379 }
380 
381 // This method should only be called from setPanningModel(const String&)!
SetPanningModel(Panner::PanningModel model)382 bool PannerHandler::SetPanningModel(Panner::PanningModel model) {
383   base::UmaHistogramEnumeration("WebAudio.PannerNode.PanningModel", model);
384 
385   if (model == Panner::PanningModel::kHRTF) {
386     // Load the HRTF database asynchronously so we don't block the
387     // Javascript thread while creating the HRTF database. It's ok to call
388     // this multiple times; we won't be constantly loading the database over
389     // and over.
390     Listener()->CreateAndLoadHRTFDatabaseLoader(Context()->sampleRate());
391   }
392 
393   if (!panner_.get() || model != panning_model_) {
394     // We need the graph lock to secure the panner backend because
395     // BaseAudioContext::Handle{Pre,Post}RenderTasks() from the audio thread
396     // can touch it.
397     BaseAudioContext::GraphAutoLocker context_locker(Context());
398 
399     // This synchronizes with process().
400     MutexLocker process_locker(process_lock_);
401     panner_ = Panner::Create(model, Context()->sampleRate(),
402                              Listener()->HrtfDatabaseLoader());
403     panning_model_ = model;
404   }
405   return true;
406 }
407 
DistanceModel() const408 String PannerHandler::DistanceModel() const {
409   switch (const_cast<PannerHandler*>(this)->distance_effect_.Model()) {
410     case DistanceEffect::kModelLinear:
411       return "linear";
412     case DistanceEffect::kModelInverse:
413       return "inverse";
414     case DistanceEffect::kModelExponential:
415       return "exponential";
416     default:
417       NOTREACHED();
418       return "inverse";
419   }
420 }
421 
SetDistanceModel(const String & model)422 void PannerHandler::SetDistanceModel(const String& model) {
423   if (model == "linear")
424     SetDistanceModel(DistanceEffect::kModelLinear);
425   else if (model == "inverse")
426     SetDistanceModel(DistanceEffect::kModelInverse);
427   else if (model == "exponential")
428     SetDistanceModel(DistanceEffect::kModelExponential);
429 }
430 
SetDistanceModel(unsigned model)431 bool PannerHandler::SetDistanceModel(unsigned model) {
432   switch (model) {
433     case DistanceEffect::kModelLinear:
434     case DistanceEffect::kModelInverse:
435     case DistanceEffect::kModelExponential:
436       if (model != distance_model_) {
437         // This synchronizes with process().
438         MutexLocker process_locker(process_lock_);
439         distance_effect_.SetModel(
440             static_cast<DistanceEffect::ModelType>(model));
441         distance_model_ = model;
442       }
443       break;
444     default:
445       NOTREACHED();
446       return false;
447   }
448 
449   return true;
450 }
451 
SetRefDistance(double distance)452 void PannerHandler::SetRefDistance(double distance) {
453   if (RefDistance() == distance)
454     return;
455 
456   // This synchronizes with process().
457   MutexLocker process_locker(process_lock_);
458   distance_effect_.SetRefDistance(distance);
459   MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
460 }
461 
SetMaxDistance(double distance)462 void PannerHandler::SetMaxDistance(double distance) {
463   if (MaxDistance() == distance)
464     return;
465 
466   // This synchronizes with process().
467   MutexLocker process_locker(process_lock_);
468   distance_effect_.SetMaxDistance(distance);
469   MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
470 }
471 
SetRolloffFactor(double factor)472 void PannerHandler::SetRolloffFactor(double factor) {
473   if (RolloffFactor() == factor)
474     return;
475 
476   // This synchronizes with process().
477   MutexLocker process_locker(process_lock_);
478   distance_effect_.SetRolloffFactor(factor);
479   MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
480 }
481 
SetConeInnerAngle(double angle)482 void PannerHandler::SetConeInnerAngle(double angle) {
483   if (ConeInnerAngle() == angle)
484     return;
485 
486   // This synchronizes with process().
487   MutexLocker process_locker(process_lock_);
488   cone_effect_.SetInnerAngle(angle);
489   MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
490 }
491 
SetConeOuterAngle(double angle)492 void PannerHandler::SetConeOuterAngle(double angle) {
493   if (ConeOuterAngle() == angle)
494     return;
495 
496   // This synchronizes with process().
497   MutexLocker process_locker(process_lock_);
498   cone_effect_.SetOuterAngle(angle);
499   MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
500 }
501 
SetConeOuterGain(double angle)502 void PannerHandler::SetConeOuterGain(double angle) {
503   if (ConeOuterGain() == angle)
504     return;
505 
506   // This synchronizes with process().
507   MutexLocker process_locker(process_lock_);
508   cone_effect_.SetOuterGain(angle);
509   MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
510 }
511 
SetPosition(float x,float y,float z,ExceptionState & exceptionState)512 void PannerHandler::SetPosition(float x,
513                                 float y,
514                                 float z,
515                                 ExceptionState& exceptionState) {
516   // This synchronizes with process().
517   MutexLocker process_locker(process_lock_);
518 
519   double now = Context()->currentTime();
520 
521   position_x_->Timeline().SetValueAtTime(x, now, exceptionState);
522   position_y_->Timeline().SetValueAtTime(y, now, exceptionState);
523   position_z_->Timeline().SetValueAtTime(z, now, exceptionState);
524 
525   MarkPannerAsDirty(PannerHandler::kAzimuthElevationDirty |
526                     PannerHandler::kDistanceConeGainDirty);
527 }
528 
SetOrientation(float x,float y,float z,ExceptionState & exceptionState)529 void PannerHandler::SetOrientation(float x,
530                                    float y,
531                                    float z,
532                                    ExceptionState& exceptionState) {
533   // This synchronizes with process().
534   MutexLocker process_locker(process_lock_);
535 
536   double now = Context()->currentTime();
537 
538   orientation_x_->Timeline().SetValueAtTime(x, now, exceptionState);
539   orientation_y_->Timeline().SetValueAtTime(y, now, exceptionState);
540   orientation_z_->Timeline().SetValueAtTime(z, now, exceptionState);
541 
542   MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
543 }
544 
CalculateAzimuthElevation(double * out_azimuth,double * out_elevation,const FloatPoint3D & position,const FloatPoint3D & listener_position,const FloatPoint3D & listener_forward,const FloatPoint3D & listener_up)545 void PannerHandler::CalculateAzimuthElevation(
546     double* out_azimuth,
547     double* out_elevation,
548     const FloatPoint3D& position,
549     const FloatPoint3D& listener_position,
550     const FloatPoint3D& listener_forward,
551     const FloatPoint3D& listener_up) {
552   // Calculate the source-listener vector
553   FloatPoint3D source_listener = position - listener_position;
554 
555   // Quick default return if the source and listener are at the same position.
556   if (source_listener.IsZero()) {
557     *out_azimuth = 0;
558     *out_elevation = 0;
559     return;
560   }
561 
562   // normalize() does nothing if the length of |sourceListener| is zero.
563   source_listener.Normalize();
564 
565   // Align axes
566   FloatPoint3D listener_right = listener_forward.Cross(listener_up);
567   listener_right.Normalize();
568 
569   FloatPoint3D listener_forward_norm = listener_forward;
570   listener_forward_norm.Normalize();
571 
572   FloatPoint3D up = listener_right.Cross(listener_forward_norm);
573 
574   float up_projection = source_listener.Dot(up);
575 
576   FloatPoint3D projected_source = source_listener - up_projection * up;
577   projected_source.Normalize();
578 
579   // Don't use AngleBetween here.  It produces the wrong value when one of the
580   // vectors has zero length.  We know here that |projected_source| and
581   // |listener_right| are "normalized", so the dot product is good enough.
582   double azimuth =
583       rad2deg(acos(clampTo(projected_source.Dot(listener_right), -1.0f, 1.0f)));
584   FixNANs(azimuth);  // avoid illegal values
585 
586   // Source  in front or behind the listener
587   double front_back = projected_source.Dot(listener_forward_norm);
588   if (front_back < 0.0)
589     azimuth = 360.0 - azimuth;
590 
591   // Make azimuth relative to "front" and not "right" listener vector
592   if ((azimuth >= 0.0) && (azimuth <= 270.0))
593     azimuth = 90.0 - azimuth;
594   else
595     azimuth = 450.0 - azimuth;
596 
597   // Elevation
598   double elevation = 90 - rad2deg(source_listener.AngleBetween(up));
599   FixNANs(elevation);  // avoid illegal values
600 
601   if (elevation > 90.0)
602     elevation = 180.0 - elevation;
603   else if (elevation < -90.0)
604     elevation = -180.0 - elevation;
605 
606   if (out_azimuth)
607     *out_azimuth = azimuth;
608   if (out_elevation)
609     *out_elevation = elevation;
610 }
611 
CalculateDistanceConeGain(const FloatPoint3D & position,const FloatPoint3D & orientation,const FloatPoint3D & listener_position)612 float PannerHandler::CalculateDistanceConeGain(
613     const FloatPoint3D& position,
614     const FloatPoint3D& orientation,
615     const FloatPoint3D& listener_position) {
616   double listener_distance = position.DistanceTo(listener_position);
617   double distance_gain = distance_effect_.Gain(listener_distance);
618   double cone_gain =
619       cone_effect_.Gain(position, orientation, listener_position);
620 
621   return float(distance_gain * cone_gain);
622 }
623 
AzimuthElevation(double * out_azimuth,double * out_elevation)624 void PannerHandler::AzimuthElevation(double* out_azimuth,
625                                      double* out_elevation) {
626   DCHECK(Context()->IsAudioThread());
627 
628   // Calculate new azimuth and elevation if the panner or the listener changed
629   // position or orientation in any way.
630   if (IsAzimuthElevationDirty() || Listener()->IsListenerDirty()) {
631     CalculateAzimuthElevation(&cached_azimuth_, &cached_elevation_,
632                               GetPosition(), Listener()->GetPosition(),
633                               Listener()->Orientation(),
634                               Listener()->UpVector());
635     is_azimuth_elevation_dirty_ = false;
636   }
637 
638   *out_azimuth = cached_azimuth_;
639   *out_elevation = cached_elevation_;
640 }
641 
DistanceConeGain()642 float PannerHandler::DistanceConeGain() {
643   DCHECK(Context()->IsAudioThread());
644 
645   // Calculate new distance and cone gain if the panner or the listener
646   // changed position or orientation in any way.
647   if (IsDistanceConeGainDirty() || Listener()->IsListenerDirty()) {
648     cached_distance_cone_gain_ = CalculateDistanceConeGain(
649         GetPosition(), Orientation(), Listener()->GetPosition());
650     is_distance_cone_gain_dirty_ = false;
651   }
652 
653   return cached_distance_cone_gain_;
654 }
655 
MarkPannerAsDirty(unsigned dirty)656 void PannerHandler::MarkPannerAsDirty(unsigned dirty) {
657   if (dirty & PannerHandler::kAzimuthElevationDirty)
658     is_azimuth_elevation_dirty_ = true;
659 
660   if (dirty & PannerHandler::kDistanceConeGainDirty)
661     is_distance_cone_gain_dirty_ = true;
662 }
663 
SetChannelCount(unsigned channel_count,ExceptionState & exception_state)664 void PannerHandler::SetChannelCount(unsigned channel_count,
665                                     ExceptionState& exception_state) {
666   DCHECK(IsMainThread());
667   BaseAudioContext::GraphAutoLocker locker(Context());
668 
669   // A PannerNode only supports 1 or 2 channels
670   if (channel_count > 0 && channel_count <= 2) {
671     if (channel_count_ != channel_count) {
672       channel_count_ = channel_count;
673       if (InternalChannelCountMode() != kMax)
674         UpdateChannelsForInputs();
675     }
676   } else {
677     exception_state.ThrowDOMException(
678         DOMExceptionCode::kNotSupportedError,
679         ExceptionMessages::IndexOutsideRange<uint32_t>(
680             "channelCount", channel_count, 1,
681             ExceptionMessages::kInclusiveBound, 2,
682             ExceptionMessages::kInclusiveBound));
683   }
684 }
685 
SetChannelCountMode(const String & mode,ExceptionState & exception_state)686 void PannerHandler::SetChannelCountMode(const String& mode,
687                                         ExceptionState& exception_state) {
688   DCHECK(IsMainThread());
689   BaseAudioContext::GraphAutoLocker locker(Context());
690 
691   ChannelCountMode old_mode = InternalChannelCountMode();
692 
693   if (mode == "clamped-max") {
694     new_channel_count_mode_ = kClampedMax;
695   } else if (mode == "explicit") {
696     new_channel_count_mode_ = kExplicit;
697   } else if (mode == "max") {
698     // This is not supported for a PannerNode, which can only handle 1 or 2
699     // channels.
700     exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
701                                       "Panner: 'max' is not allowed");
702     new_channel_count_mode_ = old_mode;
703   } else {
704     // Do nothing for other invalid values.
705     new_channel_count_mode_ = old_mode;
706   }
707 
708   if (new_channel_count_mode_ != old_mode)
709     Context()->GetDeferredTaskHandler().AddChangedChannelCountMode(this);
710 }
711 
HasSampleAccurateValues() const712 bool PannerHandler::HasSampleAccurateValues() const {
713   return position_x_->HasSampleAccurateValues() ||
714          position_y_->HasSampleAccurateValues() ||
715          position_z_->HasSampleAccurateValues() ||
716          orientation_x_->HasSampleAccurateValues() ||
717          orientation_y_->HasSampleAccurateValues() ||
718          orientation_z_->HasSampleAccurateValues();
719 }
720 
UpdateDirtyState()721 void PannerHandler::UpdateDirtyState() {
722   DCHECK(Context()->IsAudioThread());
723 
724   FloatPoint3D current_position = GetPosition();
725   FloatPoint3D current_orientation = Orientation();
726 
727   bool has_moved = current_position != last_position_ ||
728                    current_orientation != last_orientation_;
729 
730   if (has_moved) {
731     last_position_ = current_position;
732     last_orientation_ = current_orientation;
733 
734     MarkPannerAsDirty(PannerHandler::kAzimuthElevationDirty |
735                       PannerHandler::kDistanceConeGainDirty);
736   }
737 }
738 
RequiresTailProcessing() const739 bool PannerHandler::RequiresTailProcessing() const {
740   // If there's no internal panner method set up yet, assume we require tail
741   // processing in case the HRTF panner is set later, which does require tail
742   // processing.
743   return panner_ ? panner_->RequiresTailProcessing() : true;
744 }
745 
746 // ----------------------------------------------------------------
747 
PannerNode(BaseAudioContext & context)748 PannerNode::PannerNode(BaseAudioContext& context)
749     : AudioNode(context),
750       position_x_(
751           AudioParam::Create(context,
752                              Uuid(),
753                              AudioParamHandler::kParamTypePannerPositionX,
754                              0.0,
755                              AudioParamHandler::AutomationRate::kAudio,
756                              AudioParamHandler::AutomationRateMode::kVariable)),
757       position_y_(
758           AudioParam::Create(context,
759                              Uuid(),
760                              AudioParamHandler::kParamTypePannerPositionY,
761                              0.0,
762                              AudioParamHandler::AutomationRate::kAudio,
763                              AudioParamHandler::AutomationRateMode::kVariable)),
764       position_z_(
765           AudioParam::Create(context,
766                              Uuid(),
767                              AudioParamHandler::kParamTypePannerPositionZ,
768                              0.0,
769                              AudioParamHandler::AutomationRate::kAudio,
770                              AudioParamHandler::AutomationRateMode::kVariable)),
771       orientation_x_(
772           AudioParam::Create(context,
773                              Uuid(),
774                              AudioParamHandler::kParamTypePannerOrientationX,
775                              1.0,
776                              AudioParamHandler::AutomationRate::kAudio,
777                              AudioParamHandler::AutomationRateMode::kVariable)),
778       orientation_y_(
779           AudioParam::Create(context,
780                              Uuid(),
781                              AudioParamHandler::kParamTypePannerOrientationY,
782                              0.0,
783                              AudioParamHandler::AutomationRate::kAudio,
784                              AudioParamHandler::AutomationRateMode::kVariable)),
785       orientation_z_(
786           AudioParam::Create(context,
787                              Uuid(),
788                              AudioParamHandler::kParamTypePannerOrientationZ,
789                              0.0,
790                              AudioParamHandler::AutomationRate::kAudio,
791                              AudioParamHandler::AutomationRateMode::kVariable)),
792       listener_(context.listener()) {
793   SetHandler(PannerHandler::Create(
794       *this, context.sampleRate(), position_x_->Handler(),
795       position_y_->Handler(), position_z_->Handler(), orientation_x_->Handler(),
796       orientation_y_->Handler(), orientation_z_->Handler()));
797 }
798 
Create(BaseAudioContext & context,ExceptionState & exception_state)799 PannerNode* PannerNode::Create(BaseAudioContext& context,
800                                ExceptionState& exception_state) {
801   DCHECK(IsMainThread());
802 
803   return MakeGarbageCollected<PannerNode>(context);
804 }
805 
Create(BaseAudioContext * context,const PannerOptions * options,ExceptionState & exception_state)806 PannerNode* PannerNode::Create(BaseAudioContext* context,
807                                const PannerOptions* options,
808                                ExceptionState& exception_state) {
809   PannerNode* node = Create(*context, exception_state);
810 
811   if (!node)
812     return nullptr;
813 
814   node->HandleChannelOptions(options, exception_state);
815 
816   node->setPanningModel(options->panningModel());
817   node->setDistanceModel(options->distanceModel());
818 
819   node->positionX()->setValue(options->positionX());
820   node->positionY()->setValue(options->positionY());
821   node->positionZ()->setValue(options->positionZ());
822 
823   node->orientationX()->setValue(options->orientationX());
824   node->orientationY()->setValue(options->orientationY());
825   node->orientationZ()->setValue(options->orientationZ());
826 
827   node->setRefDistance(options->refDistance(), exception_state);
828   node->setMaxDistance(options->maxDistance(), exception_state);
829   node->setRolloffFactor(options->rolloffFactor(), exception_state);
830   node->setConeInnerAngle(options->coneInnerAngle());
831   node->setConeOuterAngle(options->coneOuterAngle());
832   node->setConeOuterGain(options->coneOuterGain(), exception_state);
833 
834   return node;
835 }
836 
GetPannerHandler() const837 PannerHandler& PannerNode::GetPannerHandler() const {
838   return static_cast<PannerHandler&>(Handler());
839 }
840 
panningModel() const841 String PannerNode::panningModel() const {
842   return GetPannerHandler().PanningModel();
843 }
844 
setPanningModel(const String & model)845 void PannerNode::setPanningModel(const String& model) {
846   GetPannerHandler().SetPanningModel(model);
847 }
848 
setPosition(float x,float y,float z,ExceptionState & exceptionState)849 void PannerNode::setPosition(float x,
850                              float y,
851                              float z,
852                              ExceptionState& exceptionState) {
853   GetPannerHandler().SetPosition(x, y, z, exceptionState);
854 }
855 
setOrientation(float x,float y,float z,ExceptionState & exceptionState)856 void PannerNode::setOrientation(float x,
857                                 float y,
858                                 float z,
859                                 ExceptionState& exceptionState) {
860   GetPannerHandler().SetOrientation(x, y, z, exceptionState);
861 }
862 
distanceModel() const863 String PannerNode::distanceModel() const {
864   return GetPannerHandler().DistanceModel();
865 }
866 
setDistanceModel(const String & model)867 void PannerNode::setDistanceModel(const String& model) {
868   GetPannerHandler().SetDistanceModel(model);
869 }
870 
refDistance() const871 double PannerNode::refDistance() const {
872   return GetPannerHandler().RefDistance();
873 }
874 
setRefDistance(double distance,ExceptionState & exception_state)875 void PannerNode::setRefDistance(double distance,
876                                 ExceptionState& exception_state) {
877   if (distance < 0) {
878     exception_state.ThrowRangeError(
879         ExceptionMessages::IndexExceedsMinimumBound<double>("refDistance",
880                                                             distance, 0));
881     return;
882   }
883 
884   GetPannerHandler().SetRefDistance(distance);
885 }
886 
maxDistance() const887 double PannerNode::maxDistance() const {
888   return GetPannerHandler().MaxDistance();
889 }
890 
setMaxDistance(double distance,ExceptionState & exception_state)891 void PannerNode::setMaxDistance(double distance,
892                                 ExceptionState& exception_state) {
893   if (distance <= 0) {
894     exception_state.ThrowRangeError(
895         ExceptionMessages::IndexExceedsMinimumBound<double>("maxDistance",
896                                                             distance, 0));
897     return;
898   }
899 
900   GetPannerHandler().SetMaxDistance(distance);
901 }
902 
rolloffFactor() const903 double PannerNode::rolloffFactor() const {
904   return GetPannerHandler().RolloffFactor();
905 }
906 
setRolloffFactor(double factor,ExceptionState & exception_state)907 void PannerNode::setRolloffFactor(double factor,
908                                   ExceptionState& exception_state) {
909   if (factor < 0) {
910     exception_state.ThrowRangeError(
911         ExceptionMessages::IndexExceedsMinimumBound<double>("rolloffFactor",
912                                                             factor, 0));
913     return;
914   }
915 
916   GetPannerHandler().SetRolloffFactor(factor);
917 }
918 
coneInnerAngle() const919 double PannerNode::coneInnerAngle() const {
920   return GetPannerHandler().ConeInnerAngle();
921 }
922 
setConeInnerAngle(double angle)923 void PannerNode::setConeInnerAngle(double angle) {
924   GetPannerHandler().SetConeInnerAngle(angle);
925 }
926 
coneOuterAngle() const927 double PannerNode::coneOuterAngle() const {
928   return GetPannerHandler().ConeOuterAngle();
929 }
930 
setConeOuterAngle(double angle)931 void PannerNode::setConeOuterAngle(double angle) {
932   GetPannerHandler().SetConeOuterAngle(angle);
933 }
934 
coneOuterGain() const935 double PannerNode::coneOuterGain() const {
936   return GetPannerHandler().ConeOuterGain();
937 }
938 
setConeOuterGain(double gain,ExceptionState & exception_state)939 void PannerNode::setConeOuterGain(double gain,
940                                   ExceptionState& exception_state) {
941   if (gain < 0 || gain > 1) {
942     exception_state.ThrowDOMException(
943         DOMExceptionCode::kInvalidStateError,
944         ExceptionMessages::IndexOutsideRange<double>(
945             "coneOuterGain", gain, 0, ExceptionMessages::kInclusiveBound, 1,
946             ExceptionMessages::kInclusiveBound));
947     return;
948   }
949 
950   GetPannerHandler().SetConeOuterGain(gain);
951 }
952 
Trace(Visitor * visitor)953 void PannerNode::Trace(Visitor* visitor) {
954   visitor->Trace(position_x_);
955   visitor->Trace(position_y_);
956   visitor->Trace(position_z_);
957   visitor->Trace(orientation_x_);
958   visitor->Trace(orientation_y_);
959   visitor->Trace(orientation_z_);
960   visitor->Trace(listener_);
961   AudioNode::Trace(visitor);
962 }
963 
ReportDidCreate()964 void PannerNode::ReportDidCreate() {
965   GraphTracer().DidCreateAudioNode(this);
966   GraphTracer().DidCreateAudioParam(position_x_);
967   GraphTracer().DidCreateAudioParam(position_y_);
968   GraphTracer().DidCreateAudioParam(position_z_);
969   GraphTracer().DidCreateAudioParam(orientation_x_);
970   GraphTracer().DidCreateAudioParam(orientation_y_);
971   GraphTracer().DidCreateAudioParam(orientation_z_);
972 }
973 
ReportWillBeDestroyed()974 void PannerNode::ReportWillBeDestroyed() {
975   GraphTracer().WillDestroyAudioParam(position_x_);
976   GraphTracer().WillDestroyAudioParam(position_y_);
977   GraphTracer().WillDestroyAudioParam(position_z_);
978   GraphTracer().WillDestroyAudioParam(orientation_x_);
979   GraphTracer().WillDestroyAudioParam(orientation_y_);
980   GraphTracer().WillDestroyAudioParam(orientation_z_);
981   GraphTracer().WillDestroyAudioNode(this);
982 }
983 
984 }  // namespace blink
985