workshop/IAProA/main

IAプロジェクト演習A

ここでの内容は全てクローズドになります.このウェブサイトのリンクを他に掲載したり,部外者に伝えることも禁止します.
  • 第1回:OpenframeworksとProcessing
  • 第2回:iOS/Androidアプリをつくってみる
  • 第3回:BB Rattleを作る(openframeworks)
  • 第4回:リズムパズルの制作
  • 第5回:Processingとhtml
  • 第6回:Fontをつくる
  • 第7回:i玩具のコンセプトをつくる

1. OpenframeowrksとProcessing

Openframeworks

Openframeworksの準備をします.

  1. Mac OSXが動くコンピュータ
  2. Xcode
  3. App Storeからダウンロード&インストールして下さい.Openframeworks ではOS10.6用のSDKを利用しているので, idealab/baba からダウンロードして,/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs の中にMacOSX10.6.sdkをコピーしてください.
  4. Openframeworks
  5. OpenframeworksのDownloadページからOSX用のXcodeと書かれた ファイルをダウンロードして下さい.ダウンロードが終わったら適当な場所に解凍し,これで準備完了です.
  6. apps/myApps/emptyExample/emptyExample.xcodeproj をダブルクリックして開く
  7. 下の画像にあるRunボタンを押して,画面(特になにも表示されない)が出現すればOK

以上で準備は終わりになります.examples フォルダにサンプルがたくさんあるのでいくつか実行して, どんなことができるのか見てみましょう.

Processing

Processingウェブサイトから自身の環境にあったProcessingを ダウンロードして下さい.

試しにメニューのexampleから幾つか適当に実行してみましょう.

2. iOS/Androidアプリをつくってみる

Openframeworksを利用して各アプリを作成してみましょう.iOSはOpenframeworks,AndroidはProcessingにて作成します.

iOS

実機で動作させるためには,たとえテストであってもAppleにお金を払わねばなりません.今後いろいろやってみたい人は 下記サイトの手順にしたがって,登録を済ませてください.なかなかしんどいですが,興味ある人は頑張ってください. ゼロから始めるiPhoneアプリ開発入門 apple developerサイトからアカウントを取得して,iOS devのCertificates, Identifiers & Profilesを入手すれば良いです.

というわけで,お金払いたくない人はシミュレータでも動作させることができます.ただし,カメラ等のデバイス固有の 機能に関してはシミュレータで確認することはできません.ご注意を.手順は次のとおりです.

  1. OpenframeworksからiOS用のファイルをダウンロード
  2. XcodeでemptyExampleを開く
  3. Xcodeメニューのpreference->Downloads->ComponentsからiOS Simulatorをインストールしておく
  4. 下記画像の様にSchemeの内容をあわせて,Runボタンを押す
  5. しばらく待つとsimulatorが立ち上がり,simulator上でなにも表示されないアプリを確認できればOK.

Android

こちらの内容を参照してください

3. BBRattleを作る

BB Rattle は単純なガラガラアプリになります.これを題材にしてステップバイステップで作成手順を紹介します.

3.1 最初の準備

まずはemptyExampleをコピーし,apps -> myApps の中に複製してください.フォルダの名前をemptyExampleから Rattleと変更しましょう. apps->myApps->Rattle という具合になります.次にempyExample.xcodeproj をXcodeで 開きます.プロジェクトの名前もついでに Rattle に変更しておきます.下図に示すように左のプロジェクトファイルを 選択した後,右にあるProject Nameの欄を変更してください.これでファイルネームや実行ファイル名等が一括で変更 できます.変更する際にいろいろ聞かれますが,そのまま特にダイアログ選択を変更することなく続けてください.

次に必要なアドオンを追加します.

  1. ofxBox2d
  2. ofxXmlSettings (標準でaddonsフォルダに含まれています)

3.2 空のプログラムにする

まずは単純にするため,今回は不必要なコードを削除します.下記のようにします.

test.mm
#include "testApp.h"
void testApp::setup(){
  ofxAccelerometer.setup();
  ofBackground(127,127,127);
}
void testApp::update(){
}
void testApp::draw(){
}
void testApp::exit(){
}
void testApp::touchDoubleTap(ofTouchEventArgs & touch){
}
test.h
#pragma once

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

class testApp : public ofxiPhoneApp{
    public:
        void setup();
        void update();
        void draw();
        void exit();
        void touchDoubleTap(ofTouchEventArgs & touch);
};

3.3 box2dを利用する

次にbox2dを利用してみます.最も単純に,丸オブジェクトを一つ生成するだけのプログラムです.

test.mm
#include "testApp.h"

void testApp::setup(){
  ofSetFrameRate(60);
	ofxAccelerometer.setup();
	ofBackground(127,127,127);
  box2d.init();
	box2d.setGravity(0, 10);
	box2d.setFPS(60);
	box2d.registerGrabbing();
	box2d.createBounds();
	box2d.setIterations(1, 1); // minimum for IOS

  box2d_circle.setPhysics(1, 0.6, 0.4);
  box2d_circle.setup(box2d.getWorld(),
                     ofGetWindowWidth()/2, ofGetWindowHeight(),
                     40);
}
void testApp::update(){
  box2d.update();
}
void testApp::draw(){
  ofSetColor(21, 140, 240);
  box2d_circle.draw();
}
void testApp::exit(){
}
void testApp::touchDoubleTap(ofTouchEventArgs & touch){
}
test.h
#pragma once

#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"
#include "ofxBox2d.h"
class testApp : public ofxiPhoneApp{
public:
  void setup();
  void update();
  void draw();
  void exit();
  void touchDoubleTap(ofTouchEventArgs & touch);
  ofxBox2d box2d;
  ofxBox2dCircle box2d_circle;
};

3.4 vectorクラスを利用して,沢山生成する

vectorクラスを利用して,一度に沢山のbox2dオブジェクトを生成してみます.vector とはc++のクラスの一つで, 配列を便利に扱う為に考えられたものです.例えば配列を扱う場合,char str[64]とか,str = new char[64]等の やり方で一度に必要な分量の領域を確保するのが一般的ですが,この配列の大きさが動的に変化するようなものに 対して有効に機能します.また,ダブルタップをしたら,全circleオブジェクトに対して上向きのちからを加えます.

test.mm
#include "testApp.h"

