iOS」カテゴリーアーカイブ

センターキャンセル機能を追加しました

すっかり更新が空いてしまっていましたが、この前アップデートした「Tune Changer」「ピッチシフト」「タイムストレッチ」にセンターキャンセルの機能を追加したことについて書くことにします。

https://itunes.apple.com/jp/app/tune-changer-for-karaoke-instrument/id734191615?mt=8
https://itunes.apple.com/jp/app/realtime-pitch-shifter/id670120398?mt=8
https://itunes.apple.com/jp/app/time-stretcher/id637376117?mt=8

簡単な実装で実現してますので、説明しておきたいと思います。
まずはコードを載せます。AUGraph のコールバック部分の修正だけです。

OSStatus renderCallback(void *inRefCon,
				AudioUnitRenderActionFlags *ioActionFlags,
				const AudioTimeStamp *inTimeStamp,
				UInt32 inBusNumber,
				UInt32 inNumberFrames,
				AudioBufferList *ioData)
{
	OSStatus err = noErr;
	AudioIO *def = (AudioIO *)inRefCon;

	UInt32 ioNumberFrames = inNumberFrames;
	err = ExtAudioFileRead(def.extAudioFile, &ioNumberFrames, ioData);
	if (err) {
		NSLog(@"ExtAudioFileRead err = %d", (int)err);
		return -1;
	}

	////////////////////////
	// add
	if (def.isCenterCancel) {
		// only stereo
		if (def.outputFormat.mChannelsPerFrame > 1) {
			Float32 *outL = (Float32 *)ioData->mBuffers[0].mData;
			Float32 *outR = (Float32 *)ioData->mBuffers[1].mData;
			for (int i = 0; i < ioNumberFrames; i++) {
				Float64 tmpL = (Float64)outL[i] - (Float64)outR[i];
				outL[i] = (Float32)tmpL;
				outR[i] = (Float32)tmpL;
			}
		}
	}
	////////////////////////

	/*
	 略
	*/

	return err;
}

ExtAudioFileRead から読み込んだ ioData からデータを直接読み込み、LRの差分を求めて、結果を両チャンネルに入れています。それだけです。
「Center Cancel」ボタンを押下されている場合に処理されるようになっています。

最近の書籍で Core Audio の説明はほとんどないので、何げに知られていない(?)かもしれないのですが、iOS 5以降では、オーディオデータの型は Float32 になっています。
ただ、AudioStreamBasicDescription の mFormatFlags を指定してやれば、以前(iOS 2.x)の 8.24 固定小数点数でも動いていましたが、iOS 8 SDKではそろそろ怪しいかもしれないので、このあたりも古いコードは見直していくのが無難かもしれません。

イコライザーを追加しました(Tune Changer)

従来のタイムストレッチ、ピッチシフトに加え、8バンドのグラフィックイコライザーの機能を追加しました。

IMG_0060  IMG_2414

https://itunes.apple.com/jp/app/tune-changer-for-karaoke-instrument/id734191615?mt=8
https://itunes.apple.com/jp/app/realtime-pitch-shifter/id670120398?mt=8
https://itunes.apple.com/jp/app/time-stretcher/id637376117?mt=8

イコライザーの実装は、Core Audio の kAudioUnitSubType_NBandEQ を用いました。
自前で作成するとなると、FFTを用いたりする方法になるのでしょうが、素直に用意されている AUGraph を用いる方が音質もパフォーマンスも間違いないでしょう、ということで。。

まず定義部分の実装です。
以前書いた以下のブログをもとに、変更/追加部分について触れます。
「Audio Unit によるリアルタイム・タイムストレッチ」
http://www.loopsessions.com/blog/?m=201310

ですので、タイムストレッチ -> イコライザー の流れについての実装になります。(ピッチシフトは含んでいません)

まずは変数定義です。

	AUGraph _graph;
	AudioUnit _converterUnit;
	AudioUnit _aUiPodTimeUnit;
	AudioUnit _auNBandEQUnit;
	AudioUnit _converterUnit2;
	AudioUnit _multiChannelMixerUnit;
	AudioUnit _remoteIOUnit;

 

AUGraph に NBandEQ の Node の追加します。(他の Node については省略)

	AudioComponentDescription cd;
	cd.componentType = kAudioUnitType_Effect;
	cd.componentSubType = kAudioUnitSubType_NBandEQ;
	cd.componentManufacturer = kAudioUnitManufacturer_Apple;
	cd.componentFlags = 0;
	cd.componentFlagsMask = 0;
	AUNode  auNBandEQNode;
	ret = AUGraphAddNode(_graph, &cd, &auNBandEQNode);
	ret = AUGraphNodeInfo(_graph, auNBandEQNode, NULL, &_auNBandEQUnit);

 

続いて、AudioUnit の接続の設定です。
今回の接続は以下の流れになります。
AUConverter -> AUiPodTimeOther -> AUNBandEQ -> AUConverter2 -> Remote IO

