yoppa.org


Processingで画像データを扱う – 画像の分析・再合成

ProcessingのPImageクラスは、外部のビットマップ画像(Jpeg, GIF、PNGなど)をデータとしてプログラムに読み込むことができます。読み込んだ画像は単に画面に表示するだけでなく、色や明度やサイズを変更して表示することができます。さらには、画像に含まれる全てのピクセルの色情報を読み取り配列に格納することが可能です。そのデータをもとに別の画像を再生成することが可能となり、読み込んだ画像データの色情報をもとにした多彩な表現が可能となります。

今回はProcessingに画像を読み込んで、分析再合成することで、様々な表現の可能性について探っていきます。

次回までの課題

課題: Proessingに読み込んだ画像ファイルのデータで表現する!

  • PImageに読み込んだ画像ファイルの情報から、新たなイメージを生成する
  • 読み込む画像は自由

締切は次回の授業 (11/8) まで!!

スライド資料

サンプルプログラム


イントロダクション・環境設定

初回となる今回は、まず始めにこの「アンビエント・インターフェイス」で何をやっていくのか、「アンビエント」と「インターフェイス」の意味から解説し全体の概要を説明します。そして、今期のテーマとなる「地球芸術」についてそのねらいと目標を解説していきます。

後半は、実際に今回使用していくツールであるGoole Earth Proと、SketchUp Makeのインストールと使用方法の入門を行います。最終的には、SketchUpで作成した3DモデルをGoogle Earthに配置して地球規模の彫刻制作の第一歩に挑戦します。

スライド資料


Webサービスを利用する

今回の講義の前半は、前回の課題、オンラインポートフォリオの表紙ページの制作の講評を行います。作成したページをサーバーにアップロードして、1人ずつ制作したページをみていきます。

後半は、いろいろなWebサービスの利用方法について解説します。写真、動画、音楽などのデータはWebサーバーにアップロードして貼り付けることもできますが、Webサービスを利用するとより簡易に利用することができます。数多くあるWebサービスの中から、今回は、写真共有のためのFlickr、動画共有のためのVimeo、音楽共有のためのSoundCloudを紹介します。

スライド資料


openFrameworks + OOP – オブジェクト指向プログラミング入門

これまでのopenFrameworksのプロジェクトは、ofApp.h と ofApp.cpp という2つのファイルに全てのプログラムを記述してきました。しかし、この方法では徐々にプロジェクトが複雑になり巨大化するうちに、扱いが困難になってきます。プログラミングをわかりやすく保つには、役割ごとに内容を分割して記述すべきです。openFrameworksの元となるプログラミング言語であるC++では「オブジェクト」という単位でプログラムを構造化していきます。このオブジェクトを基本単位にしたプログラミング手法のことを「オブジェクト指向プログラミング (Object Oriented Programing = OOP)」と呼びます。OOPはC++だけでなく、Java、Python、Ruby、C#、Objective-C、Swiftなどでも利用されていて、現在のプログラミング言語の主流となっているパラダイムです。

今回は、このOOPをopenFrameworksで実現する方法を「生成的な形態を生成する」というテーマに沿って徐々に発展させていきます。今回の内容が今後のより本格的な作品制作のための重要なテクニックとなっていきます。しっかり理解していきましょう。

スライド資料

サンプルコード


コンピューターで音を扱う、SuperCollider入門

ここまでの講義は、Sonic Piを用いて音楽の構造をプログラミングしてきました。今回からは、よりミクロなスケールに入っていきます。SuperColliderというコンピュータ音楽言語を使用して、音の波形そのものをプログラムで生成する方法について紹介します。

前半は、SuperColliderで実際に音を生成する前に、コンピューターで音を扱うということは一体何をしているのかを理解していきます。まずはそもそも音とは何か、どうしたら音をコンピュータで扱うことができるのかといった基本的な部分から、波形、周波数(Hz)、サンプリングと量子化、ADCとDACといった基本的な事項について学んでいきます。

後半は、いよいよSuperColliderを使用していきます。まず、アプリケーションの入手方法から、基本操作、画面構成などを説明します。その後で、SuperColliderの言語の文法の基礎について実際に音を出しながら学んでいきます。

スライド資料

サンプルプログラム

// ---------------------------------------------------------------------
// SuperCollider Basics
// ---------------------------------------------------------------------

// Introduction
{SinOsc.ar()}.play
{SinOsc.ar(220)}.play
{SinOsc.ar(220, 0, 0.8)}.play
{SinOsc.ar([220, 220], 0, 0.8)}.play
{SinOsc.ar([220, 221], 0, 0.8)}.play
{Saw.ar([220, 221])}.play
{RLPF.ar(Saw.ar([220, 221]))}.play
{RLPF.ar(Saw.ar([220, 221]), 1200)}.play
{RLPF.ar(Saw.ar([220, 221]), MouseX.kr(80, 10000, 1))}.play
{RLPF.ar(Saw.ar([220, 221]), MouseX.kr(80, 10000, 1), MouseY.kr(0.1, 1.2))}.play
{RLPF.ar(Saw.ar([80, 80.2]), MouseX.kr(80, 10000, 1), MouseY.kr(0.1, 1.2))}.play
{GVerb.ar(RLPF.ar(Saw.ar([80, 120]), MouseX.kr(80, 10000, 1), MouseY.kr(0.1, 1.2)))}.play

