tutticode 0.2.2 → 0.3.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.
Files changed (27) hide show
  1. package/README.md +11 -0
  2. package/README.zh-CN.md +10 -0
  3. package/dist/skills/tutticode/SKILL.md +11 -10
  4. package/dist/skills/tutticode/references/agent-loop.md +15 -0
  5. package/dist/skills/tutticode/references/automation.md +168 -0
  6. package/dist/skills/tutticode/references/devices/compressor.md +2 -0
  7. package/dist/skills/tutticode/references/devices/filter.md +2 -0
  8. package/dist/skills/tutticode/references/devices/gate.md +2 -0
  9. package/dist/skills/tutticode/references/devices/glue.md +2 -0
  10. package/dist/skills/tutticode/references/devices/multibanddynamics.md +2 -0
  11. package/dist/skills/tutticode/references/devices.md +2 -2
  12. package/dist/skills/tutticode/references/effect-controls.md +8 -0
  13. package/dist/skills/tutticode/references/first-sounds.md +2 -0
  14. package/dist/skills/tutticode/references/for-agents.md +26 -0
  15. package/dist/skills/tutticode/references/functions.md +67 -5
  16. package/dist/skills/tutticode/references/harmony-and-scales.md +3 -0
  17. package/dist/skills/tutticode/references/language-reference.md +362 -0
  18. package/dist/skills/tutticode/references/melodies-with-do-re-mi.md +41 -5
  19. package/dist/skills/tutticode/references/mixing-groups.md +7 -4
  20. package/dist/skills/tutticode/references/mixing.md +48 -6
  21. package/dist/skills/tutticode/references/muse.md +7 -0
  22. package/dist/skills/tutticode/references/sounds.md +26 -0
  23. package/dist/skills/tutticode/references/tuttidaw-extensions.md +13 -6
  24. package/dist/skills/tutticode-sound-for-media/SKILL.md +11 -1
  25. package/dist/skills/tutticode-sound-for-media/references/music-recipes.md +30 -6
  26. package/dist/tutticode.cjs +2 -2
  27. package/package.json +1 -1