従来の AudioUnit は SInt32型の8.24固定小数点で定義されていたのですが、iOS5以降から定義された AudioUnit については、Float32型で定義されています。
そのため、AUNBandEQ のinput側のASBD(AudioStreamBasicDescription)構造体を取得して、AUiPodTimeOther および AUConverter のoutput側にセットする必要があります。

	ret = AudioUnitSetProperty(_converterUnit,
							   kAudioUnitProperty_StreamFormat,
							   kAudioUnitScope_Input, 0,
							   &_outputFormat, size);

	ret = AudioUnitSetProperty(_converterUnit2,
							   kAudioUnitProperty_StreamFormat,
							   kAudioUnitScope_Input, 0,
							   &_outputFormat, size);

	ret = AudioUnitSetProperty(_converterUnit2,
							   kAudioUnitProperty_StreamFormat,
							   kAudioUnitScope_Output, 0,
							   &_outputFormat, size);

	////////////////
	AudioStreamBasicDescription outputFormatTmp;

	// [GET] NBandEQ I
	ret = AudioUnitGetProperty(_auNBandEQUnit,
							   kAudioUnitProperty_StreamFormat,
							   kAudioUnitScope_Input, 0,
							   &outputFormatTmp, &size);

	ret = AudioUnitSetProperty(_aUiPodTimeUnit,
							   kAudioUnitProperty_StreamFormat,
							   kAudioUnitScope_Output, 0,
							   &outputFormatTmp, size);

	ret = AudioUnitSetProperty(_converterUnit,
							   kAudioUnitProperty_StreamFormat,
							   kAudioUnitScope_Output, 0,
							   &outputFormatTmp, size);
	////////////////

	ret = AudioUnitSetProperty(_multiChannelMixerUnit,
							   kAudioUnitProperty_StreamFormat,
							   kAudioUnitScope_Output, 0,
							   &_outputFormat, size);

	ret = AudioUnitSetProperty(_multiChannelMixerUnit,
							   kAudioUnitProperty_StreamFormat,
							   kAudioUnitScope_Input, 0,
							   &_outputFormat, size);

	// remoteIO I
	ret = AudioUnitSetProperty(_remoteIOUnit,
							   kAudioUnitProperty_StreamFormat,
							   kAudioUnitScope_Input, 0,
							   &_outputFormat, size);

 

上記で説明した流れで Node を接続します。

	ret = AUGraphConnectNodeInput(_graph,
								  converterNode, 0,
								  aUiPodTimeNode, 0);

	ret = AUGraphConnectNodeInput(_graph,
								  aUiPodTimeNode, 0,
								  auNBandEQNode, 0);

	ret = AUGraphConnectNodeInput(_graph,
								  auNBandEQNode, 0,
								  converterNode2, 0);

	ret = AUGraphConnectNodeInput(_graph,
								  converterNode2, 0,
								  multiChannelMixerNode, 0);

	ret = AUGraphConnectNodeInput(_graph,
								  multiChannelMixerNode, 0,
								  remoteIONode, 0);

 

次にパラメータ部分の説明です。今回は以下の帯域(Hz)を固定で設定しましたが、実際は任意の周波数で設定することが可能です。
64, 125, 250, 500, 1k, 2k, 4k, 8k
加えて、帯域幅(Q値)の設定も可能ですが、今回はデフォルト値で固定にしています。
以下に該当部分のソースコードを全て載せましたので、詳細の説明は省略します。

AUEffectProxy_NBandEQ.h

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

#define BAND_NUM (8)

@interface AUEffectProxy : NSObject

- (id)initWithAudioUnit:(AudioUnit)audioUnit;

- (void)resetParameters;

- (Float32)getFrequency:(int)index;
- (void)setFrequency:(Float32)value index:(int)index;
- (Float32)getGain:(int)index;
- (void)setGain:(Float32)value index:(int)index;
- (Float32)getBandwidth:(int)index;
- (void)setBandwidth:(Float32)value index:(int)index;

@end

 

AUEffectProxy_NBandEQ.m

#import "AUEffectProxy_NBandEQ.h"

@interface AUEffectProxy ()
{
	AudioUnit _audioUnit;

	// デフォルト値の保存用
	Float32 gain_;
	Float32 bandwidth_;

	AudioUnitParameterID paramId_;
}

- (Float32)valueForParameter:(int)parameter;
- (void)setValue:(Float32)value forParameter:(int)parameter min:(Float32)min max:(Float32)max;
@end

@implementation AUEffectProxy

Float32 const FREQUENCY[] = {
	64.0, 125.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0, 8000.0
};

