Unreviewed, rolling out r234489.
[WebKit-https.git] / Source / WebCore / platform / audio / HRTFElevation.cpp
index b0db862..f65cde3 100644 (file)
@@ -10,7 +10,7 @@
  * 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
+ * 3.  Neither the name of Apple 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.
  *
 #include "AudioFileReader.h"
 #include "Biquad.h"
 #include "FFTFrame.h"
+#include "HRTFDatabaseLoader.h"
 #include "HRTFPanner.h"
 #include <algorithm>
 #include <math.h>
-#include <wtf/OwnPtr.h>
+#include <wtf/NeverDestroyed.h>
 
-using namespace std;
 namespace WebCore {
 
 const unsigned HRTFElevation::AzimuthSpacing = 15;
@@ -50,9 +49,57 @@ const unsigned HRTFElevation::NumberOfRawAzimuths = 360 / AzimuthSpacing;
 const unsigned HRTFElevation::InterpolationFactor = 8;
 const unsigned HRTFElevation::NumberOfTotalAzimuths = NumberOfRawAzimuths * InterpolationFactor;
 
+// Total number of components of an HRTF database.
+const size_t TotalNumberOfResponses = 240;
+
+// Number of frames in an individual impulse response.
+const size_t ResponseFrameSize = 256;
+
+// Sample-rate of the spatialization impulse responses as stored in the resource file.
+// The impulse responses may be resampled to a different sample-rate (depending on the audio hardware) when they are loaded.
+const float ResponseSampleRate = 44100;
+
+#if PLATFORM(COCOA) || USE(WEBAUDIO_GSTREAMER)
+#define USE_CONCATENATED_IMPULSE_RESPONSES
+#endif
+
+#ifdef USE_CONCATENATED_IMPULSE_RESPONSES
+// Lazily load a concatenated HRTF database for given subject and store it in a
+// local hash table to ensure quick efficient future retrievals.
+static AudioBus* getConcatenatedImpulseResponsesForSubject(const String& subjectName)
+{
+    typedef HashMap<String, AudioBus*> AudioBusMap;
+    static NeverDestroyed<AudioBusMap> audioBusMap;
+
+    AudioBus* bus;
+    AudioBusMap::iterator iterator = audioBusMap.get().find(subjectName);
+    if (iterator == audioBusMap.get().end()) {
+        auto concatenatedImpulseResponses = AudioBus::loadPlatformResource(subjectName.utf8().data(), ResponseSampleRate);
+        ASSERT(concatenatedImpulseResponses);
+        if (!concatenatedImpulseResponses)
+            return 0;
+
+        bus = concatenatedImpulseResponses.leakRef();
+        audioBusMap.get().set(subjectName, bus);
+    } else
+        bus = iterator->value;
+
+    size_t responseLength = bus->length();
+    size_t expectedLength = static_cast<size_t>(TotalNumberOfResponses * ResponseFrameSize);
+
+    // Check number of channels and length. For now these are fixed and known.
+    bool isBusGood = responseLength == expectedLength && bus->numberOfChannels() == 2;
+    ASSERT(isBusGood);
+    if (!isBusGood)
+        return 0;
+
+    return bus;
+}
+#endif
+
 // 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,
+bool HRTFElevation::calculateSymmetricKernelsForAzimuthElevation(int azimuth, int elevation, float sampleRate, const String& subjectName,
                                                                  RefPtr<HRTFKernel>& kernelL, RefPtr<HRTFKernel>& kernelR)
 {
     RefPtr<HRTFKernel> kernelL1;
@@ -71,13 +118,13 @@ bool HRTFElevation::calculateSymmetricKernelsForAzimuthElevation(int azimuth, in
         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);
+    kernelL = HRTFKernel::createInterpolatedKernel(kernelL1.get(), kernelR2.get(), 0.5f);
+    kernelR = HRTFKernel::createInterpolatedKernel(kernelR1.get(), kernelL2.get(), 0.5f);
     
     return true;
 }
 
-bool HRTFElevation::calculateKernelsForAzimuthElevation(int azimuth, int elevation, double sampleRate, const String& subjectName,
+bool HRTFElevation::calculateKernelsForAzimuthElevation(int azimuth, int elevation, float sampleRate, const String& subjectName,
                                                         RefPtr<HRTFKernel>& kernelL, RefPtr<HRTFKernel>& kernelR)
 {
     // Valid values for azimuth are 0 -> 345 in 15 degree increments.
@@ -98,9 +145,40 @@ bool HRTFElevation::calculateKernelsForAzimuthElevation(int azimuth, int elevati
     // 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;
+
+#ifdef USE_CONCATENATED_IMPULSE_RESPONSES
+    AudioBus* bus(getConcatenatedImpulseResponsesForSubject(subjectName));
+
+    if (!bus)
+        return false;
+
+    int elevationIndex = positiveElevation / AzimuthSpacing;
+    if (positiveElevation > 90)
+        elevationIndex -= AzimuthSpacing;
+
+    // The concatenated impulse response is a bus containing all
+    // the elevations per azimuth, for all azimuths by increasing
+    // order. So for a given azimuth and elevation we need to compute
+    // the index of the wanted audio frames in the concatenated table.
+    unsigned index = ((azimuth / AzimuthSpacing) * HRTFDatabase::NumberOfRawElevations) + elevationIndex;
+    bool isIndexGood = index < TotalNumberOfResponses;
+    ASSERT(isIndexGood);
+    if (!isIndexGood)
+        return false;
+
+    // Extract the individual impulse response from the concatenated
+    // responses and potentially sample-rate convert it to the desired
+    // (hardware) sample-rate.
+    unsigned startFrame = index * ResponseFrameSize;
+    unsigned stopFrame = startFrame + ResponseFrameSize;
+    RefPtr<AudioBus> preSampleRateConvertedResponse = AudioBus::createBufferFromRange(bus, startFrame, stopFrame);
+    RefPtr<AudioBus> response = AudioBus::createBySampleRateConverting(preSampleRateConvertedResponse.get(), false, sampleRate);
+    AudioChannel* leftEarImpulseResponse = response->channel(AudioBus::ChannelLeft);
+    AudioChannel* rightEarImpulseResponse = response->channel(AudioBus::ChannelRight);
+#else
     String resourceName = String::format("IRC_%s_C_R0195_T%03d_P%03d", subjectName.utf8().data(), azimuth, positiveElevation);
 
-    OwnPtr<AudioBus> impulseResponse(AudioBus::loadPlatformResource(resourceName.utf8().data(), sampleRate));
+    RefPtr<AudioBus> impulseResponse(AudioBus::loadPlatformResource(resourceName.utf8().data(), sampleRate));
 
     ASSERT(impulseResponse.get());
     if (!impulseResponse.get())
@@ -117,11 +195,12 @@ bool HRTFElevation::calculateKernelsForAzimuthElevation(int azimuth, int elevati
     
     AudioChannel* leftEarImpulseResponse = impulseResponse->channelByType(AudioBus::ChannelLeft);
     AudioChannel* rightEarImpulseResponse = impulseResponse->channelByType(AudioBus::ChannelRight);
+#endif
 
     // 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);
+    kernelL = HRTFKernel::create(leftEarImpulseResponse, fftSize, sampleRate);
+    kernelR = HRTFKernel::create(rightEarImpulseResponse, fftSize, sampleRate);
     
     return true;
 }
@@ -129,7 +208,7 @@ bool HRTFElevation::calculateKernelsForAzimuthElevation(int azimuth, int elevati
 // 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[] = {
+static const int maxElevations[] = {
         //  Azimuth
         //
     90, // 0  
@@ -158,22 +237,22 @@ static int maxElevations[] = {
     45 //  345 
 };
 
-PassOwnPtr<HRTFElevation> HRTFElevation::createForSubject(const String& subjectName, int elevation, double sampleRate)
+std::unique_ptr<HRTFElevation> HRTFElevation::createForSubject(const String& subjectName, int elevation, float sampleRate)
 {
     bool isElevationGood = elevation >= -45 && elevation <= 90 && (elevation / 15) * 15 == elevation;
     ASSERT(isElevationGood);
     if (!isElevationGood)
         return nullptr;
         
-    OwnPtr<HRTFKernelList> kernelListL = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths));
-    OwnPtr<HRTFKernelList> kernelListR = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths));
+    auto kernelListL = std::make_unique<HRTFKernelList>(NumberOfTotalAzimuths);
+    auto kernelListR = std::make_unique<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);
+        int actualElevation = std::min(elevation, maxElevation);
 
         bool success = calculateKernelsForAzimuthElevation(rawIndex * AzimuthSpacing, actualElevation, sampleRate, subjectName, kernelListL->at(interpolatedIndex), kernelListR->at(interpolatedIndex));
         if (!success)
@@ -188,18 +267,17 @@ PassOwnPtr<HRTFElevation> HRTFElevation::createForSubject(const String& subjectN
 
         // Create the interpolated convolution kernels and delays.
         for (unsigned jj = 1; jj < InterpolationFactor; ++jj) {
-            double x = double(jj) / double(InterpolationFactor); // interpolate from 0 -> 1
+            float x = float(jj) / float(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();
+    return std::make_unique<HRTFElevation>(WTFMove(kernelListL), WTFMove(kernelListR), elevation, sampleRate);
 }
 
-PassOwnPtr<HRTFElevation> HRTFElevation::createByInterpolatingSlices(HRTFElevation* hrtfElevation1, HRTFElevation* hrtfElevation2, double x, double sampleRate)
+std::unique_ptr<HRTFElevation> HRTFElevation::createByInterpolatingSlices(HRTFElevation* hrtfElevation1, HRTFElevation* hrtfElevation2, float x, float sampleRate)
 {
     ASSERT(hrtfElevation1 && hrtfElevation2);
     if (!hrtfElevation1 || !hrtfElevation2)
@@ -207,8 +285,8 @@ PassOwnPtr<HRTFElevation> HRTFElevation::createByInterpolatingSlices(HRTFElevati
         
     ASSERT(x >= 0.0 && x < 1.0);
     
-    OwnPtr<HRTFKernelList> kernelListL = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths));
-    OwnPtr<HRTFKernelList> kernelListR = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths));
+    auto kernelListL = std::make_unique<HRTFKernelList>(NumberOfTotalAzimuths);
+    auto kernelListR = std::make_unique<HRTFKernelList>(NumberOfTotalAzimuths);
 
     HRTFKernelList* kernelListL1 = hrtfElevation1->kernelListL();
     HRTFKernelList* kernelListR1 = hrtfElevation1->kernelListR();
@@ -224,8 +302,7 @@ PassOwnPtr<HRTFElevation> HRTFElevation::createByInterpolatingSlices(HRTFElevati
     // Interpolate elevation angle.
     double angle = (1.0 - x) * hrtfElevation1->elevationAngle() + x * hrtfElevation2->elevationAngle();
     
-    OwnPtr<HRTFElevation> hrtfElevation = adoptPtr(new HRTFElevation(kernelListL.release(), kernelListR.release(), static_cast<int>(angle), sampleRate));
-    return hrtfElevation.release();  
+    return std::make_unique<HRTFElevation>(WTFMove(kernelListL), WTFMove(kernelListR), static_cast<int>(angle), sampleRate);
 }
 
 void HRTFElevation::getKernelsFromAzimuth(double azimuthBlend, unsigned azimuthIndex, HRTFKernel* &kernelL, HRTFKernel* &kernelR, double& frameDelayL, double& frameDelayR)