yoppa.org


最終課題のヒント – インタラクションを表現する

今回は、最終課題のテーマ「インタラクションを表現する」に沿って作品を制作する際のヒントを、サンプルを交えて解説していきます。マウスやキーボードを使用したインタラクション、音を使用したインタラクション、映像を使用したインタラクション、センサーを使用したインタラクションなど様々な入力を処理する方法について紹介します。

スライド資料

サンプルプログラム

最終課題について

最終課題のテーマ「インタラクションを表現する」

外部からの何らかのアクションに対して反応する(インタラクション)作品を、この講義で学んだProcessingを用いて表現してください。プログラムは、Processingを使用していることを条件とします。ただし、他のプログラムと組み合わせても構いません。

ユーザーのアクションはマウスやキーボードだけとは限りません。

  • 映像
  • ネットワーク
  • 各種センサー

など様々な入力に対するインタラクションを斬新なアイデアで表現してください。

最終課題プレゼン希望調査

1月17日の講義最終日に作品のプレゼンを希望する方は、下記のフォームに記入してください。1月16日締切とします。


最終課題のヒント – インタラクション

今回は、最終課題のテーマである「インタラクション」について、課題制作のヒントになるようなトピックスを紹介していきます。マウスとキーボードによるインタラクション、音声によるインタラクション、映像によるインタラクションなどについて取り上げます。

スライド資料

サンプルファイル

最終課題について

最終課題のテーマ「インタラクション」

外部からの何らかのアクションに対して反応する作品を制作し発表してください。openFrameworksを使用していることを条件としますが、他のプログラムと組み合わせても構いません。

ユーザーのアクションはマウスやキーボードだけとは限りません。

  • 映像
  • ネットワーク
  • 各種センサー

など様々な入力を元にしてインタラクションについて面白いアイデアを実現してください。

最終課題プレゼン希望調査

1月15日の講義最終日に作品のプレゼンを希望する方は、下記のフォームに記入してください。


OSC通信 – ProcessingでSonic Piをコントロール

今回は、最終コンサートの発表のヒントとして、Sonic PiとProcessingを連携する方法をとりあげます。

Sonic Piは内部にSuperCollierのサーバーを持っていて、Sonic Piでプログラムした演奏の情報を元に音響合成しています。この両者はOSC (Open Sound Control) というプロトコルで通信しています。Sonic Piのver 3.0以降からは、このOSCを外部から受けとることができるようになりました。これによって、OSCを出力するプログラムを作成することで、外部のプログラムからSonic Piを演奏することが可能です。

今回は、OSCを送出するプログラムをProcessingで作成して、簡単なOSC通信を実現します。

スライド資料

サンプルプログラム

連携の基本

Sonic Pi

live_loop :trigger do
    use_real_time
    a, b, c = sync "/osc/trigger/synth"
    synth :pluck, note: a, pan: b, sustain: c
end

Processing

import oscP5.*;
import netP5.*;
OscP5 oscP5;
NetAddress location;

void setup() {
  size(480, 320);
  frameRate(60);
  oscP5 = new OscP5(this, 12000);
  location = new NetAddress("127.0.0.1", 4559);
}

void draw() {
  background(0);
  if (mousePressed) {
    ellipse(mouseX, mouseY, 40, 40);
  }
}

void mouseReleased() {
  float note = map(mouseX, 0, width, 20, 120);
  float pan = map(mouseY, 0, height, -1, 1);
  float sustain = 1.0;
  OscMessage msg = new OscMessage("/trigger/synth");
  msg.add(note);
  msg.add(pan);
  msg.add(sustain);
  oscP5.send(msg, location);
  println(msg);
}

演奏の工夫

Sonic Pi

live_loop :trigger do
  with_fx :flanger do
    use_real_time
    a, b, c = sync "/osc/trigger/synth"
    synth :dsaw, note: a, pan: -0.7, sustain: b, cutoff: c, amp: 0.2
    synth :dsaw, note: a-9, pan: 0.7, sustain: b, cutoff: c, amp: 0.2
    synth :dsaw, note: a-12, pan: 0.0, sustain: b, cutoff: c, amp: 0.2
  end