@@ -0,0 +1,362 @@
1
+ # TuttiCode language reference
2
+
3
+ The language spec in full detail, generated from the engine. Each section is
4
+ one reference id (spec-<id>).
5
+
6
+ ## spec-grammar: Full EBNF grammar
7
+
8
+ The normative grammar with its lexical rules (tokens, strings and escapes, comments, mini tokenization), resource limits and syntax examples.
9
+
10
+ ```text
11
+ (* PATTERN LANGUAGE 1.9.0
12
+ Normative syntax; recursive-descent parsers remain the implementation.
13
+ EBNF: , concatenation; | choice; [] optional; {} repetition; ? lexical ?.
14
+ Whitespace/comments are skipped between JS tokens, except call "(" must
15
+ not have a newline before it. Statements end with ";" or a newline/EOF.
16
+ Newlines inside block comments do not end statements or prevent calls.
17
+ Identifiers exclude the unsupported keywords listed below in expressions
18
+ and labels (bindings and arrow parameters accept any identifier token).
19
+ At statement start, let/const/var always begin a binding, not an expression.
20
+ Lexical maximal munch applies. Resource limits and numeric constraints
21
+ below are part of validity, not permission to expand without a budget.
22
+ @example lang: const groove = s("bd sd"); $: groove.fast(2);
23
+ @example lang: var a = [1, 2,]; let f = (x, i,) => { return x.transpose(i); }; note("c4").echoWith(2, 1/8, f)
24
+ @example lang: ; note(0x3c + .5 * 2 % 2).velocity(1e-1)
25
+ @example lang: note(`c4 e4`).every(4, x => x.rev())
26
+ @example lang: note('c4').off(1/8, () => note("g4"))
27
+ @example lang: setDefaultVoicings('ireal'); chord("C").anchor("D5").mode("below").offset(1).voicing().arp("0 1 2")
28
+ @example lang: bass: note("c2").inst("Drift:Deep Bass", {cutoff: 600}).fx("Reverb", {decayTime: "4s", "dryWetBalance": 0.3,})
29
+ @example lang: bus("verb").fx("Reverb"); master.fx("Limiter").mix({vol: -1}); keys: note("c3 e3").send("verb", -12).mix({pan: -0.3})
30
+ @example lang: bus("verb"); group("drums").fx("Drum Buss").mix({vol: -3}).send("verb", -12); kick: s("bd*4").group("drums")
31
+ *)
32
+ program = { statement };
33
+ statement = ";" | binding, end | label, ":", expression, end | expression, end;
34
+ binding = ("let" | "const" | "var"), identifier, "=", expression;
35
+ label = ? label-identifier ?;
36
+ end = ";" | ? newline-or-eof ?;
37
+ expression = arrow | additive;
38
+ arrow = (identifier | "(", [parameters], ")"), "=>", (expression | "{", "return", expression, [";"], "}");
39
+ parameters = identifier, {",", identifier}, [","];
40
+ additive = multiplicative, {("+" | "-"), multiplicative};
41
+ multiplicative = unary, {("*" | "/" | "%"), unary};
42
+ unary = ("+" | "-"), unary | postfix;
43
+ postfix = primary, {".", identifier | ? same-line ? , "(", [arguments], ")"};
44
+ arguments = argument, {",", argument}, [","];
45
+ argument = expression | object;
46
+ primary = number | string | ? expression-identifier ? | "[", [arguments], "]" | "(", expression, ")";
47
+ object = "{", [property, {",", property}, [","]], "}";
48
+ property = (identifier | string), ":", argument;
49
+ identifier = ? identifier-token ?;
50
+ number = ? js-number-token ?;
51
+ string = ? js-string-token ?;
52
+ (* identifier-token: [A-Za-z_$][A-Za-z0-9_$]*; e.g. bass_2, $.
53
+ expression-identifier excludes function class new for while do if import
54
+ export await async this eval with delete yield try throw switch.
55
+ label-identifier also excludes let const var (binding keywords).
56
+ js-number-token: hexadecimal 0[xX][0-9a-fA-F]+, or
57
+ (digits [ "." [digits] ] | "." digits) [ [eE] ["+"|"-"] digits ].
58
+ Numeric literals must evaluate to finite numbers; full tokens are consumed
59
+ regardless of length (subject to the compiler's source-length budget).
60
+ js-string-token: matching single/double/backtick quotes. Single/double
61
+ strings cannot contain an unescaped LF; backticks can. Backticks cannot
62
+ contain unescaped ${. Backslash escapes n t r 0 b f v decode; other
63
+ escapes decode to their following character (no JS unicode/hex decoding).
64
+ Comments: // to LF, or closed slash-star ... star-slash.
65
+ Strings in single quotes stay literal names; double/backtick strings
66
+ become mini patterns when consumed as patterns.
67
+ Object keys __proto__, prototype and constructor are refused.
68
+ *)
69
+
70
+ (* TuttiDAW device calls (1.1.0) are ordinary postfix method calls whose
71
+ names and arguments the device catalogue checks, not new syntax:
72
+ device-method = "." ("inst" | "fx" | "midifx"), "(", device-name, [",", param-object], ")";
73
+ device-name = string; constant: "Device" | "Device:Preset" | unique preset
74
+ param-object = object; keys: param keys, unique labels, on, id, sidechain
75
+ value = number | unit-string | boolean | pattern;
76
+ unit-string = '"' number unit '"'; "250ms" "2s" "-6dB" "1.2kHz" "30%"
77
+ Enum values are their label strings or a 0-based index. Device methods
78
+ are methods only: fx(...) is not a function.
79
+ *)
80
+
81
+ (* TuttiDAW mixing (1.2.0) is ordinary calls too: a bus statement and the
82
+ name master are expressions whose value is no voice.
83
+ mix-method = "." "mix", "(", mix-object, ")" | "." "send", "(", string, ",", level, ")";
84
+ mix-object = object; keys: vol (dB, -70..6), pan (-1..1), mute (boolean)
85
+ bus-statement = "bus", "(", string, ")", { "." "fx" "(" ... ")" | mix-method };
86
+ master-statement = "master", { "." "fx" "(" ... ")" | "." "mix", "(", mix-object, ")" };
87
+ level = number | pattern; dB, -70..0 for a send
88
+ Levels may be patterns or envelopes. A bus name: 1..64 characters, no
89
+ mini-notation punctuation.
90
+ *)
91
+
92
+ (* Groups (1.3.0): group-statement = "group", "(", string, ")", { "." "fx" "(" ... ")" | mix-method };
93
+ a voice joins with "." "group", "(", string, ")". No group in a
94
+ group. *)
95
+
96
+ (* Device params may be patterns (1.4.0; buses/groups/master 1.6.0) and
97
+ envelopes (1.7.0): ramp(from, to, bars); env("0:-60 3/4:0~.5 _2:-6",
98
+ bars?) pos:value in bars, ~k curve -1..1, _ step, same pos jumps;
99
+ env([[pos, value, {curve: k|[x1,y1,x2,y2]}|"hold"]]); .once() no loop. *)
100
+
101
+ (* 1.8.0: ensembleSet({...}) statement, constants. *)
102
+
103
+ (* Songs with sections (1.9.0), statements of ordinary calls:
104
+ track-statement = "track", "(", string, ")", { "." ("inst" | "fx" | "midifx") "(" ... ")" | mix-method };
105
+ section-statement = "section", "(", string, ",", bars, ",", object, ")"; keys: track names, values: patterns
106
+ form-statement = "form", "(", string, ")"; section names in order; [..] group, !n repeat, @n bars
107
+ A section name: letters, digits, _ (name#k overrides the k-th
108
+ occurrence, which may leave out bars: it keeps the section's); bars
109
+ 1..256. Without form, sections play in order. *)
110
+
111
+ (* Mini whitespace is skipped between tokens except where adjacency is
112
+ specified for suffixes. Spaces distinguish standalone bang from replication.
113
+ Layers may be empty; comma and pipe must not mix in one group.
114
+ @example mini: bd sd [~ bd] sd, hh*8
115
+ @example mini: <c4 e4 g4> [c4,e4,g4] [bd|sd]
116
+ @example mini: {bd sd, hh hh hh}%4
117
+ @example mini: 0 .. 3 . 3 .. 0
118
+ @example mini: c4!2 _ c4 ! c4@2 c4?0.3
119
+ @example mini: bd(3,8,1) hh*<2 3> sd/2 bd:1
120
+ @example mini: . - ~ . [ ] < > { }
121
+ @example mini: -2 .5 eb3 f#2 C^minor c'4
122
+ @example mini: C-7b5 C-^7 C-6
123
+ *)
124
+ mini = layers;
125
+ layers = sequence, ( {",", sequence} | {"|", sequence} );
126
+ sequence = {dot}, [group], {dot, [group]};
127
+ group = step, {gap, step | hold | ? spaced-bang ? | numeric-range};
128
+ dot = ? mini-dot ?;
129
+ gap = ? optional-mini-space ?;
130
+ hold = ? mini-hold ?;
131
+ numeric-range = ? numeric-range-tail ?;
132
+ step = term, {modifier};
133
+ modifier = ("*" | "/"), gap, term
134
+ | "!", [? adjacent-count ?]
135
+ | "@", gap, ? positive-weight ?
136
+ | "?", [? adjacent-probability ?]
137
+ | "(", nonempty-sequence, ",", nonempty-sequence, [",", nonempty-sequence], ")"
138
+ | ":", gap, term;
139
+ term = ? mini-number-or-word ? | "~" | "-"
140
+ | "[", layers, "]" | "<", layers, ">"
141
+ | "{", sequence, {",", sequence}, "}", ["%", gap, term];
142
+ nonempty-sequence = {dot}, group, {dot, [group]};
143
+ (* mini-number-or-word: maximal token from [A-Za-z0-9_#^'] plus internal
144
+ "-" when the token begins with a letter (chord qualities like C-7b5), or a leading
145
+ "-" or "." when starting a number and a decimal "." followed by digits.
146
+ All-underscore words tokenize as separate holds, never as terms.
147
+ Number classification: -?(digits["."digits] | "."digits); other tokens
148
+ are words. Finite only. A trailing decimal dot is a group separator.
149
+ mini-dot: "." with optional surrounding whitespace.
150
+ mini-hold: "_" (including runs "__") with optional whitespace.
151
+ spaced-bang: whitespace followed by "!" copies the previous event once;
152
+ "!n" replication instead requires adjacency. No leading hold or bang:
153
+ both need a previous event in the current dot-separated group.
154
+ numeric-range-tail: ".." and a number; previous node must be a numeric
155
+ atom; endpoints safe integers at most 1024 apart. E.g. "0 .. 3".
156
+ adjacent-count: adjacent number, floor >= 1; copies charged before allocation.
157
+ positive-weight: number > 0 and <= 1048576.
158
+ adjacent-probability: adjacent number in [0,1]; default "?" is 0.5.
159
+ "?", "(", ":" suffixes must be adjacent to the previous token.
160
+ Euclid arguments are nonempty sequences, two or three total.
161
+ "|" is forbidden directly inside braces; nested []/<> can use it.
162
+ Depth: mini 48, JS 120; JS chains 400; compiler budgets additionally apply.
163
+ *)
164
+
165
+ ```
166
+
167
+ ## spec-signatures: Typed signatures
168
+
169
+ Every function, method and signal with argument kinds and arity.
170
+
171
+ ```text
172
+ Argument kinds: number | pattern string | literal string | signal | function | pattern | pair | lookup | device name | params object.
173
+ Pattern means a previously built pattern. Pair means [number, pattern]. Lookup means a list of values/patterns or an object keyed by selector names.
174
+ Literal string means a single-quoted name, not a double-quoted mini pattern. setDefaultVoicings('ireal') is a statement, not a pattern source.
175
+ Optional args are marked ?. Variadic tails repeat. Zero-argument control methods name the receiver's values. Transform functions can be arrows or supported curried methods (e.g. rev).
176
+ aliasBank(bank: literal string | lookup, alias?: literal string) -> pattern; arity 1..2
177
+ all(fn: function) -> void; arity 1..1
178
+ arrange(...sections: pair) -> pattern; arity 1..*
179
+ bus(name: literal string) -> pattern; arity 1..1
180
+ cat(...patterns?: number | pattern string | signal | pattern) -> pattern; arity 0..*
181
+ chooseCycles(...patterns: number | pattern string | signal | pattern) -> pattern; arity 1..*
182
+ chord(symbols: number | pattern string | signal | pattern) -> pattern; arity 1..1
183
+ ensembleSet(sections: params object) -> pattern; arity 1..1
184
+ env(points: literal string | lookup, bars?: number) -> pattern; arity 1..2
185
+ fastcat(...patterns?: number | pattern string | signal | pattern) -> pattern; arity 0..*
186
+ form(order: literal string) -> pattern; arity 1..1
187
+ group(name: literal string) -> pattern; arity 1..1
188
+ hush() -> pattern; arity 0..0
189
+ irand(n: number) -> pattern; arity 1..1
190
+ pick(indices: number | pattern string | signal | pattern, listOrObject: lookup) -> pattern; arity 2..2
191
+ pure(value: number | pattern string | signal | pattern) -> pattern; arity 1..1
192
+ ramp(from: number | literal string, to: number | literal string, bars: number) -> pattern; arity 3..3
193
+ randcat(...patterns: number | pattern string | signal | pattern) -> pattern; arity 1..*
194
+ run(n: number | pattern string | signal) -> pattern; arity 1..1
195
+ section(name: literal string, bars: number | params object, tracks?: params object) -> pattern; arity 2..3
196
+ seq(...patterns?: number | pattern string | signal | pattern) -> pattern; arity 0..*
197
+ sequence(...patterns?: number | pattern string | signal | pattern) -> pattern; arity 0..*
198
+ setcpm(cpm: number) -> void; arity 1..1
199
+ setCpm(arg1: number) -> void; arity 1..1
200
+ setcps(cps: number) -> void; arity 1..1
201
+ setCps(arg1: number) -> void; arity 1..1
202
+ setDefault(control: literal string | lookup, value?: number | literal string) -> pattern; arity 1..2
203
+ setDefaultVoicings(name: literal string) -> void; arity 1..1
204
+ setGainCurve(fn: function) -> pattern; arity 1..1
205
+ slider(value: number, min?: number, max?: number, step?: number) -> pattern; arity 1..4
206
+ slowcat(...patterns?: number | pattern string | signal | pattern) -> pattern; arity 0..*
207
+ soundAlias(sound: literal string, alias: literal string) -> pattern; arity 2..2
208
+ squeeze(indices: number | pattern string | signal | pattern, listOrObject: lookup) -> pattern; arity 2..2
209
+ stack(...patterns?: number | pattern string | signal | pattern) -> pattern; arity 0..*
210
+ stepcat(...steps?: number | pattern string | signal | pattern | pair) -> pattern; arity 0..*
211
+ timecat(...steps?: number | pattern string | signal | pattern | pair) -> pattern; arity 0..*
212
+ timeCat(...steps?: number | pattern string | signal | pattern | pair) -> pattern; arity 0..*
213
+ toggle(value: number | pattern string | signal | pattern) -> pattern; arity 1..1 (Tutti extension, not in Strudel)
214
+ track(name: literal string) -> pattern; arity 1..1
215
+ .add(x: number | pattern string | signal | pattern) -> pattern; arity 1..1
216
+ .almostAlways(f: function) -> pattern; arity 1..1
217
+ .almostNever(f: function) -> pattern; arity 1..1
218
+ .always(f: function) -> pattern; arity 1..1
219
+ .anchor(note: number | pattern string | signal | pattern) -> pattern; arity 1..1
220
+ .arp(indices: number | pattern string | signal | pattern) -> pattern; arity 1..1
221
+ .cat(...patterns: number | pattern string | signal | pattern) -> pattern; arity 1..*
222
+ .chop(parts: number | pattern string | signal) -> pattern; arity 1..1
223
+ .chord(symbols?: number | pattern string | signal | pattern) -> pattern; arity 0..1
224
+ .chunk(n: number | pattern string | signal, f: function) -> pattern; arity 2..2
225
+ .cpm(cyclesPerMinute: number | pattern string | signal) -> pattern; arity 1..1
226
+ .degrade() -> pattern; arity 0..0
227
+ .degradeBy(amount: number | pattern string | signal) -> pattern; arity 1..1
228
+ .density(factor: number | pattern string | signal) -> pattern; arity 1..1
229
+ .dict(name: literal string | pattern string | pattern) -> pattern; arity 1..1
230
+ .div(x: number | pattern string | signal | pattern) -> pattern; arity 1..1
231
+ .early(cycles: number | pattern string | signal) -> pattern; arity 1..1
232
+ .echo(times: number, time: number, feedback: number) -> pattern; arity 3..3
233
+ .echowith(times: number, time: number, f: function) -> pattern; arity 3..3
234
+ .echoWith(times: number, time: number, f: function) -> pattern; arity 3..3
235
+ .euclid(pulses: number | pattern string | signal, steps: number | pattern string | signal) -> pattern; arity 2..2
236
+ .euclidLegato(pulses: number | pattern string | signal, steps: number | pattern string | signal, rotation?: number | pattern string | signal) -> pattern; arity 2..3
237
+ .euclidLegatoRot(pulses: number | pattern string | signal, steps: number | pattern string | signal, rotation: number | pattern string | signal) -> pattern; arity 3..3
238
+ .euclidRot(pulses: number | pattern string | signal, steps: number | pattern string | signal, rotation: number | pattern string | signal) -> pattern; arity 3..3
239
+ .every(n: number | pattern string | signal, f: function) -> pattern; arity 2..2
240
+ .fast(factor: number | pattern string | signal) -> pattern; arity 1..1
241
+ .fastcat(...patterns: number | pattern string | signal | pattern) -> pattern; arity 1..*
242
+ .firstof(n: number | pattern string | signal, f: function) -> pattern; arity 2..2
243
+ .firstOf(n: number | pattern string | signal, f: function) -> pattern; arity 2..2
244
+ .fit() -> pattern; arity 0..0
245
+ .fmap(fn: function) -> pattern; arity 1..1
246
+ .fx(name: device name, params?: params object) -> pattern; arity 1..2 (Tutti extension, not in Strudel)
247
+ .group(name: literal string) -> pattern; arity 1..1
248
+ .hush() -> pattern; arity 0..0
249
+ .inhabit(listOrObject: lookup) -> pattern; arity 1..1
250
+ .inside(n: number, f: function) -> pattern; arity 2..2
251
+ .inst(name: device name, params?: params object) -> pattern; arity 1..2 (Tutti extension, not in Strudel)
252
+ .iter(n: number | pattern string | signal) -> pattern; arity 1..1
253
+ .iterback(n: number | pattern string | signal) -> pattern; arity 1..1
254
+ .iterBack(n: number | pattern string | signal) -> pattern; arity 1..1
255
+ .jux(f: function) -> pattern; arity 1..1
256
+ .lastof(n: number | pattern string | signal, f: function) -> pattern; arity 2..2
257
+ .lastOf(n: number | pattern string | signal, f: function) -> pattern; arity 2..2
258
+ .late(cycles: number | pattern string | signal) -> pattern; arity 1..1
259
+ .layer(...f: function) -> pattern; arity 1..*
260
+ .loopAt(cycles: number | pattern string | signal) -> pattern; arity 1..1
261
+ .mask(pattern: number | pattern string | signal | pattern) -> pattern; arity 1..1
262
+ .midifx(name: device name, params?: params object) -> pattern; arity 1..2 (Tutti extension, not in Strudel)
263
+ .mix(mixer: params object) -> pattern; arity 1..1
264
+ .mode(alignment: literal string | pattern string | pattern) -> pattern; arity 1..1
265
+ .mul(x: number | pattern string | signal | pattern) -> pattern; arity 1..1
266
+ .never(f: function) -> pattern; arity 1..1
267
+ .off(time: number | pattern string | signal, f: function) -> pattern; arity 2..2
268
+ .offset(n: number | pattern string | signal) -> pattern; arity 1..1
269
+ .often(f: function) -> pattern; arity 1..1
270
+ .once() -> pattern; arity 0..0
271
+ .outside(n: number, f: function) -> pattern; arity 2..2
272
+ .palindrome() -> pattern; arity 0..0
273
+ .pick(listOrObject: lookup) -> pattern; arity 1..1
274
+ .pickOut(listOrObject: lookup) -> pattern; arity 1..1
275
+ .pickRestart(listOrObject: lookup) -> pattern; arity 1..1
276
+ .pickSqueeze(listOrObject: lookup) -> pattern; arity 1..1
277
+ .ply(n: number | pattern string | signal) -> pattern; arity 1..1
278
+ .range(min: number | pattern string | signal, max: number | pattern string | signal) -> pattern; arity 2..2
279
+ .range2(min: number | pattern string | signal, max: number | pattern string | signal) -> pattern; arity 2..2
280
+ .rangex(min: number | pattern string | signal, max: number | pattern string | signal) -> pattern; arity 2..2
281
+ .rarely(f: function) -> pattern; arity 1..1
282
+ .rev() -> pattern; arity 0..0
283
+ .rootNotes(octave?: number) -> pattern; arity 0..1
284
+ .scale(name: pattern string | pattern) -> pattern; arity 1..1
285
+ .scrub(positions: number | pattern string | signal | pattern) -> pattern; arity 1..1
286
+ .seg(n: number | pattern string | signal) -> pattern; arity 1..1
287
+ .segment(n: number | pattern string | signal) -> pattern; arity 1..1
288
+ .send(bus: literal string, dB: number | pattern string | signal) -> pattern; arity 2..2
289
+ .seq(...patterns: number | pattern string | signal | pattern) -> pattern; arity 1..*
290
+ .set(pattern: number | pattern string | signal | pattern) -> pattern; arity 1..1
291
+ .slice(count: number | pattern string | signal | pattern, indices: number | pattern string | signal | pattern) -> pattern; arity 2..2
292
+ .slow(factor: number | pattern string | signal) -> pattern; arity 1..1
293
+ .slowcat(...patterns: number | pattern string | signal | pattern) -> pattern; arity 1..*
294
+ .someCycles(f: function) -> pattern; arity 1..1
295
+ .someCyclesBy(p: number | pattern string | signal, f: function) -> pattern; arity 2..2
296
+ .sometimes(f: function) -> pattern; arity 1..1
297
+ .sometimesBy(p: number | pattern string | signal, f: function) -> pattern; arity 2..2
298
+ .sparsity(factor: number | pattern string | signal) -> pattern; arity 1..1
299
+ .splice(count: number | pattern string | signal, indices: number | pattern string | signal | pattern) -> pattern; arity 2..2
300
+ .squeeze(listOrObject: lookup) -> pattern; arity 1..1
301
+ .stack(...patterns: number | pattern string | signal | pattern) -> pattern; arity 1..*
302
+ .striate(parts: number | pattern string | signal) -> pattern; arity 1..1
303
+ .struct(pattern: number | pattern string | signal | pattern) -> pattern; arity 1..1
304
+ .stut(times: number, feedback: number, time: number) -> pattern; arity 3..3
305
+ .stutwith(times: number, time: number, f: function) -> pattern; arity 3..3
306
+ .stutWith(times: number, time: number, f: function) -> pattern; arity 3..3
307
+ .sub(x: number | pattern string | signal | pattern) -> pattern; arity 1..1
308
+ .superimpose(...f: function) -> pattern; arity 1..*
309
+ .swing(slices: number | pattern string | signal) -> pattern; arity 1..1
310
+ .swingBy(amount: number | pattern string | signal, slices: number | pattern string | signal) -> pattern; arity 2..2
311
+ .transpose(semitones: number | pattern string | signal | pattern) -> pattern; arity 1..1
312
+ .undegrade() -> pattern; arity 0..0
313
+ .undegradeBy(amount: number | pattern string | signal) -> pattern; arity 1..1
314
+ .voicing() -> pattern; arity 0..0
315
+ .voicings(dictionary?: literal string | pattern string | pattern) -> pattern; arity 0..1
316
+ .withValue(fn: function) -> pattern; arity 1..1
317
+ cosine: signal
318
+ cosine2: signal
319
+ isaw: signal
320
+ isaw2: signal
321
+ perlin: signal
322
+ rand: signal
323
+ rand2: signal
324
+ saw: signal
325
+ saw2: signal
326
+ sine: signal
327
+ sine2: signal
328
+ square: signal
329
+ square2: signal
330
+ tri: signal
331
+ tri2: signal
332
+ Controls, each .name(value) and name(value) with value: number | pattern string | signal | pattern -> pattern, or .name() naming the receiver's values.
333
+ ```
334
+
335
+ ## spec-controls: Controls and their aliases
336
+
337
+ Every control and every alias of one (cutoff=lpf...).
338
+
339
+ ```text
340
+ Controls, each .name(value) and name(value) with value: number | pattern string | signal | pattern -> pattern, or .name() naming the receiver's values: accelerate amp attack bank begin bpattack bpdecay bpenv bpf bpq bprelease bpsustain chorus clip coarse color compressor compressorAttack compressorKnee compressorRatio compressorRelease crush cut decay delay delayfeedback delaytime detune distort djf dry end fanchor fm fmattack fmdecay fmenv fmh fmsustain freq ftype gain hpattack hpdecay hpenv hpf hpq hprelease hpsustain iresponse kcutoff krush leslie loop lpattack lpdecay lpenv lpf lpq lprelease lpsustain n noise note orbit pan panchor pattack pcurve pdecay penv phaser phasercenter phaserdepth phasersweep postgain prelease psustain release room s shape size speed spread squiz sustain tremolo tremolodepth unison unit velocity vib vibmod vowel.
341
+ Control aliases: att=attack bandf=bpf bandq=bpq bp=bpf bpa=bpattack bpd=bpdecay bpe=bpenv bpr=bprelease bps=bpsustain colour=color ctf=lpf cutoff=lpf dec=decay delayfb=delayfeedback delayt=delaytime dfb=delayfeedback dist=distort dt=delaytime fmatt=fmattack fmdec=fmdecay fme=fmenv fmi=fm fmsus=fmsustain hcutoff=hpf hp=hpf hpa=hpattack hpd=hpdecay hpe=hpenv hpr=hprelease hps=hpsustain hresonance=hpq ir=iresponse legato=clip lp=lpf lpa=lpattack lpd=lpdecay lpe=lpenv lpr=lprelease lps=lpsustain patt=pattack pdec=pdecay ph=phaser phasdp=phaserdepth phc=phasercenter phd=phaserdepth phs=phasersweep prel=prelease rel=release resonance=lpq roomsize=size sound=s sus=sustain sz=size trem=tremolo tremdepth=tremolodepth v=vib vel=velocity vibrato=vib vmod=vibmod.
342
+ ```
343
+
344
+ ## spec-devices: Devices in full
345
+
346
+ Every instrument, MIDI effect and audio effect for .inst/.midifx/.fx (the core names the most-used).
347
+
348
+ ```text
349
+ voice.inst(name, params?) chooses the voice's instrument (one per voice); .midifx(name, params?) adds MIDI effects before it and .fx(name, params?) audio effects after it, in call order. TuttiDAW extensions: a program using them does not play on strudel.cc.
350
+ name: a constant string "Device" or "Device:Preset" (factory presets only), e.g. "Reverb:Large Hall", "Drift:Deep Bass", "Drum Rack:808 Core Kit". Unique bare preset names work; case/punctuation ignored.
351
+ params: an object {key: value}. Write the device's param keys (dryWetBalance, not the label "Dry/Wet"). Numbers are in the param's own unit (Hz, ms, dB, st...); unit strings convert: "250ms" "2s" "-6dB" "1.2kHz" "30%". Enums take their label ("UpDown", "1/16", "Dorian") or a 0-based index; on/off params take true/false. Out-of-range values are clamped with a warning. Reserved keys: on (device switch), id (a stable name for a repeated device: letters, digits, _ or -, not "instrument").
352
+ Params may be patterns or env/ramp (automation; "<1/16 1/8>" are labels), on buses/groups/master too.
353
+ Strudel controls keep their meaning: lpf/room/delay/... still add their devices; room/delay fold into an explicit Reverb/Delay of the voice. .inst beats s() for the instrument; s() still picks drum pads.
354
+ Devices apply where the code owns tracks: group programs and /code.
355
+ Mixing: bus(name).fx(...) is a return; voice.send(bus,-18..-10 dB) feeds it; .mix({vol,pan}) is the fader; master.fx(...) ends songs (Glue Compressor, Limiter). Voices sharing a reverb/delay: one wet bus (dryWetBalance:1) + sends, no copy per voice.
356
+ Ensemble parts: .inst("Ensemble:<Engine> <SOUND>", {...}) e.g. "Ensemble:Sample FLUTE"; part keys level atk dec sus rel pan chor dly rev (0..127, pan -64..63), engine controls by its labels (Physical: strc brit damp pos acc bow exc); their Sets: ensembleSet({reverb:{revSize:100}}).
357
+ Main keys: Drift: cutoff Hz, attack ms, release ms; Wavetable: cutoff Hz, attack ms, release ms; Analog: f1Freq Hz, a1Attack ms; Operator: aAttack ms, bLevel %; Reverb: decayTime ms, dryWetBalance; Delay: timeL ms, feedback %; Auto Filter: frequency Hz, Q; Compressor: threshold dB, ratio; Saturator: drive dB.
358
+ Instruments (.inst): Analog, Drift, Drum Rack, Ensemble, Operator, Sampler, Simpler, Wavetable; also Collision, DS Clang, DS Clap, DS Cymbal, DS FM, DS HH, DS Kick, DS Snare, DS Tom, Electric, Impulse, Meld, SixOp, Synth, Tension.
359
+ MIDI effects (.midifx): Arpeggiator, Chord, Note Length, Scale; also FM Arp, Note Echo, Pitch, Random, Velocity.
360
+ Audio effects (.fx): Auto Filter, Auto Pan-Tremolo, Chorus, Compressor, Delay, Echo, EQ Eight, Glue Compressor, Redux, Reverb, Saturator, Utility; also Align Delay, Amp, Auto Shift, Beat Repeat, Cabinet, Channel EQ, Corpus, Denoise, Drum Buss, Dynamic Tube, EQ Three, Erosion, Filter Delay, Gate, Grain Delay, Hybrid Reverb, Limiter, Multiband Dynamics, Overdrive, Pedal, Phaser-Flanger, Pitch Shifter, Resonators, Roar, Spectral Resonator, Spectral Time, Vinyl Distortion, Vocoder, Voice Leveler.
361
+ Any other device (LFO, Looper, Tuner, racks, plug-ins...) is not available in code.
362
+ ```
@@ -1,6 +1,6 @@
1
1
  # Melodies with do re mi
