yoppa.org


第5回: SuperCollider入門 5 – 時間構造をつくる、エンベロープと反復

SuperCollider入門編も大詰めです。今回は、時間構造について考えていきます。音楽は時間の中で展開される表現形式です。これまでは、音を生成したタイミングで鳴らすということをしてきました。

しかし、これを音楽作品にしていくためには、どのタイミングで何を行うかを、細かくコントロールする必要があります。今回はSuperColliderを用いて時間構造を作成する方法について解説していきます。

スライド資料

授業内で使用するスライド資料は、下記から参照してください

サンプルプログラム

今回使用したSuperColliderのプログラムです。

// インタラクティブミュージックII 2014.10.30
// 東京藝術大学AMC

// エンベロープ (Enbalope)

// Env.linen - アタック、サステイン、リリース、レベルで指定
Env.linen(0.05, 0.2, 0.5, 0.7).plot;

(
{
    var env = Env.linen(0.05, 0.1, 0.5);
    SinOsc.ar(440).dup * EnvGen.kr(env, doneAction: 2)
}.play
)

// Env.perk - パーカッシブなエンベロープ
Env.perc(0.01, 2.0).plot;
(
{
    var env = Env.perc(0.01, 1.0);
    SinOsc.ar(440).dup * EnvGen.kr(env, doneAction: 2)
}.play
)

// Env.triangle - 三角形のエンベロープ
Env.triangle(1, 1).plot;
(
{
    var env = Env.triangle(1, 1);
    SinOsc.ar(440).dup * EnvGen.kr(env, doneAction: 2)
}.play
)

// Env.sine - ハニング窓
Env.sine(1, 1).plot;
(
{
    var env = Env.sine(1, 1);
    SinOsc.ar(440).dup * EnvGen.kr(env, doneAction: 2)
}.play
)

// Env.new - 完全に自由なエンベロープ生成
// レベルの配列と、時間の配列で指定
Env.new([0, 1, 0.9, 0], [0.1, 0.5, 1]).plot;
(
{
    var env = Env.new([0, 1, 0.9, 0], [0.1, 0.5, 1]);
    SinOsc.ar(440).dup * EnvGen.kr(env, doneAction: 2)
}.play
)


// スケジューリング (Scheduling)

// Routineをスケジューリング- 1
(
Routine({
    12.do{
        arg i; // カウンタ変数
        "Count = ".post; i.postln; // カウンタ出力
        1.yield; // 1秒待つ
    }
}).play;
)
// Routineをスケジューリング - 2
(
r = Routine({
    //待ち時間を記録する変数
    var delta;
    12.do {
        //待ち時間、1~5秒をランダムに生成
        delta = rrand(1,5);
        //メッセージを出力
        "Will wait ".post; delta.postln;
        //1~5秒待つ
        delta.yield;
    }
}).play;
)

// アルゴリズミックシーケンス

// Routine実験用、Synth "singrain"
(
SynthDef("singrain", {
    arg freq = 440, amp = 0.2, sustain = 1;
    var env, sig;
    env = EnvGen.kr(Env.perc(0.01, sustain), doneAction: 2);
    sig = SinOsc.ar([freq, freq * 1.01], 0, amp) * env;
    Out.ar(0, sig);
}).add;
)

//反復して演奏
(
Routine({
    loop{
        Synth("singrain");
        1.yield;
    }
}).play;
)

//等比的に上昇
(
Routine({
    var freq;
    24.do{
        arg i;
        freq = i * 1.5 * 55;
        Synth("singrain", ["freq", freq]);
        0.2.yield;
    }
}).play;
)

//12音階をランダムに
(
Routine({
    var freq;
    loop{
        freq = [60,61,62,63,64,65,66,67,68,69,70,71].choose.midicps;
        Synth("singrain", ["freq", freq]);
        0.5.yield;
    }
}).play;
)

//音程、長さ、音量をランダムに
(
Routine({
    var freq, amp, sustain;
    loop{
        freq = [60,61,62,63,64,65,66,67,68,69,70,71].choose.midicps;
        amp = exprand(0.1, 0.5);
        sustain = rrand(1, 4) * 0.5;
        Synth("singrain", ["freq", freq, "amp", amp, "sustain", sustain]);
        (sustain * 0.8).yield;
    }
}).play;
)

//2つの音程
(
Routine({
    var freq1, freq2, amp, sustain;
    loop{
        freq1 = [60,61,62,63,64,65,66,67,68,69,70,71].choose.midicps;
        freq2 = [60,61,62,63,64,65,66,67,68,69,70,71].choose.midicps;
        amp = exprand(0.1, 0.3);
        sustain = rrand(1, 4) * 0.5;
        Synth("singrain", ["freq", freq1, "amp", amp, "sustain", sustain]);
        Synth("singrain", ["freq", freq2, "amp", amp, "sustain", sustain]);
        (sustain * 0.8).yield;
    }
}).play;
)

//2つの音程 - 2
(
Routine({
    var freq1, freq2, amp, sustain;
    loop{
        freq1 = [60,61,62,63,64,65,66,67,68,69,70,71].choose.midicps;
        freq2 = freq1 * [1.0/1.0, 2.0/1.0, 3.0/2.0, 4.0/3.0, 5.0/4.0].choose;
        amp = exprand(0.1, 0.3);
        sustain = rrand(1, 4) * 0.5;
        Synth("singrain", ["freq", freq1, "amp", amp, "sustain", sustain]);
        Synth("singrain", ["freq", freq2, "amp", amp, "sustain", sustain]);
        (sustain * 0.8).yield;
    }
}).play;
)

//4つの音程2
(
Routine({
    var note1, note2, note3, note4, amp, sustain;
    loop{
        note1 = [60,61,62,63,64,65,66,67,68,69,70,71].choose;
        note2 = note1 + [0, 5, 7].choose - [0, 12].choose;
        note3 = note2 + [0, 5, 7].choose - [0, 12].choose;
        note4 = note3 + [0, 5, 7].choose - [0, 12].choose;
        amp = exprand(0.1, 0.2);
        sustain = rrand(1, 8);
        Synth("singrain", ["freq", note1.midicps, "amp", amp, "sustain", sustain]);
        Synth("singrain", ["freq", note2.midicps, "amp", amp, "sustain", sustain]);
        Synth("singrain", ["freq", note3.midicps, "amp", amp, "sustain", sustain]);
        Synth("singrain", ["freq", note4.midicps, "amp", amp, "sustain", sustain]);
        (sustain * 0.8).yield;
    }
}).play;
)