end

Processing

import oscP5.*;
import netP5.*;
OscP5 oscP5;
NetAddress location;

void setup() {
  size(480, 320);
  frameRate(60);
  oscP5 = new OscP5(this, 12000);
  location = new NetAddress("127.0.0.1", 4559);
}

void draw() {
  background(0);
  fill(255);
  if (mousePressed) {
    ellipse(mouseX, mouseY, 40, 40);
  }
}

void mousePressed() {
  float note = map(mouseX, 0, width, 20, 120);
  float cutoff = map(mouseY, 0, height, 150, 1);
  float sustain = 1.0;
  OscMessage msg = new OscMessage("/trigger/synth");
  msg.add(note);
  msg.add(sustain);
  msg.add(cutoff);
  oscP5.send(msg, location);
  println(msg);
}

自律的に演奏する

Sonic Pi

live_loop :trigger do
  with_fx :flanger do
    use_real_time
    a, b, c = sync "/osc/trigger/synth"
    synth :dsaw, note: a, pan: -0.7, sustain: b, cutoff: c, amp: 0.2
    synth :dsaw, note: a-9, pan: 0.7, sustain: b, cutoff: c, amp: 0.2
    synth :dsaw, note: a-12, pan: 0.0, sustain: b, cutoff: c, amp: 0.2
  end
end

Processing

import oscP5.*;
import netP5.*;
OscP5 oscP5;
NetAddress location;

float x = 0, y = 0;

void setup() {
  size(1280, 720);
  frameRate(60);
  oscP5 = new OscP5(this, 12000);
  location = new NetAddress("127.0.0.1", 4559);
}

void draw() {
  background(0);
  fill(255);

  if (frameCount % 15 == 0) {
    float note = random(20, 120);
    float cutoff = random(10, 120);
    float sustain = 0.25;
    x = map(cutoff, 10, 120, 0, width);
    y = map(note, 20, 120, height, 0);
    OscMessage msg = new OscMessage("/trigger/synth");
    msg.add(note);
    msg.add(sustain);
    msg.add(cutoff);
    oscP5.send(msg, location);
    println(msg);
  }

  fill(31, 127, 255);
  ellipse(x, y, 60, 60);
}

Libraries 3 : OpenCV for Processing – コンピュータ・ビジョン、映像を使ったインタラクション

Processingを使用してインタラクティブな機能を実現するための手段は、センサーを使う方法や、KinectやLeap Motionなどのデバイスを使用する方法などいろいろ考えられます。今回は、最もシンプルな機材構成で可能な方法として、カメラの映像を解析してそこから動きや物体の輪郭を取り出す手法について取り上げます。

コンピュータで、映像から実世界の情報を取得して認識するための研究で「コンピュータ・ビジョン (Conputer Vision)」という分野が存在します。わかりやすく言うなら「ロボットの目」をつくるような研究です。このコンピュータ・ビジョンの様々な成果をオープンソースで公開しているOpenCVというライブラリーがあります。今回は、このOpenCVをProcessingで使用できるようにした、OpenCV for Processingライブラリーを使用したプログラミングを体験します。

スライド資料

サンプルプログラム

カメラキャプチャー基本

import gab.opencv.*;
import processing.video.*;

Capture video; // ライブカメラ

void setup() {
  //初期設定
  size(640, 480); //画面サイズ
  //キャプチャーするカメラのサイズ
  video = new Capture(this, 640, 480);
  //キャプチャー開始
  video.start();
}

void draw() {
  //カメラ画像を表示
  image(video, 0, 0 );
}

//キャプチャーイベント
void captureEvent(Capture c) {
  c.read();
}

OpenCV輪郭抽出

import gab.opencv.*;
import processing.video.*;

Capture video; // ライブカメラ
OpenCV opencv; // OpenCV
ArrayList contours; //輪郭の配列