2
2
 
3
- Write notes in solfège (do re mi fa sol la si) wherever a note name goes; strudel.cc gets letters.
3
+ Write notes in solfège (do re mi fa sol la si) wherever a note name goes; Export to Strudel gives letters.
4
4
 
5
5
  ## Solfège is a note name
6
6
 
@@ -42,6 +42,42 @@ note("<[do3,mi3,sol3] [la2,do3,mi3] [fa2,la2,do3] [sol2,si2,re3]>").s("piano")
42
42
  Chord symbols (`chord("C^7")`, `.voicings()`) keep their letters: there `do`
43
43
  already means D diminished (`D` and `o`).
44
44
 
45
+ ## A folk tune
46
+
47
+ Many songs are learned as do re mi. Here is the opening of the Chinese folk
48
+ song *Mo Li Hua* ("Jasmine Flower"), which uses only five notes: do re mi
49
+ sol la.
50
+
51
+ ```js
52
+ setcpm(20)
53
+ note("mi4 [mi4 sol4] [la4 do5] [do5 la4] sol4 [sol4 la4] sol4 ~").s("piano")
54
+ ```
55
+
56
+ Those five notes are the pentatonic scale. Write the tune as steps of the
57
+ scale with `n()` and `.scale("do4:pentatonic")` (0 is do, 1 re, 2 mi,
58
+ 3 sol, 4 la, 5 the next do), and a new root moves the whole tune:
59
+
60
+ ```js
61
+ setcpm(20)
62
+ n("2 [2 3] [4 5] [5 4] 3 [3 4] 3 ~").scale("<do4:pentatonic sol3:pentatonic>").s("piano")
63
+ ```
64
+
65
+ ## A first song: do re mi and drums
66
+
67
+ Give the melody a beat. The drums go in a group with one fader
68
+ (`group` and `.group`, see [Mixing: groups](mixing-groups.md)), so the whole
69
+ kit sits under the tune:
70
+
71
+ ```js
72
+ setcpm(25)
73
+ group("drums").mix({vol: -6})
74
+
75
+ kick: s("bd*2 ~ bd").bank("tr808").group("drums")
76
+ hats: s("hh*8").bank("tr808").group("drums").mix({vol: -6})
77
+ tune: note("<[do4 mi4 sol4 mi4] [la4 sol4 mi4 re4]>").s("piano")
78
+ bass: note("<do2 la1>").s("sawtooth").lpf(500)
79
+ ```
80
+
45
81
  ## Where a word is not a note
