INDEX(各項目ごとの目次)

[HOME]  [Processing関係]  [Arduino関係]  [マテリアル関係]  [秋葉原工作マップ]

2008年用ですが、部分的に内容を更新しています(2010/06/14)。
また、[建築農業工作ゼミ2009-2010]とも連動していますので、そちらにも幾つかサンプルがあります。
:

ラベル processing の投稿を表示しています。 すべての投稿を表示
ラベル processing の投稿を表示しています。 すべての投稿を表示

7/24/2008

Processing サウンド1/Sonia

今回は、Processingを使ってサウンド制御をしてみます。「Sonia」という音のライブラリを使用します。音に関してはこの他に「Minim」や「Ess」というライブラリもあります。

Sonia」を利用するには、
・「Sonia」サイトへ行く。
・「download」をクリック。
・Sonia 2_9 for Processing V90 [ZIP]の[ZIP]部分をクリック。
・ダウンロードが開始され、デスクトップなどに展開する。
・展開したファイル(sonia_v2_9)をMacosXなら/Application/Processing0135/libraries内に入れる。
 WindowsならC:¥Program File¥processing-0135-expert¥libraries内に入れる。
JSynプラグインをインストールする(画面の指示に従って自動インストール)。

・インストール完了
*Processingを開いたままインストールした場合は、Processingを再起動すれば使うことができます。

「Sonia」では、wavまたはaiffのフォーマットを読み込むことができます。まず、これらのフォーマットの音源を用意します。今回はwavフォーマットを使用することにします。

wavフォーマットの音源がない場合は、既にiTunesに登録してある曲(音源)をwavフォーマットに変換し直して使用することができます。

iTuneで音源をwavフォーマットに変換する方法:

・iTunesを起動し、「iTunes/環境設定」を開く。
・環境設定ウィンドウ内の「詳細」をクリック。
・「詳細」設定の「読み込み」をクリック。
・「読み込み方法:」を「WAVエンコーダ」にする。
・「設定」は「自動」でも構いません。
・「OK」ボタンをクリック。
・iTunesのもとの画面に戻り、曲(音源)を選択する。
・メニューバー/詳細から「選択項目をWAVに変換」を選ぶ。

以上で選択した音源はwavフォーマットに変換され、iTunes内に保存されます。
iTunes外の音源も、一旦iTunesに取り込んで変換すれば使うことができます。

用意した音源は、プログラムを書いたファイルのsketchフォルダ内のdataフォルダ内(ない場合は新規作成)に入れておきます。(dataフォルダについては、以前の「Processing 文字と画像」を参照)。
新規sketchを開いたら、一旦保存して、そのsketch名が「sketch_08724a」なら、MacosXの場合/User/username/Documents/Processing/sketch_08724a内にdataフォルダを作成し音源を入れておく必要があります。Windowsの場合、C:¥Documents and Setting¥username¥My Documents¥Processing¥sketch_08724a内となります。

音源は各自用意するか、無料音源サイトなどからダウンロードして下さい。
無料音源サイト:音の杜 http://mmworks.info/otonomori/

まずは、単純に音源を再生するプログラムです。プログラムをランさせれば、音源が再生されます。
尚、プログラムをランさせた時に「Exception in thread "Thread-2" java.lang.OutOfMemoryError:...」のような赤文字のエラーが出たら、メモリーが足りないということなので、MacosXの場合はメニューバーのProcessing>環境設定を選択し(ウィンドウが現れる)、「Set maximum available memory to ... MB」の欄に256MBなど充分なメモリー(曲や音源のサイズ)を記入し、チェック欄にチェックを入れて下さい。Windowsの場合は、File>Preferencesを選択し、同様に充分なメモリー数を記入しチェックを入れて下さい。


//ライブラリを取り込む
import pitaru.sonia_v2_9.*;
//音源tune(名前は任意)を用意
Sample tune;

void setup() {
//とりあえず画面を200角に設定
size(200,200);
//Sonia開始
Sonia.start(this);
//括弧内に音源名を指定し設定する
tune = new Sample("music.wav");
//音源の再生
tune.play();
}

void draw(){
//特になし
}

//Soniaの使用停止
public void stop(){
Sonia.stop();
super.stop();
}


まず、ライブラリを取り込んで、音源のオブジェクト名tune(名前は任意)を設定します。setup(){...}内で、Soniaの使用開始をしたら、tune = new Sample("music.wav");で、予め用意しておいたdataフォルダ内の音源を指定します。Sample()の括弧内に音源名を「"」マークで両端を括っていれておきます。tune.play()で音源の再生をします。今回の場合はsetup(){...}内にtune.play()が書かれているので、プログラムをランさせると同時に再生が開始されます。画面表示は特に使わないので、とりあえず200ピクセル角に設定してあります。最後のpublic void stop(){...}は、ウィンドウを閉じたときにSoniaの使用を停止するプログラムです。これを書いておかなければ、ウィンドウを閉じてもSonia自体のプログラムはランし続けてしまうことがあるので、忘れずに書いておいてください。

次に、クリックしたら再生し、もう一度クリックしたら停止するプログラムを書いてみます。


//ライブラリを取り込む
import pitaru.sonia_v2_9.*;
//音源tune(名前は任意)を用意
Sample tune;

//再生/停止の切替フラグを用意
//falseの時停止、trueの時再生とする
boolean start=false;

void setup() {
//とりあえず画面を200角に設定
size(200,200);
//Sonia開始
Sonia.start(this);
//音源名を指定し設定する
tune = new Sample("music.wav");
}

void draw(){
//特になし
}

//クリックした場合
void mousePressed(){
if(start==false){//フラグが停止の時
start=true; //フラグを再生にする
tune.play(); //音源再生

}else{ //フラグが再生に時
start=false; //フラグを停止にする
tune.stop(); //音源停止
}
}

//Soniaの使用停止
public void stop(){
Sonia.stop();
super.stop();
}


再生と停止を切り替えるためにboolean型の変数(フラグ)start(名前は任意)を用意し、falseなら停止、trueなら再生という状態をクリックするごとに記憶させておきます。同時にそれに応じて停止/再生をtune.stop()とtune.play()で設定します。
ただ、このままでは再生する度に音源の冒頭部分に戻ってしまいます。効果音のような短い音源の場合なら、この設定でもいいのですが、曲にようにもう少し長い音源の場合は、ポーズ(一時停止)させたほうがいいかもしれません。
以下では、クリックして停止したときに、曲の停止箇所を記憶させておき、もう一度クリックした時に、その続きから再生されるようにしてみます。


//ライブラリを取り込む
import pitaru.sonia_v2_9.*;
//音源tune(名前は任意)を用意
Sample tune;

//再生/停止の切替フラグを用意
//falseの時停止、trueの時再生とする
boolean start=false;

//音源全体の長さの変数
int totalFrames;
//再生箇所(停止箇所)の変数
int playHead;

void setup() {
//とりあえず画面を200角に設定
size(200,200);
//Sonia開始
Sonia.start(this);
//音源名を指定し設定する
tune = new Sample("music.wav");
//音源の長さを取得する
totalFrames=tune.getNumFrames();
}

void draw(){
//特になし
}

//クリックした場合
void mousePressed(){
if(start==false){//フラグが停止の時
start=true; //フラグを再生にする

//再生箇所指定で音源再生
tune.play(playHead,totalFrames);

}else{ //フラグが再生に時
start=false; //フラグを停止にする

//停止箇所を記憶させる
playHead=tune.getCurrentFrame();
tune.stop(); //音源停止
}
}

//Soniaの使用停止
public void stop(){
Sonia.stop();
super.stop();
}


音の場合、通常サンプルレートと呼ばれる数値でデータ量が扱われます。CDなどの場合44100Hzであり、1秒間に44100個の信号が扱われます。2秒ならその倍の88200個の信号になるので、基準のサンプルレートが44100Hzであれば、その数値を数えれば、再生時間(サンプル数)が計算できます。上のプログラムでは、まずtotalFramesという変数を用意して、getNumFrames()という関数で音源全体の時間の長さ(サンプル数)を取得しておきます。10秒間の音源であれば、44100×10=441000となります。そして、playHeadという一時停止させる箇所を記憶させておく変数を用意し、getCurrentFrame()によって、現在の再生箇所を取得します。つまり、マウスをクリックして停止するときに、その停止箇所を記憶させることになります。つぎに、tune.play(playHead,totalFrames)というように括弧内に再生開始箇所と終了箇所を指定して再生させます。こうすることで、クリックで停止させるごとに、停止箇所を記憶しておき、もう一度再生させるときに、前回の停止箇所から再生させることが可能になります。

関連:
Processing サウンド4/スクラッチ」--曲をスクラッチ演奏する
Processing サウンド3/テンポ」--音源再生のテンポ変換をする
Processing サウンド2/逆再生」--逆再生の音源をつくる

*MacOSX(Intel)の場合、新たにJSynプラグインをインストールする必要があります。ここをクリックすると、インストール画面に移動します。移動先のページ上の「Click Here to Install JSyn Plugin」のボタンを押してインストールしてください。

 iTunes Music Store(Japan)

6/16/2008

Arduino 加速度センサ

今回は秋月電子で購入した「KXM52-1050」という3軸加速度センサモジュールを使い、重力方向に対する傾斜角を読み取ります。このセンサでは、XYZ軸の3軸ありますが、XとY軸だけでも三次元的な傾斜角を計測することができます。一応、センサのXYZの3つの出力端子をArduinoのアナログ入力端子にそれぞれ接続することにしますが、実際使うのはXとYの出力値とします。データシートをみながらセンサの端子を以下のように接続します。

1:5V(Arduino5V端子と共有)
2:5V(Arduino5V端子と共有)
3:GND(ArduinoGND端子と共有)
4:無接続
5:GND(ArduinoGND端子と共有)
6:X軸(Arduinoアナログ入力0番ピン)
7:Y軸(Arduinoアナログ入力1番ピン)
8:Z軸(Arduinoアナログ入力2番ピン)



加速度センサを水平なところにおけば、X軸とY軸は重力方向に対して直角なので0Gとなります。5V電源の場合、0Gは2.5Vとして出力されるとデータシートには書いてあります。ArduinoのanalogRead()の1024段階(10ビット)であれば511になるはずですが、さまざまな条件で多少の誤差を含みます。実際に使用する前に、念のためArduinoの「Serial Monitor」で加速度センサの出力値をモニタリングしてみます(Arduinoのモニタリング方法については「Arduino 圧電スピーカ」を参照」。

Arduino (Serial Monitor)のプログラム:

void setup(){
//シリアル通信開始
Serial.begin(9600);
}

void loop(){
//3つの値をアナログ入力で読み込む
int x=analogRead(0);
int y=analogRead(1);
int z=analogRead(2);

//Xの値を出力(十進数)
Serial.print(x,DEC);
//値と値の間に区切りを入れる
Serial.print(",");
//Yの値を出力
Serial.print(y,DEC);
//値と値の間に区切りを入れる
Serial.print(",");
//Zの値を出力し改行する
Serial.println(z,DEC);
delay(100);
}
}

3つの値を一行で出力する際に、Arduinoの出力画面上で読みやすいようにそれぞれの値の間に「","」の区切りの記号(コンマ)をいれます。この区切り記号は何でもいいのですが、これがないとそれぞれの数値同士が隣り合わせになって読みにくくなります(また、3つの数値をそれぞれ改行して出力すると、どれがX軸の値でどれがY軸の値なのか分かりにくくなるので、3つ出力してから改行しています)。
固定した角度で計測しても数値が安定しないので、100個の値をサンプリングして平均値を求めたいと思います。平均値のプログラムを付け加えます。100個分の値の合計となると、数字も大きくなるので「int」型の整数ではなく、より大きい値が扱える「long」型の整数を変数として使います。