//4つの音程 x 2
(
r = Routine({
    var note1, note2, note3, note4, amp, sustain;
    loop{
        note1 = [60,61,62,63,64,65,66,67,68,69,70,71].choose;
        note2 = note1 + [0, 5, 7].choose - [0, 12, 24].choose;
        note3 = note2 + [0, 5, 7].choose - [0, 12, 24].choose;
        note4 = note3 + [0, 5, 7].choose - [0, 12, 24].choose;
        amp = exprand(0.05, 0.1);
        sustain = rrand(1, 8);
        Synth("singrain", ["freq", note1.midicps, "amp", amp, "sustain", sustain]);
        Synth("singrain", ["freq", note2.midicps, "amp", amp, "sustain", sustain]);
        Synth("singrain", ["freq", note3.midicps, "amp", amp, "sustain", sustain]);
        Synth("singrain", ["freq", note4.midicps, "amp", amp, "sustain", sustain]);
        (sustain * 0.8).yield;
    }
});
r.play;
r.play;
)

第8回: openFrameworks 3Dグラフィクス、OpenGL

このワークショップでは、ここまで主に2次元平面上での描画による表現を扱ってきました。今回は2次元での表現からさらに(文字通り)次元を越えて、3次元空間での表現について考えていきたいと思います。

とはいっても、その手法はこれまでとさほど変化はありません。なぜなら、openFrameworksの描画の基本はOpenGLで行っています。OpenGLはそもそも3次元のグラフィクスの描画のために開発されたライブラリであり、最初から3次元空間をとり扱うための様々な機能が備わっています。

とはいえ、3Dの物体を扱うには、これまでとは違った様々な要素が加わります。カメラ(視点)、ライティング、光と影(シェーディング)、奥行の重なりなどといった2次元の平面には無かった様々な技術や概念の理解が必要となります。

サンプルファイル

紹介したサンプルは、いつものようにGithubからダウンロード(もしくは、pull)してください。

スライド資料

スライド資料は下記から参照してください。


第4回: SuperCollider入門 4 – 楽器を定義、変調合成(RM, AM, FM)

今回は前半部分でSuperColliderの「楽器」を定義する方法について紹介します。これまでは、楽器を定義することなくUGenを組合せた音の信号を関数 {…} にまとめ、そこに「.play」のメッセージを送ることで直接音を生成していました。しかし、実際にはSuperColliderの内部ではどうなっていたかというと、SuperColliderの音響合成サーバーにテンポラリーに楽器が生成されそれを鳴らしていました。音を止めると楽器は消滅していました。今回はまず明示的に「楽器」を定義する「Synthdef」という関数について紹介します。この方法で楽器が名前付きで定義され、再利用が可能となります。また、外部のプログラムと連携することも可能となります。

後半は、SuperColliderを使用した実践的な音響合成の手法について紹介していきます。まず今回は、ある信号のパラメーターをもう一つの信号で変化(変調)させる「変調合成」という手法について紹介します。変調合成は、信号の何を変調するのかによって、AM(RM)、FMという種類に分けられます。それぞれの変調合成の音色の違いなどに注意しながら、SuperColliderでの実現のやり方を学んでいきましょう。

スライド資料

授業内で使用するスライド資料は、下記から参照してください

サンプルプログラム

今回使用したSuperColliderのプログラムです。

// インタラクティブミュージックII
// 2014.10.23

// 楽器を定義する - SynthDef

// {}.playによる出力
{SinOsc.ar.dup}.play;

// Post Windowの表示
Synth("temp__146" : 1000)

// SyntDefに変換
SynthDef("sine", {Out.ar(0, SinOsc.ar)}).play; //left
SynthDef("sine", {Out.ar(1, SinOsc.ar)}).play; //right
SynthDef("sine", {Out.ar(0, SinOsc.ar.dup)}).play; //l+r

//SynthDefをサーバーに追加
SynthDef.new("test-SinOsc", {
    Out.ar(0, SinOsc.ar(440, 0, 0.2).dup)
}).add;

//Synthを演奏
Synth("test-SinOsc");

//SynthDefの定義(引数あり)
SynthDef.new("test-SinOsc", {
    arg freq = 440, amp = 0.2;
    Out.ar(0, SinOsc.ar(freq, 0, amp).dup)
}).add;

//Synthを演奏
a = Synth("test-SinOsc"); //440Hz
b = Synth("test-SinOsc", [freq:660]); //660Hz
c = Synth("test-SinOsc", [freq:880, amp:0.5]); //880Hz, amp:0.5

a.set("freq", 330); //440Hz -> 330Hz
b.set("freq", 220, "amp", 0.3); //660Hz -> 220Hz, amp: 0.3

//終了
a.free; b.free; c.free;

// -----------------------------------------------------------
// 変調合成(RM, AM, FM)

// RM変調基本
SynthDef.new("test-rm",{
    var car, mod, rm;
    mod = SinOsc.ar(880, 0, 1.0);
    car = SinOsc.ar([440,442], 0, 0.5);
    rm = car * mod;
    Out.ar(0, rm);
}).play;

// RM変調1 - 徐々にModulatorの周波数をアップ
SynthDef.new("test-rm",{
    var car, mod, rm;
    mod = SinOsc.ar(XLine.kr(1, 4000, 30), 0, 1.0);
    car = SinOsc.ar(440, 0, 0.5).dup;
    rm = car * mod;
    Out.ar(0, rm);
}).play;

// RM変調2 - マウスでModulatorの周波数を変更
SynthDef.new("test-rm",{
    var car, mod, rm;
    mod = SinOsc.ar(MouseX.kr(1, 4000, 1), 0, 1.0);
    car = SinOsc.ar(440, 0, 0.5).dup;
    rm = car * mod;
    Out.ar(0, rm);
}).play;

// RM変調3 - 2つのModulator
SynthDef.new("test-rm",{
    var car, mod1, mod2, rm;
    car = SinOsc.ar(440, 0, 0.5);
    mod1 = SinOsc.ar(MouseX.kr(1, 4000, 1), 0, 1.0);
    mod2 = SinOsc.ar(1/[8.0, 7.0]);
    rm = car * (mod1 * mod2);
    Out.ar(0, rm);
}).play;

// AM変調 1
SynthDef.new("test-am",{
    var car, mod, am;
    mod = SinOsc.ar(XLine.kr(1, 4000, 30), 0, 0.5, 0.5);
    car = SinOsc.ar(440, 0, 0.5).dup;
    am = car * mod;
    Out.ar(0, am);
}).play;

// AM変調 2
SynthDef.new("test-am",{
    var car, mod, am;
    mod = SinOsc.ar(MouseX.kr(1, 4000, 1), 0, 0.5, 0.5);
    car = SinOsc.ar(440, 0, 0.5).dup;
    am = car * mod;
    Out.ar(0, am);
}).play;

// AM変調 3
SynthDef.new("test-am",{
    var car, mod1, mod2, am;
    car = SinOsc.ar(440, 0, 0.5);
    mod1 = SinOsc.ar(MouseX.kr(1, 4000, 1), 0, 0.5, 0.5);
    mod2 = SinOsc.ar(1/[8.0, 7.0]);
    am = car * (mod1 * mod2);
    Out.ar(0, am);
}).play;

