見出し画像

Getting in Tune.:Processing ぼんやり光る作例

ぼんやり光るジェネレーティブアートとして面白い作例ができたので、ソースコードの解説と合わせてご紹介します。
マジックナンバーが随所に出てくる非常に汚いソースです。

ただのメモじゃつまらないので、私だったらこの Processing のソースをこう読み解いていくという視点で書いてみます。



ソースコード

噂の汚いソースコードがこちらです。
4/30 にリファクタリングしたようなコメントが書いてありますが、「だったらなんでマジックナンバー残ってるんだ」というツッコミは甘んじて受けます。
マジックナンバー多すぎて、名前を付けきれなかったんです。

// Getting in Tune.
// Processing 3.2.1
// 2018.02.24
// 2018.04.30 Refactoring
// 20fps x 20s

float halfW;
float halfH;
float baseRadius;
float baseHue;
float seedSize;
float seedShape;
float pitchTune;

void setup() {
  
  size(720, 720);
  colorMode(HSB, 360, 100, 100, 100);
  smooth();

  halfW      = width / 2;
  halfH      = height / 2;
  baseRadius = halfH;
  baseHue    = random(360);
  seedSize   = random(100);

  getOutTune();  // start with out of tune

}

void draw() {

  background(0, 0, 0, 100);
  translate(halfW, halfH);
  rotate(radians(267.0 + frameCount / 10.0));	// start with vertical form

  blendMode(SCREEN);
  drawShape();
  if (frameCount % 160 == 0) {
    getOutTune();
  }

  blendMode(BLEND);
  drawScope();

  saveFrame("frames/####.png");
  if (frameCount > 20 * 20) {
    exit ();
  }

}

float customNoise(float value) {
  return pow(sin(value), 3) * cos(pow(value, 2));
}

void getOutTune() {
  // define new random shape & get out the tuning
  seedShape = radians(180) * (ceil(random(34)) * 3);  // magic number for nice shapes
  pitchTune = 1.0;
  baseHue  += 30;
}

void drawShape() {

  noStroke();
  float noiseSize  = seedSize;
  float noiseShape = seedShape;
  float noiseTune  = pitchTune;
  
  // this weird for sentence is for centering the shape
  for (float ptX = -(halfW + 150); ptX <= halfW; ptX += 0.8) {

    float ptY = map(customNoise(noiseTune) + customNoise(noiseShape), -2.0, 2.0, -baseRadius, baseRadius);
    float ptSiz = map(abs(customNoise(noiseSize)), 0.0, 1.0, 10.0, 40.0);
    float ptSat = map(abs(customNoise(noiseSize)), 0.0, 1.0, 20.0, 60.0);
    float ptBri = map(abs(customNoise(noiseSize)), 0.0, 1.0, 20.0, 10.0);
    float ptAlp = 100.0;
    float ptHue = (
                  baseHue
                  + map(ptX, -halfW, halfW, 0, 30)
                  + map(ptY, -baseRadius, baseRadius, 0, 90)
                  )
                  % 360;

    // draw glow point
    for (float i = 1.0; i < ptSiz; ++i) {
      fill(
           ptHue,
           map(i, 1.0, ptSiz, ptSat, 80.0),
           map(i, 1.0, ptSiz, ptBri, 5.0),
           ptAlp
           );
      ellipse(ptX, ptY, i, i);
    }

    noiseSize  += 0.003;
    noiseShape += 0.0024;	// important:shape size == scope size
    noiseTune  += pitchTune;

  }

  seedSize  += 0.003;	            // blink
  seedShape += 0.002 / seedShape; // roll & scroll
  pitchTune /= 1.3;	              // geting in tune

}

void drawScope() {

  strokeWeight(200.0);
  stroke(0.0, 0.0, 100.0, 100.0);
  fill(0.0, 0.0, 0.0, 0.0);
  ellipse(0.0, 0.0, width + 200.0, height + 200.0);

}


