0

I'm trying to do a app that can detect the piano's frequency. I already see this

How to calculate sound frequency in android?

FFT class: https://github.com/fjfdeztoro/fftpack

and use the example code https://github.com/sommukhopadhyay/FFTBasedSpectrumAnalyzer/tree/master/

it is my code:

package com.somitsolutions.android.spectrumanalyzer;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import ca.uol.aig.fftpack.RealDoubleFFT;


public class SoundRecordAndAnalysisActivity extends Activity implements OnClickListener{

    //code by yu-che
    int display_width = 600;//the result of display_width/5 and display_width/50 must be integer
    int frequency = 44100;
    //code by yu-che
    int channelConfiguration = AudioFormat.CHANNEL_IN_MONO;
    //origin code
    //int channelConfiguration = AudioFormat.CHANNEL_CONFIGURATION_MONO;
    int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;

    AudioRecord audioRecord;
    private RealDoubleFFT transformer;
    int blockSize;// = 256;
    Button startStopButton;
    boolean started = false;
    boolean CANCELLED_FLAG = false;


    RecordAudio recordTask;
    ImageView imageViewDisplaySpectrum;
    MyImageView imageViewScale;
    Bitmap bitmapDisplaySpectrum;

    Canvas canvasDisplaySpectrum;

    Paint paintSpectrumDisplay;

    SoundRecordAndAnalysisActivity mainActivity;
    LinearLayout main;
    int width;
    int height;
    int left_Of_BitmapScale;
    int left_Of_DisplaySpectrum;
    private final static int ID_BITMAPDISPLAYSPECTRUM = 1;
    private final static int ID_IMAGEVIEWSCALE = 2;
    TextView tv;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Display display = getWindowManager().getDefaultDisplay();
        //Point size = new Point();
        //display.get(size);
        //code by yu-che
        Point size = new Point();
        display.getSize(size);
        width = size.x;
        height = size.y;
        //origin code
        //width = display.getWidth();
        //height = display.getHeight();

