yoppa.org


第11回: p5.js – ライブコーディング、ReCodeプロジェクト

前回に引き続きp5.jsについて取り上げます。p5.jsはWebブラウザー上で動作します。ですので、Webブラウザー上にテキストエディターの機能を加えることで編集と実行環境をオンラインで実現可能です。さらに、編集中のコードを自動的に読み込んで実行することで、コードを実行しながら編集を行うという「ライブコーディング (Live Coding)」が可能となります。オンラインで公開されているp5.jsのWebエディターでこの機能が実現されています。

今回は、このp5.jsライブコーディングの機能に着目して、その可能性を探っていきます。また、関連するプロジェクトとして過去のコンピューターアートの名作をProcessingで再現するReCode Projectについて紹介します。

SFPC short time lapse from poohlaga on Vimeo.

スライド資料


第11回 : 写真 4 – データフォトグラフィー

今回から数回にわたって、巨大なデータを用いてデータから画像を生成する「データフォトグラフィー」の実験をしていきます。

今回は、まず巨大なCSVデータをいかにしてプログラムに取り込めば良いのか、また取り込んだデータをどのように解析すると個々のデータが取り出せるのかといった基本を実際のデータを用いながら解説していきます。

スライド資料


第11回:Sonic piでサウンドプログラミング3 構造化 – イテレーション・ループ・条件分岐

引き続きSonic Piを使った音楽プログラミングについて考えていきます。

今回は、Sonic Piにおけるプログラムの構造化とデータ構造に焦点を絞って解説していきます。構造化プログラミング言語における基本的なプログラム構造は「順次」「反復」「条件分岐」の3に代表されます。Sonic Piでもこの3つのプログラム構造を作成することが可能です。実際に音に出して確認しながらSonic Piにおけるプログラムの構造化を学びます。

スライド資料

サンプルコード

# 回数を指定しての反復
live_loop :loop do
  sample :bass_trance_c
  3.times do
    sample :drum_heavy_kick
    sleep 0.125
    sample :drum_cymbal_closed
    sleep 0.125
  end
  sample :drum_snare_hard
  sleep 0.25
end

#反復のネスト 1
live_loop :live do
  4.times do
    sample :drum_heavy_kick
    2.times do
      sample :elec_blip2, rate: 2
      sleep 0.25
    end
    sample :elec_snare
    4.times do
      sample :drum_tom_mid_soft
      sleep 0.125
    end
  end
end

#反復のネスト 2
live_loop :live do
  7.times do
    sample :bass_trance_c
    3.times do
      sample :drum_heavy_kick
      sleep 0.125
      2.times do
        sample :drum_cymbal_closed
        sleep 0.125 / 2.0
      end
    end
    sample :drum_snare_hard
    sleep 0.25
  end
  8.times do
    sample :drum_cymbal_closed
    sleep 0.125
  end
end

#条件分岐
i = 0
loop do
  if i % 4 == 0 then
    sample :drum_heavy_kick
  else
    sample :drum_cymbal_closed
  end
  sleep 0.125
  i = i+1
end

#コイントス
live_loop :live do
  if one_in(2)
    sample :drum_bass_hard
  end
  sleep 0.125
end

#コイントス、else文
live_loop :live do
  if one_in(4)
    sample :drum_bass_hard
  else
    sample :drum_cymbal_closed
  end
  sleep 0.125
end

#コイントスのネスト
live_loop :live do
  if one_in(4)
    sample :drum_bass_hard
  else
    if one_in(6)
      sample :drum_snare_hard
    else
      sample :drum_cymbal_closed
    end
  end
  sleep 0.125
end

#複数のループの共存
live_loop :live do
  sample :drum_heavy_kick
  4.times do
    sample :elec_blip2, rate: 2
    sleep 1.0/8.0
  end
  sample :elec_snare
  4.times do
    sample :drum_tom_mid_soft
    sleep 0.125
  end
end

live_loop :live2 do
  sample :drum_heavy_kick
  3.times do
    sample :elec_blip2, rate: 2
    sleep 1.0/8.0
  end
  sample :elec_snare
  4.times do
    sample :drum_tom_mid_soft
    sleep 0.125
  end
end

#ピアノフェイス基本
notes = [:E4, :Fs4, :B4, :Cs5, :D5, :Fs4, :E4, :Cs5, :B4, :Fs4, :D5, :Cs5]

live_loop :reich do
  i = 0
  12.times do
    play (notes[i]), release: 0.2
    sleep 0.15
    i = i + 1
  end
end

#ピアノフェイス、位相のずれ
notes = [:E4, :Fs4, :B4, :Cs5, :D5, :Fs4, :E4, :Cs5, :B4, :Fs4, :D5, :Cs5]