// AM + LFNoise
SynthDef.new("rand-am",{
    arg freq = 440, amp = 0.5, modFreq = 400;
    var car, mod1, mod2, am;
    car = SinOsc.ar(freq, 0, amp);
    mod1 = SinOsc.ar(LFNoise1.kr(5.reciprocal, modFreq), pi.rand, 0.5, 0.5);
    mod2 = SinOsc.ar(LFNoise1.kr([8,7].reciprocal).abs);
    am = car * (mod1 * mod2);
    Out.ar(0, am);
}).add;

Synth("rand-am", ["freq", 110, "amp", 0.2, "modFreq", 1200]);
Synth("rand-am", ["freq", 220, "amp", 0.2, "modFreq", 200]);
Synth("rand-am", ["freq", 440, "amp", "modFreq", 200]);
Synth("rand-am", ["freq", 880, "amp", 0.2, "modFreq", 100]);
Synth("rand-am", ["freq", 1780, "amp", 0.05, "modFreq", 20]);

//FM基本 1
SynthDef.new("test-fm",{
    arg cfreq = 440, mfreq = 111, index = 200;
    var car, mod;
    mod = SinOsc.ar(mfreq, 0, index);
    car = SinOsc.ar(cfreq + mod, 0, 0.5).dup;
    Out.ar(0, car);
}).play;

//FM基本2 徐々にMod周波数アップ
SynthDef.new("test-fm",{
    arg cfreq = 440, mfreq = 111, index = 200;
    var car, mod;
    mod = SinOsc.ar(XLine.kr(1, 1000, 30), 0, index);
    car = SinOsc.ar(cfreq + mod, 0, 0.5).dup;
    Out.ar(0, car);
}).play;

//FM基本2 徐々にindexアップ
SynthDef.new("test-fm",{
    arg cfreq = 440, mfreq = 111, index = 200;
    var car, mod;
    mod = SinOsc.ar(111, 0, XLine.kr(1, 10000, 30));
    car = SinOsc.ar(cfreq + mod, 0, 0.5).dup;
    Out.ar(0, car);
}).play;

//FMマウスで操作
SynthDef.new("test-fm",{
    var car, mod;
    mod = SinOsc.ar(MouseX.kr(1, 1000, 1), 0,
        MouseY.kr(1, 10000, 1));
    car = SinOsc.ar(440 + mod, 0, 0.5).dup;
    Out.ar(0, car);
}).play;

// FM応用 1
(
SynthDef("fm1", { arg freq = 440, detune = 2, carPartial = 1, modPartial = 1, index = 3, mul = 0.2;
    var mod, car;
    mod = SinOsc.ar(
        [freq, freq+detune] * modPartial, 0,
        freq * index * LFNoise1.kr(10.reciprocal).abs
    );
    car = SinOsc.ar((freq * carPartial) + mod, 0, mul);
    Out.ar(0, car);
}).add;
)

(
Synth("fm1", ["modPartial", 2.4]);
Synth("fm1", ["modPartial", 2.401]);
Synth("fm1", ["freq", 110, "modPartial", 3.1213, "index", 10]);
Synth("fm1", ["freq", 220, "modPartial", 10.99, "index", 20]);
)

// FM応用2 - FM + エフェクト
(
SynthDef("fm2", { arg bus = 0, freq = 440, detune = 2, carPartial = 1, modPartial = 1, index = 3, mul = 0.1;
    var mod, car;
    mod = SinOsc.ar(
        [freq, freq+detune] * modPartial,
        0,
        freq * index * LFNoise1.kr(10.reciprocal).abs
    );
    car = SinOsc.ar((freq * carPartial) + mod, 0, mul);
    Out.ar(bus, car);
}).add;

SynthDef("preDelay", { arg inbus = 2;
    ReplaceOut.ar(
        4,
        DelayN.ar(In.ar(inbus, 1), 0.048, 0.048)
    )
}).add;

SynthDef("combs", {
    ReplaceOut.ar(
        6,
        Mix.arFill(7, { CombL.ar(In.ar(4, 1), 0.1, LFNoise1.kr(Rand(0, 0.1), 0.04, 0.05), 15) })
    )
}).add;

SynthDef("allpass", { arg gain = 0.2;
    var source;
    source = In.ar(6, 1);
    4.do({source = AllpassN.ar(source, 0.050, [Rand(0, 0.05), Rand(0, 0.05)], 1) });
    ReplaceOut.ar(8, source * gain)
}).add;

SynthDef("theMixer", { arg gain = 1;
    ReplaceOut.ar(
        0,
        Mix.ar([In.ar(2, 1), In.ar(8, 2)]) * gain
    )
}).add;
)

(
Synth("fm2", ["bus", 2, "freq", 440, "modPartial", 2.4]);
Synth("fm2", ["bus", 2, "freq", 448, "modPartial", 2.401]);
Synth.tail(s, "preDelay");
Synth.tail(s, "combs");
Synth.tail(s, "allpass");
Synth.tail(s, "theMixer", ["gain", 0.64]);
)

第7回: Web応用4 – 最終課題の提出について

いよいよ今週末、最終課題の講評会です。課題の提出方法を説明します。

最終課題

最終課題のテーマ :「インタラクティブ・サウンド・アニメーション」

金曜日のサウンドの授業で作成した音を使用して、Webブラウザ上でインタラクティブなアニメーション作品を制作し発表する。

  • 音響素材 : サウンド(矢坂先生)
  • プログラミング : ネット(田所)
  • この2つの要素を融合する
  • インタラクションは、キーボード操作を基本とするが、それ以外のインタラクション(マウス、Webカメラ、センサー?)を使うのは自由
  • 授業では、p5.jsを使用して解説するが、それ以外のJavascriptのライブラリ、その他の言語を使用しても良い
  • ただし作品はWebブラウザで発表できるものに限定

課題の提出方法

課題は、多摩美のWebサーバーに提出します。

1. 課題表紙ページの作成

前半作成したTumblrのポートフォリオのページと、今回作成した「インタラクティブ・サウンド・アニメーション」の双方へのリンクをした表紙ページを作成します。下記のテンプレートから作成してください。余裕があれば、CSSを改変してデザインを変更してください。

2.「インタラクティブ・サウンド・アニメーション」のファイル一式準備

「インタラクティブ・サウンド・アニメーション」のファイル一式を、階層構造はそのままで任意のフォルダ名を設定します。(例: interactive_sound)

screenshot_483

ファイル一式は下記からダウンロードしてください。

3.iddのファイルサーバーに接続

iddのファイルサーバーに接続します。

  • afp://fs.idd.tamabi.ac.jp

4.ファイル一式のアップロード

以下のファイル/フォルダを、マウントされた個人のネットワークフォルダの「public_html」内にコピーします。(コピーする場所重要!!)

  • 「インタラクティブ・サウンド・アニメーション」のファイル一式(例: interactive_sound)
  • 表紙ページ: index.html
  • 表紙ページ: cssフォルダ

screenshot_484