- (id)initWithAudioUnit:(AudioUnit)audioUnit
{
	self = [super init];
	if (self) {
		_audioUnit = audioUnit;

		UInt32 size = sizeof(UInt32);
		AudioUnitGetPropertyInfo(_audioUnit,
								 kAudioUnitProperty_ParameterList,
								 kAudioUnitScope_Global,
								 0,
								 &size,
								 NULL);

		int numOfParams = size / sizeof(AudioUnitParameterID);

		AudioUnitParameterID paramList[numOfParams];

		AudioUnitGetProperty(_audioUnit,
							 kAudioUnitProperty_ParameterList,
							 kAudioUnitScope_Global,
							 0,
							 paramList,
							 &size);

		int iCntFreq = 0;
		for (int i = 0; i < numOfParams; i++) {
			paramId_ = paramList[i];

			AudioUnitParameterInfo paramInfo;
			size = sizeof(paramInfo);
			AudioUnitGetProperty(_audioUnit,
								 kAudioUnitProperty_ParameterInfo,
								 kAudioUnitScope_Global,
								 paramList[i],
								 &paramInfo,
								 &size);

			// init
			if (strcmp(paramInfo.name, "bypass") == 0) {
				// 「使用:0」固定
				AudioUnitParameterValue fBypass = 0;
				AudioUnitSetParameter(_audioUnit, paramList[i], kAudioUnitScope_Global, 0, fBypass, 0);
			} else if (strcmp(paramInfo.name, "frequency") == 0) {
				// set
				AudioUnitSetParameter(_audioUnit, paramList[i], kAudioUnitScope_Global, 0, FREQUENCY[iCntFreq], 0);
				iCntFreq ++;
			} else if (strcmp(paramInfo.name, "gain") == 0) {
				AudioUnitGetParameter(_audioUnit, paramList[i], kAudioUnitScope_Global, 0, &gain_);
			} else if (strcmp(paramInfo.name, "bandwidth") == 0) {
				AudioUnitGetParameter(_audioUnit, paramList[i], kAudioUnitScope_Global, 0, &bandwidth_);
			}
		}
    }
    return self;
}

- (void)resetParameters
{
	for (int i = 0; i < BAND_NUM; i++){
		[self setFrequency:FREQUENCY[i] index:i];
		[self setGain:gain_ index:i];
		[self setBandwidth:bandwidth_ index:i];
	}
}

- (Float32)getFrequency:(int)index
{
	return FREQUENCY[index];
}
- (void)setFrequency:(Float32)value index:(int)index
{
	[self setValue:value forParameter:kAUNBandEQParam_Frequency + index min:10 max:21600.0];
}

- (Float32)getGain:(int)index
{
	return [self valueForParameter:kAUNBandEQParam_Gain + index];
}
- (void)setGain:(Float32)value index:(int)index
{
	[self setValue:value forParameter:kAUNBandEQParam_Gain + index min:-96.0f max:24.0f];
}

- (Float32)getBandwidth:(int)index
{
	return [self valueForParameter:kAUNBandEQParam_Bandwidth + index];
}
- (void)setBandwidth:(Float32)value index:(int)index
{
	[self setValue:value forParameter:kAUNBandEQParam_Bandwidth + index min:0.05 max:5.0];
}

- (Float32)valueForParameter:(int)parameter
{
	Float32 value;
	OSStatus rt = AudioUnitGetParameter(_audioUnit,
										parameter,
										kAudioUnitScope_Global,
										0,
										&value);
	if (rt != noErr) {
		NSLog(@"Error getting parameter(%d)", parameter);
		return MAXFLOAT;
	}
	return value;
}

- (void)setValue:(Float32)value forParameter:(int)parameter min:(Float32)min max:(Float32)max
{
	if (value < min || value > max) {
		return;
	}
	OSStatus rt = AudioUnitSetParameter(_audioUnit,
										parameter,
										kAudioUnitScope_Global,
										0,
										value,
										0);
	if (rt != noErr) {
		NSLog(@"Error Setting parameter(%d)", parameter);
	}
}

@end

 

また、Ver.2.01 にて、他のプレーヤーアプリとのミックス再生にも対応しました。
(逆を言えば、今までは途中でiPodを起動したら再生が停止してしまっている状態でした。。)

AudioSessionInitialize は deprecated になるようですので、AVAudioSession を用いた以下の方法で実装しました。

- (void)setupAudioSession
{
	AVAudioSession *session = [AVAudioSession sharedInstance];

	// スリープモードでも再生
	// オーディオのミックス動作のオーバーライド
	NSError *setCategoryError = nil;
	[session setCategory:AVAudioSessionCategoryPlayback
			 withOptions:AVAudioSessionCategoryOptionMixWithOthers
				   error:&setCategoryError];

	[session setActive:YES error:nil];
}

 

iOSでの Audio Session は AVAudioSession の使用にシフトしていくようです。フレームワークも整備されていき、どんどん実装も簡単になっていく傾向にあるのかもしれません。

ゲームアプリをリリースしました(cocos2d-x)

先月になりますが、ゲームアプリ「十二支バトル ~ ボール当てゲーム編」をリリースしました。

十二支になれなかったネコが主人公で、十二支にバトルを挑むというコンセプトですが、実はこれは後付けで、ゲーム用のオープンソースライブラリ「cocos2d-x」を用いて、ボールを飛ばすゲームを作ってみたい、というか作っておきたいということから始まりました。

できたゲームが以下のようなものです。

画面構成は「ス○2」がかなりモデルになっています。。

以下からダウンロードできます。よかったら遊んでみてください。
appStore      googlePlay