void setup() {
  //初期設定
  size(640, 480); //画面サイズ
  //キャプチャーするカメラのサイズ
  video = new Capture(this, 640, 480);
  //OpenCVの画面サイズ
  opencv = new OpenCV(this, 640, 480);
  //キャプチャー開始
  video.start();
}

void draw() {
  //カメラの画像をOpenCVに読み込み
  opencv.loadImage(video);
  //カメラ画像を表示
  image(video, 0, 0 );
  //閾値の設定(マウスのX座標で変化)
  int threshold = int(map(mouseX, 0, width, 0, 100));
  opencv.threshold(threshold);
  //輪郭抽出
  contours = opencv.findContours();
  //描画設定
  noFill();
  strokeWeight(1);
  //検出された輪郭の数だけ、輪郭線を描く
  for (Contour contour : contours) {
    stroke(0, 255, 0);
    contour.draw();
  }
}

//キャプチャーイベント
void captureEvent(Capture c) {
  c.read();
}

OpenCVによる顔検出

import gab.opencv.*;
import processing.video.*;
import java.awt.*;

Capture video; //ビデオキャプチャー
OpenCV opencv; //OpenCV

void setup() {
  size(640, 480);
  //ビデオキャプチャー初期化
  video = new Capture(this, 640/2, 480/2);
  //OpenCV初期化(ビデオキャプチャーの半分のサイズ)
  opencv = new OpenCV(this, 640/2, 480/2);
  //顔の学習データを読み込み
  opencv.loadCascade(OpenCV.CASCADE_FRONTALFACE);
  //ビデオキャプチャー開始
  video.start();
}

void draw() {
  //二倍サイズで表示
  scale(2);
  //画像を読み込み
  opencv.loadImage(video);
  //カメラ画像を描画
  image(video, 0, 0 );
  
  //顔を検出
  Rectangle[] faces = opencv.detect();
  //検出した顔の周囲を四角形で描画
  noFill();
  stroke(0, 255, 0);
  strokeWeight(3);
  for (int i = 0; i < faces.length; i++) {
    rect(faces[i].x, faces[i].y, faces[i].width, faces[i].height);
  }
}

//キャプチャーイベント
void captureEvent(Capture c) {
  c.read();
}

OpenCVによる顔検出 - 目線を入れてみる

import gab.opencv.*;
import processing.video.*;
import java.awt.*;

Capture video; //ビデオキャプチャー
OpenCV opencv; //OpenCV

void setup() {
  size(640, 480);
  //ビデオキャプチャー初期化
  video = new Capture(this, 640/2, 480/2);
  //OpenCV初期化(ビデオキャプチャーの半分のサイズ)
  opencv = new OpenCV(this, 640/2, 480/2);
  //顔の学習データを読み込み
  opencv.loadCascade(OpenCV.CASCADE_FRONTALFACE);
  //ビデオキャプチャー開始
  video.start();
}

void draw() {
  //二倍サイズで表示
  scale(2);
  //画像を読み込み
  opencv.loadImage(video);
  //カメラ画像を描画
  image(video, 0, 0 );
  
  //顔を検出
  Rectangle[] faces = opencv.detect();
  //検出した顔の周囲を四角形で描画
  fill(0);
  for (int i = 0; i < faces.length; i++) {
    //ちょうど目の場所にくるよう、場所とサイズを調整
    float x = faces[i].x + faces[i].width * 0.15;
    float y = faces[i].y + faces[i].height * 0.3;
    float width = faces[i].width * 0.7;
    float height = faces[i].height * 0.15;
    //目線を描画
    rect(x, y, width, height);
  }
}

//キャプチャーイベント
void captureEvent(Capture c) {
  c.read();
}

Optical Flowの描画

import gab.opencv.*;
import processing.video.*;

Capture video; // ライブカメラ
OpenCV opencv; // OpenCV

void setup() {
  //初期設定
  size(640, 480); //画面サイズ
  //キャプチャーするカメラのサイズ
  video = new Capture(this, 640/2, 480/2);
  //OpenCVの画面サイズ
  opencv = new OpenCV(this, 640/2, 480/2);
  //キャプチャー開始
  video.start();
}