void testApp::setup(){
  ofSetFrameRate(60);
	ofxAccelerometer.setup();
	ofBackground(127,127,127);
  box2d.init();
	box2d.setGravity(0, 10);
	box2d.setFPS(60);
	box2d.registerGrabbing();
	box2d.createBounds();
	box2d.setIterations(1, 1); // minimum for IOS

  for( int i = 0; i < 100; i++ ){
    ofxBox2dCircle c;
    c.setPhysics(1, 0.6, 0.4);
    c.setup(box2d.getWorld(),
            ofGetWindowWidth()/2, ofGetWindowHeight()/2,
            5);
    circle.push_back(c);
  }
}
void testApp::update(){
  box2d.update();
}
void testApp::draw(){
  ofSetColor(21, 140, 240);
  for( int i = 0 ; i < circle.size(); i++ ){
    circle[i].draw();
  }
}
void testApp::exit(){
}
void testApp::touchDoubleTap(ofTouchEventArgs & touch){
	for( int i = 0; i < circle.size(); i++ ){
		circle[i].addForce(ofVec2f(0.0,-10.0), 10);
	}
}


test.h
#pragma once

#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"
#include "ofxBox2d.h"
class testApp : public ofxiPhoneApp{
public:
  void setup();
  void update();
  void draw();
  void exit();
  void touchDoubleTap(ofTouchEventArgs & touch);
  ofxBox2d box2d;
  vector <ofxBox2dCircle> circle;
};

3.5 オブジェクト同士の衝突を検出する

では次に各circleオブジェクト同士の衝突検知です.丸いオブジェクトを使っているので純粋にfor分で各 オブジェクトが衝突しているかどうかを検出することはできますが,ofxBox2dに用意されているContact Listenerを 利用してみます.

test.mm
#include "testApp.h"
void testApp::contactStart(ofxBox2dContactArgs &e) {
	if(e.a != NULL && e.b != NULL) {
		printf("contacted!!\n");
	}
}
void testApp::contactEnd(ofxBox2dContactArgs &e) {
	if(e.a != NULL && e.b != NULL) {
	}
}

void testApp::setup(){
  ofSetFrameRate(60);
	ofxAccelerometer.setup();
	ofBackground(127,127,127);
  box2d.init();
	box2d.setGravity(0, 10);
	box2d.setFPS(60);
	box2d.registerGrabbing();
	box2d.createBounds();
	box2d.setIterations(1, 1); // minimum for IOS

  for( int i = 0; i < 2; i++ ){
    ofxBox2dCircle c;
    c.setPhysics(1, 0.6, 0.4);
    c.setup(box2d.getWorld(),
            ofGetWindowWidth()/2, ofGetWindowHeight()/2,
            20);
    circle.push_back(c);
  }
	
	ofAddListener(box2d.contactStartEvents, this, &testApp::contactStart);
	ofAddListener(box2d.contactEndEvents, this, &testApp::contactEnd);
}
void testApp::update(){
  box2d.update();
}
void testApp::draw(){
  ofSetColor(21, 140, 240);
  for( int i = 0 ; i < circle.size(); i++ ){
    circle[i].draw();
  }
}
void testApp::exit(){
}
void testApp::touchDoubleTap(ofTouchEventArgs & touch){
	for( int i = 0; i < circle.size(); i++ ){
		circle[i].addForce(ofVec2f(0.0,-10.0), 10);
	}
}


test.h
#pragma once

#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"
#include "ofxBox2d.h"
class testApp : public ofxiPhoneApp{
public:
  void setup();
  void update();
  void draw();
  void exit();
  void touchDoubleTap(ofTouchEventArgs & touch);
  ofxBox2d box2d;
  vector <ofxBox2dCircle> circle;
	
	void contactStart(ofxBox2dContactArgs &e);
	void contactEnd(ofxBox2dContactArgs &e);
};

3.5 オブジェクト同士の衝突を検出して,その種類も特定する

これで各オブジェクトの衝突を検出できました.しかし 「どのオブジェクトが衝突したのかわからない」問題があります.そこで各circleオブジェクトにユーザデータをセットすることで この問題を解決します.

test.mm
#include "testApp.h"
void testApp::contactStart(ofxBox2dContactArgs &e) {
	if(e.a != NULL && e.b != NULL) {
		SoundData * aData = (SoundData*)e.a->GetBody()->GetUserData();
		SoundData * bData = (SoundData*)e.b->GetBody()->GetUserData();
		
		if(aData) {
			aData->bHit = true;
      printf("A is Contacting!!\n");
		}
		if(bData) {
			bData->bHit = true;
      printf("B is Contacting!!\n");
		}

	}
}
void testApp::contactEnd(ofxBox2dContactArgs &e) {
	if(e.a != NULL && e.b != NULL) {
    SoundData * aData = (SoundData*)e.a->GetBody()->GetUserData();
		SoundData * bData = (SoundData*)e.b->GetBody()->GetUserData();
		if(aData) {
			aData->bHit = false;
		}
		
		if(bData) {
			bData->bHit = false;
		}
  }
}

void testApp::setup(){
  ofSetFrameRate(60);
	ofxAccelerometer.setup();
	ofBackground(127,127,127);
  box2d.init();
	box2d.setGravity(0, 10);
  box2d.setFPS(60);
	box2d.registerGrabbing();
	box2d.createBounds();
	box2d.setIterations(1, 1); // minimum for IOS
  
  for( int i = 0; i < 2; i++ ){
    ofxBox2dCircle c;
    c.setPhysics(1, 0.6, 0.4);
    c.setup(box2d.getWorld(),
            ofGetWindowWidth()/2, ofGetWindowHeight()/2,
            20);
    c.setData(new SoundData());
    SoundData * sd = (SoundData*)c.getData();
		sd->soundID = i;
		sd->bHit	= false;
    circle.push_back(c);
  }
	
	ofAddListener(box2d.contactStartEvents, this, &testApp::contactStart);
	ofAddListener(box2d.contactEndEvents, this, &testApp::contactEnd);
}
void testApp::update(){
  box2d.update();
}
void testApp::draw(){
  ofSetColor(21, 140, 240);
  for( int i = 0 ; i < circle.size(); i++ ){
    circle[i].draw();
  }
}
void testApp::exit(){
}
void testApp::touchDoubleTap(ofTouchEventArgs & touch){
	for( int i = 0; i < circle.size(); i++ ){
		circle[i].addForce(ofVec2f(0.0,-10.0), 10);
	}
}


