mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-19 09:38:38 +00:00
Merge remote-tracking branch 'origin/main' into docs
This commit is contained in:
commit
fea1e71a99
@ -3,18 +3,60 @@
|
|||||||
This directory can be used to save your own patterns, which then get
|
This directory can be used to save your own patterns, which then get
|
||||||
made into a pattern swatch.
|
made into a pattern swatch.
|
||||||
|
|
||||||
0. fork and clone the strudel repository
|
Example: <https://felixroos.github.io/strudel/swatch/>
|
||||||
1. run `npm run setup` in the strudel folder
|
|
||||||
1. Save one or more .txt files in this folder
|
|
||||||
2. run `npm run repl` in the top-level strudel folder
|
|
||||||
3. open `http://localhost:3000/swatch/` !
|
|
||||||
|
|
||||||
## deploy
|
## deploy
|
||||||
|
|
||||||
1. in your fork, go to settings -> pages and select "Github Actions" as source
|
### 1. fork the [strudel repo on github](https://github.com/tidalcycles/strudel.git)
|
||||||
2. edit `website/public/CNAME` to contain `<your-username>.github.io/strudel`
|
|
||||||
3. edit `website/astro.config.mjs` to use site: `https://<your-username>.github.io` and base `/strudel`
|
### 2. clone your fork to your machine `git clone https://github.com/<your-username>/strudel.git strudel && cd strudel`
|
||||||
4. go to Actions -> `Build and Deploy` and click `Run workflow`
|
|
||||||
5. view your patterns at `<your-username>.github.io/strudel/swatch/`
|
### 3. create a separate branch like `git branch patternuary && git checkout patternuary`
|
||||||
|
|
||||||
|
### 4. save one or more .txt files in the my-patterns folder
|
||||||
|
|
||||||
|
### 5. edit `website/public/CNAME` to contain `<your-username>.github.io/strudel`
|
||||||
|
|
||||||
|
### 6. edit `website/astro.config.mjs` to use site: `https://<your-username>.github.io` and base `/strudel`, like this
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default defineConfig({
|
||||||
|
/* ... rest of config ... */
|
||||||
|
site: 'https://<your-username>.github.io',
|
||||||
|
base: '/strudel',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. commit & push the changes
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git add . && git commit -m "site config" && git push --set-upstream origin
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. deploy to github pages
|
||||||
|
|
||||||
|
- go to settings -> pages and select "Github Actions" as source
|
||||||
|
- go to settings -> environments -> github-pages and press the edit button next to `main` and type in `patternuary` (under "Deployment branches")
|
||||||
|
- go to Actions -> `Build and Deploy` and click `Run workflow` with branch `patternuary`
|
||||||
|
|
||||||
|
### 9. view your patterns at `<your-username>.github.io/strudel/swatch/`
|
||||||
|
|
||||||
Alternatively, github pages allows you to use a custom domain, like https://mycooldomain.org/swatch/. [See their documentation for details](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site).
|
Alternatively, github pages allows you to use a custom domain, like https://mycooldomain.org/swatch/. [See their documentation for details](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site).
|
||||||
|
|
||||||
|
### 10. optional: automatic deployment
|
||||||
|
|
||||||
|
If you want to automatically deploy your site on push, go to `deploy.yml` and change `workflow_dispatch` to `push`.
|
||||||
|
|
||||||
|
## running locally
|
||||||
|
|
||||||
|
- install dependencies with `npm run setup`
|
||||||
|
- run dev server with `npm run repl` and open `http://localhost:3000/strudel/swatch/`
|
||||||
|
|
||||||
|
## tests fail?
|
||||||
|
|
||||||
|
Your tests might fail if the code does not follow prettiers format.
|
||||||
|
In that case, run `npm run codeformat`. To disable that, remove `npm run format-check` from `test.yml`
|
||||||
|
|
||||||
|
## updating your fork
|
||||||
|
|
||||||
|
To update your fork, you can pull the main branch and merge it into your `patternuary` branch.
|
||||||
|
|||||||
@ -22,11 +22,11 @@ Pattern.prototype.animate = function ({ callback, sync = false, smear = 0.5 } =
|
|||||||
ctx.fillStyle = clearColor;
|
ctx.fillStyle = clearColor;
|
||||||
ctx.fillRect(0, 0, ww, wh);
|
ctx.fillRect(0, 0, ww, wh);
|
||||||
frame.forEach((f) => {
|
frame.forEach((f) => {
|
||||||
let { x, y, w, h, s, r, a = 0, fill = 'darkseagreen' } = f.value;
|
let { x, y, w, h, s, r, angle = 0, fill = 'darkseagreen' } = f.value;
|
||||||
w *= ww;
|
w *= ww;
|
||||||
h *= wh;
|
h *= wh;
|
||||||
if (r !== undefined && a !== undefined) {
|
if (r !== undefined && angle !== undefined) {
|
||||||
const radians = a * 2 * Math.PI;
|
const radians = angle * 2 * Math.PI;
|
||||||
const [cx, cy] = [(ww - w) / 2, (wh - h) / 2];
|
const [cx, cy] = [(ww - w) / 2, (wh - h) / 2];
|
||||||
x = cx + Math.cos(radians) * r * cx;
|
x = cx + Math.cos(radians) * r * cx;
|
||||||
y = cy + Math.sin(radians) * r * cy;
|
y = cy + Math.sin(radians) * r * cy;
|
||||||
@ -51,7 +51,7 @@ Pattern.prototype.animate = function ({ callback, sync = false, smear = 0.5 } =
|
|||||||
return silence;
|
return silence;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const { x, y, w, h, a, r, fill, smear } = createParams('x', 'y', 'w', 'h', 'a', 'r', 'fill', 'smear');
|
export const { x, y, w, h, angle, r, fill, smear } = createParams('x', 'y', 'w', 'h', 'angle', 'r', 'fill', 'smear');
|
||||||
|
|
||||||
export const rescale = register('rescale', function (f, pat) {
|
export const rescale = register('rescale', function (f, pat) {
|
||||||
return pat.mul(x(f).w(f).y(f).h(f));
|
return pat.mul(x(f).w(f).y(f).h(f));
|
||||||
|
|||||||
@ -59,3 +59,9 @@ export const cleanupDraw = (clearScreen = true) => {
|
|||||||
clearInterval(window.strudelScheduler);
|
clearInterval(window.strudelScheduler);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Pattern.prototype.onPaint = function (onPaint) {
|
||||||
|
// this is evil! TODO: add pattern.context
|
||||||
|
this.context = { onPaint };
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|||||||
@ -46,7 +46,7 @@ const _bjork = function (n, x) {
|
|||||||
return Math.min(ons, offs) <= 1 ? [n, x] : _bjork(...(ons > offs ? left(n, x) : right(n, x)));
|
return Math.min(ons, offs) <= 1 ? [n, x] : _bjork(...(ons > offs ? left(n, x) : right(n, x)));
|
||||||
};
|
};
|
||||||
|
|
||||||
const bjork = function (ons, steps) {
|
export const bjork = function (ons, steps) {
|
||||||
const offs = steps - ons;
|
const offs = steps - ons;
|
||||||
const x = Array(ons).fill([1]);
|
const x = Array(ons).fill([1]);
|
||||||
const y = Array(offs).fill([0]);
|
const y = Array(offs).fill([0]);
|
||||||
|
|||||||
@ -334,7 +334,12 @@ export class Pattern {
|
|||||||
* silence
|
* silence
|
||||||
*/
|
*/
|
||||||
queryArc(begin, end) {
|
queryArc(begin, end) {
|
||||||
return this.query(new State(new TimeSpan(begin, end)));
|
try {
|
||||||
|
return this.query(new State(new TimeSpan(begin, end)));
|
||||||
|
} catch (err) {
|
||||||
|
logger(`[query]: ${err.message}`, 'error');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2060,6 +2065,7 @@ export const velocity = register('velocity', function (velocity, pat) {
|
|||||||
*/
|
*/
|
||||||
// TODO - fix
|
// TODO - fix
|
||||||
export const legato = register('legato', function (value, pat) {
|
export const legato = register('legato', function (value, pat) {
|
||||||
|
value = Fraction(value);
|
||||||
return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value))));
|
return pat.withHapSpan((span) => new TimeSpan(span.begin, span.begin.add(span.end.sub(span.begin).mul(value))));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -283,3 +283,15 @@ export function pianoroll({
|
|||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOptions(drawTime, options = {}) {
|
||||||
|
let [lookbehind, lookahead] = drawTime;
|
||||||
|
lookbehind = Math.abs(lookbehind);
|
||||||
|
const cycles = lookahead + lookbehind;
|
||||||
|
const playhead = lookbehind / cycles;
|
||||||
|
return { fold: 1, ...options, cycles, playhead };
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern.prototype.punchcard = function (options) {
|
||||||
|
return this.onPaint((ctx, time, haps, drawTime) => pianoroll({ ctx, time, haps, ...getOptions(drawTime, options) }));
|
||||||
|
};
|
||||||
|
|||||||
@ -12,8 +12,8 @@ export function repl({
|
|||||||
afterEval,
|
afterEval,
|
||||||
getTime,
|
getTime,
|
||||||
transpiler,
|
transpiler,
|
||||||
editPattern,
|
|
||||||
onToggle,
|
onToggle,
|
||||||
|
editPattern,
|
||||||
}) {
|
}) {
|
||||||
const scheduler = new Cyclist({
|
const scheduler = new Cyclist({
|
||||||
interval,
|
interval,
|
||||||
@ -35,7 +35,7 @@ export function repl({
|
|||||||
getTime,
|
getTime,
|
||||||
onToggle,
|
onToggle,
|
||||||
});
|
});
|
||||||
setTime(() => scheduler.getPhase()); // TODO: refactor?
|
setTime(() => scheduler.now()); // TODO: refactor?
|
||||||
const evaluate = async (code, autostart = true) => {
|
const evaluate = async (code, autostart = true) => {
|
||||||
if (!code) {
|
if (!code) {
|
||||||
throw new Error('no code to evaluate');
|
throw new Error('no code to evaluate');
|
||||||
|
|||||||
@ -114,6 +114,14 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A discrete pattern of numbers from 0 to n-1
|
||||||
|
* @example
|
||||||
|
* run(4).scale('C4 major').note()
|
||||||
|
* // "0 1 2 3".scale('C4 major').note()
|
||||||
|
*/
|
||||||
|
export const run = (n) => saw.range(0, n).floor().segment(n);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A continuous pattern of random numbers, between 0 and 1.
|
* A continuous pattern of random numbers, between 0 and 1.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -44,6 +44,7 @@ import {
|
|||||||
ply,
|
ply,
|
||||||
rev,
|
rev,
|
||||||
time,
|
time,
|
||||||
|
run,
|
||||||
} from '../index.mjs';
|
} from '../index.mjs';
|
||||||
|
|
||||||
import { steady } from '../signal.mjs';
|
import { steady } from '../signal.mjs';
|
||||||
@ -908,6 +909,11 @@ describe('Pattern', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('run', () => {
|
||||||
|
it('Can run', () => {
|
||||||
|
expect(run(4).firstCycle()).toStrictEqual(sequence(0, 1, 2, 3).firstCycle());
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('linger', () => {
|
describe('linger', () => {
|
||||||
it('Can linger on the first quarter of a cycle', () => {
|
it('Can linger on the first quarter of a cycle', () => {
|
||||||
expect(sequence(0, 1, 2, 3, 4, 5, 6, 7).linger(0.25).firstCycle()).toStrictEqual(
|
expect(sequence(0, 1, 2, 3, 4, 5, 6, 7).linger(0.25).firstCycle()).toStrictEqual(
|
||||||
|
|||||||
2
packages/react/dist/index.cjs.js
vendored
2
packages/react/dist/index.cjs.js
vendored
File diff suppressed because one or more lines are too long
649
packages/react/dist/index.es.js
vendored
649
packages/react/dist/index.es.js
vendored
@ -1,15 +1,15 @@
|
|||||||
import l, { useCallback as N, useRef as k, useEffect as _, useMemo as K, useState as w, useLayoutEffect as G } from "react";
|
import d, { useCallback as E, useRef as A, useEffect as k, useMemo as Q, useState as _, useLayoutEffect as te } from "react";
|
||||||
import Z from "@uiw/react-codemirror";
|
import ue from "@uiw/react-codemirror";
|
||||||
import { Decoration as y, EditorView as J } from "@codemirror/view";
|
import { Decoration as M, EditorView as re } from "@codemirror/view";
|
||||||
import { StateEffect as Q, StateField as X } from "@codemirror/state";
|
import { StateEffect as ne, StateField as oe } from "@codemirror/state";
|
||||||
import { javascript as ee } from "@codemirror/lang-javascript";
|
import { javascript as de } from "@codemirror/lang-javascript";
|
||||||
import { tags as s } from "@lezer/highlight";
|
import { tags as u } from "@lezer/highlight";
|
||||||
import { createTheme as te } from "@uiw/codemirror-themes";
|
import { createTheme as fe } from "@uiw/codemirror-themes";
|
||||||
import { repl as re, logger as ne, pianoroll as oe } from "@strudel.cycles/core";
|
import { webaudioOutput as me, getAudioContext as he } from "@strudel.cycles/webaudio";
|
||||||
import { webaudioOutput as ae, getAudioContext as ce } from "@strudel.cycles/webaudio";
|
import { useInView as ge } from "react-hook-inview";
|
||||||
import { useInView as se } from "react-hook-inview";
|
import { repl as pe, logger as ve } from "@strudel.cycles/core";
|
||||||
import { transpiler as ie } from "@strudel.cycles/transpiler";
|
import { transpiler as be } from "@strudel.cycles/transpiler";
|
||||||
const le = te({
|
const Ee = fe({
|
||||||
theme: "dark",
|
theme: "dark",
|
||||||
settings: {
|
settings: {
|
||||||
background: "#222",
|
background: "#222",
|
||||||
@ -22,380 +22,421 @@ const le = te({
|
|||||||
gutterForeground: "#8a919966"
|
gutterForeground: "#8a919966"
|
||||||
},
|
},
|
||||||
styles: [
|
styles: [
|
||||||
{ tag: s.keyword, color: "#c792ea" },
|
{ tag: u.keyword, color: "#c792ea" },
|
||||||
{ tag: s.operator, color: "#89ddff" },
|
{ tag: u.operator, color: "#89ddff" },
|
||||||
{ tag: s.special(s.variableName), color: "#eeffff" },
|
{ tag: u.special(u.variableName), color: "#eeffff" },
|
||||||
{ tag: s.typeName, color: "#c3e88d" },
|
{ tag: u.typeName, color: "#c3e88d" },
|
||||||
{ tag: s.atom, color: "#f78c6c" },
|
{ tag: u.atom, color: "#f78c6c" },
|
||||||
{ tag: s.number, color: "#c3e88d" },
|
{ tag: u.number, color: "#c3e88d" },
|
||||||
{ tag: s.definition(s.variableName), color: "#82aaff" },
|
{ tag: u.definition(u.variableName), color: "#82aaff" },
|
||||||
{ tag: s.string, color: "#c3e88d" },
|
{ tag: u.string, color: "#c3e88d" },
|
||||||
{ tag: s.special(s.string), color: "#c3e88d" },
|
{ tag: u.special(u.string), color: "#c3e88d" },
|
||||||
{ tag: s.comment, color: "#7d8799" },
|
{ tag: u.comment, color: "#7d8799" },
|
||||||
{ tag: s.variableName, color: "#c792ea" },
|
{ tag: u.variableName, color: "#c792ea" },
|
||||||
{ tag: s.tagName, color: "#c3e88d" },
|
{ tag: u.tagName, color: "#c3e88d" },
|
||||||
{ tag: s.bracket, color: "#525154" },
|
{ tag: u.bracket, color: "#525154" },
|
||||||
{ tag: s.meta, color: "#ffcb6b" },
|
{ tag: u.meta, color: "#ffcb6b" },
|
||||||
{ tag: s.attributeName, color: "#c792ea" },
|
{ tag: u.attributeName, color: "#c792ea" },
|
||||||
{ tag: s.propertyName, color: "#c792ea" },
|
{ tag: u.propertyName, color: "#c792ea" },
|
||||||
{ tag: s.className, color: "#decb6b" },
|
{ tag: u.className, color: "#decb6b" },
|
||||||
{ tag: s.invalid, color: "#ffffff" }
|
{ tag: u.invalid, color: "#ffffff" }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
const j = Q.define(), ue = X.define({
|
const X = ne.define(), ye = oe.define({
|
||||||
create() {
|
create() {
|
||||||
return y.none;
|
return M.none;
|
||||||
},
|
},
|
||||||
update(e, r) {
|
update(e, t) {
|
||||||
try {
|
try {
|
||||||
for (let t of r.effects)
|
for (let r of t.effects)
|
||||||
if (t.is(j))
|
if (r.is(X))
|
||||||
if (t.value) {
|
if (r.value) {
|
||||||
const a = y.mark({ attributes: { style: "background-color: #FFCA2880" } });
|
const n = M.mark({ attributes: { style: "background-color: #FFCA2880" } });
|
||||||
e = y.set([a.range(0, r.newDoc.length)]);
|
e = M.set([n.range(0, t.newDoc.length)]);
|
||||||
} else
|
} else
|
||||||
e = y.set([]);
|
e = M.set([]);
|
||||||
return e;
|
return e;
|
||||||
} catch (t) {
|
} catch (r) {
|
||||||
return console.warn("flash error", t), e;
|
return console.warn("flash error", r), e;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
provide: (e) => J.decorations.from(e)
|
provide: (e) => re.decorations.from(e)
|
||||||
}), de = (e) => {
|
}), we = (e) => {
|
||||||
e.dispatch({ effects: j.of(!0) }), setTimeout(() => {
|
e.dispatch({ effects: X.of(!0) }), setTimeout(() => {
|
||||||
e.dispatch({ effects: j.of(!1) });
|
e.dispatch({ effects: X.of(!1) });
|
||||||
}, 200);
|
}, 200);
|
||||||
}, z = Q.define(), fe = X.define({
|
}, B = ne.define(), ke = oe.define({
|
||||||
create() {
|
create() {
|
||||||
return y.none;
|
return M.none;
|
||||||
},
|
},
|
||||||
update(e, r) {
|
update(e, t) {
|
||||||
try {
|
try {
|
||||||
for (let t of r.effects)
|
for (let r of t.effects)
|
||||||
if (t.is(z)) {
|
if (r.is(B)) {
|
||||||
const a = t.value.map(
|
const n = r.value.map(
|
||||||
(o) => (o.context.locations || []).map(({ start: i, end: u }) => {
|
(l) => (l.context.locations || []).map(({ start: m, end: f }) => {
|
||||||
const m = o.context.color || "#FFCA28";
|
const c = l.context.color || "#FFCA28";
|
||||||
let n = r.newDoc.line(i.line).from + i.column, d = r.newDoc.line(u.line).from + u.column;
|
let s = t.newDoc.line(m.line).from + m.column, g = t.newDoc.line(f.line).from + f.column;
|
||||||
const v = r.newDoc.length;
|
const b = t.newDoc.length;
|
||||||
return n > v || d > v ? void 0 : y.mark({ attributes: { style: `outline: 1.5px solid ${m};` } }).range(n, d);
|
return s > b || g > b ? void 0 : M.mark({ attributes: { style: `outline: 1.5px solid ${c};` } }).range(s, g);
|
||||||
})
|
})
|
||||||
).flat().filter(Boolean) || [];
|
).flat().filter(Boolean) || [];
|
||||||
e = y.set(a, !0);
|
e = M.set(n, !0);
|
||||||
}
|
}
|
||||||
return e;
|
return e;
|
||||||
} catch {
|
} catch {
|
||||||
return y.set([]);
|
return M.set([]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
provide: (e) => J.decorations.from(e)
|
provide: (e) => re.decorations.from(e)
|
||||||
}), me = [ee(), le, fe, ue];
|
}), Fe = [de(), Ee, ke, ye];
|
||||||
function ge({ value: e, onChange: r, onViewChanged: t, onSelectionChange: a, options: o, editorDidMount: i }) {
|
function _e({ value: e, onChange: t, onViewChanged: r, onSelectionChange: n, options: l, editorDidMount: m }) {
|
||||||
const u = N(
|
const f = E(
|
||||||
(d) => {
|
(g) => {
|
||||||
r?.(d);
|
t?.(g);
|
||||||
},
|
|
||||||
[r]
|
|
||||||
), m = N(
|
|
||||||
(d) => {
|
|
||||||
t?.(d);
|
|
||||||
},
|
},
|
||||||
[t]
|
[t]
|
||||||
), n = N(
|
), c = E(
|
||||||
(d) => {
|
(g) => {
|
||||||
d.selectionSet && a && a?.(d.state.selection);
|
r?.(g);
|
||||||
},
|
},
|
||||||
[a]
|
[r]
|
||||||
|
), s = E(
|
||||||
|
(g) => {
|
||||||
|
g.selectionSet && n && n?.(g.state.selection);
|
||||||
|
},
|
||||||
|
[n]
|
||||||
);
|
);
|
||||||
return /* @__PURE__ */ l.createElement(l.Fragment, null, /* @__PURE__ */ l.createElement(Z, {
|
return /* @__PURE__ */ d.createElement(d.Fragment, null, /* @__PURE__ */ d.createElement(ue, {
|
||||||
value: e,
|
value: e,
|
||||||
onChange: u,
|
onChange: f,
|
||||||
onCreateEditor: m,
|
onCreateEditor: c,
|
||||||
onUpdate: n,
|
onUpdate: s,
|
||||||
extensions: me
|
extensions: Fe
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
function W(...e) {
|
function T(...e) {
|
||||||
return e.filter(Boolean).join(" ");
|
return e.filter(Boolean).join(" ");
|
||||||
}
|
}
|
||||||
function pe({ view: e, pattern: r, active: t, getTime: a }) {
|
function Me({ view: e, pattern: t, active: r, getTime: n }) {
|
||||||
const o = k([]), i = k();
|
const l = A([]), m = A(0);
|
||||||
_(() => {
|
k(() => {
|
||||||
if (e)
|
if (e)
|
||||||
if (r && t) {
|
if (t && r) {
|
||||||
let u = requestAnimationFrame(function m() {
|
m.current = 0;
|
||||||
|
let f = requestAnimationFrame(function c() {
|
||||||
try {
|
try {
|
||||||
const n = a(), v = [Math.max(i.current || n, n - 1 / 10, 0), n + 1 / 60];
|
const s = n(), b = [Math.max(m.current ?? s, s - 1 / 10, -0.01), s + 1 / 60];
|
||||||
i.current = v[1], o.current = o.current.filter((p) => p.whole.end > n);
|
m.current = b[1], l.current = l.current.filter((h) => h.whole.end > s);
|
||||||
const h = r.queryArc(...v).filter((p) => p.hasOnset());
|
const i = t.queryArc(...b).filter((h) => h.hasOnset());
|
||||||
o.current = o.current.concat(h), e.dispatch({ effects: z.of(o.current) });
|
l.current = l.current.concat(i), e.dispatch({ effects: B.of(l.current) });
|
||||||
} catch {
|
} catch {
|
||||||
e.dispatch({ effects: z.of([]) });
|
e.dispatch({ effects: B.of([]) });
|
||||||
}
|
}
|
||||||
u = requestAnimationFrame(m);
|
f = requestAnimationFrame(c);
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
cancelAnimationFrame(u);
|
cancelAnimationFrame(f);
|
||||||
};
|
};
|
||||||
} else
|
} else
|
||||||
o.current = [], e.dispatch({ effects: z.of([]) });
|
l.current = [], e.dispatch({ effects: B.of([]) });
|
||||||
}, [r, t, e]);
|
}, [t, r, e]);
|
||||||
}
|
}
|
||||||
function he(e, r = !1) {
|
function Ae(e, t = !1) {
|
||||||
const t = k(), a = k(), o = (m) => {
|
const r = A(), n = A(), l = (c) => {
|
||||||
if (a.current !== void 0) {
|
if (n.current !== void 0) {
|
||||||
const n = m - a.current;
|
const s = c - n.current;
|
||||||
e(m, n);
|
e(c, s);
|
||||||
}
|
}
|
||||||
a.current = m, t.current = requestAnimationFrame(o);
|
n.current = c, r.current = requestAnimationFrame(l);
|
||||||
}, i = () => {
|
}, m = () => {
|
||||||
t.current = requestAnimationFrame(o);
|
r.current = requestAnimationFrame(l);
|
||||||
}, u = () => {
|
}, f = () => {
|
||||||
t.current && cancelAnimationFrame(t.current), delete t.current;
|
r.current && cancelAnimationFrame(r.current), delete r.current;
|
||||||
};
|
};
|
||||||
return _(() => {
|
return k(() => {
|
||||||
t.current && (u(), i());
|
r.current && (f(), m());
|
||||||
}, [e]), _(() => (r && i(), u), []), {
|
}, [e]), k(() => (t && m(), f), []), {
|
||||||
start: i,
|
start: m,
|
||||||
stop: u
|
stop: f
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function ve({ pattern: e, started: r, getTime: t, onDraw: a }) {
|
function Ne({ pattern: e, started: t, getTime: r, onDraw: n, drawTime: l = [-2, 2] }) {
|
||||||
let o = k([]), i = k(null);
|
let [m, f] = l;
|
||||||
const { start: u, stop: m } = he(
|
m = Math.abs(m);
|
||||||
N(() => {
|
let c = A([]), s = A(null);
|
||||||
const n = t();
|
k(() => {
|
||||||
if (i.current === null) {
|
if (e && t) {
|
||||||
i.current = n;
|
const i = r(), h = e.queryArc(Math.max(i, 0), i + f + 0.1);
|
||||||
|
c.current = c.current.filter((p) => p.whole.begin < i), c.current = c.current.concat(h);
|
||||||
|
}
|
||||||
|
}, [e, t]);
|
||||||
|
const { start: g, stop: b } = Ae(
|
||||||
|
E(() => {
|
||||||
|
const i = r() + f;
|
||||||
|
if (s.current === null) {
|
||||||
|
s.current = i;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const d = e.queryArc(Math.max(i.current, n - 1 / 10), n), v = 4;
|
const h = e.queryArc(Math.max(s.current, i - 1 / 10), i);
|
||||||
i.current = n, o.current = (o.current || []).filter((h) => h.whole.end > n - v).concat(d.filter((h) => h.hasOnset())), a(n, o.current);
|
s.current = i, c.current = (c.current || []).filter((p) => p.whole.end >= i - m - f).concat(h.filter((p) => p.hasOnset())), n(e, i - f, c.current, l);
|
||||||
}, [e])
|
}, [e])
|
||||||
);
|
);
|
||||||
_(() => {
|
return k(() => {
|
||||||
r ? u() : (o.current = [], m());
|
t ? g() : (c.current = [], b());
|
||||||
}, [r]);
|
}, [t]), {
|
||||||
}
|
clear: () => {
|
||||||
function be(e) {
|
c.current = [];
|
||||||
return _(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), N((r) => window.postMessage(r, "*"), []);
|
|
||||||
}
|
|
||||||
function Ee({
|
|
||||||
defaultOutput: e,
|
|
||||||
interval: r,
|
|
||||||
getTime: t,
|
|
||||||
evalOnMount: a = !1,
|
|
||||||
initialCode: o = "",
|
|
||||||
autolink: i = !1,
|
|
||||||
beforeEval: u,
|
|
||||||
afterEval: m,
|
|
||||||
editPattern: n,
|
|
||||||
onEvalError: d,
|
|
||||||
onToggle: v,
|
|
||||||
canvasId: h
|
|
||||||
}) {
|
|
||||||
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: A,
|
|
||||||
onEvalError: (g) => {
|
|
||||||
D(g), d?.(g);
|
|
||||||
},
|
|
||||||
getTime: t,
|
|
||||||
transpiler: ie,
|
|
||||||
beforeEval: ({ code: g }) => {
|
|
||||||
q(g), u?.();
|
|
||||||
},
|
|
||||||
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: (g) => {
|
|
||||||
B(g), v?.(g);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
[e, r, t]
|
|
||||||
), 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 });
|
|
||||||
},
|
|
||||||
[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: 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 () => {
|
|
||||||
L ? M.pause() : await S();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function we() {
|
function Ce(e) {
|
||||||
|
return k(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), E((t) => window.postMessage(t, "*"), []);
|
||||||
|
}
|
||||||
|
function De({
|
||||||
|
defaultOutput: e,
|
||||||
|
interval: t,
|
||||||
|
getTime: r,
|
||||||
|
evalOnMount: n = !1,
|
||||||
|
initialCode: l = "",
|
||||||
|
autolink: m = !1,
|
||||||
|
beforeEval: f,
|
||||||
|
afterEval: c,
|
||||||
|
editPattern: s,
|
||||||
|
onEvalError: g,
|
||||||
|
onToggle: b,
|
||||||
|
canvasId: i,
|
||||||
|
drawContext: h,
|
||||||
|
drawTime: p = [-2, 2]
|
||||||
|
}) {
|
||||||
|
const D = Q(() => Re(), []);
|
||||||
|
i = i || `canvas-${D}`;
|
||||||
|
const [P, R] = _(), [z, H] = _(), [y, S] = _(l), [V, q] = _(), [x, I] = _(), [N, O] = _(!1), K = y !== V, L = E((a) => !!(a?.context?.onPaint && h), [h]), { scheduler: C, evaluate: o, start: v, stop: j, pause: U } = Q(
|
||||||
|
() => pe({
|
||||||
|
interval: t,
|
||||||
|
defaultOutput: e,
|
||||||
|
onSchedulerError: R,
|
||||||
|
onEvalError: (a) => {
|
||||||
|
H(a), g?.(a);
|
||||||
|
},
|
||||||
|
getTime: r,
|
||||||
|
drawContext: h,
|
||||||
|
transpiler: be,
|
||||||
|
editPattern: s,
|
||||||
|
beforeEval: ({ code: a }) => {
|
||||||
|
S(a), f?.();
|
||||||
|
},
|
||||||
|
afterEval: ({ pattern: a, code: w }) => {
|
||||||
|
q(w), I(a), H(), R(), m && (window.location.hash = "#" + encodeURIComponent(btoa(w))), c?.();
|
||||||
|
},
|
||||||
|
onToggle: (a) => {
|
||||||
|
O(a), b?.(a);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[e, t, r]
|
||||||
|
), ce = Ce(({ data: { from: a, type: w } }) => {
|
||||||
|
w === "start" && a !== D && j();
|
||||||
|
}), Y = E(
|
||||||
|
async (a = !0) => {
|
||||||
|
const w = await o(y, a);
|
||||||
|
return ce({ type: "start", from: D }), w;
|
||||||
|
},
|
||||||
|
[o, y]
|
||||||
|
), W = E(
|
||||||
|
(a, w, G, J) => {
|
||||||
|
const { onPaint: ie } = a.context || {}, le = typeof h == "function" ? h(i) : h;
|
||||||
|
ie?.(le, w, G, J);
|
||||||
|
},
|
||||||
|
[h, i]
|
||||||
|
), $ = E(
|
||||||
|
(a) => {
|
||||||
|
if (L(a)) {
|
||||||
|
const [w, G] = p, J = a.queryArc(0, G);
|
||||||
|
W(a, -1e-3, J, p);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[p, W, L]
|
||||||
|
), Z = A();
|
||||||
|
k(() => {
|
||||||
|
!Z.current && n && y && (Z.current = !0, o(y, !1).then((a) => $(a)));
|
||||||
|
}, [n, y, o, $]), k(() => () => {
|
||||||
|
C.stop();
|
||||||
|
}, [C]);
|
||||||
|
const ae = async () => {
|
||||||
|
N ? (C.stop(), $(x)) : await Y();
|
||||||
|
}, se = P || z;
|
||||||
|
return Ne({
|
||||||
|
pattern: x,
|
||||||
|
started: L(x) && N,
|
||||||
|
getTime: () => C.now(),
|
||||||
|
drawTime: p,
|
||||||
|
onDraw: W
|
||||||
|
}), {
|
||||||
|
id: D,
|
||||||
|
canvasId: i,
|
||||||
|
code: y,
|
||||||
|
setCode: S,
|
||||||
|
error: se,
|
||||||
|
schedulerError: P,
|
||||||
|
scheduler: C,
|
||||||
|
evalError: z,
|
||||||
|
evaluate: o,
|
||||||
|
activateCode: Y,
|
||||||
|
activeCode: V,
|
||||||
|
isDirty: K,
|
||||||
|
pattern: x,
|
||||||
|
started: N,
|
||||||
|
start: v,
|
||||||
|
stop: j,
|
||||||
|
pause: U,
|
||||||
|
togglePlay: ae
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function Re() {
|
||||||
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
|
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
|
||||||
}
|
}
|
||||||
function $({ type: e }) {
|
function ee({ type: e }) {
|
||||||
return /* @__PURE__ */ l.createElement("svg", {
|
return /* @__PURE__ */ d.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",
|
||||||
viewBox: "0 0 20 20",
|
viewBox: "0 0 20 20",
|
||||||
fill: "currentColor"
|
fill: "currentColor"
|
||||||
}, {
|
}, {
|
||||||
refresh: /* @__PURE__ */ l.createElement("path", {
|
refresh: /* @__PURE__ */ d.createElement("path", {
|
||||||
fillRule: "evenodd",
|
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",
|
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"
|
clipRule: "evenodd"
|
||||||
}),
|
}),
|
||||||
play: /* @__PURE__ */ l.createElement("path", {
|
play: /* @__PURE__ */ d.createElement("path", {
|
||||||
fillRule: "evenodd",
|
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",
|
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"
|
clipRule: "evenodd"
|
||||||
}),
|
}),
|
||||||
pause: /* @__PURE__ */ l.createElement("path", {
|
pause: /* @__PURE__ */ d.createElement("path", {
|
||||||
fillRule: "evenodd",
|
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",
|
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"
|
clipRule: "evenodd"
|
||||||
|
}),
|
||||||
|
stop: /* @__PURE__ */ d.createElement("path", {
|
||||||
|
fillRule: "evenodd",
|
||||||
|
d: "M2 10a8 8 0 1116 0 8 8 0 01-16 0zm5-2.25A.75.75 0 017.75 7h4.5a.75.75 0 01.75.75v4.5a.75.75 0 01-.75.75h-4.5a.75.75 0 01-.75-.75v-4.5z",
|
||||||
|
clipRule: "evenodd"
|
||||||
})
|
})
|
||||||
}[e]);
|
}[e]);
|
||||||
}
|
}
|
||||||
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 = {
|
const xe = "_container_3i85k_1", Le = "_header_3i85k_5", Pe = "_buttons_3i85k_9", qe = "_button_3i85k_9", ze = "_buttonDisabled_3i85k_17", He = "_error_3i85k_21", Se = "_body_3i85k_25", F = {
|
||||||
container: ye,
|
container: xe,
|
||||||
header: ke,
|
header: Le,
|
||||||
buttons: _e,
|
buttons: Pe,
|
||||||
button: Fe,
|
button: qe,
|
||||||
buttonDisabled: Ce,
|
buttonDisabled: ze,
|
||||||
error: Ne,
|
error: He,
|
||||||
body: xe
|
body: Se
|
||||||
}, Me = () => ce().currentTime;
|
}, Ve = () => he().currentTime;
|
||||||
function je({ tune: e, hideOutsideView: r = !1, enableKeyboard: t, withCanvas: a = !1, canvasHeight: o = 200 }) {
|
function Ze({ tune: e, hideOutsideView: t = !1, enableKeyboard: r, drawTime: n, punchcard: l, canvasHeight: m = 200 }) {
|
||||||
const {
|
n = n || (l ? [0, 4] : void 0);
|
||||||
code: i,
|
const f = !!n, c = E(
|
||||||
setCode: u,
|
n ? (o) => document.querySelector("#" + o)?.getContext("2d") : null,
|
||||||
evaluate: m,
|
[n]
|
||||||
activateCode: n,
|
), {
|
||||||
error: d,
|
code: s,
|
||||||
isDirty: v,
|
setCode: g,
|
||||||
activeCode: h,
|
evaluate: b,
|
||||||
pattern: p,
|
activateCode: i,
|
||||||
started: F,
|
error: h,
|
||||||
scheduler: A,
|
isDirty: p,
|
||||||
togglePlay: P,
|
activeCode: D,
|
||||||
stop: D,
|
pattern: P,
|
||||||
canvasId: b,
|
started: R,
|
||||||
id: q
|
scheduler: z,
|
||||||
} = Ee({
|
togglePlay: H,
|
||||||
|
stop: y,
|
||||||
|
canvasId: S,
|
||||||
|
id: V
|
||||||
|
} = De({
|
||||||
initialCode: e,
|
initialCode: e,
|
||||||
defaultOutput: ae,
|
defaultOutput: me,
|
||||||
getTime: Me,
|
editPattern: (o) => l ? o.punchcard() : o,
|
||||||
editPattern: (c, f) => c.withContext((C) => ({ ...C, id: f }))
|
getTime: Ve,
|
||||||
});
|
evalOnMount: f,
|
||||||
ve({
|
drawContext: c,
|
||||||
pattern: p,
|
drawTime: n
|
||||||
started: a && F,
|
}), [q, x] = _(), [I, N] = ge({
|
||||||
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 [x, V] = w(), [I, R] = se({
|
|
||||||
threshold: 0.01
|
threshold: 0.01
|
||||||
}), L = k(), B = K(() => ((R || !r) && (L.current = !0), R || L.current), [R, r]);
|
}), O = A(), K = Q(() => ((N || !t) && (O.current = !0), N || O.current), [N, t]);
|
||||||
pe({
|
Me({
|
||||||
view: x,
|
view: q,
|
||||||
pattern: p,
|
pattern: P,
|
||||||
active: F && !h?.includes("strudel disable-highlighting"),
|
active: R && !D?.includes("strudel disable-highlighting"),
|
||||||
getTime: () => A.getPhase()
|
getTime: () => z.now()
|
||||||
}), G(() => {
|
}), te(() => {
|
||||||
if (t) {
|
if (r) {
|
||||||
const c = async (f) => {
|
const o = async (v) => {
|
||||||
(f.ctrlKey || f.altKey) && (f.code === "Enter" ? (f.preventDefault(), de(x), await n()) : f.code === "Period" && (D(), f.preventDefault()));
|
(v.ctrlKey || v.altKey) && (v.code === "Enter" ? (v.preventDefault(), we(q), await i()) : v.code === "Period" && (y(), v.preventDefault()));
|
||||||
};
|
};
|
||||||
return window.addEventListener("keydown", c, !0), () => window.removeEventListener("keydown", c, !0);
|
return window.addEventListener("keydown", o, !0), () => window.removeEventListener("keydown", o, !0);
|
||||||
}
|
}
|
||||||
}, [t, p, i, m, D, x]);
|
}, [r, P, s, b, y, q]);
|
||||||
const [H, M] = w([]);
|
const [L, C] = _([]);
|
||||||
return Ae(
|
return Oe(
|
||||||
N((c) => {
|
E((o) => {
|
||||||
const { data: f } = c.detail;
|
const { data: v } = o.detail;
|
||||||
f?.hap?.context?.id === q && M((O) => O.concat([c.detail]).slice(-10));
|
v?.hap?.context?.id === V && C((U) => U.concat([o.detail]).slice(-10));
|
||||||
}, [])
|
}, [])
|
||||||
), /* @__PURE__ */ l.createElement("div", {
|
), /* @__PURE__ */ d.createElement("div", {
|
||||||
className: E.container,
|
className: F.container,
|
||||||
ref: I
|
ref: I
|
||||||
}, /* @__PURE__ */ l.createElement("div", {
|
}, /* @__PURE__ */ d.createElement("div", {
|
||||||
className: E.header
|
className: F.header
|
||||||
}, /* @__PURE__ */ l.createElement("div", {
|
}, /* @__PURE__ */ d.createElement("div", {
|
||||||
className: E.buttons
|
className: F.buttons
|
||||||
}, /* @__PURE__ */ l.createElement("button", {
|
}, /* @__PURE__ */ d.createElement("button", {
|
||||||
className: W(E.button, F ? "sc-animate-pulse" : ""),
|
className: T(F.button, R ? "sc-animate-pulse" : ""),
|
||||||
onClick: () => P()
|
onClick: () => H()
|
||||||
}, /* @__PURE__ */ l.createElement($, {
|
}, /* @__PURE__ */ d.createElement(ee, {
|
||||||
type: F ? "pause" : "play"
|
type: R ? "stop" : "play"
|
||||||
})), /* @__PURE__ */ l.createElement("button", {
|
})), /* @__PURE__ */ d.createElement("button", {
|
||||||
className: W(v ? E.button : E.buttonDisabled),
|
className: T(p ? F.button : F.buttonDisabled),
|
||||||
onClick: () => n()
|
onClick: () => i()
|
||||||
}, /* @__PURE__ */ l.createElement($, {
|
}, /* @__PURE__ */ d.createElement(ee, {
|
||||||
type: "refresh"
|
type: "refresh"
|
||||||
}))), d && /* @__PURE__ */ l.createElement("div", {
|
}))), h && /* @__PURE__ */ d.createElement("div", {
|
||||||
className: E.error
|
className: F.error
|
||||||
}, d.message)), /* @__PURE__ */ l.createElement("div", {
|
}, h.message)), /* @__PURE__ */ d.createElement("div", {
|
||||||
className: E.body
|
className: F.body
|
||||||
}, B && /* @__PURE__ */ l.createElement(ge, {
|
}, K && /* @__PURE__ */ d.createElement(_e, {
|
||||||
value: i,
|
value: s,
|
||||||
onChange: u,
|
onChange: g,
|
||||||
onViewChanged: V
|
onViewChanged: x
|
||||||
})), a && /* @__PURE__ */ l.createElement("canvas", {
|
})), n && /* @__PURE__ */ d.createElement("canvas", {
|
||||||
id: b,
|
id: S,
|
||||||
className: "w-full pointer-events-none",
|
className: "w-full pointer-events-none",
|
||||||
height: o,
|
height: m,
|
||||||
ref: (c) => {
|
ref: (o) => {
|
||||||
c && c.width !== c.clientWidth && (c.width = c.clientWidth);
|
o && o.width !== o.clientWidth && (o.width = o.clientWidth);
|
||||||
}
|
}
|
||||||
}), !!H.length && /* @__PURE__ */ l.createElement("div", {
|
}), !!L.length && /* @__PURE__ */ d.createElement("div", {
|
||||||
className: "sc-bg-gray-800 sc-rounded-md sc-p-2"
|
className: "sc-bg-gray-800 sc-rounded-md sc-p-2"
|
||||||
}, H.map(({ message: c }, f) => /* @__PURE__ */ l.createElement("div", {
|
}, L.map(({ message: o }, v) => /* @__PURE__ */ d.createElement("div", {
|
||||||
key: f
|
key: v
|
||||||
}, c))));
|
}, o))));
|
||||||
}
|
}
|
||||||
function Ae(e) {
|
function Oe(e) {
|
||||||
De(ne.key, e);
|
Be(ve.key, e);
|
||||||
}
|
}
|
||||||
function De(e, r, t = !1) {
|
function Be(e, t, r = !1) {
|
||||||
_(() => (document.addEventListener(e, r, t), () => {
|
k(() => (document.addEventListener(e, t, r), () => {
|
||||||
document.removeEventListener(e, r, t);
|
document.removeEventListener(e, t, r);
|
||||||
}), [r]);
|
}), [t]);
|
||||||
}
|
}
|
||||||
const Ue = (e) => G(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
|
const Te = (e) => te(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
|
||||||
export {
|
export {
|
||||||
ge as CodeMirror,
|
_e as CodeMirror,
|
||||||
je as MiniRepl,
|
Ze as MiniRepl,
|
||||||
W as cx,
|
T as cx,
|
||||||
de as flash,
|
we as flash,
|
||||||
pe as useHighlighting,
|
Me as useHighlighting,
|
||||||
Ue as useKeydown,
|
Te as useKeydown,
|
||||||
be as usePostMessage,
|
Ce as usePostMessage,
|
||||||
Ee as useStrudel
|
De as useStrudel
|
||||||
};
|
};
|
||||||
|
|||||||
@ -26,6 +26,13 @@ export function Icon({ type }) {
|
|||||||
clipRule="evenodd"
|
clipRule="evenodd"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
stop: (
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M2 10a8 8 0 1116 0 8 8 0 01-16 0zm5-2.25A.75.75 0 017.75 7h4.5a.75.75 0 01.75.75v4.5a.75.75 0 01-.75.75h-4.5a.75.75 0 01-.75-.75v-4.5z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
),
|
||||||
}[type]
|
}[type]
|
||||||
}
|
}
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import { pianoroll } from '@strudel.cycles/core';
|
|
||||||
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
|
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||||
import React, { useLayoutEffect, useMemo, useRef, useState, useCallback, useEffect } from 'react';
|
import React, { useLayoutEffect, useMemo, useRef, useState, useCallback, useEffect } from 'react';
|
||||||
import { useInView } from 'react-hook-inview';
|
import { useInView } from 'react-hook-inview';
|
||||||
import 'tailwindcss/tailwind.css';
|
import 'tailwindcss/tailwind.css';
|
||||||
import cx from '../cx';
|
import cx from '../cx';
|
||||||
import useHighlighting from '../hooks/useHighlighting.mjs';
|
import useHighlighting from '../hooks/useHighlighting.mjs';
|
||||||
import usePatternFrame from '../hooks/usePatternFrame.mjs';
|
|
||||||
import useStrudel from '../hooks/useStrudel.mjs';
|
import useStrudel from '../hooks/useStrudel.mjs';
|
||||||
import CodeMirror6, { flash } from './CodeMirror6';
|
import CodeMirror6, { flash } from './CodeMirror6';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
@ -15,7 +13,13 @@ import { logger } from '@strudel.cycles/core';
|
|||||||
|
|
||||||
const getTime = () => getAudioContext().currentTime;
|
const getTime = () => getAudioContext().currentTime;
|
||||||
|
|
||||||
export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCanvas = false, canvasHeight = 200 }) {
|
export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, drawTime, punchcard, canvasHeight = 200 }) {
|
||||||
|
drawTime = drawTime || (punchcard ? [0, 4] : undefined);
|
||||||
|
const evalOnMount = !!drawTime;
|
||||||
|
const drawContext = useCallback(
|
||||||
|
!!drawTime ? (canvasId) => document.querySelector('#' + canvasId)?.getContext('2d') : null,
|
||||||
|
[drawTime],
|
||||||
|
);
|
||||||
const {
|
const {
|
||||||
code,
|
code,
|
||||||
setCode,
|
setCode,
|
||||||
@ -34,25 +38,13 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa
|
|||||||
} = useStrudel({
|
} = useStrudel({
|
||||||
initialCode: tune,
|
initialCode: tune,
|
||||||
defaultOutput: webaudioOutput,
|
defaultOutput: webaudioOutput,
|
||||||
|
editPattern: (pat) => (punchcard ? pat.punchcard() : pat),
|
||||||
getTime,
|
getTime,
|
||||||
editPattern: (pat, id) => {
|
evalOnMount,
|
||||||
return pat.withContext((ctx) => ({ ...ctx, id }));
|
drawContext,
|
||||||
},
|
drawTime,
|
||||||
});
|
});
|
||||||
|
|
||||||
usePatternFrame({
|
|
||||||
pattern,
|
|
||||||
started: withCanvas && started,
|
|
||||||
getTime: () => scheduler.now(),
|
|
||||||
onDraw: (time, haps) => {
|
|
||||||
const ctx = document.querySelector('#' + canvasId).getContext('2d');
|
|
||||||
pianoroll({ ctx, time, haps, autorange: 1, fold: 1, playhead: 1 });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/* useEffect(() => {
|
|
||||||
init && activateCode();
|
|
||||||
}, [init, activateCode]); */
|
|
||||||
const [view, setView] = useState();
|
const [view, setView] = useState();
|
||||||
const [ref, isVisible] = useInView({
|
const [ref, isVisible] = useInView({
|
||||||
threshold: 0.01,
|
threshold: 0.01,
|
||||||
@ -68,7 +60,7 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa
|
|||||||
view,
|
view,
|
||||||
pattern,
|
pattern,
|
||||||
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
||||||
getTime: () => scheduler.getPhase(),
|
getTime: () => scheduler.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// set active pattern on ctrl+enter
|
// set active pattern on ctrl+enter
|
||||||
@ -110,7 +102,7 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa
|
|||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<div className={styles.buttons}>
|
<div className={styles.buttons}>
|
||||||
<button className={cx(styles.button, started ? 'sc-animate-pulse' : '')} onClick={() => togglePlay()}>
|
<button className={cx(styles.button, started ? 'sc-animate-pulse' : '')} onClick={() => togglePlay()}>
|
||||||
<Icon type={started ? 'pause' : 'play'} />
|
<Icon type={started ? 'stop' : 'play'} />
|
||||||
</button>
|
</button>
|
||||||
<button className={cx(isDirty ? styles.button : styles.buttonDisabled)} onClick={() => activateCode()}>
|
<button className={cx(isDirty ? styles.button : styles.buttonDisabled)} onClick={() => activateCode()}>
|
||||||
<Icon type="refresh" />
|
<Icon type="refresh" />
|
||||||
@ -121,7 +113,7 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, withCa
|
|||||||
<div className={styles.body}>
|
<div className={styles.body}>
|
||||||
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} />}
|
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} />}
|
||||||
</div>
|
</div>
|
||||||
{withCanvas && (
|
{drawTime && (
|
||||||
<canvas
|
<canvas
|
||||||
id={canvasId}
|
id={canvasId}
|
||||||
className="w-full pointer-events-none"
|
className="w-full pointer-events-none"
|
||||||
|
|||||||
@ -3,16 +3,17 @@ import { setHighlights } from '../components/CodeMirror6';
|
|||||||
|
|
||||||
function useHighlighting({ view, pattern, active, getTime }) {
|
function useHighlighting({ view, pattern, active, getTime }) {
|
||||||
const highlights = useRef([]);
|
const highlights = useRef([]);
|
||||||
const lastEnd = useRef();
|
const lastEnd = useRef(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (view) {
|
if (view) {
|
||||||
if (pattern && active) {
|
if (pattern && active) {
|
||||||
|
lastEnd.current = 0;
|
||||||
let frame = requestAnimationFrame(function updateHighlights() {
|
let frame = requestAnimationFrame(function updateHighlights() {
|
||||||
try {
|
try {
|
||||||
const audioTime = getTime();
|
const audioTime = getTime();
|
||||||
// force min framerate of 10 fps => fixes crash on tab refocus, where lastEnd could be far away
|
// force min framerate of 10 fps => fixes crash on tab refocus, where lastEnd could be far away
|
||||||
// see https://github.com/tidalcycles/strudel/issues/108
|
// see https://github.com/tidalcycles/strudel/issues/108
|
||||||
const begin = Math.max(lastEnd.current || audioTime, audioTime - 1 / 10, 0); // negative time seems buggy
|
const begin = Math.max(lastEnd.current ?? audioTime, audioTime - 1 / 10, -0.01); // negative time seems buggy
|
||||||
const span = [begin, audioTime + 1 / 60];
|
const span = [begin, audioTime + 1 / 60];
|
||||||
lastEnd.current = span[1];
|
lastEnd.current = span[1];
|
||||||
highlights.current = highlights.current.filter((hap) => hap.whole.end > audioTime); // keep only highlights that are still active
|
highlights.current = highlights.current.filter((hap) => hap.whole.end > audioTime); // keep only highlights that are still active
|
||||||
|
|||||||
@ -2,23 +2,32 @@ import { useCallback, useEffect, useRef } from 'react';
|
|||||||
import 'tailwindcss/tailwind.css';
|
import 'tailwindcss/tailwind.css';
|
||||||
import useFrame from '../hooks/useFrame.mjs';
|
import useFrame from '../hooks/useFrame.mjs';
|
||||||
|
|
||||||
function usePatternFrame({ pattern, started, getTime, onDraw }) {
|
function usePatternFrame({ pattern, started, getTime, onDraw, drawTime = [-2, 2] }) {
|
||||||
|
let [lookbehind, lookahead] = drawTime;
|
||||||
|
lookbehind = Math.abs(lookbehind);
|
||||||
let visibleHaps = useRef([]);
|
let visibleHaps = useRef([]);
|
||||||
let lastFrame = useRef(null);
|
let lastFrame = useRef(null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (pattern && started) {
|
||||||
|
const t = getTime();
|
||||||
|
const futureHaps = pattern.queryArc(Math.max(t, 0), t + lookahead + 0.1); // +0.1 = workaround for weird holes in query..
|
||||||
|
visibleHaps.current = visibleHaps.current.filter((h) => h.whole.begin < t);
|
||||||
|
visibleHaps.current = visibleHaps.current.concat(futureHaps);
|
||||||
|
}
|
||||||
|
}, [pattern, started]);
|
||||||
const { start: startFrame, stop: stopFrame } = useFrame(
|
const { start: startFrame, stop: stopFrame } = useFrame(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
const phase = getTime();
|
const phase = getTime() + lookahead;
|
||||||
if (lastFrame.current === null) {
|
if (lastFrame.current === null) {
|
||||||
lastFrame.current = phase;
|
lastFrame.current = phase;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const haps = pattern.queryArc(Math.max(lastFrame.current, phase - 1 / 10), phase);
|
const haps = pattern.queryArc(Math.max(lastFrame.current, phase - 1 / 10), phase);
|
||||||
const cycles = 4;
|
|
||||||
lastFrame.current = phase;
|
lastFrame.current = phase;
|
||||||
visibleHaps.current = (visibleHaps.current || [])
|
visibleHaps.current = (visibleHaps.current || [])
|
||||||
.filter((h) => h.whole.end > phase - cycles) // in frame
|
.filter((h) => h.whole.end >= phase - lookbehind - lookahead) // in frame
|
||||||
.concat(haps.filter((h) => h.hasOnset()));
|
.concat(haps.filter((h) => h.hasOnset()));
|
||||||
onDraw(phase, visibleHaps.current);
|
onDraw(pattern, phase - lookahead, visibleHaps.current, drawTime);
|
||||||
}, [pattern]),
|
}, [pattern]),
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -29,6 +38,11 @@ function usePatternFrame({ pattern, started, getTime, onDraw }) {
|
|||||||
stopFrame();
|
stopFrame();
|
||||||
}
|
}
|
||||||
}, [started]);
|
}, [started]);
|
||||||
|
return {
|
||||||
|
clear: () => {
|
||||||
|
visibleHaps.current = [];
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default usePatternFrame;
|
export default usePatternFrame;
|
||||||
|
|||||||
@ -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 usePatternFrame from './usePatternFrame';
|
||||||
import usePostMessage from './usePostMessage.mjs';
|
import usePostMessage from './usePostMessage.mjs';
|
||||||
|
|
||||||
function useStrudel({
|
function useStrudel({
|
||||||
@ -16,6 +17,8 @@ function useStrudel({
|
|||||||
onEvalError,
|
onEvalError,
|
||||||
onToggle,
|
onToggle,
|
||||||
canvasId,
|
canvasId,
|
||||||
|
drawContext,
|
||||||
|
drawTime = [-2, 2],
|
||||||
}) {
|
}) {
|
||||||
const id = useMemo(() => s4(), []);
|
const id = useMemo(() => s4(), []);
|
||||||
canvasId = canvasId || `canvas-${id}`;
|
canvasId = canvasId || `canvas-${id}`;
|
||||||
@ -27,6 +30,7 @@ function useStrudel({
|
|||||||
const [pattern, setPattern] = useState();
|
const [pattern, setPattern] = useState();
|
||||||
const [started, setStarted] = useState(false);
|
const [started, setStarted] = useState(false);
|
||||||
const isDirty = code !== activeCode;
|
const isDirty = code !== activeCode;
|
||||||
|
const shouldPaint = useCallback((pat) => !!(pat?.context?.onPaint && drawContext), [drawContext]);
|
||||||
|
|
||||||
// TODO: make sure this hook reruns when scheduler.started changes
|
// TODO: make sure this hook reruns when scheduler.started changes
|
||||||
const { scheduler, evaluate, start, stop, pause } = useMemo(
|
const { scheduler, evaluate, start, stop, pause } = useMemo(
|
||||||
@ -40,12 +44,13 @@ function useStrudel({
|
|||||||
onEvalError?.(err);
|
onEvalError?.(err);
|
||||||
},
|
},
|
||||||
getTime,
|
getTime,
|
||||||
|
drawContext,
|
||||||
transpiler,
|
transpiler,
|
||||||
|
editPattern,
|
||||||
beforeEval: ({ code }) => {
|
beforeEval: ({ code }) => {
|
||||||
setCode(code);
|
setCode(code);
|
||||||
beforeEval?.();
|
beforeEval?.();
|
||||||
},
|
},
|
||||||
editPattern: editPattern ? (pat) => editPattern(pat, id) : undefined,
|
|
||||||
afterEval: ({ pattern: _pattern, code }) => {
|
afterEval: ({ pattern: _pattern, code }) => {
|
||||||
setActiveCode(code);
|
setActiveCode(code);
|
||||||
setPattern(_pattern);
|
setPattern(_pattern);
|
||||||
@ -71,19 +76,41 @@ function useStrudel({
|
|||||||
});
|
});
|
||||||
const activateCode = useCallback(
|
const activateCode = useCallback(
|
||||||
async (autostart = true) => {
|
async (autostart = true) => {
|
||||||
await evaluate(code, autostart);
|
const res = await evaluate(code, autostart);
|
||||||
broadcast({ type: 'start', from: id });
|
broadcast({ type: 'start', from: id });
|
||||||
|
return res;
|
||||||
},
|
},
|
||||||
[evaluate, code],
|
[evaluate, code],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onDraw = useCallback(
|
||||||
|
(pattern, time, haps, drawTime) => {
|
||||||
|
const { onPaint } = pattern.context || {};
|
||||||
|
const ctx = typeof drawContext === 'function' ? drawContext(canvasId) : drawContext;
|
||||||
|
onPaint?.(ctx, time, haps, drawTime);
|
||||||
|
},
|
||||||
|
[drawContext, canvasId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const drawFirstFrame = useCallback(
|
||||||
|
(pat) => {
|
||||||
|
if (shouldPaint(pat)) {
|
||||||
|
const [_, lookahead] = drawTime;
|
||||||
|
const haps = pat.queryArc(0, lookahead);
|
||||||
|
// draw at -0.001 to avoid activating haps at 0
|
||||||
|
onDraw(pat, -0.001, haps, drawTime);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[drawTime, onDraw, shouldPaint],
|
||||||
|
);
|
||||||
|
|
||||||
const inited = useRef();
|
const inited = useRef();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!inited.current && evalOnMount && code) {
|
if (!inited.current && evalOnMount && code) {
|
||||||
inited.current = true;
|
inited.current = true;
|
||||||
activateCode();
|
evaluate(code, false).then((pat) => drawFirstFrame(pat));
|
||||||
}
|
}
|
||||||
}, [activateCode, evalOnMount, code]);
|
}, [evalOnMount, code, evaluate, drawFirstFrame]);
|
||||||
|
|
||||||
// this will stop the scheduler when hot reloading in development
|
// this will stop the scheduler when hot reloading in development
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -94,12 +121,22 @@ function useStrudel({
|
|||||||
|
|
||||||
const togglePlay = async () => {
|
const togglePlay = async () => {
|
||||||
if (started) {
|
if (started) {
|
||||||
scheduler.pause();
|
scheduler.stop();
|
||||||
|
drawFirstFrame(pattern);
|
||||||
} else {
|
} else {
|
||||||
await activateCode();
|
await activateCode();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const error = schedulerError || evalError;
|
const error = schedulerError || evalError;
|
||||||
|
|
||||||
|
usePatternFrame({
|
||||||
|
pattern,
|
||||||
|
started: shouldPaint(pattern) && started,
|
||||||
|
getTime: () => scheduler.now(),
|
||||||
|
drawTime,
|
||||||
|
onDraw,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
canvasId,
|
canvasId,
|
||||||
|
|||||||
@ -1736,27 +1736,6 @@ exports[`runs examples > example "fastGap" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "fastcat" example index 0 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/3 | e5 ]",
|
|
||||||
"[ 1/3 → 2/3 | b4 ]",
|
|
||||||
"[ 2/3 → 5/6 | d5 ]",
|
|
||||||
"[ 5/6 → 1/1 | c5 ]",
|
|
||||||
"[ 1/1 → 4/3 | e5 ]",
|
|
||||||
"[ 4/3 → 5/3 | b4 ]",
|
|
||||||
"[ 5/3 → 11/6 | d5 ]",
|
|
||||||
"[ 11/6 → 2/1 | c5 ]",
|
|
||||||
"[ 2/1 → 7/3 | e5 ]",
|
|
||||||
"[ 7/3 → 8/3 | b4 ]",
|
|
||||||
"[ 8/3 → 17/6 | d5 ]",
|
|
||||||
"[ 17/6 → 3/1 | c5 ]",
|
|
||||||
"[ 3/1 → 10/3 | e5 ]",
|
|
||||||
"[ 10/3 → 11/3 | b4 ]",
|
|
||||||
"[ 11/3 → 23/6 | d5 ]",
|
|
||||||
"[ 23/6 → 4/1 | c5 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "firstOf" example index 0 1`] = `
|
exports[`runs examples > example "firstOf" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 3/4 → 1/1 | note:c3 ]",
|
"[ 3/4 → 1/1 | note:c3 ]",
|
||||||
@ -3139,6 +3118,27 @@ exports[`runs examples > example "round" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "run" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/4 | note:C4 ]",
|
||||||
|
"[ 1/4 → 1/2 | note:D4 ]",
|
||||||
|
"[ 1/2 → 3/4 | note:E4 ]",
|
||||||
|
"[ 3/4 → 1/1 | note:F4 ]",
|
||||||
|
"[ 1/1 → 5/4 | note:C4 ]",
|
||||||
|
"[ 5/4 → 3/2 | note:D4 ]",
|
||||||
|
"[ 3/2 → 7/4 | note:E4 ]",
|
||||||
|
"[ 7/4 → 2/1 | note:F4 ]",
|
||||||
|
"[ 2/1 → 9/4 | note:C4 ]",
|
||||||
|
"[ 9/4 → 5/2 | note:D4 ]",
|
||||||
|
"[ 5/2 → 11/4 | note:E4 ]",
|
||||||
|
"[ 11/4 → 3/1 | note:F4 ]",
|
||||||
|
"[ 3/1 → 13/4 | note:C4 ]",
|
||||||
|
"[ 13/4 → 7/2 | note:D4 ]",
|
||||||
|
"[ 7/2 → 15/4 | note:E4 ]",
|
||||||
|
"[ 15/4 → 4/1 | note:F4 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "s" example index 0 1`] = `
|
exports[`runs examples > example "s" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/2 | s:bd ]",
|
"[ 0/1 → 1/2 | s:bd ]",
|
||||||
@ -3484,19 +3484,6 @@ exports[`runs examples > example "sine" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "size" example index 0 1`] = `
|
|
||||||
[
|
|
||||||
"[ 0/1 → 1/2 | s:bd room:0.8 size:0 ]",
|
|
||||||
"[ 1/2 → 1/1 | s:sd room:0.8 size:0 ]",
|
|
||||||
"[ 1/1 → 3/2 | s:bd room:0.8 size:1 ]",
|
|
||||||
"[ 3/2 → 2/1 | s:sd room:0.8 size:1 ]",
|
|
||||||
"[ 2/1 → 5/2 | s:bd room:0.8 size:2 ]",
|
|
||||||
"[ 5/2 → 3/1 | s:sd room:0.8 size:2 ]",
|
|
||||||
"[ 3/1 → 7/2 | s:bd room:0.8 size:4 ]",
|
|
||||||
"[ 7/2 → 4/1 | s:sd room:0.8 size:4 ]",
|
|
||||||
]
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`runs examples > example "slow" example index 0 1`] = `
|
exports[`runs examples > example "slow" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/1 | s:bd ]",
|
"[ 0/1 → 1/1 | s:bd ]",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@ import { MiniRepl } from './MiniRepl';
|
|||||||
|
|
||||||
const getTag = (title, item) => item.tags?.find((t) => t.title === title)?.text;
|
const getTag = (title, item) => item.tags?.find((t) => t.title === title)?.text;
|
||||||
|
|
||||||
export function JsDoc({ name, h = 3, hideDescription }) {
|
export function JsDoc({ name, h = 3, hideDescription, punchcard, canvasHeight }) {
|
||||||
const item = docs[name];
|
const item = docs[name];
|
||||||
if (!item) {
|
if (!item) {
|
||||||
console.warn('Not found: ' + name);
|
console.warn('Not found: ' + name);
|
||||||
@ -40,7 +40,7 @@ export function JsDoc({ name, h = 3, hideDescription }) {
|
|||||||
{item.examples?.length ? (
|
{item.examples?.length ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{item.examples?.map((example, k) => (
|
{item.examples?.map((example, k) => (
|
||||||
<MiniRepl tune={example} key={k} />
|
<MiniRepl tune={example} key={k} {...{ punchcard, canvasHeight }} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -3,8 +3,9 @@ import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { prebake } from '../repl/prebake';
|
import { prebake } from '../repl/prebake';
|
||||||
|
|
||||||
|
let modules;
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
evalScope(
|
modules = evalScope(
|
||||||
controls,
|
controls,
|
||||||
import('@strudel.cycles/core'),
|
import('@strudel.cycles/core'),
|
||||||
// import('@strudel.cycles/tone'),
|
// import('@strudel.cycles/tone'),
|
||||||
@ -22,14 +23,20 @@ if (typeof window !== 'undefined') {
|
|||||||
prebake();
|
prebake();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MiniRepl({ tune, withCanvas }) {
|
export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
|
||||||
const [Repl, setRepl] = useState();
|
const [Repl, setRepl] = useState();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// we have to load this package on the client
|
// we have to load this package on the client
|
||||||
// because codemirror throws an error on the server
|
// because codemirror throws an error on the server
|
||||||
import('@strudel.cycles/react').then((res) => {
|
Promise.all([import('@strudel.cycles/react'), modules])
|
||||||
setRepl(() => res.MiniRepl);
|
.then(([res]) => setRepl(() => res.MiniRepl))
|
||||||
});
|
.catch((err) => console.error(err));
|
||||||
}, []);
|
}, []);
|
||||||
return Repl ? <Repl tune={tune} hideOutsideView={true} withCanvas={withCanvas} /> : <pre>{tune}</pre>;
|
return Repl ? (
|
||||||
|
<div className="mb-4">
|
||||||
|
<Repl tune={tune} hideOutsideView={true} drawTime={drawTime} punchcard={punchcard} canvasHeight={canvasHeight} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<pre>{tune}</pre>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,3 +61,7 @@ As a chained function:
|
|||||||
## silence
|
## silence
|
||||||
|
|
||||||
<JsDoc client:idle name="silence" h={0} />
|
<JsDoc client:idle name="silence" h={0} />
|
||||||
|
|
||||||
|
## run
|
||||||
|
|
||||||
|
<JsDoc client:idle name="run" h={0} punchcard />
|
||||||
|
|||||||
@ -28,7 +28,7 @@ Strudel however runs directly in your web browser, does not require any custom s
|
|||||||
The main place to actually make music with Strudel is the [Strudel REPL](https://strudel.tidalcycles.org/) ([what is a REPL?](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)), but in these pages you will also encounter interactive "MiniREPLs" where you can listen to and edit Strudel patterns.
|
The main place to actually make music with Strudel is the [Strudel REPL](https://strudel.tidalcycles.org/) ([what is a REPL?](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)), but in these pages you will also encounter interactive "MiniREPLs" where you can listen to and edit Strudel patterns.
|
||||||
Try clicking the play icon below:
|
Try clicking the play icon below:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`s("bd sd")`} />
|
<MiniRepl client:idle tune={`s("bd sd")`} punchcard />
|
||||||
|
|
||||||
Then edit the text so it reads `s("bd sd cp hh")` and click the refresh icon.
|
Then edit the text so it reads `s("bd sd cp hh")` and click the refresh icon.
|
||||||
Congratulations, you have now live coded your first Strudel pattern!
|
Congratulations, you have now live coded your first Strudel pattern!
|
||||||
@ -54,13 +54,13 @@ Alternatively, you can get a taste of what Strudel can do by clicking play on th
|
|||||||
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'],
|
||||||
}, 'github:tidalcycles/Dirt-Samples/master/');
|
}, 'github:tidalcycles/Dirt-Samples/master/');
|
||||||
stack(
|
stack(
|
||||||
s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums
|
s("bd,[~ <sd!3 sd(3,4,2)>],hh*8") // drums
|
||||||
.speed(perlin.range(.7,.9)) // random sample speed variation
|
.speed(perlin.range(.7,.9)) // random sample speed variation
|
||||||
,"<a1 b1\*2 a1(3,8) e2>" // bassline
|
,"<a1 b1\*2 a1(3,8) e2>" // bassline
|
||||||
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
|
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
|
||||||
.add(perlin.range(0,.5)) // random pitch variation
|
.add(perlin.range(0,.5)) // random pitch variation
|
||||||
.superimpose(add(.05)) // add second, slightly detuned voice
|
.superimpose(add(.05)) // add second, slightly detuned voice
|
||||||
.n() // wrap in "n"
|
.note() // wrap in "note"
|
||||||
.decay(.15).sustain(0) // make each note of equal length
|
.decay(.15).sustain(0) // make each note of equal length
|
||||||
.s('sawtooth') // waveform
|
.s('sawtooth') // waveform
|
||||||
.gain(.4) // turn down
|
.gain(.4) // turn down
|
||||||
@ -68,7 +68,7 @@ s("bd,[~ <sd!3 sd(3,4,2)>],hh(3,4)") // drums
|
|||||||
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand') // chords
|
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand') // chords
|
||||||
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||||
.add(perlin.range(0,.5)) // random pitch variation
|
.add(perlin.range(0,.5)) // random pitch variation
|
||||||
.n() // wrap in "n"
|
.note() // wrap in "note"
|
||||||
.s('sawtooth') // waveform
|
.s('sawtooth') // waveform
|
||||||
.gain(.16) // turn down
|
.gain(.16) // turn down
|
||||||
.cutoff(500) // fixed cutoff
|
.cutoff(500) // fixed cutoff
|
||||||
|
|||||||
@ -51,12 +51,12 @@ If you do just want to get a regular string that is _not_ parsed as mini-notatio
|
|||||||
|
|
||||||
We can play more notes by separating them with spaces:
|
We can play more notes by separating them with spaces:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("e5 b4 d5 c5")`} withCanvas />
|
<MiniRepl client:idle tune={`note("c e g b")`} punchcard />
|
||||||
|
|
||||||
Here, those four notes are squashed into one cycle, so each note is a quarter second long.
|
Here, those four notes are squashed into one cycle, so each note is a quarter second long.
|
||||||
Try adding or removing notes and notice how the tempo changes!
|
Try adding or removing notes and notice how the tempo changes!
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("e5 b4 d5 c5 e5 b4 d5 c5")`} withCanvas />
|
<MiniRepl client:idle tune={`note("c d e f g a b")`} punchcard />
|
||||||
|
|
||||||
Note that the overall duration of time does not change, and instead each note length descreases.
|
Note that the overall duration of time does not change, and instead each note length descreases.
|
||||||
This is a key idea, as it illustrates the 'Cycle' in TidalCycles!
|
This is a key idea, as it illustrates the 'Cycle' in TidalCycles!
|
||||||
@ -72,28 +72,28 @@ But, it will begin to make sense as we go through more elements of mini-notation
|
|||||||
|
|
||||||
We can slow the sequence down by enclosing it in brackets and dividing it by a number (`/2`):
|
We can slow the sequence down by enclosing it in brackets and dividing it by a number (`/2`):
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]/2")`} />
|
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]/2")`} punchcard />
|
||||||
|
|
||||||
The division by two means that the sequence will be played over the course of two cycles.
|
The division by two means that the sequence will be played over the course of two cycles.
|
||||||
You can also use decimal numbers for any tempo you like (`/2.75`).
|
You can also use decimal numbers for any tempo you like (`/2.75`).
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]/2.75")`} />
|
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]/2.75")`} punchcard />
|
||||||
|
|
||||||
## Angle Brackets
|
## Angle Brackets
|
||||||
|
|
||||||
Using angle brackets `<>`, we can define the sequence length based on the number of events:
|
Using angle brackets `<>`, we can define the sequence length based on the number of events:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("<e5 b4 d5 c5>")`} />
|
<MiniRepl client:idle tune={`note("<e5 b4 d5 c5>")`} punchcard />
|
||||||
|
|
||||||
The above snippet is the same as:
|
The above snippet is the same as:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]/4")`} />
|
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]/4")`} punchcard />
|
||||||
|
|
||||||
The advantage of the angle brackets, is that we can add more events without needing to change the number at the end.
|
The advantage of the angle brackets, is that we can add more events without needing to change the number at the end.
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("<e5 b4 d5 c5 e5>")`} />
|
<MiniRepl client:idle tune={`note("<e5 b4 d5 c5 e5>")`} punchcard />
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("<e5 b4 d5 c5 e5 b4>")`} />
|
<MiniRepl client:idle tune={`note("<e5 b4 d5 c5 e5 b4>")`} punchcard />
|
||||||
|
|
||||||
This is more similar to traditional music sequencers and piano rolls, where adding a note increases the perceived overall duration.
|
This is more similar to traditional music sequencers and piano rolls, where adding a note increases the perceived overall duration.
|
||||||
|
|
||||||
@ -101,15 +101,13 @@ This is more similar to traditional music sequencers and piano rolls, where addi
|
|||||||
|
|
||||||
Contrary to division, a sequence can be sped up by multiplying it by a number using the asterisk symbol (`*`):
|
Contrary to division, a sequence can be sped up by multiplying it by a number using the asterisk symbol (`*`):
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]*2")`} />
|
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]*2")`} punchcard />
|
||||||
|
|
||||||
The multiplication by two here means that the sequence will play twice a cycle.
|
The multiplication by two here means that the sequence will play twice a cycle.
|
||||||
|
|
||||||
As with divisions, multiplications can be decimal (`*2.75`):
|
As with divisions, multiplications can be decimal (`*2.75`):
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]*2.75")`} />
|
<MiniRepl client:idle tune={`note("[e5 b4 d5 c5]*2.75")`} punchcard />
|
||||||
|
|
||||||
Actually, this is not true, but this will be [fixed](https://github.com/tidalcycles/strudel/issues/314) :)
|
|
||||||
|
|
||||||
## Subdividing time with bracket nesting
|
## Subdividing time with bracket nesting
|
||||||
|
|
||||||
@ -133,7 +131,7 @@ Well, what this means is that in TidalCycles, not only can you divide time any w
|
|||||||
|
|
||||||
The "~" represents a rest, and will create silence between other events:
|
The "~" represents a rest, and will create silence between other events:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("[b4 [~ c5] d5 e5]")`} />
|
<MiniRepl client:idle tune={`note("[b4 [~ c5] d5 e5]")`} punchcard />
|
||||||
|
|
||||||
## Parallel / polyphony
|
## Parallel / polyphony
|
||||||
|
|
||||||
@ -141,17 +139,17 @@ Using commas, we can play chords.
|
|||||||
The following are the same:
|
The following are the same:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("[g3,b3,e4]")`} />
|
<MiniRepl client:idle tune={`note("[g3,b3,e4]")`} />
|
||||||
<MiniRepl client:idle tune={`note("g3,b3,e4")`} />
|
<MiniRepl client:idle tune={`note("g3,b3,e4")`} punchcard canvasHeight={80} />
|
||||||
|
|
||||||
But to play multiple chords in a sequence, we have to wrap them in brackets:
|
But to play multiple chords in a sequence, we have to wrap them in brackets:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4] [b3,e4,g4]>")`} />
|
<MiniRepl client:idle tune={`note("<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4] [b3,e4,g4]>")`} punchcard />
|
||||||
|
|
||||||
## Elongation
|
## Elongation
|
||||||
|
|
||||||
With the "@" symbol, we can specify temporal "weight" of a sequence child:
|
With the "@" symbol, we can specify temporal "weight" of a sequence child:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]@2 [a3,c3,e4] [b3,d3,f#4]>")`} />
|
<MiniRepl client:idle tune={`note("<[g3,b3,e4]@2 [a3,c3,e4] [b3,d3,f#4]>")`} punchcard />
|
||||||
|
|
||||||
Here, the first chord has a weight of 2, making it twice the length of the other chords. The default weight is 1.
|
Here, the first chord has a weight of 2, making it twice the length of the other chords. The default weight is 1.
|
||||||
|
|
||||||
@ -159,7 +157,7 @@ Here, the first chord has a weight of 2, making it twice the length of the other
|
|||||||
|
|
||||||
Using "!" we can repeat without speeding up:
|
Using "!" we can repeat without speeding up:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>")`} />
|
<MiniRepl client:idle tune={`note("<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>")`} punchcard />
|
||||||
|
|
||||||
In essence, the `x!n` is like a shortcut for `[x*n]@n`.
|
In essence, the `x!n` is like a shortcut for `[x*n]@n`.
|
||||||
|
|
||||||
@ -181,24 +179,24 @@ Using round brackets after an event, we can create rhythmical sub-divisions base
|
|||||||
This algorithm can be found in many different types of music software, and is often referred to as a [Euclidean rhythm](https://en.wikipedia.org/wiki/Euclidean_rhythm) sequencer, after computer scientist Godfriend Toussaint.
|
This algorithm can be found in many different types of music software, and is often referred to as a [Euclidean rhythm](https://en.wikipedia.org/wiki/Euclidean_rhythm) sequencer, after computer scientist Godfriend Toussaint.
|
||||||
Why is it interesting? Well, consider the following simple example:
|
Why is it interesting? Well, consider the following simple example:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`s("bd(3,8,0)")`} />
|
<MiniRepl client:idle tune={`s("bd(3,8,0)")`} punchcard canvasHeight={50} />
|
||||||
|
|
||||||
Sound familiar?
|
Sound familiar?
|
||||||
This is a popular Euclidian rhythm going by various names, such as "Pop Clave".
|
This is a popular Euclidian rhythm going by various names, such as "Pop Clave".
|
||||||
These rhythms can be found in all musical cultures, and the Euclidian rhythm algorithm allows us to express them extremely easily.
|
These rhythms can be found in all musical cultures, and the Euclidian rhythm algorithm allows us to express them extremely easily.
|
||||||
Writing this rhythm out in full require describing:
|
Writing this rhythm out in full require describing:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`s("bd ~ ~ bd ~ ~ bd ~")`} />
|
<MiniRepl client:idle tune={`s("bd ~ ~ bd ~ ~ bd ~")`} punchcard canvasHeight={50} />
|
||||||
|
|
||||||
But using the Euclidian rhythm notation, we only need to express "3 beats over 8 segments, starting on position 1".
|
But using the Euclidian rhythm notation, we only need to express "3 beats over 8 segments, starting on position 1".
|
||||||
|
|
||||||
This makes it easy to write patterns with interesting rhythmic structures and variations that still sound familiar:
|
This makes it easy to write patterns with interesting rhythmic structures and variations that still sound familiar:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("e5(2,8) b4(3,8) d5(2,8) c5(3,8)").slow(4)`} />
|
<MiniRepl client:idle tune={`note("e5(2,8) b4(3,8) d5(2,8) c5(3,8)").slow(4)`} punchcard canvasHeight={50} />
|
||||||
|
|
||||||
Note that since the example above does not use the third `offset` parameter, it can be written simply as `"(3,8)"`.
|
Note that since the example above does not use the third `offset` parameter, it can be written simply as `"(3,8)"`.
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`s("bd(3,8)")`} />
|
<MiniRepl client:idle tune={`s("bd(3,8)")`} punchcard canvasHeight={50} />
|
||||||
|
|
||||||
Let's look at those three parameters in detail.
|
Let's look at those three parameters in detail.
|
||||||
|
|
||||||
@ -207,26 +205,26 @@ Let's look at those three parameters in detail.
|
|||||||
`beats`: the first parameter controls how may beats will be played.
|
`beats`: the first parameter controls how may beats will be played.
|
||||||
Compare these:
|
Compare these:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`s("bd(2,8)")`} />
|
<MiniRepl client:idle tune={`s("bd(2,8)")`} punchcard canvasHeight={50} />
|
||||||
<MiniRepl client:idle tune={`s("bd(5,8)")`} />
|
<MiniRepl client:idle tune={`s("bd(5,8)")`} punchcard canvasHeight={50} />
|
||||||
<MiniRepl client:idle tune={`s("bd(7,8)")`} />
|
<MiniRepl client:idle tune={`s("bd(7,8)")`} punchcard canvasHeight={50} />
|
||||||
|
|
||||||
### Segments
|
### Segments
|
||||||
|
|
||||||
`segments`: the second parameter controls the total amount of segments the beats will be distributed over:
|
`segments`: the second parameter controls the total amount of segments the beats will be distributed over:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`s("bd(3,4)")`} />
|
<MiniRepl client:idle tune={`s("bd(3,4)")`} punchcard canvasHeight={50} />
|
||||||
<MiniRepl client:idle tune={`s("bd(3,8)")`} />
|
<MiniRepl client:idle tune={`s("bd(3,8)")`} punchcard canvasHeight={50} />
|
||||||
<MiniRepl client:idle tune={`s("bd(3,13)")`} />
|
<MiniRepl client:idle tune={`s("bd(3,13)")`} punchcard canvasHeight={50} />
|
||||||
|
|
||||||
### Offsets
|
### Offsets
|
||||||
|
|
||||||
`offset`: the third (optional) parameter controls the starting position for distributing the beats.
|
`offset`: the third (optional) parameter controls the starting position for distributing the beats.
|
||||||
We need a secondary rhythm to hear the difference:
|
We need a secondary rhythm to hear the difference:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`s("bd(3,8,0), hh cp")`} />
|
<MiniRepl client:idle tune={`s("bd(3,8,0), hh cp")`} punchcard />
|
||||||
<MiniRepl client:idle tune={`s("bd(3,8,3), hh cp")`} />
|
<MiniRepl client:idle tune={`s("bd(3,8,3), hh cp")`} punchcard />
|
||||||
<MiniRepl client:idle tune={`s("bd(3,8,5), hh cp")`} />
|
<MiniRepl client:idle tune={`s("bd(3,8,5), hh cp")`} punchcard />
|
||||||
|
|
||||||
## Mini-notation exercise
|
## Mini-notation exercise
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ Here's the same pattern written in three different ways:
|
|||||||
|
|
||||||
<MiniRepl client:idle tune={`freq("220 275 330 440")`} />
|
<MiniRepl client:idle tune={`freq("220 275 330 440")`} />
|
||||||
|
|
||||||
Let's look at `note`, `n` and `freq` in more detail...
|
Let's look at those in more detail...
|
||||||
|
|
||||||
## `note` names
|
## `note` names
|
||||||
|
|
||||||
|
|||||||
@ -20,15 +20,17 @@ You can learn more about both of these approaches in the pages [Synths](/learn/s
|
|||||||
|
|
||||||
# Combining notes and sounds
|
# Combining notes and sounds
|
||||||
|
|
||||||
In both of the above cases, we are no longer directly controlling the `note`/`n`/`freq` of the sound heard via `s`, as we were in the [Notes](/strudel/notes) page.
|
In both of the above cases, we are no longer directly controlling the `note`/`freq` of the sound heard via `s`, as we were in the [Notes](/strudel/notes) page.
|
||||||
|
|
||||||
So how can we both control the sound and the pitch? We can _combine_ `note`/`n`/`freq` with `s` to change the sound of our pitches:
|
So how can we both control the sound and the pitch? We can _combine_ `note`/`freq` with `s` to change the sound of our pitches:
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`note("a3 c#4 e4 a4").s("sawtooth")`} />
|
<MiniRepl client:idle tune={`note("a3 c#4 e4 a4").s("sawtooth")`} />
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`n("57 61 64 69").s("triangle")`} />
|
<MiniRepl client:idle tune={`note("57 61 64 69").s("sine")`} />
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`freq("220 275 330 440").s("sine")`} />
|
<MiniRepl client:idle tune={`freq("220 275 330 440").s("triangle")`} />
|
||||||
|
|
||||||
|
The last example will actually sound the same with or without `s`, because `triangle` is the default value for `s`.
|
||||||
|
|
||||||
What about combining different notes with different sounds at the same time?
|
What about combining different notes with different sounds at the same time?
|
||||||
|
|
||||||
@ -37,5 +39,3 @@ What about combining different notes with different sounds at the same time?
|
|||||||
Hmm, something interesting is going on there, related to there being five notes and three sounds.
|
Hmm, something interesting is going on there, related to there being five notes and three sounds.
|
||||||
|
|
||||||
Let's now take a step back and think about the Strudel [Code](/learn/code) we've been hearing so far.
|
Let's now take a step back and think about the Strudel [Code](/learn/code) we've been hearing so far.
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|||||||
@ -18,13 +18,13 @@ Strudel is written in JavaScript, while Tidal is written in Haskell.
|
|||||||
|
|
||||||
This difference is most obvious when looking at the syntax:
|
This difference is most obvious when looking at the syntax:
|
||||||
|
|
||||||
```hs
|
```haskell
|
||||||
iter 4 $ every 3 (||+ n "10 20") $ (n "0 1 3") # s "triangle" # crush 4
|
iter 4 $ every 3 (||+ n "10 20") $ (n "0 1 3") # s "triangle" # crush 4
|
||||||
```
|
```
|
||||||
|
|
||||||
One _could_ express that pattern to Strudel like so:
|
One _could_ express that pattern to Strudel like so:
|
||||||
|
|
||||||
```txt
|
```
|
||||||
iter(4, every(3, add.squeeze("10 20"), n("0 1 3").s("triangle").crush(4)))
|
iter(4, every(3, add.squeeze("10 20"), n("0 1 3").s("triangle").crush(4)))
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ operators, or change the meaning of existing ones.
|
|||||||
|
|
||||||
Before you discard Strudel as an unwieldy paren monster, look at this alternative way to write the above:
|
Before you discard Strudel as an unwieldy paren monster, look at this alternative way to write the above:
|
||||||
|
|
||||||
```txt
|
```
|
||||||
n("0 1 3").every(3, add.squeeze("10 20")).iter(4).s("triangle").crush(4)
|
n("0 1 3").every(3, add.squeeze("10 20")).iter(4).s("triangle").crush(4)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ Also, samples are always loaded from a URL rather than from the disk, although [
|
|||||||
The Strudel REPL does not support [block based evaluation](https://github.com/tidalcycles/strudel/issues/34) yet.
|
The Strudel REPL does not support [block based evaluation](https://github.com/tidalcycles/strudel/issues/34) yet.
|
||||||
You can use the following "workaround" to create multiple patterns that can be turned on and off:
|
You can use the following "workaround" to create multiple patterns that can be turned on and off:
|
||||||
|
|
||||||
```txt
|
```
|
||||||
let a = note("c a f e")
|
let a = note("c a f e")
|
||||||
|
|
||||||
let b = s("bd sd")
|
let b = s("bd sd")
|
||||||
@ -127,7 +127,7 @@ stack(
|
|||||||
|
|
||||||
Alternatively, you could write everything as one `stack` and use `.hush()` to silence a pattern:
|
Alternatively, you could write everything as one `stack` and use `.hush()` to silence a pattern:
|
||||||
|
|
||||||
```txt
|
```
|
||||||
stack(
|
stack(
|
||||||
note("c a f e"),
|
note("c a f e"),
|
||||||
s("bd sd").hush()
|
s("bd sd").hush()
|
||||||
@ -141,6 +141,6 @@ Note that strudel will always use the last statement in your code as the pattern
|
|||||||
Strudels tempo is 1 cycle per second, while tidal defaults to `0.5625`.
|
Strudels tempo is 1 cycle per second, while tidal defaults to `0.5625`.
|
||||||
You can get the same tempo as tidal with:
|
You can get the same tempo as tidal with:
|
||||||
|
|
||||||
```txt
|
```
|
||||||
note("c a f e").fast(.5625);
|
note("c a f e").fast(.5625);
|
||||||
```
|
```
|
||||||
|
|||||||
@ -29,7 +29,11 @@ add a mini repl with
|
|||||||
|
|
||||||
- `client:idle` is required to tell astro that the repl should be interactive, see [Client Directive](https://docs.astro.build/en/reference/directives-reference/#client-directives)
|
- `client:idle` is required to tell astro that the repl should be interactive, see [Client Directive](https://docs.astro.build/en/reference/directives-reference/#client-directives)
|
||||||
- `tune`: be any valid pattern code
|
- `tune`: be any valid pattern code
|
||||||
- `withCanvas`: If set, a canvas will be rendered below that shows a pianoroll (WIP)
|
- `punchcard`: if added, a punchcard / pianoroll visualization is renderd
|
||||||
|
- `drawTime`: time window for drawing, defaults to `[0, 4]`
|
||||||
|
- `canvasHeight`: height of the canvas, defaults to 100px
|
||||||
|
|
||||||
|
See `mini-notation.mdx` for usage examples
|
||||||
|
|
||||||
## In-Source Documentation
|
## In-Source Documentation
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
|||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { cleanupDraw, cleanupUi, controls, evalScope, logger } from '@strudel.cycles/core';
|
import { cleanupDraw, cleanupUi, controls, evalScope, getDrawContext, logger } from '@strudel.cycles/core';
|
||||||
import { CodeMirror, cx, flash, useHighlighting, useStrudel } from '@strudel.cycles/react';
|
import { CodeMirror, cx, flash, useHighlighting, useStrudel } from '@strudel.cycles/react';
|
||||||
import {
|
import {
|
||||||
getAudioContext,
|
getAudioContext,
|
||||||
@ -54,6 +54,12 @@ evalScope(
|
|||||||
export let loadedSamples = [];
|
export let loadedSamples = [];
|
||||||
const presets = prebake();
|
const presets = prebake();
|
||||||
|
|
||||||
|
let drawContext, clearCanvas;
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
drawContext = getDrawContext();
|
||||||
|
clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width);
|
||||||
|
}
|
||||||
|
|
||||||
Promise.all([...modules, presets]).then((data) => {
|
Promise.all([...modules, presets]).then((data) => {
|
||||||
// console.log('modules and sample registry loade', data);
|
// console.log('modules and sample registry loade', data);
|
||||||
loadedSamples = Object.entries(getLoadedSamples() || {});
|
loadedSamples = Object.entries(getLoadedSamples() || {});
|
||||||
@ -125,6 +131,7 @@ export function Repl({ embedded = false }) {
|
|||||||
setPending(false);
|
setPending(false);
|
||||||
},
|
},
|
||||||
onToggle: (play) => !play && cleanupDraw(false),
|
onToggle: (play) => !play && cleanupDraw(false),
|
||||||
|
drawContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
// init code
|
// init code
|
||||||
@ -167,7 +174,7 @@ export function Repl({ embedded = false }) {
|
|||||||
view,
|
view,
|
||||||
pattern,
|
pattern,
|
||||||
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
||||||
getTime: () => scheduler.getPhase(),
|
getTime: () => scheduler.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -203,6 +210,7 @@ export function Repl({ embedded = false }) {
|
|||||||
const handleShuffle = async () => {
|
const handleShuffle = async () => {
|
||||||
const { code, name } = getRandomTune();
|
const { code, name } = getRandomTune();
|
||||||
logger(`[repl] ✨ loading random tune "${name}"`);
|
logger(`[repl] ✨ loading random tune "${name}"`);
|
||||||
|
clearCanvas();
|
||||||
resetLoadedSamples();
|
resetLoadedSamples();
|
||||||
await prebake(); // declare default samples
|
await prebake(); // declare default samples
|
||||||
await evaluate(code, false);
|
await evaluate(code, false);
|
||||||
|
|||||||
172
website/src/repl/drawings.mjs
Normal file
172
website/src/repl/drawings.mjs
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
export const lightflower = `Pattern.prototype.nest = function(n, cycles) {
|
||||||
|
n = reify(n)
|
||||||
|
return this.echo(n, pure(cycles).div(n), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern.prototype.deepimpose = function(func, times) {
|
||||||
|
if(times===0) return this;
|
||||||
|
return this.superimpose(x=>func(x).deepimpose(func, times-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
angle(saw)
|
||||||
|
.fill('#aaffee12')
|
||||||
|
.r(.18)
|
||||||
|
.w(.06)
|
||||||
|
.h(.06)
|
||||||
|
.deepimpose(x=>x.mul(r(2).w(2).h(2)).late(1/12), 3)
|
||||||
|
.nest(6, 1)
|
||||||
|
.s('ellipse')
|
||||||
|
.mul(w(sine).h(sine).range(.5,1.25))
|
||||||
|
.off(.5, x=>x.fill('#ffeeaa12').rev().div(r(1.2)))
|
||||||
|
.slow(16)
|
||||||
|
.smear(0.6)
|
||||||
|
.animate({smear:0})
|
||||||
|
`;
|
||||||
|
|
||||||
|
// https://strudel.tidalcycles.org/?C31_NrcMfZEO
|
||||||
|
export const spiralflower = `const {innerWidth:ww,innerHeight:wh} = window;
|
||||||
|
const ctx = getDrawContext()
|
||||||
|
const piDiv180 = Math.PI / 180;
|
||||||
|
function fromPolar(angle, radius, cx, cy) {
|
||||||
|
const radians = (angle-90) * piDiv180
|
||||||
|
return [cx + Math.cos(radians) * radius, cy + Math.sin(radians) * radius]
|
||||||
|
}
|
||||||
|
const [w, h] = [200,200]
|
||||||
|
const [cx,cy] = [ww/2,wh/2];
|
||||||
|
function drawSpiralSegment(ctx, {angle,b,r, density = 2, color = 'darkseagreen', thick = 2, long = 1}) {
|
||||||
|
let i = angle;
|
||||||
|
ctx.beginPath();
|
||||||
|
while(i < b){
|
||||||
|
const radius = Math.max(Math.min(r - i*.2,1000),20);
|
||||||
|
const [x1,y1] = fromPolar(i, radius, cx, cy)
|
||||||
|
const [x2,y2] = fromPolar(i, radius+long, cx, cy)
|
||||||
|
ctx.lineWidth = thick;
|
||||||
|
ctx.moveTo(x1,y1);
|
||||||
|
ctx.strokeStyle= color
|
||||||
|
ctx.lineTo(x2,y2);
|
||||||
|
ctx.stroke()
|
||||||
|
i+=300/density;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { r, angle, b, color, density,thick} =
|
||||||
|
createParams('r', 'angle', 'b', 'color','density','thick','long');
|
||||||
|
|
||||||
|
|
||||||
|
const pattern =
|
||||||
|
r(sine.range(200,800).slow(4))
|
||||||
|
.angle(cosine.range(0, 45).slow(3))
|
||||||
|
.b(perlin.range(1000, 4000).slow(5))
|
||||||
|
.thick(sine.range(2,50).slow(2))
|
||||||
|
.long(perlin.range(1,100).slow(3))
|
||||||
|
.off(1, x=>x.color('white'))
|
||||||
|
.off(2, x=>x.color('salmon'))
|
||||||
|
.off(4, x=>x.color('purple'))
|
||||||
|
.slow(4)//.mask("x(5,8)")
|
||||||
|
|
||||||
|
|
||||||
|
function onDraw(f) {
|
||||||
|
ctx.beginPath();
|
||||||
|
drawSpiralSegment(ctx, f.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// generic draw logic
|
||||||
|
window.frame && cancelAnimationFrame(window.frame);
|
||||||
|
|
||||||
|
function render(t) {
|
||||||
|
t = Math.round(t)
|
||||||
|
const frame = pattern.slow(1000).queryArc(t, t)
|
||||||
|
ctx.fillStyle='#20001005'
|
||||||
|
ctx.fillRect(0,0,ww,wh)
|
||||||
|
//ctx.clearRect(0,0,ww,wh)
|
||||||
|
ctx.stroke()
|
||||||
|
frame.forEach(onDraw)
|
||||||
|
window.frame = requestAnimationFrame(render);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.frame = requestAnimationFrame(render);
|
||||||
|
|
||||||
|
silence
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const syncexample = `"<0 1 2 3>/2"
|
||||||
|
.off(1/2, add(4))
|
||||||
|
.off(1, add(2))
|
||||||
|
.scale(cat('C minor','C major').slow(8))
|
||||||
|
.layer(
|
||||||
|
x=>x.note().piano(),
|
||||||
|
p=>stack(
|
||||||
|
p
|
||||||
|
.angle(p.sub('c3').div(12))
|
||||||
|
.r(.5)
|
||||||
|
.s('ellipse')
|
||||||
|
.w(.1)
|
||||||
|
.h(.1),
|
||||||
|
p.x(p.sub('c3').div(12))
|
||||||
|
.y(.9)
|
||||||
|
.w(1/12)
|
||||||
|
.h(.1)
|
||||||
|
.s('rect')
|
||||||
|
).animate({sync:true,smear:0.9})
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const moveRescaleZoom = `
|
||||||
|
const rescale = register('rescale', function (f, pat) {
|
||||||
|
return pat.mul(x(f).w(f).y(f).h(f));
|
||||||
|
})
|
||||||
|
|
||||||
|
const move = register('move', function (dx, dy, pat) {
|
||||||
|
return pat.add(x(dx).y(dy));
|
||||||
|
})
|
||||||
|
|
||||||
|
const zoom = register('zoom', function (f, pat) {
|
||||||
|
const d = pure(1).sub(f).div(2);
|
||||||
|
return pat.rescale(f).move(d, d);
|
||||||
|
})
|
||||||
|
|
||||||
|
x(.5).y(.5).w(1).h(1)
|
||||||
|
.zoom(saw.slow(3))
|
||||||
|
.move(sine.range(-.1,.1),0)
|
||||||
|
.fill("#ffeeaa10")
|
||||||
|
.s('rect')
|
||||||
|
.echo(6,.5,1)
|
||||||
|
.animate({smear:0.5})`;
|
||||||
|
|
||||||
|
export const strudelS = `
|
||||||
|
const rescale = register('rescale', function (f, pat) {
|
||||||
|
return pat.mul(x(f).w(f).y(f).h(f));
|
||||||
|
})
|
||||||
|
|
||||||
|
const move = register('move', function (dx, dy, pat) {
|
||||||
|
return pat.add(x(dx).y(dy));
|
||||||
|
})
|
||||||
|
|
||||||
|
const flipY = register('flipY', function (pat) {
|
||||||
|
return pat.fmap(v => ({...v, y:1-v.y}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const zoom = register('zoom', function (f, pat) {
|
||||||
|
const d = pure(1).sub(f).div(2);
|
||||||
|
return pat.rescale(f).move(d, d);
|
||||||
|
})
|
||||||
|
|
||||||
|
Pattern.prototype.nest = function(n, cycles) {
|
||||||
|
n = reify(n)
|
||||||
|
return this.echo(n, pure(cycles).div(n), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
x(sine.div(1)).y(cosine.range(0,.5))
|
||||||
|
.w(.1).h(.1)
|
||||||
|
.mul(w(square).h(square).slow(8))
|
||||||
|
.zoom(saw.slow(8))
|
||||||
|
.layer(
|
||||||
|
id,
|
||||||
|
_=>_.flipY().move(0,0).rev()
|
||||||
|
)
|
||||||
|
.mask("0 1@2").rev()
|
||||||
|
.nest(16,9)
|
||||||
|
.s('rect')
|
||||||
|
.fill("royalblue steelblue".fast(14))
|
||||||
|
.slow(8)
|
||||||
|
.animate({smear:.99})`;
|
||||||
@ -466,14 +466,14 @@ samples({
|
|||||||
stack(
|
stack(
|
||||||
"-7 0 -7 7".struct("x(5,8,1)").fast(2).sub(7)
|
"-7 0 -7 7".struct("x(5,8,1)").fast(2).sub(7)
|
||||||
.scale(scales)
|
.scale(scales)
|
||||||
.n()
|
.note()
|
||||||
.s("sawtooth,square")
|
.s("sawtooth,square")
|
||||||
.gain(.3).attack(0.01).decay(0.1).sustain(.5)
|
.gain(.3).attack(0.01).decay(0.1).sustain(.5)
|
||||||
.apply(filter1),
|
.apply(filter1),
|
||||||
"~@3 [<2 3>,<4 5>]"
|
"~@3 [<2 3>,<4 5>]"
|
||||||
.echo(4,1/16,.7)
|
.echo(4,1/16,.7)
|
||||||
.scale(scales)
|
.scale(scales)
|
||||||
.n()
|
.note()
|
||||||
.s('square').gain(.7)
|
.s('square').gain(.7)
|
||||||
.attack(0.01).decay(0.1).sustain(0)
|
.attack(0.01).decay(0.1).sustain(0)
|
||||||
.apply(filter1),
|
.apply(filter1),
|
||||||
@ -484,7 +484,7 @@ stack(
|
|||||||
.fast(2)
|
.fast(2)
|
||||||
.echo(32, 1/8, .8)
|
.echo(32, 1/8, .8)
|
||||||
.scale(scales)
|
.scale(scales)
|
||||||
.n()
|
.note()
|
||||||
.s("sawtooth")
|
.s("sawtooth")
|
||||||
.gain(sine.range(.1,.4).slow(8))
|
.gain(sine.range(.1,.4).slow(8))
|
||||||
.attack(.001).decay(.2).sustain(0)
|
.attack(.001).decay(.2).sustain(0)
|
||||||
@ -527,7 +527,7 @@ stack(
|
|||||||
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
|
.off(1/8,x=>x.add(12).degradeBy(.5)) // random octave jumps
|
||||||
.add(perlin.range(0,.5)) // random pitch variation
|
.add(perlin.range(0,.5)) // random pitch variation
|
||||||
.superimpose(add(.05)) // add second, slightly detuned voice
|
.superimpose(add(.05)) // add second, slightly detuned voice
|
||||||
.n() // wrap in "n"
|
.note() // wrap in "note"
|
||||||
.decay(.15).sustain(0) // make each note of equal length
|
.decay(.15).sustain(0) // make each note of equal length
|
||||||
.s('sawtooth') // waveform
|
.s('sawtooth') // waveform
|
||||||
.gain(.4) // turn down
|
.gain(.4) // turn down
|
||||||
@ -536,7 +536,7 @@ stack(
|
|||||||
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand') // chords
|
,"<Am7!3 <Em7 E7b13 Em7 Ebm7b5>>".voicings('lefthand') // chords
|
||||||
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||||
.add(perlin.range(0,.5)) // random pitch variation
|
.add(perlin.range(0,.5)) // random pitch variation
|
||||||
.n() // wrap in "n"
|
.note() // wrap in "note"
|
||||||
.s('sawtooth') // waveform
|
.s('sawtooth') // waveform
|
||||||
.gain(.16) // turn down
|
.gain(.16) // turn down
|
||||||
.cutoff(500) // fixed cutoff
|
.cutoff(500) // fixed cutoff
|
||||||
@ -545,7 +545,7 @@ stack(
|
|||||||
,"a4 c5 <e6 a6>".struct("x(5,8,-1)")
|
,"a4 c5 <e6 a6>".struct("x(5,8,-1)")
|
||||||
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
.superimpose(x=>x.add(.04)) // add second, slightly detuned voice
|
||||||
.add(perlin.range(0,.5)) // random pitch variation
|
.add(perlin.range(0,.5)) // random pitch variation
|
||||||
.n() // wrap in "n"
|
.note() // wrap in "note"
|
||||||
.decay(.1).sustain(0) // make notes short
|
.decay(.1).sustain(0) // make notes short
|
||||||
.s('triangle') // waveform
|
.s('triangle') // waveform
|
||||||
.degradeBy(perlin.range(0,.5)) // randomly controlled random removal :)
|
.degradeBy(perlin.range(0,.5)) // randomly controlled random removal :)
|
||||||
@ -565,12 +565,12 @@ samples({
|
|||||||
|
|
||||||
"C^7 Am7 Dm7 G7".slow(2).voicings('lefthand')
|
"C^7 Am7 Dm7 G7".slow(2).voicings('lefthand')
|
||||||
.stack("0@6 [<1 2> <2 0> 1]@2".scale('C5 major'))
|
.stack("0@6 [<1 2> <2 0> 1]@2".scale('C5 major'))
|
||||||
.n().slow(4)
|
.note().slow(4)
|
||||||
.s('0040_FluidR3_GM_sf2_file')
|
.s('0040_FluidR3_GM_sf2_file')
|
||||||
.color('steelblue')
|
.color('steelblue')
|
||||||
.stack(
|
.stack(
|
||||||
"<-7 ~@2 [~@2 -7] -9 ~@2 [~@2 -9] -10!2 ~ [~@2 -10] -5 ~ [-3 -2 -10]@2>*2".scale('C3 major')
|
"<-7 ~@2 [~@2 -7] -9 ~@2 [~@2 -9] -10!2 ~ [~@2 -10] -5 ~ [-3 -2 -10]@2>*2".scale('C3 major')
|
||||||
.n().s('sawtooth').color('brown')
|
.note().s('sawtooth').color('brown')
|
||||||
)
|
)
|
||||||
.attack(0.05).decay(.1).sustain(.7)
|
.attack(0.05).decay(.1).sustain(.7)
|
||||||
.cutoff(perlin.range(800,2000))
|
.cutoff(perlin.range(800,2000))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user