Getting Fast Fourier Transform data on Android

How I broke down the sound waves in my sound proofing and STC app available on google play

 

In order to build this app I needed to understand how loud a sound was as well as which frequency I was hearing. Truthfully when I started this app I underestimated how difficult this would be.

 

Building this app requires a few things;

 

  1. You need to sample the pressure of the airwaves hitting the microphone very quickly in order to build a sine wave. I found a great website with some images that included the following and this really gave me the basis of my understanding. Check out this guys website for more info here http://www.vlf.it/fft_beginners/fft_beginners.html

 

In order to accomplish that I built an android class called audio control. I built an interface so that fragments could get an instance of audio control and make sure to call all the methods needed in androids lifecycle.

Here are the fields at the top of the class;







<pre>//   my inner classes
private PlayAudio playAudio;
private RecordAudio recordAudio;
private TestTone testTone;

private AudioRecord audioRecord;
private AudioTrack audioTrack;

private RealDoubleFFT transformer;
// TODO: 4/18/2018 buffer size to 4096 so we narrow our frequency range down a bit?
private int SAMPLE_RATE = 11025;  //chnageable. lowest is 11025 according to newventuresoftware we have a gaurantee capture of 44100/ im dubious based on intial testing of 100 to 20k hz
private int blockSize = 256; // keep here number of component frequency samples that our transform object will output ...so is that 44100 /2 /256 = 86.132??
//but how because you only give it the raw input data
private int halfBlock = blockSize/2;
private int hzSpec;
private int reqBars =  0; //this reworks at 116? bars total
//int reqBars = 30;</pre>

 

Down below I have an inner class which extends Async Class







<pre>
private class RecordAudio extends AsyncTask<File, double[], Boolean>
{
    String TAG = "RECORD AUDIO";

    private static final int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
    private static final int CHANNEL_MASK = AudioFormat.CHANNEL_IN_MONO;
    private static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
    long startTime =0;
    long endTime = 0;
    FileOutputStream waveOut;



    @Override
    protected Boolean doInBackground(File... files) {

        try {

            dbAverage = new int[smooth];
            recordedHighestAverage = 0;
            totalRunningAverage.clear();
            int minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_MASK, ENCODING);
            audioRecord = new AudioRecord(AUDIO_SOURCE, SAMPLE_RATE, CHANNEL_MASK, ENCODING, minBufferSize);

            if (files[0] != null) {
                waveOut = new FileOutputStream(files[0]);
                writeWavHeader(waveOut, CHANNEL_MASK, SAMPLE_RATE, ENCODING);
            } else {
                waveOut = null;
            }

            int bufferReadData;
            byte[] buffer1 = new byte[blockSize];
            long total = 0;

            try {
                startTime = SystemClock.elapsedRealtime();
                audioRecord.startRecording();

            } catch (IllegalStateException e) {

                Log.e(TAG, " Records doInBackground: " + e.toString());
            }


            while (running) {
                //Log.i(TAG, "while");

                bufferReadData = audioRecord.read(buffer1, 0, blockSize);  //we are requesting 256 byte obj and android is sending us 16bit so each byte hold half of a 16 bit!!


                publishProgress( createFFT(bufferReadData, buffer1));




                if (files[0] != null) {

                    createWavFile(total, bufferReadData, buffer1);
                }


            }

        } catch (IOException e) {
            Log.e(TAG, "Records doInBackground: " + e.toString(), e);
            stoprecording();
        } finally {

            Log.i(TAG, "Records doInBackground: calld from 2nd");

            endTime = SystemClock.elapsedRealtime();

        }