//加算用の変数
long x_sum, ysum, z_sum;
//回数の変数
int count=0;

void setup(){
Serial.begin(9600);
}

void loop(){
int x=analogRead(0);
int y=analogRead(1);
int z=analogRead(2);

//それぞれに値を足していく(合計数)
x_sum+=x;
y_sum+=y;
z_sum+=z;

//回数を+1する(カウントアップ)
count++;

//100回カウントしたら
if(count>99){
//合計数を100で割って平均値を出す
Serial.print(x_sum/100,DEC);
Serial.print(",");
Serial.print(y_sum/100,DEC);
Serial.print(",");
Serial.println(z_sum/100,DEC);
//カウントを0に戻す
count=0;
//合計数を0に戻す
x_sum=0;
y_sum=0;
z_sum=0;
}
}

まずは、X軸について計測することにします。水平状態(0G)に対して定規などを用いて−90度傾けて−1Gの値、90度傾けて+1Gの値を上記プログラムを用いて計測することにします。
プログラム上では、xの値をx_sumに足していき、変数countで何回足したかを数えておきます(1ループで一回足されます)。countが100になったら、100回分の合計数であるx_sumを100で割り、その値を出力します(yについても同様に計測します)。
0Gの値については、水平に置いて計測してもいいのですが、今回は−1Gの時の値と+1Gの時の値の中点を用いることにします。よって、以下のような計測結果になります。

  角度:   重力:X軸平均値:Y軸平均値
-90度:  -1G:  316:  271
中点0度:   0G:  536:  491
+90度:  +1G:  756:  711 

これらの値は、今回使用した加速度センサと計測状況において求められた値なので、各自で似たような方法で計測してください。

それでは、この計測結果をもとに、Processingにセンサからの出力値をシリアル通信し、Processing上の3D立体を動かしてみたいと思います。センサを傾ければ、同様に3D立体も同じ角度で傾くようにします。シリアル通信は、1024段階の値を文字列で送ることにします(「Arduino-Processing シリアル通信5」を参照)。このプログラムでは、X軸とY軸だけを読み取ることにします。

Arduinoのプログラム:

void setup(){
//シリアル通信開始
Serial.begin(9600);
}

void loop(){
//2つの値をアナログ入力で読み込む
int x=analogRead(0);
int y=analogRead(1);

if(Serial.available()>0){
//Xの値を出力
Serial.print(x,DEC);
//値と値の間に区切りを入れる
Serial.print(",");
//Yの値を改行して出力
Serial.println(y,DEC);
//合図用データを読み込みバッファを空にする
Serial.read();
}
}

センサから読み取ったXとY軸の値をそのままArduinoから送信します。
Processingでは、受け取った値を角度に変換する計算が必要になります。まず0Gを基準にして、水平時の値が0になるようにオフセット値(X軸の場合:536、Y軸の場合:491)を設けて差し引いておきます。そうすれば、

  角度:   重力:   X軸:   Y軸
-90度:  -1G: -220: -220
  中点:   0G:    0:    0
+90度:  +1G: +220: +220

となります。振り幅は0Gを基準にプラスマイナス220となります。
次に角度の計算ですが単位はラジアンを用います。−90度から+90度までの範囲なので、ラジアンでいうと−PI/2から+PI/2になります(PIは円周率のπです)。X軸の値が110であれば、振り幅である220(1G)の半分なので0.5Gになります。角度については90度の半分なので45度になりそうですが、実際は30度になります。−45度の場合は、以下の図のように約−156になります。



この計算方法は以下のようにして求められます。

acos()、asin()を用いる場合:
まず、Arduinoから送られて来たX軸の値をx、オフセット値をx_offset(今回のオフセット値は536)、オフセット調整した値をx0とすると、

x0=x-x_offset;

になり、角度をradX(ラジアン)とすると

sin(radX)=x0/220;

という関係になります。例えば、x0=110を代入すればsin(radX)=1/2なので、radXは30度となります。
Processingにはasin()acos()の関数があるので、それを利用すると

radX=asin(sin(radX));

という関係になり、sin(radX)にx0/220を代入し

radX=asin(x0/220);

となることで角度radXが求まります。
Y軸についてはacos()で求めると、

radY=acos(y0/220);

になります。

atan2()を用いる場合:
また、この関係をタンジェントで表せば、

tan(radX)=x0/sqrt(220*220-x0*x0)

となります。sqrt()は平方根(ルート)を求める関数です。
角度を求めるには、atan2()という関数を用いて、

radX=atan2(x0,sqrt(220*220-x0*x0));

とします。そうすると角度radXが求められます。

加速度センサのX軸プラス方向をProcessingの3D空間のX軸マイナス方向に対応させるために-radXに変換します。加速度センサのY軸方向を3D空間のZ軸方向に対応させて、

rotateX(-radX)
rotateZ(radY)

となります。
もし、加速度センサの回転方向と、3D立体の回転方向が逆になってしまうときは、値にマイナスを掛けます。また、90度ずれているときはPI/2を足します。実際にセンサを動かして、同じように3D立体が動くか確かめて下さい。

Processingのプログラム:

import processing.serial.*;
Serial port;

//読み取り値の変数
int x,y;

//X軸-1G時316、+1G時756であることから
//X軸のオフセット値
int x_offset=536;
//X軸の振り幅(-1G〜0G又は0G〜+1G)
int x_range=220;

//Y軸-1G時271、+1G時711であることから
//Y軸のオフセット値
int y_offset=496;
//Y軸の振り幅(-1G〜0G又は0G〜+1G)
int y_range=220;

//角度(ラジアン)の変数
float radX,radY;

void setup(){
//3D画面サイズ400×400
size(400,400,P3D);
//シリアルポート設定
port = new Serial(this,"/dev/tty.usbserial-A50019vD",9600);
//念のためバッファを空にする
port.clear();
//「10」(ラインフィード)が来る度にserialEvent()作動
port.bufferUntil(10);
//図形塗り面なし(ワイヤフレーム描画)
noFill();
}

void draw(){
//背景色を白
background(255);

//3D立体の座標を画面中央、-100奥に配置
translate(width/2,height/2,-100);

//オフセット調整(最小値-220、最大値220)
int x0=constrain(x-x_offset,-220,220);
int y0=constrain(y-y_offset,-220,220);

//角度の計算(ラジアン)
radX=asin(x0/x_range);//asin()で求める
radY=acos(y0/y_range);//acos()で求める
//radX=atan2( x0,sqrt(x_range*x_range-x0*x0) );//atan2()で求める場合
//radY=atan2( y0,sqrt(y_range*y_range-y0*y0) );

//センサX軸の角度は3D立体のX軸の角度に対応
//センサY軸の角度は3D立体のZ軸の角度に対応
//角度をそれぞれ代入
rotateX(-radX);
rotateZ(radY);

//直方体を描画
box(200,30,100);
}

//シリアル通信
void serialEvent(Serial p){
//文字列用変数を用意し、
//「10」(ラインフィード)が来るまで読み込む
String stringData=port.readStringUntil(10);

//データが空でないとき
if(stringData!=null){
//改行記号を取り除く
stringData=trim(stringData);
//コンマで区切ってデータを分解、整数化
int data[]=int(split(stringData,','));

//データ数が2個のとき
if(data.length==2){
//データの値を代入
x=data[0];
y=data[1];
//合図用データ送信
port.write(65);
}
}
}

//マウスボタンを押して通信開始
void mousePressed(){
//合図用データ送信
port.write(65);
}

void draw(){...}内の「オフセット調整」箇所の

int x0=constrain(x-x_offset,-220,220)

は、constrain()を用いて、読み取った値xからオフセット値であるx_offsetを差引き、最小値−220から最大値220までの値になるように制限しています。

ノイズのせいか、動きがぎこちない場合はフィルターのプログラムを挿入し滑らかにします。そのためには、radX、radYと同様にプログラムの冒頭でフィルター用の変数:
float filterX,filterY;

を用意しておき、void draw(){...}内の最後の角度を求める箇所を以下のように変更してください。
radX=asin(x0/x_range);//変更なし
radY=acos(y0/y_range);//変更なし

//フィルターの式
filterX+=(radX-filterX)*0.3;//新たに挿入
filterY+=(radY-filterY)*0.3;//新たに挿入

rotateX(-filterX);//変更
rotateZ(filterY); //変更

フィルターの式の「0.3」は係数であり、1.0に近づくほどフィルターの効果はなくなります。逆に0.1のように係数の値を小さくすれば、滑らかになりつつ反応が鈍く動くようになります。適度に調整してみてください。


尚、もっと簡単に加速度センサを扱いたい場合は(あまり正確な角度にこだわらないのであれば)、
//オフセット調整(最小値-220、最大値220)
int x0=constrain(x-x_offset,-220,220);
int y0=constrain(y-y_offset,-220,220);
//角度の計算(ラジアン)
radX=asin(x0/x_range);
radY=acos(y0/y_range);

の部分を、
radX=2.0*x*PI/1023;
radY=2.0*y*PI/1023;

に置き換えてもセンサを傾けた方向に3D立体が傾きます。この計算では、読み取った直接の値に比例して角度も変わります(比率が多少ずれてしまいます)。この場合は、モニタリングで調べた最小値/最大値/オフセット値などの設定もする必要はありません。式の中の「2.0」というのは係数であり、大きくすれば傾きも大きくなるので画面で確認しながら調整してください。

−90度や+90度付近では、出力値の変化が微妙になるので、きちんとした角度が出ない場合があります。出力値補正のためにZ軸の出力も利用すれば、計算は少し複雑になるかもしれませんが、±90付近まで計測可能になります。

6/12/2008

Arduino-Processing シリアル通信5


【変更】以下はArduino1.0まで対応したプログラム内容です。
特にシリアル通信においては、Arduino2.0使用の際、バイト送信する場合、
Serial.print(value,BYTE);
のかわりに、
Serial.write(value);
を使用してください。


以前行ったシリアル通信では、ArduinoのanalogRead()で読み取った値(0〜1023までの値)を0〜255(8ビット)にスケールダウン(4で割る)して送信するか、256以上の大きな値を、二つの0〜255(8ビット)の数値に分解して送っていました。今回は、たとえば「1023」という255より大きい値を、そのままの「1023」という値で送信したいと思います。そのためには、読み取った整数値(int型)を文字列として送信します。
今回は3つの可変抵抗器を読み取って(接続方法は「Arduino-Processing シリアル通信2」を参照)、Arduinoから3つの値をまとめて送信します。複数の値を送る際には数値と数値の間にデリミターという区切りの記号(今回の場合「,」コンマ)を挿入して送信します。そうすることによって、Processingでデータを受け取る際に、データ内容を混同せず読み取ることができます。最初の二つの読み取り値は、Serial.print()を使ってDEC(十進数文字列)のフォーマットで送信し、区切り記号のコンマもSerial.print()で文字列として送信します。最後の読み取り値を送る時に、DECフォーマットでSerial.println()を用い「改行」して送信します。改行することで、Processing側でデータを受け取る際に、送られて来たデータのどの部分が最後であるのかを確認することが可能になります。それでは、Arduinoのプログラムから始めます。

Arduinoのプログラム:
void setup(){
  //シリアル通信開始
  Serial.begin(9600);
}

void loop(){
  //3つのセンサの値を読み取り、変数に代入
  int x=analogRead(0);
  int y=analogRead(1);
  int z=analogRead(2);

  //合図用データが一個きたら
  if(Serial.available()>0){
    //xの値を十進数文字列で送信
    Serial.print(x,DEC);
    //区切り記号コンマを送信
    Serial.print(",");
    //yの値を十進数文字列で送信
    Serial.print(y,DEC);
    //区切り記号コンマを送信
    Serial.print(",");
    //zの値を十進数文字列かつ改行して送信
    Serial.println(z,DEC);
    //合図用データを読み込みバッファを空にする
    Serial.read();
  }
}


