LoginSignup
58
56

More than 1 year has passed since last update.

ARC環境下でAVAudioPlayerを使い柔軟かつシンプルにサウンドを再生する方法

Last updated at Posted at 2014-01-13

ARC環境下では、以下のコードでサウンドは再生されません。

- (void)playSound{
    NSString *soundPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"hogehoge.mp3";
    NSURL *urlOfSound = [NSURL fileURLWithPath:soundPath];

    AVAudioPlayer* player = [[AVAudioPlayer alloc] initWithContentsOfURL:urlOfSound error:nil];
    [player setNumberOfLoops:0];
    player.delegate = (id)self;
    [player prepareToPlay];
    [player play];
}

なぜなら、メソッドの終了後にplayerのメモリが解放されてしまうからです。
retainをして参照カウントを上げようにも、ARC環境下ではretainは使えません。
この問題に対処するために、AVAudioPlayerのインスタンスをメンバ変数にしようとしても、再生するサウンドの数だけAVAudioPlayerのインスタンスの宣言が必要になり、柔軟なシステムにはなりません。

そして、メンバ変数の場合は同じAVAudioPlayerのインスタンスを再生すると一度それまで同じインスタンスで再生されていた音が停止してから音が再生されるため、音を重ねることが苦手です。
楽器アプリや音ゲーなどで音を再生する際は、音をいくらでも重ねられた方が、自然に聞こえます。

この問題に対処するには、メソッド内で宣言したAVAudioPlayerのインスタンスを、メンバ変数の配列に格納してやればいいことになります。
サウンドの再生が終了したタイミングで、配列からAVAudioPlayerのインスタンスを削除してやることで、メモリが解放されるのでメモリが圧迫されることもありません。
以下、上記を1つのクラスにまとめたサンプルコードです。

SEManager.h
#import <Foundation/Foundation.h>

@interface SEManager : NSObject{
    NSMutableArray *soundArray;
}

@property(nonatomic) float soundVolume;

+ (SEManager *)sharedManager;
- (void)playSound:(NSString *)soundName;

@end
SEManager.m
#import "SEManager.h"
#import "AVFoundation/AVFoundation.h"

@implementation SEManager

static SEManager *sharedData_ = nil;

+ (SEManager *)sharedManager{
    @synchronized(self){
        if (!sharedData_) {
            sharedData_ = [[SEManager alloc]init];
        }
    }
    return sharedData_;
}

- (id)init
{
    self = [super init];
    if (self) {
        soundArray = [[NSMutableArray alloc] init];
        _soundVolume = 1.0;
    }
    return self;
}

- (void)playSound:(NSString *)soundName{   
    NSString *soundPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:soundName];
    NSURL *urlOfSound = [NSURL fileURLWithPath:soundPath];

    AVAudioPlayer* player = [[AVAudioPlayer alloc] initWithContentsOfURL:urlOfSound error:nil];
    [player setNumberOfLoops:0];
    player.volume = _soundVolume;
    player.delegate = (id)self;
    [soundArray insertObject:player atIndex:0];
    [player prepareToPlay];
    [player play];
}

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
    [soundArray removeObject:player];
}

@end

どのクラスからでもアクセスできるように、シングルトンにしてみました。
実際に他のクラスから音を再生する際は、SEManagerをimportした上で以下のように書きます。

    [[SEManager sharedManager] playSound:@"hogehoge.mp3"];

GitHubにサンプルコードを置いておきますね。
https://github.com/yukinaga/SEManager
SEManagerを使うことで、シンプルな実装で音がいくらでも重なって再生されますのでお試しください。

拙作『ちんあなごのうた 南の海の音楽祭』では、SEManagerを活用して柔軟かつシンプルなサウンドの管理を実現しています。
もしご興味があれば、ぜひ遊んでみてください。
https://itunes.apple.com/jp/app/chinanagonouta-nanno-haino/id611600816?mt=8

58
56
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
58
56