OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2010, Google Inc. All rights reserved. | 2 * Copyright (C) 2010, Google Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
6 * are met: | 6 * are met: |
7 * 1. Redistributions of source code must retain the above copyright | 7 * 1. Redistributions of source code must retain the above copyright |
8 * notice, this list of conditions and the following disclaimer. | 8 * notice, this list of conditions and the following disclaimer. |
9 * 2. Redistributions in binary form must reproduce the above copyright | 9 * 2. Redistributions in binary form must reproduce the above copyright |
10 * notice, this list of conditions and the following disclaimer in the | 10 * notice, this list of conditions and the following disclaimer in the |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
42 | 42 |
43 static void fixNANs(double &x) | 43 static void fixNANs(double &x) |
44 { | 44 { |
45 if (std::isnan(x) || std::isinf(x)) | 45 if (std::isnan(x) || std::isinf(x)) |
46 x = 0.0; | 46 x = 0.0; |
47 } | 47 } |
48 | 48 |
49 PannerNode::PannerNode(AudioContext* context, float sampleRate) | 49 PannerNode::PannerNode(AudioContext* context, float sampleRate) |
50 : AudioNode(context, sampleRate) | 50 : AudioNode(context, sampleRate) |
51 , m_panningModel(Panner::PanningModelHRTF) | 51 , m_panningModel(Panner::PanningModelHRTF) |
| 52 , m_position(0, 0, 0) |
| 53 , m_orientation(1, 0, 0) |
| 54 , m_velocity(0, 0, 0) |
| 55 , m_cachedPosition(0, 0, 0) |
| 56 , m_cachedOrientation(1, 0, 0) |
| 57 , m_cachedVelocity(0, 0, 0) |
52 , m_lastGain(-1.0) | 58 , m_lastGain(-1.0) |
| 59 , m_cachedAzimuth(0) |
| 60 , m_cachedElevation(0) |
| 61 , m_cachedDistanceConeGain(0) |
| 62 , m_cachedDopplerRate(1) |
53 , m_connectionCount(0) | 63 , m_connectionCount(0) |
54 { | 64 { |
55 // Load the HRTF database asynchronously so we don't block the Javascript th
read while creating the HRTF database. | 65 // Load the HRTF database asynchronously so we don't block the Javascript th
read while creating the HRTF database. |
56 // The HRTF panner will return zeroes until the database is loaded. | 66 // The HRTF panner will return zeroes until the database is loaded. |
57 m_hrtfDatabaseLoader = HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNece
ssary(context->sampleRate()); | 67 m_hrtfDatabaseLoader = HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNece
ssary(context->sampleRate()); |
58 | 68 |
59 ScriptWrappable::init(this); | 69 ScriptWrappable::init(this); |
60 addInput(adoptPtr(new AudioNodeInput(this))); | 70 addInput(adoptPtr(new AudioNodeInput(this))); |
61 addOutput(adoptPtr(new AudioNodeOutput(this, 2))); | 71 addOutput(adoptPtr(new AudioNodeOutput(this, 2))); |
62 | 72 |
63 // Node-specific default mixing rules. | 73 // Node-specific default mixing rules. |
64 m_channelCount = 2; | 74 m_channelCount = 2; |
65 m_channelCountMode = ClampedMax; | 75 m_channelCountMode = ClampedMax; |
66 m_channelInterpretation = AudioBus::Speakers; | 76 m_channelInterpretation = AudioBus::Speakers; |
67 | 77 |
68 m_distanceGain = AudioParam::create(context, "distanceGain", 1.0, 0.0, 1.0); | 78 m_distanceGain = AudioParam::create(context, "distanceGain", 1.0, 0.0, 1.0); |
69 m_coneGain = AudioParam::create(context, "coneGain", 1.0, 0.0, 1.0); | 79 m_coneGain = AudioParam::create(context, "coneGain", 1.0, 0.0, 1.0); |
70 | 80 |
71 m_position = FloatPoint3D(0, 0, 0); | 81 m_cachedListener = AudioListener::create(); |
72 m_orientation = FloatPoint3D(1, 0, 0); | |
73 m_velocity = FloatPoint3D(0, 0, 0); | |
74 | 82 |
75 setNodeType(NodeTypePanner); | 83 setNodeType(NodeTypePanner); |
76 | 84 |
77 initialize(); | 85 initialize(); |
78 } | 86 } |
79 | 87 |
80 PannerNode::~PannerNode() | 88 PannerNode::~PannerNode() |
81 { | 89 { |
82 uninitialize(); | 90 uninitialize(); |
83 } | 91 } |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
124 return; | 132 return; |
125 } | 133 } |
126 } | 134 } |
127 | 135 |
128 // The audio thread can't block on this lock, so we call tryLock() instead. | 136 // The audio thread can't block on this lock, so we call tryLock() instead. |
129 MutexTryLocker tryLocker(m_pannerLock); | 137 MutexTryLocker tryLocker(m_pannerLock); |
130 if (tryLocker.locked()) { | 138 if (tryLocker.locked()) { |
131 // Apply the panning effect. | 139 // Apply the panning effect. |
132 double azimuth; | 140 double azimuth; |
133 double elevation; | 141 double elevation; |
134 getAzimuthElevation(&azimuth, &elevation); | 142 azimuthElevation(&azimuth, &elevation); |
| 143 |
135 m_panner->pan(azimuth, elevation, source, destination, framesToProcess); | 144 m_panner->pan(azimuth, elevation, source, destination, framesToProcess); |
136 | 145 |
137 // Get the distance and cone gain. | 146 // Get the distance and cone gain. |
138 double totalGain = distanceConeGain(); | 147 float totalGain = distanceConeGain(); |
139 | 148 |
140 // Snap to desired gain at the beginning. | 149 // Snap to desired gain at the beginning. |
141 if (m_lastGain == -1.0) | 150 if (m_lastGain == -1.0) |
142 m_lastGain = totalGain; | 151 m_lastGain = totalGain; |
143 | 152 |
144 // Apply gain in-place with de-zippering. | 153 // Apply gain in-place with de-zippering. |
145 destination->copyWithGainFrom(*destination, &m_lastGain, totalGain); | 154 destination->copyWithGainFrom(*destination, &m_lastGain, totalGain); |
| 155 |
| 156 // Update the cached listener in case listener has moved. |
| 157 updateCachedListener(); |
| 158 // Now update the cached source location in case the source has changed. |
| 159 updateCachedSourceLocationInfo(); |
146 } else { | 160 } else { |
147 // Too bad - The tryLock() failed. We must be in the middle of changing
the panner. | 161 // Too bad - The tryLock() failed. We must be in the middle of changing
the panner. |
148 destination->zero(); | 162 destination->zero(); |
149 } | 163 } |
150 } | 164 } |
151 | 165 |
152 void PannerNode::initialize() | 166 void PannerNode::initialize() |
153 { | 167 { |
154 if (isInitialized()) | 168 if (isInitialized()) |
155 return; | 169 return; |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
210 m_panningModel = model; | 224 m_panningModel = model; |
211 } | 225 } |
212 break; | 226 break; |
213 default: | 227 default: |
214 return false; | 228 return false; |
215 } | 229 } |
216 | 230 |
217 return true; | 231 return true; |
218 } | 232 } |
219 | 233 |
| 234 void PannerNode::setPosition(float x, float y, float z) |
| 235 { |
| 236 // FIXME : consider thread safety about m_position in audio thread. |
| 237 // See http://crbugs.com/350583. |
| 238 FloatPoint3D position = FloatPoint3D(x, y, z); |
| 239 |
| 240 if (m_position == position) |
| 241 return; |
| 242 |
| 243 m_position = position; |
| 244 } |
| 245 |
| 246 void PannerNode::setOrientation(float x, float y, float z) |
| 247 { |
| 248 // FIXME : consider thread safety about m_orientation in audio thread. |
| 249 // See http://crbugs.com/350583. |
| 250 FloatPoint3D orientation = FloatPoint3D(x, y, z); |
| 251 |
| 252 if (m_orientation == orientation) |
| 253 return; |
| 254 |
| 255 m_orientation = orientation; |
| 256 } |
| 257 |
| 258 void PannerNode::setVelocity(float x, float y, float z) |
| 259 { |
| 260 // FIXME : consider thread safety about m_velocity in audio thread. |
| 261 // See http://crbugs.com/350583. |
| 262 FloatPoint3D velocity = FloatPoint3D(x, y, z); |
| 263 |
| 264 if (m_velocity == velocity) |
| 265 return; |
| 266 |
| 267 m_velocity = velocity; |
| 268 } |
| 269 |
220 String PannerNode::distanceModel() const | 270 String PannerNode::distanceModel() const |
221 { | 271 { |
222 switch (const_cast<PannerNode*>(this)->m_distanceEffect.model()) { | 272 switch (const_cast<PannerNode*>(this)->m_distanceEffect.model()) { |
223 case DistanceEffect::ModelLinear: | 273 case DistanceEffect::ModelLinear: |
224 return "linear"; | 274 return "linear"; |
225 case DistanceEffect::ModelInverse: | 275 case DistanceEffect::ModelInverse: |
226 return "inverse"; | 276 return "inverse"; |
227 case DistanceEffect::ModelExponential: | 277 case DistanceEffect::ModelExponential: |
228 return "exponential"; | 278 return "exponential"; |
229 default: | 279 default: |
(...skipping 22 matching lines...) Expand all Loading... |
252 case DistanceEffect::ModelExponential: | 302 case DistanceEffect::ModelExponential: |
253 m_distanceEffect.setModel(static_cast<DistanceEffect::ModelType>(model),
true); | 303 m_distanceEffect.setModel(static_cast<DistanceEffect::ModelType>(model),
true); |
254 break; | 304 break; |
255 default: | 305 default: |
256 return false; | 306 return false; |
257 } | 307 } |
258 | 308 |
259 return true; | 309 return true; |
260 } | 310 } |
261 | 311 |
262 void PannerNode::getAzimuthElevation(double* outAzimuth, double* outElevation) | 312 void PannerNode::calculateAzimuthElevation(double* outAzimuth, double* outElevat
ion) |
263 { | 313 { |
264 // FIXME: we should cache azimuth and elevation (if possible), so we only re
-calculate if a change has been made. | |
265 | |
266 double azimuth = 0.0; | 314 double azimuth = 0.0; |
267 | 315 |
268 // Calculate the source-listener vector | 316 // Calculate the source-listener vector |
269 FloatPoint3D listenerPosition = listener()->position(); | 317 FloatPoint3D listenerPosition = listener()->position(); |
270 FloatPoint3D sourceListener = m_position - listenerPosition; | 318 FloatPoint3D sourceListener = m_position - listenerPosition; |
271 | 319 |
272 if (sourceListener.isZero()) { | 320 if (sourceListener.isZero()) { |
273 // degenerate case if source and listener are at the same point | 321 // degenerate case if source and listener are at the same point |
274 *outAzimuth = 0.0; | 322 *outAzimuth = 0.0; |
275 *outElevation = 0.0; | 323 *outElevation = 0.0; |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
316 elevation = 180.0 - elevation; | 364 elevation = 180.0 - elevation; |
317 else if (elevation < -90.0) | 365 else if (elevation < -90.0) |
318 elevation = -180.0 - elevation; | 366 elevation = -180.0 - elevation; |
319 | 367 |
320 if (outAzimuth) | 368 if (outAzimuth) |
321 *outAzimuth = azimuth; | 369 *outAzimuth = azimuth; |
322 if (outElevation) | 370 if (outElevation) |
323 *outElevation = elevation; | 371 *outElevation = elevation; |
324 } | 372 } |
325 | 373 |
326 float PannerNode::dopplerRate() | 374 |
| 375 double PannerNode::calculateDopplerRate() |
327 { | 376 { |
328 double dopplerShift = 1.0; | 377 double dopplerShift = 1.0; |
329 | |
330 // FIXME: optimize for case when neither source nor listener has changed... | |
331 double dopplerFactor = listener()->dopplerFactor(); | 378 double dopplerFactor = listener()->dopplerFactor(); |
332 | 379 |
333 if (dopplerFactor > 0.0) { | 380 if (dopplerFactor > 0.0) { |
334 double speedOfSound = listener()->speedOfSound(); | 381 double speedOfSound = listener()->speedOfSound(); |
335 | 382 |
336 const FloatPoint3D &sourceVelocity = m_velocity; | 383 const FloatPoint3D &sourceVelocity = m_velocity; |
337 const FloatPoint3D &listenerVelocity = listener()->velocity(); | 384 const FloatPoint3D &listenerVelocity = listener()->velocity(); |
338 | 385 |
339 // Don't bother if both source and listener have no velocity | 386 // Don't bother if both source and listener have no velocity |
340 bool sourceHasVelocity = !sourceVelocity.isZero(); | 387 bool sourceHasVelocity = !sourceVelocity.isZero(); |
(...skipping 20 matching lines...) Expand all Loading... |
361 fixNANs(dopplerShift); // avoid illegal values | 408 fixNANs(dopplerShift); // avoid illegal values |
362 | 409 |
363 // Limit the pitch shifting to 4 octaves up and 3 octaves down. | 410 // Limit the pitch shifting to 4 octaves up and 3 octaves down. |
364 if (dopplerShift > 16.0) | 411 if (dopplerShift > 16.0) |
365 dopplerShift = 16.0; | 412 dopplerShift = 16.0; |
366 else if (dopplerShift < 0.125) | 413 else if (dopplerShift < 0.125) |
367 dopplerShift = 0.125; | 414 dopplerShift = 0.125; |
368 } | 415 } |
369 } | 416 } |
370 | 417 |
371 return static_cast<float>(dopplerShift); | 418 return dopplerShift; |
372 } | 419 } |
373 | 420 |
374 float PannerNode::distanceConeGain() | 421 float PannerNode::calculateDistanceConeGain() |
375 { | 422 { |
376 FloatPoint3D listenerPosition = listener()->position(); | 423 FloatPoint3D listenerPosition = listener()->position(); |
377 | 424 |
378 double listenerDistance = m_position.distanceTo(listenerPosition); | 425 double listenerDistance = m_position.distanceTo(listenerPosition); |
379 double distanceGain = m_distanceEffect.gain(listenerDistance); | 426 double distanceGain = m_distanceEffect.gain(listenerDistance); |
380 | 427 |
381 m_distanceGain->setValue(static_cast<float>(distanceGain)); | 428 m_distanceGain->setValue(static_cast<float>(distanceGain)); |
382 | 429 |
383 // FIXME: could optimize by caching coneGain | |
384 double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosit
ion); | 430 double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosit
ion); |
385 | 431 |
386 m_coneGain->setValue(static_cast<float>(coneGain)); | 432 m_coneGain->setValue(static_cast<float>(coneGain)); |
387 | 433 |
388 return float(distanceGain * coneGain); | 434 return float(distanceGain * coneGain); |
389 } | 435 } |
390 | 436 |
| 437 void PannerNode::azimuthElevation(double* outAzimuth, double* outElevation) |
| 438 { |
| 439 if (isAzimuthElevationDirty()) |
| 440 calculateAzimuthElevation(&m_cachedAzimuth, &m_cachedElevation); |
| 441 |
| 442 *outAzimuth = m_cachedAzimuth; |
| 443 *outElevation = m_cachedElevation; |
| 444 } |
| 445 |
| 446 double PannerNode::dopplerRate() |
| 447 { |
| 448 if (isDopplerRateDirty()) |
| 449 m_cachedDopplerRate = calculateDopplerRate(); |
| 450 |
| 451 return m_cachedDopplerRate; |
| 452 } |
| 453 |
| 454 float PannerNode::distanceConeGain() |
| 455 { |
| 456 if (isDistanceConeGainDirty()) |
| 457 m_cachedDistanceConeGain = calculateDistanceConeGain(); |
| 458 |
| 459 return m_cachedDistanceConeGain; |
| 460 } |
| 461 |
| 462 bool PannerNode::isAzimuthElevationDirty() |
| 463 { |
| 464 // Do a quick test and return if possible. |
| 465 if (m_cachedPosition != m_position) |
| 466 return true; |
| 467 |
| 468 if (m_cachedListener->position() != listener()->position() |
| 469 || m_cachedListener->orientation() != listener()->orientation() |
| 470 || m_cachedListener->upVector() != listener()->upVector()) |
| 471 return true; |
| 472 |
| 473 return false; |
| 474 } |
| 475 |
| 476 bool PannerNode::isDistanceConeGainDirty() |
| 477 { |
| 478 // Do a quick test and return if possible. |
| 479 if (m_cachedPosition != m_position || m_cachedOrientation != m_orientation) |
| 480 return true; |
| 481 |
| 482 if (m_cachedListener->position() != listener()->position()) |
| 483 return true; |
| 484 |
| 485 return false; |
| 486 } |
| 487 |
| 488 bool PannerNode::isDopplerRateDirty() |
| 489 { |
| 490 // Do a quick test and return if possible. |
| 491 if (m_cachedPosition != m_position || m_cachedVelocity != m_velocity) |
| 492 return true; |
| 493 |
| 494 if (m_cachedListener->position() != listener()->position() |
| 495 || m_cachedListener->velocity() != listener()->velocity() |
| 496 || m_cachedListener->dopplerFactor() != listener()->dopplerFactor() |
| 497 || m_cachedListener->speedOfSound() != listener()->speedOfSound()) |
| 498 return true; |
| 499 |
| 500 return false; |
| 501 } |
| 502 |
391 void PannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node, HashMap<Audi
oNode*, bool>& visitedNodes) | 503 void PannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node, HashMap<Audi
oNode*, bool>& visitedNodes) |
392 { | 504 { |
393 ASSERT(node); | 505 ASSERT(node); |
394 if (!node) | 506 if (!node) |
395 return; | 507 return; |
396 | 508 |
397 // First check if this node is an AudioBufferSourceNode. If so, let it know
about us so that doppler shift pitch can be taken into account. | 509 // First check if this node is an AudioBufferSourceNode. If so, let it know
about us so that doppler shift pitch can be taken into account. |
398 if (node->nodeType() == NodeTypeAudioBufferSource) { | 510 if (node->nodeType() == NodeTypeAudioBufferSource) { |
399 AudioBufferSourceNode* bufferSourceNode = static_cast<AudioBufferSourceN
ode*>(node); | 511 AudioBufferSourceNode* bufferSourceNode = static_cast<AudioBufferSourceN
ode*>(node); |
400 bufferSourceNode->setPannerNode(this); | 512 bufferSourceNode->setPannerNode(this); |
(...skipping 12 matching lines...) Expand all Loading... |
413 // mark it as visited and recurse through the node looking for s
ources. | 525 // mark it as visited and recurse through the node looking for s
ources. |
414 if (iterator == visitedNodes.end()) { | 526 if (iterator == visitedNodes.end()) { |
415 visitedNodes.set(connectedNode, true); | 527 visitedNodes.set(connectedNode, true); |
416 notifyAudioSourcesConnectedToNode(connectedNode, visitedNode
s); // recurse | 528 notifyAudioSourcesConnectedToNode(connectedNode, visitedNode
s); // recurse |
417 } | 529 } |
418 } | 530 } |
419 } | 531 } |
420 } | 532 } |
421 } | 533 } |
422 | 534 |
| 535 void PannerNode::updateCachedListener() |
| 536 { |
| 537 m_cachedListener->setPosition(listener()->position()); |
| 538 m_cachedListener->setOrientation(listener()->orientation()); |
| 539 m_cachedListener->setUpVector(listener()->upVector()); |
| 540 m_cachedListener->setVelocity(listener()->velocity()); |
| 541 m_cachedListener->setDopplerFactor(listener()->dopplerFactor()); |
| 542 m_cachedListener->setSpeedOfSound(listener()->speedOfSound()); |
| 543 } |
| 544 |
| 545 void PannerNode::updateCachedSourceLocationInfo() |
| 546 { |
| 547 m_cachedPosition = m_position; |
| 548 m_cachedOrientation = m_orientation; |
| 549 m_cachedVelocity = m_velocity; |
| 550 } |
| 551 |
423 } // namespace WebCore | 552 } // namespace WebCore |
424 | 553 |
425 #endif // ENABLE(WEB_AUDIO) | 554 #endif // ENABLE(WEB_AUDIO) |
OLD | NEW |