「xの値 コンマ yの値 コンマ zの値 改行」というデータが一度に送られることになります。Serial.print(value,DEC)の「DECフォーマット」の数値は文字列であり、以前使ったSerial.print(value,BYTE)の「BYTEフォーマット」の数値と異なる値になります。文字列の「1」は、BYTEフォーマットでは、「49」に相当します。十進数文字列とバイトの数値の対応は以下のようになります(ちなみに、BYTEフォーマットの「65」は文字列の「A」になります)。

DEC: BYTE:
 0  48
 1  49
 2  50
 3  51
 4  52
 5  53
 6  54
 7  55
 8  56
 9  57

アスキーコード表」にこれらの対応関係が掲載されています。
たとえば、「120」という値の場合、BYTEフォーマットならそのまま「120」となりますが、DECフォーマットでは「49 50 48」というように「1」「2」「0」という3つの文字を送ることになります。DECフォーマットでは、1桁の数値なら1バイト分のデータであり、2桁なら文字二つを送るので2バイト、3桁なら3バイト必要になります。BYTEフォーマットは、255までの数値であれば1バイトで済みますが、それ以上の数値は「Arduino-Processing シリアル通信3」で行ったように、分解して送るなどの工夫が必要となります。
DECフォーマットで、そのままの値を文字列として送信した方が分かりやすいのですが、その分バイト数が増えてしまうことにもつながります。BYTEフォーマットであれば少ないバイト数で送ることができますが、大きな数値を分解して計算し直さなければいけないので、十進数の数値として扱いづらくなります。状況に応じて使い分けるのがいいと思います。

次に、Processing側のプログラムに入る前に、どのようなかたちでデータを受け取るかということについて説明します。

例えば、3つの可変抵抗器から読み取られる値が、

x=120
y=284
z=1015

の場合、Arduinoからは、

「120 コンマ 284 コンマ 1015 改行」

というデータが送られてきます。
「コンマ」は「アスキーコード表」では「44」であり、「改行」記号は「アスキーコード表」の「13」と「10」がデータの最後に付け加えられることになります。
「13」は「キャリッジリターン(行頭に戻る)」ということであり、文字列では「\r」になります。
「10」は 「ラインフィード(次の行へ移る)」ということであり、文字列では「\n」になります。
Windowsでは、キャリッジリターンとラインフィードで改行となり、Macintoshでは、キャリッジリターンのみで改行されるので、この二つがあることで、いずれにせよ改行されることになります。

先ほどの、

「120 コンマ 284 コンマ 1015 改行」

というデータは、

"120" + "," + "284" + "," + "1015" + "\r" + "\n" 

という文字列データになります。
コンマや改行記号を手掛かりにすれば、データの順番や終わりの部分をProcessing側で判別して読み込むことができます。それでは、このようなことを踏まえてProcessingのプログラムをしてみたいと思います。PFontを用いて、数値を文字で画面に表示することにします。マウスを押したら通信開始することにします(プログラムが開始して数秒たってからマウスを押さないと反応しないときがあります)。

Processingのプログラム:
//シリアルライブラリを取り込む
import processing.serial.*;
//シリアル通信用変数portを用意
Serial port;

//フォント用変数fontを用意
PFont font;

//読み込み値の変数を用意
int x,y,z;

void setup(){
  //画面サイズ設定
  size(400,200);

  //フォントをロードする
  font = loadFont("Monaco-10.vlw");
  //フォント使用開始:サイズ10
  textFont(font, 10);
  //文字を右寄りに配置する
  textAlign(RIGHT);

  //シリアルポート設定
  port = new Serial(this,"/dev/tty.usbserial-A50019vD",9600);
  //念のためバッファを空にする
  port.clear();
  //「10」(ラインフィード)が来る度に
  //serialEvent()を発動させる
  port.bufferUntil(10);  
}

void draw(){
  //背景を白で塗りつぶす
  background(255);
  //3つの値を文字で表示する
  text(x,100,50);
  text(y,200,50);
  text(z,300,50);
}

//シリアル通信
void serialEvent(Serial p){
  //文字列の変数stringDataを用意し、
  //「10」(ラインフィード)が来るまで読み込む
  String stringData=port.readStringUntil(10);

  //文字列データが空ではないとき
  if(stringData!=null){
    //文字列データに含まれる改行記号を取り除く
    stringData=trim(stringData);

    //整数型の配列data[]を用意し、
    //コンマ記号をもとに文字列データを区切って
    //配列data[]に整数化して入れておく
    int data[]=int(split(stringData,','));

    //配列data[]内のデータが3つなら、
    if(data.length==3){
      //最初のデータをxに代入
      x=data[0];
      //次のデータをyに代入
      y=data[1];
      //その次のデータをzに代入
      z=data[2];

      //合図用データ送信
      port.write(65);
    }
  }  
}

//マウスが押されたら通信開始
void mousePressed(){
  //開始用データ送信
  port.write(65);
}

Processing上のシリアル通信では、まず初期設定setup(){...}内で、bufferUntil()を使って、指定した文字がArduinoから送られて来るたびにserialEvent()が作動するように設定しておきます。Arduinoから最後に送られてくるSerial.println()によって、データの末尾が改行記号の「\n」であることから、今回はbufferUntil()の括弧内には「10」を入れておきます。整数値「10」は文字列の改行記号の「\n」(ラインフィード)に相当します。
そして、serialEvent(){...}内では、readStringUntil()を用いて、同様に「10」つまり「\n」が来るまでデータを読み込む設定にします。読み込まれたデータは、3つの値以外にも「コンマ」や「改行」記号が含まれた連続した文字列なので、その文字列の内容を整理し直す必要があります。
「stringData!=null」は、読み込まれたデータが空(null)ではないとき、つまり何かしらのデータがあるときという条件です。データがあれば、その文字列データに含まれている余分な空白記号や改行記号をtrim()によって取り除きます。
その後、それぞれの値の区切り記号(デリミター)として用いた「,」コンマをもとに、連続したひとつのデータをsplit()で分解します。split()によって分解されたデータは、複数のデータを内包する配列に変換されます。さらに、分解されたデータは、まだ文字列なので、int()を用いて整数値へ変換します。そのためにdata[]という配列を用意し、「int data[]=int(split(stringData,','))」の中で、この一連の変換作業を行っています。配列については、「Arduino 7セグLEDの点灯」の後半でも触れていますので、参照してください。
if(data.length==3){...}は、配列data[]内のデータ数が3つあるときにという条件です。length配列の大きさ(データを何個含んでいるか)を数えます。データ数が3つあることを確認してから、配列data[]に含まれる一つ目の値「data[0]」をxに代入します(配列では、最初のデータは0番目となります)。同様にyとzについても代入します。最後に合図用データを一つ送信します(65以外の数値でも大丈夫です)。合図用データをArduinoへ送信すれば、Arduinoは再び新たなデータを送り返してきます。

連続した文字列データを個別の数値に変換する手続きを以下にもう一度書きます。
Arduinoで読み取った3つの値を、

x=120
y=284
z=1015

とすれば、
Arduinoからは、

"120" + "," + "284" + "," + "1015" + "\r" + "\n" 

という順番で文字列として送信されます。
Processingでは、port.readStringUntil(10)で括弧内の「10」つまり「\n」までを、

"120,284,1015\r\n" 

という連続したデータとして読み込みます(「\r\n」は改行記号)。合計14個の文字があるので14バイトになります。「\r」と「\n」はそれぞれ1バイトずつとなります。 
trim()で「改行」記号を削除すると、

"120,284,1015"

になります。
split()で「,」をもとに分解すると、

{"120","284","1015"}

という、3つの文字列を含んだ配列のデータに変換されます。
さらに、これら3つの文字列をint()で整数の数値に変換すると、

{120,284,1015}

になり、予め用意しておいた整数型の配列data[]に入れます。

int data[]={120,284,1015}

そして、「data.length」によって配列data[]のデータ数が3個であるかを確認し、これらの値(整数値)を順にx、y、zへ入れます。

x=data[0]
y=data[1]
z=data[2]

この手順を踏んで、連続した文字データを個別の数値として扱うことができます。


関連:
Arduino-Processing シリアル通信1」(一つの値を送る/非同期通信)
Arduino-Processing シリアル通信2」(複数の値をバイトで送る/同期通信)
Arduino-Processing シリアル通信3」(大きな値を複数送る)
Processing-Arduino シリアル通信4」(ProcessingからArduinoを制御する)
Arduino-Processing シリアル通信6」 (2台のArduinoとProcessingを通信させる)

6/06/2008

Processing ドラッグしながら図形を描く


上はこのプログラムをキャプチャした画像です。
下の白いキャンバス内にドラッグして矩形を描くことができます。eキーで白紙に戻ります。







Processingでは、予めプログラム上で図形の座標やサイズを決めてから描画させますが、
一般の描画ソフト(イラストレータなど)のように、ドラッグしながら図形の大きさを決めて描くサンプルを以下に書きます。今回は、描く図形をrect()にし、マウスボタンを押した地点から描画を開始し、ドラッグ中はrect()のサイズが変化し、マウスボタンを放した時に描画完了とします(いわゆる描画ソフトでの一般的な描き方)。重ね描きできるようにするためには、毎回background()で画面を塗りつぶさず、今回はloadPixels()updatePixels()を使うことにします。重ね描きが分かりやすいように、noFill()で図形の塗り無しで、外形線だけの描画とします。「e」のキーを押せば、白紙に戻るようにします。

Processingのプログラム:

//図形描画開始点座標の変数
int x,y;

void setup(){
size(400,300);
//最初に背景色を白にしておく
background(255);
//始点と終点座標による描画に設定
rectMode(CORNERS);
//塗り無し
noFill();
}

void draw(){
//ここには何も書かない
}

//マウスボタンが押されたら
void mousePressed(){
//マウス座標を変数に入れておく
x=mouseX;
y=mouseY;
//画面内容を記憶しておく
loadPixels();
}

//ドラッグしたら
void mouseDragged(){
//画面内容を更新する
updatePixels();
//x,yを開始点、マウス座標を終点として描画
rect(x,y,mouseX,mouseY);
}

//キーが押されたら
void keyPressed(){
//「e」キーなら
if(key=='e'){
//白で塗りつぶす
background(255);
}
}

初期設定のsetup()では、rectMode(CORNERS)にして、開始点と終点の二点間の座標による定義にしておきます。マウスボタンを押した時に、マウス座標値mouseXmouseYrect()の描画の開始点としてxとyの変数に記憶させ、その時の画面内容をloadPixels()を使って記憶しておきます(ドラッグ中の処理のための下準備)。そしてドラッグ中には、loadPixels()の時の画面内容をupdatePixels()によって表示させつつ、xとyを開始点としてrect()を描き始め、終点はドラッグ中のmouseXとmouseYの座標を使って常に変化するようにします。loadPixels()によって、画面内容を記憶させておかなかったり、updatePixels()によってその画面内容を表示させなければ、ドラッグ中のrect()の線は重なり合うように描画されます(試しに、updatePixels()を消してランさせればどうなるか分かります/下画像)。


updatePixels()を使わない場合の画面。
このようにドラッグ中の変化する線も消されずに描画されます。敢えてこの方法を使うということも考えられます。

6/05/2008

Processing 手描きの線の表現




Processingにおいて、rect()ellipse()のような幾何学図形ではなく、マウスを使って「手描き」の表現をする場合のサンプルです。マウスボタン(クリック)を押しながらドラッグすると、手描きのような線を引くことができ、「e」のキーを押せば画面は真っ白に戻るという内容です。

