@brett_lamy/docstream 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -1
- package/dist/docs/DocsRenderer.d.ts.map +1 -1
- package/dist/docs/DocsRenderer.js +2 -1
- package/dist/docs/DocsRenderer.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/strudel/StepGrid.d.ts +10 -0
- package/dist/strudel/StepGrid.d.ts.map +1 -0
- package/dist/strudel/StepGrid.js +78 -0
- package/dist/strudel/StepGrid.js.map +1 -0
- package/dist/strudel/StrudelPlayer.d.ts +7 -1
- package/dist/strudel/StrudelPlayer.d.ts.map +1 -1
- package/dist/strudel/StrudelPlayer.js +37 -8
- package/dist/strudel/StrudelPlayer.js.map +1 -1
- package/dist/strudel/StrudelRack.d.ts +24 -0
- package/dist/strudel/StrudelRack.d.ts.map +1 -0
- package/dist/strudel/StrudelRack.js +123 -0
- package/dist/strudel/StrudelRack.js.map +1 -0
- package/dist/strudel/ast/edit.d.ts +41 -0
- package/dist/strudel/ast/edit.d.ts.map +1 -0
- package/dist/strudel/ast/edit.js +78 -0
- package/dist/strudel/ast/edit.js.map +1 -0
- package/dist/strudel/ast/index.d.ts +7 -0
- package/dist/strudel/ast/index.d.ts.map +1 -0
- package/dist/strudel/ast/index.js +4 -0
- package/dist/strudel/ast/index.js.map +1 -0
- package/dist/strudel/ast/mini.d.ts +86 -0
- package/dist/strudel/ast/mini.d.ts.map +1 -0
- package/dist/strudel/ast/mini.js +269 -0
- package/dist/strudel/ast/mini.js.map +1 -0
- package/dist/strudel/ast/program.d.ts +93 -0
- package/dist/strudel/ast/program.d.ts.map +1 -0
- package/dist/strudel/ast/program.js +288 -0
- package/dist/strudel/ast/program.js.map +1 -0
- package/dist/strudel/controls.d.ts +21 -0
- package/dist/strudel/controls.d.ts.map +1 -0
- package/dist/strudel/controls.js +115 -0
- package/dist/strudel/controls.js.map +1 -0
- package/dist/strudel/engine.d.ts +5 -0
- package/dist/strudel/engine.d.ts.map +1 -1
- package/dist/strudel/engine.js +14 -0
- package/dist/strudel/engine.js.map +1 -1
- package/dist/strudel/index.d.ts +9 -1
- package/dist/strudel/index.d.ts.map +1 -1
- package/dist/strudel/index.js +5 -1
- package/dist/strudel/index.js.map +1 -1
- package/dist/styles.css +598 -0
- package/package.json +2 -1
- package/src/docs/DocsRenderer.tsx +2 -1
- package/src/index.ts +3 -2
- package/src/strudel/StepGrid.tsx +130 -0
- package/src/strudel/StrudelPlayer.tsx +79 -8
- package/src/strudel/StrudelRack.tsx +322 -0
- package/src/strudel/ast/edit.ts +95 -0
- package/src/strudel/ast/index.ts +19 -0
- package/src/strudel/ast/mini.ts +319 -0
- package/src/strudel/ast/program.ts +348 -0
- package/src/strudel/controls.tsx +182 -0
- package/src/strudel/engine.ts +14 -0
- package/src/strudel/index.ts +17 -1
- package/src/styles.css +598 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/* Strudel mini-notation ("bd*2 [~ sd] <c e g>") as a syntax tree with source ranges.
|
|
2
|
+
Written from the grammar Strudel documents (krill), not from its parser: every node carries `start`/`end`
|
|
3
|
+
offsets into the string it was parsed from, so tools can rewrite exactly the characters a node covers. */
|
|
4
|
+
export class MiniSyntaxError extends Error {
|
|
5
|
+
offset;
|
|
6
|
+
constructor(message, offset) {
|
|
7
|
+
super(`${message} at ${offset}`);
|
|
8
|
+
this.offset = offset;
|
|
9
|
+
this.name = "MiniSyntaxError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const WS = /[ \n\r\t ]/;
|
|
13
|
+
const WORD = /[\p{L}\p{Nl}0-9~\-#.^_]/u;
|
|
14
|
+
const NUMBER = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][-+]?[0-9]+)?/;
|
|
15
|
+
/** Parse a mini-notation string. Throws `MiniSyntaxError` (with the offset) on invalid input. */
|
|
16
|
+
export function parseMini(source) {
|
|
17
|
+
let i = 0;
|
|
18
|
+
const peek = (n = 0) => source[i + n] ?? "";
|
|
19
|
+
const skip = () => {
|
|
20
|
+
while (i < source.length && WS.test(source[i]))
|
|
21
|
+
i++;
|
|
22
|
+
};
|
|
23
|
+
const fail = (what) => {
|
|
24
|
+
throw new MiniSyntaxError(i < source.length ? `Unexpected "${source[i]}", expected ${what}` : `Expected ${what}`, i);
|
|
25
|
+
};
|
|
26
|
+
const expect = (ch) => {
|
|
27
|
+
skip();
|
|
28
|
+
if (peek() !== ch)
|
|
29
|
+
fail(`"${ch}"`);
|
|
30
|
+
i++;
|
|
31
|
+
};
|
|
32
|
+
const number = () => {
|
|
33
|
+
const m = NUMBER.exec(source.slice(i));
|
|
34
|
+
if (!m)
|
|
35
|
+
return null;
|
|
36
|
+
i += m[0].length;
|
|
37
|
+
return parseFloat(m[0]);
|
|
38
|
+
};
|
|
39
|
+
// A separator `.` (feet) is a lone dot between spaces; inside a word it is part of the word ("0.5", "a.b").
|
|
40
|
+
const atFeetDot = () => peek() === "." && peek(1) !== "." && (i === 0 || WS.test(source[i - 1])) && (peek(1) === "" || WS.test(peek(1)));
|
|
41
|
+
function word() {
|
|
42
|
+
const start = i;
|
|
43
|
+
while (i < source.length && WORD.test(source[i])) {
|
|
44
|
+
// ".." starts a range operator, not part of the word.
|
|
45
|
+
if (source[i] === "." && source[i + 1] === ".")
|
|
46
|
+
break;
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
const value = source.slice(start, i);
|
|
50
|
+
if (!value)
|
|
51
|
+
return null;
|
|
52
|
+
if (value === "." || value === "_") {
|
|
53
|
+
i = start;
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return { type: "atom", value, rest: value === "~" || value === "-", start, end: i };
|
|
57
|
+
}
|
|
58
|
+
function slice() {
|
|
59
|
+
skip();
|
|
60
|
+
const start = i;
|
|
61
|
+
const ch = peek();
|
|
62
|
+
if (ch === "[") {
|
|
63
|
+
i++;
|
|
64
|
+
const body = group(["]"]);
|
|
65
|
+
expect("]");
|
|
66
|
+
return { type: "subcycle", body, start, end: i };
|
|
67
|
+
}
|
|
68
|
+
if (ch === "<") {
|
|
69
|
+
i++;
|
|
70
|
+
const layers = commaLayers([">"]);
|
|
71
|
+
expect(">");
|
|
72
|
+
return { type: "alternation", layers, start, end: i };
|
|
73
|
+
}
|
|
74
|
+
if (ch === "{") {
|
|
75
|
+
i++;
|
|
76
|
+
const layers = commaLayers(["}"]);
|
|
77
|
+
expect("}");
|
|
78
|
+
let steps = null;
|
|
79
|
+
if (peek() === "%") {
|
|
80
|
+
i++;
|
|
81
|
+
steps = bare() ?? fail("a step count after %");
|
|
82
|
+
}
|
|
83
|
+
return { type: "polymeter", layers, steps, start, end: i };
|
|
84
|
+
}
|
|
85
|
+
if (atFeetDot())
|
|
86
|
+
return null;
|
|
87
|
+
return word();
|
|
88
|
+
}
|
|
89
|
+
// `*`, `/`, `:`, `..` and `%` take a bare slice: in "bd*2!" the `!` replicates bd, not 2.
|
|
90
|
+
function bare() {
|
|
91
|
+
const s = slice();
|
|
92
|
+
return s && { type: "step", slice: s, ops: [], start: s.start, end: s.end };
|
|
93
|
+
}
|
|
94
|
+
// A slice with its operators. `@`, `_` and `!` may stand apart ("a _ _", "a ! !"); the rest attach directly.
|
|
95
|
+
function step() {
|
|
96
|
+
const s = slice();
|
|
97
|
+
if (!s)
|
|
98
|
+
return null;
|
|
99
|
+
const ops = [];
|
|
100
|
+
for (;;) {
|
|
101
|
+
const before = i;
|
|
102
|
+
skip();
|
|
103
|
+
const start = i;
|
|
104
|
+
const ch = peek();
|
|
105
|
+
if (ch === "@" || (ch === "_" && !WORD.test(peek(1)))) {
|
|
106
|
+
i++;
|
|
107
|
+
ops.push({ op: "weight", amount: ch === "@" ? number() : null, start, end: i });
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (ch === "!") {
|
|
111
|
+
i++;
|
|
112
|
+
ops.push({ op: "replicate", count: number(), start, end: i });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
i = before;
|
|
116
|
+
const at = i;
|
|
117
|
+
const c = peek();
|
|
118
|
+
if (c === "*" || c === "/") {
|
|
119
|
+
i++;
|
|
120
|
+
const amount = bare() ?? fail("an amount");
|
|
121
|
+
ops.push({ op: c === "*" ? "fast" : "slow", amount, start: at, end: i });
|
|
122
|
+
}
|
|
123
|
+
else if (c === "?") {
|
|
124
|
+
i++;
|
|
125
|
+
ops.push({ op: "degrade", amount: number(), start: at, end: i });
|
|
126
|
+
}
|
|
127
|
+
else if (c === ":") {
|
|
128
|
+
i++;
|
|
129
|
+
const value = bare() ?? fail("a value after :");
|
|
130
|
+
ops.push({ op: "tail", value, start: at, end: i });
|
|
131
|
+
}
|
|
132
|
+
else if (c === "." && peek(1) === ".") {
|
|
133
|
+
i += 2;
|
|
134
|
+
const to = bare() ?? fail("a range end");
|
|
135
|
+
ops.push({ op: "range", to, start: at, end: i });
|
|
136
|
+
}
|
|
137
|
+
else if (c === "(") {
|
|
138
|
+
i++;
|
|
139
|
+
const pulses = step() ?? fail("pulses");
|
|
140
|
+
expect(",");
|
|
141
|
+
const steps = step() ?? fail("steps");
|
|
142
|
+
skip();
|
|
143
|
+
let rotation = null;
|
|
144
|
+
if (peek() === ",") {
|
|
145
|
+
i++;
|
|
146
|
+
rotation = step();
|
|
147
|
+
}
|
|
148
|
+
expect(")");
|
|
149
|
+
ops.push({ op: "euclid", pulses, steps, rotation, start: at, end: i });
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { type: "step", slice: s, ops, start: s.start, end: i };
|
|
156
|
+
}
|
|
157
|
+
function sequence(closers) {
|
|
158
|
+
skip();
|
|
159
|
+
const start = i;
|
|
160
|
+
let marked = false;
|
|
161
|
+
if (peek() === "^") {
|
|
162
|
+
marked = true;
|
|
163
|
+
i++;
|
|
164
|
+
}
|
|
165
|
+
const steps = [];
|
|
166
|
+
for (;;) {
|
|
167
|
+
skip();
|
|
168
|
+
const ch = peek();
|
|
169
|
+
if (!ch || closers.includes(ch) || ch === "," || ch === "|" || atFeetDot())
|
|
170
|
+
break;
|
|
171
|
+
const s = step();
|
|
172
|
+
if (!s)
|
|
173
|
+
fail("a step");
|
|
174
|
+
steps.push(s);
|
|
175
|
+
}
|
|
176
|
+
if (!steps.length)
|
|
177
|
+
fail("a step");
|
|
178
|
+
return { type: "sequence", steps, marked, start, end: steps[steps.length - 1].end };
|
|
179
|
+
}
|
|
180
|
+
function commaLayers(closers) {
|
|
181
|
+
const layers = [sequence(closers)];
|
|
182
|
+
for (;;) {
|
|
183
|
+
skip();
|
|
184
|
+
if (peek() !== ",")
|
|
185
|
+
return layers;
|
|
186
|
+
i++;
|
|
187
|
+
layers.push(sequence(closers));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function group(closers) {
|
|
191
|
+
const first = sequence(closers);
|
|
192
|
+
skip();
|
|
193
|
+
const sep = atFeetDot() ? "." : peek();
|
|
194
|
+
if (sep !== "," && sep !== "|" && sep !== ".")
|
|
195
|
+
return first;
|
|
196
|
+
const type = sep === "," ? "stack" : sep === "|" ? "choose" : "feet";
|
|
197
|
+
const layers = [first];
|
|
198
|
+
for (;;) {
|
|
199
|
+
skip();
|
|
200
|
+
const next = atFeetDot() ? "." : peek();
|
|
201
|
+
if (next !== sep)
|
|
202
|
+
break;
|
|
203
|
+
i++;
|
|
204
|
+
layers.push(sequence(closers));
|
|
205
|
+
}
|
|
206
|
+
return { type, layers, start: first.start, end: layers[layers.length - 1].end };
|
|
207
|
+
}
|
|
208
|
+
const root = group([]);
|
|
209
|
+
skip();
|
|
210
|
+
if (i < source.length)
|
|
211
|
+
fail("end of pattern");
|
|
212
|
+
return root;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Every atom in the tree, in source order. `{ ops: false }` skips operator arguments (the 2 in `bd*2`, the 3,8
|
|
216
|
+
* in `bd(3,8)`), leaving only the values the pattern plays.
|
|
217
|
+
*/
|
|
218
|
+
export function miniAtoms(node, { ops = true } = {}) {
|
|
219
|
+
const out = [];
|
|
220
|
+
const visit = (n) => {
|
|
221
|
+
if (!n)
|
|
222
|
+
return;
|
|
223
|
+
if ("op" in n) {
|
|
224
|
+
if (!ops)
|
|
225
|
+
return;
|
|
226
|
+
if (n.op === "fast" || n.op === "slow")
|
|
227
|
+
visit(n.amount);
|
|
228
|
+
else if (n.op === "euclid")
|
|
229
|
+
(visit(n.pulses), visit(n.steps), visit(n.rotation));
|
|
230
|
+
else if (n.op === "tail")
|
|
231
|
+
visit(n.value);
|
|
232
|
+
else if (n.op === "range")
|
|
233
|
+
visit(n.to);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
switch (n.type) {
|
|
237
|
+
case "atom":
|
|
238
|
+
out.push(n);
|
|
239
|
+
break;
|
|
240
|
+
case "step":
|
|
241
|
+
visit(n.slice);
|
|
242
|
+
n.ops.forEach(visit);
|
|
243
|
+
break;
|
|
244
|
+
case "sequence":
|
|
245
|
+
n.steps.forEach(visit);
|
|
246
|
+
break;
|
|
247
|
+
case "stack":
|
|
248
|
+
case "choose":
|
|
249
|
+
case "feet":
|
|
250
|
+
case "alternation":
|
|
251
|
+
n.layers.forEach(visit);
|
|
252
|
+
break;
|
|
253
|
+
case "polymeter":
|
|
254
|
+
n.layers.forEach(visit);
|
|
255
|
+
visit(n.steps);
|
|
256
|
+
break;
|
|
257
|
+
case "subcycle":
|
|
258
|
+
visit(n.body);
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
visit(node);
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
/** The layers of a group: a sequence is one layer. */
|
|
266
|
+
export function miniLayers(group) {
|
|
267
|
+
return group.type === "sequence" ? [group] : group.layers;
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=mini.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mini.js","sourceRoot":"","sources":["../../../src/strudel/ast/mini.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type Node as AcornNode } from "acorn";
|
|
2
|
+
import { MiniSyntaxError, type MiniGroup, type Range } from "./mini.js";
|
|
3
|
+
export type { Range };
|
|
4
|
+
export type StrudelArg = Range & ({
|
|
5
|
+
kind: "number";
|
|
6
|
+
value: number;
|
|
7
|
+
}
|
|
8
|
+
/** A double-quoted or backtick string: mini-notation. `mini` is null when it doesn't parse (see `error`). */
|
|
9
|
+
| {
|
|
10
|
+
kind: "mini";
|
|
11
|
+
source: string;
|
|
12
|
+
contentStart: number;
|
|
13
|
+
mini: MiniGroup | null;
|
|
14
|
+
error: MiniSyntaxError | null;
|
|
15
|
+
}
|
|
16
|
+
/** A single-quoted string: plain JavaScript text, as in the Strudel REPL. */
|
|
17
|
+
| {
|
|
18
|
+
kind: "string";
|
|
19
|
+
value: string;
|
|
20
|
+
}
|
|
21
|
+
/** A nested pattern or signal expression, e.g. `sine.range(200, 800).slow(4)`. */
|
|
22
|
+
| {
|
|
23
|
+
kind: "pattern";
|
|
24
|
+
chain: StrudelChain;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "function";
|
|
27
|
+
text: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "other";
|
|
30
|
+
text: string;
|
|
31
|
+
});
|
|
32
|
+
export interface StrudelCall extends Range {
|
|
33
|
+
name: string;
|
|
34
|
+
nameRange: Range;
|
|
35
|
+
args: StrudelArg[];
|
|
36
|
+
/** Between the parentheses (empty for a property access such as `.p1`). */
|
|
37
|
+
argsRange: Range;
|
|
38
|
+
/** `.name(args)` including the dot: delete it to remove the method from its chain. */
|
|
39
|
+
methodRange: Range;
|
|
40
|
+
}
|
|
41
|
+
export interface StrudelChain extends Range {
|
|
42
|
+
/** The call or identifier the chain starts from (`s("bd")`, `sine`, `"c e"`). */
|
|
43
|
+
head: StrudelCall | (Range & {
|
|
44
|
+
name: string;
|
|
45
|
+
literal?: StrudelArg;
|
|
46
|
+
});
|
|
47
|
+
methods: StrudelCall[];
|
|
48
|
+
}
|
|
49
|
+
export type TrackKind = "drums" | "melodic" | "other";
|
|
50
|
+
export interface StrudelTrack extends Range {
|
|
51
|
+
index: number;
|
|
52
|
+
/** The `$:`/`name:` label, if the track has one (`$` labels count as unnamed). */
|
|
53
|
+
label: string | null;
|
|
54
|
+
/** A `_$:` label: Strudel's own mute. */
|
|
55
|
+
muted: boolean;
|
|
56
|
+
chain: StrudelChain;
|
|
57
|
+
kind: TrackKind;
|
|
58
|
+
/** The first sound it plays (`s`/`sound`), e.g. "sawtooth" or "bd". */
|
|
59
|
+
sound: string | null;
|
|
60
|
+
}
|
|
61
|
+
export interface StrudelTempo {
|
|
62
|
+
/** Cycles per second. */
|
|
63
|
+
cps: number;
|
|
64
|
+
unit: "cps" | "cpm";
|
|
65
|
+
call: StrudelCall;
|
|
66
|
+
}
|
|
67
|
+
export interface StrudelSong {
|
|
68
|
+
code: string;
|
|
69
|
+
tempo: StrudelTempo | null;
|
|
70
|
+
tracks: StrudelTrack[];
|
|
71
|
+
/** How the tracks are written: `$:` labels, args of one `stack(...)`, or one pattern. */
|
|
72
|
+
layout: "labels" | "stack" | "single" | "empty";
|
|
73
|
+
/** Methods applied to the whole `stack(...)`, e.g. `.postgain(.8)`. */
|
|
74
|
+
master: StrudelCall[];
|
|
75
|
+
masterChain: StrudelChain | null;
|
|
76
|
+
/** Every mini-notation string in the program, in source order. */
|
|
77
|
+
minis: Extract<StrudelArg, {
|
|
78
|
+
kind: "mini";
|
|
79
|
+
}>[];
|
|
80
|
+
/** The ESTree program from acorn, for tools that need the raw JavaScript tree. */
|
|
81
|
+
program: AcornNode;
|
|
82
|
+
}
|
|
83
|
+
export declare class StrudelSyntaxError extends Error {
|
|
84
|
+
readonly offset: number;
|
|
85
|
+
constructor(message: string, offset: number);
|
|
86
|
+
}
|
|
87
|
+
/** Parse Strudel code. Throws `StrudelSyntaxError` when the JavaScript doesn't parse. */
|
|
88
|
+
export declare function parseStrudel(code: string): StrudelSong;
|
|
89
|
+
/** All calls in a chain, head first. */
|
|
90
|
+
export declare function chainCalls(chain: StrudelChain): StrudelCall[];
|
|
91
|
+
/** The last call named `name` in the chain (later calls win in Strudel). */
|
|
92
|
+
export declare function findCall(chain: StrudelChain, name: string): StrudelCall | null;
|
|
93
|
+
//# sourceMappingURL=program.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../../../src/strudel/ast/program.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/* A Strudel program as a song: the JavaScript is parsed with acorn (the parser Strudel's own transpiler uses) and
|
|
2
|
+
read the way the REPL runs it — `setcps`/`setcpm` set the tempo, `$:` labels or a top-level `stack(...)` hold
|
|
3
|
+
the tracks, and each track is a method chain (`n("0 2").scale("C:minor").s("sawtooth").lpf(800)`) whose string
|
|
4
|
+
arguments are parsed as mini-notation. Every node keeps its source range for exact edits (see edit.ts). */
|
|
5
|
+
import { parse as parseJs } from "acorn";
|
|
6
|
+
import { MiniSyntaxError, miniAtoms, parseMini } from "./mini.js";
|
|
7
|
+
export class StrudelSyntaxError extends Error {
|
|
8
|
+
offset;
|
|
9
|
+
constructor(message, offset) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.offset = offset;
|
|
12
|
+
this.name = "StrudelSyntaxError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const SYNTHS = new Set(["sine", "triangle", "square", "sawtooth", "saw", "tri", "sin", "sqr", "white", "pink", "brown", "supersaw", "pulse", "noise"]);
|
|
16
|
+
const TEMPO = new Set(["setcps", "setCps", "setcpm", "setCpm"]);
|
|
17
|
+
const SETUP = new Set(["samples", "soundfont", "loadSoundfont", "initHydra", "aliasBank", "registerSound"]);
|
|
18
|
+
const MELODIC_HEADS = new Set(["note", "n", "freq", "chord", "voicing", "arp"]);
|
|
19
|
+
/** Parse Strudel code. Throws `StrudelSyntaxError` when the JavaScript doesn't parse. */
|
|
20
|
+
export function parseStrudel(code) {
|
|
21
|
+
let program;
|
|
22
|
+
try {
|
|
23
|
+
program = parseJs(code, {
|
|
24
|
+
ecmaVersion: "latest",
|
|
25
|
+
sourceType: "module",
|
|
26
|
+
allowAwaitOutsideFunction: true,
|
|
27
|
+
allowReturnOutsideFunction: true,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
const e = err;
|
|
32
|
+
throw new StrudelSyntaxError(e.message ?? String(err), e.pos ?? 0);
|
|
33
|
+
}
|
|
34
|
+
const text = (n) => code.slice(n.start, n.end);
|
|
35
|
+
// Keyed by offset: stack arguments are read once as arguments and again as tracks.
|
|
36
|
+
const minis = new Map();
|
|
37
|
+
// Constant arithmetic (`120/4`, `-.5`, `(1+2)*3`) counts as a number.
|
|
38
|
+
function constant(node) {
|
|
39
|
+
if (node.type === "Literal" && typeof node.value === "number")
|
|
40
|
+
return node.value;
|
|
41
|
+
if (node.type === "UnaryExpression" && (node.operator === "-" || node.operator === "+")) {
|
|
42
|
+
const v = constant(node.argument);
|
|
43
|
+
return v === null ? null : node.operator === "-" ? -v : v;
|
|
44
|
+
}
|
|
45
|
+
if (node.type === "BinaryExpression") {
|
|
46
|
+
const a = constant(node.left);
|
|
47
|
+
const b = constant(node.right);
|
|
48
|
+
if (a === null || b === null)
|
|
49
|
+
return null;
|
|
50
|
+
switch (node.operator) {
|
|
51
|
+
case "+": return a + b;
|
|
52
|
+
case "-": return a - b;
|
|
53
|
+
case "*": return a * b;
|
|
54
|
+
case "/": return a / b;
|
|
55
|
+
case "%": return a % b;
|
|
56
|
+
case "**": return a ** b;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function arg(node) {
|
|
62
|
+
const range = { start: node.start, end: node.end };
|
|
63
|
+
const value = constant(node);
|
|
64
|
+
if (value !== null)
|
|
65
|
+
return { kind: "number", value, ...range };
|
|
66
|
+
const raw = node.type === "Literal" && typeof node.value === "string" ? String(node.raw) : null;
|
|
67
|
+
const template = node.type === "TemplateLiteral" && !node.expressions.length;
|
|
68
|
+
if ((raw && raw.startsWith('"')) || template) {
|
|
69
|
+
const source = raw ? node.value : String(node.quasis[0].value.cooked);
|
|
70
|
+
// Offsets are into the raw literal; escapes would shift them, so only plain strings map 1:1.
|
|
71
|
+
let mini = null;
|
|
72
|
+
let error = null;
|
|
73
|
+
try {
|
|
74
|
+
mini = parseMini(source);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
if (err instanceof MiniSyntaxError)
|
|
78
|
+
error = err;
|
|
79
|
+
else
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
const out = { kind: "mini", source, contentStart: node.start + 1, mini, error, ...range };
|
|
83
|
+
minis.set(node.start, out);
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
if (raw)
|
|
87
|
+
return { kind: "string", value: node.value, ...range };
|
|
88
|
+
if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression")
|
|
89
|
+
return { kind: "function", text: text(node), ...range };
|
|
90
|
+
const c = chain(node);
|
|
91
|
+
if (c)
|
|
92
|
+
return { kind: "pattern", chain: c, ...range };
|
|
93
|
+
return { kind: "other", text: text(node), ...range };
|
|
94
|
+
}
|
|
95
|
+
function call(node, name, nameNode, receiverEnd) {
|
|
96
|
+
const args = node.arguments.map(arg);
|
|
97
|
+
// The text between the parentheses: from just after "(" to just before ")".
|
|
98
|
+
const open = code.indexOf("(", nameNode.end);
|
|
99
|
+
return {
|
|
100
|
+
name,
|
|
101
|
+
nameRange: { start: nameNode.start, end: nameNode.end },
|
|
102
|
+
args,
|
|
103
|
+
argsRange: { start: open + 1, end: node.end - 1 },
|
|
104
|
+
methodRange: { start: receiverEnd, end: node.end },
|
|
105
|
+
start: node.start,
|
|
106
|
+
end: node.end,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// Flatten `a(x).b(y).c` into a head and its methods.
|
|
110
|
+
function chain(node) {
|
|
111
|
+
const methods = [];
|
|
112
|
+
let cur = node;
|
|
113
|
+
for (;;) {
|
|
114
|
+
if (cur.type === "CallExpression") {
|
|
115
|
+
const callee = cur.callee;
|
|
116
|
+
if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
|
|
117
|
+
const object = callee.object;
|
|
118
|
+
const property = callee.property;
|
|
119
|
+
methods.unshift(call(cur, property.name, property, object.end));
|
|
120
|
+
cur = object;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (callee.type === "Identifier") {
|
|
124
|
+
return { head: call(cur, callee.name, callee, cur.start), methods, start: node.start, end: node.end };
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
if (cur.type === "MemberExpression" && !cur.computed && cur.property.type === "Identifier") {
|
|
129
|
+
// A getter in the chain, e.g. `.p1` — a method with no arguments or parentheses.
|
|
130
|
+
const object = cur.object;
|
|
131
|
+
const property = cur.property;
|
|
132
|
+
methods.unshift({
|
|
133
|
+
name: property.name,
|
|
134
|
+
nameRange: { start: property.start, end: property.end },
|
|
135
|
+
args: [],
|
|
136
|
+
argsRange: { start: cur.end, end: cur.end },
|
|
137
|
+
methodRange: { start: object.end, end: cur.end },
|
|
138
|
+
start: cur.start,
|
|
139
|
+
end: cur.end,
|
|
140
|
+
});
|
|
141
|
+
cur = object;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (cur.type === "Identifier")
|
|
145
|
+
return { head: { name: cur.name, start: cur.start, end: cur.end }, methods, start: node.start, end: node.end };
|
|
146
|
+
if ((cur.type === "Literal" && typeof cur.value === "string") || cur.type === "TemplateLiteral") {
|
|
147
|
+
return { head: { name: "", literal: arg(cur), start: cur.start, end: cur.end }, methods, start: node.start, end: node.end };
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const calls = (c) => ("args" in c.head ? [c.head, ...c.methods] : c.methods);
|
|
153
|
+
function soundOf(c) {
|
|
154
|
+
for (const k of calls(c)) {
|
|
155
|
+
if (k.name !== "s" && k.name !== "sound")
|
|
156
|
+
continue;
|
|
157
|
+
const first = k.args[0];
|
|
158
|
+
if (first?.kind === "mini" && first.mini) {
|
|
159
|
+
const atom = miniAtoms(first.mini, { ops: false }).find((a) => !a.rest);
|
|
160
|
+
if (atom)
|
|
161
|
+
return atom.value.split(":")[0];
|
|
162
|
+
}
|
|
163
|
+
if (first?.kind === "string")
|
|
164
|
+
return first.value;
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
function kindOf(c, sound) {
|
|
169
|
+
const names = calls(c).map((k) => k.name);
|
|
170
|
+
if (names.some((n) => MELODIC_HEADS.has(n)) || names.includes("scale") || (sound && SYNTHS.has(sound)))
|
|
171
|
+
return "melodic";
|
|
172
|
+
if (sound)
|
|
173
|
+
return "drums";
|
|
174
|
+
return "other";
|
|
175
|
+
}
|
|
176
|
+
function track(node, index, label) {
|
|
177
|
+
const c = chain(node);
|
|
178
|
+
if (!c)
|
|
179
|
+
return null;
|
|
180
|
+
const sound = soundOf(c);
|
|
181
|
+
const muted = label !== null && label.startsWith("_");
|
|
182
|
+
const name = label === null ? null : label.replace(/^_/, "");
|
|
183
|
+
return {
|
|
184
|
+
index,
|
|
185
|
+
label: name === "$" || name === "" ? null : name,
|
|
186
|
+
muted,
|
|
187
|
+
chain: c,
|
|
188
|
+
kind: kindOf(c, sound),
|
|
189
|
+
sound,
|
|
190
|
+
start: node.start,
|
|
191
|
+
end: node.end,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
let tempo = null;
|
|
195
|
+
const labelled = [];
|
|
196
|
+
let root = null;
|
|
197
|
+
const unwrap = (n) => (n.type === "AwaitExpression" ? n.argument : n);
|
|
198
|
+
for (const statement of program.body) {
|
|
199
|
+
if (statement.type === "LabeledStatement") {
|
|
200
|
+
const body = statement.body;
|
|
201
|
+
if (body.type === "ExpressionStatement") {
|
|
202
|
+
const t = track(body.expression, labelled.length, statement.label.name);
|
|
203
|
+
if (t)
|
|
204
|
+
labelled.push(t);
|
|
205
|
+
}
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (statement.type !== "ExpressionStatement")
|
|
209
|
+
continue;
|
|
210
|
+
const expr = unwrap(statement.expression);
|
|
211
|
+
const callee = expr.type === "CallExpression" ? expr.callee : null;
|
|
212
|
+
const name = callee?.type === "Identifier" ? callee.name : null;
|
|
213
|
+
if (name && TEMPO.has(name)) {
|
|
214
|
+
const k = call(expr, name, callee, expr.start);
|
|
215
|
+
const value = k.args[0]?.kind === "number" ? k.args[0].value : NaN;
|
|
216
|
+
const unit = name.toLowerCase() === "setcpm" ? "cpm" : "cps";
|
|
217
|
+
tempo = { cps: unit === "cpm" ? value / 60 : value, unit, call: k };
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (name && SETUP.has(name))
|
|
221
|
+
continue;
|
|
222
|
+
root = expr;
|
|
223
|
+
}
|
|
224
|
+
let tracks = labelled;
|
|
225
|
+
let layout = labelled.length ? "labels" : "empty";
|
|
226
|
+
let master = [];
|
|
227
|
+
let masterChain = null;
|
|
228
|
+
if (!labelled.length && root) {
|
|
229
|
+
const c = chain(root);
|
|
230
|
+
if (c && "args" in c.head && c.head.name === "stack") {
|
|
231
|
+
layout = "stack";
|
|
232
|
+
master = c.methods;
|
|
233
|
+
masterChain = c;
|
|
234
|
+
tracks = c.head.args
|
|
235
|
+
.map((a, i) => (a.kind === "pattern" || a.kind === "mini" ? track(nodeAt(program, a.start, a.end), i, null) : null))
|
|
236
|
+
.filter((t) => !!t);
|
|
237
|
+
}
|
|
238
|
+
else if (c) {
|
|
239
|
+
layout = "single";
|
|
240
|
+
tracks = [track(root, 0, null)];
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
code,
|
|
245
|
+
tempo,
|
|
246
|
+
tracks,
|
|
247
|
+
layout,
|
|
248
|
+
master,
|
|
249
|
+
masterChain,
|
|
250
|
+
minis: [...minis.values()].sort((a, b) => a.start - b.start),
|
|
251
|
+
program: program,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
// The ESTree node spanning exactly [start, end) (to re-read a stack argument as a track).
|
|
255
|
+
function nodeAt(root, start, end) {
|
|
256
|
+
let found = null;
|
|
257
|
+
const visit = (n) => {
|
|
258
|
+
if (found || !n || typeof n !== "object")
|
|
259
|
+
return;
|
|
260
|
+
if (Array.isArray(n))
|
|
261
|
+
return n.forEach(visit);
|
|
262
|
+
const node = n;
|
|
263
|
+
if (typeof node.type === "string" && node.start === start && node.end === end) {
|
|
264
|
+
found = node;
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (typeof node.start === "number" && (node.start > start || node.end < end))
|
|
268
|
+
return;
|
|
269
|
+
for (const key of Object.keys(node))
|
|
270
|
+
if (key !== "type")
|
|
271
|
+
visit(node[key]);
|
|
272
|
+
};
|
|
273
|
+
visit(root);
|
|
274
|
+
return found;
|
|
275
|
+
}
|
|
276
|
+
/** All calls in a chain, head first. */
|
|
277
|
+
export function chainCalls(chain) {
|
|
278
|
+
return "args" in chain.head ? [chain.head, ...chain.methods] : chain.methods;
|
|
279
|
+
}
|
|
280
|
+
/** The last call named `name` in the chain (later calls win in Strudel). */
|
|
281
|
+
export function findCall(chain, name) {
|
|
282
|
+
const all = chainCalls(chain);
|
|
283
|
+
for (let i = all.length - 1; i >= 0; i--)
|
|
284
|
+
if (all[i].name === name)
|
|
285
|
+
return all[i];
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=program.js.map
|