ボールを投げる部分の実装について、本当に抜粋ではありますが説明します。
cocos2d-x(ver.2.x の場合ですが)では物理エンジンの Box2d も含んでいますので、これを使用します。
b2Body クラスのインスタンスを body としますと、body つまりボールに力を与える処理は以下のようにしています。
(実際は前処理がそれなりにありますがここでは省略します)

// iPower は直前のUIから取得

body->ApplyLinearImpulse(b2Vec2((float32)iPower * cos(56.0 * M_PI / 180.0), (float32)iPower * sin(56.0 * M_PI / 180.0)), body->GetWorldCenter());

body->ApplyAngularImpulse(0.0);

iPower の値を変えることでボールの飛距離が変わります。ゲームではボタンの長押しで iPower の値を可変になるようにしました。

ボール同士やボールとキャラクターとの衝突判定の取得についても cocos2d-x で用意されています。便利ですね。

このアプリでは、cocos2d-x のバージョンは ver.2.2.3 を用いました。ただ、本家では Ver.3.x が完全に主流になりつつあるようですので、次回作ではどうするかを考えないといけなさそうな感じではあります。。

区間指定のループ再生に対応しました(Tune Changer)

楽曲プレーヤーアプリ「Tune Changer」をアップデートしました。

https://itunes.apple.com/jp/app/tune-changer-for-karaoke-instrument/id734191615?mt=8

今までは曲全体のループ再生に対応していましたが、今回のアップデートでループ区間を指定できるようにしました。

IMG_2306

①:ループ区間指定カーソル
②:ループ開始時刻、ループ終了時刻 表示ラベル

カーソルを水平に動かすと、表示ラベルの時刻も連動して変更します。
また、表示ラベルをタップすると、表示ラベルに設定した時刻に再生時刻が移動します。(波形下側のカーソルも連動します)
さらに、表示ラベルをタップした状態で水平にスワイプすると、タップした方のループ区間指定カーソルを微調整することができます。

これらが今回追加した主な機能です。

ループを有効にするには、「Once/Loop」ボタンをONの状態にしてください。

区間指定のループ再生は必要であることはわかっていながらも、ようやくの対応になりました。
これで楽曲プレーヤーアプリとしての最低限の機能は揃ったのではないかと思います。
(「Tune Changer」の売りはリアルタイムでのテンポ/ピッチ変換ではありますが)

他にも、ボーカルキャンセルや、イコライザなどエフェクトの追加対応も可能かと思いますが、また気が向いたときにでも対応していきたいと思います。
問題はコントロールの配置かもしれませんが。

cocos2d-xで AID のポップアップ型広告を実装する方法(iOS, Android)

スマホ向け全画面広告を提供されているAID様のポップアップ型広告のモジュールをcocos2d-xに組み込む場合、通常のネイティブの場合とやや異なるため、こちらにまとめました。

AID様のモジュールについてはこちらをご参照ください。
http://www.aid-ad.jp/

 
まず、cocos2d-xのプロジェクト構成の確認から行います。

プロジェクト名を「TestProject」とします。
TestProject/ 以下の追加/修正を行うファイル構成を以下にまとめます。

Classes/
	Android/AdViewManager.cpp	// add
	Android/AdViewManager.h		// add
	HelloWorld.cpp
	HelloWorld.h
proj.android/
	jni/Android.mk/Android.mk
	libs/jp.live_aid.aid.jar	// add
	src/com/loopsessions/TestProject/TestProject.java
proj.ios/
	AdViewManager.h		// add
	AdViewManager.mm	// add
	AidAd.framework		// add
	AppController.h
	AppController.mm

 

【iOSでの実装方法】

基本的な手順や実装については以下を参考にしてください。
http://report.aid-ad.jp/docs/ios/manual/index.html

AdViewManager.h / AdViewManager.mm を新規作成します。

// AdViewManager.h

#ifndef ADVIEW_MANAGER_H
#define ADVIEW_MANAGER_H
 
class AdViewManager
{
public:
	static void setAidAdView();
};
 
#endif
// AdViewManager.mm

#import "AdViewManager.h"
#import "AppController.h"
 
void AdViewManager::setAidAdView()
{
	AppController *appController = (AppController *)[UIApplication sharedApplication].delegate;
	[appController setAidAdView];
}

AppController.h に実装するメソッドの宣言を記述します。

// AppController.h

#import <UIKit/UIKit.h>

@class RootViewController;

@interface AppController : NSObject <UIApplicationDelegate>
{
    UIWindow *_window;
    RootViewController *_viewController;
}

- (void)setAidAdView;

@end

AppController.mm に広告を呼び出すメソッドを記述します。

// AppController.mm

- (void)setAidAdView
{
	NSString *kMEDIA_CODE = @"idxxxxxxxx";
	AidAdAgent* agent = [AidAd agentForMedia:kMEDIA_CODE];
	[agent setDialogBlocker:self];
	[agent startLoading];
	
	[agent showDialog];
}

HelloWorld.cpp で広告を表示したい場合、呼び出したいタイミングで以下を実行してください。

// HelloWorld.cpp

// 呼び出したいタイミングで以下を実行
AdViewManager::setAidAdView();

 

【Androidでの実装方法】

基本的な手順や実装については以下を参考にしてください。
http://report.aid-ad.jp/docs/android/manual/index.html

