CSL 5.2

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

Binaural.cpp
Go to the documentation of this file.
1 ///
2 /// Binaural.cpp -- HRTF-based binaural panner/spatializers
3 ///
4 /// Classes
5 /// HRTF: holds the data that corresponds to an HRTF for a single position.
6 /// HRTFDatabase: vector of HRTFs; implemented as a Singleton because it's large.
7 /// BinauralPanner: place sources in 3D using block-wise convolution with an HRTF.
8 /// BinauralSourceCache: used for caching previous state of spatial sources.
9 ///
10 /// See the copyright notice and acknowledgment of authors in the file COPYRIGHT
11 /// Created by Jorge Castellanos on 7/19/06.
12 /// Rewritten for FFT wrappers and pluggable sound file APIs in 8/09 by STP.
13 /// Inspired by and partially based on the VST HRTF Plug-in written by Ryan Avery.
14 ///
15
16 #include "Binaural.h"
17
18 using namespace csl;
19 using namespace std;
20
21 //
22 // BinauralPanner constructor (blockSize typ. 512, FFT 1024, spect[513])
23 //
24
25 BinauralPanner::BinauralPanner(unsigned blockSize)
26 : SpatialPanner(), // inherited constructor
27 // set up FFT wrappers
28 mInFFT(blockSize * FFT_DOWNS, CSL_FFT_COMPLEX, CSL_FFT_FORWARD), // forward
29 mOutFFT(blockSize * FFT_DOWNS, CSL_FFT_COMPLEX, CSL_FFT_INVERSE), // inverse
30 mInBuf(1, blockSize * 2), // dbl length
31 mTmpBuf(1, blockSize * 2 + 8), // complex #s
32 mOutBuf(2, blockSize * 2), // dbl length
33 mBlockInd(0) { // starting index
34 unsigned hrtfLength, winSize; // set up panner variables
35 mFramesPerBlock = blockSize; // 512
36 mNumBlocks = HRTFDatabase::Database()->numBlocks(); // 16
37 #ifdef IPHONE
38 mNumBlocksToSum = mNumBlocks / SUM_DOWNS; // sum part of the HRTF
39 #else
40 mNumBlocksToSum = mNumBlocks; // sum the whole HRTF
41 #endif
42 hrtfLength = HRTFDatabase::Database()->hrtfLength(); // 512
43 winSize = HRTFDatabase::Database()->windowSize(); // 1024
44 mTmpBuf.mType = kSpectra; // temp buffer is complex spectral data
45 setCopyPolicy(kIgnore); // so the CopyPolicy doesn't overide panning
46 setNumChannels(2); // be stereo
47
48 SAFE_MALLOC(mIFFTOutL, sample, winSize); // allocate real sample buffers
49 SAFE_MALLOC(mIFFTOutR, sample, winSize);
50 SAFE_MALLOC(mHOutL, SampleComplex, hrtfLength + 1); // and complex spectral sums
51 SAFE_MALLOC(mHOutR, SampleComplex, hrtfLength + 1);
52 mInBuf.allocateBuffers(); // this holds the input samples;
53 // the other buffers are ptrs only
54 }
55
56 // Destructor frees space
57
58 BinauralPanner::~BinauralPanner() {
59 SAFE_FREE(mIFFTOutL);
60 SAFE_FREE(mIFFTOutR);
61 SAFE_FREE(mHOutL);
62 SAFE_FREE(mHOutR);
63 }
64
65 // Returns a pointer to an alocated cache data for its own use.
66
67 void * BinauralPanner::cache() {
68 return (void *)(new BinauralSourceCache(this));
69 }
70
71 //
72 // BinauralPanner::nextBuffer() does the heavy lifting of the block-wise convolution
73 //
74
75 void BinauralPanner::nextBuffer(Buffer & outputBuffer) throw (CException) {
76
77 HRTFDatabase * hrtfDatabase = HRTFDatabase::Database();
78 unsigned numFrames = outputBuffer.mNumFrames;
79 unsigned tmpBufPtr, tmpHRTFPtr = 0;
80 unsigned hrtfLength = hrtfDatabase->hrtfLength();
81 unsigned hrtf2Use = hrtfLength / LEN_DOWNS;
82 unsigned winSize = hrtfDatabase->windowSize();
83 #ifdef CSL_DEBUG
84 logMsg("BinauralPanner::nextBuffer");
85 #endif
86 if (outputBuffer.mNumChannels != 2) {
87 logMsg(kLogError, "BinauralPanner needs a stereo output buffer");
88 return;
89 }
90 if (numFrames != mFramesPerBlock) {
91 logMsg(kLogError,
92 "BinauralPanner::nextBuffer # frames %d wrong for HRTF size %d (use a block resizer).",
93 numFrames, mFramesPerBlock);
94 return;
95 }
96 outputBuffer.zeroBuffers(); // clear output
97
98 for (unsigned i = 0; i < mSources.size(); i++) { // i loops through sources
99
100 SpatialSource * source = (SpatialSource *) mSources[i]; // Get the sound source
101
102 if (source->isActive()) { // if source is active
103 mInBuf.mNumFrames = winSize; // set in # frames
104 mInBuf.zeroBuffers(); // clear buffer
105 mInBuf.mNumFrames = numFrames; // set in # frames
106 bool samePos = ! source->positionChanged(); // check this before we grab the input
107
108 source->nextBuffer(mInBuf); // get input from the source
109
110 //////// // debugging: copy input to output
111 // memcpy(outputBuffer.buffer(0), mInBuf.buffer(0), numFrames * sizeof(sample));
112 // memcpy(outputBuffer.buffer(1), mInBuf.buffer(0), numFrames * sizeof(sample));
113 // return;
114 //////// // Get the cached data
115 BinauralSourceCache * tcache = (BinauralSourceCache *) mCache[i];
116 // set input complex fft pointer
117 mTmpBuf.setBuffer(0, (SampleBuffer) tcache->mInSpect[mBlockInd]);
118 mInBuf.mNumFrames = winSize; // dbl length
119
120 mInFFT.nextBuffer(mInBuf, mTmpBuf); // Calculate input FFT into cache->mInSpect
121
122 if (samePos) { // if the position of the source is the same.
123 if (mBlockInd > 0) // assign the same HRTF you had the previous round
124 tcache->mHRTF[mBlockInd] = tcache->mHRTF[mBlockInd - 1];
125 else
126 tcache->mHRTF[mBlockInd] = tcache->mHRTF[mNumBlocks - 1];
127
128 } else { // if the position changed, then find the new HRTF
129 unsigned hrtfIndex = hrtfDatabase->hrtfAt(*source->position());
130 tcache->mHRTF[mBlockInd] = hrtfIndex;
131 // CPoint hrtfPosition = hrtfDatabase->hrtfAt(localHRTF)->mPosition;
132 // logMsg("\t\t\t\tHRTF: %5.1f @ %5.1f \t Source: %5.1f @ %5.1f",
133 // hrtfPosition.theta() * CSL_DEGS_PER_RAD, hrtfPosition.ele() * CSL_DEGS_PER_RAD,
134 // source->position()->theta() * CSL_DEGS_PER_RAD, source->position()->ele() * CSL_DEGS_PER_RAD);
135 }
136 // zero the complex spectrum buffers before summing loop
137 memset(mHOutL, 0, hrtf2Use * sizeof(SampleComplex));
138 memset(mHOutR, 0, hrtf2Use * sizeof(SampleComplex));
139
140 // Do the convolution of HRTF with the input spectra
141 tmpBufPtr = mBlockInd; // set the block counter to the current block
142 do { // Go thru previous HRTFs doing complex multiply/accumulate
143 HRTF * tempHRTF = hrtfDatabase->hrtfAt(tcache->mHRTF[tmpBufPtr]);
144 for (unsigned j = 0; j < hrtf2Use; j++) { // loop over buffers of past FFT out
145 ComplexPtr in1j = tcache->mInSpect[tmpBufPtr][j]; // input spectrum
146 ComplexPtr inLj = tempHRTF->mHrtfL[tmpHRTFPtr][j]; // L/R HRTF
147 ComplexPtr inRj = tempHRTF->mHrtfR[tmpHRTFPtr][j];
148 ComplexPtr outLj = mHOutL[j]; // L/R summation buffers
149 ComplexPtr outRj = mHOutR[j];
150 cmac(in1j, inLj, outLj); // complex MAC macro
151 cmac(in1j, inRj, outRj);
152 // old-way: complex MAC loop with 2D arrays
153 // cmac(cache->mInSpect[tmpBufPtr][j], tempHRTF->mHrtfL[tmpHRTFPtr][j], mHOutL[j]);
154 // cmac(cache->mInSpect[tmpBufPtr][j], tempHRTF->mHrtfR[tmpHRTFPtr][j], mHOutR[j]);
155 }
156 if (++tmpBufPtr >= mNumBlocksToSum)
157 tmpBufPtr = 0;
158 if (++tmpHRTFPtr >= mNumBlocksToSum)
159 tmpHRTFPtr = 0;
160 } while (tmpBufPtr != mBlockInd); // end of loop through prior inputs and HRTF spectra
161
162 // debugging: overwrite spectral sum with input
163 // memcpy(mHOutL, cache->mInSpect[mBlockInd], hrtfLength * sizeof(SampleComplex));
164 // memcpy(mHOutR, cache->mInSpect[mBlockInd], hrtfLength * sizeof(SampleComplex));
165 // Output IFFT stage
166 mTmpBuf.mNumFrames = winSize; // set up output buffer
167 mTmpBuf.setBuffer(0, (SampleBuffer) mHOutL); // IFFT input = summation buffer
168 mOutBuf.setBuffer(0, mIFFTOutL); // IFFT out = sample buffer
169
170 mOutFFT.nextBuffer(mTmpBuf, mOutBuf); // do left IFFT
171
172 mTmpBuf.setBuffer(0, (SampleBuffer) mHOutR);
173 mOutBuf.setBuffer(0, mIFFTOutR);
174
175 mOutFFT.nextBuffer(mTmpBuf, mOutBuf); // do right IFFT
176
177 SampleBuffer outL = outputBuffer.buffer(0); // Get pointers to output buffers (stereo)
178 SampleBuffer outR = outputBuffer.buffer(1);
179 SampleBuffer ifftL = mIFFTOutL; // IFFT real out data ptrs
180 SampleBuffer ifftR = mIFFTOutR;
181 SampleBuffer cacheL = tcache->mPrevOutL; // previous real output caches
182 SampleBuffer cacheR = tcache->mPrevOutR;
183 #ifdef USE_FFTW
184 sample scale = 1.0f / (float) numFrames; // window size scale
185 #else
186 sample scale = 1.0f ; // / sqrtf(numFrames);
187 // sample scale = 1.0f / sqrtf(numFrames);
188 #endif
189 for (unsigned j = 0; j < numFrames; j++) { // Overlap and add loop
190 *outL++ += ((*ifftL++ + *cacheL++) * scale); // sum/scale IFFT & cache
191 *outR++ += ((*ifftR++ + *cacheR++) * scale);
192 }
193 // copy top-half of output to cache
194 memcpy(tcache->mPrevOutL, mIFFTOutL + numFrames, numFrames * sizeof(sample));
195 memcpy(tcache->mPrevOutR, mIFFTOutR + numFrames, numFrames * sizeof(sample));
196 } // end if source active
197 } // end source loop
198
199 if (mBlockInd == 0) // test/decrement panner's block counter
200 mBlockInd = mNumBlocksToSum; // for next call
201 mBlockInd--;
202 }
203
204
205 // BinauralSourceCache constructor
206
207 BinauralSourceCache::BinauralSourceCache(BinauralPanner * parent) {
208 mNumBlocks = parent->mNumBlocks;
209 unsigned framesPerBlock = parent->mFramesPerBlock;
210 unsigned hrtfLength = HRTFDatabase::Database()->hrtfLength() + 1;
211 // unsigned windowSize = HRTFDatabase::Database()->windowSize();
212
213 // allocate buffers
214 SAFE_MALLOC(mPrevOutL, sample, framesPerBlock);
215 SAFE_MALLOC(mPrevOutR, sample, framesPerBlock);
216 SAFE_MALLOC(mHRTF, unsigned, mNumBlocks);
217
218 // create/zero the complex arrays for past inp FFTs
219 SAFE_MALLOC(mInSpect, SampleComplexVector, mNumBlocks);
220 for (unsigned i = 0; i < mNumBlocks; i++) {
221 SAFE_MALLOC(mInSpect[i], SampleComplex, hrtfLength);
222 }
223 }
224
225 BinauralSourceCache::~BinauralSourceCache() {
226 for (unsigned i = 0; i < mNumBlocks; i++) {
227 SAFE_FREE(mInSpect[i]);
228 }
229 SAFE_FREE(mInSpect);
230 SAFE_FREE(mPrevOutL);
231 SAFE_FREE(mPrevOutR);
232 SAFE_FREE(mHRTF);
233 }