        if (waveOut != null) {

            try {
                updateWavHeader(files[0]);

            } catch (IOException e) {
                Log.e(TAG, "doInBackground: ", e);

            }
        }</pre>

 

Notice above where I publish progress to my FFT method. The above code merely show you how to sample the sounds from the air and get them into a byte array for more processing. Next we need to do some data manipulation and get the Fourier Transform and Decibel information.

I found a great website that explains that a sine wave is merely a circle with time expressed as well. Great presentation on their part and although I don’t completely feel I have internalized the lesson it has definitely improved my understanding. https://betterexplained.com/articles/an-interactive-guide-to-the-fourier-transform/

 

I ended up downloading the FFT pack that is free and open source on the internet. You can download it by searching   ->    ca/uol/aig/fftpack

Add it as a library and then use the FFT as follows:

Initialize at top of class above all your methods.

<pre>private RealDoubleFFT transformer;</pre>

 

Here is the method called by our background thread gathering the audio data. As you can see I needed both bytes and short in this software and stumbled through the process of making them as seen in my little sketch below which was my reference point.

Notice below the trasnformer.ft() method is doing all the FFT work. Everything before that is just cramming data into a 16 bit configuration. Or getting me my decibel value.

 

<pre>
protected double[] createFFT(int bufferReadData, byte[] buffer1)
{
    double[] toTransform = new double[blockSize/2];
    double sum = 0;
    int block = halfBlock;
    double REF = 0.00002;
    //ByteBuffer.wrap(buffer1).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(buffer);   //this hopefully creates the buffer1 array

    // xx is correct bits. Here we load byte from buffer lilendian to big to feed into the newBuff
    //  xx xx xx xx 00 00 00 00
    //  00 00 00 00 xx xx xx xx
    //  xx xx xx xx xx xx xx xx
    // 0xFF added to fix a left padding problem??

    short newBuff, buffLil, buffBig, count = 0;

    for (int i = 0; i < blockSize/2 && i < bufferReadData/2; i++) //after 128 runs with a double increment it is pulling what data?
    {

        buffLil = buffer1[count];
        buffBig = buffer1[count + 1];
        newBuff = (short) ( buffBig <<8 | buffLil & 0xFF);
        //Log.i(TAG, "createFFT: short " + String.valueOf(newBuff));
        count ++;
        count++;

        /* testing new stuff here */
        if (newBuff > 0) {

            sum += Math.abs(newBuff);

        }else
        {
            block--;
        }


        toTransform[i] = (double) newBuff / 32768.0;             //This takes the short and divides by total short value to give a decimal double value between -1.0 to 1.0 for input into fft

    }


    double x = sum/block;
    //Log.i(TAG, "createFFT: average amplitude " + String.valueOf(x));

    double db = 0;

    if (x != 0) {

        double pressure = x/51805.5336;

        db = (20 * Math.log10(pressure/REF));

        if (db > 0){

            //Log.i(TAG, "createFFT: " + String.valueOf(db));
            int d = (int) db;
            //Log.i(TAG, "createFFT: db" + String.valueOf(d));
            averageDecibelForView = average(d);
        }




    }

    transformer.ft(toTransform);


    return(toTransform);


}</pre>
&nbsp;

 

You may also notice that I am calculating my decibels based on the guesstimated microphone pressure values someone provided me. You may also noticed That I push those into the average() method.

This fft was running very fast despite the math required and I was only refreshing the screen about 30 times per second so not all the data needed to hit the screen and surely the user didnt need to see the decibel amplitude 30 times per second.

Below you can see the result. I will get into drawing the display on my next post but it shows the current decibels top left and moving bar chart below.

 

Fast Fourier transform FFT android

 

 

 

The post Getting Fast Fourier Transform data on Android appeared first on SignalHillTechnology.

Powered by WPeMatico

Sound Proof STC APP – Available for android on google play store

The essence of construction if building a barrier between us and the elements of nature or often, just as importantly, other annoying people.

But how do we do that relating to sound?

 

In the late 60 sound proofing became a more examined topic in construction. It continues to evolve today but very little of that evolution is in the hands of the end user and installer. So despite the importance of sound proofing construction decisions are frequently left to ” Well, this should work” … But will it?

One of the main barriers is the lack of knowledge by both sound engineers in how to get more information to contractors and contractors in understanding how sounds travel through buildings.

 

I have a lot of respect for sound engineers and testing agencies but…

They fail to apply their knowledge to real world situations. Testing in a laboratory is great. It’s accurate and provides crucial research and data. In that same vein taking a leer jet is much faster and safer than driving a car. But the reality is cars get more people more miles every year than leer jets do.

In much the same way the average person can’t stand around waiting for their leer jet to arrive we cannot stand around depending on these tests to make practical use of the information collected. The average property owner or builder needs something cheap and reasonably effective yesterday.

 

I have a lot of respect for contractors and designers but…

Frequently they are guessing. They know they can get someone to analyze acoustics and sometimes they do. But most of the time they use their ears make some assumptions and just start building stuff until the sound gets to a tolerable level.

 

I designed this app to give people making decisions every day something to help in that decision making process.

To allow people to quickly grab some data for review later, or to take someone with zero knowledge and help them make a better informed decision.

Is it perfect…no. But its better than nothing.

 

Below are a few screen shots. Check it out on google play.

 

 

sound proof and stc app android

sound proof and stc app android

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

sound proof and stc app android

sound proof and stc app android

Powered by WPeMatico

Easy Time Card App – The paper time card alternative

Many offices today are still filling out paper time cards. Why you ask?

2018-03-06 11.12.29

