// This algorithm is specicied in the webaudio spec. void PannerNodeEngine::ComputeAzimuthAndElevation(float& aAzimuth, float& aElevation) { ThreeDPoint sourceListener = mPosition - mListenerPosition; if (sourceListener.IsZero()) { aAzimuth = 0.0; aElevation = 0.0; return; } sourceListener.Normalize(); // Project the source-listener vector on the x-z plane. ThreeDPoint& listenerFront = mListenerOrientation; ThreeDPoint listenerRightNorm = listenerFront.CrossProduct(mListenerUpVector); listenerRightNorm.Normalize(); ThreeDPoint listenerFrontNorm(listenerFront); listenerFrontNorm.Normalize(); ThreeDPoint up = listenerRightNorm.CrossProduct(listenerFrontNorm); double upProjection = sourceListener.DotProduct(up); ThreeDPoint projectedSource = sourceListener - up * upProjection; projectedSource.Normalize(); // Actually compute the angle, and convert to degrees double projection = projectedSource.DotProduct(listenerRightNorm); aAzimuth = 180 * acos(projection) / M_PI; // Compute whether the source is in front or behind the listener. double frontBack = projectedSource.DotProduct(listenerFrontNorm); if (frontBack < 0) { aAzimuth = 360 - aAzimuth; } // Rotate the azimuth so it is relative to the listener front vector instead // of the right vector. if ((aAzimuth >= 0) && (aAzimuth <= 270)) { aAzimuth = 90 - aAzimuth; } else { aAzimuth = 450 - aAzimuth; } aElevation = 90 - 180 * acos(sourceListener.DotProduct(up)) / M_PI; if (aElevation > 90) { aElevation = 180 - aElevation; } else if (aElevation < -90) { aElevation = -180 - aElevation; } }
// This algorithm is described in the WebAudio spec. float PannerNodeEngine::ComputeConeGain() { // Omnidirectional source if (mOrientation.IsZero() || ((mConeInnerAngle == 360) && (mConeOuterAngle == 360))) { return 1; } // Normalized source-listener vector ThreeDPoint sourceToListener = mListenerPosition - mPosition; sourceToListener.Normalize(); ThreeDPoint normalizedSourceOrientation = mOrientation; normalizedSourceOrientation.Normalize(); // Angle between the source orientation vector and the source-listener vector double dotProduct = sourceToListener.DotProduct(normalizedSourceOrientation); double angle = 180 * acos(dotProduct) / M_PI; double absAngle = fabs(angle); // Divide by 2 here since API is entire angle (not half-angle) double absInnerAngle = fabs(mConeInnerAngle) / 2; double absOuterAngle = fabs(mConeOuterAngle) / 2; double gain = 1; if (absAngle <= absInnerAngle) { // No attenuation gain = 1; } else if (absAngle >= absOuterAngle) { // Max attenuation gain = mConeOuterGain; } else { // Between inner and outer cones // inner -> outer, x goes from 0 -> 1 double x = (absAngle - absInnerAngle) / (absOuterAngle - absInnerAngle); gain = (1 - x) + mConeOuterGain * x; } return gain; }
void PannerNodeEngine::HRTFPanningFunction(const AudioBlock& aInput, AudioBlock* aOutput, StreamTime tick) { // The output of this node is always stereo, no matter what the inputs are. aOutput->AllocateChannels(2); float azimuth, elevation; ThreeDPoint position = ConvertAudioParamTimelineTo3DP(mPositionX, mPositionY, mPositionZ, tick); ThreeDPoint orientation = ConvertAudioParamTimelineTo3DP(mOrientationX, mOrientationY, mOrientationZ, tick); if (!orientation.IsZero()) { orientation.Normalize(); } ComputeAzimuthAndElevation(position, azimuth, elevation); AudioBlock input = aInput; // Gain is applied before the delay and convolution of the HRTF. input.mVolume *= ComputeConeGain(position, orientation) * ComputeDistanceGain(position); mHRTFPanner->pan(azimuth, elevation, &input, aOutput); }
void PannerNodeEngine::EqualPowerPanningFunction(const AudioBlock& aInput, AudioBlock* aOutput, StreamTime tick) { float azimuth, elevation, gainL, gainR, normalizedAzimuth, distanceGain, coneGain; int inputChannels = aInput.ChannelCount(); // Optimize the case where the position and orientation is constant for this // processing block: we can just apply a constant gain on the left and right // channel if (mPositionX.HasSimpleValue() && mPositionY.HasSimpleValue() && mPositionZ.HasSimpleValue() && mOrientationX.HasSimpleValue() && mOrientationY.HasSimpleValue() && mOrientationZ.HasSimpleValue()) { ThreeDPoint position = ConvertAudioParamTimelineTo3DP(mPositionX, mPositionY, mPositionZ, tick); ThreeDPoint orientation = ConvertAudioParamTimelineTo3DP(mOrientationX, mOrientationY, mOrientationZ, tick); if (!orientation.IsZero()) { orientation.Normalize(); } // If both the listener are in the same spot, and no cone gain is specified, // this node is noop. if (mListenerPosition == position && mConeInnerAngle == 360 && mConeOuterAngle == 360) { *aOutput = aInput; return; } // The output of this node is always stereo, no matter what the inputs are. aOutput->AllocateChannels(2); ComputeAzimuthAndElevation(position, azimuth, elevation); coneGain = ComputeConeGain(position, orientation); // The following algorithm is described in the spec. // Clamp azimuth in the [-90, 90] range. azimuth = min(180.f, max(-180.f, azimuth)); // Wrap around if (azimuth < -90.f) { azimuth = -180.f - azimuth; } else if (azimuth > 90) { azimuth = 180.f - azimuth; } // Normalize the value in the [0, 1] range. if (inputChannels == 1) { normalizedAzimuth = (azimuth + 90.f) / 180.f; } else { if (azimuth <= 0) { normalizedAzimuth = (azimuth + 90.f) / 90.f; } else { normalizedAzimuth = azimuth / 90.f; } } distanceGain = ComputeDistanceGain(position); // Actually compute the left and right gain. gainL = cos(0.5 * M_PI * normalizedAzimuth); gainR = sin(0.5 * M_PI * normalizedAzimuth); // Compute the output. ApplyStereoPanning(aInput, aOutput, gainL, gainR, azimuth <= 0); aOutput->mVolume = aInput.mVolume * distanceGain * coneGain; } else { float positionX[WEBAUDIO_BLOCK_SIZE]; float positionY[WEBAUDIO_BLOCK_SIZE]; float positionZ[WEBAUDIO_BLOCK_SIZE]; float orientationX[WEBAUDIO_BLOCK_SIZE]; float orientationY[WEBAUDIO_BLOCK_SIZE]; float orientationZ[WEBAUDIO_BLOCK_SIZE]; // The output of this node is always stereo, no matter what the inputs are. aOutput->AllocateChannels(2); if (!mPositionX.HasSimpleValue()) { mPositionX.GetValuesAtTime(tick, positionX, WEBAUDIO_BLOCK_SIZE); } else { positionX[0] = mPositionX.GetValueAtTime(tick); } if (!mPositionY.HasSimpleValue()) { mPositionY.GetValuesAtTime(tick, positionY, WEBAUDIO_BLOCK_SIZE); } else { positionY[0] = mPositionY.GetValueAtTime(tick); } if (!mPositionZ.HasSimpleValue()) { mPositionZ.GetValuesAtTime(tick, positionZ, WEBAUDIO_BLOCK_SIZE); } else { positionZ[0] = mPositionZ.GetValueAtTime(tick); } if (!mOrientationX.HasSimpleValue()) { mOrientationX.GetValuesAtTime(tick, orientationX, WEBAUDIO_BLOCK_SIZE); } else { orientationX[0] = mOrientationX.GetValueAtTime(tick); } if (!mOrientationY.HasSimpleValue()) { mOrientationY.GetValuesAtTime(tick, orientationY, WEBAUDIO_BLOCK_SIZE); } else { orientationY[0] = mOrientationY.GetValueAtTime(tick); } if (!mOrientationZ.HasSimpleValue()) { mOrientationZ.GetValuesAtTime(tick, orientationZ, WEBAUDIO_BLOCK_SIZE); } else { orientationZ[0] = mOrientationZ.GetValueAtTime(tick); } float computedGain[2*WEBAUDIO_BLOCK_SIZE + 4]; bool onLeft[WEBAUDIO_BLOCK_SIZE]; float* alignedComputedGain = ALIGNED16(computedGain); ASSERT_ALIGNED16(alignedComputedGain); for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) { ThreeDPoint position(mPositionX.HasSimpleValue() ? positionX[0] : positionX[counter], mPositionY.HasSimpleValue() ? positionY[0] : positionY[counter], mPositionZ.HasSimpleValue() ? positionZ[0] : positionZ[counter]); ThreeDPoint orientation(mOrientationX.HasSimpleValue() ? orientationX[0] : orientationX[counter], mOrientationY.HasSimpleValue() ? orientationY[0] : orientationY[counter], mOrientationZ.HasSimpleValue() ? orientationZ[0] : orientationZ[counter]); if (!orientation.IsZero()) { orientation.Normalize(); } ComputeAzimuthAndElevation(position, azimuth, elevation); coneGain = ComputeConeGain(position, orientation); // The following algorithm is described in the spec. // Clamp azimuth in the [-90, 90] range. azimuth = min(180.f, max(-180.f, azimuth)); // Wrap around if (azimuth < -90.f) { azimuth = -180.f - azimuth; } else if (azimuth > 90) { azimuth = 180.f - azimuth; } // Normalize the value in the [0, 1] range. if (inputChannels == 1) { normalizedAzimuth = (azimuth + 90.f) / 180.f; } else { if (azimuth <= 0) { normalizedAzimuth = (azimuth + 90.f) / 90.f; } else { normalizedAzimuth = azimuth / 90.f; } } distanceGain = ComputeDistanceGain(position); // Actually compute the left and right gain. float gainL = cos(0.5 * M_PI * normalizedAzimuth) * aInput.mVolume * distanceGain * coneGain; float gainR = sin(0.5 * M_PI * normalizedAzimuth) * aInput.mVolume * distanceGain * coneGain; alignedComputedGain[counter] = gainL; alignedComputedGain[WEBAUDIO_BLOCK_SIZE + counter] = gainR; onLeft[counter] = azimuth <= 0; } // Apply the gain to the output buffer ApplyStereoPanning(aInput, aOutput, alignedComputedGain, &alignedComputedGain[WEBAUDIO_BLOCK_SIZE], onLeft); } }