// Basics
{SinOsc.ar(LFNoise0.kr([10,12], mul: 600, add: 1000), 0, 0.3)}.play

{RLPF.ar(Dust.ar([12, 15]), LFNoise1.ar(1/[3, 8], 1500, 1600), 0.01, 8.0)}.play

{
    RLPF.ar(
        Dust.ar(
            [12, 15]
        ),
        LFNoise1.ar(
            1/[3, 8],
            1500,
            1600
        ),
        0.01,
        8.0
    )
}.play


// Enclosures

// [...] = Array, List
[0, 11, 10, 1, 9, 8, 2, 3, 7, 4, 6, 5].reverse
12 - [0, 11, 10, 1, 9, 8, 2, 3, 7, 4, 6, 5].reverse
[0, 2, 4, 5, 6, 7, 9, 11].scramble
[60, 62, 64, 67, 69].mirror
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].rotate
[60, 62, 64, 65, 67, 69, 71].midicps.round(0.1)
[1, 0.75, 0.5, 0.25, 0.125].choose
0.125 * [1, 2, 3, 4, 5, 6, 7, 8].choose
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].permute(6)

{Blip.ar(25, LFNoise0.kr(5, 12, 14), 0.3)}.play
{Blip.ar(25, LFNoise0.kr([5, 10], 12, 14), 0.3)}.play
{Blip.ar(25, LFNoise0.kr([5, 10, 2, 25], 12, 14), 0.3)}.play
{Blip.ar(25, LFNoise0.kr([5, 4, 7, 9, 5, 1, 9, 2], 12, 14), 0.3)}.play

// {...} = Function
dup(rand(1000.0), 5)
dup({rand(1000.0)}, 5)

exprand(1.0, 1000.0)
dup({exprand(1.0, 1000.0)}, 100)
sort(dup({exprand(1.0, 1000.0)}, 100))
round(sort(dup({exprand(1.0, 1000.0)}, 100)), 0.01)

{LFNoise0.ar}.play
{LFNoise0.ar(10000)}.plot
{LFNoise0.ar(10000)}.scope
{100.rand}.dup(10)
{100.rand} ! 10
{100.rand}.dup(10).postln.plot
{100.rand}.dup(100).sort.plot

// (...) = Message and Arguments
rand(100)
exprand(1.0, 100.0)
SinOsc.ar(arglist)
Mix.fill(arglist)
dup("echo", 20)
round([3.141, 5.9265, 358.98], 0.01)
sort([23, 54, 678, 1, 21, 91, 34, 78])
round(dup({exprand(1, 10)}, 100), 0.1)
sort(round(dup({exprand(1, 10)}, 100), 0.1))

// Receiver
[45, 13, 10, 498, 78].sort
"echo".dup(20)
50.midicps
444.cpsmidi
100.rand
{100.rand}.dup(50)
[1.001, 45.827, 187.18].round(0.1)
"I've just picked up a fault in the AE35 unit".speak

// Nesting
1000.0
1000.0.rand
1000.0.rand.round(0.01)
1000.0.rand.round(0.01).post
{1000.0.rand.round(0.01).postln}.dup(100).plot
{1000.0.rand.round(0.01).postln}.dup(100).postln.sort.plot
1000.0.rand.round(0.01).postln.asString.speak

// Ugen
LFNoise1.kr(10,100)
{SinOsc.ar([440,442])}.play

//---------------------------------------------------------
//
// SC 140
// "http://supercollider.github.io/community/sc140"
//
//---------------------------------------------------------

01
Nathaniel Virgo
{LocalOut.ar(a=CombN.ar(BPF.ar(LocalIn.ar(2)*7.5+Saw.ar([32,33],0.2),2**LFNoise0.kr(4/3,4)*300,0.1).distort,2,2,40));a}.play//#supercollider

02
LFSaw
{Splay.ar(Ringz.ar(Impulse.ar([2, 1, 4], [0.1, 0.11, 0.12]), [0.1, 0.1, 0.5])) * EnvGen.kr(Env([1, 1, 0], [120, 10]), doneAction: 2)}.play

03
Tim Walters
play{({|k|({|i|y=SinOsc;y.ar(i*k*k,y.ar(i*k**i/[4,5])*Decay.kr(Dust.kr(1/4**i),y.ar(0.1)+1*k+i,k*999))}!8).product}!16).sum}//#supercollider

