/ proof-of-concept / phase-perception
Sign in
proof-of-concept / phase-perception
phase-perception / src / components / WaveformPlot.tsx
77 lines · 2.3 KBBlameHistoryRaw
1import { useCallback } from 'react'
2import { useCanvas } from '../hooks/useCanvas'
3import { renderPeriod } from '../synth'
5type Props = {
6 amplitudes: number[]
7 /** radians */
8 phases: number[]
9 color: string
10 /** vertical half-range; pass the same value for both tones so they share a scale */
11 yMax: number
14const M = { l: 40, r: 10, t: 10, b: 22 }
16/** One period of the waveform x(t), unscaled. */
17export function WaveformPlot({ amplitudes, phases, color, yMax }: Props) {
18 const draw = useCallback(
19 (ctx: CanvasRenderingContext2D, w: number, h: number) => {
20 const pw = w - M.l - M.r
21 const ph = h - M.t - M.b
22 const y0 = M.t + ph / 2
23 const ymax = yMax > 0 ? yMax : 1
24 const toY = (v: number) => y0 - (v / ymax) * (ph / 2)
26 ctx.font = '11px system-ui, sans-serif'
27 ctx.fillStyle = '#666'
28 ctx.strokeStyle = '#ddd'
29 ctx.lineWidth = 1
31 // frame and zero line
32 ctx.strokeRect(M.l + 0.5, M.t + 0.5, pw, ph)
33 ctx.beginPath()
34 ctx.moveTo(M.l, y0 + 0.5)
35 ctx.lineTo(M.l + pw, y0 + 0.5)
36 ctx.stroke()
37 // quarter-period grid
38 for (let q = 1; q < 4; q++) {
39 const x = Math.round(M.l + (pw * q) / 4) + 0.5
40 ctx.beginPath()
41 ctx.moveTo(x, M.t)
42 ctx.lineTo(x, M.t + ph)
43 ctx.stroke()
44 }
46 // ticks
47 ctx.textAlign = 'right'
48 ctx.textBaseline = 'middle'
49 ctx.fillText(ymax.toFixed(1), M.l - 4, toY(ymax))
50 ctx.fillText('0', M.l - 4, y0)
51 ctx.fillText((-ymax).toFixed(1), M.l - 4, toY(-ymax))
52 ctx.textAlign = 'center'
53 ctx.textBaseline = 'top'
54 ctx.fillText('0', M.l, M.t + ph + 4)
55 ctx.fillText('T/2', M.l + pw / 2, M.t + ph + 4)
56 ctx.fillText('T', M.l + pw, M.t + ph + 4)
58 // curve
59 const n = Math.max(64, Math.round(pw * 2))
60 const y = renderPeriod(amplitudes, phases, n)
61 ctx.strokeStyle = color
62 ctx.lineWidth = 1.8
63 ctx.lineJoin = 'round'
64 ctx.beginPath()
65 for (let i = 0; i <= n; i++) {
66 const px = M.l + (pw * i) / n
67 const py = toY(y[i % n])
68 if (i === 0) ctx.moveTo(px, py)
69 else ctx.lineTo(px, py)
70 }
71 ctx.stroke()
72 },
73 [amplitudes, phases, color, yMax],
74 )
75 const ref = useCanvas(draw)
76 return <canvas ref={ref} className="plot waveform-plot" />
↑↓ move↵openescclose