test.h
#pragma once

#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"
#include "ofxBox2d.h"
class SoundData {
public:
	int	 soundID;
	bool bHit;
};
class testApp : public ofxiPhoneApp{
public:
  void setup();
  void update();
  void draw();
  void exit();
  void touchDoubleTap(ofTouchEventArgs & touch);
  ofxBox2d box2d;
  vector <ofxBox2dCircle> circle;
	
  void contactStart(ofxBox2dContactArgs &e);
  void contactEnd(ofxBox2dContactArgs &e);
};

3.6 衝突した時に音を鳴らす

これで各オブジェクトの衝突を検知できるようになりました.それでは次に衝突した時に 音がなるようにしてみます.もう既に記述した衝突時に呼ばれる関数contactListenerに音が なる箇所を追加するだけです.通常の再生ではofSoundPlayerを利用すれば可能ですが, iOSの場合,多重再生をするのが手間なので,今回はofxOpenAlSoundPlayerを利用します. 標準のアドオンなので,とくにaddonsフォルダにファイルを追加する必要はありません.

test.mm
#include "testApp.h"
void testApp::contactStart(ofxBox2dContactArgs &e) {
  if(e.a != NULL && e.b != NULL) {
    SoundData * aData = (SoundData*)e.a->GetBody()->GetUserData();
    SoundData * bData = (SoundData*)e.b->GetBody()->GetUserData();
    
    if(aData) {
      aData->bHit = true;
      float x = circle[aData->soundID].getVelocity().x;
      float y = circle[aData->soundID].getVelocity().y;
      float velocity = ofDist(0,0, x, y);
      sound[aData->soundID].setVolume(velocity/30.0);
      sound[aData->soundID].play();
    }
    if(bData) {
      bData->bHit = true;
      float x = circle[bData->soundID].getVelocity().x;
      float y = circle[bData->soundID].getVelocity().y;
      float velocity = ofDist(0,0, x, y);
      sound[bData->soundID].setVolume(velocity/30.0);
      sound[bData->soundID].play();
    }
  }
}

void testApp::contactEnd(ofxBox2dContactArgs &e) {
  if(e.a != NULL && e.b != NULL) {
    SoundData * aData = (SoundData*)e.a->GetBody()->GetUserData();
    SoundData * bData = (SoundData*)e.b->GetBody()->GetUserData();
    if(aData) {
      aData->bHit = false;
    }
    if(bData) {
      bData->bHit = false;
    }
  }
}

void testApp::setup(){
  ofSetFrameRate(60);
  ofxAccelerometer.setup();
  ofBackground(127,127,127);
  box2d.init();
  box2d.setGravity(0, 10);
  box2d.setFPS(60);
  box2d.registerGrabbing();
  box2d.createBounds();
  box2d.setIterations(1, 1); // minimum for IOS
  
  for( int i = 0; i < 2; i++ ){
    ofxBox2dCircle c;
    c.setPhysics(1, 0.6, 0.4);
    c.setup(box2d.getWorld(),
            ofGetWindowWidth()/2, ofGetWindowHeight()/2,
            20);
    c.setData(new SoundData());
    SoundData * sd = (SoundData*)c.getData();
    sd->soundID = i;
    sd->bHit	= false;
    circle.push_back(c);
    sound[i].loadSound("test.wav");
  }
	
  ofAddListener(box2d.contactStartEvents, this, &testApp::contactStart);
  ofAddListener(box2d.contactEndEvents, this, &testApp::contactEnd);
}
void testApp::update(){
  box2d.update();
}
void testApp::draw(){
  ofSetColor(21, 140, 240);
  for( int i = 0 ; i < circle.size(); i++ ){
    circle[i].draw();
  }
}
void testApp::exit(){
}
void testApp::touchDoubleTap(ofTouchEventArgs & touch){
  for( int i = 0; i < circle.size(); i++ ){
    circle[i].addForce(ofVec2f(0.0,-10.0), 10);
  }
}

test.h
#pragma once

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

class SoundData {
public:
	int	 soundID;
	bool bHit;
};
class testApp : public ofxiPhoneApp{
public:
  void setup();
  void update();
  void draw();
  void exit();
  void touchDoubleTap(ofTouchEventArgs & touch);
  ofxBox2d box2d;
  vector <ofxBox2dCircle> circle;
	
  void contactStart(ofxBox2dContactArgs &e);
  void contactEnd(ofxBox2dContactArgs &e);
  
  ofxOpenALSoundPlayer sound[2];
};

3.7 タマの数を増やして色も変更する

では次に,タマの数を少し多くしてBBRattleと同じ7個にします.BBRattleではタマのサイズに画像を貼り付けていますが, ここでは ofSetColor で色を変更してみましょう.今のところ各オブジェクト(タマ)は回転を表示するための線が丸の中に 表示されているので,まずはこれを消してみます.circle[i].draw()関数のところで,drawの部分だけをダブルクリック,もしくは マウス操作でハイライト選択をしてください.右クリックすると Jump to Definition があるので,これを選択すると, ofxBox2dCircle::draw()の定義部分に飛ぶことができます.ここを直接編集すればOKです.下記にあるように,ofLineだけ コメントアウトします.また,スリープ状態(動きがなくなった時)にも色をグレーに変更していますが,これも必要ないので コメントアウトしておきます.なお,ここでの修正は他のofxBox2dを利用しているアプリケーションに影響があるので, ご注意ください.

ofxBox2dCircle::draw()
void ofxBox2dCircle::draw() {
	
  if(!isBody()) return;
  ofPushMatrix();
  ofTranslate(getPosition().x, getPosition().y, 0);
  ofRotate(getRotation(), 0, 0, 1);
  ofCircle(0, 0, radius);
  
  ofPushStyle();
  ofEnableAlphaBlending();
  ofSetColor(0);
  //ofLine(0, 0, radius, 0);
  //if(isSleeping()) {
    //ofSetColor(255, 100);
    //ofCircle(0, 0, radius);
  //}
  ofPopStyle();
  ofPopMatrix();
}

3.8 ofxRealtimeMIDISoundPlayerで各タマに異なる音階を割り当てる

