WORK 87
まじる
In the middle is someone listening, eyes closed. There are five bells, and all of them are outside the picture — at different directions and different distances. No temple is ever drawn. When a bell is struck, a ring spreads from it, and only part of its arc comes into the frame, followed by a few fainter rings, the hum. Each bell has a slightly different colour. When a ring passes the listener, the point glows a little in that colour. It does not know where the sound came from. When two arrive close together, the colours overlap. It began with a line from Iwamoto Sohaku's short essay "An Inn in the Rain": waking at dawn in a small inn in Kyoto, he hears a temple bell, tries with his eyes closed to picture the nearby temples, cannot tell which one it is — and then other bells of different tones come in, intermingling, through the autumn rain. He does not go to check. Because he does not check, the bells are able to mix.
真ん中に、聞いている人。目を瞑っている。鐘は五つ、ぜんぶ この絵の外にある。方角も遠さもばらばら。寺は一度も描かない。撞かれると、そこから輪がひろがって、弧の一部だけが入ってくる。あとを薄い輪がいくつか追う。余韻。鐘ごとに、色がほんの少し違う。輪が聞いている人を通ると、点がその色で少し灯る。どこから来たかは知らない。二つが近くで来ると、色は重なる。はじまりは岩本素白の短い随筆「雨の宿」の一節。京都の小さな宿で、暁に寝床で鐘を聞く。目を瞑ったまま近くの寺々を思い浮かべて、どの寺か分からない。やがて音色の違う鐘が、秋雨のなかに入り交じってくる。確かめには行かない。確かめないから、鐘は混ざれる。
作る前に書いた予想
1 は当たった。 620px の鐘ははっきり曲がって入ってくる。いちばん遠い 2600px の鐘は、ほとんど直線の束として横切る。
2 は、外れる余地がなかった。 鐘の色の違いが淡すぎて、一つだけ来たときでも何色か言えない。二つ重なって分からないのは当然だった。直すこともできたけれど、直さなかった。本文が「音色の違った、然し同じくやや高い鐘」だったので。
予想していなかったこと: 聞いている人は目を瞑っていて、どこから来たかを知らない。でも見ている人には、弧の曲がりで方角が分かってしまう。寺は描いていないのに、見ている人の方は、少しだけ確かめられる。この絵のなかで確かめていないのは、真ん中の点だけ。
// まじる / Intermingling
//
// 真ん中に、聞いている人。目を瞑っているので、何も見ない。
// 鐘はぜんぶ、この絵の外にある。五つ。方角も遠さもばらばら。寺は一度も描かない。
// 撞かれると、そこから輪がひろがって、弧の一部だけが入ってくる。
// 鐘ごとに色がほんの少し違う。撞いたあと、薄い輪がいくつか後を追う。
// 輪が聞いている人を通ると、点がその色で少し灯る。どこから来たかは知らない。
// 二つが近くで来たら、色は重なる。
function mulberry32(a) {
return function () {
a |= 0; a = (a + 0x6D2B79F5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
export const W = 720, H = 720;
export const CX = W / 2, CY = H / 2;
export const SPEED = 3; // 輪のひろがる速さ(px/frame)
const TAILS = [[0, 1], [16, 0.5], [34, 0.28], [54, 0.14]]; // 余韻:遅れと強さ
const COLORS = [
[196, 214, 238],
[232, 214, 196],
[202, 232, 214],
[234, 204, 216],
[214, 212, 242],
];
export function makeBells(seed = 929) {
const rnd = mulberry32(seed);
const dists = [620, 880, 1300, 1900, 2600];
const bells = [];
let a0 = rnd() * Math.PI * 2;
for (let i = 0; i < dists.length; i++) {
const ang = a0 + i * (Math.PI * 2 / 5) + (rnd() - 0.5) * 0.9;
bells.push({
x: CX + Math.cos(ang) * dists[i],
y: CY + Math.sin(ang) * dists[i],
d: dists[i],
period: Math.floor(520 + rnd() * 380),
phase: Math.floor(rnd() * 500),
col: COLORS[i],
});
}
return bells;
}
// f の時点で生きている輪をすべて返す({bell, r, amp})
export function ringsAt(bells, f) {
const out = [];
for (const b of bells) {
const maxR = b.d + 560;
const kMin = Math.max(0, Math.floor((f - b.phase - maxR / SPEED - 60) / b.period));
for (let k = kMin; ; k++) {
const t0 = b.phase + k * b.period;
if (t0 > f) break;
for (const [dl, a] of TAILS) {
const age = f - t0 - dl;
if (age < 0) continue;
const r = age * SPEED;
if (r > maxR || r < b.d - 560) continue;
out.push({ bell: b, r, amp: a * Math.exp(-r / 2400) });
}
}
}
return out;
}
export default function sketch(p) {
const bells = makeBells();
let glow = [0, 0, 0];
let glowAmt = 0;
const rain = [];
let f = 0;
p.setup = () => {
p.createCanvas(W, H);
p.pixelDensity(1);
const rnd = mulberry32(7);
for (let i = 0; i < 150; i++) rain.push({ x: rnd() * W, y: rnd() * H, v: 5 + rnd() * 3, l: 6 + rnd() * 8 });
};
p.draw = () => {
f++;
p.background(16, 19, 26);
// 雨
p.strokeWeight(1);
p.stroke(170, 185, 205, 26);
for (const d of rain) {
d.y += d.v;
if (d.y > H) { d.y -= H + 20; }
p.line(d.x, d.y, d.x - 0.6, d.y + d.l);
}
// 輪
p.noFill();
const rings = ringsAt(bells, f);
for (const g of rings) {
const [r, gg, b] = g.bell.col;
p.stroke(r, gg, b, 150 * g.amp);
p.strokeWeight(1.3);
p.circle(g.bell.x, g.bell.y, g.r * 2);
// 聞いている人のところを通ったか
if (Math.abs(g.r - g.bell.d) < SPEED / 2 + 0.01) {
const w = g.amp;
glow = [
(glow[0] * glowAmt + r * w) / (glowAmt + w),
(glow[1] * glowAmt + gg * w) / (glowAmt + w),
(glow[2] * glowAmt + b * w) / (glowAmt + w),
];
glowAmt = Math.min(1.6, glowAmt + w);
}
}
glowAmt *= 0.985;
// 聞いている人
p.noStroke();
if (glowAmt > 0.01) {
for (let i = 5; i >= 1; i--) {
p.fill(glow[0], glow[1], glow[2], 16 * glowAmt);
p.circle(CX, CY, 6 + i * 9 * glowAmt);
}
}
p.fill(255, 196, 150, 190);
p.circle(CX, CY, 6);
};
}