After going through the lovely set of Java tutorials and a while spent buried in source code I'm beginning to get the feel for it. For my next step, I will dive in with a fully featured application with graphics, sound, sensor use, touch response and a full menu.
My grand idea was to make an animated screwdriver where sliding the controls up and down modulate the frequency and that frequency dictates the sensor data it returns.
Now I have a semi-working sound system but its pretty poor for what its designed to represent and I just wouldn't be happy producing a sub-par end product whether its my first or not.
the problem:
sound must begin looping when the user presses down on the control
the sound must stop when the user releases the control
when moving the control up or down the sound effect must change pitch accordingly
if the user doesn't remove there finger before backing out of the application it must plate the casing of there device with gold (Easter egg ;P)
now I'm aware of how monolithic the first 3 look and that's why I would really appreciate any help I can get.
sorry for how bad this code looks but my general plan is to create the functional components then refine the code later, no good painting the walls if the roofs not finished.
here's my user input, he set slide stuff is used in the graphics for the control
#Override
public boolean onTouchEvent(MotionEvent event)
{
//motion event for the screwdriver view
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
//make sure the users at least trying to touch the slider
if (event.getY() > SonicSlideYTop && event.getY() < SonicSlideYBottom)
{
//power setup, im using 1.5 to help out the rate on soundpool since it likes 0.5 to 1.5
SonicPower = 1.5f - ((event.getY() - SonicSlideYTop) / SonicSlideLength);
//just goes into a method which sets a private variable in my sound pool class thing
mSonicAudio.setPower(1, SonicPower);
//this handles the slides graphics
setSlideY ( (int) event.getY() );
#Override
public boolean onTouchEvent(MotionEvent event)
{
//motion event for the screwdriver view
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
//make sure the users at least trying to touch the slider
if (event.getY() > SonicSlideYTop && event.getY() < SonicSlideYBottom)
{
//power setup, im using 1.5 to help out the rate on soundpool since it likes 0.5 to 1.5
SonicPower = 1.5f - ((event.getY() - SonicSlideYTop) / SonicSlideLength);
//just goes into a method which sets a private variable in my sound pool class thing
mSonicAudio.setPower(1, SonicPower);
//this handles the slides graphics
setSlideY ( (int) event.getY() );
//this is from my latest attempt at loop pitch change, look for this in my soundPool class
mSonicAudio.startLoopedSound();
}
}
if(event.getAction() == MotionEvent.ACTION_MOVE)
{
if (event.getY() > SonicSlideYTop && event.getY() < SonicSlideYBottom)
{
SonicPower = 1.5f - ((event.getY() - SonicSlideYTop) / SonicSlideLength);
mSonicAudio.setPower(1, SonicPower);
setSlideY ( (int) event.getY() );
}
}
if(event.getAction() == MotionEvent.ACTION_UP)
{
mSonicAudio.stopLoopedSound();
SonicPower = 1.5f - ((event.getY() - SonicSlideYTop) / SonicSlideLength);
mSonicAudio.setPower(1, SonicPower);
}
return true;
}
and here's where those methods end up in my sound pool class its horribly messy but that's because I've been trying a ton of variants to get this to work, you will also notice that I begin to hard code the index, again I was trying to get the methods to work before making them work well.
package com.mattster.sonicscrewdriver;
import java.util.HashMap;
import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;
public class SoundManager
{
private float mPowerLvl = 1f;
private SoundPool mSoundPool;
private HashMap<Integer, Integer> mSoundPoolMap;
private AudioManager mAudioManager;
private Context mContext;
private int streamVolume;
private int LoopState;
private long mLastTime;
public SoundManager()
{
}
public void initSounds(Context theContext)
{
mContext = theContext;
mSoundPool = new SoundPool(2, AudioManager.STREAM_MUSIC, 0);
mSoundPoolMap = new HashMap<Integer, Integer>();
mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
streamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
}
public void addSound(int index,int SoundID)
{
mSoundPoolMap.put(1, mSoundPool.load(mContext, SoundID, 1));
}
public void playUpdate(int index)
{
if( LoopState == 1)
{
long now = System.currentTimeMillis();
if (now > mLastTime)
{
mSoundPool.play(mSoundPoolMap.get(1), streamVolume, streamVolume, 1, 0, mPowerLvl);
mLastTime = System.currentTimeMillis() + 250;
}
}
}
public void stopLoopedSound()
{
LoopState = 0;
mSoundPool.setVolume(mSoundPoolMap.get(1), 0, 0);
mSoundPool.stop(mSoundPoolMap.get(1));
}
public void startLoopedSound()
{
LoopState = 1;
}
public void setPower(int index, float mPower)
{
mPowerLvl = mPower;
mSoundPool.setRate(mSoundPoolMap.get(1), mPowerLvl);
}
}
I almost forgot, that looks pretty ineffective but I omitted my thread which actuality updates it, nothing fancy it just calls:
mSonicAudio.playUpdate(1);
There are some confusing points in there which I think are just cut and paste issues trying to get the source into this page, but assuming you're not having problems with your onTouchEvent handling, my random comments are:
It looks like you are calling play() every 250 ms while the touch is held. I can't see the loop argument to the play() call but I assume it is -1. If so, then you are launching a brand new looped sound every 250 msc (play returns a unique streamId for every audio stream you create).
I think you wanted to modify the pitch and amplitude of a single existing stream. So I think you wanted something like this:
int mySoundStreamId = 0;
...
onDown()
if( mySoundStreamId == 0 ) {
// create the one true stream
mySoundStreamId = mySOundPool.play( initial power and freq modifiers, loop = -1 )
} else {
// resume the one true stream
mySoundPool.resume( mySoundStreamId ); // note: a STREAM id is NOT a SOUND id.
}
onUp()
if( mySoundStreamId != 0 ) {
// pause the one true stream
mySoundPool.pause( mySoundStreamId ) // stop() will release all the samples you held
}
onWiggle()
if( mySoundStreamId != 0 ) {
// modify parameters of the one true stream
mySoundPool.setPitch( mySoundStreamId, newPitch ); // too lazy to look up real soundPool command
}
onGameOver
if( mySoundStreamId != 0 ) {
// shut down and release the samples of the one true stream
mySoundPool.setLoop( mySountStreamId, 0 ); // otherwise some phones will keep looping after shutdown
mySoundPool.stop( mySoundStreamId ); // no resume possible after this, need to reload samples
mySOundStreamId = 0;
}
I omit the creation/destruction of the sound pool itself. It sounds like you were successfully loading the sound data into the sound pool ok.
Note that the LOAD returns a SOUND ID which you pass to the PLAY command
but that PLAY returns a STREAM ID which you use in most of the other soundPool methods
Of course, I have my own problems with 'resume' on a looped sound, so take what I say with a grain of salt :-)
Good Luck! I guess I should have checked the time stamp. My apologies if you posted thie 3 years ago :-)
Related
I'm trying to make a simple game in Unity for GearVR. In the game I have a scene where the user can navigate through a list of items. An item can be selected if the user clicks while looking at one. For the navigation part, the user should be able to use both head movement and swipe to rotate the items (shifting by one/minus one at every right/left swipe).
Now the problem: I can make all of this work with the code below (set as component to the parent of the items), but the rotation keeps increasing the more I use swipes. I can't seem to figure out why ... still working on it.
Any kind of help is appreciated XD
private void ManageSwipe(VRInput.SwipeDirection sw)
{
from = transform.rotation;
if (sw == VRInput.SwipeDirection.LEFT)
{
to = Quaternion.Euler(new Vector3(0, from.eulerAngles.y + 30, 0));
}
if (sw == VRInput.SwipeDirection.RIGHT)
{
to = Quaternion.Euler(new Vector3(0, from.eulerAngles.y - 30, 0));
}
StartCoroutine(Rotate());
}
IEnumerator Rotate(bool v)
{
while (true)
{
transform.rotation = Quaternion.Slerp(from, to, Time.deltaTime);
yield return null;
}
}
I'm using Unity 5.4.1f1 and jdk 1.8.0.
PS. Don't be to hard on me, since this is my first question here.
By the way ... hello everyone XD
You fixed most of the problems I discussed in the comment section. One things left is still the while loop. Right now, there is no way to exit that while loop and this will result to multiple instance of the Rotate function running at the-same time.
but the rotation keeps increasing the more I use swipes
Solution is to store reference to one coroutine function then stop it before starting a new one.
Something like this.
IEnumerator lastCoroutine;
lastCoroutine = Rotate();
...
StopCoroutine(lastCoroutine);
StartCoroutine(lastCoroutine);
Instead of while (true), you should have a timer that exists the while loop. At this time, the Rotate function is continuously running. You should make it stop after moving from the rotation to the destination rotation.
Something like this should work:
while (counter < duration)
{
counter += Time.deltaTime;
transform.rotation = Quaternion.Lerp(from, to, counter / duration);
yield return null;
}
Here is what your whole code should look like:
IEnumerator lastCoroutine;
void Start()
{
lastCoroutine = Rotate();
}
private void ManageSwipe(VRInput.SwipeDirection sw)
{
//from = transform.rotation;
if (sw == VRInput.SwipeDirection.LEFT)
{
to = Quaternion.Euler(new Vector3(0, from.eulerAngles.y + 30, 0));
}
if (sw == VRInput.SwipeDirection.RIGHT)
{
to = Quaternion.Euler(new Vector3(0, from.eulerAngles.y - 30, 0));
}
//Stop old coroutine
StopCoroutine(lastCoroutine);
//Now start new Coroutine
StartCoroutine(lastCoroutine);
}
IEnumerator Rotate()
{
const float duration = 2f; //Seconds it should take to finish rotating
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
transform.rotation = Quaternion.Lerp(from, to, counter / duration);
yield return null;
}
}
You can increase or decrease the duration variable.
Try to use Lerp from the current Rotation not the last one:
transform.rotation = Quaternion.Slerp(transform.rotation, to, Time.deltaTime);
I am developing a game using Cocos2d-X 2.2.6 on top of Marmalade SDK 7.8.0 and I am having some problems to set the sound effect volume of theCocos Denshion Simple Audio Engine.
My code is this:
void AudioHelper::init()
{
s3eResult result;
context = GameContext::getInstance();
s3eSoundSetInt(S3E_SOUND_DEFAULT_FREQ, 44100);
audioEngine = SimpleAudioEngine::sharedEngine();
preloadSoundEffects();
setVolume(context->getSoundVolume());
}
void AudioHelper::preloadSoundEffects()
{
unsigned int i;
CCArray *keysArray;
CCString *key;
const CCString *sound;
keysArray = soundEffects->allKeys();
for (i = 0; i < keysArray->count(); i++)
{
key = (CCString *) keysArray->objectAtIndex(i);
sound = soundEffects->valueForKey(key->getCString());
audioEngine->preloadEffect(sound->getCString());
}
}
void AudioHelper::setVolume(int volumeLevel)
{
float volume;
// Note: there are 6 volume levels in my game, they go from
// 0 (max volume) to 5 (no sound).
volume = 1 - volumeLevel / 5.0;
audioEngine->setEffectsVolume(volume);
}
void AudioHelper::playSoundEffect(const char *effectKey)
{
const CCString *sound;
int volume;
sound = soundEffects->valueForKey(effectKey);
if (sound != NULL)
audioEngine->playEffect(sound->getCString());
}
The problem is that the sound effects volume doesn't change when I call the setVolume method, except when I set the volume level parameter to 5 (no sound). When this happens no sounds are played (as expected).
What I am doing wrong?
did you log actual values in this code?
void AudioHelper::setVolume(int volumeLevel)
{
float volume;
// Note: there are 6 volume levels in my game, they go from
// 0 (max volume) to 5 (no sound).
volume = 1 - volumeLevel / 5.0;
audioEngine->setEffectsVolume(volume);
}
you cannot mix float and int calculations in such a way. float volume here would be 1 or 0 anyway, because you are using volumeLevel as type int. your code should look like this:
void AudioHelper::setVolume(int volumeLevel)
{
float volume;
// Note: there are 6 volume levels in my game, they go from
// 0 (max volume) to 5 (no sound).
volume = 1.0f - (float)volumeLevel / 5.0f;
audioEngine->setEffectsVolume(volume);
}
also take into account that there is some limitations using CocosDenshion at win platform (no volume changing there).
s3eSound expects values between 0-255 when setting the volume.
You can trace the current volume by calling:
s3eSoundGetInt(S3E_SOUND_VOLUME)
each time you set the volume to a specific value and see whether S3E_SOUND_VOLUME changes as expected.
I notice you use:
volume = 1 - volumeLevel / 5.0;
This results a value between 1 and 0 with 0.20 decrements if providing input values from 1 to 5.
Either these values are being set wrongly or being converted incorrectly by the Simple Audio Engine to the s3eSound values.
Tracing out both will give you more insight into what might be going wrong.
Hey guys so I've been trying to solve this for awhile I have looked on many forums and tried to understand what they were trying to convey with the code using ActionScript 3 but i still get nothing. My main goal is to have a character on stage named "mainPlayer" now i want to set up touch events so that when the user drages his finger up down or side to side i want to the mainPlayer to follow the users path or if the user touches a point on the screen and holds his finger there the mainPlayer will be attracted to the touch and move to the point where the finger is currently at on the screen.
Ive seen lots of stuff with Phase and ID implemented but dont really understand whats going on
so far this is what i have set up:
public class realmEngine extends MovieClip
{
//MultiTouch gestures
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
Multitouch.inputMode = MultitouchInputMode.GESTURE;
public var mainPlayer:mcPlayer;
//PlayerControls
private var speed:Number = 8.0;
public var vx:Number = 0;
public var vy:Number = 0;
private var friction:Number = 0.85;
private var maxSpeed:Number = 15;
public function realmEngine()
{
//Add Player to Stage
mainPlayer = new mcPlayer();
stage.addChild(mainPlayer);
mainPlayer.x = (stage.stageWidth / 2) - 300;
mainPlayer.y = (stage.stageHeight / 2 );
//trace("this works");
//Setup Touch Event listeners
mainPlayer.addEventListener(TouchEvent.TOUCH_BEGIN, onTouchBegin);
stage.addEventListener(TouchEvent.TOUCH_MOVE, onTouchMove);
stage.addEventListener(TouchEvent.TOUCH_END, onTouchEnd);
//Game Loop Event Listener
stage.addEventListener(Event.ENTER_FRAME, gameLoop);
}
private function gameLoop(e:Event):void
{
mainPlayerControls();
}
private function mainPlayerControls():void
{
}
private function onTouchEnd(e:TouchEvent):void
{
}
private function onTouchMove(e:TouchEvent):void
{
}
private function onTouchBegin(e:TouchEvent):void
{
}
}
I'm not sure what to do inside the onTouch Functions in order for the object that i add to stage by Code to follow the users touch on the screen.
Can anyone lead my in the right direction or give me any advice? I woudld really appreciate it thanks guys
Yes I happen to know how to do this, I just wasn't sure if I had grasped fully what you wanted to achieve.
Note that I won't be taking into account the speed and maxSpeed variables for moving the player. It's beyond this scope and beyond the scope of the top of my head. A little bit of internet searching will get you far on that subject however!
First of all, in order to make the object follow a path drawn by the user, we need a way to store the path. For this, I suggest a Vector with Point as its datatype. It's fast and easy to work with when adding and removing elements without having to worry about its length.
We also need a way to tell wether the player sprite should move or not, in other words wether the user is pressing the finger on the screen or not.
private var _pathPoints : Vector.<Point>;
private var _isMoving : Boolean = false;
Easy-cakes. Now for the fun part!
First, we need to change the scope of the onTouchBegin event, from mainPlayer to the stage. If we don't, the user won't be able to touch an abstract point on the stage and get the player sprite to move there. Simply done with a change to
mainPlayer.addEventListener(TouchEvent.TOUCH_BEGIN, onTouchBegin);
Then we take care of when the user moves his or her finger. Nothing fancy going on here.
We're just simply storing the coordinates in our vector and storing the current state of wether the user is pressing the screen or not.
private function onTouchBegin ( e:TouchEvent ) : void
{
_pathPoints.push( new Point( e.stageX, e.stageY ) );
_isMoving = true;
}
private function onTouchMove ( e:TouchEvent ) : void
{
_pathPoints.push( new Point( e.stageX, e.stageY ) );
}
private function onTouchEnd ( e:TouchEvent ) : void
{
// Dirty but quick way of clearing the vector
_pathPoints.splice(0);
_isMoving = false;
}
Finally, for the even funnier part; the main game loop! Or "Where the Magic Happens".
private function mainPlayerControls () : void
{
// Update player position and forces
vx *= friction;
vy *= friction;
mainPlayer.x += vx;
mainPlayer.y += vy;
// Check if the player should be moving to a new point
if( _isMoving )
{
// Get a reference to the current target coordinate
var target : Point = _pathPoints[0];
// Check if the player position has reached the current target point
// We use a bounding box with dimensions equal to max speed to ensure
// that the player doesn't move across the point, move back towards it
// and start jojo-ing back and forth
if(mainPlayer.x >= target.x - maxSpeed && mainPlayer.x <= target.x + maxSpeed &&
mainPlayer.y >= target.y - maxSpeed && mainPlayer.y <= target.y)
{
// The player has reached its target
//so we remove the first element of the vector
_pathPoints.shift();
// and update the target reference
target = _pathPoints[0];
}
// Calculate velocities to the first element of the vector
vx = mainPlayer.x - target.x;
vy = mainPlayer.y - target.y;
}
}
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.