46
82
 
47
83
  In `s()` and `sound()` words are sound names, so `s("do")` looks for a sound
@@ -50,10 +86,10 @@ word too: write `note("do")`. Solfège means a pitch only in `note()` /
50
86
  `.note()`, `.scale()` roots, `.mode()` and `.anchor()`, and `.add()` /
51
87
  `.sub()` / `.transpose()` on notes.
52
88
 
53
- ## Opening in strudel.cc
89
+ ## Exporting to Strudel
54
90
 
55
- strudel.cc reads letter names only. "Open in strudel.cc" rewrites solfège to
56
- letters for you (`"do re mi"` becomes `"c d e"`), so the program plays the same
57
- there.
91
+ strudel.cc reads letter names only. "Export to Strudel" (the code page's Export
92
+ & Share tab) rewrites solfège to letters for you (`"do re mi"` becomes
93
+ `"c d e"`), so the program plays the same there.
58
94
 
59
95
  Source: https://tuttidaw.com/code/learn/melodies-with-do-re-mi
@@ -12,8 +12,10 @@ voices in it.
12
12
  | `group(name)` | a group of voices | a group track inside the program's group |
13
13
  | `.group(name)` | puts a voice in a group | the voice's track becomes a child of that group |
14
14
 
15
- Like `bus` and `.send`, these are TuttiDAW-only: a program that uses them
16
- does not play on strudel.cc.
15
+ Like `bus` and `.send`, these are TuttiDAW-only. "Export to Strudel" (the
16
+ code page's Export & Share tab) opens the program on strudel.cc without them,
17
+ after listing what it leaves out: there every voice plays on its own, without
18
+ the group's effects and fader.
17
19
 
18
20
  ## A drum group
19
21
 
@@ -51,8 +53,9 @@ bass: note("c2 ~ c2 g1").s("sawtooth").lpf(500)
51
53
  Buses belong to the program: a group can send to any bus the program
52
54
  declares, and its voices can still send on their own too.
53
55
 
54
- A group's fader, pan and send levels are numbers (a group track has no clip
55
- to move them). A group cannot be put inside another group.
56
+ A group's fader, pan, send levels and device params can be patterns, like
57
+ a voice's: they become the group track's automation (see Automation). A
58
+ group cannot be put inside another group.
56
59
 