  1. Nefarious software integration
  2. The learning curve
  3. Bugs or “features”

Almost everyone is guilty of it. Software products are designed to work with constant support resulting in monthly or yearly fees. Upgrades, server costs etc. But most people aren’t getting paid to figure out software. Many low skill employees can’t or drag their feet. They want the solution that stays the same. Not one that changes with the weather. As they sort through all the numerous buttons they passively fear that the next upgrade will change things and they will spend more time relearning. Barf.

 

But paper time cards suck!

Paper time cards take time to fill out. There is zero space to write. They get smudged and lost plus people have to hand deliver them back to the office. Someone needs to sort through them. It’s not really a great solution.

 

A better solution

For sale on google play store is the EASY TIME CARD APP. A compromise between the two.  This app is real easy. Two buttons to operate it and if the setup is too hard someone else can do it once and its done. Right is a screen shot from the main screen of the app. You can see the current day being shown as clocked in at the top.

 

At the bottom, when the user is ready to clock out just press the big red button.

 

When the user is ready to report their time they can press the button on the bottom  labeled report time. This send the time card over in an easily readable email without any work.

 

 

2018-03-06 10.57.43

Here you can see the email which is automatically made after the user clicks the “Report Time” button.

It super clear and easy to read. It tracks starting and ending times as well as total work times.

As workers go from job to job they can track how much time they are spending at each location or task.

Best of all the records can be emailed to three people. So now that single time card can be reviewed and saved digitally.

Workers are still on the honor system because these time card emails can be edited manually. This is helpful is you make a mistake and forget to clock in. Simply type in a note on the email or change a start time.

Most importantly this is something 99% of people already know how to do. This app isn’t trying to change your life. Its simply the exact same paper time cards you have relied on in a digital format!

 

Powered by WPeMatico

Easy Time Card

Many offices today are still filling out paper time cards. Why you ask?

  1. How nefarious software integration is
  2. The learning curve
  3. Bugs or “features”

Almost everyone is guilty of it. Products are designed to work with constant support resulting in monthly or yearly fees. Upgrades, server costs etc. But most people aren’t getting paid to figure out software. They want the solution that stays the same. Not one that changes with the weather. As they sort through all the numerous buttons they passively fear that the next upgrade will change things and they will spend more time relearning. Barf.

 

But paper time cards suck!

Paper time cards take time to fill out. There is zero space to write. They get smudged and lost plus people have to hand deliver them back to the office. Someone needs to sort through them. Its not really a great solution.

 

My Solution

A compromise between the two.  This app is real easy. Two buttons to operate it and if the setup is too hard someone else can do it once and its done. Below is a screen shot from the main screen of the app. You can see the current day being shown as clocked in and below. When the user is ready to clock out just press the big red button. When the user is ready to report their time they can press the button on the bottom  labeled report time.

 

Here is the main activity below which is android class that you build your app around. Here there are a few boilerplate function to get the app onto your screen and a few more to to make dates, times or save files to your device.

This UI of this app is built within the Fragment and Dialogue Fragment classes within android library which is conveniently optimized with android studio which has great autocomplete.

Me thinking about autocomplete. Colorized. Circa 1999.

The main design of this app relies on java calendar. By using the calendar .get instance I can pull a number of int objects representing the minute, hour, day, week or month of the year. In order to build this app I created a listview showing the previous data.

I have several onClick methods calling my different methods throughout the fragment class I created.

