1

I am creating a tuner for Android (similar to a guitar tuner) and I am wondering how to allow the tuner to run continuously (for a couple minutes or so). I don't want it to be a service that runs in the background, just while the user is in my app.

I have successfully used the AudioRecord class and am obtaining data that seems correct. I am in the process of filtering this data and finding the fundamental frequency of the input signal, but need help figuring out how to allow my tuner to run continuously.

This is what my code looks like so far:

import android.app.Activity;
import android.graphics.Color;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.dustin.tuner2.FFT;
import com.dustin.tuner2.Complex;


public class Tuner2 extends Activity implements OnClickListener {
    Button btnTune;
    TextView fft;
    TextView freq;
    TextView results;
    MediaRecorder recorder;
    AudioRecord tuner;
    boolean startTuning = true;
    int audioSource = MediaRecorder.AudioSource.MIC;
    int sampleRateInHz = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_SYSTEM);
    int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
    int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    int bufferSizeInBytes;
    int samples;
    short[] audioBuffer;
    short[] audioData;
    double[] temp;
    String fileName;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        btnTune = (Button)findViewById(R.id.btnTune);
        freq = (TextView)findViewById(R.id.freq);
        btnTune.setOnClickListener(this);
        bufferSizeInBytes = 4096;
        //bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
        results = (TextView)findViewById(R.id.results);
        fft = (TextView)findViewById(R.id.fft);
    }

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub

        if (v == btnTune)
        {
            onTune(startTuning);
            if (startTuning) {
                ((Button)v).setText("Stop Tuning");
            }
            else {
                ((Button)v).setText("Start Tuninig");
            }
            startTuning = !startTuning;
        }
    }

    //------------------------------------------------------------>
    private void onTune(boolean start) {
        if(start) {
            startTuning();
        } else {
            Toast.makeText(getApplicationContext(), "Tuning Stopped", Toast.LENGTH_SHORT).show();
            tuner.stop();
        }
    }

    private void startTuning()
    {
        tuner = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);

        audioData = new short[bufferSizeInBytes];
        trigger();
    }

    public void trigger(){
        acquire();
        computeFFT();
        display();
    }

    public void acquire(){
        try {
            tuner.startRecording();
            samples = tuner.read(audioData, 0, bufferSizeInBytes);
        }
        catch (Throwable t){

        }   
    }

    public void computeFFT(){
        //Conversion from short to double
        double[] micBufferData = new double[bufferSizeInBytes];//size may need to change
        final int bytesPerSample = 2; // As it is 16bit PCM
        final double amplification = 100.0; // choose a number as you like
        for (int index = 0, floatIndex = 0; index < bufferSizeInBytes - bytesPerSample + 1; index += bytesPerSample, floatIndex++) {
            double sample = 0;
            for (int b = 0; b < bytesPerSample; b++) {
                int v = audioData[index + b];
                if (b < bytesPerSample - 1 || bytesPerSample == 1) {
                    v &= 0xFF;
                }
                sample += v << (b * 8);
            }
            double sample32 = amplification * (sample / 32768.0);
            micBufferData[floatIndex] = sample32;
        }

        //Create Complex array for use in FFT
        Complex[] fftTempArray = new Complex[bufferSizeInBytes];
        for (int i=0; i<bufferSizeInBytes; i++)
        {
            fftTempArray[i] = new Complex(micBufferData[i], 0);
        }

        //Obtain array of FFT data
        final Complex[] fftArray = FFT.fft(fftTempArray);
        final Complex[] fftInverse = FFT.ifft(fftTempArray);

        //Create an array of magnitude of fftArray
        double[] magnitude = new double[fftArray.length];
        for (int i=0; i<fftArray.length; i++){
            magnitude[i]= fftArray[i].abs();
        }

        fft.setTextColor(Color.GREEN);
        fft.setText("fftArray is "+ fftArray[500] +" and fftTempArray is "+fftTempArray[500] + " and fftInverse is "+fftInverse[500]+" and audioData is "+audioData[500]+ " and magnitude is "+ magnitude[1] + ", "+magnitude[500]+", "+magnitude[1000]+" You rock dude!");
        for(int i = 2; i < samples; i++){
            fft.append(" " + magnitude[i] + " Hz");
        }
    }

    public void display(){
        results.setTextColor(Color.BLUE);
        results.setText(audioData[1]+"");
        for(int i = 2; i < samples; i++){
            results.append(" " + audioData[i]);
        }
        results.invalidate();
        //fft.setTextColor(Color.GREEN);
        //fft.setText("Buffer size is "+bufferSizeInBytes);
        //fft.setText(fftArray[1]+" Hz");
        //for(int i = 2; i < samples; i++){
        //fft.append(" " + fftArray[i] + " Hz");
        //}
        //fft.invalidate();
    }

Do I need to change something concerning the button and what it does when pressed? Would it just involve the buffer size? How often I compute the FFT?

skynet
  • 9,898
  • 5
  • 43
  • 52
dustinrwh
  • 888
  • 1
  • 14
  • 16

1 Answers1

0

Unless I am misunderstanding, you could just use a while loop that checks a boolean variable. When the user clicks the stop button set that variable to false.

while (tuning) {
    trigger();
}

you should also probably introduce a delay between these calls. It would also be wise to run this code on a thread other than the UI thread. See http://developer.android.com/resources/articles/painless-threading.html

A simple example of what I mean would be to do

new Thread(new Runnable() {
    @Override
    public void run() {
        while (tuning) {
            trigger();
            try {
                Thread.sleep(SLEEP_TIME_MS);
            } catch (InterruptedException e) {
                // handle exception
            }
        }
    }
}).start();

but then you have to worry about updating the UI as you cannot do that from this Thread. The best option is to use AsyncTask http://developer.android.com/reference/android/os/AsyncTask.html

skynet
  • 9,898
  • 5
  • 43
  • 52
  • Thanks for your response! Where exactly should I put this code of the new Thread? I am still learning what everything does in Java and Android. Also, do you know of a tutorial of how to implement AsyncTask? I'm lost just looking at the documentation. – dustinrwh Nov 30 '11 at 23:01
  • The painless threading link is a good place to start, I don't really know a better one but it should be enough to get you started – skynet Dec 01 '11 at 02:22