webamp/js/components/EqualizerWindow/EqGraph.tsx
2019-12-23 10:47:50 -08:00

111 lines
3.1 KiB
TypeScript

import React, { useState, useMemo, useLayoutEffect } from "react";
import { percentToRange, clamp } from "../../utils";
import { BANDS } from "../../constants";
import spline from "./spline";
import * as Selectors from "../../selectors";
import { usePromiseValueOrNull, useTypedSelector } from "../../hooks";
import { Slider } from "../../types";
const GRAPH_HEIGHT = 19;
const GRAPH_WIDTH = 113;
function EqGraph() {
const sliders = useTypedSelector(Selectors.getSliders);
const preampLineImagePromise = useTypedSelector(Selectors.getPreampLineImage);
const preampLineImage = usePromiseValueOrNull(preampLineImagePromise);
const [canvasNode, setCanvasNode] = useState<HTMLCanvasElement | null>(null);
const canvasCtx = useMemo(() => canvasNode?.getContext("2d") ?? null, [
canvasNode,
]);
const colorPattern = useColorPattern(canvasCtx);
useLayoutEffect(() => {
if (
canvasCtx == null ||
canvasNode == null ||
preampLineImage == null ||
colorPattern == null
) {
return;
}
const width = Number(canvasNode.width);
const height = Number(canvasNode.height);
canvasCtx.clearRect(0, 0, width, height);
drawEqLine({ colorPattern, sliders, canvasCtx, preampLineImage });
}, [canvasCtx, canvasNode, colorPattern, preampLineImage, sliders]);
return (
<canvas
id="eqGraph"
ref={setCanvasNode}
width={GRAPH_WIDTH}
height={GRAPH_HEIGHT}
/>
);
}
function useColorPattern(canvasCtx: CanvasRenderingContext2D | null) {
const lineColorsImagePromise = useTypedSelector(Selectors.getLineColorsImage);
const lineColorsImage = usePromiseValueOrNull(lineColorsImagePromise);
return useMemo(() => {
if (canvasCtx == null || lineColorsImage == null) {
return null;
}
return canvasCtx.createPattern(lineColorsImage, "repeat-x");
}, [canvasCtx, lineColorsImage]);
}
function drawEqLine({
colorPattern,
sliders,
canvasCtx,
preampLineImage,
}: {
sliders: Record<Slider, number>;
colorPattern: CanvasPattern;
preampLineImage: HTMLImageElement;
canvasCtx: CanvasRenderingContext2D;
}) {
const preampValue = percentToRange(sliders.preamp / 100, 0, GRAPH_HEIGHT - 1);
canvasCtx.drawImage(
preampLineImage,
0,
preampValue,
preampLineImage.width,
preampLineImage.height
);
const amplitudes = BANDS.map(band => sliders[band]);
canvasCtx.fillStyle = colorPattern;
const paddingLeft = 2; // TODO: This should be 1.5
const min = 0;
const max = GRAPH_HEIGHT - 1;
const xs: number[] = [];
const ys: number[] = [];
amplitudes.forEach((value, i) => {
const percent = (100 - value) / 100;
// Each band is 12 pixels widex
xs.push(i * 12);
ys.push(percentToRange(percent, min, max));
});
const allYs = spline(xs, ys);
const maxX = xs[xs.length - 1];
let lastY = ys[0];
for (let x = 0; x <= maxX; x++) {
const y = clamp(Math.round(allYs[x]), 0, GRAPH_HEIGHT - 1);
const yTop = Math.min(y, lastY);
const height = 1 + Math.abs(lastY - y);
canvasCtx.fillRect(paddingLeft + x, yTop, 1, height);
lastY = y;
}
}
export default EqGraph;