/*
Copyright (C) 2018 deconbatch

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

ソースのライセンスは GPLv3 で公開させていただきます。
フリーソフトウェアです。

自由に使っていただいて、じゃんじゃん作り変えて、どんどんソースも公開してもらって、バンバン拡めていっちゃいましょう!

ソースコード解説

この作品のテーマは『ずれていたチューニングがバッチリ合ったら奇妙な模様が現れた!』です。

定期的にチューニングをずらし、それと同時に模様のパターンを変更しています。
チューニングがずれると画面が「ぶしゃ〜」っとなるので、模様が変わったことを誤魔化せるという寸法です。

リアルタイムに見るためというより、パラパラマンガ方式の動画のコマ絵を作成するために書きました。

draw() を読んで概要を知る

まずは setup() でキャンバスサイズとカラーモード等の設定を確認しましょう。
正方形のキャンバスで、カラーモードは HSB ですね。

void setup() {
  
  size(720, 720);
  colorMode(HSB, 360, 100, 100, 100);

次に draw() の中をチェックして概要を掴んでいきます。
draw() の中では、毎回黒で塗りつぶして、キャンバスの中心に座標の基準を持ってきて、少しずつキャンバスを回転させているようです。

  background(0, 0, 0, 100);
  translate(halfW, halfH);
  rotate(radians(267.0 + frameCount / 10.0));	// start with vertical form

回転の値が何やら怪しい数式になっていますが、詳細には入らずに置いておきましょう。
キャンバスを回転させているということがわかれば十分です。

次に blendMode を SCREEN に変更しています。
SCREEN ということは、これはきっと次の drawShape() でぼんやり光らせようとしてますね。奇妙な形を描画しているのはここでしょう。
そして 160 フレーム毎に getOutTune() を呼んでいます。定期的に何かしているということですね。

  blendMode(SCREEN);
  drawShape();
  if (frameCount % 160 == 0) {
    getOutTune();
  }

最後に blendMode を上書きモードの BLEND にして drawScope(); を呼んでいます。
BLEND ということは、ここではきっと何かを上書きしているんですね。
そして、出来上がった絵をファイルに保存、これを 20x20 フレーム分繰り返して終了ですね。

  blendMode(BLEND);
  drawScope();

  saveFrame("frames/####.png");
  if (frameCount > 20 * 20) {
    exit ();
  }

20x20 フレーム分というのは、ソースの最初のコメントにあった

// 20fps x 20s

のことでしょう。
20fps のフレームレートで、20秒間のビデオにするためのようです。

ポイントを押さえる

draw() を読む限り、今回のソースのメインは drawShape() でしょう。
定期的に何かしていた getOutTune() も気になります。

まずは getOutTune() の方をチェックしてみましょう。

void getOutTune() {
  // define new random shape & get out the tuning
  seedShape = radians(180) * (ceil(random(34)) * 3);  // magic number for nice shapes
  pitchTune = 1.0;
  baseHue  += 30;
}

3つの変数の値を変更しています。この変数は全てグローバル変数ですし、ソース全体の動きをコントロールするものに違いありません。
この変数を追っていくのがソースを読み解くポイントになりそうです。

変数名から想像するに、それぞれの用途は以下のようなものでしょう。

・seedShape 形の元
・pitchTune チューニングの高低
・baseHue 基本の色

ポイントを追いながらメインのルーチンを読む

先の3つの変数に着目しながら今回のソースのメインルーチンであろう drawShape() を読んでいきましょう。

drawShape() の概要
drawShape() は大きく以下のような構成になっています。

  // ローカル変数セット

  for (float ptX = -(halfW + 150); ptX <= halfW; ptX += 0.8) {

    // ローカル変数で何かの計算

    for (float i = 1.0; i < ptSiz; ++i) {
      // 計算した値で描画
    }

    // ローカル変数の値変更

  }

  // グローバル変数の値変更

これは Processing で動画を作るときの定番の構成ですね。
ローカル変数は描画するものの形や色を計算するため、グローバル変数はコマ毎の動きをコントロールするためのものです。

2つ目の for ループはその中に

      ellipse(ptX, ptY, i, i);

とあることから、サイズの異なる複数の円を同じ場所に描画するため、つまりぼんやり光らせるためのループですね。
このループは無視しておいてよさそうです。

1つめの for ループは変な値のループになってますが、halfW(= width / 2、キャンバスの横幅の半分)から想像するに描画の x軸の走査のようです。

「ローカル変数で何かの計算」を見ると、描画のポイントの y軸の計算、円のサイズ、色の彩度、明るさ、色相の計算をしているようです。

    // ローカル変数で何かの計算

    float ptY = map(customNoise(noiseTune) + customNoise(noiseShape), -2.0, 2.0, -baseRadius, baseRadius);
    float ptSiz = map(abs(customNoise(noiseSize)), 0.0, 1.0, 10.0, 40.0);
    float ptSat = map(abs(customNoise(noiseSize)), 0.0, 1.0, 20.0, 60.0);
    float ptBri = map(abs(customNoise(noiseSize)), 0.0, 1.0, 20.0, 10.0);
    float ptAlp = 100.0;
    float ptHue = (
                  baseHue
                  + map(ptX, -halfW, halfW, 0, 30)
                  + map(ptY, -baseRadius, baseRadius, 0, 90)
                  )
                  % 360;

奇妙な模様は y軸の計算で出てくる customNoise() で形作っているようです。

ここで見るのはここまで。計算式まで見ると流れを見失ってしまうので、まだ計算式の中までは見ません

3つの変数に着目
さて、先の3つの変数に着目してみましょう。

まずは seedShape、これは以下の 2箇所で出てきます。
これは定番の使い方ですね。

  // ローカル変数セット
  float noiseShape = seedShape;

描画のための初期値としてローカル変数にセット

  // グローバル変数の値変更
  seedShape += 0.002 / seedShape; // roll & scroll

動画のために初期値をちょっと変更(次のフレームではちょっと変更した値が描画の初期値として使われるというわけ)

noiseShape は「ローカル変数で何かの計算」の中で、描画ポイントの y軸の計算に使われています。

    // ローカル変数で何かの計算
    float ptY = map(customNoise(noiseTune) + customNoise(noiseShape), -2.0, 2.0, -baseRadius, baseRadius);

そして「ローカル変数の値変更」の中で、描画の x軸に沿って変化するようになっています。

    // ローカル変数の値変更
    noiseShape += 0.0024;	// important:shape size == scope size

この値の変化が奇妙な模様を形作っているようですね。

さらに、seedShape は定期的に呼び出される getOutTune() の中でランダムな値がセットされます。
奇妙な模様の初期値を定期的に変えているということですね。

void getOutTune() {
  // define new random shape & get out the tuning
  seedShape = radians(180) * (ceil(random(34)) * 3);  // magic number for nice shapes
  pitchTune = 1.0;
  baseHue  += 30;
}


次に pitchTune。これは以下の 3箇所で出てきます。
seedShape より一箇所多く出てきます。

  // ローカル変数セット
  float noiseTune  = pitchTune;

これは seedShape と同じく描画のための初期値としてローカル変数にセットですね。

    // ローカル変数の値変更
    noiseTune  += pitchTune;

ここが seedShape には無かった部分です。
描画の x軸に沿って noiseTune を pitchTune 分加算していってます。

  // グローバル変数の値変更
  pitchTune /= 1.3;	              // geting in tune

ここは定番の「動画のために初期値をちょっと変更」ですね。
フレームが進むにつれて少しずつゼロに近づけていってます。

ということは、フレームが進むにつれて x軸に沿った noiseTune の値変化が少なくなるということですね。
noiseTune は「ローカル変数で何かの計算」の中で、noiseShape と共に描画ポイントの y軸の計算に使われています。

    // ローカル変数で何かの計算
    float ptY = map(customNoise(noiseTune) + customNoise(noiseShape), -2.0, 2.0, -baseRadius, baseRadius);

pitchTune は定期的に呼び出される getOutTune() の中で値 1.0 をセットされ、フレームが進むに連れて少しずつゼロに近づいていきます。

void getOutTune() {
  // define new random shape & get out the tuning
  seedShape = radians(180) * (ceil(random(34)) * 3);  // magic number for nice shapes
  pitchTune = 1.0;
  baseHue  += 30;
}

まとめると、
・描画ポイントの y軸は最初は x軸に沿って大きく変化する。
・フレームが進むにつれて y軸の x軸に沿った変化は小さくなって、やがて変化はゼロに近くなる。
・定期的にまた大きな変化が始まる。

ということになります。

これがチューニングのずらし、ぶしゃ〜の正体です。


最後は baseHue です。
これは簡単ですね。基本の色をどれにするかをランダムに決めて、定期的に色相環上を 30度進めて色を変えています。

描画の色の決定はこのような式になっています。

    float ptHue = (
                  baseHue
                  + map(ptX, -halfW, halfW, 0, 30)
                  + map(ptY, -baseRadius, baseRadius, 0, 90)
                  )
                  % 360;

基本色から x軸に沿って 30度、 y軸に沿って 90度変化させています。
最後の % 360 は、加算していくと 360度を超える可能性があるので、360 で割った余りを取っています。


マジックナンバー

このソースではいわゆるマジックナンバーが放ったらかしになっています。
よくないですね〜。

マジックナンバーについてはこちらを参照ください。

たとえばここ。

  for (float ptX = -(halfW + 150); ptX <= halfW; ptX += 0.8) {

何のこの for 文? 150 って何よ?

はい、すみません。
この 150 が無いと、こんな感じで奇妙な模様が真ん中に来なかったんです。てへっ!


じゃ、ここは?

  rotate(radians(267.0 + frameCount / 10.0));	// start with vertical form

267.0 ってまた微妙な数値だな。何これ?

はっ!これはですね、ぶしゃ〜が終わって奇妙な模様が現れたタイミングでちょうど垂直になるように調整した結果でして、これが 270 とかだと、

ちょっと斜めにずれちゃんですねー。

ちなみに 87.0 でもいいんですけど、なぜ 267.0 にしたかは不明です。
これぞマジックナンバー! よっ!日本一!

…真似しちゃいけません。

奇妙な模様形成のロジック

と、このソースの概要は以上です。

で、この奇妙な模様を描画する計算ロジックなんですが、それはこの部分になります。

float customNoise(float value) {
  return pow(sin(value), 3) * cos(pow(value, 2));
}

そして、この計算式、実は偶然の産物です。

元々はパーリンノイズに頼らずに、自前のノイズ関数を試してみようといろいろ試行錯誤して出した式です。
プロットしてみるとこんな感じ。

ぐちゃぐちゃな値でしょ? ノイズというよりランダムですねこりゃ。

ところがある時、パラメータの値の差分がある値のときに規則性のある戻り値を得られることを偶然に発見したんです。
3.07 のときに注目!

これが出てきたときには驚きました!

この偶然のおかげで、このような作品を作ることができました。
いろいろやってみるもんですね。
全然スマートじゃないけど。

この関数、まだまだ可能性を秘めてる感じがします。
ぜひ使ってあらたな可能性を引き出してください。

というか、数学の得意な方ならなぜこのような結果になるのか説明できちゃんじゃないかな?

スクリーンショット

ソースコードの説明ってなかなか難しいですね。
やたら長くなってしまったので、最後はキレイなこれぞジェネラティブアートなスクリーンショットで締めさせていただきます。

読んでくださってありがとうございます。




この記事が面白かったらサポートしていただけませんか? ぜんざい好きな私に、ぜんざいをお腹いっぱい食べさせてほしい。あなたのことを想いながら食べるから、ぜんざいサポートお願いね 💕