package ut.ewh.audiometrytest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Build;
import android.provider.MediaStore;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
public class Calibration extends ActionBarActivity {
final private int sampleRate = 44100;
final private int numSamples = 4 * sampleRate;
final private int frequencies[] = {500, 1000, 3000, 4000, 6000, 8000};
final private int volume = 30000;
final private double mGain = 0.0044;
final private double mAlpha = 0.9;
final private int bufferSize = AudioRecord.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
double[] inputSignalImaginary = new double[2048];
final private double[] dbHLCorrectionCoefficients = {13.5, 7.5, 11.5, 12, 16, 15.5}; //based off of ANSI Standards
public double calibrationArray[] = new double[frequencies.length];
public static boolean running = true;
public void gotoCalibrationComplete(){
Intent intent = new Intent(this, CalibrationComplete.class);
startActivity(intent);
}
public byte[] genTone(float increment, int volume) {
float angle = 0;
double sample[] = new double[numSamples];
byte generatedSnd[] = new byte[2 * numSamples];
for (int i = 0; i < numSamples; i++) {
sample[i] = Math.sin(angle);
angle += increment;
}
int idx = 0;
for (final double dVal : sample) {
final short val = (short) ((dVal * volume));
//volume controlled by the value multiplied by dVal; max value is 32767
generatedSnd[idx++] = (byte) (val & 0x00ff);
generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
}
return generatedSnd;
}
public AudioTrack playSound(byte[] generatedSnd) {
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, generatedSnd.length, AudioTrack.MODE_STATIC);
audioTrack.write(generatedSnd, 0, generatedSnd.length);
//audioTrack.play();
return audioTrack;
}
public int newBitReverse(int j){
int b = 0;
while (j!=0){
b<<=1;
b|=( j &1);
j>>=1;
}
return b;
}
public double[] fftAnalysis(double[] inputReal, double[] inputImag){
int n = 2048;
int nu = 11;
double[] bufferReal = new double[n];
double[] shortenedReal = new double[n];
//shorten buffer data to a power of two
for (int s = 0; s < n; s++){
shortenedReal[s] = inputReal[s];
}
// Computing the coefficients for everything ahead of time
double[][] Real = new double[nu+1][n];
int counter = 2;
for (int l = 1; l <= nu; l++){
for(int i = 0; i<n; i++){
Real[l][i] = Math.cos(((double)2)*Math.PI*((double)i)/((double)counter));
}
counter *= 2;
}
double[][] Imag = new double[nu+1][n];
counter = 2;
for (int l = 1; l <= nu; l++){
for(int i = 0; i<n; i++){
Imag[l][i] = -1*Math.sin(((double)2)*Math.PI*((double)i)/((double)counter));
}
counter *= 2;
}
// Populate bufferReal with inputReal in bit-reversed order
for (int x = 0; x < shortenedReal.length; x ++){
int p = newBitReverse(x);
bufferReal[x] = shortenedReal[p];
//Log.i("Check", "bufferReal: " + bufferReal[x] + " shortenedReal: " + shortenedReal[p]);
}
// begin transform
int step = 1;
for (int level = 1; level <= nu; level ++){
int increm = step * 2;
for (int j = 0; j < step; j++){
for(int i = j; i < n; i += increm){
double realCoefficient = Real[level][j];
double imagCoefficient = Imag[level][j];
realCoefficient *= bufferReal[i+step];
imagCoefficient *= bufferReal[i+step];
bufferReal[i+step] = bufferReal[i];
inputImag[i+step] = inputImag[i];
bufferReal[i+step] -= realCoefficient;
inputImag[i+step] -= imagCoefficient;
bufferReal[i] += realCoefficient;
inputImag[i] += imagCoefficient;
}
}
step *= 2;
}
double[] transformResult = new double[bufferReal.length];
// Calculate magnitude of FFT coefficients
for (int q = 0; q < bufferReal.length; q++){
transformResult[q] = Math.sqrt(Math.pow(bufferReal[q], 2) + Math.pow(inputImag[q], 2));
}
return transformResult;
}
public double[] dbListen(int frequency) {
double rmsArray[] = new double[5];
for (int j = 0; j < rmsArray.length; j++) {
AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
short[] buffer = new short[bufferSize];
try {
audioRecord.startRecording();
} catch (IllegalStateException e) {
}
int bufferReadResult = audioRecord.read(buffer, 0, buffer.length);
//Convert buffer from type short[] to double[]
double[] inputSignal = new double[buffer.length];
for(int x=0;x<buffer.length; x++){
inputSignal[x] = (double)buffer[x];
}
double[] outputSignal = fftAnalysis(inputSignal, inputSignalImaginary);
int k = frequency*2048/sampleRate; // Selects the value from the transform array corresponding to the desired frequency
rmsArray[j] = outputSignal[k];
// RMS Routine
double rms = 0;
for (int i = 0; i < buffer.length; i++) {
rms += buffer[i] * buffer[i];
}
// //smoothing of rms
//double mRmsSmoothed = (1 - mAlpha) * rms;
// RMS Decibel Calculation
// FFT Decibel Calculation
audioRecord.stop();
audioRecord.release();
}
//Log.i("Array Check", "rmsArray: " + rmsArray[0] + " " + rmsArray[1] + " " + rmsArray[2] + " " + rmsArray[3] + " " + rmsArray[4]);
return rmsArray;
}
static void stopThread(){
running = false;
}
public class calibrateThread extends Thread {
public void run() {
running = true;
for (int i = 0; i < frequencies.length; i++) {
int frequency = frequencies[i];
final float increment = (float) (Math.PI) * frequency / sampleRate;
AudioTrack audioTrack = playSound(genTone(increment, volume));
if (!running){
return;
}
double backgroundRms[] = dbListen(frequency);
audioTrack.play();
double soundRms[] = dbListen(frequency);
double resultingRms[] = new double[5];
double resultingdB[] = new double[5];
for(int x = 0; x < resultingRms.length; x++){
resultingRms[x] = soundRms[x]/backgroundRms[x];
resultingdB[x] = 20 * Math.log10(resultingRms[x]) + 70;
resultingdB[x] -= dbHLCorrectionCoefficients[i];
Log.i("FFT Decibel", "Reading "+ resultingdB[x]);
}
double rmsSum = 0;
int numCounter = 0;
for (int j = 0; j < 5; j++) {
if (resultingRms[j] > 0) {
rmsSum += resultingRms[j];
numCounter ++;
} else {
}
}
double dBAverage = 0;
for(int q = 1; q < resultingdB.length; q++){
dBAverage += resultingdB[q];
}
dBAverage /= (resultingdB.length - 1);
calibrationArray[i] = dBAverage / volume; // create ratio of dB/binary. Will be used in testProctoring for final conversion.
if (!running){
return;
}
try{
Thread.sleep(1000);
} catch (InterruptedException e) {};
audioTrack.release();
}
int counter = 0;
byte calibrationByteArray[] = new byte[calibrationArray.length * 8];
for (int x = 0; x < calibrationArray.length; x++){
byte tmpByteArray[] = new byte[8];
ByteBuffer.wrap(tmpByteArray).putDouble(calibrationArray[x]);
for (int j = 0; j < 8; j++){
calibrationByteArray[counter] = tmpByteArray[j];
counter++;
}
}
try{
FileOutputStream fos = openFileOutput("CalibrationPreferences", Context.MODE_PRIVATE);
try{
fos.write(calibrationByteArray);
fos.close();
} catch (IOException q) {}
} catch (FileNotFoundException e) {
}
gotoCalibrationComplete();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_calibration);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
getWindow().setStatusBarColor(getResources().getColor(R.color.primary_dark));
}
AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE);
am.setStreamVolume(AudioManager.STREAM_MUSIC, 9 , 0);
Thread runningThread = new Thread(new Runnable() {
public void run() {
final calibrateThread calibrateThread = new Calibration.calibrateThread();
calibrateThread.run();
}
});
runningThread.start();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.calibration, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onStop(){
super.onStop();
stopThread();
}
}