Merge pull request #263 from tidalcycles/fix-tutorial-bugs

fix tutorial bugs
This commit is contained in:
Felix Roos 2022-11-17 11:08:37 +01:00 committed by GitHub
commit 547d925065
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 207 additions and 177 deletions

View File

@ -10,7 +10,7 @@
"test-coverage": "vitest --coverage", "test-coverage": "vitest --coverage",
"bootstrap": "lerna bootstrap", "bootstrap": "lerna bootstrap",
"setup": "npm i && npm run bootstrap && cd repl && npm i && cd ../tutorial && npm i", "setup": "npm i && npm run bootstrap && cd repl && npm i && cd ../tutorial && npm i",
"snapshot": "cd repl && npm run snapshot", "snapshot": "vitest run -u --silent",
"repl": "cd repl && npm run dev", "repl": "cd repl && npm run dev",
"osc": "cd packages/osc && npm run server", "osc": "cd packages/osc && npm run server",
"build": "rm -rf out && cd repl && npm run build && cd ../tutorial && npm run build", "build": "rm -rf out && cd repl && npm run build && cd ../tutorial && npm run build",

View File

@ -1050,7 +1050,7 @@ export class Pattern {
* @param {function} func function to apply * @param {function} func function to apply
* @returns Pattern * @returns Pattern
* @example * @example
* note("c3 d3 e3 g3").every(4, x=>x.rev()) * note("c3 d3 e3 g3").each(4, x=>x.rev())
*/ */
each(n, func) { each(n, func) {
const pat = this; const pat = this;

View File

@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
// returns true if the given string is a note // returns true if the given string is a note
export const isNote = (name) => /^[a-gA-G][#b]*[0-9]$/.test(name); export const isNote = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name);
export const tokenizeNote = (note) => { export const tokenizeNote = (note) => {
if (typeof note !== 'string') { if (typeof note !== 'string') {
return []; return [];

File diff suppressed because one or more lines are too long

View File

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

View File

@ -1,6 +1,7 @@
import { useRef, useCallback, useEffect, useMemo, useState } from 'react'; import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
import { repl } from '@strudel.cycles/core'; import { repl } from '@strudel.cycles/core';
import { transpiler } from '@strudel.cycles/transpiler'; import { transpiler } from '@strudel.cycles/transpiler';
import usePostMessage from './usePostMessage.mjs';
function useStrudel({ function useStrudel({
defaultOutput, defaultOutput,
@ -14,6 +15,7 @@ function useStrudel({
onEvalError, onEvalError,
onToggle, onToggle,
}) { }) {
const id = useMemo(() => s4(), []);
// scheduler // scheduler
const [schedulerError, setSchedulerError] = useState(); const [schedulerError, setSchedulerError] = useState();
const [evalError, setEvalError] = useState(); const [evalError, setEvalError] = useState();
@ -57,7 +59,19 @@ function useStrudel({
}), }),
[defaultOutput, interval, getTime], [defaultOutput, interval, getTime],
); );
const activateCode = useCallback(async (autostart = true) => evaluate(code, autostart), [evaluate, code]); const broadcast = usePostMessage(({ data: { from, type } }) => {
if (type === 'start' && from !== id) {
// console.log('message', from, type);
stop();
}
});
const activateCode = useCallback(
async (autostart = true) => {
await evaluate(code, autostart);
broadcast({ type: 'start', from: id });
},
[evaluate, code],
);
const inited = useRef(); const inited = useRef();
useEffect(() => { useEffect(() => {
@ -103,3 +117,9 @@ function useStrudel({
} }
export default useStrudel; export default useStrudel;
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}

View File

@ -11,7 +11,6 @@
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"test": "vitest run --reporter verbose -v --no-isolate", "test": "vitest run --reporter verbose -v --no-isolate",
"snapshot": "vitest run -u --silent",
"add-license": "cat etc/agpl-header.txt ../docs/static/js/*LICENSE.txt > /tmp/strudel-license.txt && cp /tmp/strudel-license.txt ../docs/static/js/*LICENSE.txt", "add-license": "cat etc/agpl-header.txt ../docs/static/js/*LICENSE.txt > /tmp/strudel-license.txt && cp /tmp/strudel-license.txt ../docs/static/js/*LICENSE.txt",
"predeploy": "npm run build", "predeploy": "npm run build",
"deploy": "gh-pages -d ../docs", "deploy": "gh-pages -d ../docs",

View File

@ -19,7 +19,7 @@ evalScope(
import('@strudel.cycles/osc'), import('@strudel.cycles/osc'),
); );
prebake(); // prebake();
export function MiniRepl({ tune }) { export function MiniRepl({ tune }) {
return <_MiniRepl tune={tune} hideOutsideView={true} />; return <_MiniRepl tune={tune} hideOutsideView={true} />;

View File

@ -10,6 +10,9 @@ import Tutorial from './tutorial.rendered.mdx';
// import ApiDoc from './ApiDoc'; // import ApiDoc from './ApiDoc';
import './style.scss'; import './style.scss';
import '@strudel.cycles/react/dist/style.css'; import '@strudel.cycles/react/dist/style.css';
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
initAudioOnFirstClick();
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>

View File

@ -767,10 +767,10 @@ exports[`runs examples > example "dry" example index 0 1`] = `
exports[`runs examples > example "each" example index 0 1`] = ` exports[`runs examples > example "each" example index 0 1`] = `
[ [
"3/4 -> 1/1: {\\"note\\":\\"c3\\"}", "0/1 -> 1/4: {\\"note\\":\\"c3\\"}",
"1/2 -> 3/4: {\\"note\\":\\"d3\\"}", "1/4 -> 1/2: {\\"note\\":\\"d3\\"}",
"1/4 -> 1/2: {\\"note\\":\\"e3\\"}", "1/2 -> 3/4: {\\"note\\":\\"e3\\"}",
"0/1 -> 1/4: {\\"note\\":\\"g3\\"}", "3/4 -> 1/1: {\\"note\\":\\"g3\\"}",
"1/1 -> 5/4: {\\"note\\":\\"c3\\"}", "1/1 -> 5/4: {\\"note\\":\\"c3\\"}",
"5/4 -> 3/2: {\\"note\\":\\"d3\\"}", "5/4 -> 3/2: {\\"note\\":\\"d3\\"}",
"3/2 -> 7/4: {\\"note\\":\\"e3\\"}", "3/2 -> 7/4: {\\"note\\":\\"e3\\"}",
@ -779,10 +779,10 @@ exports[`runs examples > example "each" example index 0 1`] = `
"9/4 -> 5/2: {\\"note\\":\\"d3\\"}", "9/4 -> 5/2: {\\"note\\":\\"d3\\"}",
"5/2 -> 11/4: {\\"note\\":\\"e3\\"}", "5/2 -> 11/4: {\\"note\\":\\"e3\\"}",
"11/4 -> 3/1: {\\"note\\":\\"g3\\"}", "11/4 -> 3/1: {\\"note\\":\\"g3\\"}",
"3/1 -> 13/4: {\\"note\\":\\"c3\\"}", "15/4 -> 4/1: {\\"note\\":\\"c3\\"}",
"13/4 -> 7/2: {\\"note\\":\\"d3\\"}", "7/2 -> 15/4: {\\"note\\":\\"d3\\"}",
"7/2 -> 15/4: {\\"note\\":\\"e3\\"}", "13/4 -> 7/2: {\\"note\\":\\"e3\\"}",
"15/4 -> 4/1: {\\"note\\":\\"g3\\"}", "3/1 -> 13/4: {\\"note\\":\\"g3\\"}",
] ]
`; `;

View File

@ -253,7 +253,7 @@ Using round brackets, we can create rhythmical sub-divisions based on three para
The first parameter controls how may beats will be played. The first parameter controls how may beats will be played.
The second parameter controls the total amount of segments the beats will be distributed over. The second parameter controls the total amount of segments the beats will be distributed over.
The third (optional) parameter controls the starting position for distributing the beats. The third (optional) parameter controls the starting position for distributing the beats.
One popular Euclidian rhythm (going by various names, such as "Pop Clave") is "(3,8,1)" or simply "(3,8)", One popular Euclidian rhythm (going by various names, such as "Pop Clave") is "(3,8,0)" or simply "(3,8)",
resulting in a rhythmical structure of "x ~ ~ x ~ ~ x ~" (3 beats over 8 segments, starting on position 1). resulting in a rhythmical structure of "x ~ ~ x ~ ~ x ~" (3 beats over 8 segments, starting on position 1).
<MiniRepl tune={`note("e5(2,8) b4(3,8) d5(2,8) c5(3,8)").slow(4)`} /> <MiniRepl tune={`note("e5(2,8) b4(3,8) d5(2,8) c5(3,8)").slow(4)`} />
@ -358,7 +358,7 @@ For pitched sounds, you can use `note`, just like with synths:
<MiniRepl <MiniRepl
tune={`samples({ tune={`samples({
"gtr": 'gtr/0001_cleanC.wav', 'gtr': 'gtr/0001_cleanC.wav',
}, 'github:tidalcycles/Dirt-Samples/master/'); }, 'github:tidalcycles/Dirt-Samples/master/');
note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s('gtr').gain(.5)`} note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s('gtr').gain(.5)`}
/> />
@ -368,7 +368,7 @@ If we want them to behave more like a synth, we can add `clip(1)`:
<MiniRepl <MiniRepl
tune={`samples({ tune={`samples({
"gtr": 'gtr/0001_cleanC.wav', 'gtr': 'gtr/0001_cleanC.wav',
}, 'github:tidalcycles/Dirt-Samples/master/'); }, 'github:tidalcycles/Dirt-Samples/master/');
note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s('gtr').clip(1) note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s('gtr').clip(1)
.gain(.5)`} .gain(.5)`}
@ -380,8 +380,8 @@ If we have 2 samples with different base pitches, we can make them in tune by sp
<MiniRepl <MiniRepl
tune={`samples({ tune={`samples({
"gtr": 'gtr/0001_cleanC.wav', 'gtr': 'gtr/0001_cleanC.wav',
"moog": { 'g3': 'moog/005_Mighty%20Moog%20G3.wav' }, 'moog': { 'g3': 'moog/005_Mighty%20Moog%20G3.wav' },
}, 'github:tidalcycles/Dirt-Samples/master/'); }, 'github:tidalcycles/Dirt-Samples/master/');
note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s("gtr,moog").clip(1) note("g3 [bb3 c4] <g4 f4 eb4 f3>@2").s("gtr,moog").clip(1)
.gain(.5)`} .gain(.5)`}
@ -393,7 +393,7 @@ We can also declare different samples for different regions of the keyboard:
<MiniRepl <MiniRepl
tune={`samples({ tune={`samples({
"moog": { 'moog': {
'g2': 'moog/004_Mighty%20Moog%20G2.wav', 'g2': 'moog/004_Mighty%20Moog%20G2.wav',
'g3': 'moog/005_Mighty%20Moog%20G3.wav', 'g3': 'moog/005_Mighty%20Moog%20G3.wav',
'g4': 'moog/006_Mighty%20Moog%20G4.wav', 'g4': 'moog/006_Mighty%20Moog%20G4.wav',
@ -769,9 +769,9 @@ Together with layer, struct and voicings, this can be used to create a basic bac
<MiniRepl <MiniRepl
tune={`"<C^7 A7b13 Dm7 G7>".layer( tune={`"<C^7 A7b13 Dm7 G7>".layer(
x => x.voicings(['d3','g4']).struct("~ x"), x => x.voicings(['d3','g4']).struct("~ x").note(),
x => x.rootNotes(2).tone(synth(osc('sawtooth4')).chain(out())) x => x.rootNotes(2).note().s('sawtooth').cutoff(800)
).note()`} )`}
/> />
<!-- TODO: use range instead of octave. --> <!-- TODO: use range instead of octave. -->
@ -786,17 +786,15 @@ Strudel also supports midi via [webmidi](https://npmjs.com/package/webmidi).
### midi(outputName?) ### midi(outputName?)
Make sure to have a midi device connected or to use an IAC Driver. Either connect a midi device or use the IAC Driver (Mac) or Midi Through Port (Linux) for internal midi messages.
If no outputName is given, it uses the first midi output it finds. If no outputName is given, it uses the first midi output it finds.
Midi is currently not supported by the mini repl used here, but you can [open the midi example in the repl](https://strudel.tidalcycles.org/#c3RhY2soIjxDXjcgQTcgRG03IEc3PiIubS52b2ljaW5ncygpLCAnPEMzIEEyIEQzIEcyPicubSkKICAubWlkaSgp). <MiniRepl
In the REPL, you will se a log of the available MIDI devices.
<!--<MiniRepl
tune={`stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>") tune={`stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>")
.midi()`} .midi()`}
/>--> />
In the console, you will see a log of the available MIDI devices as soon as you run the code, e.g. `Midi connected! Using "Midi Through Port-0".`
# Superdirt API # Superdirt API