yoppa.org


夢の生活もそろそろ終わり

DSC_2276

ここ一週間は、あちこち出歩いていて、更新がすっかり停滞してしまった…

この夢の北海道生活も、今週で終わり。2日には帰京します。あー、楽しかったけど、まだまだ滞在したい気分。最後に、勉強の成果のiPhoneアプリの申請をしたい。そして、停滞してしまっている諸々のタスクを消化していかないと。

夏休みの宿題に終われる小学生の気分。


openFrameworksのプロジェクトをXCode4 + Gitでバージョン管理

あ、いかんBlogの更新をサボってしまった。というわけで、コネタ的な更新。

XCode4から、ファイルのバージョン管理システムが統合され、とても便利になった。SubversionだけでなくGitにも対応しているのが嬉しい。そんなわけで、これを機会に自分自身のプロジェクトのバージョン管理をSubversionからGitに移行しようということで試してみた。

通常であれば、XCode4のプロジェクトを新規に作成する際にSource Controlのチェックボックスをチェックするだけ。とても簡単。

XcodeScreenSnapz001.png

ところがoFの場合は用意さているXCodeのプロジェクトファイルをコピーして始める方法が一般的なので、この方法がつかえない。既存のプロジェクトをGitの管理下に移行する必要がある。といっても、手順はさほど複雑ではない。まずいつものようにemptyExampleをフォルダごとコピーする。例えば、emptyExampleGitといった名前のフォルダにする。ターミナルを立ち上げて、このemptyExampleGitフォルダまで移動。例えばこんな感じ。

% cd /Users/tado/Documents/of_preRelease_v007_iphone/apps/myApps/emptyExampleGit/

ここに、gitの環境を構築する。まずは以下のコマンドでGitを初期化して、.gitディレクトリを作成する。

% git init

普通はこのままでも良いのかもしれないが、XCodeの設定ファイルなどはGitで無視されるように設定ファイルを書いたほうがより便利かもしれない。下記のようなファイルをテキストエディタで作成し、「.gitignore」というファイル名で保存する。

# hidden/temp files
.DS_Store
*.swp
*~.nib
 
# Build dir
build/
 
# Xcode project files except for the project file
*.xcodeproj/
!*.xcodeproj/project.pbxproj

もしその他にもGit管理下から外したいファイルがある場合はこのファイルを適宜編集する。これで準備ができたので、カレントディレクトリの内容を全てGitに追加。追加された内容を確認する。

% git add .
% git status

問題ないようであれば、最初のコミットを行う。

% git commit -m "initial commit."

これでこのemptyExampleGitはGitの管理下に入ったはず。このままプロジェクトファイルを起動するとGitでバージョン管理された状態で使用できる。

しかし毎回この操作をするのもちょっと面倒。なので、新規にプロジェクトを作成する際には、このemptyExampleGitプロジェクトのクローンを作成して始めるのが楽ではないかと思う。(※ このやりかたがGitの作法的に正しいかどうか、ちょっと自信なし。問題あるようなら指摘してください…)

クローンを作成するのはXCode4を使えば簡単。まずXCodeを起動して、「Welcome to XCode」の画面を開く。ここで「Connect to a repository」を選択する。

XcodeScreenSnapz002.png

次に表示される画面のlocationに、先程作成したemptyExampleGitディレクトリの場所をフルパスで記入。

XcodeScreenSnapz003.png

プロジェクト名を指定して、バージョン管理システムの種類をGitに指定。あとはプロジェクトの保存場所を聞かれるので、プロジェクト名を新規に決めてoFのappディレクトリの中のサブディレクトリ (※ myAppなど) に保存する。

XcodeScreenSnapz004.png

これでプロジェクトファイルを開くと、Gitでバージョン管理された状態になっているはず。便利!!

XcodeScreenSnapz006.png

オーガナイザーにもきちんと登録されています。

XcodeScreenSnapz007.png

お試しを!


着々と進行中

2sec0.png

前回のiPhoneのiPodから音楽データを読み込む仕組みをつかって、ビジュアライザのアプリを作成中。土台の部分が徐々にできつつある。コーディング作業が俄然楽しくなってきた。複数のモードを切り替えられるようにして、読み込みの部分などのバグが安定したらアプリの申請も視野に入れながら進めていきたい。

