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   auto listener = Listener();
185   MutexTryLocker try_listener_locker(listener->ListenerLock());
186 
187   if (try_listener_locker.Locked()) {
188     if (!Context()->HasRealtimeConstraint() &&
189         panning_model_ == Panner::PanningModel::kHRTF) {
190       // For an OfflineAudioContext, we need to make sure the HRTFDatabase
191       // is loaded before proceeding.  For realtime contexts, we don't
192       // have to wait.  The HRTF panner handles that case itself.
193       listener->WaitForHRTFDatabaseLoaderThreadCompletion();
194     }
195 
196     if ((HasSampleAccurateValues() || listener->HasSampleAccurateValues()) &&
197         (IsAudioRate() || listener->IsAudioRate())) {
198       // It's tempting to skip sample-accurate processing if
199       // isAzimuthElevationDirty() and isDistanceConeGain() both return false.
200       // But in general we can't because something may scheduled to start in the
201       // middle of the rendering quantum.  On the other hand, the audible effect
202       // may be small enough that we can afford to do this optimization.
203       ProcessSampleAccurateValues(destination, source.get(), frames_to_process);
204     } else {
205       // Apply the panning effect.
206       double azimuth;
207       double elevation;
208 
209       // Update dirty state in case something has moved; this can happen if the
210       // AudioParam for the position or orientation component is set directly.
211       UpdateDirtyState();
212 
213       AzimuthElevation(&azimuth, &elevation);
214 
215       panner_->Pan(azimuth, elevation, source.get(), destination,
216                    frames_to_process, InternalChannelInterpretation());
217 
218       // Get the distance and cone gain.
219       float total_gain = DistanceConeGain();
220 
221       // Apply gain in-place.
222       destination->CopyWithGainFrom(*destination, total_gain);
223     }
224   } else {
225     // Too bad - The tryLock() failed.  We must be in the middle of changing the
226     // properties of the panner or the listener.
227     destination->Zero();
228   }
229 }
230 
ProcessSampleAccurateValues(AudioBus * destination,const AudioBus * source,uint32_t frames_to_process)231 void PannerHandler::ProcessSampleAccurateValues(AudioBus* destination,
232                                                 const AudioBus* source,
233                                                 uint32_t frames_to_process) {
234   CHECK_LE(frames_to_process, audio_utilities::kRenderQuantumFrames);
235 
236   // Get the sample accurate values from all of the AudioParams, including the
237   // values from the AudioListener.
238   float panner_x[audio_utilities::kRenderQuantumFrames];
239   float panner_y[audio_utilities::kRenderQuantumFrames];
240   float panner_z[audio_utilities::kRenderQuantumFrames];
241 
242   float orientation_x[audio_utilities::kRenderQuantumFrames];
243   float orientation_y[audio_utilities::kRenderQuantumFrames];
244   float orientation_z[audio_utilities::kRenderQuantumFrames];
245 
246   position_x_->CalculateSampleAccurateValues(panner_x, frames_to_process);
247   position_y_->CalculateSampleAccurateValues(panner_y, frames_to_process);
248   position_z_->CalculateSampleAccurateValues(panner_z, frames_to_process);
249   orientation_x_->CalculateSampleAccurateValues(orientation_x,
250                                                 frames_to_process);
251   orientation_y_->CalculateSampleAccurateValues(orientation_y,
252                                                 frames_to_process);
253   orientation_z_->CalculateSampleAccurateValues(orientation_z,
254                                                 frames_to_process);
255 
256   // Get the automation values from the listener.
257   auto listener = Listener();
258   const float* listener_x =
259       listener->GetPositionXValues(audio_utilities::kRenderQuantumFrames);
260   const float* listener_y =
261       listener->GetPositionYValues(audio_utilities::kRenderQuantumFrames);
262   const float* listener_z =
263       listener->GetPositionZValues(audio_utilities::kRenderQuantumFrames);
264 
265   const float* forward_x =
266       listener->GetForwardXValues(audio_utilities::kRenderQuantumFrames);
267   const float* forward_y =
268       listener->GetForwardYValues(audio_utilities::kRenderQuantumFrames);
269   const float* forward_z =
270       listener->GetForwardZValues(audio_utilities::kRenderQuantumFrames);
271 
272   const float* up_x =
273       listener->GetUpXValues(audio_utilities::kRenderQuantumFrames);
274   const float* up_y =
275       listener->GetUpYValues(audio_utilities::kRenderQuantumFrames);
276   const float* up_z =
277       listener->GetUpZValues(audio_utilities::kRenderQuantumFrames);
278 
279   // Compute the azimuth, elevation, and total gains for each position.
280   double azimuth[audio_utilities::kRenderQuantumFrames];
281   double elevation[audio_utilities::kRenderQuantumFrames];
282   float total_gain[audio_utilities::kRenderQuantumFrames];
283 
284   for (unsigned k = 0; k < frames_to_process; ++k) {
285     FloatPoint3D panner_position(panner_x[k], panner_y[k], panner_z[k]);
286     FloatPoint3D orientation(orientation_x[k], orientation_y[k],
287                              orientation_z[k]);
288     FloatPoint3D listener_position(listener_x[k], listener_y[k], listener_z[k]);
289     FloatPoint3D listener_forward(forward_x[k], forward_y[k], forward_z[k]);
290     FloatPoint3D listener_up(up_x[k], up_y[k], up_z[k]);
291 
292     CalculateAzimuthElevation(&azimuth[k], &elevation[k], panner_position,
293                               listener_position, listener_forward, listener_up);
294 
295     // Get distance and cone gain
296     total_gain[k] = CalculateDistanceConeGain(panner_position, orientation,
297                                               listener_position);
298   }
299 
300   // Update cached values in case automations end.
301   if (frames_to_process > 0) {
302     cached_azimuth_ = azimuth[frames_to_process - 1];
303     cached_elevation_ = elevation[frames_to_process - 1];
304     cached_distance_cone_gain_ = total_gain[frames_to_process - 1];
305   }
306 
307   panner_->PanWithSampleAccurateValues(azimuth, elevation, source, destination,
308                                        frames_to_process,
309                                        InternalChannelInterpretation());
310   destination->CopyWithSampleAccurateGainValuesFrom(*destination, total_gain,
311                                                     frames_to_process);
312 }
313 
ProcessOnlyAudioParams(uint32_t frames_to_process)314 void PannerHandler::ProcessOnlyAudioParams(uint32_t frames_to_process) {
315   float values[audio_utilities::kRenderQuantumFrames];
316 
317   DCHECK_LE(frames_to_process, audio_utilities::kRenderQuantumFrames);
318 
319   position_x_->CalculateSampleAccurateValues(values, frames_to_process);
320   position_y_->CalculateSampleAccurateValues(values, frames_to_process);
321   position_z_->CalculateSampleAccurateValues(values, frames_to_process);
322 
323   orientation_x_->CalculateSampleAccurateValues(values, frames_to_process);
324   orientation_y_->CalculateSampleAccurateValues(values, frames_to_process);
325   orientation_z_->CalculateSampleAccurateValues(values, frames_to_process);
326 }
327 
Initialize()328 void PannerHandler::Initialize() {
329   if (IsInitialized())
330     return;
331 
332   auto listener = Listener();
333   panner_ = Panner::Create(panning_model_, Context()->sampleRate(),
334                            listener->HrtfDatabaseLoader());
335   listener->AddPanner(*this);
336 
337   // The panner is already marked as dirty, so |last_position_| and
338   // |last_orientation_| will bet updated on first use.  Don't need to
339   // set them here.
340 
341   AudioHandler::Initialize();
342 }
343 
Uninitialize()344 void PannerHandler::Uninitialize() {
345   if (!IsInitialized())
346     return;
347 
348   panner_.reset();
349   auto listener = Listener();
350   if (listener) {
351     // Listener may have gone in the same garbage collection cycle, which means
352     // that the panner does not need to be removed.
353     listener->RemovePanner(*this);
354   }
355 
356   AudioHandler::Uninitialize();
357 }
358 
Listener() const359 CrossThreadPersistent<AudioListener> PannerHandler::Listener() const {
360   return listener_.Lock();
361 }
362 
PanningModel() const363 String PannerHandler::PanningModel() const {
364   switch (panning_model_) {
365     case Panner::PanningModel::kEqualPower:
366       return "equalpower";
367     case Panner::PanningModel::kHRTF:
368       return "HRTF";
369   }
370   NOTREACHED();
371   return "equalpower";
372 }
373 
SetPanningModel(const String & model)374 void PannerHandler::SetPanningModel(const String& model) {
375   // WebIDL should guarantee that we are never called with an invalid string
376   // for the model.
377   if (model == "equalpower")
378     SetPanningModel(Panner::PanningModel::kEqualPower);
379   else if (model == "HRTF")
380     SetPanningModel(Panner::PanningModel::kHRTF);
381   else
382     NOTREACHED();
383 }
384 
385 // This method should only be called from setPanningModel(const String&)!
SetPanningModel(Panner::PanningModel model)386 bool PannerHandler::SetPanningModel(Panner::PanningModel model) {
387   base::UmaHistogramEnumeration("WebAudio.PannerNode.PanningModel", model);
388 
389   if (model == Panner::PanningModel::kHRTF) {
390     // Load the HRTF database asynchronously so we don't block the
391     // Javascript thread while creating the HRTF database. It's ok to call
392     // this multiple times; we won't be constantly loading the database over
393     // and over.
394     Listener()->CreateAndLoadHRTFDatabaseLoader(Context()->sampleRate());
395   }
396 
397   if (!panner_.get() || model != panning_model_) {
398     // We need the graph lock to secure the panner backend because
399     // BaseAudioContext::Handle{Pre,Post}RenderTasks() from the audio thread
400     // can touch it.
401     BaseAudioContext::GraphAutoLocker context_locker(Context());
402 
403     // This synchronizes with process().
404     MutexLocker process_locker(process_lock_);
405     panner_ = Panner::Create(model, Context()->sampleRate(),
406                              Listener()->HrtfDatabaseLoader());
407     panning_model_ = model;
408   }
409   return true;
410 }
411 
DistanceModel() const412 String PannerHandler::DistanceModel() const {
413   switch (const_cast<PannerHandler*>(this)->distance_effect_.Model()) {
414     case DistanceEffect::kModelLinear:
415       return "linear";
416     case DistanceEffect::kModelInverse:
417       return "inverse";
418     case DistanceEffect::kModelExponential:
419       return "exponential";
420     default:
421       NOTREACHED();
422       return "inverse";
423   }
424 }
425 
SetDistanceModel(const String & model)426 void PannerHandler::SetDistanceModel(const String& model) {
427   if (model == "linear")
428     SetDistanceModel(DistanceEffect::kModelLinear);
429   else if (model == "inverse")
430     SetDistanceModel(DistanceEffect::kModelInverse);
431   else if (model == "exponential")
432     SetDistanceModel(DistanceEffect::kModelExponential);
433 }
434 
SetDistanceModel(unsigned model)435 bool PannerHandler::SetDistanceModel(unsigned model) {
436   switch (model) {
437     case DistanceEffect::kModelLinear:
438     case DistanceEffect::kModelInverse:
439     case DistanceEffect::kModelExponential:
440       if (model != distance_model_) {
441         // This synchronizes with process().
442         MutexLocker process_locker(process_lock_);
443         distance_effect_.SetModel(
444             static_cast<DistanceEffect::ModelType>(model));
445         distance_model_ = model;
446       }
447       break;
448     default:
449       NOTREACHED();
450       return false;
451   }
452 
453   return true;
454 }
455 
SetRefDistance(double distance)456 void PannerHandler::SetRefDistance(double distance) {
457   if (RefDistance() == distance)
458     return;
459 
460   // This synchronizes with process().
461   MutexLocker process_locker(process_lock_);
462   distance_effect_.SetRefDistance(distance);
463   MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
464 }
465 
SetMaxDistance(double distance)466 void PannerHandler::SetMaxDistance(double distance) {
467   if (MaxDistance() == distance)
468     return;
469 
470   // This synchronizes with process().
471   MutexLocker process_locker(process_lock_);
472   distance_effect_.SetMaxDistance(distance);
473   MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
474 }
475 
SetRolloffFactor(double factor)476 void PannerHandler::SetRolloffFactor(double factor) {
477   if (RolloffFactor() == factor)
478     return;
479 
480   // This synchronizes with process().
481   MutexLocker process_locker(process_lock_);
482   distance_effect_.SetRolloffFactor(factor);
483   MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
484 }
485 
SetConeInnerAngle(double angle)486 void PannerHandler::SetConeInnerAngle(double angle) {
487   if (ConeInnerAngle() == angle)
488     return;
489 
490   // This synchronizes with process().
491   MutexLocker process_locker(process_lock_);
492   cone_effect_.SetInnerAngle(angle);
493   MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
494 }
495 
SetConeOuterAngle(double angle)496 void PannerHandler::SetConeOuterAngle(double angle) {
497   if (ConeOuterAngle() == angle)
498     return;
499 
500   // This synchronizes with process().
501   MutexLocker process_locker(process_lock_);
502   cone_effect_.SetOuterAngle(angle);
503   MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
504 }
505 
SetConeOuterGain(double angle)506 void PannerHandler::SetConeOuterGain(double angle) {
507   if (ConeOuterGain() == angle)
508     return;
509 
510   // This synchronizes with process().
511   MutexLocker process_locker(process_lock_);
512   cone_effect_.SetOuterGain(angle);
513   MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
514 }
515 
SetPosition(float x,float y,float z,ExceptionState & exceptionState)516 void PannerHandler::SetPosition(float x,
517                                 float y,
518                                 float z,
519                                 ExceptionState& exceptionState) {
520   // This synchronizes with process().
521   MutexLocker process_locker(process_lock_);
522 
523   double now = Context()->currentTime();
524 
525   position_x_->Timeline().SetValueAtTime(x, now, exceptionState);
526   position_y_->Timeline().SetValueAtTime(y, now, exceptionState);
527   position_z_->Timeline().SetValueAtTime(z, now, exceptionState);
528 
529   MarkPannerAsDirty(PannerHandler::kAzimuthElevationDirty |
530                     PannerHandler::kDistanceConeGainDirty);
531 }
532 
SetOrientation(float x,float y,float z,ExceptionState & exceptionState)533 void PannerHandler::SetOrientation(float x,
534                                    float y,
535                                    float z,
536                                    ExceptionState& exceptionState) {
537   // This synchronizes with process().
538   MutexLocker process_locker(process_lock_);
539 
540   double now = Context()->currentTime();
541 
542   orientation_x_->Timeline().SetValueAtTime(x, now, exceptionState);
543   orientation_y_->Timeline().SetValueAtTime(y, now, exceptionState);
544   orientation_z_->Timeline().SetValueAtTime(z, now, exceptionState);
545 
546   MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
547 }
548 
CalculateAzimuthElevation(double * out_azimuth,double * out_elevation,const FloatPoint3D & position,const FloatPoint3D & listener_position,const FloatPoint3D & listener_forward,const FloatPoint3D & listener_up)549 void PannerHandler::CalculateAzimuthElevation(
550     double* out_azimuth,
551     double* out_elevation,
552     const FloatPoint3D& position,
553     const FloatPoint3D& listener_position,
554     const FloatPoint3D& listener_forward,
555     const FloatPoint3D& listener_up) {
556   // Calculate the source-listener vector
557   FloatPoint3D source_listener = position - listener_position;
558 
559   // Quick default return if the source and listener are at the same position.
560   if (source_listener.IsZero()) {
561     *out_azimuth = 0;
562     *out_elevation = 0;
563     return;
564   }
565 
566   // normalize() does nothing if the length of |sourceListener| is zero.
567   source_listener.Normalize();
568 
569   // Align axes
570   FloatPoint3D listener_right = listener_forward.Cross(listener_up);
571   listener_right.Normalize();
572 
573   FloatPoint3D listener_forward_norm = listener_forward;
574   listener_forward_norm.Normalize();
575 
576   FloatPoint3D up = listener_right.Cross(listener_forward_norm);
577 
578   float up_projection = source_listener.Dot(up);
579 
580   FloatPoint3D projected_source = source_listener - up_projection * up;
581   projected_source.Normalize();
582 
583   // Don't use AngleBetween here.  It produces the wrong value when one of the
584   // vectors has zero length.  We know here that |projected_source| and
585   // |listener_right| are "normalized", so the dot product is good enough.
586   double azimuth =
587       rad2deg(acos(clampTo(projected_source.Dot(listener_right), -1.0f, 1.0f)));
588   FixNANs(azimuth);  // avoid illegal values
589 
590   // Source  in front or behind the listener
591   double front_back = projected_source.Dot(listener_forward_norm);
592   if (front_back < 0.0)
593     azimuth = 360.0 - azimuth;
594 
595   // Make azimuth relative to "front" and not "right" listener vector
596   if ((azimuth >= 0.0) && (azimuth <= 270.0))
597     azimuth = 90.0 - azimuth;
598   else
599     azimuth = 450.0 - azimuth;
600 
601   // Elevation
602   double elevation = 90 - rad2deg(source_listener.AngleBetween(up));
603   FixNANs(elevation);  // avoid illegal values
604 
605   if (elevation > 90.0)
606     elevation = 180.0 - elevation;
607   else if (elevation < -90.0)
608     elevation = -180.0 - elevation;
609 
610   if (out_azimuth)
611     *out_azimuth = azimuth;
612   if (out_elevation)
613     *out_elevation = elevation;
614 }
615 
CalculateDistanceConeGain(const FloatPoint3D & position,const FloatPoint3D & orientation,const FloatPoint3D & listener_position)616 float PannerHandler::CalculateDistanceConeGain(
617     const FloatPoint3D& position,
618     const FloatPoint3D& orientation,
619     const FloatPoint3D& listener_position) {
620   double listener_distance = position.DistanceTo(listener_position);
621   double distance_gain = distance_effect_.Gain(listener_distance);
622   double cone_gain =
623       cone_effect_.Gain(position, orientation, listener_position);
624 
625   return float(distance_gain * cone_gain);
626 }
627 
AzimuthElevation(double * out_azimuth,double * out_elevation)628 void PannerHandler::AzimuthElevation(double* out_azimuth,
629                                      double* out_elevation) {
630   DCHECK(Context()->IsAudioThread());
631 
632   auto listener = Listener();
633   // Calculate new azimuth and elevation if the panner or the listener changed
634   // position or orientation in any way.
635   if (IsAzimuthElevationDirty() || listener->IsListenerDirty()) {
636     CalculateAzimuthElevation(&cached_azimuth_, &cached_elevation_,
637                               GetPosition(), listener->GetPosition(),
638                               listener->Orientation(), listener->UpVector());
639     is_azimuth_elevation_dirty_ = false;
640   }
641 
642   *out_azimuth = cached_azimuth_;
643   *out_elevation = cached_elevation_;
644 }
645 
DistanceConeGain()646 float PannerHandler::DistanceConeGain() {
647   DCHECK(Context()->IsAudioThread());
648 
649   auto listener = Listener();
650   // Calculate new distance and cone gain if the panner or the listener
651   // changed position or orientation in any way.
652   if (IsDistanceConeGainDirty() || listener->IsListenerDirty()) {
653     cached_distance_cone_gain_ = CalculateDistanceConeGain(
654         GetPosition(), Orientation(), listener->GetPosition());
655     is_distance_cone_gain_dirty_ = false;
656   }
657 
658   return cached_distance_cone_gain_;
659 }
660 
MarkPannerAsDirty(unsigned dirty)661 void PannerHandler::MarkPannerAsDirty(unsigned dirty) {
662   if (dirty & PannerHandler::kAzimuthElevationDirty)
663     is_azimuth_elevation_dirty_ = true;
664 
665   if (dirty & PannerHandler::kDistanceConeGainDirty)
666     is_distance_cone_gain_dirty_ = true;
667 }
668 
SetChannelCount(unsigned channel_count,ExceptionState & exception_state)669 void PannerHandler::SetChannelCount(unsigned channel_count,
670                                     ExceptionState& exception_state) {
671   DCHECK(IsMainThread());
672   BaseAudioContext::GraphAutoLocker locker(Context());
673 
674   // A PannerNode only supports 1 or 2 channels
675   if (channel_count > 0 && channel_count <= 2) {
676     if (channel_count_ != channel_count) {
677       channel_count_ = channel_count;
678       if (InternalChannelCountMode() != kMax)
679         UpdateChannelsForInputs();
680     }
681   } else {
682     exception_state.ThrowDOMException(
683         DOMExceptionCode::kNotSupportedError,
684         ExceptionMessages::IndexOutsideRange<uint32_t>(
685             "channelCount", channel_count, 1,
686             ExceptionMessages::kInclusiveBound, 2,
687             ExceptionMessages::kInclusiveBound));
688   }
689 }
690 
SetChannelCountMode(const String & mode,ExceptionState & exception_state)691 void PannerHandler::SetChannelCountMode(const String& mode,
692                                         ExceptionState& exception_state) {
693   DCHECK(IsMainThread());
694   BaseAudioContext::GraphAutoLocker locker(Context());
695 
696   ChannelCountMode old_mode = InternalChannelCountMode();
697 
698   if (mode == "clamped-max") {
699     new_channel_count_mode_ = kClampedMax;
700   } else if (mode == "explicit") {
701     new_channel_count_mode_ = kExplicit;
702   } else if (mode == "max") {
703     // This is not supported for a PannerNode, which can only handle 1 or 2
704     // channels.
705     exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
706                                       "Panner: 'max' is not allowed");
707     new_channel_count_mode_ = old_mode;
708   } else {
709     // Do nothing for other invalid values.
710     new_channel_count_mode_ = old_mode;
711   }
712 
713   if (new_channel_count_mode_ != old_mode)
714     Context()->GetDeferredTaskHandler().AddChangedChannelCountMode(this);
715 }
716 
HasSampleAccurateValues() const717 bool PannerHandler::HasSampleAccurateValues() const {
718   return position_x_->HasSampleAccurateValues() ||
719          position_y_->HasSampleAccurateValues() ||
720          position_z_->HasSampleAccurateValues() ||
721          orientation_x_->HasSampleAccurateValues() ||
722          orientation_y_->HasSampleAccurateValues() ||
723          orientation_z_->HasSampleAccurateValues();
724 }
725 
IsAudioRate() const726 bool PannerHandler::IsAudioRate() const {
727   return position_x_->IsAudioRate() || position_y_->IsAudioRate() ||
728          position_z_->IsAudioRate() || orientation_x_->IsAudioRate() ||
729          orientation_y_->IsAudioRate() || orientation_z_->IsAudioRate();
730 }
731 
UpdateDirtyState()732 void PannerHandler::UpdateDirtyState() {
733   DCHECK(Context()->IsAudioThread());
734 
735   FloatPoint3D current_position = GetPosition();
736   FloatPoint3D current_orientation = Orientation();
737 
738   bool has_moved = current_position != last_position_ ||
739                    current_orientation != last_orientation_;
740 
741   if (has_moved) {
742     last_position_ = current_position;
743     last_orientation_ = current_orientation;
744 
745     MarkPannerAsDirty(PannerHandler::kAzimuthElevationDirty |
746                       PannerHandler::kDistanceConeGainDirty);
747   }
748 }
749 
RequiresTailProcessing() const750 bool PannerHandler::RequiresTailProcessing() const {
751   // If there's no internal panner method set up yet, assume we require tail
752   // processing in case the HRTF panner is set later, which does require tail
753   // processing.
754   return panner_ ? panner_->RequiresTailProcessing() : true;
755 }
756 
757 // ----------------------------------------------------------------
758 
PannerNode(BaseAudioContext & context)759 PannerNode::PannerNode(BaseAudioContext& context)
760     : AudioNode(context),
761       position_x_(
762           AudioParam::Create(context,
763                              Uuid(),
764                              AudioParamHandler::kParamTypePannerPositionX,
765                              0.0,
766                              AudioParamHandler::AutomationRate::kAudio,
767                              AudioParamHandler::AutomationRateMode::kVariable)),
768       position_y_(
769           AudioParam::Create(context,
770                              Uuid(),
771                              AudioParamHandler::kParamTypePannerPositionY,
772                              0.0,
773                              AudioParamHandler::AutomationRate::kAudio,
774                              AudioParamHandler::AutomationRateMode::kVariable)),
775       position_z_(
776           AudioParam::Create(context,
777                              Uuid(),
778                              AudioParamHandler::kParamTypePannerPositionZ,
779                              0.0,
780                              AudioParamHandler::AutomationRate::kAudio,
781                              AudioParamHandler::AutomationRateMode::kVariable)),
782       orientation_x_(
783           AudioParam::Create(context,
784                              Uuid(),
785                              AudioParamHandler::kParamTypePannerOrientationX,
786                              1.0,
787                              AudioParamHandler::AutomationRate::kAudio,
788                              AudioParamHandler::AutomationRateMode::kVariable)),
789       orientation_y_(
790           AudioParam::Create(context,
791                              Uuid(),
792                              AudioParamHandler::kParamTypePannerOrientationY,
793                              0.0,
794                              AudioParamHandler::AutomationRate::kAudio,
795                              AudioParamHandler::AutomationRateMode::kVariable)),
796       orientation_z_(
797           AudioParam::Create(context,
798                              Uuid(),
799                              AudioParamHandler::kParamTypePannerOrientationZ,
800                              0.0,
801                              AudioParamHandler::AutomationRate::kAudio,
802                              AudioParamHandler::AutomationRateMode::kVariable)),
803       listener_(context.listener()) {
804   SetHandler(PannerHandler::Create(
805       *this, context.sampleRate(), position_x_->Handler(),
806       position_y_->Handler(), position_z_->Handler(), orientation_x_->Handler(),
807       orientation_y_->Handler(), orientation_z_->Handler()));
808 }
809 
Create(BaseAudioContext & context,ExceptionState & exception_state)810 PannerNode* PannerNode::Create(BaseAudioContext& context,
811                                ExceptionState& exception_state) {
812   DCHECK(IsMainThread());
813 
814   return MakeGarbageCollected<PannerNode>(context);
815 }
816 
Create(BaseAudioContext * context,const PannerOptions * options,ExceptionState & exception_state)817 PannerNode* PannerNode::Create(BaseAudioContext* context,
818                                const PannerOptions* options,
819                                ExceptionState& exception_state) {
820   PannerNode* node = Create(*context, exception_state);
821 
822   if (!node)
823     return nullptr;
824 
825   node->HandleChannelOptions(options, exception_state);
826 
827   node->setPanningModel(options->panningModel());
828   node->setDistanceModel(options->distanceModel());
829 
830   node->positionX()->setValue(options->positionX());
831   node->positionY()->setValue(options->positionY());
832   node->positionZ()->setValue(options->positionZ());
833 
834   node->orientationX()->setValue(options->orientationX());
835   node->orientationY()->setValue(options->orientationY());
836   node->orientationZ()->setValue(options->orientationZ());
837 
838   node->setRefDistance(options->refDistance(), exception_state);
839   node->setMaxDistance(options->maxDistance(), exception_state);
840   node->setRolloffFactor(options->rolloffFactor(), exception_state);
841   node->setConeInnerAngle(options->coneInnerAngle());
842   node->setConeOuterAngle(options->coneOuterAngle());
843   node->setConeOuterGain(options->coneOuterGain(), exception_state);
844 
845   return node;
846 }
847 
GetPannerHandler() const848 PannerHandler& PannerNode::GetPannerHandler() const {
849   return static_cast<PannerHandler&>(Handler());
850 }
851 
panningModel() const852 String PannerNode::panningModel() const {
853   return GetPannerHandler().PanningModel();
854 }
855 
setPanningModel(const String & model)856 void PannerNode::setPanningModel(const String& model) {
857   GetPannerHandler().SetPanningModel(model);
858 }
859 
setPosition(float x,float y,float z,ExceptionState & exceptionState)860 void PannerNode::setPosition(float x,
861                              float y,
862                              float z,
863                              ExceptionState& exceptionState) {
864   GetPannerHandler().SetPosition(x, y, z, exceptionState);
865 }
866 
setOrientation(float x,float y,float z,ExceptionState & exceptionState)867 void PannerNode::setOrientation(float x,
868                                 float y,
869                                 float z,
870                                 ExceptionState& exceptionState) {
871   GetPannerHandler().SetOrientation(x, y, z, exceptionState);
872 }
873 
distanceModel() const874 String PannerNode::distanceModel() const {
875   return GetPannerHandler().DistanceModel();
876 }
877 
setDistanceModel(const String & model)878 void PannerNode::setDistanceModel(const String& model) {
879   GetPannerHandler().SetDistanceModel(model);
880 }
881 
refDistance() const882 double PannerNode::refDistance() const {
883   return GetPannerHandler().RefDistance();
884 }
885 
setRefDistance(double distance,ExceptionState & exception_state)886 void PannerNode::setRefDistance(double distance,
887                                 ExceptionState& exception_state) {
888   if (distance < 0) {
889     exception_state.ThrowRangeError(
890         ExceptionMessages::IndexExceedsMinimumBound<double>("refDistance",
891                                                             distance, 0));
892     return;
893   }
894 
895   GetPannerHandler().SetRefDistance(distance);
896 }
897 
maxDistance() const898 double PannerNode::maxDistance() const {
899   return GetPannerHandler().MaxDistance();
900 }
901 
setMaxDistance(double distance,ExceptionState & exception_state)902 void PannerNode::setMaxDistance(double distance,
903                                 ExceptionState& exception_state) {
904   if (distance <= 0) {
905     exception_state.ThrowRangeError(
906         ExceptionMessages::IndexExceedsMinimumBound<double>("maxDistance",
907                                                             distance, 0));
908     return;
909   }
910 
911   GetPannerHandler().SetMaxDistance(distance);
912 }
913 
rolloffFactor() const914 double PannerNode::rolloffFactor() const {
915   return GetPannerHandler().RolloffFactor();
916 }
917 
setRolloffFactor(double factor,ExceptionState & exception_state)918 void PannerNode::setRolloffFactor(double factor,
919                                   ExceptionState& exception_state) {
920   if (factor < 0) {
921     exception_state.ThrowRangeError(
922         ExceptionMessages::IndexExceedsMinimumBound<double>("rolloffFactor",
923                                                             factor, 0));
924     return;
925   }
926 
927   GetPannerHandler().SetRolloffFactor(factor);
928 }
929 
coneInnerAngle() const930 double PannerNode::coneInnerAngle() const {
931   return GetPannerHandler().ConeInnerAngle();
932 }
933 
setConeInnerAngle(double angle)934 void PannerNode::setConeInnerAngle(double angle) {
935   GetPannerHandler().SetConeInnerAngle(angle);
936 }
937 
coneOuterAngle() const938 double PannerNode::coneOuterAngle() const {
939   return GetPannerHandler().ConeOuterAngle();
940 }
941 
setConeOuterAngle(double angle)942 void PannerNode::setConeOuterAngle(double angle) {
943   GetPannerHandler().SetConeOuterAngle(angle);
944 }
945 
coneOuterGain() const946 double PannerNode::coneOuterGain() const {
947   return GetPannerHandler().ConeOuterGain();
948 }
949 
setConeOuterGain(double gain,ExceptionState & exception_state)950 void PannerNode::setConeOuterGain(double gain,
951                                   ExceptionState& exception_state) {
952   if (gain < 0 || gain > 1) {
953     exception_state.ThrowDOMException(
954         DOMExceptionCode::kInvalidStateError,
955         ExceptionMessages::IndexOutsideRange<double>(
956             "coneOuterGain", gain, 0, ExceptionMessages::kInclusiveBound, 1,
957             ExceptionMessages::kInclusiveBound));
958     return;
959   }
960 
961   GetPannerHandler().SetConeOuterGain(gain);
962 }
963 
Trace(Visitor * visitor) const964 void PannerNode::Trace(Visitor* visitor) const {
965   visitor->Trace(position_x_);
966   visitor->Trace(position_y_);
967   visitor->Trace(position_z_);
968   visitor->Trace(orientation_x_);
969   visitor->Trace(orientation_y_);
970   visitor->Trace(orientation_z_);
971   visitor->Trace(listener_);
972   AudioNode::Trace(visitor);
973 }
974 
ReportDidCreate()975 void PannerNode::ReportDidCreate() {
976   GraphTracer().DidCreateAudioNode(this);
977   GraphTracer().DidCreateAudioParam(position_x_);
978   GraphTracer().DidCreateAudioParam(position_y_);
979   GraphTracer().DidCreateAudioParam(position_z_);
980   GraphTracer().DidCreateAudioParam(orientation_x_);
981   GraphTracer().DidCreateAudioParam(orientation_y_);
982   GraphTracer().DidCreateAudioParam(orientation_z_);
983 }
984 
ReportWillBeDestroyed()985 void PannerNode::ReportWillBeDestroyed() {
986   GraphTracer().WillDestroyAudioParam(position_x_);
987   GraphTracer().WillDestroyAudioParam(position_y_);
988   GraphTracer().WillDestroyAudioParam(position_z_);
989   GraphTracer().WillDestroyAudioParam(orientation_x_);
990   GraphTracer().WillDestroyAudioParam(orientation_y_);
991   GraphTracer().WillDestroyAudioParam(orientation_z_);
992   GraphTracer().WillDestroyAudioNode(this);
993 }
994 
995 }  // namespace blink
996