iPhone Syncing a time sequence with music - iphone

I'm using AVAudioPlayer to play music in my iPhone app.
In a class that I wrote I have an array that contains random ascending integers. (2, 4, 9, 17, 18, 20,...)
These integers represent times in the song at which a certain event should occur. So if you take the above array, after 2 seconds of the song playing, some method should be called. After 4 seconds, another method should be called. And so on.
I have tried using a repeating NSTimer:
NSTimer *myTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerTick) userInfo:nil repeats:YES];
Everytime it fires, it checks whether the value of the Audioplayer and of the current arrayindex are the same:
- (void) timerTick {
if([[myArray objectAtIndex:currentIndex] intValue] == (int)(player.currentTime)) {
//here the event-method is called
currentIndex++;
}
}
This code actually works, but only for some time. After some time however, myTimer and the timer that controls the musicplayer are out of sync. So it misses an element of myArray and an infinite loop starts. I don't know exactly why they get out of sync, but I think it could be because the timer and the player aren't being started at exactly the same time or maybe because of short performance lags.
I think I have to approach this in a totally different way. Is key-value observing a way to do this? I could add my class as an observer to the player object, so that it gets notified when the player.currentTime value changes. But that would cause a LOT of notifications to be send and I think it would be really bad for performance.
Any help much appreciated!

Ok here is my solution: I found an open source app that does almost the same thing my app should do, which helped me a lot.
I'm going to stick with the code I already have, with a little modification it should be precise enough for my purposes. Here it is:
currentIndex = 0;
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:#selector(timerTick) userInfo:nil repeats:YES];
- (void) timerTick {
if(timerRunning){
if([[myArray objectAtIndex:currentIndex] intValue] <= (int)(player.currentTime*100)) {
//some event code
currentIndex++;
}
}
}
The important change is from == to <= in the if-condition. When it gets asynchronous and misses an element of myArray, the error is corrected the next hundredth of a second. That's good enough.
Thanks for your help!

It could be that the timers are reasonably in sync, but your code just takes to long to execute (i.e: longer then 1 second).
Couldn't you just use the timer of the musicplayer, and spawn a thread each time an event should occur? This way the timer stays uninterrupted, and your thread will do what it needs to do (lets say the heavy stuff).
If you reall y need two timers, I guess you could create a background threads that keeps those two timers in sync, but I think you're asking for trouble there..

Real world synchronization with music is very hard because users can notice mis-syncs of just a tenth of a second or less. You might find that AVAudioPlayer is to simple for what you need. You might have to control the rate the music plays using AudioQueueServices so that you can sync the music to the code instead of the other way around. You could see time to fire your code coming up and then start the methods before the music actually plays. Done skillfully this would sync the music most of the time.

Related

Stop NSTimer Keeps Increasing Intervals

I have a strange issue with my countDown timer. This timer is counts down from a set time (i.e. 60 seconds). This bit of code is placed in myViewDidLoad method. Everything works unless I go back and load the view again. Every time the view loads, there is an increment of 1 second in the countdown.
For example:
First Load: 60, 59, 58...
Second Load: 60, 58, 56...
Third Load: 60, 57, 54...
My code is below. Does anyone know why this is happening? Do I need to release something somewhere? Thank you!
countDown=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self
selector:#selector(TimeOver) userInfo:nil repeats:YES];
Each time your view loads, you create a new timer, but the old ones still exist. In your timer's action method, TimeOver, you are decrementing an index variable that keeps track of the seconds, and each timer runs that method every time it fires. So, if you have three timers, the index will decrease by three each time.
You need to either not create new a new timer whenever your view loads or, better, destroy the timer when your view disappears:
[countDown invalidate];
countdown = nil;
and recreate it when it reappears.
Also, be aware that your timer's action method has an incorrect signature. It should be a method which returns nothing and takes one argument, which is the timer itself, like so:
- (void)timeOver: (NSTimer *)tim;
Also, Cocoa methods should not start with capital letters.

iphone : Play and pause a song for particular time?

In my app,
song play through AVAudioPlayer,
now i want to play a song for particular time than pause for particular time & play again.
How can i do that?
If you use AVPlayer instead of AVAudioPlayer then you can use the method
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block
Which will call the block at the set interval for as long as the item is playing. The parameter Time passed to the block will tell you how long the current item has been playing for.
Therefore all you'd need to do would be to test the Time value and if it's a certain length then call pause to stop the player. You could then setup a one-time timer to fire after a certain amount of time to recommence playback.
You need to use an NSTimer and invoke a method after a given period of time. So, make two methods, pause: and play:, for example
- (void)pause:(id)sender {
[player pause];
}
and then invoke this method using a scheduled time after a given time period like this:
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:#selector(pause:) userInfo:nil repeats:no
so in this case, after 3 seconds, pause will be invoked in 'self'.

Objective-c -> iphone design: delayed action

