| 1 | import { COLS, ROWS } from '../audio/scales'; |
| 2 | |
| 3 | export function cellAt( |
| 4 | x: number, |
| 5 | y: number, |
| 6 | W: number, |
| 7 | H: number, |
| 8 | ): number { |
| 9 | const col = Math.min(COLS - 1, Math.max(0, Math.floor((x / W) * COLS))); |
| 10 | const row = Math.min(ROWS - 1, Math.max(0, Math.floor((y / H) * ROWS))); |
| 11 | return row * COLS + col; |
| 12 | } |
| 13 | |
| 14 | export function cellRowCol(idx: number): { row: number; col: number } { |
| 15 | return { row: Math.floor(idx / COLS), col: idx % COLS }; |
| 16 | } |
| 17 | |
| 18 | export function drawGrid( |
| 19 | ctx: CanvasRenderingContext2D, |
| 20 | W: number, |
| 21 | H: number, |
| 22 | flashes: Map<number, number>, |
| 23 | now: number, |
| 24 | flashMs: number, |
| 25 | ) { |
| 26 | const cw = W / COLS; |
| 27 | const ch = H / ROWS; |
| 28 | |
| 29 | for (const [idx, end] of flashes) { |
| 30 | const remain = end - now; |
| 31 | if (remain <= 0) { |
| 32 | flashes.delete(idx); |
| 33 | continue; |
| 34 | } |
| 35 | const a = (remain / flashMs) * 0.18; |
| 36 | const { row, col } = cellRowCol(idx); |
| 37 | ctx.fillStyle = `rgba(245, 243, 238, ${a})`; |
| 38 | ctx.fillRect(col * cw, row * ch, cw, ch); |
| 39 | } |
| 40 | |
| 41 | ctx.strokeStyle = 'rgba(245, 243, 238, 0.08)'; |
| 42 | ctx.lineWidth = 1; |
| 43 | ctx.beginPath(); |
| 44 | for (let c = 1; c < COLS; c++) { |
| 45 | const x = c * cw; |
| 46 | ctx.moveTo(x, 0); |
| 47 | ctx.lineTo(x, H); |
| 48 | } |
| 49 | for (let r = 1; r < ROWS; r++) { |
| 50 | const y = r * ch; |
| 51 | ctx.moveTo(0, y); |
| 52 | ctx.lineTo(W, y); |
| 53 | } |
| 54 | ctx.stroke(); |
| 55 | } |