TheTwoNotes
TheTwoNotes

Reputation: 463

Accurate repeating iPhone sounds

I am working with an app that needs to play a sound "tap" at 60 ms intervals (making for 1000 taps per minute). When I get past about 120 ms intervals (500 taps per minute), the sounds become very erratic and seem to be piling up on one another.

The sound byte that I'm using is 10 ms long, and I have a thread timer that is running at 20 ms intervals. I am using AVAudioPlayer to play the sound, and have tried formats of wav, caf and m4a. I have tested with NSLog outputs to see when the AVAudioPlayer is being fired off, and when the timer loop is polling...and all the times seem accurate enough.

I have added Audio Session handling in my viewDidLoad() method as follows:

 [[AVAudioSession sharedInstance]
 setCategory: AVAudioSessionCategoryPlayback
 error: &setCategoryError];

Here is my timer loop that is running in it's own thread:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 

// Give the sound thread high priority to keep the timing steady.
[NSThread setThreadPriority:1.0];

while (running) {     // loop until stopped. 
    [self performSelectorOnMainThread:@selector(playOutput) withObject:nil waitUntilDone:NO];

    // An autorelease pool to prevent the build-up of temporary objects.
    NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];

    NSDate *curtainTime = [[NSDate alloc] initWithTimeIntervalSinceNow:sleepValue];
    NSDate *currentTime = [[NSDate alloc] init];
    // Wake up periodically to see if we've been cancelled. 
    while (running && ([currentTime compare:curtainTime] == NSOrderedAscending)) { 
        [NSThread sleepForTimeInterval:0.015];
        [currentTime release];
        currentTime = [[NSDate alloc] init];
    }

    [curtainTime release]; 
    [currentTime release]; 
    [loopPool drain];
}
[pool drain];

Here is how I am initializing my AVAudioPlayer:

NSString *beatOne = @"3h";
NSString *beatOneSoundFile = [[NSBundle mainBundle]
                              pathForResource:beatOne ofType:@"caf"];
NSURL *beatOneURL = [[NSURL alloc] initFileURLWithPath:beatOneSoundFile];
beatPlayerOne = [[AVAudioPlayer alloc]initWithContentsOfURL:beatOneURL error:nil];
beatPlayerOne.delegate = self;
beatPlayerOne.currentTime = 0;
[beatPlayerOne prepareToPlay];
[beatOneURL release];

And here is where the audio player is played:

[beatPlayerOne pause];
beatPlayerOne.currentTime = 0;
[beatPlayerOne play];

Thanks in advance for any help...

Upvotes: 1

Views: 876

Answers (3)

apple16
apple16

Reputation: 1147

You should always avoid [NSThread Sleep] as much as possible. Use NSTimer instead:

[NSTimer scheduledTimerWithTimeInterval:(0.015) target:self selector:@selector(timerfn) userInfo:nil repeats:NO];
//the @selector(timerfn) is which function it calls

recall this timer in the function it calls if you want to continue replaying the sound

OR

save the timer to a variable like so:

 mytimer=[NSTimer scheduledTimerWithTimeInterval:(0.015) target:self selector:@selector(timerfn) userInfo:nil repeats:YES];

and use:

 [mytimer invalidate];
    mytimer=nil;

to kill it.

Then just play the sound in the function. You could also use [NSTimer alloc] and [mytimer release] i think...

Also, 66.6 calls a second does seem somewhat overkill.

Upvotes: 0

Luke
Luke

Reputation: 7210

As Dan Ray said, NSTimer and NSThread are not reliable for precise timing. If I were you, I would use an audio queue - it will request buffers of audio through an input callback, and you will fill them. Provided you respond promptly, it will request and reproduce these buffers at exactly the hardware sample rate, so you can do some simple calculations to figure out the timing of your taps and fill each buffer from your file or from samples stored in an array or vector. You'd fill it using AudioFileReadPackets if you choose to go straight from your file.

This will be a little more work but it's much more versatile and powerful. You can use audio queues for live synthesis and processing of audio, so their timing is perfectly precise as long as you meet their deadlines. If you're careful about processor usage, you could have a tap sound every few milliseconds with no issue.

Take a look at the SpeakHere sample application to get started with audio queues. Audio units are also useful for this sort of thing, and you can provide data to them through a callback, but they take a little more setup and have a steeper learning curve.

Good luck!

Upvotes: 0

Dan Ray
Dan Ray

Reputation: 21893

NSTimer (and, relatedly, NSThread sleep) are not reliable "clock-timing" mechanisms. If something blocks the main thread, the run loop won't end until it unblocks, and timers and thread sleeps won't be called on time.

Also the resolution on NSTimer is at BEST about 1/30 of a second, though the framework makes no promises about the regularity of its firing. As you've seen, it's not regular. If your mail app fires up in the background, it's likely to throw all your fancy buzzing audio out of whack.

You'll be best off, I think, to generate longer sound files that contain various speeds of clicks, and fire them less frequently.

Upvotes: 2

Related Questions