mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-11 21:58:31 +00:00
begin reimplementing draw logic for parallel use
This commit is contained in:
parent
0792d0d59d
commit
2d1b62a978
@ -153,3 +153,131 @@ Pattern.prototype.pianoroll = function ({
|
||||
);
|
||||
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({
|
||||
time,
|
||||
haps,
|
||||
cycles = 4,
|
||||
playhead = 0.5,
|
||||
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,
|
||||
ctx,
|
||||
} = {}) {
|
||||
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;
|
||||
}
|
||||
if (!autorange && fold) {
|
||||
console.warn('disabling autorange has no effect when fold is enabled');
|
||||
}
|
||||
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();
|
||||
|
||||
// onQuery
|
||||
const { min, max, values } = haps.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) => a - b);
|
||||
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
|
||||
|
||||
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 <= time + to && event.whole.end >= time + from; */
|
||||
haps
|
||||
// .filter(inFrame)
|
||||
.forEach((event) => {
|
||||
const isActive = event.whole.begin <= time && event.whole.end > time;
|
||||
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(time / 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();
|
||||
return this;
|
||||
}
|
||||
|
||||
2
packages/react/dist/index.cjs.js
vendored
2
packages/react/dist/index.cjs.js
vendored
File diff suppressed because one or more lines are too long
541
packages/react/dist/index.es.js
vendored
541
packages/react/dist/index.es.js
vendored
@ -1,15 +1,15 @@
|
||||
import n, { useCallback as _, useRef as H, useEffect as L, useMemo as V, useState as w, useLayoutEffect as j } from "react";
|
||||
import X from "@uiw/react-codemirror";
|
||||
import { Decoration as E, EditorView as U } from "@codemirror/view";
|
||||
import { StateEffect as $, StateField as G } from "@codemirror/state";
|
||||
import { javascript as Y } from "@codemirror/lang-javascript";
|
||||
import { tags as r } from "@lezer/highlight";
|
||||
import { createTheme as Z } from "@uiw/codemirror-themes";
|
||||
import { useInView as ee } from "react-hook-inview";
|
||||
import { webaudioOutput as te, getAudioContext as re } from "@strudel.cycles/webaudio";
|
||||
import { repl as oe } from "@strudel.cycles/core";
|
||||
import { transpiler as ne } from "@strudel.cycles/transpiler";
|
||||
const ae = Z({
|
||||
import l, { useCallback as M, useRef as E, useEffect as C, useMemo as B, useState as _, useLayoutEffect as W } from "react";
|
||||
import Y from "@uiw/react-codemirror";
|
||||
import { Decoration as y, EditorView as $ } from "@codemirror/view";
|
||||
import { StateEffect as G, StateField as J } from "@codemirror/state";
|
||||
import { javascript as Z } from "@codemirror/lang-javascript";
|
||||
import { tags as s } from "@lezer/highlight";
|
||||
import { createTheme as ee } from "@uiw/codemirror-themes";
|
||||
import { repl as te, pianoroll as re } from "@strudel.cycles/core";
|
||||
import { webaudioOutput as oe, getAudioContext as ne } from "@strudel.cycles/webaudio";
|
||||
import { useInView as ae } from "react-hook-inview";
|
||||
import { transpiler as se } from "@strudel.cycles/transpiler";
|
||||
const ce = ee({
|
||||
theme: "dark",
|
||||
settings: {
|
||||
background: "#222",
|
||||
@ -22,299 +22,364 @@ const ae = Z({
|
||||
gutterForeground: "#8a919966"
|
||||
},
|
||||
styles: [
|
||||
{ tag: r.keyword, color: "#c792ea" },
|
||||
{ tag: r.operator, color: "#89ddff" },
|
||||
{ tag: r.special(r.variableName), color: "#eeffff" },
|
||||
{ tag: r.typeName, color: "#c3e88d" },
|
||||
{ tag: r.atom, color: "#f78c6c" },
|
||||
{ tag: r.number, color: "#c3e88d" },
|
||||
{ tag: r.definition(r.variableName), color: "#82aaff" },
|
||||
{ tag: r.string, color: "#c3e88d" },
|
||||
{ tag: r.special(r.string), color: "#c3e88d" },
|
||||
{ tag: r.comment, color: "#7d8799" },
|
||||
{ tag: r.variableName, color: "#c792ea" },
|
||||
{ tag: r.tagName, color: "#c3e88d" },
|
||||
{ tag: r.bracket, color: "#525154" },
|
||||
{ tag: r.meta, color: "#ffcb6b" },
|
||||
{ tag: r.attributeName, color: "#c792ea" },
|
||||
{ tag: r.propertyName, color: "#c792ea" },
|
||||
{ tag: r.className, color: "#decb6b" },
|
||||
{ tag: r.invalid, color: "#ffffff" }
|
||||
{ tag: s.keyword, color: "#c792ea" },
|
||||
{ tag: s.operator, color: "#89ddff" },
|
||||
{ tag: s.special(s.variableName), color: "#eeffff" },
|
||||
{ tag: s.typeName, color: "#c3e88d" },
|
||||
{ tag: s.atom, color: "#f78c6c" },
|
||||
{ tag: s.number, color: "#c3e88d" },
|
||||
{ tag: s.definition(s.variableName), color: "#82aaff" },
|
||||
{ tag: s.string, color: "#c3e88d" },
|
||||
{ tag: s.special(s.string), color: "#c3e88d" },
|
||||
{ tag: s.comment, color: "#7d8799" },
|
||||
{ tag: s.variableName, color: "#c792ea" },
|
||||
{ tag: s.tagName, color: "#c3e88d" },
|
||||
{ tag: s.bracket, color: "#525154" },
|
||||
{ tag: s.meta, color: "#ffcb6b" },
|
||||
{ tag: s.attributeName, color: "#c792ea" },
|
||||
{ tag: s.propertyName, color: "#c792ea" },
|
||||
{ tag: s.className, color: "#decb6b" },
|
||||
{ tag: s.invalid, color: "#ffffff" }
|
||||
]
|
||||
});
|
||||
const B = $.define(), se = G.define({
|
||||
const O = G.define(), ie = J.define({
|
||||
create() {
|
||||
return E.none;
|
||||
return y.none;
|
||||
},
|
||||
update(e, t) {
|
||||
try {
|
||||
for (let o of t.effects)
|
||||
if (o.is(B))
|
||||
if (o.value) {
|
||||
const a = E.mark({ attributes: { style: "background-color: #FFCA2880" } });
|
||||
e = E.set([a.range(0, t.newDoc.length)]);
|
||||
for (let r of t.effects)
|
||||
if (r.is(O))
|
||||
if (r.value) {
|
||||
const a = y.mark({ attributes: { style: "background-color: #FFCA2880" } });
|
||||
e = y.set([a.range(0, t.newDoc.length)]);
|
||||
} else
|
||||
e = E.set([]);
|
||||
e = y.set([]);
|
||||
return e;
|
||||
} catch (o) {
|
||||
return console.warn("flash error", o), e;
|
||||
} catch (r) {
|
||||
return console.warn("flash error", r), e;
|
||||
}
|
||||
},
|
||||
provide: (e) => U.decorations.from(e)
|
||||
}), ce = (e) => {
|
||||
e.dispatch({ effects: B.of(!0) }), setTimeout(() => {
|
||||
e.dispatch({ effects: B.of(!1) });
|
||||
provide: (e) => $.decorations.from(e)
|
||||
}), le = (e) => {
|
||||
e.dispatch({ effects: O.of(!0) }), setTimeout(() => {
|
||||
e.dispatch({ effects: O.of(!1) });
|
||||
}, 200);
|
||||
}, z = $.define(), ie = G.define({
|
||||
}, H = G.define(), ue = J.define({
|
||||
create() {
|
||||
return E.none;
|
||||
return y.none;
|
||||
},
|
||||
update(e, t) {
|
||||
try {
|
||||
for (let o of t.effects)
|
||||
if (o.is(z)) {
|
||||
const a = o.value.map(
|
||||
(s) => (s.context.locations || []).map(({ start: u, end: d }) => {
|
||||
const f = s.context.color || "#FFCA28";
|
||||
let c = t.newDoc.line(u.line).from + u.column, i = t.newDoc.line(d.line).from + d.column;
|
||||
for (let r of t.effects)
|
||||
if (r.is(H)) {
|
||||
const a = r.value.map(
|
||||
(n) => (n.context.locations || []).map(({ start: c, end: i }) => {
|
||||
const d = n.context.color || "#FFCA28";
|
||||
let o = t.newDoc.line(c.line).from + c.column, u = t.newDoc.line(i.line).from + i.column;
|
||||
const m = t.newDoc.length;
|
||||
return c > m || i > m ? void 0 : E.mark({ attributes: { style: `outline: 1.5px solid ${f};` } }).range(c, i);
|
||||
return o > m || u > m ? void 0 : y.mark({ attributes: { style: `outline: 1.5px solid ${d};` } }).range(o, u);
|
||||
})
|
||||
).flat().filter(Boolean) || [];
|
||||
e = E.set(a, !0);
|
||||
e = y.set(a, !0);
|
||||
}
|
||||
return e;
|
||||
} catch {
|
||||
return E.set([]);
|
||||
return y.set([]);
|
||||
}
|
||||
},
|
||||
provide: (e) => U.decorations.from(e)
|
||||
}), le = [Y(), ae, ie, se];
|
||||
function de({ value: e, onChange: t, onViewChanged: o, onSelectionChange: a, options: s, editorDidMount: u }) {
|
||||
const d = _(
|
||||
(i) => {
|
||||
t?.(i);
|
||||
provide: (e) => $.decorations.from(e)
|
||||
}), de = [Z(), ce, ue, ie];
|
||||
function fe({ value: e, onChange: t, onViewChanged: r, onSelectionChange: a, options: n, editorDidMount: c }) {
|
||||
const i = M(
|
||||
(u) => {
|
||||
t?.(u);
|
||||
},
|
||||
[t]
|
||||
), f = _(
|
||||
(i) => {
|
||||
o?.(i);
|
||||
), d = M(
|
||||
(u) => {
|
||||
r?.(u);
|
||||
},
|
||||
[o]
|
||||
), c = _(
|
||||
(i) => {
|
||||
i.selectionSet && a && a?.(i.state.selection);
|
||||
[r]
|
||||
), o = M(
|
||||
(u) => {
|
||||
u.selectionSet && a && a?.(u.state.selection);
|
||||
},
|
||||
[a]
|
||||
);
|
||||
return /* @__PURE__ */ n.createElement(n.Fragment, null, /* @__PURE__ */ n.createElement(X, {
|
||||
return /* @__PURE__ */ l.createElement(l.Fragment, null, /* @__PURE__ */ l.createElement(Y, {
|
||||
value: e,
|
||||
onChange: d,
|
||||
onCreateEditor: f,
|
||||
onUpdate: c,
|
||||
extensions: le
|
||||
onChange: i,
|
||||
onCreateEditor: d,
|
||||
onUpdate: o,
|
||||
extensions: de
|
||||
}));
|
||||
}
|
||||
function K(...e) {
|
||||
function I(...e) {
|
||||
return e.filter(Boolean).join(" ");
|
||||
}
|
||||
function ue({ view: e, pattern: t, active: o, getTime: a }) {
|
||||
const s = H([]), u = H();
|
||||
L(() => {
|
||||
function me({ view: e, pattern: t, active: r, getTime: a }) {
|
||||
const n = E([]), c = E();
|
||||
C(() => {
|
||||
if (e)
|
||||
if (t && o) {
|
||||
let d = requestAnimationFrame(function f() {
|
||||
if (t && r) {
|
||||
let i = requestAnimationFrame(function d() {
|
||||
try {
|
||||
const c = a(), m = [Math.max(u.current || c, c - 1 / 10, 0), c + 1 / 60];
|
||||
u.current = m[1], s.current = s.current.filter((g) => g.whole.end > c);
|
||||
const h = t.queryArc(...m).filter((g) => g.hasOnset());
|
||||
s.current = s.current.concat(h), e.dispatch({ effects: z.of(s.current) });
|
||||
const o = a(), m = [Math.max(c.current || o, o - 1 / 10, 0), o + 1 / 60];
|
||||
c.current = m[1], n.current = n.current.filter((v) => v.whole.end > o);
|
||||
const g = t.queryArc(...m).filter((v) => v.hasOnset());
|
||||
n.current = n.current.concat(g), e.dispatch({ effects: H.of(n.current) });
|
||||
} catch {
|
||||
e.dispatch({ effects: z.of([]) });
|
||||
e.dispatch({ effects: H.of([]) });
|
||||
}
|
||||
d = requestAnimationFrame(f);
|
||||
i = requestAnimationFrame(d);
|
||||
});
|
||||
return () => {
|
||||
cancelAnimationFrame(d);
|
||||
cancelAnimationFrame(i);
|
||||
};
|
||||
} else
|
||||
s.current = [], e.dispatch({ effects: z.of([]) });
|
||||
}, [t, o, e]);
|
||||
n.current = [], e.dispatch({ effects: H.of([]) });
|
||||
}, [t, r, e]);
|
||||
}
|
||||
const fe = "_container_3i85k_1", me = "_header_3i85k_5", ge = "_buttons_3i85k_9", pe = "_button_3i85k_9", he = "_buttonDisabled_3i85k_17", be = "_error_3i85k_21", ve = "_body_3i85k_25", v = {
|
||||
container: fe,
|
||||
header: me,
|
||||
buttons: ge,
|
||||
button: pe,
|
||||
buttonDisabled: he,
|
||||
error: be,
|
||||
body: ve
|
||||
};
|
||||
function O({ type: e }) {
|
||||
return /* @__PURE__ */ n.createElement("svg", {
|
||||
function ge(e, t = !1) {
|
||||
const r = E(), a = E(), n = (d) => {
|
||||
if (a.current !== void 0) {
|
||||
const o = d - a.current;
|
||||
e(d, o);
|
||||
}
|
||||
a.current = d, r.current = requestAnimationFrame(n);
|
||||
}, c = () => {
|
||||
r.current = requestAnimationFrame(n);
|
||||
}, i = () => {
|
||||
r.current && cancelAnimationFrame(r.current), delete r.current;
|
||||
};
|
||||
return C(() => {
|
||||
r.current && (i(), c());
|
||||
}, [e]), C(() => (t && c(), i), []), {
|
||||
start: c,
|
||||
stop: i
|
||||
};
|
||||
}
|
||||
function pe({ pattern: e, started: t, getTime: r, onDraw: a }) {
|
||||
let n = E([]), c = E(null);
|
||||
const { start: i, stop: d } = ge(
|
||||
M(() => {
|
||||
const o = r();
|
||||
if (c.current === null) {
|
||||
c.current = o;
|
||||
return;
|
||||
}
|
||||
const u = e.queryArc(Math.max(c.current, o - 1 / 10), o), m = 4;
|
||||
c.current = o, n.current = (n.current || []).filter((g) => g.whole.end > o - m).concat(u.filter((g) => g.hasOnset())), a(o, n.current);
|
||||
}, [e])
|
||||
);
|
||||
C(() => {
|
||||
t ? i() : (n.current = [], d());
|
||||
}, [t]);
|
||||
}
|
||||
function he(e) {
|
||||
return C(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), M((t) => window.postMessage(t, "*"), []);
|
||||
}
|
||||
function ve({
|
||||
defaultOutput: e,
|
||||
interval: t,
|
||||
getTime: r,
|
||||
evalOnMount: a = !1,
|
||||
initialCode: n = "",
|
||||
autolink: c = !1,
|
||||
beforeEval: i,
|
||||
afterEval: d,
|
||||
onEvalError: o,
|
||||
onToggle: u,
|
||||
canvasId: m
|
||||
}) {
|
||||
const g = B(() => be(), []);
|
||||
m = m || `canvas-${g}`;
|
||||
const [v, F] = _(), [k, A] = _(), [b, D] = _(n), [x, R] = _(), [S, z] = _(), [N, T] = _(!1), L = b !== x, { scheduler: f, evaluate: h, start: V, stop: K, pause: Q } = B(
|
||||
() => te({
|
||||
interval: t,
|
||||
defaultOutput: e,
|
||||
onSchedulerError: F,
|
||||
onEvalError: (p) => {
|
||||
A(p), o?.(p);
|
||||
},
|
||||
getTime: r,
|
||||
transpiler: se,
|
||||
beforeEval: ({ code: p }) => {
|
||||
D(p), i?.();
|
||||
},
|
||||
afterEval: ({ pattern: p, code: q }) => {
|
||||
R(q), z(p), A(), F(), c && (window.location.hash = "#" + encodeURIComponent(btoa(q))), d?.();
|
||||
},
|
||||
onToggle: (p) => {
|
||||
T(p), u?.(p);
|
||||
}
|
||||
}),
|
||||
[e, t, r]
|
||||
), X = he(({ data: { from: p, type: q } }) => {
|
||||
q === "start" && p !== g && K();
|
||||
}), P = M(
|
||||
async (p = !0) => {
|
||||
await h(b, p), X({ type: "start", from: g });
|
||||
},
|
||||
[h, b]
|
||||
), j = E();
|
||||
return C(() => {
|
||||
!j.current && a && b && (j.current = !0, P());
|
||||
}, [P, a, b]), C(() => () => {
|
||||
f.stop();
|
||||
}, [f]), {
|
||||
id: g,
|
||||
canvasId: m,
|
||||
code: b,
|
||||
setCode: D,
|
||||
error: v || k,
|
||||
schedulerError: v,
|
||||
scheduler: f,
|
||||
evalError: k,
|
||||
evaluate: h,
|
||||
activateCode: P,
|
||||
activeCode: x,
|
||||
isDirty: L,
|
||||
pattern: S,
|
||||
started: N,
|
||||
start: V,
|
||||
stop: K,
|
||||
pause: Q,
|
||||
togglePlay: async () => {
|
||||
N ? f.pause() : await P();
|
||||
}
|
||||
};
|
||||
}
|
||||
function be() {
|
||||
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
|
||||
}
|
||||
function U({ type: e }) {
|
||||
return /* @__PURE__ */ l.createElement("svg", {
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
className: "sc-h-5 sc-w-5",
|
||||
viewBox: "0 0 20 20",
|
||||
fill: "currentColor"
|
||||
}, {
|
||||
refresh: /* @__PURE__ */ n.createElement("path", {
|
||||
refresh: /* @__PURE__ */ l.createElement("path", {
|
||||
fillRule: "evenodd",
|
||||
d: "M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",
|
||||
clipRule: "evenodd"
|
||||
}),
|
||||
play: /* @__PURE__ */ n.createElement("path", {
|
||||
play: /* @__PURE__ */ l.createElement("path", {
|
||||
fillRule: "evenodd",
|
||||
d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",
|
||||
clipRule: "evenodd"
|
||||
}),
|
||||
pause: /* @__PURE__ */ n.createElement("path", {
|
||||
pause: /* @__PURE__ */ l.createElement("path", {
|
||||
fillRule: "evenodd",
|
||||
d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",
|
||||
clipRule: "evenodd"
|
||||
})
|
||||
}[e]);
|
||||
}
|
||||
function Ee(e) {
|
||||
return L(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), _((t) => window.postMessage(t, "*"), []);
|
||||
}
|
||||
function we({
|
||||
defaultOutput: e,
|
||||
interval: t,
|
||||
getTime: o,
|
||||
evalOnMount: a = !1,
|
||||
initialCode: s = "",
|
||||
autolink: u = !1,
|
||||
beforeEval: d,
|
||||
afterEval: f,
|
||||
onEvalError: c,
|
||||
onToggle: i
|
||||
const we = "_container_3i85k_1", ye = "_header_3i85k_5", Ee = "_buttons_3i85k_9", ke = "_button_3i85k_9", _e = "_buttonDisabled_3i85k_17", Ce = "_error_3i85k_21", Fe = "_body_3i85k_25", w = {
|
||||
container: we,
|
||||
header: ye,
|
||||
buttons: Ee,
|
||||
button: ke,
|
||||
buttonDisabled: _e,
|
||||
error: Ce,
|
||||
body: Fe
|
||||
}, Ne = () => ne().currentTime;
|
||||
function Be({
|
||||
tune: e,
|
||||
hideOutsideView: t = !1,
|
||||
init: r,
|
||||
enableKeyboard: a,
|
||||
withCanvas: n = !1,
|
||||
canvasHeight: c = 200
|
||||
}) {
|
||||
const m = V(() => ye(), []), [h, g] = w(), [C, N] = w(), [p, y] = w(s), [M, S] = w(), [k, D] = w(), [F, x] = w(!1), b = p !== M, { scheduler: A, evaluate: T, start: J, stop: q, pause: Q } = V(
|
||||
() => oe({
|
||||
interval: t,
|
||||
defaultOutput: e,
|
||||
onSchedulerError: g,
|
||||
onEvalError: (l) => {
|
||||
N(l), c?.(l);
|
||||
},
|
||||
getTime: o,
|
||||
transpiler: ne,
|
||||
beforeEval: ({ code: l }) => {
|
||||
y(l), d?.();
|
||||
},
|
||||
afterEval: ({ pattern: l, code: P }) => {
|
||||
S(P), D(l), N(), g(), u && (window.location.hash = "#" + encodeURIComponent(btoa(P))), f?.();
|
||||
},
|
||||
onToggle: (l) => {
|
||||
x(l), i?.(l);
|
||||
}
|
||||
}),
|
||||
[e, t, o]
|
||||
), W = Ee(({ data: { from: l, type: P } }) => {
|
||||
P === "start" && l !== m && q();
|
||||
}), R = _(
|
||||
async (l = !0) => {
|
||||
await T(p, l), W({ type: "start", from: m });
|
||||
},
|
||||
[T, p]
|
||||
), I = H();
|
||||
return L(() => {
|
||||
!I.current && a && p && (I.current = !0, R());
|
||||
}, [R, a, p]), L(() => () => {
|
||||
A.stop();
|
||||
}, [A]), {
|
||||
code: p,
|
||||
setCode: y,
|
||||
error: h || C,
|
||||
schedulerError: h,
|
||||
scheduler: A,
|
||||
evalError: C,
|
||||
evaluate: T,
|
||||
activateCode: R,
|
||||
activeCode: M,
|
||||
isDirty: b,
|
||||
pattern: k,
|
||||
started: F,
|
||||
start: J,
|
||||
stop: q,
|
||||
pause: Q,
|
||||
togglePlay: async () => {
|
||||
F ? A.pause() : await R();
|
||||
}
|
||||
};
|
||||
}
|
||||
function ye() {
|
||||
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
|
||||
}
|
||||
const ke = () => re().currentTime;
|
||||
function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
|
||||
const {
|
||||
code: s,
|
||||
setCode: u,
|
||||
evaluate: d,
|
||||
activateCode: f,
|
||||
error: c,
|
||||
isDirty: i,
|
||||
activeCode: m,
|
||||
pattern: h,
|
||||
started: g,
|
||||
scheduler: C,
|
||||
togglePlay: N,
|
||||
stop: p
|
||||
} = we({
|
||||
code: i,
|
||||
setCode: d,
|
||||
evaluate: o,
|
||||
activateCode: u,
|
||||
error: m,
|
||||
isDirty: g,
|
||||
activeCode: v,
|
||||
pattern: F,
|
||||
started: k,
|
||||
scheduler: A,
|
||||
togglePlay: b,
|
||||
stop: D,
|
||||
canvasId: x
|
||||
} = ve({
|
||||
initialCode: e,
|
||||
defaultOutput: te,
|
||||
getTime: ke
|
||||
}), [y, M] = w(), [S, k] = ee({
|
||||
threshold: 0.01
|
||||
}), D = H(), F = V(() => ((k || !t) && (D.current = !0), k || D.current), [k, t]);
|
||||
return ue({
|
||||
view: y,
|
||||
pattern: h,
|
||||
active: g && !m?.includes("strudel disable-highlighting"),
|
||||
getTime: () => C.getPhase()
|
||||
}), j(() => {
|
||||
if (a) {
|
||||
const x = async (b) => {
|
||||
(b.ctrlKey || b.altKey) && (b.code === "Enter" ? (b.preventDefault(), ce(y), await f()) : b.code === "Period" && (p(), b.preventDefault()));
|
||||
};
|
||||
return window.addEventListener("keydown", x, !0), () => window.removeEventListener("keydown", x, !0);
|
||||
defaultOutput: oe,
|
||||
getTime: Ne
|
||||
});
|
||||
pe({
|
||||
pattern: F,
|
||||
started: k,
|
||||
getTime: () => A.now(),
|
||||
onDraw: (f, h) => {
|
||||
const V = document.querySelector("#" + x).getContext("2d");
|
||||
re({ ctx: V, time: f, haps: h, autorange: 1, fold: 1, playhead: 1 });
|
||||
}
|
||||
}, [a, h, s, d, p, y]), /* @__PURE__ */ n.createElement("div", {
|
||||
className: v.container,
|
||||
ref: S
|
||||
}, /* @__PURE__ */ n.createElement("div", {
|
||||
className: v.header
|
||||
}, /* @__PURE__ */ n.createElement("div", {
|
||||
className: v.buttons
|
||||
}, /* @__PURE__ */ n.createElement("button", {
|
||||
className: K(v.button, g ? "sc-animate-pulse" : ""),
|
||||
onClick: () => N()
|
||||
}, /* @__PURE__ */ n.createElement(O, {
|
||||
type: g ? "pause" : "play"
|
||||
})), /* @__PURE__ */ n.createElement("button", {
|
||||
className: K(i ? v.button : v.buttonDisabled),
|
||||
onClick: () => f()
|
||||
}, /* @__PURE__ */ n.createElement(O, {
|
||||
});
|
||||
const [R, S] = _(), [z, N] = ae({
|
||||
threshold: 0.01
|
||||
}), T = E(), L = B(() => ((N || !t) && (T.current = !0), N || T.current), [N, t]);
|
||||
return me({
|
||||
view: R,
|
||||
pattern: F,
|
||||
active: k && !v?.includes("strudel disable-highlighting"),
|
||||
getTime: () => A.getPhase()
|
||||
}), W(() => {
|
||||
if (a) {
|
||||
const f = async (h) => {
|
||||
(h.ctrlKey || h.altKey) && (h.code === "Enter" ? (h.preventDefault(), le(R), await u()) : h.code === "Period" && (D(), h.preventDefault()));
|
||||
};
|
||||
return window.addEventListener("keydown", f, !0), () => window.removeEventListener("keydown", f, !0);
|
||||
}
|
||||
}, [a, F, i, o, D, R]), /* @__PURE__ */ l.createElement("div", {
|
||||
className: w.container,
|
||||
ref: z
|
||||
}, /* @__PURE__ */ l.createElement("div", {
|
||||
className: w.header
|
||||
}, /* @__PURE__ */ l.createElement("div", {
|
||||
className: w.buttons
|
||||
}, /* @__PURE__ */ l.createElement("button", {
|
||||
className: I(w.button, k ? "sc-animate-pulse" : ""),
|
||||
onClick: () => b()
|
||||
}, /* @__PURE__ */ l.createElement(U, {
|
||||
type: k ? "pause" : "play"
|
||||
})), /* @__PURE__ */ l.createElement("button", {
|
||||
className: I(g ? w.button : w.buttonDisabled),
|
||||
onClick: () => u()
|
||||
}, /* @__PURE__ */ l.createElement(U, {
|
||||
type: "refresh"
|
||||
}))), c && /* @__PURE__ */ n.createElement("div", {
|
||||
className: v.error
|
||||
}, c.message)), /* @__PURE__ */ n.createElement("div", {
|
||||
className: v.body
|
||||
}, F && /* @__PURE__ */ n.createElement(de, {
|
||||
value: s,
|
||||
onChange: u,
|
||||
onViewChanged: M
|
||||
})));
|
||||
}))), m && /* @__PURE__ */ l.createElement("div", {
|
||||
className: w.error
|
||||
}, m.message)), /* @__PURE__ */ l.createElement("div", {
|
||||
className: w.body
|
||||
}, L && /* @__PURE__ */ l.createElement(fe, {
|
||||
value: i,
|
||||
onChange: d,
|
||||
onViewChanged: S
|
||||
})), n && /* @__PURE__ */ l.createElement("canvas", {
|
||||
id: x,
|
||||
className: "w-full pointer-events-none",
|
||||
height: c,
|
||||
ref: (f) => {
|
||||
f && f.width !== f.clientWidth && (f.width = f.clientWidth);
|
||||
}
|
||||
}));
|
||||
}
|
||||
const Te = (e) => j(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
|
||||
const Oe = (e) => W(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
|
||||
export {
|
||||
de as CodeMirror,
|
||||
Se as MiniRepl,
|
||||
K as cx,
|
||||
ce as flash,
|
||||
ue as useHighlighting,
|
||||
Te as useKeydown,
|
||||
Ee as usePostMessage,
|
||||
we as useStrudel
|
||||
fe as CodeMirror,
|
||||
Be as MiniRepl,
|
||||
I as cx,
|
||||
le as flash,
|
||||
me as useHighlighting,
|
||||
Oe as useKeydown,
|
||||
he as usePostMessage,
|
||||
ve as useStrudel
|
||||
};
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
import React, { useState, useMemo, useRef, useEffect, useLayoutEffect } from 'react';
|
||||
import { pianoroll } from '@strudel.cycles/core';
|
||||
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||
import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useInView } from 'react-hook-inview';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import cx from '../cx';
|
||||
import useHighlighting from '../hooks/useHighlighting.mjs';
|
||||
import CodeMirror6, { flash } from './CodeMirror6';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import './style.css';
|
||||
import styles from './MiniRepl.module.css';
|
||||
import { Icon } from './Icon';
|
||||
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||
import usePatternFrame from '../hooks/usePatternFrame.mjs';
|
||||
import useStrudel from '../hooks/useStrudel.mjs';
|
||||
import CodeMirror6, { flash } from './CodeMirror6';
|
||||
import { Icon } from './Icon';
|
||||
import styles from './MiniRepl.module.css';
|
||||
import './style.css';
|
||||
|
||||
const getTime = () => getAudioContext().currentTime;
|
||||
|
||||
export function MiniRepl({ tune, hideOutsideView = false, init, enableKeyboard }) {
|
||||
export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCanvas = false, canvasHeight = 200 }) {
|
||||
const {
|
||||
code,
|
||||
setCode,
|
||||
@ -26,11 +28,23 @@ export function MiniRepl({ tune, hideOutsideView = false, init, enableKeyboard }
|
||||
scheduler,
|
||||
togglePlay,
|
||||
stop,
|
||||
canvasId,
|
||||
} = useStrudel({
|
||||
initialCode: tune,
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime,
|
||||
});
|
||||
|
||||
usePatternFrame({
|
||||
pattern,
|
||||
started,
|
||||
getTime: () => scheduler.now(),
|
||||
onDraw: (time, haps) => {
|
||||
const ctx = document.querySelector('#' + canvasId).getContext('2d');
|
||||
pianoroll({ ctx, time, haps, autorange: 1, fold: 1, playhead: 1 });
|
||||
},
|
||||
});
|
||||
|
||||
/* useEffect(() => {
|
||||
init && activateCode();
|
||||
}, [init, activateCode]); */
|
||||
@ -88,6 +102,18 @@ export function MiniRepl({ tune, hideOutsideView = false, init, enableKeyboard }
|
||||
<div className={styles.body}>
|
||||
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} />}
|
||||
</div>
|
||||
{withCanvas && (
|
||||
<canvas
|
||||
id={canvasId}
|
||||
className="w-full pointer-events-none"
|
||||
height={canvasHeight}
|
||||
ref={(el) => {
|
||||
if (el && el.width !== el.clientWidth) {
|
||||
el.width = el.clientWidth;
|
||||
}
|
||||
}}
|
||||
></canvas>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
43
packages/react/src/hooks/useFrame.mjs
Normal file
43
packages/react/src/hooks/useFrame.mjs
Normal file
@ -0,0 +1,43 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
function useFrame(callback, autostart = false) {
|
||||
const requestRef = useRef();
|
||||
const previousTimeRef = useRef();
|
||||
|
||||
const animate = (time) => {
|
||||
if (previousTimeRef.current !== undefined) {
|
||||
const deltaTime = time - previousTimeRef.current;
|
||||
callback(time, deltaTime);
|
||||
}
|
||||
previousTimeRef.current = time;
|
||||
requestRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
const start = () => {
|
||||
requestRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
const stop = () => {
|
||||
requestRef.current && cancelAnimationFrame(requestRef.current);
|
||||
delete requestRef.current;
|
||||
};
|
||||
useEffect(() => {
|
||||
if (requestRef.current) {
|
||||
stop();
|
||||
start();
|
||||
}
|
||||
}, [callback]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autostart) {
|
||||
start();
|
||||
}
|
||||
return stop;
|
||||
}, []); // Make sure the effect only runs once
|
||||
|
||||
return {
|
||||
start,
|
||||
stop,
|
||||
};
|
||||
}
|
||||
|
||||
export default useFrame;
|
||||
34
packages/react/src/hooks/usePatternFrame.mjs
Normal file
34
packages/react/src/hooks/usePatternFrame.mjs
Normal file
@ -0,0 +1,34 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import useFrame from '../hooks/useFrame.mjs';
|
||||
|
||||
function usePatternFrame({ pattern, started, getTime, onDraw }) {
|
||||
let visibleHaps = useRef([]);
|
||||
let lastFrame = useRef(null);
|
||||
const { start: startFrame, stop: stopFrame } = useFrame(
|
||||
useCallback(() => {
|
||||
const phase = getTime();
|
||||
if (lastFrame.current === null) {
|
||||
lastFrame.current = phase;
|
||||
return;
|
||||
}
|
||||
const haps = pattern.queryArc(Math.max(lastFrame.current, phase - 1 / 10), phase);
|
||||
const cycles = 4;
|
||||
lastFrame.current = phase;
|
||||
visibleHaps.current = (visibleHaps.current || [])
|
||||
.filter((h) => h.whole.end > phase - cycles) // in frame
|
||||
.concat(haps.filter((h) => h.hasOnset()));
|
||||
onDraw(phase, visibleHaps.current);
|
||||
}, [pattern]),
|
||||
);
|
||||
useEffect(() => {
|
||||
if (started) {
|
||||
startFrame();
|
||||
} else {
|
||||
visibleHaps.current = [];
|
||||
stopFrame();
|
||||
}
|
||||
}, [started]);
|
||||
}
|
||||
|
||||
export default usePatternFrame;
|
||||
@ -14,8 +14,10 @@ function useStrudel({
|
||||
afterEval,
|
||||
onEvalError,
|
||||
onToggle,
|
||||
canvasId,
|
||||
}) {
|
||||
const id = useMemo(() => s4(), []);
|
||||
canvasId = canvasId || `canvas-${id}`;
|
||||
// scheduler
|
||||
const [schedulerError, setSchedulerError] = useState();
|
||||
const [evalError, setEvalError] = useState();
|
||||
@ -97,6 +99,8 @@ function useStrudel({
|
||||
};
|
||||
const error = schedulerError || evalError;
|
||||
return {
|
||||
id,
|
||||
canvasId,
|
||||
code,
|
||||
setCode,
|
||||
error,
|
||||
|
||||
@ -22,7 +22,7 @@ if (typeof window !== 'undefined') {
|
||||
prebake();
|
||||
}
|
||||
|
||||
export function MiniRepl({ tune }) {
|
||||
export function MiniRepl({ tune, withCanvas }) {
|
||||
const [Repl, setRepl] = useState();
|
||||
useEffect(() => {
|
||||
// we have to load this package on the client
|
||||
@ -31,5 +31,5 @@ export function MiniRepl({ tune }) {
|
||||
setRepl(() => res.MiniRepl);
|
||||
});
|
||||
}, []);
|
||||
return Repl ? <Repl tune={tune} hideOutsideView={true} /> : <pre>{tune}</pre>;
|
||||
return Repl ? <Repl tune={tune} hideOutsideView={true} withCanvas={withCanvas} /> : <pre>{tune}</pre>;
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ Here's the same pattern written in three different ways:
|
||||
|
||||
- `note`: letter notation, good for those who are familiar with western music theory:
|
||||
|
||||
<MiniRepl client:idle tune={`note("a3 c#4 e4 a4")`} />
|
||||
<MiniRepl client:idle tune={`note("a3 c#4 e4 a4")`} withCanvas />
|
||||
|
||||
- `n`: number notation, good for those who want to use recognisable pitches, but don't care about music theory:
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user