今日はニセコアンヌプリのサマーゴンドラに乗って、山の中腹にあるレストハウスでノマドワークしてみた。見晴らしが素晴しく、普段あまり刺激されていない脳の領域が活発化する感じで、作業効率がなかなか良かった。帰りは調子にのって徒歩で下山。途中「熊が出没したので立ち入り禁止」という張り紙などがあり、北海道感満点。


iPodライブラリ内の曲をopenFrameworksで読み込んでサウンドレベルを取得する

picker.png

iPhone開発修行、ちょっと進展したのでご報告。

現在オーディオ・ビジュアル表現のためのアプリを作成したいと考えていて、やはりこういったジャンルはiPhone用のopenFrameworksを利用するほうが効率が良さそう。openFrameworksでサウンドを活用する場合、あらかじめリソースの中に.caf形式のサウンドファイルを入れておいて、それを参照するやり方が一番標準的な方法だ。また、マイク入力を利用してリアルタイムのサウンドストリームを利用することも可能となっている。

ところが、openFrameworksはそのままではiPhone内のiPodライブラリ内の曲のデータは参照できない。せっかくなら、自分のiPhoneに入れた音楽を使用していろいろ面白いビジュアライズをやってみたい。そんなわけで、iOSのSDKを直に利用してiPodライブラリ内のサウンドデータを参照する方法がないものか探ってみた。

iPodライブラリの内容を参照し、曲をピックアップする方法は比較的簡単だった。iOS SKDにはMPMediaPickerControllerというiPodライブラリの曲を選択するためのUIを提供するクラスがある。これを活用するとすぐに高機能な曲目ピッカーが活用できる。openFrameworksでMPMediaPickerControllerを利用するには、以前このブログの「iOSのGUIをopenFrameworksのプロジェクトに追加する」で紹介した方法で、oFにUIViewControllerのサブクラスを追加して、そのUIViewControllerのサブクラスにMPMediaPickerControllerのインスタンスを生成して、delegateしてあげれば良い。MPMediaPickerControllerを利用するには、プロジェクトにMediaPlayer.frameworkを追加する必要がある。また、MPMediaPickerControllerはiOSシミュレータでは動かすことはできない。必ず実機で動かすようにして欲しい。

ここで取得したアイテムはMPMediaItemクラスのインスタンスとして取得される。これを、MPMusicPlayerControllerというプレイヤーのクラスに読みこんでplayしてあげれば、それだけで曲は再生できる。非常に楽ちん。

ところが、MPMusicPlayerControllerは曲の再生はポーズなどのコントロールはできるものの、サウンドデータそのものには全くアクセスできない。ここでしばらく悩んでしまった。いろいろググったところ、素晴しい記事を発見。

詳細はこの2つの記事を参考にしてほしいのだが、ポイントとしては、MPMediaItemからiPodのリソースのURLを取得した上で、普通のオーディオファイルとして書きだしている。

書き出した生のオーディオデータを再生するには、AVAudioPlayerクラスを活用するのが便利なようだ。AVAudioPlayerでサウンドを再生し、そのレベルを取得するメソッドを追加してその値をoFのtestAppから見られるようにしてみた。これで、あとはoFの方でいろいろ工夫すれば、iPodの音楽を活用したオーディオビジュアルなアプリができるはず。

ここまでの内容のソースコードにまとめてみた。MPPikerクラスが、iPodのライブラリを読み込んで再生する部分。

testApp.h

#pragma once

#import <MediaPlayer/MediaPlayer.h>
#import <AVFoundation/AVFoundation.h>

#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"

class testApp : public ofxiPhoneApp {
	
public:
	void setup();
	void update();
	void draw();
	void exit();
	
	void touchDown(ofTouchEventArgs &touch);
	void touchMoved(ofTouchEventArgs &touch);
	void touchUp(ofTouchEventArgs &touch);
	void touchDoubleTap(ofTouchEventArgs &touch);
	void touchCancelled(ofTouchEventArgs &touch);

	void lostFocus();
	void gotFocus();
	void gotMemoryWarning();
	void deviceOrientationChanged(int newOrientation);
};

testApp.mm

#include "testApp.h"
#include "MPPicker.h"

MPPicker *picker;

