From 95719654f3bfc5d3cc98c33816b402e2c0e38bd7 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 5 May 2023 09:52:07 +0200 Subject: [PATCH] refactor: remove old draw logic + pianoroll now uses .onPaint --- packages/core/draw.mjs | 38 +--------- packages/core/index.mjs | 1 - packages/core/pianoroll.mjs | 136 ++---------------------------------- packages/core/repl.mjs | 2 - packages/core/time.mjs | 11 --- packages/core/ui.mjs | 23 ------ website/src/repl/Repl.jsx | 5 +- 7 files changed, 10 insertions(+), 206 deletions(-) delete mode 100644 packages/core/time.mjs diff --git a/packages/core/draw.mjs b/packages/core/draw.mjs index 4bfd3257..81700f6c 100644 --- a/packages/core/draw.mjs +++ b/packages/core/draw.mjs @@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { Pattern, getTime, State, TimeSpan } from './index.mjs'; +import { Pattern } from './index.mjs'; export const getDrawContext = (id = 'test-canvas') => { let canvas = document.querySelector('#' + id); @@ -19,45 +19,9 @@ export const getDrawContext = (id = 'test-canvas') => { return canvas.getContext('2d'); }; -Pattern.prototype.draw = function (callback, { from, to, onQuery }) { - if (window.strudelAnimation) { - cancelAnimationFrame(window.strudelAnimation); - } - const ctx = getDrawContext(); - let cycle, - events = []; - const animate = (time) => { - const t = getTime(); - if (from !== undefined && to !== undefined) { - const currentCycle = Math.floor(t); - if (cycle !== currentCycle) { - cycle = currentCycle; - const begin = currentCycle + from; - const end = currentCycle + to; - setTimeout(() => { - events = this.query(new State(new TimeSpan(begin, end))) - .filter(Boolean) - .filter((event) => event.part.begin.equals(event.whole.begin)); - onQuery?.(events); - }, 0); - } - } - callback(ctx, events, t, time); - window.strudelAnimation = requestAnimationFrame(animate); - }; - requestAnimationFrame(animate); - return this; -}; - export const cleanupDraw = (clearScreen = true) => { const ctx = getDrawContext(); clearScreen && ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); - if (window.strudelAnimation) { - cancelAnimationFrame(window.strudelAnimation); - } - if (window.strudelScheduler) { - clearInterval(window.strudelScheduler); - } }; Pattern.prototype.onPaint = function (onPaint) { diff --git a/packages/core/index.mjs b/packages/core/index.mjs index b6c74848..30504a5b 100644 --- a/packages/core/index.mjs +++ b/packages/core/index.mjs @@ -19,7 +19,6 @@ export * from './speak.mjs'; export * from './evaluate.mjs'; export * from './repl.mjs'; export * from './logger.mjs'; -export * from './time.mjs'; export * from './draw.mjs'; export * from './animate.mjs'; export * from './pianoroll.mjs'; diff --git a/packages/core/pianoroll.mjs b/packages/core/pianoroll.mjs index 635b7fac..7f5f2fec 100644 --- a/packages/core/pianoroll.mjs +++ b/packages/core/pianoroll.mjs @@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { Pattern, noteToMidi, getDrawContext, freqToMidi, isNote } from './index.mjs'; +import { Pattern, noteToMidi, freqToMidi } from './index.mjs'; const scale = (normalized, min, max) => normalized * (max - min) + min; const getValue = (e) => { @@ -29,134 +29,6 @@ const getValue = (e) => { return value; }; -Pattern.prototype.pianoroll = function ({ - cycles = 4, - playhead = 0.5, - overscan = 1, - flipTime = 0, - flipValues = 0, - hideNegative = false, - // inactive = '#C9E597', - // inactive = '#FFCA28', - inactive = '#7491D2', - active = '#FFCA28', - // background = '#2A3236', - background = 'transparent', - smear = 0, - playheadColor = 'white', - minMidi = 10, - maxMidi = 90, - autorange = 0, - timeframe: timeframeProp, - fold = 0, - vertical = 0, -} = {}) { - const ctx = getDrawContext(); - const w = ctx.canvas.width; - const h = ctx.canvas.height; - let from = -cycles * playhead; - let to = cycles * (1 - playhead); - - if (timeframeProp) { - console.warn('timeframe is deprecated! use from/to instead'); - from = 0; - to = timeframeProp; - } - const timeAxis = vertical ? h : w; - const valueAxis = vertical ? w : h; - let timeRange = vertical ? [timeAxis, 0] : [0, timeAxis]; // pixel range for time - const timeExtent = to - from; // number of seconds that fit inside the canvas frame - const valueRange = vertical ? [0, valueAxis] : [valueAxis, 0]; // pixel range for values - let valueExtent = maxMidi - minMidi + 1; // number of "slots" for values, overwritten if autorange true - let barThickness = valueAxis / valueExtent; // pixels per value, overwritten if autorange true - let foldValues = []; - flipTime && timeRange.reverse(); - flipValues && valueRange.reverse(); - - this.draw( - (ctx, events, t) => { - ctx.fillStyle = background; - ctx.globalAlpha = 1; // reset! - if (!smear) { - ctx.clearRect(0, 0, w, h); - ctx.fillRect(0, 0, w, h); - } - const inFrame = (event) => - (!hideNegative || event.whole.begin >= 0) && event.whole.begin <= t + to && event.whole.end >= t + from; - events.filter(inFrame).forEach((event) => { - const isActive = event.whole.begin <= t && event.whole.end > t; - ctx.fillStyle = event.context?.color || inactive; - ctx.strokeStyle = event.context?.color || active; - ctx.globalAlpha = event.context.velocity ?? 1; - const timePx = scale((event.whole.begin - (flipTime ? to : from)) / timeExtent, ...timeRange); - let durationPx = scale(event.duration / timeExtent, 0, timeAxis); - const value = getValue(event); - const valuePx = scale( - fold ? foldValues.indexOf(value) / foldValues.length : (Number(value) - minMidi) / valueExtent, - ...valueRange, - ); - let margin = 0; - const offset = scale(t / timeExtent, ...timeRange); - let coords; - if (vertical) { - coords = [ - valuePx + 1 - (flipValues ? barThickness : 0), // x - timeAxis - offset + timePx + margin + 1 - (flipTime ? 0 : durationPx), // y - barThickness - 2, // width - durationPx - 2, // height - ]; - } else { - coords = [ - timePx - offset + margin + 1 - (flipTime ? durationPx : 0), // x - valuePx + 1 - (flipValues ? 0 : barThickness), // y - durationPx - 2, // widith - barThickness - 2, // height - ]; - } - isActive ? ctx.strokeRect(...coords) : ctx.fillRect(...coords); - }); - ctx.globalAlpha = 1; // reset! - const playheadPosition = scale(-from / timeExtent, ...timeRange); - // draw playhead - ctx.strokeStyle = playheadColor; - ctx.beginPath(); - if (vertical) { - ctx.moveTo(0, playheadPosition); - ctx.lineTo(valueAxis, playheadPosition); - } else { - ctx.moveTo(playheadPosition, 0); - ctx.lineTo(playheadPosition, valueAxis); - } - ctx.stroke(); - }, - { - from: from - overscan, - to: to + overscan, - onQuery: (events) => { - const { min, max, values } = events.reduce( - ({ min, max, values }, e) => { - const v = getValue(e); - return { - min: v < min ? v : min, - max: v > max ? v : max, - values: values.includes(v) ? values : [...values, v], - }; - }, - { min: Infinity, max: -Infinity, values: [] }, - ); - if (autorange) { - minMidi = min; - maxMidi = max; - valueExtent = maxMidi - minMidi + 1; - } - foldValues = values.sort((a, b) => String(a).localeCompare(String(b))); - barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent; - }, - }, - ); - return this; -}; - // this function allows drawing a pianoroll without ties to Pattern.prototype // it will probably replace the above in the future export function pianoroll({ @@ -295,3 +167,9 @@ function getOptions(drawTime, options = {}) { Pattern.prototype.punchcard = function (options) { return this.onPaint((ctx, time, haps, drawTime) => pianoroll({ ctx, time, haps, ...getOptions(drawTime, options) })); }; + +Pattern.prototype.pianoroll = function (options) { + return this.onPaint((ctx, time, haps, drawTime) => + pianoroll({ ctx, time, haps, ...getOptions(drawTime, { fold: 0, ...options }) }), + ); +}; diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 7c96bb66..de979258 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -1,7 +1,6 @@ import { Cyclist } from './cyclist.mjs'; import { evaluate as _evaluate } from './evaluate.mjs'; import { logger } from './logger.mjs'; -import { setTime } from './time.mjs'; import { evalScope } from './evaluate.mjs'; export function repl({ @@ -35,7 +34,6 @@ export function repl({ getTime, onToggle, }); - setTime(() => scheduler.now()); // TODO: refactor? const evaluate = async (code, autostart = true) => { if (!code) { throw new Error('no code to evaluate'); diff --git a/packages/core/time.mjs b/packages/core/time.mjs deleted file mode 100644 index 80daaf53..00000000 --- a/packages/core/time.mjs +++ /dev/null @@ -1,11 +0,0 @@ -let time; -export function getTime() { - if (!time) { - throw new Error('no time set! use setTime to define a time source'); - } - return time(); -} - -export function setTime(func) { - time = func; -} diff --git a/packages/core/ui.mjs b/packages/core/ui.mjs index df8230ec..cc148553 100644 --- a/packages/core/ui.mjs +++ b/packages/core/ui.mjs @@ -4,19 +4,6 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { getTime } from './time.mjs'; - -function frame(callback) { - if (window.strudelAnimation) { - cancelAnimationFrame(window.strudelAnimation); - } - const animate = (animationTime) => { - callback(animationTime, getTime()); - window.strudelAnimation = requestAnimationFrame(animate); - }; - requestAnimationFrame(animate); -} - export const backgroundImage = function (src, animateOptions = {}) { const container = document.getElementById('code'); const bg = 'background-image:url(' + src + ');background-size:contain;'; @@ -28,18 +15,8 @@ export const backgroundImage = function (src, animateOptions = {}) { className: () => (container.className = value + ' ' + initialClassName), })[option](); }; - const funcOptions = Object.entries(animateOptions).filter(([_, v]) => typeof v === 'function'); const stringOptions = Object.entries(animateOptions).filter(([_, v]) => typeof v === 'string'); stringOptions.forEach(([option, value]) => handleOption(option, value)); - - if (funcOptions.length === 0) { - return; - } - frame((_, t) => - funcOptions.forEach(([option, value]) => { - handleOption(option, value(t)); - }), - ); }; export const cleanupUi = () => { diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 4ad387fe..d8270b16 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -50,10 +50,9 @@ const modulesLoading = evalScope( const presets = prebake(); -let drawContext, clearCanvas; +let drawContext; if (typeof window !== 'undefined') { drawContext = getDrawContext(); - clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width); } const getTime = () => getAudioContext().currentTime; @@ -208,7 +207,7 @@ export function Repl({ embedded = false }) { const handleShuffle = async () => { const { code, name } = getRandomTune(); logger(`[repl] ✨ loading random tune "${name}"`); - clearCanvas(); + cleanupDraw(); resetLoadedSounds(); scheduler.setCps(1); await prebake(); // declare default samples