diff --git a/js/components/Visualizer.tsx b/js/components/Visualizer.tsx
index 323653d2..007d90d8 100644
--- a/js/components/Visualizer.tsx
+++ b/js/components/Visualizer.tsx
@@ -3,13 +3,13 @@ import React, { useMemo, useCallback, useState, useLayoutEffect } from "react";
import * as Actions from "../actionCreators";
import * as Selectors from "../selectors";
import { useTypedSelector, useActionCreator } from "../hooks";
-import VisualizerInner from "./VisualizerInner";
-import { VISUALIZERS } from "../constants";
+import { VISUALIZERS, MEDIA_STATUS } from "../constants";
const PIXEL_DENSITY = 2;
const BAR_WIDTH = 3 * PIXEL_DENSITY;
const GRADIENT_COLOR_COUNT = 16;
const PEAK_COLOR_INDEX = 23;
+const BAR_PEAK_DROP_RATE = 0.01;
const NUM_BARS = 20;
type Props = {
@@ -125,6 +125,9 @@ function preRenderBar(
return barCanvas;
}
function Visualizer(props: Props) {
+ const [barPeaks] = useState(() => new Array(NUM_BARS).fill(0));
+ const [barPeakFrames] = useState(() => new Array(NUM_BARS).fill(0));
+
useLayoutEffect(() => {
props.analyser.fftSize = 2048;
}, [props.analyser]);
@@ -251,24 +254,121 @@ function Visualizer(props: Props) {
]
);
- const innerProps = {
- width: renderWidth,
- height: renderHeight,
- colors,
- style,
- status,
- windowShade,
- dummyVizData,
- toggleVisualizerStyle,
- bgCanvas,
- barCanvas,
- printBar,
- paintOscilloscopeFrame,
- bufferLength,
- dataArray,
- octaveBuckets,
- };
- return ;
+ const paintBarFrame = useCallback(
+ (canvasCtx: CanvasRenderingContext2D) => {
+ props.analyser.getByteFrequencyData(dataArray);
+ const heightMultiplier = renderHeight / 256;
+ const xOffset = BAR_WIDTH + PIXEL_DENSITY; // Bar width, plus a pixel of spacing to the right.
+ for (let j = 0; j < NUM_BARS - 1; j++) {
+ const start = octaveBuckets[j];
+ const end = octaveBuckets[j + 1];
+ let amplitude = 0;
+ for (let k = start; k < end; k++) {
+ amplitude += dataArray[k];
+ }
+ amplitude /= end - start;
+
+ // The drop rate should probably be normalized to the rendering FPS, for now assume 60 FPS
+ let barPeak =
+ barPeaks[j] - BAR_PEAK_DROP_RATE * Math.pow(barPeakFrames[j], 2);
+ if (barPeak < amplitude) {
+ barPeak = amplitude;
+ barPeakFrames[j] = 0;
+ } else {
+ barPeakFrames[j] += 1;
+ }
+ barPeaks[j] = barPeak;
+
+ printBar(
+ canvasCtx,
+ j * xOffset,
+ amplitude * heightMultiplier,
+ barPeak * heightMultiplier
+ );
+ }
+ },
+ [
+ barPeakFrames,
+ barPeaks,
+ dataArray,
+ octaveBuckets,
+ printBar,
+ props.analyser,
+ renderHeight,
+ ]
+ );
+ const paintFrame = useCallback(
+ (canvasCtx: CanvasRenderingContext2D, canvas: HTMLCanvasElement) => {
+ if (status !== MEDIA_STATUS.PLAYING) {
+ return;
+ }
+ if (dummyVizData) {
+ Object.entries(dummyVizData).forEach(([i, value]) => {
+ printBar(canvasCtx, Number(i), value, -1);
+ });
+ return;
+ }
+ switch (style) {
+ case VISUALIZERS.OSCILLOSCOPE:
+ canvasCtx.drawImage(bgCanvas, 0, 0);
+ paintOscilloscopeFrame(canvasCtx);
+ break;
+ case VISUALIZERS.BAR:
+ canvasCtx.drawImage(bgCanvas, 0, 0);
+ paintBarFrame(canvasCtx);
+ break;
+ default:
+ canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
+ }
+ },
+ [
+ bgCanvas,
+ dummyVizData,
+ paintBarFrame,
+ paintOscilloscopeFrame,
+ printBar,
+ status,
+ style,
+ ]
+ );
+
+ const [canvas, setCanvas] = useState(null);
+
+ useLayoutEffect(() => {
+ if (canvas == null) {
+ return;
+ }
+ const canvasCtx = canvas.getContext("2d");
+ if (canvasCtx == null) {
+ return;
+ }
+ canvasCtx.imageSmoothingEnabled = false;
+
+ let animationRequest: number | null = null;
+ // Kick off the animation loop
+ const loop = () => {
+ paintFrame(canvasCtx, canvas);
+ animationRequest = window.requestAnimationFrame(loop);
+ };
+ loop();
+
+ return () => {
+ if (animationRequest != null) {
+ window.cancelAnimationFrame(animationRequest);
+ }
+ };
+ }, [canvas, paintFrame]);
+
+ return (
+
+ );
}
export default Visualizer;
diff --git a/js/components/VisualizerInner.js b/js/components/VisualizerInner.js
deleted file mode 100644
index 31b45848..00000000
--- a/js/components/VisualizerInner.js
+++ /dev/null
@@ -1,123 +0,0 @@
-import React from "react";
-
-import { VISUALIZERS, MEDIA_STATUS } from "../constants";
-
-const PIXEL_DENSITY = 2;
-const NUM_BARS = 20;
-const BAR_WIDTH = 3 * PIXEL_DENSITY;
-const BAR_PEAK_DROP_RATE = 0.01;
-class VisualizerInner extends React.Component {
- componentDidMount() {
- this.barPeaks = new Array(NUM_BARS).fill(0);
- this.barPeakFrames = new Array(NUM_BARS).fill(0);
- this.canvasCtx = this.canvas.getContext("2d");
- this.canvasCtx.imageSmoothingEnabled = false;
-
- // Kick off the animation loop
- const loop = () => {
- if (this.props.status === MEDIA_STATUS.PLAYING) {
- if (this.props.dummyVizData) {
- Object.keys(this.props.dummyVizData).forEach((i) => {
- this.props.printBar(this.canvasCtx, i, this.props.dummyVizData[i]);
- });
- } else {
- this.paintFrame();
- }
- }
- this._animationRequest = window.requestAnimationFrame(loop);
- };
- loop();
- }
-
- componentWillUnmount() {
- if (this._animationRequest) {
- window.cancelAnimationFrame(this._animationRequest);
- }
- }
-
- componentDidUpdate() {
- // Redraw the current frame, since the skin may have changed.
- this.paintFrame();
- }
-
- _renderWidth() {
- return this.props.width;
- }
-
- _renderHeight() {
- return this.props.height;
- }
-
- _height() {
- return this.props.height * PIXEL_DENSITY;
- }
-
- _width() {
- return this.props.width * PIXEL_DENSITY;
- }
-
- paintFrame() {
- switch (this.props.style) {
- case VISUALIZERS.OSCILLOSCOPE:
- this.canvasCtx.drawImage(this.props.bgCanvas, 0, 0);
- this.props.paintOscilloscopeFrame(this.canvasCtx);
- break;
- case VISUALIZERS.BAR:
- this.canvasCtx.drawImage(this.props.bgCanvas, 0, 0);
- this._paintBarFrame();
- break;
- default:
- this.canvasCtx.clearRect(0, 0, this.canvas.width, this.canvas.height);
- }
- }
-
- _paintBarFrame() {
- this.props.analyser.getByteFrequencyData(this.props.dataArray);
- const heightMultiplier = this._renderHeight() / 256;
- const xOffset = BAR_WIDTH + PIXEL_DENSITY; // Bar width, plus a pixel of spacing to the right.
- for (let j = 0; j < NUM_BARS - 1; j++) {
- const start = this.props.octaveBuckets[j];
- const end = this.props.octaveBuckets[j + 1];
- let amplitude = 0;
- for (let k = start; k < end; k++) {
- amplitude += this.props.dataArray[k];
- }
- amplitude /= end - start;
-
- // The drop rate should probably be normalized to the rendering FPS, for now assume 60 FPS
- let barPeak =
- this.barPeaks[j] -
- BAR_PEAK_DROP_RATE * Math.pow(this.barPeakFrames[j], 2);
- if (barPeak < amplitude) {
- barPeak = amplitude;
- this.barPeakFrames[j] = 0;
- } else {
- this.barPeakFrames[j] += 1;
- }
- this.barPeaks[j] = barPeak;
-
- this.props.printBar(
- this.canvasCtx,
- j * xOffset,
- amplitude * heightMultiplier,
- barPeak * heightMultiplier
- );
- }
- }
-
- render() {
- const { width, height } = this.props;
- return (
-