2010-09-22 Chris Rogers <crogers@google.com>
authorcrogers@google.com <crogers@google.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 22 Sep 2010 08:45:48 +0000 (08:45 +0000)
committercrogers@google.com <crogers@google.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 22 Sep 2010 08:45:48 +0000 (08:45 +0000)
        Reviewed by James Robinson.

        Add HRTFElevation files
        https://bugs.webkit.org/show_bug.cgi?id=45864

        No new tests since audio API is not yet implemented.

        * platform/audio/HRTFElevation.cpp: Added.
        (WebCore::HRTFElevation::calculateSymmetricKernelsForAzimuthElevation):
        (WebCore::HRTFElevation::calculateKernelsForAzimuthElevation):
        (WebCore::HRTFElevation::createForSubject):
        (WebCore::HRTFElevation::createByInterpolatingSlices):
        (WebCore::HRTFElevation::getKernelsFromAzimuth):
        * platform/audio/HRTFElevation.h: Added.
        (WebCore::HRTFElevation::kernelListL):
        (WebCore::HRTFElevation::kernelListR):
        (WebCore::HRTFElevation::elevationAngle):
        (WebCore::HRTFElevation::numberOfAzimuths):
        (WebCore::HRTFElevation::sampleRate):
        (WebCore::HRTFElevation::HRTFElevation):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@68029 268f45cc-cd09-0410-ab3c-d52691b4dbfc

WebCore/ChangeLog
WebCore/platform/audio/HRTFElevation.cpp [new file with mode: 0644]
WebCore/platform/audio/HRTFElevation.h [new file with mode: 0644]

index bb2dbcd41b5eb92f91d6e4ce6fca48e3d053b1d3..04b58151530bfa33e3b6e5167abb5ad55866344a 100644 (file)
@@ -1,3 +1,26 @@
+2010-09-22  Chris Rogers  <crogers@google.com>
+
+        Reviewed by James Robinson.
+
+        Add HRTFElevation files
+        https://bugs.webkit.org/show_bug.cgi?id=45864
+
+        No new tests since audio API is not yet implemented.
+
+        * platform/audio/HRTFElevation.cpp: Added.
+        (WebCore::HRTFElevation::calculateSymmetricKernelsForAzimuthElevation):
+        (WebCore::HRTFElevation::calculateKernelsForAzimuthElevation):
+        (WebCore::HRTFElevation::createForSubject):
+        (WebCore::HRTFElevation::createByInterpolatingSlices):
+        (WebCore::HRTFElevation::getKernelsFromAzimuth):
+        * platform/audio/HRTFElevation.h: Added.
+        (WebCore::HRTFElevation::kernelListL):
+        (WebCore::HRTFElevation::kernelListR):
+        (WebCore::HRTFElevation::elevationAngle):
+        (WebCore::HRTFElevation::numberOfAzimuths):
+        (WebCore::HRTFElevation::sampleRate):
+        (WebCore::HRTFElevation::HRTFElevation):
+
 2010-09-22  Alejandro G. Castro  <alex@igalia.com>
 
         Reviewed by Martin Robinson.
 2010-09-22  Alejandro G. Castro  <alex@igalia.com>
 
         Reviewed by Martin Robinson.
