何てことはない、ただの小ネタ。
UIScrollViewのジェスチャ関係のプロパティいじるの面白いです。
例えば2本指でタッチしなきゃうごかないよー><なwebView組み込むなら
[self.webView.scrollView.panGestureRecognizer setMinimumNumberOfTouches:2];
こんなかんじ。
この記事はProcessing Advent Calendar 22日目の記事です。
久々のブログ更新がAdvent Calendarというね…
何かネタが無いか振られるかしないと書かない性分のようです(ヽ´ω`)
Advent Calendarに向けて何しよーかなーと考えていた時期、くろねこは毎週の中二病が楽しみでした。
(ヽ´ω`)「爆ぜろリアル、弾けろシナプスー」
(ヽ´ω`)「なんか昔こういうのやってた気がするー」
(ヽ´ω`)「ほら、こう、壁に向かって手をかざして扉よ開けー的な」
(ヽ´ω`)「扉ないにしても何かキーンって共鳴して模様が浮かび上がるとかさ」
(ヽ´ω`)「…これ実装できるかも」
てなことで、「手をかざすといい感じの共鳴音を上げるとともに視覚効果を演出する」ようなものを実装してみました。
(動画中の○は手の座標です。)
上記コンセプトを分解して、以下の様な感じに仕上げていきます。
・「手をかざすと」→kinectで右手の座標を検知
・「いい感じの共鳴音を上げる」→キーンと来る高周波数の音を生成・出力
・「視覚効果を演出」→手の座標が画面中央に近いほど画面が荒れるようなエフェクト(これに合わせて生成音の音量も大きくする)
処理の流れはこんな感じです。
キーンとくる良い感じの音は単純なサイン波で十分かなと思いP5でも生成可能だなと検討していたのですが、
より凝った音を作りこみたいなーという展望があり、draw()内の処理をなるべく少なくしたかったので、OSCを利用してPdに音声処理を委託することにしました。
今回の記事では映像演出の話は置いといて、OSCを利用してP5とPd間でどのようにメッセージを送るかというところを紹介します。
OSCとは?
電子楽器やアプリケーション、PC間のための通信プロトコルです。
@tadokoroさんのブログ記事にて分かりやすく説明してくれています。
openFramewoks – OSC (Open Sound Control) を利用したネットワーク連携
OSC送信側(P5)
まずはOscP5インスタンスを生成し、ポート番号と送信先アドレスを指定しましょう。
今回はlocalhostへ。
// oscP5 setting oscP5 = new OscP5(this, 10001); oscDestination = new NetAddress("127.0.0.1", 10001);
Pd側に送る情報が決定したら、メッセージを生成して送信します。
// send osc message with velocity value OscMessage messageForPd = new OscMessage("/p5/soundValue"); messageForPd.add(velocityValue(distance)); oscP5.send(messageForPd, oscDestination);
基本はこれだけです。
OSC受信側(Pd)
dumpOSCとOSCrouteでメッセージを取り出します。
Pd側で注意すべきことが1点。
実行したけどdumpOSCでエラー出て何故か受信できないという現象に出会った場合、dumpOSCにて指定したポートを既に使用している場合があります。
つまり、同じプログラム、又は同一ポートを利用したプログラムが立ち上がっているとこの現象に出会ってしまいます。
上記プログラムが起動していないことを確認、もしくは起動しているプログラムを一旦落としてから再度開いてみてください。
ソースコード全体(P5、Pdともに)をご覧になりたい方は、githubにアップロードしたので御覧ください∩(・ω・)∩
https://github.com/laprasdrum/Chuuni-P5-Pd-Kinect
明日はすつーかさんですね。
よろしくお願いします!
前回の記事の続き。
たーせるさんはtwitter4jを使ってらしたので、僕は違う角度から攻めてみます。
使うのはこちら。
僕の愛器kaossilatorです。
今回は、kaossilatorの入力に合わせてparticleが飛び跳ねるように改良します。
必要になるのはSound APIあたり。
Processingではminimというライブラリを利用します。
Minim | code.compartmental
音出ますけど、ちっちゃめです。
ざっと説明すると、
ライン入力で受け取った音声をバッファに格納→音量で飛び跳ねる高さを指定→各particleのupdate()関数に渡してy軸ベクトルの成分として加算
そんなに難しいことはないですし、前回のソースにちょっと手を加えるだけで動きます。
気を付けないといけないのは、はじめに使用するバッファサイズとparticleの数を統一するくらいですかね。
今回particleの数=バッファサイズを512に指定しました。
以下ソースコード。
変更点は、setup()で入力バッファ生成、draw()で飛び跳ね値の設定、update()で飛び跳ね値の加算。
あとはminim利用に必要なライブラリと関数の追加です。
おためしあれー∩(・ω・)∩
import processing.opengl.*; import javax.media.opengl.*; import ddf.minim.*; import ddf.minim.signals.*; import ddf.minim.analysis.*; import ddf.minim.effects.*; PGraphics tex; List<Agent> agents; long time; int groundArea = 1000; int agentArea = 700; int agentNumber = 512; // equal to line input buffer size // for boid theory int DIST_THRESHOLD1 = 10; int DIST_THRESHOLD2 = 20; int DIST_THRESHOLD3 = 30; float FACTOR_COHESION = 100; float FACTOR_SEPARATION = 10; float FACTOR_ALINGMENT = 10; float VELOCITY_LIMIT = 3; float TRAIL_SCALE = 2; float rCohesion = 1.0; // pull to center of agent float rSeparation = 0.8; // avoid bunching up float rAlignment = 0.1; // match average agent speed // for Sound API Minim minim; AudioInput in; int JUMPHIGH = 300; void setup(){ size(1280, 800, OPENGL); tex = createTexture(); minim=new Minim(this); in = minim.getLineIn(Minim.STEREO,agentNumber); agents = new ArrayList<Agent>(); for(int i = 0; i < agentNumber; ++i){ float x = random(-agentArea, agentArea); float z = random(-agentArea, agentArea); agents.add(new Agent(new PVector(x, 0, z))); } } void draw(){ background(0); // camera setting float angle = 0.05f * radians(time); camera(800, -200, 0, 0, 0, 0, 0, 1, 0); rotateY(angle); pushStyle(); // enable additive synthesis PGraphicsOpenGL pgl = (PGraphicsOpenGL)g; GL gl = pgl.beginGL(); gl.glDisable(GL.GL_DEPTH_TEST); gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE); pgl.endGL(); // draw agents noStroke(); fill(0); for(int i = 0; i < agents.size(); ++i){ // decide how much agent jump off float jump = pow(in.left.get(i),2) * JUMPHIGH; if(jump > 0){ jump *= -1; } agents.get(i).update(jump); } popStyle(); // draw ground pushStyle(); stroke(255, 255, 255, 100); for(int x = -groundArea; x <= groundArea; x += groundArea/10) line(x, 0, -groundArea, x, 0, groundArea); for(int z = -groundArea; z <= groundArea; z += groundArea/10) line(-groundArea, 0, z, groundArea, 0, z); popStyle(); time++; } void stop(){ in.close(); minim.stop(); super.stop(); } PGraphics createTexture(){ PGraphics tex = createGraphics(100, 100, P2D); float r = tex.width * 2 / 3; tex.beginDraw(); tex.background(0); tex.noStroke(); tex.fill(100, 255, 150); tex.ellipse(tex.width/2, tex.height/2, r, r); tex.filter(BLUR, 10.0); tex.endDraw(); return tex; } class Agent{ private final float TERRITORY_SIZE = 2000; private final float NOISE_SCALE = 0.001f; private final float AGENT_SIZE = random(50); private PVector center; private PVector offset; private PVector pos; private long elapse; float xOffset, zOffset; // for noise injection PVector vCohesion; PVector vSeparation; PVector vAlignment; PVector velocity; Agent(PVector center){ this.center = center; pos = new PVector(); offset = new PVector(); xOffset = random(TERRITORY_SIZE); zOffset = random(TERRITORY_SIZE); vCohesion = new PVector(); vSeparation = new PVector(); vAlignment = new PVector(); velocity = new PVector(); } PVector getPos(){ return pos; } PVector getVelocity(){ return velocity; } void update(float jump){ // redraw agent // apply boid theory vCohesion.x = vCohesion.y = vSeparation.x = vSeparation.y = vAlignment.x = vAlignment.y = 0; /** * Boid Theory * 1. Cohesion: distance itself from objects -> close to others * 2. Separation: too close to objects -> keep it away from each other * 3. Alignment: run parallel to others **/ cohesion(); separation(); alignment(); velocity.x += rCohesion * vCohesion.x + rSeparation * vSeparation.x + rAlignment * vAlignment.x; velocity.z += rCohesion * vCohesion.z + rSeparation * vSeparation.z + rAlignment * vAlignment.z; limitVelocity(); // tercel_method (use perlin noise) + boid theory elapse++; offset.x = (noise((elapse + xOffset + velocity.x) * NOISE_SCALE) -0.5f) * TERRITORY_SIZE; offset.z = (noise((elapse + zOffset + velocity.z) * NOISE_SCALE) -0.5f) * TERRITORY_SIZE; pushMatrix(); pos.set(center.x + offset.x, center.y - AGENT_SIZE + 0.5 + jump, center.z + offset.z); translate(pos.x, pos.y, pos.z); // 以下変更なしです∩(・ω・)∩ // billboarding PMatrix3D billboardMat = (PMatrix3D)g.getMatrix(); // set unit matrix as rotation factor billboardMat.m00 = billboardMat.m11 = billboardMat.m22 = 1; billboardMat.m01 = billboardMat.m02 = billboardMat.m10 = billboardMat.m12 = billboardMat.m20 = billboardMat.m21 = 0; resetMatrix(); applyMatrix(billboardMat); beginShape(QUADS); texture(tex); vertex(-0.5f * AGENT_SIZE, -AGENT_SIZE, 0, 0, 0); vertex(-0.5f * AGENT_SIZE, 0, 0, 0, tex.height-1); vertex(0.5f * AGENT_SIZE, 0, 0, tex.width, tex.height-1); vertex(0.5f * AGENT_SIZE, -AGENT_SIZE, 0, tex.width, 0); endShape(); popMatrix(); } void limitVelocity(){ float velocitySize = sqrt(sq(velocity.x) + sq(velocity.z)); if(velocitySize > VELOCITY_LIMIT){ velocity.x = (velocity.x/velocitySize)*VELOCITY_LIMIT; velocity.z = (velocity.z/velocitySize)*VELOCITY_LIMIT; } } void cohesion(){ float len = 0; int count = 0; for(int i=0; i < agentNumber; ++i){ Agent a = agents.get(i); if(this != a){ len = dist(this.pos.x, this.pos.z, a.getPos().x, a.getPos().z); if(len > DIST_THRESHOLD2 && len < DIST_THRESHOLD3){ vCohesion.x += a.getPos().x; vCohesion.z += a.getPos().z; count++; } } } if(count > 0){ vCohesion.x /= count; vCohesion.z /= count; vCohesion.x = (vCohesion.x - this.pos.x) / FACTOR_COHESION; vCohesion.z = (vCohesion.z - this.pos.z) / FACTOR_COHESION; } } void separation(){ float len = 0; for(int i=0; i < agentNumber; ++i){ Agent a = agents.get(i); if(this != a){ len = dist(this.pos.x, this.pos.z, a.getPos().x, a.getPos().z); if(len < DIST_THRESHOLD1){ vSeparation.x -= (a.getPos().x - this.pos.x)/FACTOR_SEPARATION; vSeparation.z -= (a.getPos().z - this.pos.z)/FACTOR_SEPARATION; } } } } void alignment(){ float len = 0; int count = 0; for(int i=0; i < agentNumber; ++i){ Agent a = agents.get(i); if(this != a){ len = dist(this.pos.x, this.pos.z, a.getPos().x, a.getPos().z); if(len > DIST_THRESHOLD1 && len < DIST_THRESHOLD2){ vAlignment.x += a.getVelocity().x; vAlignment.z += a.getVelocity().z; count++; } } } if(count > 0){ vAlignment.x /= count; vAlignment.z /= count; vAlignment.x = (vAlignment.x - velocity.x)/FACTOR_ALINGMENT; vAlignment.z = (vAlignment.z - velocity.z)/FACTOR_ALINGMENT; } } }
たーせるさんの記事を見て遊びたくなったのでforkさせていただきました。
particleの動きを面白く出来ないかなーと思い、boid理論を適用して生き物っぽく動かしてみた。
boid理論の実装コードもどっかから拾ってきたやつだけど、ソース元忘れてしまった。
ガチャガチャ組み合わせただけなのでオリジナリティないです。
きっと誰かが上手いことforkしてくれることを信じてます!
以下ソースコード
import processing.opengl.*; import javax.media.opengl.*; PGraphics tex; List<Agent> agents; long time; int groundArea = 1000; int agentArea = 200; int agentNumber = 100; // for boid theory int DIST_THRESHOLD1 = 10; int DIST_THRESHOLD2 = 20; int DIST_THRESHOLD3 = 30; float FACTOR_COHESION = 100; float FACTOR_SEPARATION = 10; float FACTOR_ALINGMENT = 10; float VELOCITY_LIMIT = 3; float TRAIL_SCALE = 2; float rCohesion = 1.0; // pull to center of agent float rSeparation = 0.8; // avoid bunching up float rAlignment = 0.1; // match average agent speed void setup(){ size(800, 600, OPENGL); tex = createTexture(); agents = new ArrayList<Agent>(); for(int i = 0; i < agentNumber; ++i){ float x = random(-agentArea, agentArea); float z = random(-agentArea, agentArea); agents.add(new Agent(new PVector(x, 0, z))); } } void draw(){ background(0); // camera setting float angle = 0.05f * radians(time); camera(800, -200, 0, 0, 0, 0, 0, 1, 0); rotateY(angle); pushStyle(); // enable additive synthesis PGraphicsOpenGL pgl = (PGraphicsOpenGL)g; GL gl = pgl.beginGL(); gl.glDisable(GL.GL_DEPTH_TEST); gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE); pgl.endGL(); // draw agents noStroke(); fill(0); for(int i = 0; i < agents.size(); ++i){ agents.get(i).update(); } popStyle(); // draw ground and axis pushStyle(); stroke(255, 255, 255, 100); for(int x = -groundArea; x <= groundArea; x += groundArea/10) line(x, 0, -groundArea, x, 0, groundArea); for(int z = -groundArea; z <= groundArea; z += groundArea/10) line(-groundArea, 0, z, groundArea, 0, z); popStyle(); time++; } PGraphics createTexture(){ PGraphics tex = createGraphics(100, 100, P2D); float r = tex.width * 2 / 3; tex.beginDraw(); tex.background(0); tex.noStroke(); tex.fill(100, 255, 150); tex.ellipse(tex.width/2, tex.height/2, r, r); tex.filter(BLUR, 10.0); tex.endDraw(); return tex; } class Agent{ private final float TERRITORY_SIZE = 2000; private final float NOISE_SCALE = 0.001f; private final float AGENT_SIZE = random(50); private PVector center; private PVector offset; private PVector pos; private long elapse; float xOffset, zOffset; // for noise injection PVector vCohesion; PVector vSeparation; PVector vAlignment; PVector velocity; Agent(PVector center){ this.center = center; pos = new PVector(); offset = new PVector(); xOffset = random(TERRITORY_SIZE); zOffset = random(TERRITORY_SIZE); vCohesion = new PVector(); vSeparation = new PVector(); vAlignment = new PVector(); velocity = new PVector(); } PVector getPos(){ return pos; } PVector getVelocity(){ return velocity; } void update(){ // redraw agent // apply boid theory vCohesion.x = vCohesion.y = vSeparation.x = vSeparation.y = vAlignment.x = vAlignment.y = 0; /** * Boid Theory * 1. Cohesion: distance itself from objects -> close to others * 2. Separation: too close to objects -> keep it away from each other * 3. Alignment: run parallel to others **/ cohesion(); separation(); alignment(); velocity.x += rCohesion * vCohesion.x + rSeparation * vSeparation.x + rAlignment * vAlignment.x; velocity.z += rCohesion * vCohesion.z + rSeparation * vSeparation.z + rAlignment * vAlignment.z; limitVelocity(); // tercel_method (use perlin noise) + boid theory elapse++; offset.x = (noise((elapse + xOffset + velocity.x) * NOISE_SCALE) -0.5f) * TERRITORY_SIZE; offset.z = (noise((elapse + zOffset + velocity.z) * NOISE_SCALE) -0.5f) * TERRITORY_SIZE; pushMatrix(); pos.set(center.x + offset.x, center.y - AGENT_SIZE + 0.5, center.z + offset.z); translate(pos.x, pos.y, pos.z); // billboarding PMatrix3D billboardMat = (PMatrix3D)g.getMatrix(); // set unit matrix as rotation factor billboardMat.m00 = billboardMat.m11 = billboardMat.m22 = 1; billboardMat.m01 = billboardMat.m02 = billboardMat.m10 = billboardMat.m12 = billboardMat.m20 = billboardMat.m21 = 0; resetMatrix(); applyMatrix(billboardMat); beginShape(QUADS); texture(tex); vertex(-0.5f * AGENT_SIZE, -AGENT_SIZE, 0, 0, 0); vertex(-0.5f * AGENT_SIZE, 0, 0, 0, tex.height-1); vertex(0.5f * AGENT_SIZE, 0, 0, tex.width, tex.height-1); vertex(0.5f * AGENT_SIZE, -AGENT_SIZE, 0, tex.width, 0); endShape(); popMatrix(); } void limitVelocity(){ float velocitySize = sqrt(sq(velocity.x) + sq(velocity.z)); if(velocitySize > VELOCITY_LIMIT){ velocity.x = (velocity.x/velocitySize)*VELOCITY_LIMIT; velocity.z = (velocity.z/velocitySize)*VELOCITY_LIMIT; } } void cohesion(){ float len = 0; int count = 0; for(int i=0; i < agentNumber; ++i){ Agent a = agents.get(i); if(this != a){ len = dist(this.pos.x, this.pos.z, a.getPos().x, a.getPos().z); if(len > DIST_THRESHOLD2 && len < DIST_THRESHOLD3){ vCohesion.x += a.getPos().x; vCohesion.z += a.getPos().z; count++; } } } if(count > 0){ vCohesion.x /= count; vCohesion.z /= count; vCohesion.x = (vCohesion.x - this.pos.x) / FACTOR_COHESION; vCohesion.z = (vCohesion.z - this.pos.z) / FACTOR_COHESION; } } void separation(){ float len = 0; for(int i=0; i < agentNumber; ++i){ Agent a = agents.get(i); if(this != a){ len = dist(this.pos.x, this.pos.z, a.getPos().x, a.getPos().z); if(len < DIST_THRESHOLD1){ vSeparation.x -= (a.getPos().x - this.pos.x)/FACTOR_SEPARATION; vSeparation.z -= (a.getPos().z - this.pos.z)/FACTOR_SEPARATION; } } } } void alignment(){ float len = 0; int count = 0; for(int i=0; i < agentNumber; ++i){ Agent a = agents.get(i); if(this != a){ len = dist(this.pos.x, this.pos.z, a.getPos().x, a.getPos().z); if(len > DIST_THRESHOLD1 && len < DIST_THRESHOLD2){ vAlignment.x += a.getVelocity().x; vAlignment.z += a.getVelocity().z; count++; } } } if(count > 0){ vAlignment.x /= count; vAlignment.z /= count; vAlignment.x = (vAlignment.x - velocity.x)/FACTOR_ALINGMENT; vAlignment.z = (vAlignment.z - velocity.z)/FACTOR_ALINGMENT; } } }
iOS deviceで加速度センサーをロギング+csvファイル出力まで。
加速度の取得に大事なのは次の3つ。
1. UIAccelerometerDelegateをプロトコル宣言しておく。
@interface HogeViewController : UIViewController <UIAccelerometerDelegate>{ ...
2. 検出するタイミングを指定。
[[UIAccelerometer sharedAccelerometer]setUpdateInterval:0.1]; [[UIAccelerometer sharedAccelerometer]setDelegate:self];
UpdateIntervalの値を小さくしすぎると、実機テストの際バッテリーをかなり消耗する。
3. (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)accelerationで検出。
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration{ NSLog(@"x:%f",acceleration.x); // x-axis NSLog(@"y:%f",acceleration.y); // y-axis NSLog(@"z:%f",acceleration.z); // z-axis }
センサーの向きはハードウェア依存なので、shouldAutorotateToInterfaceOrientationの設定でviewが回転する場合は注意が必要。
ログ出力は出力フォーマット生成、出力先パス指定、ファイル生成という流れ。
ファイル名は(log_[実行時刻].csv)としてみた。
NSMutableArray accelerationData = [[NSMutableArray array] retain]; // センサーログ NSDate startTime = [[NSDate date] retain]; // プログラム実行開始時刻 ... // 出力フォーマット生成 NSTimeInterval elapsedTime = [startTime timeIntervalSinceNow]; // 経過時刻 NSString* str = [NSString stringWithFormat:@"%f,%f,%f,%f",-elapsedTime,acceleration.x,acceleration.y,acceleration.z]; [accelerationData addObject:str]; ... // 出力先パス指定 NSDateFormatter* format = [[NSDateFormatter alloc]init]; NSString *dateFormatter = @"yyMMdd_HHmmss"; [format setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"JST"]]; [format setDateFormat:dateFormatter]; NSString *dateStr = [format stringFromDate:startTime]; NSString* path = [[NSString stringWithString:@"~/Documents"]stringByExpandingTildeInPath]; path = [path stringByAppendingString:@"log_"]; path = [path stringByAppendingString:dateStr]; path = [path stringByAppendingString:@".csv"]; ... // ファイル生成 NSString* fullstring = [accelerationData componentsJoinedByString:@"\n"]; [fullstring writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
accelerationDataやstartTimeはあちこちのメソッドで参照されるため、retainしている。
動的にログデータを追加し、まとめてファイルに出力したかったので、accelerationDataはNSArrayではなくNSMutableArrayで定義。
・・・サンプルコードを色々繋ぎ合わせたものなので、高速化のためには改良が必要。
参考:Posting Source Code — Support — WordPress.com
void startRequest(){ // Creating and starting the request BasicAuth wiki_authenticator = new BasicAuth(user, pass); Authenticator.setDefault(wiki_authenticator); System.setProperty("java.net.useSystemProxies","true"); pagenumber = int(random(urls.size()-1)); String request_page = urls.get(pagenumber); req = new HTMLRequest(this,request_page); req.makeRequest(); }
[sourcecode]…[/sourcecode]で囲めば良し。
languageで使用言語を設定できる。
[code]…[/code]と省略しても良いらしい。