 @Override
public void onClick(View view) {

switch (view.getId()){

case R.id.clockIn_Out_button:

if (CURRENT_STATUS.equals(CLOCKED_IN)) {

clockOut();

} else { clockInFromClick(view); }

break;

case R.id.report_button:

deleteOldData();

boolean infoToReport = createReport();

if (infoToReport) {
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("message/rfc822");
i.putExtra(Intent.EXTRA_EMAIL, recipientOfEmail);
i.putExtra(Intent.EXTRA_SUBJECT, subjectOfEmail);
i.putExtra(Intent.EXTRA_TEXT, bodyOfEmail);

try {

startActivity(Intent.createChooser(i, "Send email..."));

} catch (android.content.ActivityNotFoundException ex) {
Toast.makeText(getActivity(), "No email Client installed", Toast.LENGTH_LONG).show();
}

}else if (reportErrorExplain.equals("You must fill out your name in settings") || reportErrorExplain.equals("You must fill out an email recipient in settings")) {
Toast.makeText(getActivity(), reportErrorExplain, Toast.LENGTH_SHORT).show();

new Handler().postDelayed(new Runnable() {
@Override
public void run() {
((MainActivity)getActivity()).loadSettingsFrag();

}
}, 2000);

}
else {

Toast.makeText(getActivity(), reportErrorExplain, Toast.LENGTH_SHORT).show();
}

toReport.clear();

clearScreen();

break;

}

}

My big mistake was not breaking down the data processing into smaller more usable methods. Instead I ended up with two lengthy methods for pulling data from the device and making a human readable list. One of these methods serves the device users list. The other serves a method which creates the email to be sent. See below!

&lt;pre&gt;
public void createList()
{

    eachDaysData = new String[20];
   String s;
   String[] d = new String[20];



    for (int i = 0; i &lt; 20; i++) {


       s = ((MainActivity)getActivity()).createCalendarDate(i);

        //Log.i("createdates: ", s);

        d[i] = ((MainActivity)getActivity()).getData(s);      //at this point d has each days raw data



    }


    for (int i = 0; i &lt; d.length; i++) {

        if (i == 0 &amp;&amp; CURRENT_STATUS.equals(CLOCKED_IN)){
            String q = ((MainActivity)getActivity()).createCalendarDate(i);
            eachDaysData[i] = q + "\n" + "CURRENT STATUS: CLOCKED IN";
        }


        else if (!d[i].equals("")){

           String min = "";
           String myHr = "";
            String prevHour = "";
            String prevMinute = "";
            String startTime = "";
            String endTime = "";
            String timeWorked, timeBreaked;
            int pHr,pMt, Hr, Mt;
            int cumHrW = 0;
            int cumMinW = 0;
            int cumHrB = 0;
            int cumMinB = 0;
            String currLoc ="", nextLoc ="";

            boolean inEvent = true;

            Log.i("createList: ", d[i]);

            String[] e = d[i].split(DELIM_O);    //e[0] = "clockedin_13_53, e[1] = "clockedout_13_53

            for (int j = 0; j &lt; e.length; j++) {

               String[] p = e[j].split(DELIM_I);  //p[0] = clockedin p[1] = 13 p[2] = 53 p[3] = "some location"

                if (p.length == 4) {

                    addToRecentLocations(p[3]);
                   // Log.i("location found", "true");
                }

                if (startTime.equals("")) {                                                        //this does start time

                    String marker = "am";

                    int t = Integer.valueOf(p[1]);
                    if (t &gt; 12){  t+= -12; marker = "pm"; }
                   myHr = String.valueOf(t);

                    t = Integer.valueOf(p[2]);
                    if (t &lt; 10){min = "0" + String.valueOf(t);}
                    else {min = String.valueOf(t);}


                    startTime += myHr + ":" + min + marker;
                }
                if (j == e.length-1){                                                               //this records endtime


                    if (p[0].equals(CLOCKED_IN)){



                    }else {


                        String marker = "am";

                        int t = Integer.valueOf(p[1]);
                        if (t &gt; 11) {
                            marker = "pm";
                        }
                        if (t &gt; 12) {
                            t += -12;
                        }
                        myHr = String.valueOf(t);

                        t = Integer.valueOf(p[2]);
                        if (t &lt; 10) {
                            min = "0" + String.valueOf(t);
                        } else {
                            min = String.valueOf(t);
                        }

                        endTime += myHr + ":" + min + marker;

                    }

                }



               if (j != 0) {

                 //  Log.i(TAG, "createList: " + prevHour);
                 //  Log.i(TAG, "createList: "+ prevMinute);
                 //  Log.i(TAG, "createList: " + p[1]);
                 //  Log.i(TAG, "createList: " + p[2]);


                   pHr = Integer.parseInt(prevHour);
                   pMt = Integer.parseInt(prevMinute);
                   Hr = Integer.parseInt(p[1]);
                   Mt = Integer.parseInt(p[2]);

                   if (!inEvent) {

                       cumHrW += Hr - pHr;

                       cumMinW += Mt - pMt;
                   }else {
                       cumHrB += Hr -pHr;
                       cumMinB += Mt -pMt;

                   }


               }


                prevHour = p[1];
                prevMinute = p[2];



                inEvent = !inEvent;

            }

                   //this is where we put what we figured out into text

            if (cumMinW &gt; 60) {

                cumHrW += cumMinW /60;
                cumMinW = cumMinW%60;
            }else if (cumMinW &lt; 0 ){
                cumMinW = -cumMinW;
                cumHrW =cumHrW - 1-( cumMinW/60);
                cumMinW = 60 - cumMinW%60;
            }


            if (cumMinW &lt; 10){min = "0" + String.valueOf(cumMinW);}
            else {min = String.valueOf(cumMinW);}

           timeWorked = " " + String.valueOf(cumHrW) + ":" + min + " Total Time Worked";

            if (cumMinB &gt; 60) {

                cumHrB += cumMinB /60;
                cumMinB = cumMinB %60;
            }else if (cumMinB &lt; 0 ){
                cumMinB = -cumMinB;
                cumHrB = cumHrB- 1 -(cumMinB /60);
                cumMinB = 60 - cumMinB%60;
            }

            if (cumMinB &lt; 10)
            {min = "0" + String.valueOf(cumMinB);}
            else  {min = String.valueOf(cumMinB);}
               timeBreaked = " " + String.valueOf(cumHrB) + ":" + min + " Total Breaks ";

            String q = ((MainActivity)getActivity()).createCalendarDate(i);

            Log.i("createList: ", q);

            eachDaysData[i] = q + "\n" + startTime + " - " + endTime + " Start and Finish" + "\n" + timeBreaked + "\n" + timeWorked;



        }else  {


           String q = ((MainActivity)getActivity()).createCalendarDate(i);

            eachDaysData[i] = q + "\nNo work performed";


        }

    }



}

After all said and done though its working perfectly. I’m debugging it now and tracking time on my different projects. My pan is to release it on google play store and maybe figure out a way to market it a bit better than just waiting for people to search it.

 

The post Easy Time Card appeared first on SignalHillTechnology.

Powered by WPeMatico