では最後に ofxRealtimeMIDISoundPlayer を利用して,各タマの音に音階を与えましょう.ofxRealtimeMIDISoundPlayerは馬場が作成した アドオンになるので,ワークショップの際に配ります.MIDI音源を利用せずに,Shortmessage的なやり方で,サンプリングしてきた音色 を鳴らすためのものです.ベンド等のエフェクトは実装していません.ノートオフ機能もないです.ただ,利用したい音源のドの音をオクターブ 毎にもってくるだけで,そこから音階を生成し,sendShortMessageで音源のように利用できます. 受け取ったofxRealtimeMIDISoundPlayerフォルダをaddonsフォルダにコピーした後,xcodeのaddonsに追加します. 次にsoundsというフォルダも渡すので,それをRattle.xcodeprojのあるフォルダのbin/data/ にコピーします.では下記コードを利用して 実行してみて下さい.今回 touchDoubleTap のなかも少しいじっています.

test.mm
#include "testApp.h"
void testApp::contactStart(ofxBox2dContactArgs &e) {
  if(e.a != NULL && e.b != NULL) {
    SoundData * aData = (SoundData*)e.a->GetBody()->GetUserData();
    SoundData * bData = (SoundData*)e.b->GetBody()->GetUserData();
    
    if(aData) {
      aData->bHit = true;
      float x = circle[aData->soundID].getVelocity().x;
      float y = circle[aData->soundID].getVelocity().y;
      float velocity = ofDist(0,0, x, y);

      midi.sendShortMessage(0x90,
                                            aData->soundID*2+60, velocity*4);
    }
    if(bData) {
      bData->bHit = true;
      float x = circle[bData->soundID].getVelocity().x;
      float y = circle[bData->soundID].getVelocity().y;
      float velocity = ofDist(0,0, x, y);
      midi.sendShortMessage(0x90,
                                            bData->soundID*2+60, velocity*4);
    }
  }
}

void testApp::contactEnd(ofxBox2dContactArgs &e) {
  if(e.a != NULL && e.b != NULL) {
    SoundData * aData = (SoundData*)e.a->GetBody()->GetUserData();
    SoundData * bData = (SoundData*)e.b->GetBody()->GetUserData();
    if(aData) {
      aData->bHit = false;
    }
    if(bData) {
      bData->bHit = false;
    }
  }
}

void testApp::setup(){
  ofSetFrameRate(60);
  midi.init();
  ofxAccelerometer.setup();
  ofBackground(127,127,127);
  box2d.init();
  box2d.setGravity(0, 10);
  box2d.setFPS(60);
  box2d.registerGrabbing();
  box2d.createBounds();
  box2d.setIterations(1, 1); // minimum for IOS
  
  for( int i = 0; i < 7; i++ ){
    ofxBox2dCircle c;
    c.setPhysics(1, 0.6, 0.4);
    c.setup(box2d.getWorld(),
            ofGetWindowWidth()/2, ofGetWindowHeight()/2,
            20);
    c.setData(new SoundData());
    SoundData * sd = (SoundData*)c.getData();
    sd->soundID = i;
    sd->bHit	= false;
    circle.push_back(c);
  }
	
  ofAddListener(box2d.contactStartEvents, this, &testApp::contactStart);
  ofAddListener(box2d.contactEndEvents, this, &testApp::contactEnd);
  
}
void testApp::update(){
  box2d.update();
}
void testApp::draw(){
  ofSetColor(21, 140, 240);
  for( int i = 0 ; i < circle.size(); i++ ){
    circle[i].draw();
  }
}
void testApp::exit(){
}
void testApp::touchDoubleTap(ofTouchEventArgs & touch){
  for( int i = 0; i < circle.size(); i++ ){
    circle[i].addForce(ofVec2f((ofRandom(50)-25), ofRandom(50)-25), 100);
  }
}
test.h
// Created by Tetstuaki Baba 2013/05/03
#pragma once

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

class SoundData {
public:
	int	 soundID;
	bool bHit;
};
class testApp : public ofxiPhoneApp{
public:
  void setup();
  void update();
  void draw();
  void exit();
  void touchDoubleTap(ofTouchEventArgs & touch);
  ofxBox2d box2d;
  vector  circle;
	
  void contactStart(ofxBox2dContactArgs &e);
  void contactEnd(ofxBox2dContactArgs &e);

  ofxRealtimeMIDISoundPlayer midi;
};

test.mm

test.h

4. リズムパズルの制作(iOS版)

ここまで制作した知見を基に,リズムで遊ぶパズル「リズムパズル」を作成してみましょう.Box2dを利用して,4x4のマス目上の オブジェクトを動かすことで,目的のリズムを作成していきます.まずはベースとなるプロジェクトファイルの準備になります. これまで作成してきたRattleをひな形にしましょう.Rattleのフォルダをコピーし,代わりにRhythmPuzzleと名前を変更しましょう. フォルダの名前だけでなく,xcodeからプロジェクト名も変更するのを忘れずに.

4.1 適切な重力設定をし,オブジェクトを四角で作成し,画面に4x4の升目状にならべる

test.mm
#include "testApp.h"
void testApp::contactStart(ofxBox2dContactArgs &e) {
  if(e.a != NULL && e.b != NULL) {
    ObjectData * aData = (ObjectData*)e.a->GetBody()->GetUserData();
    ObjectData * bData = (ObjectData*)e.b->GetBody()->GetUserData();
    
    if(aData) {
      aData->bHit = true;
      float x = rects[aData->id].getVelocity().x;
      float y = rects[aData->id].getVelocity().y;
      float velocity = ofDist(0,0, x, y);

      midi.sendShortMessage(0x90,aData->id*2+60, velocity*4);
    }
    if(bData) {
      bData->bHit = true;
      float x = rects[bData->id].getVelocity().x;
      float y = rects[bData->id].getVelocity().y;
      float velocity = ofDist(0,0, x, y);
      midi.sendShortMessage(0x90,bData->id*2+60, velocity*4);
    }
  }
}

void testApp::contactEnd(ofxBox2dContactArgs &e) {
  if(e.a != NULL && e.b != NULL) {
    ObjectData * aData = (ObjectData*)e.a->GetBody()->GetUserData();
    ObjectData * bData = (ObjectData*)e.b->GetBody()->GetUserData();
    if(aData) {
      aData->bHit = false;
    }
    if(bData) {
      bData->bHit = false;
    }
  }
}

