Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Android

Android: Using the accelerometer to create a simple maraca app

Hi everyone

The short: Does anyone know a tutorial that will help me understand how to program the detection of certain gestures using the accelerometer?

The long:

I'm trying to understand how to use the accelerometer by creating a simple maraca simulator.

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)

Or if anyone can suggest a whole new way of achieving what I'm trying to achieve, please go ahead! I'm getting a bit tired of not being able to get this working! :)

Thanks

2 Answers

Ben Jakuben
STAFF
Ben Jakuben
Treehouse Teacher

Sorry you never got a reply about this! Did you get it working, or are you still having trouble with it?

Hi Ben!

Thanks for your response. I eventually got to the bottom of it, and the app will be finished soon. The accelerometer is a beast! But I've learnt a lot.

Thanks for your help.

Dicky

Ben Jakuben
Ben Jakuben
Treehouse Teacher

Yeah, there aren't great APIs for it. It's powerful, though! Let us know when the app is published so we can check it out. :)