void draw() {
  //描画スケール設定
  scale(2.0);
  //カメラの画像をOpenCVに読み込み
  opencv.loadImage(video);
  //カメラ画像を表示
  image(video, 0, 0 );
  //OpticalFlowを計算
  opencv.calculateOpticalFlow();
  //描画設定
  stroke(255,0,0);
  //OpticalFlowを描画
  opencv.drawOpticalFlow();
}

//キャプチャーイベント
void captureEvent(Capture c) {
  c.read();
}

OpticalFlow + Particle

import gab.opencv.*;
import processing.video.*;

Capture video; // ライブカメラ
OpenCV opencv; // OpenCV

int NUM = 500;
ParticleVec3[] particles = new ParticleVec3[NUM];

void setup() {
  //初期設定
  size(640, 480, P3D); //画面サイズ
  //キャプチャーするカメラのサイズ
  video = new Capture(this, 640/2, 480/2);
  //OpenCVの画面サイズ
  opencv = new OpenCV(this, 640/2, 480/2);
  //キャプチャー開始
  video.start();
  
  for (int i = 0; i < NUM; i++) {
    particles[i] = new ParticleVec3();
    particles[i].radius = 4.0;
    particles[i].position.set(random(width/2), random(height/2), 0);
    particles[i].minx = 0;
    particles[i].miny = 0;
    particles[i].maxx = width/2;
    particles[i].maxy = height/2;
  }
}

void draw() {
  background(0);
  blendMode(ADD);
  //描画スケール設定
  scale(2.0);
  //カメラの画像をOpenCVに読み込み
  opencv.loadImage(video);
  //カメラ画像を表示
  //image(video, 0, 0 );
  //OpticalFlowを計算
  opencv.calculateOpticalFlow();
  //描画設定
  stroke(255, 0, 0);
  //OpticalFlowを描画
  opencv.drawOpticalFlow();
  
  noStroke();
  fill(0, 127, 255);
  for (int i = 0; i < NUM; i++) {
    //パーティクルの位置を更新
    particles[i].update();
    //パーティクルを描画
    particles[i].draw();
    //画面の端で反対側から出現するように
    particles[i].throughOffWalls();
    //OpticalFlowから力を算出してパーティクルに反映する
    if (particles[i].position.x > 0
        && particles[i].position.x < width/2
        && particles[i].position.y > 0
        && particles[i].position.y < height/2 ) {
      PVector vec = opencv.getFlowAt(int(particles[i].position.x),
                                     int(particles[i].position.y));
      particles[i].addForce(vec.mult(0.1));
    }
  }
}

//キャプチャーイベント
void captureEvent(Capture c) {
  c.read();
}

class ParticleVec3 {
  PVector position;
  PVector velocity;
  PVector acceleration;
  float friction;
  float radius;
  float mass;
  float minx, miny, minz;
  float maxx, maxy, maxz;

  ParticleVec3() {
    radius = 4.0;
    friction = 0.01;
    mass = 1.0;
    position = new PVector(width/2.0, height/2.0, 0);
    velocity = new PVector(0, 0, 0);
    acceleration = new PVector(0, 0, 0);
    minx = 0;
    miny = 0;
    minz = -height;
    maxx = width;
    maxy = height;
    maxz = height;
  }

  void update() {
    velocity.add(acceleration);
    velocity.mult(1.0 - friction);
    position.add(velocity);
    acceleration.set(0, 0, 0);
  }

  void draw() {
    pushMatrix();
    translate(position.x, position.y, position.z);
    ellipse(0, 0, radius * 2, radius * 2);
    popMatrix();
  }

  void addForce(PVector force) {
    force.div(mass);
    acceleration.add(force);
  }

