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 42namespace blink { 43 44const unsigned HRTFElevation::AzimuthSpacing = 15; 45const unsigned HRTFElevation::NumberOfRawAzimuths = 360 / AzimuthSpacing; 46const unsigned HRTFElevation::InterpolationFactor = 8; 47const unsigned HRTFElevation::NumberOfTotalAzimuths = NumberOfRawAzimuths * InterpolationFactor; 48 49// Total number of components of an HRTF database. 50const size_t TotalNumberOfResponses = 240; 51 52// Number of frames in an individual impulse response. 53const size_t ResponseFrameSize = 256; 54 55// Sample-rate of the spatialization impulse responses as stored in the resource file. 56// The impulse responses may be resampled to a different sample-rate (depending on the audio hardware) when they are loaded. 57const float ResponseSampleRate = 44100; 58 59#if USE(CONCATENATED_IMPULSE_RESPONSES) 60 61// This table maps the index into the elevation table with the corresponding angle. See 62// https://bugs.webkit.org/show_bug.cgi?id=98294#c9 for the elevation angles and their order in the 63// concatenated response. 64const int ElevationIndexTableSize = 10; 65const int ElevationIndexTable[ElevationIndexTableSize] = { 66 0, 15, 30, 45, 60, 75, 90, 315, 330, 345 67}; 68 69// Lazily load a concatenated HRTF database for given subject and store it in a 70// local hash table to ensure quick efficient future retrievals. 71static PassRefPtr<AudioBus> getConcatenatedImpulseResponsesForSubject(const String& subjectName) 72{ 73 typedef HashMap<String, RefPtr<AudioBus> > AudioBusMap; 74 DEFINE_STATIC_LOCAL(AudioBusMap, audioBusMap, ()); 75 DEFINE_STATIC_LOCAL(Mutex, mutex, ()); 76 77 MutexLocker locker(mutex); 78 RefPtr<AudioBus> bus; 79 AudioBusMap::iterator iterator = audioBusMap.find(subjectName); 80 if (iterator == audioBusMap.end()) { 81 RefPtr<AudioBus> concatenatedImpulseResponses(AudioBus::loadPlatformResource(subjectName.utf8().data(), ResponseSampleRate)); 82 ASSERT(concatenatedImpulseResponses); 83 if (!concatenatedImpulseResponses) 84 return nullptr; 85 86 bus = concatenatedImpulseResponses; 87 audioBusMap.set(subjectName, bus); 88 } else 89 bus = iterator->value; 90 91 size_t responseLength = bus->length(); 92 size_t expectedLength = static_cast<size_t>(TotalNumberOfResponses * ResponseFrameSize); 93 94 // Check number of channels and length. For now these are fixed and known. 95 bool isBusGood = responseLength == expectedLength && bus->numberOfChannels() == 2; 96 ASSERT(isBusGood); 97 if (!isBusGood) 98 return nullptr; 99 100 return bus; 101} 102#endif 103 104bool HRTFElevation::calculateKernelsForAzimuthElevation(int azimuth, int elevation, float sampleRate, const String& subjectName, 105 RefPtr<HRTFKernel>& kernelL, RefPtr<HRTFKernel>& kernelR) 106{ 107 // Valid values for azimuth are 0 -> 345 in 15 degree increments. 108 // Valid values for elevation are -45 -> +90 in 15 degree increments. 109 110 bool isAzimuthGood = azimuth >= 0 && azimuth <= 345 && (azimuth / 15) * 15 == azimuth; 111 ASSERT(isAzimuthGood); 112 if (!isAzimuthGood) 113 return false; 114 115 bool isElevationGood = elevation >= -45 && elevation <= 90 && (elevation / 15) * 15 == elevation; 116 ASSERT(isElevationGood); 117 if (!isElevationGood) 118 return false; 119 120 // Construct the resource name from the subject name, azimuth, and elevation, for example: 121 // "IRC_Composite_C_R0195_T015_P000" 122 // Note: the passed in subjectName is not a string passed in via JavaScript or the web. 123 // It's passed in as an internal ASCII identifier and is an implementation detail. 124 int positiveElevation = elevation < 0 ? elevation + 360 : elevation; 125 126#if USE(CONCATENATED_IMPULSE_RESPONSES) 127 RefPtr<AudioBus> bus(getConcatenatedImpulseResponsesForSubject(subjectName)); 128 129 if (!bus) 130 return false; 131 132 // Just sequentially search the table to find the correct index. 133 int elevationIndex = -1; 134 135 for (int k = 0; k < ElevationIndexTableSize; ++k) { 136 if (ElevationIndexTable[k] == positiveElevation) { 137 elevationIndex = k; 138 break; 139 } 140 } 141 142 bool isElevationIndexGood = (elevationIndex >= 0) && (elevationIndex < ElevationIndexTableSize); 143 ASSERT(isElevationIndexGood); 144 if (!isElevationIndexGood) 145 return false; 146 147 // The concatenated impulse response is a bus containing all 148 // the elevations per azimuth, for all azimuths by increasing 149 // order. So for a given azimuth and elevation we need to compute 150 // the index of the wanted audio frames in the concatenated table. 151 unsigned index = ((azimuth / AzimuthSpacing) * HRTFDatabase::NumberOfRawElevations) + elevationIndex; 152 bool isIndexGood = index < TotalNumberOfResponses; 153 ASSERT(isIndexGood); 154 if (!isIndexGood) 155 return false; 156 157 // Extract the individual impulse response from the concatenated 158 // responses and potentially sample-rate convert it to the desired 159 // (hardware) sample-rate. 160 unsigned startFrame = index * ResponseFrameSize; 161 unsigned stopFrame = startFrame + ResponseFrameSize; 162 RefPtr<AudioBus> preSampleRateConvertedResponse(AudioBus::createBufferFromRange(bus.get(), startFrame, stopFrame)); 163 RefPtr<AudioBus> response(AudioBus::createBySampleRateConverting(preSampleRateConvertedResponse.get(), false, sampleRate)); 164 AudioChannel* leftEarImpulseResponse = response->channel(AudioBus::ChannelLeft); 165 AudioChannel* rightEarImpulseResponse = response->channel(AudioBus::ChannelRight); 166#else 167 String resourceName = String::format("IRC_%s_C_R0195_T%03d_P%03d", subjectName.utf8().data(), azimuth, positiveElevation); 168 169 RefPtr<AudioBus> impulseResponse(AudioBus::loadPlatformResource(resourceName.utf8().data(), sampleRate)); 170 171 ASSERT(impulseResponse.get()); 172 if (!impulseResponse.get()) 173 return false; 174 175 size_t responseLength = impulseResponse->length(); 176 size_t expectedLength = static_cast<size_t>(256 * (sampleRate / 44100.0)); 177 178 // Check number of channels and length. For now these are fixed and known. 179 bool isBusGood = responseLength == expectedLength && impulseResponse->numberOfChannels() == 2; 180 ASSERT(isBusGood); 181 if (!isBusGood) 182 return false; 183 184 AudioChannel* leftEarImpulseResponse = impulseResponse->channelByType(AudioBus::ChannelLeft); 185 AudioChannel* rightEarImpulseResponse = impulseResponse->channelByType(AudioBus::ChannelRight); 186#endif 187 188 // Note that depending on the fftSize returned by the panner, we may be truncating the impulse response we just loaded in. 189 const size_t fftSize = HRTFPanner::fftSizeForSampleRate(sampleRate); 190 kernelL = HRTFKernel::create(leftEarImpulseResponse, fftSize, sampleRate); 191 kernelR = HRTFKernel::create(rightEarImpulseResponse, fftSize, sampleRate); 192 193 return true; 194} 195 196// The range of elevations for the IRCAM impulse responses varies depending on azimuth, but the minimum elevation appears to always be -45. 197// 198// Here's how it goes: 199static int maxElevations[] = { 200 // Azimuth 201 // 202 90, // 0 203 45, // 15 204 60, // 30 205 45, // 45 206 75, // 60 207 45, // 75 208 60, // 90 209 45, // 105 210 75, // 120 211 45, // 135 212 60, // 150 213 45, // 165 214 75, // 180 215 45, // 195 216 60, // 210 217 45, // 225 218 75, // 240 219 45, // 255 220 60, // 270 221 45, // 285 222 75, // 300 223 45, // 315 224 60, // 330 225 45 // 345 226}; 227 228PassOwnPtr<HRTFElevation> HRTFElevation::createForSubject(const String& subjectName, int elevation, float sampleRate) 229{ 230 bool isElevationGood = elevation >= -45 && elevation <= 90 && (elevation / 15) * 15 == elevation; 231 ASSERT(isElevationGood); 232 if (!isElevationGood) 233 return nullptr; 234 235 OwnPtr<HRTFKernelList> kernelListL = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths)); 236 OwnPtr<HRTFKernelList> kernelListR = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths)); 237 238 // Load convolution kernels from HRTF files. 239 int interpolatedIndex = 0; 240 for (unsigned rawIndex = 0; rawIndex < NumberOfRawAzimuths; ++rawIndex) { 241 // Don't let elevation exceed maximum for this azimuth. 242 int maxElevation = maxElevations[rawIndex]; 243 int actualElevation = std::min(elevation, maxElevation); 244 245 bool success = calculateKernelsForAzimuthElevation(rawIndex * AzimuthSpacing, actualElevation, sampleRate, subjectName, kernelListL->at(interpolatedIndex), kernelListR->at(interpolatedIndex)); 246 if (!success) 247 return nullptr; 248 249 interpolatedIndex += InterpolationFactor; 250 } 251 252 // Now go back and interpolate intermediate azimuth values. 253 for (unsigned i = 0; i < NumberOfTotalAzimuths; i += InterpolationFactor) { 254 int j = (i + InterpolationFactor) % NumberOfTotalAzimuths; 255 256 // Create the interpolated convolution kernels and delays. 257 for (unsigned jj = 1; jj < InterpolationFactor; ++jj) { 258 float x = float(jj) / float(InterpolationFactor); // interpolate from 0 -> 1 259 260 (*kernelListL)[i + jj] = HRTFKernel::createInterpolatedKernel(kernelListL->at(i).get(), kernelListL->at(j).get(), x); 261 (*kernelListR)[i + jj] = HRTFKernel::createInterpolatedKernel(kernelListR->at(i).get(), kernelListR->at(j).get(), x); 262 } 263 } 264 265 OwnPtr<HRTFElevation> hrtfElevation = adoptPtr(new HRTFElevation(kernelListL.release(), kernelListR.release(), elevation, sampleRate)); 266 return hrtfElevation.release(); 267} 268 269PassOwnPtr<HRTFElevation> HRTFElevation::createByInterpolatingSlices(HRTFElevation* hrtfElevation1, HRTFElevation* hrtfElevation2, float x, float sampleRate) 270{ 271 ASSERT(hrtfElevation1 && hrtfElevation2); 272 if (!hrtfElevation1 || !hrtfElevation2) 273 return nullptr; 274 275 ASSERT(x >= 0.0 && x < 1.0); 276 277 OwnPtr<HRTFKernelList> kernelListL = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths)); 278 OwnPtr<HRTFKernelList> kernelListR = adoptPtr(new HRTFKernelList(NumberOfTotalAzimuths)); 279 280 HRTFKernelList* kernelListL1 = hrtfElevation1->kernelListL(); 281 HRTFKernelList* kernelListR1 = hrtfElevation1->kernelListR(); 282 HRTFKernelList* kernelListL2 = hrtfElevation2->kernelListL(); 283 HRTFKernelList* kernelListR2 = hrtfElevation2->kernelListR(); 284 285 // Interpolate kernels of corresponding azimuths of the two elevations. 286 for (unsigned i = 0; i < NumberOfTotalAzimuths; ++i) { 287 (*kernelListL)[i] = HRTFKernel::createInterpolatedKernel(kernelListL1->at(i).get(), kernelListL2->at(i).get(), x); 288 (*kernelListR)[i] = HRTFKernel::createInterpolatedKernel(kernelListR1->at(i).get(), kernelListR2->at(i).get(), x); 289 } 290 291 // Interpolate elevation angle. 292 double angle = (1.0 - x) * hrtfElevation1->elevationAngle() + x * hrtfElevation2->elevationAngle(); 293 294 OwnPtr<HRTFElevation> hrtfElevation = adoptPtr(new HRTFElevation(kernelListL.release(), kernelListR.release(), static_cast<int>(angle), sampleRate)); 295 return hrtfElevation.release(); 296} 297 298void HRTFElevation::getKernelsFromAzimuth(double azimuthBlend, unsigned azimuthIndex, HRTFKernel* &kernelL, HRTFKernel* &kernelR, double& frameDelayL, double& frameDelayR) 299{ 300 bool checkAzimuthBlend = azimuthBlend >= 0.0 && azimuthBlend < 1.0; 301 ASSERT(checkAzimuthBlend); 302 if (!checkAzimuthBlend) 303 azimuthBlend = 0.0; 304 305 unsigned numKernels = m_kernelListL->size(); 306 307 bool isIndexGood = azimuthIndex < numKernels; 308 ASSERT(isIndexGood); 309 if (!isIndexGood) { 310 kernelL = 0; 311 kernelR = 0; 312 return; 313 } 314 315 // Return the left and right kernels. 316 kernelL = m_kernelListL->at(azimuthIndex).get(); 317 kernelR = m_kernelListR->at(azimuthIndex).get(); 318 319 frameDelayL = m_kernelListL->at(azimuthIndex)->frameDelay(); 320 frameDelayR = m_kernelListR->at(azimuthIndex)->frameDelay(); 321 322 int azimuthIndex2 = (azimuthIndex + 1) % numKernels; 323 double frameDelay2L = m_kernelListL->at(azimuthIndex2)->frameDelay(); 324 double frameDelay2R = m_kernelListR->at(azimuthIndex2)->frameDelay(); 325 326 // Linearly interpolate delays. 327 frameDelayL = (1.0 - azimuthBlend) * frameDelayL + azimuthBlend * frameDelay2L; 328 frameDelayR = (1.0 - azimuthBlend) * frameDelayR + azimuthBlend * frameDelay2R; 329} 330 331} // namespace blink 332 333#endif // ENABLE(WEB_AUDIO) 334