以下の画面中央(白い矩形内)に描くことができます。









Processingのプログラム:

void setup(){
//画面サイズ幅400、高さ300
size(400,300);
//滑らかな描画(アンチエイリアス)
smooth();
//背景色を255(白)に設定
background(255);
}

void draw(){
//ここには何も書かなくてよい
}

//マウスドラッグ中のプログラム
void mouseDragged(){
//線を描画
line(pmouseX,pmouseY,mouseX,mouseY);
}

//キーが押された時のプログラム
void keyPressed(){
//もしキーが「e」なら
if(key=='e'){
//背景色を白で塗り直す
background(255);
}
}


上のプログラムでは、mouseDragged()keyPressed()の時にしか描画しないので、draw(){...}内には特に書く項目はありません。線を引くには、line(x1,y1,x2,y2)というように四つの値が入り、開始点(x1,y1)から終点(x2,y2)まで線を引きます。line()内のpmouseXは、一つ前(1ループ前)のマウスのX座標値であり、mouseX()は現在のX座標値です。つまり、一つ前のマウス座標値から現在のマウス座標値まで線を引くということになります。ドラッグ中は、その内容が繰り返され連続した線になります。すべてを白紙に戻すには、キーを押したときに、background()を用いて画面全体を塗り直します。draw(){..}内にbackground()を書いておくと、描画されている線も同時に上塗りされて消えてしまうので、setup(){...}の中で、最初に一度設定しておき、線だけが重ね描きされるようにします。

Arduinoにつないだセンサから読み取った値を、シリアル通信を通してline()内に入れれば、センサによって自由な線を描くことができます。応用的な使い方を考えてみて下さい。

6/01/2008

Processing-Arduino シリアル通信4


【変更】以下はArduino1.0まで対応したプログラム内容です。
特にシリアル通信においては、Arduino2.0使用の際、バイト送信する場合、
Serial.print(value,BYTE);
のかわりに、
Serial.write(value);
を使用してください。


前回までは、Arduino基盤に接続したセンサなどからProcessingの画像を動かしていましたが、今回はその逆で、Processingで制作した画像からArduinoを制御したいと思います。前回同様合図用のデータを送り、確認し合いながら通信します。Processingでマウスで動かすことができるスライダをつくり、256段階の値をArduino側に送り、PWM出力を用いてLEDの明るさを調節できる内容とします。Processing側から操作するので、まずProcessing側のプログラムから書きます。

Processingのプログラム:
//シリアルライブラリの取り込み
import processing.serial.*;
Serial port;

//X座標の変数
int x=0;

void setup(){
  size(255,100);
  //シリアルポートの設定
  port=new Serial(this,"/dev/tty.usbserial-A4001Kjl",9600);
}

void draw(){
  background(100);
  line(x,0,x,height);
}

void serialEvent(Serial p){
  //データ数が0個より多いとき
  if(port.available()>0){
    //X座標を送信
    port.write(x);
    //Arduinoからの合図用データを
    //読み込んでバッファを空にする
    port.read();
  }
}

void mouseDragged(){
  //ドラッグ中のX座標にマウスX座標を
  //最小値0,最大値255で入れる
  x=constrain(mouseX,0,255);
}

void keyPressed(){
  //sキーを押したら
  if(key=='s'){
    //通信開始用データ送信
    port.write(0);
  }
}


今回は、キーボードの「s」キーを押すことでシリアル通信を開始することにしました。「s」キーを押さなければ、Processing、Arduinoの両方のプログラムは、どちらも待機中となります。そのため、Processing側から「s」キーを押して、通信開始のきっかけとなるデータを1個(1バイト分)送ります。
Arduino側では、11番ピン(PWMピン)にLEDのプラス側を、GNDにマイナス側をつなぎます。必要に応じてその間に抵抗(220Ω)を直列つなぎします。

Arduinoのプログラム:
//読み取り値の変数
int val=0;

void setup(){
  //シリアル通信開始
  Serial.begin(9600);
}

void loop(){
  //データが0個より多いときの時
  if(Serial.available()>0){
    //データの読み込み
    val=Serial.read();
    //合図用データ送信(1バイト)
    Serial.write(65);
  }
  //アナログ出力(11番ピン)に読み込み値を入れる
  analogWrite(11,val);
}

手順としては、
(1)両方のプログラムが開始される。
(2)合図用データの待機中。
(3)Processingから「s」キーで通信開始用データを送信。
(4)Arduinoのバッファ内データを数える。
(5)Arduinoバッファ内データが1個以上ならデータ読み込み。
(6)その結果、Arduinoバッファ内データが空になる。
(7)Arduinoから合図用データ送信。
(8)Proccessingバッファ内データを数える。
(9)Processingバッファ内データが1個以上ならX座標データ送信。
(10)合図用データを読み込み、バッファを空にする。
 *以後は、(4)へ戻り通信を繰り返す。

追記:
上記プログラムで通信が途切れてしまう場合、ArduinoよりProcessingの処理速度が速すぎるのかもしれません。Processingのvoid setup(){...}内でframeRate(30)程度にするか、draw(){...}内にdelay(20)程度を挿入し少しスピードダウンすると安定するかもしれません。
あるいは、ProcessingからArduinoへ一方向的にデータを送る内容なので非同期通信にしてもいいかもしれません。その場合もProcessingのvoid setup(){...}内でframeRate(30)程度にスピードダウンし、void draw(){...}内にport.write(x)を入れて(serialEvent()は使わず)30fpsで定期的に送信し、Arduino側でそのデータを受け取るようにします(合図用データは送る必要はありません)。


関連:
Arduino-Processing シリアル通信1」(一つの値を送る/非同期通信)
Arduino-Processing シリアル通信2」(複数の値をバイトで送る/同期通信)
Arduino-Processing シリアル通信3」(大きな値を複数送る)
Arduino-Processing シリアル通信5」(複数の値を文字列で送信する)
Arduino-Processing シリアル通信6」 (2台のArduinoとProcessingを通信させる)

5/28/2008

Arduino-Processing シリアル通信3


【変更】以下はArduino1.0まで対応したプログラム内容です。
特にシリアル通信においては、Arduino2.0使用の際、バイト送信する場合、
Serial.print(value,BYTE);
のかわりに、
Serial.write(value);
を使用してください。


前回までのサンプルでは、ArduinoのSerial.write()を用いて、0〜255までの256段階の値を扱っていました。analogRead()からは、0〜1023までの1024段階の値を読み取ることができるのに、わざわざ4で割って256段階にスケーリングして解像度を落としていました。
今回はanalogRead()から読み取った1024段階の値を解像度を落とさずProcessingへ送信したいと思います。1024段階の値を二つの値に分解して送信する方法を用いることにします。大きな画面上で図形などを滑らかに動かすときや、センサなどから読み取った値を精度高く計算したいときは、解像度を低くできない場合があります。そのような時に、今回の方法を用いるといいでしょう。
今回もまた3個の可変抵抗器を基盤につなぎ(配線については前回ブログ参照)、それぞれをanalogRead()で1024段階で読み取り、Processing上でも1024段階で表現したいと思います。

Arduinoのプログラム:
//3つの変数を用意
int x, y, z;

void setup(){
  //シリアル通信開始
  Serial.begin(9600);
}

void loop(){
  //x,y,zの変数へ読み込んだ値を入れる
  x = analogRead(0);
  y = analogRead(1);
  z = analogRead(2);

  //Processingからの合図用データが
  //バッファ領域内に一つ以上になったら
  if(Serial.available() > 0){
    //それぞれの値を2つの値に
    //分解するための変数を用意し
    //計算結果を入れる
    int x_high = x / 256;
    int x_low = x % 256;
    int y_high = y / 256;
    int y_low = y % 256;
    int z_high = z / 256;
    int z_low= z % 256;
    //分解した値を送る
    Serial.write(x_high);
    Serial.write(x_low);
    Serial.write(y_high);
    Serial.write(y_low);
    Serial.write(z_high);
    Serial.write(z_low);
    
    //合図用データを読み込んで、
    //バッファ領域を空にする
    Serial.read(); 
  }
}

以上のプログラムで、x,y,zは整数型の変数です。整数型の計算では、例えば10/3=3、3/10=0であり、小数点以下の部分は無視されます(四捨五入もされません)。ちなみに、小数点まで求める場合は、float型の変数を用います。
それから「%」(Modulo)という割り算の余りを求める計算式があります。10%3=1、3/10=3、10%4=2、10%5=0となります。
今回はこの二つの計算方法を使って、0〜1023の1024段階の値をx_highとx_lowという二つの値に分解して、二つのデータとしてArduinoから送ります。送られたデータはProcessing上で再び合成されて、一つの値として扱われるようにします。
例えばArduinoのanalogRead()から950という読み取り値があったら、
x_high = 950 / 256;
x_low = 950 % 256;

として、
x_high = 3;
x_low = 182;

となります。
この二つの値3と182をそれぞれProcessingへ送信し、Processing上で、
x_high * 256 + x_low = 950

つまり、
3 * 256 + 182 = 950

と計算し直して、もとの値950を得ることになります。実は、分解された値は前回同様最大値が255(256段階)であり、大きな値に対して256が幾つ分あり、そしてその余りが幾つかということを計算して送信しています。
同様にyとzについても分解して送信し、Processing上で合成します。
今回は3つの値があり、それぞれを二つに分解して送信するので、合計6回送信します。

Processingの方では、円のXY座標と大きさをそれぞれ調節可能にするプログラムとします。解像度が1024段階あるので、画面サイズは大きめにしておきます。前回同様、マウスで画面をクリックしたら、通信開始するプログラムにします。

Processingのプログラム:
//シリアルライブラリを取り込む
import processing.serial.*;

Serial myPort;
int x,y,z;

void setup(){
  //大きめの画面サイズに設定
  size(1000,700);
  //滑らかな描画にする
  smooth();
  //シリアルポート設定
  myPort=new Serial(this,"/dev/tty.usbserial-A4001Kjl",9600);
  //バッファ領域を空にしておく
  myPort.clear();
  //図形の塗りを200に設定
  fill(200);
}

void draw(){
  //背景を白に設定
  background(255);
  //円の描画
  //XY座標と幅、高さに変数を入れる
  ellipse(x,y,z,z);
}

void serialEvent(Serial p) {
  //バッファ領域に6個のデータがあるとき
  if(myPort.available()>5){
    //分解された値を読み込む変数を用意し
    //各変数に読み込み値を入れる
    int x_high = myPort.read();
    int x_low = myPort.read();
    int y_high = myPort.read();
    int y_low = myPort.read();
    int z_high = myPort.read();
    int z_low = myPort.read();
    
    //読み込んだ値をそれぞれ合成し、
    //1024段階の値としてx,y,zに代入
    x = x_high * 256 + x_low;
    y = y_high * 256 + y_low;
    z = z_high * 256 + z_low;
    
    //合図用のデータを送る
    myPort.write(65);
  } 
}

//マウスが押されたら通信開始
void mousePressed(){
  myPort.write(65);
}

0〜255というのは、1バイト分のデータであり、二進数の
00000000〜11111111

の値ということになります。00000000は0であり、11111111は255です。0か1が8桁あり8ビット(=1バイト)となります。
16ビットなら16桁あり
0000000000000000〜1111111111111111

となり、0〜65535となります。16ビットなので2バイトあります。
今回値を二つに分けて送信した方法というのは、16ビットを
00000000|00000000

というように上位8桁と下位8桁に分けて送信したことと同じです。
もし、256という値なら、
00000001|00000000

