Home | History | Annotate | Download | only in audio
      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  *
      8  * 1.  Redistributions of source code must retain the above copyright
      9  *     notice, this list of conditions and the following disclaimer.
     10  * 2.  Redistributions in binary form must reproduce the above copyright
     11  *     notice, this list of conditions and the following disclaimer in the
     12  *     documentation and/or other materials provided with the distribution.
     13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     14  *     its contributors may be used to endorse or promote products derived
     15  *     from this software without specific prior written permission.
     16  *
     17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 #include "config.h"
     30 
     31 #if ENABLE(WEB_AUDIO)
     32 
     33 #include "platform/audio/HRTFElevation.h"
     34 
     35 #include <math.h>
     36 #include <algorithm>
     37 #include "platform/audio/AudioBus.h"
     38 #include "platform/audio/HRTFPanner.h"
     39 #include "wtf/ThreadingPrimitives.h"
     40 #include "wtf/text/StringHash.h"
     41 
     42 using namespace std;
     43 
     44 namespace WebCore {
     45 
     46 const unsigned HRTFElevation::AzimuthSpacing = 15;
     47 const unsigned HRTFElevation::NumberOfRawAzimuths = 360 / AzimuthSpacing;
     48 const unsigned HRTFElevation::InterpolationFactor = 8;
     49 const unsigned HRTFElevation::NumberOfTotalAzimuths = NumberOfRawAzimuths * InterpolationFactor;
     50 
     51 // Total number of components of an HRTF database.
     52 const size_t TotalNumberOfResponses = 240;
     53 
     54 // Number of frames in an individual impulse response.
     55 const size_t ResponseFrameSize = 256;
     56 
     57 // Sample-rate of the spatialization impulse responses as stored in the resource file.
     58 // The impulse responses may be resampled to a different sample-rate (depending on the audio hardware) when they are loaded.
     59 const float ResponseSampleRate = 44100;
     60 
     61 #if USE(CONCATENATED_IMPULSE_RESPONSES)
     62 // Lazily load a concatenated HRTF database for given subject and store it in a
     63 // local hash table to ensure quick efficient future retrievals.
     64 static PassRefPtr<AudioBus> getConcatenatedImpulseResponsesForSubject(const String& subjectName)
     65 {
     66     typedef HashMap<String, RefPtr<AudioBus> > AudioBusMap;
     67     DEFINE_STATIC_LOCAL(AudioBusMap, audioBusMap, ());
     68     DEFINE_STATIC_LOCAL(Mutex, mutex, ());
     69 
     70     MutexLocker locker(mutex);
     71     RefPtr<AudioBus> bus;
     72     AudioBusMap::iterator iterator = audioBusMap.find(subjectName);
     73     if (iterator == audioBusMap.end()) {
     74         RefPtr<AudioBus> concatenatedImpulseResponses(AudioBus::loadPlatformResource(subjectName.utf8().data(), ResponseSampleRate));
     75         ASSERT(concatenatedImpulseResponses);
     76         if (!concatenatedImpulseResponses)
     77             return nullptr;
     78 
     79         bus = concatenatedImpulseResponses;
     80         audioBusMap.set(subjectName, bus);
     81     } else
     82         bus = iterator->value;
     83 
     84     size_t responseLength = bus->length();
     85     size_t expectedLength = static_cast<size_t>(TotalNumberOfResponses * ResponseFrameSize);
     86 
     87     // Check number of channels and length. For now these are fixed and known.
     88     bool isBusGood = responseLength == expectedLength && bus->numberOfChannels() == 2;
     89     ASSERT(isBusGood);
     90     if (!isBusGood)
     91         return nullptr;
     92 
     93     return bus;
     94 }
     95 #endif
     96 
     97 // 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
     98 // where the roles of left and right ears are reversed with respect to each other.
     99 bool HRTFElevation::calculateSymmetricKernelsForAzimuthElevation(int azimuth, int elevation, float sampleRate, const String& subjectName,
    100                                                                  RefPtr<HRTFKernel>& kernelL, RefPtr<HRTFKernel>& kernelR)
    101 {
    102     RefPtr<HRTFKernel> kernelL1;
    103     RefPtr<HRTFKernel> kernelR1;
    104     bool success = calculateKernelsForAzimuthElevation(azimuth, elevation, sampleRate, subjectName, kernelL1, kernelR1);
    105     if (!success)
    106         return false;
    107 
    108     // And symmetric version
    109     int symmetricAzimuth = !azimuth ? 0 : 360 - azimuth;
    110 
    111     RefPtr<HRTFKernel> kernelL2;
    112     RefPtr<HRTFKernel> kernelR2;
    113     success = calculateKernelsForAzimuthElevation(symmetricAzimuth, elevation, sampleRate, subjectName, kernelL2, kernelR2);
    114     if (!success)
    115         return false;
    116 
    117     // Notice L/R reversal in symmetric version.
    118     kernelL = HRTFKernel::createInterpolatedKernel(kernelL1.get(), kernelR2.get(), 0.5f);
    119     kernelR = HRTFKernel::createInterpolatedKernel(kernelR1.get(), kernelL2.get(), 0.5f);
    120 
    121     return true;
    122 }
    123 
    124 bool HRTFElevation::calculateKernelsForAzimuthElevation(int azimuth, int elevation, float sampleRate, const String& subjectName,
    125                                                         RefPtr<HRTFKernel>& kernelL, RefPtr<HRTFKernel>& kernelR)
    126 {
    127     // Valid values for azimuth are 0 -> 345 in 15 degree increments.
    128     // Valid values for elevation are -45 -> +90 in 15 degree increments.
    129 
    130     bool isAzimuthGood = azimuth >= 0 && azimuth <= 345 && (azimuth / 15) * 15 == azimuth;
    131     ASSERT(isAzimuthGood);
    132     if (!isAzimuthGood)
    133         return false;
    134 
    135     bool isElevationGood = elevation >= -45 && elevation <= 90 && (elevation / 15) * 15 == elevation;
    136     ASSERT(isElevationGood);
    137     if (!isElevationGood)
    138         return false;
    139 
    140     // Construct the resource name from the subject name, azimuth, and elevation, for example:
    141     // "IRC_Composite_C_R0195_T015_P000"
    142     // Note: the passed in subjectName is not a string passed in via JavaScript or the web.
    143     // It's passed in as an internal ASCII identifier and is an implementation detail.
    144     int positiveElevation = elevation < 0 ? elevation + 360 : elevation;
    145 
    146 #if USE(CONCATENATED_IMPULSE_RESPONSES)
    147     RefPtr<AudioBus> bus(getConcatenatedImpulseResponsesForSubject(subjectName));
    148 
    149     if (!bus)
    150         return false;
    151 
    152     int elevationIndex = positiveElevation / AzimuthSpacing;
    153     if (positiveElevation > 90)
    154         elevationIndex -= AzimuthSpacing;
    155 
    156     // The concatenated impulse response is a bus containing all
    157     // the elevations per azimuth, for all azimuths by increasing
    158     // order. So for a given azimuth and elevation we need to compute
    159     // the index of the wanted audio frames in the concatenated table.
    160     unsigned index = ((azimuth / AzimuthSpacing) * HRTFDatabase::NumberOfRawElevations) + elevationIndex;
    161     bool isIndexGood = index < TotalNumberOfResponses;
    162     ASSERT(isIndexGood);
    163     if (!isIndexGood)
    164         return false;
    165 
    166     // Extract the individual impulse response from the concatenated
    167     // responses and potentially sample-rate convert it to the desired
    168     // (hardware) sample-rate.
    169     unsigned startFrame = index * ResponseFrameSize;
    170     unsigned stopFrame = startFrame + ResponseFrameSize;
    171     RefPtr<AudioBus> preSampleRateConvertedResponse(AudioBus::createBufferFromRange(bus.get(), startFrame, stopFrame));
    172     RefPtr<AudioBus> response(AudioBus::createBySampleRateConverting(preSampleRateConvertedResponse.get(), false, sampleRate));
    173     AudioChannel* leftEarImpulseResponse = response->channel(AudioBus::ChannelLeft);
    174     AudioChannel* rightEarImpulseResponse = response->channel(AudioBus::ChannelRight);
    175 #else
    176     String resourceName = String::format("IRC_%s_C_R0195_T%03d_P%03d", subjectName.utf8().data(), azimuth, positiveElevation);
    177 
    178     RefPtr<AudioBus> impulseResponse(AudioBus::loadPlatformResource(resourceName.utf8().data(), sampleRate));
    179 
    180     ASSERT(impulseResponse.get());
    181     if (!impulseResponse.get())
    182         return false;
    183 
    184     size_t responseLength = impulseResponse->length();
    185     size_t expectedLength = static_cast<size_t>(256 * (sampleRate / 44100.0));
    186 
    187     // Check number of channels and length.  For now these are fixed and known.
    188     bool isBusGood = responseLength == expectedLength && impulseResponse->numberOfChannels() == 2;
    189     ASSERT(isBusGood);
    190     if (!isBusGood)
    191         return false;
    192 
    193     AudioChannel* leftEarImpulseResponse = impulseResponse->channelByType(AudioBus::ChannelLeft);
    194     AudioChannel* rightEarImpulseResponse = impulseResponse->channelByType(AudioBus::ChannelRight);
    195 #endif
    196 
    197     // Note that depending on the fftSize returned by the panner, we may be truncating the impulse response we just loaded in.
    198     const size_t fftSize = HRTFPanner::fftSizeForSampleRate(sampleRate);
    199     kernelL = HRTFKernel::create(leftEarImpulseResponse, fftSize, sampleRate);
    200     kernelR = HRTFKernel::create(rightEarImpulseResponse, fftSize, sampleRate);
    201 
    202     return true;
    203 }
    204 
    205 // The range of elevations for the IRCAM impulse responses varies depending on azimuth, but the minimum elevation appears to always be -45.
    206 //
    207 // Here's how it goes:
    208 static int maxElevations[] = {
    209         //  Azimuth
    210         //
    211     90, // 0
    212     45, // 15
    213     60, // 30
    214     45, // 45
    215     75, // 60
    216     45, // 75
    217     60, // 90
    218     45, // 105
    219     75, // 120
    220     45, // 135
    221     60, // 150
    222     45, // 165
    223     75, // 180
    224     45, // 195
    225     60, // 210
    226     45, // 225
    227     75, // 240
    228     45, // 255
    229     60, // 270
    230     45, // 285
    231     75, // 300
    232     45, // 315
    233     60, // 330
    234     45 //  345
    235 };
    236 
    237 PassOwnPtr<HRTFElevation> HRTFElevation::createForSubject(const String& subjectName, int elevation, float sampleRate)
    238 {
    239     bool isElevationGood = elevation >= -45 && elevation <= 90 && (elevation / 15) * 15 == elevation;
    240     ASSERT(isElevationGood);
    241     if (!isElevationGood)
    242         return nullptr;
    243 
    244     OwnPtr<HRTFKernelList> kernelListL = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths));
    245     OwnPtr<HRTFKernelList> kernelListR = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths));
    246 
    247     // Load convolution kernels from HRTF files.
    248     int interpolatedIndex = 0;
    249     for (unsigned rawIndex = 0; rawIndex < NumberOfRawAzimuths; ++rawIndex) {
    250         // Don't let elevation exceed maximum for this azimuth.
    251         int maxElevation = maxElevations[rawIndex];
    252         int actualElevation = min(elevation, maxElevation);
    253 
    254         bool success = calculateKernelsForAzimuthElevation(rawIndex * AzimuthSpacing, actualElevation, sampleRate, subjectName, kernelListL->at(interpolatedIndex), kernelListR->at(interpolatedIndex));
    255         if (!success)
    256             return nullptr;
    257 
    258         interpolatedIndex += InterpolationFactor;
    259     }
    260 
    261     // Now go back and interpolate intermediate azimuth values.
    262     for (unsigned i = 0; i < NumberOfTotalAzimuths; i += InterpolationFactor) {
    263         int j = (i + InterpolationFactor) % NumberOfTotalAzimuths;
    264 
    265         // Create the interpolated convolution kernels and delays.
    266         for (unsigned jj = 1; jj < InterpolationFactor; ++jj) {
    267             float x = float(jj) / float(InterpolationFactor); // interpolate from 0 -> 1
    268 
    269             (*kernelListL)[i + jj] = HRTFKernel::createInterpolatedKernel(kernelListL->at(i).get(), kernelListL->at(j).get(), x);
    270             (*kernelListR)[i + jj] = HRTFKernel::createInterpolatedKernel(kernelListR->at(i).get(), kernelListR->at(j).get(), x);
    271         }
    272     }
    273 
    274     OwnPtr<HRTFElevation> hrtfElevation = adoptPtr(new HRTFElevation(kernelListL.release(), kernelListR.release(), elevation, sampleRate));
    275     return hrtfElevation.release();
    276 }
    277 
    278 PassOwnPtr<HRTFElevation> HRTFElevation::createByInterpolatingSlices(HRTFElevation* hrtfElevation1, HRTFElevation* hrtfElevation2, float x, float sampleRate)
    279 {
    280     ASSERT(hrtfElevation1 && hrtfElevation2);
    281     if (!hrtfElevation1 || !hrtfElevation2)
    282         return nullptr;
    283 
    284     ASSERT(x >= 0.0 && x < 1.0);
    285 
    286     OwnPtr<HRTFKernelList> kernelListL = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths));
    287     OwnPtr<HRTFKernelList> kernelListR = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths));
    288 
    289     HRTFKernelList* kernelListL1 = hrtfElevation1->kernelListL();
    290     HRTFKernelList* kernelListR1 = hrtfElevation1->kernelListR();
    291     HRTFKernelList* kernelListL2 = hrtfElevation2->kernelListL();
    292     HRTFKernelList* kernelListR2 = hrtfElevation2->kernelListR();
    293 
    294     // Interpolate kernels of corresponding azimuths of the two elevations.
    295     for (unsigned i = 0; i < NumberOfTotalAzimuths; ++i) {
    296         (*kernelListL)[i] = HRTFKernel::createInterpolatedKernel(kernelListL1->at(i).get(), kernelListL2->at(i).get(), x);
    297         (*kernelListR)[i] = HRTFKernel::createInterpolatedKernel(kernelListR1->at(i).get(), kernelListR2->at(i).get(), x);
    298     }
    299 
    300     // Interpolate elevation angle.
    301     double angle = (1.0 - x) * hrtfElevation1->elevationAngle() + x * hrtfElevation2->elevationAngle();
    302 
    303     OwnPtr<HRTFElevation> hrtfElevation = adoptPtr(new HRTFElevation(kernelListL.release(), kernelListR.release(), static_cast<int>(angle), sampleRate));
    304     return hrtfElevation.release();
    305 }
    306 
    307 void HRTFElevation::getKernelsFromAzimuth(double azimuthBlend, unsigned azimuthIndex, HRTFKernel* &kernelL, HRTFKernel* &kernelR, double& frameDelayL, double& frameDelayR)
    308 {
    309     bool checkAzimuthBlend = azimuthBlend >= 0.0 && azimuthBlend < 1.0;
    310     ASSERT(checkAzimuthBlend);
    311     if (!checkAzimuthBlend)
    312         azimuthBlend = 0.0;
    313 
    314     unsigned numKernels = m_kernelListL->size();
    315 
    316     bool isIndexGood = azimuthIndex < numKernels;
    317     ASSERT(isIndexGood);
    318     if (!isIndexGood) {
    319         kernelL = 0;
    320         kernelR = 0;
    321         return;
    322     }
    323 
    324     // Return the left and right kernels.
    325     kernelL = m_kernelListL->at(azimuthIndex).get();
    326     kernelR = m_kernelListR->at(azimuthIndex).get();
    327 
    328     frameDelayL = m_kernelListL->at(azimuthIndex)->frameDelay();
    329     frameDelayR = m_kernelListR->at(azimuthIndex)->frameDelay();
    330 
    331     int azimuthIndex2 = (azimuthIndex + 1) % numKernels;
    332     double frameDelay2L = m_kernelListL->at(azimuthIndex2)->frameDelay();
    333     double frameDelay2R = m_kernelListR->at(azimuthIndex2)->frameDelay();
    334 
    335     // Linearly interpolate delays.
    336     frameDelayL = (1.0 - azimuthBlend) * frameDelayL + azimuthBlend * frameDelay2L;
    337     frameDelayR = (1.0 - azimuthBlend) * frameDelayR + azimuthBlend * frameDelay2R;
    338 }
    339 
    340 } // namespace WebCore
    341 
    342 #endif // ENABLE(WEB_AUDIO)
    343