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
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と組み合わせて,音空間との身体的なインタラクションをしてみましょう.