57
60
  ## A voice without a group
58
61
 
@@ -1,6 +1,6 @@
1
1
  # Mixing: buses and master
2
2
 
3
- Shared reverbs and delays on buses, sends from the voices, each voice's fader and pan, and the program's master chain - the same tracks Open in TuttiDAW makes.
3
+ Shared reverbs and delays on buses, sends from the voices, each voice's fader and pan, sidechain ducking, and the program's master chain - the same tracks Open in TuttiDAW makes.
4
4
 
5
5
  A program can mix itself the way a studio project is mixed: **buses** for
6
6
  shared effects, **sends** from the voices into them, a **fader and pan** per
@@ -14,8 +14,9 @@ makes exactly these tracks.
14
14
  | `.mix({vol, pan, mute})` | a voice's (or a bus's) mixer | the track's fader, pan and mute |
15
15
  | `master` | the program's own bus | the program group's devices and fader |
16
16
 
17
- These are TuttiDAW-only, like `.fx`: a program that uses them does not play
18
- on strudel.cc.
17
+ These are TuttiDAW-only, like `.fx`. "Export to Strudel" (the code page's
18
+ Export & Share tab) opens the program on strudel.cc without them, after
19
+ listing what it leaves out: there the voices play dry, each at its own level.
19
20
 
20
21
  ## Buses and sends
