← INDEX / 一覧 NYAMCO WORKS 63 / 71
p5.js sketch works/hanareru/sketch.js runs here
作品はここで実行されます(後から差し替え)

WORK 63

Dispersion

はなれる

YEAR
2026
TOOLS
p5.js
TECHNIQUE
p5.js / dispersive delay, center of mass

One signal goes out as a single straight line. Every component travels straight; only their speeds differ. No one tilts it, and yet it leans further the further it goes. Each component stops when it reaches the far line, in the order it arrives, and at the end they are a straight line again. Once they are lined up, nothing anywhere records how far apart they had been.

ひとつの合図が、まっすぐな一本の線として出ていく。成分はどれもまっすぐ進む。速さが少しずつ違うだけで、誰も傾けていないのに、進むほどに倒れていく。到着線に着いた順に止まり、最後はまた一直線に戻る。並んでしまえば、どれだけ離れていたかは、どこにも残らない。

ばねリバーブを自分で書いてみた日に出てきた。ばねの中を通る波は、周波数によって速さが違う——低いところが遅れて、高いところが先に着く。だからインパルスを入れると、返ってくるのは「ばらけたもの」になる。

数字にすると消える。残響の長さ(RT60)という一つの値では、ばねと部屋がほとんど区別できなかった。帯域ごとの重心を並べたときだけ、差が出た。低域 107ms、高域 12ms。

赤い印は重心。合図がいまどこにあるかを、一つの値で言っている。まっすぐ進むし、嘘もつかない。ただ、着いたあとには残らない。

SOURCE / ソース
// ── はなれる (Dispersion) ─────────────────────────────────────────
// ひとつの合図は、まっすぐな一本の線として出ていく。
// 成分はどれもまっすぐ進む。速さが少しずつ違うだけで、
// 誰も傾けていないのに、進むほどに倒れていく。
// 到着線に着いた順に止まり、最後はまた一直線に並ぶ。
// 並んでしまえば、ばらけたことは、どこにも残らない。
export default function sketch(p) {
  const W = 720, H = 720;

  const M = 40;             // ひとつの合図に入っている成分の数
  const INTERVAL = 168;     // 次の合図までのフレーム
  const V_BASE = 1.05;      // いちばん遅い成分の速さ
  const V_SPREAD = 2.35;    // 速さの開き
  const CURVE = 2.1;        // 開き方の曲がり(分散はまっすぐではない)
  const X0 = 96;
  const X_END = W - 92;
  const Y_TOP = 108, Y_BOT = H - 108;

  let pulses = [];
  let ink, mark, guide;
  let nextAt = 0;

  p.setup = () => {
    p.createCanvas(W, H);
    const d = new Date();
    const day = d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate();
    p.randomSeed(day * 613);
    p.noiseSeed(day * 271);

    ink   = p.color(34, 36, 42);
    mark  = p.color(198, 62, 74);
    guide = p.color(186, 188, 184);

    p.background(250, 250, 247);
  };

  function fire() {
    const comps = [];
    for (let i = 0; i < M; i++) {
      const u = i / (M - 1);
      comps.push({
        x: X0,
        y: p.lerp(Y_TOP, Y_BOT, u),
        v: V_BASE + V_SPREAD * Math.pow(u, CURVE),
        landed: false,
      });
    }
    pulses.push({ comps, alive: M });
  }

  p.draw = () => {
    // 前の姿を薄く沈める。倒れていく途中が何段階も重なって残る
    p.noStroke();
    p.fill(250, 250, 247, 20);
    p.rect(0, 0, W, H);

    if (p.frameCount >= nextAt) { fire(); nextAt = p.frameCount + INTERVAL; }

    // 出発線と到着線
    p.stroke(guide); p.strokeWeight(1);
    p.line(X0, Y_TOP - 22, X0, Y_BOT + 22);
    p.line(X_END, Y_TOP - 22, X_END, Y_BOT + 22);

    for (const pu of pulses) {
      let sum = 0;
      for (const c of pu.comps) {
        if (!c.landed) {
          c.x += c.v;
          if (c.x >= X_END) { c.x = X_END; c.landed = true; pu.alive--; }
        }
        sum += c.x;
      }
      const cx = sum / M;

      // 合図そのもの。一本の線
      p.noFill();
      const done = pu.alive === 0;
      p.stroke(p.red(ink), p.green(ink), p.blue(ink), done ? 165 : 205);
      p.strokeWeight(done ? 2.6 : 2.0);
      p.beginShape();
      for (const c of pu.comps) p.vertex(c.x, c.y);
      p.endShape();

      // 重心。ひとつだけ、はっきりした印
      p.noStroke();
      p.fill(mark);
      p.circle(cx, (Y_TOP + Y_BOT) / 2, 6.2);
    }

    if (pulses.length > 14) pulses.shift();
  };
}