Androidの場合は、ファイル構成が変更したことをMakeファイル(Android.mk)に記述する必要があります。

# Android.mk

LOCAL_SRC_FILES := hellocpp/main.cpp \
                   ../../Classes/AppDelegate.cpp \
                   ../../Classes/HelloWorld.cpp \
                   ../../Classes/Android/AdViewManager.cpp

LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../Classes
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../Classes/Android

(略)

AdViewManager.cpp / AdViewManager.h を新規作成します。
AdViewManager.h はiOSと同じ内容です。

// AdViewManager.cpp

#include "AdViewManager.h"
#include <jni.h>
#include "platform/android/jni/JniHelper.h"

// パッケージ名 + Javaクラス名
#define CLASS_NAME "com/loopsessions/TestProject/TestProject"

using namespace std;
using namespace cocos2d;

void AdViewManager::setAidAdView()
{
	cocos2d::JniMethodInfo t;
	if (cocos2d::JniHelper::getStaticMethodInfo(t, CLASS_NAME, "setAidAdView", "()V")) {
		t.env->CallStaticVoidMethod(t.classID, t.methodID);
		t.env->DeleteLocalRef(t.classID);
	}
}

TestProject.java に広告を呼び出すメソッドを記述します。

// TestProject.java

	(略)

public class TestProject extends Cocos2dxActivity
{
	(略)
	private static AdController mAidAdController = null;
	(略)

	public static void setAidAdView() {
		activity.runOnUiThread(new Runnable() {
			public void run() {
				if (mAidAdController == null) {
					String MEDIA_CODE = "idxxxxxxxx";
					mAidAdController = new AdController(MEDIA_CODE, activity);
				}
				mAidAdController.startPreloading();
				mAidAdController.showDialog(AdController.DialogType.ON_EXIT);
			}
		});
    }

}

広告表示の呼び出しは、iOS/Android共通のコードになります。

// HelloWorld.cpp

// 呼び出したいタイミングで以下を実行
AdViewManager::setAidAdView();

Audio Unit によるリアルタイム・タイムストレッチ(3. パラメータ設定)

本タイトルでは最後となる、タイムストレッチのパラメータ設定について説明していきます。
まず、前回説明した prepareAUGraph 関数内でコメントにしていた箇所を有効にします。

- (OSStatus)prepareAUGraph
{
	
	// (略)
	
	// 6. AUiPodTimeOtherへ設定するためのプロキシオブジェクトを作成
	_aUiPodTimeProxy = [[AUiPodTimeProxy alloc] initWithReverbUnit:_aUiPodTimeUnit];
	
	
	// (略)
	
}

上記で生成している AUiPodTimeProxy クラスでタイムストレッチのパラメータ設定を行います。

まず、AUiPodTimeProxy クラスの定義を示します。
初期化関数として、AudioUnit のパラメータを引数として必要とするため、自作の initWithAudioUnit 関数を用意しています。

@interface AUiPodTimeProxy : NSObject
{
    AudioUnit _aUiPodTimeUnit;
	
	Float32 _playbackRate;
	
	AudioUnitParameterID _paramId;
}

@property(atomic) Float32 playbackRate;

- (id)initWithAudioUnit:(AudioUnit)aUiPodTimeUnit;

@end

実装部分は以下のようになります。

@implementation AUiPodTimeProxy

@synthesize playbackRate = _playbackRate;

- (id)initWithAudioUnit:(AudioUnit)aUiPodTimeUnit
{
    self = [super init];
    if (self) {
        _aUiPodTimeUnit = aUiPodTimeUnit;
		_playbackRate = self.playbackRate;
		
		UInt32 size = sizeof(UInt32);
		AudioUnitGetPropertyInfo(_aUiPodTimeUnit,
								 kAudioUnitProperty_ParameterList,
								 kAudioUnitScope_Global,
								 0,
								 &size,
								 NULL);
		
		int numOfParams = size / sizeof(AudioUnitParameterID);
		AudioUnitParameterID paramList[numOfParams];
		
		AudioUnitGetProperty(_aUiPodTimeUnit,
							 kAudioUnitProperty_ParameterList,
							 kAudioUnitScope_Global,
							 0,
							 paramList,
							 &size);
		
		for (int i = 0; i < numOfParams; i++) {
			_paramId = paramList[i];
			
			AudioUnitParameterInfo paramInfo;
			size = sizeof(paramInfo);
			AudioUnitGetProperty(_aUiPodTimeUnit,
								 kAudioUnitProperty_ParameterInfo,
								 kAudioUnitScope_Global,
								 paramList[i],
								 &paramInfo,
								 &size);
			
			AudioUnitSetParameter(_aUiPodTimeUnit,
								  paramList[i],
								  kAudioUnitScope_Global,
								  0,
								  _playbackRate,
								  0);
		}
    }
    return self;
}

