3

I have 6 buttons that each play an audio sample from a .caf file. If I press a button the sound plays fine, if I wait for it to end and press it again it plays fine but if I press the button fast then the sound will pop and click before playing.

I initially didn't have this popping problem when simply allocating the AVAudioPlayer each time the button is clicked but this created a memory leak with multiple allocations. So I created 6 AVAudioPlayers for each button and re-used it, this got rid of the memory leak but now the samples click/pop when overwritten.

I have tried lots of different ways to stop this happening from setting the volume to 0, stopping the AVAudioPlayer instance before playing the next sample etc but cant find the correct way to repeatedly play the same sample sound with fast button presses and stop the popping.

I do have the AVAudioPlayer property as retain in the .h and use autorelease in the alloc statement.

Any help please?

** Edit: found a solution, its not pretty but it works.

Basically I created 10 AVAudioPlayers that autorelease and if one [myPlayer1 isPlaying] then I use the next one.

e.g.

 BOOL done = NO;

if(![self.audioPlayer0 isPlaying] && done == NO) {
    self.audioPlayer0 = [ [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil] autorelease ];
    [audioPlayer0 play];
    done = YES;
}

if(![self.audioPlayer1 isPlaying] && done == NO) {
    self.audioPlayer1 = [ [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil] autorelease ];
    [audioPlayer1 play];
    done = YES;
}

if(![self.audioPlayer2 isPlaying] && done == NO) {
    self.audioPlayer2 = [ [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil] autorelease ];
    [audioPlayer2 play];
    done = YES;
}

if(![self.audioPlayer3 isPlaying] && done == NO) {
    self.audioPlayer3 = [ [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil] autorelease ];
    [audioPlayer3 play];
    done = YES;
}

if(![self.audioPlayer4 isPlaying] && done == NO) {
    self.audioPlayer4 = [ [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil] autorelease ];
    [audioPlayer4 play];
    done = YES;
}

if(![self.audioPlayer5 isPlaying] && done == NO) {
    self.audioPlayer5 = [ [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil] autorelease ];
    [audioPlayer5 play];
    done = YES;
}

if(![self.audioPlayer6 isPlaying] && done == NO) {
    self.audioPlayer6 = [ [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil] autorelease ];
    [audioPlayer6 play];
    done = YES;
}

if(![self.audioPlayer7 isPlaying] && done == NO) {
    self.audioPlayer7 = [ [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil] autorelease ];
    [audioPlayer7 play];
    done = YES;
}

if(![self.audioPlayer8 isPlaying] && done == NO) {
    self.audioPlayer8 = [ [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil] autorelease ];
    [audioPlayer8 play];
    done = YES;
}

if(![self.audioPlayer9 isPlaying] && done == NO) {
    self.audioPlayer9 = [ [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil] autorelease ];
    [audioPlayer9 play];
}
GeoffCoope
  • 952
  • 9
  • 20

2 Answers2

1

Why remember all ten audio players instead of dynamically creating them?

In your new solution, what you're effectively doing is looping through all the audio players to find one that isn't playing. Why not do the same thing in an array?

NSMutableArray *playerHolder = [[NSMutableArray alloc] init];
int maxNumberOfBuffers = 10;

for (int i=0; i<maxNumberOfBuffers; i++)
{
    AVAudioPlayer *audioPlayer;
    [playerHolder addObject:audioPlayer];
}

Then, simply look through the array when trying to play something:

for (int i=0; i<playerHolder.count; i++)
{
    if (![[playerHolder objectAtIndex:i] isPlaying])
    {
        AVAudioPlayer *freePlayer = [playerHolder objectAtIndex:i];
        freePlayer = [ [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil] autorelease ];
        [freePlayer play];
    }
}

In theory, that should achieve the same thing as your solution, without the clutter.

Daniel G. Wilson
  • 14,955
  • 2
  • 32
  • 39
  • Just note that in both solutions there is nothing to handle a situation where all the players are being used. If that were to happen, the sound would simply not be played. In both solutions, you could increase the number of players to about 16 and not have an issue. It all depends on the length of your sounds. – Daniel G. Wilson Jul 06 '11 at 11:46
  • Got the array working nicely now, had to make a few changes to the example code to get it to work at my end but the concept was sound. Thanks again. – GeoffCoope Jul 06 '11 at 14:49
  • 1
    Beware: that code is buggy. Here's how I'm doing it: https://gist.github.com/alexch/5453875 (not yet totally debugged -- ymmv). BTW this technique is known as an "instance pool". – AlexChaffee Apr 24 '13 at 17:22
  • Thanks for clarifying that Alex - would you mind elaborating on why it is buggy? I assume it has something to do with a lack of correct memory management, which in theory would be solved if one used ARC. Also, this was written as pseudo-code, completely from memory, so I'm honestly not surprised that it has issues. – Daniel G. Wilson Apr 24 '13 at 22:35
0

I can't be sure, but the problem probably occurs because you are truncating the wave at a non zero-crossing, which causes a vertical portion in the waveform (nasty harmonics, click/pop). You need to fade the waveform instead of cutting it out. Look into raised cosine smoothing/shaping.

spender
  • 117,338
  • 33
  • 229
  • 351
  • This could work too but after looking into setting it up decided to try generating a new AVAudioPlayer if the existing one was still playing. This allows for multiple samples to overlay each other rather than one at a time with a single AVAudioPlayer object. Thanks anyway. – GeoffCoope Jul 06 '11 at 14:55