live_loop :reich1 do
  i = 0
  12.times do
    play (notes[i]), release: 0.2
    sleep 0.15
    i = i + 1
  end
end

live_loop :reich2 do
  i = 0
  12.times do
    play (notes[i]), release: 0.2
    sleep 0.151
    i = i + 1
  end
end

#ピアノフェイズ、いろいろアレンンジ
notes = [:E4, :Fs4, :B4, :Cs5, :D5, :Fs4, :E4, :Cs5, :B4, :Fs4, :D5, :Cs5]

use_synth :prophet
with_fx :reverb do
  live_loop :reich1 do
    i = 0
    12.times do
      play (notes[i]), release: 0.4, pan: 0.8, cutoff: 80
      sleep 0.15
      i = i + 1
    end
  end
  
  live_loop :reich2 do
    i = 0
    12.times do
      play (notes[i])+12, release: 0.4, pan: -0.8, cutoff: 80
      sleep 0.151
      i = i + 1
    end
  end
end

第10回: Webブラウザー上で表現する – p5.js

p5js

p5.jsは、Javascript版のProcessingです。Javascriptをベースにしている最大の利点はWebブラウザー上で実行可能という点です。作成したスケッチをWebサーバー上ですぐに世界に向けて公開可能です。文法はProcessingをベースにしているので、習得も容易です。ただし、ベースとなる言語が違うため若干オリジナルのProcessingと異なる部分もあります。この差異に注意しつつ、p5.jsでのプログラミングの導入方法を解説します。

スライド資料

「MOTサテライト2018秋」展示プランのアンケート


第10回 : 写真3 – 学習する知覚、機械学習に触れてみる

「写真」シリーズの3回目として、7回目に行った「写真 2 – ライブカメラの映像を処理、コンピュータビジョン導入」の内容をさらに発展させ、機械学習を用いたコンピュータによる知覚について考えていきます。まず始めに機械学習(Machine Learning)を用いたアート作品についていろいろな実例を見た上で、openFrameworksとofxMSATensorFlowを使用して、機械学習を活用したプログラムを実際に自分のマシン上で動かしてみて理解を深めていきます。

スライド資料


第10回:Sonic piでサウンドプログラミング2 – サンプルを使う、ランダム化

Sonic Piでプログラミングによる音楽制作を行います。今回は、前回のシンセを使用した音の再生とは別の方法、サンプルを使用した音の再生を取り上げます。サンプルは、サウンドファイルを読み込んで指定したタイミングで音を再生します。前回のシンセがシンセサイザーとすると、サンプルはサンプラーに相当します。演奏するタイミングだけでなく、音量、定位、再生スピードなど様々なパラメータを設定可能です。また、Sonic Piにあらかじめ用意されたサンプルだけでなく、ファイルの場所を指定して外部ファイルを読み込むことも可能です。

今回はさらに、Sonic Piにおけるランダム (乱数) の生成とその使用方法について解説します。Sonic Piでは、単純な乱数だけでなく、指定した範囲の乱数 (rrand, rrand_i)、リストの中からランダムに選択(choose)、サイコロをふってその結果で選択(dice) など様々なランダム化の関数が用意されています。音楽への応用を考えながら、乱数を使用していきます。

スライド資料


驚きの体験と「メディアアート」

先日オープンしたteamLabの新しい「デジタルアートミュージアム」の内覧会にお邪魔してきた。

帰宅してすぐに以下のTweetをした。

今となっては、「安易に批判」というあたりはちょっと挑発的過ぎたと反省しているのだけれど、圧倒的な規模と完成度に正直感動してしまった部分は今でも変わりない。

そして、予想した通り議論を巻き起こした。永松歩さんがとても素晴しいまとめをしているのでリンクする。

個人的な観測範囲だと、teamLabに批判的な方々の意見は以下のようなものが見受けられた。

  • 批評性が無いのでアートではない
  • 規模や物量で感動するのは単純すぎる
  • 「面白い」や「驚きの体験」というだけで思考停止している
  • 子供だましで大人の鑑賞に耐えるものではない

そこから、日本のメディアアートやメディアアーティストの批評性の無さを批判するような意見も多かった。自分も最近までそうした意見の側にべったりついていて、日本のメディアアートは批評性が無いから駄目なのだという意見をけっこう鵜呑みにしていた。

しかし、昨年Ars Electronica Festivalに参加して、ちょっと考えが変化しつつある。Ars Electronicaといえばメディアアート (英語では、New Media Art) を古くから牽引している本流 (権威?) のひとつとして誰も反対する人はいないだろう。Festival期間中はコンペであるPrix Ars Electronicaの受賞展の他に、その年のテーマに沿った企画展や周囲の大学などによるサテライト展示が数多く展示される。

