From 7ebf216ef20f10c9e077674b722795fd5188e78f Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Wed, 8 Apr 2020 20:15:11 -0700 Subject: [PATCH] Type the rest of the visualizer --- js/components/Visualizer.tsx | 140 ++++++++++++++++++++++++++----- js/components/VisualizerInner.js | 123 --------------------------- js/types.ts | 4 +- 3 files changed, 122 insertions(+), 145 deletions(-) delete mode 100644 js/components/VisualizerInner.js 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 ( - (this.canvas = node)} - style={{ width, height }} - width={width * PIXEL_DENSITY} - height={height * PIXEL_DENSITY} - onClick={this.props.toggleVisualizerStyle} - /> - ); - } -} - -export default VisualizerInner; diff --git a/js/types.ts b/js/types.ts index fbc23d1a..4db0cb26 100644 --- a/js/types.ts +++ b/js/types.ts @@ -94,7 +94,7 @@ export type SkinImages = { [sprite: string]: string }; // TODO: type these keys export type SkinRegion = { [windowName: string]: string[] }; -export interface DummyVizData { +export type DummyVizData = { 0: 11.75; 8: 11.0625; 16: 8.5; @@ -111,7 +111,7 @@ export interface DummyVizData { 104: 4.375; 112: 3.625; 120: 1.5625; -} +}; export interface SkinGenExColors { itemBackground: string;