21
22
 
@@ -86,14 +87,55 @@ bass: note("<c2 g1 a1 f1>").ply(4).s("sawtooth").lpf(600)
86
87
 
87
88
  On this page the master is what you hear. In a project, a program lives in
88
89
  its own group track, so `master` is that group's chain; it feeds the
89
- project's master like any other track.
90
+ project's master like any other track. A bus's and the master's `vol`,
91
+ `pan` and device params can be patterns too (see Automation).
92
+
93
+ ## Sidechain: duck under the kick
94
+
95
+ A compressor normally listens to the sound it compresses. With
96
+ `sidechain` it listens to another voice instead: give the pad's compressor
97
+ `sidechain: "kick"` and the pad gets quieter on every kick hit, then comes
98
+ back - the "pumping" of dance music, and room for the kick in any mix.
99
+
100
+ ```js
101
+ kick: s("bd*4").bank("tr909")
102
+ pad: chord("<Am F C G>").voicing().s("supersaw").lpf(2500).gain(0.3)
103
+ .fx("Compressor", {threshold: -30, ratio: 8, attack: 1, release: 120, sidechain: "kick"})
104
+ ```
105
+
106
+ `release` sets how fast the pad comes back (shorter pumps harder), and
107
+ `threshold` and `ratio` how deep it ducks. Name the source like this:
108
+
109
+ | `sidechain:` | listens to |
110
+ |---|---|
111
+ | `"kick"` | the voice labelled `kick:` |
112
+ | `"bus:verb"` | the bus `bus("verb")` |
113
+ | `"group:drums"` | the group `group("drums")` |
114
+
115
+ Two more keys shape what the compressor hears: `sidechainGain` (dB, how
116
+ loud the source is to the detector) and `sidechainFilter` (a frequency:
117
+ only the source's lows below it key the compressor, so the kick's low thump
118
+ ducks the pad and the hats do not; `false` turns the filter off):
119
+
120
+ ```js
121
+ group("drums")
122
+ kick: s("bd*4").bank("tr909").group("drums")
123
+ hats: s("hh*8").bank("tr909").group("drums")
124
+ bass: note("<a1 f1 c2 g1>").ply(8).s("sawtooth").lpf(500)
125
+ .fx("Compressor", {threshold: -30, ratio: 6, sidechain: "group:drums", sidechainFilter: "150Hz"})
126
+ ```
127
+
128
+ Devices with a sidechain input take it: Compressor, Glue Compressor, Gate,
129
+ Auto Filter and Multiband Dynamics. A source the program does not have is a
130
+ warning, and the sidechain stays off; a voice cannot key itself.
90
131
 