void testApp::setup(){
  ofSetFrameRate(60);
  midi.init();
  ofxAccelerometer.setup();
  ofBackground(127,127,127);
  box2d.init();
  box2d.setGravity(0, 0);
  box2d.setFPS(60);
  box2d.registerGrabbing();
  box2d.createBounds(0.0,0.0,ofGetWindowWidth(), ofGetWindowWidth());

  box2d.setIterations(1, 1); // minimum for IOS
  
  for( int i = 0; i < 15; i++ ){
    ofxBox2dRect r;
    r.setPhysics(100, 0.0, 1.0);
    object_position[i].x = (ofGetWindowWidth()/4)*(i%4)+(ofGetWindowWidth()/4)/2;
    object_position[i].y = (ofGetWindowWidth()/4)*(i/4)+(ofGetWindowWidth()/4)/2;
    r.setup(box2d.getWorld(),
            object_position[i].x,
            object_position[i].y,
            (ofGetWindowWidth()/8)*0.98,
            (ofGetWindowWidth()/8)*0.98
            );
    
    r.setData(new ObjectData());
    r.setFixedRotation(true);
    r.bodyDef.fixedRotation = true;
    ObjectData * sd = (ObjectData*)r.getData();
    sd->id = i;
    sd->bHit	= false;
    rects.push_back(r);
  }
	
  ofAddListener(box2d.contactStartEvents, this, &testApp::contactStart);
  ofAddListener(box2d.contactEndEvents, this, &testApp::contactEnd);
  
}
void testApp::update(){
  box2d.update();
  for( int i = 0; i < rects.size(); i++ ){
    rects[i].setVelocity(
                         rects[i].getVelocity().x*0.6,
                         rects[i].getVelocity().y*0.6);
  }
}
void testApp::draw(){
  ofSetColor(21, 140, 240);
  for( int i = 0 ; i < rects.size(); i++ ){
    rects[i].draw();
  }
}
void testApp::exit(){
}
void testApp::touchDoubleTap(ofTouchEventArgs & touch){
  for( int i = 0; i < rects.size(); i++ ){
    rects[i].addForce(ofVec2f((ofRandom(50)-25), ofRandom(50)-25), 100);
  }
}
test.h

#pragma once

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

class ObjectData {
public:
	int	 id;
	bool bHit;
};

class testApp : public ofxiPhoneApp{
public:
  void setup();
  void update();
  void draw();
  void exit();
  void touchDoubleTap(ofTouchEventArgs & touch);
  ofxBox2d box2d;
  vector <ofxBox2dRect> rects;
	
  void contactStart(ofxBox2dContactArgs &e);
  void contactEnd(ofxBox2dContactArgs &e);
  ofxRealtimeMIDISoundPlayer midi;

  ofPoint object_position[16];

};

4.2 時間をカウントして,指定した時間間隔で指定したタイミングがわかるモノを準備する

では次に,シーケンサをつくるために,時間とシーケンスをカウントしてくれる関数を準備しようと 思います.ここで,まず先に関数のプロトタイプ宣言(型宣言)から書き始めるのではなく,こんな 関数があったらいいな,という具合に実際に使う場所に関数を書いて,後から定義してみましょう.

具体的にはupdate()ないに,どのような関数があって,時間やシーケンスをカウントしてくれる と嬉しいでしょうか.今回この関数によって,16個のマス目位置を決められた時間間隔とともに 判別できる必要があるので,戻り値はマス目位置としましょう.ただしここでこの関数はupdate()内 にて何度も呼び出しが行われるため,例えばシーケンス番号が1から2へ移る場合もプログラム的には

......1111111112222222222.....
という具合に値が変化していきます.すると,シーケンサとしては,位置移動の一発目で一度だけ音を鳴らしたい ので,一度2がでた後,2が続いても音を出力したくありません.ここで様々なやり方が考えられますが, classを一つ作成することで対応してみます. まずはupdate()関数に下記を追記してください.

update()内
if( mst.bIsFirstPositionChange == true ){
  // make sounds
}
test.hに追記
class musicalSequenceTimer{
public:
  bool bIsFirstPositionChange;
  int getSequencerPosition(){
  }
};

こんな感じのclassをつくると便利そうですよね? ではこれの中身を作っていきます.実際に利用するにはこのクラス内で時間をカウントする 必要があります.Openframeworksにはプログラム実行時からの経過時間を返す関数 ofGetElapsedTimeが用意 されているので,これを利用します.現在16分割のステップシーケンサとなっていますが,様々なステップ数に 対応できるように,ここもパラメータで調整可能にしておきます.各ステップの移動速度も設定可能にしておきましょう.

draw()内に追加
 ofCircle(object_position[mst.getSequencerPosition()].x,
             object_position[mst.getSequencerPosition()].y,
             30);
    char str[64];
    sprintf(str, "%d", mst.getSequencerPosition());
    ofSetColor(255,255,255);
    ofDrawBitmapString(str,
                       object_position[mst.getSequencerPosition()].x,
                       object_position[mst.getSequencerPosition()].y);

musicalSequenceTimerクラス(test.hに記述)
class musicalSequenceTimer{
public:
  musicalSequenceTimer(){
    bIsFirstPositionChange = false;
    time_start = ofGetElapsedTimeMillis();
    time_step = 500;
    number_of_division = 16;
  }
  ~musicalSequenceTimer(){
  }
  bool bIsFirstPositionChange;
  void update(){
    static int previous_step_position=0;
    time_sequencer = (ofGetElapsedTimeMillis()-time_start)%(number_of_division*time_step);
    if( previous_step_position != getSequencerPosition() ){
      bIsFirstPositionChange = true;
    }
    else{
      bIsFirstPositionChange = false;
    }
    previous_step_position = getSequencerPosition();
  }
  int getSequencerPosition(){
    return time_sequencer/time_step;
  }
  void draw(int x, int y){
    char str[64];
    sprintf(str,"sequence time: %d\n", time_sequencer);
    ofDrawBitmapString(str, x,y);
  }
private:
  int sequencer_position;
  int time_step;
  int time_sequencer;
  unsigned long long time_start;
  int number_of_division;
};

4.3 musicalSequenceTimerを別ファイルにまとめる(addons化)する

作成したmusicalSequenceTimerクラスは現在test.hにそのまま記述していますが,これを別ファイルに記述しなおします. そうすることで,test.hがスッキリします.まずsrcフォルダの中にmusicalSequenceTimer.mm musicalSequenceTimer.hを 作成してください.test.cppとtest.hをコピーして名前を変えるのが手っ取り早いです.次にmusicalSequenceTimer.mmとmusicalSequenceTimer.h の中身を空にしてください..mmファイル(.cppファイル)の場合はこちらに関数の内容を記述し,.hファイルには関数や変数の宣言を主に記述します. 具体的には下記のようなコードを記述し,test.hからは class musicalSequenceTimer の宣言部分をすべて削除し,代わりに作成したmusicalSequenceTimer.hを インクルードしておきます.

