tutticode 0.2.1 → 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 +14 -5
  2. package/README.zh-CN.md +13 -3
  3. package/dist/skills/tutticode/SKILL.md +13 -13
  4. package/dist/skills/tutticode/references/agent-loop.md +17 -4
  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 +28 -2
  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 +13 -3
  25. package/dist/skills/tutticode-sound-for-media/references/music-recipes.md +30 -6
  26. package/dist/tutticode.cjs +8 -8
  27. package/package.json +1 -1
package/README.md CHANGED
@@ -12,8 +12,7 @@ tuttidaw.com/code, so a file sounds exactly like the editor. Nothing is
12
12
  rendered on a server and it costs nothing. Every result carries an `editUrl`
13
13
  that opens the program in TuttiDAW to keep editing.
14
14
 
15
- Once TuttiDAW switches its limits on (they are being rolled out; until then
16
- no render is cut or tagged), renders without an account are limited to 60 s,
15
+ Renders without an account are limited to 60 s,
17
16
  and renders over 30 s carry a spoken "register at TuttiDAW" tag. A free
18
17
  account lifts both:
19
18
  `tutticode login` (renders up to 5 minutes, no tag). See
@@ -74,6 +73,17 @@ clipping"). `--lufs` applies one static gain; when the true-peak ceiling
74
73
  cannot be met the render fails with `clip`, unless you add `--limit`: a
75
74
  true-peak limiter on the mix (not the stems), reported as `limitDb`.
76
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
+
77
87
  `--json` prints the render report: length, bars, tempo, loudness and true
78
88
  peak, the gain applied, per-voice levels, the bar and beat grid (seconds, for
79
89
  syncing cuts), the check report, the analysis, the `editUrl` and the written
@@ -107,9 +117,8 @@ The reference documents are also MCP resources (`tutticode://skills/...`).
107
117
 
108
118
  ## Sign-in and limits
109
119
 
110
- The limits below apply once TuttiDAW switches them on (they are being rolled
111
- out; until then every render runs in full and untagged). The sign-in works
112
- today.
120
+ TuttiDAW enforces the limits below. A CLI older than 0.2.1 is refused with
121
+ error `upgrade` (exit 6): update it with `npm i -g tutticode`.
113
122
 
114
123
  | | Without sign-in | Free account |
115
124
  | --- | --- | --- |
package/README.zh-CN.md CHANGED
@@ -10,7 +10,7 @@ MCP server,适用于 Claude Code、Copilot CLI、Codex、Cursor、Claude Desktop
10
10
  文件和编辑器里听到的一模一样。不经过服务器渲染,也不收费。每个结果都带一个
11
11
  `editUrl`,可以在 TuttiDAW 里打开这段程序继续编辑。
12
12
 
13
- TuttiDAW 启用限制后(正在逐步上线;在此之前渲染既不截断也不加标签),不登录时每次渲染最长 60 秒,超过 30 秒的渲染会混入一段语音标签
13
+ 不登录时每次渲染最长 60 秒,超过 30 秒的渲染会混入一段语音标签
14
14
  "register at TuttiDAW"。用免费账号登录(`tutticode login`)就没有这些限制:
15
15
  每次最长 5 分钟,没有语音标签。见[登录与限制](#登录与限制)。
16
16
 
@@ -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` 和写出的文件路径。永远不会把音频打印到终端。
@@ -93,8 +103,8 @@ TuttiCode" 元数据;仅登录后有效。
93
103
 
94
104
  ## 登录与限制
95
105
 
96
- 下面的限制在 TuttiDAW 启用后生效(正在逐步上线;在此之前每次渲染都完整、不加标签)。
97
- 登录现在就能用。
106
+ TuttiDAW 已经启用下面的限制。低于 0.2.1 的 CLI 会被拒绝,报错 `upgrade`(退出码 6):
107
+ 用 `npm i -g tutticode` 升级。
98
108
 
99
109
  | | 不登录 | 免费账号 |
100
110
  | --- | --- | --- |
@@ -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"`); constants only for now.
43
- Mixing: `bus("verb").fx("Reverb")` + `.send("verb", -12)`, `.mix({vol, pan,
44
- mute})`, `master.fx("Limiter")`, `group("drums")` + `.group("drums")`.
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. **Patterned device params** (`{cutoff: "<300 900>"}`) are not applied
59
- yet; use Strudel controls (`.lpf("<300 900>")`) for movement.
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')`).
@@ -75,7 +75,7 @@ The same tools and report shapes run on two backends; prefer the local one.
75
75
  - **Local `tutticode`** (default; free): the `tutticode` CLI
76
76
  and MCP server (`npm i -g tutticode`, `tutticode mcp`) render in the
77
77
  user's own browser with the tuttidaw.com/code engine and write files to
78
- disk, up to 300 s (60 s without sign-in once the limits are on, below). Its tool descriptions
78
+ disk, up to 300 s (60 s without sign-in, below). Its tool descriptions
79
79
  say "Runs locally". Without MCP,
