support logs in mini repl

+ pass hap to logger
+ add editPattern hook to repl + useStrudel
+ do not throw when webaudio gets plain values
This commit is contained in:
Felix Roos 2023-01-09 23:26:20 +01:00
parent 10ee11c886
commit 69ecb7b54f
8 changed files with 317 additions and 228 deletions

View File

@ -776,8 +776,10 @@ export class Pattern {
);
}
log(func = (_, hap) => `[hap] ${hap.showWhole(true)}`) {
return this.onTrigger((...args) => logger(func(...args)), false);
log(func = (_, hap) => `[hap] ${hap.showWhole(true)}`, getData = (_, hap) => ({ hap })) {
return this.onTrigger((...args) => {
logger(func(...args), undefined, getData(...args));
}, false);
}
logValues(func = id) {
@ -1045,7 +1047,12 @@ Pattern.prototype.factories = {
// Elemental patterns
// Nothing
/**
* Does absolutely nothing..
* @name silence
* @example
* silence // "~"
*/
export const silence = new Pattern(() => []);
/** A discrete value that repeats once per cycle.
@ -1372,7 +1379,11 @@ export const round = register('round', function (pat) {
* Assumes a numerical pattern. Returns a new pattern with all values set to
* their mathematical floor. E.g. `3.7` replaced with to `3`, and `-4.2`
* replaced with `-5`.
* @name floor
* @memberof Pattern
* @returns Pattern
* @example
* "42 42.1 42.5 43".floor().note()
*/
export const floor = register('floor', function (pat) {
return pat.asNumber().fmap((v) => Math.floor(v));
@ -1382,7 +1393,11 @@ export const floor = register('floor', function (pat) {
* Assumes a numerical pattern. Returns a new pattern with all values set to
* their mathematical ceiling. E.g. `3.2` replaced with `4`, and `-4.2`
* replaced with `-4`.
* @name ceil
* @memberof Pattern
* @returns Pattern
* @example
* "42 42.1 42.5 43".ceil().note()
*/
export const ceil = register('ceil', function (pat) {
return pat.asNumber().fmap((v) => Math.ceil(v));

View File

@ -12,6 +12,7 @@ export function repl({
afterEval,
getTime,
transpiler,
editPattern,
onToggle,
}) {
const scheduler = new Cyclist({
@ -41,8 +42,10 @@ export function repl({
}
try {
beforeEval?.({ code });
const { pattern } = await _evaluate(code, transpiler);
let { pattern } = await _evaluate(code, transpiler);
logger(`[eval] code updated`);
pattern = editPattern?.(pattern) || pattern;
scheduler.setPattern(pattern, autostart);
afterEval?.({ code, pattern });
return pattern;

File diff suppressed because one or more lines are too long

View File

@ -1,15 +1,15 @@
import i, { useCallback as N, useRef as E, useEffect as F, useMemo as V, useState as _, useLayoutEffect as U } from "react";
import Y from "@uiw/react-codemirror";
import { Decoration as y, EditorView as W } from "@codemirror/view";
import { StateEffect as $, StateField as G } from "@codemirror/state";
import { javascript as Z } from "@codemirror/lang-javascript";
import l, { useCallback as N, useRef as k, useEffect as _, useMemo as K, useState as w, useLayoutEffect as G } from "react";
import Z from "@uiw/react-codemirror";
import { Decoration as y, EditorView as J } from "@codemirror/view";
import { StateEffect as Q, StateField as X } from "@codemirror/state";
import { javascript as ee } 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({
import { createTheme as te } from "@uiw/codemirror-themes";
import { repl as re, logger as ne, pianoroll as oe } from "@strudel.cycles/core";
import { webaudioOutput as ae, getAudioContext as ce } from "@strudel.cycles/webaudio";
import { useInView as se } from "react-hook-inview";
import { transpiler as ie } from "@strudel.cycles/transpiler";
const le = te({
theme: "dark",
settings: {
background: "#222",
@ -42,14 +42,14 @@ const ce = ee({
{ tag: s.invalid, color: "#ffffff" }
]
});
const B = $.define(), ie = G.define({
const j = Q.define(), ue = X.define({
create() {
return y.none;
},
update(e, r) {
try {
for (let t of r.effects)
if (t.is(B))
if (t.is(j))
if (t.value) {
const a = y.mark({ attributes: { style: "background-color: #FFCA2880" } });
e = y.set([a.range(0, r.newDoc.length)]);
@ -60,25 +60,25 @@ const B = $.define(), ie = G.define({
return console.warn("flash error", t), e;
}
},
provide: (e) => W.decorations.from(e)
}), le = (e) => {
e.dispatch({ effects: B.of(!0) }), setTimeout(() => {
e.dispatch({ effects: B.of(!1) });
provide: (e) => J.decorations.from(e)
}), de = (e) => {
e.dispatch({ effects: j.of(!0) }), setTimeout(() => {
e.dispatch({ effects: j.of(!1) });
}, 200);
}, H = $.define(), ue = G.define({
}, z = Q.define(), fe = X.define({
create() {
return y.none;
},
update(e, r) {
try {
for (let t of r.effects)
if (t.is(H)) {
if (t.is(z)) {
const a = t.value.map(
(n) => (n.context.locations || []).map(({ start: c, end: l }) => {
const d = n.context.color || "#FFCA28";
let o = r.newDoc.line(c.line).from + c.column, u = r.newDoc.line(l.line).from + l.column;
const f = r.newDoc.length;
return o > f || u > f ? void 0 : y.mark({ attributes: { style: `outline: 1.5px solid ${d};` } }).range(o, u);
(o) => (o.context.locations || []).map(({ start: i, end: u }) => {
const m = o.context.color || "#FFCA28";
let n = r.newDoc.line(i.line).from + i.column, d = r.newDoc.line(u.line).from + u.column;
const v = r.newDoc.length;
return n > v || d > v ? void 0 : y.mark({ attributes: { style: `outline: 1.5px solid ${m};` } }).range(n, d);
})
).flat().filter(Boolean) || [];
e = y.set(a, !0);
@ -88,291 +88,314 @@ const B = $.define(), ie = G.define({
return y.set([]);
}
},
provide: (e) => W.decorations.from(e)
}), de = [Z(), ce, ue, ie];
function fe({ value: e, onChange: r, onViewChanged: t, onSelectionChange: a, options: n, editorDidMount: c }) {
const l = N(
(u) => {
r?.(u);
provide: (e) => J.decorations.from(e)
}), me = [ee(), le, fe, ue];
function ge({ value: e, onChange: r, onViewChanged: t, onSelectionChange: a, options: o, editorDidMount: i }) {
const u = N(
(d) => {
r?.(d);
},
[r]
), d = N(
(u) => {
t?.(u);
), m = N(
(d) => {
t?.(d);
},
[t]
), o = N(
(u) => {
u.selectionSet && a && a?.(u.state.selection);
), n = N(
(d) => {
d.selectionSet && a && a?.(d.state.selection);
},
[a]
);
return /* @__PURE__ */ i.createElement(i.Fragment, null, /* @__PURE__ */ i.createElement(Y, {
return /* @__PURE__ */ l.createElement(l.Fragment, null, /* @__PURE__ */ l.createElement(Z, {
value: e,
onChange: l,
onCreateEditor: d,
onUpdate: o,
extensions: de
onChange: u,
onCreateEditor: m,
onUpdate: n,
extensions: me
}));
}
function j(...e) {
function W(...e) {
return e.filter(Boolean).join(" ");
}
function me({ view: e, pattern: r, active: t, getTime: a }) {
const n = E([]), c = E();
F(() => {
function pe({ view: e, pattern: r, active: t, getTime: a }) {
const o = k([]), i = k();
_(() => {
if (e)
if (r && t) {
let l = requestAnimationFrame(function d() {
let u = requestAnimationFrame(function m() {
try {
const o = a(), f = [Math.max(c.current || o, o - 1 / 10, 0), o + 1 / 60];
c.current = f[1], n.current = n.current.filter((v) => v.whole.end > o);
const m = r.queryArc(...f).filter((v) => v.hasOnset());
n.current = n.current.concat(m), e.dispatch({ effects: H.of(n.current) });
const n = a(), v = [Math.max(i.current || n, n - 1 / 10, 0), n + 1 / 60];
i.current = v[1], o.current = o.current.filter((p) => p.whole.end > n);
const h = r.queryArc(...v).filter((p) => p.hasOnset());
o.current = o.current.concat(h), e.dispatch({ effects: z.of(o.current) });
} catch {
e.dispatch({ effects: H.of([]) });
e.dispatch({ effects: z.of([]) });
}
l = requestAnimationFrame(d);
u = requestAnimationFrame(m);
});
return () => {
cancelAnimationFrame(l);
cancelAnimationFrame(u);
};
} else
n.current = [], e.dispatch({ effects: H.of([]) });
o.current = [], e.dispatch({ effects: z.of([]) });
}, [r, t, e]);
}
function ge(e, r = !1) {
const t = E(), a = E(), n = (d) => {
function he(e, r = !1) {
const t = k(), a = k(), o = (m) => {
if (a.current !== void 0) {
const o = d - a.current;
e(d, o);
const n = m - a.current;
e(m, n);
}
a.current = d, t.current = requestAnimationFrame(n);
}, c = () => {
t.current = requestAnimationFrame(n);
}, l = () => {
a.current = m, t.current = requestAnimationFrame(o);
}, i = () => {
t.current = requestAnimationFrame(o);
}, u = () => {
t.current && cancelAnimationFrame(t.current), delete t.current;
};
return F(() => {
t.current && (l(), c());
}, [e]), F(() => (r && c(), l), []), {
start: c,
stop: l
return _(() => {
t.current && (u(), i());
}, [e]), _(() => (r && i(), u), []), {
start: i,
stop: u
};
}
function pe({ pattern: e, started: r, getTime: t, onDraw: a }) {
let n = E([]), c = E(null);
const { start: l, stop: d } = ge(
function ve({ pattern: e, started: r, getTime: t, onDraw: a }) {
let o = k([]), i = k(null);
const { start: u, stop: m } = he(
N(() => {
const o = t();
if (c.current === null) {
c.current = o;
const n = t();
if (i.current === null) {
i.current = n;
return;
}
const u = e.queryArc(Math.max(c.current, o - 1 / 10), o), f = 4;
c.current = o, n.current = (n.current || []).filter((m) => m.whole.end > o - f).concat(u.filter((m) => m.hasOnset())), a(o, n.current);
const d = e.queryArc(Math.max(i.current, n - 1 / 10), n), v = 4;
i.current = n, o.current = (o.current || []).filter((h) => h.whole.end > n - v).concat(d.filter((h) => h.hasOnset())), a(n, o.current);
}, [e])
);
F(() => {
r ? l() : (n.current = [], d());
_(() => {
r ? u() : (o.current = [], m());
}, [r]);
}
function he(e) {
return F(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), N((r) => window.postMessage(r, "*"), []);
function be(e) {
return _(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), N((r) => window.postMessage(r, "*"), []);
}
function ve({
function Ee({
defaultOutput: e,
interval: r,
getTime: t,
evalOnMount: a = !1,
initialCode: n = "",
autolink: c = !1,
beforeEval: l,
afterEval: d,
onEvalError: o,
onToggle: u,
canvasId: f
initialCode: o = "",
autolink: i = !1,
beforeEval: u,
afterEval: m,
editPattern: n,
onEvalError: d,
onToggle: v,
canvasId: h
}) {
const m = V(() => be(), []);
f = f || `canvas-${m}`;
const [v, k] = _(), [M, T] = _(), [b, A] = _(n), [C, S] = _(), [z, D] = _(), [x, L] = _(!1), h = b !== C, { scheduler: g, evaluate: R, start: J, stop: O, pause: Q } = V(
() => te({
const p = K(() => we(), []);
h = h || `canvas-${p}`;
const [F, A] = w(), [P, D] = w(), [b, q] = w(o), [x, V] = w(), [I, R] = w(), [L, B] = w(!1), H = b !== x, { scheduler: M, evaluate: c, start: f, stop: C, pause: O } = K(
() => re({
interval: r,
defaultOutput: e,
onSchedulerError: k,
onEvalError: (p) => {
T(p), o?.(p);
onSchedulerError: A,
onEvalError: (g) => {
D(g), d?.(g);
},
getTime: t,
transpiler: se,
beforeEval: ({ code: p }) => {
A(p), l?.();
transpiler: ie,
beforeEval: ({ code: g }) => {
q(g), u?.();
},
afterEval: ({ pattern: p, code: q }) => {
S(q), D(p), T(), k(), c && (window.location.hash = "#" + encodeURIComponent(btoa(q))), d?.();
editPattern: n ? (g) => n(g, p) : void 0,
afterEval: ({ pattern: g, code: T }) => {
V(T), R(g), D(), A(), i && (window.location.hash = "#" + encodeURIComponent(btoa(T))), m?.();
},
onToggle: (p) => {
L(p), u?.(p);
onToggle: (g) => {
B(g), v?.(g);
}
}),
[e, r, t]
), X = he(({ data: { from: p, type: q } }) => {
q === "start" && p !== m && O();
}), P = N(
async (p = !0) => {
await R(b, p), X({ type: "start", from: m });
), Y = be(({ data: { from: g, type: T } }) => {
T === "start" && g !== p && C();
}), S = N(
async (g = !0) => {
await c(b, g), Y({ type: "start", from: p });
},
[R, b]
), K = E();
return F(() => {
!K.current && a && b && (K.current = !0, P());
}, [P, a, b]), F(() => () => {
g.stop();
}, [g]), {
id: m,
canvasId: f,
[c, b]
), U = k();
return _(() => {
!U.current && a && b && (U.current = !0, S());
}, [S, a, b]), _(() => () => {
M.stop();
}, [M]), {
id: p,
canvasId: h,
code: b,
setCode: A,
error: v || M,
schedulerError: v,
scheduler: g,
evalError: M,
evaluate: R,
activateCode: P,
activeCode: C,
isDirty: h,
pattern: z,
started: x,
start: J,
stop: O,
pause: Q,
setCode: q,
error: F || P,
schedulerError: F,
scheduler: M,
evalError: P,
evaluate: c,
activateCode: S,
activeCode: x,
isDirty: H,
pattern: I,
started: L,
start: f,
stop: C,
pause: O,
togglePlay: async () => {
x ? g.pause() : await P();
L ? M.pause() : await S();
}
};
}
function be() {
function we() {
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
}
function I({ type: e }) {
return /* @__PURE__ */ i.createElement("svg", {
function $({ 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__ */ i.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__ */ i.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__ */ i.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]);
}
const we = "_container_3i85k_1", ye = "_header_3i85k_5", Ee = "_buttons_3i85k_9", ke = "_button_3i85k_9", _e = "_buttonDisabled_3i85k_17", Fe = "_error_3i85k_21", Ce = "_body_3i85k_25", w = {
container: we,
header: ye,
buttons: Ee,
button: ke,
buttonDisabled: _e,
error: Fe,
body: Ce
}, Ne = () => ne().currentTime;
function Be({ tune: e, hideOutsideView: r = !1, enableKeyboard: t, withCanvas: a = !1, canvasHeight: n = 200 }) {
const ye = "_container_3i85k_1", ke = "_header_3i85k_5", _e = "_buttons_3i85k_9", Fe = "_button_3i85k_9", Ce = "_buttonDisabled_3i85k_17", Ne = "_error_3i85k_21", xe = "_body_3i85k_25", E = {
container: ye,
header: ke,
buttons: _e,
button: Fe,
buttonDisabled: Ce,
error: Ne,
body: xe
}, Me = () => ce().currentTime;
function je({ tune: e, hideOutsideView: r = !1, enableKeyboard: t, withCanvas: a = !1, canvasHeight: o = 200 }) {
const {
code: c,
setCode: l,
evaluate: d,
activateCode: o,
error: u,
isDirty: f,
activeCode: m,
pattern: v,
started: k,
scheduler: M,
togglePlay: T,
stop: b,
canvasId: A
} = ve({
code: i,
setCode: u,
evaluate: m,
activateCode: n,
error: d,
isDirty: v,
activeCode: h,
pattern: p,
started: F,
scheduler: A,
togglePlay: P,
stop: D,
canvasId: b,
id: q
} = Ee({
initialCode: e,
defaultOutput: oe,
getTime: Ne
defaultOutput: ae,
getTime: Me,
editPattern: (c, f) => c.withContext((C) => ({ ...C, id: f }))
});
pe({
pattern: v,
started: a && k,
getTime: () => M.now(),
onDraw: (h, g) => {
const R = document.querySelector("#" + A).getContext("2d");
re({ ctx: R, time: h, haps: g, autorange: 1, fold: 1, playhead: 1 });
ve({
pattern: p,
started: a && F,
getTime: () => A.now(),
onDraw: (c, f) => {
const C = document.querySelector("#" + b).getContext("2d");
oe({ ctx: C, time: c, haps: f, autorange: 1, fold: 1, playhead: 1 });
}
});
const [C, S] = _(), [z, D] = ae({
const [x, V] = w(), [I, R] = se({
threshold: 0.01
}), x = E(), L = V(() => ((D || !r) && (x.current = !0), D || x.current), [D, r]);
return me({
view: C,
pattern: v,
active: k && !m?.includes("strudel disable-highlighting"),
getTime: () => M.getPhase()
}), U(() => {
}), L = k(), B = K(() => ((R || !r) && (L.current = !0), R || L.current), [R, r]);
pe({
view: x,
pattern: p,
active: F && !h?.includes("strudel disable-highlighting"),
getTime: () => A.getPhase()
}), G(() => {
if (t) {
const h = async (g) => {
(g.ctrlKey || g.altKey) && (g.code === "Enter" ? (g.preventDefault(), le(C), await o()) : g.code === "Period" && (b(), g.preventDefault()));
const c = async (f) => {
(f.ctrlKey || f.altKey) && (f.code === "Enter" ? (f.preventDefault(), de(x), await n()) : f.code === "Period" && (D(), f.preventDefault()));
};
return window.addEventListener("keydown", h, !0), () => window.removeEventListener("keydown", h, !0);
return window.addEventListener("keydown", c, !0), () => window.removeEventListener("keydown", c, !0);
}
}, [t, v, c, d, b, C]), /* @__PURE__ */ i.createElement("div", {
className: w.container,
ref: z
}, /* @__PURE__ */ i.createElement("div", {
className: w.header
}, /* @__PURE__ */ i.createElement("div", {
className: w.buttons
}, /* @__PURE__ */ i.createElement("button", {
className: j(w.button, k ? "sc-animate-pulse" : ""),
onClick: () => T()
}, /* @__PURE__ */ i.createElement(I, {
type: k ? "pause" : "play"
})), /* @__PURE__ */ i.createElement("button", {
className: j(f ? w.button : w.buttonDisabled),
onClick: () => o()
}, /* @__PURE__ */ i.createElement(I, {
}, [t, p, i, m, D, x]);
const [H, M] = w([]);
return Ae(
N((c) => {
const { data: f } = c.detail;
f?.hap?.context?.id === q && M((O) => O.concat([c.detail]).slice(-10));
}, [])
), /* @__PURE__ */ l.createElement("div", {
className: E.container,
ref: I
}, /* @__PURE__ */ l.createElement("div", {
className: E.header
}, /* @__PURE__ */ l.createElement("div", {
className: E.buttons
}, /* @__PURE__ */ l.createElement("button", {
className: W(E.button, F ? "sc-animate-pulse" : ""),
onClick: () => P()
}, /* @__PURE__ */ l.createElement($, {
type: F ? "pause" : "play"
})), /* @__PURE__ */ l.createElement("button", {
className: W(v ? E.button : E.buttonDisabled),
onClick: () => n()
}, /* @__PURE__ */ l.createElement($, {
type: "refresh"
}))), u && /* @__PURE__ */ i.createElement("div", {
className: w.error
}, u.message)), /* @__PURE__ */ i.createElement("div", {
className: w.body
}, L && /* @__PURE__ */ i.createElement(fe, {
value: c,
onChange: l,
onViewChanged: S
})), a && /* @__PURE__ */ i.createElement("canvas", {
id: A,
}))), d && /* @__PURE__ */ l.createElement("div", {
className: E.error
}, d.message)), /* @__PURE__ */ l.createElement("div", {
className: E.body
}, B && /* @__PURE__ */ l.createElement(ge, {
value: i,
onChange: u,
onViewChanged: V
})), a && /* @__PURE__ */ l.createElement("canvas", {
id: b,
className: "w-full pointer-events-none",
height: n,
ref: (h) => {
h && h.width !== h.clientWidth && (h.width = h.clientWidth);
height: o,
ref: (c) => {
c && c.width !== c.clientWidth && (c.width = c.clientWidth);
}
}));
}), !!H.length && /* @__PURE__ */ l.createElement("div", {
className: "sc-bg-gray-800 sc-rounded-md sc-p-2"
}, H.map(({ message: c }, f) => /* @__PURE__ */ l.createElement("div", {
key: f
}, c))));
}
const Oe = (e) => U(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
function Ae(e) {
De(ne.key, e);
}
function De(e, r, t = !1) {
_(() => (document.addEventListener(e, r, t), () => {
document.removeEventListener(e, r, t);
}), [r]);
}
const Ue = (e) => G(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
export {
fe as CodeMirror,
Be as MiniRepl,
j as cx,
le as flash,
me as useHighlighting,
Oe as useKeydown,
he as usePostMessage,
ve as useStrudel
ge as CodeMirror,
je as MiniRepl,
W as cx,
de as flash,
pe as useHighlighting,
Ue as useKeydown,
be as usePostMessage,
Ee as useStrudel
};

View File

@ -1 +1 @@
.cm-editor{background-color:transparent!important;height:100%;z-index:11;font-size:18px}.cm-theme-light{width:100%}.cm-line>*{background:#00000095}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sc-h-5{height:1.25rem}.sc-w-5{width:1.25rem}@keyframes sc-pulse{50%{opacity:.5}}.sc-animate-pulse{animation:sc-pulse 2s cubic-bezier(.4,0,.6,1) infinite}._container_3i85k_1{overflow:hidden;border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(34 34 34 / var(--tw-bg-opacity))}._header_3i85k_5{display:flex;justify-content:space-between;border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}._buttons_3i85k_9{display:flex}._button_3i85k_9{display:flex;width:4rem;cursor:pointer;align-items:center;justify-content:center;border-right-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}._button_3i85k_9:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}._buttonDisabled_3i85k_17{display:flex;width:4rem;cursor:pointer;cursor:not-allowed;align-items:center;justify-content:center;--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}._error_3i85k_21{padding:.25rem;text-align:right;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}._body_3i85k_25{position:relative;overflow:auto}
.cm-editor{background-color:transparent!important;height:100%;z-index:11;font-size:18px}.cm-theme-light{width:100%}.cm-line>*{background:#00000095}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sc-h-5{height:1.25rem}.sc-w-5{width:1.25rem}@keyframes sc-pulse{50%{opacity:.5}}.sc-animate-pulse{animation:sc-pulse 2s cubic-bezier(.4,0,.6,1) infinite}.sc-rounded-md{border-radius:.375rem}.sc-bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity))}.sc-p-2{padding:.5rem}._container_3i85k_1{overflow:hidden;border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(34 34 34 / var(--tw-bg-opacity))}._header_3i85k_5{display:flex;justify-content:space-between;border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}._buttons_3i85k_9{display:flex}._button_3i85k_9{display:flex;width:4rem;cursor:pointer;align-items:center;justify-content:center;border-right-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}._button_3i85k_9:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}._buttonDisabled_3i85k_17{display:flex;width:4rem;cursor:pointer;cursor:not-allowed;align-items:center;justify-content:center;--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}._error_3i85k_21{padding:.25rem;text-align:right;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}._body_3i85k_25{position:relative;overflow:auto}

View File

@ -1,6 +1,6 @@
import { pianoroll } from '@strudel.cycles/core';
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
import React, { useLayoutEffect, useMemo, useRef, useState } from 'react';
import React, { useLayoutEffect, useMemo, useRef, useState, useCallback, useEffect } from 'react';
import { useInView } from 'react-hook-inview';
import 'tailwindcss/tailwind.css';
import cx from '../cx';
@ -11,6 +11,7 @@ import CodeMirror6, { flash } from './CodeMirror6';
import { Icon } from './Icon';
import styles from './MiniRepl.module.css';
import './style.css';
import { logger } from '@strudel.cycles/core';
const getTime = () => getAudioContext().currentTime;
@ -29,10 +30,14 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa
togglePlay,
stop,
canvasId,
id: replId,
} = useStrudel({
initialCode: tune,
defaultOutput: webaudioOutput,
getTime,
editPattern: (pat, id) => {
return pat.withContext((ctx) => ({ ...ctx, id }));
},
});
usePatternFrame({
@ -86,6 +91,20 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa
}
}, [enableKeyboard, pattern, code, evaluate, stop, view]);
const [log, setLog] = useState([]);
useLogger(
useCallback((e) => {
const { data } = e.detail;
const logId = data?.hap?.context?.id;
// const logId = data?.pattern?.meta?.id;
if (logId === replId) {
setLog((l) => {
return l.concat([e.detail]).slice(-10);
});
}
}, []),
);
return (
<div className={styles.container} ref={ref}>
<div className={styles.header}>
@ -114,6 +133,28 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa
}}
></canvas>
)}
{!!log.length && (
<div className="sc-bg-gray-800 sc-rounded-md sc-p-2">
{log.map(({ message }, i) => (
<div key={i}>{message}</div>
))}
</div>
)}
</div>
);
}
// TODO: dedupe
function useLogger(onTrigger) {
useEvent(logger.key, onTrigger);
}
// TODO: dedupe
function useEvent(name, onTrigger, useCapture = false) {
useEffect(() => {
document.addEventListener(name, onTrigger, useCapture);
return () => {
document.removeEventListener(name, onTrigger, useCapture);
};
}, [onTrigger]);
}

View File

@ -12,6 +12,7 @@ function useStrudel({
autolink = false,
beforeEval,
afterEval,
editPattern,
onEvalError,
onToggle,
canvasId,
@ -44,6 +45,7 @@ function useStrudel({
setCode(code);
beforeEval?.();
},
editPattern: editPattern ? (pat) => editPattern(pat, id) : undefined,
afterEval: ({ pattern: _pattern, code }) => {
setActiveCode(code);
setPattern(_pattern);

View File

@ -195,9 +195,14 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => {
hap.value = { note: hap.value };
} */
if (typeof hap.value !== 'object') {
throw new Error(
`hap.value ${hap.value} is not supported by webaudio output. Hint: append .note() or .s() to the end`,
logger(
`hap.value "${hap.value}" is not supported by webaudio output. Hint: append .note() or .s() to the end`,
'error',
);
/* throw new Error(
`hap.value "${hap.value}"" is not supported by webaudio output. Hint: append .note() or .s() to the end`,
); */
return;
}
// calculate correct time (tone.js workaround)
let t = ac.currentTime + deadline;