I am developing some application like Runtastic Pedometer using the algorithm but I am not getting any similarity between the results.
my code is as follows:
public void onSensorChanged(SensorEvent event)
{
Sensor sensor = event.sensor;
synchronized (this)
{
if (sensor.getType() == Sensor.TYPE_ORIENTATION) {}
else {
int j = (sensor.getType() == Sensor.TYPE_ACCELEROMETER) ? 1 : 0;
if (j == 1) {
float vSum = 0;
for (int i=0 ; i<3 ; i++) {
final float v = mYOffset + event.values[i] * mScale[j];
vSum += v;
}
int k = 0;
float v = vSum / 3;
//Log.e("data", "data"+v);
float direction = (v > mLastValues[k] ? 1 : (v < mLastValues[k] ? -1 : 0));
if (direction == - mLastDirections[k]) {
// Direction changed
int extType = (direction > 0 ? 0 : 1); // minumum or maximum?
mLastExtremes[extType][k] = mLastValues[k];
float diff = Math.abs(mLastExtremes[extType][k] - mLastExtremes[1 - extType][k]);
if (diff > mLimit) {
boolean isAlmostAsLargeAsPrevious = diff > (mLastDiff[k]*2/3);
boolean isPreviousLargeEnough = mLastDiff[k] > (diff/3);
boolean isNotContra = (mLastMatch != 1 - extType);
if (isAlmostAsLargeAsPrevious && isPreviousLargeEnough && isNotContra) {
for (StepListener stepListener : mStepListeners) {
stepListener.onStep();
}
mLastMatch = extType;
}
else {
Log.i(TAG, "no step");
mLastMatch = -1;
}
}
mLastDiff[k] = diff;
}
mLastDirections[k] = direction;
mLastValues[k] = v;
}
}
}
}
for registering sensors:
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(
Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(mStepDetector,mSensor,SensorManager.SENSOR_DELAY_NORMAL);
in the algorithm i have different levels for sensitivity as public void
setSensitivity(float sensitivity) {
mLimit = sensitivity; // 1.97 2.96 4.44 6.66 10.00 15.00 22.50 33.75 50.62
}
on various sensitivity level my result is:
sensitivity rantastic pedometer my app
10.00 3870 5500
11.00 3000 4000
11.15 3765 4576
13.00 2000 890
11.30 754 986
I am not getting any proper pattern to match with the requirement.
As per my analysis this application is using Sensor.TYPE_MAGNETIC_FIELD for steps calculation please let me know some algorithm so that I can meet with the requirement.
The first thing you need to do is decide on an algorithm. As far as I know there are roughly speaking three ways to detect steps using accelerometers that are described in the literature:
Use the Pythagorean theorem to calculate the magnitude of the acceleration vector of each sample from the accelerometer. Low-pass filter the magnitude signal to remove high frequency noise and then look for peaks and valleys in the filtered signal. You may need to add additional requirements to remove false positives. This is by far the simplest way to detect steps, it is also the way that most if not all ordinary pedometers of the sort that you can buy from a sports store work.
Use Pythagoras' like in (1), then run the signal through an FFT and compare the output from the FFT to known outputs of walking. This requires you to have access to a fairly large amount of training data.
Feed the accelerometer data into an algorithm that uses some suitable machine learning technique, for example a neural network or a digital wavelet transform. You can of course include other sensors in this approach. This also requires you to have access to a fairly large amount of training data.
Once you have decided on an algorithm you will probably want to use something like Matlab or SciPy to test your algorithm on your computer using recordings that you have made on Android phones. Dump accelerometer data to a cvs file on your phone, make a record of how many steps the file represents, copy the file to your computer and run your algorithm on the data to see if it gets the step count right. That way you can detect problems with the algorithm and correct them.
If this sounds difficult, then the best way to get access to good step detection is probably to wait until more phones come with the built-in step counter that KitKat enables.
https://github.com/bagilevi/android-pedometer
i hope this might be helpfull
I am using step detection in my walking instrument.
I get nice results of step detection.
I use achartengine to plot accelerometer data.
Take a look here.
What I do:
Analysis of magnitude vector for accelerometer sensor.
Setting a changeable threshold level. When signal from accelerometer is above it I count it as a step.
Setting the time of inactive state (for step detection) after first crossing of the threshold.
Point 3. is calculated:
arbitrary setting the maximum tempo of our walking (e.g. 120bpm)
if 60bpm - 1000msec per step, then 120bpm - 500msec per step
accelerometer passes data with certain desired frequency (SENSOR_DELAY_NORMAL, SENSOR_DELAY_GAME, etc.). When DELAY_GAME: T ~= 20ms (this is included in Android documentation)
n - samples to omit (after passing the threshold)
n = 500msec / T
n = 500 / 20 = 25 (plenty of them. You can adjust this value).
after that, the threshold becomes active.
Take a look at this picture:
This is my realization. It was written about 1.5-2 years ago. And I really don't remember all this stuff that I wrote. But it worked. And it worked good for my needs.
I know that this is really big class (some methods are deleted), but may be it will be helpful. If not, I'll just remove this answer...
public class StepDetector implements SensorEventListener
{
public static final int MAX_BUFFER_SIZE = 5;
private static final int Y_DATA_COUNT = 4;
private static final double MIN_GRAVITY = 2;
private static final double MAX_GRAVITY = 1200;
public void onSensorChanged(final SensorEvent sensorEvent)
{
final float[] values = sensorEvent.values;
final Sensor sensor = sensorEvent.sensor;
if (sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
{
magneticDetector(values, sensorEvent.timestamp / (500 * 10 ^ 6l));
}
if (sensor.getType() == Sensor.TYPE_ACCELEROMETER)
{
accelDetector(values, sensorEvent.timestamp / (500 * 10 ^ 6l));
}
}
private ArrayList<float[]> mAccelDataBuffer = new ArrayList<float[]>();
private ArrayList<Long> mMagneticFireData = new ArrayList<Long>();
private Long mLastStepTime = null;
private ArrayList<Pair> mAccelFireData = new ArrayList<Pair>();
private void accelDetector(float[] detectedValues, long timeStamp)
{
float[] currentValues = new float[3];
for (int i = 0; i < currentValues.length; ++i)
{
currentValues[i] = detectedValues[i];
}
mAccelDataBuffer.add(currentValues);
if (mAccelDataBuffer.size() > StepDetector.MAX_BUFFER_SIZE)
{
double avgGravity = 0;
for (float[] values : mAccelDataBuffer)
{
avgGravity += Math.abs(Math.sqrt(
values[0] * values[0] + values[1] * values[1] + values[2] * values[2]) - SensorManager.STANDARD_GRAVITY);
}
avgGravity /= mAccelDataBuffer.size();
if (avgGravity >= MIN_GRAVITY && avgGravity < MAX_GRAVITY)
{
mAccelFireData.add(new Pair(timeStamp, true));
}
else
{
mAccelFireData.add(new Pair(timeStamp, false));
}
if (mAccelFireData.size() >= Y_DATA_COUNT)
{
checkData(mAccelFireData, timeStamp);
mAccelFireData.remove(0);
}
mAccelDataBuffer.clear();
}
}
private void checkData(ArrayList<Pair> accelFireData, long timeStamp)
{
boolean stepAlreadyDetected = false;
Iterator<Pair> iterator = accelFireData.iterator();
while (iterator.hasNext() && !stepAlreadyDetected)
{
stepAlreadyDetected = iterator.next().first.equals(mLastStepTime);
}
if (!stepAlreadyDetected)
{
int firstPosition = Collections.binarySearch(mMagneticFireData, accelFireData.get(0).first);
int secondPosition = Collections
.binarySearch(mMagneticFireData, accelFireData.get(accelFireData.size() - 1).first - 1);
if (firstPosition > 0 || secondPosition > 0 || firstPosition != secondPosition)
{
if (firstPosition < 0)
{
firstPosition = -firstPosition - 1;
}
if (firstPosition < mMagneticFireData.size() && firstPosition > 0)
{
mMagneticFireData = new ArrayList<Long>(
mMagneticFireData.subList(firstPosition - 1, mMagneticFireData.size()));
}
iterator = accelFireData.iterator();
while (iterator.hasNext())
{
if (iterator.next().second)
{
mLastStepTime = timeStamp;
accelFireData.remove(accelFireData.size() - 1);
accelFireData.add(new Pair(timeStamp, false));
onStep();
break;
}
}
}
}
}
private float mLastDirections;
private float mLastValues;
private float mLastExtremes[] = new float[2];
private Integer mLastType;
private ArrayList<Float> mMagneticDataBuffer = new ArrayList<Float>();
private void magneticDetector(float[] values, long timeStamp)
{
mMagneticDataBuffer.add(values[2]);
if (mMagneticDataBuffer.size() > StepDetector.MAX_BUFFER_SIZE)
{
float avg = 0;
for (int i = 0; i < mMagneticDataBuffer.size(); ++i)
{
avg += mMagneticDataBuffer.get(i);
}
avg /= mMagneticDataBuffer.size();
float direction = (avg > mLastValues ? 1 : (avg < mLastValues ? -1 : 0));
if (direction == -mLastDirections)
{
// Direction changed
int extType = (direction > 0 ? 0 : 1); // minumum or maximum?
mLastExtremes[extType] = mLastValues;
float diff = Math.abs(mLastExtremes[extType] - mLastExtremes[1 - extType]);
if (diff > 8 && (null == mLastType || mLastType != extType))
{
mLastType = extType;
mMagneticFireData.add(timeStamp);
}
}
mLastDirections = direction;
mLastValues = avg;
mMagneticDataBuffer.clear();
}
}
public static class Pair implements Serializable
{
Long first;
boolean second;
public Pair(long first, boolean second)
{
this.first = first;
this.second = second;
}
#Override
public boolean equals(Object o)
{
if (o instanceof Pair)
{
return first.equals(((Pair) o).first);
}
return false;
}
}
}
One main difference I spotted between your implementation and the code in the grepcode project is the way you register the listener.
Your code:
mSensorManager.registerListener(mStepDetector,
mSensor,
SensorManager.SENSOR_DELAY_NORMAL);
Their code:
mSensorManager.registerListener(mStepDetector,
mSensor,
SensorManager.SENSOR_DELAY_FASTEST);
This is a big difference. SENSOR_DELAY_NORMAL is intended for orientation changes, and is therefor not that fast (ever noticed that it takes some time between you rotating the device, and the device actually rotating? That's because this is some functionality that does not need to be super fast (that would probably be pretty annoying even). The rate at which you get updates is not that high).
On the other hand, SENSOR_DELAY_FASTEST is intended for things like pedometers: you want the sensor data as fast and often as possible, so your calculations of steps will be as accurate as possible.
Try to switch to the SENSOR_DELAY_FASTEST rate, and test again! It should make a big difference.
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType()==Sensor.TYPE_ACCELEROMETER ){
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
currentvectorSum = (x*x + y*y + z*z);
if(currentvectorSum < 100 && inStep==false){
inStep = true;
}
if(currentvectorSum > 125 && inStep==true){
inStep = false;
numSteps++;
Log.d("TAG_ACCELEROMETER", "\t" + numSteps);
}
}
}
Related
I'm building an augmented reality app with POI.
Most of the app is done but now I am trying to find a better stabilization of my sensors, in particular the Accelerometer. I have used low pass filter, but POI are still bouncing to much. The thing I was trying to do is get 5 or more readings of accelerometer and then divide it by n. That should give me better readings but i have no idea how to make it. I can make a for loop but I can't just make for(int i=0; i<n; i++) , because i++ should be done only when the value of accelerometer has changed. If I do it with a simple for loop, I will get an error because the loop is done faster then the sensors are changed. The thing i was looking for is a timer that will changed only when the sensors are changed too.
This is what i have done so far:
static final float ALPHA = 0.15f; // if ALPHA = 1 OR 0, no filter applies.
// low level pass filter, so i can get steadier reading of mobile sensors
// using only accelerometer and compass
protected float[] lowPass(float[] input, float[] output) {
if (output == null) return input;
for (int i = 0; i < input.length; i++) {
output[i] = output[i] + ALPHA * (input[i] - output[i]);
// output[i] = input[i]*ALPHA + output[i]*(1.0f-ALPHA);
}
return output;
}
public void onSensorChanged(SensorEvent event) {
StringBuilder msg = new StringBuilder(event.sensor.getName())
.append(" ");
for (float value : event.values) {
msg.append("[").append(String.format("%.3f", value)).append("]");
}
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
lastAccelerometer = lowPass(event.values.clone(), lastAccelerometer);
accelData = msg.toString();
break;
case Sensor.TYPE_GYROSCOPE:
gyroData = msg.toString();
break;
case Sensor.TYPE_MAGNETIC_FIELD:
lastCompass = lowPass(event.values.clone(), lastCompass);
compassData = msg.toString();
break;
}
this.invalidate();
}
I found the solution :
protected float[] getMovingAvg() {
float [] output = new float [3];
for( int i = 0; i< moveVector.size(); i++) {
output[0] += moveVector.get(i)[0];
output[1] += moveVector.get(i)[1];
output[2] += moveVector.get(i)[2];
}
output[0] = output[0]/moveVector.size();
output[1] = output[1]/moveVector.size();
output[2] = output[2]/moveVector.size();
if(moveVector.size() >= 70) {
moveVector.remove(0);
}
return output;
}
I have an app that needs to detect shake possibly whenever the user has their phone's screen on. I've found plenty of examples of how to detect shake. The example below being the most intriguing so far with use of Google code and adding in the gravity component. My question is, can this code be improved? Shake detection is pretty solid and i'm getting no false positives. I'm mostly concerned with battery life improvement.
private static final int mMinimumForce = 5;
private static final int mShakeFrequency = 500;
private static final int mMovesRequired = 4;
private float[] mGravity = { 0.0f, 0.0f, 0.0f };
private float[] mAcceleration = { 0.0f, 0.0f, 0.0f };
private static final int mXAxis = 0;
private static final int mYAxis = 1;
private static final int mZAxis = 2;
private long mCurrentTime = 0;
private long mLastTime = 0;
private int mMoveCount = 0;
private final float mAlpha = 0.8f;
public void onSensorChanged(SensorEvent event)
{
if(event.sensor.getType() != Sensor.TYPE_ACCELEROMETER)
{
return;
}
// Set linear acceleration
// Gravity components of x, y, and z acceleration
mGravity[mXAxis] = mAlpha * mGravity[mXAxis] + (1 - mAlpha) * event.values[mXAxis];
mGravity[mYAxis] = mAlpha * mGravity[mYAxis] + (1 - mAlpha) * event.values[mYAxis];
mGravity[mZAxis] = mAlpha * mGravity[mZAxis] + (1 - mAlpha) * event.values[mZAxis];
// Linear acceleration of x, y, z with gravity effect removed
mAcceleration[mXAxis] = event.values[mXAxis] - mGravity[mXAxis];
mAcceleration[mYAxis] = event.values[mYAxis] - mGravity[mYAxis];
mAcceleration[mZAxis] = event.values[mZAxis] - mGravity[mZAxis];
// Set maximum linear acceleration amongst x, y, z
float maxAcceleration = mAcceleration[mXAxis];
if (mAcceleration[mYAxis] > maxAcceleration)
{
maxAcceleration = mAcceleration[mYAxis];
}
if (mAcceleration[mZAxis] > maxAcceleration)
{
maxAcceleration = mAcceleration[mZAxis];
}
// Process shake
if (maxAcceleration > mMinimumForce)
{
Log.d(TAG, "Shake detected");
mCurrentTime = System.currentTimeMillis();
if (mLastTime == 0)
{
mLastTime = mCurrentTime;
}
long elapsedTime = mCurrentTime - mLastTime;
if (elapsedTime > mShakeFrequency)
{
mLastTime = 0;
mMoveCount = 0;
}
else
{
mMoveCount++;
if (mMoveCount > mMovesRequired)
{
Log.d(TAG, "Shake moves detected: " + mMovesRequired);
// do some work here
mLastTime = 0;
mMoveCount = 0;
}
}
}
}
Google I/O docs have great information on all of your concerns. Here's one such document.
https://dl.google.com/io/2009/pres/W_0300_CodingforLife-BatteryLifeThatIs.pdf
Your point on floating point math is correct. While your code doesn't do much as far as calculations, the constant calling of it at high frequency could tax the CPU.
Your point on accelerometer using battery. While each device is different regarding power consumption and this device doesn't use close to what the gyroscope does, it will show a marked difference if used non-stop on a full day of active screen use.
I agree that coding to the highest standards and efficiency is good regardless of if you see a marked difference in battery usage. It's just being a good citizen. If I can get 30 more minutes out of my phone, give it to me!!!
My suggestions on your code, which are really just reflective of Google's recommendations and many you're already speaking about.
Register your listener with the lowest possible polling rate.
Supplement point one with a filter at the beginning of the code to immediately return based on that poll rate not being satisfied. (Current Time - Last Time) > POLL_RATE. This is important because Android may not adhere to the poll rate registered in the listener.
Favor integer math over floating point if you can.
Investigate whether you can use AlarmManager and/or other sensors first that are more cost effective before engaging the accelerometer. I don't know if this is possible in your case, but worth checking into.
is that possible to slow the the accelerometer update frequenzy to 1hz and how?
i've tried it on nexus 7 tab with this: changed the sensorDelay_Normal to 1.000.000 but nothing changed.
Thank You!
here is the code:
mAccelerometer.registerListener(listener,mAccelerometer.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),SensorManager.1000000);
This is how I get 1Hz acceleration:
static int ACCE_FILTER_DATA_MIN_TIME = 1000; // 1000ms
long lastSaved = System.currentTimeMillis();
#Override
public void onSensorChanged(SensorEvent event) {
if ((System.currentTimeMillis() - lastSaved) > ACCE_FILTER_DATA_MIN_TIME) {
lastSaved = System.currentTimeMillis();
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
}
}
You can always disregard measurements except one each second. Or do you want to spare battery?
okay i have found the solution:
public static final double accFreq = 15; //15 = 1 sec
long nowA = 0;
long timeA = 0;
int tempA = 0;
public SensorEventListener listener = new SensorEventListener() {
long tSA;
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
// Get timestamp of the event
tSA = event.timestamp;
if (nowA != 0) {
tempA++;
if (tempA == settings.accFreq) {
timeA = tSA - nowA;
tempA = 0;
Log.e("Accelerometer:", "" + x + " " + y + " " + z);
}
}
if (tempA == 0) {
nowA = tSA;
}
}
}
This is the only way i can get it to work in 1Hz!
A custom value for the value of the sensor speed dont work well.
If you take a look into the class you will find the "getDelay" function in every .registerListener() methode.
public boolean registerListener(SensorEventListener listener, Sensor sensor, int rateUs,
int maxBatchReportLatencyUs) {
int delay = getDelay(rateUs);
return registerListenerImpl(listener, sensor, delay, null,maxBatchReportLatencyUs, 0);
}
Wich is also defined in the same class:
private static int getDelay(int rate) {
int delay = -1;
switch (rate) {
case SENSOR_DELAY_FASTEST:
delay = 0;
break;
case SENSOR_DELAY_GAME:
delay = 20000;
break;
case SENSOR_DELAY_UI:
delay = 66667;
break;
case SENSOR_DELAY_NORMAL:
delay = 200000;
break;
default:
delay = rate;
break;
}
return delay;
}
So normaly it should work to set a custom value.
But if you try to use custom values above about 100 milliseconds (100000 microseconds) the measured time between the events can jump up to 1000 milliseconds and stay there. If you go for 1000 ,2000 or much higher milliseconds the time between the events still are around 1000 milliseconds.
You can use SENSOR_DELAY_NORMAL (= 200 milliseconds) and catch the event if enough time passed by checking that every time (like David did)
If you go higher you should not use this enery draining approach - instead register the sensorListener catch the value and unregister it every time.
With the current approach the application is still getting events every 60ms. Just because it discards 14 out of 15 events, does NOT reduce the battery consumption as the sensor hardware still needs to process data once every 60ms and the system receives it and sends it to the application. i.e NOT much battery savings.
(nitpick alert) To be accurate with the above method, one needs to pick a sample from 16 or 17 events as each event occurs at around 60ms on an average.
The device freezing upon registering multiple sensorEventListeners at SENSOR_DELAY_NORMAL is NOT an issue with the Android framework (which can easily handle the scenario). This indicates an issue with how the application is written. The first thing to look for is a lot of cpu-intensive code within the main UI-thread. Follow the developer guidelines to make the application responsive with multiple sensorEventListeners() registered with SENSOR_DELAY_NORMAL and multiple worker threads performing the cpu-intensive calculations.
I'm trying to learn how to use the accelerometer by creating (what I thought would be) a simple app to mimic a crude maraca.
The objective is that when the phone is flicked downwards quickly, it emits a maraca sound at the end of that flick, and likewise a different sound is emitted at the end of an upward flick.
The strategy for implementing this is to detect when the acceleration passes over a certain threshold. When this happens, ShakeIsHappening is set to true, and the data from the z axis is fed into an array. A comparison is made to see whether the first position in the z array is greater or lesser than the most recent position, to see whether the phone has been moved upwards or downwards. This is stored in a boolean called zup.
Once the acceleration goes below zero, we assume the flick movement has ended and emit a sound, chosen depending on whether the movement was up or down (zup).
Here is the code:
public class MainActivity extends Activity implements SensorEventListener {
private float mAccelNoGrav;
private float mAccelWithGrav;
private float mLastAccelWithGrav;
ArrayList<Float> z = new ArrayList<Float>();
public static float finalZ;
public static boolean shakeIsHappening;
public static int beatnumber = 0;
public static float highZ;
public static float lowZ;
public static boolean flick;
public static boolean pull;
public static SensorManager sensorManager;
public static Sensor accelerometer;
private SoundPool soundpool;
private HashMap<Integer, Integer> soundsMap;
private boolean zup;
private boolean shakeHasHappened;
public static int shakesound1 = 1;
public static int shakesound2 = 2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
results = (TextView) findViewById(R.id.results);
clickresults = (TextView) findViewById(R.id.clickresults);
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
accelerometer = sensorManager
.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mAccelNoGrav = 0.00f;
mAccelWithGrav = SensorManager.GRAVITY_EARTH;
mLastAccelWithGrav = SensorManager.GRAVITY_EARTH;
soundpool = new SoundPool(4, AudioManager.STREAM_MUSIC, 100);
soundsMap = new HashMap<Integer, Integer>();
soundsMap.put(shakesound1, soundpool.load(this, R.raw.shake1, 1));
soundsMap.put(shakesound2, soundpool.load(this, R.raw.shake1, 1));
}
public void playSound(int sound, float fSpeed) {
AudioManager mgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
float streamVolumeCurrent = mgr.getStreamVolume(AudioManager.STREAM_MUSIC);
float streamVolumeMax = mgr.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
float volume = streamVolumeCurrent / streamVolumeMax;
soundpool.play(soundsMap.get(sound), volume, volume, 1, 0, fSpeed);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
#Override
public void onSensorChanged(SensorEvent event) {
float x = event.values[0];
float y = event.values[1];
z.add((event.values[2])-SensorManager.GRAVITY_EARTH);
mLastAccelWithGrav = mAccelWithGrav;
mAccelWithGrav = android.util.FloatMath.sqrt(x * x + y * y + z.indexOf(z.size()-1) * z.indexOf(z.size()-1));
float delta = mAccelWithGrav - mLastAccelWithGrav;
mAccelNoGrav = mAccelNoGrav * 0.9f + delta; // Low-cut filter
if (mAccelNoGrav > 3) {
shakeIsHappening = true;
z.clear();
}
if (mAccelNoGrav < 0) {
if (shakeIsHappening) {
shakeIsHappening = false;
shakeHasHappened = true;
}
}
if (shakeIsHappening && z.size() != 0) {
if (z.get(z.size()-1) > z.get(0)) {
zup = true;
} else if (z.get(0) > z.get(z.size()-1)) {
zup = false;
}
}
if (shakeHasHappened) {
Log.d("click", "up is" + zup + "Low Z:" + z.get(0) + " high Z:" + z.get(z.size()-1));
if (!zup) {
shakeHasHappened = false;
playSound(shakesound2, 1.0f);
z.clear();
} else if (zup) {
shakeHasHappened = false;
playSound(shakesound1, 1.0f);
z.clear();
}
}
}
Some of the problems I'm having are:
I think ShakeHasHappened kicks in when deceleration starts, when acceleration goes below zero. Perhaps this should be when deceleration stops, when acceleration has gone negative and is now moving back towards zero. Does that sound sensible?
The way of detecting whether the motion is up or down isn't working - is this because I'm not getting an accurate reading of where the phone is when looking at the z axis because the acceleration is also included in the z-axis data and therefore isn't giving me an accurate position of the phone?
I'm getting lots of double clicks, and I can't quite work out why this is. Sometimes it doesn't click at all.
If anyone wants to have a play around with this code and see if they can find a way of making it more accurate and more efficient, please go ahead and share your findings. And if anyone can spot why it's not working the way I want it to, again please share your thoughts.
To link sounds to this code, drop your wav files into your res\raw folder and reference them in the R.raw.shake1 bit (no extension)
Thanks
EDIT: I've done a bit of research and have stumbled across something called Dynamic Time Warping. I don't know anything about this yet, but will start to look in to it. Does anyone know if DTW could be a different method of achieving a working maraca simulator app?
I can give you some pointers on this:
First of all, I noticed that you're using the same resource for both outcomes:
soundsMap.put(shakesound1, soundpool.load(this, R.raw.shake1, 1));
soundsMap.put(shakesound2, soundpool.load(this, R.raw.shake1, 1));
The resource in case of shakesound2 should be R.raw.shake2.
Second, the following only deals with one of the motions:
if (mAccelNoGrav > 3)
This should be changed to:
if (mAccelNoGrav > 3 || mAccelNoGrav < -3)
Currently, you are not intercepting downward motion.
Third, acceleration value of 3 is rather low. If you want to avoid/filter-out normal arm movement, this value should be around 6 or 7 and -6 or -7.
Fourth, you do not need to store z values to check whether the motion was up or down. You can check whether:
mAccelnoGrav > 6 ===> implies motion was upwards
mAccelnoGrav < -6 ===> implies motion was downwards
You can use this information to set zup accordingly.
Fifth: I can only guess that you are using if (mAccelNoGrav < 0) to play the sound when the motion ends. In that case, this check should be changed to:
if (mAccelNoGrav < epsilon || mAccelNoGrav > -epsilon)
where epsilon is some range such as (-1, 1).
Sixth, you should include a lockout period in your application. This would be the period after all conditions have been met and a sound is about to be played. For the next, say 1000 ms, don't process the sensor values. Let the motion stabilize. You'll need this to avoid getting multiple playbacks.
Note: Please include comments in your code. At the very least, place comments on every block of code to convey what you are trying to accomplish with it.
I tried to implement it by myself a time ago, and ended up using this solution.-
http://jarofgreen.co.uk/2013/02/android-shake-detection-library/
based on the same concept in your question.
The below code is my attempt to send mMyView to the front or the back of the set of children of mPivotParent so it will be rendered on top or behind the others. Hiding the view will not suffice in my case.
mPivotParent is a FrameLayout.
Looking at mPivotParent.mChildren shows that my code below "works" in that the ordering is being set correctly. Yet it has no impact on the z order. Not only this, but the framerate gets cumulatively slower and slower the more times the repositioning code gets called. There are 4 children total and mPivotParent.mChildrenCount remains at 4 throughout as expected.
I'm targeting API Level 7.
#Override
public boolean onTouchEvent(MotionEvent event) {
Display display = getWindowManager().getDefaultDisplay();
float x = event.getRawX();
float sWidth = (int)display.getWidth();
float xLerpFromCenter = ((x / sWidth) - .5f) * 2.f; // [-1, 1]
mRotateAnimation.mfLerp = xLerpFromCenter;
if(xLerpFromCenter < -0.2f && mPivotParent.getChildAt(0) != mMyView)
{
mPivotParent.removeView(mMyView);
mPivotParent.addView(mMyView, 0);
refreshEverything();
}
else if(xLerpFromCenter > 0.2f && mPivotParent.getChildAt(0) == mMyView)
{
mPivotParent.removeView(mMyView);
mPivotParent.addView(mMyView, mPivotParent.getChildCount() - 1);
refreshEverything();
}
return super.onTouchEvent(event);
}
private void refreshEverything()
{
for(int i = 0; i < mPivotParent.getChildCount(); ++i)
{
mPivotParent.getChildAt(i).invalidate();
mPivotParent.getChildAt(i).requestLayout();
}
mPivotParent.invalidate();
mPivotParent.requestLayout();
}
Partial Solution
Here's a somewhat inefficient hack but it works for my purpose, which is to take the top item and send it to the back, keeping all other items in their same order.
private void putFrontAtBack()
{
for(int i = 0; i < mPivotParent.getChildCount() - 1; ++i)
{
mPivotParent.getChildAt(0).bringToFront();
}
refreshEverything();
}
Note: This doesn't work in the general case of arbitrary re-ordering.
Try this.
private void reorder(int[] order)
{
if(order == null || order.length != mPivotParent.getChildCount()) return;
for(int i = order.length - 1; i >= 0; i--)
{
mPivotParent.getChildAt(order[i]).bringToFront();
}
refreshEverything();
}
This code provides arbitrary reordering. The integer array "order" is used to indicate the new order of each view, where order[n]=x means the new order of childAt(x) is n.