  void bounceOffWalls() {
    if (position.x > maxx) {
      position.x = maxx;
      velocity.x *= -1;
    }
    if (position.x < minx) {
      position.x = minx;
      velocity.x *= -1;
    }
    if (position.y > maxy) {
      position.y = maxy;
      velocity.y *= -1;
    }
    if (position.y < miny) {
      position.y = miny;
      velocity.y *= -1;
    }
    if (position.z > maxz) {
      position.z = maxz;
      velocity.z *= -1;
    }
    if (position.z < minz) {
      position.z = minz;
      velocity.z *= -1;
    }
  }

  void throughOffWalls() {
    if (position.x < minx) {
      position.x = maxx;
    }
    if (position.y < miny) {
      position.y = maxy;
    }
    if (position.z < minz) {
      position.z = maxz;
    }
    if (position.x > maxx) {
      position.x = minx;
    }
    if (position.y > maxy) {
      position.y = miny;
    }
    if (position.z > maxz) {
      position.z = minz;
    }
  }

  void addAttractionForce(PVector force, float radius, float scale) {
    float length = PVector.dist(position, force);
    PVector diff = new PVector();
    diff = position.get();
    diff.sub(force);
    boolean bAmCloseEnough = true;
    if (radius > 0) {
      if (length > radius) {
        bAmCloseEnough = false;
      }
    }
    if (bAmCloseEnough == true) {
      float pct = 1 - (length / radius);
      diff.normalize();
      diff.mult(scale);
      diff.mult(pct);
      acceleration.sub(diff);
    }
  }
}

KML Touring – 地球上を自由に飛び回る

今回は、最終作品制作のためのヒントとして、Google Earthの「ツアー」機能を紹介します。Google Earthのツアーは、指定した地点間をなめらかにアニメーションしながらの移動を可能にします。さらにKMLファイルを記述することで、移動の軌跡やカメラアングルなど細かく指定することが可能です。さらにGPSでトラッキングしたデータを使用することで、移動した軌跡をGogole Earth上でなめらかなアニメーションで再現することが出来るようになります。

スライド資料

KML Touring – 地球上を自由に飛び回る

サンプルファイル


openFrameworks – 映像を使ったインタラクション ofxOpenCv、ofxCv

今回は、Addonの利用の2回目として、ofxOpenCvとofxCvを扱います。メディアアート作品では、カメラから取得した映像を用いてインタラクションを行う事例が沢山存在しています。映像を使ったインタラクションは、特別なセンサーを使用することなく、また鑑賞者に直接接触することなく高度なインタラクションが可能となり、多くの可能性を秘めた手法です。また、近年では映像の中から物体を認識したり、映像の中の微妙な差分や動きを検出したりといった、コンピュータ・ビジョン(Computer Vision = CV)の技術が発展し、高度な映像解析が活用できるようになりました。今回は、こうしたCVの技術の中でもオープンソースで多くの利用実績のあるOpenCVというCVのためのライブラリをopenFrameworksで活用する方法について紹介していきます。

スライド資料

サンプルファイル

最終課題について

最終課題テーマ : 「インタラクション」

外部からの何らかのアクションに対して反応する作品を制作し発表してください。openFrameworksを作品内で使用していることを必須条件にします。ただし、他のプログラムと組み合わせた作品であれば問題ありません。

ユーザーのアクションはマウスやキーボードだけとは限りません。いろいろなインタラクションの可能性について考えてみてください。

  • 映像
  • ネットワーク
  • 各種センサー
  • …etc.

Sonic Piで外部サンプルを読み込む、最終コンサートに向けて

今回は、Sonic Piに外部のサンプルを読み込む方法について紹介します。Sonic Piには楽器(Synth)を演奏する機能と、サンプリングされた音(Sample)を演奏する機能がありました。前回紹介したSonic Piでオリジナルな楽器を定義することができる方法と同様に、外部のサウンドファイルをSonic Piに取り込んで、今まで使用してきたSampleと同じように使用することが可能です。

外部のサウンドファイルが活用できるようになると、Free Soundや、Dirt-Samplesなどの外部の膨大なサンプル音源が使用できるようになり演奏の幅が大きく拡がります。

