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
package/README.md
CHANGED
|
@@ -73,6 +73,17 @@ clipping"). `--lufs` applies one static gain; when the true-peak ceiling
|
|
|
73
73
|
cannot be met the render fails with `clip`, unless you add `--limit`: a
|
|
74
74
|
true-peak limiter on the mix (not the stems), reported as `limitDb`.
|
|
75
75
|
|
|
76
|
+
A program can be a whole song (up to 128 bars): each track's sound once, each
|
|
77
|
+
section's content in one block, the order in a form. `render` renders one
|
|
78
|
+
pass of the form by default.
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
track("bass").inst("Drift:Deep Bass").mix({vol: -3})
|
|
82
|
+
section("verse", 8, { drums: s("bd ~ sd ~, hh*8"), bass: note("<c2 eb2>*4") })
|
|
83
|
+
section("chorus", 8, { drums: s("bd sd bd sd, hh*16"), bass: note("<c2 ab1>*8") })
|
|
84
|
+
form("verse chorus verse chorus")
|
|
85
|
+
```
|
|
86
|
+
|
|
76
87
|
`--json` prints the render report: length, bars, tempo, loudness and true
|
|
77
88
|
peak, the gain applied, per-voice levels, the bar and beat grid (seconds, for
|
|
78
89
|
syncing cuts), the check report, the analysis, the `editUrl` and the written
|
package/README.zh-CN.md
CHANGED
|
@@ -63,6 +63,16 @@ cat song.tc | tutticode check - # 从 stdin 读程序
|
|
|
63
63
|
偷偷加限制器;需要时加 `--limit`:对混音(不含分轨)加 true peak 限制器,
|
|
64
64
|
报告里的 `limitDb` 是它最多压下的量。
|
|
65
65
|
|
|
66
|
+
程序可以是一整首歌(最长 128 小节):每条轨道的音色写一次,每个段落的内容写在一个块里,
|
|
67
|
+
顺序写在 form 里。`render` 默认渲染整个曲式一遍。
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
track("bass").inst("Drift:Deep Bass").mix({vol: -3})
|
|
71
|
+
section("verse", 8, { drums: s("bd ~ sd ~, hh*8"), bass: note("<c2 eb2>*4") })
|
|
72
|
+
section("chorus", 8, { drums: s("bd sd bd sd, hh*16"), bass: note("<c2 ab1>*8") })
|
|
73
|
+
form("verse chorus verse chorus")
|
|
74
|
+
```
|
|
75
|
+
|
|
66
76
|
`--json` 输出渲染报告:时长、小节数、速度、响度和 true peak、施加的增益、每个
|
|
67
77
|
声部的电平、小节和拍的时间点(秒,用来对齐剪辑点)、检查报告、分析结果、
|
|
68
78
|
`editUrl` 和写出的文件路径。永远不会把音频打印到终端。
|
|
@@ -39,10 +39,10 @@ _lead: n("0 2 4 7").scale("C:minor") // a leading _ mutes a voice
|
|
|
39
39
|
`.range(a, b)` `.slow(n)` `.segment(n)`.
|
|
40
40
|
- TuttiDAW only: `.inst("Device:Preset", {key: value})` instrument,
|
|
41
41
|
`.midifx(...)` before it, `.fx(...)` after it, in call order. Params use the
|
|
42
|
-
device's keys and units (`"4s"`, `"-6dB"`, `"1/16"`);
|
|
43
|
-
Mixing: `bus("verb").fx("Reverb")`
|
|
44
|
-
mute})
|
|
45
|
-
`toggle(1)` is an inline switch; `note("do re mi")` = `note("c d e")
|
|
42
|
+
device's keys and units (`"4s"`, `"-6dB"`, `"1/16"`); a pattern automates.
|
|
43
|
+
Mixing: voices sharing a reverb/delay get one `bus("verb").fx("Reverb", {dryWetBalance: 1})` and a `.send("verb", -12)` each,
|
|
44
|
+
not an `.fx`/`.room` per voice; `.mix({vol, pan, mute})`; finish a piece with `master.fx("Limiter")`; `group("drums")` + `.group("drums")`.
|
|
45
|
+
`toggle(1)` is an inline switch; `note("do re mi")` = `note("c d e")`; `ensembleSet({reverb: {revSize: 100}})` sets the Ensemble Sets' buses/master.
|
|
46
46
|
|
|
47
47
|
## Top mistakes
|
|
48
48
|
|
|
@@ -55,8 +55,8 @@ _lead: n("0 2 4 7").scale("C:minor") // a leading _ mutes a voice
|
|
|
55
55
|
`"A:minor:pentatonic"`. `"C:harmonic minor"` is two steps.
|
|
56
56
|
4. **Device calls inside callbacks** fail: `every(2, x => x.fx(...))` is an
|
|
57
57
|
error; a voice has one chain. Put `.inst/.fx/.midifx` on the voice.
|
|
58
|
-
5. **
|
|
59
|
-
|
|
58
|
+
5. **Automation**: `.mix({vol: ramp(-60, 0, 4)})` fades over 4 bars (`.once()`: no loop); `env("0:200 2:4000~.5 _4:0")`
|
|
59
|
+
is points pos:value (`~k` curve, `_` step); patterns `"<300 900>"` step. On a bus/group/master: Arrangement lanes.
|
|
60
60
|
6. **One `.inst` per voice**; a second is an error. Use another voice.
|
|
61
61
|
7. **Single vs double quotes**: double quotes are mini-notation; single
|
|
62
62
|
quotes are plain strings (`register('name', ...)`, `.scale('C minor')`).
|
|
@@ -131,18 +131,19 @@ Read only what the task needs:
|
|
|
131
131
|
- `references/mini-notation.md`: Mini-notation. The rhythm language inside the quotes - sequences, rests, groups, alternation, polymeter, Euclidean rhythms and chance.
|
|
132
132
|
- `references/time-and-structure.md`: Time and structure. Functions that speed up, shift, reverse, repeat and combine patterns, and arrange them into sections.
|
|
133
133
|
- `references/harmony-and-scales.md`: Harmony and scales. Note names and octaves, scale degrees, transposition, chord symbols, voicings and arpeggios.
|
|
134
|
-
- `references/melodies-with-do-re-mi.md`: Melodies with do re mi. Write notes in solfège (do re mi fa sol la si) wherever a note name goes;
|
|
134
|
+
- `references/melodies-with-do-re-mi.md`: Melodies with do re mi. Write notes in solfège (do re mi fa sol la si) wherever a note name goes; Export to Strudel gives letters.
|
|
135
135
|
- `references/sounds.md`: Sounds. Drum kits and banks, synth waveforms, General MIDI instrument names, Ensemble sounds and your own samples.
|
|
136
136
|
- `references/effect-controls.md`: Effect controls. Filters, reverb, delay, distortion, panning, envelopes and synth controls - and moving them with signals.
|
|
137
137
|
- `references/sample-operations.md`: Sample operations. Play part of a sample, change its speed, cut it into slices, fit it to the tempo and choke it.
|
|
138
138
|
- `references/tuttidaw-extensions.md`: TuttiDAW extensions. Load any TuttiDAW instrument, MIDI effect or audio effect with presets and parameters; voices, groups and Open in TuttiDAW.
|
|
139
|
-
- `references/
|
|
139
|
+
- `references/automation.md`: Automation. Fades with ramp(), points and curves with env(), patterns and signals - device parameters, faders and sends that move over time, as the same automation Open in TuttiDAW shows.
|
|
140
|
+
- `references/mixing.md`: Mixing: buses and master. 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.
|
|
140
141
|
- `references/mixing-groups.md`: Mixing: groups. Put voices in a group with its own effects, fader and sends - a drum bus inside the program, the same group track Open in TuttiDAW makes.
|
|
141
142
|
- `references/javascript.md`: JavaScript in code. Variables, functions, loops and register - and the sandbox, determinism and limits that keep shared code safe.
|
|
142
143
|
- `references/muse.md`: Muse on /TuttiCode. Ask TuttiDAW's AI assistant to write, explain, fix or restyle your program, from text, a Strudel tune, a screenshot or a reference track.
|
|
143
144
|
- `references/for-agents.md`: TuttiCode for agents. Let Claude Code, Copilot, Codex or any MCP client write TuttiCode and render it to audio files in your own browser, with the tutticode CLI, MCP server and skills.
|
|
144
|
-
- `references/functions.md`: every function, method and signal, with signature, description and example (generated from the engine).
|
|
145
|
+
- `references/functions.md`: every function, method and signal, with signature, description and example; `references/language-reference.md`: the full EBNF grammar, typed signatures, control aliases (both generated from the engine).
|
|
145
146
|
- `references/devices.md`: every instrument and effect for `.inst/.fx/.midifx`, with `references/devices/<id>.md` per device: param keys, ranges, units, presets (generated from the device catalogue).
|
|
146
147
|
|
|
147
|
-
Language: PATTERN LANGUAGE 1.
|
|
148
|
+
Language: PATTERN LANGUAGE 1.9.0, engine 23.
|
|
148
149
|
Human guide: https://tuttidaw.com/code/learn; agents: https://tuttidaw.com/code/learn/for-agents
|
|
@@ -128,6 +128,21 @@ or mute (`_`) the voices you are not listening to.
|
|
|
128
128
|
`jazz-swing` and others); a render without a master chain sits a few LU
|
|
129
129
|
under a style's mastered loudness, so match its spectrum first.
|
|
130
130
|
|
|
131
|
+
A program mixes itself: shared effects on a bus the voices send to, levels
|
|
132
|
+
with `.mix`, and the program's own master chain, which every render goes
|
|
133
|
+
through. A limiter there lets a `lufs` target fit under the ceiling
|
|
134
|
+
without `limit: true`:
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
bus("verb").fx("Reverb", {dryWetBalance: 1})
|
|
138
|
+
master.fx("Limiter", {gainDb: 3, ceiling: -3})
|
|
139
|
+
drums: s("bd*4, ~ cp, hh*8").bank("tr909").mix({vol: -3})
|
|
140
|
+
keys: chord("<Cm7 Fm7>").voicing().inst("Electric").send("verb", -10)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`group("drums")` + `.group("drums")` gives several voices one fader and
|
|
144
|
+
chain. `note("do re mi")` is `note("c d e")` (fixed do).
|
|
145
|
+
|
|
131
146
|
## Comparing
|
|
132
147
|
|
|
133
148
|
`code_compare` returns `deltas[]` (`metric`, `current`, `target`,
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Automation
|
|
2
|
+
|
|
3
|
+
Fades with ramp(), points and curves with env(), patterns and signals - device parameters, faders and sends that move over time, as the same automation Open in TuttiDAW shows.
|
|
4
|
+
|
|
5
|
+
A device parameter does not have to stay put. Give it a **pattern** and it
|
|
6
|
+
changes from cycle to cycle; give it a **signal** and it sweeps. This is
|
|
7
|
+
automation, written in code: "Open in TuttiDAW" shows it as an envelope on
|
|
8
|
+
the voice's clip, and the voice plays it the same way on /code.
|
|
9
|
+
|
|
10
|
+
| Value | What the device gets |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `0.4`, `"4s"`, `slider(0.4)` | a setting that stays |
|
|
13
|
+
| `ramp(-60, 0, 4)` | a straight line from -60 to 0 over 4 bars, then again |
|
|
14
|
+
| `env("0:200 2:4000~.5 4:200")` | points joined by lines and curves |
|
|
15
|
+
| `"<0.1 0.6>"`, `"0 0.5 1 0.5"` | a step at each event: the value holds until the next one |
|
|
16
|
+
| `sine.range(300, 3000)` | a smooth sweep, sampled 16 times per cycle |
|
|
17
|
+
|
|
18
|
+
Like `.inst`, `.fx` and `.midifx` themselves, this is TuttiDAW-only.
|
|
19
|
+
|
|
20
|
+
## Fades: ramp
|
|
21
|
+
|
|
22
|
+
`ramp(from, to, bars)` is a straight line from one value to another over
|
|
23
|
+
a number of bars. A fade-in on the kick's fader, over four bars:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
kick: s("bd*4").mix({vol: ramp(-60, 0, 4)})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The ramp then starts again, like a pattern loops. Add `.once()` and it
|
|
30
|
+
plays once from the start of the program, then holds its last value:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
pad: note("<c3 eb3 g3 bb3>").s("sawtooth").release(0.3)
|
|
34
|
+
.mix({vol: ramp(-60, -6, 8).once()})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The program's loop grows to cover a ramp or an envelope (here 8 bars), so
|
|
38
|
+
on /code you hear all of it.
|
|
39
|
+
|
|
40
|
+
## Points, lines and curves: env
|
|
41
|
+
|
|
42
|
+
`env("...")` draws any shape as points `position:value`, joined by
|
|
43
|
+
straight lines. Positions are in bars (`1.5`, `3/4`); values are in the
|
|
44
|
+
parameter's unit, units allowed (`"-6dB"`, `"2kHz"`):
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
pad: note("c3").s("sawtooth")
|
|
48
|
+
.fx("Auto Filter", {frequency: env("0:200 2:4000~.5 4:200")})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| Written | Means |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `2:4000` | at bar 2 the value is 4000; a straight line leads there |
|
|
54
|
+
| `2:4000~.5` | the segment ending there bends (`-1..1`; positive stays near the start longer, like the studio's curve) |
|
|
55
|
+
| `_2:4000` | hold the value before until bar 2, then jump |
|
|
56
|
+
| `2:0 2:1` | two points at one position: a jump |
|
|
57
|
+
|
|
58
|
+
The envelope loops over its last position. A second argument makes the loop
|
|
59
|
+
longer: `env("0:0 2:1", 4)` rises for two bars and holds for two. Before
|
|
60
|
+
its first point it holds the first value.
|
|
61
|
+
|
|
62
|
+
## The exact form
|
|
63
|
+
|
|
64
|
+
The studio can also store a curve drawn as a Bezier (Live's curve handles).
|
|
65
|
+
Code writes it as a list of points, each `[position, value]` with an
|
|
66
|
+
optional segment: `{curve: k}`, `{curve: [x1, y1, x2, y2]}` or `"hold"`:
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
lead: note("c4 eb4 g4").s("sawtooth")
|
|
70
|
+
.mix({pan: env([[0, -1], [2, 1, {curve: [0.2, 0, 0.8, 1]}], [4, -1, "hold"]])})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
You rarely write this by hand: "Copy as TuttiCode code" in the studio
|
|
74
|
+
writes the shortest form that keeps the automation exactly - a `ramp`,
|
|
75
|
+
an `env` string, or this list.
|
|
76
|
+
|
|
77
|
+
## A wetter reverb every other bar
|
|
78
|
+
|
|
79
|
+
`"<0.1 0.6>"` is 0.1 in the first cycle and 0.6 in the second:
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
pad: note("<c3 eb3 g3 bb3>").s("sawtooth").release(0.3)
|
|
83
|
+
.fx("Reverb", {dryWetBalance: "<0.1 0.6>", decayTime: "3s"})
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`decayTime` is a constant, so it stays at 3 seconds. Several parameters
|
|
87
|
+
of one device can move at once, each with its own pattern.
|
|
88
|
+
|
|
89
|
+
## A filter sweep
|
|
90
|
+
|
|
91
|
+
A signal moves continuously. `sine.range(300, 3000)` goes from 300 Hz up to
|
|
92
|
+
3000 Hz and back once a cycle; `.slow(4)` stretches that over four:
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
bass: note("c2*8").s("sawtooth")
|
|
96
|
+
.fx("Auto Filter", {frequency: sine.range(300, 3000).slow(4)})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The program then loops after four cycles, like any pattern that changes
|
|
100
|
+
over four.
|
|
101
|
+
|
|
102
|
+
## Units, instruments and MIDI effects
|
|
103
|
+
|
|
104
|
+
Patterns take the same values as constants, in the parameter's unit:
|
|
105
|
+
strings with a unit convert (`"<-6dB 0dB>"`, `"<1s 4s>"`). Instrument
|
|
106
|
+
parameters automate too:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
lead: note("c4 eb4 g4 bb4").inst("Drift", {cutoff: "<400 1200 2400 800>"})
|
|
110
|
+
.fx("Utility", {gainDb: "<-6dB 0dB>"})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
A choice parameter steps between its names. Arpeggiator's rate names
|
|
114
|
+
(`1/16`, `1/8`) read as names inside a pattern, not as a division:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
arp: note("[c3,e3,g3]").midifx("Arpeggiator", {style: "<Up Down>", rate: "<1/16 1/8>"})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Names with spaces (`"Up & Down"`) cannot be steps of a pattern: use their
|
|
121
|
+
index. A percentage (`"30%"`) works as a constant only, because `%` means
|
|
122
|
+
something else in mini-notation - write `0.3` in a pattern.
|
|
123
|
+
|
|
124
|
+
## Buses, groups and the master
|
|
125
|
+
|
|
126
|
+
A bus, a group and the master move the same way: their `.mix({vol, pan})`,
|
|
127
|
+
a group's `.send(bus, level)` and their devices' parameters take patterns
|
|
128
|
+
and signals too.
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
bus("verb").fx("Reverb", {dryWetBalance: 1, decayTime: "<2s 6s>"})
|
|
132
|
+
.mix({vol: sine.range(-18, -6).slow(4)})
|
|
133
|
+
group("drums").fx("Drum Buss", {drive: "<0 20>"}).send("verb", "<-30 -12>")
|
|
134
|
+
master.mix({vol: "<0 -3>"})
|
|
135
|
+
|
|
136
|
+
kick: s("bd*4").group("drums")
|
|
137
|
+
pad: note("<c3 eb3>").s("sawtooth").release(0.3).send("verb", -6)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
These tracks have no clip of their own, so "Open in TuttiDAW" writes their
|
|
141
|
+
automation into the **Arrangement**: on the return track, the drums group
|
|
142
|
+
and the program's group (the master), one copy per loop across the span
|
|
143
|
+
the voice clips cover. Lengthen the clips and evaluate again: the
|
|
144
|
+
automation follows. The points the code wrote are the code's - edit one
|
|
145
|
+
and that bus, group or master is detached (Re-attach brings the code back).
|
|
146
|
+
Points you draw before or after that span are yours: the code never
|
|
147
|
+
touches them.
|
|
148
|
+
|
|
149
|
+
## What cannot move
|
|
150
|
+
|
|
151
|
+
- **Static settings** take a constant: an Ensemble part's `voice` or filter
|
|
152
|
+
type. A pattern there is an error that lists the parameters that can move.
|
|
153
|
+
- A pattern of names for the device itself (`.fx("<Reverb Delay>")`) is an
|
|
154
|
+
error: a voice has one chain.
|
|
155
|
+
|
|
156
|
+
## In the studio
|
|
157
|
+
|
|
158
|
+
The envelope belongs to the code, like the voice's other values: an
|
|
159
|
+
evaluate rewrites it, and the device shows the pattern's first value.
|
|
160
|
+
`ramp` and `env` arrive as real points with their curves, editable like
|
|
161
|
+
any envelope. One as long as the voice's clip loops with the clip; a
|
|
162
|
+
longer or shorter one is an unlinked clip envelope with its own loop, and
|
|
163
|
+
a `.once()` one an unlinked envelope with its loop switched off. Edit
|
|
164
|
+
the envelope by hand and the voice is detached from the code (Re-attach
|
|
165
|
+
brings the code back). An envelope holds at most 1024 points; a pattern
|
|
166
|
+
that changes more often is held on a coarser grid, with a warning.
|
|
167
|
+
|
|
168
|
+
Source: https://tuttidaw.com/code/learn/automation
|
|
@@ -35,6 +35,8 @@ Accepted names: `compressor`.
|
|
|
35
35
|
| `sideListen` | S/C Listen | `true` / `false` |
|
|
36
36
|
| `lookahead` | Lookahead | `0 ms` `1 ms` `10 ms` |
|
|
37
37
|
|
|
38
|
+
Sidechain: `sidechain: "kick"` keys it by the voice labelled `kick:`, `"bus:verb"` by the bus `verb` and `"group:drums"` by the group `drums` of the program. `sidechainGain` (dB) sets how loud the source is to it; `sidechainFilter` (a frequency, e.g. `"150Hz"`, or `false`) lets only the source's lows key it.
|
|
39
|
+
|
|
38
40
|
## Factory presets
|
|
39
41
|
|
|
40
42
|
Acoustic · Acoustic Kick · Gentle Glue · Vocal Leveler
|
|
@@ -50,6 +50,8 @@ Accepted names: `autofilter`, `filter`.
|
|
|
50
50
|
| `envSahRate` | Env S&H Rate | -6 .. 0 |
|
|
51
51
|
| `djControl` | Control | -1 .. 1 |
|
|
52
52
|
|
|
53
|
+
Sidechain: `sidechain: "kick"` keys it by the voice labelled `kick:`, `"bus:verb"` by the bus `verb` and `"group:drums"` by the group `drums` of the program. `sidechainGain` (dB) sets how loud the source is to it; `sidechainFilter` (a frequency, e.g. `"150Hz"`, or `false`) lets only the source's lows key it.
|
|
54
|
+
|
|
53
55
|
## Factory presets
|
|
54
56
|
|
|
55
57
|
Auto Filter Sweep · Auto Wah · Low Cut
|
|
@@ -31,6 +31,8 @@ Accepted names: `gate`.
|
|
|
31
31
|
| `lookahead` | Lookahead | `0 ms` `1 ms` `10 ms` |
|
|
32
32
|
| `scFilterMode` | SC EQ Mode | `Low Shelf` `Bell` `High Shelf` `Low Pass` `Band Pass` `High Pass` |
|
|
33
33
|
|
|
34
|
+
Sidechain: `sidechain: "kick"` keys it by the voice labelled `kick:`, `"bus:verb"` by the bus `verb` and `"group:drums"` by the group `drums` of the program. `sidechainGain` (dB) sets how loud the source is to it; `sidechainFilter` (a frequency, e.g. `"150Hz"`, or `false`) lets only the source's lows key it.
|
|
35
|
+
|
|
34
36
|
## Factory presets
|
|
35
37
|
|
|
36
38
|
Drum Gate · Flipside · Soft Cleanup
|
|
@@ -31,6 +31,8 @@ Accepted names: `gluecompressor`, `glue`.
|
|
|
31
31
|
| `scFilterOn` | S/C EQ On | `true` / `false` |
|
|
32
32
|
| `scOn` | S/C On | `true` / `false` |
|
|
33
33
|
|
|
34
|
+
Sidechain: `sidechain: "kick"` keys it by the voice labelled `kick:`, `"bus:verb"` by the bus `verb` and `"group:drums"` by the group `drums` of the program. `sidechainGain` (dB) sets how loud the source is to it; `sidechainFilter` (a frequency, e.g. `"150Hz"`, or `false`) lets only the source's lows key it.
|
|
35
|
+
|
|
34
36
|
## Factory presets
|
|
35
37
|
|
|
36
38
|
Drum Buss Glue · Gentle Mix Bus · Parallel Crush
|
|
@@ -57,6 +57,8 @@ Accepted names: `multibanddynamics`.
|
|
|
57
57
|
| `bands.2.attackMs` | High Attack | 0.1 .. 5000 ms (log) |
|
|
58
58
|
| `bands.2.releaseMs` | High Release | 0.1 .. 5000 ms (log) |
|
|
59
59
|
|
|
60
|
+
Sidechain: `sidechain: "kick"` keys it by the voice labelled `kick:`, `"bus:verb"` by the bus `verb` and `"group:drums"` by the group `drums` of the program. `sidechainGain` (dB) sets how loud the source is to it; `sidechainFilter` (a frequency, e.g. `"150Hz"`, or `false`) lets only the source's lows key it.
|
|
61
|
+
|
|
60
62
|
## Factory presets
|
|
61
63
|
|
|
62
64
|
De-Mud Upward Low · Gentle Multiband Glue · Multiband Expander
|
|
@@ -77,8 +77,8 @@ ranges and units.
|
|
|
77
77
|
| [Drum Buss](devices/drumbuss.md) | available | 3 | 11 |
|
|
78
78
|
| [Dynamic Tube](devices/dynamic-tube.md) | available | 2 | 8 |
|
|
79
79
|
| [Echo](devices/echo.md) | tested end to end | 5 | 38 |
|
|
80
|
-
| Ensemble Bus | not available in code
|
|
81
|
-
| Ensemble Master | not available in code
|
|
80
|
+
| Ensemble Bus | not available in code: an Ensemble Set's buses are set with ensembleSet({...}) | 0 | 9 |
|
|
81
|
+
| Ensemble Master | not available in code: an Ensemble Set's master is set with ensembleSet({...}) | 0 | 26 |
|
|
82
82
|
| Envelope Follower | not available in code yet: modulators need mapping targets, which come in a later version | 5 | 8 |
|
|
83
83
|
| [EQ Eight](devices/eqeight.md) | tested end to end | 3 | 51 |
|
|
84
84
|
| [EQ Three](devices/eqthree.md) | available | 2 | 9 |
|
|
@@ -41,6 +41,10 @@ s("~ cp ~ cp").room(0.6).size(4)
|
|
|
41
41
|
note("<c4 eb4> ~ ~ ~").s("triangle").delay(0.5).delaytime(3/16).delayfeedback(0.55)
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
`.room` and `.delay` give each voice its own reverb or echo. For one reverb
|
|
45
|
+
shared by several voices, so they sound like one room, use a bus and send to
|
|
46
|
+
it: see [Mixing](mixing.md).
|
|
47
|
+
|
|
44
48
|
## Drive and lo-fi
|
|
45
49
|
|
|
46
50
|
`.shape(0..1)` and `.distort(...)` add drive; `.crush(bits)` reduces the bit
|
|
@@ -63,6 +67,10 @@ note("c3 eb3 g3 bb3").s("square").crush(5)
|
|
|
63
67
|
s("hh*8").pan("0 1 0.25 0.75").gain(0.7)
|
|
64
68
|
```
|
|
65
69
|
|
|
70
|
+
To set a voice's level and pan on its track's fader instead, the way you
|
|
71
|
+
balance a mix, use `.mix({vol: -6, pan: -0.3})` (vol in dB): see
|
|
72
|
+
[Mixing](mixing.md).
|
|
73
|
+
|
|
66
74
|
## Modulation effects
|
|
67
75
|
|
|
68
76
|
`.phaser(rate)` and `.chorus(amount)` add movement; `.compressor(...)`
|
|
@@ -45,6 +45,8 @@ s("bd*2 [~ bd] sd ~, hh*8").bank("tr909")
|
|
|
45
45
|
|
|
46
46
|
`note` plays pitches. Write note names (`c`, `d`, `eb`, `f#`) with an octave
|
|
47
47
|
number, or MIDI numbers.
|
|
48
|
+
Solfège works too: `note("do re mi")` plays `c d e` (see
|
|
49
|
+
[Melodies with do re mi](melodies-with-do-re-mi.md)).
|
|
48
50
|
|
|
49
51
|
```js
|
|
50
52
|
note("c3 eb3 g3 bb3")
|
|
@@ -75,6 +75,32 @@ You get `riser.wav`, its loudness and true peak, and an `editUrl` that
|
|
|
75
75
|
opens the program here, so you can listen, change it and render it again
|
|
76
76
|
yourself. Press play above to hear what the agent made.
|
|
77
77
|
|
|
78
|
+
## Example: a bed that mixes itself
|
|
79
|
+
|
|
80
|
+
For music with several voices the agent mixes in the code, as in the
|
|
81
|
+
[Mixing](mixing.md) lesson: one shared reverb on a bus that the voices send
|
|
82
|
+
to, each voice's level with `.mix`, and a limiter on the `master` so the
|
|
83
|
+
loudness target fits under the peak ceiling:
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
setcpm(15) // 60 BPM
|
|
87
|
+
bus("verb").fx("Reverb", {dryWetBalance: 1, decayTime: "4s"})
|
|
88
|
+
master.fx("Limiter", {gainDb: 3, ceiling: -3})
|
|
89
|
+
drone: note("c2").s("sawtooth").lpf(300).attack(1).release(1).mix({vol: -6}).send("verb", -18)
|
|
90
|
+
strings: chord("<Cm Ab>").voicing().s("supersaw").attack(2).release(2).lpf(1200).mix({vol: -10}).send("verb", -4)
|
|
91
|
+
pulse: note("c6*8").s("sine").decay(0.1).sustain(0).mix({vol: -8}).send("verb", -14)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
tutticode render tension.tc -o tension.wav --loops 3 --tail 3 --fade-out 4 --lufs -18 --json
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Each voice's `lufs` in the report (`voices`) says whether the balance is
|
|
99
|
+
what the agent meant; it changes a `.mix` or a `.send` and renders again.
|
|
100
|
+
Drums can share one fader with `group("drums")` and `.group("drums")`
|
|
101
|
+
([Mixing: groups](mixing-groups.md)), and melodies can be written as
|
|
102
|
+
`note("do re mi")` ([Melodies with do re mi](melodies-with-do-re-mi.md)).
|
|
103
|
+
|
|
78
104
|
## Privacy
|
|
79
105
|
|
|
80
106
|
tutticode renders on your computer: the program goes from the agent to the
|
|
@@ -64,9 +64,36 @@ Sets chord symbols; also available as .chord(symbols) or .chord().
|
|
|
64
64
|
chord("<Bbm9 Fm9>/4").dict("ireal").voicing()
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
### `ensembleSet(sections)` *(TuttiDAW only)*
|
|
68
|
+
|
|
69
|
+
TuttiDAW extension, not in Strudel: sets the shared effects of the program's Ensemble Sets (the Sets its .inst("Ensemble:...") voices play in): the Chorus, Delay and Reverb buses and the Set's master, in the devices' own values; once per program, constants only.
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
ensembleSet({reverb: {revType: "HALL", revSize: 100}, delay: {dlyTime: "1/8D", dlyFdbk: 40}})
|
|
73
|
+
flute: note("g4 a4 c5 d5").inst("Ensemble:Sample FLUTE", {rev: 60})
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `env(points, bars?)` *(TuttiDAW only)*
|
|
77
|
+
|
|
78
|
+
TuttiDAW extension, not in Strudel: automation as points "position:value" (positions in bars), joined by straight lines and looped over the last position (or bars); ~k (-1..1) bends the segment ending at a point, _ before a position holds until it, two points at one position jump. A list [[position, value, segment]] is the exact form.
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
pad: note("c3").fx("Auto Filter", {frequency: env("0:200 4:4000~.5 8:200")})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `form(order)` *(TuttiDAW only)*
|
|
85
|
+
|
|
86
|
+
TuttiDAW extension, not in Strudel: the order the section(...) blocks play in, once per program: section names in order, [..] to group, !n to repeat, name@n for the bars of that one occurrence. The whole form is the program loop (up to 128 bars on TuttiCode).
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
section("verse", 8, {drums: s("bd ~ sd ~")})
|
|
90
|
+
section("chorus", 8, {drums: s("bd sd bd sd")})
|
|
91
|
+
form("[verse chorus]!2 verse@4")
|
|
92
|
+
```
|
|
93
|
+
|
|
67
94
|
### `group(name)` *(TuttiDAW only)*
|
|
68
95
|
|
|
69
|
-
TuttiDAW extension, not in Strudel: declares a group, a group track inside the program; give it .fx(...), .mix(...) and .send(bus, dB),
|
|
96
|
+
TuttiDAW extension, not in Strudel: declares a group, a group track inside the program; give it .fx(...), .mix(...) and .send(bus, dB). On a voice, .group(name) puts its track in that group (a name the program does not declare is a warning).
|
|
70
97
|
|
|
71
98
|
```js
|
|
72
99
|
group("drums").fx("Drum Buss").mix({vol: -3})
|
|
@@ -123,6 +150,14 @@ One event per cycle with the value.
|
|
|
123
150
|
note(pure(60))
|
|
124
151
|
```
|
|
125
152
|
|
|
153
|
+
### `ramp(from, to, bars)` *(TuttiDAW only)*
|
|
154
|
+
|
|
155
|
+
TuttiDAW extension, not in Strudel: a straight line from one value to another over a number of bars, then again (a loop); as a mixer level, a send or a device param it is automation with real points. Add .once() to play it once and hold the end.
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
kick: s("bd*4").mix({vol: ramp(-60, 0, 4)})
|
|
159
|
+
```
|
|
160
|
+
|
|
126
161
|
### `register(name, fn)`
|
|
127
162
|
|
|
128
163
|
Defines a new method (and a function) from your own code; the pattern comes last.
|
|
@@ -156,6 +191,16 @@ Declares external sample references; browser consumers load audio explicitly.
|
|
|
156
191
|
samples('github:user/repo')
|
|
157
192
|
```
|
|
158
193
|
|
|
194
|
+
### `section(name, bars, tracks?)` *(TuttiDAW only)*
|
|
195
|
+
|
|
196
|
+
TuttiDAW extension, not in Strudel: a section of a song - everything each track plays in it, in one block. Each pattern starts at the section's first bar and loops in it; a track it does not list is silent there. "verse#2" changes only the second verse (the tracks it lists). Sections play in form(...) order, else in the order they are written.
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
section("verse", 16, {drums: s("bd ~ sd ~, hh*8"), bass: note("<c2 eb2 g1 bb1>*4")})
|
|
200
|
+
section("chorus", 16, {drums: s("bd sd bd sd, hh*16"), bass: note("<c2 ab1>*8")})
|
|
201
|
+
form("verse chorus verse chorus")
|
|
202
|
+
```
|
|
203
|
+
|
|
159
204
|
### `seq(...patterns)`
|
|
160
205
|
|
|
161
206
|
Squeezes all patterns into one cycle, one after another.
|
|
@@ -263,6 +308,15 @@ A value pattern with an inline on/off control (Tutti extension, not in Strudel).
|
|
|
263
308
|
note("60 64").mask(toggle(1))
|
|
264
309
|
```
|
|
265
310
|
|
|
311
|
+
### `track(name)` *(TuttiDAW only)*
|
|
312
|
+
|
|
313
|
+
TuttiDAW extension, not in Strudel: declares a track of a song with sections and its sound, once - give it .inst(...), .fx(...), .midifx(...), .mix(...), .send(bus, dB) and .group(name). What it plays goes in the section(...) blocks; a track no section lists plays nothing.
|
|
314
|
+
|
|
315
|
+
```js
|
|
316
|
+
track("bass").inst("Drift:Deep Bass").mix({vol: -3})
|
|
317
|
+
section("verse", 8, {bass: note("<c2 eb2>*4")})
|
|
318
|
+
```
|
|
319
|
+
|
|
266
320
|
## Methods
|
|
267
321
|
|
|
268
322
|
### `.accelerate(amount?)`
|
|
@@ -795,10 +849,10 @@ note("c2 eb2 g2").s("sawtooth").lpf(500).lpenv(4).ftype("<0 1 2>")
|
|
|
795
849
|
|
|
796
850
|
### `.fx(name, params?)` *(TuttiDAW only)*
|
|
797
851
|
|
|
798
|
-
TuttiDAW extension, not in Strudel: adds an audio effect after the instrument of the voice, in call order.
|
|
852
|
+
TuttiDAW extension, not in Strudel: adds an audio effect after the instrument of the voice, in call order; patterns automate params.
|
|
799
853
|
|
|
800
854
|
```js
|
|
801
|
-
note("c4 e4").fx("
|
|
855
|
+
note("c4 e4").fx("Auto Filter", {frequency: sine.range(300, 3000).slow(4)})
|
|
802
856
|
```
|
|
803
857
|
|
|
804
858
|
### `.gain(level?)`
|
|
@@ -883,7 +937,7 @@ note("c d e f").inside(2, rev)
|
|
|
883
937
|
|
|
884
938
|
### `.inst(name, params?)` *(TuttiDAW only)*
|
|
885
939
|
|
|
886
|
-
TuttiDAW extension, not in Strudel: plays the voice on a TuttiDAW instrument ("Device" or "Device:Preset"), params in the device units.
|
|
940
|
+
TuttiDAW extension, not in Strudel: plays the voice on a TuttiDAW instrument ("Device" or "Device:Preset"), params in the device units; patterns automate params.
|
|
887
941
|
|
|
888
942
|
```js
|
|
889
943
|
note("c2 eb2").inst("Drift:Deep Bass", {cutoff: 600})
|
|
@@ -1059,7 +1113,7 @@ note("[c3,e3,g3]").midifx("Arpeggiator", {style: "UpDown", rate: "1/16"})
|
|
|
1059
1113
|
|
|
1060
1114
|
### `.mix(mixer)` *(TuttiDAW only)*
|
|
1061
1115
|
|
|
1062
|
-
TuttiDAW extension, not in Strudel: the voice's mixer: vol in dB (-70..6), pan -1..1, mute true / false; a bus's, a group's and the master's too.
|
|
1116
|
+
TuttiDAW extension, not in Strudel: the voice's mixer: vol in dB (-70..6), pan -1..1, mute true / false; a bus's, a group's and the master's too. Patterns automate vol and pan.
|
|
1063
1117
|
|
|
1064
1118
|
```js
|
|
1065
1119
|
note("c3 e3").mix({vol: -6, pan: -0.3})
|
|
@@ -1121,6 +1175,14 @@ Applies f to about 75% of the events.
|
|
|
1121
1175
|
s("hh*8").often(x => x.gain(0.4))
|
|
1122
1176
|
```
|
|
1123
1177
|
|
|
1178
|
+
### `.once()` *(TuttiDAW only)*
|
|
1179
|
+
|
|
1180
|
+
TuttiDAW extension, not in Strudel: an env() or ramp() plays once from the start, then holds its last value.
|
|
1181
|
+
|
|
1182
|
+
```js
|
|
1183
|
+
kick: s("bd*4").mix({vol: ramp(-60, 0, 8).once()})
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1124
1186
|
### `.orbit(orbit?)`
|
|
1125
1187
|
|
|
1126
1188
|
Voices on one orbit share its delay and reverb settings.
|
|
@@ -102,4 +102,7 @@ stack of note names.
|
|
|
102
102
|
note("<[a2,e3,a3,c4] [f2,c3,a3,c4] [c3,g3,c4,e4] [g2,d3,b3,d4]>")
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
+
Next: [Melodies with do re mi](melodies-with-do-re-mi.md) writes the same note
|
|
106
|
+
names, and scale roots, in solfège.
|
|
107
|
+
|
|
105
108
|
Source: https://tuttidaw.com/code/learn/harmony-and-scales
|