- (Float32)playbackRate
{
	Float32 value = 0.0f;
	OSStatus ret = AudioUnitGetParameter(_aUiPodTimeUnit,
										 _paramId,
										 kAudioUnitScope_Global,
										 0,
										 &value);
	if (ret != noErr) {
		NSLog(@"Error getting parameter(%d)", parameter);
	}
	return value;
}
- (void)setPlaybackRate:(Float32)value
{
	// AUiPodTimeOther の場合
	int parameter = 0;
	Float32 min = 0.5f;
	Float32 max = 2.0f;
	
	if (value < min || value > max) {
		NSLog(@"Invalid value(%f)<%f - %f> for parameter(%d). Ignored.", value, min, max, parameter);
		return;
	}
	
	OSStatus ret = AudioUnitSetParameter(_aUiPodTimeUnit,
										 parameter,
										 kAudioUnitScope_Global,
										 0,
										 value,
										 0);
	if (ret != noErr) {
		NSLog(@"Error setting parameter(%d)", parameter);
	}
}

@end

initWithAudioUnit 関数について説明します。
AudioUnitGetPropertyInfo で、AudioUnitGetProperty で取得する paramList のサイズを取得します。
次の AudioUnitGetProperty(第2引数を kAudioUnitProperty_ParameterList)で paramList の各IDを取得します。
for文内の AudioUnitGetProperty(第2引数を kAudioUnitProperty_ParameterInfo)で各IDのパラメータが取得できます。

タイムストレッチ(AUiPodTimeOther)の場合、取得する値は以下のようになります。

 numOfParams = 1
 paramList[0] = 0
 paramInfo.name = rate
 paramInfo.minValue = 0.5
 paramInfo.maxValue = 2.0
 paramInfo.defaultValue = 1.0

最後の AudioUnitSetParameter で、上記の値を設定します。

playbackRate プロパティを用いて、タイムストレッチの値を取得/設定することができます。
スライダー(UISlider)などを用いてコントロールすると良いと思います。

さすがAppleが用意している機能だけあって、リアルタイムでもスムーズにタイムストレッチをしてくれるので気持ちいいです。よかったら試してみてください。

Audio Unit によるリアルタイム・タイムストレッチ(2. タイムストレッチ)

今回はタイムストレッチの処理を行う実装の説明を行います。
前回の prepareAUGraph 関数を追記/修正することになります。

「2. AUNodeの作成」において、componentSubType が kAudioUnitSubType_AUiPodTimeOther の AUNode を追加することで、タイムストレッチの処理を行うことができます。

「5. Nodeの接続」は以下のようになります。
AUConverter -> AUiPodTimeOther -> Remote IO

- (OSStatus)prepareAUGraph
{
	// 1. AUGraphの準備
	NewAUGraph(&_graph);
	AUGraphOpen(_graph);
	
	// 2. AUNodeの作成
	AudioComponentDescription cd;
	
	cd.componentType = kAudioUnitType_FormatConverter;
	cd.componentSubType = kAudioUnitSubType_AUConverter;
	cd.componentManufacturer = kAudioUnitManufacturer_Apple;
	cd.componentFlags = 0;
	cd.componentFlagsMask = 0;
	AUNode converterNode;
	AUGraphAddNode(_graph, &cd, &converterNode);
	AUGraphNodeInfo(_graph, converterNode, NULL, &_converterUnit);
	
	cd.componentType = kAudioUnitType_FormatConverter;
	cd.componentSubType = kAudioUnitSubType_AUiPodTimeOther;
	cd.componentManufacturer = kAudioUnitManufacturer_Apple;
	cd.componentFlags = 0;
	cd.componentFlagsMask = 0;
	AUNode aUiPodTimeNode;
	AUGraphAddNode(_graph, &cd, &aUiPodTimeNode);
	AUGraphNodeInfo(_graph, aUiPodTimeNode, NULL, &_aUiPodTimeUnit);
	
	cd.componentType = kAudioUnitType_Output;
	cd.componentSubType = kAudioUnitSubType_RemoteIO;
	cd.componentManufacturer = kAudioUnitManufacturer_Apple;
	cd.componentFlags = 0;
	cd.componentFlagsMask = 0;
	AUNode remoteIONode;
	AUGraphAddNode(_graph, &cd, &remoteIONode);
	AUGraphNodeInfo(_graph, remoteIONode, NULL, &_remoteIOUnit);
	
	// 3. Callbackの作成
	AURenderCallbackStruct callbackStruct;
	callbackStruct.inputProc = renderCallback;
	callbackStruct.inputProcRefCon = self;
	AUGraphSetNodeInputCallback(_graph,
								converterNode,
								0,	// bus number
								&callbackStruct);
	
	// 4. 各NodeをつなぐためのASBDの設定
	AudioStreamBasicDescription asbd = {0};
	UInt32 size = sizeof(asbd);
	
	// converter IO
	AudioUnitSetProperty(_converterUnit,
						 kAudioUnitProperty_StreamFormat,
						 kAudioUnitScope_Input, 0,
						 &_outputFormat, size);
	
	AudioUnitSetProperty(_converterUnit,
						 kAudioUnitProperty_StreamFormat,
						 kAudioUnitScope_Output, 0,
						 &_outputFormat, size);
	
	// aUiPodTime IO
	AudioUnitSetProperty(_aUiPodTimeUnit,
						 kAudioUnitProperty_StreamFormat,
						 kAudioUnitScope_Output, 0,
						 &_outputFormat, size);
	
	AudioUnitSetProperty(_aUiPodTimeUnit,
						 kAudioUnitProperty_StreamFormat,
						 kAudioUnitScope_Input, 0,
						 &_outputFormat, size);
	
	// remoteIO I
	AudioUnitSetProperty(_remoteIOUnit,
						 kAudioUnitProperty_StreamFormat,
						 kAudioUnitScope_Input, 0,
						 &_outputFormat, size);
	
	// 5. Nodeの接続
	// AUConverter -> AUiPodTimeOther
	AUGraphConnectNodeInput(_graph,
							converterNode, 0,
							aUiPodTimeNode, 0);
	
	// AUiPodTimeOther -> Remote IO
	AUGraphConnectNodeInput(_graph,
							aUiPodTimeNode, 0,
							remoteIONode, 0);
	
	// 6. AUiPodTimeOtherへ設定するためのプロキシオブジェクトを作成
	// (次回に説明するため、今回はコメント)
//	_aUiPodTimeProxy = [[AUiPodTimeProxy alloc] initWithReverbUnit:_aUiPodTimeUnit];
	
	// 7. AUGraphを初期化
	AUGraphInitialize(_graph);
	
	return ret;
}

