1

So I am having situation where I want to loop a sound and gradually decrease the delay between sounds. Means, sound beep should be played most often as time passing.

Right now, I am using an AVAudioPlayer with a sound which has duration of 1 sec, but the actual sound lasts only 0.5 sec. The other 0.5 sec of the sound is silenced. So when I use numberOfLoops property and set it to -1 (endless repeating) in combination with rate property on AVAudioPlayer and change it to some higher value than 1, I get desired result... Still, this is not that ideal, because sound may end up distorted (because of pitch).

I am looking for something like SKAction in SpriteKit for example, where I can run a block of code in after certain delay... What would be an optimal way for doing this in UIKit ?

Whirlwind
  • 14,286
  • 11
  • 68
  • 157
  • Have you looked at using an NSTimer event, such as [NSTimer scheduledTimerWithTimeInterval: ? You could always get the event to create a new event for a shorter and shorter time, firing your sound action each time – Dominic Jun 14 '16 at 10:17
  • @Dominic Actually I have completely forgot about that idea because I mostly work with SpriteKit and in SpriteKit NSTimer is a no-no most of the times :) – Whirlwind Jun 14 '16 at 10:41

1 Answers1

0

To get a block of code to run after a certain delay in UIKit, one way is to use GCD, namely dispatch_after.

- (void)myMethod {
        __weak __typeof__(self) weakSelf = self;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.dispatchInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            if (weakSelf.isRunning){
                [weakSelf myMethod];
            }
        });

        // Code to play audio here
    }

Here, dispatchInterval is a float representing the number of seconds before the loop repeats. So just update dispatchInterval to reduce the loop length each time it fires. To start the loop just set isRunning to YES and call myMethod, to stop the loop just set isRunning to NO. This approach avoids having to use NSTimer, which can cause issues with retaining strong references to self.

GCBenson
  • 516
  • 1
  • 3
  • 9
  • Thanks for your response. Well NSTimer will hold strong ref to self, but we have NSTimer invalidate method, right ? – Whirlwind Jun 14 '16 at 10:41
  • By the way, do I have to define dispatchInterval as a property ? – Whirlwind Jun 14 '16 at 10:47
  • Theres a discussion about this here http://stackoverflow.com/questions/16821736/weak-reference-to-nstimer-target-to-prevent-retain-cycle. Basically, if you don't rely on the destruction of the object that owns the timer to invalidate it, then it would be fine to use a timer! Would be a bit simpler I suppose, but at least now you have two options. – GCBenson Jun 14 '16 at 10:48
  • Yeh dispatch interval would be a property – GCBenson Jun 14 '16 at 10:49
  • 1
    Well somehow it didn't worked with GCD. A method is called (if I am not wrong) correctly. But the code which plays sound have some odd and unequal delays. – Whirlwind Jun 14 '16 at 11:08
  • Yeh so you won't get sample accurate timing with that method. Just looking at your original post again, you already have the result you want but are worried about distortion? The sound would only distort if the volume gets too high, which you can control either by lowering the gain on the audio, or the volume of the audio player. So keep the volume in check and you'll be ok. If it's clipping (harsh noise when the sound repeats) you're worried about, you could put a small attack (fade in) on the audio sample to get around this. – GCBenson Jun 14 '16 at 11:15
  • The thing is that sound will be *distorted* because it will be player every time at higher rate. And that is what i don't want. When changing rate on AVAudioPlayer instance, sound distortion is unavoidable. I think all this has nothing with the sound volume ... But I might be wrong... – Whirlwind Jun 14 '16 at 11:18
  • Ahhhh I see now. So I'm not sure you can avoid that with AVAudioPlayer. You can try using a CADisplayLink to get a callback to fire at 60fps. That will give you way better timing than GCD. Count milliseconds between callbacks and have a threshold, each time the threshold is met, reset the count and reduce the threshold. You could drop down to Core Audio to get sample accurate timing, but the learning curve is steep and it's probably overkill for what you want. – GCBenson Jun 14 '16 at 11:26
  • Okay, thanks for pointing that. I got pretty decent results with AVAudioPlayer and rate stuff. No delay, but rather the sound is a bit pitched at higher rates. Will look at CADisplayLink though. – Whirlwind Jun 14 '16 at 11:33