diff --git a/js/__tests__/EqGraph--test.js b/js/__tests__/EqGraph--test.js new file mode 100644 index 00000000..9b7d0b3b --- /dev/null +++ b/js/__tests__/EqGraph--test.js @@ -0,0 +1,29 @@ +jest.unmock('../components/EqGraph.jsx'); + +import { + roundToEven, + getY +} from '../components/EqGraph.jsx'; + +describe('roundToEven', () => { + it('leaves even numbers as is', () => { + const actual = roundToEven(4); + const expected = 4; + expect(actual).toEqual(expected); + }); + it('handles zero', () => { + const actual = roundToEven(0); + const expected = 0; + expect(actual).toEqual(expected); + }); + it('handles odd numbers', () => { + const actual = roundToEven(3); + const expected = 4; + expect(actual).toEqual(expected); + }); +}); + +describe('getY', () => { + it('gives the first sprite', () => { + }); +}); diff --git a/js/components/EqGraph.jsx b/js/components/EqGraph.jsx index 1a3a9305..d8ce74ea 100644 --- a/js/components/EqGraph.jsx +++ b/js/components/EqGraph.jsx @@ -1,13 +1,21 @@ import React from 'react'; import {connect} from 'react-redux'; +import {getCurvePoints} from 'cardinal-spline-js'; + +import {BANDS} from '../constants'; + +export const roundToEven = (value) => ( + 2 * Math.round(value / 2) +); export const getY = (value) => ( - 32 - ((value / 100) * 32) + roundToEven(32 - ((value / 100) * 32)) ); class EqGraph extends React.Component { constructor(props) { super(props); + this.state = {}; } componentDidMount() { @@ -15,64 +23,91 @@ class EqGraph extends React.Component { this.canvasCtx.imageSmoothingEnabled = false; this.width = this.refs.canvas.width * 1; // Cast to int this.height = this.refs.canvas.height * 1; // Cast to int - this.bgImage = new Image(); - this.bgImage.onload = () => { - this.createColorPattern(); - this.componentDidUpdate(); - }; - this.bgImage.src = 'bg.png'; } componentDidUpdate() { this.canvasCtx.clearRect(0, 0, this.width, this.height); + //this.createColorPattern(); this.drawPreampLine(); this.drawEqLine(); // This should paint on top of the preamp line } - createColorPattern() { - const colorsCanvas = document.createElement('canvas'); - const colorsCtx = colorsCanvas.getContext('2d'); - colorsCanvas.width = this.bgImage.width * 2; - colorsCanvas.height = this.bgImage.height * 2; - colorsCtx.drawImage(this.bgImage, 0, 0, colorsCanvas.width, colorsCanvas.height); - this.colorPattern = this.canvasCtx.createPattern(colorsCanvas, 'repeat-x'); + shouldComponentUpdate(nextProps) { + if (this.props.lineColorsImage !== nextProps.lineColorsImage) { + this.createColorPattern(nextProps.lineColorsImage); + } + if (this.props.preampLineUrl !== nextProps.preampLineUrl) { + this.createPreampLineImage(nextProps.preampLineUrl); + } + return true; + } + + createPreampLineImage(preampLineUrl) { + const preampLineImg = new Image(); + preampLineImg.onload = () => { + this.setState({preampLineImg}); + }; + preampLineImg.src = preampLineUrl; + } + + createColorPattern(lineColorsImage) { + const bgImage = new Image(); + bgImage.onload = () => { + const colorsCanvas = document.createElement('canvas'); + const colorsCtx = colorsCanvas.getContext('2d'); + colorsCanvas.width = bgImage.width * 2; + colorsCanvas.height = bgImage.height * 2; + colorsCtx.drawImage(bgImage, 0, 0, colorsCanvas.width, colorsCanvas.height); + this.setState({ + colorPattern: this.canvasCtx.createPattern(colorsCanvas, 'repeat-x') + }); + }; + bgImage.src = lineColorsImage; } drawEqLine() { + if (!this.state.colorPattern) { + // The skin has not finished loading yet + return; + } const {props} = this; - const values = [ - props[60], - props[170], - props[310], - props[600], - props[1000], - props[3000], - props[6000], - props[12000], - props[14000], - props[16000] - ]; + const amplitudes = BANDS.map((band) => props[band]); - this.canvasCtx.strokeStyle = this.colorPattern; + this.canvasCtx.strokeStyle = this.state.colorPattern; this.canvasCtx.lineWidth = 2; this.canvasCtx.beginPath(); const paddingLeft = 4; - // TODO: Curve these lines - values.forEach((value, i) => { - this.canvasCtx.lineTo(paddingLeft + (i * 16), getY(value)); + + let points = []; + amplitudes.forEach((value, i) => { + points = points.concat(paddingLeft + (i * 16), getY(value)); }); + + // Spline between points in order to create nice curves + const tension = 0.5; + const resolution = 4; // Points in each segment + const smoothPoints = getCurvePoints(points, tension, resolution); + for (let i = 0; i < smoothPoints.length; i += 2) { + this.canvasCtx.lineTo(smoothPoints[i], smoothPoints[i + 1]); + } + this.canvasCtx.stroke(); } drawPreampLine() { - // TODO: Use the line from the skin image - const preampValue = getY(this.props.preamp); - this.canvasCtx.lineWidth = 2; - this.canvasCtx.strokeStyle = 'white'; - this.canvasCtx.beginPath(); - this.canvasCtx.moveTo(0, preampValue); - this.canvasCtx.lineTo(this.width, preampValue); - this.canvasCtx.stroke(); + const {preampLineImg} = this.state; + if (!preampLineImg) { + // The skin has not finished loading yet + return; + } + const preampValue = getY(100 - this.props.preamp); + this.canvasCtx.drawImage( + this.state.preampLineImg, + 0, + preampValue, + preampLineImg.width * 2, + preampLineImg.height * 2 + ); } render() { @@ -85,4 +120,8 @@ class EqGraph extends React.Component { } } -export default connect((state) => state.equalizer.sliders)(EqGraph); +export default connect((state) => ({ + ...state.equalizer.sliders, + lineColorsImage: state.display.skinImages.EQ_GRAPH_LINE_COLORS, + preampLineUrl: state.display.skinImages.EQ_PREAMP_LINE +}))(EqGraph); diff --git a/js/constants.js b/js/constants.js new file mode 100644 index 00000000..1b073e10 --- /dev/null +++ b/js/constants.js @@ -0,0 +1,12 @@ +export const BANDS = [ + 60, + 170, + 310, + 600, + 1000, + 3000, + 6000, + 12000, + 14000, + 16000 +]; diff --git a/package.json b/package.json index 73188a51..3efe49f8 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "dependencies": { "babel-plugin-transform-object-rest-spread": "^6.8.0", "babel-plugin-transform-react-jsx": "^6.8.0", + "cardinal-spline-js": "^2.3.6", "classnames": "^2.2.5", "jszip": "^2.6.0", "react": "^15.2.0",