musicalSequenceTimer.mm
#include "musicalSequenceTimer.h"

musicalSequenceTimer::musicalSequenceTimer(){
  bIsFirstPositionChange = false;
  time_start = ofGetElapsedTimeMillis();
  time_step = 500;
  number_of_division = 16;
}

musicalSequenceTimer::~musicalSequenceTimer(){
}

void musicalSequenceTimer::update(){
  static int previous_step_position=0;
  time_sequencer = (ofGetElapsedTimeMillis()-time_start)%(number_of_division*time_step);
  if( previous_step_position != getSequencerPosition() ){
    bIsFirstPositionChange = true;
  }
  else{
    bIsFirstPositionChange = false;
  }
  previous_step_position = getSequencerPosition();
}

int musicalSequenceTimer::getSequencerPosition(){
  return time_sequencer/time_step;
}

void musicalSequenceTimer::draw(int x, int y){
  char str[64];
  sprintf(str,"sequence time: %d\n", time_sequencer);
  ofDrawBitmapString(str, x,y);
}

musicalSequenceTimer.h
#pragma once
#include "ofMain.h"

class musicalSequenceTimer{
public:
  musicalSequenceTimer();
  ~musicalSequenceTimer();
  bool bIsFirstPositionChange;
  void update();
  int getSequencerPosition();
  void draw(int x, int y);
private:
  int sequencer_position;
  int time_step;
  int time_sequencer;
  unsigned long long time_start;
  int number_of_division;
};


4.4 ステップシーケンスに合わせて音を鳴らす

test.mm
#include "testApp.h"
void testApp::contactStart(ofxBox2dContactArgs &e) {
  if(e.a != NULL && e.b != NULL) {
    ObjectData * aData = (ObjectData*)e.a->GetBody()->GetUserData();
    ObjectData * bData = (ObjectData*)e.b->GetBody()->GetUserData();
    
    if(aData) {
      aData->bHit = true;
      float x = rects[aData->id].getVelocity().x;
      float y = rects[aData->id].getVelocity().y;
      float velocity = ofDist(0,0, x, y);
      //midi.sendShortMessage(0x90,aData->id*2+48, velocity*4);
    }
    if(bData) {
      bData->bHit = true;
      float x = rects[bData->id].getVelocity().x;
      float y = rects[bData->id].getVelocity().y;
      float velocity = ofDist(0,0, x, y);
      //midi.sendShortMessage(0x90,bData->id*2+48, velocity*4);
    }
  }
}

void testApp::contactEnd(ofxBox2dContactArgs &e) {
  if(e.a != NULL && e.b != NULL) {
    ObjectData * aData = (ObjectData*)e.a->GetBody()->GetUserData();
    ObjectData * bData = (ObjectData*)e.b->GetBody()->GetUserData();
    if(aData) {
      aData->bHit = false;
    }
    if(bData) {
      bData->bHit = false;
    }
  }

}

void testApp::setup(){

  ofSetFrameRate(60);
  midi.init();
  ofxAccelerometer.setup();
  ofBackground(127,127,127);
  box2d.init();
  box2d.setGravity(0, 0);
  box2d.setFPS(60);
  box2d.registerGrabbing();
  box2d.createBounds(0.0,0.0,ofGetWindowWidth(), ofGetWindowWidth());

  box2d.setIterations(1, 1); // minimum for IOS
  
  majorscale[0] = 0;
  majorscale[1] = 2;
  majorscale[2] = 4;
  majorscale[3] = 5;
  majorscale[4] = 7;
  majorscale[5] = 9;
  majorscale[6] = 11;
  majorscale[7] = 12;

  for( int i = 0; i < 16; i++ ){
    object_position[i].x = (ofGetWindowWidth()/4)*(i%4)+(ofGetWindowWidth()/4)/2;
    object_position[i].y = (ofGetWindowWidth()/4)*(i/4)+(ofGetWindowWidth()/4)/2;
  }
  for( int i = 0; i < 15; i++ ){
    ofxBox2dRect r;
    r.setPhysics(100, 0.0, 1.0);
   
    r.setup(box2d.getWorld(),
            object_position[i].x,
            object_position[i].y,
            (ofGetWindowWidth()/8)*0.98,
            (ofGetWindowWidth()/8)*0.98
            );
    
    r.setData(new ObjectData());
    r.setFixedRotation(true);
    r.bodyDef.fixedRotation = true;
    ObjectData * sd = (ObjectData*)r.getData();
    sd->id = i;
    sd->bHit	= false;
    sd->note  = majorscale[(i%8)]+60;
    sd->velocity = 80;
    sd->color.r = (int)ofRandom(0);
    sd->color.g = (sd->note-60)*10;
    sd->color.b =(sd->note-60)*10+50;
    rects.push_back(r);
  }
	
  ofAddListener(box2d.contactStartEvents, this, &testApp::contactStart);
  ofAddListener(box2d.contactEndEvents, this, &testApp::contactEnd);
  
}
int testApp::getNearestStepPosition(ofVec2f *p){
  double min = 1000;
  int min_num=0;
  double d;
  for( int i = 0; i < rects.size(); i++ ){
    d = ofDist(rects[i].getPosition().x,
               rects[i].getPosition().y,
               p->x,
               p->y);
    if( d < min ){
      min = d;
      min_num = i;
    }
  }
  if( min > 10 ){
    min_num = -1;
  }
  return min_num;
}
void testApp::update(){
  box2d.update();
  for( int i = 0; i < rects.size(); i++ ){
    rects[i].setVelocity(
                         rects[i].getVelocity().x*0.6,
                         rects[i].getVelocity().y*0.6);
  }
  
  mst.update();
  if( mst.bIsFirstPositionChange == true ){
    int number_step;
    number_step = getNearestStepPosition(&object_position[mst.getSequencerPosition()]);
    if( number_step >= 0 ){
      ObjectData * sd = (ObjectData*)rects[number_step].getData();
      midi.sendShortMessage(0x92,sd->note, 80);
    }
  }
  

}
void testApp::draw(){
  ofSetColor(21, 140, 240);
  for( int i = 0 ; i < rects.size(); i++ ){
    ObjectData * sd = (ObjectData*)rects[i].getData();
    ofSetColor(sd->color);
    rects[i].draw();
  }

  ofCircle(object_position[mst.getSequencerPosition()].x,
           object_position[mst.getSequencerPosition()].y,
           30);
  char str[64];
  sprintf(str, "%d", mst.getSequencerPosition());
  ofSetColor(255,255,255);
  ofDrawBitmapString(str,
                     object_position[mst.getSequencerPosition()].x,
                     object_position[mst.getSequencerPosition()].y);
  
  
  mst.draw(20, ofGetWindowWidth()+20);
}
void testApp::exit(){
}
void testApp::touchDoubleTap(ofTouchEventArgs & touch){
  for( int i = 0; i < rects.size(); i++ ){
    rects[i].addForce(ofVec2f((ofRandom(50)-25), ofRandom(50)-25), 100);
  }
}