となり、上位8桁だけをみれば00000001なので、8ビットの1と等しいことになります。下位8桁は00000000なので、8ビットの0になります。つまり、256の場合は、1(上位)と0(下位)が送信されるということになります。
950を16ビットの二進数であらわすと、
0000001110110110

であり、同様に上位8ビット、下位8ビットで分けると、
00000011|10110110

になり、
上位8ビット00000011は10進数の3であり、下位10110110は182になり、3と182を送信することになります。
ArduinoやProcessingにも二進数/ビット演算の数式や関数があります。
>>」という記号を用いて、
950 >> 8

と書けば、この値は3となります。つまり、950/256=3と同じ結果が得られます。
>>」というのは「右にシフトする」という意味で、「950 >> 8」においては「950を8桁右にシフトする」ということになります。二進数で言えば、
0000001110110110

という16桁を8桁右にずらす(シフト)ので、
0000000000000011

となり、十進数の3になります。
950 >> 1

なら、1桁右にずらすので、
0000000111011011

となります。ずらされて右端からあふれてしまった0や1は消えてしまいます。
そのほか「&」というのもあります。
950 & 255

と書けば、
182となり、950 % 256と同じ結果が得られます。以下のようにそれぞれを上下に重ね合わせて、950を255でマスキングするような感じです(ビットマスク)。
00000011|10110110  950
00000000|11111111  255
00000000|10110110  182

255の0のある桁(上位8桁)の真上の950の桁00000011はすべて消されて0となり、255の1のある桁(下位8桁)の真上の950の桁10110110だけ残ります。255は16桁のうち上位8桁はすべて0が並んでおり、下位8桁はすべて1が並んでいるので、255というマスキングを950に施すということは、950の上位8桁を消して(0にする)、下位8桁だけを取り出すということになります。つまり、
950 >> 8 = 3
950 & 255 = 182

という計算でも、950/256=3、950%256=182と同じ値が得られるということになるので、Arduino上のxの値に関しては、
x = analogRead(0);
byte mask = B11111111;
Serial.write(x >> 8);
Serial.write(x & mask);

と書くことができます。「byte mask = B11111111」というのは、maskというバイト型の変数を用意し、その値が11111111であるということで、xの値の下位8桁だけを取り出すために使っています。Arduinoにおいてバイトの場合は、8桁の二進数の頭の部分に「B」(二進数/バイナリーのフォーマット)を付けます。Processing上では、
(3 << 8) + 182 = 950

というように、逆に「3を左に8桁シフト」してから「182を足す」と「950」になるので、Processingでは、
x = (x_high << 8) + x_low;

と書けば同じ値を得ることができます。

二進数の表記やビット演算はサンプルプログラムやデータシートの中にも出てくることがあります。ビットやバイトを使わなくてもプログラムできますが、覚えておくと便利なときもあり、考え方や計算もよりシンプルになる場合があります。


関連:
Arduino-Processing シリアル通信1」(一つの値を送る/非同期通信)
Arduino-Processing シリアル通信2」(複数の値をバイトで送る/同期通信)
Processing-Arduino シリアル通信4」(ProcessingからArduinoを制御する)
Arduino-Processing シリアル通信5」(複数の値を文字列で送信する)
Arduino-Processing シリアル通信6」(2台のArduinoとProcessingを通信させる)

5/27/2008

Arduino-Processing シリアル通信2


【変更】以下はArduino1.0まで対応したプログラム内容です。
特にシリアル通信においては、Arduino2.0使用の際、バイト送信する場合、
Serial.print(value,BYTE);
のかわりに、
Serial.write(value);
を使用してください。


前回のシリアル通信では、ひとつの値をArduinoからProcessingへ一方的に送り続ける内容でした。今回は、Arduino側から三つの可変抵抗器を使って、三つの異なる値をProcessingへ送りたいと思います。

例えば、Processing上の3DモデルのX座標、Y座標、Z座標の値を送り、それぞれのツマミで三次元的に立体を動かすことができるプログラムになるということです。
Arduinoから3つの値を送るには、
Serial.write(x);
Serial.write(y);
Serial.write(z);

となります。
3つの値がXYZの順番で送られる場合、XYZ XYZ XYZ・・・と繰り返されますが、Processing側の読み取りを開始するタイミングがずれると、最初の幾つかの値をスキップしてしまい、YZX YZX YZX・・・、あるいはZXY ZXY ZXY・・・となってしまいます。このように、順番がずれないようにするためには通信上の工夫が必要となります。今回の方法では、3つの値を一方的に送るのではなく、受取確認をしながらお互いに通信します。以下にプログラムを書きます。

Arduino側のプログラム:
//x,y,zの3つの変数を用意し、初期値を0とする
int x=0;
int y=0;
int z=0;

void setup(){
  //シリアル通信開始
  Serial.begin(9600);
}

void loop(){
  //アナログ入力ピン0,1,2を
  //それぞれx,y,zに対応させる
  //値を4で割って最大値255にする
  x=analogRead(0)/4;
  y=analogRead(1)/4;
  z=analogRead(2)/4;

  //Processingから合図のデータが
  //一つ送られてきたらという条件
  if(Serial.available()>0){
    //x,y,zの順番で値を送る
    Serial.write(x);
    Serial.write(y);
    Serial.write(z);
    //先ほどのProcessingからの
    //合図のデータを読み込む
    Serial.read();
  }
}

Arduinoプログラム上のSerial.available()は、外部(この場合Processing)から1バイト分のデータが何個送られてきているかを数えてくれます。送られてくるデータは、Arduinoであれ、Processingであれ、それぞれのメモリ上(バッファ領域)に一旦貯められます。その後、Serial.read()によって、貯められているデータから順番にひとつずつ読み込む処理をします。もし、読み込む処理をしなければ、送られたデータは次々とバッファ領域に溜まり続けます(設定した限界をこえるとそれ以上貯めることはできなくなります)。if(Serial.available()>0){...}という条件は、データが1個以上貯まったら(0個より多い場合)、{...}内の処理をするという意味です。

このプログラムの場合の手順は以下のようになります。

(1)まず、Processingから合図用のデータが送られてくる。
(2)合図用のデータが、一旦Arduinoのバッファ領域に貯められる。
(3)Serial.available()で、現在何個データが貯まっているか数える。
(4)合図用のデータが1個以上あれば、(5)以下を実行する。
(5)Serial.write(x)で、新たなxの値を送信する。
(6)Serial.write(y)で、新たなyの値を送信する。
(7)Serial.write(z)で、新たなzの値を送信する。
(8)Serial.read()で、バッファ領域内の合図用のデータを読み込む。
(9)結果、バッファ領域が空になる(データの個数が0個になる)。
(10)Processingからの合図用のデータを待つ。

その後は(1)に戻り、同様の処理を繰り返します。要するに、Processingからの合図のデータが毎回1個送られて、それを確認後、Arduinoはx、y、zの値を送り返すという手順を踏みます。

次にProcessing側のプログラムを書きます。Arduinoのプログラムで書いたように、Processingからは、合図用のデータを1個送る必要があります。そうすれば、Arduinoから3個のデータが送られてくるので、Processingのバッファ領域にデータが3個貯まったときに、読み込む処理をさせればいいということになります。今回はx、y、zの3個の値なので3Dの図形を動かすプログラムにします。

Processing側のプログラム:
//シリアルライブラリを取り込む
import processing.serial.*;
//シリアルのオブジェクトmyPortを用意
Serial myPort;

//x,y,zの3個の変数を用意
int x=0;
int y=0;
int z=0;

void setup(){
  //3D用の画面サイズとして設定
  size(255,255,P3D);
  //シリアルポートの設定(「A4001Kjl」は基盤により異なる)
  //Windowsの場合は、"COM5"などとなる(前回ブログを参照)
  myPort=new Serial(this,"/dev/tty.usbserial-A4001Kjl",9600);
  //3D図形の塗りは無し(ワイヤーフレーム描画)
  noFill();
}

void draw(){
  //背景色を白に設定
  background(255);
  //3Dの位置座標にx,y,z入れる
  translate(x,y,z);
  //一辺50のボックス(立方体)を描画
  box(50);
}

//シリアル通信処理
void serialEvent(Serial p){
  //Arduinoから送られてきたデータが
  //3個(2より多い)の場合
  if(myPort.available()>2){
    //x,y,zの順番でデータを読み込む
    x=myPort.read();
    y=myPort.read();
    z=myPort.read();
    //読み込み後、合図データ送信
    myPort.write(65);
  }
}

//マウスが押されたら通信開始とする
void mousePressed(){
  //念のためバッファ領域を空にする
  myPort.clear();
  //とりあえず65という合図用データを送る
  myPort.write(65);
}

以上が、Processing側のプログラムです。3Dの場合は、size()の括弧内に画面幅、高さ以外に「P3D」を書き足します。ワイヤーフレームで描画した方が、今回の場合分かりやすいと思うので、noFill()をつかって塗り面を無しにしました。
void draw(){...}内のtranslate()は、その後に描画される3D図形の座標値をいれます。translate()に含まれる値が変化することで、3D図形は移動します。box()は、3Dの直方体を描画します。box(50,100,80)というように3つ値を入れれば、幅、高さ、奥行きをそれぞれ定義できます。box(50)の場合は、各辺が50の立方体になります。
シリアル通信の部分は、まずmousePressed()でマウスを押したときに、myPort.clear()でProcessingのバッファ領域内に貯まっているデータをとりあえず空にします(初期化)。そして通信を開始するきっかけとなる合図のデータをmyPort.write(65)で送ります。65という値を送っていますが、0〜255の値であれば何でも構いません。Arduino側は送られてくるデータの個数を数えるのであって、データの中身の値については、何でもいいことになります。つまり、合図用に何らかのデータを1個送ればいいということです。一旦合図用のデータがProcessingから送られれば、次にArduinoがそのデータを受取り、そして3個のデータを送り返してきます。void serialEvent(Serial p){...}内のif(myPort.available()>2){...}内では、Arduinoから送られて来たデータがProcessingのバッファ領域に3個貯まったら(2個より多くなったら)myPort.read()で3回読み込み、x,y,zの3個の変数にそれぞれ値を入れていき、それらのデータは、box()の3D座標になるtranslate()に代入され、box()が動きます。



もう一度、手順をはじめから書くと、

(1)Arduinoの電源がオンになり、Arduinoのプログラムが開始。
(2)Processingのプログラムを立ち上げる。
(3)Arduinoは、Processingからの合図用データを待つ。
(4)Processing側でマウスを押す(通信開始)。
(5)Processingのバッファ領域を一旦空にする(初期化)。
(6)Processingから合図用データが1個送られる。
 *以下、Arduino上での処理
(7)Arduinoのバッファ領域に合図用データが一旦貯められる。
(8)Arduinoのバッファ領域内のデータの個数を数える。
(9)データの個数が1個以上のとき、以下の処理を実行。
(10)Arduinoから、新たなxの値を送信する。
(11)Arduinoから、新たなyの値を送信する。
(12)Arduinoから、新たなzの値を送信する。
(13)バッファ領域内の合図用のデータを読み込む。
(14)読み込んだ結果、Arduinoのバッファ領域が空になる。
(15)次の合図用データがバッファ領域に貯まるまで待機。
 *以下、Processing上での処理
(16)Processingのバッファ領域にデータが貯められる。
(17)Processingのバッファ領域内のデータの個数を数える。
(18)データの個数が3個になったら、以下の処理を実行。
(19)xの値として1番目のデータを読み込む。
(20)yの値として2番目のデータを読み込む。
(21)zの値として3番目のデータを読み込む。
(22)その結果、Processingのバッファ領域が空になる。
(23)Processingから合図用データが1個送られる。
(24)次の3個のデータがバッファ領域に貯まるまで待機。
 *その後は(7)へ戻り処理を繰り返します。