後半は1月20日に行われる最終コンサートに向けて、今後の進めかたについて説明していきます。

スライド資料

最終コンサートに向けたアンケート

最終コンサートでどのような作品を発表するかを考えて、以下のフォームに記入してください。12/20の講義開始時間を締切にします。


Processing Libraries 2 : Sound – 音の再生と視覚化

今回はProcessingのライブラリー活用の2回目として、Processingで音を扱うためのSoundライブラリーを紹介します。

Soundライブラリーは、Contributionライブラリーではなく、Processing Foundationで開発されているライブラリーで、Processingのパッケージに最初から含まれています。サウンドファイルの再生や、マイクやライン入力のキャプチャーだけでなく、音にエフェクトをかけたり、波形自体を生成したりと、サウンドに関する様々な機能が活用できます。さらに、FFTクラスを使用すると、音に含まれる周波数成分をリアルタイムに解析することが可能となります。この機能を使うことで、音の周波数成分を可視化して「音を視る」ことが可能となります。今回は、Soundライブラリーを用いたサウンドファイルの再生から、FFTを使用した音のビジュアライズまでを順番に解説していきます。

スライド資料

サンプルプログラム

// 01 サウンドの再生
import processing.sound.*;
SoundFile soundfile;

void setup() {
  soundfile = new SoundFile(this, "sound.aiff");
  soundfile.loop();
}

void draw(){
}

// 02 音量とスピードをマウスで変化
import processing.sound.*;
SoundFile soundfile;

void setup() {
  size(800, 600);
  soundfile = new SoundFile(this, "sound.aiff");
  soundfile.loop();
}

void draw() {
  background(0);
  float rate = map(mouseX, 0, width, 0.0, 2.0);
  float amp = map(mouseY, 0, height, 1.0, 0.0);
  soundfile.rate(rate);
  soundfile.amp(amp);
}

// 03 ディレイ
import processing.sound.*;
SoundFile soundfile;
Delay delay;

void setup() {
  size(800, 600);
  soundfile = new SoundFile(this, "sound.aiff");
  delay = new Delay(this);
  soundfile.loop();
  delay.process(soundfile, 5);
}

void draw() {
  background(0);
  float delayTime = map(mouseY, 0, height, 0.0001, 1.0);
  delay.time(delayTime);
  float feedback = map(mouseX, 0, width, 0.0, 0.9);
  delay.feedback(feedback);
}

//04 音量を解析
import processing.sound.*;
SoundFile soundfile;
Amplitude rms;

void setup() {
  size(800, 600);
  fill(0, 127, 255);
  noStroke();
  soundfile = new SoundFile(this, "sound.aiff");
  soundfile.loop();
  rms = new Amplitude(this);
  rms.input(soundfile);
}

void draw() {
  background(0);
  float diameter = map(rms.analyze(), 0.0, 1.0, 0.0, width);
  ellipse(width/2, height/2, diameter, diameter);
}

//05 音量を解析 (サウンド入力)
import processing.sound.*;
AudioIn input;
Amplitude rms;

void setup() {
  size(800, 600);
  fill(0, 127, 255);
  noStroke();
  input = new AudioIn(this, 0);
  input.start();
  rms = new Amplitude(this);
  rms.input(input);
}

void draw() {
  background(0);
  float diameter = map(rms.analyze(), 0.0, 1.0, 0.0, width);
  ellipse(width/2, height/2, diameter, diameter);
}

//06 FFT基本
import processing.sound.*;
AudioIn in;
FFT fft;
int bands = 1024;
float scale = 20.0;

void setup() {
  size(800, 600);
  in = new AudioIn(this, 0);
  in.start();
  fft = new FFT(this, bands);
  fft.input(in);
}

void draw() {
  background(0);
  fft.analyze();
  noFill();
  stroke(255);
  beginShape();
  for (int i = 0; i < bands; i++) {
    vertex(i * width/float(bands), height - fft.spectrum[i] * height * scale);
  }
  endShape();
}