        blockSize = 8196;



    }

    @Override
    public void onWindowFocusChanged (boolean hasFocus) {
        //left_Of_BitmapScale = main.getC.getLeft();
        MyImageView  scale = (MyImageView)main.findViewById(ID_IMAGEVIEWSCALE);
        ImageView bitmap = (ImageView)main.findViewById(ID_BITMAPDISPLAYSPECTRUM);
        left_Of_BitmapScale = scale.getLeft();
        left_Of_DisplaySpectrum = bitmap.getLeft();
    }
    private class RecordAudio extends AsyncTask<Void, double[], Boolean> {

        @Override
        protected Boolean doInBackground(Void... params) {

            int bufferSize = AudioRecord.getMinBufferSize(frequency,
                    channelConfiguration, audioEncoding);
            audioRecord = new AudioRecord(
                    MediaRecorder.AudioSource.DEFAULT, frequency,
                    channelConfiguration, audioEncoding, bufferSize);
            int bufferReadResult;
            short[] buffer = new short[blockSize];
            double[] toTransform = new double[blockSize];
            try {
                audioRecord.startRecording();
            } catch (IllegalStateException e) {
                Log.e("Recording failed", e.toString());

            }
            while (started) {

                if (isCancelled() || (CANCELLED_FLAG == true)) {

                    started = false;
                    //publishProgress(cancelledResult);
                    Log.d("doInBackground", "Cancelling the RecordTask");
                    break;
                } else {
                    bufferReadResult = audioRecord.read(buffer, 0, blockSize);

                    for (int i = 0; i < blockSize && i < bufferReadResult; i++) {
                        toTransform[i] = (double) buffer[i] / 32768.0; // signed 16 bit
                    }

                    transformer.ft(toTransform);

                    publishProgress(toTransform);

                }

            }
            return true;
        }
        @Override
        protected void onProgressUpdate(double[]...progress) {
            Log.e("RecordingProgress", "Displaying in progress");

            Log.d("Test:", Integer.toString(progress[0].length));


            if (width > 512) {
                int i = 0;
                int intensity = 0;
                double scale =0;
                while (true){

                    //scale = 1600/(2000/50)*(display_width/50);
                    //canvasDisplaySpectrum.drawLine((int)scale, 150, (int)scale,150-50 , paintSpectrumDisplay);
                    if ( 0 == i){
                        intensity = (int)(progress[0][0]* progress[0][0]);
                        canvasDisplaySpectrum.drawLine(0, 150, 0,150-intensity , paintSpectrumDisplay);
                        i++;
                    } else if ( (blockSize-1) == i) {
                        intensity = (int)(progress[0][(blockSize-1)]* progress[0][(blockSize-1)]);
                        scale = (i/2+0.5)*frequency/(blockSize-1)/(2000/50)*(display_width/50);
                        canvasDisplaySpectrum.drawLine((int)scale, 150, (int)scale,150-intensity , paintSpectrumDisplay);
                        break;
                    } else{
                        intensity = (int)(progress[0][i]*progress[0][i]+ progress[0][(i+1)]*progress[0][(i+1)]);
                        scale = (i/2+0.5)*frequency/(blockSize-1)/(2000/50)*(display_width/50);
                        canvasDisplaySpectrum.drawLine((int)scale, 150, (int)scale,150-intensity , paintSpectrumDisplay);
                        i = i +2;
                    }

                }
                /*
                for (int i = 0; i < progress[0].length; i++) {

                    //int x = 2 * i;
                    //int downy = (int) (150 - (progress[0][i] * 10));
                    //int upy = 150;
                    //canvasDisplaySpectrum.drawLine(x, downy, x, upy, paintSpectrumDisplay);


                    canvasDisplaySpectrum.drawLine(0, 120, 0, 150, paintSpectrumDisplay);

                    canvasDisplaySpectrum.drawLine(display_width/50*10, 170, display_width/50*10, 150, paintSpectrumDisplay);
                }
                */

                imageViewDisplaySpectrum.invalidate();
            } else {
                for (int i = 0; i < progress[0].length; i++) {
                    int x = i;
                    int downy = (int) (150 - (progress[0][i] * 10));
                    int upy = 150;
                    canvasDisplaySpectrum.drawLine(x, downy, x, upy, paintSpectrumDisplay);
                }

                imageViewDisplaySpectrum.invalidate();
            }



        }
        @Override
        protected void onPostExecute(Boolean result) {
            super.onPostExecute(result);
            try{
                audioRecord.stop();
            }
            catch(IllegalStateException e){
                Log.e("Stop failed", e.toString());

            }

            canvasDisplaySpectrum.drawColor(Color.BLACK);
            imageViewDisplaySpectrum.invalidate();

        }
    }

    protected void onCancelled(Boolean result){

        try{
            audioRecord.stop();
        }
        catch(IllegalStateException e){
            Log.e("Stop failed", e.toString());

        }
           /* //recordTask.cancel(true);
            Log.d("FFTSpectrumAnalyzer","onCancelled: New Screen");
            Intent intent = new Intent(Intent.ACTION_MAIN);
            intent.addCategory(Intent.CATEGORY_HOME);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
*/
    }

    public void onClick(View v) {
        if (started == true) {
            //started = false;
            CANCELLED_FLAG = true;
            //recordTask.cancel(true);
            try{
                audioRecord.stop();
            }
            catch(IllegalStateException e){
                Log.e("Stop failed", e.toString());

            }
            startStopButton.setText("Start");

            canvasDisplaySpectrum.drawColor(Color.BLACK);

        }

        else {
            started = true;
            CANCELLED_FLAG = false;
            startStopButton.setText("Stop");
            recordTask = new RecordAudio();
            recordTask.execute();
        }

    }
    //code by yu-che
    /*
    static SoundRecordAndAnalysisActivity getMainActivity(){

        return mainActivity;
    }
    */
    public void onStop(){
        super.onStop();
            /* try{
                 audioRecord.stop();
             }
             catch(IllegalStateException e){
                 Log.e("Stop failed", e.toString());

             }*/
        recordTask.cancel(true);
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }

    public void onStart(){

        super.onStart();
        main = new LinearLayout(this);
        main.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,android.view.ViewGroup.LayoutParams.MATCH_PARENT));
        main.setOrientation(LinearLayout.VERTICAL);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        transformer = new RealDoubleFFT(blockSize);

        imageViewDisplaySpectrum = new ImageView(this);
        if(width > 512){
            bitmapDisplaySpectrum = Bitmap.createBitmap((int)512,(int)300,Bitmap.Config.ARGB_8888);
        }
        else{
            bitmapDisplaySpectrum = Bitmap.createBitmap((int)256,(int)150,Bitmap.Config.ARGB_8888);
        }
        LinearLayout.LayoutParams layoutParams_imageViewScale = null;
        //Bitmap scaled = Bitmap.createScaledBitmap(bitmapDisplaySpectrum, 320, 480, true);
        canvasDisplaySpectrum = new Canvas(bitmapDisplaySpectrum);
        //canvasDisplaySpectrum = new Canvas(scaled);
        paintSpectrumDisplay = new Paint();
        paintSpectrumDisplay.setColor(Color.GREEN);
        imageViewDisplaySpectrum.setImageBitmap(bitmapDisplaySpectrum);
        if(width >512){
            //imageViewDisplaySpectrum.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT));
            LinearLayout.LayoutParams layoutParams_imageViewDisplaySpectrum=new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            ((MarginLayoutParams) layoutParams_imageViewDisplaySpectrum).setMargins(100, 600, 0, 0);
            imageViewDisplaySpectrum.setLayoutParams(layoutParams_imageViewDisplaySpectrum);
            layoutParams_imageViewScale= new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            //layoutParams_imageViewScale.gravity = Gravity.CENTER_HORIZONTAL;
            ((MarginLayoutParams) layoutParams_imageViewScale).setMargins(100, 20, 0, 0);

        }

        else if ((width >320) && (width<512)){
            LinearLayout.LayoutParams layoutParams_imageViewDisplaySpectrum=new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            ((MarginLayoutParams) layoutParams_imageViewDisplaySpectrum).setMargins(60, 250, 0, 0);
            //layoutParams_imageViewDisplaySpectrum.gravity = Gravity.CENTER_HORIZONTAL;
            imageViewDisplaySpectrum.setLayoutParams(layoutParams_imageViewDisplaySpectrum);

            //imageViewDisplaySpectrum.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT));
            layoutParams_imageViewScale=new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            ((MarginLayoutParams) layoutParams_imageViewScale).setMargins(60, 20, 0, 100);
            //layoutParams_imageViewScale.gravity = Gravity.CENTER_HORIZONTAL;
        }

        else if (width < 320){
                /*LinearLayout.LayoutParams layoutParams_imageViewDisplaySpectrum=new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
                ((MarginLayoutParams) layoutParams_imageViewDisplaySpectrum).setMargins(30, 100, 0, 100);
                imageViewDisplaySpectrum.setLayoutParams(layoutParams_imageViewDisplaySpectrum);*/
            imageViewDisplaySpectrum.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT));
            layoutParams_imageViewScale=new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            //layoutParams_imageViewScale.gravity = Gravity.CENTER;
        }
        imageViewDisplaySpectrum.setId(ID_BITMAPDISPLAYSPECTRUM);
        main.addView(imageViewDisplaySpectrum);

        imageViewScale = new MyImageView(this);
        imageViewScale.setLayoutParams(layoutParams_imageViewScale);
        imageViewScale.setId(ID_IMAGEVIEWSCALE);

        //imageViewScale.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT));
        main.addView(imageViewScale);

        tv=new TextView(this);
        tv.setText("frequency");
        tv.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT));


        startStopButton = new Button(this);
        startStopButton.setText("Start");
        startStopButton.setOnClickListener(this);
        startStopButton.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT));

        main.addView(startStopButton);
        main.addView(tv);
        setContentView(main);

        mainActivity = this;

    }
    @Override
    public void onBackPressed() {
        super.onBackPressed();

        try{
            audioRecord.stop();
        }
        catch(IllegalStateException e){
            Log.e("Stop failed", e.toString());

        }
        recordTask.cancel(true);
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }

    @Override
    protected void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        try{
            audioRecord.stop();
        }
        catch(IllegalStateException e){
            Log.e("Stop failed", e.toString());

        }
        recordTask.cancel(true);
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
    //Custom Imageview Class
    //底下的座標軸
    public class MyImageView extends ImageView {
        Paint paintScaleDisplay;
        Bitmap bitmapScale;
        Canvas canvasScale;
        public MyImageView(Context context) {
            super(context);
            // TODO Auto-generated constructor stub
            if(width >display_width){
                bitmapScale = Bitmap.createBitmap(display_width,50,Bitmap.Config.ARGB_8888);
            }
            else{
                bitmapScale =  Bitmap.createBitmap(256,50,Bitmap.Config.ARGB_8888);
            }
            //坐標軸
            paintScaleDisplay = new Paint();
            paintScaleDisplay.setColor(Color.BLUE);
            paintScaleDisplay.setStyle(Paint.Style.FILL);

            canvasScale = new Canvas(bitmapScale);

            setImageBitmap(bitmapScale);
            invalidate();
        }
        @Override
        protected void onDraw(Canvas canvas)
        {
            // TODO Auto-generated method stub
            super.onDraw(canvas);

            if(width > 512){
                canvasScale.drawLine(0, 30,  display_width, 30, paintScaleDisplay);
                for(int i = 0,j = 0; i< display_width; i=i+display_width/5, j++){
                    for (int k = i; k<(i+display_width/5); k=k+display_width/50){
                        canvasScale.drawLine(k, 30, k, 23, paintScaleDisplay);
                    }
                    canvasScale.drawLine(i, 40, i, 20, paintScaleDisplay);
                    String text = Integer.toString(j*400) + " Hz";
                    canvasScale.drawText(text, i, 45, paintScaleDisplay);
                }
                canvas.drawBitmap(bitmapScale, 0, 0, paintScaleDisplay);
            }
            else if ((width >320) && (width<512)){
                canvasScale.drawLine(0, 30, 0 + 256, 30, paintScaleDisplay);
                for(int i = 0,j = 0; i<256; i=i+64, j++){
                    for (int k = i; k<(i+64); k=k+8){
                        canvasScale.drawLine(k, 30, k, 25, paintScaleDisplay);
                    }
                    canvasScale.drawLine(i, 40, i, 25, paintScaleDisplay);
                    String text = Integer.toString(j) + " KHz";
                    canvasScale.drawText(text, i, 45, paintScaleDisplay);
                }
                canvas.drawBitmap(bitmapScale, 0, 0, paintScaleDisplay);
            }

            else if (width <320){
                canvasScale.drawLine(0, 30,  256, 30, paintScaleDisplay);
                for(int i = 0,j = 0; i<256; i=i+64, j++){
                    for (int k = i; k<(i+64); k=k+8){
                        canvasScale.drawLine(k, 30, k, 25, paintScaleDisplay);
                    }
                    canvasScale.drawLine(i, 40, i, 25, paintScaleDisplay);
                    String text = Integer.toString(j) + " KHz";
                    canvasScale.drawText(text, i, 45, paintScaleDisplay);
                }
                canvas.drawBitmap(bitmapScale, 0, 0, paintScaleDisplay);
            }
        }
    }
}