Serial.available()
myPort.available()を使うことで、バッファ領域内に貯められているデータの個数を数え、その個数をもとにデータを送受信するタイミングを制御することができます。これ以外にも、送られる複数のデータの先頭部分や最後の部分に「.」(ピリオド)などの特定のデータを付け加えて送ることで、読み込み開始地点や読み込み終了地点を知らせる方法もあります。
シリアル通信を使うことで、コンピュータの内側と外側の世界をつなぐことができます。今後も様々な表現に応じて「シリアル通信」の技術は、繰り返し登場してきます。今回一気に理解できなくても、徐々に使いこなしていくことで、身についていくと思います。


関連:
Arduino-Processing シリアル通信1」(一つの値を送る/非同期通信)
Arduino-Processing シリアル通信3」(大きな値を複数送る)
Processing-Arduino シリアル通信4」(ProcessingからArduinoを制御する)
Arduino-Processing シリアル通信5」(複数の値を文字列で送信する)
Arduino-Processing シリアル通信6」 (2台のArduinoとProcessingを通信させる)

5/26/2008

Arduino-Processing シリアル通信1

【変更】以下はArduino1.0までのシリアル通信に対応したプログラム内容です。
Arduino2.0使用の際は、バイト送信する場合は、
Serial.print(value,BYTE);
のかわりに、
Serial.write(value);
を使用してください。

今回はシリアル通信を用いて、Arduino基盤に接続した入力装置(可変抵抗器)で、Processingで描かれた図形を動かしてみます。
Arduino側のシリアル通信は、前回のモニタリングで使用したときのような感じです。Processingにおいても、シリアル通信の設定が必要になります。

通信の流れとして:
Arduino基盤に接続した入力装置からの値をanalogRead()で読み取る。
その値をSerial.print()でProcessing側に送る。
Processing側で、その値をSerial.read()で読み取る。
Processing上の図形の座標値に入れる。
という感じです。

まずは、Arduino側からプログラムしていきます。今回入力装置となる可変抵抗器を基盤に接続します。


Arduino側のプログラム:
int val; //読み取り値の変数を用意

void setup(){
  Serial.begin(9600);
}
void loop(){
  //アナログ入力0番ピンの値を読み取り(0~1023)
  //4で割った値を変数valに入れる(0~255)
  val=analogRead(0)/4;
  //シリアルでvalを送信
  Serial.write(val);
  //1秒間に20回ループ(0.05sec)とする
  delay(50);
}

上記プログラムの説明:
Processingと通信する場合、Serial.write()で一度に送ることができる値は、0〜255までの数値になるので、analogRead()で読み取った値(0〜1023)を4で割って、最大値が255になるようにスケーリングした値をvalに代入し送信します。delay(50)程度にし、一秒間に20回送信することにします。Arduino側から一方的にデータを送り続けるので、あまりにも多くのデータを送りすぎると、Processing側での受取処理が追いつかなくなり、反応に時差がでることがあります。

Processingのプログラムについて:
Processing側のプログラムでは、シリアル通信(Serial)はライブラリに含まれており、必要に応じてその機能を取り込む(import)必要があります。
Processingのsketchを開いたら、メニューバーのSketch>Import Library>serialを選択します。そうすると、プログラムを書く欄に自動的に「import processing.serial.*;」という一文が追加されます。これによって、シリアル機能が導入されます。そして以下のようにmyPort(名前は任意)というシリアルのためのインスタンスを用意し、シリアルポート、通信速度を設定します。Processingのシリアル通信についての説明は、Processing画面のメニューバーでHelp>Referenceへ行き、そのページ内の上部のLibrariesをクリックし、さらにページ内のCore Librariesの欄にSerialという項目があるので、そこをクリックします(Processingのサイトにも同じSerialのページがあります)。

Processing側のプログラム:
//シリアルライブラリを取り入れる
import processing.serial.*;
//myPort(任意名)というインスタンスを用意
Serial myPort;

int x; //図形のX座標の変数を用意

void setup(){
  //画面サイズ
  size(256,256);
  //シリアルポートの設定
  myPort=new Serial(this,"/dev/tty.usbserial-A4001Kjl",9600);
}

void draw(){
  //背景色を白に設定
  background(255);
  //XY座標を(x,100)に設定し、
  //幅50、高さ50の円を描画
  ellipse(x,100,50,50);
}

void serialEvent(Serial p){
  //変数xにシリアル通信で読み込んだ値を代入
  x=myPort.read();
}

上記プログラムの説明:
初期設定のsetup()内の「myPort=new Serial(this,"/dev/tty.usbserial-A4001Kjl",9600);」の設定において、MacOSXの場合は「/dev/tty.usbserial-********」の箇所の********の部分は使用しているArduino基盤によって異なります。Arduino画面のメニューバーのTools>Serial Portのなかから使用している基盤のシリアルポートと同じものを書いて、両端を「"」マークで括ってください。Windowsの場合は、「COM*」(*は番号)のようにポートが表示されるので、同様に「COM*」を「"」マークで括ってください。
通信速度の「9600」は、通常この設定で構いません。

シリアル通信が行われるたびに、serialEvent()内のx=myPort.read()によってArduino基盤から送られて来た値を読み込み、変数xに代入され、最終的にellipse()のX座標に代入されます。Arduinoから送られてくる値は0〜255であるため、Processing上のellipse()のX座標の移動範囲も0〜255(256段階)となります。それに合わせて、Processingの画面幅を256に設定しました。つまり、ellipse()を1ピクセルずつ左右に動かすことができます。


関連:
Arduino-Processing シリアル通信2」(複数の値をバイトで送る/同期通信)
Arduino-Processing シリアル通信3」(大きな値を複数送る)
Processing-Arduino シリアル通信4」(ProcessingからArduinoを制御する)
Arduino-Processing シリアル通信5」(複数の値を文字列で送信する)
Arduino-Processing シリアル通信6」(2台のArduinoとProcessingを通信させる)

5/14/2008

Processing 文字と画像

文字の表示についてはPFontというクラスを用います。文字を使う場合、Processingを開いたあと、メニューバーにあるTools>Create Font...をクリックします。そうするとCreate Fontという別のウィンドウが現れ、使用したいフォントとそのサイズ選択します。視覚的に滑らかなフォントを使いたいのであればSmoothにチェックを入れます。基本的な記号、数字、アルファベットを使うのであればAll Charactersのチェックを外したままで大丈夫です。
日本語などを使う場合はチェックを入れて使いますがデータはその分大きくなります。All Charactersにチェックを入れると、全てのフォントデータを取り込むためにやや時間がかかるときがあります。日本語の大きなサイズのフォントを取り込む際にエラーが出る場合は、上記の方法のかわりにcreateFont()をつかった方法を用いてみてください(サンプルは以下にあります)。
ここでは、Monacoというフォント、サイズは12、Smoothにチェックを入れることにします。Filename欄にはMonaco-12が表示され、その拡張子.vlwがあることが確認できます。OKボタンを押してウィンドウを閉じると指定したフォントの準備が整います。現在開いているsketchファイル(例えば「sketch_08513a」という名前)を一旦保存(File>save)すると、Macintoshであれば、/User/username/Documents/Processing/sketch_08513a/data内にフォントのファイル(例えばMonaco-12.vlw) が入ってます。WindowsならC:¥Documents and Setting¥username¥My Documents¥Processing¥sketch_08513a¥data内に入っています。文字を使用する場合、必要なフォントがsketchフォルダ内のdataフォルダに入っていないとエラーがでます。同様に、jpegなどの画像や音源などを使用する際も、dataフォルダに入れておく必要があります。
以下では、クリックによって文字が切り替わるプログラムをします。一文字ごとの変数にはchar、そして単語などの連なった文字(文字列)にはString型の変数を用います。尚、文字列は「"文字"」のようにダブルクオーテーションマークで文字列の両端を括ります。一文字だけのcharなら「'A'」のようにシングルクオーテーションマークで括ります。

PFont font; //フォントの使用
String tx; //文字列の変数を用意

void setup(){
size(400,200);
//指定フォントのロード
font = loadFont("Monaco-12.vlw");
//フォントを使用開始、サイズ:12
textFont(font,12);
//初期の表示文字を"CLICK"に設定
tx="CLICK";
}

void draw(){
text(tx,200,100);
}

void mousePressed(){
tx="HELLO";
}
void mouseReleased(){
tx="CLICK";
}

日本語フォントを使う場合は、createFont()を使用します。その際「Tools>Create Font...」でフォントを選択する必要はありません。

PFont myFont;

void setup() {
size(400, 200);
myFont = createFont("Osaka", 32);
textFont(myFont);
}

void draw(){
background(0);
text("建築発明工作ゼミ", 10, height/2);
}

画像の取り込みについては、PImageというクラスを使います。Processingでは、gif、jpeg、tga、pngの画像フォーマットに対応しています。予め用意した画像をdataフォルダ(前述)に入れておく必要があります(dataフォルダがない場合は、sketchフォルダ内に「data」という名前をつけてフォルダを作成しておきます)。
以下のサンプルでは画像を二種類(start.jpgとstop.jpg)用意し、最初に画像Aが表示されており、マウスボタンを押したら画像Bに切り替わり、放したら画像Aに戻るというプログラムをします。切り替えには前回用いたflagというboolean型の変数を用いて、現在画像Aが表示されているか画像Bが表示されているか記憶させておきます。

PImage imgA;//画像A用にimgAを用意
PImage imgB;//画像B用にimgBを用意
boolean flag;//切り替えのフラグを用意

void setup(){
size(400,200);
//imgAには"start.jpg"という画像をロード
imgA=loadImage("start.jpg");
//imgBには"stop.jpg"という画像をロード
imgB=loadImage("stop.jpg");
//最初にimgAを100,50の位置に表示しておく
image(imgA,100,50);
//フラグはfalse(imgA表示)に設定
flag=false;
}

void draw(){
background(0);
if(flag==false){//falseの場合imgA表示
image(imgA,100,50);
}else{//それ以外(true)の場合imgB表示
image(imgB,100,50);
}
}
void mousePressed(){
//押すとtrue(imgB表示)
flag=true;
}
void mouseReleased(){
//放すとfalse(imgA表示)
flag=false;
}

5/13/2008

Processing マウス入力4

今までのボタン操作において、「押したらオン、放したらオフ」になるというプログラムを書いてきましたが、「押したらオン、もう一回押したらオフ」になるトグルスイッチというものもあります。トグルスイッチのプログラムには、変数を用意しておいて、オンやオフになっている状態を記憶させておく必要があります。そのようなものをフラグといいます。サンプルとして、正方形をクリックし、オンなら緑、オフなら赤というプログラムを書きます。まず、オン/オフ用としてflag(変数名は任意)という変数を用意しておきます。いままで使ってきたint型の変数を使うことも可能ですが、オン/オフの二つの状態しかないので、より単純なboolean型の変数を用います。変数の値としては、truefalseだけになります。つまり、flagがtrueのときは、スイッチがオンになっている状態とし、flagがfalseのときはオフになっている状態であるという設定にします。

boolean flag; //boolean型の変数flagを用意

void setup(){
//画面サイズ
size(400,200);
//スイッチの初期値はfalse(オフ)に設定
flag=false;
//図形の初期色を赤(オフ)に設定
fill(255,0,0);
}

void draw(){
background(100);
rect(200,100,50,50);
}

void mousePressed(){
//マウス座標が正方形の範囲内の時
if(mouseX>=200 && mouseX<=250 && mouseY>=100 && mouseY<=150){
if(flag==false){ //クリックする前がオフの時
fill(0,255,0);
flag=true; //クリックしたらオンの状態に変更する
}else{ //クリックする前がオンの時
fill(255,0,0);
flag=false; //クリックしたらオフの状態に変更する
}
}
}

