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