michael@0: /* michael@0: * Copyright (C) 2010 Google Inc. All rights reserved. michael@0: * michael@0: * Redistribution and use in source and binary forms, with or without michael@0: * modification, are permitted provided that the following conditions michael@0: * are met: michael@0: * michael@0: * 1. Redistributions of source code must retain the above copyright michael@0: * notice, this list of conditions and the following disclaimer. michael@0: * 2. Redistributions in binary form must reproduce the above copyright michael@0: * notice, this list of conditions and the following disclaimer in the michael@0: * documentation and/or other materials provided with the distribution. michael@0: * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of michael@0: * its contributors may be used to endorse or promote products derived michael@0: * from this software without specific prior written permission. michael@0: * michael@0: * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY michael@0: * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED michael@0: * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE michael@0: * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY michael@0: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES michael@0: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; michael@0: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND michael@0: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF michael@0: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: */ michael@0: michael@0: #include "HRTFElevation.h" michael@0: michael@0: #include "speex/speex_resampler.h" michael@0: #include "mozilla/PodOperations.h" michael@0: #include "AudioSampleFormat.h" michael@0: michael@0: #include "IRC_Composite_C_R0195-incl.cpp" michael@0: michael@0: using namespace std; michael@0: using namespace mozilla; michael@0: michael@0: namespace WebCore { michael@0: michael@0: const int elevationSpacing = irc_composite_c_r0195_elevation_interval; michael@0: const int firstElevation = irc_composite_c_r0195_first_elevation; michael@0: const int numberOfElevations = MOZ_ARRAY_LENGTH(irc_composite_c_r0195); michael@0: michael@0: const unsigned HRTFElevation::NumberOfTotalAzimuths = 360 / 15 * 8; michael@0: michael@0: const int rawSampleRate = irc_composite_c_r0195_sample_rate; michael@0: michael@0: // Number of frames in an individual impulse response. michael@0: const size_t ResponseFrameSize = 256; michael@0: michael@0: size_t HRTFElevation::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t amount = aMallocSizeOf(this); michael@0: michael@0: amount += m_kernelListL.SizeOfExcludingThis(aMallocSizeOf); michael@0: for (size_t i = 0; i < m_kernelListL.Length(); i++) { michael@0: amount += m_kernelListL[i]->sizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: return amount; michael@0: } michael@0: michael@0: size_t HRTFElevation::fftSizeForSampleRate(float sampleRate) michael@0: { michael@0: // The IRCAM HRTF impulse responses were 512 sample-frames @44.1KHz, michael@0: // but these have been truncated to 256 samples. michael@0: // An FFT-size of twice impulse response size is used (for convolution). michael@0: // So for sample rates of 44.1KHz an FFT size of 512 is good. michael@0: // We double the FFT-size only for sample rates at least double this. michael@0: // If the FFT size is too large then the impulse response will be padded michael@0: // with zeros without the fade-out provided by HRTFKernel. michael@0: MOZ_ASSERT(sampleRate > 1.0 && sampleRate < 1048576.0); michael@0: michael@0: // This is the size if we were to use all raw response samples. michael@0: unsigned resampledLength = michael@0: floorf(ResponseFrameSize * sampleRate / rawSampleRate); michael@0: // Keep things semi-sane, with max FFT size of 1024 and minimum of 4. michael@0: // "size |= 3" ensures a minimum of 4 (with the size++ below) and sets the michael@0: // 2 least significant bits for rounding up to the next power of 2 below. michael@0: unsigned size = min(resampledLength, 1023U); michael@0: size |= 3; michael@0: // Round up to the next power of 2, making the FFT size no more than twice michael@0: // the impulse response length. This doubles size for values that are michael@0: // already powers of 2. This works by filling in 7 bits to right of the michael@0: // most significant bit. The most significant bit is no greater than michael@0: // 1 << 9, and the least significant 2 bits were already set above. michael@0: size |= (size >> 1); michael@0: size |= (size >> 2); michael@0: size |= (size >> 4); michael@0: size++; michael@0: MOZ_ASSERT((size & (size - 1)) == 0); michael@0: michael@0: return size; michael@0: } michael@0: michael@0: nsReturnRef HRTFElevation::calculateKernelForAzimuthElevation(int azimuth, int elevation, SpeexResamplerState* resampler, float sampleRate) michael@0: { michael@0: int elevationIndex = (elevation - firstElevation) / elevationSpacing; michael@0: MOZ_ASSERT(elevationIndex >= 0 && elevationIndex <= numberOfElevations); michael@0: michael@0: int numberOfAzimuths = irc_composite_c_r0195[elevationIndex].count; michael@0: int azimuthSpacing = 360 / numberOfAzimuths; michael@0: MOZ_ASSERT(numberOfAzimuths * azimuthSpacing == 360); michael@0: michael@0: int azimuthIndex = azimuth / azimuthSpacing; michael@0: MOZ_ASSERT(azimuthIndex * azimuthSpacing == azimuth); michael@0: michael@0: const int16_t (&impulse_response_data)[ResponseFrameSize] = michael@0: irc_composite_c_r0195[elevationIndex].azimuths[azimuthIndex]; michael@0: michael@0: // When libspeex_resampler is compiled with FIXED_POINT, samples in michael@0: // speex_resampler_process_float are rounded directly to int16_t, which michael@0: // only works well if the floats are in the range +/-32767. On such michael@0: // platforms it's better to resample before converting to float anyway. michael@0: #ifdef MOZ_SAMPLE_TYPE_S16 michael@0: # define RESAMPLER_PROCESS speex_resampler_process_int michael@0: const int16_t* response = impulse_response_data; michael@0: const int16_t* resampledResponse; michael@0: #else michael@0: # define RESAMPLER_PROCESS speex_resampler_process_float michael@0: float response[ResponseFrameSize]; michael@0: ConvertAudioSamples(impulse_response_data, response, ResponseFrameSize); michael@0: float* resampledResponse; michael@0: #endif michael@0: michael@0: // Note that depending on the fftSize returned by the panner, we may be truncating the impulse response. michael@0: const size_t resampledResponseLength = fftSizeForSampleRate(sampleRate) / 2; michael@0: michael@0: nsAutoTArray resampled; michael@0: if (sampleRate == rawSampleRate) { michael@0: resampledResponse = response; michael@0: MOZ_ASSERT(resampledResponseLength == ResponseFrameSize); michael@0: } else { michael@0: resampled.SetLength(resampledResponseLength); michael@0: resampledResponse = resampled.Elements(); michael@0: speex_resampler_skip_zeros(resampler); michael@0: michael@0: // Feed the input buffer into the resampler. michael@0: spx_uint32_t in_len = ResponseFrameSize; michael@0: spx_uint32_t out_len = resampled.Length(); michael@0: RESAMPLER_PROCESS(resampler, 0, response, &in_len, michael@0: resampled.Elements(), &out_len); michael@0: michael@0: if (out_len < resampled.Length()) { michael@0: // The input should have all been processed. michael@0: MOZ_ASSERT(in_len == ResponseFrameSize); michael@0: // Feed in zeros get the data remaining in the resampler. michael@0: spx_uint32_t out_index = out_len; michael@0: in_len = speex_resampler_get_input_latency(resampler); michael@0: out_len = resampled.Length() - out_index; michael@0: RESAMPLER_PROCESS(resampler, 0, nullptr, &in_len, michael@0: resampled.Elements() + out_index, &out_len); michael@0: out_index += out_len; michael@0: // There may be some uninitialized samples remaining for very low michael@0: // sample rates. michael@0: PodZero(resampled.Elements() + out_index, michael@0: resampled.Length() - out_index); michael@0: } michael@0: michael@0: speex_resampler_reset_mem(resampler); michael@0: } michael@0: michael@0: #ifdef MOZ_SAMPLE_TYPE_S16 michael@0: nsAutoTArray floatArray; michael@0: floatArray.SetLength(resampledResponseLength); michael@0: float *floatResponse = floatArray.Elements(); michael@0: ConvertAudioSamples(resampledResponse, michael@0: floatResponse, resampledResponseLength); michael@0: #else michael@0: float *floatResponse = resampledResponse; michael@0: #endif michael@0: #undef RESAMPLER_PROCESS michael@0: michael@0: return HRTFKernel::create(floatResponse, resampledResponseLength, sampleRate); michael@0: } michael@0: michael@0: // The range of elevations for the IRCAM impulse responses varies depending on azimuth, but the minimum elevation appears to always be -45. michael@0: // michael@0: // Here's how it goes: michael@0: static int maxElevations[] = { michael@0: // Azimuth michael@0: // michael@0: 90, // 0 michael@0: 45, // 15 michael@0: 60, // 30 michael@0: 45, // 45 michael@0: 75, // 60 michael@0: 45, // 75 michael@0: 60, // 90 michael@0: 45, // 105 michael@0: 75, // 120 michael@0: 45, // 135 michael@0: 60, // 150 michael@0: 45, // 165 michael@0: 75, // 180 michael@0: 45, // 195 michael@0: 60, // 210 michael@0: 45, // 225 michael@0: 75, // 240 michael@0: 45, // 255 michael@0: 60, // 270 michael@0: 45, // 285 michael@0: 75, // 300 michael@0: 45, // 315 michael@0: 60, // 330 michael@0: 45 // 345 michael@0: }; michael@0: michael@0: nsReturnRef HRTFElevation::createBuiltin(int elevation, float sampleRate) michael@0: { michael@0: if (elevation < firstElevation || michael@0: elevation > firstElevation + numberOfElevations * elevationSpacing || michael@0: (elevation / elevationSpacing) * elevationSpacing != elevation) michael@0: return nsReturnRef(); michael@0: michael@0: // Spacing, in degrees, between every azimuth loaded from resource. michael@0: // Some elevations do not have data for all these intervals. michael@0: // See maxElevations. michael@0: static const unsigned AzimuthSpacing = 15; michael@0: static const unsigned NumberOfRawAzimuths = 360 / AzimuthSpacing; michael@0: static_assert(AzimuthSpacing * NumberOfRawAzimuths == 360, michael@0: "Not a multiple"); michael@0: static const unsigned InterpolationFactor = michael@0: NumberOfTotalAzimuths / NumberOfRawAzimuths; michael@0: static_assert(NumberOfTotalAzimuths == michael@0: NumberOfRawAzimuths * InterpolationFactor, "Not a multiple"); michael@0: michael@0: HRTFKernelList kernelListL; michael@0: kernelListL.SetLength(NumberOfTotalAzimuths); michael@0: michael@0: SpeexResamplerState* resampler = sampleRate == rawSampleRate ? nullptr : michael@0: speex_resampler_init(1, rawSampleRate, sampleRate, michael@0: SPEEX_RESAMPLER_QUALITY_DEFAULT, nullptr); michael@0: michael@0: // Load convolution kernels from HRTF files. michael@0: int interpolatedIndex = 0; michael@0: for (unsigned rawIndex = 0; rawIndex < NumberOfRawAzimuths; ++rawIndex) { michael@0: // Don't let elevation exceed maximum for this azimuth. michael@0: int maxElevation = maxElevations[rawIndex]; michael@0: int actualElevation = min(elevation, maxElevation); michael@0: michael@0: kernelListL[interpolatedIndex] = calculateKernelForAzimuthElevation(rawIndex * AzimuthSpacing, actualElevation, resampler, sampleRate); michael@0: michael@0: interpolatedIndex += InterpolationFactor; michael@0: } michael@0: michael@0: if (resampler) michael@0: speex_resampler_destroy(resampler); michael@0: michael@0: // Now go back and interpolate intermediate azimuth values. michael@0: for (unsigned i = 0; i < NumberOfTotalAzimuths; i += InterpolationFactor) { michael@0: int j = (i + InterpolationFactor) % NumberOfTotalAzimuths; michael@0: michael@0: // Create the interpolated convolution kernels and delays. michael@0: for (unsigned jj = 1; jj < InterpolationFactor; ++jj) { michael@0: float x = float(jj) / float(InterpolationFactor); // interpolate from 0 -> 1 michael@0: michael@0: kernelListL[i + jj] = HRTFKernel::createInterpolatedKernel(kernelListL[i], kernelListL[j], x); michael@0: } michael@0: } michael@0: michael@0: return nsReturnRef(new HRTFElevation(&kernelListL, elevation, sampleRate)); michael@0: } michael@0: michael@0: nsReturnRef HRTFElevation::createByInterpolatingSlices(HRTFElevation* hrtfElevation1, HRTFElevation* hrtfElevation2, float x, float sampleRate) michael@0: { michael@0: MOZ_ASSERT(hrtfElevation1 && hrtfElevation2); michael@0: if (!hrtfElevation1 || !hrtfElevation2) michael@0: return nsReturnRef(); michael@0: michael@0: MOZ_ASSERT(x >= 0.0 && x < 1.0); michael@0: michael@0: HRTFKernelList kernelListL; michael@0: kernelListL.SetLength(NumberOfTotalAzimuths); michael@0: michael@0: const HRTFKernelList& kernelListL1 = hrtfElevation1->kernelListL(); michael@0: const HRTFKernelList& kernelListL2 = hrtfElevation2->kernelListL(); michael@0: michael@0: // Interpolate kernels of corresponding azimuths of the two elevations. michael@0: for (unsigned i = 0; i < NumberOfTotalAzimuths; ++i) { michael@0: kernelListL[i] = HRTFKernel::createInterpolatedKernel(kernelListL1[i], kernelListL2[i], x); michael@0: } michael@0: michael@0: // Interpolate elevation angle. michael@0: double angle = (1.0 - x) * hrtfElevation1->elevationAngle() + x * hrtfElevation2->elevationAngle(); michael@0: michael@0: return nsReturnRef(new HRTFElevation(&kernelListL, static_cast(angle), sampleRate)); michael@0: } michael@0: michael@0: void HRTFElevation::getKernelsFromAzimuth(double azimuthBlend, unsigned azimuthIndex, HRTFKernel* &kernelL, HRTFKernel* &kernelR, double& frameDelayL, double& frameDelayR) michael@0: { michael@0: bool checkAzimuthBlend = azimuthBlend >= 0.0 && azimuthBlend < 1.0; michael@0: MOZ_ASSERT(checkAzimuthBlend); michael@0: if (!checkAzimuthBlend) michael@0: azimuthBlend = 0.0; michael@0: michael@0: unsigned numKernels = m_kernelListL.Length(); michael@0: michael@0: bool isIndexGood = azimuthIndex < numKernels; michael@0: MOZ_ASSERT(isIndexGood); michael@0: if (!isIndexGood) { michael@0: kernelL = 0; michael@0: kernelR = 0; michael@0: return; michael@0: } michael@0: michael@0: // Return the left and right kernels, michael@0: // using symmetry to produce the right kernel. michael@0: kernelL = m_kernelListL[azimuthIndex]; michael@0: int azimuthIndexR = (numKernels - azimuthIndex) % numKernels; michael@0: kernelR = m_kernelListL[azimuthIndexR]; michael@0: michael@0: frameDelayL = kernelL->frameDelay(); michael@0: frameDelayR = kernelR->frameDelay(); michael@0: michael@0: int azimuthIndex2L = (azimuthIndex + 1) % numKernels; michael@0: double frameDelay2L = m_kernelListL[azimuthIndex2L]->frameDelay(); michael@0: int azimuthIndex2R = (numKernels - azimuthIndex2L) % numKernels; michael@0: double frameDelay2R = m_kernelListL[azimuthIndex2R]->frameDelay(); michael@0: michael@0: // Linearly interpolate delays. michael@0: frameDelayL = (1.0 - azimuthBlend) * frameDelayL + azimuthBlend * frameDelay2L; michael@0: frameDelayR = (1.0 - azimuthBlend) * frameDelayR + azimuthBlend * frameDelay2R; michael@0: } michael@0: michael@0: } // namespace WebCore