91
132
  ## In the studio
92
133
 
93
134
  Open the program in TuttiDAW and you get its group with one track per
94
135
  voice, the buses as return tracks inside the group, and the master chain on
95
- the group. The values the code sets belong to the code: change one by hand
96
- (a device param, a fader, pan or mute, or a device added, removed or moved
136
+ the group, each sidechain routed from its source's track. The values the
137
+ code sets belong to the code: change one by hand (a device param, a
138
+ sidechain source, a fader, pan or mute, or a device added, removed or moved
97
139
  on a bus or the master) and that voice, bus or master is detached from the
98
140
  code (Re-attach brings it back). Remove
99
141
  a bus from the code and its return track stays, marked "not in code".
@@ -31,6 +31,13 @@ bass: n("<0 -2 -4 -5>").scale("F2:minor").s("gm_acoustic_bass")
31
31
 
32
32
  - Every change is checked first: Muse compiles and renders the program before
33
33
  it touches the editor, and repairs its own mistake once if the check fails.
34
+ A part that would not sound as written (a note that is not a pitch, a
35
+ voice whose notes are all dropped, an ignored call) fails the check too;
36
+ silence on purpose (`silence`, rests, a muted voice) is fine. If the
37
+ repair fails, it keeps the best version that plays, with the broken
38
+ voices commented out, and says what it left out. If you edited the code
39
+ meanwhile, your edit stays and the panel offers **Use Muse's version**
40
+ instead; after a further edit it no longer writes over yours.
34
41
  - A change arrives as **one undoable edit**: Ctrl/Cmd+Z takes it back.
35
42
  - If you changed the code while Muse was working, it asks before overwriting
36
43
  your edit (unless Autopilot is on).
@@ -79,6 +79,32 @@ reverb. On TuttiCode up to four Ensemble voices share one Set; **Open in
79
79
  TuttiDAW** builds the same Set in your project. The [device
80
80
  catalogue](devices.md) lists every Ensemble sound.
81
81
 
82
+ ### The Set's chorus, delay and reverb
83
+
84
+ A part sends to the Set's shared effects with its own `chor`, `dly` and
85
+ `rev` (0..127). `ensembleSet({...})` sets those effects, and the Set's
86
+ master, for every Ensemble Set the program plays in:
87
+
88
+ ```js
89
+ setcpm(24)
90
+ ensembleSet({reverb: {revType: "HALL", revSize: 110}, delay: {dlyTime: "1/8D", dlyFdbk: 40}})
91
+ pluck: n("0 2 4 6 4 2").scale("E:minor").inst("Ensemble:Physical PLUCK", {rev: 50, dly: 30})
92
+ flute: note("<b4 a4 g4 e4>").inst("Ensemble:Sample FLUTE", {rev: 70})
93
+ ```
94
+
95
+ | Section | Keys |
96
+ |---|---|
97
+ | `chorus` | `choRate`, `choDepth` (0..127) |
98
+ | `delay` | `dlyTime` (`"1/4"`, `"1/8"`, `"1/8D"`, `"1/4D"`, `"8T"`...), `dlyFdbk` (0..120), `dlyColor`, `dlyMix` (0..127) |
99
+ | `reverb` | `revType` (`"ROOM"`, `"SPRING"`, `"HALL"`), `revSize`, `revDamp` (0..127) |
100
+ | `master` | `master` (level, 0..127), `lowcut` (`"OFF"`, `"LOWCUT"`, `"BASS+"`), `tune` (cents, -50..50) |
101
+
102
+ Leave out what you do not change: a new Set's values stay. Write one
103
+ `ensembleSet` per program, with numbers and names, not patterns. The
104
+ code writes these values each time it runs, so in the studio a change
105
+ you make by hand lasts until the next evaluate. A Learn example opened in
106
+ TuttiCode starts with its lesson's `ensembleSet`.
107
+
82
108
  ## Your own samples
83
109
 
84
110
  `samples(...)` declares audio files hosted on the web (HTTPS). The names you
@@ -12,8 +12,15 @@ find in the studio's browser - with their factory presets and parameters:
12
12
  | `.midifx(name, params)` | a MIDI effect | before the instrument, in call order |
13
13
  | `.fx(name, params)` | an audio effect | after the instrument, in call order |
14
14
 
15
- These are TuttiDAW-only: a program that uses them does not play on
16
- strudel.cc, so TuttiCode hides its "Open in strudel.cc" link for it.
15
+ Mixing adds more: `bus(name)` and `.send(bus, dB)` for shared effects,
16
+ `.mix({vol, pan, mute})` for a track's fader, `master` for the program's own
17
+ chain ([Mixing](mixing.md)), and `group(name)` / `.group(name)` for group tracks
18
+ ([Mixing: groups](mixing-groups.md)).
19
+
20
+ All of these are TuttiDAW-only: strudel.cc has no such devices or tracks.
21
+ To take a program to strudel.cc, use "Export to Strudel" in the code page's
22
+ Export & Share tab: it lists what it leaves out, then opens the code without
23
+ the TuttiDAW-only calls, with `do re mi` turned into letters.
17
24
 
18
25
  ## Instruments: .inst
19
26
 
@@ -83,10 +90,10 @@ s("bd*4, ~ sd, hh*8")
83
90
  .fx("Compressor", { threshold: "-18dB", ratio: 4, attack: "10ms" })
84
91
  ```
85
92
 
86
- Values outside a parameter's range are clamped, with a warning. Device
87
- parameters take constant values for now; a pattern such as `"<300 600>"` as a
88
- device parameter is reported and not applied yet. Strudel controls
89
- (`.lpf("<300 600>")`) can already change over time.
93
+ Values outside a parameter's range are clamped, with a warning. A pattern
94
+ or a signal as a device parameter automates it: `{cutoff: "<300 600>"}`
95
+ changes the cutoff every cycle, `sine.range(300, 3000)` sweeps it (the
96
+ "Automation" lesson).
90
97
 
91
98
  ## Strudel controls and devices together
92
99
 
@@ -45,7 +45,7 @@ agent gets the approval). Never change the program to remove the tag.
45
45
  Recipe index:
46
46
 
47
47
  - Sound effects (`references/sfx-recipes.md`): `riser`, `impact`, `whoosh`, `ui-click`, `ui-confirm`, `ui-error`, `notification`, `transition`, `ambience-bed`
48
- - Music beds (`references/music-recipes.md`): `lofi-bed`, `upbeat-pop-bed`, `cinematic-tension`, `ambient-bed`, `chiptune-loop`
48
+ - Music beds (`references/music-recipes.md`): `lofi-bed`, `upbeat-pop-bed`, `cinematic-tension`, `ambient-bed`, `chiptune-loop`, `kick-ducked-bed`
49
49
 
50
50
  ## Loudness and length
51
51
 
@@ -91,6 +91,16 @@ Recipe index:
91
91
  - a render with a `watermark` (the anonymous voice tag) without telling
92
92
  the user it is there and that `tutticode login` removes it.
93
93
 
94
+ ## Mixing a bed
95
+
96
+ With several voices, mix like a studio: one `bus("verb")` (and a
97
+ `bus("echo")`) that the voices `.send("verb", dB)` to, so they share one
98
+ room, instead of a `.room()` per voice; `.mix({vol: -6})` (dB) to balance
99
+ the voices; `master.fx("Limiter", ...)` last, so the loudness target fits
100
+ under the ceiling; `group("drums")` + `.group("drums")` for one drum fader.
101
+ Single-voice effects keep `.room` / `.delay`. Details: the `tutticode`
102
+ skill's `references/mixing.md`.
103
+
94
104
  ## Sound effects
95
105
 
96
106
  Synth voices work everywhere. Noise is `s("white")`, `s("pink")` or