投稿者「LoopSessions」のアーカイブ

Processingを用いたプロジェクションマッピング

以前にもブログで紹介しているルービックキューブへのプロジェクションマッピングについて、またまたですが異なるアプローチで試してみました。

今回は Processing を利用しました。(実は今までの中で最もシンプル)

プロジェクションマッピングでの課題はキャリブレーション(マッピングのための調整)ですが、Processing には「keystone」という射影変換を実現するライブラリが公開されているとのことで、こちらを最大限に活用させていただきました。

ライブラリの追加方法です。
1. メニューから次の項目を選択する。 Sketch > Import Library > Add Library
2. 「keystone」を選択してインストールする

先に作成したデモ映像を載せておきます。

投影環境はこんな感じです。
IMG_1201

コードについては、簡易な説明にしておきます。
「keystone」ライブラリに関する主要なインスタンスは以下になります。

projectionMappingCube.pde

Keystone ks;	// Keystone本体(画面全体を指す)

 

SurfaceBase.pde

class SurfaceBase 
{
  CornerPinSurface surface;	// 射影平面
  PGraphics offscreen;	// 射影平面内部のグラフィックス

  SurfaceBase() {
    
    surface = ks.createCornerPinSurface(600, 600, 60);
    offscreen = createGraphics(600, 600, P3D);
    
    // 略
  }

  // 略
}

 

あらかじめ割り当てたキーを押下することで、投影の表示を切り替えを行うのに加え、キャリブレーションに関する処理についても行っています。

projectionMappingCube.pde

// 起動時に呼ばれる
void setup() {
  // 略
}

// 描画更新時に呼ばれる
void draw() {
  // 略
}

// キー押下時に呼ばれる
void keyPressed() {
  switch(key) {
  case '0':
  case '1':
  case '2':

    // 略

    break;

  case 'c':
    // キャリブレーション(CornerPinSurface の各頂点をマウスで設定できる)
    ks.toggleCalibration();
    break;

  case 'l':
    // 前回保存したキャリブレーション情報をロード
    ks.load();
    break;

  case 's':
    // キャリブレーション結果を保存
    ks.save();
    break;
  }
}

 

キャリブレーションする前は、実行した画面の左上に接した正方形が表示されることになりますが、キー’c’を押下することで形状を調整できるようになります。調整が完了したら、再度キー’c’を押下することでキャリブレーション状態から抜けることができます。
キー’s’を押下すると、同一ディレクトリに「keystone.xml」が作成され、調整した状態が保存されます。これで、次回起動したときに、キー’l’を押下することで前回調整した状態に復元されます。

デモ映像の抜粋版のコードを Github にアップしました。
https://github.com/JunichiMinamino/projectionMappingCube

今回は比較的負荷が少ない処理の内容にとどめたので、Processing で作成するのに適しているかと思います。より負荷の高い処理を行いたい場合は openFrameworks を利用する方が良いかと思います。
今回はキャリブレーションについてもがんばってみたこともあり、少しコツがわかってきた感じがします。またの機会に再度oFでも試してみようと思います。

WordPressでの抜粋表示

弊社のブログは WordPress を用いていますが、前から気になっていた、ブログトップの投稿一覧を抜粋表示(各投稿を例えば200文字まで表示)する方法についてまとめました。

弊社は古いですが「Twenty Twelve」を使用していますので、その前提で書いていきます。

抜粋表示の方法については、いろいろな方のブログで見つかるのですが、いざ試してみると、動作面でどうしてもうまくいかなかったので保留にしていました。
そのよく書かれている方法は以下の通りです。

content.php 内に以下の呼び出しが見つかります。(引数は省略)

<?php the_content(); ?>

これを以下のように書き換えます。

<?php the_excerpt(); ?>

そうすると、ブログトップの投稿一覧が抜粋表示されます。
ですが、いずれかの投稿を選択してみると、その投稿ページに移りますが、なんとそちらでも抜粋表示になっているという、、それでは肝心の内容が見れないよ、という状態になっていました。

それからさらに調べたところ、以下の記述にすることによって解決できました。

<?php 
if (is_home()) {
	the_excerpt();		// 抜粋を表示
} else {
	the_content();		// 全文を表示
}
?>

つまりは、「is_home()」というブログトップかどうかの判別関数を用いれば良いということでした。ですが、これを触れているブログはほとんど見当たらなかったので、その方々はどうされているのかな、というのが今となっては気になっています。。

少しカスタマイズしたかったので、functions.php に以下を追記しました。

function new_excerpt_mblength($length) {
	return 250;
}
add_filter('excerpt_mblength', 'new_excerpt_mblength');

function new_excerpt_more($more) {
	return '<a href="'. get_permalink($post->ID) . '">' . '...続きを読む' . '</a>';  
}
add_filter('excerpt_more', 'new_excerpt_more');

というわけで、こちらが表示結果です。
http://www.loopsessions.com/blog/

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

すっかり更新が空いてしまっていましたが、この前アップデートした「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();

逆プロジェクションマッピング

前回に引き続き、プロジェクションマッピングを試してみました。対象は前回と同じルービックキューブですが、今回は実物への投影を試してみました。