void testApp::setup(){
    ofSetFrameRate(30);
    ofEnableAlphaBlending();
    ofSetCircleResolution(32);
	ofRegisterTouchEvents(this);
	ofxAccelerometer.setup();
	ofxiPhoneAlerts.addListener(this);
    ofBackground(0, 0, 0);
    picker = [[MPPicker alloc] initWithNibName:@"MPPicker" bundle:nil];
    [ofxiPhoneGetUIWindow() addSubview:picker.view];
}

void testApp::update(){

}

void testApp::draw(){
    //左右のサウンドレベルを取得
    float levelL = [picker getLevelWithChannel:0];
    float levelR = [picker getLevelWithChannel:1];

    //サウンドレベル半径にして円を描画
    ofSetColor(0, 63, 255, 127);
    ofTranslate(ofGetWidth()/3, ofGetHeight()/2);
    ofEllipse(0, 0, levelL * 400.0, levelL * 400.0);
    ofTranslate(ofGetWidth()/3, 0);
    ofEllipse(0, 0, levelR * 400.0, levelR * 400.0);
    
}

void testApp::exit(){ }
void testApp::touchDown(ofTouchEventArgs &touch){ }
void testApp::touchMoved(ofTouchEventArgs &touch){ }
void testApp::touchUp(ofTouchEventArgs &touch){ }
void testApp::touchDoubleTap(ofTouchEventArgs &touch){ }
void testApp::lostFocus(){ }
void testApp::gotFocus(){ }
void testApp::gotMemoryWarning(){ }
void testApp::deviceOrientationChanged(int newOrientation){ }
void testApp::touchCancelled(ofTouchEventArgs& args){ }

MPPicker.h

#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>
#import <AVFoundation/AVFoundation.h>

#include "testApp.h"

@interface MPPicker : UIViewController <MPMediaPickerControllerDelegate, UITableViewDelegate, AVAudioPlayerDelegate> {
    testApp *myApp;
    AVAudioPlayer *player;
    IBOutlet UIBarButtonItem *showPickerButton;
    IBOutlet UIBarButtonItem *playButton;
    IBOutlet UIBarButtonItem *pauseButton;
}

-(IBAction)showMediaPicker:(id)sender;
-(IBAction)playMediaItem:(id)sender;
-(IBAction)pauseMediaItem:(id)sender;
-(float)getLevelWithChannel:(int)ch;

@end

MPPicker.mm

#import "MPPicker.h"

@implementation MPPicker

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
    }
    return self;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}


// iPodの曲目のピッカーを表示
-(IBAction)showMediaPicker:(id)sender
{
    MPMediaPickerController *picker = [[MPMediaPickerController alloc] initWithMediaTypes: MPMediaTypeAnyAudio];
    [picker setDelegate: self];
    [picker setAllowsPickingMultipleItems: NO];
    picker.prompt = NSLocalizedString (@"Add songs to play", "Prompt in media item picker");

    // pickerをModalViewに表示
    [self presentModalViewController: picker animated: YES];
    [picker release];
    [player stop];
}

// Pickerで取得したデータをファイルに書き出し
- (void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
    MPMediaItem *item = [mediaItemCollection.items lastObject];
    NSURL *url = [item valueForProperty:MPMediaItemPropertyAssetURL];
    AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:url options:nil];
    
    AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]
                                           initWithAsset:urlAsset
                                           presetName:AVAssetExportPresetPassthrough];
    
    
    NSArray *tracks = [urlAsset tracksWithMediaType:AVMediaTypeAudio];
    AVAssetTrack *track = [tracks objectAtIndex:0];
    id desc = [track.formatDescriptions objectAtIndex:0];
    const AudioStreamBasicDescription *audioDesc = CMAudioFormatDescriptionGetStreamBasicDescription((CMAudioFormatDescriptionRef)desc);
    FourCharCode formatID = audioDesc->mFormatID;
    
    NSString *fileType = nil;
    NSString *ex = nil;
    
    switch (formatID) {
            
        case kAudioFormatLinearPCM:
        {
            UInt32 flags = audioDesc->mFormatFlags;
            if (flags & kAudioFormatFlagIsBigEndian) {
                fileType = @"public.aiff-audio";
                ex = @"aif";
            } else {
                fileType = @"com.microsoft.waveform-audio";
                ex = @"wav";
            }
        }
            break;
            
        case kAudioFormatMPEGLayer3:
            fileType = @"com.apple.quicktime-movie";
            ex = @"mp3";
            break;
            
        case kAudioFormatMPEG4AAC:
            fileType = @"com.apple.m4a-audio";
            ex = @"m4a";
            break;
            
        case kAudioFormatAppleLossless:
            fileType = @"com.apple.m4a-audio";
            ex = @"m4a";
            break;
            
        default:
            break;
    }
    
    exportSession.outputFileType = fileType;
    
    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *filePath = [[docDir stringByAppendingPathComponent:[item valueForProperty:MPMediaItemPropertyTitle]] stringByAppendingPathExtension:ex];
    exportSession.outputURL = [NSURL fileURLWithPath:filePath];
        
    [exportSession exportAsynchronouslyWithCompletionHandler:^{
        
        if (exportSession.status == AVAssetExportSessionStatusCompleted) {
            NSLog(@"export session completed");
            [exportSession release];
            player = [[AVAudioPlayer alloc] initWithContentsOfURL:exportSession.outputURL error:nil];
            player.meteringEnabled = YES;
            [player play];
        } else {
            NSLog(@"export session error");
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Export session error\nPlease select again" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
            [alert show];
        }
    }];
    [self dismissModalViewControllerAnimated:YES];
}

