mirror of
https://github.com/captbaritone/webamp.git
synced 2026-08-03 15:34:25 +00:00
Use morganherlocker/cubic-spline for spline
This allows us to do a simpler line interpolation which is closer to what Winamp actually does: 1. Derive a Y value for every column 2. Draw a pixel at the Y value for the first column 3. For each successive column: 3a. Draw a vertical line from the previous Y value to the current Y value
This commit is contained in:
parent
848d649ac6
commit
491c7591d0
4 changed files with 110 additions and 169 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
|
@ -2,8 +2,7 @@ import React from "react";
|
|||
import { connect } from "react-redux";
|
||||
import { percentToRange, clamp } from "../../utils";
|
||||
import { BANDS } from "../../constants";
|
||||
import { getCurvePoints } from "./spline";
|
||||
import line from "./bresenham";
|
||||
import spline from "./spline";
|
||||
|
||||
const GRAPH_HEIGHT = 19;
|
||||
const GRAPH_WIDTH = 113;
|
||||
|
|
@ -81,36 +80,24 @@ class EqGraph extends React.Component {
|
|||
const min = 0;
|
||||
const max = GRAPH_HEIGHT - 1;
|
||||
|
||||
const points = amplitudes.reduce((prev, value, i) => {
|
||||
const xs = [];
|
||||
const ys = [];
|
||||
amplitudes.forEach((value, i) => {
|
||||
const percent = (100 - value) / 100;
|
||||
const y = percentToRange(percent, min, max);
|
||||
const x = i * 12; // Each band is 12 pixels wide
|
||||
return prev.concat(x, y);
|
||||
}, []);
|
||||
|
||||
// Spline between points in order to create nice curves
|
||||
const tension = 0.8;
|
||||
const resolution = 4; // Points in each segment
|
||||
const smoothPoints = getCurvePoints(points, tension, resolution);
|
||||
const smoothPointCoords = [];
|
||||
for (let i = 0; i < smoothPoints.length; i += 2) {
|
||||
// Splining can push peaks out of bounds. So we fudge them back in.
|
||||
const x = Math.round(smoothPoints[i]);
|
||||
const y = Math.round(clamp(smoothPoints[i + 1], min, max));
|
||||
smoothPointCoords.push({ x, y });
|
||||
}
|
||||
|
||||
let prev = smoothPointCoords.shift();
|
||||
|
||||
smoothPointCoords.forEach(next => {
|
||||
for (const point of line(prev, next)) {
|
||||
// Note: Technially, we are double drawing each point given to us by
|
||||
// getCurvePoints, since the end of each line is the same as the start
|
||||
// of the next.
|
||||
this.canvasCtx.fillRect(paddingLeft + point.x, point.y, 1, 1);
|
||||
}
|
||||
prev = next;
|
||||
// Each band is 12 pixels widex
|
||||
xs.push(i * 12);
|
||||
ys.push(percentToRange(percent, min, max));
|
||||
});
|
||||
|
||||
const maxX = xs[xs.length - 1];
|
||||
let lastY = ys[0];
|
||||
for (let x = 0; x <= maxX; x++) {
|
||||
const y = clamp(Math.round(spline(x, xs, ys)), 0, GRAPH_HEIGHT - 1);
|
||||
const yTop = Math.min(y, lastY);
|
||||
const height = 1 + Math.abs(lastY - y);
|
||||
this.canvasCtx.fillRect(paddingLeft + x, yTop, 1, height);
|
||||
lastY = y;
|
||||
}
|
||||
}
|
||||
|
||||
drawPreampLine() {
|
||||
|
|
|
|||
|
|
@ -1,71 +0,0 @@
|
|||
// Adapted from https://github.com/nquicenob/bresenham-line by Nicolas Quiceno
|
||||
interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
type Sign = 1 | -1;
|
||||
function getSing(num: number): Sign {
|
||||
return num > 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
function getInitValues(startPoint: Point, finalPoint: Point) {
|
||||
const abs = Math.abs;
|
||||
const diffx = finalPoint.x - startPoint.x;
|
||||
const diffy = finalPoint.y - startPoint.y;
|
||||
|
||||
return {
|
||||
absDiff: {
|
||||
x: abs(diffx),
|
||||
y: abs(diffy)
|
||||
},
|
||||
sign: {
|
||||
x: getSing(diffx),
|
||||
y: getSing(diffy)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getBreakFn(sign: Sign): (current: number, final: number) => boolean {
|
||||
return sign < 0
|
||||
? (current, final) => current >= final
|
||||
: (current, final) => current <= final;
|
||||
}
|
||||
|
||||
function calcMainCoordinates(absDiff: Point): ["x", "y"] | ["y", "x"] {
|
||||
return absDiff.x > absDiff.y ? ["x", "y"] : ["y", "x"];
|
||||
}
|
||||
|
||||
export default function line(point: Point, finalPoint: Point) {
|
||||
const { absDiff, sign } = getInitValues(point, finalPoint);
|
||||
const [mainCoordinate, coordinate] = calcMainCoordinates(absDiff);
|
||||
|
||||
const final = finalPoint[mainCoordinate];
|
||||
|
||||
const mainSign = sign[mainCoordinate];
|
||||
const secondSign = sign[coordinate];
|
||||
|
||||
const mainDiff = absDiff[mainCoordinate];
|
||||
const secondDiff = absDiff[coordinate];
|
||||
|
||||
const breakFn = getBreakFn(mainSign);
|
||||
|
||||
let mainValue = point[mainCoordinate];
|
||||
let secondValue = point[coordinate];
|
||||
|
||||
let eps = 0;
|
||||
|
||||
const points = [];
|
||||
for (; breakFn(mainValue, final); mainValue += mainSign) {
|
||||
points.push({
|
||||
[mainCoordinate]: mainValue,
|
||||
[coordinate]: secondValue
|
||||
});
|
||||
eps += secondDiff;
|
||||
if (eps << 1 >= mainDiff) {
|
||||
secondValue += secondSign;
|
||||
eps -= mainDiff;
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
|
@ -1,79 +1,104 @@
|
|||
/*! Curve calc function for canvas 2.3.1
|
||||
* Epistemex (c) 2013-2014
|
||||
* License: MIT
|
||||
*/
|
||||
// Adapted from https://github.com/morganherlocker/cubic-spline
|
||||
|
||||
/**
|
||||
* Calculates an array containing points representing a cardinal spline through given point array.
|
||||
* Points must be arranged as: [x1, y1, x2, y2, ..., xn, yn].
|
||||
*
|
||||
* The points for the cardinal spline are returned as a new array.
|
||||
*
|
||||
* @param {Array} points - point array
|
||||
* @param {Number} [tension=0.5] - tension. Typically between [0.0, 1.0] but can be exceeded
|
||||
* @param {Number} [numOfSeg=20] - number of segments between two points (line resolution)
|
||||
* @returns {Float32Array} New array with the calculated points that was added to the path
|
||||
*/
|
||||
export function getCurvePoints(points, tension = 0.5, numOfSeg = 25) {
|
||||
let i = 1,
|
||||
l = points.length,
|
||||
rPos = 0,
|
||||
cachePtr = 4;
|
||||
const rLen = (l - 2) * numOfSeg + 2,
|
||||
res = new Float32Array(rLen),
|
||||
cache = new Float32Array((numOfSeg + 2) * 4);
|
||||
// for cloning point array
|
||||
const pts = points.slice(0);
|
||||
export default function spline(x, xs, ys) {
|
||||
let ks = xs.map(() => {
|
||||
return 0;
|
||||
});
|
||||
ks = getNaturalKs(xs, ys, ks);
|
||||
let i = 1;
|
||||
while (xs[i] < x) i++;
|
||||
const t = (x - xs[i - 1]) / (xs[i] - xs[i - 1]);
|
||||
const a = ks[i - 1] * (xs[i] - xs[i - 1]) - (ys[i] - ys[i - 1]);
|
||||
const b = -ks[i] * (xs[i] - xs[i - 1]) + (ys[i] - ys[i - 1]);
|
||||
const q =
|
||||
(1 - t) * ys[i - 1] + t * ys[i] + t * (1 - t) * (a * (1 - t) + b * t);
|
||||
return q;
|
||||
}
|
||||
|
||||
pts.unshift(points[1]); // copy 1. point and insert at beginning
|
||||
pts.unshift(points[0]);
|
||||
pts.push(points[l - 2], points[l - 1]); // duplicate end-points
|
||||
function getNaturalKs(xs, ys, ks) {
|
||||
const n = xs.length - 1;
|
||||
const A = zerosMat(n + 1, n + 2);
|
||||
|
||||
// cache inner-loop calculations as they are based on t alone
|
||||
cache[0] = 1; // 1,0,0,0
|
||||
|
||||
for (; i < numOfSeg; i++) {
|
||||
const st = i / numOfSeg,
|
||||
st2 = st * st,
|
||||
st3 = st2 * st,
|
||||
st23 = st3 * 2,
|
||||
st32 = st2 * 3;
|
||||
|
||||
cache[cachePtr++] = st23 - st32 + 1; // c1
|
||||
cache[cachePtr++] = st32 - st23; // c2
|
||||
cache[cachePtr++] = st3 - 2 * st2 + st; // c3
|
||||
cache[cachePtr++] = st3 - st2; // c4
|
||||
for (
|
||||
let i = 1;
|
||||
i < n;
|
||||
i++ // rows
|
||||
) {
|
||||
A[i][i - 1] = 1 / (xs[i] - xs[i - 1]);
|
||||
A[i][i] = 2 * (1 / (xs[i] - xs[i - 1]) + 1 / (xs[i + 1] - xs[i]));
|
||||
A[i][i + 1] = 1 / (xs[i + 1] - xs[i]);
|
||||
A[i][n + 1] =
|
||||
3 *
|
||||
((ys[i] - ys[i - 1]) / ((xs[i] - xs[i - 1]) * (xs[i] - xs[i - 1])) +
|
||||
(ys[i + 1] - ys[i]) / ((xs[i + 1] - xs[i]) * (xs[i + 1] - xs[i])));
|
||||
}
|
||||
|
||||
cache[++cachePtr] = 1; // 0,1,0,0
|
||||
A[0][0] = 2 / (xs[1] - xs[0]);
|
||||
A[0][1] = 1 / (xs[1] - xs[0]);
|
||||
A[0][n + 1] = (3 * (ys[1] - ys[0])) / ((xs[1] - xs[0]) * (xs[1] - xs[0]));
|
||||
|
||||
// calc. points
|
||||
for (let j = 2, t; j < l; j += 2) {
|
||||
const pt1 = pts[j],
|
||||
pt2 = pts[j + 1],
|
||||
pt3 = pts[j + 2],
|
||||
pt4 = pts[j + 3],
|
||||
t1x = (pt3 - pts[j - 2]) * tension,
|
||||
t1y = (pt4 - pts[j - 1]) * tension,
|
||||
t2x = (pts[j + 4] - pt1) * tension,
|
||||
t2y = (pts[j + 5] - pt2) * tension;
|
||||
A[n][n - 1] = 1 / (xs[n] - xs[n - 1]);
|
||||
A[n][n] = 2 / (xs[n] - xs[n - 1]);
|
||||
A[n][n + 1] =
|
||||
(3 * (ys[n] - ys[n - 1])) / ((xs[n] - xs[n - 1]) * (xs[n] - xs[n - 1]));
|
||||
|
||||
for (t = 0; t < numOfSeg; t++) {
|
||||
const c = t << 2, //t * 4;
|
||||
c1 = cache[c],
|
||||
c2 = cache[c + 1],
|
||||
c3 = cache[c + 2],
|
||||
c4 = cache[c + 3];
|
||||
return solve(A, ks);
|
||||
}
|
||||
|
||||
res[rPos++] = c1 * pt1 + c2 * pt3 + c3 * t1x + c4 * t2x;
|
||||
res[rPos++] = c1 * pt2 + c2 * pt4 + c3 * t1y + c4 * t2y;
|
||||
function solve(A, ks) {
|
||||
const m = A.length;
|
||||
for (
|
||||
let k = 0;
|
||||
k < m;
|
||||
k++ // column
|
||||
) {
|
||||
// pivot for column
|
||||
let i_max = 0;
|
||||
let vali = Number.NEGATIVE_INFINITY;
|
||||
for (var i = k; i < m; i++)
|
||||
if (A[i][k] > vali) {
|
||||
i_max = i;
|
||||
vali = A[i][k];
|
||||
}
|
||||
swapRows(A, k, i_max);
|
||||
|
||||
// for all rows below pivot
|
||||
for (var i = k + 1; i < m; i++) {
|
||||
for (var j = k + 1; j < m + 1; j++)
|
||||
A[i][j] = A[i][j] - A[k][j] * (A[i][k] / A[k][k]);
|
||||
A[i][k] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// add last point
|
||||
l = points.length - 2;
|
||||
res[rPos++] = points[l];
|
||||
res[rPos] = points[l + 1];
|
||||
|
||||
return res;
|
||||
for (
|
||||
var i = m - 1;
|
||||
i >= 0;
|
||||
i-- // rows = columns
|
||||
) {
|
||||
const v = A[i][m] / A[i][i];
|
||||
ks[i] = v;
|
||||
for (
|
||||
var j = i - 1;
|
||||
j >= 0;
|
||||
j-- // rows
|
||||
) {
|
||||
A[j][m] -= A[j][i] * v;
|
||||
A[j][i] = 0;
|
||||
}
|
||||
}
|
||||
return ks;
|
||||
}
|
||||
|
||||
function zerosMat(r, c) {
|
||||
const A = [];
|
||||
for (let i = 0; i < r; i++) {
|
||||
A.push([]);
|
||||
for (let j = 0; j < c; j++) A[i].push(0);
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
function swapRows(m, k, l) {
|
||||
const p = m[k];
|
||||
m[k] = m[l];
|
||||
m[l] = p;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue