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