mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-24 11:58:35 +00:00
Merge pull request #1024 from tidalcycles/fix-draw-bugs
better theme integration for visuals + various fixes
This commit is contained in:
commit
aa25265cc2
@ -128,6 +128,7 @@ export class StrudelMirror {
|
|||||||
id,
|
id,
|
||||||
initialCode = '',
|
initialCode = '',
|
||||||
onDraw,
|
onDraw,
|
||||||
|
drawContext,
|
||||||
drawTime = [0, 0],
|
drawTime = [0, 0],
|
||||||
autodraw,
|
autodraw,
|
||||||
prebake,
|
prebake,
|
||||||
@ -140,13 +141,14 @@ export class StrudelMirror {
|
|||||||
this.widgets = [];
|
this.widgets = [];
|
||||||
this.painters = [];
|
this.painters = [];
|
||||||
this.drawTime = drawTime;
|
this.drawTime = drawTime;
|
||||||
this.onDraw = onDraw;
|
this.drawContext = drawContext;
|
||||||
|
this.onDraw = onDraw || this.draw;
|
||||||
this.id = id || s4();
|
this.id = id || s4();
|
||||||
|
|
||||||
this.drawer = new Drawer((haps, time) => {
|
this.drawer = new Drawer((haps, time) => {
|
||||||
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
|
const currentFrame = haps.filter((hap) => hap.isActive(time));
|
||||||
this.highlight(currentFrame, time);
|
this.highlight(currentFrame, time);
|
||||||
this.onDraw?.(haps, time, currentFrame, this.painters);
|
this.onDraw(haps, time, this.painters);
|
||||||
}, drawTime);
|
}, drawTime);
|
||||||
|
|
||||||
this.prebaked = prebake();
|
this.prebaked = prebake();
|
||||||
@ -236,6 +238,9 @@ export class StrudelMirror {
|
|||||||
// when no painters are set, [0,0] is enough (just highlighting)
|
// when no painters are set, [0,0] is enough (just highlighting)
|
||||||
this.drawer.setDrawTime(this.painters.length ? this.drawTime : [0, 0]);
|
this.drawer.setDrawTime(this.painters.length ? this.drawTime : [0, 0]);
|
||||||
}
|
}
|
||||||
|
draw(haps, time) {
|
||||||
|
this.painters?.forEach((painter) => painter(this.drawContext, time, haps, this.drawTime));
|
||||||
|
}
|
||||||
async drawFirstFrame() {
|
async drawFirstFrame() {
|
||||||
if (!this.onDraw) {
|
if (!this.onDraw) {
|
||||||
return;
|
return;
|
||||||
@ -246,7 +251,7 @@ export class StrudelMirror {
|
|||||||
await this.repl.evaluate(this.code, false);
|
await this.repl.evaluate(this.code, false);
|
||||||
this.drawer.invalidate(this.repl.scheduler, -0.001);
|
this.drawer.invalidate(this.repl.scheduler, -0.001);
|
||||||
// draw at -0.001 to avoid haps at 0 to be visualized as active
|
// draw at -0.001 to avoid haps at 0 to be visualized as active
|
||||||
this.onDraw?.(this.drawer.visibleHaps, -0.001, [], this.painters);
|
this.onDraw?.(this.drawer.visibleHaps, -0.001, this.painters);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('first frame could not be painted');
|
console.warn('first frame could not be painted');
|
||||||
}
|
}
|
||||||
|
|||||||
2
packages/codemirror/themes.mjs
vendored
2
packages/codemirror/themes.mjs
vendored
@ -37,6 +37,7 @@ import whitescreen, { settings as whitescreenSettings } from './themes/whitescre
|
|||||||
import teletext, { settings as teletextSettings } from './themes/teletext';
|
import teletext, { settings as teletextSettings } from './themes/teletext';
|
||||||
import algoboy, { settings as algoboySettings } from './themes/algoboy';
|
import algoboy, { settings as algoboySettings } from './themes/algoboy';
|
||||||
import terminal, { settings as terminalSettings } from './themes/terminal';
|
import terminal, { settings as terminalSettings } from './themes/terminal';
|
||||||
|
import { setTheme } from '@strudel/draw';
|
||||||
|
|
||||||
export const themes = {
|
export const themes = {
|
||||||
strudelTheme,
|
strudelTheme,
|
||||||
@ -513,6 +514,7 @@ export function activateTheme(name) {
|
|||||||
.map(([key, value]) => `--${key}: ${value} !important;`)
|
.map(([key, value]) => `--${key}: ${value} !important;`)
|
||||||
.join('\n')}
|
.join('\n')}
|
||||||
}`;
|
}`;
|
||||||
|
setTheme(themeSettings);
|
||||||
// tailwind dark mode
|
// tailwind dark mode
|
||||||
if (themeSettings.light) {
|
if (themeSettings.light) {
|
||||||
document.documentElement.classList.remove('dark');
|
document.documentElement.classList.remove('dark');
|
||||||
|
|||||||
@ -106,23 +106,23 @@ function getCanvasWidget(id, options = {}) {
|
|||||||
|
|
||||||
registerWidget('_pianoroll', (id, options = {}, pat) => {
|
registerWidget('_pianoroll', (id, options = {}, pat) => {
|
||||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||||
return pat.id(id).pianoroll({ fold: 1, ...options, ctx, id });
|
return pat.tag(id).pianoroll({ fold: 1, ...options, ctx, id });
|
||||||
});
|
});
|
||||||
|
|
||||||
registerWidget('_punchcard', (id, options = {}, pat) => {
|
registerWidget('_punchcard', (id, options = {}, pat) => {
|
||||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||||
return pat.id(id).punchcard({ fold: 1, ...options, ctx, id });
|
return pat.tag(id).punchcard({ fold: 1, ...options, ctx, id });
|
||||||
});
|
});
|
||||||
|
|
||||||
registerWidget('_spiral', (id, options = {}, pat) => {
|
registerWidget('_spiral', (id, options = {}, pat) => {
|
||||||
let _size = options.size || 275;
|
let _size = options.size || 275;
|
||||||
options = { width: _size, height: _size, ...options, size: _size / 5 };
|
options = { width: _size, height: _size, ...options, size: _size / 5 };
|
||||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||||
return pat.id(id).spiral({ ...options, ctx, id });
|
return pat.tag(id).spiral({ ...options, ctx, id });
|
||||||
});
|
});
|
||||||
|
|
||||||
registerWidget('_scope', (id, options = {}, pat) => {
|
registerWidget('_scope', (id, options = {}, pat) => {
|
||||||
options = { width: 500, height: 60, pos: 0.5, scale: 1, ...options };
|
options = { width: 500, height: 60, pos: 0.5, scale: 1, ...options };
|
||||||
const ctx = getCanvasWidget(id, options).getContext('2d');
|
const ctx = getCanvasWidget(id, options).getContext('2d');
|
||||||
return pat.id(id).scope({ ...options, ctx, id });
|
return pat.tag(id).scope({ ...options, ctx, id });
|
||||||
});
|
});
|
||||||
|
|||||||
@ -67,6 +67,9 @@ export class Cyclist {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
now() {
|
now() {
|
||||||
|
if (!this.started) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
const secondsSinceLastTick = this.getTime() - this.lastTick - this.clock.duration;
|
const secondsSinceLastTick = this.getTime() - this.lastTick - this.clock.duration;
|
||||||
return this.lastBegin + secondsSinceLastTick * this.cps; // + this.clock.minLatency;
|
return this.lastBegin + secondsSinceLastTick * this.cps; // + this.clock.minLatency;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,6 +49,27 @@ export class Hap {
|
|||||||
return this.whole.begin.add(this.duration);
|
return this.whole.begin.add(this.duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isActive(currentTime) {
|
||||||
|
return this.whole.begin <= currentTime && this.endClipped >= currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
isInPast(currentTime) {
|
||||||
|
return currentTime > this.endClipped;
|
||||||
|
}
|
||||||
|
isInNearPast(margin, currentTime) {
|
||||||
|
return currentTime - margin <= this.endClipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
isInFuture(currentTime) {
|
||||||
|
return currentTime < this.whole.begin;
|
||||||
|
}
|
||||||
|
isInNearFuture(margin, currentTime) {
|
||||||
|
return currentTime < this.whole.begin && currentTime > this.whole.begin - margin;
|
||||||
|
}
|
||||||
|
isWithinTime(min, max) {
|
||||||
|
return this.whole.begin <= max && this.endClipped >= min;
|
||||||
|
}
|
||||||
|
|
||||||
wholeOrPart() {
|
wholeOrPart() {
|
||||||
return this.whole ? this.whole : this.part;
|
return this.whole ? this.whole : this.part;
|
||||||
}
|
}
|
||||||
@ -70,6 +91,10 @@ export class Hap {
|
|||||||
return this.whole != undefined && this.whole.begin.equals(this.part.begin);
|
return this.whole != undefined && this.whole.begin.equals(this.part.begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasTag(tag) {
|
||||||
|
return this.context.tags?.includes(tag);
|
||||||
|
}
|
||||||
|
|
||||||
resolveState(state) {
|
resolveState(state) {
|
||||||
if (this.stateful && this.hasOnset()) {
|
if (this.stateful && this.hasOnset()) {
|
||||||
console.log('stateful');
|
console.log('stateful');
|
||||||
|
|||||||
@ -2474,13 +2474,13 @@ export const hsl = register('hsl', (h, s, l, pat) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the id of the hap in, for filtering in the future.
|
* Tags each Hap with an identifier. Good for filtering. The function populates Hap.context.tags (Array).
|
||||||
* @name id
|
* @name tag
|
||||||
* @noAutocomplete
|
* @noAutocomplete
|
||||||
* @param {string} id anything unique
|
* @param {string} tag anything unique
|
||||||
*/
|
*/
|
||||||
Pattern.prototype.id = function (id) {
|
Pattern.prototype.tag = function (tag) {
|
||||||
return this.withContext((ctx) => ({ ...ctx, id }));
|
return this.withContext((ctx) => ({ ...ctx, tags: (ctx.tags || []).concat([tag]) }));
|
||||||
};
|
};
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
@ -39,51 +39,36 @@ function stopAnimationFrame(id) {
|
|||||||
function stopAllAnimations() {
|
function stopAllAnimations() {
|
||||||
Object.keys(animationFrames).forEach((id) => stopAnimationFrame(id));
|
Object.keys(animationFrames).forEach((id) => stopAnimationFrame(id));
|
||||||
}
|
}
|
||||||
Pattern.prototype.draw = function (callback, { id = 'std', from, to, onQuery, ctx } = {}) {
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
stopAnimationFrame(id);
|
|
||||||
ctx = 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);
|
|
||||||
animationFrames[id] = requestAnimationFrame(animate);
|
|
||||||
};
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// this is a more generic helper to get a rendering callback for the currently active haps
|
let memory = {};
|
||||||
// TODO: this misses events that are prolonged with clip or duration (would need state)
|
Pattern.prototype.draw = function (fn, options) {
|
||||||
Pattern.prototype.onFrame = function (id, fn, offset = 0) {
|
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
let { id = 1, lookbehind = 0, lookahead = 0 } = options;
|
||||||
|
let __t = Math.max(getTime(), 0);
|
||||||
stopAnimationFrame(id);
|
stopAnimationFrame(id);
|
||||||
|
lookbehind = Math.abs(lookbehind);
|
||||||
|
// init memory, clear future haps of old pattern
|
||||||
|
memory[id] = (memory[id] || []).filter((h) => !h.isInFuture(__t));
|
||||||
|
let newFuture = this.queryArc(__t, __t + lookahead).filter((h) => h.hasOnset());
|
||||||
|
memory[id] = memory[id].concat(newFuture);
|
||||||
|
|
||||||
|
let last;
|
||||||
const animate = () => {
|
const animate = () => {
|
||||||
const t = getTime() + offset;
|
const _t = getTime();
|
||||||
const haps = this.queryArc(t, t);
|
const t = _t + lookahead;
|
||||||
fn(haps, t, this);
|
// filter out haps that are too far in the past
|
||||||
|
memory[id] = memory[id].filter((h) => h.isInNearPast(lookbehind, _t));
|
||||||
|
// begin where we left off in last frame, but max -0.1s (inactive tab throttles to 1fps)
|
||||||
|
let begin = Math.max(last || t, t - 1 / 10);
|
||||||
|
const haps = this.queryArc(begin, t).filter((h) => h.hasOnset());
|
||||||
|
memory[id] = memory[id].concat(haps);
|
||||||
|
last = t; // makes sure no haps are missed
|
||||||
|
fn(memory[id], _t, t, this);
|
||||||
animationFrames[id] = requestAnimationFrame(animate);
|
animationFrames[id] = requestAnimationFrame(animate);
|
||||||
};
|
};
|
||||||
requestAnimationFrame(animate);
|
animationFrames[id] = requestAnimationFrame(animate);
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -198,3 +183,18 @@ export class Drawer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getComputedPropertyValue(name) {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return '#fff';
|
||||||
|
}
|
||||||
|
return getComputedStyle(document.documentElement).getPropertyValue(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let theme = {};
|
||||||
|
export function getTheme() {
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
export function setTheme(_theme) {
|
||||||
|
theme = _theme;
|
||||||
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Pattern, noteToMidi, freqToMidi } from '@strudel/core';
|
import { Pattern, noteToMidi, freqToMidi } from '@strudel/core';
|
||||||
|
import { getTheme, getDrawContext } from './draw.mjs';
|
||||||
|
|
||||||
const scale = (normalized, min, max) => normalized * (max - min) + min;
|
const scale = (normalized, min, max) => normalized * (max - min) + min;
|
||||||
const getValue = (e) => {
|
const getValue = (e) => {
|
||||||
@ -36,26 +37,23 @@ const getValue = (e) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Pattern.prototype.pianoroll = function (options = {}) {
|
Pattern.prototype.pianoroll = function (options = {}) {
|
||||||
let { cycles = 4, playhead = 0.5, overscan = 1, hideNegative = false, ctx, id } = options;
|
let { cycles = 4, playhead = 0.5, overscan = 0, hideNegative = false, ctx = getDrawContext(), id = 1 } = options;
|
||||||
|
|
||||||
let from = -cycles * playhead;
|
let from = -cycles * playhead;
|
||||||
let to = cycles * (1 - playhead);
|
let to = cycles * (1 - playhead);
|
||||||
|
const inFrame = (hap, t) => (!hideNegative || hap.whole.begin >= 0) && hap.isWithinTime(t + from, t + to);
|
||||||
this.draw(
|
this.draw(
|
||||||
(ctx, haps, t) => {
|
(haps, time) => {
|
||||||
const inFrame = (event) =>
|
|
||||||
(!hideNegative || event.whole.begin >= 0) && event.whole.begin <= t + to && event.endClipped >= t + from;
|
|
||||||
pianoroll({
|
pianoroll({
|
||||||
...options,
|
...options,
|
||||||
time: t,
|
time,
|
||||||
ctx,
|
ctx,
|
||||||
haps: haps.filter(inFrame),
|
haps: haps.filter((hap) => inFrame(hap, time)),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: from - overscan,
|
lookbehind: from - overscan,
|
||||||
to: to + overscan,
|
lookahead: to + overscan,
|
||||||
ctx,
|
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -106,11 +104,8 @@ export function pianoroll({
|
|||||||
flipTime = 0,
|
flipTime = 0,
|
||||||
flipValues = 0,
|
flipValues = 0,
|
||||||
hideNegative = false,
|
hideNegative = false,
|
||||||
// inactive = '#C9E597',
|
inactive = getTheme().foreground,
|
||||||
// inactive = '#FFCA28',
|
active = getTheme().foreground,
|
||||||
inactive = '#7491D2',
|
|
||||||
active = '#FFCA28',
|
|
||||||
// background = '#2A3236',
|
|
||||||
background = 'transparent',
|
background = 'transparent',
|
||||||
smear = 0,
|
smear = 0,
|
||||||
playheadColor = 'white',
|
playheadColor = 'white',
|
||||||
@ -137,7 +132,7 @@ export function pianoroll({
|
|||||||
let to = cycles * (1 - playhead);
|
let to = cycles * (1 - playhead);
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
haps = haps.filter((hap) => hap.context.id === id);
|
haps = haps.filter((hap) => hap.hasTag(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeframeProp) {
|
if (timeframeProp) {
|
||||||
@ -277,8 +272,8 @@ export function getDrawOptions(drawTime, options = {}) {
|
|||||||
|
|
||||||
export const getPunchcardPainter =
|
export const getPunchcardPainter =
|
||||||
(options = {}) =>
|
(options = {}) =>
|
||||||
(ctx, time, haps, drawTime, paintOptions = {}) =>
|
(ctx, time, haps, drawTime) =>
|
||||||
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { ...paintOptions, ...options }) });
|
pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, options) });
|
||||||
|
|
||||||
Pattern.prototype.punchcard = function (options) {
|
Pattern.prototype.punchcard = function (options) {
|
||||||
return this.onPaint(getPunchcardPainter(options));
|
return this.onPaint(getPunchcardPainter(options));
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Pattern } from '@strudel/core';
|
import { Pattern } from '@strudel/core';
|
||||||
|
import { getTheme } from './draw.mjs';
|
||||||
|
|
||||||
// polar coords -> xy
|
// polar coords -> xy
|
||||||
function fromPolar(angle, radius, cx, cy) {
|
function fromPolar(angle, radius, cx, cy) {
|
||||||
@ -19,7 +20,7 @@ function spiralSegment(options) {
|
|||||||
cy = 100,
|
cy = 100,
|
||||||
rotate = 0,
|
rotate = 0,
|
||||||
thickness = margin / 2,
|
thickness = margin / 2,
|
||||||
color = 'steelblue',
|
color = getTheme().foreground,
|
||||||
cap = 'round',
|
cap = 'round',
|
||||||
stretch = 1,
|
stretch = 1,
|
||||||
fromOpacity = 1,
|
fromOpacity = 1,
|
||||||
@ -61,7 +62,8 @@ function drawSpiral(options) {
|
|||||||
playheadThickness = thickness,
|
playheadThickness = thickness,
|
||||||
padding = 0,
|
padding = 0,
|
||||||
steady = 1,
|
steady = 1,
|
||||||
inactiveColor = '#ffffff50',
|
activeColor = getTheme().foreground,
|
||||||
|
inactiveColor = getTheme().gutterForeground,
|
||||||
colorizeInactive = 0,
|
colorizeInactive = 0,
|
||||||
fade = true,
|
fade = true,
|
||||||
// logSpiral = true,
|
// logSpiral = true,
|
||||||
@ -73,7 +75,7 @@ function drawSpiral(options) {
|
|||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
haps = haps.filter((hap) => hap.context.id === id);
|
haps = haps.filter((hap) => hap.hasTag(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
const [w, h] = [ctx.canvas.width, ctx.canvas.height];
|
const [w, h] = [ctx.canvas.width, ctx.canvas.height];
|
||||||
@ -102,7 +104,8 @@ function drawSpiral(options) {
|
|||||||
const isActive = hap.whole.begin <= time && hap.endClipped > time;
|
const isActive = hap.whole.begin <= time && hap.endClipped > time;
|
||||||
const from = hap.whole.begin - time + inset;
|
const from = hap.whole.begin - time + inset;
|
||||||
const to = hap.endClipped - time + inset - padding;
|
const to = hap.endClipped - time + inset - padding;
|
||||||
const color = hap.value?.color;
|
const hapColor = hap.value?.color || activeColor;
|
||||||
|
const color = colorizeInactive || isActive ? hapColor : inactiveColor;
|
||||||
const opacity = fade ? 1 - Math.abs((hap.whole.begin - time) / min) : 1;
|
const opacity = fade ? 1 - Math.abs((hap.whole.begin - time) / min) : 1;
|
||||||
spiralSegment({
|
spiralSegment({
|
||||||
ctx,
|
ctx,
|
||||||
@ -110,7 +113,7 @@ function drawSpiral(options) {
|
|||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
rotate,
|
rotate,
|
||||||
color: colorizeInactive || isActive ? color : inactiveColor,
|
color,
|
||||||
fromOpacity: opacity,
|
fromOpacity: opacity,
|
||||||
toOpacity: opacity,
|
toOpacity: opacity,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -41,17 +41,8 @@ if (typeof HTMLElement !== 'undefined') {
|
|||||||
initialCode: '// LOADING',
|
initialCode: '// LOADING',
|
||||||
pattern: silence,
|
pattern: silence,
|
||||||
drawTime,
|
drawTime,
|
||||||
onDraw: (haps, time, frame, painters) => {
|
drawContext,
|
||||||
painters.length && drawContext.clearRect(0, 0, drawContext.canvas.width * 2, drawContext.canvas.height * 2);
|
|
||||||
painters?.forEach((painter) => {
|
|
||||||
// ctx time haps drawTime paintOptions
|
|
||||||
painter(drawContext, time, haps, drawTime, { clear: false });
|
|
||||||
});
|
|
||||||
},
|
|
||||||
prebake,
|
prebake,
|
||||||
afterEval: ({ code }) => {
|
|
||||||
// window.location.hash = '#' + code2hash(code);
|
|
||||||
},
|
|
||||||
onUpdateState: (state) => {
|
onUpdateState: (state) => {
|
||||||
const event = new CustomEvent('update', {
|
const event = new CustomEvent('update', {
|
||||||
detail: state,
|
detail: state,
|
||||||
|
|||||||
@ -53,10 +53,12 @@ export function transpiler(input, options = {}) {
|
|||||||
return this.replace(sliderWithLocation(node));
|
return this.replace(sliderWithLocation(node));
|
||||||
}
|
}
|
||||||
if (isWidgetMethod(node)) {
|
if (isWidgetMethod(node)) {
|
||||||
|
const type = node.callee.property.name;
|
||||||
|
const index = widgets.filter((w) => w.type === type).length;
|
||||||
const widgetConfig = {
|
const widgetConfig = {
|
||||||
to: node.end,
|
to: node.end,
|
||||||
index: widgets.length,
|
index,
|
||||||
type: node.callee.property.name,
|
type,
|
||||||
};
|
};
|
||||||
emitWidgets && widgets.push(widgetConfig);
|
emitWidgets && widgets.push(widgetConfig);
|
||||||
return this.replace(widgetWithLocation(node, widgetConfig));
|
return this.replace(widgetWithLocation(node, widgetConfig));
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Pattern, clamp } from '@strudel/core';
|
import { Pattern, clamp } from '@strudel/core';
|
||||||
import { getDrawContext } from '../draw/index.mjs';
|
import { getDrawContext, getTheme } from '@strudel/draw';
|
||||||
import { analysers, getAnalyzerData } from 'superdough';
|
import { analysers, getAnalyzerData } from 'superdough';
|
||||||
|
|
||||||
export function drawTimeScope(
|
export function drawTimeScope(
|
||||||
@ -132,10 +132,13 @@ Pattern.prototype.fscope = function (config = {}) {
|
|||||||
* @example
|
* @example
|
||||||
* s("sawtooth").scope()
|
* s("sawtooth").scope()
|
||||||
*/
|
*/
|
||||||
|
let latestColor = {};
|
||||||
Pattern.prototype.tscope = function (config = {}) {
|
Pattern.prototype.tscope = function (config = {}) {
|
||||||
let id = config.id ?? 1;
|
let id = config.id ?? 1;
|
||||||
return this.analyze(id).draw(
|
return this.analyze(id).draw(
|
||||||
() => {
|
(haps) => {
|
||||||
|
config.color = haps[0]?.value?.color || getTheme().foreground;
|
||||||
|
latestColor[id] = config.color;
|
||||||
clearScreen(config.smear, '0,0,0', config.ctx);
|
clearScreen(config.smear, '0,0,0', config.ctx);
|
||||||
drawTimeScope(analysers[id], config);
|
drawTimeScope(analysers[id], config);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -39,16 +39,6 @@ export function MiniRepl({
|
|||||||
|
|
||||||
const init = useCallback(({ code, shouldDraw }) => {
|
const init = useCallback(({ code, shouldDraw }) => {
|
||||||
const drawContext = shouldDraw ? document.querySelector('#' + canvasId)?.getContext('2d') : null;
|
const drawContext = shouldDraw ? document.querySelector('#' + canvasId)?.getContext('2d') : null;
|
||||||
let onDraw;
|
|
||||||
if (shouldDraw) {
|
|
||||||
onDraw = (haps, time, frame, painters) => {
|
|
||||||
painters.length && drawContext?.clearRect(0, 0, drawContext.canvas.width * 2, drawContext.canvas.height * 2);
|
|
||||||
painters?.forEach((painter) => {
|
|
||||||
// ctx time haps drawTime paintOptions
|
|
||||||
painter(drawContext, time, haps, drawTime, { clear: false });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const editor = new StrudelMirror({
|
const editor = new StrudelMirror({
|
||||||
id,
|
id,
|
||||||
@ -60,7 +50,7 @@ export function MiniRepl({
|
|||||||
initialCode: '// LOADING',
|
initialCode: '// LOADING',
|
||||||
pattern: silence,
|
pattern: silence,
|
||||||
drawTime,
|
drawTime,
|
||||||
onDraw,
|
drawContext,
|
||||||
editPattern: (pat, id) => {
|
editPattern: (pat, id) => {
|
||||||
if (onTrigger) {
|
if (onTrigger) {
|
||||||
pat = pat.onTrigger(onTrigger, false);
|
pat = pat.onTrigger(onTrigger, false);
|
||||||
|
|||||||
@ -65,15 +65,8 @@ export function Repl({ embedded = false }) {
|
|||||||
const init = useCallback(() => {
|
const init = useCallback(() => {
|
||||||
const drawTime = [-2, 2];
|
const drawTime = [-2, 2];
|
||||||
const drawContext = getDrawContext();
|
const drawContext = getDrawContext();
|
||||||
const onDraw = (haps, time, frame, painters) => {
|
|
||||||
painters.length && drawContext.clearRect(0, 0, drawContext.canvas.width * 2, drawContext.canvas.height * 2);
|
|
||||||
painters?.forEach((painter) => {
|
|
||||||
// ctx time haps drawTime paintOptions
|
|
||||||
painter(drawContext, time, haps, drawTime, { clear: false });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const editor = new StrudelMirror({
|
const editor = new StrudelMirror({
|
||||||
sync: true,
|
sync: false,
|
||||||
defaultOutput: webaudioOutput,
|
defaultOutput: webaudioOutput,
|
||||||
getTime: () => getAudioContext().currentTime,
|
getTime: () => getAudioContext().currentTime,
|
||||||
setInterval,
|
setInterval,
|
||||||
@ -84,7 +77,7 @@ export function Repl({ embedded = false }) {
|
|||||||
initialCode: '// LOADING',
|
initialCode: '// LOADING',
|
||||||
pattern: silence,
|
pattern: silence,
|
||||||
drawTime,
|
drawTime,
|
||||||
onDraw,
|
drawContext,
|
||||||
prebake: async () => Promise.all([modulesLoading, presets]),
|
prebake: async () => Promise.all([modulesLoading, presets]),
|
||||||
onUpdateState: (state) => {
|
onUpdateState: (state) => {
|
||||||
setReplState({ ...state });
|
setReplState({ ...state });
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user