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