test.h
#pragma once

#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"
#include "ofxBox2d.h"
#include "ofxRealtimeMIDISoundPlayer.h"
#include "musicalSequenceTimer.h"



class ObjectData {
public:
	int	 id;
  int note;
  int velocity;
  ofColor color;
	bool bHit;
};

class testApp : public ofxiPhoneApp{
public:
  void setup();
  void update();
  void draw();
  void exit();
  void touchDoubleTap(ofTouchEventArgs & touch);
  ofxBox2d box2d;
  vector <ofxBox2dRect> rects;
	
  void contactStart(ofxBox2dContactArgs &e);
  void contactEnd(ofxBox2dContactArgs &e);
  
  ofxRealtimeMIDISoundPlayer midi;
  
  ofVec2f object_position[16];
  musicalSequenceTimer mst;
  int getNearestStepPosition(ofVec2f* p);
  int majorscale[8];
};

動画

4.5 ofxUIを利用してGUIを作成する

Openframeworksのアドオンである,ofxUIを利用します.ofxUIの導入手順はここでは説明しません. ofxUIのGithubのREADMEに導入の手順が書いてある のでそれに沿って進めて下さい.準備ができたらスライダーでシーケンサの再生速度を操作できるようにしましょう.

4.6 ネットワーク同期機能をつける

作成したシーケンサはそれぞれ単体で動作しますが,ネットワーク通信を利用して,タイムラインを同期させる機能を 追加してみます.例えば3台でアプリを立ち上げているとき,一台を親機として,親機のタイムラインとその他2台の子機の タイムラインを同期させてみます.具体的に同期させると言っていますが,要は子機が親機のタイムライン情報を取得できれば 良いだけです.なので,親機から時間情報を各子機に送信すればよさそうです.私達がネットワーク通信と読んでいるものは 一般的にはTCP/IP通信のことです.さらに今回はUDP通信プロトコルでブロードキャスティングをすることで,より手軽に 各端末同期を試みてみます.UDP通信プロトコルとは,データ(パケット)が相手に届いているかいないかを判断しない 通信プロトコルです.逆にTCP通信プロトコルは相手に届けることを保証するものです.次にブロードキャスティングとは, 任意のネットワーク上にいる端末すべてに一括してデータを送信するものです.

4.6.1 サンプルを実行してみましょう

OSX用のOpenframeworksにはofxNetworkのサンプル(networksUdpRecieverExampleとnetworksUdpSenderExample)が あるので,それぞれを同時に実行してみましょう.するとsender側で描いたマウスの軌跡がそのままReciever側で描画されている のが確認できると思います.testApp.cppを見るとわかりますが,127.0.0.1へコネクションを作成しています.これは ローカルホストと呼ばれるIPアドレスの指定方法で,そのソフトウェアを実行しているコンピュータのことを指しています. この場合は自分のコンピュータだけにデータを飛ばせますが,この部分をブロードキャスティングアドレスに変更するだけで (無線LANルータなどにつながった)全台の端末に一度にデータを飛ばせるようになります.なお,このページではこの機能 を利用したOSC(オープンサウンドコントロール)を利用して,実装を進めていきます.

4.6.1 musicalSequenceTimerにNetwork機能を追加