単に投影させるだけではおもしろくない、ということで、openFrameworks のAddonにある、ofxBullet という3Dの物理エンジンを用いていくつかギミックを入れてみることにしました。

投影環境はこんな感じです。

IMG_2091

作成した映像はこちら。

実際に投影して改めて認識したことですが、、プロジェクションマッピングにおいてはキャリブレーション(マッピングのための調整)のための施策や考慮がかなり重要であるようです。
今回の場合は斜めから投影していることもあり、投影画像全体を変形して出力させるべきであったかと思いましたが、その方法が見つからなかったので、残念ながら次回までの課題となりました。実際、上面は全然合っていませんが、今回は見逃していただくということで。。

以下は映像での実現方法について説明します。映像では以下の項目の内容を試しています。

1. 投影 色変化
2. ブロックが下に落ちる(全体)
3. ブロックが下に落ちる(1つずつ)
4. ブロックの分解
5. 逆投影

27個の立方体のブロックを並べることでルービックキューブを実現しています。

最初に各ブロックの生成について説明しておきます。ブロックはvector配列で管理しています。

vector<ofxBulletBaseShape *> shapes;

以下の処理でブロックを1つ生成しています。

void testApp::addShapeBox(int x, int y, int z, float mass, float size)
{
	shapes.push_back(new ofxBulletBox());
	int i = shapes.size() - 1;

	ofQuaternion qt(0.0, 1.0, 0.0, 1.0);
	((ofxBulletBox *)shapes[i])->create(world.world, ofVec3f(x, y, z), qt, mass, size, size, size);

	shapes[i]->setActivationState(DISABLE_DEACTIVATION);
	shapes[i]->add();
}

ポイントは mass の設定で、mass = 0.0; と設定することで、重力に影響しない固定のブロックになります。また、重力に影響する可変のブロックは mass = 1.0; で設定しています。
また、ofQuaternion を上記のように定義することで、Z軸を中心に45度回転したブロックを設置することができます。

ブロックの消去は以下の処理で行います。これでブロックの作成/消去が自在に行えます。

void testApp::deleteShape(int index)
{
	delete shapes[index];
	shapes[index] = NULL;
	shapes.erase( shapes.begin() + index );
}

また、映像では最初に透明の床を用意してあり、床の上にルービックキューブ状のブロックを置き、適切なタイミングで床の作成/消去を設定しています。

「2」では、全てのブロックを重力に影響する可変に設定して、床を消去することで実現しています。
「3」では、あらかじめ床を消去しておき、全てのブロックを重力に影響しない固定に設定して、ブロックを1つずつ可変な状態に生成し直すことを行っています。

「4」では、各ブロックに対して、瞬間的に力を加えることではじき飛ばされるようなアクションを実現しています。

void testApp::addShapeBox(int index)
{
	shapes[index]->activate();
	shapes[index]->applyForce(ofVec3f(100, -100, 100), ofVec3f(3, -50, 3));
}

最後の「5」ですが、可変のブロックの上から非常に重い球を落として、それに合わせて実物のボールを置くという、タイトルにもある「逆プロジェクションマッピング」というのを試してみようかと思いましたが、かなりズレてしまってますね。。まあ今回はこれでご勘弁ください。

個人的にはキャリブレーションの課題が心残りですが、それが解決すれば、いろいろおもしろいことが展開していけそうに思いました。

プロジェクションマッピング テスト

ちまたで流行っているプロジェクションマッピングを試してみました。
東京駅など建設物に投影する本格的なものは、After Effects などグラフィックスソフトを使うのが一般的かと思いますが、私はそちらの方はやったことがないので、プログラムにてできることをやってみました。

まずは機器を準備ということで、プロジェクターは QUMI Q5 を購入しました。
なんと、ここまで小型化されてかつ高性能なのには驚かされました。

IMG_1931

今回のテスト動画です。

比較的やりやすそうなターゲットとして、ルービックキューブへの投影を試してみました。
加えて、周辺からボール状の小さな物体をたくさん発生させて、ルービックキューブの投影と衝突させるという効果を出してみたいと思いました。

実際に着手していくと課題点もいくつか出てきました。
まず、ルービックキューブ実物を投影してみようとすると、ターゲットが小さいため直接投影させるには難しいと感じたので、ルービックキューブの写真を印刷したもので投影することにしました。
印刷したものに投影してみると、各面が平行四辺形でもないただの四角形なため、各面の9個のブロックの座標をマッピング画像に合わせることが非常に難しかったです。(四角形のベクトル計算から立ち戻りましたが、結局合っていないです。。)

プログラムは openFrameworks で実現しています。
AddOnは ofxQuadWarp と ofxBox2d を使用しました。
ルービックキューブの各面の作成は、ofxQuadWarp を3つ生成して、投影したプロジェクター画面に合わせて、あらかじめ座標を手動で調整しました。(つまり、キャリブレーションは手動で行いました。)
ルービックキューブの輪郭と降ってくるボール状の物体は ofxBox2d による物理エンジンを適用しています。

今回のテスト動画の演出については、Macにて割り当てたkeyを押して切り替えることでアクションを起こしています。
かつ、もう一方の手で持っていたiPhoneで撮影したため、ブレは気になるところですが、、何卒ご了承いただければと思います。

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