04
Nathaniel Virgo
b=Buffer.read(s,"sounds/a11wlk01.wav");play{t=Impulse.kr(5);PlayBuf.ar(1,b,1,t,Demand.kr(t,0,Dseq(1e3*[103,41,162,15,141,52,124,190],4)))!2}

05
Batuhan Bozkurt
play{f=LocalIn.ar(2).tanh;k=Latch.kr(f[0].abs,Impulse.kr(1/4));LocalOut.ar(f+CombC.ar(Blip.ar([4,6],100*k+50,0.9),1,k*0.3,50*f));f}//44.1kHz

06
Batuhan Bozkurt (refactored by Charles Celeste Hutchins)
f={|t|Pbind(\note,Pseq([-1,1,6,8,9,1,-1,8,6,1,9,8]+5,319),\dur,t)};Ptpar([0,f.(1/6),12,f.(0.1672)],1).play//#supercollider reich RT @earslap

07
Thor Magnusson
play{x=SinOsc;y=LFNoise0;a=y.ar(8);(x.ar(Pulse.ar(1)*24)+x.ar(90+(a*90))+MoogFF.ar(Saw.ar(y.ar(4,333,666)),a*XLine.ar(1,39,99,99,0,2)))!2/3}

08
Charlie Hoistman
Ptpar(({|i|[i*8,Pbind(\scale,[0,2,4,7,9],\degree,Pseq(32.fib.fold(0,10),4)+(2*i+i)-10,\dur,1+2**i%2/6)]}!4).flat).play // #supercollider