5.ページの確認

以下のリンクにある、IDDのMy Profileページの「ホームページアドレス」をから、自身のページが正しくアップロードされていることを確認します。

参考

IDDのWebページの公開などの詳細な情報は、idd ITサポートのページを参考にしてください。


第7回: openFrameworks 作品制作のためのTips、画像・動画を使用、グリッチ(glitch)

ワークショップの後半は、作品制作のためのTipsとなるようなテーマを各回とり上げていこうと思います。

今回は、作品に画像(Jpeg、PNG、GIFなど)や動画(mp4、mov、aviなど)のデータを読み込んで表示・再生する方法を紹介します。さらに画像や動画のピクセル単位でのデータにアクセスしその情報から画像や動画を再構成する方法について紹介します。最後に、おまけとして画像データをわざと破壊することで崩れた画像を生成する「グリッチ(glitch)」というテクニックを紹介します。

サンプルファイル

紹介したサンプルは、いつものようにGithubからダウンロード(もしくは、pull)してください。

スライド資料

スライド資料は下記から参照してください。


第3回: SuperCollider入門 3 – 音を混ぜる(Mix)、ハーモニー

前回は、SuperColliderの基本的な文法について概観しました。

まず始めに前回の復習を簡単にした上で、今回は「音を混ぜる」つまり音のミキシングについて実践していきます。

SuperColliderでは、音を変数に格納して、数値を計算するように簡単に信号を足しあわせてミックスすることが可能です。また、Mix関数によって音を混ぜあわせることも可能です。まず前半で音を混ぜる基礎について解説します。

後半は、ランダムな関数や、周波数の比率によって、アリゴリズミックにハーモニーを作りだす手法について探っていきます。

スライド資料

授業内で使用するスライド資料は、下記から参照してください。

サンプルプログラム

今回使用したSuperColliderのプログラムです。

// インタラクティブ・ミュージックII
// 東京藝術大学
// 2014.10.16

// 先週の復習

{ SinOsc.ar([440, 442], 0, 0.2) }.play;

// 音を混ぜる
// Mix 例1
{
    SinOsc.ar([440,442], 0, 0.2)
    + SinOsc.ar([660,663], 0, 0.2)
}.play;

// Mix 例2
{
    a = SinOsc.ar([440,442], 0, 0.2);
    b = SinOsc.ar([660,663], 0, 0.2);
    a + b;
}.play;

// Mixクラス
{
    a = SinOsc.ar([440,442], 0, 0.2);
    b = SinOsc.ar([660,663], 0, 0.2);
    c = SinOsc.ar([220,221], 0, 0.2);
    Mix([a, b, c]);
}.play;

// ランダムな周波数を重ねる
(
var num = 8;
{ Mix.fill(num, {
    SinOsc.ar([50+1000.rand, 50+1000.rand], pi.rand, 1.0/num)
})}.play;
)

// number.rand から ExpRand(low, hi)へ
(
var num = 8;
{Mix.fill(num,{
    SinOsc.ar([ExpRand(50,800), ExpRand(50,800)], pi.rand, 1.0/num)
})}.play;
)

// アルゴリズミック、ハーモニー
(
var num = 8;
{Mix.fill(num,{
    var freqR, freqL;
    freqL = 220 * [1.0, 3.0/2.0, 4.0/3.0].choose;
    freqR = 220 * [1.0, 3.0/2.0, 4.0/3.0].choose;
    SinOsc.ar([freqL, freqR], pi.rand, 1.0/num);
})}.play;
)

// アルゴリズミック、ハーモニー 2
(
var num = 8;
{ Mix.fill(num,{
    var freqR, freqL;
    freqL = 220 * [1.0, 3.0/2.0, 4.0/3.0].choose
        * [1.0, 3.0/2.0, 4.0/3.0].choose;
    freqR = 220 * [1.0, 3.0/2.0, 4.0/3.0].choose
        * [1.0, 3.0/2.0, 4.0/3.0].choose;
    SinOsc.ar([freqL, freqR], pi.rand, 1.0/num);
})}.play;
)

// アルゴリズミック、ハーモニー 3
(
var num = 8;
{ Mix.fill(num, {
    var freqR, freqL;
    freqL = 220 * [1.0, 3.0/2.0, 4.0/3.0].choose
        * [1.0, 3.0/2.0, 4.0/3.0].choose + 10.0.rand;
    freqR = 220 * [1.0, 3.0/2.0, 4.0/3.0].choose
        * [1.0, 3.0/2.0, 4.0/3.0].choose + 10.0.rand;
    SinOsc.ar([freqL, freqR], pi.rand, 1.0/num);
})}.play;
)

// アルゴリズミック、ハーモニー 4
(
var num = 32.0;
{ Mix.fill(num,{
    var freqR, freqL;
    freqL = 440 * ((3.0/2.0) ** rrand(1,6)) * (0.5 ** rrand(1, 8));
    freqR = 442 * ((3.0/2.0) ** rrand(1,6)) * (0.5 ** rrand(1, 8));
    SinOsc.ar([freqL, freqR], pi.rand, 1.0/num);
})}.play;
)

// アルゴリズミック・ハーモニー、バリエーション
{Mix.fill(24,{SinOsc.ar([440,441]*(3.0/2.0**rrand(1,6))*(0.5** rrand(1,8)),0,1.0/24)})}.play;

{Mix.fill(24,{SinOsc.ar([440,441]*(4.0/3.0**rrand(1,6))*(0.5** rrand(1,8)),0,1.0/24)})}.play;

{Mix.fill(24,{SinOsc.ar([440,441]*(5.0/3.0**rrand(1,8))*(0.5** rrand(1,8)),0,1.0/24)})}.play;

{Mix.fill(24,{SinOsc.ar([440,441]*(9.0/8.0**rrand(1,20))*(0.5** rrand(1,8)),0,1.0/24)})}.play;


// 楽器を定義する - SynthDef

// {}.playによる出力
{SinOsc.ar}.play;

// Post Windowの表示
Synth("temp__146" : 1000)

// {}.playによる出力
{SinOsc.ar}.play;

// SyntDefに変換
SynthDef("sine", {Out.ar(0, SinOsc.ar)}).play; //left
SynthDef("sine", {Out.ar(1, SinOsc.ar)}).play; //right

//関数による記述
{ SinOsc.ar(440, 0, 0.2) }.play;

//SynthDefをサーバーに追加
SynthDef.new("test-SinOsc", {
    Out.ar(0, SinOsc.ar(440, 0, 0.2))
}).add;

//Synthを演奏
Synth("test-SinOsc");

//SynthDefの定義(引数あり)
SynthDef.new("test-SinOsc", {
    arg freq = 440, amp = 0.2;
    Out.ar(0, SinOsc.ar(freq, 0, amp))
}).add;

//Synthを演奏
a = Synth("test-SinOsc"); //440Hz
b = Synth("test-SinOsc", [freq:660]); //660Hz
c = Synth("test-SinOsc", [freq:880, amp:0.5]); //880Hz, amp:0.5