それらを見て回ると、確かに、社会や技術に対する批評を主眼にした作品も多い。例えば台湾の作家Lien-Cheng WangによるReading Planはとても印象に残った展示なのだが、台湾の初等教育の全体主義的な様子を強烈に皮肉ったものだった。

その一方で、まず始めに驚きや発見を提供する作品も沢山あった。Deep Space 8K で上映されるプログラムは、巨大で高精細な映像へ没入する体験を圧倒的な強度で体感できる。Kimchi and Chips による Light Barrier 3rd Edition も、鏡の反射により空間上に3Dの形状が浮かび上がるという驚きの体験だ。

もちろん、驚きの体験の背後に批評やメディア技術に対する洞察などが含まれている場合も多いのだが、鑑賞している多くの観客はそんなことよりもまず目の前の体験を重視しているように見受けられた。

そもそも、Ars Electronica Festivalの歴史を振り返ってみると、冨田勲によるドナウ川にピラミッドを浮かばせて行ったサウンドクラウドなどの大規模なスペクタクルを体験させるイベントも数多く行っている。

Ars Electronica 1984 – The Universe by Isao Tomita from ars history on Vimeo.

Ars Electronica Future Labによる大量のドローンによる作品も、個人的には現代の花火大会のように感じる。

New Media Artの歴史を辿っていくと、例えばフェナキストスコープゾートロープ は「動く絵」という驚きの体験を提供するメディアアート作品だ。衛生中継が可能となった時代にはHole-In-Space という遠隔地が巨大なホールで結ばれるという体験が作品になった。Aspen Movie Mapは、ハイパーメディアで仮想の街をインタラクティブに散策できるという驚きの体験だった。

もちろん、その背後にはメディアに対する洞察があるし、社会批評を含むものもある。しかし、多くの作品に共通するのは新しいメディアに対する「驚き」がまず制作のそもそものきっかけになっているのではないだろうか。

そうした姿勢は、いわゆる現代アートの世界からは「アートじゃない」と常に批判され下に見られてきた。しかし、Ars Electronica Centerを始めとする多くの機関やアーティストの地道な活動で、現代アートのひとつのジャンルとして認められるようになってきたのではないだろうか。

そして、teamLabの展示について改めて考えると、彼等の提供する世界は「驚き」の体験の創出のために現在使用可能なメディアを総動員して実現された、ある意味とてもNew Media Art的な作品なのかもしれないと感じるようになってきた。

さらに今回、初期の作品よりも格段に表現や内容が進歩してきている印象を受けた。おそらく多くの優秀なスタッフが新しくチームに加わってきているのだと思う。彼等も今回の展示の反応をネットでチェックしていると思うので、今後そうした批判も踏まえてさらに洗練されていきそうに感じた。

批評性を主眼においたメディアアート、驚きの体験を提供するメディアアート、どちらもメディアアートだしどちらが優れているとか上とか下とかは無いように思う。

そしてこれだけ多くの議論を巻き起している時点で、社会に対して意義のある活動をしていると感じる。


第9回 : 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);
    }
  }
}

第9回 : ライブコーディング入門 – Sonic Pi

前回は、Kode LifeでGLSLをライブコーディングしてみました。今回は音楽(音)のライブコーディングに挑戦します。

音楽のライブコーディングが可能なプログラミング言語やライブラリーは沢山存在しています。

今回は、数あるライブコーディングの環境の中でも最も導入が簡単と思われる、Sonic Piを使用してライブコーディングの入門を行います。まず始めにライブコーディングの歴史や特徴について簡単に解説した上で、Sonic Piで実際にライブコーディングを体験します。

スライド資料


第9回: Sonic piでサウンドプログラミング入門

「メディア芸術の基礎」の前半では、主にProcessingを用いたビジュアルプログラミングを行ってきました。後半はまた新たな内容をとりあげていきます。

後半の講義では、音響や音楽をプログラミングを用いて処理し表現を行います。サウンドを扱うための開発環境として「Sonic Pi」を使用します。Sonic piは、教育現場でのプログラミングや音楽の授業をサポートするように設計された、ライブコーディング可能な無料のサウンドプログラミング開発環境です。「カノンからダブステップまで」というキャッチフレーズに代表されるように、古典〜現代の音楽を作曲できる、Mac OS XやWindows、さらにはRaspberry Piでも動かすことが可能で、柔軟なプログラミングが可能です。

今回は、Sonic Piの入門として、インストールから操作方法、そしてプログラミングの基本を学んでいきます。

スライド資料