content/media/webaudio/blink/HRTFPanner.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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  */
    25 #include "HRTFPanner.h"
    26 #include "HRTFDatabaseLoader.h"
    28 #include "FFTConvolver.h"
    29 #include "HRTFDatabase.h"
    31 using namespace std;
    32 using namespace mozilla;
    33 using dom::ChannelInterpretation;
    35 namespace WebCore {
    37 // The value of 2 milliseconds is larger than the largest delay which exists in any HRTFKernel from the default HRTFDatabase (0.0136 seconds).
    38 // We ASSERT the delay values used in process() with this value.
    39 const double MaxDelayTimeSeconds = 0.002;
    41 const int UninitializedAzimuth = -1;
    42 const unsigned RenderingQuantum = WEBAUDIO_BLOCK_SIZE;
    44 HRTFPanner::HRTFPanner(float sampleRate, mozilla::TemporaryRef<HRTFDatabaseLoader> databaseLoader)
    45     : m_databaseLoader(databaseLoader)
    46     , m_sampleRate(sampleRate)
    47     , m_crossfadeSelection(CrossfadeSelection1)
    48     , m_azimuthIndex1(UninitializedAzimuth)
    49     , m_azimuthIndex2(UninitializedAzimuth)
    50     // m_elevation1 and m_elevation2 are initialized in pan()
    51     , m_crossfadeX(0)
    52     , m_crossfadeIncr(0)
    53     , m_convolverL1(HRTFElevation::fftSizeForSampleRate(sampleRate))
    54     , m_convolverR1(m_convolverL1.fftSize())
    55     , m_convolverL2(m_convolverL1.fftSize())
    56     , m_convolverR2(m_convolverL1.fftSize())
    57     , m_delayLine(MaxDelayTimeSeconds * sampleRate, 1.0)
    58 {
    59     MOZ_ASSERT(m_databaseLoader);
    60     MOZ_COUNT_CTOR(HRTFPanner);
    62     m_tempL1.SetLength(RenderingQuantum);
    63     m_tempR1.SetLength(RenderingQuantum);
    64     m_tempL2.SetLength(RenderingQuantum);
    65     m_tempR2.SetLength(RenderingQuantum);
    66 }
    68 HRTFPanner::~HRTFPanner()
    69 {
    70     MOZ_COUNT_DTOR(HRTFPanner);
    71 }
    73 size_t HRTFPanner::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
    74 {
    75     size_t amount = aMallocSizeOf(this);
    77     if (m_databaseLoader) {
    78         m_databaseLoader->sizeOfIncludingThis(aMallocSizeOf);
    79     }
    81     amount += m_convolverL1.sizeOfExcludingThis(aMallocSizeOf);
    82     amount += m_convolverR1.sizeOfExcludingThis(aMallocSizeOf);
    83     amount += m_convolverL2.sizeOfExcludingThis(aMallocSizeOf);
    84     amount += m_convolverR2.sizeOfExcludingThis(aMallocSizeOf);
    85     amount += m_delayLine.SizeOfExcludingThis(aMallocSizeOf);
    86     amount += m_tempL1.SizeOfExcludingThis(aMallocSizeOf);
    87     amount += m_tempL2.SizeOfExcludingThis(aMallocSizeOf);
    88     amount += m_tempR1.SizeOfExcludingThis(aMallocSizeOf);
    89     amount += m_tempR2.SizeOfExcludingThis(aMallocSizeOf);
    91     return amount;
    92 }
    94 void HRTFPanner::reset()
    95 {
    96     m_azimuthIndex1 = UninitializedAzimuth;
    97     m_azimuthIndex2 = UninitializedAzimuth;
    98     // m_elevation1 and m_elevation2 are initialized in pan()
    99     m_crossfadeSelection = CrossfadeSelection1;
   100     m_crossfadeX = 0.0f;
   101     m_crossfadeIncr = 0.0f;
   102     m_convolverL1.reset();
   103     m_convolverR1.reset();
   104     m_convolverL2.reset();
   105     m_convolverR2.reset();
   106     m_delayLine.Reset();
   107 }
   109 int HRTFPanner::calculateDesiredAzimuthIndexAndBlend(double azimuth, double& azimuthBlend)
   110 {
   111     // Convert the azimuth angle from the range -180 -> +180 into the range 0 -> 360.
   112     // The azimuth index may then be calculated from this positive value.
   113     if (azimuth < 0)
   114         azimuth += 360.0;
   116     HRTFDatabase* database = m_databaseLoader->database();
   117     MOZ_ASSERT(database);
   119     int numberOfAzimuths = database->numberOfAzimuths();
   120     const double angleBetweenAzimuths = 360.0 / numberOfAzimuths;
   122     // Calculate the azimuth index and the blend (0 -> 1) for interpolation.
   123     double desiredAzimuthIndexFloat = azimuth / angleBetweenAzimuths;
   124     int desiredAzimuthIndex = static_cast<int>(desiredAzimuthIndexFloat);
   125     azimuthBlend = desiredAzimuthIndexFloat - static_cast<double>(desiredAzimuthIndex);
   127     // We don't immediately start using this azimuth index, but instead approach this index from the last index we rendered at.
   128     // This minimizes the clicks and graininess for moving sources which occur otherwise.
   129     desiredAzimuthIndex = max(0, desiredAzimuthIndex);
   130     desiredAzimuthIndex = min(numberOfAzimuths - 1, desiredAzimuthIndex);
   131     return desiredAzimuthIndex;
   132 }
   134 void HRTFPanner::pan(double desiredAzimuth, double elevation, const AudioChunk* inputBus, AudioChunk* outputBus)
   135 {
   136 #ifdef DEBUG
   137     unsigned numInputChannels =
   138         inputBus->IsNull() ? 0 : inputBus->mChannelData.Length();
   140     MOZ_ASSERT(numInputChannels <= 2);
   141     MOZ_ASSERT(inputBus->mDuration == WEBAUDIO_BLOCK_SIZE);
   142 #endif
   144     bool isOutputGood = outputBus && outputBus->mChannelData.Length() == 2 && outputBus->mDuration == WEBAUDIO_BLOCK_SIZE;
   145     MOZ_ASSERT(isOutputGood);
   147     if (!isOutputGood) {
   148         if (outputBus)
   149             outputBus->SetNull(outputBus->mDuration);
   150         return;
   151     }
   153     HRTFDatabase* database = m_databaseLoader->database();
   154     if (!database) { // not yet loaded
   155         outputBus->SetNull(outputBus->mDuration);
   156         return;
   157     }
   159     // IRCAM HRTF azimuths values from the loaded database is reversed from the panner's notion of azimuth.
   160     double azimuth = -desiredAzimuth;
   162     bool isAzimuthGood = azimuth >= -180.0 && azimuth <= 180.0;
   163     MOZ_ASSERT(isAzimuthGood);
   164     if (!isAzimuthGood) {
   165         outputBus->SetNull(outputBus->mDuration);
   166         return;
   167     }
   169     // Normally, we'll just be dealing with mono sources.
   170     // If we have a stereo input, implement stereo panning with left source processed by left HRTF, and right source by right HRTF.
   172     // Get destination pointers.
   173     float* destinationL =
   174         static_cast<float*>(const_cast<void*>(outputBus->mChannelData[0]));
   175     float* destinationR =
   176         static_cast<float*>(const_cast<void*>(outputBus->mChannelData[1]));
   178     double azimuthBlend;
   179     int desiredAzimuthIndex = calculateDesiredAzimuthIndexAndBlend(azimuth, azimuthBlend);
   181     // Initially snap azimuth and elevation values to first values encountered.
   182     if (m_azimuthIndex1 == UninitializedAzimuth) {
   183         m_azimuthIndex1 = desiredAzimuthIndex;
   184         m_elevation1 = elevation;
   185     }
   186     if (m_azimuthIndex2 == UninitializedAzimuth) {
   187         m_azimuthIndex2 = desiredAzimuthIndex;
   188         m_elevation2 = elevation;
   189     }
   191     // Cross-fade / transition over a period of around 45 milliseconds.
   192     // This is an empirical value tuned to be a reasonable trade-off between
   193     // smoothness and speed.
   194     const double fadeFrames = sampleRate() <= 48000 ? 2048 : 4096;
   196     // Check for azimuth and elevation changes, initiating a cross-fade if needed.
   197     if (!m_crossfadeX && m_crossfadeSelection == CrossfadeSelection1) {
   198         if (desiredAzimuthIndex != m_azimuthIndex1 || elevation != m_elevation1) {
   199             // Cross-fade from 1 -> 2
   200             m_crossfadeIncr = 1 / fadeFrames;
   201             m_azimuthIndex2 = desiredAzimuthIndex;
   202             m_elevation2 = elevation;
   203         }
   204     }
   205     if (m_crossfadeX == 1 && m_crossfadeSelection == CrossfadeSelection2) {
   206         if (desiredAzimuthIndex != m_azimuthIndex2 || elevation != m_elevation2) {
   207             // Cross-fade from 2 -> 1
   208             m_crossfadeIncr = -1 / fadeFrames;
   209             m_azimuthIndex1 = desiredAzimuthIndex;
   210             m_elevation1 = elevation;
   211         }
   212     }
   214     // Get the HRTFKernels and interpolated delays.
   215     HRTFKernel* kernelL1;
   216     HRTFKernel* kernelR1;
   217     HRTFKernel* kernelL2;
   218     HRTFKernel* kernelR2;
   219     double frameDelayL1;
   220     double frameDelayR1;
   221     double frameDelayL2;
   222     double frameDelayR2;
   223     database->getKernelsFromAzimuthElevation(azimuthBlend, m_azimuthIndex1, m_elevation1, kernelL1, kernelR1, frameDelayL1, frameDelayR1);
   224     database->getKernelsFromAzimuthElevation(azimuthBlend, m_azimuthIndex2, m_elevation2, kernelL2, kernelR2, frameDelayL2, frameDelayR2);
   226     bool areKernelsGood = kernelL1 && kernelR1 && kernelL2 && kernelR2;
   227     MOZ_ASSERT(areKernelsGood);
   228     if (!areKernelsGood) {
   229         outputBus->SetNull(outputBus->mDuration);
   230         return;
   231     }
   233     MOZ_ASSERT(frameDelayL1 / sampleRate() < MaxDelayTimeSeconds && frameDelayR1 / sampleRate() < MaxDelayTimeSeconds);
   234     MOZ_ASSERT(frameDelayL2 / sampleRate() < MaxDelayTimeSeconds && frameDelayR2 / sampleRate() < MaxDelayTimeSeconds);
   236     // Crossfade inter-aural delays based on transitions.
   237     double frameDelaysL[WEBAUDIO_BLOCK_SIZE];
   238     double frameDelaysR[WEBAUDIO_BLOCK_SIZE];
   239     {
   240       float x = m_crossfadeX;
   241       float incr = m_crossfadeIncr;
   242       for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
   243         frameDelaysL[i] = (1 - x) * frameDelayL1 + x * frameDelayL2;
   244         frameDelaysR[i] = (1 - x) * frameDelayR1 + x * frameDelayR2;
   245         x += incr;
   246       }
   247     }
   249     // First run through delay lines for inter-aural time difference.
   250     m_delayLine.Write(*inputBus);
   251     // "Speakers" means a mono input is read into both outputs (with possibly
   252     // different delays).
   253     m_delayLine.ReadChannel(frameDelaysL, outputBus, 0,
   254                             ChannelInterpretation::Speakers);
   255     m_delayLine.ReadChannel(frameDelaysR, outputBus, 1,
   256                             ChannelInterpretation::Speakers);
   257     m_delayLine.NextBlock();
   259     bool needsCrossfading = m_crossfadeIncr;
   261     // Have the convolvers render directly to the final destination if we're not cross-fading.
   262     float* convolutionDestinationL1 = needsCrossfading ? m_tempL1.Elements() : destinationL;
   263     float* convolutionDestinationR1 = needsCrossfading ? m_tempR1.Elements() : destinationR;
   264     float* convolutionDestinationL2 = needsCrossfading ? m_tempL2.Elements() : destinationL;
   265     float* convolutionDestinationR2 = needsCrossfading ? m_tempR2.Elements() : destinationR;
   267     // Now do the convolutions.
   268     // Note that we avoid doing convolutions on both sets of convolvers if we're not currently cross-fading.
   270     if (m_crossfadeSelection == CrossfadeSelection1 || needsCrossfading) {
   271         m_convolverL1.process(kernelL1->fftFrame(), destinationL, convolutionDestinationL1, WEBAUDIO_BLOCK_SIZE);
   272         m_convolverR1.process(kernelR1->fftFrame(), destinationR, convolutionDestinationR1, WEBAUDIO_BLOCK_SIZE);
   273     }
   275     if (m_crossfadeSelection == CrossfadeSelection2 || needsCrossfading) {
   276         m_convolverL2.process(kernelL2->fftFrame(), destinationL, convolutionDestinationL2, WEBAUDIO_BLOCK_SIZE);
   277         m_convolverR2.process(kernelR2->fftFrame(), destinationR, convolutionDestinationR2, WEBAUDIO_BLOCK_SIZE);
   278     }
   280     if (needsCrossfading) {
   281         // Apply linear cross-fade.
   282         float x = m_crossfadeX;
   283         float incr = m_crossfadeIncr;
   284         for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
   285             destinationL[i] = (1 - x) * convolutionDestinationL1[i] + x * convolutionDestinationL2[i];
   286             destinationR[i] = (1 - x) * convolutionDestinationR1[i] + x * convolutionDestinationR2[i];
   287             x += incr;
   288         }
   289         // Update cross-fade value from local.
   290         m_crossfadeX = x;
   292         if (m_crossfadeIncr > 0 && fabs(m_crossfadeX - 1) < m_crossfadeIncr) {
   293             // We've fully made the crossfade transition from 1 -> 2.
   294             m_crossfadeSelection = CrossfadeSelection2;
   295             m_crossfadeX = 1;
   296             m_crossfadeIncr = 0;
   297         } else if (m_crossfadeIncr < 0 && fabs(m_crossfadeX) < -m_crossfadeIncr) {
   298             // We've fully made the crossfade transition from 2 -> 1.
   299             m_crossfadeSelection = CrossfadeSelection1;
   300             m_crossfadeX = 0;
   301             m_crossfadeIncr = 0;
   302         }
   303     }
   304 }
   306 int HRTFPanner::maxTailFrames() const
   307 {
   308     // Although the ideal tail time would be the length of the impulse
   309     // response, there is additional tail time from the approximations in the
   310     // implementation.  Because HRTFPanner is implemented with a DelayKernel
   311     // and a FFTConvolver, the tailTime of the HRTFPanner is the sum of the
   312     // tailTime of the DelayKernel and the tailTime of the FFTConvolver.
   313     // The FFTConvolver has a tail time of fftSize(), including latency of
   314     // fftSize()/2.
   315     return m_delayLine.MaxDelayTicks() + fftSize();
   316 }
   318 } // namespace WebCore

mercurial