PannerNode.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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 ANY
  14. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  15. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  16. * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
  17. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  18. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  19. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  20. * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  21. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  22. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  23. */
  24. #include "config.h"
  25. #if ENABLE(WEB_AUDIO)
  26. #include "PannerNode.h"
  27. #include "AudioBufferSourceNode.h"
  28. #include "AudioBus.h"
  29. #include "AudioContext.h"
  30. #include "AudioNodeInput.h"
  31. #include "AudioNodeOutput.h"
  32. #include "ExceptionCode.h"
  33. #include "HRTFPanner.h"
  34. #include "ScriptExecutionContext.h"
  35. #include <wtf/MathExtras.h>
  36. using namespace std;
  37. namespace WebCore {
  38. static void fixNANs(double &x)
  39. {
  40. if (std::isnan(x) || std::isinf(x))
  41. x = 0.0;
  42. }
  43. PannerNode::PannerNode(AudioContext* context, float sampleRate)
  44. : AudioNode(context, sampleRate)
  45. , m_panningModel(Panner::PanningModelHRTF)
  46. , m_lastGain(-1.0)
  47. , m_connectionCount(0)
  48. {
  49. addInput(adoptPtr(new AudioNodeInput(this)));
  50. addOutput(adoptPtr(new AudioNodeOutput(this, 2)));
  51. // Node-specific default mixing rules.
  52. m_channelCount = 2;
  53. m_channelCountMode = ClampedMax;
  54. m_channelInterpretation = AudioBus::Speakers;
  55. m_distanceGain = AudioParam::create(context, "distanceGain", 1.0, 0.0, 1.0);
  56. m_coneGain = AudioParam::create(context, "coneGain", 1.0, 0.0, 1.0);
  57. m_position = FloatPoint3D(0, 0, 0);
  58. m_orientation = FloatPoint3D(1, 0, 0);
  59. m_velocity = FloatPoint3D(0, 0, 0);
  60. setNodeType(NodeTypePanner);
  61. initialize();
  62. }
  63. PannerNode::~PannerNode()
  64. {
  65. uninitialize();
  66. }
  67. void PannerNode::pullInputs(size_t framesToProcess)
  68. {
  69. // We override pullInputs(), so we can detect new AudioSourceNodes which have connected to us when new connections are made.
  70. // These AudioSourceNodes need to be made aware of our existence in order to handle doppler shift pitch changes.
  71. if (m_connectionCount != context()->connectionCount()) {
  72. m_connectionCount = context()->connectionCount();
  73. // Recursively go through all nodes connected to us.
  74. notifyAudioSourcesConnectedToNode(this);
  75. }
  76. AudioNode::pullInputs(framesToProcess);
  77. }
  78. void PannerNode::process(size_t framesToProcess)
  79. {
  80. AudioBus* destination = output(0)->bus();
  81. if (!isInitialized() || !input(0)->isConnected() || !m_panner.get()) {
  82. destination->zero();
  83. return;
  84. }
  85. AudioBus* source = input(0)->bus();
  86. if (!source) {
  87. destination->zero();
  88. return;
  89. }
  90. // The audio thread can't block on this lock, so we call tryLock() instead.
  91. MutexTryLocker tryLocker(m_pannerLock);
  92. if (tryLocker.locked()) {
  93. // Apply the panning effect.
  94. double azimuth;
  95. double elevation;
  96. getAzimuthElevation(&azimuth, &elevation);
  97. m_panner->pan(azimuth, elevation, source, destination, framesToProcess);
  98. // Get the distance and cone gain.
  99. double totalGain = distanceConeGain();
  100. // Snap to desired gain at the beginning.
  101. if (m_lastGain == -1.0)
  102. m_lastGain = totalGain;
  103. // Apply gain in-place with de-zippering.
  104. destination->copyWithGainFrom(*destination, &m_lastGain, totalGain);
  105. } else {
  106. // Too bad - The tryLock() failed. We must be in the middle of changing the panner.
  107. destination->zero();
  108. }
  109. }
  110. void PannerNode::reset()
  111. {
  112. m_lastGain = -1.0; // force to snap to initial gain
  113. if (m_panner.get())
  114. m_panner->reset();
  115. }
  116. void PannerNode::initialize()
  117. {
  118. if (isInitialized())
  119. return;
  120. m_panner = Panner::create(m_panningModel, sampleRate(), context()->hrtfDatabaseLoader());
  121. AudioNode::initialize();
  122. }
  123. void PannerNode::uninitialize()
  124. {
  125. if (!isInitialized())
  126. return;
  127. m_panner.clear();
  128. AudioNode::uninitialize();
  129. }
  130. AudioListener* PannerNode::listener()
  131. {
  132. return context()->listener();
  133. }
  134. String PannerNode::panningModel() const
  135. {
  136. switch (m_panningModel) {
  137. case EQUALPOWER:
  138. return "equalpower";
  139. case HRTF:
  140. return "HRTF";
  141. case SOUNDFIELD:
  142. return "soundfield";
  143. default:
  144. ASSERT_NOT_REACHED();
  145. return "HRTF";
  146. }
  147. }
  148. void PannerNode::setPanningModel(const String& model)
  149. {
  150. if (model == "equalpower")
  151. setPanningModel(EQUALPOWER);
  152. else if (model == "HRTF")
  153. setPanningModel(HRTF);
  154. else if (model == "soundfield")
  155. setPanningModel(SOUNDFIELD);
  156. else
  157. ASSERT_NOT_REACHED();
  158. }
  159. bool PannerNode::setPanningModel(unsigned model)
  160. {
  161. switch (model) {
  162. case EQUALPOWER:
  163. case HRTF:
  164. if (!m_panner.get() || model != m_panningModel) {
  165. // This synchronizes with process().
  166. MutexLocker processLocker(m_pannerLock);
  167. OwnPtr<Panner> newPanner = Panner::create(model, sampleRate(), context()->hrtfDatabaseLoader());
  168. m_panner = newPanner.release();
  169. m_panningModel = model;
  170. }
  171. break;
  172. case SOUNDFIELD:
  173. // FIXME: Implement sound field model. See // https://bugs.webkit.org/show_bug.cgi?id=77367.
  174. context()->scriptExecutionContext()->addConsoleMessage(JSMessageSource, WarningMessageLevel, "'soundfield' panning model not implemented.");
  175. break;
  176. default:
  177. return false;
  178. }
  179. return true;
  180. }
  181. String PannerNode::distanceModel() const
  182. {
  183. switch (const_cast<PannerNode*>(this)->m_distanceEffect.model()) {
  184. case DistanceEffect::ModelLinear:
  185. return "linear";
  186. case DistanceEffect::ModelInverse:
  187. return "inverse";
  188. case DistanceEffect::ModelExponential:
  189. return "exponential";
  190. default:
  191. ASSERT_NOT_REACHED();
  192. return "inverse";
  193. }
  194. }
  195. void PannerNode::setDistanceModel(const String& model)
  196. {
  197. if (model == "linear")
  198. setDistanceModel(DistanceEffect::ModelLinear);
  199. else if (model == "inverse")
  200. setDistanceModel(DistanceEffect::ModelInverse);
  201. else if (model == "exponential")
  202. setDistanceModel(DistanceEffect::ModelExponential);
  203. else
  204. ASSERT_NOT_REACHED();
  205. }
  206. bool PannerNode::setDistanceModel(unsigned model)
  207. {
  208. switch (model) {
  209. case DistanceEffect::ModelLinear:
  210. case DistanceEffect::ModelInverse:
  211. case DistanceEffect::ModelExponential:
  212. m_distanceEffect.setModel(static_cast<DistanceEffect::ModelType>(model), true);
  213. break;
  214. default:
  215. return false;
  216. }
  217. return true;
  218. }
  219. void PannerNode::getAzimuthElevation(double* outAzimuth, double* outElevation)
  220. {
  221. // FIXME: we should cache azimuth and elevation (if possible), so we only re-calculate if a change has been made.
  222. double azimuth = 0.0;
  223. // Calculate the source-listener vector
  224. FloatPoint3D listenerPosition = listener()->position();
  225. FloatPoint3D sourceListener = m_position - listenerPosition;
  226. if (sourceListener.isZero()) {
  227. // degenerate case if source and listener are at the same point
  228. *outAzimuth = 0.0;
  229. *outElevation = 0.0;
  230. return;
  231. }
  232. sourceListener.normalize();
  233. // Align axes
  234. FloatPoint3D listenerFront = listener()->orientation();
  235. FloatPoint3D listenerUp = listener()->upVector();
  236. FloatPoint3D listenerRight = listenerFront.cross(listenerUp);
  237. listenerRight.normalize();
  238. FloatPoint3D listenerFrontNorm = listenerFront;
  239. listenerFrontNorm.normalize();
  240. FloatPoint3D up = listenerRight.cross(listenerFrontNorm);
  241. float upProjection = sourceListener.dot(up);
  242. FloatPoint3D projectedSource = sourceListener - upProjection * up;
  243. projectedSource.normalize();
  244. azimuth = 180.0 * acos(projectedSource.dot(listenerRight)) / piDouble;
  245. fixNANs(azimuth); // avoid illegal values
  246. // Source in front or behind the listener
  247. double frontBack = projectedSource.dot(listenerFrontNorm);
  248. if (frontBack < 0.0)
  249. azimuth = 360.0 - azimuth;
  250. // Make azimuth relative to "front" and not "right" listener vector
  251. if ((azimuth >= 0.0) && (azimuth <= 270.0))
  252. azimuth = 90.0 - azimuth;
  253. else
  254. azimuth = 450.0 - azimuth;
  255. // Elevation
  256. double elevation = 90.0 - 180.0 * acos(sourceListener.dot(up)) / piDouble;
  257. fixNANs(elevation); // avoid illegal values
  258. if (elevation > 90.0)
  259. elevation = 180.0 - elevation;
  260. else if (elevation < -90.0)
  261. elevation = -180.0 - elevation;
  262. if (outAzimuth)
  263. *outAzimuth = azimuth;
  264. if (outElevation)
  265. *outElevation = elevation;
  266. }
  267. float PannerNode::dopplerRate()
  268. {
  269. double dopplerShift = 1.0;
  270. // FIXME: optimize for case when neither source nor listener has changed...
  271. double dopplerFactor = listener()->dopplerFactor();
  272. if (dopplerFactor > 0.0) {
  273. double speedOfSound = listener()->speedOfSound();
  274. const FloatPoint3D &sourceVelocity = m_velocity;
  275. const FloatPoint3D &listenerVelocity = listener()->velocity();
  276. // Don't bother if both source and listener have no velocity
  277. bool sourceHasVelocity = !sourceVelocity.isZero();
  278. bool listenerHasVelocity = !listenerVelocity.isZero();
  279. if (sourceHasVelocity || listenerHasVelocity) {
  280. // Calculate the source to listener vector
  281. FloatPoint3D listenerPosition = listener()->position();
  282. FloatPoint3D sourceToListener = m_position - listenerPosition;
  283. double sourceListenerMagnitude = sourceToListener.length();
  284. double listenerProjection = sourceToListener.dot(listenerVelocity) / sourceListenerMagnitude;
  285. double sourceProjection = sourceToListener.dot(sourceVelocity) / sourceListenerMagnitude;
  286. listenerProjection = -listenerProjection;
  287. sourceProjection = -sourceProjection;
  288. double scaledSpeedOfSound = speedOfSound / dopplerFactor;
  289. listenerProjection = min(listenerProjection, scaledSpeedOfSound);
  290. sourceProjection = min(sourceProjection, scaledSpeedOfSound);
  291. dopplerShift = ((speedOfSound - dopplerFactor * listenerProjection) / (speedOfSound - dopplerFactor * sourceProjection));
  292. fixNANs(dopplerShift); // avoid illegal values
  293. // Limit the pitch shifting to 4 octaves up and 3 octaves down.
  294. if (dopplerShift > 16.0)
  295. dopplerShift = 16.0;
  296. else if (dopplerShift < 0.125)
  297. dopplerShift = 0.125;
  298. }
  299. }
  300. return static_cast<float>(dopplerShift);
  301. }
  302. float PannerNode::distanceConeGain()
  303. {
  304. FloatPoint3D listenerPosition = listener()->position();
  305. double listenerDistance = m_position.distanceTo(listenerPosition);
  306. double distanceGain = m_distanceEffect.gain(listenerDistance);
  307. m_distanceGain->setValue(static_cast<float>(distanceGain));
  308. // FIXME: could optimize by caching coneGain
  309. double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosition);
  310. m_coneGain->setValue(static_cast<float>(coneGain));
  311. return float(distanceGain * coneGain);
  312. }
  313. void PannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node)
  314. {
  315. ASSERT(node);
  316. if (!node)
  317. return;
  318. // 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.
  319. if (node->nodeType() == NodeTypeAudioBufferSource) {
  320. AudioBufferSourceNode* bufferSourceNode = reinterpret_cast<AudioBufferSourceNode*>(node);
  321. bufferSourceNode->setPannerNode(this);
  322. } else {
  323. // Go through all inputs to this node.
  324. for (unsigned i = 0; i < node->numberOfInputs(); ++i) {
  325. AudioNodeInput* input = node->input(i);
  326. // For each input, go through all of its connections, looking for AudioBufferSourceNodes.
  327. for (unsigned j = 0; j < input->numberOfRenderingConnections(); ++j) {
  328. AudioNodeOutput* connectedOutput = input->renderingOutput(j);
  329. AudioNode* connectedNode = connectedOutput->node();
  330. notifyAudioSourcesConnectedToNode(connectedNode); // recurse
  331. }
  332. }
  333. }
  334. }
  335. } // namespace WebCore
  336. #endif // ENABLE(WEB_AUDIO)