a.set("freq", 330); //440Hz -> 330Hz
b.set("freq", 220, "amp", 0.3); //660Hz -> 220Hz, amp: 0.3

//終了
a.free; b.free; c.free;

第6回: openFrameworks Addonを使用する 2 – ofxOpenCv、ofxCv

映像解析を使用したインタラクション

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

今回は、こうしたCVの技術の中でもオープンソースで多くの利用実績のあるOpenCVというCVのためのライブラリをopenFrameworksで活用する方法について紹介していきます。

スライド資料

授業のスライド資料は、下記から参照してください。

作品制作相談

授業の後半は、最終作品に向けて、個別に制作相談を行います。


第6回: Web応用3 – 最終課題「インタラクティブ・サウンド・アニメーション」に向けて

今回は、最終課題に向けて、具体的なサンプルをいろいろ紹介していきます。

最終課題

最終課題のテーマ :「インタラクティブ・サウンド・アニメーション」

金曜日のサウンドの授業で作成した音を使用して、Webブラウザ上でインタラクティブなアニメーション作品を制作し発表する。

  • 音響素材 : サウンド(矢坂先生)
  • プログラミング : ネット(田所)
  • この2つの要素を融合する
  • インタラクションは、キーボード操作を基本とするが、それ以外のインタラクション(マウス、Webカメラ、センサー?)を使うのは自由
  • 授業では、p5.jsを使用して解説するが、それ以外のJavascriptのライブラリ、その他の言語を使用しても良い
  • ただし作品はWebブラウザで発表できるものに限定

先週のプログラムの復習

先週作成した、キー入力によって、アニメーションと音を再生するサンプルについて、再度復習します。

sketch.js

var sample = [];
var animation;
var num;

function setup() {
  createCanvas(windowWidth, windowHeight);
  sample[0] = loadSound('assets/se01.wav');
  sample[1] = loadSound('assets/se02.wav');
  sample[2] = loadSound('assets/se03.wav');
  sample[3] = loadSound('assets/se04.wav');
}

function draw() {
  background(0);
  if(animation){
    animation.draw();
  }
}

function keyTyped() {
  if (key == 'a') {
    sample[0].play();
    animation = new Anim_a();
  }
  if (key == 's') {
    sample[1].play();
    animation = new Anim_s();
  }
  if (key == 'd') {
    sample[2].play();
    animation = new Anim_d();
  }
  if (key == 'f') {
    sample[3].play();
    animation = new Anim_f();
  }
}

animations.js

// ------------------------------------------------------
// Animation A
function Anim_a() {
  this.x = width / 2;
  this.y = height / 2;
  this.diameter = 0;
  this.speed = 10;
}

Anim_a.prototype.draw = function() {
  noStroke();
  fill(0, 127, 255);
  ellipse(this.x, this.y, this.diameter, this.diameter);
  this.diameter += this.speed;
};

// ------------------------------------------------------
// Animation S
function Anim_s() {
  this.width = 0;
  this.speed = 80;
}

Anim_s.prototype.draw = function() {
  noStroke();
  fill(255, 0, 0);
  rectMode(CORNER);
  rect(0, 0, this.width, height);
  this.width += this.speed;
};

// ------------------------------------------------------
// Animation D
function Anim_d() {
  this.rotate = 0;
  this.size = 0;
  this.speed = 50;
}

Anim_d.prototype.draw = function() {
  push();
  fill(255, 255, 0);
  noStroke();
  translate(width / 2, height / 2);
  rotate(radians(this.rotate));
  rectMode(CENTER);
  rect(0, 0, this.size, this.size);
  pop();
  this.rotate += this.speed;
  this.size += this.speed;
};

// ------------------------------------------------------
// Animation F
function Anim_f() {
  this.bgColor = 255;
  this.speed = -2;
}

Anim_f.prototype.draw = function() {
  noStroke();
  fill(this.bgColor);
  rect(0, 0, width, height);
  this.bgColor += this.speed;
};

アニメーションのためのコードサンプル

最終課題に向けて、プログラミングでアニメーションを表現するサンプルをいろいろ紹介します。これ以降のサンプルは、以下のテンプレートをもとに発展させていきます。

sketch.js

var sample = [];
var animation;
var num;

function setup() {
  createCanvas(windowWidth, windowHeight);
  sample[0] = loadSound('assets/se01.wav');
}

function draw() {
  background(0);
  if(animation){
    animation.draw();
  }
}

function keyTyped() {
  if (key == 'a') {
    sample[0].play();
    animation = new Anim_a();
  }
}

animation.js

function Anim_a() {
  //初期設定
}

Anim_a.prototype.draw = function() {
  //アニメーションの描画
};

直線的な動き

動きをつくるには、図形の位置をすこしずつ変化させます。

animation.js

function Anim_a() {
  //初期設定
  this.x = 10;
  this.y = height/2.0;
}

Anim_a.prototype.draw = function() {
  //アニメーションの描画
  this.x += 20;
  fill(31, 127, 255);  
  ellipse(this.x, this.y, 40, 40);
};

動きの起点をランダムに

初期設定の歳にrandom()関数を利用して、初期位置を毎回異なる場所にすることが可能です。

function Anim_a() {
  //初期設定
  this.x = 10;
  this.y = random(height);
}

Anim_a.prototype.draw = function() {
  //アニメーションの描画
  this.x += 20;
  fill(31, 127, 255);  
  ellipse(this.x, this.y, 40, 40);
};

関数を利用した動き

様々な数学的な関数を使用して、動きを生みだす方法があります。例えば、三角関数のsin()を使用して波のような動きを作ることが可能です。

sketch.js

function Anim_a() {
  //初期設定
  this.x = 10;
  this.y = height/2;
  this.count = 0;
}

Anim_a.prototype.draw = function() {
  //アニメーションの描画
  this.x += 20;
  this.y = sin(this.count / 5.0) * height/4 + height/2;
  fill(31, 127, 255);  
  ellipse(this.x, this.y, 40, 40);
  this.count++;
};

x座標とy座標、それぞれcos()関数とsin()関数にすると、円を描きます。

function Anim_a() {
  //初期設定
  this.x = 10;
  this.y = height/2;
  this.count = 0;
}

Anim_a.prototype.draw = function() {
  //アニメーションの描画
  this.x = cos(this.count / 4.0) * height/3 + width/2;
  this.y = sin(this.count / 4.0) * height/3 + height/2;
  fill(31, 127, 255);
  ellipse(this.x, this.y, 40, 40);
  this.count++;
};

拡大・縮小・変形

形を生成する関数の大きさに関するパラメータを操作することで、拡大、縮小、変形などの動きが生みだせます。

拡大

animation.js

function Anim_a() {
  //初期設定
  this.x = width/2;
  this.y = height/2;
  this.size = 0;
  rectMode(CENTER);
}

