Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* |
michael@0 | 2 | * Copyright (C) 2010, Google Inc. All rights reserved. |
michael@0 | 3 | * |
michael@0 | 4 | * Redistribution and use in source and binary forms, with or without |
michael@0 | 5 | * modification, are permitted provided that the following conditions |
michael@0 | 6 | * are met: |
michael@0 | 7 | * 1. Redistributions of source code must retain the above copyright |
michael@0 | 8 | * notice, this list of conditions and the following disclaimer. |
michael@0 | 9 | * 2. Redistributions in binary form must reproduce the above copyright |
michael@0 | 10 | * notice, this list of conditions and the following disclaimer in the |
michael@0 | 11 | * documentation and/or other materials provided with the distribution. |
michael@0 | 12 | * |
michael@0 | 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY |
michael@0 | 14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
michael@0 | 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
michael@0 | 16 | * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
michael@0 | 17 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
michael@0 | 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
michael@0 | 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
michael@0 | 20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
michael@0 | 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
michael@0 | 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
michael@0 | 23 | */ |
michael@0 | 24 | |
michael@0 | 25 | #include "HRTFPanner.h" |
michael@0 | 26 | #include "HRTFDatabaseLoader.h" |
michael@0 | 27 | |
michael@0 | 28 | #include "FFTConvolver.h" |
michael@0 | 29 | #include "HRTFDatabase.h" |
michael@0 | 30 | |
michael@0 | 31 | using namespace std; |
michael@0 | 32 | using namespace mozilla; |
michael@0 | 33 | using dom::ChannelInterpretation; |
michael@0 | 34 | |
michael@0 | 35 | namespace WebCore { |
michael@0 | 36 | |
michael@0 | 37 | // The value of 2 milliseconds is larger than the largest delay which exists in any HRTFKernel from the default HRTFDatabase (0.0136 seconds). |
michael@0 | 38 | // We ASSERT the delay values used in process() with this value. |
michael@0 | 39 | const double MaxDelayTimeSeconds = 0.002; |
michael@0 | 40 | |
michael@0 | 41 | const int UninitializedAzimuth = -1; |
michael@0 | 42 | const unsigned RenderingQuantum = WEBAUDIO_BLOCK_SIZE; |
michael@0 | 43 | |
michael@0 | 44 | HRTFPanner::HRTFPanner(float sampleRate, mozilla::TemporaryRef<HRTFDatabaseLoader> databaseLoader) |
michael@0 | 45 | : m_databaseLoader(databaseLoader) |
michael@0 | 46 | , m_sampleRate(sampleRate) |
michael@0 | 47 | , m_crossfadeSelection(CrossfadeSelection1) |
michael@0 | 48 | , m_azimuthIndex1(UninitializedAzimuth) |
michael@0 | 49 | , m_azimuthIndex2(UninitializedAzimuth) |
michael@0 | 50 | // m_elevation1 and m_elevation2 are initialized in pan() |
michael@0 | 51 | , m_crossfadeX(0) |
michael@0 | 52 | , m_crossfadeIncr(0) |
michael@0 | 53 | , m_convolverL1(HRTFElevation::fftSizeForSampleRate(sampleRate)) |
michael@0 | 54 | , m_convolverR1(m_convolverL1.fftSize()) |
michael@0 | 55 | , m_convolverL2(m_convolverL1.fftSize()) |
michael@0 | 56 | , m_convolverR2(m_convolverL1.fftSize()) |
michael@0 | 57 | , m_delayLine(MaxDelayTimeSeconds * sampleRate, 1.0) |
michael@0 | 58 | { |
michael@0 | 59 | MOZ_ASSERT(m_databaseLoader); |
michael@0 | 60 | MOZ_COUNT_CTOR(HRTFPanner); |
michael@0 | 61 | |
michael@0 | 62 | m_tempL1.SetLength(RenderingQuantum); |
michael@0 | 63 | m_tempR1.SetLength(RenderingQuantum); |
michael@0 | 64 | m_tempL2.SetLength(RenderingQuantum); |
michael@0 | 65 | m_tempR2.SetLength(RenderingQuantum); |
michael@0 | 66 | } |
michael@0 | 67 | |
michael@0 | 68 | HRTFPanner::~HRTFPanner() |
michael@0 | 69 | { |
michael@0 | 70 | MOZ_COUNT_DTOR(HRTFPanner); |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | size_t HRTFPanner::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const |
michael@0 | 74 | { |
michael@0 | 75 | size_t amount = aMallocSizeOf(this); |
michael@0 | 76 | |
michael@0 | 77 | if (m_databaseLoader) { |
michael@0 | 78 | m_databaseLoader->sizeOfIncludingThis(aMallocSizeOf); |
michael@0 | 79 | } |
michael@0 | 80 | |
michael@0 | 81 | amount += m_convolverL1.sizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 82 | amount += m_convolverR1.sizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 83 | amount += m_convolverL2.sizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 84 | amount += m_convolverR2.sizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 85 | amount += m_delayLine.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 86 | amount += m_tempL1.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 87 | amount += m_tempL2.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 88 | amount += m_tempR1.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 89 | amount += m_tempR2.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 90 | |
michael@0 | 91 | return amount; |
michael@0 | 92 | } |
michael@0 | 93 | |
michael@0 | 94 | void HRTFPanner::reset() |
michael@0 | 95 | { |
michael@0 | 96 | m_azimuthIndex1 = UninitializedAzimuth; |
michael@0 | 97 | m_azimuthIndex2 = UninitializedAzimuth; |
michael@0 | 98 | // m_elevation1 and m_elevation2 are initialized in pan() |
michael@0 | 99 | m_crossfadeSelection = CrossfadeSelection1; |
michael@0 | 100 | m_crossfadeX = 0.0f; |
michael@0 | 101 | m_crossfadeIncr = 0.0f; |
michael@0 | 102 | m_convolverL1.reset(); |
michael@0 | 103 | m_convolverR1.reset(); |
michael@0 | 104 | m_convolverL2.reset(); |
michael@0 | 105 | m_convolverR2.reset(); |
michael@0 | 106 | m_delayLine.Reset(); |
michael@0 | 107 | } |
michael@0 | 108 | |
michael@0 | 109 | int HRTFPanner::calculateDesiredAzimuthIndexAndBlend(double azimuth, double& azimuthBlend) |
michael@0 | 110 | { |
michael@0 | 111 | // Convert the azimuth angle from the range -180 -> +180 into the range 0 -> 360. |
michael@0 | 112 | // The azimuth index may then be calculated from this positive value. |
michael@0 | 113 | if (azimuth < 0) |
michael@0 | 114 | azimuth += 360.0; |
michael@0 | 115 | |
michael@0 | 116 | HRTFDatabase* database = m_databaseLoader->database(); |
michael@0 | 117 | MOZ_ASSERT(database); |
michael@0 | 118 | |
michael@0 | 119 | int numberOfAzimuths = database->numberOfAzimuths(); |
michael@0 | 120 | const double angleBetweenAzimuths = 360.0 / numberOfAzimuths; |
michael@0 | 121 | |
michael@0 | 122 | // Calculate the azimuth index and the blend (0 -> 1) for interpolation. |
michael@0 | 123 | double desiredAzimuthIndexFloat = azimuth / angleBetweenAzimuths; |
michael@0 | 124 | int desiredAzimuthIndex = static_cast<int>(desiredAzimuthIndexFloat); |
michael@0 | 125 | azimuthBlend = desiredAzimuthIndexFloat - static_cast<double>(desiredAzimuthIndex); |
michael@0 | 126 | |
michael@0 | 127 | // We don't immediately start using this azimuth index, but instead approach this index from the last index we rendered at. |
michael@0 | 128 | // This minimizes the clicks and graininess for moving sources which occur otherwise. |
michael@0 | 129 | desiredAzimuthIndex = max(0, desiredAzimuthIndex); |
michael@0 | 130 | desiredAzimuthIndex = min(numberOfAzimuths - 1, desiredAzimuthIndex); |
michael@0 | 131 | return desiredAzimuthIndex; |
michael@0 | 132 | } |
michael@0 | 133 | |
michael@0 | 134 | void HRTFPanner::pan(double desiredAzimuth, double elevation, const AudioChunk* inputBus, AudioChunk* outputBus) |
michael@0 | 135 | { |
michael@0 | 136 | #ifdef DEBUG |
michael@0 | 137 | unsigned numInputChannels = |
michael@0 | 138 | inputBus->IsNull() ? 0 : inputBus->mChannelData.Length(); |
michael@0 | 139 | |
michael@0 | 140 | MOZ_ASSERT(numInputChannels <= 2); |
michael@0 | 141 | MOZ_ASSERT(inputBus->mDuration == WEBAUDIO_BLOCK_SIZE); |
michael@0 | 142 | #endif |
michael@0 | 143 | |
michael@0 | 144 | bool isOutputGood = outputBus && outputBus->mChannelData.Length() == 2 && outputBus->mDuration == WEBAUDIO_BLOCK_SIZE; |
michael@0 | 145 | MOZ_ASSERT(isOutputGood); |
michael@0 | 146 | |
michael@0 | 147 | if (!isOutputGood) { |
michael@0 | 148 | if (outputBus) |
michael@0 | 149 | outputBus->SetNull(outputBus->mDuration); |
michael@0 | 150 | return; |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | HRTFDatabase* database = m_databaseLoader->database(); |
michael@0 | 154 | if (!database) { // not yet loaded |
michael@0 | 155 | outputBus->SetNull(outputBus->mDuration); |
michael@0 | 156 | return; |
michael@0 | 157 | } |
michael@0 | 158 | |
michael@0 | 159 | // IRCAM HRTF azimuths values from the loaded database is reversed from the panner's notion of azimuth. |
michael@0 | 160 | double azimuth = -desiredAzimuth; |
michael@0 | 161 | |
michael@0 | 162 | bool isAzimuthGood = azimuth >= -180.0 && azimuth <= 180.0; |
michael@0 | 163 | MOZ_ASSERT(isAzimuthGood); |
michael@0 | 164 | if (!isAzimuthGood) { |
michael@0 | 165 | outputBus->SetNull(outputBus->mDuration); |
michael@0 | 166 | return; |
michael@0 | 167 | } |
michael@0 | 168 | |
michael@0 | 169 | // Normally, we'll just be dealing with mono sources. |
michael@0 | 170 | // If we have a stereo input, implement stereo panning with left source processed by left HRTF, and right source by right HRTF. |
michael@0 | 171 | |
michael@0 | 172 | // Get destination pointers. |
michael@0 | 173 | float* destinationL = |
michael@0 | 174 | static_cast<float*>(const_cast<void*>(outputBus->mChannelData[0])); |
michael@0 | 175 | float* destinationR = |
michael@0 | 176 | static_cast<float*>(const_cast<void*>(outputBus->mChannelData[1])); |
michael@0 | 177 | |
michael@0 | 178 | double azimuthBlend; |
michael@0 | 179 | int desiredAzimuthIndex = calculateDesiredAzimuthIndexAndBlend(azimuth, azimuthBlend); |
michael@0 | 180 | |
michael@0 | 181 | // Initially snap azimuth and elevation values to first values encountered. |
michael@0 | 182 | if (m_azimuthIndex1 == UninitializedAzimuth) { |
michael@0 | 183 | m_azimuthIndex1 = desiredAzimuthIndex; |
michael@0 | 184 | m_elevation1 = elevation; |
michael@0 | 185 | } |
michael@0 | 186 | if (m_azimuthIndex2 == UninitializedAzimuth) { |
michael@0 | 187 | m_azimuthIndex2 = desiredAzimuthIndex; |
michael@0 | 188 | m_elevation2 = elevation; |
michael@0 | 189 | } |
michael@0 | 190 | |
michael@0 | 191 | // Cross-fade / transition over a period of around 45 milliseconds. |
michael@0 | 192 | // This is an empirical value tuned to be a reasonable trade-off between |
michael@0 | 193 | // smoothness and speed. |
michael@0 | 194 | const double fadeFrames = sampleRate() <= 48000 ? 2048 : 4096; |
michael@0 | 195 | |
michael@0 | 196 | // Check for azimuth and elevation changes, initiating a cross-fade if needed. |
michael@0 | 197 | if (!m_crossfadeX && m_crossfadeSelection == CrossfadeSelection1) { |
michael@0 | 198 | if (desiredAzimuthIndex != m_azimuthIndex1 || elevation != m_elevation1) { |
michael@0 | 199 | // Cross-fade from 1 -> 2 |
michael@0 | 200 | m_crossfadeIncr = 1 / fadeFrames; |
michael@0 | 201 | m_azimuthIndex2 = desiredAzimuthIndex; |
michael@0 | 202 | m_elevation2 = elevation; |
michael@0 | 203 | } |
michael@0 | 204 | } |
michael@0 | 205 | if (m_crossfadeX == 1 && m_crossfadeSelection == CrossfadeSelection2) { |
michael@0 | 206 | if (desiredAzimuthIndex != m_azimuthIndex2 || elevation != m_elevation2) { |
michael@0 | 207 | // Cross-fade from 2 -> 1 |
michael@0 | 208 | m_crossfadeIncr = -1 / fadeFrames; |
michael@0 | 209 | m_azimuthIndex1 = desiredAzimuthIndex; |
michael@0 | 210 | m_elevation1 = elevation; |
michael@0 | 211 | } |
michael@0 | 212 | } |
michael@0 | 213 | |
michael@0 | 214 | // Get the HRTFKernels and interpolated delays. |
michael@0 | 215 | HRTFKernel* kernelL1; |
michael@0 | 216 | HRTFKernel* kernelR1; |
michael@0 | 217 | HRTFKernel* kernelL2; |
michael@0 | 218 | HRTFKernel* kernelR2; |
michael@0 | 219 | double frameDelayL1; |
michael@0 | 220 | double frameDelayR1; |
michael@0 | 221 | double frameDelayL2; |
michael@0 | 222 | double frameDelayR2; |
michael@0 | 223 | database->getKernelsFromAzimuthElevation(azimuthBlend, m_azimuthIndex1, m_elevation1, kernelL1, kernelR1, frameDelayL1, frameDelayR1); |
michael@0 | 224 | database->getKernelsFromAzimuthElevation(azimuthBlend, m_azimuthIndex2, m_elevation2, kernelL2, kernelR2, frameDelayL2, frameDelayR2); |
michael@0 | 225 | |
michael@0 | 226 | bool areKernelsGood = kernelL1 && kernelR1 && kernelL2 && kernelR2; |
michael@0 | 227 | MOZ_ASSERT(areKernelsGood); |
michael@0 | 228 | if (!areKernelsGood) { |
michael@0 | 229 | outputBus->SetNull(outputBus->mDuration); |
michael@0 | 230 | return; |
michael@0 | 231 | } |
michael@0 | 232 | |
michael@0 | 233 | MOZ_ASSERT(frameDelayL1 / sampleRate() < MaxDelayTimeSeconds && frameDelayR1 / sampleRate() < MaxDelayTimeSeconds); |
michael@0 | 234 | MOZ_ASSERT(frameDelayL2 / sampleRate() < MaxDelayTimeSeconds && frameDelayR2 / sampleRate() < MaxDelayTimeSeconds); |
michael@0 | 235 | |
michael@0 | 236 | // Crossfade inter-aural delays based on transitions. |
michael@0 | 237 | double frameDelaysL[WEBAUDIO_BLOCK_SIZE]; |
michael@0 | 238 | double frameDelaysR[WEBAUDIO_BLOCK_SIZE]; |
michael@0 | 239 | { |
michael@0 | 240 | float x = m_crossfadeX; |
michael@0 | 241 | float incr = m_crossfadeIncr; |
michael@0 | 242 | for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) { |
michael@0 | 243 | frameDelaysL[i] = (1 - x) * frameDelayL1 + x * frameDelayL2; |
michael@0 | 244 | frameDelaysR[i] = (1 - x) * frameDelayR1 + x * frameDelayR2; |
michael@0 | 245 | x += incr; |
michael@0 | 246 | } |
michael@0 | 247 | } |
michael@0 | 248 | |
michael@0 | 249 | // First run through delay lines for inter-aural time difference. |
michael@0 | 250 | m_delayLine.Write(*inputBus); |
michael@0 | 251 | // "Speakers" means a mono input is read into both outputs (with possibly |
michael@0 | 252 | // different delays). |
michael@0 | 253 | m_delayLine.ReadChannel(frameDelaysL, outputBus, 0, |
michael@0 | 254 | ChannelInterpretation::Speakers); |
michael@0 | 255 | m_delayLine.ReadChannel(frameDelaysR, outputBus, 1, |
michael@0 | 256 | ChannelInterpretation::Speakers); |
michael@0 | 257 | m_delayLine.NextBlock(); |
michael@0 | 258 | |
michael@0 | 259 | bool needsCrossfading = m_crossfadeIncr; |
michael@0 | 260 | |
michael@0 | 261 | // Have the convolvers render directly to the final destination if we're not cross-fading. |
michael@0 | 262 | float* convolutionDestinationL1 = needsCrossfading ? m_tempL1.Elements() : destinationL; |
michael@0 | 263 | float* convolutionDestinationR1 = needsCrossfading ? m_tempR1.Elements() : destinationR; |
michael@0 | 264 | float* convolutionDestinationL2 = needsCrossfading ? m_tempL2.Elements() : destinationL; |
michael@0 | 265 | float* convolutionDestinationR2 = needsCrossfading ? m_tempR2.Elements() : destinationR; |
michael@0 | 266 | |
michael@0 | 267 | // Now do the convolutions. |
michael@0 | 268 | // Note that we avoid doing convolutions on both sets of convolvers if we're not currently cross-fading. |
michael@0 | 269 | |
michael@0 | 270 | if (m_crossfadeSelection == CrossfadeSelection1 || needsCrossfading) { |
michael@0 | 271 | m_convolverL1.process(kernelL1->fftFrame(), destinationL, convolutionDestinationL1, WEBAUDIO_BLOCK_SIZE); |
michael@0 | 272 | m_convolverR1.process(kernelR1->fftFrame(), destinationR, convolutionDestinationR1, WEBAUDIO_BLOCK_SIZE); |
michael@0 | 273 | } |
michael@0 | 274 | |
michael@0 | 275 | if (m_crossfadeSelection == CrossfadeSelection2 || needsCrossfading) { |
michael@0 | 276 | m_convolverL2.process(kernelL2->fftFrame(), destinationL, convolutionDestinationL2, WEBAUDIO_BLOCK_SIZE); |
michael@0 | 277 | m_convolverR2.process(kernelR2->fftFrame(), destinationR, convolutionDestinationR2, WEBAUDIO_BLOCK_SIZE); |
michael@0 | 278 | } |
michael@0 | 279 | |
michael@0 | 280 | if (needsCrossfading) { |
michael@0 | 281 | // Apply linear cross-fade. |
michael@0 | 282 | float x = m_crossfadeX; |
michael@0 | 283 | float incr = m_crossfadeIncr; |
michael@0 | 284 | for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) { |
michael@0 | 285 | destinationL[i] = (1 - x) * convolutionDestinationL1[i] + x * convolutionDestinationL2[i]; |
michael@0 | 286 | destinationR[i] = (1 - x) * convolutionDestinationR1[i] + x * convolutionDestinationR2[i]; |
michael@0 | 287 | x += incr; |
michael@0 | 288 | } |
michael@0 | 289 | // Update cross-fade value from local. |
michael@0 | 290 | m_crossfadeX = x; |
michael@0 | 291 | |
michael@0 | 292 | if (m_crossfadeIncr > 0 && fabs(m_crossfadeX - 1) < m_crossfadeIncr) { |
michael@0 | 293 | // We've fully made the crossfade transition from 1 -> 2. |
michael@0 | 294 | m_crossfadeSelection = CrossfadeSelection2; |
michael@0 | 295 | m_crossfadeX = 1; |
michael@0 | 296 | m_crossfadeIncr = 0; |
michael@0 | 297 | } else if (m_crossfadeIncr < 0 && fabs(m_crossfadeX) < -m_crossfadeIncr) { |
michael@0 | 298 | // We've fully made the crossfade transition from 2 -> 1. |
michael@0 | 299 | m_crossfadeSelection = CrossfadeSelection1; |
michael@0 | 300 | m_crossfadeX = 0; |
michael@0 | 301 | m_crossfadeIncr = 0; |
michael@0 | 302 | } |
michael@0 | 303 | } |
michael@0 | 304 | } |
michael@0 | 305 | |
michael@0 | 306 | int HRTFPanner::maxTailFrames() const |
michael@0 | 307 | { |
michael@0 | 308 | // Although the ideal tail time would be the length of the impulse |
michael@0 | 309 | // response, there is additional tail time from the approximations in the |
michael@0 | 310 | // implementation. Because HRTFPanner is implemented with a DelayKernel |
michael@0 | 311 | // and a FFTConvolver, the tailTime of the HRTFPanner is the sum of the |
michael@0 | 312 | // tailTime of the DelayKernel and the tailTime of the FFTConvolver. |
michael@0 | 313 | // The FFTConvolver has a tail time of fftSize(), including latency of |
michael@0 | 314 | // fftSize()/2. |
michael@0 | 315 | return m_delayLine.MaxDelayTicks() + fftSize(); |
michael@0 | 316 | } |
michael@0 | 317 | |
michael@0 | 318 | } // namespace WebCore |