ese 519

Dependents:   PROJECT_3D_AUDIO

Revision:
0:2076b4d80327
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Mixer3D.cpp	Tue Apr 07 21:09:22 2015 +0000
@@ -0,0 +1,298 @@
+#include <math.h>
+#include "Mixer3D.h"
+//#include "World.h"
+#include "fft.h"
+#include "mit_hrtf_lib.h"
+#include <sys/time.h>
+
+Mixer3D::Mixer3D(int bufSize, int smpRate, int bitD) :
+bufferSize(bufSize), sampleRate(smpRate), bitDepth(bitD))
+{
+    cout << "BUF_SIZE: " << bufSize << endl;
+    cout << "SAMPLE_RATE: " << smpRate << endl;
+    cout << "BIT_DEPTH: " << bitD << endl;
+    inputAO = new Complex[2*bufferSize];
+    overlapInput = new Complex[2 * bufferSize];
+    fInput = new Complex[2 * bufferSize];
+    fFilter = new Complex[2 * bufferSize];
+    
+    azimuths = new int[World::MAX_OBJ];
+    elevations = new int[World::MAX_OBJ];
+    prevAzimuths = new int[World::MAX_OBJ];
+    prevElevations = new int[World::MAX_OBJ];
+
+    updateAngles(); // initialize azimuths and elevations
+
+    outputLeft = new Complex*[World::MAX_OBJ];
+    outputRight = new Complex*[World::MAX_OBJ];
+    overlapLeft = new Complex*[World::MAX_OBJ];
+    overlapRight = new Complex*[World::MAX_OBJ];
+
+    leftFilter = new short[2 * bufferSize];
+    rightFilter = new short[2 * bufferSize];
+   
+      
+}
+
+Mixer3D::~Mixer3D()
+{
+    delete [] inputAO;
+    delete [] outputLeft;
+    delete [] outputLeft;
+    delete [] leftFilter;
+    delete [] rightFilter;
+    delete [] complexLeftFilter;
+    delete [] complexRightFilter;
+    delete [] overlapLeft;
+    delete [] overlapRight;
+    delete [] overlapInput;
+    delete [] prevAzimuths;
+    delete [] prevElevations;
+    delete [] azimuths;
+    delete [] elevations;
+}
+
+void Mixer3D::updateAngles() {
+    AudioObj* iAudioObj;
+    Location iLocation;
+    for (int i = 0; i < myWorld->getNumObj(); i++) {
+        iAudioObj = myWorld->getAudioObj(i);
+        iLocation = iAudioObj->getLocation();
+        
+        if (iAudioObj->isGpsObject()) {
+            azimuths[i] = player.computeAzimuthFromGpsCoordinates(&iLocation);
+            elevations[i] = player.computeElevationFromGpsCoordinates(&iLocation);
+        } else {
+            azimuths[i] = player.computeAzimuth(&iLocation);
+            elevations[i] = player.computeElevation(&iLocation);
+        }
+    }
+}
+
+bool Mixer3D::isPowerOfTwo(int x) {
+    return !(x == 0) && !(x & (x - 1));
+}
+
+int Mixer3D::loadHRTF(int* pAzimuth, int* pElevation, unsigned int samplerate, unsigned int diffused, Complex *&leftFilterIn, Complex *&rightFilterIn)
+{
+    int size = mit_hrtf_get(pAzimuth, pElevation, samplerate, diffused, leftFilter, rightFilter);
+
+    if (size == 0) {
+        // TODO: Throw MIT HRTF filter does not exist exception
+    }
+    for (int i = 0; i < size; i++)
+    {
+        leftFilterIn[i] = (double)(leftFilter[i]);
+        rightFilterIn[i] = (double)(rightFilter[i]);
+    }
+
+    return size;
+}
+void Mixer3D::convolution(Complex *input, Complex *filter, Complex *output, long nSig, long nFil, long nFFT) {
+
+	if (input == NULL || filter == NULL) {
+		throw invalid_argument("Input and Filter must be non-NULL.");
+	}
+
+	if (!isPowerOfTwo(nFFT) || nFFT < nSig || nFFT < nFil) {
+        throw invalid_argument("NFFT must be a power of two, bigger than the signal length, and bigger than the filter length.");
+	}
+
+	// Perform FFT on both input and filter.
+    // TODO: "Streamline" CFFT class?
+	CFFT::Forward(input, fInput, (unsigned int)nFFT);
+	CFFT::Forward(filter, fFilter, (unsigned int)nFFT);
+
+	for (int i = 0; i < nFFT; i++) {
+		output[i] = fInput[i] * fFilter[i];
+    }
+	CFFT::Inverse(output, (unsigned int)nFFT);
+}
+
+void Mixer3D::stereoConvolution(Complex *input, Complex *leftFilter, Complex *rightFilter, Complex *leftOutput, Complex *rightOutput, long nSIG, long nFIL, long nFFT)
+{
+    // TODO: Modify parameter name, input is a data member
+    convolution(input, leftFilter, leftOutput, nSIG, nFIL, nFFT);
+    convolution(input, rightFilter, rightOutput, nSIG, nFIL, nFFT);
+}
+
+void Mixer3D::computeValidAudioObjects(vector<AudioObj*> &validAudioObjectsOut) {
+    AudioObj* iAudioObj;
+    float iDistance;
+    Location iLocation;
+    for (int i=0; i < myWorld->getNumObj(); i++) {
+        iAudioObj = myWorld->getAudioObj(i);
+        if (iAudioObj->isActive()) {
+            iLocation = iAudioObj->getLocation();
+            if (iAudioObj->isGpsObject()) {
+                if (player.isTrackingGps()) {
+                    iDistance = player.computeDistanceFromGpsCoordinates(&iLocation);
+                } else {
+                    // not tracking gps, so skip
+                    continue;
+                }
+            } else {
+                iDistance = player.computeDistanceFrom(&iLocation);
+            }
+            
+            if (iDistance < MAX_GPS_OBJ_DISTANCE) {
+                validAudioObjectsOut.push_back(iAudioObj);
+            }
+        }
+    }
+}
+
+void Mixer3D::performMix(short *ioDataLeft, short *ioDataRight)
+{
+    double runtime;
+    struct timeval start, finish;
+    gettimeofday(&start, NULL);
+    //Zero the output arrays.
+    for(int i = 0; i < bufferSize; i++)
+    {
+       ioDataLeft[i] = 0;
+       ioDataRight[i] = 0;
+    }
+    
+    //Iterate through all audio objects, obtain input data and calculate resulting audio data for each object.
+    AudioObj* iAudioObj;
+    Location iLocation;
+    float iVolume, iDistance, iAmplitudeFactor;
+    vector<AudioObj*> validAudioObjects;
+    
+    // get valid audio objects
+    computeValidAudioObjects(validAudioObjects);
+    int nValidAudioObjects = (int)validAudioObjects.size();
+    for (int i = 0; i < nValidAudioObjects; i++) {
+        iAudioObj = validAudioObjects[i];
+        
+        // loading in input data for the iteration accordingly
+        if (!(iAudioObj->fillAudioData(inputAO, bufferSize))) {
+            cout << "Audio Object" << i << " not loading input quickly enough" << endl;
+            continue;
+        }
+        
+        iVolume = iAudioObj->getVolume();
+        if (iVolume == 0) {
+            // skip 'muted' audio objects, but not after loading their input.
+            // that way they are still playing, but silently.
+            continue;
+        }
+        
+        // handle background objects
+        if (iAudioObj->isBackgroundObject()) {
+            // copy input directly to output buffers
+            for (int j=0; j < bufferSize; j++) {
+                // scale input from [-1, 1] to proper scale depending on bit depth
+                ioDataLeft[j] += inputAO[j].real()*iVolume*pow(2, bitDepth) / nValidAudioObjects;
+                ioDataRight[j] += inputAO[j].real()*iVolume*pow(2, bitDepth) / nValidAudioObjects;
+            }
+            continue;
+        }
+        
+        iLocation = iAudioObj->getLocation();
+        
+        // handle gps objects
+        if (iAudioObj->isGpsObject()) {
+            if (player.isTrackingGps()) {
+                iDistance = player.computeDistanceFromGpsCoordinates(&iLocation);
+                if (iDistance > MAX_GPS_OBJ_DISTANCE) {
+                    // not within range, skip this audio object
+                    continue;
+                }
+            } else {
+                // gps tracking hasn't started yet
+                continue;
+            }
+        } else {
+            iDistance = player.computeDistanceFrom(&iLocation);
+        }
+
+        if (iDistance == 0) {
+            iAmplitudeFactor = iVolume;
+        } else {
+            // ensure that the amplitude factor can never be > 1 to prevent clipping
+            if (iDistance > 1) {
+                iAmplitudeFactor = iVolume / pow(iDistance, 2);
+            } else {
+                iAmplitudeFactor = iVolume;
+            }
+        }
+        
+        // dynamically calculate the Azimuth and Elevation between every object and the player
+        updateAngles();
+        
+        for(int j = 0; j < bufferSize * 2; j++) {
+            if ( j >= bufferSize ) {
+                // zero pad
+                inputAO[j] = 0;
+            } else {
+                inputAO[j] *= iAmplitudeFactor;
+            }
+        }
+       
+        if (azimuths[i] != prevAzimuths[i] ||
+           elevations[i] != prevElevations[i]) {
+            // object location relative to player has changed, so fetch a new filter
+            filterLength = loadHRTF(&azimuths[i], &elevations[i], sampleRate, 1, complexLeftFilter[i], complexRightFilter[i]);
+            
+            // zero pad
+            for (int j = filterLength; j < 2 * bufferSize; j++) {
+                complexLeftFilter[i][j] = 0;
+                complexRightFilter[i][j] = 0;
+            }
+            
+            if (azimuths[i] < 0) {
+                   azimuths[i] = -azimuths[i];
+            }
+            
+            
+            //Since the filter changed, we perform a convolution on the old input with the new filter and pull out its tail.
+            stereoConvolution(overlapInput, complexLeftFilter[i], complexRightFilter[i], outputLeft[i], outputRight[i], bufferSize, filterLength, 2 * bufferSize);
+        
+            // update the overlap part for the next iteration
+            for (int j = 0; j < bufferSize; j++) {
+                    overlapLeft[i][j] = outputLeft[i][j + bufferSize];
+                    overlapRight[i][j] = outputRight[i][j + bufferSize];
+            }
+        }
+        
+        //Perform the convolution of the current input and current filter.
+        stereoConvolution(inputAO, complexLeftFilter[i], complexRightFilter[i], outputLeft[i], outputRight[i], bufferSize, filterLength, 2 * bufferSize);
+  
+        //Output the data to the output arrays in short integer format. In addition to pushing the output of the main
+        //convolution, we also need to add the overlapped tail of the last output and divide by 2. Finally, we need
+        //to divide by the number of audio objects to ensure no clipping.
+        for (int j = 0; j < bufferSize; j++)
+        {
+            ioDataLeft[j]  += (short)( (outputLeft[i][j].real() + overlapLeft[i][j].real()) / (2*nValidAudioObjects));
+            ioDataRight[j] += (short)( (outputRight[i][j].real() + overlapRight[i][j].real()) / (2*nValidAudioObjects));
+        }
+        
+        //Updating the overlapInput for the next iteration for the correpsonding object
+        for (int j = 0; j < bufferSize * 2; j++) {
+            if (j >= bufferSize) {
+                overlapInput[j] = 0;
+            } else {
+                overlapInput[j] = inputAO[j];
+            }
+        }
+
+        // TODO: If the filter has been changed, didn't we already do this?
+        //Updating the default overlap information for the next iteration if the filter won't be changed
+        for (int j = 0 ; j < bufferSize; j++) {
+            overlapLeft[i][j] = outputLeft[i][j + bufferSize];
+            overlapRight[i][j] = outputRight[i][j + bufferSize];
+        }
+    
+        //storing the Azimuth value in this iteration for the comparison for the next iteration so that
+        //we can know that whether the filter needs to be changed in the next iteration.
+        prevAzimuths[i] = azimuths[i];
+        prevElevations[i] = elevations[i];
+    }
+    gettimeofday(&finish, NULL);
+    runtime = finish.tv_sec - start.tv_sec;
+    runtime += (finish.tv_usec - start.tv_usec) / 1000000.0;
+    
+    cout << "performMix: " << runtime << endl;
+}