if()内の条件式では、flag==falseというように「==」イコールを二つ用いて下さい。ただし、その後の「クリックしたらオンの状態に変更する」箇所では、flag=trueというように「=」イコールが一つになります。
フラグを用いることで、もう既に済んでしまっている出来事の状態を記憶させておくことができます。それに応じて、条件式を設定すれば、より複雑な状況をプログラムしていくことが可能になります。例えば3回押したらオンになるスイッチもint型変数をカウントとして使えば可能となります。

boolean flag; //オンかオフを判定するフラグ変数
int count; //クリック回数をカウントする変数

void setup(){
size(400,200);
flag=false; //オフに設定しておく
fill(255,0,0);//正方形の色(オフ)を赤にしておく
count=0; //0回目に設定しておく
}

void draw(){
background(100);
rect(200,100,50,50);
}

void mousePressed(){
if(mouseX>=200 && mouseX<=250 && mouseY>=100 && mouseY<=150){
count++; //正方形内をクリックするごとに回数が+1される
if(count>=3){ //クリック回数が3以上なら
if(flag==false){ //スイッチがオフなら
fill(0,255,0); //緑(オン)に変更する
flag=true; //スイッチをオンに変更する
}else{ //スイッチがオンなら
fill(255,0,0); //オフ(赤)に変更する
flag=false; //スイッチをオフに変更する
}
count=0; //カウントを0に戻す
}
}
}

クリックされる前の状態がオンのときはオフへ、オフのときはオンへ切り替えるboolean型の変数flagと同時に、クリック回数を記憶しておくint型変数countがあります。それぞれ切り替わる際には、flagをfalseからtrueへ、あるいはtrueからfalseへ、そして回数も3から0へと変更するような後処理についても忘れずプログラムしておく必要があります。

Processing マウス入力3

クリックやドラッグ以外に、マウス(カーソル)を図形の上に重ねた時に、色や形が変わるロールオーバーという機能があります。Processingでは、ロールオーバーの関数は用意されていないので、自前でプログラムする必要があります。原理的には、マウス(カーソル)の座標mouseXmouseYが、その図形上にあるかどうかをif文を用いて判定するプログラムになります。
例として、画面上の座標(200,100)に配置された一辺50pixelの正方形(赤)にマウス(カーソル)が重なった ら黄色に変化し、マウスボタンを押したら緑に、放したらもとの赤に戻るというプログラムを書きます。

void setup(){
size(400,200);//画面サイズ
fill(255,0,0);//正方形の初期色を赤に設定
}
void draw(){
background(100);
rect(200,100,50,50);

//マウスが正方形内にある場合
if(mouseX>=200 && mouseX<=250 && mouseY>=100 && mouseY<=150){
if(mousePressed){//クリックした場合:緑
fill(0,255,0);
}else{//それ以外(クリックしない場合):黄
fill(255,255,0);
}
}else{ //それ以外(マウスが正方形外にある場合):赤
fill(255,0,0);
}
}

マウスの座標であるmouseXmouseYに対して、if文によって正方形内にマウスがあるかないかを判定しています。X座標に関しては正方形の左端(200)から右端(250)の座標、Y座標に関しては上端(100)から下端(150)の座標の範囲にマウスがある場合ということを、複数の条件としてif文に設定しています。&&は、複数の条件「Aという条件かつBという条件」という意味です。マウスの座標が200以上かつ250以下なので、200<=mouseX<=250と書きたいところですが、mouseX>=200 && mouseX<=250というように分けて書かないとエラーがでます。ちなみに「Aという条件またはBという条件」の場合は、||を用います。

このプログラムでは条件式が少し複雑ですが、まずマウスが正方形内にあるかないかという条件を設定します。もし正方形外ならそのままfill(255,0,0)で赤、もし正方形内であれば、ロールオーバーかクリックかという二つの条件に分かれて、mousePressedされれば、fill(0,255,0)で緑、そうでなければ、ロールオーバーのままfill(255,255,0)で黄色になるという仕組みです。
if(mousePressed){};というようにmousePressedは、if文の()内に入れて使うことはできますが、mouseReleasedはmouseReleased()として使うのであって、if文の()内に直接入れることはできません。

Processing マウス入力2



mouseXmouseYという関数を使えば、動かしているマウスのXY 座標を直接用いることができます。
矩形を描くrect()のXY座標値にmouseXmouseYを入れれば、マウス(カーソル)の動きに合わせて図形が動きます。例として、正方形(50pixel角)を動かすプログラムを以下に書いてみます。さらに、mousePressed()mouseReleased()を加えて、マウスボタンを押したら緑、放したら赤に戻る正方形の色も変化する内容にします。

int a; //正方形の1辺の長さの変数を用意する

void setup(){
//画面サイズ400×200に設定
size(400,200);
//正方形の一辺aを50に設定
a=50;
//正方形の位置基準を図形中央に設定
rectMode(CENTER);
//最初の塗り色を赤に設定
fill(255,0,0);
}

void draw(){
//背景色をとりあえず100に設定
background(100);
//正方形のXY座標にmouseX,mouseYを設定
rect(mouseX,mouseY,a,a);
}

void mousePressed(){
//押されたら塗り色を緑へ変える
fill(0,255,0);
}

void mouseReleased(){
//放したら塗り色を赤へ戻す
fill(255,0,0);
}

この場合、正方形は常にマウス(カーソル)に合わせて動きます。
さらに、正方形をクリックしてドラッグできるようなプログラムにするなら、正方形のXY座標用の変数も用意して、以下のようになります。

int a; //正方形の1辺の長さの変数を用意する
int x; //正方形のX座標の変数を用意する
int y; //正方形のY座標の変数を用意する

void setup(){
//画面サイズ400×200に設定
size(400,200);
//正方形の一辺aを50に設定
a=50;
//正方形の位置基準を図形中央に設定
rectMode(CENTER);
//最初の塗り色を赤に設定
fill(255,0,0);
//正方形の初期位置を画面中央にしておく
x=width/2;
y=height/2;
}

void draw(){
//背景色をとりあえず100に設定
background(100);
//描画される正方形に各変数を代入しておく
rect(x,y,a,a);
}

void mousePressed(){
//押されたら塗り色を緑へ変える
fill(0,255,0);
}

void mouseDragged(){
//ドラッグ中はrect()に代入されたXY座標用の
//変数x,yにマウス座標のmouseXとmouseYが
//対応するようにしておく
x=mouseX;
y=mouseY;
}

void mouseReleased(){
//放したら塗り色を赤へ戻す
fill(255,0,0);
}

setup()内にあるwidthは画面サイズ幅、heightは画面サイズ高さです。一度画面サイズをsize(400,200);と設定してしまえば、その後画面サイズである400や200という数値はwidthheightを使って代入することができます。
以上のプログラムでも問題ないかもしれないですが、やや不自然な挙動がひとつあります。正方形をクリックしてドラッグした時に正方形が少し瞬間移動してしまう点です(正方形の中心以外の箇所をクリックしてドラッグするとその不自然な挙動ははっきりします)。正方形の中心以外の箇所をクリックしたままドラッグできるようにするためには、クリックした地点の座標と正方形の中心座標との差分を計算に入れなければなりません。そのため、その差分の変数をとりあえずdx,dyとして用意しておき、以下のようになります。

int a; //正方形の1辺の長さの変数を用意する
int x; //正方形のX座標の変数を用意する
int y; //正方形のY座標の変数を用意する
int dx; //差分用のX座標変数
int dy; //差分用のY座標変数

void setup(){
//画面サイズ400×300に設定
size(400,300);
//正方形の一辺aを50に設定
a=50;
//正方形の位置基準を図形中央に設定
rectMode(CENTER);
//最初の塗り色を赤に設定
fill(255,0,0);
//正方形の初期位置を画面中央にしておく
x=width/2;
y=height/2;
//初期設定ではとりあえず差分を0にしておく
//(これは書かなくても大丈夫ですが、
// 一応書いておきます)
dx=0;
dy=0;
}

void draw(){
//背景色をとりあえず100に設定
background(100);
//描画される正方形に各変数を代入しておく
rect(x,y,a,a);
}

void mousePressed(){
//押されたら塗り色を緑へ変える
fill(0,255,0);
//クリックされた瞬間に正方形の座標と
//マウス座標の差分を求めておく
dx=x-mouseX;
dy=y-mouseY;
}

void mouseDragged(){
//ドラッグ中はrect()に代入されたXY座標用の
//変数x,yにマウス座標のmouseXとmouseYが
//対応するようにしておき、
//mousePressed()の中で求めた差分dx,dyを
//付け足す
x=mouseX+dx;
y=mouseY+dy;
}

void mouseReleased(){
//放したら塗り色を赤へ戻す
fill(255,0,0);
}
このプログラムでは、クリックした時に、正方形の位置座標であるx,yとマウスのmouseX,mouseYのズレを予め用意した変数dx,dyへ記憶させておき、最終的に正方形の座標にその差分であるdx,dyを付け足してズレを解消する微調整を行っています。インタラクティブな内容になるほど、ユーザ側への配慮が必要となってくるので、このようにプログラムもやや複雑で細かくなってきますが、逆に表現の幅を広げる工夫にもつながると思います。

マルチパッドフィルム Multi pad Slick Film #601AFAG for MacBook Late 2008 Series / MPSF601AFAG
マイクロソリューション Micro Solution Inc.
売り上げランキング: 3020

5/12/2008

Processing マウス入力1

前回までは、ループ処理を使って図形を動かしていました。ただ、このままでは一方的に動画を表示していることに過ぎないので、録画されたコンテンツを再生していることと、何ら違いがありません。今回は、ユーザ側からのマウス入力のプログラムを加えることで、一方的に表示されている内容を、相互作用的な内容へと変化させます。
例として、前回の入れ子で動く正方形のプログラム(「Processingの描画サンプル」)にマウス入力に反応するプログラムを書き足します。ここでは、mousePressed()というマウスのボタン(左クリック)が押された時に発動するプログラムを付け足してみます。
そのためには、

void mousePressed(){
//ここにボタンが押されたときに発動するプログラムを書く
}

というプログラムを前回のプログラムの最後に付け足します。

例えば、クリックしたら二つの正方形の位置がそれぞれ0に戻るというプログラムであれば、

int a=10;
int b=a*12;
int c=b*3;
int ax; //小さい正方形(黒)のX座標の変数
int bx; //大きい正方形(赤)のX座標の変数

void setup(){
size(360,120);
noStroke();
}

void draw(){
background(0);
fill(250,20,20);
rect(bx,height/2-b/2,b,b);
bx=bx+1;
if(bx>c){
bx=-b;
}
fill(0);
rect(ax+bx,height/2-a/2,a,a);
ax++;
if(ax>b){
ax=-a;
}
}

void mousePressed(){
ax=0;
bx=0;
}

となります(axは前回用いたプログラムの小さな黒い正方形のx座標のための変数、bxは大きな赤い正方形のx座標のための変数)。draw()の中の上から4行目の「bx=bx+1;」を「bx+=1;」あるいは「bx++;」と書くこともできます。同様にさらに6行下の「ax++;」は「ax=ax+1;」あるいは「ax+=1;」と同じことです。以前の「Processingの基本操作 その3」でも触れましたが、「++」はインクリメントと呼ばれ、変数に+1を自己代入して+1ずつ増加させていくことです。
mousePressed()のほかにmouseReleased()mouseDragged()mouseMoved()があります。
mousePressed()は、マウスボタンを押した瞬間に発動します。
mouseRelease()は、マウスボタンを押して放される瞬間に発動します。
mouseDragged()は、クリックし、そのままドラッグしている間発動し続けます。
mouseMoved()は、マウスが動いている間(クリックしなくても)発動し続けます。
それぞれの挙動の違いを各自確かめてみてください。

