small refactoring
This commit is contained in:
@@ -16,7 +16,7 @@ public class Metronome extends TimerTask {
|
|||||||
boolean loaded = false;
|
boolean loaded = false;
|
||||||
private int soundID;
|
private int soundID;
|
||||||
|
|
||||||
public Metronome(Context ){
|
public Metronome(Context context){
|
||||||
soundPool = new SoundPool.Builder().setMaxStreams(10).build();
|
soundPool = new SoundPool.Builder().setMaxStreams(10).build();
|
||||||
soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
|
soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,250 +0,0 @@
|
|||||||
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 java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Timer;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
|
|
||||||
import de.tonifetzer.conductorswatch.bpmEstimation.AccelerometerData;
|
|
||||||
import de.tonifetzer.conductorswatch.bpmEstimation.AccelerometerWindowBuffer;
|
|
||||||
import de.tonifetzer.conductorswatch.bpmEstimation.BpmEstimator;
|
|
||||||
import de.tonifetzer.conductorswatch.network.SensorDataFileStreamer;
|
|
||||||
import de.tonifetzer.conductorswatch.utilities.ByteStreamWriter;
|
|
||||||
import de.tonifetzer.conductorswatch.utilities.Utils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by toni on 13/11/17.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class Estimator implements SensorEventListener {
|
|
||||||
|
|
||||||
private SensorManager mSensorManager;
|
|
||||||
private Sensor mAccelerometer;
|
|
||||||
private Sensor mRawAccelerometer;
|
|
||||||
private Context mContext;
|
|
||||||
private AccelerometerWindowBuffer mAccelerometerWindowBuffer;
|
|
||||||
private BpmEstimator mBpmEstimator;
|
|
||||||
private double mCurrentBpm;
|
|
||||||
|
|
||||||
private ByteStreamWriter mByteStreamWriterAcc;
|
|
||||||
private ByteStreamWriter mByteStreamWriterGyro;
|
|
||||||
private SensorDataFileStreamer mStreamer;
|
|
||||||
|
|
||||||
private Timer mTimer = new Timer();
|
|
||||||
|
|
||||||
|
|
||||||
public Estimator(Context mContext){
|
|
||||||
this.mContext = mContext;
|
|
||||||
this.mCurrentBpm = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
|
|
||||||
mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
|
|
||||||
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
|
|
||||||
mRawAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
|
||||||
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_FASTEST);
|
|
||||||
mSensorManager.registerListener(this, mRawAccelerometer, SensorManager.SENSOR_DELAY_FASTEST);
|
|
||||||
|
|
||||||
|
|
||||||
mAccelerometerWindowBuffer = new AccelerometerWindowBuffer(6000, 750);
|
|
||||||
mBpmEstimator = new BpmEstimator(mAccelerometerWindowBuffer, 0, 5000);
|
|
||||||
|
|
||||||
mTimer.scheduleAtFixedRate(new TimerTask() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
if (mAccelerometerWindowBuffer.isNextWindowReady()) {
|
|
||||||
|
|
||||||
LinkedList<Double> bpmList = new LinkedList<>();
|
|
||||||
|
|
||||||
//todo: wie viele dieser Klassen kann ich wegwerfen um das Ergebnis nich schlechter zu machen?
|
|
||||||
double bpm60 = mBpmEstimator.estimate(mAccelerometerWindowBuffer.getFixedSizedWindow());
|
|
||||||
double bpm85 = mBpmEstimator.estimate(mAccelerometerWindowBuffer.getFixedSizedWindow(3500, 750));
|
|
||||||
double bpm110 = mBpmEstimator.estimate(mAccelerometerWindowBuffer.getFixedSizedWindow(2600, 750));
|
|
||||||
double bpm135 = mBpmEstimator.estimate(mAccelerometerWindowBuffer.getFixedSizedWindow(2000, 750));
|
|
||||||
double bpm160 = mBpmEstimator.estimate(mAccelerometerWindowBuffer.getFixedSizedWindow(1600,750));
|
|
||||||
double bpm200 = mBpmEstimator.estimate(mAccelerometerWindowBuffer.getFixedSizedWindow(1200, 750));
|
|
||||||
|
|
||||||
//add to list
|
|
||||||
//todo: make this cool...
|
|
||||||
//vielleicht einen weighted mean?
|
|
||||||
bpmList.add(bpm60);
|
|
||||||
bpmList.add(bpm85);
|
|
||||||
bpmList.add(bpm110); //110, 135, 160 langen auch schon
|
|
||||||
bpmList.add(bpm135);
|
|
||||||
bpmList.add(bpm160);
|
|
||||||
bpmList.add(bpm200);
|
|
||||||
|
|
||||||
//Log.d("BPM: ", bpmList.toString());
|
|
||||||
|
|
||||||
//remove all -1 and calc bpmMean
|
|
||||||
while(bpmList.remove(Double.valueOf(-1))) {}
|
|
||||||
|
|
||||||
//remove outliers
|
|
||||||
//todo: aktuell wird die liste hier sortiert.. eig net so schön.
|
|
||||||
Utils.removeOutliersZScore(bpmList, 3.4);
|
|
||||||
//Utils.removeOutliersHeuristic();
|
|
||||||
|
|
||||||
//Log.d("BPM: ", bpmList.toString());
|
|
||||||
|
|
||||||
double bpm = -1;
|
|
||||||
if(!bpmList.isEmpty()) {
|
|
||||||
double bpmMean = Utils.mean(bpmList);
|
|
||||||
double bpmMedian = Utils.median(bpmList);
|
|
||||||
|
|
||||||
double bpmDiffSlowFast = bpmList.getFirst() - bpmList.getLast();
|
|
||||||
if (Math.abs(bpmDiffSlowFast) > 25) {
|
|
||||||
|
|
||||||
double tmpBPM = bpmMean + 25;
|
|
||||||
|
|
||||||
while(bpm == -1){
|
|
||||||
if (tmpBPM < 60) {
|
|
||||||
bpm = bpm60;
|
|
||||||
if(bpm == -1){
|
|
||||||
bpm = bpmMean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (tmpBPM < 85) {
|
|
||||||
bpm = bpm85;
|
|
||||||
} else if (tmpBPM < 110) {
|
|
||||||
bpm = bpm110;
|
|
||||||
} else if (tmpBPM < 135) {
|
|
||||||
bpm = bpm135;
|
|
||||||
} else if (tmpBPM < 160) {
|
|
||||||
bpm = bpm160;
|
|
||||||
} else {
|
|
||||||
bpm = bpm200;
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpBPM -= 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Log.d("BPM: ", "CHANGE");
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
bpm = bpmMean;
|
|
||||||
//Log.d("BPM: ", "STAY");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for (OnBpmEstimatorListener listener : listeners) {
|
|
||||||
listener.onNewDataAvailable(bpm); //135 gibt gute ergebnisse!
|
|
||||||
}
|
|
||||||
|
|
||||||
//update the windowSize and updaterate depending on current bpm
|
|
||||||
//updateWindowSizeAndOverlap(bpm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 0, 100);
|
|
||||||
|
|
||||||
mByteStreamWriterAcc = new ByteStreamWriter();
|
|
||||||
mByteStreamWriterGyro = new ByteStreamWriter();
|
|
||||||
|
|
||||||
mStreamer = new SensorDataFileStreamer(mContext);
|
|
||||||
new Thread(mStreamer).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
|
|
||||||
mTimer.cancel();
|
|
||||||
mTimer.purge();
|
|
||||||
|
|
||||||
mSensorManager.unregisterListener(this);
|
|
||||||
|
|
||||||
//send data and close the ByteStreamWriter
|
|
||||||
for (OnBpmEstimatorListener listener : listeners) {
|
|
||||||
//listener.onEstimationStopped(mByteStreamWriter.getByteArray());
|
|
||||||
}
|
|
||||||
mByteStreamWriterAcc.close();
|
|
||||||
mByteStreamWriterGyro.close();
|
|
||||||
|
|
||||||
mStreamer.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSensorChanged(SensorEvent se) {
|
|
||||||
|
|
||||||
//TODO: at the moment this runs in main thread... since worker fragment runs also in main thread
|
|
||||||
|
|
||||||
//ca 200hz, every 5 to 6 ms we have an update
|
|
||||||
if (se.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) {
|
|
||||||
mAccelerometerWindowBuffer.add(new AccelerometerData(System.currentTimeMillis(), se.values[0], se.values[1], se.values[2]));
|
|
||||||
mByteStreamWriterAcc.writeSensor3D(Sensor.TYPE_LINEAR_ACCELERATION, se.values[0], se.values[1], se.values[2]);
|
|
||||||
|
|
||||||
mStreamer.sendByteArray(mByteStreamWriterAcc.getByteArray());
|
|
||||||
mByteStreamWriterAcc.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (se.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
|
|
||||||
mByteStreamWriterGyro.writeSensor3D(Sensor.TYPE_ACCELEROMETER, se.values[0], se.values[1], se.values[2]);
|
|
||||||
|
|
||||||
mStreamer.sendByteArray(mByteStreamWriterGyro.getByteArray());
|
|
||||||
mByteStreamWriterGyro.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAccuracyChanged(Sensor sensor, int i) {
|
|
||||||
// do nothin
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for callback calculated bpm
|
|
||||||
*/
|
|
||||||
public interface OnBpmEstimatorListener {
|
|
||||||
void onNewDataAvailable(double bpm);
|
|
||||||
void onEstimationStopped(byte[] sensorData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<OnBpmEstimatorListener> listeners = new CopyOnWriteArrayList<OnBpmEstimatorListener>();
|
|
||||||
public void add(OnBpmEstimatorListener listener){listeners.add(listener);}
|
|
||||||
public void remove(OnBpmEstimatorListener listener){listeners.remove(listener);}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple function that sets die windowSize and Overlap time to a specific value
|
|
||||||
* depending on the currentBPM. Nothing rly dynamical. However, should work out
|
|
||||||
* for our purposes.
|
|
||||||
* @param bpm
|
|
||||||
*/
|
|
||||||
private void updateWindowSizeAndOverlap(double bpm){
|
|
||||||
|
|
||||||
//round to nearest tenner. this limits the number of windowsize and overlap changes.
|
|
||||||
int newBpmRounded = (int) Math.round(bpm / 10.0) * 10;
|
|
||||||
int curBpmRounded = (int) Math.round(mCurrentBpm / 10.0) * 10;
|
|
||||||
|
|
||||||
//TODO: i guess this is not the best method.. if the default sizes always produces -1, we run into problems
|
|
||||||
if(bpm != -1 && curBpmRounded != newBpmRounded){
|
|
||||||
|
|
||||||
int overlap_ms = 60000 / newBpmRounded;
|
|
||||||
int window_ms = overlap_ms * 5;
|
|
||||||
|
|
||||||
//idea: wenn man mehrere fenster parallel laufen lässt und beobachtet, müsste das kleinste fenster die tempowechsel eigentlich am
|
|
||||||
// besten mitbekommen. dieses fenster dann bestimmen lassen?
|
|
||||||
|
|
||||||
mAccelerometerWindowBuffer.setWindowSize(window_ms);
|
|
||||||
mAccelerometerWindowBuffer.setOverlapSize(overlap_ms);
|
|
||||||
|
|
||||||
} else if (bpm == -1){
|
|
||||||
//if bpm is -1 due to a non-classification, reset to default.
|
|
||||||
|
|
||||||
//idea: anstatt auf einen festen wert zu setzen, könnte man das fenster dann auch einfach ein wenig größer / kleiner machen.
|
|
||||||
mAccelerometerWindowBuffer.setWindowSize(3000);
|
|
||||||
mAccelerometerWindowBuffer.setOverlapSize(750);
|
|
||||||
}
|
|
||||||
|
|
||||||
mCurrentBpm = bpm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package de.tonifetzer.conductorswatch;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Vibrator;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by toni on 13/11/17.
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class Metronome extends TimerTask {
|
|
||||||
|
|
||||||
private Vibrator mVibrator;
|
|
||||||
|
|
||||||
Metronome(Context context){
|
|
||||||
mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
mVibrator.vibrate(10);
|
|
||||||
|
|
||||||
for (OnMetronomeListener listener:listeners) {
|
|
||||||
listener.onNewClick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for callback metronome clicks
|
|
||||||
* I know that java has observable.. but this why it is more clean and easy
|
|
||||||
*/
|
|
||||||
public interface OnMetronomeListener {
|
|
||||||
void onNewClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<OnMetronomeListener> listeners = new CopyOnWriteArrayList<OnMetronomeListener>();
|
|
||||||
public void add(OnMetronomeListener listener){listeners.add(listener);}
|
|
||||||
public void remove(OnMetronomeListener listener){listeners.remove(listener);}
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,9 @@ import android.widget.TextView;
|
|||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import de.tonifetzer.conductorswatch.bpmEstimation.Estimator;
|
||||||
import de.tonifetzer.conductorswatch.ui.Croller;
|
import de.tonifetzer.conductorswatch.ui.Croller;
|
||||||
|
import de.tonifetzer.conductorswatch.ui.Metronome;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ public class AccelerometerWindowBuffer extends ArrayList<AccelerometerData> {
|
|||||||
public AccelerometerWindowBuffer getFixedSizedWindow(int size, int overlap){
|
public AccelerometerWindowBuffer getFixedSizedWindow(int size, int overlap){
|
||||||
AccelerometerWindowBuffer other = new AccelerometerWindowBuffer(size, overlap);
|
AccelerometerWindowBuffer other = new AccelerometerWindowBuffer(size, overlap);
|
||||||
|
|
||||||
double sampleRate = ((getYongest().ts - getOldest().ts) / super.size());
|
double sampleRate = (double) ((getYongest().ts - getOldest().ts) / super.size());
|
||||||
|
|
||||||
//if current size is smaller then wanted size, start at 0 and provide smaller list
|
//if current size is smaller then wanted size, start at 0 and provide smaller list
|
||||||
int start = 0;
|
int start = 0;
|
||||||
|
|||||||
@@ -1,214 +0,0 @@
|
|||||||
package de.tonifetzer.conductorswatch.bpmEstimation;
|
|
||||||
|
|
||||||
import de.tonifetzer.conductorswatch.utilities.MovingFilter;
|
|
||||||
import de.tonifetzer.conductorswatch.utilities.SimpleKalman;
|
|
||||||
import de.tonifetzer.conductorswatch.utilities.Utils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by toni on 17/12/17.
|
|
||||||
*/
|
|
||||||
public class BpmEstimator {
|
|
||||||
|
|
||||||
private AccelerometerWindowBuffer mBuffer;
|
|
||||||
private double mSampleRate_ms;
|
|
||||||
|
|
||||||
private LinkedList<Double> mBpmHistory_X;
|
|
||||||
private LinkedList<Double> mBpmHistory_Y;
|
|
||||||
private LinkedList<Double> mBpmHistory_Z;
|
|
||||||
|
|
||||||
private LinkedList<Double> mBpmHistory;
|
|
||||||
private int mResetCounter;
|
|
||||||
private int mResetLimit_ms;
|
|
||||||
|
|
||||||
private MovingFilter mMvg;
|
|
||||||
//private SimpleKalman mKalman;
|
|
||||||
|
|
||||||
|
|
||||||
public BpmEstimator(AccelerometerWindowBuffer windowBuffer, double sampleRate_ms, int resetAfter_ms){
|
|
||||||
mBuffer = windowBuffer;
|
|
||||||
mSampleRate_ms = sampleRate_ms;
|
|
||||||
|
|
||||||
mBpmHistory_X = new LinkedList<>();
|
|
||||||
mBpmHistory_Y = new LinkedList<>();
|
|
||||||
mBpmHistory_Z = new LinkedList<>();
|
|
||||||
|
|
||||||
mBpmHistory = new LinkedList<>();
|
|
||||||
mResetCounter = 0;
|
|
||||||
mResetLimit_ms = resetAfter_ms;
|
|
||||||
|
|
||||||
|
|
||||||
//TODO: this is to easy. since the new dyn. windowsize produces smaller update times, we need to consider something, that
|
|
||||||
//TODO: holds more values, if they are similar, an resets the history if not.
|
|
||||||
mMvg = new MovingFilter(2);
|
|
||||||
//mKalman = new SimpleKalman();
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: we use the buffer from outside.. this buffer is continuously updated.. not good!
|
|
||||||
public double estimate(AccelerometerWindowBuffer fixedWindow){
|
|
||||||
|
|
||||||
double sampleRate = mSampleRate_ms;
|
|
||||||
if(sampleRate <= 0){
|
|
||||||
sampleRate = Math.round(Utils.mean(Utils.diff(fixedWindow.getTs())));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(sampleRate == 0){
|
|
||||||
int breakhere = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
AccelerometerInterpolator interp = new AccelerometerInterpolator(fixedWindow, sampleRate);
|
|
||||||
|
|
||||||
//are we conducting?
|
|
||||||
//just look at the newest 512 samples
|
|
||||||
//List<AccelerometerData> subBuffer = mBuffer.subList(mBuffer.size() - 512, mBuffer.size());
|
|
||||||
|
|
||||||
double[] xAutoCorr = new AutoCorrelation(interp.getX(), fixedWindow.size()).getCorr();
|
|
||||||
double[] yAutoCorr = new AutoCorrelation(interp.getY(), fixedWindow.size()).getCorr();
|
|
||||||
double[] zAutoCorr = new AutoCorrelation(interp.getZ(), fixedWindow.size()).getCorr();
|
|
||||||
|
|
||||||
|
|
||||||
//find a peak within range of 250 ms
|
|
||||||
int peakWidth = (int) Math.round(250 / sampleRate);
|
|
||||||
Peaks pX = new Peaks(xAutoCorr, peakWidth, 0.1f, 0, false);
|
|
||||||
Peaks pY = new Peaks(yAutoCorr, peakWidth, 0.1f, 0, false);
|
|
||||||
Peaks pZ = new Peaks(zAutoCorr, peakWidth, 0.1f, 0, false);
|
|
||||||
|
|
||||||
mBpmHistory_X.add(pX.getBPM(sampleRate));
|
|
||||||
mBpmHistory_Y.add(pY.getBPM(sampleRate));
|
|
||||||
mBpmHistory_Z.add(pZ.getBPM(sampleRate));
|
|
||||||
|
|
||||||
double estimatedBPM = getBestBpmEstimation(pX, pY, pZ);
|
|
||||||
if(estimatedBPM != -1){
|
|
||||||
|
|
||||||
//moving avg (lohnt dann, wenn wir viele daten haben)
|
|
||||||
mMvg.add(estimatedBPM);
|
|
||||||
mBpmHistory.add(mMvg.getAverage());
|
|
||||||
|
|
||||||
//mBpmHistory.add(estimatedBPM);
|
|
||||||
|
|
||||||
//moving median (lohnt nur bei konstantem tempo, da wir nur ein tempo damit gut halten können.)
|
|
||||||
//mMvg.add(estimatedBPM);
|
|
||||||
//mBpmHistory.add(mMvg.getMedian());
|
|
||||||
|
|
||||||
//kalman filter (lohnt dann, wenn wir konstantes tempo haben, mit startangabe!)
|
|
||||||
|
|
||||||
//standard last element
|
|
||||||
//mBpmHistory.add(estimatedBPM);
|
|
||||||
//mResetCounter = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int resetAfter = (int) Math.round(mResetLimit_ms / (mBuffer.getOverlapSize()));
|
|
||||||
if(++mResetCounter > resetAfter){
|
|
||||||
mBpmHistory.clear();
|
|
||||||
|
|
||||||
//TODO: send signal to clear.
|
|
||||||
//mBuffer.clear();
|
|
||||||
mMvg.clear();
|
|
||||||
mResetCounter = 0;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//last element
|
|
||||||
return mBpmHistory.getLast();
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getMeanBpm(){
|
|
||||||
return Utils.mean(mBpmHistory);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getMedianBPM(){
|
|
||||||
return Utils.median(mBpmHistory);
|
|
||||||
}
|
|
||||||
|
|
||||||
private double getBestBpmEstimation(Peaks peaksX, Peaks peaksY, Peaks peaksZ) throws IllegalArgumentException {
|
|
||||||
|
|
||||||
int cntNumAxis = 0;
|
|
||||||
double sumCorr = 0; //to prevent division by zero
|
|
||||||
double sumRms = 0;
|
|
||||||
double sumNumInter = 0;
|
|
||||||
|
|
||||||
double corrMeanX = 0, corrRmsX = 0;
|
|
||||||
int corrNumInterX = 0;
|
|
||||||
if(peaksX.hasPeaks()){
|
|
||||||
corrMeanX = Utils.geometricMean(peaksX.getPeaksValueWithoutZeroIdxAndNegativeValues());
|
|
||||||
corrRmsX = Utils.rms(peaksX.getPeaksValueWithoutZeroIdx());
|
|
||||||
corrNumInterX = Utils.intersectionNumber(peaksX.getData(), 0.2f);
|
|
||||||
|
|
||||||
++cntNumAxis;
|
|
||||||
sumCorr += corrMeanX;
|
|
||||||
sumRms += corrRmsX;
|
|
||||||
sumNumInter += corrNumInterX;
|
|
||||||
}
|
|
||||||
|
|
||||||
double corrMeanY = 0, corrRmsY = 0;
|
|
||||||
int corrNumInterY = 0;
|
|
||||||
if(peaksY.hasPeaks()){
|
|
||||||
corrMeanY = Utils.geometricMean(peaksY.getPeaksValueWithoutZeroIdxAndNegativeValues());
|
|
||||||
corrRmsY = Utils.rms(peaksY.getPeaksValueWithoutZeroIdx());
|
|
||||||
corrNumInterY = Utils.intersectionNumber(peaksY.getData(), 0.2f);
|
|
||||||
|
|
||||||
++cntNumAxis;
|
|
||||||
sumCorr += corrMeanY;
|
|
||||||
sumRms += corrRmsY;
|
|
||||||
sumNumInter += corrNumInterY;
|
|
||||||
}
|
|
||||||
|
|
||||||
double corrMeanZ = 0, corrRmsZ = 0;
|
|
||||||
int corrNumInterZ = 0;
|
|
||||||
if(peaksZ.hasPeaks()){
|
|
||||||
corrMeanZ = Utils.geometricMean(peaksZ.getPeaksValueWithoutZeroIdxAndNegativeValues());
|
|
||||||
corrRmsZ = Utils.rms(peaksZ.getPeaksValueWithoutZeroIdx());
|
|
||||||
corrNumInterZ = Utils.intersectionNumber(peaksZ.getData(), 0.2f);
|
|
||||||
|
|
||||||
++cntNumAxis;
|
|
||||||
sumCorr += corrMeanZ;
|
|
||||||
sumRms += corrRmsZ;
|
|
||||||
sumNumInter += corrNumInterZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
//no peaks, reject
|
|
||||||
if(cntNumAxis == 0){
|
|
||||||
//throw new IllegalArgumentException("All Peaks are empty! -> Reject Estimation");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
System.out.println("RMS-X: " + corrRmsX);
|
|
||||||
System.out.println("GEO-X: " + corrMeanX);
|
|
||||||
System.out.println("INTER-X: " + corrNumInterX);
|
|
||||||
|
|
||||||
System.out.println("RMS-Y: " + corrRmsY);
|
|
||||||
System.out.println("GEO-Y: " + corrMeanY);
|
|
||||||
System.out.println("INTER-Y: " + corrNumInterY);
|
|
||||||
|
|
||||||
System.out.println("RMS-Z: " + corrRmsZ);
|
|
||||||
System.out.println("GEO-Z: " + corrMeanZ);
|
|
||||||
System.out.println("INTER-Z: " + corrNumInterZ);
|
|
||||||
*/
|
|
||||||
|
|
||||||
//values to low, reject
|
|
||||||
//TODO: this is a pretty simple assumption. first shot!
|
|
||||||
if(corrRmsX < 0.25 && corrRmsY < 0.25 && corrRmsZ < 0.25){
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
double quantityX = ((corrMeanX / sumCorr) + (corrRmsX / sumRms) + (corrNumInterX / sumNumInter)) / cntNumAxis;
|
|
||||||
double quantityY = ((corrMeanY / sumCorr) + (corrRmsY / sumRms) + (corrNumInterY / sumNumInter)) / cntNumAxis;
|
|
||||||
double quantityZ = ((corrMeanZ / sumCorr) + (corrRmsZ / sumRms) + (corrNumInterZ / sumNumInter)) / cntNumAxis;
|
|
||||||
|
|
||||||
//get best axis by quantity and estimate bpm
|
|
||||||
if(quantityX > quantityY && quantityX > quantityZ){
|
|
||||||
return mBpmHistory_X.getLast();
|
|
||||||
}
|
|
||||||
else if(quantityY > quantityZ){
|
|
||||||
return mBpmHistory_Y.getLast();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return mBpmHistory_Z.getLast();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user