// 曲をキャンセルした時
- (void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker {
	[self dismissModalViewControllerAnimated:YES];
}

// 曲を再生
-(IBAction)playMediaItem:(id)sender
{
    [player play];
}

// 曲をポーズ
-(IBAction)pauseMediaItem:(id)sender
{
    [player pause];
}

// チャンネルを指定して、再生レベルを取得
-(float)getLevelWithChannel:(int)ch
{
    if(player.playing){
        [player updateMeters];
        float db = [player averagePowerForChannel:ch];
        float power = pow(10, (0.05 * db));
        return power;
    } else {
        return nil;
    }
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];
    myApp = (testApp*)ofGetAppPtr();
    player.delegate = self;
}


- (void)viewDidUnload
{
    [super viewDidUnload];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    //return (interfaceOrientation == UIInterfaceOrientationPortrait);
    return NO;
}

@end

Box2Dと組みあわせて、物理シミュレーションを使用したビジュアライザーや、ofImagePickerを使用してカメラ入力の画像を音でモジュレーションするアプリなどいろいろ夢が膨らむ。もう少しoFとの連携を強化したものを、addonにして配布したら感謝されるんだろうか? ofxMPMediaPickerとか。

プロジェクトをダウンロード

下記のリンクよりプロジェクトファイル一式がダウンロードできます。of_preRelease_v007_iphone、XCode 4.1 + iOS SDK 3.2で作成しています。


徐々に復活

new_pachview.png

ようやく足の調子も復活してきた。ちょっと足が痛いだけで、気力も萎えてしまうものだ。なんだか、ここ数日あまり生産性が上がらなかった。

徐々に復活してきたので、またいろいろ勉強再開していきたい。とりあえずは「iOS開発におけるパターンによるオートマティズム」学習のまとめとして、以前作っていたPachubeのモニタアプリを、木下さんの書籍のパターンで再作成してみた。複数のフィードも登録できるように。きちんとパターンが構築されたプロジェクトは、改変や拡張が容易であるということを実感。しかしなぜかXMLのパースのバグがあり、いまだに解決できず。ダメプログラマ…

そろそろ、より表現を重視したアプリに移行せねば。休暇が終わってしまう…


勉強勉強

iOS開発におけるパターンによるオートマティズム

前回の続き。いろいろAppleのサンプルを漁りながら、アプリを作ろうとするものの、まだ理解できない部分も多い。もっと根本的な理解が必要なのかもと思っていたところに、とても素晴しい本を献本していただいた。

木下誠さん著の「iOS開発におけるパターンによるオートマティズム」という書籍。いままで、なんとなく理解していたつもりになっていたMVCパターンを使用したアプリ構築のための様々なノウハウが凝縮されている。Objective-Cについてはある程度理解したのだが、いざアプリを構築しようとした時にどこから手をつけて良いのか途方に暮れてしまうようなレベルの開発者(例えば自分)にちょうと良いレベルの内容。アプリの構造やその実現のためのパターンがたくさん紹介されているので、実践的でとても勉強になりそうだ。