5/03/2008

Processingの描画サンプル

以下は、「One inch /One foot / One yard」のテーマをもとにProcessingで作成したプログラムの例です。



このプログラムでは、画面の横幅を1yard、大きな正方形(赤)を1footの正方形、小さな正方形(黒)を1inchの正方形とみなしています。入れ子になった正方形が左から右へ動くというサンプルです。
「One inch /One foot / One yard」は、単位やスケールに関わるテーマです。このようなシンプルな表現で構わないので、次回の授業までに作成してきてください。
ちなみに、このプログラムでは、noStroke()という関数を使っています。noStroke()は、rect()などの「図形の外形線を描かない」というコマンドです。それ以外は、このブログで説明してある方法を用いてプログラムできると思います。

以下がプログラムソースです。

int a=10;
int b=a*12;
int c=b*3;
int ax;
int bx;

void setup(){
size(360,120);
noStroke();
}
void draw(){
background(0);
fill(250,20,20);
rect(bx,height/2-b/2,b,b);
bx=bx+1;
if(bx>c){
bx=-b;
}
fill(0);
rect(ax+bx,height/2-a/2,a,a);
ax++;
if(ax>b){
ax=-a;
}
}

4/20/2008

Processingの基本操作 その3

前回までのプログラムは静止画を描くものでした。
つぎは、動画のプログラムに入ります。
例えば、ellipse()を用いて描いた円が画面の中で右方向に動いていくようなものをプログラムします。
円が画面左から右へ移動するには、円のX座標が常に変化していきます。そのために、前回使った変数を設定しておきます。

まず、X座標のために整数型の変数xを用意する。
円の半径のために整数型の変数rを用意する。
X座標をとりあえず20に設定。
円の半径を25に設定。
画面のサイズは横長にして幅400、高さ200に設定。
背景色をとりあえず255(白)に設定。
Y座標値は100に固定し、変数xとrを用いて円を描く。
そうすると、以下のようになります。

int x;
int r;
x=20;
r=25;
size(400,200);
background(255);
ellipse(x,100,r,r);

ただし、このままでは前回同様静止画の円が描画されます。
動画を描画させるには、setup()draw()という関数を用いて、以下のようなフォーマットを用意します。

void setup(){
//ここに初期設定を記入
}

void draw(){
//ここに描画内容を記入
}

初期設定には、円の最初の位置や大きさなどを書いておきます。
描画処理を行わせるdraw()には、アニメのセル画のように次から次へと何枚もの絵を描き続ける内容を書きます。
それでは、setup()とdraw()のフォーマットに、はじめに書いた円のプログラムを書き足します。

int x;
int r;

void setup(){
size(400,200);
x=20;
r=25;
}

void draw(){
background(255);
ellipse(x,100,r,r);
x=x+1;
}

draw(){...}内に書かれた内容は、以下のように繰り返し処理されます。

最初にbackground(255);を処理し、
つぎにellipse(x,100,r,r);を処理し、
その次にx=x+1;を処理し、
その次は、最初に戻ってbackground(255);を処理し、
つぎにellipse(x,100,r,r);を処理し、
その次にx=x+1;を処理し、
再びbackground(255);を処理し、
・・・

というように、以後も同様の順番で処理を繰り返します。これをループといいます。
具体的には、まず背景色(白)を画面に塗って、その上に円を描き、次回の準備として円のX座標値を+1しておき、再び背景色(白)で画面を真っ白にしてから、また円を描き、さらにX座標値を+1するという感じです。この繰り返しのループ処理と最後のx=x+1;というのが、円を右方向へ動かすためのプログラムになります。左辺のxは、次回のxの値であり、右辺のxは今回の計算で用いられた値を意味します。つまり、今回のX座標に+1した値を次回のX座標に自己代入するということになります。xには+1が延々と足されていくので、xは処理を繰り返すごとに増大していきます。よって、円のX座標は、初期設定のx=20という値から21、22、23・・・と変化していきます。このプログラムでは、円が画面の右端からはみ出て行っても動き続けるので、画面の右端まで行ったら、左端に戻るような条件を書き足します。

int x;
int r;

void setup(){
size(400,200);
x=20;
r=25;
}

void draw(){
background(255);
ellipse(x,100,r,r);
x=x+1;
if(x>400){
x=0;
}
}

付け加えたif(x>400){x=0;}という部分は、「もしxが増大して400(画面の右端の座標)を超えたら、xの値を0に戻す」という意味です。このように、if文をつかうことで、さまざまな条件分岐が可能になります。
x=x+1;をx++;として書くこともできます。
また、x=x+2;とすれば、スピードは倍になります。
Y座標に関しても変数を用意し、同様にyの値に処理を付け加えれば、円を斜めに動かすことができます。

Processingの基本操作 その2

長方形を描く場合は、rect()という関数を前回用いました。
円や楕円の場合は、ellipse()という関数を用います。
rect()と同様に、ellipse()も、カッコの中に入る数値は4つ必要であり、
ellipse(X座標,Y座標,幅,高さ)によって楕円の位置や形が定義されます。
正円を描くのであれば、幅と高さの数値を同じにすればいいので、

ellipse(20,30,50,50);

のように3番目と4番目の数値が等しくなります。
同様にrect()の場合も、

rect(20,30,50,50);

なら、一辺50ピクセルの正方形となります。

rect(20,30,50,50);
ellipse(20,30,50,50);

と書けば、一辺50ピクセルの正方形の上に直径50ピクセルの円が重なって描かれます。
このとき二つの位置のXY座標は(20,30)ですが、ぴったり重なるように配置されません。
それは、円の位置を決める基準点は円の中心座標になるのですが、正方形(長方形)の場合は、左上の角が基準点になっているからです。ぴったり重なるように配置するには、どちらかの図形をずらさなければなりません。
例えば、

rect(20,30,50,50);
ellipse(20+25,30+25,50,50);

というように、円を25ピクセルずらした分だけ20+25というように数式を入れることもできます。
しかし、もう一つの方法は、ellipseMode(CORNER)という関数があり、円の基準点自体をrect()と同様に左上の角に変更することです。よって、

rect(20,30,50,50); //基準点が左上角の正方形を描画
ellipseMode(CORNER); //円の基準点を左上角に設定
ellipse(20,30,50,50); //円の描画

と書くこともできます。ellipseMode(CORNER)をellipse()の手前に書いておくことで、それ以降は左上角が基準点になります。
尚、各行のダブルスラッシュ「//」以降に書かれた内容はコメントと呼ばれ、プログラムには影響しない記述内容になります。このままRunさせてもエラーはでません。プログラムにおいてメモ書きのような説明や注釈を書き添えておきたいときは、「//」を用います。


つぎに、正方形の一辺と円の直径が同じということは、もともとのrect()の幅と高さ、そしてellipse()の幅と高さの四つの値が同じであるため、同じ値の部分を変数を用いて以下のように書くことも可能です。

int a=50;
rect(20,30,a,a);
ellipseMode(CORNER);
ellipse(20,30,a,a);

int は、aという値が整数であるというタイプを指定する記号です(もし小数点が含まれる数値ならfloatを用います)。
int a=50;と書いてaに50という整数値を代入しておきます。あとは、rect()の幅と高さ、そしてellipse()の幅と高さの四つの値にaを書いておけば、すべてが50となります。冒頭のint a=50;をint a=100;と書き直せば、正方形も円も一気に2倍の大きさに変えることができます。つまり、後々変化させたい値の部分に変数を用いれば、いちいち個別に書き直す手間が省けます。
同様に、正方形と円のXY座標値もそれぞれ同じことから、

int a=50;
int x=20;
int y=30;
rect(x,y,a,a);
ellipseMode(CORNER);
ellipse(x,y,a,a);

と書けば、xとyに異なる数値を代入することで、正方形と円の位置を一気に変えることができます。
特にたくさんの図形を用いて、それらの位置や大きさなどが連関している場合、このような変数を用いれば個別に数値を書かずに一気に変換することが容易になります。

Processingの基本操作 その1

Processingのアプリケーションを開くと以下のような画面が現れます。


画面上部メニューバーのHelp>Referenceを選ぶと、プログラミングで使用される言語や関数がカテゴリーごとに列挙されているページが現れます(ProcessingのサイトにもReferenceのページがあります)。すべての言語を覚えるのは大変なので、必要に応じて辞書のように参照しながら、プログラムを書いていきます。それぞれの言語(関数)をクリックすれば、使用法のサンプルページが出てきます。
例えば、Shapeのカテゴリーの2D Primitivesの中のrect()という関数は、長方形を描くためのものです。

rect(10,30,40,20);

と書けば、X座標が10で、Y座標が30の場所に、幅40で高さ20(単位:ピクセル)の長方形を描くということになります。
カッコの中の4つの数値はそれぞれ順番にX座標、Y座標、幅、高さに対応しています。
四つの数値の間のカンマや最後のセミコロンを忘れないように書いてください。
大文字、小文字(半角英字)も間違わないように書いてください。
ちなみに、画面上の(0,0)座標は左上の角となります。左から右へX座標はプラスになっていきますが、注意しなければいけないことは、Y座標に関しては、上から下へ向けてプラスになるので、いわゆる数学で扱われる座標の向きとは異なります。
とりあえず、rect(10,30,40,20);をプログラムを書く欄に記入して、左側にあるRunボタンもしくはメニューバーのSketch>Runをクリックすれば、プログラムが開始され、その結果が別画面で現れます。その画面を閉じれば、そのプログラムは終了します。

以下のようにもう一行追加すれば、

size(200,300);
rect(10,30,40,20);

表示画面の大きさを幅200、高さ300ピクセルと指定して先ほどの長方形を描くことができます。
size()のカッコの中の二つの数値は画面の幅と高さに対応しています。もし、画面サイズを指定せず何も書かなければ、自動的に初期設定のサイズ(100x100ピクセル)が適用されることになります。

さらに、もう一行追加して、

size(200,300);
background(100);
rect(10,30,40,20);

と書けば、表示画面の背景色を指定して描画されます。
background()のカッコの中には、0から255までの数値が入ります。0は黒、255は白、その間の数値は段階ごとのグレーになります。background(255,100,30);のように3つの数値をカンマで区切って入れれば、それぞれの数値はRed,Green,Blueに対応しRGB色を指定することができます。例えば、赤ならbackground(255,0,0);で黄色ならbackground(255,255,0);です。
このような細かな設定については、冒頭で述べた「画面上部メニューバーのHelp>Reference」のページへ行き、それぞれの言語の使用法から調べることができます。

4/19/2008

Processingのダウンロードとインストール

まずは、今後の制作や表現をアシストするプログラミングの基本から始めます。
このゼミでは、Processingという開発環境(プログラミングのためのソフト)を用います。
無料でインターネットからダウンロードして利用でき、図形描画、画像、動画、音、3Dモデル、外部機器との接続(Webカメラやゲームコントローラ、自作の入出力ディバイスなど)、様々なものを扱うことが可能となります。
Processing(http://processing.org)のサイトにアクセスし、サイト内のダウンロードページ(http://processing.org/download/index.html)からダウンロードすることができます。




上画像のバージョンは135ですが、最新版(2010年5月現在では)は1.1になっています。

ダウンロードしたファイルをダブルクリックして展開するとProcessingというフォルダが現れ、それをMacintoshの場合アプリケーションのフォルダ、Windowsの場合Program Filesに移し入れます。これでインストールは完了です。
インストールしたProcessingのアイコンをダブルクリックすればアプリケーションが開きます。


[目次:Processing関係]  [HOMEへ戻る]  [目次:Arduino関係]