Merge pull request #360 from tidalcycles/docs

doc structuring
This commit is contained in:
Felix Roos 2023-01-09 23:37:34 +01:00 committed by GitHub
commit e8b09f1124
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 928 additions and 545 deletions

View File

@ -73,7 +73,7 @@ const bjork = function (ons, steps) {
*/
/**
* Like `iter`, but has an additional parameter for 'rotating' the resulting sequence.
* Like `euclid`, but has an additional parameter for 'rotating' the resulting sequence.
* @memberof Pattern
* @name euclidRot
* @param {number} pulses the number of onsets / beats

View File

@ -712,7 +712,7 @@ export class Pattern {
* @memberof Pattern
* @example
* s("hh*2").stack(
* n("c2(3,8)")
* note("c2(3,8)")
* )
*/
stack(...pats) {
@ -729,7 +729,7 @@ export class Pattern {
* @memberof Pattern
* @example
* s("hh*2").seq(
* n("c2(3,8)")
* note("c2(3,8)")
* )
*/
seq(...pats) {
@ -742,7 +742,7 @@ export class Pattern {
* @memberof Pattern
* @example
* s("hh*2").cat(
* n("c2(3,8)")
* note("c2(3,8)")
* )
*/
cat(...pats) {
@ -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.
@ -1215,7 +1222,17 @@ function _sequenceCount(x) {
}
return [reify(x), 1];
}
/**
* Aligns one or more given sequences to the given number of steps per cycle.
*
* @name polymeterSteps
* @param {number} steps how many items are placed in one cycle
* @param {any[]} sequences one or more arrays of Patterns / values
* @example
* polymeterSteps(2, ["c", "d", "e", "f", "g", "f", "e", "d"])
* .note().stack(s("bd")) // 1 cycle = 1 bd = 2 notes
* // note("{c d e f g f e d}%2").stack(s("bd"))
*/
export function polymeterSteps(steps, ...args) {
const seqs = args.map((a) => _sequenceCount(a));
if (seqs.length == 0) {
@ -1238,6 +1255,14 @@ export function polymeterSteps(steps, ...args) {
return stack(...pats);
}
/**
* Combines the given lists of patterns with the same pulse. This will create so called polymeters when different sized sequences are used.
* @name polymeter
* @example
* polymeter(["c", "eb", "g"], ["c2", "g2"]).note()
* // "{c eb g, c2 g2}".note()
*
*/
export function polymeter(...args) {
return polymeterSteps(0, ...args);
}
@ -1354,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));
@ -1364,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;

View File

@ -899,14 +899,14 @@ exports[`runs examples > example "cat" example index 0 1`] = `
[
"[ 0/1 → 1/2 | s:hh ]",
"[ 1/2 → 1/1 | s:hh ]",
"[ 1/1 → 9/8 | n:c2 ]",
"[ 11/8 → 3/2 | n:c2 ]",
"[ 7/4 → 15/8 | n:c2 ]",
"[ 1/1 → 9/8 | note:c2 ]",
"[ 11/8 → 3/2 | note:c2 ]",
"[ 7/4 → 15/8 | note:c2 ]",
"[ 2/1 → 5/2 | s:hh ]",
"[ 5/2 → 3/1 | s:hh ]",
"[ 3/1 → 25/8 | n:c2 ]",
"[ 27/8 → 7/2 | n:c2 ]",
"[ 15/4 → 31/8 | n:c2 ]",
"[ 3/1 → 25/8 | note:c2 ]",
"[ 27/8 → 7/2 | note:c2 ]",
"[ 15/4 → 31/8 | note:c2 ]",
]
`;
@ -920,6 +920,27 @@ exports[`runs examples > example "cat" example index 0 2`] = `
]
`;
exports[`runs examples > example "ceil" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:42 ]",
"[ 1/4 → 1/2 | note:43 ]",
"[ 1/2 → 3/4 | note:43 ]",
"[ 3/4 → 1/1 | note:43 ]",
"[ 1/1 → 5/4 | note:42 ]",
"[ 5/4 → 3/2 | note:43 ]",
"[ 3/2 → 7/4 | note:43 ]",
"[ 7/4 → 2/1 | note:43 ]",
"[ 2/1 → 9/4 | note:42 ]",
"[ 9/4 → 5/2 | note:43 ]",
"[ 5/2 → 11/4 | note:43 ]",
"[ 11/4 → 3/1 | note:43 ]",
"[ 3/1 → 13/4 | note:42 ]",
"[ 13/4 → 7/2 | note:43 ]",
"[ 7/2 → 15/4 | note:43 ]",
"[ 15/4 → 4/1 | note:43 ]",
]
`;
exports[`runs examples > example "chooseCycles" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:bd ]",
@ -1680,6 +1701,27 @@ exports[`runs examples > example "firstOf" example index 0 1`] = `
]
`;
exports[`runs examples > example "floor" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:42 ]",
"[ 1/4 → 1/2 | note:42 ]",
"[ 1/2 → 3/4 | note:42 ]",
"[ 3/4 → 1/1 | note:43 ]",
"[ 1/1 → 5/4 | note:42 ]",
"[ 5/4 → 3/2 | note:42 ]",
"[ 3/2 → 7/4 | note:42 ]",
"[ 7/4 → 2/1 | note:43 ]",
"[ 2/1 → 9/4 | note:42 ]",
"[ 9/4 → 5/2 | note:42 ]",
"[ 5/2 → 11/4 | note:42 ]",
"[ 11/4 → 3/1 | note:43 ]",
"[ 3/1 → 13/4 | note:42 ]",
"[ 13/4 → 7/2 | note:42 ]",
"[ 7/2 → 15/4 | note:42 ]",
"[ 15/4 → 4/1 | note:43 ]",
]
`;
exports[`runs examples > example "freq" example index 0 1`] = `
[
"[ 0/1 → 1/4 | freq:220 s:superzow ]",
@ -2312,6 +2354,52 @@ exports[`runs examples > example "perlin" example index 0 1`] = `
]
`;
exports[`runs examples > example "polymeter" example index 0 1`] = `
[
"[ 0/1 → 1/3 | note:c ]",
"[ 1/3 → 2/3 | note:eb ]",
"[ 2/3 → 1/1 | note:g ]",
"[ 1/1 → 4/3 | note:c ]",
"[ 4/3 → 5/3 | note:eb ]",
"[ 5/3 → 2/1 | note:g ]",
"[ 2/1 → 7/3 | note:c ]",
"[ 7/3 → 8/3 | note:eb ]",
"[ 8/3 → 3/1 | note:g ]",
"[ 3/1 → 10/3 | note:c ]",
"[ 10/3 → 11/3 | note:eb ]",
"[ 11/3 → 4/1 | note:g ]",
"[ 0/1 → 1/3 | note:c2 ]",
"[ 1/3 → 2/3 | note:g2 ]",
"[ 2/3 → 1/1 | note:c2 ]",
"[ 1/1 → 4/3 | note:g2 ]",
"[ 4/3 → 5/3 | note:c2 ]",
"[ 5/3 → 2/1 | note:g2 ]",
"[ 2/1 → 7/3 | note:c2 ]",
"[ 7/3 → 8/3 | note:g2 ]",
"[ 8/3 → 3/1 | note:c2 ]",
"[ 3/1 → 10/3 | note:g2 ]",
"[ 10/3 → 11/3 | note:c2 ]",
"[ 11/3 → 4/1 | note:g2 ]",
]
`;
exports[`runs examples > example "polymeterSteps" example index 0 1`] = `
[
"[ 0/1 → 1/2 | note:c ]",
"[ 1/2 → 1/1 | note:d ]",
"[ 1/1 → 3/2 | note:e ]",
"[ 3/2 → 2/1 | note:f ]",
"[ 2/1 → 5/2 | note:g ]",
"[ 5/2 → 3/1 | note:f ]",
"[ 3/1 → 7/2 | note:e ]",
"[ 7/2 → 4/1 | note:d ]",
"[ 0/1 → 1/1 | s:bd ]",
"[ 1/1 → 2/1 | s:bd ]",
"[ 2/1 → 3/1 | s:bd ]",
"[ 3/1 → 4/1 | s:bd ]",
]
`;
exports[`runs examples > example "pure" example index 0 1`] = `
[
"[ 0/1 → 1/1 | e4 ]",
@ -2662,24 +2750,24 @@ exports[`runs examples > example "seq" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:hh ]",
"[ 1/4 → 1/2 | s:hh ]",
"[ 1/2 → 9/16 | n:c2 ]",
"[ 11/16 → 3/4 | n:c2 ]",
"[ 7/8 → 15/16 | n:c2 ]",
"[ 1/2 → 9/16 | note:c2 ]",
"[ 11/16 → 3/4 | note:c2 ]",
"[ 7/8 → 15/16 | note:c2 ]",
"[ 1/1 → 5/4 | s:hh ]",
"[ 5/4 → 3/2 | s:hh ]",
"[ 3/2 → 25/16 | n:c2 ]",
"[ 27/16 → 7/4 | n:c2 ]",
"[ 15/8 → 31/16 | n:c2 ]",
"[ 3/2 → 25/16 | note:c2 ]",
"[ 27/16 → 7/4 | note:c2 ]",
"[ 15/8 → 31/16 | note:c2 ]",
"[ 2/1 → 9/4 | s:hh ]",
"[ 9/4 → 5/2 | s:hh ]",
"[ 5/2 → 41/16 | n:c2 ]",
"[ 43/16 → 11/4 | n:c2 ]",
"[ 23/8 → 47/16 | n:c2 ]",
"[ 5/2 → 41/16 | note:c2 ]",
"[ 43/16 → 11/4 | note:c2 ]",
"[ 23/8 → 47/16 | note:c2 ]",
"[ 3/1 → 13/4 | s:hh ]",
"[ 13/4 → 7/2 | s:hh ]",
"[ 7/2 → 57/16 | n:c2 ]",
"[ 59/16 → 15/4 | n:c2 ]",
"[ 31/8 → 63/16 | n:c2 ]",
"[ 7/2 → 57/16 | note:c2 ]",
"[ 59/16 → 15/4 | note:c2 ]",
"[ 31/8 → 63/16 | note:c2 ]",
]
`;
@ -2733,6 +2821,8 @@ exports[`runs examples > example "shape" example index 0 1`] = `
]
`;
exports[`runs examples > example "silence" example index 0 1`] = `[]`;
exports[`runs examples > example "sine" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:Eb4 ]",
@ -2956,18 +3046,18 @@ exports[`runs examples > example "stack" example index 0 1`] = `
"[ 5/2 → 3/1 | s:hh ]",
"[ 3/1 → 7/2 | s:hh ]",
"[ 7/2 → 4/1 | s:hh ]",
"[ 0/1 → 1/8 | n:c2 ]",
"[ 3/8 → 1/2 | n:c2 ]",
"[ 3/4 → 7/8 | n:c2 ]",
"[ 1/1 → 9/8 | n:c2 ]",
"[ 11/8 → 3/2 | n:c2 ]",
"[ 7/4 → 15/8 | n:c2 ]",
"[ 2/1 → 17/8 | n:c2 ]",
"[ 19/8 → 5/2 | n:c2 ]",
"[ 11/4 → 23/8 | n:c2 ]",
"[ 3/1 → 25/8 | n:c2 ]",
"[ 27/8 → 7/2 | n:c2 ]",
"[ 15/4 → 31/8 | n:c2 ]",
"[ 0/1 → 1/8 | note:c2 ]",
"[ 3/8 → 1/2 | note:c2 ]",
"[ 3/4 → 7/8 | note:c2 ]",
"[ 1/1 → 9/8 | note:c2 ]",
"[ 11/8 → 3/2 | note:c2 ]",
"[ 7/4 → 15/8 | note:c2 ]",
"[ 2/1 → 17/8 | note:c2 ]",
"[ 19/8 → 5/2 | note:c2 ]",
"[ 11/4 → 23/8 | note:c2 ]",
"[ 3/1 → 25/8 | note:c2 ]",
"[ 27/8 → 7/2 | note:c2 ]",
"[ 15/4 → 31/8 | note:c2 ]",
]
`;

View File

@ -49,19 +49,29 @@ export const SIDEBAR: Sidebar = {
{ text: 'Sounds', link: 'learn/sounds' },
{ text: 'Coding syntax', link: 'learn/code' },
{ text: 'Mini-Notation', link: 'learn/mini-notation' },
],
'Making Sound': [
{ text: 'Samples', link: 'learn/samples' },
{ text: 'Synths', link: 'learn/synths' },
{ text: 'Audio Effects', link: 'learn/effects' },
{ text: 'Functions', link: 'learn/functions' },
],
'Pattern Functions': [
{ text: 'Introduction', link: 'functions/intro' },
{ text: 'Pattern Constructors', link: 'learn/factories' },
{ text: 'Time Modifiers', link: 'learn/time-modifiers' },
{ text: 'Control Parameters', link: 'functions/value-modifiers' },
{ text: 'Signals', link: 'learn/signals' },
{ text: 'Tonal', link: 'learn/tonal' },
{ text: 'Conditional Modifiers', link: 'learn/conditional-modifiers' },
{ text: 'Tonal Modifiers', link: 'learn/tonal' },
],
More: [
{ text: 'Patterns', link: 'technical-manual/patterns' },
{ text: 'Pattern Alignment', link: 'technical-manual/alignment' },
{ text: 'MIDI & OSC', link: 'learn/input-output' },
{ text: 'Strudel vs Tidal', link: 'learn/strudel-vs-tidal' },
],
'Technical Manual': [
{ text: 'Patterns', link: 'technical-manual/patterns' },
Development: [
{ text: 'REPL', link: 'technical-manual/repl' },
{ text: 'Pattern Alignment', link: 'technical-manual/alignment' },
{ text: 'Docs', link: 'technical-manual/docs' },
{ text: 'Testing', link: 'technical-manual/testing' },
],

View File

@ -2,7 +2,7 @@ import jsdoc from '../../../doc.json'; // doc.json is built with `npm run jsdoc-
const docs = jsdoc.docs.reduce((acc, obj) => Object.assign(acc, { [obj.longname]: obj }), {});
import { MiniRepl } from './MiniRepl';
export function JsDoc({ name, h = 3 }) {
export function JsDoc({ name, h = 3, hideDescription }) {
const item = docs[name];
if (!item) {
console.warn('Not found: ' + name);
@ -16,7 +16,7 @@ export function JsDoc({ name, h = 3 }) {
return (
<>
{!!h && <CustomHeading>{item.longname}</CustomHeading>}
<div dangerouslySetInnerHTML={{ __html: description }} />
{!hideDescription && <div dangerouslySetInnerHTML={{ __html: description }} />}
<ul>
{item.params?.map((param, i) => (
<li key={i}>

View File

@ -0,0 +1,73 @@
---
title: Introduction
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Functional JavaScript API
While the mini notation is powerful on its own, there is much more to discover.
Internally, the mini notation will expand to use the actual functional JavaScript API.
For example, this Pattern in Mini Notation:
<MiniRepl client:only="react" tune={`note("c3 eb3 g3")`} />
is equivalent to this Pattern without Mini Notation:
<MiniRepl client:only="react" tune={`note(seq(c3, eb3, g3))`} />
Similarly, there is an equivalent function for every aspect of the mini notation.
Which representation to use is a matter of context. As a rule of thumb, you can think of the JavaScript API
to fit better for the larger context, while mini notation is more practical for individiual rhythms.
## Limits of Mini Notation
While the Mini Notation is a powerful way to write rhythms shortly, it also has its limits. Take this example:
<MiniRepl
client:idle
tune={`stack(
note("c2 eb2(3,8)").s('sawtooth').cutoff(800),
s("bd,~ sd,hh*4")
)`}
/>
Here, we are using mini notation for the individual rhythms, while using the function `stack` to mix them.
While stack is also available as `,` in mini notation, we cannot use it here, because we have different types of sounds.
## Combining Patterns
You can freely mix JS patterns, mini patterns and values! For example, this pattern:
<MiniRepl
client:idle
tune={`cat(
stack(g3,b3,e4),
stack(a3,c3,e4),
stack(b3,d3,fs4),
stack(b3,e4,g4)
).note()`}
/>
...is equivalent to:
<MiniRepl
client:idle
tune={`cat(
"g3,b3,e4",
"a3,c3,e4",
"b3,d3,f#4",
"b3,e4,g4"
).note()`}
/>
... as well as:
<MiniRepl client:only="react" tune={`note("<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4] [b3,e4,g4]>")`} />
While mini notation is almost always shorter, it only has a handful of modifiers: \* / ! @.
When using JS patterns, there is a lot more you can do.

View File

@ -0,0 +1,154 @@
---
title: Control Parameters
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Control Parameters
Besides functions that control time, we saw earlier that functions like `note` and `cutoff` control different parameters (short params) of an event.
Let's now look more closely at how these `param(eter) functions` work.
# Parameter Functions
A very powerful feature of tidal patterns is that each parameter can be controlled independently:
<MiniRepl
client:only="react"
tune={`note("c a f e")
.cutoff("<500 1000 2000 [4000 8000]>")
.gain(.8)
.s('sawtooth')
.log()`}
/>
In this example, the parameters `note`, `cutoff`, `gain` and `s` are controlled independently by either patterns or plain values (numbers / text).
After pressing play, we can observe the time and parameter values of each event (hap) in the output created by `.log()`.
## Plain vs Parameterized Values
Patterns that are not wrapped inside a param function will contain unlabeled `plain values`:
<MiniRepl client:only="react" tune={`"<c e g>".log()`} />
This will not generate any sound output, because Strudel could only guess which param is meant by these letters.
Now compare that to the version wrapped in `note`:
<MiniRepl client:only="react" tune={`note("<c e g>").log()`} />
Now it is clear that these letters are meant to be played as notes.
Under the hood, the `note` function (as well as all other param functions)
will wrap each plain value in an object. If the note function did not exist, we would need to write:
<MiniRepl client:only="react" tune={`cat({note:'c'},{note:'e'},{note:'g'}).log()`} />
This will have the same output, though it is rather unwieldy to read and write.
## Wrapping Parameter Functions
To avoid too much nesting, param functions can also be chained like this:
<MiniRepl client:only="react" tune={`cat('c', 'e', 'g').note().log()`} />
This is equivalent to `note(cat('c','e','g')).log()`.
You can use this with any function that declares a type (like `n`, `s`, `note`, `freq` etc), just make sure to leave the parens empty!
## Plain Value Modification
Patterns of plain values can be modified with any of the following operators:
<MiniRepl client:only="react" tune={`"50 60 70".add("<0 1 2>").log()`} />
Here, the add function modifies the numbers on the left.
Again, there is no output because these numbers have no meaning without a param.
## Param Value Modification
To modify a parameter value, you can either:
- Use the operator on the plain value pattern, inside the param function:
<MiniRepl client:only="react" tune={`note("50 60 70".add("<0 1 2>")).room(.1).log()`} />
- Similarly, use the operator on the plain value pattern and wrap it later:
<MiniRepl client:only="react" tune={`"50 60 70".add("<0 1 2>").note().room(.1).log()`} />
- Specify which param should be modified inside the operator function:
<MiniRepl client:only="react" tune={`note("50 60 70").room(.1).add(note("<0 1 2>")).log()`} />
- Modify _all_ numeral params:
<MiniRepl client:only="react" tune={`note("50 60 70").room(.1).add("<0 1 2>").log()`} />
Which of these 3 ways to use strongly depends on the context!
Note that the order of chaining param functions also matters!
In the last example, the `room` value would not have changed if it was applied later:
<MiniRepl client:only="react" tune={`note("50 60 70").add("<0 1 2>").room(.1).log()`} />
This shows how the execution of the chained functions goes from left to right.
In this case, the `.add` will only modify what's on the left side.
# Operators
This group of functions allows to modify the value of events.
## add
<JsDoc client:idle name="Pattern.add" h={0} />
## sub
<JsDoc client:idle name="Pattern.sub" h={0} />
## mul
<JsDoc client:idle name="Pattern.mul" h={0} />
## div
<JsDoc client:idle name="Pattern.div" h={0} />
## round
<JsDoc client:idle name="Pattern.round" h={0} />
## floor
<JsDoc client:idle name="Pattern.floor" h={0} />
## ceil
<JsDoc client:idle name="Pattern.ceil" h={0} />
## range
<JsDoc client:idle name="Pattern.range" h={0} />
# Custom Parameters
You can also create your own parameters:
<MiniRepl
client:only="react"
tune={`let x = createParam('x')
x(sine.range(0, 200))
`}
/>
Multiple params can also be created in a more consice way, using `createParams`:
<MiniRepl
client:only="react"
tune={`let { x, y } = createParams('x', 'y');
x(sine.range(0, 200)).y(cosine.range(0, 200));
`}
/>
Note that these params will not do anything until you give them meaning in your custom output!

View File

@ -0,0 +1,43 @@
---
title: Conditional Modifiers
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Conditional Modifiers
## every
<JsDoc client:idle name="Pattern.every" h={0} />
## when
<JsDoc client:idle name="Pattern.when" h={0} />
# Accumulation Modifiers
## stack
<JsDoc client:idle name="Pattern.stack" h={0} />
## superimpose
<JsDoc client:idle name="Pattern.superimpose" h={0} />
## layer
<JsDoc client:idle name="Pattern.layer" h={0} />
## off
<JsDoc client:idle name="Pattern.off" h={0} />
## echo
<JsDoc client:idle name="Pattern.echo" h={0} />
## echoWith
<JsDoc client:idle name="Pattern.echoWith" h={0} />

View File

@ -0,0 +1,63 @@
---
title: Pattern Constructors
description: Strudel Tutorial
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Pattern Constructors
The following functions will return a pattern.
These are the equivalents used by the Mini Notation:
| function | mini |
| ------------------------------ | ---------------- |
| `cat(x, y)` | `"<x y>"` |
| `seq(x, y)` | `"x y"` |
| `stack(x, y)` | `"x,y"` |
| `timeCat([3,x],[2,y])` | `"x@2 y@2"` |
| `polymeter([a, b, c], [x, y])` | `"{a b c, x y}"` |
| `polymeterSteps(2, x, y, z)` | `"{x y z}%2"` |
| `silence` | `"~"` |
## cat
<JsDoc client:idle name="cat" h={0} />
You can also use cat as a chained function like this:
<JsDoc client:idle name="Pattern.cat" h={0} hideDescription />
## seq
<JsDoc client:idle name="seq" h={0} />
Or as a chained function:
<JsDoc client:idle name="Pattern.seq" h={0} hideDescription />
## stack
<JsDoc client:idle name="stack" h={0} />
As a chained function:
<JsDoc client:idle name="Pattern.stack" h={0} hideDescription />
## timeCat
<JsDoc client:idle name="timeCat" h={0} />
## polymeter
<JsDoc client:idle name="polymeter" h={0} />
## polymeterSteps
<JsDoc client:idle name="polymeterSteps" h={0} />
## silence
<JsDoc client:idle name="silence" h={0} />

View File

@ -1,250 +0,0 @@
---
title: What is Strudel?
description: Strudel Tutorial
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Functional JavaScript API
While the mini notation is powerful on its own, there is much more to discover.
Internally, the mini notation will expand to use the actual functional JavaScript API.
For example, this Pattern in Mini Notation:
<MiniRepl client:only="react" tune={`note("c3 eb3 g3")`} />
is equivalent to this Pattern without Mini Notation:
<MiniRepl client:only="react" tune={`note(seq(c3, eb3, g3))`} />
Similarly, there is an equivalent function for every aspect of the mini notation.
Which representation to use is a matter of context. As a rule of thumb, you can think of the JavaScript API
to fit better for the larger context, while mini notation is more practical for individiual rhythms.
## Limits of Mini Notation
While the Mini Notation is a powerful way to write rhythms shortly, it also has its limits. Take this example:
<MiniRepl
client:idle
tune={`stack(
note("c2 eb2(3,8)").s('sawtooth').cutoff(800),
s("bd,~ sd,hh*4")
)`}
/>
Here, we are using mini notation for the individual rhythms, while using the function `stack` to mix them.
While stack is also available as `,` in mini notation, we cannot use it here, because we have different types of sounds.
## Notes
Notes are automatically available as variables:
<MiniRepl client:only="react" tune={`note(seq(d4, fs4, a4)) // note("d4 f#4 a4")`} />
An important difference to the mini notation:
For sharp notes, the letter "s" is used instead of "#", because JavaScript does not support "#" in a variable name.
The above is the same as:
<MiniRepl client:only="react" tune={`note(seq('d4', 'f#4', 'a4'))`} />
Using strings, you can also use "#".
## Alternative Syntax
In the above example, we are nesting a function inside a function, which makes reading the parens a little more difficult.
To avoid getting to many nested parens, there is an alternative syntax to add a type to a pattern:
<MiniRepl client:only="react" tune={`seq(d4, fs4, a4).note()`} />
You can use this with any function that declares a type (like `n`, `s`, `note`, `freq` etc), just make sure to leave the parens empty!
## Pattern Factories
The following functions will return a pattern.
### cat
<JsDoc client:idle name="cat" h={0} />
### seq
<JsDoc client:idle name="seq" h={0} />
### stack
<JsDoc client:idle name="stack" h={0} />
### timeCat
<JsDoc client:idle name="timeCat" h={0} />
## Combining Patterns
You can freely mix JS patterns, mini patterns and values! For example, this pattern:
<MiniRepl
client:idle
tune={`cat(
stack(g3,b3,e4),
stack(a3,c3,e4),
stack(b3,d3,fs4),
stack(b3,e4,g4)
).note()`}
/>
...is equivalent to:
<MiniRepl
client:idle
tune={`cat(
"g3,b3,e4",
"a3,c3,e4",
"b3,d3,f#4",
"b3,e4,g4"
).note()`}
/>
... as well as:
<MiniRepl client:only="react" tune={`note("<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4] [b3,e4,g4]>")`} />
While mini notation is almost always shorter, it only has a handful of modifiers: \* / ! @.
When using JS patterns, there is a lot more you can do.
## Time Modifiers
The following functions modify a pattern temporal structure in some way.
### Pattern.slow
<JsDoc client:idle name="Pattern.slow" h={0} />
### Pattern.fast
<JsDoc client:idle name="Pattern.fast" h={0} />
### Pattern.early
<JsDoc client:idle name="Pattern.early" h={0} />
### Pattern.late
<JsDoc client:idle name="Pattern.late" h={0} />
### Pattern.legato
<JsDoc client:idle name="Pattern.legato" h={0} />
### Pattern.struct
<JsDoc client:idle name="Pattern.struct" h={0} />
### Pattern.euclid
<JsDoc client:idle name="Pattern.euclid" h={0} />
### Pattern.euclidLegato
<JsDoc client:idle name="Pattern.euclidLegato" h={0} />
### Pattern.rev
<JsDoc client:idle name="Pattern.rev" h={0} />
### Pattern.iter
<JsDoc client:idle name="Pattern.iter" h={0} />
### Pattern.iterBack
<JsDoc client:idle name="Pattern.iterBack" h={0} />
## Conditional Modifiers
### Pattern.every
<JsDoc client:idle name="Pattern.every" h={0} />
### Pattern.when
<JsDoc client:idle name="Pattern.when" h={0} />
## Accumulation Modifiers
### Pattern.stack
<JsDoc client:idle name="Pattern.stack" h={0} />
### Pattern.superimpose
<JsDoc client:idle name="Pattern.superimpose" h={0} />
### Pattern.layer
<JsDoc client:idle name="Pattern.layer" h={0} />
### Pattern.off
<JsDoc client:idle name="Pattern.off" h={0} />
### Pattern.echo
<JsDoc client:idle name="Pattern.echo" h={0} />
### Pattern.echoWith
<JsDoc client:idle name="Pattern.echoWith" h={0} />
## Concat Modifiers
### Pattern.seq
<JsDoc client:idle name="Pattern.seq" h={0} />
### Pattern.cat
<JsDoc client:idle name="Pattern.cat" h={0} />
## Value Modifiers
### Pattern.add
<JsDoc client:idle name="Pattern.add" h={0} />
### Pattern.sub
<JsDoc client:idle name="Pattern.sub" h={0} />
### Pattern.mul
<JsDoc client:idle name="Pattern.mul" h={0} />
### Pattern.div
<JsDoc client:idle name="Pattern.div" h={0} />
### Pattern.round
<JsDoc client:idle name="Pattern.round" h={0} />
### Pattern.apply
<JsDoc client:idle name="Pattern.apply" h={0} />
### Pattern.range
<JsDoc client:idle name="Pattern.range" h={0} />
### Pattern.chunk
<JsDoc client:idle name="Pattern.chunk" h={0} />
### Pattern.chunkBack
<JsDoc client:idle name="Pattern.chunkBack" h={0} />

View File

@ -10,16 +10,16 @@ import { JsDoc } from '../../docs/JsDoc';
# Notes
Pitches are an essential building block for music.
In Strudel, there are three different ways to express a pitch, `note`, `n` and `freq`.
In Strudel, pitches can be expressed as note names, note numbers or frequencies.
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")`} />
- `n`: number notation, good for those who want to use recognisable pitches, but don't care about music theory:
- `note`: number notation, good for those who want to use recognisable pitches, but don't care about music theory:
<MiniRepl client:idle tune={`n("57 61 64 69")`} />
<MiniRepl client:idle tune={`note("57 61 64 69")`} />
- `freq`: frequency notation, good for those who want to go beyond standardised tuning systems:
@ -27,28 +27,28 @@ Here's the same pattern written in three different ways:
Let's look at `note`, `n` and `freq` in more detail...
# `note`
## `note` names
Notes are notated with the note letter, followed by the octave number. You can notate flats with `b` and sharps with `#`.
Notes names can be notated with the note letter, followed by the octave number. You can notate flats with `b` and sharps with `#`.
<MiniRepl client:idle tune={`note("a3 c#4 e4 a4")`} />
By the way, you can edit the contents of the player, and press "update" to hear your change!
You can also press "play" on the next player without needing to stop the last one.
# `n`
## `note` numbers
If you prefer, you can also use numbers with `n` instead:
If you prefer, you can also use numbers with `note` instead:
<MiniRepl client:idle tune={`n("57 61 64 69")`} />
<MiniRepl client:idle tune={`note("57 61 64 69")`} />
These numbers are interpreted as so called [MIDI numbers](https://www.inspiredacoustics.com/en/MIDI_note_numbers_and_center_frequencies), where adjacent whole numbers are one 'semitone' apart.
You could also write decimal numbers to get 'microtonal' pitches (in between the black and white piano notes):
<MiniRepl client:idle tune={`n("74.5 75 75.5 76")`} />
<MiniRepl client:idle tune={`note("74.5 75 75.5 76")`} />
# `freq`
## `freq`
To get maximum freedom, you can also use `freq` to directly control the frequency:
@ -76,13 +76,13 @@ The less distance we can hear between the frequencies!
Why is this? [Human hearing operates logarithmically](https://www.audiocheck.net/soundtests_nonlinear.php).
# From notes to sounds
## From notes to sounds
In this page, when we played a pattern of notes like this:
<MiniRepl client:idle tune={`note("a3 c#4 e4 a4")`} />
We heard a simple synthesised sound, in fact we heard a [square wave oscillator](https://en.wikipedia.org/wiki/Square_wave).
We heard a simple synthesised sound, in fact we heard a [triangle wave oscillator](https://en.wikipedia.org/wiki/Triangle_wave).
This is the default synthesiser used by Strudel, but how do we then make different sounds in Strudel?

View File

@ -56,54 +56,54 @@ These methods add random behavior to your Patterns.
<JsDoc client:idle name="chooseCycles" h={0} />
## Pattern.degradeBy
## degradeBy
<JsDoc client:idle name="Pattern.degradeBy" h={0} />
## Pattern.degrade
## degrade
<JsDoc client:idle name="Pattern.degrade" h={0} />
## Pattern.undegradeBy
## undegradeBy
<JsDoc client:idle name="Pattern.undegradeBy" h={0} />
## Pattern.sometimesBy
## sometimesBy
<JsDoc client:idle name="Pattern.sometimesBy" h={0} />
## Pattern.sometimes
## sometimes
<JsDoc client:idle name="Pattern.sometimes" h={0} />
## Pattern.someCyclesBy
## someCyclesBy
<JsDoc client:idle name="Pattern.someCyclesBy" h={0} />
## Pattern.someCycles
## someCycles
<JsDoc client:idle name="Pattern.someCycles" h={0} />
## Pattern.often
## often
<JsDoc client:idle name="Pattern.often" h={0} />
## Pattern.rarely
## rarely
<JsDoc client:idle name="Pattern.rarely" h={0} />
## Pattern.almostNever
## almostNever
<JsDoc client:idle name="Pattern.almostNever" h={0} />
## Pattern.almostAlways
## almostAlways
<JsDoc client:idle name="Pattern.almostAlways" h={0} />
## Pattern.never
## never
<JsDoc client:idle name="Pattern.never" h={0} />
## Pattern.always
## always
<JsDoc client:idle name="Pattern.always" h={0} />

View File

@ -0,0 +1,75 @@
---
title: Time Modifiers
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Time Modifiers
The following functions modify a pattern temporal structure in some way.
Some of these have equivalent operators in the Mini Notation:
| function | mini |
| ---------------------- | ------------ |
| `"x".slow(2)` | `"x/2"` |
| `"x".fast(2)` | `"x*2"` |
| `"x".euclid(3,8)` | `"x(3,8)"` |
| `"x".euclidRot(3,8,1)` | `"x(3,8,1)"` |
## slow
<JsDoc client:idle name="Pattern.slow" h={0} />
## fast
<JsDoc client:idle name="Pattern.fast" h={0} />
## early
<JsDoc client:idle name="Pattern.early" h={0} />
## late
<JsDoc client:idle name="Pattern.late" h={0} />
## legato
<JsDoc client:idle name="Pattern.legato" h={0} />
## struct
<JsDoc client:idle name="Pattern.struct" h={0} />
## euclid
<JsDoc client:idle name="Pattern.euclid" h={0} />
### euclidRot
<JsDoc client:idle name="Pattern.euclidRot" h={0} />
### euclidLegato
<JsDoc client:idle name="Pattern.euclidLegato" h={0} />
## rev
<JsDoc client:idle name="Pattern.rev" h={0} />
## iter
<JsDoc client:idle name="Pattern.iter" h={0} />
### iterBack
<JsDoc client:idle name="Pattern.iterBack" h={0} />
## chunk
<JsDoc client:idle name="Pattern.chunk" h={0} />
### chunkBack
<JsDoc client:idle name="Pattern.chunkBack" h={0} />

View File

@ -47,6 +47,7 @@ Usage:
- `name`: function name, as named with `@name` in jsdoc
- `h`: level of heading. `0` will hide the heading. Hiding it allows using a manual heading which results in a nav link being generated in the right sidebar.
- `hideDescription`: if set, the description will be hidden
### Writing jsdoc

View File

@ -1,4 +1,7 @@
/** @type {import('tailwindcss').Config} */
const defaultTheme = require('tailwindcss/defaultTheme');
module.exports = {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
theme: {
@ -17,6 +20,20 @@ module.exports = {
// header: 'transparent',
footer: '#00000050',
},
typography(theme) {
return {
DEFAULT: {
css: {
'code::before': {
content: 'none', // dont wrap code in backticks
},
'code::after': {
content: 'none',
},
},
},
};
},
},
},
plugins: [require('@tailwindcss/typography')],