Anim_a.prototype.draw = function() {
  //アニメーションの描画
  fill(31, 127, 255);
  rect(this.x, this.y, this.size, this.size);
  this.size += 20;
};

縮小

function Anim_a() {
  //初期設定
  this.x = width/2;
  this.y = height/2;
  this.size = width;
  rectMode(CENTER);
}

Anim_a.prototype.draw = function() {
  //アニメーションの描画
  fill(31, 127, 255);
  rect(this.x, this.y, this.size, this.size);
  this.size /= 1.2;
};

色の値を徐々に変化させていくことも可能です。

function Anim_a() {
  //初期設定
  this.r = 0;
  this.g = 0;
  this.b = 255;
  rectMode(CORNER);
}

Anim_a.prototype.draw = function() {
  //アニメーションの描画
  fill(this.r, this.g, this.b);
  rect(0, 0, width, height);
  this.r = (this.r + 2) % 255;
  this.b = (this.b - 2) % 255;
};

実習

後半は、最終課題に向けて実習とします。様々な動きを試しながら、ばりばりと制作しましょう!

制作には以下の制作テンプレートを活用してください!!

制作テンプレート


第2回: SuperCollider入門 2 – 関数とUGen

今回も前回に引き続き、SuperColliderの入門的な解説をしていきます。SuperColliderで音を生成する簡単なプログラムを作成して、その構造を理解できるところまでを目標とします。

まず始めに、SuperColliderのコードの理解の鍵となる、3種類の括弧についてその意味を確実に理解します。

  • 小括弧 (…)
  • 中括弧 {…}
  • 大括弧 […]

次に、コードの実例を参照しながら、「変数」「関数」「引数」「オブジェクト」「メッセージ」「Ugen(ユニットジェネレータ)」といった概念について解説していきます。

スライド資料

今回の授業内容のスライド資料は下記のリンクを参照してください。

サンプルプログラム

スライドの中で紹介しているSuperColliderのプログラムです。

// Interactive Music 141009

// 今日の目標
{[SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)]}.play;

// ---------------------------------------------------------------------------
// 言語の基本

// 関数
f = {"関数を評価しています。".postln;};
f;
f.value;

// 変数「a」に数値を代入
a = 1;
a;

// 計算結果を代入することも可能
b = 1 + 1;
b;

// 文字列だって入る
c = "Hello";
c;

// アルファベット1文字でない場合は、「var」が必要
{
  var num = 10;
  num = num * 2;
  num;
}.value;

// 足し算
f = {
    2 + 3;
};
f.value;

// もう少し複雑な例: 整数の二乗を出力
f = {
    a = 10.rand;
    a * a;
};
f.value;

// 引数
f = {
    arg a, b; // 引数
    a / b;
};

// 引数の指定 - 順番で区別
f.value(10, 2);

// 引数の指定 - キーワードで指定
f.value(b: 2, a: 10);

// ---------------------------------------------------------------------------
// 音
{ SinOsc.ar(440, 0, 0.2) }.play;

// 上のプログラムを分解
(
{             // 関数の開始
  SinOsc.ar(  // オーディオレイトでSinOscを出力
    440,      // 周波数 440Hz
    0,        // 位相 0
    0.2)      // 音量 0.2
}.play;       // 関数を閉じてPlayメソッドを実行
)

// scope
{ SinOsc.ar(440, 0, 0.2) }.scope;

// SinOscくみあわせ
(
{ var ampOsc;
    ampOsc = SinOsc.kr(0.5, 1.5pi, 0.5, 0.5);
    SinOsc.ar(440, 0, ampOsc);
}.play;
)

// 再度このコードについて
{[SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)]}.play;

// 配列
a = ["foo", "bar"]; // "foo", "bar"の順にデータを格納
a.at(0);            // 1番目のデータ: "foo"
a.at(1);            // 2番目のデータ: "bar"
a.at(2);            // nil(空)が出力される

// 以下のような記述方法も
a[0];               // a.at(0)と同じ意味

// 2つは同じ意味

{[SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)]}.play;
{ SinOsc.ar([440, 442], 0, 0.2) }.play;

// 応用例

// ハーモニー
(
{ var freq;
    freq = [[660, 880], [440, 660], 1320, 880].choose;
    SinOsc.ar(freq, 0, 0.2);
}.play;
)

// 音の移動(定位)
(
{
    Pan2.ar(SinOsc.ar(440, 0, 0.2), SinOsc.kr(0.5));
}.play;
)

// 移動する2つのSin波
(
{
    a = Pan2.ar(SinOsc.ar(220, 0, 0.2), SinOsc.kr(0.5));
    b = Pan2.ar(SinOsc.ar(330, 0, 0.2), SinOsc.kr(-0.6));
    a + b;
}.play;
)

第5回: openFrameworks Addonを使用する 1 – ofxGui、ofxBox2d

Addon(アドオン)とは、openFrameworksに機能を拡張するためのライブラリーやフレームワークです。processingのLibrariesのように、openFrameworks単体ではできなかった様々な機能を実現することが可能となります。Addonは、oF本体の開発者以外でも独自に開発して追加することが可能であり、繰り返し用いる機能や、CやC++で記述された既存のライブラリーをopenFrameworksに統合することができます。

今回は、Addonの使用の入門編として、プロジェクトにGUIを追加する「ofxGui」と、高度な物理演算ライブラリ「ofxBox2d」、さらにBox2Dに高度流体シミュレーションを付加した「ofxLiquidFun」をとりあげます。

Addon(アドオン)とは

Addonとは、oepnFrameworksの機能を何らかの方法で拡張するコードで、「ofx」という接頭辞で始まります。Addonは、ProcessingでいうLibrariesのような存在で、openFrameworks単体ではできなかった様々な機能を実現したり、頻繁に使用する機能をまとめたりすることが可能です。Addonは、openFrameworks本体の開発者以外でも独自に開発して追加することが可能です。

Addonsの目的

アドオンとは、oepnFrameworksの機能を何らかの方法で拡張するコードです。アドオンを作る理由は2つあります。

  • 外部ライブラリやフレームワークを、openFrameworksに適用して簡単に統合させることができる。例: KinectコントローラーをopenFrameworksで使用するためのofxKinectや、MIDIコマンドを送受信するためのofxMidiなど。
  • 自分自身または他のopenFrameworksのプログラマーにとって、複雑な作業を単純化できる。例: julapyによるofxQuadWarpや、ofTheoによるofxControlPanelなど。

openFrameworksのパッケージに付属されたAddon

