strudel-docker/packages/draw/pitchwheel.mjs
2024-04-01 15:19:50 +02:00

115 lines
2.6 KiB
JavaScript

import { Pattern, midiToFreq } from '@strudel/core';
import { getTheme, getDrawContext } from './draw.mjs';
const c = midiToFreq(36);
const circlePos = (cx, cy, radius, angle) => {
angle = angle * Math.PI * 2;
const x = Math.sin(angle) * radius + cx;
const y = Math.cos(angle) * radius + cy;
return [x, y];
};
const freq2angle = (freq, root) => {
return 0.5 - (Math.log2(freq / root) % 1);
};
export function pitchwheel({
time,
haps,
ctx,
id,
connectdots = 0,
centerlines = 1,
circle = 0,
edo = 12,
root = c,
} = {}) {
const w = ctx.canvas.width;
const h = ctx.canvas.height;
ctx.clearRect(0, 0, w, h);
const color = getTheme().foreground;
const hapRadius = 10;
const margin = 10;
const size = Math.min(w, h);
const thickness = 4;
const radius = size / 2 - thickness / 2 - hapRadius - margin;
const centerX = w / 2;
const centerY = h / 2;
if (id) {
haps = haps.filter((hap) => hap.hasTag(id));
}
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.globalAlpha = 1;
ctx.lineWidth = thickness;
if (circle) {
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
ctx.stroke();
}
if (edo) {
Array.from({ length: edo }, (_, i) => {
ctx.beginPath();
const angle = freq2angle(root * Math.pow(2, i / edo), root);
const [x, y] = circlePos(centerX, centerY, radius, angle);
ctx.arc(x, y, hapRadius, 0, 2 * Math.PI);
ctx.stroke();
});
}
let shape = [];
haps.forEach((hap) => {
const freq = getFrequency(hap);
const angle = freq2angle(freq, root);
const [x, y] = circlePos(centerX, centerY, radius, angle);
const hapColor = hap.value.color || color;
shape.push([x, y]);
ctx.strokeStyle = hapColor;
ctx.fillStyle = hapColor;
const { velocity = 1, gain = 1 } = hap.value || {};
ctx.globalAlpha = velocity * gain;
ctx.beginPath();
ctx.moveTo(x + hapRadius, y);
ctx.arc(x, y, hapRadius, 0, 2 * Math.PI);
ctx.fill();
if (centerlines) {
ctx.moveTo(centerX, centerY);
ctx.lineTo(x, y);
}
ctx.stroke();
});
ctx.strokeStyle = color;
ctx.globalAlpha = 1;
if (shape.length && connectdots) {
ctx.beginPath();
ctx.moveTo(shape[0][0], shape[0][1]);
shape.forEach(([x, y]) => {
ctx.lineTo(x, y);
});
ctx.lineTo(shape[0][0], shape[0][1]);
ctx.stroke();
}
return;
}
Pattern.prototype.pitchwheel = function (options = {}) {
let { ctx = getDrawContext(), id = 1 } = options;
this.onPaint((_, time, haps) =>
pitchwheel({
...options,
time,
ctx,
haps: haps.filter((hap) => hap.isActive(time)),
id,
}),
);
return this;
};