すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

openAL入門 - 2.音空間をつくろう!

前回はopenALつかって単純に音をならすとこだけをやりました. ただ,これではopenALの本当の利点はわかりにくいです.

openALの最大の利点は,いろんな音を仮想空間中に配置して,あたかもその場にいるかのごとく体験できるということ.

そこで今回は,複数音源を配置した空間を作ってみましょう.

基本的に前回のコードをベースとして改造していきます.

NUM_BUFFER個のバッファを準備して音声データを読み込んだ後に, NUM_SOURCE個のソースそれぞれにバッファを対応させていきます.

同一音源を複数配置したい場合でも,読み込むバッファは一つで大丈夫です.

んじゃみてみましょう

header

#import <UIKit/UIKit.h>
#import <OpenAL/al.h>
#import <OpenAL/alc.h>
#import <AudioToolbox/AudioToolbox.h>
#import "MyOpenALSupport.h"

#define NUM_BUFFER 3
#define NUM_SOURCE 3

@interface ismViewController : UIViewController{

    ALuint buffers[NUM_BUFFER];
    ALuint sources[NUM_SOURCE];

}

@end

implement

#import "ismViewController.h"

@interface ismViewController ()

@end

@implementation ismViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // 1.openALコンテクストの用意
    ALCdevice *device = alcOpenDevice(NULL); //一番始めのデバイスを使います
    
    if (device)
    {
        ALCcontext *context = alcCreateContext(device, NULL); //コンテクストの作成
        alcMakeContextCurrent(context); //作成したコンテクストをカレントに設定?
    }
    
    // 2.buffer用意
    ALenum error;

    alGetError(); //Gen前に呼んでおく.errorをクリア.
    alGenBuffers(NUM_BUFFER, buffers); //曲データ1つにつきバッファ1つ.

    if ((error = alGetError()) != AL_NO_ERROR)
    {
        // エラー処理
        NSLog(@"alGenBuffers error");
        return;
    }
    
    // 3.source用意
    
    alGetError(); // gen前.errorをクリア.
    alGenSources(NUM_SOURCE, sources); //空間に配置する数の分生成する.
    
    if ((error = alGetError()) != AL_NO_ERROR)
    {
        // エラー処理
        NSLog(@"alGenSources error");
        return;
    }
    
    // 4.sourceのプロパティ設定
    float thetaDiff = M_PI * 2 / (float)NUM_SOURCE;
    float radius = 20.0;
    for(int i=0; i < NUM_SOURCE; i++){
        alSourcei( sources[i], AL_LOOPING , AL_TRUE);   // 繰り返し
        alSourcei( sources[i], AL_PITCH   , 1.0f);      //
        alSourcei( sources[i], AL_GAIN    , 0.45f);     // 音量
        alSource3f(sources[i], AL_POSITION, radius * sin(thetaDiff), 0, radius * cos(thetaDiff)); // 音源位置
    }
    
    
    // 5.caf読み込み
    void    *data;
    ALenum  format;     // フォーマット
    ALsizei size;       // ファイルサイズ
    ALsizei freq;       // 周波数
    NSBundle *bundle = [NSBundle mainBundle]; //iPhoneのdir読み込みのため.
    NSString *fileNames[] = {@"music1", @"music2", @"music3"}; //buffer分

    // bufferによみこみ
    for (int i=0; i < NUM_BUFFER; i++) {
        CFURLRef fileURL = (__bridge CFURLRef)[NSURL fileURLWithPath:[bundle pathForResource:fileNames[i]
                                                                                      ofType:@"caf"]];
        data = MyGetOpenALAudioData(fileURL, &size, &format, &freq);  // sample内の読み込み関数を拾ってきた.
        alBufferDataStaticProc(buffers[i], format, data, size, freq); // bufferにデータを登録
    }
    
    // source と buffer の接続
    for (int i=0; i<NUM_SOURCE; i++) {
        alSourcei(sources[i], AL_BUFFER, buffers[i]);
    }
    
    // 6.listerの設定
    ALfloat listenerPos[] = {0.0, 0.0, 0.0};
    ALfloat listenerVel[] = {0.0, 0.0, 0.0};
    ALfloat listenerOri[] = {0.0, 0.0, -1.0, 0.0, 1.0, 0.0}; // look.x.y.z / up.x.y.z

    alListenerfv(AL_POSITION, listenerPos);
    alListenerfv(AL_VELOCITY, listenerVel);
    alListenerfv(AL_ORIENTATION, listenerOri);
    
    // 7.再生
    alSourcePlayv(NUM_SOURCE, sources);
}

これで複数音源の読み込みができました. 起動すると幾つかの音源(music1~3)が自分の周りで鳴っている感じがすると思います.

ちょこっと改造

