mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-10 21:28:31 +00:00
158 lines
4.5 KiB
JavaScript
158 lines
4.5 KiB
JavaScript
import { Pattern } from '@strudel/core';
|
|
import { getTheme } from './draw.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 = getTheme().foreground,
|
|
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();
|
|
}
|
|
|
|
function drawSpiral(options) {
|
|
let {
|
|
stretch = 1,
|
|
size = 80,
|
|
thickness = size / 2,
|
|
cap = 'butt', // round butt squar,
|
|
inset = 3, // start angl,
|
|
playheadColor = '#ffffff',
|
|
playheadLength = 0.02,
|
|
playheadThickness = thickness,
|
|
padding = 0,
|
|
steady = 1,
|
|
activeColor = getTheme().foreground,
|
|
inactiveColor = getTheme().gutterForeground,
|
|
colorizeInactive = 0,
|
|
fade = true,
|
|
// logSpiral = true,
|
|
ctx,
|
|
time,
|
|
haps,
|
|
drawTime,
|
|
id,
|
|
} = options;
|
|
|
|
if (id) {
|
|
haps = haps.filter((hap) => hap.hasTag(id));
|
|
}
|
|
|
|
const [w, h] = [ctx.canvas.width, ctx.canvas.height];
|
|
ctx.clearRect(0, 0, w * 2, h * 2);
|
|
const [cx, cy] = [w / 2, h / 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 hapColor = hap.value?.color || activeColor;
|
|
const color = colorizeInactive || isActive ? hapColor : inactiveColor;
|
|
const opacity = fade ? 1 - Math.abs((hap.whole.begin - time) / min) : 1;
|
|
spiralSegment({
|
|
ctx,
|
|
...settings,
|
|
from,
|
|
to,
|
|
rotate,
|
|
color,
|
|
fromOpacity: opacity,
|
|
toOpacity: opacity,
|
|
});
|
|
});
|
|
spiralSegment({
|
|
ctx,
|
|
...playhead,
|
|
rotate,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Displays a spiral visual.
|
|
*
|
|
* @name spiral
|
|
* @param {Object} options Object containing all the optional following parameters as key value pairs:
|
|
* @param {number} stretch controls the rotations per cycle ratio, where 1 = 1 cycle / 360 degrees
|
|
* @param {number} size the diameter of the spiral
|
|
* @param {number} thickness line thickness
|
|
* @param {string} cap style of line ends: butt (default), round, square
|
|
* @param {string} inset number of rotations before spiral starts (default 3)
|
|
* @param {string} playheadColor color of playhead, defaults to white
|
|
* @param {number} playheadLength length of playhead in rotations, defaults to 0.02
|
|
* @param {number} playheadThickness thickness of playheadrotations, defaults to thickness
|
|
* @param {number} padding space around spiral
|
|
* @param {number} steady steadyness of spiral vs playhead. 1 = spiral doesn't move, playhead does.
|
|
* @param {number} activeColor color of active segment. defaults to foreground of theme
|
|
* @param {number} inactiveColor color of inactive segments. defaults to gutterForeground of theme
|
|
* @param {boolean} colorizeInactive wether or not to colorize inactive segments, defaults to 0
|
|
* @param {boolean} fade wether or not past and future should fade out. defaults to 1
|
|
* @param {boolean} logSpiral wether or not the spiral should be logarithmic. defaults to 0
|
|
* @example
|
|
* note("c2 a2 eb2")
|
|
* .euclid(5,8)
|
|
* .s('sawtooth')
|
|
* .lpenv(4).lpf(300)
|
|
* ._spiral({ steady: .96 })
|
|
*/
|
|
Pattern.prototype.spiral = function (options = {}) {
|
|
return this.onPaint((ctx, time, haps, drawTime) => drawSpiral({ ctx, time, haps, drawTime, ...options }));
|
|
};
|