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