openFrameworksのパッケージには、最初からいくつかのAddonが付属しています。openFrameworks 0.8.4には以下のアドオンが付属しています。

  • ofx3DModelLoader : 3Dモデルを読み込み
  • ofxOpenCv : OpenCVを活用したコンピュータビジョン
  • ofxAssimpModelLoader : 様々なフォーマットの3Dモデルを読み込み
  • ofxOsc : Open Sound Control(OSC)で外部のアプリケーションと通信
  • ofxGui : プロジェクトにGUIに追加
  • ofxVectorGraphics : ベクター画像を扱う
  • ofxKinect : Kinect(ゲームコントローラー)からの情報を取得
  • ofxXmlSettings : アプリケーションの設定を、XML形式で保存
  • ofxSvg : SVG(Scalable Vector Graphics)を扱う
  • ofxNetwork : tcp/ip を用いたネットワーク通信
  • ofxThreadedImageLoader : 連番のイメージファイルを読み込み

Addonを入手する

最初から付属しているAddon以外にも、大量のAddonが世界中の開発者によって開発され公開されています。Addonは主にGithubを利用して公開されるのが一般的です。

Githubに公開されたAddonに関する情報は、ofxAddonsのサイトにジャンル毎にまとめられています。

http://ofxaddons.com/

ここから、必要となるAddonを選択すると、個別のGithubのページにリンクしています。このページの「Download Zip」ボタン をクリックしてZip形式でダウンロードします。

ダウンロードしたAddonは、openFrameworksのパッケージ内のaddonsフォルダに格納します。

  • of_v0.8.4_osx_release/addons

これでAddonの準備は完了です。

プロジェクトへのAddonの追加

プロジェクトにAddonを追加するには、ProjectGeneratorを利用すると便利です。

ProjectGeneratorの画面で「Addons:」ボタンを押すと、現在addonsフォルダ以下に格納したアドオンの一覧が表示されます。プロジェクトに追加したいアドオンをチェックしてGenerateすると、生成されたプロジェクトにAddonが組込まれています。

screenshot_412

Addonを使ってみる 1 – ofxGui

では、1つAddonを使ってプロジェクトを作成してみましょう。ひとつ目は、ofxGuiです。

長らく待望されていた、openFrameworksのオフィシャルGUIで、プログラムで頻繁に調整する必要のあるパラメータをGUIから設定可能にします。設定した項目は、XML形式で設定ファイルとして保存と読込をすることができます。openFrameworksのv.0.8.0以降パッケージに付属して配布されているので、すぐに利用可能です。

まず、ofxGuiを組み込んだプロジェクトを作成します。ProjectGeneratorを起動して、以下の2つをチェックします。

  • ofxGui
  • ofxXmlSettings

ofxXmlSetteingは、ofxGuiで設定した値を保存するために使用します。

screenshot_413

では、まず簡単なプログラムを作成して、そのパラメータをGUIを用いて操作できるようにしてみましょう。色のついた円を画面に表示するプログラムを作成します。

この円のパラメーターは、ofApp.h内で以下のパラメータで指定されています。

// 円のパラメーター
float radius;
ofColor color;
ofVec2f position;

これらの値をGUIでコントロールできるように変更していきます。ofxGuiには型に応じて様々なパーツが用意されています。

  • ofxIntSlider : 整数型 (int) のスライダー
  • ofxFloatSlider : 浮動小数点型 (float) のスライダー
  • ofxVec2Slider : 2次元ベクトルのスライダー
  • ofxColorSlider : カラー生成スライダー
  • ofxButton : ボタン
  • ofxToggle : トグルスイッチ
  • ofxLabel : ラベル (テキスト表示)
  • ofxPanel : GUIの外枠

こうしたパーツを利用して、円を描くパラメータを置き換えていきます。

testApp.h

#pragma once

#include "ofMain.h"
#include "ofxGui.h"

class ofApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
    
    // GUIのパラメーター
    ofxPanel gui;
    ofxFloatSlider radius;
    ofxColorSlider color;
    ofxVec2Slider position;
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    ofSetFrameRate(60);
    ofBackground(127);
    ofSetCircleResolution(32);
    
    // colorの初期値、最小値、最大値を設定
    ofColor initColor = ofColor(0, 127, 255, 255);
    ofColor minColor = ofColor(0,0,0,0);
    ofColor maxColor = ofColor(255,255,255,255);
    
    // positionの初期値、最小値、最大値を設定
    ofVec2f initPos = ofVec2f(ofGetWidth()/2, ofGetHeight()/2);
    ofVec2f minPos = ofVec2f(0, 0);
    ofVec2f maxPos = ofVec2f(ofGetWidth(), ofGetHeight());
    
    // GUIを設定
    gui.setup();
    gui.add(radius.setup("radius", 200, 0, 400));
    gui.add(color.setup("color", initColor, minColor, maxColor));
    gui.add(position.setup("position", initPos, minPos, maxPos));
}

//--------------------------------------------------------------
void ofApp::update(){
    
}

//--------------------------------------------------------------
void ofApp::draw(){
    // パラメータを適用して円を描画
    ofSetColor(color);
    ofCircle(ofVec2f(position), radius);
    
    // GUIを表示
    gui.draw();
}

これで、円の半径、色、位置をGUIから変更できるようになりました。

screenshot_414

では、もう少し複雑なプロジェクトにGUIを組み込んでみましょう。前回作成した引力をつかったサンプルにGUIを組み込んでみたいと思います。

このプロジェクトのパーティクルのパラメータをGUIでコントロールしてみましょう。例えば、以下の項目を変更できるようにしてみます。

  • パーティクルの数 : num (int)
  • 摩擦力 : friction(float)
  • パーティクルのサイズ : size(float)
  • 引き付けられる引力の強さ : attraction(float)
  • パーティクルの色 : fgColor(ofColor)
  • 背景色 : bgColor(ofColor)

ここで、size、attractoin、fgColor、bgColorはGUIから設定した値をそのままプログラムに埋めこめば適用可能です。ところが、num(パーティクルの数)とfriction(摩擦力)は、値を変更した後でそれぞれのパーティクルに値を設定する処理をする必要があります。

ofxGuiではパラメータを変更した後に特定の処理をする場合、イベントリスナーを設定するという方法で変更するようにしています。例えば、frictionを設定した後には、全てのパーティクルのインスタンスのプロパティーを変更する必要があります。また、numを変更した場合には、設定した数に応じてパーティクルを格納した配列を操作する必要が生じてきます。以下のようにしてイベントリスナー追加します。

まずofApp::setup()にfrictionとnumのリスナーを追加します。

// イベントリスナー
num.addListener(this, &ofApp::onNumChanged);
friction.addListener(this, &ofApp::onFrictionChanged);

この設定したイベントリスナーは、ofApp内に関数として実装します。

// 摩擦を変更
void ofApp::onFrictionChanged(float &friction){
    for (int i = 0; i < particles.size (); i++) {
        particles[i].friction = friction;
    }
}

