ese 519

Dependents:   PROJECT_3D_AUDIO

Committer:
niv17
Date:
Tue Apr 07 21:09:22 2015 +0000
Revision:
0:2076b4d80327
sonic initial april 7

Who changed what in which revision?

UserRevisionLine numberNew contents of line
niv17 0:2076b4d80327 1 #include <math.h>
niv17 0:2076b4d80327 2 #include "Mixer3D.h"
niv17 0:2076b4d80327 3 //#include "World.h"
niv17 0:2076b4d80327 4 #include "fft.h"
niv17 0:2076b4d80327 5 #include "mit_hrtf_lib.h"
niv17 0:2076b4d80327 6 #include <sys/time.h>
niv17 0:2076b4d80327 7
niv17 0:2076b4d80327 8 Mixer3D::Mixer3D(int bufSize, int smpRate, int bitD) :
niv17 0:2076b4d80327 9 bufferSize(bufSize), sampleRate(smpRate), bitDepth(bitD))
niv17 0:2076b4d80327 10 {
niv17 0:2076b4d80327 11 cout << "BUF_SIZE: " << bufSize << endl;
niv17 0:2076b4d80327 12 cout << "SAMPLE_RATE: " << smpRate << endl;
niv17 0:2076b4d80327 13 cout << "BIT_DEPTH: " << bitD << endl;
niv17 0:2076b4d80327 14 inputAO = new Complex[2*bufferSize];
niv17 0:2076b4d80327 15 overlapInput = new Complex[2 * bufferSize];
niv17 0:2076b4d80327 16 fInput = new Complex[2 * bufferSize];
niv17 0:2076b4d80327 17 fFilter = new Complex[2 * bufferSize];
niv17 0:2076b4d80327 18
niv17 0:2076b4d80327 19 azimuths = new int[World::MAX_OBJ];
niv17 0:2076b4d80327 20 elevations = new int[World::MAX_OBJ];
niv17 0:2076b4d80327 21 prevAzimuths = new int[World::MAX_OBJ];
niv17 0:2076b4d80327 22 prevElevations = new int[World::MAX_OBJ];
niv17 0:2076b4d80327 23
niv17 0:2076b4d80327 24 updateAngles(); // initialize azimuths and elevations
niv17 0:2076b4d80327 25
niv17 0:2076b4d80327 26 outputLeft = new Complex*[World::MAX_OBJ];
niv17 0:2076b4d80327 27 outputRight = new Complex*[World::MAX_OBJ];
niv17 0:2076b4d80327 28 overlapLeft = new Complex*[World::MAX_OBJ];
niv17 0:2076b4d80327 29 overlapRight = new Complex*[World::MAX_OBJ];
niv17 0:2076b4d80327 30
niv17 0:2076b4d80327 31 leftFilter = new short[2 * bufferSize];
niv17 0:2076b4d80327 32 rightFilter = new short[2 * bufferSize];
niv17 0:2076b4d80327 33
niv17 0:2076b4d80327 34
niv17 0:2076b4d80327 35 }
niv17 0:2076b4d80327 36
niv17 0:2076b4d80327 37 Mixer3D::~Mixer3D()
niv17 0:2076b4d80327 38 {
niv17 0:2076b4d80327 39 delete [] inputAO;
niv17 0:2076b4d80327 40 delete [] outputLeft;
niv17 0:2076b4d80327 41 delete [] outputLeft;
niv17 0:2076b4d80327 42 delete [] leftFilter;
niv17 0:2076b4d80327 43 delete [] rightFilter;
niv17 0:2076b4d80327 44 delete [] complexLeftFilter;
niv17 0:2076b4d80327 45 delete [] complexRightFilter;
niv17 0:2076b4d80327 46 delete [] overlapLeft;
niv17 0:2076b4d80327 47 delete [] overlapRight;
niv17 0:2076b4d80327 48 delete [] overlapInput;
niv17 0:2076b4d80327 49 delete [] prevAzimuths;
niv17 0:2076b4d80327 50 delete [] prevElevations;
niv17 0:2076b4d80327 51 delete [] azimuths;
niv17 0:2076b4d80327 52 delete [] elevations;
niv17 0:2076b4d80327 53 }
niv17 0:2076b4d80327 54
niv17 0:2076b4d80327 55 void Mixer3D::updateAngles() {
niv17 0:2076b4d80327 56 AudioObj* iAudioObj;
niv17 0:2076b4d80327 57 Location iLocation;
niv17 0:2076b4d80327 58 for (int i = 0; i < myWorld->getNumObj(); i++) {
niv17 0:2076b4d80327 59 iAudioObj = myWorld->getAudioObj(i);
niv17 0:2076b4d80327 60 iLocation = iAudioObj->getLocation();
niv17 0:2076b4d80327 61
niv17 0:2076b4d80327 62 if (iAudioObj->isGpsObject()) {
niv17 0:2076b4d80327 63 azimuths[i] = player.computeAzimuthFromGpsCoordinates(&iLocation);
niv17 0:2076b4d80327 64 elevations[i] = player.computeElevationFromGpsCoordinates(&iLocation);
niv17 0:2076b4d80327 65 } else {
niv17 0:2076b4d80327 66 azimuths[i] = player.computeAzimuth(&iLocation);
niv17 0:2076b4d80327 67 elevations[i] = player.computeElevation(&iLocation);
niv17 0:2076b4d80327 68 }
niv17 0:2076b4d80327 69 }
niv17 0:2076b4d80327 70 }
niv17 0:2076b4d80327 71
niv17 0:2076b4d80327 72 bool Mixer3D::isPowerOfTwo(int x) {
niv17 0:2076b4d80327 73 return !(x == 0) && !(x & (x - 1));
niv17 0:2076b4d80327 74 }
niv17 0:2076b4d80327 75
niv17 0:2076b4d80327 76 int Mixer3D::loadHRTF(int* pAzimuth, int* pElevation, unsigned int samplerate, unsigned int diffused, Complex *&leftFilterIn, Complex *&rightFilterIn)
niv17 0:2076b4d80327 77 {
niv17 0:2076b4d80327 78 int size = mit_hrtf_get(pAzimuth, pElevation, samplerate, diffused, leftFilter, rightFilter);
niv17 0:2076b4d80327 79
niv17 0:2076b4d80327 80 if (size == 0) {
niv17 0:2076b4d80327 81 // TODO: Throw MIT HRTF filter does not exist exception
niv17 0:2076b4d80327 82 }
niv17 0:2076b4d80327 83 for (int i = 0; i < size; i++)
niv17 0:2076b4d80327 84 {
niv17 0:2076b4d80327 85 leftFilterIn[i] = (double)(leftFilter[i]);
niv17 0:2076b4d80327 86 rightFilterIn[i] = (double)(rightFilter[i]);
niv17 0:2076b4d80327 87 }
niv17 0:2076b4d80327 88
niv17 0:2076b4d80327 89 return size;
niv17 0:2076b4d80327 90 }
niv17 0:2076b4d80327 91 void Mixer3D::convolution(Complex *input, Complex *filter, Complex *output, long nSig, long nFil, long nFFT) {
niv17 0:2076b4d80327 92
niv17 0:2076b4d80327 93 if (input == NULL || filter == NULL) {
niv17 0:2076b4d80327 94 throw invalid_argument("Input and Filter must be non-NULL.");
niv17 0:2076b4d80327 95 }
niv17 0:2076b4d80327 96
niv17 0:2076b4d80327 97 if (!isPowerOfTwo(nFFT) || nFFT < nSig || nFFT < nFil) {
niv17 0:2076b4d80327 98 throw invalid_argument("NFFT must be a power of two, bigger than the signal length, and bigger than the filter length.");
niv17 0:2076b4d80327 99 }
niv17 0:2076b4d80327 100
niv17 0:2076b4d80327 101 // Perform FFT on both input and filter.
niv17 0:2076b4d80327 102 // TODO: "Streamline" CFFT class?
niv17 0:2076b4d80327 103 CFFT::Forward(input, fInput, (unsigned int)nFFT);
niv17 0:2076b4d80327 104 CFFT::Forward(filter, fFilter, (unsigned int)nFFT);
niv17 0:2076b4d80327 105
niv17 0:2076b4d80327 106 for (int i = 0; i < nFFT; i++) {
niv17 0:2076b4d80327 107 output[i] = fInput[i] * fFilter[i];
niv17 0:2076b4d80327 108 }
niv17 0:2076b4d80327 109 CFFT::Inverse(output, (unsigned int)nFFT);
niv17 0:2076b4d80327 110 }
niv17 0:2076b4d80327 111
niv17 0:2076b4d80327 112 void Mixer3D::stereoConvolution(Complex *input, Complex *leftFilter, Complex *rightFilter, Complex *leftOutput, Complex *rightOutput, long nSIG, long nFIL, long nFFT)
niv17 0:2076b4d80327 113 {
niv17 0:2076b4d80327 114 // TODO: Modify parameter name, input is a data member
niv17 0:2076b4d80327 115 convolution(input, leftFilter, leftOutput, nSIG, nFIL, nFFT);
niv17 0:2076b4d80327 116 convolution(input, rightFilter, rightOutput, nSIG, nFIL, nFFT);
niv17 0:2076b4d80327 117 }
niv17 0:2076b4d80327 118
niv17 0:2076b4d80327 119 void Mixer3D::computeValidAudioObjects(vector<AudioObj*> &validAudioObjectsOut) {
niv17 0:2076b4d80327 120 AudioObj* iAudioObj;
niv17 0:2076b4d80327 121 float iDistance;
niv17 0:2076b4d80327 122 Location iLocation;
niv17 0:2076b4d80327 123 for (int i=0; i < myWorld->getNumObj(); i++) {
niv17 0:2076b4d80327 124 iAudioObj = myWorld->getAudioObj(i);
niv17 0:2076b4d80327 125 if (iAudioObj->isActive()) {
niv17 0:2076b4d80327 126 iLocation = iAudioObj->getLocation();
niv17 0:2076b4d80327 127 if (iAudioObj->isGpsObject()) {
niv17 0:2076b4d80327 128 if (player.isTrackingGps()) {
niv17 0:2076b4d80327 129 iDistance = player.computeDistanceFromGpsCoordinates(&iLocation);
niv17 0:2076b4d80327 130 } else {
niv17 0:2076b4d80327 131 // not tracking gps, so skip
niv17 0:2076b4d80327 132 continue;
niv17 0:2076b4d80327 133 }
niv17 0:2076b4d80327 134 } else {
niv17 0:2076b4d80327 135 iDistance = player.computeDistanceFrom(&iLocation);
niv17 0:2076b4d80327 136 }
niv17 0:2076b4d80327 137
niv17 0:2076b4d80327 138 if (iDistance < MAX_GPS_OBJ_DISTANCE) {
niv17 0:2076b4d80327 139 validAudioObjectsOut.push_back(iAudioObj);
niv17 0:2076b4d80327 140 }
niv17 0:2076b4d80327 141 }
niv17 0:2076b4d80327 142 }
niv17 0:2076b4d80327 143 }
niv17 0:2076b4d80327 144
niv17 0:2076b4d80327 145 void Mixer3D::performMix(short *ioDataLeft, short *ioDataRight)
niv17 0:2076b4d80327 146 {
niv17 0:2076b4d80327 147 double runtime;
niv17 0:2076b4d80327 148 struct timeval start, finish;
niv17 0:2076b4d80327 149 gettimeofday(&start, NULL);
niv17 0:2076b4d80327 150 //Zero the output arrays.
niv17 0:2076b4d80327 151 for(int i = 0; i < bufferSize; i++)
niv17 0:2076b4d80327 152 {
niv17 0:2076b4d80327 153 ioDataLeft[i] = 0;
niv17 0:2076b4d80327 154 ioDataRight[i] = 0;
niv17 0:2076b4d80327 155 }
niv17 0:2076b4d80327 156
niv17 0:2076b4d80327 157 //Iterate through all audio objects, obtain input data and calculate resulting audio data for each object.
niv17 0:2076b4d80327 158 AudioObj* iAudioObj;
niv17 0:2076b4d80327 159 Location iLocation;
niv17 0:2076b4d80327 160 float iVolume, iDistance, iAmplitudeFactor;
niv17 0:2076b4d80327 161 vector<AudioObj*> validAudioObjects;
niv17 0:2076b4d80327 162
niv17 0:2076b4d80327 163 // get valid audio objects
niv17 0:2076b4d80327 164 computeValidAudioObjects(validAudioObjects);
niv17 0:2076b4d80327 165 int nValidAudioObjects = (int)validAudioObjects.size();
niv17 0:2076b4d80327 166 for (int i = 0; i < nValidAudioObjects; i++) {
niv17 0:2076b4d80327 167 iAudioObj = validAudioObjects[i];
niv17 0:2076b4d80327 168
niv17 0:2076b4d80327 169 // loading in input data for the iteration accordingly
niv17 0:2076b4d80327 170 if (!(iAudioObj->fillAudioData(inputAO, bufferSize))) {
niv17 0:2076b4d80327 171 cout << "Audio Object" << i << " not loading input quickly enough" << endl;
niv17 0:2076b4d80327 172 continue;
niv17 0:2076b4d80327 173 }
niv17 0:2076b4d80327 174
niv17 0:2076b4d80327 175 iVolume = iAudioObj->getVolume();
niv17 0:2076b4d80327 176 if (iVolume == 0) {
niv17 0:2076b4d80327 177 // skip 'muted' audio objects, but not after loading their input.
niv17 0:2076b4d80327 178 // that way they are still playing, but silently.
niv17 0:2076b4d80327 179 continue;
niv17 0:2076b4d80327 180 }
niv17 0:2076b4d80327 181
niv17 0:2076b4d80327 182 // handle background objects
niv17 0:2076b4d80327 183 if (iAudioObj->isBackgroundObject()) {
niv17 0:2076b4d80327 184 // copy input directly to output buffers
niv17 0:2076b4d80327 185 for (int j=0; j < bufferSize; j++) {
niv17 0:2076b4d80327 186 // scale input from [-1, 1] to proper scale depending on bit depth
niv17 0:2076b4d80327 187 ioDataLeft[j] += inputAO[j].real()*iVolume*pow(2, bitDepth) / nValidAudioObjects;
niv17 0:2076b4d80327 188 ioDataRight[j] += inputAO[j].real()*iVolume*pow(2, bitDepth) / nValidAudioObjects;
niv17 0:2076b4d80327 189 }
niv17 0:2076b4d80327 190 continue;
niv17 0:2076b4d80327 191 }
niv17 0:2076b4d80327 192
niv17 0:2076b4d80327 193 iLocation = iAudioObj->getLocation();
niv17 0:2076b4d80327 194
niv17 0:2076b4d80327 195 // handle gps objects
niv17 0:2076b4d80327 196 if (iAudioObj->isGpsObject()) {
niv17 0:2076b4d80327 197 if (player.isTrackingGps()) {
niv17 0:2076b4d80327 198 iDistance = player.computeDistanceFromGpsCoordinates(&iLocation);
niv17 0:2076b4d80327 199 if (iDistance > MAX_GPS_OBJ_DISTANCE) {
niv17 0:2076b4d80327 200 // not within range, skip this audio object
niv17 0:2076b4d80327 201 continue;
niv17 0:2076b4d80327 202 }
niv17 0:2076b4d80327 203 } else {
niv17 0:2076b4d80327 204 // gps tracking hasn't started yet
niv17 0:2076b4d80327 205 continue;
niv17 0:2076b4d80327 206 }
niv17 0:2076b4d80327 207 } else {
niv17 0:2076b4d80327 208 iDistance = player.computeDistanceFrom(&iLocation);
niv17 0:2076b4d80327 209 }
niv17 0:2076b4d80327 210
niv17 0:2076b4d80327 211 if (iDistance == 0) {
niv17 0:2076b4d80327 212 iAmplitudeFactor = iVolume;
niv17 0:2076b4d80327 213 } else {
niv17 0:2076b4d80327 214 // ensure that the amplitude factor can never be > 1 to prevent clipping
niv17 0:2076b4d80327 215 if (iDistance > 1) {
niv17 0:2076b4d80327 216 iAmplitudeFactor = iVolume / pow(iDistance, 2);
niv17 0:2076b4d80327 217 } else {
niv17 0:2076b4d80327 218 iAmplitudeFactor = iVolume;
niv17 0:2076b4d80327 219 }
niv17 0:2076b4d80327 220 }
niv17 0:2076b4d80327 221
niv17 0:2076b4d80327 222 // dynamically calculate the Azimuth and Elevation between every object and the player
niv17 0:2076b4d80327 223 updateAngles();
niv17 0:2076b4d80327 224
niv17 0:2076b4d80327 225 for(int j = 0; j < bufferSize * 2; j++) {
niv17 0:2076b4d80327 226 if ( j >= bufferSize ) {
niv17 0:2076b4d80327 227 // zero pad
niv17 0:2076b4d80327 228 inputAO[j] = 0;
niv17 0:2076b4d80327 229 } else {
niv17 0:2076b4d80327 230 inputAO[j] *= iAmplitudeFactor;
niv17 0:2076b4d80327 231 }
niv17 0:2076b4d80327 232 }
niv17 0:2076b4d80327 233
niv17 0:2076b4d80327 234 if (azimuths[i] != prevAzimuths[i] ||
niv17 0:2076b4d80327 235 elevations[i] != prevElevations[i]) {
niv17 0:2076b4d80327 236 // object location relative to player has changed, so fetch a new filter
niv17 0:2076b4d80327 237 filterLength = loadHRTF(&azimuths[i], &elevations[i], sampleRate, 1, complexLeftFilter[i], complexRightFilter[i]);
niv17 0:2076b4d80327 238
niv17 0:2076b4d80327 239 // zero pad
niv17 0:2076b4d80327 240 for (int j = filterLength; j < 2 * bufferSize; j++) {
niv17 0:2076b4d80327 241 complexLeftFilter[i][j] = 0;
niv17 0:2076b4d80327 242 complexRightFilter[i][j] = 0;
niv17 0:2076b4d80327 243 }
niv17 0:2076b4d80327 244
niv17 0:2076b4d80327 245 if (azimuths[i] < 0) {
niv17 0:2076b4d80327 246 azimuths[i] = -azimuths[i];
niv17 0:2076b4d80327 247 }
niv17 0:2076b4d80327 248
niv17 0:2076b4d80327 249
niv17 0:2076b4d80327 250 //Since the filter changed, we perform a convolution on the old input with the new filter and pull out its tail.
niv17 0:2076b4d80327 251 stereoConvolution(overlapInput, complexLeftFilter[i], complexRightFilter[i], outputLeft[i], outputRight[i], bufferSize, filterLength, 2 * bufferSize);
niv17 0:2076b4d80327 252
niv17 0:2076b4d80327 253 // update the overlap part for the next iteration
niv17 0:2076b4d80327 254 for (int j = 0; j < bufferSize; j++) {
niv17 0:2076b4d80327 255 overlapLeft[i][j] = outputLeft[i][j + bufferSize];
niv17 0:2076b4d80327 256 overlapRight[i][j] = outputRight[i][j + bufferSize];
niv17 0:2076b4d80327 257 }
niv17 0:2076b4d80327 258 }
niv17 0:2076b4d80327 259
niv17 0:2076b4d80327 260 //Perform the convolution of the current input and current filter.
niv17 0:2076b4d80327 261 stereoConvolution(inputAO, complexLeftFilter[i], complexRightFilter[i], outputLeft[i], outputRight[i], bufferSize, filterLength, 2 * bufferSize);
niv17 0:2076b4d80327 262
niv17 0:2076b4d80327 263 //Output the data to the output arrays in short integer format. In addition to pushing the output of the main
niv17 0:2076b4d80327 264 //convolution, we also need to add the overlapped tail of the last output and divide by 2. Finally, we need
niv17 0:2076b4d80327 265 //to divide by the number of audio objects to ensure no clipping.
niv17 0:2076b4d80327 266 for (int j = 0; j < bufferSize; j++)
niv17 0:2076b4d80327 267 {
niv17 0:2076b4d80327 268 ioDataLeft[j] += (short)( (outputLeft[i][j].real() + overlapLeft[i][j].real()) / (2*nValidAudioObjects));
niv17 0:2076b4d80327 269 ioDataRight[j] += (short)( (outputRight[i][j].real() + overlapRight[i][j].real()) / (2*nValidAudioObjects));
niv17 0:2076b4d80327 270 }
niv17 0:2076b4d80327 271
niv17 0:2076b4d80327 272 //Updating the overlapInput for the next iteration for the correpsonding object
niv17 0:2076b4d80327 273 for (int j = 0; j < bufferSize * 2; j++) {
niv17 0:2076b4d80327 274 if (j >= bufferSize) {
niv17 0:2076b4d80327 275 overlapInput[j] = 0;
niv17 0:2076b4d80327 276 } else {
niv17 0:2076b4d80327 277 overlapInput[j] = inputAO[j];
niv17 0:2076b4d80327 278 }
niv17 0:2076b4d80327 279 }
niv17 0:2076b4d80327 280
niv17 0:2076b4d80327 281 // TODO: If the filter has been changed, didn't we already do this?
niv17 0:2076b4d80327 282 //Updating the default overlap information for the next iteration if the filter won't be changed
niv17 0:2076b4d80327 283 for (int j = 0 ; j < bufferSize; j++) {
niv17 0:2076b4d80327 284 overlapLeft[i][j] = outputLeft[i][j + bufferSize];
niv17 0:2076b4d80327 285 overlapRight[i][j] = outputRight[i][j + bufferSize];
niv17 0:2076b4d80327 286 }
niv17 0:2076b4d80327 287
niv17 0:2076b4d80327 288 //storing the Azimuth value in this iteration for the comparison for the next iteration so that
niv17 0:2076b4d80327 289 //we can know that whether the filter needs to be changed in the next iteration.
niv17 0:2076b4d80327 290 prevAzimuths[i] = azimuths[i];
niv17 0:2076b4d80327 291 prevElevations[i] = elevations[i];
niv17 0:2076b4d80327 292 }
niv17 0:2076b4d80327 293 gettimeofday(&finish, NULL);
niv17 0:2076b4d80327 294 runtime = finish.tv_sec - start.tv_sec;
niv17 0:2076b4d80327 295 runtime += (finish.tv_usec - start.tv_usec) / 1000000.0;
niv17 0:2076b4d80327 296
niv17 0:2076b4d80327 297 cout << "performMix: " << runtime << endl;
niv17 0:2076b4d80327 298 }