ビジュアライザーはとりあえずお預けにして、しばらくまだ基礎固めをしていこうかと。

足の調子は相変らず…


足痛い

オフシーズンのさびれたスキー場、なんだか和む…

なんだか本格的に足が痛むようになってしまった… 今日はせっかく良い天気なのに、むー。足が痛いと集中力も途切れがち。開発も遅々として進まず。まあ、しばらくは安静にしてます。


今後の展開

ここ数日近所をジョギングしたり山道を歩いてたりしたら、なんだかまた足に違和感が… うーむ、また足痛の再発の予感。大事をとってしばらくは運動はお休み。ちょっと気分が落ちこむ。そのせいか、昨日一日はiOSプログラミング修行も停滞してしまった。

このままではまずいので、UIまわりの勉強はいったんここまでとして、より興味のある分野を調べてみたい。やはり、音響+映像というテーマに俄然興味があるので、手始めにiPodに入っている曲のデータを活用したアプリの作成方法を調べてみようかと思う。できれば、お手製iTunesビジュアライザー的なオーディオビジュアルなものをつくりたい。目指せ、Flight 404!! 頭の中ではこんなイメージ (まだ技術が遠く及ばないが…)。

手始めに、ここらへんの資料を読んでみようかと。


基礎練の続き

pachube_viewer.png

前回の続き。

TableViewの使い方も徐々にわかってきた。iPhoneアプリの王道のナビゲーションである、TableViewのセルを選択すると画面が横にスライドして下位の階層が表示されるというナビゲーションの作成方法も作れるようになった。

そんなわけで、前回のPachubeビューワーを改良して、それぞれの項目の「グラフを表示」を選択すると、画面が横にスライドして過去1日分のグラフが表示されるようにしてみた。これだけで、だいぶiPhoneアプリっぽい操作感になるものだ。Navigation-baseのアプリケーションの基本的な枠組が徐々にわかってきた。もちろん、まだまだ未知の部分だらけなのだが。

さらにiOS SDKの基礎練習を続けたい。


基礎練

Screen Shot 2011-08-10 at 13.23.53.png

徐々に今後のための勉強を始めている。

今までiOSで開発するときには、完全にopenFrameworksに頼りきっていたので、Objective-Cの基本やInterfaceBuilderを使ったUI構築など、超基本的な部分が抜け落ちている部分が多い。良い機会なので、まずはObjective-Cを使用したiOS SDKによる開発を基本からじっくりやっていこうかと。

iOS SDKといえば、まずはUIKitでしょ。そしてUIKitの代表格はTableViewに違いない、という勝手な決めつけのもと、まずはTableViewに情報を読みこんで表示するというプログラムを作成できるまでを目指した。

データは、最近使用して嵌っているPachubeを選択。APIが充実しているので、いろいろできそう。最初の第一歩として、現在Pachubeで公開している留守宅の温度と湿度のデータをAPI経由で参照して、現在の値をXML Feedを取得しTableViewに表示するところまでが目標。

いざ、やってみると、Objective-Cの基本文法の理解が大分怪しい。@propertyと@synthesizeの使いかたとか、@protocolって何? とかそういった言語の基本構造を、リファレンスをおさらいしながら進める。また、TableViewの基本についてもお勉強。こうした部分は、Appleの資料が大いに参考になった。

まだ全然理解は深まってはいないのだが、とにかく作りながら覚えていこうということで、ネットのいろいろなチュートリアルを参照しながら、Patchubeのデータを読み込んで表示するアプリを作っていく。XMLの読み込みからTableViewへの反映に関しては、このチュートリアルがとても分かり易かった。

なんだかモヤモヤしていた霧が徐々に晴れつつある感じ。まだ薄く霧はたちこめているのだけれど。

結局丸一日かけて、ようやくPatchubeのFeedを表示することができた。しかし…地味。もう少し表示に工夫したい。また、グラフの表示などより詳細な情報へのアクセスもトラいしてみたい。

しかしObjective-Cの理解は遅々としてなかなか進まず、自分の能力の無さを痛感。とはいえ、まだ時間はあるのでじっくり進めます。

追記

その後、いろいろ試行錯誤の結果、ちょっとはましになってきた。それぞれの項目からグラフ表示をできるようにしたいのだけれど、まだ道程は長い…