From eaab36b051fb1018a8fc36852064382f3befbaf7 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 25 Jun 2023 14:23:18 +0200 Subject: [PATCH] add spiral viz --- packages/core/index.mjs | 1 + packages/core/spiral.mjs | 117 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 packages/core/spiral.mjs diff --git a/packages/core/index.mjs b/packages/core/index.mjs index 16ef3be4..bed63f9a 100644 --- a/packages/core/index.mjs +++ b/packages/core/index.mjs @@ -24,6 +24,7 @@ export * from './time.mjs'; export * from './draw.mjs'; export * from './animate.mjs'; export * from './pianoroll.mjs'; +export * from './spiral.mjs'; export * from './ui.mjs'; export { default as drawLine } from './drawLine.mjs'; export { default as gist } from './gist.js'; diff --git a/packages/core/spiral.mjs b/packages/core/spiral.mjs new file mode 100644 index 00000000..bba63e34 --- /dev/null +++ b/packages/core/spiral.mjs @@ -0,0 +1,117 @@ +import { Pattern } from './index.mjs'; + +// polar coords -> xy +function fromPolar(angle, radius, cx, cy) { + const radians = ((angle - 90) * Math.PI) / 180; + return [cx + Math.cos(radians) * radius, cy + Math.sin(radians) * radius]; +} + +const xyOnSpiral = (angle, margin, cx, cy, rotate = 0) => fromPolar((angle + rotate) * 360, margin * angle, cx, cy); // TODO: logSpiral + +// draw spiral / segment of spiral +function spiralSegment(options) { + let { + ctx, + from = 0, + to = 3, + margin = 50, + cx = 100, + cy = 100, + rotate = 0, + thickness = margin / 2, + color = '#0000ff30', + cap = 'round', + stretch = 1, + fromOpacity = 1, + toOpacity = 1, + } = options; + from *= stretch; + to *= stretch; + rotate *= stretch; + ctx.lineWidth = thickness; + ctx.lineCap = cap; + ctx.strokeStyle = color; + ctx.globalAlpha = fromOpacity; + + ctx.beginPath(); + let [sx, sy] = xyOnSpiral(from, margin, cx, cy, rotate); + ctx.moveTo(sx, sy); + + const increment = 1 / 60; + let angle = from; + while (angle <= to) { + const [x, y] = xyOnSpiral(angle, margin, cx, cy, rotate); + //ctx.lineWidth = angle*thickness; + ctx.globalAlpha = ((angle - from) / (to - from)) * toOpacity; + ctx.lineTo(x, y); + angle += increment; + } + ctx.stroke(); +} + +Pattern.prototype.spiral = function (options = {}) { + const { + stretch = 1, + size = 80, + thickness = size / 2, + cap = 'butt', // round butt squar, + inset = 3, // start angl, + playheadColor = '#ffffff90', + playheadLength = 0.02, + playheadThickness = thickness, + padding = 0, + steady = 1, + inactiveColor = '#ffffff20', + colorizeInactive = 0, + fade = true, + // logSpiral = true, + } = options; + + function spiral({ ctx, time, haps, drawTime }) { + ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight); + const [cx, cy] = [ctx.canvas.width / 2, ctx.canvas.height / 2]; + const settings = { + margin: size / stretch, + cx, + cy, + stretch, + cap, + thickness, + }; + + const playhead = { + ...settings, + thickness: playheadThickness, + from: inset - playheadLength, + to: inset, + color: playheadColor, + }; + + const [min] = drawTime; + const rotate = steady * time; + haps.forEach((hap) => { + const isActive = hap.whole.begin <= time && hap.endClipped > time; + const from = hap.whole.begin - time + inset; + const to = hap.endClipped - time + inset - padding; + const { color } = hap.context; + const opacity = fade ? 1 - Math.abs((hap.whole.begin - time) / min) : 1; + spiralSegment({ + ctx, + ...settings, + from, + to, + rotate, + color: colorizeInactive || isActive ? color : inactiveColor, + fromOpacity: opacity, + toOpacity: opacity, + }); + }); + spiralSegment({ + ctx, + ...playhead, + rotate, + }); + } + + return this.onPaint((ctx, time, haps, drawTime) => spiral({ ctx, time, haps, drawTime })); +};