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.
- package/README.md +11 -0
- package/README.zh-CN.md +10 -0
- package/dist/skills/tutticode/SKILL.md +11 -10
- package/dist/skills/tutticode/references/agent-loop.md +15 -0
- package/dist/skills/tutticode/references/automation.md +168 -0
- package/dist/skills/tutticode/references/devices/compressor.md +2 -0
- package/dist/skills/tutticode/references/devices/filter.md +2 -0
- package/dist/skills/tutticode/references/devices/gate.md +2 -0
- package/dist/skills/tutticode/references/devices/glue.md +2 -0
- package/dist/skills/tutticode/references/devices/multibanddynamics.md +2 -0
- package/dist/skills/tutticode/references/devices.md +2 -2
- package/dist/skills/tutticode/references/effect-controls.md +8 -0
- package/dist/skills/tutticode/references/first-sounds.md +2 -0
- package/dist/skills/tutticode/references/for-agents.md +26 -0
- package/dist/skills/tutticode/references/functions.md +67 -5
- package/dist/skills/tutticode/references/harmony-and-scales.md +3 -0
- package/dist/skills/tutticode/references/language-reference.md +362 -0
- package/dist/skills/tutticode/references/melodies-with-do-re-mi.md +41 -5
- package/dist/skills/tutticode/references/mixing-groups.md +7 -4
- package/dist/skills/tutticode/references/mixing.md +48 -6
- package/dist/skills/tutticode/references/muse.md +7 -0
- package/dist/skills/tutticode/references/sounds.md +26 -0
- package/dist/skills/tutticode/references/tuttidaw-extensions.md +13 -6
- package/dist/skills/tutticode-sound-for-media/SKILL.md +11 -1
- package/dist/skills/tutticode-sound-for-media/references/music-recipes.md +30 -6
- package/dist/tutticode.cjs +2 -2
- 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;
|
|
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
|
-
##
|
|
89
|
+
## Exporting to Strudel
|
|
54
90
|
|
|
55
|
-
strudel.cc reads letter names only. "
|
|
56
|
-
letters for you (`"do re mi"` becomes
|
|
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
|
|
16
|
-
|
|
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
|
|
55
|
-
|
|
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
|
|
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
|
|
96
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|