But when the frequency is lower than A3(220Hz).

The fundamental can't be caught. Just caught it's overtones.

I try to change sample rate and blocksize,but I don't get a better result.

Is something wrong with my code?Or some details I don't find? Someone can teach me I will be appreciated >.<

  • Changing sample rate to a higher one will only help you catch higher harmonics. Is the fundamental lower in energy than the overtones? If yes, then try adjusting a threshold value so that even bins / frequencies having low energy are retrieved. – dsp_user Sep 13 '17 at 12:49
  • Oh.I see the spectrum that the fundamental is almost few or none when the frequency is low.So,I think adjusting a threshold value can't solve my problem ><.I think maybe is device's limit or somthing wrong with my code at present.I'm not sure. – Fang Ian Sep 13 '17 at 13:10
  • The fundamental does not necessarily have the largest magnitude - if you're looking for *pitch* then a slightly better method is to measure the frequency between successive harmonics. However for reliable pitch detection you need to use a proper algorithm, such as Harmonic Product Spectrum, rather than an *ad hoc* method based on an FFT. – Paul R Sep 13 '17 at 13:13
  • How many samples do you send to your FFT function? – dsp_user Sep 13 '17 at 13:14
  • @Paul, if indeed pitch detection is what the OP's after, then also autocorrelation seems an obvious choice. – dsp_user Sep 13 '17 at 13:17
  • @dsp_user: possibly - it depends on the nature of the sound or instrument for which you are trying to find the pitch. Note also that autocorrelation is just the inverse FFT of the power spectrum, so you're not gaining any information that's not already present in the power spectrum. Certainly a lot better than just looking at the frequency of the fundamental though. – Paul R Sep 13 '17 at 13:21
  • @dsp_user It's 8192 now(the upper code blocksize=8196 is my mistake).I try many times to get a better result.I want the Fn be smaller(Fn=(n-1)*Fs/N). – Fang Ian Sep 13 '17 at 13:30
  • @Paul Oh! I will try it! thx~ – Fang Ian Sep 13 '17 at 13:31
  • 1
    This should be closed as a duplicate of a bunch of other questions confusing peak magnitude frequency and musical pitch. – hotpaw2 Sep 13 '17 at 16:02

1 Answers1

0

A peak magnitude frequency estimator is not a musical pitch detector/estimator. So the incorrect results have nothing to do with the code. Except it being the wrong algorithm choice for the OPs purpose.

hotpaw2
  • 70,107
  • 14
  • 90
  • 153