Sorry to bother, but I am in a bit of a pickle and I was wondering if anyone here could give me a hand.
I am currently designing a game in which enemies appear on the left side of the screen (out of boundaries) and move to the right. I have used a number of codes (this is using Sparrow Framework) and pretty much the number of enemies increases as you beat them. i.e. lvl 1-> 1 enemy, lvl 2-> 2 enemies, lvl3-> 3 enemies, etc...
I am having some trouble though with producing enemies. I have them appearing on 1 of 5 set paths (path numbers in NSMutableArray), selected by a random number generator, however they often appear on the same path, 1 on top of the other.
To produce the enemies, i am running a number of methods:
addEnemy -> produces the enemies (animations) which then travel from left to right.
onTouchEnemy -> if i touch the enemy, they die. activates drawEnemies
drawEnemies -> calls addEnemy a number of times equal to your lvl. coded as:
for(int i = 0; i < level; i++){
[self performSelector:#selector(addEnemy) withObject:nil afterDelay:3.0];
}
Is there a way to program so that there is a delay between activating the produce enemies method? I tried this afterDelay, but for some reason the program just ignores the 3 second delay and just produces enemies all in 1 go. This is rather irritating since i would like them to appear in a more orderly fashion.
I thank anyone willing to give me a hand with this. Sjkato.
performSelector:withObject:afterDelay: appears to ignore its delay because of the way the code executes. That for loop will iterate almost instantly, queuing up 3 calls to the addEnemy method. After 3 seconds the addEnemy methods execute, almost all at the same time.
To get a better result, you should look at NSTimer. You could set an interval of 3 seconds and tell it to repeat (you can invalidate the timer after the desired number of enemies has been produced).
Something like:
// creates and returns a new timer
// the timer is retained by the run loop for as long as it is valid
// invalidating the timer will cause the runloop to release it.
myTimer = [NSTimer scheduledTimerWithTimeInterval:3.0
target:self
selector:#selector(addEnemy)
userInfo:nil
repeats:YES];
This will cause the addEnemy method to be fired once every 3 seconds. You should keep a tally of how many enemies you have already made, and after the last one is made, stop the timer so that it doesn't fire again.
if (numberOfDesiredEnemies == numberOfEnemiesProduced)
{
[myTimer invalidate], timer = nil;
}
Did you mean to do this, with a extra "*i" at the end? Like this:
for(int i = 0; i < level; i++){
[self performSelector:#selector(addEnemy) withObject:nil afterDelay:3.0 * i];
}
Try to look through NSTimer class.
There are some methods that provide ability to perform selectors with predefined delay and loop conditions.

NSTimer to smooth out playback position

I have an audio player and I want to show the current time of the the playback. I'm using a custom play class.
The app downloads the mp3 to a file then plays from the file when 5% has been downloaded. I have a progress view update as the file plays and update a label on each call to the progress view. However, this is jerky... sometimes even going backward a digit or two.
I was considering using an NSTimer to smooth things out. I would be fired every second to a method and pass the percentage played figure to the method then update the label.
First, does this seem reasonable?
Second, how do I pass the percentage (a float) over to the target of the timer. Right now I am putting the percent played into a dictionary but this seems less than optimal.
This is what is called update the progress bar:
-(void)updateAudioProgress:(Percentage)percent {
audio = percent;
if (!seekChanging) slider.value = percent;
NSMutableDictionary *myDictionary = [[NSMutableDictionary alloc] init];
[myDictionary setValue:[NSNumber numberWithFloat:percent] forKey:#"myPercent"];
[NSTimer scheduledTimerWithTimeInterval:5
target:self
selector:#selector(myTimerMethod:)
userInfo:myDictionary
repeats:YES];
[myDictionary release];
}
This is called first after 5 seconds but then updates each time the method is called.
As always, comments and pointers appreciated.
UserInfo is a void *, so you could just pass an int for percentage directly, or just a pointer to your NSNumber object; it doesn't have to be an NSDictionary. Apart from that, using a dictionary is a standard approach, and doesn't have excessive overhead.
Note that you are creating a new timer with a 5 second repeat every time this method is called, which may or may not be your intention. When you create repeating timers (or normal ones) you should make sure to invalidate them when not required.

What is a better way to create a game loop on the iPhone other than using NSTimer?

I am programming a game on the iPhone. I am currently using NSTimer to trigger my game update/render. The problem with this is that (after profiling) I appear to lose a lot of time between updates/renders and this seems to be mostly to do with the time interval that I plug into NSTimer.
So my question is what is the best alternative to using NSTimer?
One alternative per answer please.
You can get a better performance with threads, try something like this:
- (void) gameLoop
{
while (running)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self renderFrame];
[pool release];
}
}
- (void) startLoop
{
running = YES;
#ifdef THREADED_ANIMATION
[NSThread detachNewThreadSelector:#selector(gameLoop)
toTarget:self withObject:nil];
#else
timer = [NSTimer scheduledTimerWithTimeInterval:1.0f/60
target:self selector:#selector(renderFrame) userInfo:nil repeats:YES];
#endif
}
- (void) stopLoop
{
[timer invalidate];
running = NO;
}
In the renderFrame method You prepare the framebuffer, draw frame and present the framebuffer on screen. (P.S. There is a great article on various types of game loops and their pros and cons.)
I don't know about the iPhone in particular, but I may still be able to help:
Instead of simply plugging in a fixed delay at the end of the loop, use the following:
Determine a refresh interval that you would be happy with and that is larger than a single pass through your main loop.
At the start of the loop, take a current timestamp of whatever resolution you have available and store it.
At the end of the loop, take another timestamp, and determine the elapsed time since the last timestamp (initialize this before the loop).
sleep/delay for the difference between your ideal frame time and the already elapsed time this for the frame.
At the next frame, you can even try to compensate for inaccuracies in the sleep interval by comparing to the timestamp at the start of the previous loop. Store the difference and add/subtract it from the sleep interval at the end of this loop (sleep/delay can go too long OR too short).
You might want to have an alert mechanism that lets you know if you're timing is too tight(i,e, if your sleep time after all the compensating is less than 0, which would mean you're taking more time to process than your frame rate allows). The effect will be that your game slows down. For extra points, you may want to simplify rendering for a while, if you detect this happening, until you have enough spare capacity again.
Use the CADisplayLink, you can find how in the OpenGL ES template project provided in XCODE (create a project starting from this template and give a look to the EAGLView class, this example is based on open GL, but you can use CADisplayLink only for other kind of games