サンプルを確認できたので,次はRhythmPuzzleで確認してみましょう.PCが1台しかない人は,iphoneやipad,もしくは 隣の人のPCを使って確認してみましょう.まずはmusicalSequenceTimerにネットワーク機能を追加します.networkSyncModeに 変数(#defineでわかりやすく定義しました)を操作することでローカル内のネットワークと動機します.サンプルでは送信先アドレスに 192.168.3.255と設定していますが,これはルータの設定内容に依存するので,503室でなければ動きません.

musicalSequenceTimer.mm
#include "musicalSequenceTimer.h"

musicalSequenceTimer::musicalSequenceTimer(){
  bIsFirstPositionChange = false;
  time_start = ofGetElapsedTimeMillis();
  time_step = 500;
  number_of_division = 16;
  receiver.setup(2503);
  sender.setup("192.168.3.255", 2503);

}

musicalSequenceTimer::~musicalSequenceTimer(){
}
int musicalSequenceTimer::setTimeStep(int step)
{
  int old_step_position;
  old_step_position = getSequencerPosition();
  time_step = step;
  time_start = ofGetElapsedTimeMillis() -time_step*old_step_position;
  
}
void musicalSequenceTimer::setNetworkSyncMode(int mode){
  // 0: no network, 1: sender mode(master), 2: receiver mode(slave)
  networkSyncMode = mode;
}
void musicalSequenceTimer::update(){
  static int previous_step_position=0;
  if( networkSyncMode == MST_SYNC_MODE_RECEIVER ){
    // check for waiting messages
    while( receiver.hasWaitingMessages() ){
      ofxOscMessage m;
      receiver.getNextMessage( &m );
      if( m.getAddress() == "/seq" ){
        time_sequencer = m.getArgAsInt32( 0 );
        time_step = m.getArgAsInt32(1);
      }
    }
  }
  else if( networkSyncMode == MST_SYNC_MODE_SENDER ){
    time_sequencer = (ofGetElapsedTimeMillis()-time_start)%(number_of_division*time_step);
    ofxOscMessage m;
    m.setAddress("/seq");
    m.addIntArg(time_sequencer);
    m.addIntArg(time_step);
    sender.sendMessage(m);
    while( receiver.hasWaitingMessages() ){
      receiver.getNextMessage( &m );
      if( m.getAddress() == "/seq" ){
        time_sequencer = m.getArgAsInt32( 0 );
        time_step = m.getArgAsInt32(1);
      }
    }
  }
  else{
    time_sequencer = (ofGetElapsedTimeMillis()-time_start)%(number_of_division*time_step);
  }
  if( previous_step_position != getSequencerPosition() ){
    bIsFirstPositionChange = true;
  }
  else{
    bIsFirstPositionChange = false;
  }
  previous_step_position = getSequencerPosition();
}

int musicalSequenceTimer::getSequencerPosition(){
  return time_sequencer/time_step;
}

void musicalSequenceTimer::draw(int x, int y){
  char str[64];
  sprintf(str,"sequence time: %d\n", time_sequencer);
  ofDrawBitmapString(str, x,y);
}

musicalSequenceTimer.h
#pragma once
#include "ofMain.h"
#include "ofxOsc.h"

#define MST_SYNC_MODE_NO 0
#define MST_SYNC_MODE_SENDER 1
#define MST_SYNC_MODE_RECEIVER 2

class musicalSequenceTimer{
public:
  musicalSequenceTimer();
  ~musicalSequenceTimer();
  bool bIsFirstPositionChange;
  void update();
  int setTimeStep(int step);
  int getSequencerPosition();
  void setNetworkSyncMode(int mode);
  
  void draw(int x, int y);
  int number_of_division;

  int sequencer_position;
  int time_step;
  int time_sequencer;
  
  unsigned long long time_start;
private:
  int networkSyncMode;
  ofxOscReceiver receiver;
  ofxOscSender sender;
};


4.6.2 RhythmPuzzleに適用

test.mmのguiEventを修正
void testApp::guiEvent(ofxUIEventArgs &e)
{
  if(e.widget->getName() == "Step Speed"){
    ofxUISlider *slider = (ofxUISlider *) e.widget;
    mst.setTimeStep(slider->getScaledValue());
    
  }
  if(e.widget->getName() == "Receive" ){
    ofxUILabelToggle *labeltoggle = (ofxUILabelToggle *)e.widget;
    if(labeltoggle->getValue())mst.setNetworkSyncMode(MST_SYNC_MODE_RECEIVER);
    else mst.setNetworkSyncMode(MST_SYNC_MODE_NO);
  }
  if(e.widget->getName() == "Send" ){
    ofxUILabelToggle *labeltoggle = (ofxUILabelToggle *)e.widget;
    if(labeltoggle->getValue())mst.setNetworkSyncMode(MST_SYNC_MODE_SENDER);
    else mst.setNetworkSyncMode(MST_SYNC_MODE_NO);

  }
}
test.h
特に修正なし

以上で,同一ローカルネットワーク上の端末を同期することができました.

5. Processingとhtml

iOSでもAndroidでもWindowsでもMacでも動くアプリケーションを作りたい.と思うことが多々あるでしょう.Unityや cocos2dxを利用して,一つのソースからいくつかのプラットフォーム向けにアプリケーションを作成するやり方もあります. ここでは,このような希望に対して一つのソリューションを記載します.早い話がhtmlとjavascriptで,ブラウザで実行 してしまえば良い.という話です.というわけでこのセクション終わり.

6. Fontをつくる

ここではillustratorなどで作成したパスデータをフォントファイルにまとめる方法を紹介します. 各フォントパスデータをeps等で書き出し,それをFontForgeというファイルでアルファベットに 割り当てて行きます.とても地味な作業になりますが,自分だけのフォントファイルをお金をかけずにてっとり 早く作成できます.ではまずFontForgeをインストールして,,,といきたいところですが,一文字ずつ Illustratorで作成したデータをFontForgeで読み込んで行くのは結構な手間がかかります.まずはザクッと ttfファイルを作成し,その後FontForgeで微調整を行うのが,馬場の経験からも無駄のないやり方と思います.

以上の理由から,ざっくりttfファイルを作成するために,myScriptFont.comというウェブサービスを利用します. 手順は次のとおりです.

  1. myScriptFont.comにアクセス
  2. template(pdf)ファイルをダウンロードし,プリントアウト
  3. 印刷された箇所にそれぞれフォントを手書きする
  4. 記述し終えたらスキャンしデータに戻す
  5. myscriptfontのウェブサイトからアップロードする(2MBまで)
  6. 出来上がったファイルをダウンロード
以上となります.とても簡単ですね.てっとり早っくやってみたい人の為に,テンプレートに馬場が手書き(筆)で描いた ファイルを置いておきます.これをアップロードして,フォントが作られるのを確認してみましょう.なお,このように手書きで 書いてもよいですが,ちゃんとしたものをつくるときは,このテンプレートごとイラストレーターで読み込んで,直接各フォントを 作成し,一旦画像ファイルに書きだした後,myscriptfontで読み込むのが良いかとおもいます.

これで手軽にサクッとttfファイルを作れました,後はこれをシステムにインストールすればすぐに様々なアプリケーション から利用可能です.しかし,大雑把には作れたけど,このあとベースラインや高さなどを微調整したいと思うことがあります. その場合には FontForge を使いましょう.各フォントセットを個別に編集できます.

6.1 FontForgeのインストール

FontForgeとは新しいフォントの作成や既存フォントの編集に利用できるアウトラインフォントエディタです. 修正BSDライセンスに沿う形で配布されています.OSX用のバイナリ(実行ファイル)も配布されていますが, おそらく最近のOSXのバージョンでは動かないとおもいます.なので,ここでは macports を利用してソースコード からコンパイル&インストールします.簡単なので,下記を参照して試して下さい. ここでのインストールには場合によって1時間以上かかる場合もあるので,インストールの際は注意してください. 決してワークショップ中には行わないでください

  1. まずは macports をインストールしてください.Installing MacPorts にある,各自のOS環境にあったものを ダウンロード&インストールしてください.
  2. ターミナルをたちあげて,ports -v selfupdate を実行してください.ただし,首都大の中ではrsyncが利用できないので,学外のネットワークにいるときに実行してください.
  3. ports install fontforge を実行
  4. Applicationフォルダにfontforgeという実行ファイルができているとおもいます.

以上の説明にて,フォント作成のセクションを終わりにします.是非実践してみてください.

i玩具のコンセプト


Copyright (c) 2015 Tetsuaki BABA all rights reserved.