How to detect frequency using Goertzel algorithm

825 views Asked by At

I am really struggling to figure this out. Essentially I am trying to find what frequency is being played via the mic. To my understand, I need to bruteforce the Goertzel algorithm. So essentially I just try every frequency using the Goertzel algorithm until I find the correct one. However, I do not understand how I actually know when the Goertzel algorithm has found the correct algorithm. Could someone please help me.

MainActivity.java

import androidx.appcompat.app.AppCompatActivity;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private Button recordButton;
    private TextView result;

    private AudioRecord recording;
    private static final int RECORDER_SAMPLERATE = 10000;
    private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_MONO;
    private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
    int bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE, RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING);
    double[] dbSample = new double[bufferSize];
    short[] sample = new short[bufferSize];
    private int frequency = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recordButton = findViewById(R.id.recordButton);
        result = findViewById(R.id.resultTextView);
        recordButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                recording = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, RECORDER_SAMPLERATE,
                                            RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING, bufferSize);
                recording.startRecording();
                int bufferReadResult = recording.read(sample, 0, bufferSize);


                for (int j = 0; j < bufferSize && j < bufferReadResult; j++) {
                    dbSample[j] = (double) sample[j];
                    goertzel.processSample(dbSample[j]);
                }

                // Is this correct?
                magnitude = Math.sqrt(goertzel.getMagnitudeSquared());
                if(magnitude > maxMagnitude){
                    maxMagnitude = magnitude;
                    System.out.println("Freq is: " + Integer.toString(frequency));
                }
                goertzel.resetGoertzel();
                frequency += 1;
            }
        });

    }
}

Goertzel.java

public class Goertzel {
    private float samplingRate;
    private float targetFrequency;
    private long n;
    private double coeff, Q1, Q2;
    private double sine, cosine;

    public Goertzel(float samplingRate, float targetFrequency, long inN) {
        this.samplingRate = samplingRate;
        this.targetFrequency = targetFrequency;
        n = inN;
    }

    public void resetGoertzel() {
        Q1 = 0;
        Q2 = 0;
    }

    public void initGoertzel() {
        int k;
        float floatN;
        double omega;
        floatN = (float) n;
        k = (int) (0.5 + ((floatN * targetFrequency) / samplingRate));
        omega = (2.0 * Math.PI * k) / floatN;
        sine = Math.sin(omega);
        cosine = Math.cos(omega);
        coeff = 2.0 * cosine;
        resetGoertzel();
    }

    public void processSample(double sample) {
        double Q0;
        Q0 = coeff * Q1 - Q2 + sample;
        Q2 = Q1;
        Q1 = Q0;
    }

    public double[] getRealImag(double[] parts) {
        parts[0] = (Q1 - Q2 * cosine);
        parts[1] = (Q2 * sine);
        return parts;
    }

    public double getMagnitudeSquared() {
        return (Q1 * Q1 + Q2 * Q2 - Q1 * Q2 * coeff);
    }
}
1

There are 1 answers

6
greeble31 On

You've asked about brute-forcing Goertzel specifically, so here is an annotated JUnit test that illustrates a reasonable approach:

public class TestGoertzel
{
    private float[] freqs;
    private Goertzel[] goertzels;
    private static final int RECORDER_SAMPLERATE = 10000;
    private static final int INPUT_SAMPLES = 256;   //Roughly 26 ms of audio. This small array size was
        //chosen b/c the number of frequency "bins" is typically related to the number of input samples,
        //for engineering applications. If we only check 256 samples of audio, our "DFT" need only include
        //128 output "bins". You can resize this to suit, but keep in mind that the processing time will
        //increase exponentially.

    @Test
    public void test()
    {
        freqs = new float[INPUT_SAMPLES / 2];   //To prevent frequency-domain aliasing, we cannot test for 256 frequencies; only the first 128.

        goertzels = new Goertzel[freqs.length];

        for(int n = 0; n < freqs.length; ++n)
        {
            freqs[n] = n * RECORDER_SAMPLERATE / INPUT_SAMPLES;     //Determine the frequency of a wave that can fit exactly n cycles in a block of audio INPUT_SAMPLES long.

            //Create a Goertzel for each frequency "bin":
            goertzels[n] = new Goertzel(RECORDER_SAMPLERATE, freqs[n], INPUT_SAMPLES);
            goertzels[n].initGoertzel();        //Might as well create them all at the beginning, then "reset" them as necessary.
        }

        //This gives you an idea of the quality of output that can be had for a real signal from your
        //microphone. The curve is not perfect, but shows the "smearing" characteristic of a wave
        //whose frequency does not fall neatly into a single "bin":
        testFrequency(1500.0f);

        //Uncomment this to see a full unit test:
        //for(float freq : freqs)
        //{
        //  testFrequency(freq);
        //}
    }

    private void testFrequency(float freqHz)
    {
        System.out.println(String.format("Testing input signal of frequency %5.1fHz", freqHz));
        short[] audio = generateAudioWave(freqHz, (short) 1000);

        short[] magnitudes = detectFrequencies(audio);

        for(int i = 0; i < magnitudes.length; ++i)
        {
            System.out.println(String.format("%5.1fHz: %d", freqs[i], magnitudes[i]));
        }
    }

    private short[] generateAudioWave(float freqHz, short peakAmp)
    {
        short[] ans = new short[INPUT_SAMPLES];

        float w0 = (float) ((2 * Math.PI) * freqHz / RECORDER_SAMPLERATE);

        for(int i = 0; i < ans.length; ++i)
        {
            ans[i] = (short) (Math.sin(w0 * i) * peakAmp);
        }

        return ans;
    }


    private short[] detectFrequencies(short[] audio)
    {
        short[] ans = new short[freqs.length];

        for(int i = 0; i < goertzels.length; ++i)
        {
            Goertzel goertzel = goertzels[i];
            goertzel.resetGoertzel();

            for(short s : audio)
            {
                goertzel.processSample((double) s);
            }

            ans[i] = (short) (Math.sqrt(goertzel.getMagnitudeSquared()) * 2 / INPUT_SAMPLES);
        }

        return ans;
    }
}

Basically, for every 256 samples of audio you read in, you take that array, and run it past an array of Goertzels which cover the frequencies you are interested in (each Goertzel only measures one frequency). That gives you an output spectrum. You may interpret that spectrum how you choose; I took your question to mean, "how do you find the frequency of the LOUDEST component of the input audio?". In that case, you would search the return value of detectFrequencies() for the largest magnitude. The corresponding member of freqs is your answer.

The fact is, you probably don't want Goertzel, you want an FFT, due to FFT's superior "computational efficiency". Because Goertzel is somewhat slower (to cover a spectrum as fully as an FFT), you may have trouble getting this answer to run in real time.

As an aside, I don't think a samplerate of 10000 is supported, on Android.