//06 FFT : 明度で表現
import processing.sound.*;
AudioIn in;
FFT fft;
int bands = 128;
float scale = 5000.0;

void setup() {
  size(800, 600);
  noStroke();
  in = new AudioIn(this, 0);
  in.start();
  fft = new FFT(this, bands);
  fft.input(in);
}

void draw() {
  background(0);
  fft.analyze();
  float w = width/float(bands)/2.0;
  for (int i = 0; i < bands; i++) {
    fill(fft.spectrum[i] * scale);
    rect(width/2.0 + i * w, 0, w, height);
    rect(width/2.0 - i * w, 0, w, height);
  }
}

//07 FFT : 明度と色相で表現
import processing.sound.*;
AudioIn in;
FFT fft;
int bands = 128;
float scale = 5000.0;

void setup() {
  size(800, 600);
  colorMode(HSB, 360, 100, 100, 100);
  noStroke();
  in = new AudioIn(this, 0);
  in.start();
  fft = new FFT(this, bands);
  fft.input(in);
}

void draw() {
  background(0);
  fft.analyze();
  float w = width/float(bands)/2.0;
  for (int i = 0; i < bands; i++) {
    float hue = 360/float(bands) * i;
    fill(hue, 100, fft.spectrum[i] * scale);
    rect(width/2.0 + i * w, 0, w, height);
    rect(width/2.0 - i * w, 0, w, height);
  }
}


//08 FFT : 円の大きさで表現
import processing.sound.*;
AudioIn in;
FFT fft;
int bands = 128;
float scale = 40000.0;

void setup() {
  size(800, 600, P2D);
  noStroke();
  blendMode(ADD);
  colorMode(HSB, 360, 100, 100, 100);
  in = new AudioIn(this, 0);
  in.start();
  fft = new FFT(this, bands);
  fft.input(in);
}

void draw() {
  background(0);
  fft.analyze();
  float w = width/float(bands)/2.0;
  for (int i = 0; i < bands; i++) {
    float hue = 360/float(bands) * i;
    fill(hue, 100, 6);
    float diameter = fft.spectrum[i] * scale;
    ellipse(width/2.0 + i * w, height/2.0, diameter, diameter);
    ellipse(width/2.0 - i * w, height/2.0, diameter, diameter);
  }
}

//09 立方体でビジュアライズ
import processing.sound.*;
AudioIn in;
FFT fft;
int bands = 128;
float scale = 4000.0;

void setup() {
  size(800, 600, P3D);
  noStroke();
  colorMode(HSB, 360, 100, 100, 100);
  in = new AudioIn(this, 0);
  in.start();
  fft = new FFT(this, bands);
  fft.input(in);
}

void draw() {
  blendMode(ADD);
  background(0);
  fft.analyze();
  for (int i = 0; i < bands; i++) {
    float hue = map(i, 0, bands/4.0, 0, 360);
    fill(hue, 100, 100, 30);
    float boxSize = map(fft.spectrum[i], 0, 1, 0, scale);
    float rot = map(i, 0, bands, 0, PI*8);
    pushMatrix();
    translate(width/2, height/2);
    rotateX(rot+frameCount/frameRate);
    rotateY((rot+frameCount/frameRate)*1.2);
    box(boxSize);
    popMatrix();
  }
}

openFrameworks Addon 3 – Open Sound Control (OSC) によるアプリケーション間通信 ofxOsc

今回は、Addonの使用の3回目として、ネットワークを活用したインタラクションについて考えていきます。ofxOscは、openFrameworksでOpen Sound Control(OSC)という通信プロトコルを使用するためのAddonです。OSCは、openFrameworksのアプリケーション同士をEthernetを介して通信することができ、これにより、ネットワークを介して複数のユーザが1つのアプリケーションを操作することが可能となります。またopenFrameworksのアプリケーションを他のアプリケーション、例えば、Max/MSPやPd、SuperCollider、さらにはTouchOSCといったiPhoneアプリなどからコントロールすることが出来るようになります。

スライド資料

サンプルプログラム