commit 2eaf328d0567088e06c16a860c19cb1f1b4c0591 Author: Jan Feemers Date: Wed Jun 10 16:33:57 2020 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..603b140 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..681f41a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..440480e --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..37a7509 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..fa67076 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + + defaultConfig { + applicationId "com.feemers.android.fftdrawer" + minSdkVersion 16 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/feemers/android/fftdrawer/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/feemers/android/fftdrawer/ExampleInstrumentedTest.java new file mode 100644 index 0000000..72d7574 --- /dev/null +++ b/app/src/androidTest/java/com/feemers/android/fftdrawer/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package com.feemers.android.fftdrawer; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + + assertEquals("com.feemers.android.fftdrawer", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7d68f54 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/feemers/android/fftdrawer/Animation.java b/app/src/main/java/com/feemers/android/fftdrawer/Animation.java new file mode 100644 index 0000000..379d07d --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/Animation.java @@ -0,0 +1,8 @@ +package com.feemers.android.fftdrawer; + +import android.graphics.Canvas; + +public interface Animation { + void Draw(Canvas canvas); + void SetBackgroundColor(int c); +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/Bar.java b/app/src/main/java/com/feemers/android/fftdrawer/Bar.java new file mode 100644 index 0000000..b2a199e --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/Bar.java @@ -0,0 +1,28 @@ +package com.feemers.android.fftdrawer; + +import android.graphics.Color; + +public class Bar { + private float value; + private int harmonics; + private float frequency; + private final int color; + + public Bar(float value, int harmonics) { + this.value = value; + this.harmonics = harmonics; + color = Color.rgb(0,255,0); + } + + public float getValue(){ + return value; + } + + public int getHarmonics() { + return harmonics; + } + + public int getColor(){ + return color; + } +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/BarChart.java b/app/src/main/java/com/feemers/android/fftdrawer/BarChart.java new file mode 100644 index 0000000..4cc2204 --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/BarChart.java @@ -0,0 +1,62 @@ +package com.feemers.android.fftdrawer; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; + +import java.util.ArrayList; + +public class BarChart implements Animation { + + private final int length; + private ArrayList bars = null; + + private Paint paint; + private int backgroundColor; + + public BarChart(int length){ + this.length = length; + bars = new ArrayList<>(); + + paint = new Paint(); + paint.setAntiAlias(true); + backgroundColor = Color.rgb(0,0,0); + } + + public void newValues(double[] values){ + bars.clear(); + for(int i = 0; i < length; i++){ + float tmp = (float) values[i]; + bars.add(new Bar(tmp, i)); + } + } + + @Override + public void Draw(Canvas canvas) { + paint.setStyle(Paint.Style.FILL); + canvas.drawColor(backgroundColor); + // find max + float maxValue = 0; + for(Bar b : bars){ + maxValue = maxValue >= b.getValue() ? maxValue : b.getValue(); + } + for(Bar b : bars){ + paint.setColor(b.getColor()); + float width = canvas.getWidth(); + float heigth = canvas.getHeight(); + float widthOfBar = width / length; + + float left= b.getHarmonics() * widthOfBar; + float top = heigth - heigth * b.getValue() / maxValue; + float rigth = (b.getHarmonics()+1) * widthOfBar; + float buttom = heigth; + + canvas.drawRect(left, top, rigth ,buttom, paint); + } + } + + @Override + public void SetBackgroundColor(int c) { + backgroundColor = c; + } +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/BarChartView.java b/app/src/main/java/com/feemers/android/fftdrawer/BarChartView.java new file mode 100644 index 0000000..884aeb2 --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/BarChartView.java @@ -0,0 +1,94 @@ +package com.feemers.android.fftdrawer; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.util.AttributeSet; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +public class BarChartView extends SurfaceView implements Runnable { + + private Animation animation; + private SurfaceHolder surfaceHolder; + private Thread thread = null; + private volatile boolean running = false; + private int backgroudColor = 0; + private String name = ""; + private int sleepTime = 0; + private volatile boolean update; + + + public BarChartView(Context context) { + super(context); + surfaceHolder = getHolder(); + } + public BarChartView(Context context, AttributeSet attributeSet){ + super(context, attributeSet); + surfaceHolder = getHolder(); + ReadXML(attributeSet); + } + private void ReadXML(AttributeSet attributeSet){ + android.content.res.TypedArray typedArray = getContext().obtainStyledAttributes(attributeSet, R.styleable.BarChartView); + //String s = typedArray.getString(R.styleable.GrafikView_android_text); + //name = (s!=null) ? s : ""; + backgroudColor = typedArray.getColor(R.styleable.BarChartView_backgroundColor, Color.BLACK); + typedArray.recycle(); + log(", hintergrundFarbe=" + backgroudColor); + } + + public void setAnimation(Animation animation){ + this.animation = animation; + this.animation.SetBackgroundColor(backgroudColor); + } + + private void log(String s) { + Log.d(this.getClass().getSimpleName(), s); + } + + public void Start(){ + running = true; + thread = new Thread(this); + thread.start(); + log("Thread started"); + } + + public void Stop(){ + running = false; + while(true){ + try{ + thread.join(); + log("Thread stopped"); + break; + }catch (InterruptedException e){ + e.printStackTrace(); + } + } + } + public void Draw(){ + update = true; + } + + @Override + public void run() { + while (running) { + if (update) { + if (!surfaceHolder.getSurface().isValid()) { + continue; + } + Canvas canvas = surfaceHolder.lockCanvas(); + if (animation != null) { + animation.Draw(canvas); + update = false; + } + surfaceHolder.unlockCanvasAndPost(canvas); + } + try { + Thread.sleep(5); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/MainActivity.java b/app/src/main/java/com/feemers/android/fftdrawer/MainActivity.java new file mode 100644 index 0000000..213b00e --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/MainActivity.java @@ -0,0 +1,76 @@ +package com.feemers.android.fftdrawer; + +import androidx.appcompat.app.AppCompatActivity; + +import android.content.Context; +import android.hardware.SensorManager; +import android.os.Bundle; +import android.widget.TextView; +import android.widget.TimePicker; + +import com.feemers.android.fftdrawer.SignalProcessing.DetectorTimedomain; +import com.feemers.android.fftdrawer.SignalProcessing.Logger; +import com.feemers.android.fftdrawer.SignalProcessing.SignalProcessingControl; + +import java.util.Observable; +import java.util.Observer; + +public class MainActivity extends AppCompatActivity implements Observer { + private TextView textView1; + private BarChartView grafikView1; + private TextView textView2; + private BarChartView grafikView2; + private SignalProcessingControl signalProcessingControl; + private DetectorTimedomain detectorTimedomain; + private SensorManager sensorManager; + private BarChart barChart; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + textView1 = findViewById(R.id.textView1); + textView1.setText("Nr.1 "); + textView2 = findViewById(R.id.textView2); + textView2.setText("Nr. 2"); + + + double[] test = new double[64]; + for (int i = 0; i < 64; i++){ + test[i] = 0; + } + + barChart = new BarChart(64); + barChart.newValues(test); + + grafikView1 = findViewById(R.id.graficView1); + grafikView1.setAnimation(barChart); + grafikView2 = findViewById(R.id.graficView2); + grafikView2.setAnimation(new BarChart(64)); + + Logger logger = new Logger(this.getClass().getSimpleName(), ""); + detectorTimedomain = new DetectorTimedomain(20); + detectorTimedomain.setThreshold(12); + + sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); + signalProcessingControl = new SignalProcessingControl(sensorManager, logger); + signalProcessingControl.init(); + + signalProcessingControl.getNotifier().addObserver(this); + signalProcessingControl.start(); + + + grafikView1.Start(); + grafikView2.Start(); + + + } + + @Override + public void update(Observable o, Object arg) { + double[] newData = signalProcessingControl.getNotifier().getData(); + barChart.newValues(newData); + grafikView1.Draw(); + grafikView2.Draw(); + } +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/Complex.java b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/Complex.java new file mode 100644 index 0000000..e3ec40c --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/Complex.java @@ -0,0 +1,148 @@ +package com.feemers.android.fftdrawer.SignalProcessing; + +/****************************************************************************** + * Compilation: javac Complex.java + * Execution: java Complex + * + * Data type for complex numbers. + * + * The data type is "immutable" so once you create and initialize + * a Complex object, you cannot change it. The "final" keyword + * when declaring re and im enforces this rule, making it a + * compile-time error to change the .re or .im instance variables after + * they've been initialized. + * + * % java Complex + * a = 5.0 + 6.0i + * b = -3.0 + 4.0i + * Re(a) = 5.0 + * Im(a) = 6.0 + * b + a = 2.0 + 10.0i + * a - b = 8.0 + 2.0i + * a * b = -39.0 + 2.0i + * b * a = -39.0 + 2.0i + * a / b = 0.36 - 1.52i + * (a / b) * b = 5.0 + 6.0i + * conj(a) = 5.0 - 6.0i + * |a| = 7.810249675906654 + * tan(a) = -6.685231390246571E-6 + 1.0000103108981198i + * + ******************************************************************************/ + +public class Complex { + private final double re; // the real part + private final double im; // the imaginary part + + // create a new object with the given real and imaginary parts + public Complex(double real, double imag) { + re = real; + im = imag; + } + + // return a string representation of the invoking Complex object + public String toString() { + if (im == 0) return re + ""; + if (re == 0) return im + "i"; + if (im < 0) return re + " - " + (-im) + "i"; + return re + " + " + im + "i"; + } + + // return abs/modulus/magnitude + public double abs() { + return Math.hypot(re, im); + } + + // return angle/phase/argument, normalized to be between -pi and pi + public double phase() { + return Math.atan2(im, re); + } + + // return a new Complex object whose value is (this + b) + public Complex plus(Complex b) { + Complex a = this; // invoking object + double real = a.re + b.re; + double imag = a.im + b.im; + return new Complex(real, imag); + } + + // return a new Complex object whose value is (this - b) + public Complex minus(Complex b) { + Complex a = this; + double real = a.re - b.re; + double imag = a.im - b.im; + return new Complex(real, imag); + } + + // return a new Complex object whose value is (this * b) + public Complex times(Complex b) { + Complex a = this; + double real = a.re * b.re - a.im * b.im; + double imag = a.re * b.im + a.im * b.re; + return new Complex(real, imag); + } + + // return a new object whose value is (this * alpha) + public Complex scale(double alpha) { + return new Complex(alpha * re, alpha * im); + } + + // return a new Complex object whose value is the conjugate of this + public Complex conjugate() { + return new Complex(re, -im); + } + + // return a new Complex object whose value is the reciprocal of this + public Complex reciprocal() { + double scale = re*re + im*im; + return new Complex(re / scale, -im / scale); + } + + // return the real or imaginary part + public double re() { return re; } + public double im() { return im; } + + // return a / b + public Complex divides(Complex b) { + Complex a = this; + return a.times(b.reciprocal()); + } + + // return a new Complex object whose value is the complex exponential of this + public Complex exp() { + return new Complex(Math.exp(re) * Math.cos(im), Math.exp(re) * Math.sin(im)); + } + + // return a new Complex object whose value is the complex sine of this + public Complex sin() { + return new Complex(Math.sin(re) * Math.cosh(im), Math.cos(re) * Math.sinh(im)); + } + + // return a new Complex object whose value is the complex cosine of this + public Complex cos() { + return new Complex(Math.cos(re) * Math.cosh(im), -Math.sin(re) * Math.sinh(im)); + } + + // return a new Complex object whose value is the complex tangent of this + public Complex tan() { + return sin().divides(cos()); + } + + + + // a static version of plus + public static Complex plus(Complex a, Complex b) { + double real = a.re + b.re; + double imag = a.im + b.im; + Complex sum = new Complex(real, imag); + return sum; + } + + // See Section 3.3. + public boolean equals(Object x) { + if (x == null) return false; + if (this.getClass() != x.getClass()) return false; + Complex that = (Complex) x; + return (this.re == that.re) && (this.im == that.im); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/DetectorFFT.java b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/DetectorFFT.java new file mode 100644 index 0000000..68dbdf5 --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/DetectorFFT.java @@ -0,0 +1,72 @@ +package com.feemers.android.fftdrawer.SignalProcessing; + +import android.util.Log; + +public class DetectorFFT extends DetectorImpl { + private int m_sample_cnt; //TODO: move this to DetectorImpl + private Complex[] m_samples; + + public DetectorFFT(int windowSize) + { + super(windowSize); + Log.d("DetectorFFT", "DetectorFFT() - Constructor called"); + m_sample_cnt = 0; + m_samples = new Complex[m_buffer_SIZE]; + for(int i = 0; i < m_buffer_SIZE; i++){ + m_samples[i] = new Complex(0, 0); + } + + } + + @Override + public void onNewSample(double sample) { + super.onNewSample(sample); + m_samples[m_sample_cnt] = new Complex(sample, 0); + m_sample_cnt++; + + if(m_sample_cnt % 100 == 0){ + check_for_earthquake_fft(); + } + if (m_sample_cnt >= m_buffer_SIZE) + { + + m_sample_cnt = 0; + } + } + + public void check_for_earthquake_fft() + { + //Log.d("DetectorFFT", "check_for_earthquake_fft() was called"); + // Calculate the fft of the protected classmember m_buffer + // If the highest amplitude exceeds the threshold, send info via notifier + FFT fft = new FFT(); + /*double[] tmp = new double[512]; + tmp = new double[] {0,1,1.22464679914735e-16,-1,-2.44929359829471e-16,1,3.67394039744206e-16,-1,-4.89858719658941e-16,1,-1.16403343982657e-15,-1,-7.34788079488412e-16,1,-2.69546091939735e-15,-1,-9.79717439317883e-16,1,1.10218211923262e-15,-1,2.32806687965315e-15,1,-2.20560219973841e-15,-1,-1.46957615897682e-15,1,-1.96067283990894e-15,-1,5.39092183879471e-15,1,-1.71574348007947e-15,-1,-1.95943487863577e-15,1,-1.47081412025000e-15,-1,-2.20436423846524e-15,1,-1.22588476042053e-15,-1,4.65613375930630e-15,1,-9.80955400591059e-16,-1,4.41120439947683e-15,1,-7.84145339836259e-15,-1,-2.93915231795365e-15,1,-4.91096680932118e-16,-1,3.92134567981788e-15,1,-7.35159467870365e-15,-1,1.07818436775894e-14,1,-1.23796127317672e-18,-1,3.43148696015894e-15,1,-6.86173595904471e-15,-1,-3.91886975727153e-15,1,4.88620758385765e-16,-1,2.94162824050000e-15,1,-6.37187723938577e-15,-1,-4.40872847693047e-15,1,9.78479478044706e-16,-1,2.45176952084106e-15,1,-5.88201851972683e-15,-1,9.31226751861259e-15,1,-1.27425165174984e-14,-1,1.96191080118212e-15,1,8.81869491513412e-15,-1,8.82240879895365e-15,1,1.95819691736259e-15,-1,1.56829067967252e-14,1,-4.90230108040894e-15,-1,-5.87830463590730e-15,1,-1.17627990781805e-14,-1,9.82193361864236e-16,1,9.79841235445200e-15,-1,7.84269135963577e-15,1,2.93791435668047e-15,-1,1.47031893574073e-14,1,-3.92258364109106e-15,-1,2.15636873551788e-14,1,-1.07830816388626e-14,-1,2.47592254635343e-18,1,1.07781297937699e-14,-1,6.86297392031788e-15,1,3.91763179599835e-15,-1,1.37234719180894e-14,1,-2.94286620177318e-15,-1,-7.83773951454306e-15,1,-9.80336419954471e-15,-1,-9.77241516771529e-16,1,-1.66638621973162e-14,-1,5.88325648100000e-15,1,-2.35243601950878e-14,-1,1.27437544787715e-14,1,-1.96314876245530e-15,-1,-8.81745695386094e-15,1,-8.82364676022683e-15,-1,-1.95695895608941e-15,1,1.27375646724057e-14,-1,4.90353904168212e-15,1,5.87706667463412e-15,-1,1.17640370394537e-14,1,-9.83431323137413e-16,-1,1.86245350372252e-14,1,-7.84392932090894e-15,-1,2.54850330349967e-14,1,-1.47044273186805e-14,-1,3.92382160236424e-15,1,6.85678411395200e-15,-1,-1.76373898302682e-14,1,-2.84254233142235e-14,-1,1.76448175979073e-14,1,-6.86421188159106e-15,-1,-3.91639383472518e-15,1,1.46969995510414e-14,-1,3.13658135934504e-14,1,-2.05852078771341e-14,-1,9.80460216081789e-15,1,9.76003555498353e-16,-1,-1.17566092718146e-14,1,2.25372149881308e-14,-1,2.35255981563610e-14,1,-1.27449924400447e-14,-1,1.96438672372847e-15,1,8.81621899258777e-15,-1,-1.95968247089040e-14,1,-2.64659884355878e-14,-1,1.56853827192715e-14,1,-4.90477700295530e-15,-1,-5.87582871336094e-15,1,-4.01869844311308e-14,-1,2.94063787148146e-14,1,-1.86257729984984e-14,-1,7.84516728218212e-15,1,-5.39079804266739e-14,-1,4.31273747103577e-14,1,-3.23467689940414e-14,-1,2.15661632777252e-14,1,-6.76289764222170e-14,-1,4.95184509270686e-18,1,1.07756538712235e-14,-1,-2.15562595875398e-14,1,3.23368653038560e-14,-1,1.37259478406358e-14,1,-2.94534212431953e-15,-1,-7.83526359199671e-15,1,1.86158693083129e-14,-1,2.74469438361788e-14,1,-1.66663381198626e-14,-1,5.88573240354636e-15,1,4.89487331276988e-15,-1,-1.56754790290861e-14,1,-3.03873341154057e-14,-1,1.96067283990894e-14,1,-8.82612268277318e-15,-1,-1.95448303354306e-15,1,-4.41083301109487e-14,-1,3.33277243946325e-14,1,-2.25471186783162e-14,-1,1.17665129620000e-14,1,-5.78293261064918e-14,-1,4.70487203901755e-14,1,-3.62681146738593e-14,-1,2.54875089575431e-14,1,4.21365156195812e-14,-1,3.92629752491059e-15,1,6.85430819140565e-15,-1,-1.76349139077219e-14,1,2.84155196240381e-14,-1,1.76472935204537e-14,1,-6.86668780413741e-15,-1,-3.91391791217882e-15,1,1.46945236284951e-14,-1,-2.54751293448113e-14,1,-2.05876837996805e-14,-1,9.80707808336424e-15,1,9.73527632951999e-16,-1,-1.17541333492682e-14,1,-3.43086797952235e-14,-1,2.35280740789073e-14,1,-1.27474683625911e-14,-1,1.96686264627483e-15,1,-4.80296757907666e-14,-1,3.72490700744504e-14,1,-2.64684643581341e-14,-1,1.56878586418179e-14,1,-6.17506717863097e-14,-1,5.09700660699934e-14,1,-4.01894603536772e-14,-1,2.94088546373610e-14,1,-1.86282489210447e-14,-1,7.84764320472847e-15,1,2.93296251158776e-15,-1,-1.37135682279040e-14,1,2.44941739442202e-14,-1,-3.52747796605365e-14,1,4.60553853768527e-14,-1,5.68508466284471e-14,1,-4.60702409121308e-14,-1,3.52896351958146e-14,1,-2.45090294794984e-14,-1,1.37284237631821e-14,1,-2.94781804686588e-15,-1,-7.83278766945035e-15,1,1.86133933857666e-14,-1,-2.93939991020828e-14,1,-7.35122329032170e-14,-1,6.27316271869007e-14,1,-5.19510214705845e-14,-1,4.11704157542683e-14,1,-3.03898100379520e-14,-1,1.96092043216358e-14,1,-8.82859860531953e-15,-1,-1.95200711099671e-15,1,-1.00954224894303e-13,-1,-2.35132185436292e-14,1,3.42938242599454e-14,-1,-4.50744299762617e-14,1,5.58550356925779e-14,-1,4.70511963127219e-14,1,-3.62705905964057e-14,-1,2.54899848800894e-14,1,-1.47093791637732e-14,-1,3.92877344745694e-15,1,-1.06835005452757e-13,-1,-1.76324379851755e-14,1,-8.52737940201243e-14,-1,-3.91936494178080e-14,1,4.99742551341243e-14,-1,5.29319768711755e-14,1,7.15354665667567e-14,-1,3.13707654385431e-14,1,9.30966779993892e-14,-1,9.80955400591059e-15,1,9.71051710405646e-16,-1,-1.17516574267219e-14,1,2.25322631430381e-14,-1,8.03739688622617e-14,1,4.40934745756706e-14,-1,5.88127574296292e-14,1,6.56546860083031e-14,-1,3.72515459969967e-14,1,-2.64709402806805e-14,-1,1.56903345643642e-14,1,-4.90972884804800e-15,-1,1.07815960853348e-13,1,1.66514825845845e-14,-1,8.62547494207153e-14,1,3.82126940172170e-14,-1,6.46935379880828e-14,1,-5.39129322717666e-14,-1,4.31323265554504e-14,1,-3.23517208391341e-14,-1,1.35257952844434e-13,1,-1.07905094065017e-14,-1,9.90369018541372e-18,1,1.07707020261308e-14,-1,-2.15513077424471e-14,1,-8.13549242628527e-14,-1,-4.31125191750795e-14,1,-5.97937128302203e-14,-1,-6.46737306077120e-14,1,-3.82325013975878e-14,-1,2.74518956812715e-14,1,-1.66712899649553e-14,-1,5.89068424863906e-15,1,-1.08796916253939e-13,-1,-1.56705271839934e-14,1,-8.72357048213064e-14,-1,-3.72317386166259e-14,1,-6.56744933886739e-14,-1,5.48938876723577e-14,1,-4.41132819560414e-14,-1,3.33326762397252e-14,1,-2.25520705234090e-14,-1,1.17714648070927e-14,1,-1.14677696812393e-13,-1,-9.78974662553977e-15,1,-9.31164853797600e-14,-1}; + + for (int i = 0; i < m_buffer_SIZE; i++) + { + m_samples[i] = new Complex(tmp[i], 0); + }*/ + + Complex[] Y = fft.fft(m_samples); + + double[] Y_abs = new double[m_buffer_SIZE / 2]; + String Y_abs_str = ""; + String m_samples_str = ""; + for(int i = 0; i < (m_buffer_SIZE / 2.0); i++) + { + Y_abs[i] = Y[i].abs(); + Y_abs[i] = Y_abs[i] / (m_buffer_SIZE / 2.0); // scaling to real amplitudes of timedomain + + Y_abs_str += Y_abs[i] + ","; // For debugging only + m_samples_str += m_samples + ","; // For debugging only + + } + m_Notifier.onNewFFTData(Y_abs); + + Log.d("DetectorFFT", "m_sampleRate: " + m_sampleRate); + Log.d("DetectorFFT", "Frequency-Data was calculated."); + + + } +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/DetectorImpl.java b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/DetectorImpl.java new file mode 100644 index 0000000..13ed0da --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/DetectorImpl.java @@ -0,0 +1,46 @@ +package com.feemers.android.fftdrawer.SignalProcessing; + +import java.util.ArrayList; + + +public class DetectorImpl implements IDetector { + protected ArrayList m_buffer; + protected int m_buffer_SIZE; //TODO: Make this this dependent to the sampleRate + protected double m_sampleRate; + protected ThresholdCalculator m_thresholdCalculator; + protected double m_threshold; + protected Notifier m_Notifier; + + public DetectorImpl(int windowSize) + { + m_buffer = new ArrayList<>(); + m_buffer_SIZE = 128; //TODO later: Change this hardcoded value and calculate it during runtime -> depending on sampleRate + m_sampleRate = 0; + m_thresholdCalculator = new ThresholdCalculator(windowSize); + m_threshold = m_thresholdCalculator.getThreshold(); + } + + @Override + public void onNewSample(double sample) { + m_thresholdCalculator.onNewSample(sample); + m_threshold = m_thresholdCalculator.getThreshold(); + } + + @Override + public void setSampleRate(double sampleRate) { + m_sampleRate = sampleRate; + } + + @Override + public void setThreshold(double threshold) { + // Call this to set the constant part of the threshold determined by the user + m_thresholdCalculator.setConstantThresholdPart(threshold); + } + + public void connectNotifier(Notifier notifier){ + if(notifier == null){ + throw new AssertionError("Notifier is null"); + } + m_Notifier = notifier; + } +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/DetectorTimedomain.java b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/DetectorTimedomain.java new file mode 100644 index 0000000..2c41217 --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/DetectorTimedomain.java @@ -0,0 +1,72 @@ +package com.feemers.android.fftdrawer.SignalProcessing; + +import android.util.Log; +import android.util.Pair; + +import java.util.ArrayList; + +public class DetectorTimedomain extends DetectorImpl { + private boolean m_calculate_amplitude; + private int m_threshold_cnt; + private int m_sample_cnt; + + public DetectorTimedomain(int windowSize) { + super(windowSize); + Log.d("DetectorTimedomain", "DetectorTimedomain() - Constructor called"); + m_calculate_amplitude = false; + m_threshold_cnt = 0; + m_sample_cnt = 0; + } + + @Override + public void onNewSample(double sample) { + super.onNewSample(sample); + check_for_earthquake(sample); + } + + public void check_for_earthquake(double sample) + { + // This method checks if threshold was exceeded and calculates the amplitude if enough values are available + // Possible combinitions of the return values: + // 1: false, 0.0 -> Threshold wasn't exceeded + // 2: true, 0.0 -> Threshold was exceeded, but no value for amplitude is available yet (because m_buffer isn't filled yet) + // 3: true, amplitude -> Threshold was exceeded and a value for amplitude is available + //TODO: find better solution for returning the results -> temp solution: No return-values but printing to logcat instead. Use Notifier later. + + Pair return_pair = new Pair(false, 0.0); + if ((sample > m_threshold) && !(m_calculate_amplitude)) { + Log.d("DetectorTimedomain", "threshold was exceeded. threshold: " + m_threshold); //TODO: send this info with notifier instead + m_threshold_cnt++; + // Later: add Logger entry + m_calculate_amplitude = true; + m_buffer = new ArrayList<>(); //resetting to an empty ArrayList + } + + if (m_calculate_amplitude) { + if (m_sample_cnt >= m_buffer_SIZE) { + //Vector sensor_samples is filled -> determine amplitude + double amplitude = calculate_maximum(m_buffer); + m_sample_cnt = 0; + m_calculate_amplitude = false; + Log.d("DetectorTimedomain", "Maximum calculated: " + amplitude); //TODO: send this message with notifier instead + return_pair = new Pair(true, amplitude); + // Later: add Logger entry + } else { + m_buffer.add(sample); + m_sample_cnt++; + return_pair = new Pair(true, 0.0); + } + } + m_Notifier.sendEvent(); + //return return_pair; + } + + private double calculate_maximum(ArrayList samples) { + double maximum = 0; + for (double sample : samples) { + if (maximum < sample) + maximum = sample; + } + return maximum; + } +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/FFT.java b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/FFT.java new file mode 100644 index 0000000..a9d09b1 --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/FFT.java @@ -0,0 +1,206 @@ +package com.feemers.android.fftdrawer.SignalProcessing; + + +/****************************************************************************** + * Compilation: javac FFT.java + * Execution: java FFT n + * Dependencies: Complex.java + * + * Compute the FFT and inverse FFT of a length n complex sequence + * using the radix 2 Cooley-Tukey algorithm. + + * Bare bones implementation that runs in O(n log n) time and O(n) + * space. Our goal is to optimize the clarity of the code, rather + * than performance. + * + * This implementation uses the primitive root of unity w = e^(-2 pi i / n). + * Some resources use w = e^(2 pi i / n). + * + * Reference: https://www.cs.princeton.edu/~wayne/kleinberg-tardos/pdf/05DivideAndConquerII.pdf + * + * Limitations + * ----------- + * - assumes n is a power of 2 + * + * - not the most memory efficient algorithm (because it uses + * an object type for representing complex numbers and because + * it re-allocates memory for the subarray, instead of doing + * in-place or reusing a single temporary array) + * + * For an in-place radix 2 Cooley-Tukey FFT, see + * https://introcs.cs.princeton.edu/java/97data/InplaceFFT.java.html + * + ******************************************************************************/ + +public class FFT { + + // compute the FFT of x[], assuming its length n is a power of 2 + public static Complex[] fft(Complex[] x) { + int n = x.length; + + // base case + if (n == 1) return new Complex[] { x[0] }; + + // radix 2 Cooley-Tukey FFT + if (n % 2 != 0) { + throw new IllegalArgumentException("n is not a power of 2"); + } + + // compute FFT of even terms + Complex[] even = new Complex[n/2]; + for (int k = 0; k < n/2; k++) { + even[k] = x[2*k]; + } + Complex[] evenFFT = fft(even); + + // compute FFT of odd terms + Complex[] odd = even; // reuse the array (to avoid n log n space) + for (int k = 0; k < n/2; k++) { + odd[k] = x[2*k + 1]; + } + Complex[] oddFFT = fft(odd); + + // combine + Complex[] y = new Complex[n]; + for (int k = 0; k < n/2; k++) { + double kth = -2 * k * Math.PI / n; + Complex wk = new Complex(Math.cos(kth), Math.sin(kth)); + y[k] = evenFFT[k].plus (wk.times(oddFFT[k])); + y[k + n/2] = evenFFT[k].minus(wk.times(oddFFT[k])); + } + return y; + } + + + // compute the inverse FFT of x[], assuming its length n is a power of 2 + public static Complex[] ifft(Complex[] x) { + int n = x.length; + Complex[] y = new Complex[n]; + + // take conjugate + for (int i = 0; i < n; i++) { + y[i] = x[i].conjugate(); + } + + // compute forward FFT + y = fft(y); + + // take conjugate again + for (int i = 0; i < n; i++) { + y[i] = y[i].conjugate(); + } + + // divide by n + for (int i = 0; i < n; i++) { + y[i] = y[i].scale(1.0 / n); + } + + return y; + + } + + // compute the circular convolution of x and y + public static Complex[] cconvolve(Complex[] x, Complex[] y) { + + // should probably pad x and y with 0s so that they have same length + // and are powers of 2 + if (x.length != y.length) { + throw new IllegalArgumentException("Dimensions don't agree"); + } + + int n = x.length; + + // compute FFT of each sequence + Complex[] a = fft(x); + Complex[] b = fft(y); + + // point-wise multiply + Complex[] c = new Complex[n]; + for (int i = 0; i < n; i++) { + c[i] = a[i].times(b[i]); + } + + // compute inverse FFT + return ifft(c); + } + + + // compute the linear convolution of x and y + public static Complex[] convolve(Complex[] x, Complex[] y) { + Complex ZERO = new Complex(0, 0); + + Complex[] a = new Complex[2*x.length]; + for (int i = 0; i < x.length; i++) a[i] = x[i]; + for (int i = x.length; i < 2*x.length; i++) a[i] = ZERO; + + Complex[] b = new Complex[2*y.length]; + for (int i = 0; i < y.length; i++) b[i] = y[i]; + for (int i = y.length; i < 2*y.length; i++) b[i] = ZERO; + + return cconvolve(a, b); + } + + // compute the DFT of x[] via brute force (n^2 time) + public static Complex[] dft(Complex[] x) { + int n = x.length; + Complex ZERO = new Complex(0, 0); + Complex[] y = new Complex[n]; + for (int k = 0; k < n; k++) { + y[k] = ZERO; + for (int j = 0; j < n; j++) { + int power = (k * j) % n; + double kth = -2 * power * Math.PI / n; + Complex wkj = new Complex(Math.cos(kth), Math.sin(kth)); + y[k] = y[k].plus(x[j].times(wkj)); + } + } + return y; + } + + + /*************************************************************************** + * Test client and sample execution + * + * % java FFT 4 + * x + * ------------------- + * -0.03480425839330703 + * 0.07910192950176387 + * 0.7233322451735928 + * 0.1659819820667019 + * + * y = fft(x) + * ------------------- + * 0.9336118983487516 + * -0.7581365035668999 + 0.08688005256493803i + * 0.44344407521182005 + * -0.7581365035668999 - 0.08688005256493803i + * + * z = ifft(y) + * ------------------- + * -0.03480425839330703 + * 0.07910192950176387 + 2.6599344570851287E-18i + * 0.7233322451735928 + * 0.1659819820667019 - 2.6599344570851287E-18i + * + * c = cconvolve(x, x) + * ------------------- + * 0.5506798633981853 + * 0.23461407150576394 - 4.033186818023279E-18i + * -0.016542951108772352 + * 0.10288019294318276 + 4.033186818023279E-18i + * + * d = convolve(x, x) + * ------------------- + * 0.001211336402308083 - 3.122502256758253E-17i + * -0.005506167987577068 - 5.058885073636224E-17i + * -0.044092969479563274 + 2.1934338938072244E-18i + * 0.10288019294318276 - 3.6147323062478115E-17i + * 0.5494685269958772 + 3.122502256758253E-17i + * 0.240120239493341 + 4.655566391833896E-17i + * 0.02755001837079092 - 2.1934338938072244E-18i + * 4.01805098805014E-17i + * + ***************************************************************************/ + +} \ No newline at end of file diff --git a/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/IDetector.java b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/IDetector.java new file mode 100644 index 0000000..07d4e0e --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/IDetector.java @@ -0,0 +1,8 @@ +package com.feemers.android.fftdrawer.SignalProcessing; + +public interface IDetector { + public void onNewSample(double sample); + public void setSampleRate(double sampleRate); + public void setThreshold(double threshold); + public void connectNotifier(Notifier notifier); +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/ISensor.java b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/ISensor.java new file mode 100644 index 0000000..260a91b --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/ISensor.java @@ -0,0 +1,11 @@ +package com.feemers.android.fftdrawer.SignalProcessing; + +public interface ISensor { + public double getSampleRate(); + + public void connectDetector(IDetector detector); + + public void startSampling(); + + public void stopSampling(); +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/Logger.java b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/Logger.java new file mode 100644 index 0000000..a5976ea --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/Logger.java @@ -0,0 +1,76 @@ +package com.feemers.android.fftdrawer.SignalProcessing; + +import android.app.Activity; +import android.util.Log; +import android.widget.TextView; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public class Logger +{ + private TextView textView; + private StringBuffer sb = new StringBuffer(); + private String tag; + private Activity activity; + + public Logger(String tag, String logInitText) + { + this.activity = null; + this.tag = tag; + sb.append(logInitText); + } + + public Logger(Activity activity, String tag, TextView textView, String logInitText) + { + this.activity = activity; + this.tag = tag; + this.textView = textView; + sb.append(logInitText); + } + + public void log(String s) + { + Log.d(tag, s); + sb.append(s).append("\n"); + if (textView != null) + { + if(activity !=null) + { + activity.runOnUiThread(new Runnable() + { + @Override + public void run() + { + textView.setText(sb.toString()); + } + }); + } + else + { + textView.setText(sb.toString()); + } + } + } + + public void log(Exception e) + { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + log(sw.toString()); + } + + public void clearLog() + { + sb.setLength(0); + if (textView != null) + { + textView.setText(""); + } + } + + public String getLoggedText() + { + return sb.toString(); + } +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/MySensor.java b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/MySensor.java new file mode 100644 index 0000000..382983d --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/MySensor.java @@ -0,0 +1,95 @@ +package com.feemers.android.fftdrawer.SignalProcessing; + +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.util.Log; + +public class MySensor extends SensorImpl implements SensorEventListener { + private SensorManager sensorManager; + private Sensor m_sensor; + private int sensorType = Sensor.TYPE_ACCELEROMETER; + private long time_former_value; + private long time_current_value; + + private int sample_cnt; + + public MySensor() + { + m_sensor = null; + time_former_value = 0; + time_current_value = 0; + sample_cnt = 0; + } + + @Override + public void onSensorChanged(SensorEvent event) + { + //Log.d("MySensor","onSensorChanged()\n"); + + // Calculating the sample rate + time_current_value = System.currentTimeMillis(); + long period = time_current_value - time_former_value; + if ((time_former_value != 0) && (period != 0)) { + m_sampleRate = (double) (1.0 / period) * 1000.0; // in Hz + m_detector.setSampleRate(m_sampleRate); + } + + /*if (sample_cnt == 100) //TODO: Delete this part before final use (inc. sample_cnt) -> only meant for debugging + { + Log.d("MySensor", "m_sampleRate:" + m_sampleRate + "\n"); + Log.d("MySensor", "time_current_value:" + time_current_value + "\n"); + Log.d("MySensor", "time_former_value:" + time_former_value + "\n"); + Log.d("MySensor", "currentTimeMillis:" + System.currentTimeMillis() + "\n"); + Log.d("MySensor", "period:" + period + "\n"); + sample_cnt = 0; + }*/ + + time_former_value = time_current_value; + // Calculating the acceleration + double acceleration = (Math.sqrt(event.values[0] * event.values[0] + event.values[1] * (event.values[1] + event.values[2] * event.values[2]))) - 9.81; + m_detector.onNewSample(acceleration); + sample_cnt++; + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + Log.d("MySensor","onAccuracyChanged()\n"); + } + + @Override + public double getSampleRate() { + return m_sampleRate; + } + + @Override + public void startSampling() { + if (m_sensor != null) { + if (sensorManager.registerListener(this, m_sensor, SensorManager.SENSOR_DELAY_GAME)) { + Log.d("MySensor", "Bei Sensor angemeldet.\n"); + } else { + Log.d("MySensor", "Kann dem Sensor keinen Beobachter hinzufügen\n"); + } + } + } + + @Override + public void stopSampling() { + if (m_sensor != null) { + sensorManager.unregisterListener(this, m_sensor); + Log.d("MySensor","Von Sensor abgemeldet.\n"); + } + } + + + public void connectSensorManager(SensorManager sensorManager){ + this.sensorManager = sensorManager; + if (sensorManager.getSensorList(sensorType).size() == 0) { + Log.d("MySensor", "Es gibt den Sensor nicht.\n"); + m_sensor = null; + } else { + m_sensor = sensorManager.getSensorList(sensorType).get(0); + } + } +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/Notifier.java b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/Notifier.java new file mode 100644 index 0000000..783d2b3 --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/Notifier.java @@ -0,0 +1,26 @@ +package com.feemers.android.fftdrawer.SignalProcessing; + +import android.util.Log; + +import java.util.Observable; + +public class Notifier extends Observable { + private double[] data; + + public Notifier(){ + + } + public void sendEvent(){ + Log.d("Hi", "sendEvent: Test"); + } + + public void onNewFFTData(double[] data){ + this.data = data; + setChanged(); + notifyObservers(); + } + + public double[] getData() { + return data; + } +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/SensorFake.java b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/SensorFake.java new file mode 100644 index 0000000..d5528aa --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/SensorFake.java @@ -0,0 +1,18 @@ +package com.feemers.android.fftdrawer.SignalProcessing; + +public class SensorFake extends SensorImpl { + @Override + public double getSampleRate() { + return 0; + } + + @Override + public void startSampling() { + + } + + @Override + public void stopSampling() { + + } +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/SensorImpl.java b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/SensorImpl.java new file mode 100644 index 0000000..d947a5c --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/SensorImpl.java @@ -0,0 +1,19 @@ +package com.feemers.android.fftdrawer.SignalProcessing; + +public abstract class SensorImpl implements ISensor { + protected IDetector m_detector; + protected double m_sampleRate; + + public SensorImpl() + { + m_detector = null; + m_sampleRate = 0; + } + + public void connectDetector(IDetector detector){ + if(detector == null){ + throw new AssertionError("detector == null"); + } + m_detector = detector; + } +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/SignalProcessingControl.java b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/SignalProcessingControl.java new file mode 100644 index 0000000..0012d92 --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/SignalProcessingControl.java @@ -0,0 +1,51 @@ +package com.feemers.android.fftdrawer.SignalProcessing; + +import android.hardware.SensorManager; + +import java.util.Observer; + + +public class SignalProcessingControl { + private IDetector detector; + private ISensor sensor; + private Notifier notifier; + private Logger logger; + + public SignalProcessingControl(SensorManager sensorManager, Logger logger) { + this.logger = logger; + logger.log("SignalProcessingControl()"); + + // create notifier + notifier = new Notifier(); + + // create detector + //detector = new DetectorTimedomain(20); + detector = new DetectorFFT(20); + detector.setThreshold(6); //TODO: Remove hardcoded threshold and read from GUI instead + detector.connectNotifier(notifier); + + // create & link hardware to sensor -> sensorManager + MySensor mySensor = new MySensor(); + mySensor.connectSensorManager(sensorManager); + this.sensor = mySensor; + } + + public void init() { + logger.log("SignalProcessingControl: init()"); + sensor.connectDetector(detector); + } + + public void start() { + logger.log("SignalProcessingControl: start()"); + sensor.startSampling(); + } + + public void stop() { + logger.log("SignalProcessingControl: stop()"); + sensor.stopSampling(); + } + + public Notifier getNotifier() { + return notifier; + } +} diff --git a/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/ThresholdCalculator.java b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/ThresholdCalculator.java new file mode 100644 index 0000000..03c1522 --- /dev/null +++ b/app/src/main/java/com/feemers/android/fftdrawer/SignalProcessing/ThresholdCalculator.java @@ -0,0 +1,67 @@ +package com.feemers.android.fftdrawer.SignalProcessing; + +class ThresholdCalculator { + private double[] m_buffer; + private int m_windowSize; + private int m_bufferCounter; + private int m_bufferInitCounter; + private double m_constantThresholdPart; + private double m_adaptiveThresholdPart; + + public ThresholdCalculator(int windowSize) { + if (windowSize <= 0) { + throw new AssertionError("windowSize should be postive"); + } + m_windowSize = windowSize; + reset(); + } + + public double getThreshold() { + // This method is meant for the use inside of a detector implementation + // Should be called every time a new sensor value is available + // Returns the sum of the constant part of the threshold (determined by the user) and the + // adaptive part of the threshold (determined by the noise level of the signal) + return m_constantThresholdPart + m_adaptiveThresholdPart; + } + + public void setConstantThresholdPart(double constantThresholdPart) { + // This method should be called when the User changes the constant part of the threshold with the GUI + this.m_constantThresholdPart = constantThresholdPart; + } + + public void onNewSample(double sample) { + // store sample + m_buffer[m_bufferCounter] = sample; + // increase counter + m_bufferCounter++; + // check if we are on the end of Array -> go to start + if (m_bufferCounter >= m_windowSize) { + m_bufferCounter = 0; + } + + // when we start the application we will have an arry with emty values + // hence we define how many values are already new + // after N_sample == windowSize this will have no further effect + if (m_bufferInitCounter < m_windowSize) { + m_bufferInitCounter++; + } + + // now calculate Mean + double absMean = 0; + for (int i = 0; i < m_bufferInitCounter; i++) { + double sampleVal = m_buffer[i]; + absMean += Math.sqrt(sampleVal * sampleVal); + } + m_adaptiveThresholdPart = absMean / m_bufferInitCounter; + } + + // for reset this class reset + public void reset() { + m_bufferCounter = 0; + m_bufferInitCounter = 0; + m_buffer = new double[m_windowSize]; + for (int i = 0; i < m_windowSize; i++) { + m_buffer[i] = 0; + } + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..6e43243 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,32 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a571e60 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..61da551 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c41dd28 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..db5080a Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6dba46d Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..da31a87 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..15ac681 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b216f2d Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..f25a419 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e96783c Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/attributes.xml b/app/src/main/res/values/attributes.xml new file mode 100644 index 0000000..9fefef5 --- /dev/null +++ b/app/src/main/res/values/attributes.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..030098f --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #6200EE + #3700B3 + #03DAC5 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..1d9cbd4 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + FFTDrawer + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/test/java/com/feemers/android/fftdrawer/ExampleUnitTest.java b/app/src/test/java/com/feemers/android/fftdrawer/ExampleUnitTest.java new file mode 100644 index 0000000..e5d9743 --- /dev/null +++ b/app/src/test/java/com/feemers/android/fftdrawer/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.feemers.android.fftdrawer; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..9494940 --- /dev/null +++ b/build.gradle @@ -0,0 +1,29 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + jcenter() + + } + dependencies { + classpath 'com.android.tools.build:gradle:3.6.1' + + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..199d16e --- /dev/null +++ b/gradle.properties @@ -0,0 +1,20 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..68acb52 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jun 08 18:27:45 CEST 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..7a36de4 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name='FFTDrawer' +include ':app'