ちょこっと手を加えて,音空間を移動できるようにしてみましょう. 簡単に,スライダーによってリスナのX,Y,Z位置と向きを変更できるようにしてみました.画面イメージはこんな感じ.画像でのビジュアライズなどはしていませんw

f:id:ism1000ch:20131230182157p:plain

header追記

@property (weak, nonatomic) IBOutlet UILabel *XValLabel;
@property (weak, nonatomic) IBOutlet UILabel *YValLabel;
@property (weak, nonatomic) IBOutlet UILabel *ZValLabel;
@property (weak, nonatomic) IBOutlet UILabel *DirValLabel;

- (IBAction)play1:(id)sender;
- (IBAction)play2:(id)sender;
- (IBAction)play3:(id)sender;
- (IBAction)XChange:(id)sender;
- (IBAction)YChange:(id)sender;
- (IBAction)ZChange:(id)sender;
- (IBAction)DirChange:(id)sender;

- (void)setListenerPos;

imple追記

//べた書きでの位置・向き変更
- (IBAction)play1:(id)sender {
    ALfloat listenerPos[] = {5.0, 5.0, 0.0};
    ALfloat listenerVel[] = {0.0, 0.0, 0.0};
    ALfloat listenerOri[] = {0.0, 0.0, -1.0, 0.0, 1.0, 0.0}; // look / up
    
    alListenerfv(AL_POSITION, listenerPos);
    alListenerfv(AL_VELOCITY, listenerVel);
    alListenerfv(AL_ORIENTATION, listenerOri);
    
}

- (IBAction)play2:(id)sender {
    ALfloat listenerPos[] = {-5.0, -5.0, 0.0};
    ALfloat listenerVel[] = {0.0, 0.0, 0.0};
    ALfloat listenerOri[] = {0.0, 0.0, -1.0, 0.0, 1.0, 0.0}; // look / up
    
    alListenerfv(AL_POSITION, listenerPos);
    alListenerfv(AL_VELOCITY, listenerVel);
    alListenerfv(AL_ORIENTATION, listenerOri);
}

- (IBAction)play3:(id)sender {
    ALfloat listenerPos[] = {0.0, 0.0, 10.0};
    ALfloat listenerVel[] = {0.0, 0.0, 0.0};
    ALfloat listenerOri[] = {0.0, 0.0, -1.0, 0.0, 1.0, 0.0}; // look / up
    
    alListenerfv(AL_POSITION, listenerPos);
    alListenerfv(AL_VELOCITY, listenerVel);
    alListenerfv(AL_ORIENTATION, listenerOri);
}

//スライダでの位置・向き変更
- (IBAction)XChange:(id)sender {
    UISlider *sl = sender;
    NSLog(@"value:%f", sl.value);
    _XValLabel.text = [NSString stringWithFormat:@"%f", 30 * sl.value];
    [self setListenerPos];
}

- (IBAction)YChange:(id)sender {
    UISlider *sl = sender;
    NSLog(@"value:%f", sl.value);
    _YValLabel.text = [NSString stringWithFormat:@"%f", 30 * sl.value];
    [self setListenerPos];
}

- (IBAction)ZChange:(id)sender {
    UISlider *sl = sender;
    NSLog(@"value:%f", sl.value);
    _ZValLabel.text = [NSString stringWithFormat:@"%f", 30 * sl.value];
    [self setListenerPos];
}

- (void)setListenerPos {
    ALfloat listenerPos[] = {
        [_XValLabel.text floatValue],
        [_YValLabel.text floatValue],
        [_ZValLabel.text floatValue],
    };
    alListenerfv(AL_POSITION, listenerPos);
}

- (IBAction)DirChange:(id)sender {
    UISlider *sl = sender;
    float theta = sl.value * M_PI * 2;
    _DirValLabel.text = [NSString stringWithFormat:@"%f", theta];
    
    ALfloat listenerOri[] = {sin(theta),0.0,cos(theta),0.0,1.0,0.0};
    alListenerfv(AL_ORIENTATION, listenerOri);
}

値べた書きでの処理と,スライダによる処理の二つを書いてみました. 前半のボタンによる処理が値べた書きでの位置に変更するもの, 後半のスライダによる処理が任意に位置を変更するもの.

openALではalListenerfvとかalSourceiとか,al(Source/Listener)(データ型)という名の関数で音空間の設定を行います. 「どのようなデータを設定するか」のフラグ(AL_BUFFERとかAL_POSITIONとか)と共にデータ配列を与えることにより操作ができます.与えるフラグによって必要なデータ数が違ったりするので,このあたりは公式リファレンスをみるとよいでしょう.

まとめ

openALを使って複数音を配置した環境を作成してみました. そしてsliderによる操作で空間を移動できるようにしてみました.

次回はcoreMotionと組み合わせて,音空間との身体的なインタラクションをしてみましょう.