I am creating a tuning fork app, where you pat the iPhone into the palm of your other hand, or against a soft surface in order to set the fork zinging.
So I would like to detect the energy contained in each 'bump'
(EDIT: Removed a ton of gumpf)
Can anyone help me crack this one?
Thanks to one of the wizards on freenode's #math channel (thanks Igor), I have a really good working solution.
You use the standard method to fire a callback at the maximum frequency possible (100Hz), which will contain the instantaneous acceleration x,y,z values.
I will let the code speak for itself, good code should always speak for itself.
typedef
struct {
double x,y,z;
}
vec_d3;
#define RECENT_COUNT 10
#define SMOOTH_IP( x, x_new, fac ) x = fac * x + ( 1. - fac ) * x_new
- (void) accelerometer: (UIAccelerometer *) accelerometer
didAccelerate: (UIAcceleration *) acceleration
{
// smooth incoming acceleration values
static vec_d3 smooth = { DOUBLE_EMPTY, 0, 0 };
{
if ( smooth.x == DOUBLE_EMPTY )
{
smooth.x = acceleration.x;
smooth.y = acceleration.y;
smooth.z = acceleration.z;
return;
}
SMOOTH_IP( smooth.x, acceleration.x, 0.9 );
SMOOTH_IP( smooth.y, acceleration.y, 0.9 );
SMOOTH_IP( smooth.z, acceleration.z, 0.9 );
}
// keep track of last k smoothed acceleration values
static vec_d3 recent[ RECENT_COUNT ];
{
static int ptr = 0;
static BOOL gotEnoughData = NO;
recent[ ptr ] = smooth;
ptr++;
if ( ptr == RECENT_COUNT )
{
ptr = 0;
gotEnoughData = YES;
}
// return if array not filled yet
if ( ! gotEnoughData )
return;
}
// get the resultant variation in acceleration over the whole array
double variation;
{
vec_d3 min = smooth, max = smooth;
for ( int i=0; i < RECENT_COUNT; i++ )
{
min.x = MIN( min.x, recent[ i ].x );
min.y = MIN( min.y, recent[ i ].y );
min.z = MIN( min.z, recent[ i ].z );
max.x = MAX( max.x, recent[ i ].x );
max.y = MAX( max.y, recent[ i ].y );
max.z = MAX( max.z, recent[ i ].z );
}
vec_d3 V = (vec_d3)
{
.x = max.x - min.x,
.y = max.y - min.y,
.z = max.z - min.z
};
variation = sqrt(
V.x * V.x +
V.y * V.y +
V.z * V.z
);
}
// smooth it
static double var_smoothed = DOUBLE_EMPTY;
{
if ( var_smoothed == DOUBLE_EMPTY )
{
var_smoothed = variation;
return;
}
SMOOTH_IP( var_smoothed, variation, 0.9 );
}
// see if it's just passed a peak
{
static double varSmoothed_last = DOUBLE_EMPTY;
if ( varSmoothed_last == DOUBLE_EMPTY )
{
varSmoothed_last = var_smoothed;
return;
}
static double varSmoothed_preLast = DOUBLE_EMPTY;
if ( varSmoothed_preLast == DOUBLE_EMPTY )
{
varSmoothed_preLast = varSmoothed_last;
varSmoothed_last = var_smoothed;
return;
}
#define THRESHOLD_IMPULSE .15
if ( varSmoothed_last > varSmoothed_preLast
&& varSmoothed_last > var_smoothed
&& varSmoothed_last > THRESHOLD_IMPULSE )
{
LOG ( @"PotPeak @ %f", varSmoothed_last );
// hit a peak at imp_last
[self peakedWithImpulse: varSmoothed_last ];
}
varSmoothed_preLast = varSmoothed_last;
varSmoothed_last = var_smoothed;
}
}
First, the questions you asked:
A) Just calibrate. The standing acceleration is the steady pull of gravity, and any sudden deviation is the addition of acceleration due to hitting something. You'll also have to keep track of changes in orientation as reported by the gyroscope, so that you can disregard gravity's sudden shift to a new direction.
B) Integrate the acceleration to get the change in velocity. That squared, times mass of the phone, divided by two, is the energy (in the resting frame).
C) This one's basically unsolvable. Theoretically, if the case is flexible, the acceleration from an impact will tend to appear as a spike-like curve with a characteristic shape, so that if you have a couple of points that aren't on the peak you can estimate the whole shape. But I suspect the device is too rigid and the sampling too sparse. There's nothing you can do about this, unless the hardware will integrate acceleration for you, which I doubt (I don't know the iPhone).
But energy probably isn't the best measure to use anyway; you can deliver as much energy hitting it with a pillow as tapping it with a hammer, but you wouldn't expect a tuning fork to ring as loud. Peak acceleration might be better, but that still relies on good data from the accelerometer.
Could you use the microphone? Try recording what it hears when you tap it against you knee, and look for features that aren't present in ordinary sound, like, I don't know, big low-frequency amplitude, wide spectrum, maybe even a characteristic resonance of the case. It may still respond a little bit to a loud noise in the environment, but then that's quite realistic.
What you want is a Kalman Filter. Google that phrase; writing about one is beyond the scope of an internet forum such as this. There are books and books and books on Kalman Filters.
One issue: The iphone interface isn't really built for what you need. My employer had to work with Apple to access and munge with internals precisely because of this problem. And no, I can't say much about what they did or what the project is.
Regarding the accelerometer reading when held flat: It should register 1G up, not down. There is no way to sense gravity. (Newtonian POV: There's no such thing as a gravity shield. Relativistic POV: Gravity is a fictitious force.) The accelerometer isn't sensing gravity. It is sensing the Earth or table pushing the accelerometer upward.