So I have been trying to figure out the metronome sample code from apple.
I am not interested in the animation, i get that, at least most of it. What i would want to understand is how to get the timer working, just the metronome itself. Usually if i want to separate two events like the tick and tock sound in the program with a specified time interval, i would just put a delay for the required number of seconds. I don't get where the delay is coming between the tick and tock sounds. How is the program waiting lets say at 60bpm for 1 sec in their code? I can't seem to figure out.
I just want to be able to setup the timer and figure out the delays required to set the intervals between two events.
I am pasting the sample code from apple which i think would be relative to the time calculations:
// Give sound thread high priority to keep the timing steady
[NSThread setThreadPriority:1.0];
BOOL continuePlaying = YES;
while (continuePlaying) { // loop until cancelled
// Use an autorelease pool to prevent the build-up of temporary date objects
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
The Metronome example uses threading; if you're not familiar with threading, then I would look up NSTimer instead. It will be easier to get your timer working.
The Metronome example uses threading; if you're not familiar with threading, then I would look up NSTimer instead. It will be easier to get your timer working.
Is there any reference that I can get for understanding threading? I know the concept in a generic sense but i just don't understand the implementation of it in this context? How am i making delays between one action and the other?
- (void)startDriverTimer(id)info {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Give sound thread high priority to keep the timing steady
[NSThread setThreadPriority:1.0];
BOOL continuePlaying = YES;
while (continuePlaying) { // loop until cancelled
// Use an autorelease pool to prevent the build-up of temporary date objects
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
[self playSound];
[self performSelectorOnMainThread:@selector(animateArmTo OppositeExtreme) withObject:nil waitUntilDone:NO];
NSDate *curtainTime = [NSDate dateWithTimeIntervalSinceNow:self.duration];
NSDate *currentTime = [NSDate date];
// wake up periodically to see if we've been cancelled
while (continuePlaying && ([currentTime compare:curtainTime] != NSOrderedDescending)) {
if ([soundPlayerThread isCancelled] == YES) {
continuePlaying = NO;
}
[NSThread sleepForTimeInterval:0.001];
currentTime = [NSDate date];
}
[loopPool release];
}
[pool release];
}
Okay - this method has two while loops - the big while loop around everything, and the little while loop around the sleep command.
The big while loop plays the sound, calls the animation, and creates a date called "curtainTime" that contains the (current date + self.duration). Then control passes into the little while loop, which loops until either we reach curtainTime, or the thread gets canceled. Once curtainTime is reached, we iterate the big loop again (play sound, call animation.)
As far as I can tell, the timing must be coming from thet self.duration. What is duration being set to? One second?
Thanks for your help, I appreciate it indeed, in fact i will buy your app as a token of my appreciation
while (continuePlaying && ([currentTime compare:curtainTime] != NSOrderedDescending)) {
if ([soundPlayerThread isCancelled] == YES) {
continuePlaying = NO;
}
I knew this is the line of code that was implementing the timing, but could not figure out that it was waiting for current time to be same as curtain time. Duration is indeed 60/bpm which is what the metronome uses to calculate the delay.
Also do you understand why, after we get out of the small loop, they have
[NSThread sleepForTimeInterval:0.001];
currentTime = [NSDate date];
why does the thread need to sleep? would this not mess up the timing? Is .001s a standard sleep duration? Also I could not figure out why they have the current time calculated here again, cos the next time we get back in the big loop, we have
NSDate *currentTime = [NSDate date];
In my app , i want this metronome running, and give me interrupts at specific time intervals. I would like to play a short sound as they do but i also need to insert 2 images at the same click, small images maybe 20x 20. Do you think i will have to do threading for this? Also why do you think this example has used threads, instead of using a timer/NSTimer?
Also do you understand why, after we get out of the small loop, they have
[NSThread sleepForTimeInterval:0.001];
currentTime = [NSDate date];
why does the thread need to sleep? would this not mess up the timing? Is .001s a standard sleep duration?
The small loop checks the time, sleeps a bit, then repeats. There's no need to check the time a million times a second - so the thread sleeps for a bit. I wouldn't say its a standard sleep duration - if it were shorter you'd waste more CPU/battery, if it were longer then the timing might be off (it would take longer to notice that you've reached the curtain time.)
Quote:
Also I could not figure out why they have the current time calculated here again, cos the next time we get back in the big loop, we have
NSDate *currentTime = [NSDate date];
The small loop repeatedly checks currentTime against curtainTime (waiting for currentTime to reach curtainTime). So we need to update currentTime after every sleep, to find out what time it is now.
NSDate is not a counter that keeps track of current time - it just stores the time that [NSDate date] was called. So if you didn't update currentTime after sleeping, then the loop would never exit (currentTime would never increase).
Quote:
In my app , i want this metronome running, and give me interrupts at specific time intervals. I would like to play a short sound as they do but i also need to insert 2 images at the same click, small images maybe 20x 20. Do you think i will have to do threading for this? Also why do you think this example has used threads, instead of using a timer/NSTimer?
Some people have complained that timers are not accurate enough - that you can't get more accurate than 50-100 ms. That's probably why this example uses threads. I haven't noticed this in my app, but I'm only running at 30 frames per second. If you don't need super tight timing, it's certainly easier to use NSTimer.
Hi!
In this particular code, there is a delay in tempo.
The Metronomes 210 is actually 180 in reality.
I have managed to get it to 210 -> 200, but it is still not right.
Can someone tell me why the delay (we are working with NSDate, I do not see why such a delay) and is there a way to get rid of it.
I figured I could find a formula and alter the tempo, but is there another way?
Hi!
In this particular code, there is a delay in tempo.
The Metronomes 210 is actually 180 in reality.
I have managed to get it to 210 -> 200, but it is still not right.
Can someone tell me why the delay (we are working with NSDate, I do not see why such a delay) and is there a way to get rid of it.
I figured I could find a formula and alter the tempo, but is there another way?
Meny thanks for your replies,
Trotelj
This is a pretty awful way to do timing, truth be told.
The code in the thread is creating a pile of NSDate objects, and then draining it's auto-release pool to get rid of them. That's memory wasteful.
There is a much simpler, cleaner way to check times. NSDate has a class method +timeIntervalSinceReferenceDate that gives you a double precision value of the number of seconds since some arbitrary date (1 January 1970?) with very high accuracy.
You can use timeIntervalSinceReferenceDate to check the amount of elapsed time without creating a bunch of NSDate objects.
The app is also written using NSThreads, which are about 2 generations out of date. With Mac OS 10.5 and iOS 3, we were told to use NSOperations and NSOperationQueues instead. Much simpler and cleaner. Having written a BUNCH of code using NSThreads, I would have to agree that NSOperationQueues are much, much cleaner and easier to use.
With Mac OS 10.6 and iOS4.0, we have an even better way to do async processing: blocks and Grand Central Dispatch. I haven't had occasion to use GCD yet, but it looks cleaner and easier still then NSOperationQueues.
In short, don't look at the timing code in Metronome too closely. It isn't a sterling example of the right way to do things.
Check out this password generator app that shows various techniques including using a data container singleton object to share data between objects in your project.