80
80
  call the CLI: `tutticode check prog.tc`, `tutticode render prog.tc -o
81
81
  out.wav --seconds 12 --lufs -16 --json`, `tutticode open prog.tc`.
@@ -95,8 +95,7 @@ The same tools and report shapes run on two backends; prefer the local one.
95
95
  | `code_reference` | (local) search these references |
96
96
  | `code_account` | (local) signed in or not, the length limits, today's usage |
97
97
 
98
- **Without sign-in**, once the limits are on (until then nothing is cut or
99
- tagged), local renders stop at 60 s (error `limit`) and over 30 s carry a
98
+ **Without sign-in**, local renders stop at 60 s (error `limit`) and over 30 s carry a
100
99
  spoken "register at TuttiDAW" tag (`watermark`; never edit code to remove it).
101
100
  Tell the user once; `tutticode login` (free) lifts both. Agents: run
102
101
  `tutticode login --device --no-wait --json` and give the user its link.
@@ -132,18 +131,19 @@ Read only what the task needs:
132
131
  - `references/mini-notation.md`: Mini-notation. The rhythm language inside the quotes - sequences, rests, groups, alternation, polymeter, Euclidean rhythms and chance.
133
132
  - `references/time-and-structure.md`: Time and structure. Functions that speed up, shift, reverse, repeat and combine patterns, and arrange them into sections.
134
133
  - `references/harmony-and-scales.md`: Harmony and scales. Note names and octaves, scale degrees, transposition, chord symbols, voicings and arpeggios.
135
- - `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; strudel.cc gets letters.
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.
136
135
  - `references/sounds.md`: Sounds. Drum kits and banks, synth waveforms, General MIDI instrument names, Ensemble sounds and your own samples.
137
136
  - `references/effect-controls.md`: Effect controls. Filters, reverb, delay, distortion, panning, envelopes and synth controls - and moving them with signals.
138
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.
139
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.
140
- - `references/mixing.md`: Mixing: buses and master. 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.
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.
141
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.
142
142
  - `references/javascript.md`: JavaScript in code. Variables, functions, loops and register - and the sandbox, determinism and limits that keep shared code safe.
143
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.
144
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.
145
- - `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).
146
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).
147
147
 
148
- Language: PATTERN LANGUAGE 1.3.0, engine 17.
148
+ Language: PATTERN LANGUAGE 1.9.0, engine 23.
149
149
  Human guide: https://tuttidaw.com/code/learn; agents: https://tuttidaw.com/code/learn/for-agents
@@ -91,10 +91,8 @@ or mute (`_`) the voices you are not listening to.
91
91
 
92
92
  ## Sign-in, limits and the voice tag (*local*)
93
93
 
94
- - **Limits.** Once TuttiDAW switches them on (they are being rolled out;
95
- until then no render is cut or tagged), a render without sign-in is at
96
- most 60 s, and a render over 30 s carries a spoken "register at
97
- TuttiDAW" tag throughout. A free account (`tutticode login`) lifts both. `code_account` (or `tutticode
94
+ - **Limits.** A render without sign-in is at most 60 s, and a render
95
+ over 30 s carries a spoken "register at TuttiDAW" tag throughout. A free account (`tutticode login`) lifts both. `code_account` (or `tutticode
98
96
  whoami --json`) says which applies (`limits.maxSeconds`,
99
97
  `limits.watermarkAfterSeconds`, null = never tagged): plan within it,
100
98
  for example keep an anonymous draft at 30 s or less.
@@ -130,6 +128,21 @@ or mute (`_`) the voices you are not listening to.
130
128
  `jazz-swing` and others); a render without a master chain sits a few LU
131
129
  under a style's mastered loudness, so match its spectrum first.
132
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
+
133
146
  ## Comparing
134
147
 
135
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 yet: an Ensemble Set's buses come in a later version | 0 | 7 |
81
- | Ensemble Master | not available in code yet: an Ensemble Set's master comes in a later version | 0 | 25 |
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")
@@ -41,8 +41,8 @@ a target or an earlier render). Only where the audio is made differs:
41
41
 
42
42
  - **tutticode on your computer** (the default): renders in your own browser
43
43
  and writes the files to your disk. Free, up to 5 minutes of audio per
44
- render with a free account (`tutticode login`); without sign-in, once the
45
- limits are switched on (being rolled out), up to 60 seconds, and renders
44
+ render with a free account (`tutticode login`); without sign-in, up to
45
+ 60 seconds, and renders
46
46
  over 30 seconds carry a spoken "register at TuttiDAW" tag. It also offers `code_open` (open a program here for you to hear),
47
47
  `code_reference` (search the language reference) and `code_account`
48
48
  (signed in or not, and the limits).
@@ -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), and put voices in it with .group(name).
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("Reverb:Large Hall", {decayTime: "4s"})
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