// パーティクルの数を変更
void ofApp::onNumChanged(int &num){
    // もしパーティクルの数が設定数よりも少ない場合
    if (num < particles.size()) {
        // パーティクルを削除
        for (int i = 0; i < particles.size() - num; i++) {
            particles.pop_back();
        }
    }
    // もしパーティクルの数が設定数よりも多い場合
    if (num > particles.size()) {
        // パーティクルを追加
        int addNum = num - particles.size();
        for (int i = 0; i < addNum; i++) {
            Particle p;
            p.friction = friction;
            p.setup(ofVec2f(ofRandom(ofGetWidth()), ofRandom(ofGetHeight())), ofVec2f(0, 0));
            particles.push_back(p);
        }
    }
}

このようにすることで、値を変更したタイミングで特定の処理をすることが可能となります。

最終的に以下のようなプログラムになりました。

screenshot_418

Addonを使ってみる 2 - ofxBox2D

ofxBox2Dとは

もう一つ、別のAddonを使用してみましょう。今度は、openFrameworksのパッケージには同梱されていないAddonsをダウンロードして使用してみます。

今回は、2次元での物理演算ライブラリofxBox2Dを取り上げてみます。

ofxBox2Dは、Box2Dという2次元での物理シミュレーションのためのライブラリをopenFrameworksのAddonとして作成したものです。このofxBox2Dを使用すると重力や物体同士の衝突判定、引力、ばねなどの様々な物理シミュレーションが簡単に利用可能です。

ofxBox2Dの入手

ofxBox2Dは下記のGithubリポジトリからダウンロードします。

Zipをダウンロードして展開したフォルダーを、addonsフォルダに格納します。

ofxBox2Dプロジェクトの作成

ofxBox2Dを利用したプロジェクトは、ProjectGeneratorを利用して作成します。Addons欄でofxBox2Dをチェックして生成します。

screenshot_421

ofxBox2D物理世界の生成

ofxBox2Dでは物理法則を適用した世界を生成し、そこに物体を追加していきます。まず、物理世界を生成して追加してみましょう。

ofApp.h

#pragma once

#include "ofMain.h"
#include "ofxBox2d.h"

class ofApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
    
    ofxBox2d box2d; // Box2Dの世界
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    // 画面の基本設定
    ofSetFrameRate(60);
    ofBackground(0);
    
    // Box2Dの世界の設定
    box2d.init();               // 初期化
    box2d.setGravity(0, 10);    // 重力
    box2d.createBounds();       // 画面の周囲に壁を作成
    box2d.setFPS(30.0);         // box2Dの世界でのFPS
    box2d.registerGrabbing();   // 物体をつかめるようにする
}

//--------------------------------------------------------------
void ofApp::update(){
    box2d.update();             // box2Dの更新
}

//--------------------------------------------------------------
void ofApp::draw(){

}

ofxBox2d、物体を追加する(単体)

では、作成した世界に物体を追加してみましょう。ofxBox2Dにはあらかじめ物理法則を適用可能な基本図形が用意されています。

  • 円 – ofxBox2dCircle
  • 四角形 – ofxBox2dRect
  • ポリゴン – ofxBox2dPolygon

この中から、円(ofxBox2dCircle)をひとつ追加してみましょう。

ofApp.h

#pragma once

#include "ofMain.h"
#include "ofxBox2d.h"

class ofApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
    
    ofxBox2d box2d;         // Box2Dの世界
    ofxBox2dCircle circle;  // 円
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    // 画面の基本設定
    ofSetFrameRate(60);
    ofBackground(0);
    
    // Box2Dの世界の設定
    box2d.init();               // 初期化
    box2d.setGravity(0, 10);    // 重力
    box2d.createBounds();       // 画面の周囲に壁を作成
    box2d.setFPS(30.0);         // box2Dの世界でのFPS
    box2d.registerGrabbing();   // 物体をつかめるようにする
    
    circle.setPhysics(3.0, 0.53, 0.1);  // 円の物理パラメータを設定
    circle.setup(box2d.getWorld(), ofGetWidth() / 2.0, 100, 40); // 円を物理世界に追加
}

//--------------------------------------------------------------
void ofApp::update(){
    box2d.update();             // box2Dの更新
}

//--------------------------------------------------------------
void ofApp::draw(){
    ofSetColor(0, 127, 255);
    circle.draw();              // 円の描画
}

これで、物理世界に円が追加されました。マウスで掴んで投げることも可能です。

screenshot_422

ofxBox2d、複数の物体を追加する(配列)

では、次にvectorを使用して、大量の物体を追加してみましょう。

ofxBox2Dではvectorの使用法に若干注意が必要です。これまでの例のようにvectorを使用してofxBox2dCircleの可変長配列を作成すると、以下のようになるでしょう。しかし、この方法ではエラーになります。

vector  circles;
ofxBox2dCircle circle;
circles.push_back(circle);

ofxBox2DではofPtrというポインタを賢く管理する仕組みを導入するようになっています。

// in your header files
vector  > circles;

// now add a circle to the vector
ofPtr circle = ofPtr(new ofxBox2dCircle);

// to grab the pointer you use the get() function of ofPtr (std::shared_ptr)
circle.get()->setPhysics(3.0, 0.53, 0.1);
circle.get()->setup(box2d.getWorld(), 100, 100, 10);
circles.push_back(circle);

これを踏まえて、「c」のキーを押すと円を追加、「b」のキーを押すと四角形を追加するようにしてみましょう。

ofApp.h

#pragma once

#include "ofMain.h"
#include "ofxBox2d.h"

class ofApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed(int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
    
    ofxBox2d box2d;                             // Box2Dの世界
    vector  >    circles;    // 円の配列
    vector  > boxes;        // 四角の配列
};

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
    // 画面の基本設定
    ofSetFrameRate(60);
    ofBackground(0);
    
    // Box2Dの世界の設定
    box2d.init();               // 初期化
    box2d.setGravity(0, 10);    // 重力
    box2d.createBounds();       // 画面の周囲に壁を作成
    box2d.setFPS(30.0);         // box2Dの世界でのFPS
    box2d.registerGrabbing();   // 物体をつかめるようにする
}

//--------------------------------------------------------------
void ofApp::update(){
    box2d.update();             // box2Dの更新
}

//--------------------------------------------------------------
void ofApp::draw(){
    // 円を描画
    for(int i=0; idraw();
    }
    // 四角を描画
    for(int i=0; idraw();
    }
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
    // cキーで円を追加
    if(key == 'c') {
        float r = ofRandom(4, 20);
        circles.push_back(ofPtr(new ofxBox2dCircle));
        circles.back().get()->setPhysics(3.0, 0.53, 0.1);
        circles.back().get()->setup(box2d.getWorld(), mouseX, mouseY, r);
        
    }
    // bキーで四角を追加
    if(key == 'b') {
        float w = ofRandom(4, 20);
        float h = ofRandom(4, 20);
        boxes.push_back(ofPtr(new ofxBox2dRect));
        boxes.back().get()->setPhysics(3.0, 0.53, 0.1);
        boxes.back().get()->setup(box2d.getWorld(), mouseX, mouseY, w, h);
    }
}

これで、たくさんの図形を追加することができるようになりました。

screenshot_424