diff --git a/android/ConductorsWatch/app/build.gradle b/android/ConductorsWatch/app/build.gradle index 95aac04..7eec5f0 100644 --- a/android/ConductorsWatch/app/build.gradle +++ b/android/ConductorsWatch/app/build.gradle @@ -41,5 +41,5 @@ dependencies { implementation 'com.android.support:recyclerview-v7:26.1.0' implementation 'com.android.support:wear:26.1.0' compileOnly 'com.google.android.wearable:wearable:2.1.0' - //compile 'com.sdsmdg.harjot:croller:1.0.7' + compile 'com.github.wendykierp:JTransforms:3.1' } diff --git a/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/BpmEstimator.java b/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/BpmEstimator.java index fd4641e..4a9f6ec 100644 --- a/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/BpmEstimator.java +++ b/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/BpmEstimator.java @@ -1,39 +1,101 @@ package de.tonifetzer.conductorswatch; +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Handler; + import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadLocalRandom; +import de.tonifetzer.conductorswatch.utilities.Utils; + /** * Created by toni on 13/11/17. */ -public class BpmEstimator implements Runnable { +//TODO: diesen estimator testen. kommen alle messungen? wie sind die messungen zeitlich voneinander verschieden? +//TODO: klapp das wirklich mit den 4ms. passt der buffer? gehen auch höhere zeiten? +//TODO: einfügen der logik autoCorr + FindPeaks +public class BpmEstimator implements SensorEventListener { - private volatile boolean mRunning = true; + private SensorManager mSensorManager; + private Sensor mAccelerometer; + private Context mContext; + private Utils.AccelerometerWindowBuffer mAccelerometerWindowBuffer; - @Override - public void run() { - while(mRunning){ - int randomNum = ThreadLocalRandom.current().nextInt(0, 240 + 1); + private Handler mHandler; + private int mUpdaterate_ms = 100; + private boolean mSensorUpdateFlag = false; - for (OnBpmEstimatorListener listener:listeners) { - listener.onNewDataAvailable((float) randomNum); + + private final Runnable mProcessSensors = new Runnable() { + @Override + public void run() { + + if(mAccelerometerWindowBuffer.isNextWindowReady()){ + + //int randomNum = ThreadLocalRandom.current().nextInt(0, 240 + 1); + long diff = mAccelerometerWindowBuffer.getYongest().ts - mAccelerometerWindowBuffer.getOldest().ts; + + for (OnBpmEstimatorListener listener:listeners) { + listener.onNewDataAvailable((float) diff); + } } - try { - Thread.sleep(60000 / 60); - } catch (InterruptedException e) { - e.printStackTrace(); - } + mSensorUpdateFlag = true; + // The Runnable is posted to run again here: + mHandler.postDelayed(this, mUpdaterate_ms); } + }; + + public BpmEstimator(Context mContext){ + this.mContext = mContext; } + public void start() { + + mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); + mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION); + mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_FASTEST); + + mAccelerometerWindowBuffer = new Utils.AccelerometerWindowBuffer(4096, 256); + + mHandler = new Handler(); + mHandler.post(mProcessSensors); //start runnable + } + + public void stop() { - mRunning = false; + mHandler.removeCallbacks(mProcessSensors); //remove runnable + mSensorManager.unregisterListener(this); } + @Override + public void onSensorChanged(SensorEvent se) { + + // einkommentieren, falls die updaterate beschränkt werden soll. aktuell maximum speed + //if(mSensorUpdateFlag) { + + //ca 200hz, every 5 to 6 ms we have an update + if (se.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) { + mAccelerometerWindowBuffer.add(new Utils.AccelerometerData(System.currentTimeMillis(), se.values[0], se.values[1], se.values[2])); + } + + // mSensorUpdateFlag = false; + //} + } + + @Override + public void onAccuracyChanged(Sensor sensor, int i) { + // do nothin + } + + /** * Interface for callback calculated bpm */ diff --git a/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/MainActivity.java b/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/MainActivity.java index 1af5423..64d0c7e 100644 --- a/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/MainActivity.java +++ b/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/MainActivity.java @@ -121,6 +121,7 @@ public class MainActivity extends WearableActivity implements WorkerFragment.OnF mHandler.removeCallbacks(mLongPressed); if(mLongPressHandlerActivated){ mLongPressHandlerActivated = false; + mPreviousMovePoint = null; return true; } return false; diff --git a/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/WorkerFragment.java b/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/WorkerFragment.java index 4ec9cfe..9a0cae6 100644 --- a/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/WorkerFragment.java +++ b/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/WorkerFragment.java @@ -29,7 +29,7 @@ public class WorkerFragment extends Fragment implements Metronome.OnMetronomeLis private BpmEstimator mBpmEstimator; private Metronome mMetronome; - private Thread mBpmThread; + //private Thread mBpmThread; private Thread mMetronomeThread; private Vibrator mVibrator; @@ -58,9 +58,9 @@ public class WorkerFragment extends Fragment implements Metronome.OnMetronomeLis super.onCreate(savedInstanceState); // init bpm estimator and listener - mBpmEstimator = new BpmEstimator(); + mBpmEstimator = new BpmEstimator(getContext()); mBpmEstimator.add(this); - mBpmThread = new Thread(mBpmEstimator, "estThread"); + //mBpmThread = new Thread(mBpmEstimator, "estThread"); mBpmList = new Vector(); @@ -99,7 +99,7 @@ public class WorkerFragment extends Fragment implements Metronome.OnMetronomeLis super.onStart(); // start the worker thread for bpm estimator - mBpmThread.start(); + mBpmEstimator.start(); // start the worker thread for metronom mMetronomeThread.start(); @@ -111,12 +111,12 @@ public class WorkerFragment extends Fragment implements Metronome.OnMetronomeLis // stop the worker thread for bpm estimator mBpmEstimator.stop(); - mBpmThread.interrupt(); + /*mBpmThread.interrupt(); try { mBpmThread.join(); } catch (InterruptedException e) { e.printStackTrace(); - } + }*/ // stop the worker thread for metronom mMetronome.stop(); diff --git a/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/utilities/Utils.java b/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/utilities/Utils.java index b3305d9..f71b8fa 100644 --- a/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/utilities/Utils.java +++ b/android/ConductorsWatch/app/src/main/java/de/tonifetzer/conductorswatch/utilities/Utils.java @@ -2,12 +2,13 @@ package de.tonifetzer.conductorswatch.utilities; import android.content.Context; import android.content.res.Resources; -import android.graphics.Color; -import android.graphics.Point; import android.util.DisplayMetrics; -import android.view.MotionEvent; -import de.tonifetzer.conductorswatch.Croller; +import org.jtransforms.fft.DoubleFFT_1D; + +import java.util.ArrayList; +import java.util.Queue; + public class Utils { public static float getDistance(float x1, float y1, float x2, float y2) { @@ -26,4 +27,86 @@ public class Utils { return px / ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT); } + public static class AccelerometerData { + + public float x,y,z; + public long ts; + + public AccelerometerData(long ts, float x, float y, float z){ + this.ts = ts; + this.x = x; + this.y = y; + this.z = z; + } + } + + //TODO: implement methods providing x,y,z and ts as solo vectors + //TODO: implement sliding window counter + public static class AccelerometerWindowBuffer extends ArrayList { + + private int mWindowSize; + private int mOverlapSize; + private int mOverlapCounter; + + public AccelerometerWindowBuffer(int windowSize, int overlap){ + this.mWindowSize = windowSize; + this.mOverlapSize = overlap; + mOverlapCounter = 1; + } + + public boolean add(AccelerometerData ad){ + boolean r = super.add(ad); + if (size() > mWindowSize){ + removeRange(0, size() - mWindowSize); + } + + ++mOverlapCounter; + return r; + } + + public boolean isNextWindowReady(){ + if(size() == mWindowSize && mOverlapCounter > mOverlapSize){ + mOverlapCounter = 1; + return true; + } + return false; + } + + public AccelerometerData getYongest() { + return get(size() - 1); + } + + public AccelerometerData getOldest() { + return get(0); + } + } + + + public static double sqr(double x) { + return x * x; + } + + //TODO: implement maxLag as input + //TODO: implement positive and negative lag output + public void fftAutoCorrelation(double [] x, double [] ac) { + int n = x.length; + // Assumes n is even. + DoubleFFT_1D fft = new DoubleFFT_1D(n); + fft.realForward(x); + //ac[0] = sqr(x[0]); // For normal xcov + ac[0] = 0; // For statistical convention, zero out the mean + ac[1] = sqr(x[1]); + for (int i = 2; i < n; i += 2) { + ac[i] = sqr(x[i]) + sqr(x[i+1]); + ac[i+1] = 0; + } + DoubleFFT_1D ifft = new DoubleFFT_1D(n); + ifft.realInverse(ac, true); + //For statistical convention, normalize by dividing through with variance + for (int i = 1; i < n; i++){ + ac[i] /= ac[0]; + } + ac[0] = 1; + } + } diff --git a/octave/AutoCorrMethodNew_Watch.m b/octave/AutoCorrMethodNew_Watch.m index ab02fd4..f55a51f 100644 --- a/octave/AutoCorrMethodNew_Watch.m +++ b/octave/AutoCorrMethodNew_Watch.m @@ -6,7 +6,7 @@ %load file provided by the sensor readout app %% SMARTWATCH LG WEAR ------> 100 hz - 1000hz -measurements = dlmread('../measurements/lgWear/PR_recording_80bpm_4-4_177596720.csv', ';'); %* +measurements = dlmread('../../measurements/lgWear/PR_recording_80bpm_4-4_177596720.csv', ';'); %* %measurements = dlmread('../measurements/lgWear/recording_48bpm_4-4_176527527.csv', ';'); %measurements = dlmread('../measurements/lgWear/recording_48bpm_4-4_176606785.csv', ';'); %measurements = dlmread('../measurements/lgWear/recording_48bpm_4-4_176696356.csv', ';');