<?phpnamespace App\Scene;use App\Audio\Scale;use App\Render\Gfx;use JumPHP\Renderer\Renderer;/** * A checkerboard that ripples like cloth: every tile moves and swells with a travelling wave, so the flat grid looks curved and deep. On every * beat (twice a second) a note of the Hirajoshi scale rings and a bright wave of colour runs out from the middle. 4 seconds make a * whole number of waves and beats: it loops with no seam. */final class WaveGridScene extends VideoScene{ public const SECONDS = 24.0; protected const LOOPS = true; public const PERIOD = 4.0; public const BEATS = 8; private const COLS = 11; private const ROWS = 17; private const CELL = 25.0; private int $lastBeat = -1; private float $beatAge = 9.0; protected function melodyNotes(): string { return 'C4'; } protected function headingText(): string { return 'IT IS PERFECTLY FLAT'; } protected function hookText(): string { return 'CAN YOU SEE THE WAVES?'; } protected function captionText(): string { return $this->t('BEATS {n}', ['n' => max(0, $this->lastBeat)]); } protected function build(): void { $this->lastBeat = -1; $this->beatAge = 9.0; } protected function tick(float $dt): void { $this->beatAge += $dt; $n = (int)floor($this->time / self::PERIOD * self::BEATS); if ($n > $this->lastBeat) { $this->lastBeat = $n; $this->beatAge = 0.0; $this->play(Scale::freq(Scale::HIRAJOSHI, 3 + [0, 2, 4, 2, 5, 3, 1, 4][$n % 8], 57), 'marimba', 0.8, 0.3, 0.0); if ($n % 4 === 0) { $this->pad([Scale::freq(Scale::HIRAJOSHI, 0, 45), Scale::freq(Scale::HIRAJOSHI, 3, 45)], 3.6, 0.06); } $this->pulse = 1.0; $this->tint = Gfx::hsv($n * 0.11); } } protected function drawWorld(Renderer $r): void { $w = 2 * M_PI * $this->time / self::PERIOD; $ox = (270 - self::COLS * self::CELL) / 2; $oy = 262 - self::ROWS * self::CELL / 2; for ($j = -1; $j <= self::ROWS; $j++) { for ($i = -1; $i <= self::COLS; $i++) { $x = $ox + ($i + 0.5) * self::CELL; $y = $oy + ($j + 0.5) * self::CELL; $dx = ($i - (self::COLS - 1) / 2) / 5.0; $dy = ($j - (self::ROWS - 1) / 2) / 8.0; $dist = sqrt($dx * $dx + $dy * $dy); $wave = sin(2 * M_PI * 1.15 * $dist - $w) + 0.55 * sin(2.4 * $dx + 1.3 * $dy + 2 * $w) ; $x += 3.4 * sin(1.1 * ($j + 0.5) + $w) + 2.2 * $wave * $dx; $y += 3.4 * sin(1.1 * ($i + 0.5) - $w) + 2.2 * $wave * $dy; $size = self::CELL * (0.86 + 0.11 * $wave); $dark = (($i + $j) % 2 + 2) % 2 === 0; $hue = 0.62 + 0.18 * sin($w + $dist * 2.2); // the beat's wave of light runs out from the middle $ring = abs($dist * 0.9 - $this->beatAge * 1.5); $lit = $ring < 0.55 ? (1 - $ring / 0.55) * max(0.0, 1 - $this->beatAge / 1.1) : 0.0; $rgb = $dark ? Gfx::hsv($hue, 0.7, 0.42 + 0.2 * $wave) : Gfx::hsv($hue + 0.12, 0.35, 0.92 + 0.08 * $wave); $rgb = Gfx::mix($rgb, [255, 255, 255], $lit * 0.75); $r->fillRect($x - $size / 2, $y - $size / 2, $size, $size, Gfx::color($rgb)); } } }}