jevstrudel.git / packages / draw / spiral.mjs
1import { Pattern } from '@strudel/core';
2import { getTheme } from './draw.mjs';

polar coords -> xy

5function fromPolar(angle, radius, cx, cy) {
6  const radians = ((angle - 90) * Math.PI) / 180;
7  return [cx + Math.cos(radians) * radius, cy + Math.sin(radians) * radius];
8}
10const xyOnSpiral = (angle, margin, cx, cy, rotate = 0) => fromPolar((angle + rotate) * 360, margin * angle, cx, cy); // TODO: logSpiral

draw spiral / segment of spiral

13function spiralSegment(options) {
14  let {
15    ctx,
16    from = 0,
17    to = 3,
18    margin = 50,
19    cx = 100,
20    cy = 100,
21    rotate = 0,
22    thickness = margin / 2,
23    color = getTheme().foreground,
24    cap = 'round',
25    stretch = 1,
26    fromOpacity = 1,
27    toOpacity = 1,
28  } = options;
29  from *= stretch;
30  to *= stretch;
31  rotate *= stretch;
32  ctx.lineWidth = thickness;
33  ctx.lineCap = cap;
34  ctx.strokeStyle = color;
35  ctx.globalAlpha = fromOpacity;
36
37  ctx.beginPath();
38  let [sx, sy] = xyOnSpiral(from, margin, cx, cy, rotate);
39  ctx.moveTo(sx, sy);
40
41  const increment = 1 / 60;
42  let angle = from;
43  while (angle <= to) {
44    const [x, y] = xyOnSpiral(angle, margin, cx, cy, rotate);
45    //ctx.lineWidth = angle*thickness;
46    ctx.globalAlpha = ((angle - from) / (to - from)) * toOpacity;
47    ctx.lineTo(x, y);
48    angle += increment;
49  }
50  ctx.stroke();
51}
53function drawSpiral(options) {
54  let {
55    stretch = 1,
56    size = 80,
57    thickness = size / 2,
58    cap = 'butt', // round butt squar,
59    inset = 3, // start angl,
60    playheadColor = '#ffffff',
61    playheadLength = 0.02,
62    playheadThickness = thickness,
63    padding = 0,
64    steady = 1,
65    activeColor = getTheme().foreground,
66    inactiveColor = getTheme().gutterForeground,
67    colorizeInactive = 0,
68    fade = true,
69    // logSpiral = true,
70    ctx,
71    time,
72    haps,
73    drawTime,
74    id,
75  } = options;
76
77  if (id) {
78    haps = haps.filter((hap) => hap.hasTag(id));
79  }
80
81  const [w, h] = [ctx.canvas.width, ctx.canvas.height];
82  ctx.clearRect(0, 0, w * 2, h * 2);
83  const [cx, cy] = [w / 2, h / 2];
84  const settings = {
85    margin: size / stretch,
86    cx,
87    cy,
88    stretch,
89    cap,
90    thickness,
91  };
92
93  const playhead = {
94    ...settings,
95    thickness: playheadThickness,
96    from: inset - playheadLength,
97    to: inset,
98    color: playheadColor,
99  };
100
101  const [min] = drawTime;
102  const rotate = steady * time;
103  haps.forEach((hap) => {
104    const isActive = hap.whole.begin <= time && hap.endClipped > time;
105    const from = hap.whole.begin - time + inset;
106    const to = hap.endClipped - time + inset - padding;
107    const hapColor = hap.value?.color || activeColor;
108    const color = colorizeInactive || isActive ? hapColor : inactiveColor;
109    const opacity = fade ? 1 - Math.abs((hap.whole.begin - time) / min) : 1;
110    spiralSegment({
111      ctx,
112      ...settings,
113      from,
114      to,
115      rotate,
116      color,
117      fromOpacity: opacity,
118      toOpacity: opacity,
119    });
120  });
121  spiralSegment({
122    ctx,
123    ...playhead,
124    rotate,
125  });
126}

Displays a spiral visual.

@name spiral @tags visualization @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 })

156Pattern.prototype.spiral = function (options = {}) {
157  return this.onPaint((ctx, time, haps, drawTime) => drawSpiral({ ctx, time, haps, drawTime, ...options }));
158};