※こちらの内容は既に古い情報となっています。新しく書き直した記事を参照してください。
画像を表示する
Processingで画像を表示するためには、画像ファイルをそのスケッチのある階層に「data」フォルダを作成し、その中に画像ファイルを入れる
Jepg、GIF、PNG形式に対応している
画像を表示するには、image() 関数を用いる
image(画像ファイル名, x, y); – 座標(x, y)を左上にして、画像を表示
image(画像ファイル名, x, y, width, height); – 座標(x, y)を左上にして、幅(width)と高さ(height)を指定して画像を表示
画像を表示
PImage img;
void setup() {
size(640, 426);
img = loadImage("photo.jpg");
}
void draw() {
image(img, 0, 0);
}
位置とサイズを指定して画像を表示
PImage img;
void setup() {
size(640, 426);
img = loadImage("photo.jpg");
}
void draw() {
background(0);
image(img, width/4, height/4, width/2, height/2);
}
画像の色や透明度を変更する
image() で表示した画像の、色や透明度を操作することができる
tint() 関数
tint(gray) – グレースケール画像のレベルを変更
tint(gray, alpha) – グレースケール画像のレベルと透明度を変更
tint(red, green, blue) – カラー画像のRGBの値を変更
tint(red, green, blue, alpha) – カラー画像のRGBの値と透明度を変更
画像の色を変更してみる
イメージのレベルを変更
PImage img;
void setup() {
size(640, 426);
img = loadImage("photo.jpg");
}
void draw() {
background(0);
noTint();
image(img, 0, 0);
tint(102);
image(img, width/2, 0);
}
イメージのカラーを変更
PImage img;
void setup() {
size(640, 426);
img = loadImage("photo.jpg");
}
void draw() {
background(0);
noTint();
image(img, 0, 0);
tint(31, 127, 255);
image(img, width/2, 0);
}
イメージの透明度(アルファ)を変更
PImage img;
void setup() {
size(640, 426);
img = loadImage("photo.jpg");
}
void draw() {
background(0);
for(int i = 0; i < 8; i++) {
int xpos = i * width / 8;
fill(0);
rect(xpos, 0, width, height);
tint(255,255,255, i * 32);
image(img, xpos, 0);
}
}
ピクセルの情報を取得
表示した画像の情報をピクセル単位取得することができる
取得した画像の情報をもとに、より高度なイメージの操作、イメージの情報をもとにした表現を行うことが可能となる
ピクセル情報の取得には、get() 関数を用いる
get関数
get() – ウィンドウ内のピクセル全てを取得、イメージを返す
get(x, y) – 指定した座標、(x, y)のピクセルを取得、色の値を返す
get(x, y, width, hegiht) – 指定した座標、(x, y) から幅(width)、高さ(height)を指定してイメージを取得
get()を使用した例
マウスの位置のピクセルの色を取得 – 取得したRGBの値で左上に3つの正方形を描く
PImage img;
void setup() {
size(640, 426);
stroke(255, 102);
img = loadImage("photo.jpg");
}
void draw() {
background(0);
image(img, 0, 0);
noStroke();
fill(0);
rect(20, 20, 60, 20);
color c = get(mouseX, mouseY);
fill(red(c), 0, 0);
rect(20, 20, 20, 20);
fill(0, green(c), 0);
rect(40, 20, 20, 20);
fill(0, 0, blue(c));
rect(60, 20, 20, 20);
stroke(255, 102);
line(mouseX, 0, mouseX, height);
line(0, mouseY, width, mouseY);
}
画像をモザイク化する
loadPixels() – 現在画面に表示されているピクセル情報を記録する
loadPixels() で格納したピクセルの色情報は、pixels[] 配列に格納される
pixels[] 配列を一定間隔で読みだすことで、画像をモザイク化する
PImage img;
int mosaicWidth = 30;
int mosaicHeight = 20;
void setup() {
size(640, 426);
noStroke();
img = loadImage("photo.jpg");
}
void draw() {
background(0);
image(img, 0, 0);
loadPixels();
for(int j = 0; j < height; j+=mosaicHeight) {
for(int i = 0; i < width; i+=mosaicWidth) {
color c = pixels[j * width + i];
fill(c);
rect(i, j, mosaicWidth, mosaicHeight);
}
}
}
void mouseDragged() {
mosaicWidth = mouseX / 4 + 10;
mosaicHeight = mouseY / 4 + 10;
}
画像ファイルをスキャンする
loadPixels() を活用して、画像の特定のy座標の色だけをとりだして、バーコード状に並べる
PImage img;
void setup() {
size(640, 426);
stroke(255, 102);
img = loadImage("photo.jpg");
image(img, 0, 0);
loadPixels();
}
void draw() {
for(int i = 0; i < width; i++) {
color c = pixels[width * mouseY + i];
stroke(c);
line(i, 0, i, height);
}
stroke(255, 102);
line(0, mouseY, width, mouseY);
}
フラクタルフォトモザイク
モザイクのひとつひとつが同じ写真になっている
モザイク状に配置した写真の明度をtintで変化させている
PImage img;
int mosaicWidth = 30;
int mosaicHeight = 20;
void setup() {
size(640, 426);
noStroke();
background(0);
img = loadImage("photo.jpg");
image(img, 0, 0);
loadPixels();
}
void draw() {
for(int j = 0; j < height; j+=mosaicHeight) {
for(int i = 0; i < width; i+=mosaicWidth) {
color c = pixels[j * width + i];
tint(red(c), green(c), blue(c));
image(img, i, j, mosaicWidth, mosaicHeight);
}
}
}
画像をピクセレイト
画像のピクセルの色情報をもとに、画像を再描画する – ピクセレイト
円の大きさと色で、画像を再構成する
PImage img;
int mosaicSize = 12;
void setup() {
size(640, 426);
noStroke();
img = loadImage("photo.jpg");
image(img, 0, 0);
loadPixels();
}
void draw() {
background(0);
for(int j = 0; j < height; j+=mosaicSize) {
for(int i = 0; i < width; i+=mosaicSize) {
color c = pixels[j * width + i];
fill(c, 127);
ellipse(i, j, brightness(c)/6.0, brightness(c)/6.0);
}
}
}
四角形の大きさと角度で、画像を再構成する
PImage img;
int mosaicSize = 6;
void setup() {
size(640, 426);
noStroke();
img = loadImage("photo.jpg");
image(img, 0, 0);
loadPixels();
rectMode(CENTER);
smooth();
}
void draw() {
background(0);
for(int j = 0; j < height; j+=mosaicSize) {
for(int i = 0; i < width; i+=mosaicSize) {
color c = pixels[j * width + i];
fill(c, 127);
pushMatrix();
translate(i, j);
rotate(brightness(c));
rect(0, 0, brightness(c)/6.0, 2);
popMatrix();
}
}
}
画像ファイルのデータを利用した表現
サンプルファイルのダウンロード
今日とりあげた全てのサンプルは下記からダウンロードしてください。
今日の内容
トウィーンを扱うライブラリを使用して、高度なアニメーション表現に調整
ライブラリにはTweenMaxを使用する
様々なイージング関数を試してみる
Tweenライブラリ
アニメーションの補完のためのライブラリ
位置だけでなく、ムービークリップの属性(プロパティー)であれば、ほとんどのものを補完してくれる
大きさ (width, height)
角度 (rotation)
透明度 (alpha)
フィルター … etc
有志により、様々なライブラリが開発されている
今回は高速さと多機能さが売りのTweenMaxを使用します
TweenMax のダウンロード
TweenMaxのページからライブラリをダウンロードする
TweenMaxを利用したインタラクティブなムービーの作成:基本
マウスをクリックした場所にTween
必ずダウンロードしたフォルダ「greensock-tweening-platform-as3」の中にある「com」フォルダを、作成するflaファイルと同じ場所に置く
画面にムービークリップを一つ配置する
ムービークリップのインスタンス名を「mc」に
タイムラインの第1フレームにスクリプトを記入
まず最初に、Tweenライブラリをインポートする
[code language=”javascript”]
import com.greensock.*;
this.stage.addEventListener(MouseEvent.CLICK, clickHandler);
function clickHandler(e:MouseEvent):void {
TweenLite.to(this.mc, 1, {x:mouseX, y:mouseY});
}
[/code]
いろいろなイージング関数を試してみる
あらたにイージングのためのライブラリをインポートする
import com.greensock.easing.*;
TweenLiteの指定にイージングを追加する
指数関数によるイースイン・アウト
[code language=”javascript”]
import com.greensock.*;
import com.greensock.easing.*;
this.stage.addEventListener(MouseEvent.CLICK, clickHandler);
function clickHandler(e:MouseEvent):void {
TweenLite.to(this.mc, 1, {x:mouseX, y:mouseY, ease:Expo.easeInOut});
}
[/code]
バウント効果によるイースアウト
[code language=”javascript”]
import com.greensock.*;
import com.greensock.easing.*;
this.stage.addEventListener(MouseEvent.CLICK, clickHandler);
function clickHandler(e:MouseEvent):void {
TweenLite.to(this.mc, 1, {x:mouseX, y:mouseY, ease:Bounce.easeOut});
}
[/code]
ばねの動きによるイースアウト
[code language=”javascript”]
import com.greensock.*;
import com.greensock.easing.*;
this.stage.addEventListener(MouseEvent.CLICK, clickHandler);
function clickHandler(e:MouseEvent):void {
TweenLite.to(this.mc, 1, {x:mouseX, y:mouseY, ease:Elastic.easeOut});
}
[/code]
位置+サイズの変更
[code language=”javascript”]
import com.greensock.*;
import com.greensock.easing.*;
this.stage.addEventListener(MouseEvent.CLICK, clickHandler);
function clickHandler(e:MouseEvent):void {
var size:Number = Math.random() * 200;
TweenLite.to(this.mc, 1, {x:mouseX, y:mouseY, width:size, height:size, ease:Elastic.easeOut});
}
[/code]
TintPluginの利用:色の変化
ランダムに色が変化する
[code language=”javascript”]
import com.greensock.*;
import com.greensock.easing.*;
import com.greensock.plugins.*;
TweenPlugin.activate([TintPlugin]);
this.stage.addEventListener(MouseEvent.CLICK, clickHandler);
function clickHandler(e:MouseEvent):void {
var scale:Number = Math.random() * 4.0;
var color:Number = Math.random() * 0xffffff;
import com.greensock.plugins.*;
TweenLite.to(this.mc, 1, {x:mouseX, y:mouseY, scaleX:scale, scaleY:scale, tint:color, ease:Elastic.easeOut});
}
[/code]
Tweenの連鎖:onCompleteの利用
Tweenが完了すると、すぐに新たなTweenを適用する
[code language=”javascript”]
import com.greensock.*;
import com.greensock.easing.*;
import com.greensock.plugins.*;
TweenPlugin.activate([TintPlugin]);
this.stage.addEventListener(MouseEvent.CLICK, clickHandler);
function clickHandler(e:MouseEvent):void {
myTween();
}
function myTween():void {
var scale:Number=Math.random()*4.0;
var color:Number=Math.random()*0xffffff;
var toX:Number=Math.random()*stage.stageWidth;
var toY:Number=Math.random()*stage.stageHeight;
import com.greensock.plugins.*;
TweenLite.to(this.mc, 1, {x:toX, y:toY, scaleX:scale, scaleY:scale, tint:color, ease:Elastic.easeOut, onComplete:myTween});
}
[/code]
今日の内容
サウンドファイルの再生「Buddha Machine」的なアプリを作ってみる
サウンドの録音と再生を使用した音アプリに挑戦
録音した音のデータをインタラクティブに操作する
サウンドファイルの再生
サンプル1:サウンドファイルの再生
オリジナルな"Buddha Machine"的アプリを制作できる雛形
サウンドファイル(caf形式)と表示する画像を入れ替えれば、そのままアプリにできるテンプレートを以下に掲載
音ファイル(caf形式)は、「プロジェクトのフォルダ」→「bin」→「data」→「mySound.caf」に入れる
caf形式のフォーマット – リニアPCM、16bit リトルエンディアン符号付き整数、22050Hz
表示する画像ファイル(PNG形式)は、「プロジェクトのフォルダ」→「bin」→「data」→「myScreen.png」に入れる
PNGファイルノフォーマット – PNG 320 x 480 pixel
ファイルの配置
testApp.h
[code language=”cpp”]
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"
#include "ofxALSoundPlayer.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 lostFocus();
void gotFocus();
void gotMemoryWarning();
void deviceOrientationChanged(int newOrientation);
//表示する画面イメージ
ofImage image;
//OpenALを利用して音を再生するプレーヤー
ofxALSoundPlayer synth;
};
[/code]
testApp.mm
[code language=”cpp”]
#include "testApp.h"
//————————————————————–
void testApp::setup(){
ofRegisterTouchEvents(this);
ofxAccelerometer.setup();
ofxiPhoneAlerts.addListener(this);
//画面イメージを読みこみ
image.loadImage("myScreen.png");
//ループ再生するcafファイルをロードして、Synthにわりあて
synth.loadLoopingSound("mySound.caf");
//読み込んだcafファイルを再生
synth.play();
}
//————————————————————–
void testApp::update(){
}
//————————————————————–
void testApp::draw(){
//画面を表示
image.draw(0, 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){
}
[/code]
サウンドを録音・再生する (サンプリング&プレイバック)
あらかじめ録音したサウンドファイルを再生するのではなく、iPhoneのマイク入力から音を録音したり、録音した音を再生するには、SoundStreamを使用する必要がある
SoundStreamは、音のサンプルのデータ自体を数値として配列に記憶し、それを再生することができる機能 → サンプリング&プレイバック
サンプリグ (標本化)とは
時間的に連続した信号を一定の間隔をおいて測定することにより、離散的な(連続でない)データとして収集すること
時間的に連続した信号 = アナログ信号
離散的な信号 = デジタル信号
サンプリング周波数と量子化ビット数
サンプリング周波数 – 1秒間にいくつのサンプルを使用してサンプリングするか、単位はHz
量子化ビットレイト – AD変換の際に信号を何段階の数値で表現するか、単位はbit
iPhoneシミュレータでサンプリング&プレイバックのプレビューを行う際には「Audio MIDI 設定で」下記の設定にする
サンプリング周波数、44.1KHz
量子化ビットレイト – 24bit
Sound Streamの機能
SoundStreamを使用するにはまず ofSoundStreamSetup() で初期設定を行う、通常は、setup() の中で指定
ofSoundStreamSetup(int nOutputs, int nInputs, ofSimpleApp * OFSA, int sampleRate, int bufferSize, int nBuffers)
サウンドの録音再生の初期設定を行う
int nOutput – 再生する音のチャンネル数
int nInput – 録音する音のチャンネル数
ofSimpleApp * OFSA – 呼び出し元のofSimpleApp (普通は、this でOK)
int sampleRate – サンプリングレイト
int bufferSize – 音をバッファ(格納)するサイズ
Sound Streamに関連するイベント
testApp では、ofSoundStreamSetup() の設定すると音の入出力に関するイベントが発生する
audioReceived(float * input, int bufferSize, int nChannels)
音が入力(録音)される際に発生するイベント
float * input – 入力された音のサンプルの配列
int bufferSize – バッファサイズ、一度に格納されるサンプルの長さ
int nChannels – チャンネル数
audioRequested(float * output, int bufferSize, int nChannels)
音が出力(再生)される際に発生するイベント
float * output – 音として出力するサウンドのサンプルの配列
int bufferSize – バッファサイズ、一度に格納されるサンプルの長さ
int nChannels – チャンネル数
サンプル2:サンプリング&プレイバック
音を録音して再生するという「サンプリング&プレイバック」の機能をシンプルに実現してみる
画面をタッチ(touchDown)すると録音、タッチしている指を離すと(touchUp)録音した音を再生するように設計
録音した音の波形を画面に表示する
testApp.h
[code language=”cpp”]
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"
//録音・再生する音の長さ
#define LENGTH 44100 * 3
class testApp : public ofxiPhoneApp{
public:
void setup();
void update();
void draw();
void touchDown(ofTouchEventArgs &touch);
void touchMoved(ofTouchEventArgs &touch);
void touchUp(ofTouchEventArgs &touch);
void touchDoubleTap(ofTouchEventArgs &touch);
void audioReceived( float * input, int bufferSize, int nChannels );
void audioRequested( float * output, int bufferSize, int nChannels );
float buffer[LENGTH]; //オーディオバッファ
int sampleRate; //サンプリングレイト
int recPos; //レコード位置
int playPos; //再生位置
int mode; //現在のモード、0:off, 1:recording, 2:play
};
[/code]
testApp.mm
[code language=”cpp”]
#include "testApp.h"
//————————————————————–
void testApp::setup(){
//iPhone基本設定
ofRegisterTouchEvents(this);
ofBackground(0, 0, 0);
ofSetFrameRate(60);
//横位置で使用
ofxiPhoneSetOrientation(OFXIPHONE_ORIENTATION_LANDSCAPE_RIGHT);
//サンプリングレイトの設定
sampleRate = 44100;
//サウンド録音再生の初期化
ofSoundStreamSetup(1, 1, this, sampleRate, LENGTH, 4);
//再生モードに
mode = 2;
//録音、再生の位置を先頭に
recPos = 0;
playPos = 0;
}
//————————————————————–
void testApp::update(){
}
//————————————————————–
void testApp::draw(){
ofSetColor(255, 255, 255);
if (mode == 1) {
//録音モードの場合、recordingの表示をして、背景を青に
ofDrawBitmapString("recording", 10, 20);
ofBackground(255, 0, 0);
} else if (mode == 2) {
//再生モードの場合、playingの表示をして、背景を赤に
ofDrawBitmapString("playing", 10, 20);
ofBackground(0, 0, 255);
}
//画面の幅と録音サンプル数の比率を計算
int ratio = LENGTH / ofGetWidth();
//画面の横幅にあわせて、波形を描画
for (int i = 0; i < LENGTH; i+=ratio){
ofLine(i/ratio,ofGetHeight()/2,i/ratio,ofGetHeight()/2+buffer[i]*100.0f);
}
}
//————————————————————–
//オーディオ入力の処理
void testApp::audioReceived(float * input, int bufferSize, int nChannels){
//もし録音モードだったら
if (mode == 1) {
//バッファされたサンプルの数だけ処理
for (int i = 0; i < bufferSize*nChannels; i++){
//録音位置が設定した最大の長さに逹っしていなければ
if(recPos<LENGTH){
//バッファにサウンド入力を設定
buffer[recPos] = input[i];
//録音位置を進める
recPos++;
} else {
//もし最大位置を越えていたら、最初に戻る(ループ録音)
recPos = 0;
}
}
}
}
//————————————————————–
//オーディオ再生の処理
void testApp::audioRequested(float * output, int bufferSize, int nChannels){
//もし再生モードだったら
if (mode == 2) {
//バッファされたサンプル数だけ処理
for (int i = 0; i < bufferSize*nChannels; i++) {
//再生位置が設定した最大の長さに逹っしていなければ
if(playPos<LENGTH){
//バッファに格納したサウンドを再生
output[i] = buffer[playPos];
//再生位置を進める
playPos++;
} else {
//もし最大位置を越えていたら、最初に戻る(ループ再生)
playPos = 0;
}
}
}
}
//————————————————————–
void testApp::touchDown(ofTouchEventArgs &touch){
//画面をタッチすると、録音モードへ
mode = 1;
recPos = 0;
}
//————————————————————–
void testApp::touchMoved(ofTouchEventArgs &touch){
}
//————————————————————–
void testApp::touchUp(ofTouchEventArgs &touch){
//画面をから指を離すと、再生モードへ
mode = 2;
playPos = 0;
}
//————————————————————–
void testApp::touchDoubleTap(ofTouchEventArgs &touch){
}
[/code]
サンプル3:録音した音をインタラクティブに操作する
録音した音のピッチや音量を変化できるようにする
簡易版"Sampletoy" – 参考:Sampletoy
録音したサンプルを再生する際に工夫を加えてみる
そのままの状態で録音開始
画面をタッチすると再生
画面をタッチしながら横に移動すると、音のピッチが変化
画面をタッチしながら縦に移動すると、音量が変化
testApp.h
[code language=”cpp”]
#pragma once
#include "ofMain.h"
#include "ofxiPhone.h"
#include "ofxiPhoneExtras.h"
//録音・再生する音の長さ
#define LENGTH 44100 * 3
class testApp : public ofxiPhoneApp{
public:
void setup();
void update();
void draw();
void touchDown(ofTouchEventArgs &touch);
void touchMoved(ofTouchEventArgs &touch);
void touchUp(ofTouchEventArgs &touch);
void touchDoubleTap(ofTouchEventArgs &touch);
void audioReceived( float * input, int bufferSize, int nChannels );
void audioRequested( float * output, int bufferSize, int nChannels );
float buffer[LENGTH]; //オーディオバッファ
int sampleRate; //サンプリングレイト
int recPos; //レコード位置
float playPos; //再生位置
int mode; //現在のモード、0:off, 1:recording, 2:play
float audioLevel; //音量
float playSpeed; //再生スピード(ピッチを変更できるようFloat型で)
};
[/code]
testApp.mm
[code language=”cpp”]
#include "testApp.h"
//————————————————————–
void testApp::setup(){
//iPhone基本設定
ofRegisterTouchEvents(this);
ofBackground(0, 0, 0);
ofSetFrameRate(60);
//横位置で使用
ofxiPhoneSetOrientation(OFXIPHONE_ORIENTATION_LANDSCAPE_RIGHT);
//サンプリングレイトの設定
sampleRate = 44100;
//サウンド録音再生の初期化
ofSoundStreamSetup(1, 1, this, sampleRate, LENGTH, 4);
//録音モードに
mode = 1;
//録音、再生の位置を先頭に
recPos = 0;
playPos = 0;
playSpeed = 1.0;
}
//————————————————————–
void testApp::update(){
}
//————————————————————–
void testApp::draw(){
ofSetColor(255, 255, 255);
if (mode == 1) {
//録音モードの場合、recordingの表示をして、背景を青に
ofDrawBitmapString("recording", 10, 20);
ofBackground(255, 0, 0);
} else if (mode == 2) {
//再生モードの場合、playingの表示をして、背景を赤に
ofDrawBitmapString("playing", 10, 20);
ofDrawBitmapString("pos = " + ofToString(playSpeed, 3), 10, 40);
ofBackground(0, 0, 255);
}
//画面の幅と録音サンプル数の比率を計算
int ratio = LENGTH / ofGetWidth();
//画面の横幅にあわせて、波形を描画
for (int i = 0; i < LENGTH; i+=ratio){
ofLine(i/ratio,ofGetHeight()/2,i/ratio,ofGetHeight()/2+buffer[i]*100.0f);
}
}
//————————————————————–
//オーディオ入力の処理
void testApp::audioReceived(float * input, int bufferSize, int nChannels){
//もし録音モードだったら
if (mode == 1) {
//バッファされたサンプルの数だけ処理
for (int i = 0; i < bufferSize*nChannels; i++){
//録音位置が設定した最大の長さに逹っしていなければ
if(recPos<LENGTH){
//バッファにサウンド入力を設定
buffer[recPos] = input[i];
//録音位置を進める
recPos++;
}else {
//もし最大位置を越えていたら、最初に戻る(ループ再生)
recPos = 0;
}
}
}
}
//————————————————————–
//オーディオ再生の処理
void testApp::audioRequested(float * output, int bufferSize, int nChannels){
//もし再生モードだったら
if (mode == 2) {
//バッファされたサンプル数だけ処理
for (int i = 0; i < bufferSize*nChannels; i++) {
//再生位置が設定した最大の長さに逹っしていなければ
if(playPos<LENGTH){
//バッファに格納したサウンドを再生
output[i] = buffer[int(playPos)] * audioLevel;
//音程にあわせて、再生位置を移動する
playPos += playSpeed;
} else {
//もし最大位置を越えていたら、最初に戻る(ループ再生)
playPos = 0;
}
}
}
}
//————————————————————–
void testApp::touchDown(ofTouchEventArgs &touch){
//画面をから指を離すと、再生モードへ
mode = 2;
playPos = 0;
//音量を設定
audioLevel = (ofGetHeight() – touch.y) / ofGetHeight() * 3.0;
//音程を設定
playSpeed = touch.x / ofGetWidth() * 2.0;
}
//————————————————————–
void testApp::touchMoved(ofTouchEventArgs &touch){
//音量を設定
audioLevel = (ofGetHeight() – touch.y) / ofGetHeight() * 3.0;
//音程を設定
playSpeed = touch.x / ofGetWidth() * 2.0;
}
//————————————————————–
void testApp::touchUp(ofTouchEventArgs &touch){
//画面をタッチすると、録音モードへ
mode = 1;
recPos = 0;
}
//————————————————————–
void testApp::touchDoubleTap(ofTouchEventArgs &touch){
}
[/code]
サンプルファイルのダウンロード
今回の授業で紹介した全てのサンプルは、下記のリンクからダウンロード可能です
ArduinoとProcessingの連携2:大きな値を送信する、データの流れを視覚化する
今日の内容
先週の課題の講評会
センサーからの入力を視覚化する – ArduinoとProcessingの連携のつづき
大きな値を送受信する – 大きな値 → 0〜1023 (1024段階 = 210 = 10bit)
先週のように、0〜255にマッピングするのではなく、全ての値が反映されるように送受信する方法を学ぶ – ビット演算の使用
データの流れを視覚化する
ノイズの影響などを除去しながらデータが変化する様子を視覚化する
先週の課題の講評会
先週の課題:
Generative Gestaltungのコードを活用して、Arduinoにとりつけたセンサーや可変抵抗、スイッチなどを操作すると形態や動きが変化するプログラムをProcessingで作成する。
使用するセンサーは自由に選択してください。温度センサー、光センサー、可変抵抗以外のものでもOK
複数のセンサー、可変抵抗を組み合せても構いません
大きな値を送受信する
先週の授業でのArduino → Processingのデータの送受信
Arduino – 0 〜 1023 (1024段階、210)
Serialで一度に送出できる範囲 – 0 〜 255 (256段階、28)
Arduinoの値をSerialにあわせるため、map() 関数で値を補正 – map(val, 0, 1023, 0, 255);
値の解像度が1/4になってしまっていた – できることなら全ての値を送りたい
今日用いる手法:値を分割して送信する
値を8bitずつで分解、2回にわけて値を送出する
bit演算を用いて、分解、再合成を行う
ビット(bit)とは
ビット (bit) は、デジタルコンピュータが扱うデータの最小単位 – “binary digit” の略
2通りの状態しか表現できない – “0” または “1”
例:3bit – 3桁のbit
3bitで表現できる数 → 0 〜 7 の8通り
bitで表現できる数は、2の乗数で計算できる
3bit = 2^3 = 8
8bit = 1byte
00000000 から 11111111
10進数の数値にすると、0 から 255
Serial通信で一度に送れる数値は、1byte つまり 0 (00000000) 〜 255 (11111111)
Arduino – Processingの通信の流れ
1024 = 16bit → ArduinoのAnalog inの精度
255 = 8bit → Serial で一度に送出できる値
Arduinoの入力値16bitを、8bit のかたまり2つに分解する
2回にわけて8bit( = 1byte) ずつSerialで送信
例:入力の値が、950 (0000001110110110)だったら
16bitの列から上位の8bitをとりだすには? – bit シフトを行う – 各桁を左または右に移動する
例
右方向に1bitシフト
0000001110110110 >> 1 = 0000000111011011
右に1桁移動
16bitの列から上位の8bitを取り出すには
右方向に8bitシフト
0000001110110110 >> 8 = 0000000000000011
右に8桁移動 → 上位の8桁をとりだした状態
10進数で表現すると
950 >> 8 = 3
16bitの列から下位の8bitをとりだすには?
例:下位8bitだけをとりだす
まとめ – 16bitの数値の、上位 8bit と下位 8bit を分解するには
上位 8bit:8桁 bitシフトを行う
下位 8bit:下位 8bit をマスクする
例:入力が 950 (0000001110110110) の場合
上位8bit:950 >> 8 = 3 (00000011)
下位8bit:950 & 255 = 182 (110110110)
それぞれを、Serialで送信してあげればよい
以上の手順を、ArduinoとProcessing側双方で実装してみる
Arduino側
//アナログ入力の数を定義する
#define NUM 1
//アナログ入力の値を格納する配列
int val[NUM];
void setup() {
//シリアル通信の開始
Serial.begin(9600);
}
void loop() {
//アナログ入力の数だけ繰り返し
for(int i=0; i 0){
//アナログ入力の数だけ繰り返し
for(int i=0; i > 8, BYTE);
//下位 8bit を送出
Serial.print(val[i] & 255, BYTE);
}
//合図用データを読み込んでバッファ領域を空にする
Serial.read();
}
}
Processing側
import processing.serial.*;
//アナログ入力の数を指定
int NUM = 6;
//Serialクラスのインスタンス
Serial myPort;
//Serialより読み込んだデータを格納する配列
int[] val = new int[NUM];
void setup() {
//画面を生成
size(400, 400);
//ポートの名前を取得
String portName = Serial.list()[1];
//Serialクラスをインスタンス化
myPort = new Serial(this, portName, 9600);
}
void draw() {
//背景を黒に
background(0);
//塗りを白に
fill(255);
//入力の数だけ繰り返し
for(int i=0; i NUM * 2 - 1) {
//入力の数だけ繰り返し
for(int i=0; i
データの流れを視覚化する
データの流れを視覚化する1
データからノイズを除去する
センサーからの入力値には、意図しない信号(ノイズ)がのってしまう場合がある
Processing側の工夫で、急激な入力信号の変化は除去したい
実際の値の変化を、指定した値でeasingする → なめらかな変化となる
Arduino側
//アナログ入力の数を定義する
#define NUM 1
//アナログ入力の値を格納する配列
int val[NUM];
void setup() {
//シリアル通信の開始
Serial.begin(9600);
}
void loop() {
//アナログ入力の数だけ繰り返し
for(int i=0; i 0) {
//アナログ入力の数だけ繰り返し
for(int i=0; i > 8, BYTE);
//下位 8bit を送出
Serial.print(val[i] & 255, BYTE);
}
//合図用データを読み込んでバッファ領域を空にする
Serial.read();
}
}
Processing側
import processing.serial.*;
//アナログ入力の数を指定
int NUM = 1;
//現在走査しているX座標
int x;
//なめらかさ
float easing = 0.05;
//なめらかに変換した値
float easedVal;
//現在Serial信号を受信しているか
boolean running = false;
//Serialクラスのインスタンス
Serial myPort;
//Serialより読み込んだデータを格納する配列
int[] val = new int[NUM];
void setup() {
//画面を生成
size(1024, 480);
//フレームレート
frameRate(30);
//ポートの名前を取得
String portName = Serial.list()[1];
//Serialクラスをインスタンス化
myPort = new Serial(this, portName, 9600);
//背景を黒に
background(255);
}
void draw() {
//もし現在信号を受信していたら
if(running) {
//入力値から目標とする値をマッピング
float targetVal = map(val[0], 0, 1024, 0, height/2);
//目標値をなめらかに変換
easedVal += (targetVal - easedVal) * easing;
//グラフの描画
//白い線で前の値を消す
stroke(255);
line(x, 0, x, height);
//色を指定
stroke(63,127,255);
//現在走査している場所を線で示す
line(x+1, 0, x+1, height);
//なめらかにしていない値を描画
line(x, height/2, x, targetVal);
//なめらかにした値を描画
line(x, height, x, easedVal + height/2);
//X座標の更新
x++;
//もし画面の端まできたら、最初から
if(x > width) {
x = 0;
}
}
else {
//まだ信号を受信していなかったら
//画面をクリックするようにメッセージを出す
background(255);
fill(0);
text("click on screen", 10, 20);
}
}
//シリアル入力を検出した際に発生するイベント
void serialEvent(Serial p) {
//もし、バッファーにアナログ入力の数の2倍(上位8bitと下位8bit)あれば
if(myPort.available() > NUM * 2 - 1) {
//入力の数だけ繰り返し
for(int i=0; i
データの流れを視覚化する 2
視覚的な部分をもう少し工夫してみる
データを横にスキャンするのではなく、円形にスキャンしてみる
Arduino側のプログラムは同じ
Processing側
import processing.serial.*;
//アナログ入力の数を指定
int NUM = 1;
//半径
float radius;
//現在の角度
float angle;
//現在Serial信号を受信しているか
boolean running = false;
//Serialクラスのインスタンス
Serial myPort;
//Serialより読み込んだデータを格納する配列
int[] val = new int[NUM];
void setup() {
//画面を生成
size(640, 640);
colorMode(HSB,360,100,100,100);
frameRate(15);
strokeWeight(3);
//ポートの名前を取得
String portName = Serial.list()[1];
//Serialクラスをインスタンス化
myPort = new Serial(this, portName, 9600);
//背景を黒に
background(0);
//スムージング
smooth();
}
void draw() {
if(running) {
//入力された値から円の半径をマッピング
float radius = map(val[0], 0, 1023, 0, height/2);
//円の中心点を指定
int middleX = width/2;
int middleY = height/2;
//三角関数を仕様して角度から座標を算出
float x = middleX + cos(angle) * height/2;
float y = middleY + sin(angle) * height/2;
//黒い線で前の値を消す
stroke(0,0,0);
line(middleX, middleY, x, y);
x = middleX + cos(angle) * radius;
y = middleY + sin(angle) * radius;
//値によって色相を変化させる
float h = map(val[0], 0, 1023, 180, 360);
//算出した色相で線を描く
stroke(h, 75, 100);
line(middleX, middleY, x, y);
//角度の更新
angle += 0.01;
}
else {
//まだ信号を受信していなかったら
//画面をクリックするようにメッセージを出す
background(0);
fill(0,0,100);
text("click on screen", 10, 20);
}
}
//シリアル入力を検出した際に発生するイベント
void serialEvent(Serial p) {
//もし、バッファーにアナログ入力の数の2倍(上位8bitと下位8bit)あれば
if(myPort.available() > NUM * 2 - 1) {
//入力の数だけ繰り返し
for(int i=0; i
サンプルコードのダウンロード
今回の授業で紹介した全てのサンプルは、下記のリンクからダウンロード可能です
openFrameworks の開発者Zach Liebermanさんが来日していて、渋谷で飲んでいるということで、集まりに参加させていただいた。感謝。oF本にサインしていただく。これで、先日多摩美でレクチャーしたときにいただいたTheo Watsonさんのサインとあわせて、openFrameworks主要開発者のサインが揃った。あとは、Arturo Castroさんのサインが揃えばコンプリートか。
ブログには毎回書かなくなったけど、ラニンニングを継続中。週に3日くらいのペースで1時間走る感じ。半年くらい前に始めた当初は1時間で8kmくらいしか走れなかったのだけど、最近ついに1時間で10kmを走りきることができるようになった。ひとつ大きな壁を突破した心境。
人間40歳が目前に迫るくらいの年齢になると、進歩することが少なくなってくる。いろいろな面で。なので、こうして自分の記録を更新できたということ自体が楽しい。今後はこのペースで毎回安定して走ることができるように継続していきたい。
今日の内容
Processingで3DCGプログラミング
3Dの座標系
OpenGLとは
2Dのアニメーションを3Dに拡張してみる
3D座標で図形を描く
視点の移動
3Dのを用いた高度なアニメーション
コンピュータで3Dを表現するには
コンピュータ画面で3Dを表現したい
コンピュータのディスプレイは、2D (縦横に並んだピクセル)
奥行は擬似的に表現するしかない
画面に、立体や空間などの3次元の存在を投影して描画する = 3DCG (3次元グラフィックス)
2次元平面に3次元の存在を投影するには、様々な数学的な知識が必要
Processingではこうした演算を自動的に行うことが可能
3次元の座標系をそのまま使用できる
高速な表示のためのライブラリ(OpenGL)も標準で使用可
コンピュータ画面での3Dを表示する
3D空間の座標系
X (幅)、 Y (高さ) に加えて、奥行を表現する座標軸 Z が加わる。
3Dの座標系
3Dプログラミング基本
まず2Dの図形を回転するプログラムを作成してみる
translateで画面の中心を座標の原点に
以下の処理を繰り返す
背景描画
rotateで回転
rectを描画
回転する角度を更新
float rot=0;
void setup(){
size(400,400);
frameRate(60);
smooth();
fill(63,127,255);
stroke(255);
//四角形を中心を原点に描画するモードに
rectMode(CENTER);
}
void draw(){
background(0);
//画面の中心に原点(0,0)を移動する
translate(width/2,height/2);
//画面の中心を軸にrotだけ回転
rotate(rot);
//四角を描く
rect(0,0,200,200);
//角度を更新
rot += 0.06;
}
現状の回転する四角形のプログラム
2D的な視点では平面上で回転している、これを3D的な視点に変更してみる
現状では、Z軸を中心軸としてXY平面上に置いてある物体が回転している
では軸をZ軸ではなく、他の軸(X軸、Y軸)にすると果してどうなるのか
回転する軸を指定してrotateする関数
rotateX(angle); // X軸を中心に回転
rotateY(angle); // Y軸を中心に回転軸を中心に回転
rotateZ(angle); // Z軸を中心に回転軸を中心に回転
3D座標を用いたプログラミングをする際の注意点
レンダラー (描画の際の方式) を指定する必要がある
P3D – Processing専用の3D描画エンジン
OpenGL – 3Dグラフィックスのためのプログラムインターフェイス、高速に3D画像を描画できる
P3Dを使用する場合
size (幅, 高さ, P3D); // size関数にP3Dの指定
OpenGLを使用する場合
プログラムの先頭でOpenGLのライブラリを読み込み
size関数にOPENGLの指定をする
import processing.opengl.*; // OpenGLライブラリの読み込み
size(幅, 高さ, OPENGL); // size関数にOPENGLの指定
OpenGLを使用した3次元空間での回転
x軸を中心に回転
import processing.opengl.*;
float rot=0;
void setup(){
size(400,400,OPENGL);
frameRate(60);
fill(63,127,255);
stroke(255);
rectMode(CENTER);
}
void draw(){
background(0);
translate(width/2,height/2);
//X軸を中心に回転させる
rotateX(rot);
rect(0,0,200,200);
rot += 0.06;
}
x,y,z軸をそれぞれ回転
import processing.opengl.*;
// x, y, z それぞれの軸での回転角度
float rotX, rotY, rotZ;
void setup(){
size(400,400,OPENGL);
frameRate(60);
fill(63,127,255);
stroke(255);
rectMode(CENTER);
}
void draw(){
background(0,0,20);
translate(width/2,height/2);
//X軸を中心に回転
rotateX(rotX);
//Y軸を中心に回転
rotateY(rotY);
//Z軸を中心に回転
rotateZ(rotZ);
//四角形を描く
rect(0,0,200,200);
//それぞれの軸の回転角度を更新
rotX += 0.02;
rotY += 0.03;
rotZ += 0.05;
}
3D図形の描画デモ
四角形を3D空間にタイル状に敷き詰める
for文を2重に入れ子にして、X,Y方向にグリッド状に四角形を敷きつめていく
ただし個々の四角形はすこしだけ斜めに傾けて立体感を強調する
マウスの位置によって、座標全体を回転してみる
まずは単純に描画してみる
import processing.opengl.*;
void setup() {
size(400, 400, OPENGL);
noStroke();
fill(255, 190);
}
void draw() {
background(0);
translate(width / 2, height / 2, 0);
//マウスの位置で座標全体を回転する
rotateX(mouseX / 200.0);
rotateY(mouseY / 100.0);
//四角形描画を中心を原点に
rectMode(CENTER);
//敷きつめるタイルの一片の長さ
int dim = 18;
//XY平面を正方形でタイリング
for (int i = -height / 2; i < height / 2; i += dim) {
for (int j = -width / 2; j < width / 2; j += dim) {
pushMatrix();
translate(i, j);
rotateX(radians(30));
rotateY(radians(30));
rect(0, 0, dim, dim);
popMatrix();
}
}
}
ライティング
Processinには様々なライティングの方法を用いることができる
ambientLight() – 環境光
directionalLight() – 一定方向から差し込む平行光
pointLight() – 点光源
spotLight() – スポットライト
様々な光を活用しながら、複雑なライティングをしてみる
import processing.opengl.*;
void setup() {
size(400, 400, OPENGL);
noStroke();
fill(255, 190);
}
void draw() {
//環境光
ambientLight(63, 31, 31);
//平行光
directionalLight(255, 255, 255, -1, 0, 0);
//点光源
pointLight(63, 127, 255, mouseX, mouseY, 200);
//スポットライト
spotLight(100, 100, 100, mouseX, mouseY, 200, 0, 0, -1, PI, 2);
background(0);
translate(width / 2, height / 2, -20);
rotateX(mouseX / 200.0);
rotateY(mouseY / 100.0);
int dim = 18;
for (int i = -height / 2; i < height / 2; i += dim * 1.4) {
for (int j = -width / 2; j < width / 2; j += dim * 1.4) {
pushMatrix();
translate(i, j);
rotateX(radians(30));
rotateY(radians(30));
box(dim, dim, dim);
popMatrix();
}
}
}
カメラ
Processingでは何も指定していない時にはカメラ(視点)の場所は、中心点(0, 0, 0)。しかし、camera() 関数を使用することで、視点をコントロールすることができる
camera() 関数
camera(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ)
eyeX, eyeY, eyeZ – 視点の位置
centerX, centerY, centerZ – 注視する中心位置
upX, upY, upZ – カメラの向き
マウスの位置でカメラの位置をコントロールしてみる
import processing.opengl.*;
void setup() {
size(400, 400, OPENGL);
noStroke();
fill(255, 190);
}
void draw() {
background(0);
ambientLight(63, 31, 31);
directionalLight(255, 255, 255, -1, 0, 0);
pointLight(63, 127, 255, mouseX, mouseY, 200);
spotLight(100, 100, 100, mouseX, mouseY, 200, 0, 0, -1, PI, 2);
//カメラを定義、マウスの位置でカメラの位置が変化する
camera(mouseX, mouseY, 200, width / 2.0, height / 2.0, 0, 0, 1, 0);
translate(width / 2, height / 2, -20);
int dim = 18;
for (int i = -height / 2; i < height / 2; i += dim * 1.4) {
for (int j = -width / 2; j < width / 2; j += dim * 1.4) {
pushMatrix();
translate(i, j);
rotateX(radians(30));
rotateY(radians(30));
box(dim, dim, dim);
popMatrix();
}
}
}
3D空間でのアニメーション
少しずつスピードを変化させながら回転する立方体を作成してみる
import processing.opengl.*;
float a;
int NUM = 128;
float offset = PI / NUM;
color[] colors = new color[NUM];
void setup() {
size(400, 400, OPENGL);
noStroke();
colorMode(HSB, 360, 100, 100, 100);
frameRate(30);
for (int i = 0; i < NUM; i++) {
colors[i] = color(i * 2 + 100, 70, 100, 25);
}
lights();
}
void draw() {
background(0);
ambientLight(63, 31, 31);
directionalLight(255, 255, 255, -1, 0, 0);
pointLight(63, 127, 255, mouseX, mouseY, 200);
spotLight(100, 100, 100, mouseX, mouseY, 200, 0, 0, -1, PI, 2);
translate(width / 2, height / 2, -20);
rotateX(mouseX / 200.0);
rotateY(mouseY / 100.0);
for (int i = 0; i < NUM; i++) {
pushMatrix();
fill(colors[i]);
rotateY(a + offset * i);
rotateX(a / 2 + offset * i);
rotateZ(a / 3 + offset * i);
box(width / 2);
popMatrix();
}
a += 0.01;
}
サンプルファイルのダウンロード
今日とりあげた全てのサンプルは下記からダウンロードしてください。
本日の内容
ActionScript 3.0 を用いたより実践的なプログラミングに挑戦する
より具体的な機能をもったユーザーインタフェイスを作成してみる
ムービプレーヤーを作成する
Webで動画を表示・操作することのできる、簡単なムービプレーヤーを作成する
コンポーネントを効果的に使用すると、意外と容易に作成可能
完成イメージ
制作テンプレートのダウンロード
まず最初に制作のためのテンプレートを下記からダウンロードしてください。
ムービープレーヤーの作成手順
FLVPlayerの配置
ウィンドウ → コンポーネント を開く
Videoの中からFLVPlaybackを選択して、ステージ上に配置する
FLVPlayqバックを選択して、プロパティウィンドウのパラメータのタブを選択して下記のように設定する
インスタンス名:theVideo
autoPlay : true
scaleMode : noScale
skin : パラメータのコラムを選択した状態で、右側の虫めがねのアイコンをクリックする
skinの選択画面が表示されるので、リストから SkinUnderPlayStopSeekMuteVol.swf を選択
source : パラメータのコラムを選択した状態で、右側の虫めがねのアイコンをクリックする
再生するムービーファイルのコンテンツパスを設定する画面が表示される
Popeye_forPresiden768K_000.flv を指定する
この状態で一旦ムービーのプレビューをしてみる
シーン選択ボタンの配置
コンポーネントウィンドウを開く (ウィンドウ → コンポーネント)
User Intreface から Buttonを選択
ステージ上に、6つボタンを配置する
それぞれのボタンのパラメータを次のように設定する
インスタンス名: scene0、Label : Scene 0
インスタンス名: scene1、Label : Scene 1
インスタンス名: scene2、Label : Scene 2
インスタンス名: scene3、Label : Scene 3
インスタンス名: scene4、Label : Scene 4
インスタンス名: scene5、Label : Scene 5
スクリプトの記述
actionsのレイヤーに移動して、フレーム内に下記のフレームスクリプトを記述する
[code language=”javascript”]
//それぞれの動画の再生ボタンのリスナーを設定
this.scene0.addEventListener(MouseEvent.CLICK, showScene0);
this.scene1.addEventListener(MouseEvent.CLICK, showScene1);
this.scene2.addEventListener(MouseEvent.CLICK, showScene2);
this.scene3.addEventListener(MouseEvent.CLICK, showScene3);
this.scene4.addEventListener(MouseEvent.CLICK, showScene4);
this.scene5.addEventListener(MouseEvent.CLICK, showScene5);
//動画0を再生
function showScene0(event:MouseEvent):void
{
this.theVideo.source = "Popeye_forPresiden768K_000.flv";
}
//動画1を再生
function showScene1(event:MouseEvent):void
{
this.theVideo.source = "Popeye_forPresiden768K_001.flv";
}
//動画2を再生
function showScene2(event:MouseEvent):void
{
this.theVideo.source = "Popeye_forPresiden768K_002.flv";
}
//動画3を再生
function showScene3(event:MouseEvent):void
{
this.theVideo.source = "Popeye_forPresiden768K_003.flv";
}
//動画4を再生
function showScene4(event:MouseEvent):void
{
this.theVideo.source = "Popeye_forPresiden768K_004.flv";
}
//動画5を再生
function showScene5(event:MouseEvent):void
{
this.theVideo.source = "Popeye_forPresiden768K_005.flv";
}
[/code]
完成!
今日の内容
ArduinoとProcessingを連携する Arduinoのアナログインにセンサーからの情報を入力
ProcessingからArduinoの情報を取得してリアルタイムに情報を視覚化 今後のBio Media Artの肝となってくる部分!!
ArduinoとProcessinの連携方法
Serial 通信による連携 Funnelによる連携
この授業では、Serial通信のほうを採用 参考として、Funnelを使用した方法も紹介
シリアル通信:全体の構成
サンプル1:可変抵抗の値を送信
まずはシンプルな構成で連携をテスト ブレッドボードとArduinoの配線
可変抵抗1つ
ArduinoのAnalog in 0に抵抗値を接続
USBケーブルで、ArduinoをPCに接続
Arduino
Analog in の値を取得 – analogRead()
シリアルで値を送信 – Serial.print()
Processing
シリアルポートに接続
シリアルポートから値を読み取る
可変抵抗器 (ポテンショメーター)
抵抗値を変更することができる抵抗器
今回は、10KΩの可変抵抗器を用意
Arduino側のプログラム
setup() – 初期設定
loop() – メインループ
Analog in 0 の値を読み取る
Analog in の値の範囲は、0〜1024
Serialで一度に出力できる範囲は、0〜255 (1 BYTE)
入力値を、0〜255の範囲にマッピング (map関数)
シリアルで補正した値を送信
一定間隔静止して、繰り返す
int in0; //アナログ入力0
int outByte; //シリアルで出力する値
void setup(){
//シリアル通信開始
Serial.begin(9600);
}
void loop(){
//アナログ入力0番ピンの値を読み取り(0~1023)
in0 = analogRead(0);
//値を、0〜255の範囲にマップ
outByte = map(in0, 0, 1023, 0, 255);
//シリアルでoutByteを送信(BYTEフォーマット)
Serial.print(outByte,BYTE);
//1秒間に10回ループ
delay(100);
}
Processing側プログラム setup() – 初期設定 serialEvent() – シリアルイベント シリアル通信でデータを受信した際に発生するイベント read() で値を取得する (0〜255) 取得した値をモニタする – println() draw() – 描画 取得した値を背景のグレースケールのレベルに対応させている
import processing.serial.*;
// Serialクラスのインスタンス
Serial myPort;
// シリアルポートから取得したデータ(Byte)
int inByte;
void setup()
{
size(640, 640);
// Macのシリアルのリストの最初のポートがFTDIアダプタのポート
String portName = Serial.list()[0];
// ポートとスピードを設定して、Serialクラスを初期化、
myPort = new Serial(this, portName, 9600);
}
void draw()
{
// シリアルから取得した値を背景色に設定
background(inByte);
}
void serialEvent(Serial p){
// 設定したシリアルポートからデータを読み取り
inByte = myPort.read();
}
完成! 可変抵抗の値を変化させると、背景色の濃度が変化する
サンプル2:複数の値を送信
複数のセンサーからの値を取得するには Arduinoには、Analog in が最大5つまで使用できる 複数のAnalog入力の値を取得してみる まずは可変抵抗でテスト ブレットボードとArduinoの配線例
ArduinoとProcessinを連携する際の注意 Arduinoからのシリアル出力は、値が順番に送られる
例:入力が3つ(byte0, byte1, byte2)の場合
byte0, byte1, byte2, byte0, byte1, byte2, ….
Processingの受信方法を工夫しないと、値がずれてしまう場合がある 例:
012012012012… – 想定した値
120120120120… – 順番がずれた値
順番がずれてしまわないように、Processing側で値のセットを受け取ったら読み込み完了の合図を送る 同期のイメージ
int in0, in1, in2; //アナログ入力0
int outByte0, outByte1, outByte2; //シリアルで出力する値
void setup(){
//シリアル通信開始
Serial.begin(9600);
}
void loop(){
//アナログ入力0〜2番ピンの値を読み取り(0~1023)
in0 = analogRead(0);
in1 = analogRead(1);
in2 = analogRead(2);
//値を、0〜255の範囲にマップ
outByte0 = map(in0, 0, 1023, 0, 255);
outByte1 = map(in1, 0, 1023, 0, 255);
outByte2 = map(in2, 0, 1023, 0, 255);
//Processingと同期
if(Serial.available()>0){
//シリアルでoutByteを送信(BYTEフォーマット)
Serial.print(outByte0,BYTE);
Serial.print(outByte1,BYTE);
Serial.print(outByte2,BYTE);
}
//1秒間に10回ループ
delay(100);
}
import processing.serial.*;
// Serialクラスのインスタンス
Serial myPort;
// シリアルポートから取得したデータ(Byte)
int inByte0, inByte1, inByte2;
void setup()
{
size(640, 640);
// Macのシリアルのリストの最初のポートがFTDIアダプタのポート
String portName = Serial.list()[0];
// ポートとスピードを設定して、Serialクラスを初期化、
myPort = new Serial(this, portName, 9600);
noStroke();
smooth();
}
void draw()
{
background(inByte0);
fill(inByte1);
float diameter = float(inByte2)/255.0*width;
ellipse(width/2, height/2, diameter, diameter);
}
void serialEvent(Serial p){
//Arduinoから送られてきたデータが3個来たら
if(myPort.available()>2){
//順番にデータを読み込む
inByte0 = myPort.read();
inByte1 = myPort.read();
inByte2 = myPort.read();
//Arduinoへ読み込み完了の合図を送る
myPort.write(255);
}
}
//マウスが押されたら通信開始
void mousePressed(){
//バッファ領域を空にする
myPort.clear();
//合図用データを送る
myPort.write(255);
}
可変抵抗の設定で、背景色、描画色、円の大きさが変化する
サンプル3:光センサーを使う
可変抵抗でなくても、変化するアナログ値であれば、なんでも利用可能 可変抵抗を各種センサーに入れ替えてみる
光センサー (CdSセル)
測距センサー (赤外線センサー)
感圧センサー
振動センサー
曲げセンサー
温度センサー
傾きセンサー
まずは、光センサーを使用してみます Arduinoと、Processingのプログラムは、そのまま流用可能 ブレットボードとArduinoの配線例
サンプル4:温度センサーを使う
温度センサーをつかって、温度に反応するようにしてみる LM35DZ – 温度センサー、0℃で0V、20℃で200mVになる
課題:センサーの値を視覚化する
Generative Gestaltungのコードを活用して、Arduinoにとりつけたセンサーや可変抵抗、スイッチなどを操作すると形態や動きが変化するプログラムをProcessingで作成する。 使用するセンサーは自由に選択してください。温度センサー、光センサー、可変抵抗以外のものでもOK 複数のセンサー、可変抵抗を組み合せても構いません 次回の月曜の授業まで!
課題のサンプル
Generative Gestaltungの”M_1_2_01″を改造して作成 マウスのX座標によって変化する変数”faderX”を、シリアル入力の値に変更 色も微妙に変化させている Arduino側のコードは、サンプル1と一緒
int in0; //アナログ入力0
int outByte; //シリアルで出力する値
void setup(){
//シリアル通信開始
Serial.begin(9600);
}
void loop(){
//アナログ入力0番ピンの値を読み取り(0~1023)
in0 = analogRead(0);
//値を、0〜255の範囲にマップ
outByte = map(in0, 0, 1023, 0, 255);
//シリアルでoutByteを送信(BYTEフォーマット)
Serial.print(outByte,BYTE);
//1秒間に10回ループ
delay(100);
}
Processing側コード Generative Gestaltung M_1_2_01 を改造
import processing.serial.*;
import processing.pdf.*;
boolean savePDF = false;
int actRandomSeed = 0;
int count = 360;
// Serialクラスのインスタンス
Serial myPort;
// シリアルポートから取得したデータ(Byte)
int inByte;
void setup()
{
size(800, 800);
colorMode(HSB, 360,100,100,100);
cursor(CROSS);
smooth();
// Macのシリアルのリストの最初のポートがFTDIアダプタのポート
String portName = Serial.list()[0];
// ポートとスピードを設定して、Serialクラスを初期化、
myPort = new Serial(this, portName, 9600);
}
void draw() {
if (savePDF) beginRecord(PDF, timestamp()+".pdf");
background(0);
noStroke();
float faderX = inByte/255.0;
randomSeed(actRandomSeed);
float angle = radians(360/float(count));
for (int i=0; i
今日の内容
iPhoneで時計をつくってみる
openFrameworksで、現在の時刻の取得は簡単に実現可能
あとは、その数値を使って、いろいろな表現を試す
時刻の取得
openFrameworksでは、現在の時間、分、秒を取得する関数が用意されている
だたし、この時間はiPhone (シミュレータの場合はMac) の時計を参照しているので、常に正確とは限らない
時刻を取得する関数
ofGetHours(); – 「時」を取得 (0〜24)
ofGetMinutes(); – 「分」を取得 (0〜60)
ofGetSeconds(); – 「秒」を取得 (0〜60)
サンプル1:時刻を表示する
testApp.mm
[code language=”cpp”]
#include "testApp.h"
//————————————————————–
void testApp::setup(){
ofRegisterTouchEvents(this);
ofxAccelerometer.setup();
ofxiPhoneAlerts.addListener(this);
ofBackground(0,0,0);
}
//————————————————————–
void testApp::update(){
}
//————————————————————–
void testApp::draw(){
//秒の取得
int s = ofGetSeconds();
//分の取得
int m = ofGetMinutes();
//時の取得
int h = ofGetHours();
//文字で表示する
ofSetColor(255, 255, 255);
string time = ofToString(h, 0) + ":" + ofToString(m, 0) + ":" + ofToString(s, 0);
ofDrawBitmapString(time, 10, 20);
}
//————————————————————–
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){
}
[/code]
サンプル2:デジタル時計
フォントを読み込んで、きれいな文字で表示
時、分、秒ともに、値が10以下の時は、左に0を追加して2桁表示にする
testApp.h
[code language=”cpp”]
#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 touchDown(ofTouchEventArgs &touch);
void touchMoved(ofTouchEventArgs &touch);
void touchUp(ofTouchEventArgs &touch);
void touchDoubleTap(ofTouchEventArgs &touch);
void lostFocus();
void gotFocus();
void gotMemoryWarning();
void deviceOrientationChanged(int newOrientation);
ofTrueTypeFont verdana;
};
[/code]
testApp.mm
[code language=”cpp”]
#include "testApp.h"
//————————————————————–
void testApp::setup(){
ofRegisterTouchEvents(this);
ofxAccelerometer.setup();
ofxiPhoneAlerts.addListener(this);
ofBackground(0,0,0);
//フォントを読み込み
verdana.loadFont("verdana.ttf",36, true, true);
}
//————————————————————–
void testApp::update(){
}
//————————————————————–
void testApp::draw(){
string s, m, h;
ofSetColor(255, 255, 255);
//秒を読み込み、10以下の場合には左に0づめで2桁に
if (ofGetSeconds()<10) {
s = "0"+ofToString(ofGetSeconds(), 0);
} else {
s = ofToString(ofGetSeconds(), 0);
}
//分を読み込み、10以下の場合には左に0づめで2桁に
if (ofGetMinutes()<10) {
m = "0"+ofToString(ofGetMinutes(), 0);
} else {
m = ofToString(ofGetMinutes(), 0);
}
//時を読み込み、10以下の場合には左に0づめで2桁に
if (ofGetHours()<10) {
h = "0"+ofToString(ofGetHours(), 0);
} else {
h = ofToString(ofGetHours(), 0);
}
//読み込んだフォントで、時刻を表示
string time = h + ":" + m + ":" + s;
verdana.drawString(time, 50, ofGetHeight()/2);
}
//————————————————————–
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){
}
[/code]
サンプル3:アナログ時計(簡易版)
取得した時刻から、アナログ時計を作成する
時針、分針、秒針のそれぞれの角度を求める
時針:12時間で360°変化する。1時間あたりの変化は、360 / 12 = 30°
時刻から時針の角度を求める
現在の時刻(時) x 30
分針:60分で360°変化する。1時間あたりの変化は、360 / 60 = 6°
時刻から分針の角度を求める
現在の時刻(分) x 6
秒針:60秒で360°変化する。1時間あたりの変化は、360 / 60 = 6°
時刻から秒針の角度を求める
現在の時刻(秒) x 6
testApp.mm
[code language=”cpp”]
#include "testApp.h"
//————————————————————–
void testApp::setup(){
ofRegisterTouchEvents(this);
ofxAccelerometer.setup();
ofxiPhoneAlerts.addListener(this);
ofBackground(0, 0, 0);
ofEnableSmoothing();
}
//————————————————————–
void testApp::update(){
}
//————————————————————–
void testApp::draw(){
//時刻を取得
int s = ofGetSeconds();
int m = ofGetMinutes();
int h = ofGetHours()%12;
//時計の大きさを設定
float clockSize = ofGetWidth()/2 – 20;
//背景に円を描く
ofSetColor(255, 255, 255);
ofNoFill();
ofTranslate(ofGetWidth()/2, ofGetHeight()/2);
ofSetLineWidth(3);
ofSetCircleResolution(64);
ofCircle(0, 0, clockSize);
//秒針
ofPushMatrix();
ofRotateZ(s*6.0);
ofSetLineWidth(1);
ofLine(0, 0, 0, -clockSize);
ofPopMatrix();
//分
ofPushMatrix();
ofRotateZ(m*6.0);
ofSetLineWidth(2);
ofLine(0, 0, 0, -clockSize);
ofPopMatrix();
//時針
ofPushMatrix();
ofRotateZ(h*30.0);
ofSetLineWidth(4);
ofLine(0, 0, 0, -clockSize*0.75);
ofPopMatrix();
}
//————————————————————–
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){
}
[/code]
サンプル4:アナログ時計(完成版)
作成したプログラムの表示と実際のアナログ時計と比べてみる
なにか違いはないだろうか?
分が切り替わった瞬間の動き
時間が切り替わった瞬間の動き
実際の時計
時針、分針も常にゆっくりと動いている
時刻が変化した瞬間に動くのではなく、ゆっくりと移動して、気がつくと角度が変化している
実際の分針
秒の影響を受けている
毎秒あたり、1/60の影響 (1分 = 60秒なので)
m = m + (s/60);
実際の時針
分の影響を受けている
毎分あたり、1/60の影響 (1時間 = 60分なので)
h = h + (m/60);
分針と時針の補正をしたアナログ時計に修正する
文字盤を追加
角度を変更しながら、一定間隔に目盛を刻んでいく
小さな目盛:6度づつ
大きな目盛:30度づつ
testApp.mm
[code language=”cpp”]
#include "testApp.h"
//————————————————————–
void testApp::setup(){
ofRegisterTouchEvents(this);
ofxAccelerometer.setup();
ofxiPhoneAlerts.addListener(this);
ofBackground(0, 0, 0);
ofEnableSmoothing();
}
//————————————————————–
void testApp::update(){
}
//————————————————————–
void testApp::draw(){
//時刻を取得
//秒を取得
float s = ofGetSeconds();
//秒の影響を加えた、分の算出
float m = ofGetMinutes() + (s/60.0);
//分の影響を加えた、時の算出
float h = ofGetHours()%12 + (m/60);
//時計の大きさ
float clockSize = ofGetWidth()/2 – 20;
//座標全体を中心に移動
ofTranslate(ofGetWidth()/2, ofGetHeight()/2);
//時計の背景
ofSetColor(127,127,127);
ofFill();
//分の目盛を描く
for (int i=0; i<60; i++) {
ofRotateZ(6);
ofCircle(clockSize, 0, 2);
}
//時の目盛を描く
for (int i=0; i<12; i++) {
ofRotateZ(30);
ofCircle(clockSize, 0, 4);
}
ofSetColor(255, 255, 255);
ofNoFill();
//秒針
ofPushMatrix();
ofRotateZ(s*6.0);
ofSetLineWidth(1);
ofLine(0, 0, 0, -clockSize);
ofPopMatrix();
//分
ofPushMatrix();
ofRotateZ(m*6.0);
ofSetLineWidth(2);
ofLine(0, 0, 0, -clockSize);
ofPopMatrix();
//時針
ofPushMatrix();
ofRotateZ(h*30.0);
ofSetLineWidth(4);
ofLine(0, 0, 0, -clockSize*0.75);
ofPopMatrix();
}
//————————————————————–
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){
}
[/code]
サンプル5:大きさによる時間の表現
時刻の数値をより自由な発想で使用してみる
時、分、秒、ミリ秒の値で半径を変化させながら、円を描いてみる
大きさによる時計
testApp.mm
[code language=”cpp”]
#include "testApp.h"
//————————————————————–
void testApp::setup(){
ofRegisterTouchEvents(this);
ofxAccelerometer.setup();
ofxiPhoneAlerts.addListener(this);
ofBackground(0, 0, 0);
ofEnableSmoothing();
}
//————————————————————–
void testApp::update(){
}
//————————————————————–
void testApp::draw(){
//時刻を取得
int ms = ofGetElapsedTimeMillis() % 1000;
int s = ofGetSeconds();
int m = ofGetMinutes();
int h = ofGetHours();
//円の最大の大きさを設定
float circleSize = ofGetHeight()/8;
ofSetColor(127, 127, 127);
//ミリ秒
ofCircle(ofGetWidth()/2, ofGetHeight()/8*7, ms/1000.0*circleSize);
//秒
ofCircle(ofGetWidth()/2, ofGetHeight()/8*5, s/60.0*circleSize);
//分
ofCircle(ofGetWidth()/2, ofGetHeight()/8*3, m/60.0*circleSize);
//時
ofCircle(ofGetWidth()/2, ofGetHeight()/8, h/24.0*circleSize);}
//————————————————————–
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){
}
[/code]
サンプル6:位置による時間の表現
時、分、秒、ミリ秒の値で位置を変化させながら、直線を引いてみる
testApp.h
[code language=”cpp”]
#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 touchDown(ofTouchEventArgs &touch);
void touchMoved(ofTouchEventArgs &touch);
void touchUp(ofTouchEventArgs &touch);
void touchDoubleTap(ofTouchEventArgs &touch);
void lostFocus();
void gotFocus();
void gotMemoryWarning();
void deviceOrientationChanged(int newOrientation);
ofTrueTypeFont verdana;
};
[/code]
testApp.mm
[code language=”cpp”]
#include "testApp.h"
//————————————————————–
void testApp::setup(){
ofRegisterTouchEvents(this);
ofxAccelerometer.setup();
ofxiPhoneAlerts.addListener(this);
ofBackground(0, 0, 0);
ofEnableSmoothing();
ofSetBackgroundAuto(false);
ofEnableAlphaBlending();
verdana.loadFont("verdana.ttf",12, false, true);
}
//————————————————————–
void testApp::update(){
}
//————————————————————–
void testApp::draw(){
//背景
ofSetColor(0, 0, 0, 15);
ofRect(0, 0, ofGetWidth(), ofGetHeight());
//時刻を取得
int ms = ofGetElapsedTimeMillis() % 1000;
int s = ofGetSeconds();
int m = ofGetMinutes();
int h = ofGetHours();
//枠
ofSetColor(63, 63, 63);
ofSetLineWidth(1);
ofLine(0, ofGetHeight()/4, ofGetWidth(), ofGetHeight()/4);
ofLine(0, ofGetHeight()/2, ofGetWidth(), ofGetHeight()/2);
ofLine(0, ofGetHeight()/4*3, ofGetWidth(), ofGetHeight()/4*3);
//線の位置で時間を表現
ofSetColor(255, 255, 255);
ofSetLineWidth(3);
//ミリ秒
verdana.drawString(ofToString(ms, 0), ms/1000.0*ofGetWidth()+4, ofGetHeight()/8+6);
ofLine(ms/1000.0*ofGetWidth(), 0, ms/1000.0*ofGetWidth(), ofGetHeight()/4);
//秒
verdana.drawString(ofToString(s, 0), s/60.0*ofGetWidth()+4, ofGetHeight()/8*3+6);
ofLine(s/60.0*ofGetWidth(), ofGetHeight()/4, s/60.0*ofGetWidth(), ofGetHeight()/2);
//分
verdana.drawString(ofToString(m, 0), m/60.0*ofGetWidth()+4, ofGetHeight()/8*5+6);
ofLine(m/60.0*ofGetWidth(), ofGetHeight()/2, m/60.0*ofGetWidth(), ofGetHeight()/4*3);
//時
verdana.drawString(ofToString(h, 0), h/24.0*ofGetWidth()+4, ofGetHeight()/8*7+6);
ofLine(h/24.0*ofGetWidth(), ofGetHeight()/4*3, h/24.0*ofGetWidth(), ofGetHeight());
}
//————————————————————–
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){
}
[/code]
サンプルファイルのダウンロード
今日とりあげた全てのサンプルは、下記よりダウンロードすることができます。
11月にハーフマラソンを走るという目標ができたので、週2、3回は1時間くらい走って徐々に長距離を走る体力をつけていきたい。というわけで、昨日はティップネスのトレッドミル(ランニングマシン)で、10km/hのペースでどこまで走れるかトライしてみた。
できれば、1時間10km/h、1kmあたり6分ちょうどのペースで、そのまま1時間走りたいというのが目標。ところが、いざ走ってみると、最初の5kmは快調だったのだが、6km過ぎくらいからかなりバテてきた。7km過ぎからだいぶ辛くなり8kmをちょっと過ぎた地点でリタイア。10kmまではまだまだトレーニングが足りないみたいだ。当面は、1時間で10kmを走るというのを目標に、練習していきたい。
久しぶりに体重測ったら、ちょっとだけ痩せてた。おそらく本人にしか気付かない微妙な変化なんだけど…