タイムストレッチの値を変更するための処理は、上記でコメントしている箇所にて行うことになります。説明は次回に行います。

(追記:2015/03/23)
現時点での最新OS(iOS 8)で確認したところ、kAudioUnitSubType_AUiPodTimeOther についてもFloat32型での定義扱いになっているようです。(最初に本記事を書いた頃はそうではなかったのですが)
そのため、上記はそのまま使うと実行時エラーになると思います。
修正方法は、「イコライザーを追加しました(Tune Changer)」を参考にしていただければと思います。

Audio Unit によるリアルタイム・タイムストレッチ(1. 準備)

Audio Unit によるリアルタイムにタイムストレッチを行う方法を説明していきます。

タイムストレッチとは、音程を保ったまま再生速度を変える処理です。AUGraph(Audio Unit を複数接続するサービス)に含まれる機能を用いて、リアルタイム・タイムストレッチを実現することができます。

以下のアプリで実現していますので、よかったら試してみてください。
Time Stretcher – Realtime Time Stretch

今回は準備編ということで、オーディオファイルを読み込み、再生するまでの処理についてまとめます。

まず、クラスを定義します。

@interface AudioIO : NSObject
{
	ExtAudioFileRef _extAudioFile;
	AudioStreamBasicDescription _outputFormat;
	UInt32 _numberOfChannels;
	SInt64 _totalFrames;
	SInt64 _currentFrame;
	
	AUGraph _graph;
	AudioUnit _remoteIOUnit;
	AudioUnit _converterUnit;
	AudioUnit _aUiPodTimeUnit;
}

オーディオファイルの準備を行います。指定したオーディオファイルのファイルフォーマットを _outputFormat に読み込ませています。

AudioStreamBasicDescription AUCanonicalASBD(Float64 sampleRate, UInt32 channel)
{
	AudioStreamBasicDescription audioFormat;
	audioFormat.mSampleRate = sampleRate;
	audioFormat.mFormatID = kAudioFormatLinearPCM;
//	audioFormat.mFormatFlags = kAudioFormatFlagsAudioUnitCanonical;  // CA_CANONICAL_DEPRECATED
	audioFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsNonInterleaved;
	audioFormat.mChannelsPerFrame = channel;
	audioFormat.mBytesPerPacket = sizeof(Float32);
	audioFormat.mBytesPerFrame = sizeof(Float32);
	audioFormat.mFramesPerPacket = 1;
	audioFormat.mBitsPerChannel = 8 * sizeof(Float32);
	audioFormat.mReserved = 0;
	return audioFormat;
}

- (SInt64)prepareAudioFile:(NSURL*)fileURL
{
	// ExAudioFileの作成
	ExtAudioFileOpenURL((CFURLRef)fileURL, &_extAudioFile);
	
	// ファイルフォーマットを取得
	AudioStreamBasicDescription inputFormat;
	UInt32 size = sizeof(AudioStreamBasicDescription);
	ExtAudioFileGetProperty(_extAudioFile,
							kExtAudioFileProperty_FileDataFormat,
							&size,
							&inputFormat);
	
	// Audio Unit正準形のASBDにサンプリングレート、チャンネル数を設定
	_numberOfChannels = inputFormat.mChannelsPerFrame;
	_outputFormat = AUCanonicalASBD(inputFormat.mSampleRate, inputFormat.mChannelsPerFrame);
	
	// 読み込むフォーマットをAudio Unit正準形に設定
	ExtAudioFileSetProperty(_extAudioFile,
							kExtAudioFileProperty_ClientDataFormat,
							sizeof(AudioStreamBasicDescription),
							&_outputFormat);
	
	// トータルフレーム数を取得しておく
	SInt64 fileLengthFrames = 0;
	size = sizeof(SInt64);
	ExtAudioFileGetProperty(_extAudioFile,
							kExtAudioFileProperty_FileLengthFrames,
							&size,
							&fileLengthFrames);
	_totalFrames = fileLengthFrames;
	
	// 位置を0に移動
	ExtAudioFileSeek(_extAudioFile, 0);
	_currentFrame = 0;
	
	return fileLengthFrames;
}

