So I'm looking at some code and basically there are a row of images and you swipe on the row, and the card images move in the row according to the swipe. Like you give it a flick, and then it moves 5 cards down the list. You give it a small flick, then you move 1 card down the list. It also has the ability to enter a card number, like if you were starting at card 1, and wanted to go to card 52, you type in 52, and it scrolls through all the 51 cards to get to card 52 and stops. Both the swipe gesture, and goToCard code call the below method:
- (void)doAnimation {
double elapsed = CACurrentMediaTime() - startTime;
if (elapsed >= runDelta) [self endAnimation];
else {
[self updateAnimationAtTime:elapsed];
}
}
- (void)updateAnimationAtTime:(double)elapsed
{
int max = 52;
if (elapsed > runDelta) elapsed = runDelta;
double delta = fabs(startSpeed) * elapsed - FRICTION * elapsed * elapsed / 2;
if (startSpeed < 0) delta = -delta;
offset = startOff + delta;
}
It looks like the developer was trying to match physics velocity equation of
DeltaX = Vot + (1/2) accel * time^2
It works pretty good when I test it in that the card images speed up according to your swipe in the beginning, and then slows down as you get closer to the runDelta (endpoint).
There's another function that calls updateAnimationAtTime like this:
- (void)goToCard:(int)pos {
int max = 52;
startSpeed = sqrt(fabs(pos - startOff) * FRICTION * 2);
runDelta = fabs(startSpeed / FRICTION);
startTime = CACurrentMediaTime();
timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(doAnimation) userInfo:nil repeats:YES];
}
It looks like in the above function, the startSpeed is calculated like it would be in physics:
Vf^2 = Vo^2 + 2ug(Xf - Xi)
where u is the coefficition of friction and they are assuming gravity is equal to 1.
So the code works, but when the # of cards gets increased from 52 to 300, the animation looks funny since it's cycling through 300 cards to go from start to finish. I've never really implemented physics related stuff in code and was wondering if anyone had any suggestions or tips in how to change the code to still give the user the effect that we're cycling through all the cards, but not cycle through all of them when it goes past 50 as that's where the animation starts to look funny as it starts to spin so fast that the original effect of cycling through cards is lost.
Please let me know if anything needs to be clarified in my question. Thanks!
Related
I’m trying to create a kick drum sound that must sound exactly the same when looped at different tempi. The implementation below sounds exactly the same when repeated once every second, but it sounds to me like every other kick has a higher pitch when played every half second. It’s like there is a clipping sound or something.
var context = new AudioContext();
function playKick(when) {
var oscillator = context.createOscillator();
var gain = context.createGain();
oscillator.connect(gain);
gain.connect(context.destination);
oscillator.frequency.setValueAtTime(150, when);
gain.gain.setValueAtTime(1, when);
oscillator.frequency.exponentialRampToValueAtTime(0.001, when + 0.5);
gain.gain.exponentialRampToValueAtTime(0.001, when + 0.5);
oscillator.start(when);
oscillator.stop(when + 0.5);
}
for (var i = 0; i < 16; i++) {
playKick(i * 0.5); // Sounds fine with multiplier set to 1
}
Here’s the same code on JSFiddle: https://jsfiddle.net/1kLn26p4/3/
Not true; oscillator.start will begin the phase at 0. The problem is that you're starting the "when" parameter at zero; you should start it at context.currentTime.
for (var i = 0; i < 16; i++) {
playKick(context.current time + i * 0.5); // Sounds fine with multiplier set to 1
}
The oscillator is set to start at the same time as the change from the default frequency of 440 Hz to 150 Hz. Sometimes this results in a glitch as the transition is momentarily audible.
The glitch can be prevented by setting the frequency of the oscillator node to 150 Hz at the time of creation. So add:
oscillator.frequency.value = 150;
If you want to make the glitch more obvious out of curiosity, try:
oscillator.frequency.value = 5000;
and you should be able to hear what is happening.
Updated fiddle.
EDIT
In addition the same problem is interacting with the timing of the ramp. You can further improve the sound by ensuring that the setValueAtTime event always occurs a short time after playback starts:
oscillator.frequency.setValueAtTime(3500, when + 0.001);
Again, not perfect at 3500 Hz, but it's an improvement, and I'm not sure you'll achieve sonic perfection with Web Audio. The best you can do is try to mask these glitches until implementations improve. At actual kick drum frequencies (e.g. the 150 Hz in your original Q.), I can't tell any difference between successive sounds. Hopefully that's good enough.
Revised fiddle.
I have built an emulated Analog VU Meter for a recording app and have everything hooked up properly and working the way I expect except for one aspect. If you watch this 13-second video of the VU meter in action, you will see that the needle bounces all over the place and is not really what would happen in a real VU meter. For an example of what I am looking for, try out the Apple "Voice Memos" app and see.
My logic so far is easy:
#define VU_METER_FREQUENCY 1.0/5.0
- (void)someMethod {
_updateTimer = [NSTimer
scheduledTimerWithTimeInterval:VU_METER_FREQUENCY
target:self
selector:#selector(_refresh)
userInfo:nil
repeats:YES];
}
- (void)_refresh {
// if we have no queue, but still have levels, gradually bring them down
if (_delegate == nil) {
CFAbsoluteTime thisFire = CFAbsoluteTimeGetCurrent();
// calculate how much time passed since the last draw
CFAbsoluteTime timePassed = thisFire - _peakFalloffLastFire;
needleValue = needleValue - timePassed * VU_METER_LEVEL_FALL_OFF_PER_SECOND;
if (needleValue < VU_METER_MIN_DB) {
needleValue = VU_METER_MIN_DB;
TT_INVALIDATE_TIMER(_updateTimer);
}
_peakFalloffLastFire = thisFire;
} else {
prevNeedleValue = needleValue;
needleValue = [_delegate currentDB];
}
[self updateNeedle];
}
- (void)updateNeedle {
[UIView beginAnimations:nil context:NULL]; // arguments are optional
[UIView setAnimationDuration:VU_METER_FREQUENCY];
[UIView setAnimationCurve:(needleValue > prevNeedleValue ? UIViewAnimationCurveEaseOut : UIViewAnimationCurveEaseIn)];
CGFloat radAngle = [self radianAngleForValue:needleValue];
self.needle.transform = CGAffineTransformMakeRotation(radAngle);
[UIView commitAnimations];
}
Basically, I setup a timer to run at VU_METER_FREQUENCY and update the needle rotation using a UIView animation with easing that is preferential to keep the needle higher. I am looking for a way to adjust this somehow to provide a smoother needle, with my benchmark being as close as possible to Apple's analog VU Meter. To get the needleValue, I am using AudioQueue's mAveragePower and querying it every time currentDB is called. How can I smooth this?
One thing I would suggest is changing this.
#define VU_METER_FREQUENCY 1.0/5.0
That says update 5x a second, the issue is that I think apple will hold 0.2s of samples, so you really are getting an average of the sounds, hence the meter is not really following the highs and lows of the sounds but more of a lower average.
I think this setting can go as high as 1.0/60 (60hz).
As for making the meter smooth, that is a little tricker.
You could do something like this.
Create an array that holds 7-8 values.
every time you get a reading, add it to the array and pop the 7th value of (eg only hold the last seven values.
Find the average of the array (sum the array / divide by the number of elements in the array.
Display this average.
So its a bit like filling up a pipe, and once you stop filling it will take some time for it to empty and needle will slowly fall down.
OR you could only allow the needle to fall down only so much every cycle.
Lets say the needle swings be 0 (lowest value) and 1 (highest value far right hand side).
Lets also say you sample at 20hz (20x times a second).
Every time you update the position only allow the needle to rise say 0.1 of a value max and fall only 0.05 of value.
you could do something like this and play with the values to get it nice and smooth.
if newValue>currentMeterValue
currentMeterValue = Min(currentMeterValue + 0.1, newValue);
else
currentMeterValue = Max(currentMeterValue - 0.05, newValue);
OR
You simply move the meter at a rate proportionally to the distance between each value (this should smooth it nicely) and actually be close to real meter with a spring pushing against the needle which is powered by an electromagnet.
currentMeterValue += (newValue - currentMeterValue)/4.0;
According to Wikipedia, the behavior of a VUMeter is defined in in ANSI specification C16.5-1942. The needle full rise and fall time is supposed to be 300 mSec, averaging loudness over that duration.
I would try a 1-pole low-pass filter on the needle angle to approximate that angular rate, and animate the meter manually on a frame-by-frame basis using CADisplayLink based drawRect animation. View animation might not give the same responsiveness.
I've tried several ways of measuring the steps a user makes with an iPhone by reading the accelerometer, but none have been very accurate. The most accurate implementation I've used is the following:
float xx = acceleration.x;
float yy = acceleration.y;
float zz = acceleration.z;
float dot = (mOldAccX * xx) + (mOldAccY * yy) + (mOldAccZ * zz);
float a = ABS(sqrt(mOldAccX * mOldAccX + mOldAccY * mOldAccY + mOldAccZ * mOldAccZ));
float b = ABS(sqrt(xx * xx + yy * yy + zz * zz));
dot /= (a * b);
if (dot <= 0.994 && dot > 0.90) // bounce
{
if (!isChange)
{
isChange = YES;
mNumberOfSteps += 1;
} else {
isChange = NO;
}
}
mOldAccX = xx;
mOldAccY = yy;
mOldAccZ = zz;
}
However, this only catches 80% of the user's steps. How can I improve the accuracy of my pedometer?
Here is some more precise answer to detect each step. But yes in my case I am getting + or - 1 step with every 25 steps. So I hope this might be helpful to you. :)
if (dot <= 0.90) {
if (!isSleeping) {
isSleeping = YES;
[self performSelector:#selector(wakeUp) withObject:nil afterDelay:0.3];
numSteps += 1;
self.stepsCount.text = [NSString stringWithFormat:#"%d", numSteps];
}
}
- (void)wakeUp {
isSleeping = NO;
}
ok, I'm assuming this code is within the addAcceleration function...
-(void)addAcceleration:(UIAcceleration*)accel
So, you could increase your sampling rate to get a finer granularity of detection. So for example, if you are currently taking 30 samples per second, you could increase it to 40, 50, or 60 etc... Then decide if you need to count a number of samples that fall within your bounce and consider that a single step. It sounds like you are not counting some steps due to missing some of the bounces.
Also, what is the purpose of toggling isChange? Shouldn't you use a counter with a reset after x number of counts? If you are within your bounce...
if (dot <= 0.994 && dot > 0.90) // bounce
you would have to hit this sweet spot 2 times, but the way you have set this up, it may not be two consecutive samples in a row, it may be a first sample and a 5th sample, or a 2nd sample and an 11th sample. That is where you are loosing step counts.
Keep in mind that not everyone makes the same big steps. So the dot calculation should be adjusted according to someone's length, step size.
You should adjust the bounce threshold accordingly. Try to make the program learn about it's passenger.
** STILL NOT WORKING **
I am using below formula to move the ball circular, where accelX and accelY are the values from accelerometer, it is working fine.
But the problem in this code is mRadius (I fixed its value to 50), i need to change mRadius according to accelerometer values and also i need bouncing effect when it touches the track. Currently i am developing code by assuming only one ball is on the board.
float degrees = -atan2(accelX, accelY);
int x = cCentrePoint.x + mRadius * cos(degrees);
int y = cCentrePoint.y + mRadius * sin(degrees);
Here is the snap of the game i want to develop:
Balls Game http://iphront.com/wp-content/uploads/2009/12/bdece528ea334033.jpg.jpg
Updated: I am sending the updated code...
mRadius = 5;
mRange = NSMakeRange(0,60);
-(void) updateBall: (UIAccelerationValue) accelX
withY:(UIAccelerationValue)accelY
{
float degrees = -atan2(accelX, accelY);
int x = cCentrePoint.x + mRadius * cos(degrees);
int y = cCentrePoint.y + mRadius * sin(degrees);
//self.targetRect is rect of ball Object
self.targetRect = CGRectMake(newX, newY, 8, 9);
self.currentRect = self.targetRect;
static NSDate *lastDrawTime;
if(lastDrawTime!=nil)
{
NSTimeInterval secondsSinceLastDraw =
-([lastDrawTime timeIntervalSinceNow]);
ballXVelocity = ballXVelocity + (accelX * secondsSinceLastDraw)
* [self isTouchedTrack:mRadius andRange:mRange];
ballYVelocity = ballYVelocity + -(accelY * secondsSinceLastDraw)
* [self isTouchedTrack:mRadius andRange:mRange];
distXTravelled = distXTravelled + secondsSinceLastDraw
* ballXVelocity * 50;
distYTravelled = distYTravelled + secondsSinceLastDraw
* ballYVelocity * 50;
//Updating the ball rect
CGRect temp = self.targetRect;
temp.origin.x += distXTravelled;
temp.origin.y += distYTravelled;
//calculating new radius after updating ball position
int radius = (temp.origin.x - cCentrePoint.x) /
cos(degreesToRadians(degrees));
if( !NSLocationInRange(abs(radius),mRange))
{
//Colided with the tracks...Need a better logic here
ballXVelocity = -ballXVelocity;
}
else
{
// Need a better logic here
self.targetRect = temp;
}
}
[lastDrawTime release];
lastDrawTime = [ [NSDate alloc] init];
}
In the above code i have initialized mRadius and mRange(indicate track) to some constant for testing, i am not getting the moving of the ball as i expected( bouncing effect when Collided with track ) with respect to accelerometer. Help me to recognize where i went wrong or send some code snippets or links which does the similar job.
I am searching for better logic than my code, if you found share with me.
If I understand your code correctly, then the ball's position is directly controlled by the iPhone's orientation (tilt). So, tilting the iPhone to the right will place the ball at the right side of the track (3 o'clock). I believe you may want the balls acceleration (or, at least, its velocity) to be controlled. Then, you integrate the acceleration to velocity and the velocity to place, taking into account the constraints (the track walls).
The way it is set now, I don't see how you'd control more than one ball (as per the image you posted).
Then, for the bouncing effect: if you mean bouncing by the track's wall, then this will be a small modulation of the mRadius. If you mean bounce by other ball, then you'd modulate the angular position (by means of angular velocity) of the two balls to reflect the reaction.
EDIT: for integration of acceleration to velocity and then to position, for the purpose of this game, you can do with 1st order rectangular integration. Also, it will be more realistic to make the acceleration proportional to the tilt angle. Given the accel values from the iPhone itself, you can assign a 1:1 relation between the balls accel and the device reading. So, you'd like something like:
BallAccX = AccelX * Am_I_NOT_touching_a_wall_in_X_direction() * Ka
BallVelX = BallVelX + BallAccX * dT * Kv
BallPosX = BallPosX + BallVelX * dT * Kp
Note: the above formulae for velocity and position are 1st order approximation but should be sufficient for the purpose of this game.
Ka, Kv, Kp are some proportion coefficients. Choose them to make the relation between the sensed acceleration and the ball movement as you like. dT is the time difference between updates of the state of the ball. The function Am_I_NOT_touching_a_wall_in_X_direction() returns a 1 if the ball is free to move horizontally (in the direction of the tilt) and 0 otherwise.
Calculations for Y movement is symmetrical.
After trying alot I thought it is not easy to produce real time effect without using any physics engine. So its better to use BOX2d or Chipmunks or any other physics engines.
I'm asking them at 50Hz / 50 times per second for data. When I suddenly flip the device on the x-axis by 90 degrees while the device was flat on a table with display facing up bevore, the values move pretty slowly to the "target" value for that position.
Now the weird thing is: If I increase the measurement-rate, the value will move faster to that new value upon suddenly flipping the device by 90 degrees. But if I just ask once per second for the new value, it take's very long until the value reaches the target. What can be the reason for this?
I don't do any kind of data aggregation, and don't accumulate anything. I just do some simple filtering to get rid of the noise. My method looks like this:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
// Use a basic low-pass filter to only keep the gravity in the accelerometer values for the X and Y axes
// accelerationX is an instance variable
accelerationX = acceleration.x * 0.05 + accelerationX * (1.0 - 0.05);
// round
int i = accelerationX * 100;
float clippedAccelerationValue = i;
clippedAccelerationValue /= 100;
[self moveViews:clippedAccelerationValue];
}
later on, in my -moveViews: method, I do this:
-(IBAction)moveSceneForPseudo3D:(float)accelerationValue {
if(fabs(lastAccelerationValue - accelerationValue) > 0.02) { // some little treshold to prevent flickering when it lays on a table
float viewAccelerationOffset = accelerationValue * 19 * -1;
newXPos = initialViewOrigin + viewAccelerationOffset;
myView.frame = CGRectMake(newXPos, myView.frame.origin.y, myView.frame.size.width, myView.frame.size.height);
lastAccelerationValue = accelerationValue;
}
}
As a result, of the device gets turned 90 degrees on the x-achsis, or 180 degrees, the view just moves pretty slowly to it's target position. I don't know if that's because of the physics of the accelerometers, or if it's a bug in my filtering code. I only know that there are fast paced games where the accelerometers are used for steering, so I almost can't imagine that's a hardware problem.
This line:
accelerationX = acceleration.x * 0.05 + accelerationX * (1.0 - 0.05);
is a low-pass filter, which works by computing a moving average of the x acceleration. In other words, each time that callback is called, you're only moving the accelerationX by 5% towards the new accelerometer value. That's why it takes many iterations before accelerationX reflects the new orientation.
What you should do is increase the 0.05 value, to say 0.2. I'd make a global #define and play around with different values along with different refresh rates.