diff --git a/WebCore/platform/audio/HRTFElevation.cpp b/WebCore/platform/audio/HRTFElevation.cpp
new file mode 100644 (file)
index 0000000..ac1eb4d
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#if ENABLE(WEB_AUDIO)
+
+#include "HRTFElevation.h"
+
+#include "AudioBus.h"
+#include "AudioFileReader.h"
+#include "AudioResources.h"
+#include "Biquad.h"
+#include "FFTFrame.h"
+#include "HRTFPanner.h"
+#include <algorithm>
+#include <math.h>
+#include <wtf/OwnPtr.h>
+
+using namespace std;
+namespace WebCore {
+
+const unsigned HRTFElevation::AzimuthSpacing = 15;
+const unsigned HRTFElevation::NumberOfRawAzimuths = 360 / AzimuthSpacing;
+const unsigned HRTFElevation::InterpolationFactor = 8;
+const unsigned HRTFElevation::NumberOfTotalAzimuths = NumberOfRawAzimuths * InterpolationFactor;
+
+// Takes advantage of the symmetry and creates a composite version of the two measured versions.  For example, we have both azimuth 30 and -30 degrees
+// where the roles of left and right ears are reversed with respect to each other.
+bool HRTFElevation::calculateSymmetricKernelsForAzimuthElevation(int azimuth, int elevation, double sampleRate, const String& subjectName,
+                                                                 RefPtr<HRTFKernel>& kernelL, RefPtr<HRTFKernel>& kernelR)
+{
+    RefPtr<HRTFKernel> kernelL1;
+    RefPtr<HRTFKernel> kernelR1;
+    bool success = calculateKernelsForAzimuthElevation(azimuth, elevation, sampleRate, subjectName, kernelL1, kernelR1);
+    if (!success)
+        return false;
+        
+    // And symmetric version
+    int symmetricAzimuth = !azimuth ? 0 : 360 - azimuth;
+                                                              
+    RefPtr<HRTFKernel> kernelL2;
+    RefPtr<HRTFKernel> kernelR2;
+    success = calculateKernelsForAzimuthElevation(symmetricAzimuth, elevation, sampleRate, subjectName, kernelL2, kernelR2);
+    if (!success)
+        return false;
+        
+    // Notice L/R reversal in symmetric version.
+    kernelL = HRTFKernel::createInterpolatedKernel(kernelL1.get(), kernelR2.get(), 0.5);
+    kernelR = HRTFKernel::createInterpolatedKernel(kernelR1.get(), kernelL2.get(), 0.5);
+    
+    return true;
+}
+
+bool HRTFElevation::calculateKernelsForAzimuthElevation(int azimuth, int elevation, double sampleRate, const String& subjectName,
+                                                        RefPtr<HRTFKernel>& kernelL, RefPtr<HRTFKernel>& kernelR)
+{
+    // Valid values for azimuth are 0 -> 345 in 15 degree increments.
+    // Valid values for elevation are -45 -> +90 in 15 degree increments.
+
+    bool isAzimuthGood = azimuth >= 0 && azimuth <= 345 && (azimuth / 15) * 15 == azimuth;
+    ASSERT(isAzimuthGood);
+    if (!isAzimuthGood)
+        return false;
+
+    bool isElevationGood = elevation >= -45 && elevation <= 90 && (elevation / 15) * 15 == elevation;
+    ASSERT(isElevationGood);
+    if (!isElevationGood)
+        return false;
+    
+    // Construct the resource name from the subject name, azimuth, and elevation, for example:
+    // "IRC_Composite_C_R0195_T015_P000"
+    // Note: the passed in subjectName is not a string passed in via JavaScript or the web.
+    // It's passed in as an internal ASCII identifier and is an implementation detail.
+    int positiveElevation = elevation < 0 ? elevation + 360 : elevation;
+    String resourceName = String::format("IRC_%s_C_R0195_T%03d_P%03d", subjectName.utf8().data(), azimuth, positiveElevation);
+
+    OwnPtr<AudioBus> impulseResponse(createBusFromAudioFileResource(resourceName, sampleRate));
+
+    ASSERT(impulseResponse.get());
+    if (!impulseResponse.get())
+        return false;
+    
+    size_t responseLength = impulseResponse->length();
+
+    // Check number of channels and length.  For now these are fixed and known.
+    bool isBusGood = responseLength == 512 && impulseResponse->numberOfChannels() == 2;
+    ASSERT(isBusGood);
+    if (!isBusGood)
+        return false;
+    
+    AudioChannel* leftEarImpulseResponse = impulseResponse->channelByType(AudioBus::ChannelLeft);
+    AudioChannel* rightEarImpulseResponse = impulseResponse->channelByType(AudioBus::ChannelRight);
+
+    // Note that depending on the fftSize returned by the panner, we may be truncating the impulse response we just loaded in.
+    const size_t fftSize = HRTFPanner::fftSizeForSampleRate(sampleRate);
+    kernelL = HRTFKernel::create(leftEarImpulseResponse, fftSize, sampleRate, true);
+    kernelR = HRTFKernel::create(rightEarImpulseResponse, fftSize, sampleRate, true);
+    
+    return true;
+}
+
+// The range of elevations for the IRCAM impulse responses varies depending on azimuth, but the minimum elevation appears to always be -45.
+//
+// Here's how it goes:
+static int maxElevations[] = {
+        //  Azimuth
+        //
+    90, // 0  
+    45, // 15 
+    60, // 30 
+    45, // 45 
+    75, // 60 
+    45, // 75 
+    60, // 90 
+    45, // 105 
+    75, // 120 
+    45, // 135 
+    60, // 150 
+    45, // 165 
+    75, // 180 
+    45, // 195 
+    60, // 210 
+    45, // 225 
+    75, // 240 
+    45, // 255 
+    60, // 270 
+    45, // 285 
+    75, // 300 
+    45, // 315 
+    60, // 330 
+    45 //  345 
+};
+
+PassOwnPtr<HRTFElevation> HRTFElevation::createForSubject(const String& subjectName, int elevation, double sampleRate)
+{
+    bool isElevationGood = elevation >= -45 && elevation <= 90 && (elevation / 15) * 15 == elevation;
+    ASSERT(isElevationGood);
+    if (!isElevationGood)
+        return 0;
+        
+    OwnPtr<HRTFKernelList> kernelListL = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths));
+    OwnPtr<HRTFKernelList> kernelListR = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths));
+
+    // Load convolution kernels from HRTF files.
+    int interpolatedIndex = 0;
+    for (unsigned rawIndex = 0; rawIndex < NumberOfRawAzimuths; ++rawIndex) {
+        // Don't let elevation exceed maximum for this azimuth.
+        int maxElevation = maxElevations[rawIndex];
+        int actualElevation = min(elevation, maxElevation);
+
+        bool success = calculateKernelsForAzimuthElevation(rawIndex * AzimuthSpacing, actualElevation, sampleRate, subjectName, kernelListL->at(interpolatedIndex), kernelListR->at(interpolatedIndex));
+        if (!success)
+            return 0;
+            
+        interpolatedIndex += InterpolationFactor;
+    }
+
+    // Now go back and interpolate intermediate azimuth values.
+    for (unsigned i = 0; i < NumberOfTotalAzimuths; i += InterpolationFactor) {
+        int j = (i + InterpolationFactor) % NumberOfTotalAzimuths;
+
+        // Create the interpolated convolution kernels and delays.
+        for (unsigned jj = 1; jj < InterpolationFactor; ++jj) {
+            double x = double(jj) / double(InterpolationFactor); // interpolate from 0 -> 1
+
+            (*kernelListL)[i + jj] = HRTFKernel::createInterpolatedKernel(kernelListL->at(i).get(), kernelListL->at(j).get(), x);
+            (*kernelListR)[i + jj] = HRTFKernel::createInterpolatedKernel(kernelListR->at(i).get(), kernelListR->at(j).get(), x);
+        }
+    }
+    
+    OwnPtr<HRTFElevation> hrtfElevation = adoptPtr(new HRTFElevation(kernelListL.release(), kernelListR.release(), elevation, sampleRate));
+    return hrtfElevation.release();
+}
+
+PassOwnPtr<HRTFElevation> HRTFElevation::createByInterpolatingSlices(HRTFElevation* hrtfElevation1, HRTFElevation* hrtfElevation2, double x, double sampleRate)
+{
+    ASSERT(hrtfElevation1 && hrtfElevation2);
+    if (!hrtfElevation1 || !hrtfElevation2)
+        return 0;
+        
+    ASSERT(x >= 0.0 && x < 1.0);
+    
+    OwnPtr<HRTFKernelList> kernelListL = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths));
+    OwnPtr<HRTFKernelList> kernelListR = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths));
+
+    HRTFKernelList* kernelListL1 = hrtfElevation1->kernelListL();
+    HRTFKernelList* kernelListR1 = hrtfElevation1->kernelListR();
+    HRTFKernelList* kernelListL2 = hrtfElevation2->kernelListL();
+    HRTFKernelList* kernelListR2 = hrtfElevation2->kernelListR();
+    
+    // Interpolate kernels of corresponding azimuths of the two elevations.
+    for (unsigned i = 0; i < NumberOfTotalAzimuths; ++i) {
+        (*kernelListL)[i] = HRTFKernel::createInterpolatedKernel(kernelListL1->at(i).get(), kernelListL2->at(i).get(), x);
+        (*kernelListR)[i] = HRTFKernel::createInterpolatedKernel(kernelListR1->at(i).get(), kernelListR2->at(i).get(), x);
+    }
+
+    // Interpolate elevation angle.
+    double angle = (1.0 - x) * hrtfElevation1->elevationAngle() + x * hrtfElevation2->elevationAngle();
+    
+    OwnPtr<HRTFElevation> hrtfElevation = adoptPtr(new HRTFElevation(kernelListL.release(), kernelListR.release(), angle, sampleRate));
+    return hrtfElevation.release();  
+}
+
+void HRTFElevation::getKernelsFromAzimuth(double azimuthBlend, unsigned azimuthIndex, HRTFKernel* &kernelL, HRTFKernel* &kernelR, double& frameDelayL, double& frameDelayR)
+{
+    bool checkAzimuthBlend = azimuthBlend >= 0.0 && azimuthBlend < 1.0;
+    ASSERT(checkAzimuthBlend);
+    if (!checkAzimuthBlend)
+        azimuthBlend = 0.0;
+    
+    unsigned numKernels = m_kernelListL->size();
+
+    bool isIndexGood = azimuthIndex < numKernels;
+    ASSERT(isIndexGood);
+    if (!isIndexGood) {
+        kernelL = 0;
+        kernelR = 0;
+        return;
+    }
+    
+    // Return the left and right kernels.
+    kernelL = m_kernelListL->at(azimuthIndex).get();
+    kernelR = m_kernelListR->at(azimuthIndex).get();
+
+    frameDelayL = m_kernelListL->at(azimuthIndex)->frameDelay();
+    frameDelayR = m_kernelListR->at(azimuthIndex)->frameDelay();
+
+    int azimuthIndex2 = (azimuthIndex + 1) % numKernels;
+    double frameDelay2L = m_kernelListL->at(azimuthIndex2)->frameDelay();
+    double frameDelay2R = m_kernelListR->at(azimuthIndex2)->frameDelay();
+
+    // Linearly interpolate delays.
+    frameDelayL = (1.0 - azimuthBlend) * frameDelayL + azimuthBlend * frameDelay2L;
+    frameDelayR = (1.0 - azimuthBlend) * frameDelayR + azimuthBlend * frameDelay2R;
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(WEB_AUDIO)
diff --git a/WebCore/platform/audio/HRTFElevation.h b/WebCore/platform/audio/HRTFElevation.h
new file mode 100644 (file)
index 0000000..b388b34
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ *     its contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HRTFElevation_h
+#define HRTFElevation_h
+
+#include "HRTFKernel.h"
+#include <wtf/Noncopyable.h>
+#include <wtf/OwnPtr.h>
+#include <wtf/PassOwnPtr.h>
+#include <wtf/PassRefPtr.h>
+#include <wtf/RefCounted.h>
+#include <wtf/RefPtr.h>
+#include <wtf/text/CString.h>
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+// HRTFElevation contains all of the HRTFKernels (one left ear and one right ear per azimuth angle) for a particular elevation.
+
+class HRTFElevation : public Noncopyable {
+public:
+    // Loads and returns an HRTFElevation with the given HRTF database subject name and elevation from browser (or WebKit.framework) resources.
+    // Normally, there will only be a single HRTF database set, but this API supports the possibility of multiple ones with different names.
+    // Interpolated azimuths will be generated based on InterpolationFactor.
+    // Valid values for elevation are -45 -> +90 in 15 degree increments.
+    static PassOwnPtr<HRTFElevation> createForSubject(const String& subjectName, int elevation, double sampleRate);
+
+    // Given two HRTFElevations, and an interpolation factor x: 0 -> 1, returns an interpolated HRTFElevation.
+    static PassOwnPtr<HRTFElevation> createByInterpolatingSlices(HRTFElevation* hrtfElevation1, HRTFElevation* hrtfElevation2, double x, double sampleRate);
+
+    // Returns the list of left or right ear HRTFKernels for all the azimuths going from 0 to 360 degrees.
+    HRTFKernelList* kernelListL() { return m_kernelListL.get(); }
+    HRTFKernelList* kernelListR() { return m_kernelListR.get(); }
+
+    double elevationAngle() const { return m_elevationAngle; }
+    unsigned numberOfAzimuths() { return NumberOfTotalAzimuths; }
+    double sampleRate() const { return m_sampleRate; }
+    
+    // Returns the left and right kernels for the given azimuth index.
+    // The interpolated delays based on azimuthBlend: 0 -> 1 are returned in frameDelayL and frameDelayR.
+    void getKernelsFromAzimuth(double azimuthBlend, unsigned azimuthIndex, HRTFKernel* &kernelL, HRTFKernel* &kernelR, double& frameDelayL, double& frameDelayR);
+    
+    // Spacing, in degrees, between every azimuth loaded from resource.
+    static const unsigned AzimuthSpacing;
+    
+    // Number of azimuths loaded from resource.
+    static const unsigned NumberOfRawAzimuths;
+
+    // Interpolates by this factor to get the total number of azimuths from every azimuth loaded from resource.
+    static const unsigned InterpolationFactor;
+    
+    // Total number of azimuths after interpolation.
+    static const unsigned NumberOfTotalAzimuths;
+
+    // Given a specific azimuth and elevation angle, returns the left and right HRTFKernel.
+    // Valid values for azimuth are 0 -> 345 in 15 degree increments.
+    // Valid values for elevation are -45 -> +90 in 15 degree increments.
+    // Returns true on success.
+    static bool calculateKernelsForAzimuthElevation(int azimuth, int elevation, double sampleRate, const String& subjectName,
+                                                    RefPtr<HRTFKernel>& kernelL, RefPtr<HRTFKernel>& kernelR);
+
+    // Given a specific azimuth and elevation angle, returns the left and right HRTFKernel in kernelL and kernelR.
+    // This method averages the measured response using symmetry of azimuth (for example by averaging the -30.0 and +30.0 azimuth responses).
+    // Returns true on success.
+    static bool calculateSymmetricKernelsForAzimuthElevation(int azimuth, int elevation, double sampleRate, const String& subjectName,
+                                                             RefPtr<HRTFKernel>& kernelL, RefPtr<HRTFKernel>& kernelR);
+private:
+    HRTFElevation(PassOwnPtr<HRTFKernelList> kernelListL, PassOwnPtr<HRTFKernelList> kernelListR, int elevation, double sampleRate)
+        : m_kernelListL(kernelListL)
+        , m_kernelListR(kernelListR)
+        , m_elevationAngle(elevation)
+        , m_sampleRate(sampleRate)
+    {
+    }
+
+    OwnPtr<HRTFKernelList> m_kernelListL;
+    OwnPtr<HRTFKernelList> m_kernelListR;
+    double m_elevationAngle;
+    double m_sampleRate;
+};
+
+} // namespace WebCore
+
+#endif // HRTFElevation_h