上記で読み込んだオーディオフォーマットを AUGraph で再生します。
簡単のために、まずは単純にオーディオファイルを普通に再生する手順を以下に示します。

- (OSStatus)prepareAUGraph
{
	OSStatus ret = noErr;

	// 1. AUGraphの準備
	NewAUGraph(&_graph);
	AUGraphOpen(_graph);
	
	// 2. AUNodeの作成
	AudioComponentDescription cd;
	
	cd.componentType = kAudioUnitType_FormatConverter;
	cd.componentSubType = kAudioUnitSubType_AUConverter;
	cd.componentManufacturer = kAudioUnitManufacturer_Apple;
	cd.componentFlags = 0;
	cd.componentFlagsMask = 0;
	AUNode converterNode;
	AUGraphAddNode(_graph, &cd, &converterNode);
	AUGraphNodeInfo(_graph, converterNode, NULL, &_converterUnit);
	
	cd.componentType = kAudioUnitType_Output;
	cd.componentSubType = kAudioUnitSubType_RemoteIO;
	cd.componentManufacturer = kAudioUnitManufacturer_Apple;
	cd.componentFlags = 0;
	cd.componentFlagsMask = 0;
	AUNode remoteIONode;
	AUGraphAddNode(_graph, &cd, &remoteIONode);
	AUGraphNodeInfo(_graph, remoteIONode, NULL, &_remoteIOUnit);
	
	// 3. Callbackの作成
	AURenderCallbackStruct callbackStruct;
	callbackStruct.inputProc = renderCallback;
	callbackStruct.inputProcRefCon = self;
	AUGraphSetNodeInputCallback(_graph,
								converterNode,
								0,	// bus number
								&callbackStruct);
	
	// 4. 各NodeをつなぐためのASBDの設定
	AudioStreamBasicDescription asbd = {0};
	UInt32 size = sizeof(asbd);
	
	// converter IO
	AudioUnitSetProperty(_converterUnit,
						 kAudioUnitProperty_StreamFormat,
						 kAudioUnitScope_Input, 0,
						 &_outputFormat, size);
	
	AudioUnitSetProperty(_converterUnit,
						 kAudioUnitProperty_StreamFormat,
						 kAudioUnitScope_Output, 0,
						 &_outputFormat, size);
	
	// remoteIO I
	AudioUnitSetProperty(_remoteIOUnit,
						 kAudioUnitProperty_StreamFormat,
						 kAudioUnitScope_Input, 0,
						 &_outputFormat, size);
	
	// 5. Nodeの接続
	// AUConverter -> Remote IO
	AUGraphConnectNodeInput(_graph,
							converterNode, 0,
							remoteIONode, 0);
	
	// 6. AUGraphを初期化
	AUGraphInitialize(_graph);
	
	return ret;
}

上記では簡略化のために省略していますが、AUGraph の関数を用いる際は、戻り値のチェックを逐次行うことをお勧めします。正しく定義できていない場合、AudioUnitSetProperty や AUGraphInitialize でエラーとなることが経験上多いです。

上記で定義したコールバックの実装は以下のようになります。再生時にくり返し呼び出されます。

OSStatus renderCallback(void *inRefCon,
			AudioUnitRenderActionFlags *ioActionFlags,
			const AudioTimeStamp *inTimeStamp,
			UInt32 inBusNumber,
			UInt32 inNumberFrames,
			AudioBufferList *ioData)
{
	OSStatus err = noErr;
	AudioIO *def = (AudioIO *)inRefCon;

	UInt32 ioNumberFrames = inNumberFrames;
	err = ExtAudioFileRead(def.extAudioFile, &ioNumberFrames, ioData);

	return err;
}

再生/停止を行います。

- (void)start
{
	if (_graph) {
		Boolean isRunning = false;
		OSStatus ret = AUGraphIsRunning(_graph, &isRunning);
		if (ret == noErr && !isRunning) {
			AUGraphStart(_graph);
		}
	}
}

- (void)stop
{
	if (_graph) {
		Boolean isRunning = false;
		OSStatus ret = AUGraphIsRunning(_graph, &isRunning);
		if (ret == noErr && isRunning) {
			AUGraphStop(_graph);
		}
	}
}

アプリ終了時は、以下の解放処理を行います。

- (void)releaseAUGraph
{
	[self stop];
	if(_graph != NULL) {
		AUGraphUninitialize(_graph);
		AUGraphClose(_graph);
		DisposeAUGraph(_graph);
		_graph = NULL;
	}
}

- (void)releaseAudioFile
{
	if (_extAudioFile != NULL) {
		ExtAudioFileDispose(_extAudioFile);
	}
}

次回はリアルタイム・タイムストレッチの場合の実装について説明を行います。