09
MCLD
{LocalOut.ar(a=DynKlank.ar(`[LocalIn.ar.clip2(LFPulse.kr([1,2,1/8]).sum/2)**100*100],Impulse.ar(10)));HPF.ar(a).clip2}.play//

10
Julian Rohrhuber
/*eclecticity*/ Ndef(\x, { SinOsc.ar(BrownNoise.ar(30!2, 200), Ndef(\x).ar * LFNoise1.kr(1!2,1,1)) }).play;

11
Micromoog
play{VarSaw.ar((Hasher.ar(Latch.ar(SinOsc.ar((1..4)!2),Impulse.ar([5/2,5])))*300+300).round(60),0,LFNoise2.ar(2,1/3,1/2))/5}//#supercollider

12
Jose Padovani
play{x=165;b=SinOsc;p=Trig.ar(Saw.ar(x),1);y=b.ar(p*x);z=b.ar(p);(GVerb.ar(GrainIn.ar(2,y,y/2,z,p*z,-1),9))/9}//basso gettato #SuperCollider

13
Batuhan Bozkurt
play{LeakDC.ar(BRF.ar(Saw.ar(8,Decay2.kr(x=Duty.kr(1/8,0,Drand([0,Drand((0.4,0.5..1))],inf)),0.01,0.3))**1.5,x*20+[45.1,45],0.1)).tanh}//#sc

14
Nathaniel Virgo
Ndef('x',{x=Ndef('x').ar+0.01;a=BPF.ar(x,6**Latch.ar(x,Dust.ar(x))*200,0.1).sin;9.do{a=AllpassN.ar(a,0.2,{0.2.rand}!2,9)};a+a.mean}).play;

15
Jason Dixon
{x=Array.fill(5,{[0.00001,0.03].asSpec.map(LFNoise2.kr(3))});Splay.ar(Friction.ar(LFTri.ar(50),friction:x,mass:x*30000))}.play

16
Batuhan Bozkurt
play{AllpassC.ar(SinOsc.ar(55).tanh,0.4,TExpRand.ar(2e-4, 0.4,Impulse.ar(8)).round([2e-3,4e-3]),2)};// #supercollider with bass please...

17
redFrik
{RHPF.ar(GbmanN.ar([2300,1150]),LFSaw.ar(Pulse.ar(4,[1,2]/8,1,LFPulse.ar(1/8)/5+1))+2)}.play //punk (loud!)

18
Nathaniel Virgo
play{p=PinkNoise.ar(1!2);BRF.ar(p+Blip.ar(p+2,400),150,2,0.1)+LPF.ar(FreeVerb2.ar(*LPF.ar(p+0.2*Dust.ar(0.1),60)++[1,1,0.2,1e4]).tanh,2000)}

19
MCLD
{a=[0.02,0.1,1,2,3,4]; k=LFPar.kr(a+0.5).sum; f=Latch.kr(k,Impulse.kr(a)); Splay.ar(SinOsc.ar(f*100+300)/5)}.play // #supercollider

20
Sciss
play{2.collect{RecordBuf.ar(Limiter.ar(HPF.ar(Convolution2.ar(k=Crackle.ar(l=Line.kr(1,2,90)),b=LocalBuf(2048),Dust.kr(4)),8)+k)*(2-l),b)}}

21
Andrea Valle
{13.do{|i|k="SuperCollider"[i].ascii;20.do{|u|{MoogFF.ar(Saw.ar((k/4).midicps)*EnvGen.ar(Env.perc),u+k*9,k/30)}.play;(k*0.001).wait}}}.fork

22
MCLD
play{a=Duty.kr(0.1,0,Dseq(fib(32).wrap(20,55).midicps,inf));HPF.ar(LeakDC.ar(Splay.ar(LFCub.ar([-1,a,a/3,-2])))*9).clip2/9};//#supercollider

生成的な形をつくる – Processing オブジェクト指向プログラミング入門

今回はProcessingで生成的(Generative)な形態を生みだすにはどうすればよいのか、試行錯誤しながら実験していきます。まず初めに、コードを用いた生成的な表現の実例をいくつか紹介した後、実際にProcessingでプログラミングしていきます。

まず始めに、完全にランダムな確率で動きまわる「ランダムウォーク」な動きをする点の動きをつくり、その軌跡を描いてみます。次にこのランダムな動きを増殖させていきます。増殖の際に今回は全てを一つのプログラムに書くのではなく、それぞれの点を細かなプログラムで実装し、その小さなプログラム達を組合せることで一つの機能を生みだすような設計にします。この小さなプログラムを「オブジェクト (Object)」と呼び、オブジェクトを構成単位にしてプログラムを作成していく手法を、オブジェクト指向プログラミング (OOP) と呼びます。このOOPの考え方は今後も重要な内容となってきますので、実例を通して確実に理解していきましょう。

スライド資料

サンプルコード

サンプル1: 先週の復習

PVector location;
PVector velocity;
void setup() {
  size(800, 600);
  frameRate(60);
  location = new PVector(width/2, height/2);
  velocity = new PVector(random(-10, 10), random(-10, 10));
}

void draw() {
  background(0);

  location.add(velocity);

  noStroke();
  fill(0, 127, 255);
  ellipse(location.x, location.y, 20, 20);

  if (location.x < 0 || location.x > width) {
    velocity.x = velocity.x * -1;
  }
  if (location.y < 0 || location.y > height) {
    velocity.y = velocity.y * -1;
  }
}

サンプル2: ランダムウォーク基本

PVector location;
PVector velocity;
void setup() {
  size(800, 600);
  frameRate(60);
  location = new PVector(width/2, height/2);
}

void draw() {
  background(0);
  velocity = new PVector(random(-1, 1), random(-1, 1));
  location.add(velocity);

  noStroke();
  fill(0, 127, 255);
  ellipse(location.x, location.y, 20, 20);
}

サンプル3: ランダムウォークによる軌跡

PVector location;
PVector velocity;
void setup() {
  size(800, 600);
  frameRate(60);
  location = new PVector(width/2, height/2);
  velocity = new PVector();
  background(0);
}

void draw() {
  velocity.x = random(-1, 1);
  velocity.y = random(-1, 1);
  location.add(velocity);
  noStroke();
  fill(0, 127, 255);
  ellipse(location.x, location.y, 2, 2);
}

サンプル4: ランダムウォークによる軌跡 (10倍速)

PVector location;
PVector velocity;
void setup() {
  size(800, 600);
  frameRate(60);
  location = new PVector(width/2, height/2);
  velocity = new PVector();
  background(0);
}

void draw() {
  for (int i = 0; i < 10; i++) {
    velocity.x = random(-1, 1);
    velocity.y = random(-1, 1);
    location.add(velocity);
    noStroke();
    fill(0, 127, 255, 31);
    ellipse(location.x, location.y, 2, 2);
  }
}

サンプル5: ランダムウォーク・オブジェクト

Walker walker;

void setup() {
  size(800, 600);
  frameRate(60);
  
  walker = new Walker();
  
  background(0);
}

void draw() {
  walker.draw();
}

class Walker {
  PVector location;
  PVector velocity;

  Walker() {
    location = new PVector(width/2, height/2);
    velocity = new PVector();
  }

  void draw() {
    for (int i = 0; i < 10; i++) {
      velocity.x = random(-1, 1);
      velocity.y = random(-1, 1);
      location.add(velocity);
      noStroke();
      fill(0, 127, 255, 31);
      ellipse(location.x, location.y, 2, 2);
    }
  }
}

サンプル6: ランダムウォーク・オブジェクトの配列 (10個)

int NUM = 10;
Walker[] walker = new Walker[NUM];

void setup() {
  size(800, 600);
  frameRate(60);

  for (int i = 0; i < NUM; i++) {
    walker[i] = new Walker();
  }

  background(0);
}

void draw() {
  for (int i = 0; i < NUM; i++) {
    walker[i].draw();
  }
}

class Walker {
  PVector location;
  PVector velocity;

  Walker() {
    location = new PVector(width/2, height/2);
    velocity = new PVector();
  }

  void draw() {
    for (int i = 0; i < 10; i++) {
      velocity.x = random(-1, 1);
      velocity.y = random(-1, 1);
      location.add(velocity);
      noStroke();
      fill(0, 127, 255, 15);
      ellipse(location.x, location.y, 2, 2);
    }
  }
}

サンプル6: ランダムウォーク・オブジェクトの配列 (100個)

int NUM = 100;
Walker[] walker = new Walker[NUM];

void setup() {
  size(800, 600);
  frameRate(60);

  for (int i = 0; i < NUM; i++) {
    walker[i] = new Walker();
  }

  background(0);
  blendMode(ADD);
}

void draw() {
  for (int i = 0; i < NUM; i++) {
    walker[i].draw();
  }
}

class Walker {
  PVector location;
  PVector velocity;

  Walker() {
    location = new PVector(width/2, height/2);
    velocity = new PVector();
  }

  void draw() {
    for (int i = 0; i < 10; i++) {
      velocity.x = random(-1, 1);
      velocity.y = random(-1, 1);
      location.add(velocity);
      noStroke();
      fill(0, 127, 255, 5);
      ellipse(location.x, location.y, 2, 2);
    }
  }
}

サンプル7: ランダムウォーク・オブジェクトの配列 (2000個 + 画面効果)

int NUM = 2000;
Walker[] walker = new Walker[NUM];

void setup() {
  size(1920, 1200, P3D);
  frameRate(60);
  noCursor();

  for (int i = 0; i < NUM; i++) {
    walker[i] = new Walker();
  }

  background(0);
}

void draw() {
  blendMode(BLEND);
  fill(0, 15);
  rect(0, 0, width, height);
  for (int i = 0; i < NUM; i++) {
    walker[i].draw();
  }
}

class Walker {
  PVector location;
  PVector velocity;

  Walker() {
    location = new PVector(width/2, height/2);
    velocity = new PVector();
  }

  void draw() {
    for (int i = 0; i < 4; i++) {
      velocity.x = random(-1, 1);
      velocity.y = random(-1, 1);
      location.add(velocity);
      noStroke();
      blendMode(ADD);
      fill(15, 63, 255);
      ellipse(location.x, location.y, 2, 2);
    }
  }
}

動きを生みだす – アニメーションとベクトル

今回からいよいよ動きのある表現(= アニメーション)について扱っていきます。アニメーションを実現するには、まず時間を扱う基本構造を知る必要があります。Processingでは、setup(), draw() という2つのブロックにわけて、初期化と更新を行うことでアニメーションを実現しています。まず始めはこの基本構造について理解します。次に、これから動きを扱う際に、向きと大きさをもった「ベクトル」という概念を理解します。ベクトルを理解することで、位置や運動を整理して記述することが可能となります。最後に、この基本構造をベクトルを活用して簡単なアニメーションを作成します。

スライド資料

サンプルコード

サンプルコード 1

//初期化関数
void setup() {
  size(800, 600);
  frameRate(12);   //書き換え頻度の設定
  background(0);
}
//メインループ
void draw() {
  float diameter = random(100);
  noStroke();
  fill(random(255), random(255), random(255));
  ellipse(random(width), random(height), diameter, diameter);
}

サンプルコード 2

float locationX, locationY; //円の中心位置を格納する変数
float velocityX, velocityY; //円の速度を格納する変数
void setup() {
    size(800, 600); //800x600 pixelの画面を生成
    frameRate(60); //フレームレート
    locationX = width/2; //円の初期位置X
    locationY = height/2; //円の初期位置Y
    velocityX = 3; //円の初期位置X
    velocityY = 2; //円の初期位置Y
}

void draw() {
    background(0); //背景を描画
    locationX = locationX + velocityX; //円のX座標を更新
    locationY = locationY + velocityY; //円のY座標を更新
    noStroke(); //枠線なし
    fill(0, 127, 255); //塗りの色
    ellipse(locationX, locationY, 20, 20); //指定した位置に円を描画
}

サンプルコード 3

float locationX, locationY; //円の中心位置を格納する変数
float velocityX, velocityY; //円の速度を格納する変数

void setup() {
    size(800, 600); //800x600 pixelの画面を生成
    frameRate(60);  //フレームレート
    locationX = width/2; //円の初期位置X
    locationY = height/2; //円の初期位置Y
    velocityX = 3;  //円の初期位置X
    velocityY = 2;  //円の初期位置Y
}

void draw() {
    background(0); //背景を描画
    locationX = locationX + velocityX; //円のX座標を更新
    locationY = locationY + velocityY; //円のY座標を更新
    noStroke(); //枠線なし
    fill(0, 127, 255); //塗りの色
    ellipse(locationX, locationY, 20, 20); //指定した位置に円を描画

    if (locationX < 0 || locationX > width) { //もし画面の左端、または右端に到達したら
        velocityX = velocityX * -1; //X方向のスピードを反転
    }
    if (locationY < 0 || locationY > height) { //もし画面の下端、または上端に到達したら
        velocityY = velocityY * -1; //Y方向のスピードを反転
    }
}

サンプルコード 4

PVector location; //円の中心位置を格納する変数
PVector velocity; //円の速度を格納する変数
void setup() {
    size(800, 600); //800x600 pixelの画面を生成
    frameRate(60); //フレームレート
    location = new PVector(random(width), random(height));
    velocity = new PVector(random(-10, 10), random(-10, 10));
}

void draw() {
    background(0); //背景を描画
    location.add(velocity); //位置のベクトルに速度のベクトルを加算、次の位置になる

    noStroke(); //枠線なし
    fill(0, 127, 255); //塗りの色
    ellipse(location.x, location.y, 20, 20); //指定した位置に円を描画
    if (location.x < 0 || location.x > width) { //もし画面の左端、または右端に到達したら
        velocity.x = velocity.x * -1; //X方向のスピードを反転
    }
    if (location.y < 0 || location.y > height) { //もし画面の下端、または上端に到達したら
        velocity.y = velocity.y * -1; //Y方向のスピードを反転
    }
}

サンプルコード 5

int NUM = 100; //配列の数
//位置のベクトルの配列
PVector[] location = new PVector[NUM];
//速度のベクトルの配列
PVector[] velocity = new PVector[NUM];

void setup() {
  size(800, 600); //800x600pixelの画面を生成
  frameRate(60); //フレームレート
  noStroke(); //枠線なし
  fill(0, 127, 255); //塗りの色
  for (int i = 0; i < NUM; i++) { //配列の数だけ繰り返し
    //位置のベクトルの初期設定
    location[i] = new PVector(random(width), random(height));
    //速度のベクトルの初期設定
    velocity[i] = new PVector(random(-4, 4), random(-4, 4));
  }
}

void draw() {
  background(15); //背景を描画
  for (int i = 0; i < NUM; i++) { //配列の数だけ繰り返し
    //指定した位置に円を描画
    ellipse(location[i].x, location[i].y, 20, 20);
    //位置のベクトルに速度のベクトルを加算、次の位置になる
    location[i].add(velocity[i]);
    //もし画面の左端、または右端に到達したら
    if ((location[i].x > width) || (location[i].x < 0)) {
      velocity[i].x *= -1; //X方向のスピードを反転
    }
    //もし画面の下端、または上端に到達したら
    if ((location[i].y > height) || (location[i].y < 0)) {
      velocity[i].y *= -1; //Y方向のスピードを反転
    }
  }
}

サンプルコード 6

int NUM = 500; //配列の数
//位置のベクトルの配列
PVector[] location = new PVector[NUM];
//速度のベクトルの配列
PVector[] velocity = new PVector[NUM];
//塗りの色の配列
color[] col = new color[NUM];
//円の大きさ(直径)の配列
float[] diameter = new float[NUM];

void setup() {
  size(800, 600); //800x600pixelの画面を生成
  frameRate(60); //フレームレート
  noStroke();
  for (int i = 0; i < NUM; i++) { //配列の数だけ繰り返し
    //位置のベクトルの初期設定
    location[i] = new PVector(random(width), random(height));
    //速度のベクトルの初期設定
    velocity[i] = new PVector(random(-4, 4), random(-4, 4));
    //色の初期設定
    col[i] = color(random(255), random(255), random(255), 192);
    //大きさの初期設定
    diameter[i] = random(3, 40);
  }
}

void draw() {
  background(15); //背景を描画
  //配列の数だけ繰り返し
  for (int i = 0; i < NUM; i++) {
    fill(col[i]); //色を指定
    //指定した位置に円を描画
    ellipse(location[i].x, location[i].y, diameter[i], diameter[i]);
    //位置のベクトルに速度のベクトルを加算、次の位置になる
    location[i].add(velocity[i]);
    //もし画面の左端、または右端に到達したら
    if ((location[i].x > width) || (location[i].x < 0)) {
      velocity[i].x *= -1; //X方向のスピードを反転
    }
    //もし画面の下端、または上端に到達したら
    if ((location[i].y > height) || (location[i].y < 0)) {
      velocity[i].y *= -1; //Y方向のスピードを反転
    }
  }
}

openFrameworksアニメーションの基本

今回は、openFrameworksで図形を動かす「アニメーション」について、その基本を学んでいきます。まず始めに、位置と速度をベクトル(ofVec2f)で定義して、ベクトルの演算によって動きを生みだす方法について解説します。次に、そのベクトルを配列(Array)で表現することで、複数の物体を同時に動かしてみます。

物体の上限の数がプログラムを起動する段階で決まっていない場合もあります。そのような際には、配列の要素の数を動的に変更可能な可変長配列(std::vector)を使用すると便利です。可変長配列を使用してアニメーションする物体の上限数を可変にする方法を実際のサンプルを通して紹介していきます。

最後に簡単なインタラクションを実装してみます。マウスで画面上をクリックすると、クリックした場所から次々と物体が生成されアニメーションさせます。

スライド資料

サンプルコード

Example 1. アニメーションの基本

Example 2. 物体の数を増やしてみる – 配列とくりかえし

Example 3. 数の上限がわからない時には? – 可変長配列vectorを使う

Example 4. 一定の数に制限する

Example 5. マウスをドラッグした位置から物体を生成


CSSのボックスモデル、Twitter Bootstrapを使う

今回はさらにCSSによるWebデザインについて詳しく探求していきます。

前半は、CSSの「ボックスモデル」について取り上げます。Webページでは、文書内のすべての要素は、ボックスと呼ばれる四角形の領域を生成します。この四角形の領域の余白や境界線に関する共通の属性が決められていて、これをCSS「ボックスモデル」と呼んでいます。このボックスモデルを理解して適切に使用することで、余白やレイアウトの自由度が生まれ、Webページのデザインの可能性が飛躍的に向上します。

後半は、より本格的なCSSによるデザインの導入を行います。CSSを用いた凝ったページデザインをしようと思うと、徐々にCSSの記述量が増えていき最終的に膨大な分量をCSS作成する必要が出てきます。しかし、テキストの段組やボタンやアイコンなどのパーツなど、CSSを用いたデザインの多くの部分で共通する枠組みが存在します。こうした枠組みをまとめた「CSSフレームワーク」が存在していて、ネット上でフリーで公開されています。こうしたCSSフレームワークの中で利用者の多い「Twitter Bootstrap」を取り上げ、フレームワークを利用した本格的なWebデザインを体験します。

スライド資料


Sonic Pi 実践1 : 構造化 – イテレーション・ループ・条件分岐、データ構造 – リスト・和音・音階

今日の内容

  • より高度なSonic Piのプログラミング
  • プログラムの構造
    • ブロック
    • イテレーション・ループ
    • 条件分岐
  • 和音と旋律
    • 和音 chord の使用
    • 音階 scale の使用

スライド資料

Sonic Piでプログラムの構造を作る

  • 構造化プログラミング、3つの構成要素
  • 順次 (Sequence)、反復 (Iteration)、分岐 (Selection)

たとえば、Processingだと…

// 順次
fill(31, 127, 255)
ellipse(widht/2, height/2, 100, 100);

// 反復
for(int i = 0; i < 100; i++){
  ...
}

//分岐
if(position.x > 100){
  ...
}

今までSonic Piで扱ってきたのは、順次と (単純な)反復

loop do
  use_synth :fm
  freq = choose([ 60, 67 ])
  play freq
  play freq + choose([ 0, 4, 5, 7])
  play freq + choose([ 0, 4, 5, 7])
  play freq + choose([-12, 0, 12])
  sleep choose([ 0.25, 0.5])
end
  • より複雑な反復のパターンを理解
  • 回数を指定した反復、反復のネスト

反復

回数を指定した反復

3.times do
  play 50
  sleep 0.5
  sample :elec_blup
  sleep 0.5
  play 62
  sleep 0.25
end

反復のネスト(入れ子構造)

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

今まで使用してきた「loop」や「live-loop」は無限ループだった

loop 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
  4.times do
    sample :drum_heavy_kick
    2.times do
      sample :elec_blip2, rate: 2
      sleep 0.25
    end
  end
end

条件分岐

  • 条件分岐
  • Sonic Piでもif文が使える

条件分岐の例

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

  • if文と、one_in() を組合せる
    • one_in(N) : N回に1回の割合で発生させるランダム
    • サイコロを振る感覚

if と one_in のサンプル

loop do
  if one_in(3)
    sample :drum_heavy_kick
  else
    sample :drum_cymbal_closed
  end
  sleep 0.125
end

if と one_in のサンプル 2

loop do
  if one_in(3)
    sample :drum_heavy_kick
    sleep 0.25
  else
    sample :drum_cymbal_closed
    sleep 0.125
  end
end

実習1

  • 反復を利用して、かっこいいリズムパターンを組んでみる
    • 反復とsleepによる微妙なニュアンス
    • 条件分岐とone_inによるゆらぎ
    • loopとlive_loop、どちらを使ったほうがやりやすいか?

例 : 複数のリズムの共存

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

Sonic Piのデータ構造

  • リスト(list)を使用してみる
  • リスト: データの集合、配列のようなもの
  • 前回、chooseを使用した際に既に使用していた

chooseとリスト

loop do
  play choose([:c, :f, :g, :a, :b])
  sleep 0.25
end

  • 一度に演奏
play [52, 55, 59]
  • オプションを指定して一度に演奏
play [52, 55, 59], amp: 0.3
  • 音階で指定
play [:E3, :G3, :B3]

リストの要素にアクセス (55が演奏される)

loop do
  play [52, 55, 59][1]
  sleep 0.25
end

和音

  • chordを使用すると、コードネームを使用して和音を生成できる
  • 様々な複雑な和音が用意されている
play chord(:C4, :major)

いろいろなコードが定義されているので、試してみる!

play chord(:C4, :major)
sleep 1.0
play chord(:C4, :major7)
sleep 1.0
play chord(:C4, :minor)
sleep 1.0
play chord(:C4, :minor7)
sleep 1.0
play chord(:C4, :dim)
sleep 1.0
play chord(:C4, :dim7)
sleep 1.0
play chord(:C4, :sus2)
sleep 1.0
play chord(:C4, :sus4)
sleep 1.0
play chord(:C4, :augmented)
sleep 1.0

  • playの代わりに play_pattern_timed を使うとリストをアルペジオ演奏できる
  • play_pattern_timed コードのリスト, 速さ
  • 例: Cメジャーを、0.25のタイミングで
play_pattern_timed chord(:C4, :major), 0.25

いろいろなアルペジオを試してみる

loop do
  play_pattern_timed chord(:C4, :major), 0.25
  sleep 0.5
  play_pattern_timed chord(:C4, :major7), 0.25
  sleep 0.5
  play_pattern_timed chord(:C4, :minor), 0.25
  sleep 0.5
  play_pattern_timed chord(:C4, :minor7), 0.25
  sleep 0.5
  play_pattern_timed chord(:C4, :dim), 0.25
  sleep 0.5
  play_pattern_timed chord(:C4, :dim7), 0.25
  sleep 0.5
  play_pattern_timed chord(:C4, :add9), 0.25
  sleep 0.5
  play_pattern_timed chord(:C4, :add13), 0.25
  sleep 0.5
end

音階(scale)

  • Sonic Piでは、和音と同様に様々な音階(scale)が定義されている
  • 例:「ドレミファソラシド」が演奏される → メジャースケール
loop do
  play_pattern_timed scale(:C4, :major), 0.25
end

いろいろな音階(scale)を試してみる

loop do
  play_pattern_timed scale(:C4, :major), 0.25
  sleep 0.25
  play_pattern_timed scale(:C4, :minor), 0.25
  sleep 0.25
  play_pattern_timed scale(:C4, :aeolian), 0.25
  sleep 0.25
  play_pattern_timed scale(:C4, :ahirbhairav), 0.25
  sleep 0.25
  play_pattern_timed scale(:C4, :augmented), 0.25
  sleep 0.25
  play_pattern_timed scale(:C4, :augmented2), 0.25
  sleep 0.25
  play_pattern_timed scale(:C4, :bartok), 0.25
  sleep 0.25
end

chooseとchord、chooseとscaleを組み合わせる

  • ランダムな選択と強い制約
  • 制限された調整の中でのランダムが簡単に実現できる

例1: マイナーコード (短三和音) から音を1つ選ぶ

loop do
  play choose(chord(:C4, :minor))
  sleep 0.25
end

例2: メジャーコード (長三和音) から音を1つ選ぶ

loop do
  play choose(chord(:C4, :major))
  sleep 0.25
end

スケールを使用した即興

use_synth :prophet
live_loop :live do
  play choose(scale(:c3, :minor, num_octaves: 3)), cutoff: rrand(60, 100)
  play choose(scale(:c4, :minor, num_octaves: 3)), cutoff: rrand(60, 100)
  play choose(scale(:c5, :minor, num_octaves: 3)), cutoff: rrand(60, 100)
  sleep 0.25
end

スケールを書き換えて聞き比べてみる

use_synth :prophet
live_loop :live do
  play choose(scale(:c3, :lydian, num_octaves: 3)), cutoff: rrand(60, 100)
  play choose(scale(:c4, :lydian, num_octaves: 3)), cutoff: rrand(60, 100)
  play choose(scale(:c5, :lydian, num_octaves: 3)), cutoff: rrand(60, 100)
  sleep 0.25
end

複数の楽器で + さらにパラメータを加えてみる

live_loop :live do
  use_synth :prophet
  cut = rrand(20, 120)
  6.times do
    play choose(scale(:c2, :aeolian, num_octaves: 5)), cutoff: cut, pan: rrand(-1, 1)
  end
  use_synth :chiplead
  4.times do
    play choose(scale(:c3, :aeolian, num_octaves: 5)), cutoff: cut, pan: rrand(-1, 1), amp: 0.7
  end
  sleep 0.25
end

参考: 音階 (モード) による即興

次回までの課題!

  • これまで紹介してきたSonic Piの機能を活用して、ループする旋律を作曲する
    • 楽器(synth)とサンプル(sample)
    • 演奏(play)とパラメータ(amp, pan, atack, release, cutoff…)
    • 乱数(rrand, rrand_i, choose, one_in)
    • リスト(list)
    • 和音(chord)
    • 音階(scale)
  • 次回簡単な発表会を行います!